Skip to content
Ferralk

Ferralk / Contributing

Fuzzing ferralk-glob

pattern_parser and pattern_matcher exercise parsing and matching with arbitrary bytes and option combinations. pattern_matcher also holds a one-member PatternSet to its member's verdicts, so the set's literal index never skips a pattern that matches. Their checked-in seeds derive from the executable matcher corpus. On macOS, macos_dirent_parser and macos_bulk_record_parser separately fuzz the feature-gated getdirentries64 and getattrlistbulk record validators without issuing syscalls or touching paths. Linux has the equivalent linux_dirent_parser. The native targets keep small structure-aware checked-in records and CI preserves each target's corpus between runs, so their bounded budgets start from valid parser shapes. cargo run --manifest-path fuzz/Cargo.toml --bin generate_macos_native_seeds and cargo run --manifest-path fuzz/Cargo.toml --bin generate_linux_native_seeds regenerate the native records byte-for-byte; the matching native tests replay and validate every checked-in seed before CI fuzzes it. The Linux generator finds the repository from its Cargo manifest, so it can run from any directory; its checked-in native corpus is intentionally little-endian, matching the reviewed Linux native-backend targets.

ferralk_vs_fast_glob is differential: it feeds one pattern and one candidate to both Ferralk and Oxc fast-glob and asserts the same verdict. It keeps only the syntax both engines document the same way, excluding each recorded divergence by the shape of the pattern, so a failure is a new finding rather than a known difference. The divergences and their exclusions are tabulated in docs/fast-glob-reference.md. A disagreement is reported as a ready-to-paste corpus/fast-glob.jsonl line. Filtered or unparseable inputs return libFuzzer's rejected-corpus verdict, so they cannot displace comparable inputs in the evolving corpus. The shared globstar subset includes every **, whole-component or attached, since both engines read it as recursive only as a whole path component (ADR-0020); a trailing /** and a whole-component run of three or more stars keep the documented structural exclusion.

cargo test --manifest-path fuzz/Cargo.toml --lib --locked checks the subset boundary and replays every checked-in differential seed through both matchers. The automated and manual differential jobs also print a shared-subset hit-rate checkpoint after fuzzing. The checkpoint is updated every 4,096 executions, so the displayed numerator and denominator omit at most 4,095 final inputs; the adjacent libFuzzer final statistics retain its exact execution count.

The glob targets used to skip patterns whose brace expansion was too large, because Pattern::compile expanded braces eagerly with no budget and about a hundred bytes of nine-way groups exhausted memory. Pattern::compile now rejects those patterns itself, so the targets hand them straight to it and exercise the real error path.

pattern_matcher sends at most 384 pattern bytes into the compiler and returns libFuzzer's rejected-corpus verdict for larger patterns. Candidate length stays unrestricted, so the long-path seeds still exercise matcher stack and state bounds. A compact sub-kilobyte seed retains the compiled-IR budget rejection path, and the fuzz-library tests ensure every checked-in matcher seed remains below the pattern ceiling and replays it through the compiler. Automated and manual workflow runs first replay that checked-in corpus without truncation, then set libFuzzer's total input limit to 128 bytes for mutation, keeping nightly exploration focused on syntax shape without losing the compiled-IR or long-candidate regression paths.

ferralk_vs_fast_glob depends on that budget for its own speed: fast-glob backtracks over brace alternatives rather than expanding them, and spends 42 s on the ten-group pattern from issue #42. Compiling before fast_glob::glob_match keeps such a pattern away from it. Raising MAX_BRACE_ALTERNATIVES means re-measuring fast-glob at the new limit.

The glob targets run automatically: a short budget per pull request and a long nightly run, both from .github/workflows/glob-fuzz.yml. A failed scheduled run opens or refreshes the repository's nightly glob-fuzz tracking issue, in addition to uploading its reproducer. The same workflow runs on demand, where the dispatch inputs pick one target or all of them and set the budget.

Install cargo-fuzz, then run cargo fuzz run pattern_parser, cargo fuzz run pattern_matcher, or cargo fuzz run ferralk_vs_fast_glob from the repository root. Run cargo fuzz run macos_dirent_parser or cargo fuzz run macos_bulk_record_parser on macOS for the native record parsers. Run cargo fuzz run linux_dirent_parser on Linux for the native record parser.

cargo-fuzz needs a nightly compiler, and the root rust-toolchain.toml selects stable, so name the nightly CI uses: cargo +"$(cat .github/nightly-toolchain)" fuzz run pattern_parser, and the same prefix for every other target. CONTRIBUTING describes that pin.

Crash artifacts belong under fuzz/artifacts. Minimize a saved input with cargo fuzz tmin followed by the target and artifact, then convert the minimized behavior into a source-linked corpus regression case before fixing it.