Drop four fuzzer-self-noise sources found across three PyPy fleets - #265
Merged
Conversation
Every one of these is fusil scoring text the TARGET echoed rather than a crash
the target reported. Together they accounted for 8 kept crash dirs across
fleets 05-07.
1. help() prints fusil's own comments. pydoc.getdoc falls back to
inspect.getcomments() for an object with no docstring, so the comment block
above an undocumented class in a spliced sample file is printed verbatim by
help(obj) -- and _sitebuiltins._Helper.__call__ is a normal fuzz target. Two
comments in samples/tricky_objects.py contained "segfault" (a 1.0 word) and
scored a session 100%. Reworded, plus SelfNoiseVocabularyTests scans the
emitted script's COMMENT lines for the 1.0 vocabulary so it cannot creep
back. Only comments are checked: the same words legitimately occur in real
emitted code (SystemError in the bomb list, AssertionError in a class
statement), which inspect.getcomments never reaches.
2. A traceback is the target quoting itself, in two shapes. The source line
(`LOGGER.critical('Future %s in unexpected state: %s',`) and the frame line
(`File ".../logging/__init__.py", line 1536, in critical`), where the scored
word is the crashing function's NAME. concurrent.futures._base kept a dir via
each half in two different fleets. The frame-line rule requires the comma
Python tracebacks put before `in`; faulthandler writes `line N in func`
WITHOUT one, so genuine fatal-signal reports still score -- pinned by a test.
3. bdb's tracer echoes every traced event and the value involved
(`+++ return <class 'SystemError'>`). The repr of an arbitrary fuzz value
routinely holds a scored word, so tracing any module under bdb/pdb
manufactures crashes. Ignore the tracer prefixes; no diagnostic uses them.
4. SIGINT handlers called directly as fuzz targets. asyncio's Runner._on_sigint
and pdb.sigint_handler both `raise KeyboardInterrupt()` unconditionally; that
BaseException escapes the generated script's `except Exception` handlers, and
an uncaught KeyboardInterrupt makes the interpreter re-raise SIGINT, so the
process looks "killed by signal 2" and WatchProcess scores it 1.0.
_on_sigint was already blacklisted as "asyncio.runners:Runner" (#263), but
Runner is re-exported as asyncio.Runner, so a session whose target module is
asyncio never matches that key -- and reached it anyway through the runtime
generic-method loop. pdb.sigint_handler was never keyed at all. Both now live
in METHOD_BLACKLIST, which is name-based and module-agnostic, the same
mechanism that already covers raise_signal. The module-keyed entry stays: it
still filters the static generation path for the defining module.
WatchProcess is deliberately left alone. Fixing the cause beats masking the
symptom, and changing how signals are scored is far riskier than a denylist
entry.
The golden snapshot moves because METHOD_BLACKLIST is emitted into every
generated script as _FUSIL_METHOD_BLACKLIST -- which is exactly why the
name-based fix reaches the paths the module-keyed one missed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WPBSmN87d2BqUnrDbojUT1
This was referenced Aug 29, 2026
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.
Every one of these is fusil scoring text the target echoed rather than a crash the target reported. Together they accounted for 8 kept crash dirs across PyPy fleets 05–07.
1.
help()prints fusil's own commentspydoc.getdocfalls back toinspect.getcomments()for an object with no docstring, so the comment block above an undocumented class in a spliced sample file is printed verbatim byhelp(obj)— and_sitebuiltins._Helper.__call__is a normal fuzz target.Two comments in
samples/tricky_objects.pycarried the word. Reworded, plusSelfNoiseVocabularyTestsscans the emitted script's comment lines for the 1.0 vocabulary so it cannot creep back. Only comments are checked — the same words legitimately occur in real emitted code (SystemErrorin the bomb list,AssertionErrorin a class statement), whichinspect.getcommentsnever reaches.2. A traceback is the target quoting itself — in two shapes
LOGGER.critical('Future %s in unexpected state: %s',File ".../logging/__init__.py", line 1536, in criticalconcurrent.futures._basekept a dir via each half. The frame-line rule requires the comma Python tracebacks put beforein; faulthandler writesline N in funcwithout one, so genuine fatal-signal reports still score — pinned by a test.3.
bdb's tracer echoes traced valuesThe repr of an arbitrary fuzz value routinely holds a scored word, so tracing any module under
bdb/pdbmanufactures crashes. Ignores the tracer prefixes (+++,---,!!!); no diagnostic uses them.4. SIGINT handlers called directly as fuzz targets
asyncio'sRunner._on_sigintandpdb.sigint_handlerbothraise KeyboardInterrupt()unconditionally. ThatBaseExceptionescapes the generated script'sexcept Exceptionhandlers, and an uncaughtKeyboardInterruptmakes the interpreter re-raise SIGINT — so the process looks killed by signal 2 andWatchProcessscores it 1.0._on_sigintwas already blacklisted by #263 as"asyncio.runners:Runner". ButRunneris re-exported asasyncio.Runner, so a session whose target module isasyncionever matches that key — and reached it anyway through the runtime generic-method loop.pdb.sigint_handlerwas never keyed at all. Both now live inMETHOD_BLACKLIST, which is name-based and module-agnostic, the same mechanism that already coversraise_signal. The module-keyed entry stays: it still filters the static generation path for the defining module.WatchProcessis deliberately left alone — fixing the cause beats masking the symptom, and changing how signals are scored is far riskier than a denylist entry.Note on the golden snapshot
It moves because
METHOD_BLACKLISTis emitted into every generated script as_FUSIL_METHOD_BLACKLIST— which is exactly why the name-based fix reaches the paths the module-keyed one missed.Verification
1277 tests pass;
ruff checkandruff format --checkboth clean. Each fix was verified to fail without the change (the vocabulary test was checked by reintroducing the word).🤖 Generated with Claude Code
https://claude.ai/code/session_01WPBSmN87d2BqUnrDbojUT1