Skip to content

Repository files navigation

zwanzig

Build status MIT License Zig

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:

Platform Archive
Linux x86_64 zwanzig-vX.Y.Z-linux-x86_64.tar.gz
macOS ARM64 zwanzig-vX.Y.Z-macos-aarch64.tar.gz
Windows x86_64 zwanzig-vX.Y.Z-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 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.14.0.tar.gz

Add a lint step to build.zig:

const zwanzig = b.dependency("zwanzig", .{
    .target = b.graph.host,
    .optimize = .ReleaseFast,
});
const run_zwanzig = b.addRunArtifact(zwanzig.artifact("zwanzig"));
run_zwanzig.addArgs(&.{"src"});

const lint_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 Linux runner is x86_64, so it uses the Linux x86_64 archive:

name: Zwanzig

on:
  push:
  pull_request:

permissions:
  contents: read
  security-events: write

env:
  ZWANZIG_VERSION: v0.14.0

jobs:
  analyze:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7

      - name: Install Zwanzig
        run: |
          archive="zwanzig-${ZWANZIG_VERSION}-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 analysis
        run: zwanzig --format sarif src/ > results.sarif || true

      - name: Upload SARIF results
        uses: github/codeql-action/upload-sarif@v4
        with:
          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

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages