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