summaryrefslogtreecommitdiffstats
path: root/Lib/test/test_frame.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/test/test_frame.py')
-rw-r--r--Lib/test/test_frame.py64
1 files changed, 64 insertions, 0 deletions
diff --git a/Lib/test/test_frame.py b/Lib/test/test_frame.py
index 4b86a60..2222ad6 100644
--- a/Lib/test/test_frame.py
+++ b/Lib/test/test_frame.py
@@ -326,5 +326,69 @@ class TestIncompleteFrameAreInvisible(unittest.TestCase):
gc.enable()
+ @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()