mirror of
https://github.com/python/cpython.git
synced 2025-09-01 06:28:36 +00:00
[3.12] gh-108654: restore comprehension locals before handling exception (GH-108659) (#108700)
gh-108654: restore comprehension locals before handling exception (GH-108659)
(cherry picked from commit d52c4482a8
)
Co-authored-by: Carl Meyer <carl@oddbird.net>
Co-authored-by: Dong-hee Na <donghee.na92@gmail.com>
This commit is contained in:
parent
320d398262
commit
1a15d20b75
3 changed files with 90 additions and 14 deletions
|
@ -561,6 +561,41 @@ class ListComprehensionTest(unittest.TestCase):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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"]})
|
||||||
|
|
||||||
|
def test_exception_in_post_comp_call(self):
|
||||||
|
code = """
|
||||||
|
value = [1, None]
|
||||||
|
try:
|
||||||
|
[v for v in value].sort()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
"""
|
||||||
|
self._check_in_scopes(code, {"value": [1, None]})
|
||||||
|
|
||||||
|
|
||||||
__test__ = {'doctests' : doctests}
|
__test__ = {'doctests' : doctests}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Restore locals shadowed by an inlined comprehension if the comprehension
|
||||||
|
raises an exception.
|
|
@ -5438,6 +5438,8 @@ typedef struct {
|
||||||
PyObject *pushed_locals;
|
PyObject *pushed_locals;
|
||||||
PyObject *temp_symbols;
|
PyObject *temp_symbols;
|
||||||
PyObject *fast_hidden;
|
PyObject *fast_hidden;
|
||||||
|
jump_target_label cleanup;
|
||||||
|
jump_target_label end;
|
||||||
} inlined_comprehension_state;
|
} inlined_comprehension_state;
|
||||||
|
|
||||||
static int
|
static int
|
||||||
|
@ -5543,11 +5545,45 @@ push_inlined_comprehension_state(struct compiler *c, location loc,
|
||||||
// `pushed_locals` on the stack, but this will be reversed when we swap
|
// `pushed_locals` on the stack, but this will be reversed when we swap
|
||||||
// out the comprehension result in pop_inlined_comprehension_state
|
// out the comprehension result in pop_inlined_comprehension_state
|
||||||
ADDOP_I(c, loc, SWAP, PyList_GET_SIZE(state->pushed_locals) + 1);
|
ADDOP_I(c, loc, SWAP, PyList_GET_SIZE(state->pushed_locals) + 1);
|
||||||
|
|
||||||
|
// Add our own cleanup handler to restore comprehension locals in case
|
||||||
|
// of exception, 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;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
pop_inlined_comprehension_state(struct compiler *c, location loc,
|
pop_inlined_comprehension_state(struct compiler *c, location loc,
|
||||||
inlined_comprehension_state state)
|
inlined_comprehension_state state)
|
||||||
|
@ -5564,19 +5600,22 @@ pop_inlined_comprehension_state(struct compiler *c, location loc,
|
||||||
Py_CLEAR(state.temp_symbols);
|
Py_CLEAR(state.temp_symbols);
|
||||||
}
|
}
|
||||||
if (state.pushed_locals) {
|
if (state.pushed_locals) {
|
||||||
// pop names we pushed to stack earlier
|
ADDOP(c, NO_LOCATION, POP_BLOCK);
|
||||||
Py_ssize_t npops = PyList_GET_SIZE(state.pushed_locals);
|
ADDOP_JUMP(c, NO_LOCATION, JUMP, state.end);
|
||||||
// Preserve the list/dict/set result of the comprehension as TOS. This
|
|
||||||
// reverses the SWAP we did in push_inlined_comprehension_state to get
|
// cleanup from an exception inside the comprehension
|
||||||
// the outermost iterable to TOS, so we can still just iterate
|
USE_LABEL(c, state.cleanup);
|
||||||
// pushed_locals in simple reverse order
|
// discard incomplete comprehension result (beneath exc on stack)
|
||||||
ADDOP_I(c, loc, SWAP, npops + 1);
|
ADDOP_I(c, NO_LOCATION, SWAP, 2);
|
||||||
for (Py_ssize_t i = npops - 1; i >= 0; --i) {
|
ADDOP(c, NO_LOCATION, POP_TOP);
|
||||||
k = PyList_GetItem(state.pushed_locals, i);
|
if (restore_inlined_comprehension_locals(c, loc, state) < 0) {
|
||||||
if (k == NULL) {
|
|
||||||
return ERROR;
|
return ERROR;
|
||||||
}
|
}
|
||||||
ADDOP_NAME(c, loc, STORE_FAST_MAYBE_NULL, k, varnames);
|
ADDOP_I(c, NO_LOCATION, RERAISE, 0);
|
||||||
|
|
||||||
|
USE_LABEL(c, state.end);
|
||||||
|
if (restore_inlined_comprehension_locals(c, loc, state) < 0) {
|
||||||
|
return ERROR;
|
||||||
}
|
}
|
||||||
Py_CLEAR(state.pushed_locals);
|
Py_CLEAR(state.pushed_locals);
|
||||||
}
|
}
|
||||||
|
@ -5619,7 +5658,7 @@ compiler_comprehension(struct compiler *c, expr_ty e, int type,
|
||||||
expr_ty val)
|
expr_ty val)
|
||||||
{
|
{
|
||||||
PyCodeObject *co = NULL;
|
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;
|
comprehension_ty outermost;
|
||||||
int scope_type = c->u->u_scope_type;
|
int scope_type = c->u->u_scope_type;
|
||||||
int is_top_level_await = IS_TOP_LEVEL_AWAIT(c);
|
int is_top_level_await = IS_TOP_LEVEL_AWAIT(c);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue