Skip to content

ericsssan/zbc

Repository files navigation

zbc

Infers Zig lifetime, ownership, and cleanup bugs from your code — no annotations, no runtime instrumentation.

Example

const Owner = struct {
    data: []u8,
    pub fn deinit(self: *Owner, gpa: Allocator) void { gpa.free(self.data); }
};

var owner = Owner{ .data = try gpa.alloc(u8, 16) };
const x = owner.data;
owner.deinit(gpa);   // frees self.data — zbc reads the body to know this
_ = x;               // → heap-use-after-free

Build integration

zig fetch --save https://github.com/ericsssan/zbc/archive/refs/tags/v0.1.0.tar.gz
// build.zig
const zbc_dep = b.dependency("zbc", .{
    .target = target,
    .optimize = .ReleaseFast,
});

const zbc_run = b.addRunArtifact(zbc_dep.artifact("zbc"));
zbc_run.addArg("src/");

b.default_step.dependOn(&zbc_run.step);

CLI

For standalone use:

zig build -Doptimize=ReleaseFast
zbc src/
zbc --format=compact src/   # grep-friendly
zbc --list-rules
zbc --explain <rule-id>

Exit 0 if clean, 1 if problems found.

Rules

45 rules in two families:

Flow analysis — must-not-escape / must-not-free-twice guarantees via per-fn CFG and abstract interpretation:

heap-use-after-free, heap-double-free, arena-use-after-kill, arena-escape, stack-escape, use-undefined, allocator-mismatch, interior-pointer-destroy

Pattern detectors — catches recurring bug shapes from real Zig PRs:

  • Heap leak / aliasing: heap-leak, partial-union-write, aliased-heap-dupe, clobbered-by-struct-reset, realloc-byte-count, asymmetric-field-free, free-without-null-then-check, overwrite-without-deinit
  • Error-path cleanup: missing-errdefer-between-tries, free-then-try-realloc, destroy-after-deinit-in-loop, dead-errdefer-in-result-fn, duplicate-errdefer, missing-errdefer-on-out-param, unreleased-refs-on-error, unreleased-factory-handle
  • Pointer / slice stability: hashmap-getptr-rehash, arraylist-items-slice, fd-write-after-close, stack-fallback-escape, slice-of-arena-into-heap, borrowed-slice-into-out-param, borrowed-slice-into-stack-buffer-returned, memset-undef-after-len-truncation, sentinel-strip-free-size-mismatch
  • Tagged-union semantics: tagged-union-retag-with-old-payload-read, union-deinit-without-inert-reset, self-undefined-after-destroy, return-borrowed-payload
  • Lifecycle / sibling consistency: reset-skips-pooled-resource-release, missing-deinit-on-composed-owner, owned-field-no-outer-cleanup, deinit-order-violates-construction-dep, defer-and-errdefer-free-overlap, move-out-without-restore
  • Concurrency / hardening: publish-then-touch-self, assert-on-untrusted-input

zbc --explain <rule-id> prints the rationale, canonical bug, and fix.

False positives and missed bugs

If zbc fires on correct code (false positive) or misses a real bug (false negative), please open an issue with a minimal reproducer. Both matter — FP reports drive suppression improvements, FN reports drive new rules and deeper inference.

Suppressions

Silence a false positive with a source comment:

buf[idx - 1] // zbc-disable-line: index-minus-one-without-zero-guard

// zbc-disable-next-line: heap-use-after-free
_ = ptr;

_ = val; // zbc-disable-line: *   (all rules on this line)

Sponsor

If zbc saves you debugging time, consider sponsoring development.

Acknowledgements

  • ZLS — type-resolution internals (DocumentStore, InternPool, Analyser) extracted and adapted into src/type_engine/ as zbc's embedded type engine. Not imported as a package; vendored and modified in-tree.

About

A deep static analyzer for Zig.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages