Infers Zig lifetime, ownership, and cleanup bugs from your code — no annotations, no runtime instrumentation.
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-freezig 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);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.
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.
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.
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)If zbc saves you debugging time, consider sponsoring development.
- ZLS — type-resolution internals
(
DocumentStore,InternPool,Analyser) extracted and adapted intosrc/type_engine/as zbc's embedded type engine. Not imported as a package; vendored and modified in-tree.