Targets #269 (PythonObjectCache race), #270 (GridLayer dangling parent), #275 (UIEntity missing tp_dealloc), #277 (GridChunk dangling parent). Exercises timer/animation callbacks that mutate scene and drawable lifetimes across firing boundaries, including scene swap mid-callback and closure captures that can survive past their target's lifetime. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| seeds | ||
| .gitignore | ||
| fuzz_anim_timer_scene.py | ||
| fuzz_common.cpp | ||
| fuzz_common.py | ||
| fuzz_fov.py | ||
| fuzz_grid_entity.py | ||
| fuzz_maps_procgen.py | ||
| fuzz_pathfinding_behavior.py | ||
| fuzz_property_types.py | ||
| README.md | ||
McRogueFace Python API fuzzing harness (#283)
Native clang+libFuzzer+ASan harness that drives the mcrfpy Python API from
Python fuzz targets. libFuzzer instruments the C++ engine code (where all the
#258-#278 bugs live); Python drives the fuzzing logic through a simple byte
consumer. No atheris dependency — Python-level coverage would add nothing here
because the bugs live below the API boundary.
Prerequisites
clang-18,clang++-18,lld-18onPATH(Debian:apt install clang-18 lld-18)libclang_rt.fuzzer-18-dev(for-fsanitize=fuzzer) — verify withclang-18 -print-file-name=libclang_rt.fuzzer-x86_64.a- Debug CPython built per top-level CLAUDE.md (
tools/build_debug_python.sh)
Build
make fuzz-build
Produces build-fuzz/mcrfpy_fuzz, a single libFuzzer-linked executable. All
six fuzz targets share this binary — target selection is by env var.
Run
make fuzz # 30s smoke on each of 6 targets
make fuzz FUZZ_SECONDS=300 # 5min each
make fuzz-long TARGET=grid_entity SECONDS=3600 # 1hr on one target
make fuzz-repro TARGET=grid_entity CRASH=tests/fuzz/crashes/grid_entity-abc123
make clean-fuzz # Wipe build-fuzz/, corpora/, crashes/
Corpora live under tests/fuzz/corpora/<target>/ (gitignored — libFuzzer
grows these), crashes under tests/fuzz/crashes/ (gitignored — triage
dir). Seed inputs committed to tests/fuzz/seeds/<target>/ are read-only.
Targets
| Script | Surface | Hunts |
|---|---|---|
fuzz_grid_entity.py |
EntityCollection append/remove/insert/extend/slice across differently-sized grids, entity.die during iteration |
#258-#263, #273, #274 |
fuzz_property_types.py |
Random property get/set with type confusion on Frame/Caption/Sprite/Entity/Grid/TileLayer/ColorLayer | #267, #268, #272 |
fuzz_anim_timer_scene.py |
Animation + Timer state machine, Frame reparenting, scene swap in callbacks | #269, #270, #275, #277 |
fuzz_maps_procgen.py |
HeightMap/DiscreteMap ops and conversions, NoiseSource.sample, BSP.to_heightmap | new |
fuzz_fov.py |
grid.compute_fov + is_in_fov, transparent toggling | new |
fuzz_pathfinding_behavior.py |
DijkstraMap, grid.step, entity behavior fields | #273-adjacent |
Any target not yet implemented is a stub that still compiles and runs cleanly
— make fuzz reports it as a no-op.
Adding a new target
- Add
<name>toFUZZ_TARGETSin the Makefile. - Create
tests/fuzz/fuzz_<name>.pydefiningfuzz_one_input(data: bytes) -> None. - Create
tests/fuzz/seeds/<name>/.gitkeepso the seed dir exists. - Import
ByteStreamandEXPECTED_EXCEPTIONSfromfuzz_common. Wrap the fuzz body intry: ... except EXPECTED_EXCEPTIONS: passso Python noise doesn't pollute libFuzzer output — real bugs come from ASan/UBSan.
No C++ code changes are needed to add a target. The harness loads
fuzz_<MCRF_FUZZ_TARGET>.py by name at init time.
Triage
A crash in tests/fuzz/crashes/ is a file containing the exact bytes that
triggered it. Reproduce with make fuzz-repro TARGET=<name> CRASH=<path>.
The binary will rerun ONCE against that input and ASan will print the stack.
Useful ASan tweaks when investigating:
ASAN_OPTIONS="detect_leaks=0:symbolize=1:print_stacktrace=1" \
./build-fuzz/mcrfpy_fuzz path/to/crash_input
If the crash reproduces a known fixed issue (#258-#278), delete the crash file
and move on. If it's new, file a Gitea issue with the crash file attached and
apply appropriate system:* and priority:* labels per CLAUDE.md.
CI integration
Not wired into tests/run_tests.py. Fuzz runs are non-deterministic and too
long for normal suite runs. Follow-up issue will add a scheduled weekly job.
References
- Plan:
/home/john/.claude/plans/abundant-gliding-hummingbird.md - libFuzzer: https://llvm.org/docs/LibFuzzer.html
- Bug inventory: #279 (meta), #258-#278 (individual bugs)