Skip to content

DavidLee18/pagurus

Repository files navigation

pagurus

A Clang/LLVM plugin that implements Rust-style borrow checking for C, catching memory safety bugs at compile time.

What does it check?

pagurus performs comprehensive memory safety analysis covering:

  • Memory safety: use-after-free, double-free, null-deref, memory leaks
  • Borrow conflicts: double mutable borrow, simultaneous mutable and shared borrow
  • Lifetime violations: returning pointers to local variables, use-while-borrowed
  • Array bounds: constant index out of bounds
  • Dangerous functions: unsafe functions like gets, strcpy, format string vulnerabilities
  • Move semantics: use-after-move for resource-owning types
  • Drop semantics: missing destructor calls, manual memory management

Complete check reference

AST level (-fplugin=)

Rule Name Description
E001 use-after-free Dereference or call after free()
E002 double-free free() called twice on the same pointer
E004 return-of-local return &local — dangling reference
E005 null-deref Dereference without null check after malloc
E006 uninit-use Variable read before initialisation
E007 memory-leak Heap allocation not freed on return path
E009 format-string Non-literal format arg to printf/scanf family
E010 unsafe-fn gets, strcpy, sprintf, strtok, mktemp, …
E011 array-oob Constant array index out of declared bounds
E012 double-mut-borrow &x passed as mutable argument twice
E013 mut-shared-conflict Mutable and shared borrow simultaneously active
E015 assign-while-borrowed Assignment to a live-borrowed variable
E016 use-while-mut-borrowed Read of a mutably-borrowed variable
E017 two-phase-conflict &x and x in the same call (C §6.5.2.2p10)
E018 volatile-borrow / atomic-borrow Mutable borrow of volatile or _Atomic variable
E019 use-after-move Drop-annotated struct used after its value was moved
E020 missing-drop Drop-annotated struct goes out of scope without drop function call
E021 drop-necessary Struct with pointer fields but no drop annotation

LLVM IR level (-fpass-plugin=)

Rule Name Analysis method
IR-E001 use-after-free AliasAnalysis::isMustAlias + DominatorTree
IR-E001b use-after-free (GEP) GEP element of freed object
IR-E002 double-free Two free() calls that MustAlias
IR-E015 borrow-conflict MemorySSA def-def pairs + AliasAnalysis
IR-E015b borrow-conflict (GEP) GEP pointer arithmetic alias
IR-E015c borrow-conflict (bitcast) Union type-pun / bitcast alias
IR-E015 (loop) loop-carried borrow MemorySSA MemPhi nodes across back-edges
IR-E018 atomic-borrow-conflict AtomicRMWInst/AtomicCmpXchgInst on borrowed alloca
IR-Drop drop-injection Automatically inserts drop calls at scope-exit

IR checks catch patterns invisible at AST level: bit-cast/type-pun aliases, GEP pointer arithmetic, loop-carried borrows (SSA φ-nodes), and atomic instruction races on borrowed memory.

Quick start

Build

# Ubuntu 24.04
sudo apt install clang-18 llvm-18-dev libclang-18-dev cmake

mkdir build && cd build
cmake .. \
  -DLLVM_DIR=$(llvm-config-18 --cmakedir) \
  -DClang_DIR=/usr/lib/llvm-18/lib/cmake/clang
make -j$(nproc)

See BUILDING.md for detailed build instructions, MLIR integration, and runtime dependencies.

Usage

# AST checks only (E001–E021):
clang -fplugin=./build/pagurus_plugin.so -c your_file.c

# AST + LLVM IR analysis (adds GEP/bitcast/loop-carried checks + drop injection):
clang -fplugin=./build/pagurus_plugin.so \
      -fpass-plugin=./build/pagurus_plugin.so \
      -g -O0 -c your_file.c

Compile mode (default)

Every invocation runs in compile mode by default: the plugin rewrites the source to produce <input>.pagurus.c that:

  1. Has every #pragma pagurus … line removed (plain C, no pagurus-isms)
  2. Has drop-function calls injected at scope-exit / early-return points

All other errors (E001–E019, E021) still fire and cause build failure. E020 is suppressed because the transformation fixes it automatically.

# Writes your_file.pagurus.c — pagurus annotations stripped,
# missing drop calls injected. E020 is not reported (fixed by injection).
clang -fplugin=./build/pagurus_plugin.so -c your_file.c

Dry-run mode

Pass dry-run as a plugin argument to enable inspector mode. All diagnostics (E001–E021, including E020) are reported, a textual report of what changes would be made is printed to stderr, and no output file is written.

clang -fplugin=./build/pagurus_plugin.so \
      -Xclang -plugin-arg-pagurus -Xclang dry-run \
      -c your_file.c

Example dry-run output:

pagurus [dry-run]: changes that would be applied:
  remove line 5: #pragma pagurus drop(Buf)
  inject before line 12: buf_free(b);
warning: E020[missing-drop]: `b` goes out of scope without calling `buf_free`

Using #pragma pagurus annotations

pagurus supports pragma-based annotations for lifetime constraints and drop functions:

// Explicitly release a borrow (mirrors Rust `drop(borrow)`):
#pragma pagurus borrow_end(p)

// Function lifetime annotation — return tied to first parameter:
#pragma pagurus 'a 'b -> 'a
int *get_x(int *x, int *y);

// Drop function annotation — required destructor:
#pragma pagurus drop(StrStruct)
void str_free(struct StrStruct s);

// Struct lifetime parameters:
#pragma pagurus 'a 'b
struct Buf {
    #pragma pagurus 'a
    const char *data;
    #pragma pagurus 'b
    char *scratch;
};

See ANNOTATIONS.md for the complete reference.

Multi-file projects

Run pagurus across an entire codebase using the included tools:

# Check all files under src/ with 4 parallel jobs
./pagurus-check --plugin=./build/pagurus_plugin.so \
                --cflags="-Iinclude" \
                --jobs=4 --dir=src

# Or use a compilation database
bear make
./pagurus-check --plugin=./build/pagurus_plugin.so \
                --compile-db=compile_commands.json

Integrate into Makefiles:

# myproject/Makefile
PAGURUS_PLUGIN = /path/to/build/pagurus_plugin.so
include /path/to/pagurus.mk

# Then run:
# make pagurus-check

See INTEGRATION.md for complete integration guide.

Key features

  • Non-lexical lifetimes (NLL): Precise borrow tracking with loan release at last use
  • Control flow analysis: Conditional and loop-aware borrow propagation
  • Inter-procedural: Function summaries for return-alias and parameter effects
  • Move semantics: Rust-style ownership transfer for drop-annotated types
  • Drop injection: Automatic RAII-style cleanup at IR level with -fpass-plugin=
  • Source transformation: Produces plain C code without pagurus annotations
  • Two-tier analysis: AST for precision + IR for patterns invisible at source level

Documentation

  • BUILDING.md — Build instructions, MLIR integration, runtime dependencies
  • ANNOTATIONS.md — Complete #pragma pagurus reference
  • INTEGRATION.md — Multi-file project integration with pagurus-check and pagurus.mk
  • ARCHITECTURE.md — Technical architecture and implementation details

License

MIT

About

🚧 a rust-style borrow-checking clang-plugin for C, inspired by CORAL!

Topics

Resources

License

Stars

Watchers

Forks

Contributors