Fuzzing
Every identifier in this crate is a small value object built by parsing untrusted text, and each one
exposes its canonical string through an unchecked byte to string conversion (from_utf8_unchecked).
That makes the parsers a natural place for coverage guided fuzzing: it explores far more shapes of
malformed input than hand written tests, and it proves the unchecked conversion can never observe
invalid UTF-8.
The fuzz targets live in the fuzz/ directory as a standalone crate. They reuse the Arbitrary
implementations (the arbitrary feature) to generate valid values, and the serde feature to check
serialization round trips.
Prerequisites
Fuzzing uses cargo-fuzz, which builds with libFuzzer and
requires a nightly toolchain. The library itself stays on stable; only the fuzzer runtime needs
nightly.
cargo install cargo-fuzz
rustup toolchain install nightly
Targets
There is one target per identifier:
country_codecfiisincnpj
How each target drives the code
To reach the deep logic instead of bouncing off the length and character gates, every target feeds the same input through four lenses:
- As arbitrary UTF-8 text handed to
parse. - As raw bytes handed to
from_bytes. - As a structure aware candidate: a string built to the exact canonical length and character class (for example two letters plus nine alphanumerics plus a digit for an ISIN), so the checksum or taxonomy logic runs on near valid input on almost every execution.
- As an
Arbitrarygenerated value, which exercises the acceptance path directly.
A committed dictionary per target (fuzz/dict/<target>.dict) supplies tokens (category letters,
sample codes, boundary punctuation) that steer the mutator toward interesting inputs.
Invariants every accepted value must satisfy
- Soundness:
as_str()is valid UTF-8 equal toas_bytes()(this guards thefrom_utf8_unchecked). - Shape: the canonical form has the fixed length, and every byte is in the allowed class for its position.
- Constructor agreement:
parse,new,from_bytes,FromStr, and everyTryFromimpl (&str,[u8; N],&[u8]) all return the same value, and parsing the canonical form is idempotent. - Accessor consistency: segment accessors match the canonical string. For an ISIN the stored check
digit equals the recomputed one, and
country()agrees withCountryCode::parseof the prefix (it isNonefor structurally valid but ISO 3166-1 unassigned prefixes such asXS). For a CNPJis_root(),branch_number(), and the punctuatedformatted()form stay consistent with the branch segment andDisplay. - Trait conversions:
PartialEq<str>/PartialEq<&str>andAsRef<str>/AsRef<[u8]>agree with the canonical string and bytes. - serde: the value round trips through its JSON string.
- Rendering:
DisplayandDebugnever panic. Formatting a rejected value’s error never panics, and for a rejected input anInvalidLengtherror reports the same length the parser measured.
Running
The just recipes wrap the nightly invocation and feed in the committed seed corpus:
just fuzz country_code # run for 60 seconds (default)
just fuzz cfi 120 # run a target for 120 seconds
just fuzz-build # just compile every target
Equivalently, without just:
cargo +nightly fuzz run country_code fuzz/corpus/country_code fuzz/seeds/country_code
A crash writes a reproducer under fuzz/artifacts/<target>/. Replay it with:
cargo +nightly fuzz run <target> fuzz/artifacts/<target>/<crash-file>
Corpus and seeds
fuzz/seeds/<target>/holds known inputs (valid codes and edge shapes such as wrong length, bad character, and reserved codes), committed to git, to give the fuzzer a head start. Treat this directory as read only.fuzz/dict/<target>.dictis the committed libFuzzer dictionary of useful tokens.fuzz/corpus/<target>/is the growing working corpus that libFuzzer writes to. It is ignored by git, along withfuzz/artifacts/andfuzz/target/.
When a target uncovers an interesting new input, consider minimizing it and adding it to the seeds directory so the case is preserved.
Measuring coverage
To see how much of the parsing and validation code the corpus actually exercises:
just fuzz-coverage cnpj
That writes fuzz/coverage/<target>/coverage.profdata. Render a per file report with the nightly
llvm-cov, for example:
LLVMCOV="$(rustc +nightly --print target-libdir)/../bin/llvm-cov"
BIN="$(find target -type f -path '*coverage*release/cnpj' ! -name '*.*' | head -1)"
"$LLVMCOV" report "$BIN" -instr-profile=fuzz/coverage/cnpj/coverage.profdata
The parser and validation modules should sit near full line coverage. Formatting and error modules are also exercised, though some rendering branches are covered more thoroughly by the unit tests.