Skip to content

Commit 3dd0feb

Browse files
pythongh-125038: Crash after genexpr.gi_frame.f_locals manipulations 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.
1 parent d20c43d commit 3dd0feb

File tree

5 files changed

+124
-6
lines changed

5 files changed

+124
-6
lines changed

Lib/test/test_frame.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,61 @@ def make_frame():
515515
with self.assertRaises(TypeError):
516516
FrameLocalsProxy(frame=sys._getframe()) # no keyword arguments
517517

518+
def test_generator_f_locals(self):
519+
def get_generator_genexpr(new_value):
520+
g = (x for x in range(10))
521+
g.gi_frame.f_locals['.0'] = new_value
522+
return g
523+
524+
def get_generator_fn_call(new_value):
525+
def gen(it):
526+
for x in it:
527+
yield x
528+
529+
g = gen(range(10))
530+
g.gi_frame.f_locals['it'] = new_value
531+
return g
532+
533+
err_msg_pattern_genexpr = "'for' requires an object with __iter__ method, got %s"
534+
err_msg_pattern_fn_call = "'%s' object is not iterable"
535+
536+
sequences = [
537+
range(0),
538+
range(20),
539+
[1, 2, 3],
540+
(2,),
541+
set((13, 48, 211)),
542+
frozenset((15, 8, 6)),
543+
dict([(1, 2), (3, 4)]),
544+
]
545+
546+
for seq in sequences:
547+
err_msg_genexpr = err_msg_pattern_genexpr % type(seq).__name__
548+
with self.assertRaisesRegex(TypeError, err_msg_genexpr):
549+
list(get_generator_genexpr(seq))
550+
self.assertListEqual(list(get_generator_genexpr(iter(seq))),
551+
list(seq))
552+
553+
self.assertListEqual(list(get_generator_fn_call(seq)),
554+
list(seq))
555+
self.assertListEqual(list(get_generator_fn_call(iter(seq))),
556+
list(seq))
557+
558+
non_sequences = [
559+
None,
560+
42,
561+
3.0,
562+
2j,
563+
]
564+
565+
for obj in non_sequences:
566+
err_msg_genexpr = err_msg_pattern_genexpr % type(obj).__name__
567+
with self.assertRaisesRegex(TypeError, err_msg_genexpr):
568+
list(get_generator_genexpr(obj))
569+
err_msg_fn_call = err_msg_pattern_fn_call % type(obj).__name__
570+
with self.assertRaisesRegex(TypeError, err_msg_fn_call):
571+
list(get_generator_fn_call(obj))
572+
518573

519574
class FrameLocalsProxyMappingTests(mapping_tests.TestHashMappingProtocol):
520575
"""Test that FrameLocalsProxy behaves like a Mapping (with exceptions)"""
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix crash when iterating over a generator expression after direct changes on ``gi_frame.f_locals``.
2+
Tests on generator modifying through ``gi_frame.f_locals`` are added,
3+
both to genexpr generators and function generators. Patch by Mikhail Efimov.

Python/bytecodes.c

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2806,7 +2806,16 @@ dummy_func(
28062806
replaced op(_FOR_ITER, (iter -- iter, next)) {
28072807
/* before: [iter]; after: [iter, iter()] *or* [] (and jump over END_FOR.) */
28082808
PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
2809-
PyObject *next_o = (*Py_TYPE(iter_o)->tp_iternext)(iter_o);
2809+
PyTypeObject *type = Py_TYPE(iter_o);
2810+
iternextfunc iternext = type->tp_iternext;
2811+
if (iternext == NULL) {
2812+
_PyErr_Format(tstate, PyExc_TypeError,
2813+
"'for' requires an object with "
2814+
"__iter__ method, got %.100s",
2815+
type->tp_name);
2816+
ERROR_NO_POP();
2817+
}
2818+
PyObject *next_o = (*iternext)(iter_o);
28102819
if (next_o == NULL) {
28112820
if (_PyErr_Occurred(tstate)) {
28122821
int matches = _PyErr_ExceptionMatches(tstate, PyExc_StopIteration);
@@ -2832,7 +2841,16 @@ dummy_func(
28322841
op(_FOR_ITER_TIER_TWO, (iter -- iter, next)) {
28332842
/* before: [iter]; after: [iter, iter()] *or* [] (and jump over END_FOR.) */
28342843
PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
2835-
PyObject *next_o = (*Py_TYPE(iter_o)->tp_iternext)(iter_o);
2844+
PyTypeObject *type = Py_TYPE(iter_o);
2845+
iternextfunc iternext = type->tp_iternext;
2846+
if (iternext == NULL) {
2847+
_PyErr_Format(tstate, PyExc_TypeError,
2848+
"'for' requires an object with "
2849+
"__iter__ method, got %.100s",
2850+
type->tp_name);
2851+
ERROR_NO_POP();
2852+
}
2853+
PyObject *next_o = (*iternext)(iter_o);
28362854
if (next_o == NULL) {
28372855
if (_PyErr_Occurred(tstate)) {
28382856
int matches = _PyErr_ExceptionMatches(tstate, PyExc_StopIteration);
@@ -2856,7 +2874,16 @@ dummy_func(
28562874
_Py_CODEUNIT *target;
28572875
_PyStackRef iter_stackref = TOP();
28582876
PyObject *iter = PyStackRef_AsPyObjectBorrow(iter_stackref);
2859-
PyObject *next = (*Py_TYPE(iter)->tp_iternext)(iter);
2877+
PyTypeObject *type = Py_TYPE(iter);
2878+
iternextfunc iternext = type->tp_iternext;
2879+
if (iternext == NULL) {
2880+
_PyErr_Format(tstate, PyExc_TypeError,
2881+
"'for' requires an object with "
2882+
"__iter__ method, got %.100s",
2883+
type->tp_name);
2884+
ERROR_NO_POP();
2885+
}
2886+
PyObject *next = (*iternext)(iter);
28602887
if (next != NULL) {
28612888
PUSH(PyStackRef_FromPyObjectSteal(next));
28622889
target = next_instr;

Python/executor_cases.c.h

Lines changed: 12 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/generated_cases.c.h

Lines changed: 24 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)