test_frame.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. import gc
  2. import re
  3. import sys
  4. import textwrap
  5. import threading
  6. import types
  7. import unittest
  8. import weakref
  9. from test import support
  10. from test.support import threading_helper
  11. from test.support.script_helper import assert_python_ok
  12. class ClearTest(unittest.TestCase):
  13. """
  14. Tests for frame.clear().
  15. """
  16. def inner(self, x=5, **kwargs):
  17. 1/0
  18. def outer(self, **kwargs):
  19. try:
  20. self.inner(**kwargs)
  21. except ZeroDivisionError as e:
  22. exc = e
  23. return exc
  24. def clear_traceback_frames(self, tb):
  25. """
  26. Clear all frames in a traceback.
  27. """
  28. while tb is not None:
  29. tb.tb_frame.clear()
  30. tb = tb.tb_next
  31. def test_clear_locals(self):
  32. class C:
  33. pass
  34. c = C()
  35. wr = weakref.ref(c)
  36. exc = self.outer(c=c)
  37. del c
  38. support.gc_collect()
  39. # A reference to c is held through the frames
  40. self.assertIsNot(None, wr())
  41. self.clear_traceback_frames(exc.__traceback__)
  42. support.gc_collect()
  43. # The reference was released by .clear()
  44. self.assertIs(None, wr())
  45. def test_clear_does_not_clear_specials(self):
  46. class C:
  47. pass
  48. c = C()
  49. exc = self.outer(c=c)
  50. del c
  51. f = exc.__traceback__.tb_frame
  52. f.clear()
  53. self.assertIsNot(f.f_code, None)
  54. self.assertIsNot(f.f_locals, None)
  55. self.assertIsNot(f.f_builtins, None)
  56. self.assertIsNot(f.f_globals, None)
  57. def test_clear_generator(self):
  58. endly = False
  59. def g():
  60. nonlocal endly
  61. try:
  62. yield
  63. self.inner()
  64. finally:
  65. endly = True
  66. gen = g()
  67. next(gen)
  68. self.assertFalse(endly)
  69. # Clearing the frame closes the generator
  70. gen.gi_frame.clear()
  71. self.assertTrue(endly)
  72. def test_clear_executing(self):
  73. # Attempting to clear an executing frame is forbidden.
  74. try:
  75. 1/0
  76. except ZeroDivisionError as e:
  77. f = e.__traceback__.tb_frame
  78. with self.assertRaises(RuntimeError):
  79. f.clear()
  80. with self.assertRaises(RuntimeError):
  81. f.f_back.clear()
  82. def test_clear_executing_generator(self):
  83. # Attempting to clear an executing generator frame is forbidden.
  84. endly = False
  85. def g():
  86. nonlocal endly
  87. try:
  88. 1/0
  89. except ZeroDivisionError as e:
  90. f = e.__traceback__.tb_frame
  91. with self.assertRaises(RuntimeError):
  92. f.clear()
  93. with self.assertRaises(RuntimeError):
  94. f.f_back.clear()
  95. yield f
  96. finally:
  97. endly = True
  98. gen = g()
  99. f = next(gen)
  100. self.assertFalse(endly)
  101. # Clearing the frame closes the generator
  102. f.clear()
  103. self.assertTrue(endly)
  104. def test_lineno_with_tracing(self):
  105. def record_line():
  106. f = sys._getframe(1)
  107. lines.append(f.f_lineno-f.f_code.co_firstlineno)
  108. def test(trace):
  109. record_line()
  110. if trace:
  111. sys._getframe(0).f_trace = True
  112. record_line()
  113. record_line()
  114. expected_lines = [1, 4, 5]
  115. lines = []
  116. test(False)
  117. self.assertEqual(lines, expected_lines)
  118. lines = []
  119. test(True)
  120. self.assertEqual(lines, expected_lines)
  121. @support.cpython_only
  122. def test_clear_refcycles(self):
  123. # .clear() doesn't leave any refcycle behind
  124. with support.disable_gc():
  125. class C:
  126. pass
  127. c = C()
  128. wr = weakref.ref(c)
  129. exc = self.outer(c=c)
  130. del c
  131. self.assertIsNot(None, wr())
  132. self.clear_traceback_frames(exc.__traceback__)
  133. self.assertIs(None, wr())
  134. class FrameAttrsTest(unittest.TestCase):
  135. def make_frames(self):
  136. def outer():
  137. x = 5
  138. y = 6
  139. def inner():
  140. z = x + 2
  141. 1/0
  142. t = 9
  143. return inner()
  144. try:
  145. outer()
  146. except ZeroDivisionError as e:
  147. tb = e.__traceback__
  148. frames = []
  149. while tb:
  150. frames.append(tb.tb_frame)
  151. tb = tb.tb_next
  152. return frames
  153. def test_locals(self):
  154. f, outer, inner = self.make_frames()
  155. outer_locals = outer.f_locals
  156. self.assertIsInstance(outer_locals.pop('inner'), types.FunctionType)
  157. self.assertEqual(outer_locals, {'x': 5, 'y': 6})
  158. inner_locals = inner.f_locals
  159. self.assertEqual(inner_locals, {'x': 5, 'z': 7})
  160. def test_clear_locals(self):
  161. # Test f_locals after clear() (issue #21897)
  162. f, outer, inner = self.make_frames()
  163. outer.clear()
  164. inner.clear()
  165. self.assertEqual(outer.f_locals, {})
  166. self.assertEqual(inner.f_locals, {})
  167. def test_locals_clear_locals(self):
  168. # Test f_locals before and after clear() (to exercise caching)
  169. f, outer, inner = self.make_frames()
  170. outer.f_locals
  171. inner.f_locals
  172. outer.clear()
  173. inner.clear()
  174. self.assertEqual(outer.f_locals, {})
  175. self.assertEqual(inner.f_locals, {})
  176. def test_f_lineno_del_segfault(self):
  177. f, _, _ = self.make_frames()
  178. with self.assertRaises(AttributeError):
  179. del f.f_lineno
  180. class ReprTest(unittest.TestCase):
  181. """
  182. Tests for repr(frame).
  183. """
  184. def test_repr(self):
  185. def outer():
  186. x = 5
  187. y = 6
  188. def inner():
  189. z = x + 2
  190. 1/0
  191. t = 9
  192. return inner()
  193. offset = outer.__code__.co_firstlineno
  194. try:
  195. outer()
  196. except ZeroDivisionError as e:
  197. tb = e.__traceback__
  198. frames = []
  199. while tb:
  200. frames.append(tb.tb_frame)
  201. tb = tb.tb_next
  202. else:
  203. self.fail("should have raised")
  204. f_this, f_outer, f_inner = frames
  205. file_repr = re.escape(repr(__file__))
  206. self.assertRegex(repr(f_this),
  207. r"^<frame at 0x[0-9a-fA-F]+, file %s, line %d, code test_repr>$"
  208. % (file_repr, offset + 23))
  209. self.assertRegex(repr(f_outer),
  210. r"^<frame at 0x[0-9a-fA-F]+, file %s, line %d, code outer>$"
  211. % (file_repr, offset + 7))
  212. self.assertRegex(repr(f_inner),
  213. r"^<frame at 0x[0-9a-fA-F]+, file %s, line %d, code inner>$"
  214. % (file_repr, offset + 5))
  215. class TestIncompleteFrameAreInvisible(unittest.TestCase):
  216. def test_issue95818(self):
  217. # See GH-95818 for details
  218. code = textwrap.dedent(f"""
  219. import gc
  220. gc.set_threshold(1,1,1)
  221. class GCHello:
  222. def __del__(self):
  223. print("Destroyed from gc")
  224. def gen():
  225. yield
  226. fd = open({__file__!r})
  227. l = [fd, GCHello()]
  228. l.append(l)
  229. del fd
  230. del l
  231. gen()
  232. """)
  233. assert_python_ok("-c", code)
  234. @support.cpython_only
  235. def test_sneaky_frame_object(self):
  236. def trace(frame, event, arg):
  237. """
  238. Don't actually do anything, just force a frame object to be created.
  239. """
  240. def callback(phase, info):
  241. """
  242. Yo dawg, I heard you like frames, so I'm allocating a frame while
  243. you're allocating a frame, so you can have a frame while you have a
  244. frame!
  245. """
  246. nonlocal sneaky_frame_object
  247. sneaky_frame_object = sys._getframe().f_back
  248. # We're done here:
  249. gc.callbacks.remove(callback)
  250. def f():
  251. while True:
  252. yield
  253. old_threshold = gc.get_threshold()
  254. old_callbacks = gc.callbacks[:]
  255. old_enabled = gc.isenabled()
  256. old_trace = sys.gettrace()
  257. try:
  258. # Stop the GC for a second while we set things up:
  259. gc.disable()
  260. # Create a paused generator:
  261. g = f()
  262. next(g)
  263. # Move all objects to the oldest generation, and tell the GC to run
  264. # on the *very next* allocation:
  265. gc.collect()
  266. gc.set_threshold(1, 0, 0)
  267. # Okay, so here's the nightmare scenario:
  268. # - We're tracing the resumption of a generator, which creates a new
  269. # frame object.
  270. # - The allocation of this frame object triggers a collection
  271. # *before* the frame object is actually created.
  272. # - During the collection, we request the exact same frame object.
  273. # This test does it with a GC callback, but in real code it would
  274. # likely be a trace function, weakref callback, or finalizer.
  275. # - The collection finishes, and the original frame object is
  276. # created. We now have two frame objects fighting over ownership
  277. # of the same interpreter frame!
  278. sys.settrace(trace)
  279. gc.callbacks.append(callback)
  280. sneaky_frame_object = None
  281. gc.enable()
  282. next(g)
  283. # g.gi_frame should be the the frame object from the callback (the
  284. # one that was *requested* second, but *created* first):
  285. self.assertIs(g.gi_frame, sneaky_frame_object)
  286. finally:
  287. gc.set_threshold(*old_threshold)
  288. gc.callbacks[:] = old_callbacks
  289. sys.settrace(old_trace)
  290. if old_enabled:
  291. gc.enable()
  292. @support.cpython_only
  293. @threading_helper.requires_working_threading()
  294. def test_sneaky_frame_object_teardown(self):
  295. class SneakyDel:
  296. def __del__(self):
  297. """
  298. Stash a reference to the entire stack for walking later.
  299. It may look crazy, but you'd be surprised how common this is
  300. when using a test runner (like pytest). The typical recipe is:
  301. ResourceWarning + -Werror + a custom sys.unraisablehook.
  302. """
  303. nonlocal sneaky_frame_object
  304. sneaky_frame_object = sys._getframe()
  305. class SneakyThread(threading.Thread):
  306. """
  307. A separate thread isn't needed to make this code crash, but it does
  308. make crashes more consistent, since it means sneaky_frame_object is
  309. backed by freed memory after the thread completes!
  310. """
  311. def run(self):
  312. """Run SneakyDel.__del__ as this frame is popped."""
  313. ref = SneakyDel()
  314. sneaky_frame_object = None
  315. t = SneakyThread()
  316. t.start()
  317. t.join()
  318. # sneaky_frame_object can be anything, really, but it's crucial that
  319. # SneakyThread.run's frame isn't anywhere on the stack while it's being
  320. # torn down:
  321. self.assertIsNotNone(sneaky_frame_object)
  322. while sneaky_frame_object is not None:
  323. self.assertIsNot(
  324. sneaky_frame_object.f_code, SneakyThread.run.__code__
  325. )
  326. sneaky_frame_object = sneaky_frame_object.f_back
  327. if __name__ == "__main__":
  328. unittest.main()