Skip to content

cs01/llvm-project

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Nullsafe C: An experimental C/C++ compiler

Test Null-Safety    Try Online

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 analyzes pointer nullability using flow-sensitive analysis. By default, unannotated pointers have unspecified nullability (no warnings on existing code), but you can configure different defaults to suit your needs.

It provides safety 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.

Examples

With -fflow-sensitive-nullability -fnullability-default=nullable, unannotated pointers are treated as nullable:

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

Annotated 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

Installation

Quick Install

curl -fsSL https://raw.githubusercontent.com/cs01/llvm-project/null-safe-c-dev/install.sh | bash

Or download manually from releases.

On mac you may need to do the following:

brew install zstd
xcode-select --install  # If not already installed

Windows

Builds not available at this time, you must clone and build locally.

What's Included

Each release includes:

  • clang - The Null-Safe C compiler with flow-sensitive null checking
  • clangd - Language server for IDE integration (VSCode, vim, Neovim, Emacs, etc.)

IDE Integration

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!

Memory Safety

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.

Why you still might want to try this

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-safety for buffer safety)
  • Incremental deployment (warnings by default, can enable per-file)

Usage

Two-Flag System

Nullability checking is controlled by two independent flags:

1. -fflow-sensitive-nullability - Enables flow-sensitive nullability analysis

  • Must be enabled to get any nullability checking
  • When disabled, no nullability analysis is performed

2. -fnullability-default=<mode> - Sets the default nullability for unannotated pointers

  • unknown (default): No warnings on unannotated code, ideal for gradual migration
  • nullable: Unannotated pointers are nullable, forces null checks (defensive)
  • nonnull: Unannotated pointers are non-null, cleaner code (ergonomic)

Examples

# Large existing codebase - gradual migration (no warnings by default)
clang -fflow-sensitive-nullability -fnullability-default=unknown mycode.c

# New defensive project - force null checks everywhere
clang -fflow-sensitive-nullability -fnullability-default=nullable mycode.c

# New ergonomic project - assume pointers are safe
clang -fflow-sensitive-nullability -fnullability-default=nonnull mycode.c

# Treat nullability issues as errors
clang -fflow-sensitive-nullability -fnullability-default=nullable -Werror=nullability mycode.c

Features

  • Configurable defaults: Choose between unknown (gradual migration), nullable (defensive), or nonnull (ergonomic)
  • Flow-sensitive narrowing: if (p) proves p is non-null in that scope
  • Early-exit patterns: Understands return, goto, break, continue
  • Pointer arithmetic: q = p + 1 preserves narrowing from p
  • Type checking through function calls, returns, and assignments
  • Works with Typedefs
  • Pragma support: #pragma clang assume_nonnull begin/end for file/region defaults
  • 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: clangd built from this fork has the same logic and warnings as clang

Null-Safe C Standard Library

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.

About

Nullsafe C: An experimental C/C++ compiler

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

No packages published

Languages

  • LLVM 41.4%
  • C++ 31.2%
  • C 13.1%
  • Assembly 9.9%
  • MLIR 1.4%
  • Python 0.8%
  • Other 2.2%