summaryrefslogtreecommitdiff
path: root/Lib/test/test_frame.py
diff options
context:
space:
mode:
authorBrandt Bucher <brandtbucher@microsoft.com>2022-12-06 06:01:38 -0800
committerGitHub <noreply@github.com>2022-12-06 14:01:38 +0000
commitb72014c783e5698beb18ee1249597e510b8bcb5a (patch)
tree348e6b38b80f9ec04a12f94d99c1199ebaa75c1c /Lib/test/test_frame.py
parent85d5a7e8ef472a4a64e5de883cf313c111a8ec77 (diff)
downloadcpython-git-b72014c783e5698beb18ee1249597e510b8bcb5a.tar.gz
GH-99729: Unlink frames before clearing them (GH-100030)
Diffstat (limited to 'Lib/test/test_frame.py')
-rw-r--r--Lib/test/test_frame.py42
1 files changed, 42 insertions, 0 deletions
diff --git a/Lib/test/test_frame.py b/Lib/test/test_frame.py
index a7db22007d..ed413f105e 100644
--- a/Lib/test/test_frame.py
+++ b/Lib/test/test_frame.py
@@ -2,6 +2,7 @@ import gc
import re
import sys
import textwrap
+import threading
import types
import unittest
import weakref
@@ -11,6 +12,7 @@ except ImportError:
_testcapi = None
from test import support
+from test.support import threading_helper
from test.support.script_helper import assert_python_ok
@@ -329,6 +331,46 @@ class TestIncompleteFrameAreInvisible(unittest.TestCase):
if old_enabled:
gc.enable()
+ @support.cpython_only
+ @threading_helper.requires_working_threading()
+ def test_sneaky_frame_object_teardown(self):
+
+ class SneakyDel:
+ def __del__(self):
+ """
+ Stash a reference to the entire stack for walking later.
+
+ It may look crazy, but you'd be surprised how common this is
+ when using a test runner (like pytest). The typical recipe is:
+ ResourceWarning + -Werror + a custom sys.unraisablehook.
+ """
+ nonlocal sneaky_frame_object
+ sneaky_frame_object = sys._getframe()
+
+ class SneakyThread(threading.Thread):
+ """
+ A separate thread isn't needed to make this code crash, but it does
+ make crashes more consistent, since it means sneaky_frame_object is
+ backed by freed memory after the thread completes!
+ """
+
+ def run(self):
+ """Run SneakyDel.__del__ as this frame is popped."""
+ ref = SneakyDel()
+
+ sneaky_frame_object = None
+ t = SneakyThread()
+ t.start()
+ t.join()
+ # sneaky_frame_object can be anything, really, but it's crucial that
+ # SneakyThread.run's frame isn't anywhere on the stack while it's being
+ # torn down:
+ self.assertIsNotNone(sneaky_frame_object)
+ while sneaky_frame_object is not None:
+ self.assertIsNot(
+ sneaky_frame_object.f_code, SneakyThread.run.__code__
+ )
+ sneaky_frame_object = sneaky_frame_object.f_back
@unittest.skipIf(_testcapi is None, 'need _testcapi')
class TestCAPI(unittest.TestCase):