Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-116767: fix crash on 'async with' with many context managers #118348

Merged
merged 7 commits into from May 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion Include/cpython/code.h
Expand Up @@ -226,7 +226,7 @@ struct PyCodeObject _PyCode_DEF(1);
*/
#define PY_PARSER_REQUIRES_FUTURE_KEYWORD

#define CO_MAXBLOCKS 20 /* Max static block nesting within a function */
#define CO_MAXBLOCKS 21 /* Max static block nesting within a function */

PyAPI_DATA(PyTypeObject) PyCode_Type;

Expand Down
36 changes: 32 additions & 4 deletions Lib/test/test_syntax.py
Expand Up @@ -2392,13 +2392,40 @@ def bug():
code += "): yield a"
return code

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

for n in range(CO_MAXBLOCKS):
for n in range(MAX_MANAGERS):
with self.subTest(f"within range: {n=}"):
compile(get_code(n), "<string>", "exec")

for n in range(CO_MAXBLOCKS, CO_MAXBLOCKS + 5):
for n in range(MAX_MANAGERS, MAX_MANAGERS + 5):
with self.subTest(f"out of range: {n=}"):
self._check_error(get_code(n), "too many statically nested blocks")

@support.cpython_only
def test_async_with_statement_many_context_managers(self):
# See gh-116767

def get_code(n):
code = [ textwrap.dedent("""
async def bug():
async with (
a
""") ]
for i in range(n):
code.append(f" as a{i}, a\n")
code.append("): yield a")
return "".join(code)

CO_MAXBLOCKS = 21 # static nesting limit of the compiler
MAX_MANAGERS = CO_MAXBLOCKS - 1 # One for the StopIteration block

for n in range(MAX_MANAGERS):
with self.subTest(f"within range: {n=}"):
compile(get_code(n), "<string>", "exec")

for n in range(MAX_MANAGERS, MAX_MANAGERS + 5):
with self.subTest(f"out of range: {n=}"):
self._check_error(get_code(n), "too many statically nested blocks")

Expand Down Expand Up @@ -2536,7 +2563,8 @@ def test_syntax_error_on_deeply_nested_blocks(self):
while 20:
while 21:
while 22:
break
while 23:
break
"""
self._check_error(source, "too many statically nested blocks")

Expand Down
@@ -0,0 +1 @@
Fix crash in compiler on 'async with' that has many context managers.
18 changes: 16 additions & 2 deletions Python/compile.c
Expand Up @@ -113,7 +113,8 @@ compiler IR.

enum fblocktype { WHILE_LOOP, FOR_LOOP, TRY_EXCEPT, FINALLY_TRY, FINALLY_END,
WITH, ASYNC_WITH, HANDLER_CLEANUP, POP_VALUE, EXCEPTION_HANDLER,
EXCEPTION_GROUP_HANDLER, ASYNC_COMPREHENSION_GENERATOR };
EXCEPTION_GROUP_HANDLER, ASYNC_COMPREHENSION_GENERATOR,
STOP_ITERATION };

struct fblockinfo {
enum fblocktype fb_type;
Expand Down Expand Up @@ -1503,6 +1504,7 @@ compiler_unwind_fblock(struct compiler *c, location *ploc,
case EXCEPTION_HANDLER:
case EXCEPTION_GROUP_HANDLER:
case ASYNC_COMPREHENSION_GENERATOR:
case STOP_ITERATION:
return SUCCESS;

case FOR_LOOP:
Expand Down Expand Up @@ -2232,14 +2234,26 @@ compiler_function_body(struct compiler *c, stmt_ty s, int is_async, Py_ssize_t f
c->u->u_metadata.u_argcount = asdl_seq_LEN(args->args);
c->u->u_metadata.u_posonlyargcount = asdl_seq_LEN(args->posonlyargs);
c->u->u_metadata.u_kwonlyargcount = asdl_seq_LEN(args->kwonlyargs);

NEW_JUMP_TARGET_LABEL(c, start);
USE_LABEL(c, start);
bool add_stopiteration_handler = c->u->u_ste->ste_coroutine || c->u->u_ste->ste_generator;
if (add_stopiteration_handler) {
/* wrap_in_stopiteration_handler will push a block, so we need to account for that */
RETURN_IF_ERROR(
compiler_push_fblock(c, NO_LOCATION, STOP_ITERATION,
start, NO_LABEL, NULL));
}

for (Py_ssize_t i = first_instr; i < asdl_seq_LEN(body); i++) {
VISIT_IN_SCOPE(c, stmt, (stmt_ty)asdl_seq_GET(body, i));
}
if (c->u->u_ste->ste_coroutine || c->u->u_ste->ste_generator) {
if (add_stopiteration_handler) {
if (wrap_in_stopiteration_handler(c) < 0) {
compiler_exit_scope(c);
return ERROR;
}
compiler_pop_fblock(c, STOP_ITERATION, start);
}
PyCodeObject *co = optimize_and_assemble(c, 1);
compiler_exit_scope(c);
Expand Down