Skip to content

Commit

Permalink
pythongh-125038: Crash after genexpr.gi_frame.f_locals manipulations …
Browse files Browse the repository at this point in the history
…is fixed

Some iterator checks are added for _FOR_ITER,
_FOR_ITER_TIER_TWO and INSTRUMENTED_FOR_ITER bytecode implementations.
TypeError is raised in case of tp_iternext == NULL.
Tests on generator modifying through gi_frame.f_locals are added, both
to genexpr generators and function generators.
  • Loading branch information
efimov-mikhail committed Oct 9, 2024
1 parent d20c43d commit 3dd0feb
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 6 deletions.
55 changes: 55 additions & 0 deletions Lib/test/test_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,61 @@ def make_frame():
with self.assertRaises(TypeError):
FrameLocalsProxy(frame=sys._getframe()) # no keyword arguments

def test_generator_f_locals(self):
def get_generator_genexpr(new_value):
g = (x for x in range(10))
g.gi_frame.f_locals['.0'] = new_value
return g

def get_generator_fn_call(new_value):
def gen(it):
for x in it:
yield x

g = gen(range(10))
g.gi_frame.f_locals['it'] = new_value
return g

err_msg_pattern_genexpr = "'for' requires an object with __iter__ method, got %s"
err_msg_pattern_fn_call = "'%s' object is not iterable"

sequences = [
range(0),
range(20),
[1, 2, 3],
(2,),
set((13, 48, 211)),
frozenset((15, 8, 6)),
dict([(1, 2), (3, 4)]),
]

for seq in sequences:
err_msg_genexpr = err_msg_pattern_genexpr % type(seq).__name__
with self.assertRaisesRegex(TypeError, err_msg_genexpr):
list(get_generator_genexpr(seq))
self.assertListEqual(list(get_generator_genexpr(iter(seq))),
list(seq))

self.assertListEqual(list(get_generator_fn_call(seq)),
list(seq))
self.assertListEqual(list(get_generator_fn_call(iter(seq))),
list(seq))

non_sequences = [
None,
42,
3.0,
2j,
]

for obj in non_sequences:
err_msg_genexpr = err_msg_pattern_genexpr % type(obj).__name__
with self.assertRaisesRegex(TypeError, err_msg_genexpr):
list(get_generator_genexpr(obj))
err_msg_fn_call = err_msg_pattern_fn_call % type(obj).__name__
with self.assertRaisesRegex(TypeError, err_msg_fn_call):
list(get_generator_fn_call(obj))


class FrameLocalsProxyMappingTests(mapping_tests.TestHashMappingProtocol):
"""Test that FrameLocalsProxy behaves like a Mapping (with exceptions)"""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix crash when iterating over a generator expression after direct changes on ``gi_frame.f_locals``.
Tests on generator modifying through ``gi_frame.f_locals`` are added,
both to genexpr generators and function generators. Patch by Mikhail Efimov.
33 changes: 30 additions & 3 deletions Python/bytecodes.c
Original file line number Diff line number Diff line change
Expand Up @@ -2806,7 +2806,16 @@ dummy_func(
replaced op(_FOR_ITER, (iter -- iter, next)) {
/* before: [iter]; after: [iter, iter()] *or* [] (and jump over END_FOR.) */
PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
PyObject *next_o = (*Py_TYPE(iter_o)->tp_iternext)(iter_o);
PyTypeObject *type = Py_TYPE(iter_o);
iternextfunc iternext = type->tp_iternext;
if (iternext == NULL) {
_PyErr_Format(tstate, PyExc_TypeError,
"'for' requires an object with "
"__iter__ method, got %.100s",
type->tp_name);
ERROR_NO_POP();
}
PyObject *next_o = (*iternext)(iter_o);
if (next_o == NULL) {
if (_PyErr_Occurred(tstate)) {
int matches = _PyErr_ExceptionMatches(tstate, PyExc_StopIteration);
Expand All @@ -2832,7 +2841,16 @@ dummy_func(
op(_FOR_ITER_TIER_TWO, (iter -- iter, next)) {
/* before: [iter]; after: [iter, iter()] *or* [] (and jump over END_FOR.) */
PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
PyObject *next_o = (*Py_TYPE(iter_o)->tp_iternext)(iter_o);
PyTypeObject *type = Py_TYPE(iter_o);
iternextfunc iternext = type->tp_iternext;
if (iternext == NULL) {
_PyErr_Format(tstate, PyExc_TypeError,
"'for' requires an object with "
"__iter__ method, got %.100s",
type->tp_name);
ERROR_NO_POP();
}
PyObject *next_o = (*iternext)(iter_o);
if (next_o == NULL) {
if (_PyErr_Occurred(tstate)) {
int matches = _PyErr_ExceptionMatches(tstate, PyExc_StopIteration);
Expand All @@ -2856,7 +2874,16 @@ dummy_func(
_Py_CODEUNIT *target;
_PyStackRef iter_stackref = TOP();
PyObject *iter = PyStackRef_AsPyObjectBorrow(iter_stackref);
PyObject *next = (*Py_TYPE(iter)->tp_iternext)(iter);
PyTypeObject *type = Py_TYPE(iter);
iternextfunc iternext = type->tp_iternext;
if (iternext == NULL) {
_PyErr_Format(tstate, PyExc_TypeError,
"'for' requires an object with "
"__iter__ method, got %.100s",
type->tp_name);
ERROR_NO_POP();
}
PyObject *next = (*iternext)(iter);
if (next != NULL) {
PUSH(PyStackRef_FromPyObjectSteal(next));
target = next_instr;
Expand Down
13 changes: 12 additions & 1 deletion Python/executor_cases.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 24 additions & 2 deletions Python/generated_cases.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 3dd0feb

Please sign in to comment.