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>
This commit is contained in:
Mikhail Efimov 2025-01-22 15:50:01 +03:00 committed by GitHub
parent 24c84d816f
commit 8e20e42cc6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 101 additions and 9 deletions

View file

@ -652,6 +652,89 @@ class GeneratorCloseTest(unittest.TestCase):
self.assertIsNone(f_wr())
# See https://github.com/python/cpython/issues/125723
class GeneratorDeallocTest(unittest.TestCase):
def test_frame_outlives_generator(self):
def g1():
a = 42
yield sys._getframe()
def g2():
a = 42
yield
def g3(obj):
a = 42
obj.frame = sys._getframe()
yield
class ObjectWithFrame():
def __init__(self):
self.frame = None
def get_frame(index):
if index == 1:
return next(g1())
elif index == 2:
gen = g2()
next(gen)
return gen.gi_frame
elif index == 3:
obj = ObjectWithFrame()
next(g3(obj))
return obj.frame
else:
return None
for index in (1, 2, 3):
with self.subTest(index=index):
frame = get_frame(index)
frame_locals = frame.f_locals
self.assertIn('a', frame_locals)
self.assertEqual(frame_locals['a'], 42)
def test_frame_locals_outlive_generator(self):
frame_locals1 = None
def g1():
nonlocal frame_locals1
frame_locals1 = sys._getframe().f_locals
a = 42
yield
def g2():
a = 42
yield sys._getframe().f_locals
def get_frame_locals(index):
if index == 1:
nonlocal frame_locals1
next(g1())
return frame_locals1
if index == 2:
return next(g2())
else:
return None
for index in (1, 2):
with self.subTest(index=index):
frame_locals = get_frame_locals(index)
self.assertIn('a', frame_locals)
self.assertEqual(frame_locals['a'], 42)
def test_frame_locals_outlive_generator_with_exec(self):
def g():
a = 42
yield locals(), sys._getframe().f_locals
locals_ = {'g': g}
for i in range(10):
exec("snapshot, live_locals = next(g())", locals=locals_)
for l in (locals_['snapshot'], locals_['live_locals']):
self.assertIn('a', l)
self.assertEqual(l['a'], 42)
class GeneratorThrowTest(unittest.TestCase):
def test_exception_context_with_yield(self):