diff options
| author | Brandt Bucher <brandtbucher@microsoft.com> | 2022-12-06 06:01:38 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-12-06 14:01:38 +0000 |
| commit | b72014c783e5698beb18ee1249597e510b8bcb5a (patch) | |
| tree | 348e6b38b80f9ec04a12f94d99c1199ebaa75c1c /Lib/test/test_frame.py | |
| parent | 85d5a7e8ef472a4a64e5de883cf313c111a8ec77 (diff) | |
| download | cpython-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.py | 42 |
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): |
