mirror of
https://github.com/python/cpython.git
synced 2025-08-30 21:48:47 +00:00
GH-97002: Prevent _PyInterpreterFrame
s from backing more than one PyFrameObject
(GH-97996)
This commit is contained in:
parent
cbf0afd8a1
commit
21a2d9ff55
3 changed files with 91 additions and 6 deletions
|
@ -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()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue