diff --git a/Include/internal/pycore_compile.h b/Include/internal/pycore_compile.h index 911cc1f10f15131..8f3effff589666f 100644 --- a/Include/internal/pycore_compile.h +++ b/Include/internal/pycore_compile.h @@ -110,6 +110,7 @@ enum _PyCompile_FBlockType { COMPILE_FBLOCK_EXCEPTION_HANDLER, COMPILE_FBLOCK_EXCEPTION_GROUP_HANDLER, COMPILE_FBLOCK_ASYNC_COMPREHENSION_GENERATOR, + COMPILE_FBLOCK_INLINED_COMPREHENSION, COMPILE_FBLOCK_STOP_ITERATION, }; diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py index 7859387dccef376..864478f44a1861d 100644 --- a/Lib/test/test_syntax.py +++ b/Lib/test/test_syntax.py @@ -3503,6 +3503,21 @@ def test_syntax_error_on_deeply_nested_blocks(self): """ self._check_error(source, "too many statically nested blocks") + @support.cpython_only + def test_nested_inlined_comprehensions_block_limit(self): + # Each inlined comprehension with locals emits SETUP_FINALLY, which + # must count toward CO_MAXBLOCKS (gh-156091). + def src(depth): + e = "i for i in r" + for _ in range(depth - 1): + e = "[" + e + "] for i in r" + return "x = [" + e + "]" + + CO_MAXBLOCKS = 21 + compile(src(CO_MAXBLOCKS), "", "exec") + self._check_error(src(CO_MAXBLOCKS + 1), + "too many statically nested blocks") + @support.cpython_only def test_error_on_parser_stack_overflow(self): source = "-" * 100000 + "4" diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-09-04-14-06-00.gh-issue-156091.nested-comp.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-04-14-06-00.gh-issue-156091.nested-comp.rst new file mode 100644 index 000000000000000..fdceb3209b7f7cb --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-04-14-06-00.gh-issue-156091.nested-comp.rst @@ -0,0 +1,3 @@ +Fix a crash when compiling deeply nested inlined list, set, or dict +comprehensions. A :exc:`SyntaxError` is now raised when the nesting exceeds +the compiler's static block limit. diff --git a/Python/codegen.c b/Python/codegen.c index dbe889c54411829..774b54dfb9df0e1 100644 --- a/Python/codegen.c +++ b/Python/codegen.c @@ -540,6 +540,7 @@ codegen_unwind_fblock(compiler *c, location *ploc, case COMPILE_FBLOCK_EXCEPTION_HANDLER: case COMPILE_FBLOCK_EXCEPTION_GROUP_HANDLER: case COMPILE_FBLOCK_ASYNC_COMPREHENSION_GENERATOR: + case COMPILE_FBLOCK_INLINED_COMPREHENSION: case COMPILE_FBLOCK_STOP_ITERATION: return SUCCESS; @@ -4873,8 +4874,11 @@ codegen_push_inlined_comprehension_locals(compiler *c, location loc, NEW_JUMP_TARGET_LABEL(c, cleanup); state->cleanup = cleanup; - // no need to push an fblock for this "virtual" try/finally; there can't - // be return/continue/break inside a comprehension + // Count against CO_MAXBLOCKS: SETUP_FINALLY consumes an except-stack + // slot even though return/continue/break cannot appear here. + RETURN_IF_ERROR(_PyCompile_PushFBlock( + c, loc, COMPILE_FBLOCK_INLINED_COMPREHENSION, + cleanup, NO_LABEL, NULL)); ADDOP_JUMP(c, loc, SETUP_FINALLY, cleanup); } return SUCCESS; @@ -4920,6 +4924,8 @@ codegen_pop_inlined_comprehension_locals(compiler *c, location loc, { if (state->pushed_locals) { ADDOP(c, NO_LOCATION, POP_BLOCK); + _PyCompile_PopFBlock(c, COMPILE_FBLOCK_INLINED_COMPREHENSION, + state->cleanup); NEW_JUMP_TARGET_LABEL(c, end); ADDOP_JUMP(c, NO_LOCATION, JUMP_NO_INTERRUPT, end);