Try it online: Interactive Playground - See null-safety warnings in real-time
Nullsafe C adds NULL checks to catch errors at compile-time. It is 100% compatible with existing C codebases and can be used incrementally to identify safety issues at compile-time.
This provides the following benefits:
- Catches errors at compile-time rather than runtime, reducing crashes
- Improves developer experience by shifting errors left and showing issues in the IDE as you code
- Makes code more readable and maintainable, including annotations with
_Nonnull - Adds type checking that more modern languages have (Rust, TypeScript, Kotlin)
Nullsafe C treats all pointers as potentially null ('nullable') unless it is certain they are not. It does this in two ways.
The first is by semantic analysis: if you test a pointer with if(p), then it knows that branch contains a non-null pointer.
The second is by using Clang's Nullability attributes, in particular _Nonnull. If a pointer is marked as _Nonnull the compiler will require a pointer it knows it not null is passed to it. This can be done either by passing a _Nonnull-annotated pointer, or by doing type narrowing.
If using a compiler other than clang, you can add #define _Nonnull as a no-op. You will not get the same compile checks as with Nullsafe C (clang fork), but the compillation will still succeed without error.
When a function is called, it will reset any type refinements, assuming there may be side effects. If the variable is marked const in the function signature the type narrowing is maintained, because it is guaranteed to not be made null within the function.
Note that this does not change any of the generated code. There are no performance impacts, it strictly does compile-time checks.
void unsafe(int *data) {
*data = 42; // warning: dereferencing nullable pointer of type 'int * _Nullable'
}Try it in the interactive playground
Type narrowing:
void safe(int *data) {
if (data) {
*data = 42; // OK - data is non-null here
}
}Try it in the interactive playground
Anontated with _Nonnull:
void safe_typed(int *_Nonnull data) {
*data = 42; // OK - we know data is not null so we can derefernce it
}Try it in the interactive playground
curl -fsSL https://raw.githubusercontent.com/cs01/llvm-project/null-safe-c-dev/install.sh | bashOr download manually from releases.
On mac you may need to do the following:
brew install zstd
xcode-select --install # If not already installedBuilds not available at this time, you must clone and build locally.
Each release includes:
clang- The Null-Safe C compiler with flow-sensitive null checkingclangd- Language server for IDE integration (VSCode, vim, Neovim, Emacs, etc.)
Once installed, configure your editor to use the null-safe clangd. Install the clangd extension from llvm and set the path to the clangd binary you just downloaded.
VS Code:
// settings.json
{
"clangd.path": "/path/to/null-safe-clang/bin/clangd"
}Neovim/vim:
require('lspconfig').clangd.setup({
cmd = { '/path/to/null-safe-clang/bin/clangd' }
})This gives you real-time null-safety warnings as you type!
Do not that this is not a comprehensive solution, since Null pointer dereferences are just one category of memory safety bugs.
| Safety Issue | Standard C | Null-Safe Clang (null checking) |
|---|---|---|
| Null pointer dereferences | ❌ Unsafe | ✅ Fixed |
| Buffer overflows | ❌ Unsafe | ❌ Unsafe |
| Use-after-free | ❌ Unsafe | ❌ Unsafe |
| Double-free | ❌ Unsafe | ❌ Unsafe |
| Uninitialized memory | ❌ Unsafe | ❌ Unsafe |
Although this doesn't fix all memory safety issues, it catches Null pointer dereferences for free. For additional memory safety, consider a language other than C.
While Null-Safe Clang doesn't solve all memory safety issues in C, null pointer dereferences are a significant problem:
- Many memory safety bugs involve null pointer dereferences
- Easier to adopt than rewriting in Rust (100% compatible with existing C code)
- Complements other efforts (combine with
-fbounds-safetyfor buffer safety) - Incremental deployment (warnings by default, can enable per-file)
Basic usage (warnings enabled by default):
# Warnings for nullable dereferences
clang -fsyntax-only mycode.c
# Treat nullability issues as errors
clang -fsyntax-only -Werror=nullability mycode.c
# Turn off nullability checking
clang -fsyntax-only -fno-strict-nullability mycode.c- Nullable-by-default: All pointers are
_Nullableunless marked_Nonnull - Flow-sensitive narrowing:
if (p)provespis non-null in that scope - Early-exit patterns: Understands
return,goto,break,continue - Pointer arithmetic:
q = p + 1preserves narrowing fromp - Type checking through function calls, returns, and assignments
- Works with Typedefs
- Assumes functions have side effects (use
__attribute__((pure))or__attribute__((const))to preserve narrowing) - Null-safe headers: Annotated C standard library in
clang/nullsafe-headers/ - IDE integration:
clangdbuilt from this fork has the same logic and warnings as clang
Nullability-annotated headers for string.h, stdlib.h, and stdio.h are available in clang/nullsafe-headers/. See clang/nullsafe-headers/README.md for details.