test(fuzz): add a coverage-guided fuzzing harness for the pool surface#115
Merged
Conversation
Add pool_fuzz.cpp, a stateful opcode interpreter that drives the pool's public surface — create -> alloc/free -> destroy, plus dynamic growth — through randomized operation sequences and asserts, against a shadow model, the invariants the pool promises: - no two live blocks alias (a free list vending a block twice is caught); - a per-block canary written on allocation is intact at free time; - free(NULL) and free(foreign) are defined no-ops that never corrupt the free list (ADR-0012); - the InstrumentedPool live count tracks the shadow set exactly, and after draining the pool reports zero live and balanced counters (ADR-0025). Both fixed and dynamic pools are exercised. A double-free of an in-range block is deliberately not exercised — the default build does not detect it by design (ADR-0012); hardening (ADR-0043) owns that. One engine-agnostic source yields two build roles: - pool_fuzz — the libFuzzer target, opt-in behind PBR_MEMORY_POOL_BUILD_FUZZERS (OFF by default) and Clang-only. A `fuzz` preset compiles every TU with -fsanitize=fuzzer-no-link,address,undefined and links the target with -fsanitize=fuzzer, so coverage feedback spans the library. A dedicated CI job replays the seed corpus and fuzzes for a bounded 60s on every PR, uploading any crash as a reproducer. - pool_fuzz_replay — built with the normal test suite (no engine): its standalone main replays the seed corpus (CTest pool_fuzz_replay), making the corpus a portable regression gate everywhere — including MSVC, where libFuzzer is unavailable — and keeping the harness inside the clang-tidy gate. Test-only and additive; the release build and the ADR-0014 benchmark numbers are untouched. Decided in ADR-0044; ROADMAP item 9.3. Closes #108. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements ROADMAP item 9.3 — a coverage-guided fuzzing harness (issue #108), decided in ADR-0044. The test tier was example-based (doctest) and the benchmark a fixed loop; there was no fuzz target anywhere in the repo. An allocator's interesting defects live in sequences — allocate-to-exhaustion, LIFO vs. FIFO frees, free-then-reallocate across a growth event, foreign-pointer rejections interleaved with valid traffic — which a coverage-guided fuzzer explores automatically.
The harness — stateful interpreter + shadow oracle
pool_fuzz.cppreads the fuzzer's bytes as a small program: the leading bytes configure a pool (block size / count / fixed-vs-dynamic + growth factor, valid by construction under ADR-0009 §2) and each remaining byte is an opcode —alloc,free(valid)(an index byte picks which live block),free(NULL),free(foreign). A shadow model of the live blocks asserts, after every step, the invariants the pool promises — turning a silent corruption into a saved reproducer:InstrumentedPool(ADR-0025) is the oracle:live_tracks the shadow set exactly, and after draining the pool reports zero live, balanced counts, and a matching peak-live.Both fixed and dynamic pools are exercised. A double-free of an in-range block is deliberately not fuzzed — the default build does not detect it by design (ADR-0012); hardening (ADR-0043) owns that.
Two build roles from one source
pool_fuzz— the libFuzzer target, opt-in behindPBR_MEMORY_POOL_BUILD_FUZZERS(OFF by default) and Clang-only (aFATAL_ERRORguards other compilers). Afuzzpreset compiles every TU with-fsanitize=fuzzer-no-link,address,undefined(coverage spanning the library) and links the target with-fsanitize=fuzzer.pool_fuzz_replay— built with the normal test suite (no engine); its standalonemainreplays the seed corpus (CTestpool_fuzz_replay), making the corpus a portable regression gate everywhere — including MSVC, where libFuzzer is unavailable — and keeping the harness inside the clang-tidy gate.CI
A dedicated
fuzzjob (Clang/POSIX) builds the target, replays the seed corpus as a gate, then fuzzes for a bounded 60 s on every PR; a crash fails the job and uploads the input as an artifact, feeding the bug ledger (ADR-0039).Validation
debugbuild + fullctestgreen locally (MSVC 19.51), including the newpool_fuzz_replaycorpus gate; the harness was additionally stress-run over 1,500 randomized inputs via the standalone replay with no false positives.clang-formatclean;clang-tidy(-p build/debug) clean onpool_fuzz.cpp; the non-ClangFATAL_ERRORguard verified.consistency_lint.pyOK; all doc links resolve; the spec's translation rows are alreadystale, so this PR adds no i18n debt.fuzzjob (libFuzzer is unavailable on the maintainer's MSVC box by design).Docs
ADR-0044 (+ index row), spec §6.4 / §7,
ROADMAP.md(9.3 → done),CHANGELOG.md[Unreleased], and a corpus README. Per ADR-0044, theREADME.mdtest-strategy refresh is deferred to thev1.2.0release PR to keep this PR off the translated docs surface (same sequencing as ADR-0043).Closes #108.
🤖 Generated with Claude Code