diff options
author | Brandt Bucher <brandtbucher@microsoft.com> | 2022-10-06 23:20:01 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-10-06 23:20:01 (GMT) |
commit | 21a2d9ff550977f2668e2cf1cc15793bf27fa109 (patch) | |
tree | dd65296818dec2b91b48292b04d022929967a406 /Lib/test/test_frame.py | |
parent | cbf0afd8a1474d68310331af9218606959d4cc22 (diff) | |
download | cpython-21a2d9ff550977f2668e2cf1cc15793bf27fa109.zip cpython-21a2d9ff550977f2668e2cf1cc15793bf27fa109.tar.gz cpython-21a2d9ff550977f2668e2cf1cc15793bf27fa109.tar.bz2 |
GH-97002: Prevent `_PyInterpreterFrame`s from backing more than one `PyFrameObject` (GH-97996)
Diffstat (limited to 'Lib/test/test_frame.py')
-rw-r--r-- | Lib/test/test_frame.py | 65 |
1 files changed, 65 insertions, 0 deletions
diff --git a/Lib/test/test_frame.py b/Lib/test/test_frame.py index 5dda2fb..4b86a60 100644 --- a/Lib/test/test_frame.py +++ b/Lib/test/test_frame.py @@ -1,3 +1,4 @@ +import gc import re import sys import textwrap @@ -261,5 +262,69 @@ class TestIncompleteFrameAreInvisible(unittest.TestCase): """) assert_python_ok("-c", code) + @support.cpython_only + def test_sneaky_frame_object(self): + + def trace(frame, event, arg): + """ + Don't actually do anything, just force a frame object to be created. + """ + + def callback(phase, info): + """ + Yo dawg, I heard you like frames, so I'm allocating a frame while + you're allocating a frame, so you can have a frame while you have a + frame! + """ + nonlocal sneaky_frame_object + sneaky_frame_object = sys._getframe().f_back + # We're done here: + gc.callbacks.remove(callback) + + def f(): + while True: + yield + + old_threshold = gc.get_threshold() + old_callbacks = gc.callbacks[:] + old_enabled = gc.isenabled() + old_trace = sys.gettrace() + try: + # Stop the GC for a second while we set things up: + gc.disable() + # Create a paused generator: + g = f() + next(g) + # Move all objects to the oldest generation, and tell the GC to run + # on the *very next* allocation: + gc.collect() + gc.set_threshold(1, 0, 0) + # Okay, so here's the nightmare scenario: + # - We're tracing the resumption of a generator, which creates a new + # frame object. + # - The allocation of this frame object triggers a collection + # *before* the frame object is actually created. + # - During the collection, we request the exact same frame object. + # This test does it with a GC callback, but in real code it would + # likely be a trace function, weakref callback, or finalizer. + # - The collection finishes, and the original frame object is + # created. We now have two frame objects fighting over ownership + # of the same interpreter frame! + sys.settrace(trace) + gc.callbacks.append(callback) + sneaky_frame_object = None + gc.enable() + next(g) + # g.gi_frame should be the the frame object from the callback (the + # one that was *requested* second, but *created* first): + self.assertIs(g.gi_frame, sneaky_frame_object) + finally: + gc.set_threshold(*old_threshold) + gc.callbacks[:] = old_callbacks + sys.settrace(old_trace) + if old_enabled: + gc.enable() + + if __name__ == "__main__": unittest.main() |