Skip to content

Commit

Permalink
pythongh-108654: restore comprehension locals before handling exception
Browse files Browse the repository at this point in the history
  • Loading branch information
carljm committed Aug 30, 2023
1 parent 59e4693 commit 627ff93
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 15 deletions.
25 changes: 25 additions & 0 deletions Lib/test/test_listcomps.py
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,31 @@ def test_iter_var_available_in_locals(self):
}
)

def test_comp_in_try_except(self):
template = """
value = ["a"]
try:
[{func}(value) for value in value]
except:
pass
"""
for func in ["str", "int"]:
code = template.format(func=func)
raises = func != "str"
with self.subTest(raises=raises):
self._check_in_scopes(code, {"value": ["a"]})

def test_comp_in_try_finally(self):
code = """
def f(value):
try:
[{func}(value) for value in value]
finally:
return value
ret = f(["a"])
"""
self._check_in_scopes(code, {"ret": ["a"]})


__test__ = {'doctests' : doctests}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Restore locals shadowed by an inlined comprehension if the comprehension
raises an exception.
69 changes: 54 additions & 15 deletions Python/compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -5529,6 +5529,8 @@ typedef struct {
PyObject *pushed_locals;
PyObject *temp_symbols;
PyObject *fast_hidden;
jump_target_label cleanup;
jump_target_label end;
} inlined_comprehension_state;

static int
Expand Down Expand Up @@ -5641,6 +5643,42 @@ push_inlined_comprehension_state(struct compiler *c, location loc,
ADDOP_I(c, loc, SWAP, PyList_GET_SIZE(state->pushed_locals) + 1);
}

if (c->u->u_nfblocks > 0) {
// If we are inside a try block, we need to add our own cleanup handler
// to restore comprehension locals, so they have the correct values
// inside an exception handler or finally block.
NEW_JUMP_TARGET_LABEL(c, cleanup);
state->cleanup = cleanup;
NEW_JUMP_TARGET_LABEL(c, end);
state->end = end;

// no need to push an fblock for this "virtual" try/finally; there can't
// be return/continue/break inside a comprehension
ADDOP_JUMP(c, loc, SETUP_FINALLY, cleanup);
}

return SUCCESS;
}

static int
restore_inlined_comprehension_locals(struct compiler *c, location loc,
inlined_comprehension_state state)
{
PyObject *k;
// pop names we pushed to stack earlier
Py_ssize_t npops = PyList_GET_SIZE(state.pushed_locals);
// Preserve the comprehension result (or exception) as TOS. This
// reverses the SWAP we did in push_inlined_comprehension_state to get
// the outermost iterable to TOS, so we can still just iterate
// pushed_locals in simple reverse order
ADDOP_I(c, loc, SWAP, npops + 1);
for (Py_ssize_t i = npops - 1; i >= 0; --i) {
k = PyList_GetItem(state.pushed_locals, i);
if (k == NULL) {
return ERROR;
}
ADDOP_NAME(c, loc, STORE_FAST_MAYBE_NULL, k, varnames);
}
return SUCCESS;
}

Expand All @@ -5651,6 +5689,20 @@ pop_inlined_comprehension_state(struct compiler *c, location loc,
c->u->u_in_inlined_comp--;
PyObject *k, *v;
Py_ssize_t pos = 0;
if (IS_LABEL(state.cleanup)) {
ADDOP_JUMP(c, NO_LOCATION, JUMP, state.end);

// cleanup from an exception inside the comprehension
USE_LABEL(c, state.cleanup);
// discard incomplete comprehension result (beneath exc on stack)
ADDOP_I(c, NO_LOCATION, SWAP, 2);
ADDOP(c, NO_LOCATION, POP_TOP);
restore_inlined_comprehension_locals(c, loc, state);
ADDOP_I(c, NO_LOCATION, RERAISE, 0);
ADDOP(c, NO_LOCATION, POP_BLOCK);

USE_LABEL(c, state.end);
}
if (state.temp_symbols) {
while (PyDict_Next(state.temp_symbols, &pos, &k, &v)) {
if (PyDict_SetItem(c->u->u_ste->ste_symbols, k, v)) {
Expand All @@ -5660,20 +5712,7 @@ pop_inlined_comprehension_state(struct compiler *c, location loc,
Py_CLEAR(state.temp_symbols);
}
if (state.pushed_locals) {
// pop names we pushed to stack earlier
Py_ssize_t npops = PyList_GET_SIZE(state.pushed_locals);
// Preserve the list/dict/set result of the comprehension as TOS. This
// reverses the SWAP we did in push_inlined_comprehension_state to get
// the outermost iterable to TOS, so we can still just iterate
// pushed_locals in simple reverse order
ADDOP_I(c, loc, SWAP, npops + 1);
for (Py_ssize_t i = npops - 1; i >= 0; --i) {
k = PyList_GetItem(state.pushed_locals, i);
if (k == NULL) {
return ERROR;
}
ADDOP_NAME(c, loc, STORE_FAST_MAYBE_NULL, k, varnames);
}
restore_inlined_comprehension_locals(c, loc, state);
Py_CLEAR(state.pushed_locals);
}
if (state.fast_hidden) {
Expand Down Expand Up @@ -5715,7 +5754,7 @@ compiler_comprehension(struct compiler *c, expr_ty e, int type,
expr_ty val)
{
PyCodeObject *co = NULL;
inlined_comprehension_state inline_state = {NULL, NULL};
inlined_comprehension_state inline_state = {NULL, NULL, NULL, NO_LABEL, NO_LABEL};
comprehension_ty outermost;
int scope_type = c->u->u_scope_type;
int is_top_level_await = IS_TOP_LEVEL_AWAIT(c);
Expand Down

0 comments on commit 627ff93

Please sign in to comment.