Skip to content

Commit c1bf487

Browse files
authored
gh-116767: fix crash on 'async with' with many context managers (GH-118348)
Account for `add_stopiteration_handler` pushing a block for `async with`. To allow generator functions that previously almost hit the `CO_MAXBLOCKS` limit by nesting non-async blocks, the limit is increased by 1. This increase allows one more block in non-generator functions.
1 parent f6fab21 commit c1bf487

File tree

4 files changed

+50
-7
lines changed

4 files changed

+50
-7
lines changed

Include/cpython/code.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ struct PyCodeObject _PyCode_DEF(1);
226226
*/
227227
#define PY_PARSER_REQUIRES_FUTURE_KEYWORD
228228

229-
#define CO_MAXBLOCKS 20 /* Max static block nesting within a function */
229+
#define CO_MAXBLOCKS 21 /* Max static block nesting within a function */
230230

231231
PyAPI_DATA(PyTypeObject) PyCode_Type;
232232

Lib/test/test_syntax.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2392,13 +2392,40 @@ def bug():
23922392
code += "): yield a"
23932393
return code
23942394

2395-
CO_MAXBLOCKS = 20 # static nesting limit of the compiler
2395+
CO_MAXBLOCKS = 21 # static nesting limit of the compiler
2396+
MAX_MANAGERS = CO_MAXBLOCKS - 1 # One for the StopIteration block
23962397

2397-
for n in range(CO_MAXBLOCKS):
2398+
for n in range(MAX_MANAGERS):
23982399
with self.subTest(f"within range: {n=}"):
23992400
compile(get_code(n), "<string>", "exec")
24002401

2401-
for n in range(CO_MAXBLOCKS, CO_MAXBLOCKS + 5):
2402+
for n in range(MAX_MANAGERS, MAX_MANAGERS + 5):
2403+
with self.subTest(f"out of range: {n=}"):
2404+
self._check_error(get_code(n), "too many statically nested blocks")
2405+
2406+
@support.cpython_only
2407+
def test_async_with_statement_many_context_managers(self):
2408+
# See gh-116767
2409+
2410+
def get_code(n):
2411+
code = [ textwrap.dedent("""
2412+
async def bug():
2413+
async with (
2414+
a
2415+
""") ]
2416+
for i in range(n):
2417+
code.append(f" as a{i}, a\n")
2418+
code.append("): yield a")
2419+
return "".join(code)
2420+
2421+
CO_MAXBLOCKS = 21 # static nesting limit of the compiler
2422+
MAX_MANAGERS = CO_MAXBLOCKS - 1 # One for the StopIteration block
2423+
2424+
for n in range(MAX_MANAGERS):
2425+
with self.subTest(f"within range: {n=}"):
2426+
compile(get_code(n), "<string>", "exec")
2427+
2428+
for n in range(MAX_MANAGERS, MAX_MANAGERS + 5):
24022429
with self.subTest(f"out of range: {n=}"):
24032430
self._check_error(get_code(n), "too many statically nested blocks")
24042431

@@ -2536,7 +2563,8 @@ def test_syntax_error_on_deeply_nested_blocks(self):
25362563
while 20:
25372564
while 21:
25382565
while 22:
2539-
break
2566+
while 23:
2567+
break
25402568
"""
25412569
self._check_error(source, "too many statically nested blocks")
25422570

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix crash in compiler on 'async with' that has many context managers.

Python/compile.c

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,8 @@ compiler IR.
113113

114114
enum fblocktype { WHILE_LOOP, FOR_LOOP, TRY_EXCEPT, FINALLY_TRY, FINALLY_END,
115115
WITH, ASYNC_WITH, HANDLER_CLEANUP, POP_VALUE, EXCEPTION_HANDLER,
116-
EXCEPTION_GROUP_HANDLER, ASYNC_COMPREHENSION_GENERATOR };
116+
EXCEPTION_GROUP_HANDLER, ASYNC_COMPREHENSION_GENERATOR,
117+
STOP_ITERATION };
117118

118119
struct fblockinfo {
119120
enum fblocktype fb_type;
@@ -1503,6 +1504,7 @@ compiler_unwind_fblock(struct compiler *c, location *ploc,
15031504
case EXCEPTION_HANDLER:
15041505
case EXCEPTION_GROUP_HANDLER:
15051506
case ASYNC_COMPREHENSION_GENERATOR:
1507+
case STOP_ITERATION:
15061508
return SUCCESS;
15071509

15081510
case FOR_LOOP:
@@ -2232,14 +2234,26 @@ compiler_function_body(struct compiler *c, stmt_ty s, int is_async, Py_ssize_t f
22322234
c->u->u_metadata.u_argcount = asdl_seq_LEN(args->args);
22332235
c->u->u_metadata.u_posonlyargcount = asdl_seq_LEN(args->posonlyargs);
22342236
c->u->u_metadata.u_kwonlyargcount = asdl_seq_LEN(args->kwonlyargs);
2237+
2238+
NEW_JUMP_TARGET_LABEL(c, start);
2239+
USE_LABEL(c, start);
2240+
bool add_stopiteration_handler = c->u->u_ste->ste_coroutine || c->u->u_ste->ste_generator;
2241+
if (add_stopiteration_handler) {
2242+
/* wrap_in_stopiteration_handler will push a block, so we need to account for that */
2243+
RETURN_IF_ERROR(
2244+
compiler_push_fblock(c, NO_LOCATION, STOP_ITERATION,
2245+
start, NO_LABEL, NULL));
2246+
}
2247+
22352248
for (Py_ssize_t i = first_instr; i < asdl_seq_LEN(body); i++) {
22362249
VISIT_IN_SCOPE(c, stmt, (stmt_ty)asdl_seq_GET(body, i));
22372250
}
2238-
if (c->u->u_ste->ste_coroutine || c->u->u_ste->ste_generator) {
2251+
if (add_stopiteration_handler) {
22392252
if (wrap_in_stopiteration_handler(c) < 0) {
22402253
compiler_exit_scope(c);
22412254
return ERROR;
22422255
}
2256+
compiler_pop_fblock(c, STOP_ITERATION, start);
22432257
}
22442258
PyCodeObject *co = optimize_and_assemble(c, 1);
22452259
compiler_exit_scope(c);

0 commit comments

Comments
 (0)