8000 gh-125723: Fix crash with f_locals when generator frame outlive thei… · python/cpython@8e20e42 · GitHub
[go: up one dir, main page]

Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 8e20e42

Browse files
efimov-mikhailEclips4ncoghlan
authored
gh-125723: Fix crash with f_locals when generator frame outlive their generator (#126956)
Co-authored-by: Kirill Podoprigora <kirill.bast9@mail.ru> Co-authored-by: Alyssa Coghlan <ncoghlan@gmail.com>
1 parent 24c84d8 commit 8e20e42

File tree

4 files changed

+101
-9
lines changed

4 files changed

+101
-9
lines changed

Include/internal/pycore_object.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ extern void _Py_ForgetReference(PyObject *);
6262
PyAPI_FUNC(int) _PyObject_IsFreed(PyObject *);
6363

6464
/* We need to maintain an internal copy of Py{Var}Object_HEAD_INIT to avoid
65-
designated initializer conflicts in C++20. If we use the deinition in
65+
designated initializer conflicts in C++20. If we use the definition in
< 8000 code>6666
object.h, we will be mixing designated and non-designated initializers in
6767
pycore objects which is forbiddent in C++20. However, if we then use
6868
designated initializers in object.h then Extensions without designated break.

Lib/test/test_generators.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,89 @@ def genfn():
652652
self.assertIsNone(f_wr())
653653

654654

655+
# See https://github.com/python/cpython/issues/125723
656+
class GeneratorDeallocTest(unittest.TestCase):
657+
def test_frame_outlives_generator(self):
658+
def g1():
659+
a = 42
660+
yield sys._getframe()
661+
662+
def g2():
663+
a = 42
664+
yield
665+
666+
def g3(obj):
667+
a = 42
668+
obj.frame = sys._getframe()
669+
yield
670+
671+
class ObjectWithFrame():
672+
def __init__(self):
673+
self.frame = None
674+
675+
def get_frame(index):
676+
if index == 1:
677+
return next(g1())
678+
elif index == 2:
679+
gen = g2()
680+
next(gen)
681+
return gen.gi_frame
682+
elif index == 3:
683+
obj = ObjectWithFrame()
684+
next(g3(obj))
685+
return obj.frame
686+
else:
687+
return None
688+
689+
for index in (1, 2, 3):
690+
with self.subTest(index=index):
691+
frame = get_frame(index)
692+
frame_locals = frame.f_locals
693+
self.assertIn('a', frame_locals)
694+
self.assertEqual(frame_locals['a'], 42)
695+
696+
def test_frame_locals_outlive_generator(self):
697+
frame_locals1 = None
698+
699+
def g1():
700+
nonlocal frame_locals1
701+
frame_locals1 = sys._getframe().f_locals
702+
a = 42
703+
yield
704+
705+
def g2():
706+
a = 42
707+
yield sys._getframe().f_locals
708+
709+
def get_frame_locals(index):
710+
if index == 1:
711+
nonlocal frame_locals1
712+
next(g1())
713+
return frame_locals1
714+
if index == 2:
715+
return next(g2())
716+
else:
717+
return None
718+
719+
for index in (1, 2):
720+
with self.subTest(index=index):
721+
frame_locals = get_frame_locals(index)
722+
self.assertIn('a', frame_locals)
723+
self.assertEqual(frame_locals['a'], 42)
724+
725+
def test_frame_locals_outlive_generator_with_exec(self):
726+
def g():
727+
a = 42
728+
yield locals(), sys._getframe().f_locals
729+
730+
locals_ = {'g': g}
731+
for i in range(10):
732+
exec("snapshot, live_locals = next(g())", locals=locals_)
733+
for l in (locals_['snapshot'], locals_['live_locals']):
734+
self.assertIn('a', l)
735+
self.assertEqual(l['a'], 42)
736+
737+
655738
class GeneratorThrowTest(unittest.TestCase):
656739

657740
def test_exception_context_with_yield(self):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix crash with ``gi_frame.f_locals`` when generator frames outlive their
2+
generator. Patch by Mikhail Efimov.

Objects/genobject.c

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,19 @@ _PyGen_Finalize(PyObject *self)
134134
PyErr_SetRaisedException(exc);
135135
}
136136

137+
static void
138+
gen_clear_frame(PyGenObject *gen)
139+
{
140+
if (gen->gi_frame_state == FRAME_CLEARED)
141+
return;
142+
143+
gen->gi_frame_state = FRAME_CLEARED;
144+
_PyInterpreterFrame *frame = &gen->gi_iframe;
145+
frame->previous = NULL;
146+
_PyFrame_ClearExceptCode(frame);
147+
_PyErr_ClearExcState(&gen->gi_exc_state);
148+
}
149+
137150
static void
138151
gen_dealloc(PyObject *self)
139152
{
@@ -159,13 +172,7 @@ gen_dealloc(PyObject *self)
159172
if (PyCoro_CheckExact(gen)) {
160173
Py_CLEAR(((PyCoroObject *)gen)->cr_origin_or_finalizer);
161174
}
162-
if (gen->gi_frame_state != FRAME_CLEARED) {
163-
_PyInterpreterFrame *frame = &gen->gi_iframe;
164-
gen->gi_frame_state = FRAME_CLEARED;
165-
frame->previous = NULL;
166-
_PyFrame_ClearExceptCode(frame);
167-
_PyErr_ClearExcState(&gen->gi_exc_state);
168-
}
175+
gen_clear_frame(gen);
169176
assert(gen->gi_exc_state.exc_value == NULL);
170177
PyStackRef_CLEAR(gen->gi_iframe.f_executable);
171178
Py_CLEAR(gen->gi_name);
@@ -400,7 +407,7 @@ gen_close(PyObject *self, PyObject *args)
400407
// RESUME after YIELD_VALUE and exception depth is 1
401408
assert((oparg & RESUME_OPARG_LOCATION_MASK) != RESUME_AT_FUNC_START);
402409
gen->gi_frame_state = FRAME_COMPLETED;
403-
_PyFrame_ClearLocals(&gen->gi_iframe);
410+
gen_clear_frame(gen);
404411
Py_RETURN_NONE;
405412
}
406413
}

0 commit comments

Comments
 (0)
0