FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Compiler crash: inlined comprehensions overflow `_PyCfgExceptStack`, bypassing the `u_nfblocks` block-nesting limit · Issue #156091 · python/cpython · GitHub

Repository navigation

Compiler crash: inlined comprehensions overflow _PyCfgExceptStack, bypassing the u_nfblocks block-nesting limit #156091

Description

Crash report

What happened?

Compiling source with 23 or more nested list/set/dict comprehensions crashes
the compiler: an assertion failure in debug builds, a segfault in release
(NDEBUG) builds. Nothing is executed — compile() alone is enough. The input
is ~300 bytes.

Reproduction

def src(d):
    e = "i for i in r"
    for _ in range(d - 1):
        e = "[" + e + "] for i in r"
    return "x = [" + e + "]"

compile(src(23), "<s>", "exec")   # crashes; depth 22 compiles fine

Observed behavior

Cliff is exactly CO_MAXBLOCKS + 2 nested comprehensions:

Build Crash at Behavior
main (20e6c2fc), assertions on depth 23 python: Python/flowgraph.c: push_except_block: Assertion 'stack->depth <= CO_MAXBLOCKS' failed
main (20e6c2fc), -DNDEBUG (-O0 and -O2) depth 23 SIGSEGV
3.12.3 (CO_MAXBLOCKS = 20), distro release build depth 22 SIGSEGV
any build depth ≤ cliff−1 compiles fine

Release-build backtrace (NDEBUG, -O0 -g, gdb):

Thread 1 "fuzz_pycompile_" received signal SIGSEGV, Segmentation fault.
0x00005b2b9480d547 in except_stack_top (stack=0x7ba300866070) at Python/flowgraph.c:724
warning: Source file is more recent than executable.
724	    return stack->handlers[stack->depth];

=== full backtrace (NDEBUG, -O0 -g, HEAD 20e6c2fc) ===
#0  0x00005b2b9480d547 in except_stack_top (stack=0x7ba300866070) at Python/flowgraph.c:724
#1  0x00005b2b94809f6d in label_exception_targets (entryblock=0x7ba300529d40) at Python/flowgraph.c:921
#2  0x00005b2b94809a4d in _PyCfg_OptimizeCodeUnit (g=0x7ba300589350, consts=0x7ba3005a6a80, const_cache=0x7ba30053b140, nlocals=1, nparams=0, firstlineno=1) at Python/flowgraph.c:3798
#3  0x00005b2b947ff534 in optimize_and_assemble_code_unit (u=0x5b2ba5b4cd30, const_cache=0x7ba30053b140, code_flags=0, filename=0x7ba30053b0b0) at Python/compile.c:1481
#4  0x00005b2b947ff29b in _PyCompile_OptimizeAndAssemble (c=0x7ba300542f10, addNone=1) at Python/compile.c:1523
#5  0x00005b2b947ff771 in compiler_mod (c=0x7ba300542f10, mod=0x5b2ba5b6a600) at Python/compile.c:901
#6  0x00005b2b947ff644 in _PyAST_Compile (mod=0x5b2ba5b6a600, filename=0x7ba30053b0b0, pflags=0x7ffeec136588, optimize=0, arena=0x7ba300831b90, module=0x0) at Python/compile.c:1536
#7  0x00005b2b9487244b in _Py_CompileString (str=0x7ffeec132580 "x = ", '[' <repeats 23 times>, "i for i in r] for i in r] for i in r] for i in r] for i in r] for i in r] for i in r] for i in r] for i in r] for i in r] for i in r] for i in r] for i in r] for i in r] for"..., filename=0x7ba30053b0b0, start=257, flags=0x7ffeec136588, optimize=0, module=0x0) at Python/pythonrun.c:1595
#8  0x00005b2b948722e8 in Py_CompileStringObject (str=0x7ffeec132580 "x = ", '[' <repeats 23 times>, "i for i in r] for i in r] for i in r] for i in r] for i in r] for i in r] for i in r] for i in r] for i in r] for i in r] for i in r] for i in r] for i in r] for i in r] for"..., filename=0x7ba30053b0b0, start=257, flags=0x7ffeec136588, optimize=0) at Python/pythonrun.c:1561
#9  0x00005b2b94872548 in Py_CompileStringExFlags (str=0x7ffeec132580 "x = ", '[' <repeats 23 times>, "i for i in r] for i in r] for i in r] for i in r] for i in r] for i in r] for i in r] for i in r] for i in r] for i in r] for i in r] for i in r] for i in r] for i in r] for"..., filename_str=0x5b2b94b12d58 "<fuzz input>", start=257, flags=0x7ffeec136588, optimize=0) at Python/pythonrun.c:1608
#10 0x00005b2b9467b515 in fuzz_pycompile (data=0x5b2ba5ac08f0 "\002\001", size=307) at Modules/_xxtestfuzz/fuzzer.c:564
#11 0x00005b2b9467b342 in _run_fuzz (data=0x5b2ba5ac08f0 "\002\001", size=307, fuzzer=0x5b2b9467b370 <fuzz_pycompile>) at Modules/_xxtestfuzz/fuzzer.c:583
#12 0x00005b2b9467b30b in LLVMFuzzerTestOneInput (data=0x5b2ba5ac08f0 "\002\001", size=307) at Modules/_xxtestfuzz/fuzzer.c:724
#13 0x00005b2b94638813 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) ()
#14 0x00005b2b94622691 in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) ()
#15 0x00005b2b94628358 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) ()
#16 0x00005b2b94651973 in main ()
#17 0x00007ba30062a1ca in __libc_start_call_main (main=main@entry=0x5b2b94651950 <main>, argc=argc@entry=2, argv=argv@entry=0x7ffeec136df8) at ../sysdeps/nptl/libc_start_call_main.h:58
#18 0x00007ba30062a28b in __libc_start_main_impl (main=0x5b2b94651950 <main>, argc=2, argv=0x7ffeec136df8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7ffeec136de8) at ../csu/libc-start.c:360
#19 0x00005b2b9461d035 in _start ()

The faulting address equals &handlers[0] + depth * 8, where depth is a
corrupted value (see below), ~1 GB away from the struct.

Root cause

Since PEP 709 (3.12), non-generator comprehensions are inlined into the
enclosing scope. Each inlined comprehension emits one "virtual try/finally"
(SETUP_FINALLY cleanup to restore shadowed outer locals) — but it is
deliberately not counted as an fblock, in codegen_push_inlined_comprehension_locals
(Python/codegen.c):

// no need to push an fblock for this "virtual" try/finally; there can't
// be return/continue/break inside a comprehension

So the only static nesting guard, u_nfblocks >= CO_MAXBLOCKS → "too many statically nested blocks" (Python/compile.c), never sees these blocks.

The backend then trusts the frontend limit and uses a fixed-size stack in
Python/flowgraph.c:

struct _PyCfgExceptStack {
    basicblock *handlers[CO_MAXBLOCKS+2];
    int depth;
};

The N-th inlined comprehension pushes the N-th block; at nesting depth
CO_MAXBLOCKS + 2 the push writes one slot past the array.

  • Assertions on: assert(stack->depth <= CO_MAXBLOCKS) in
    push_except_block fires → abort.
  • NDEBUG: the out-of-bounds store writes handlers[CO_MAXBLOCKS+2],
    which by struct layout is exactly the depth field
    (&handlers[CO_MAXBLOCKS+2] == &depth, confirmed with gdb). depth is
    clobbered with the low 32 bits of a basicblock *; the next
    except_stack_top uses it as an index into handlers[] → wild read →
    deterministic SIGSEGV.

Suggested fix

Count the inlined-comprehension cleanup blocks toward u_nfblocks so the
existing "too many statically nested blocks" SyntaxError fires (consistent
with gh-118348), or make _PyCfgExceptStack dynamically sized / bounds-checked.

Environment

  • CPython main 20e6c2fc7c174342214d561845419c4030f8638f (2026-08-19)
  • Ubuntu 24.04

CPython versions tested on:

3.15, CPython main branch, 3.16, 3.14, 3.13, 3.12

Operating systems tested on:

Linux

Output from running 'python -VV' on the command line:

No response

Linked PRs

Activity

  1. added
    type-crashA hard crash of the interpreter, possibly with a core dump
    on Aug 20, 2026
  2. added 2 commits that reference this issue on Sep 5, 2026
  3. added a commit that references this issue on Sep 12, 2026
  4. added a commit that references this issue on Sep 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    interpreter-core(Objects, Python, Grammar, and Parser dirs)type-crashA hard crash of the interpreter, possibly with a core dump

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions


      Back | FazBrowse Home | New Git URL