Skip to content

fix: stop CoroutineStateCorruptor shadowing global sys in the harness - #878

Merged
devdanzin merged 1 commit into
mainfrom
fix/coroutine-corruptor-import-sys-shadow
Jun 30, 2026
Merged

devdanzin merged 1 commit into
mainfrom
fix/coroutine-corruptor-import-sys-shadow

Conversation

@devdanzin

Copy link
Copy Markdown
Owner

Summary

  • CoroutineStateCorruptor injected a function-local import sys into the harness, which shadows the module-global sys. Every sys.stderr coverage marker that runs before it then raised UnboundLocalError, killing the child before the JIT warmed → score 0.0.
  • The injected imports also accumulated across generations (FuzzerSetupNormalizer stripped gc/random but not sys), so the breakage got baked into saved corpus files.
  • Fix: the mutator now injects only import asyncio (which the coroutine needs; sys is always module-global via the boilerplate), and FuzzerSetupNormalizer now strips import sys so already-poisoned corpus files self-heal on their next mutation.

Impact

This was ~47% of all 0.0-score children in a real run (jit_run9): a NameError: 'sys' half (fixed earlier by #867) and an UnboundLocalError: 'sys' half (this PR, ~20% of children). A reproduction driving the current MutationController over the real corpus:

  • before: ~10–12% of children fail with UnboundLocalError: sys
  • after: 0% — including on the already-poisoned jit_run9 corpus files, confirming the normalizer heals them.

Test plan

  • test_injects_asyncio_but_not_sys — mutator injects asyncio, not sys.
  • test_fuzzer_setup_normalizer_strips_sys_import — module-level import sys is stripped.
  • test_normalizer_heals_function_local_import_sys — a harness that uses sys before a function-local import sys raises UnboundLocalError before normalization and runs cleanly after.
  • Full suite: 2162 passed; ruff clean; mypy lafleur/ clean on changed files.

Closes #877

🤖 Generated with Claude Code

CoroutineStateCorruptor injected `import sys` into the harness function body.
A function-local `import sys` makes `sys` local to the whole function, so every
`[fN]`/`[EVIL]` coverage marker (`print(..., file=sys.stderr)`) that runs before
it raised `UnboundLocalError` — killing the child before the JIT warmed and
scoring it 0.0. The injected imports also accumulated across generations
(FuzzerSetupNormalizer did not strip them), poisoning saved corpus files. A real
run wasted ~20% of children on this; a reproduction on the unfixed code showed
~10-12%, dropping to 0% after the fix (including on already-poisoned corpus).

- CoroutineStateCorruptor now injects only `import asyncio` (the coroutine needs
  it; sys is always module-global via the boilerplate) and adjusts the injection
  point from 2 to 1 prepended imports.
- FuzzerSetupNormalizer.visit_Import now also strips `import sys`, so existing
  poisoned corpus files self-heal on their next mutation and the failure class is
  neutralized regardless of source.

Closes #877

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@devdanzin
devdanzin merged commit 59e2f32 into main Jun 30, 2026
0 of 2 checks passed
@devdanzin
devdanzin deleted the fix/coroutine-corruptor-import-sys-shadow branch June 30, 2026 23:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CoroutineStateCorruptor injects function-local 'import sys', shadowing the global and breaking ~10-20% of children with UnboundLocalError

1 participant