# Fuzzing the Slint compiler

This directory is a standalone cargo project, deliberately kept out of the main
Slint workspace so the `libfuzzer-sys` dependency is not pulled in for everyone.
Run `cargo` from this directory (or use `run_fuzzer.sh`, which passes
`--fuzz-dir` for you) rather than from the repo root.

It fuzzes the compiler via [`cargo fuzz`](https://rust-fuzz.github.io/book/cargo-fuzz.html)
(libFuzzer). The target `compiler_fuzzing` runs an input buffer through the full
pipeline: `parse` → `compile_syntax_node` (all middle-end passes) →
`generator::generate(OutputFormat::Llr)` (back-end lowering + optimization passes
+ code generation). The back-end is driven through `generate()` rather than by
calling `llr::lower_to_item_tree` directly, so the fuzzer only ever exercises
document shapes that a real compilation for that output format actually produces.

## Running

```sh
internal/compiler/fuzz/run_fuzzer.sh    # from the repo root
```

`run_fuzzer.sh` regenerates the seed corpus on startup, then launches libFuzzer
with the dictionary and a size cap.

## Why the setup looks the way it does

A coverage-guided fuzzer is only as good as its starting corpus. Random byte
mutation almost never discovers valid Slint grammar from scratch, so an
empty-start run gets stuck bouncing off the lexer/parser and never reaches the
deep compiler stages. Two ingredients fix that:

- **`seeds/`** — generated by `run_fuzzer.sh` from every `*.slint` file (≤16 KB)
  in `tests/cases/` and `internal/compiler/tests/`. **Gitignored and
  regenerable** — do not check it in. It is passed to libFuzzer as a read-only
  corpus dir; new discoveries are written to `corpus/compiler_fuzzing/` instead.
- **`slint.dict`** — Slint keywords/operators/builtins spliced into mutations so
  they produce valid tokens (`self`, `property`, `:=`, `=>`) instead of garbage.
  **Checked in** (not regenerable from the tree).

### Single-buffer limitation

The harness feeds one source buffer, so multi-file `import` resolution is barely
exercised (`typeloader.rs` coverage stays low). That is inherent to this harness,
not something more seeds will fix.

## Measuring coverage

Requires the LLVM tools component (one-time):

```sh
rustup component add llvm-tools-preview --toolchain nightly-x86_64-unknown-linux-gnu
```

Generate and merge the profile data over the seed corpus:

```sh
cargo fuzz coverage --fuzz-dir internal/compiler/fuzz compiler_fuzzing \
    internal/compiler/fuzz/seeds
```

Render a per-file report (find the instrumented binary under the workspace
target dir — note the doubled `coverage/.../release/` path):

```sh
LLVMCOV=~/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/bin/llvm-cov
BIN=./target/x86_64-unknown-linux-gnu/coverage/x86_64-unknown-linux-gnu/release/compiler_fuzzing
PROF=internal/compiler/fuzz/coverage/compiler_fuzzing/coverage.profdata

"$LLVMCOV" report "$BIN" -instr-profile="$PROF" \
    -ignore-filename-regex='/\.cargo/|/rustc/|/registry/'
```

Healthy targets: middle-end `passes/` at 90–98%, and the `llr/*` back-end files
non-zero (they are reached because the harness compiles for `OutputFormat::Llr`
and runs `generate()` on a successful compile).

## Maintenance

- After a long run, compact the corpus: `cargo fuzz cmin --fuzz-dir internal/compiler/fuzz compiler_fuzzing`
- Crashing inputs land in `artifacts/`; reproduce with
  `cargo fuzz run --fuzz-dir internal/compiler/fuzz compiler_fuzzing artifacts/compiler_fuzzing/<crash-file>`
