Skip to content

Repository files navigation

zwanzig

Build statusMIT LicenseZig

Zwanzig is a static analyzer and linter for Zig code, combining fast AST/token rules with CFG-driven analysis built on ZIR output.

Installation and usage

Release binary

Download the archive for your platform from the latest release:

PlatformEmbedded frontendArchive
Linux x86_64Zig 0.15.2zwanzig-vX.Y.Z-zig-0.15.2-linux-x86_64.tar.gz
Linux x86_64Zig 0.16.0zwanzig-vX.Y.Z-zig-0.16.0-linux-x86_64.tar.gz
macOS ARM64Zig 0.15.2zwanzig-vX.Y.Z-zig-0.15.2-macos-aarch64.tar.gz
macOS ARM64Zig 0.16.0zwanzig-vX.Y.Z-zig-0.16.0-macos-aarch64.tar.gz
Windows x86_64Zig 0.15.2zwanzig-vX.Y.Z-zig-0.15.2-windows-x86_64.zip
Windows x86_64Zig 0.16.0zwanzig-vX.Y.Z-zig-0.16.0-windows-x86_64.zip

Extract the archive and either add its directory to PATH or invoke the executable directly:

./zwanzig src/

On Windows, run .\zwanzig.exe src\ instead.

Zig frontend compatibility

Zwanzig embeds the Zig frontend used to build it. Source builds support Zig 0.15.2 and Zig 0.16.0, and select the matching compatibility layer automatically. Release archive names include the embedded frontend version, so choose the zig-0.15.2 archive for a Zig 0.15.2 project and the zig-0.16.0 archive for a Zig 0.16.0 project. Run zwanzig --version to verify which frontend a binary contains.

The two frontend artifacts are maintained through the v0.17.x release line. v0.18.0 is the first planned release without a Zig 0.15.2 artifact; users on Zig 0.15.2 should stay on the latest v0.17.x release.

To build from source with the 0.15.2 frontend, use the default shell:

nix develop -c just build

To build with the 0.16.0 frontend, select the migration shell:

nix develop .#zig016 -c just build

Zig build dependency

Pin Zwanzig as a dependency in your Zig project. This command adds the dependency URL and content hash to build.zig.zon:

zig fetch --save=zwanzig https://github.com/forketyfork/zwanzig/archive/refs/tags/v0.15.1.tar.gz

Add a lint step to build.zig:

constzwanzig=b.dependency("zwanzig", .{
.target=b.graph.host,
.optimize=.ReleaseFast,
});
construn_zwanzig=b.addRunArtifact(zwanzig.artifact("zwanzig"));
run_zwanzig.addArgs(&.{"src"});
constlint_step=b.step("lint", "Run Zwanzig");
lint_step.dependOn(&run_zwanzig.step);

b.graph.host builds Zwanzig for the machine running the build, including when your project targets another platform. ReleaseFast avoids the substantial overhead of running the analyzer in Debug mode.

Run the new step with:

zig build lint

GitHub Actions (SARIF)

Download a pinned release binary before running Zwanzig. The example selects the Zig 0.15.2 frontend; set ZWANZIG_ZIG_FRONTEND to 0.16.0 for a Zig 0.16.0 project. The Linux runner is x86_64, so it uses the Linux x86_64 archive:

name: Zwanzigon:
push:
pull_request:
permissions:
contents: readsecurity-events: writeenv:
ZWANZIG_VERSION: v0.15.1ZWANZIG_ZIG_FRONTEND: 0.15.2jobs:
analyze:
runs-on: ubuntu-lateststeps:
- uses: actions/checkout@v7
- name: Install Zwanzigrun: | archive="zwanzig-${ZWANZIG_VERSION}-zig-${ZWANZIG_ZIG_FRONTEND}-linux-x86_64.tar.gz" curl --fail --location --silent --show-error \ "https://github.com/forketyfork/zwanzig/releases/download/${ZWANZIG_VERSION}/${archive}" \ --output "${RUNNER_TEMP}/${archive}" mkdir -p "${RUNNER_TEMP}/zwanzig" tar -xzf "${RUNNER_TEMP}/${archive}" -C "${RUNNER_TEMP}/zwanzig" echo "${RUNNER_TEMP}/zwanzig" >> "${GITHUB_PATH}" - name: Run Zwanzig analysisrun: zwanzig --format sarif src/ > results.sarif || true
- name: Upload SARIF resultsuses: github/codeql-action/upload-sarif@v4with:
sarif_file: results.sarif

Pinning the version keeps CI reproducible. Update ZWANZIG_VERSION when you want to adopt a newer release. The || true lets the SARIF upload run when Zwanzig reports diagnostics.

Features

  • Rule/checker registration with shared --do/--skip filtering
  • Lazy parsing with cached AST/tokens per file
  • Type-aware analysis via ZIR
  • CFG-based, path-sensitive checkers
  • Graphviz DOT dumps for CFGs, exploded graphs, and path traces
  • Parallel analysis across files

Rules

AST/token rules:

  • dupe-import: duplicate @import statements
  • todo: // TODO comments
  • file-as-struct: file naming based on struct-like top-level fields
  • unused-decl: unused container-level declarations
  • unused-parameter: unused function parameters
  • unreachable-code: code after unconditional terminators or fully terminating branches
  • empty-defer: empty defer {} blocks
  • empty-errdefer: empty errdefer {} blocks
  • shadowed-variable: name reuse across scopes
  • sentinel-alloc: sentinel-terminated allocations losing sentinel type
  • identifier-style: naming conventions for types/functions/values
  • deinit-lifecycle: risky cleanup/reinit lifecycle patterns around defer/errdefer

Engine-backed checkers:

  • unreachable-code-engine: constant-condition unreachable code
  • optional-unwrap: forced optional unwraps with .?
  • empty-catch-engine: empty catch {} blocks
  • swallowed-error: catch blocks that ignore errors without rethrowing or logging
  • store-violations-engine: allocator/resource misuse (double-free, leaks, use-after-free/close)
  • stack-escape-engine: stack-backed values escaping via return or async/thread capture
  • divide-by-zero-engine: path-sensitive divide/modulo-by-zero detection
  • slice-bounds-engine: array/slice out-of-bounds access detection

Limitations

  • ZIR/type info requires valid, parseable Zig code
  • Full type resolution needs complete build context; standalone analysis has limited type inference
  • Nested-scope type info is still limited to module-level declarations
  • Interprocedural analysis is limited to simple direct calls in a single file; cross-file calls are treated as external
  • Incremental cache stores metadata only; CFG caching is planned but not yet wired in

Docs

License

MIT

About

A static analyzer and linter for Zig

Topics

Resources

Contributing

Stars

28 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages