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
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 inputis ~300 bytes.
Reproduction
Observed behavior
Cliff is exactly
CO_MAXBLOCKS + 2nested comprehensions:20e6c2fc), assertions onpython: Python/flowgraph.c: push_except_block: Assertion 'stack->depth <= CO_MAXBLOCKS' failed20e6c2fc),-DNDEBUG(-O0 and -O2)CO_MAXBLOCKS= 20), distro release buildRelease-build backtrace (NDEBUG,
-O0 -g, gdb):The faulting address equals
&handlers[0] + depth * 8, wheredepthis acorrupted 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_FINALLYcleanup to restore shadowed outer locals) — but it isdeliberately not counted as an fblock, in
codegen_push_inlined_comprehension_locals(Python/codegen.c):
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:
The N-th inlined comprehension pushes the N-th block; at nesting depth
CO_MAXBLOCKS + 2the push writes one slot past the array.assert(stack->depth <= CO_MAXBLOCKS)inpush_except_blockfires → abort.handlers[CO_MAXBLOCKS+2],which by struct layout is exactly the
depthfield(
&handlers[CO_MAXBLOCKS+2] == &depth, confirmed with gdb).depthisclobbered with the low 32 bits of a
basicblock *; the nextexcept_stack_topuses it as an index intohandlers[]→ wild read →deterministic SIGSEGV.
Suggested fix
Count the inlined-comprehension cleanup blocks toward
u_nfblocksso theexisting
"too many statically nested blocks"SyntaxError fires (consistentwith gh-118348), or make
_PyCfgExceptStackdynamically sized / bounds-checked.Environment
20e6c2fc7c174342214d561845419c4030f8638f(2026-08-19)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