(root)/
Python-3.12.0/
Lib/
test/
test_frame.py
       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()