Fuzzing¶
Mighty ships four cargo-fuzz targets that exercise the front-end and
the Cranelift back-end against arbitrary input. They were introduced in
v0.9 as part of the pre-1.0 bug-bash. This page documents what each
target asserts, how to run them, how to reproduce a finding, and the
planned CI integration.
Targets¶
| Crate | Target | Property |
|---|---|---|
mty-syntax |
parser_fuzz |
parse(s) never panics on any UTF-8 input. Errors are recorded in ParseResult.errors; crashes are bugs. |
mty-types |
typeck_fuzz |
parse + HIR lower + check_package_typed never panics. This exercises name resolution, HM inference, bidirectional check, effects. |
mty-fmt |
fmt_idempotence |
format(format(x)) == format(x) for any x (idempotence + no panic in the formatter or trivia engine). |
mty-codegen-cranelift |
codegen_fuzz |
When the front-end accepts a program cleanly, lowering to Cranelift IR + emitting an object file never panics. |
Each target lives in crates/<crate>/fuzz/fuzz_targets/<target>.rs
with a sibling Cargo.toml and corpus/<target>/ seed directory.
cargo fuzz init creates these as standalone packages (each has
[workspace] in its manifest to opt out of the root workspace), so
they don't pull in the heavyweight workspace build for normal
cargo test.
Seed corpus¶
Each target's corpus/<target>/ starts with:
- All 20
examples/*.mtyfiles (example_*.mty) - 5 self-host source files (
selfhost_*.mty) empty.mty(zero bytes)minimal_main.mty(fn main() {}\n)
libFuzzer extends the corpus as it discovers new coverage. Don't check the extended corpus into git — the seeds are enough to bootstrap a fresh box, and the rest is host-specific.
Running the smoke fuzz¶
Prerequisite: Rust nightly (libFuzzer needs -Z sanitizer and friends)
and the cargo-fuzz subcommand:
Then, from any crate that owns a fuzz directory:
The -max_total_time=300 caps the run at 5 minutes (the v0.9 smoke
budget). Drop it for an unbounded run.
The first build is slow (it has to compile the whole front-end with
-Cinstrument-coverage and libFuzzer wired in). Subsequent runs are
fast.
Reproducing a fuzz finding¶
When libFuzzer hits a crash it writes the input to
fuzz/artifacts/<target>/crash-<hex>. Replay it deterministically
with:
For a minimised reproduction:
Triage rule of thumb (v0.9):
- Panic / abort / segfault → v1.0 blocker. File in
FUZZ_V0_9_NOTES.mdwith input excerpt + which crate panicked. Fix in the smallest scope possible. - Stack overflow on deeply-nested input → ship with a depth limit in the parser (v0.9.x) or document as a known limit (v1.x).
- Slow input (>1s) → log but don't block; revisit when we benchmark.
Adding a new target¶
- From the crate root:
- The auto-generated
fuzz/Cargo.tomlneeds an explicit[workspace]line (just[workspace]on its own — empty table). Without it, cargo gets confused about workspace membership becausefuzz/is physically inside a workspace member. - Add any extra path-deps your target needs (typically
mty-driverfor end-to-end pipelines). - Write a
#[no_main]target infuzz_targets/<name>.rsusinglibfuzzer_sys::fuzz_target!. Convert raw bytes to UTF-8 withstd::str::from_utf8and bail onErr— most of our inputs are source code. - Seed
fuzz/corpus/<name>/with the same example/self-host set the other targets use, plus a couple of minimal cases. - Build once locally to verify (
cargo +nightly fuzz build <name>). - Document the new target in this file and in
FUZZ_V0_9_NOTES.md.
CI integration (planned)¶
Cargo-fuzz needs nightly Rust and only runs on Linux/macOS reliably in CI. The plan:
- Nightly job: 5 minutes per target on Linux. Upload any new artifacts/crash inputs as build artifacts. Open a tracking issue on the first novel crash signature each week.
- PR fast path: build all four targets (
cargo +nightly fuzz build) so the targets stay compileable — no fuzz time spent, just the type-check / link gate. - Release gate: 30 minutes per target before a tag. If a new crash appears, block the release.
Until that infrastructure lands, contributors are encouraged to run the smoke fuzz locally before tagging.
Architecture¶
The four targets cover the pipeline at orthogonal stages:
UTF-8 bytes
│
▼
┌──────────────────┐
│ mty-syntax │ ← parser_fuzz
│ (parse) │
└────────┬─────────┘
│ GreenNode
├──────────────────────┐
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ mty-fmt │ │ mty-hir lower │
│ (format×2) │ │ + types │ ← typeck_fuzz
└──────────────────┘ └────────┬─────────┘
↑ fmt_idempotence │ typed pkg
▼
┌──────────────────┐
│ borrow check │
│ IR lower │
│ monomorphize │
│ cranelift IR │ ← codegen_fuzz
│ object emit │
└──────────────────┘
A bug in an early stage tends to surface in the early target first —
e.g. a parser overflow is parser_fuzz, not typeck_fuzz. When
triaging, start at the leftmost target that crashes on a given input.