Skip to content

The skip-trivial guard printed "Skipping" and then fuzzed anyway - #275

Merged
devdanzin merged 1 commit into
mainfrom
cffi-ffi-methods-called-despite-skip-guard
Sep 6, 2026
Merged

devdanzin merged 1 commit into
mainfrom
cffi-ffi-methods-called-despite-skip-guard

Conversation

@devdanzin

Copy link
Copy Markdown
Owner

The bug

skip_trivial_type has correctly identified cffi objects since #268. A PyPy stdlib module hands out a live FFI as an attribute — _curses_panel.ffi, _ssl.ffi, _lzma.ffi, at least ten of them — and type(ffi).__module__ really is _cffi_backend, so the check returns True.

It just never mattered. Two of the three emission sites put nothing in the guard's body except the print, and emitted the method fuzzing after the if rather than inside an else:

if skip_trivial_type(fuzz_target_module.ffi):
    print('Skipping deep diving on ...')      # <- the entire body
# General method fuzzing for fuzz_target_module.ffi
try:
    res_obj0m1 = callMethod("obj0m1", fuzz_target_module.ffi, "typeof", ...

So the script reported the skip and then called the object anyway.

ffi.memmove() and its siblings take their arguments as raw pointers and fault by contract on whatever they are handed — exactly as ctypes does on CPython — so a crash there is never a target defect. Five consecutive PyPy fleets kept dirs from this; the most recent is a _curses_panel SIGSEGV in fleet_10 whose last line before the fault is [obj12m14] FFI.memmove().

The fix

Both broken sites now open a real else:. The third already did, which is why it never contributed.

The plugin path is untouched: a dispatcher that opens its own trailing else still owns the fallback, and the new one is opened only when no dispatcher did. A pass keeps the block valid when an object turns out to have no methods to fuzz.

The golden snapshot moves accordingly — the method-fuzzing block is one level deeper, inside the else.

Tests

Asserting on the emitted text would not have caught this, because the print was always there. The new tests assert on the parsed structure:

  • every if skip_trivial_type(x): carries the work in its orelse
  • the taken branch only reports, never calls the object
  • the emitted function skips a _cffi_backend-typed instance and leaves an ordinary object alone

All three fail on the pre-fix generator (verified by stashing the fix). 1303 tests pass; ruff check + ruff format --check clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WPBSmN87d2BqUnrDbojUT1

`skip_trivial_type` has correctly identified cffi objects since #268 -- a PyPy
stdlib module hands out a live `FFI` as an attribute (`_curses_panel.ffi`,
`_ssl.ffi`, `_lzma.ffi`, ... at least ten of them), and `type(ffi).__module__`
really is `_cffi_backend`, so the check returns True. It just never mattered:
two of the three emission sites put nothing in the guard's body except the
"Skipping deep diving" print, and emitted the method fuzzing after the `if`
statement rather than inside an `else`. The generated script therefore reported
the skip and then called the object anyway:

    if skip_trivial_type(fuzz_target_module.ffi):
        print('Skipping deep diving on ...')      # the entire body
    # General method fuzzing for fuzz_target_module.ffi
    try:
        res_obj0m1 = callMethod("obj0m1", fuzz_target_module.ffi, "typeof", ...

`ffi.memmove()` and its siblings take their arguments as raw pointers and fault
by contract on whatever they are handed -- exactly as ctypes does on CPython --
so a crash there is never a target defect. Five consecutive PyPy fleets kept
dirs from it; the most recent is a `_curses_panel` SIGSEGV in fleet_10, whose
last line before the fault is `[obj12m14] FFI.memmove()`.

Both broken sites now open a real `else:`. The third site already did, which is
why it never contributed. The plugin path is untouched: a dispatcher that opens
its own trailing `else` still owns the fallback, and the new one is opened only
when no dispatcher did.

The golden snapshot moves accordingly -- the method-fuzzing block is one level
deeper, inside the else.

Asserting on the emitted text would not have caught this, because the print was
always there. The new tests assert on the parsed structure instead: every
`if skip_trivial_type(x):` must carry the work in its `orelse`, the taken branch
must only report, and the emitted function must skip a `_cffi_backend`-typed
instance while leaving an ordinary object alone. They fail on the pre-fix
generator.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WPBSmN87d2BqUnrDbojUT1
@devdanzin
devdanzin merged commit 44ecb9b into main Sep 6, 2026
2 checks passed
@devdanzin
devdanzin deleted the cffi-ffi-methods-called-despite-skip-guard branch September 6, 2026 05:23
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.

1 participant