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