|
2 | 2 | import re |
3 | 3 | import sys |
4 | 4 | import textwrap |
| 5 | +import threading |
5 | 6 | import types |
6 | 7 | import unittest |
7 | 8 | import weakref |
|
11 | 12 | _testcapi = None |
12 | 13 |
|
13 | 14 | from test import support |
| 15 | +from test.support import threading_helper |
14 | 16 | from test.support.script_helper import assert_python_ok |
15 | 17 |
|
16 | 18 |
|
@@ -329,6 +331,46 @@ def f(): |
329 | 331 | if old_enabled: |
330 | 332 | gc.enable() |
331 | 333 |
|
| 334 | + @support.cpython_only |
| 335 | + @threading_helper.requires_working_threading() |
| 336 | + def test_sneaky_frame_object_teardown(self): |
| 337 | + |
| 338 | + class SneakyDel: |
| 339 | + def __del__(self): |
| 340 | + """ |
| 341 | + Stash a reference to the entire stack for walking later. |
| 342 | +
|
| 343 | + It may look crazy, but you'd be surprised how common this is |
| 344 | + when using a test runner (like pytest). The typical recipe is: |
| 345 | + ResourceWarning + -Werror + a custom sys.unraisablehook. |
| 346 | + """ |
| 347 | + nonlocal sneaky_frame_object |
| 348 | + sneaky_frame_object = sys._getframe() |
| 349 | + |
| 350 | + class SneakyThread(threading.Thread): |
| 351 | + """ |
| 352 | + A separate thread isn't needed to make this code crash, but it does |
| 353 | + make crashes more consistent, since it means sneaky_frame_object is |
| 354 | + backed by freed memory after the thread completes! |
| 355 | + """ |
| 356 | + |
| 357 | + def run(self): |
| 358 | + """Run SneakyDel.__del__ as this frame is popped.""" |
| 359 | + ref = SneakyDel() |
| 360 | + |
| 361 | + sneaky_frame_object = None |
| 362 | + t = SneakyThread() |
| 363 | + t.start() |
| 364 | + t.join() |
| 365 | + # sneaky_frame_object can be anything, really, but it's crucial that |
| 366 | + # SneakyThread.run's frame isn't anywhere on the stack while it's being |
| 367 | + # torn down: |
| 368 | + self.assertIsNotNone(sneaky_frame_object) |
| 369 | + while sneaky_frame_object is not None: |
| 370 | + self.assertIsNot( |
| 371 | + sneaky_frame_object.f_code, SneakyThread.run.__code__ |
| 372 | + ) |
| 373 | + sneaky_frame_object = sneaky_frame_object.f_back |
332 | 374 |
|
333 | 375 | @unittest.skipIf(_testcapi is None, 'need _testcapi') |
334 | 376 | class TestCAPI(unittest.TestCase): |
|
0 commit comments