17db96d56Sopenharmony_ciimport gc 27db96d56Sopenharmony_ciimport re 37db96d56Sopenharmony_ciimport sys 47db96d56Sopenharmony_ciimport textwrap 57db96d56Sopenharmony_ciimport threading 67db96d56Sopenharmony_ciimport types 77db96d56Sopenharmony_ciimport unittest 87db96d56Sopenharmony_ciimport weakref 97db96d56Sopenharmony_ci 107db96d56Sopenharmony_cifrom test import support 117db96d56Sopenharmony_cifrom test.support import threading_helper 127db96d56Sopenharmony_cifrom test.support.script_helper import assert_python_ok 137db96d56Sopenharmony_ci 147db96d56Sopenharmony_ci 157db96d56Sopenharmony_ciclass ClearTest(unittest.TestCase): 167db96d56Sopenharmony_ci """ 177db96d56Sopenharmony_ci Tests for frame.clear(). 187db96d56Sopenharmony_ci """ 197db96d56Sopenharmony_ci 207db96d56Sopenharmony_ci def inner(self, x=5, **kwargs): 217db96d56Sopenharmony_ci 1/0 227db96d56Sopenharmony_ci 237db96d56Sopenharmony_ci def outer(self, **kwargs): 247db96d56Sopenharmony_ci try: 257db96d56Sopenharmony_ci self.inner(**kwargs) 267db96d56Sopenharmony_ci except ZeroDivisionError as e: 277db96d56Sopenharmony_ci exc = e 287db96d56Sopenharmony_ci return exc 297db96d56Sopenharmony_ci 307db96d56Sopenharmony_ci def clear_traceback_frames(self, tb): 317db96d56Sopenharmony_ci """ 327db96d56Sopenharmony_ci Clear all frames in a traceback. 337db96d56Sopenharmony_ci """ 347db96d56Sopenharmony_ci while tb is not None: 357db96d56Sopenharmony_ci tb.tb_frame.clear() 367db96d56Sopenharmony_ci tb = tb.tb_next 377db96d56Sopenharmony_ci 387db96d56Sopenharmony_ci def test_clear_locals(self): 397db96d56Sopenharmony_ci class C: 407db96d56Sopenharmony_ci pass 417db96d56Sopenharmony_ci c = C() 427db96d56Sopenharmony_ci wr = weakref.ref(c) 437db96d56Sopenharmony_ci exc = self.outer(c=c) 447db96d56Sopenharmony_ci del c 457db96d56Sopenharmony_ci support.gc_collect() 467db96d56Sopenharmony_ci # A reference to c is held through the frames 477db96d56Sopenharmony_ci self.assertIsNot(None, wr()) 487db96d56Sopenharmony_ci self.clear_traceback_frames(exc.__traceback__) 497db96d56Sopenharmony_ci support.gc_collect() 507db96d56Sopenharmony_ci # The reference was released by .clear() 517db96d56Sopenharmony_ci self.assertIs(None, wr()) 527db96d56Sopenharmony_ci 537db96d56Sopenharmony_ci def test_clear_does_not_clear_specials(self): 547db96d56Sopenharmony_ci class C: 557db96d56Sopenharmony_ci pass 567db96d56Sopenharmony_ci c = C() 577db96d56Sopenharmony_ci exc = self.outer(c=c) 587db96d56Sopenharmony_ci del c 597db96d56Sopenharmony_ci f = exc.__traceback__.tb_frame 607db96d56Sopenharmony_ci f.clear() 617db96d56Sopenharmony_ci self.assertIsNot(f.f_code, None) 627db96d56Sopenharmony_ci self.assertIsNot(f.f_locals, None) 637db96d56Sopenharmony_ci self.assertIsNot(f.f_builtins, None) 647db96d56Sopenharmony_ci self.assertIsNot(f.f_globals, None) 657db96d56Sopenharmony_ci 667db96d56Sopenharmony_ci def test_clear_generator(self): 677db96d56Sopenharmony_ci endly = False 687db96d56Sopenharmony_ci def g(): 697db96d56Sopenharmony_ci nonlocal endly 707db96d56Sopenharmony_ci try: 717db96d56Sopenharmony_ci yield 727db96d56Sopenharmony_ci self.inner() 737db96d56Sopenharmony_ci finally: 747db96d56Sopenharmony_ci endly = True 757db96d56Sopenharmony_ci gen = g() 767db96d56Sopenharmony_ci next(gen) 777db96d56Sopenharmony_ci self.assertFalse(endly) 787db96d56Sopenharmony_ci # Clearing the frame closes the generator 797db96d56Sopenharmony_ci gen.gi_frame.clear() 807db96d56Sopenharmony_ci self.assertTrue(endly) 817db96d56Sopenharmony_ci 827db96d56Sopenharmony_ci def test_clear_executing(self): 837db96d56Sopenharmony_ci # Attempting to clear an executing frame is forbidden. 847db96d56Sopenharmony_ci try: 857db96d56Sopenharmony_ci 1/0 867db96d56Sopenharmony_ci except ZeroDivisionError as e: 877db96d56Sopenharmony_ci f = e.__traceback__.tb_frame 887db96d56Sopenharmony_ci with self.assertRaises(RuntimeError): 897db96d56Sopenharmony_ci f.clear() 907db96d56Sopenharmony_ci with self.assertRaises(RuntimeError): 917db96d56Sopenharmony_ci f.f_back.clear() 927db96d56Sopenharmony_ci 937db96d56Sopenharmony_ci def test_clear_executing_generator(self): 947db96d56Sopenharmony_ci # Attempting to clear an executing generator frame is forbidden. 957db96d56Sopenharmony_ci endly = False 967db96d56Sopenharmony_ci def g(): 977db96d56Sopenharmony_ci nonlocal endly 987db96d56Sopenharmony_ci try: 997db96d56Sopenharmony_ci 1/0 1007db96d56Sopenharmony_ci except ZeroDivisionError as e: 1017db96d56Sopenharmony_ci f = e.__traceback__.tb_frame 1027db96d56Sopenharmony_ci with self.assertRaises(RuntimeError): 1037db96d56Sopenharmony_ci f.clear() 1047db96d56Sopenharmony_ci with self.assertRaises(RuntimeError): 1057db96d56Sopenharmony_ci f.f_back.clear() 1067db96d56Sopenharmony_ci yield f 1077db96d56Sopenharmony_ci finally: 1087db96d56Sopenharmony_ci endly = True 1097db96d56Sopenharmony_ci gen = g() 1107db96d56Sopenharmony_ci f = next(gen) 1117db96d56Sopenharmony_ci self.assertFalse(endly) 1127db96d56Sopenharmony_ci # Clearing the frame closes the generator 1137db96d56Sopenharmony_ci f.clear() 1147db96d56Sopenharmony_ci self.assertTrue(endly) 1157db96d56Sopenharmony_ci 1167db96d56Sopenharmony_ci def test_lineno_with_tracing(self): 1177db96d56Sopenharmony_ci def record_line(): 1187db96d56Sopenharmony_ci f = sys._getframe(1) 1197db96d56Sopenharmony_ci lines.append(f.f_lineno-f.f_code.co_firstlineno) 1207db96d56Sopenharmony_ci 1217db96d56Sopenharmony_ci def test(trace): 1227db96d56Sopenharmony_ci record_line() 1237db96d56Sopenharmony_ci if trace: 1247db96d56Sopenharmony_ci sys._getframe(0).f_trace = True 1257db96d56Sopenharmony_ci record_line() 1267db96d56Sopenharmony_ci record_line() 1277db96d56Sopenharmony_ci 1287db96d56Sopenharmony_ci expected_lines = [1, 4, 5] 1297db96d56Sopenharmony_ci lines = [] 1307db96d56Sopenharmony_ci test(False) 1317db96d56Sopenharmony_ci self.assertEqual(lines, expected_lines) 1327db96d56Sopenharmony_ci lines = [] 1337db96d56Sopenharmony_ci test(True) 1347db96d56Sopenharmony_ci self.assertEqual(lines, expected_lines) 1357db96d56Sopenharmony_ci 1367db96d56Sopenharmony_ci @support.cpython_only 1377db96d56Sopenharmony_ci def test_clear_refcycles(self): 1387db96d56Sopenharmony_ci # .clear() doesn't leave any refcycle behind 1397db96d56Sopenharmony_ci with support.disable_gc(): 1407db96d56Sopenharmony_ci class C: 1417db96d56Sopenharmony_ci pass 1427db96d56Sopenharmony_ci c = C() 1437db96d56Sopenharmony_ci wr = weakref.ref(c) 1447db96d56Sopenharmony_ci exc = self.outer(c=c) 1457db96d56Sopenharmony_ci del c 1467db96d56Sopenharmony_ci self.assertIsNot(None, wr()) 1477db96d56Sopenharmony_ci self.clear_traceback_frames(exc.__traceback__) 1487db96d56Sopenharmony_ci self.assertIs(None, wr()) 1497db96d56Sopenharmony_ci 1507db96d56Sopenharmony_ci 1517db96d56Sopenharmony_ciclass FrameAttrsTest(unittest.TestCase): 1527db96d56Sopenharmony_ci 1537db96d56Sopenharmony_ci def make_frames(self): 1547db96d56Sopenharmony_ci def outer(): 1557db96d56Sopenharmony_ci x = 5 1567db96d56Sopenharmony_ci y = 6 1577db96d56Sopenharmony_ci def inner(): 1587db96d56Sopenharmony_ci z = x + 2 1597db96d56Sopenharmony_ci 1/0 1607db96d56Sopenharmony_ci t = 9 1617db96d56Sopenharmony_ci return inner() 1627db96d56Sopenharmony_ci try: 1637db96d56Sopenharmony_ci outer() 1647db96d56Sopenharmony_ci except ZeroDivisionError as e: 1657db96d56Sopenharmony_ci tb = e.__traceback__ 1667db96d56Sopenharmony_ci frames = [] 1677db96d56Sopenharmony_ci while tb: 1687db96d56Sopenharmony_ci frames.append(tb.tb_frame) 1697db96d56Sopenharmony_ci tb = tb.tb_next 1707db96d56Sopenharmony_ci return frames 1717db96d56Sopenharmony_ci 1727db96d56Sopenharmony_ci def test_locals(self): 1737db96d56Sopenharmony_ci f, outer, inner = self.make_frames() 1747db96d56Sopenharmony_ci outer_locals = outer.f_locals 1757db96d56Sopenharmony_ci self.assertIsInstance(outer_locals.pop('inner'), types.FunctionType) 1767db96d56Sopenharmony_ci self.assertEqual(outer_locals, {'x': 5, 'y': 6}) 1777db96d56Sopenharmony_ci inner_locals = inner.f_locals 1787db96d56Sopenharmony_ci self.assertEqual(inner_locals, {'x': 5, 'z': 7}) 1797db96d56Sopenharmony_ci 1807db96d56Sopenharmony_ci def test_clear_locals(self): 1817db96d56Sopenharmony_ci # Test f_locals after clear() (issue #21897) 1827db96d56Sopenharmony_ci f, outer, inner = self.make_frames() 1837db96d56Sopenharmony_ci outer.clear() 1847db96d56Sopenharmony_ci inner.clear() 1857db96d56Sopenharmony_ci self.assertEqual(outer.f_locals, {}) 1867db96d56Sopenharmony_ci self.assertEqual(inner.f_locals, {}) 1877db96d56Sopenharmony_ci 1887db96d56Sopenharmony_ci def test_locals_clear_locals(self): 1897db96d56Sopenharmony_ci # Test f_locals before and after clear() (to exercise caching) 1907db96d56Sopenharmony_ci f, outer, inner = self.make_frames() 1917db96d56Sopenharmony_ci outer.f_locals 1927db96d56Sopenharmony_ci inner.f_locals 1937db96d56Sopenharmony_ci outer.clear() 1947db96d56Sopenharmony_ci inner.clear() 1957db96d56Sopenharmony_ci self.assertEqual(outer.f_locals, {}) 1967db96d56Sopenharmony_ci self.assertEqual(inner.f_locals, {}) 1977db96d56Sopenharmony_ci 1987db96d56Sopenharmony_ci def test_f_lineno_del_segfault(self): 1997db96d56Sopenharmony_ci f, _, _ = self.make_frames() 2007db96d56Sopenharmony_ci with self.assertRaises(AttributeError): 2017db96d56Sopenharmony_ci del f.f_lineno 2027db96d56Sopenharmony_ci 2037db96d56Sopenharmony_ci 2047db96d56Sopenharmony_ciclass ReprTest(unittest.TestCase): 2057db96d56Sopenharmony_ci """ 2067db96d56Sopenharmony_ci Tests for repr(frame). 2077db96d56Sopenharmony_ci """ 2087db96d56Sopenharmony_ci 2097db96d56Sopenharmony_ci def test_repr(self): 2107db96d56Sopenharmony_ci def outer(): 2117db96d56Sopenharmony_ci x = 5 2127db96d56Sopenharmony_ci y = 6 2137db96d56Sopenharmony_ci def inner(): 2147db96d56Sopenharmony_ci z = x + 2 2157db96d56Sopenharmony_ci 1/0 2167db96d56Sopenharmony_ci t = 9 2177db96d56Sopenharmony_ci return inner() 2187db96d56Sopenharmony_ci 2197db96d56Sopenharmony_ci offset = outer.__code__.co_firstlineno 2207db96d56Sopenharmony_ci try: 2217db96d56Sopenharmony_ci outer() 2227db96d56Sopenharmony_ci except ZeroDivisionError as e: 2237db96d56Sopenharmony_ci tb = e.__traceback__ 2247db96d56Sopenharmony_ci frames = [] 2257db96d56Sopenharmony_ci while tb: 2267db96d56Sopenharmony_ci frames.append(tb.tb_frame) 2277db96d56Sopenharmony_ci tb = tb.tb_next 2287db96d56Sopenharmony_ci else: 2297db96d56Sopenharmony_ci self.fail("should have raised") 2307db96d56Sopenharmony_ci 2317db96d56Sopenharmony_ci f_this, f_outer, f_inner = frames 2327db96d56Sopenharmony_ci file_repr = re.escape(repr(__file__)) 2337db96d56Sopenharmony_ci self.assertRegex(repr(f_this), 2347db96d56Sopenharmony_ci r"^<frame at 0x[0-9a-fA-F]+, file %s, line %d, code test_repr>$" 2357db96d56Sopenharmony_ci % (file_repr, offset + 23)) 2367db96d56Sopenharmony_ci self.assertRegex(repr(f_outer), 2377db96d56Sopenharmony_ci r"^<frame at 0x[0-9a-fA-F]+, file %s, line %d, code outer>$" 2387db96d56Sopenharmony_ci % (file_repr, offset + 7)) 2397db96d56Sopenharmony_ci self.assertRegex(repr(f_inner), 2407db96d56Sopenharmony_ci r"^<frame at 0x[0-9a-fA-F]+, file %s, line %d, code inner>$" 2417db96d56Sopenharmony_ci % (file_repr, offset + 5)) 2427db96d56Sopenharmony_ci 2437db96d56Sopenharmony_ciclass TestIncompleteFrameAreInvisible(unittest.TestCase): 2447db96d56Sopenharmony_ci 2457db96d56Sopenharmony_ci def test_issue95818(self): 2467db96d56Sopenharmony_ci # See GH-95818 for details 2477db96d56Sopenharmony_ci code = textwrap.dedent(f""" 2487db96d56Sopenharmony_ci import gc 2497db96d56Sopenharmony_ci 2507db96d56Sopenharmony_ci gc.set_threshold(1,1,1) 2517db96d56Sopenharmony_ci class GCHello: 2527db96d56Sopenharmony_ci def __del__(self): 2537db96d56Sopenharmony_ci print("Destroyed from gc") 2547db96d56Sopenharmony_ci 2557db96d56Sopenharmony_ci def gen(): 2567db96d56Sopenharmony_ci yield 2577db96d56Sopenharmony_ci 2587db96d56Sopenharmony_ci fd = open({__file__!r}) 2597db96d56Sopenharmony_ci l = [fd, GCHello()] 2607db96d56Sopenharmony_ci l.append(l) 2617db96d56Sopenharmony_ci del fd 2627db96d56Sopenharmony_ci del l 2637db96d56Sopenharmony_ci gen() 2647db96d56Sopenharmony_ci """) 2657db96d56Sopenharmony_ci assert_python_ok("-c", code) 2667db96d56Sopenharmony_ci 2677db96d56Sopenharmony_ci @support.cpython_only 2687db96d56Sopenharmony_ci def test_sneaky_frame_object(self): 2697db96d56Sopenharmony_ci 2707db96d56Sopenharmony_ci def trace(frame, event, arg): 2717db96d56Sopenharmony_ci """ 2727db96d56Sopenharmony_ci Don't actually do anything, just force a frame object to be created. 2737db96d56Sopenharmony_ci """ 2747db96d56Sopenharmony_ci 2757db96d56Sopenharmony_ci def callback(phase, info): 2767db96d56Sopenharmony_ci """ 2777db96d56Sopenharmony_ci Yo dawg, I heard you like frames, so I'm allocating a frame while 2787db96d56Sopenharmony_ci you're allocating a frame, so you can have a frame while you have a 2797db96d56Sopenharmony_ci frame! 2807db96d56Sopenharmony_ci """ 2817db96d56Sopenharmony_ci nonlocal sneaky_frame_object 2827db96d56Sopenharmony_ci sneaky_frame_object = sys._getframe().f_back 2837db96d56Sopenharmony_ci # We're done here: 2847db96d56Sopenharmony_ci gc.callbacks.remove(callback) 2857db96d56Sopenharmony_ci 2867db96d56Sopenharmony_ci def f(): 2877db96d56Sopenharmony_ci while True: 2887db96d56Sopenharmony_ci yield 2897db96d56Sopenharmony_ci 2907db96d56Sopenharmony_ci old_threshold = gc.get_threshold() 2917db96d56Sopenharmony_ci old_callbacks = gc.callbacks[:] 2927db96d56Sopenharmony_ci old_enabled = gc.isenabled() 2937db96d56Sopenharmony_ci old_trace = sys.gettrace() 2947db96d56Sopenharmony_ci try: 2957db96d56Sopenharmony_ci # Stop the GC for a second while we set things up: 2967db96d56Sopenharmony_ci gc.disable() 2977db96d56Sopenharmony_ci # Create a paused generator: 2987db96d56Sopenharmony_ci g = f() 2997db96d56Sopenharmony_ci next(g) 3007db96d56Sopenharmony_ci # Move all objects to the oldest generation, and tell the GC to run 3017db96d56Sopenharmony_ci # on the *very next* allocation: 3027db96d56Sopenharmony_ci gc.collect() 3037db96d56Sopenharmony_ci gc.set_threshold(1, 0, 0) 3047db96d56Sopenharmony_ci # Okay, so here's the nightmare scenario: 3057db96d56Sopenharmony_ci # - We're tracing the resumption of a generator, which creates a new 3067db96d56Sopenharmony_ci # frame object. 3077db96d56Sopenharmony_ci # - The allocation of this frame object triggers a collection 3087db96d56Sopenharmony_ci # *before* the frame object is actually created. 3097db96d56Sopenharmony_ci # - During the collection, we request the exact same frame object. 3107db96d56Sopenharmony_ci # This test does it with a GC callback, but in real code it would 3117db96d56Sopenharmony_ci # likely be a trace function, weakref callback, or finalizer. 3127db96d56Sopenharmony_ci # - The collection finishes, and the original frame object is 3137db96d56Sopenharmony_ci # created. We now have two frame objects fighting over ownership 3147db96d56Sopenharmony_ci # of the same interpreter frame! 3157db96d56Sopenharmony_ci sys.settrace(trace) 3167db96d56Sopenharmony_ci gc.callbacks.append(callback) 3177db96d56Sopenharmony_ci sneaky_frame_object = None 3187db96d56Sopenharmony_ci gc.enable() 3197db96d56Sopenharmony_ci next(g) 3207db96d56Sopenharmony_ci # g.gi_frame should be the the frame object from the callback (the 3217db96d56Sopenharmony_ci # one that was *requested* second, but *created* first): 3227db96d56Sopenharmony_ci self.assertIs(g.gi_frame, sneaky_frame_object) 3237db96d56Sopenharmony_ci finally: 3247db96d56Sopenharmony_ci gc.set_threshold(*old_threshold) 3257db96d56Sopenharmony_ci gc.callbacks[:] = old_callbacks 3267db96d56Sopenharmony_ci sys.settrace(old_trace) 3277db96d56Sopenharmony_ci if old_enabled: 3287db96d56Sopenharmony_ci gc.enable() 3297db96d56Sopenharmony_ci 3307db96d56Sopenharmony_ci @support.cpython_only 3317db96d56Sopenharmony_ci @threading_helper.requires_working_threading() 3327db96d56Sopenharmony_ci def test_sneaky_frame_object_teardown(self): 3337db96d56Sopenharmony_ci 3347db96d56Sopenharmony_ci class SneakyDel: 3357db96d56Sopenharmony_ci def __del__(self): 3367db96d56Sopenharmony_ci """ 3377db96d56Sopenharmony_ci Stash a reference to the entire stack for walking later. 3387db96d56Sopenharmony_ci 3397db96d56Sopenharmony_ci It may look crazy, but you'd be surprised how common this is 3407db96d56Sopenharmony_ci when using a test runner (like pytest). The typical recipe is: 3417db96d56Sopenharmony_ci ResourceWarning + -Werror + a custom sys.unraisablehook. 3427db96d56Sopenharmony_ci """ 3437db96d56Sopenharmony_ci nonlocal sneaky_frame_object 3447db96d56Sopenharmony_ci sneaky_frame_object = sys._getframe() 3457db96d56Sopenharmony_ci 3467db96d56Sopenharmony_ci class SneakyThread(threading.Thread): 3477db96d56Sopenharmony_ci """ 3487db96d56Sopenharmony_ci A separate thread isn't needed to make this code crash, but it does 3497db96d56Sopenharmony_ci make crashes more consistent, since it means sneaky_frame_object is 3507db96d56Sopenharmony_ci backed by freed memory after the thread completes! 3517db96d56Sopenharmony_ci """ 3527db96d56Sopenharmony_ci 3537db96d56Sopenharmony_ci def run(self): 3547db96d56Sopenharmony_ci """Run SneakyDel.__del__ as this frame is popped.""" 3557db96d56Sopenharmony_ci ref = SneakyDel() 3567db96d56Sopenharmony_ci 3577db96d56Sopenharmony_ci sneaky_frame_object = None 3587db96d56Sopenharmony_ci t = SneakyThread() 3597db96d56Sopenharmony_ci t.start() 3607db96d56Sopenharmony_ci t.join() 3617db96d56Sopenharmony_ci # sneaky_frame_object can be anything, really, but it's crucial that 3627db96d56Sopenharmony_ci # SneakyThread.run's frame isn't anywhere on the stack while it's being 3637db96d56Sopenharmony_ci # torn down: 3647db96d56Sopenharmony_ci self.assertIsNotNone(sneaky_frame_object) 3657db96d56Sopenharmony_ci while sneaky_frame_object is not None: 3667db96d56Sopenharmony_ci self.assertIsNot( 3677db96d56Sopenharmony_ci sneaky_frame_object.f_code, SneakyThread.run.__code__ 3687db96d56Sopenharmony_ci ) 3697db96d56Sopenharmony_ci sneaky_frame_object = sneaky_frame_object.f_back 3707db96d56Sopenharmony_ci 3717db96d56Sopenharmony_ciif __name__ == "__main__": 3727db96d56Sopenharmony_ci unittest.main() 373