(root)/
Python-3.11.7/
Lib/
test/
test_gdb/
test_pretty_print.py
       1  import re
       2  import sys
       3  from test import support
       4  
       5  from .util import (
       6      BREAKPOINT_FN, GDB_VERSION,
       7      run_gdb, setup_module, DebuggerTests)
       8  
       9  
      10  def setUpModule():
      11      setup_module()
      12  
      13  
      14  class ESC[4;38;5;81mPrettyPrintTests(ESC[4;38;5;149mDebuggerTests):
      15      def get_gdb_repr(self, source,
      16                       cmds_after_breakpoint=None,
      17                       import_site=False):
      18          # Given an input python source representation of data,
      19          # run "python -c'id(DATA)'" under gdb with a breakpoint on
      20          # builtin_id and scrape out gdb's representation of the "op"
      21          # parameter, and verify that the gdb displays the same string
      22          #
      23          # Verify that the gdb displays the expected string
      24          #
      25          # For a nested structure, the first time we hit the breakpoint will
      26          # give us the top-level structure
      27  
      28          # NOTE: avoid decoding too much of the traceback as some
      29          # undecodable characters may lurk there in optimized mode
      30          # (issue #19743).
      31          cmds_after_breakpoint = cmds_after_breakpoint or ["backtrace 1"]
      32          gdb_output = self.get_stack_trace(source, breakpoint=BREAKPOINT_FN,
      33                                            cmds_after_breakpoint=cmds_after_breakpoint,
      34                                            import_site=import_site)
      35          # gdb can insert additional '\n' and space characters in various places
      36          # in its output, depending on the width of the terminal it's connected
      37          # to (using its "wrap_here" function)
      38          m = re.search(
      39              # Match '#0 builtin_id(self=..., v=...)'
      40              r'#0\s+builtin_id\s+\(self\=.*,\s+v=\s*(.*?)?\)'
      41              # Match ' at Python/bltinmodule.c'.
      42              # bpo-38239: builtin_id() is defined in Python/bltinmodule.c,
      43              # but accept any "Directory\file.c" to support Link Time
      44              # Optimization (LTO).
      45              r'\s+at\s+\S*[A-Za-z]+/[A-Za-z0-9_-]+\.c',
      46              gdb_output, re.DOTALL)
      47          if not m:
      48              self.fail('Unexpected gdb output: %r\n%s' % (gdb_output, gdb_output))
      49          return m.group(1), gdb_output
      50  
      51      def test_getting_backtrace(self):
      52          gdb_output = self.get_stack_trace('id(42)')
      53          self.assertTrue(BREAKPOINT_FN in gdb_output)
      54  
      55      def assertGdbRepr(self, val, exp_repr=None):
      56          # Ensure that gdb's rendering of the value in a debugged process
      57          # matches repr(value) in this process:
      58          gdb_repr, gdb_output = self.get_gdb_repr('id(' + ascii(val) + ')')
      59          if not exp_repr:
      60              exp_repr = repr(val)
      61          self.assertEqual(gdb_repr, exp_repr,
      62                           ('%r did not equal expected %r; full output was:\n%s'
      63                            % (gdb_repr, exp_repr, gdb_output)))
      64  
      65      @support.requires_resource('cpu')
      66      def test_int(self):
      67          'Verify the pretty-printing of various int values'
      68          self.assertGdbRepr(42)
      69          self.assertGdbRepr(0)
      70          self.assertGdbRepr(-7)
      71          self.assertGdbRepr(1000000000000)
      72          self.assertGdbRepr(-1000000000000000)
      73  
      74      def test_singletons(self):
      75          'Verify the pretty-printing of True, False and None'
      76          self.assertGdbRepr(True)
      77          self.assertGdbRepr(False)
      78          self.assertGdbRepr(None)
      79  
      80      def test_dicts(self):
      81          'Verify the pretty-printing of dictionaries'
      82          self.assertGdbRepr({})
      83          self.assertGdbRepr({'foo': 'bar'}, "{'foo': 'bar'}")
      84          # Python preserves insertion order since 3.6
      85          self.assertGdbRepr({'foo': 'bar', 'douglas': 42}, "{'foo': 'bar', 'douglas': 42}")
      86  
      87      def test_lists(self):
      88          'Verify the pretty-printing of lists'
      89          self.assertGdbRepr([])
      90          self.assertGdbRepr(list(range(5)))
      91  
      92      @support.requires_resource('cpu')
      93      def test_bytes(self):
      94          'Verify the pretty-printing of bytes'
      95          self.assertGdbRepr(b'')
      96          self.assertGdbRepr(b'And now for something hopefully the same')
      97          self.assertGdbRepr(b'string with embedded NUL here \0 and then some more text')
      98          self.assertGdbRepr(b'this is a tab:\t'
      99                             b' this is a slash-N:\n'
     100                             b' this is a slash-R:\r'
     101                             )
     102  
     103          self.assertGdbRepr(b'this is byte 255:\xff and byte 128:\x80')
     104  
     105          self.assertGdbRepr(bytes([b for b in range(255)]))
     106  
     107      @support.requires_resource('cpu')
     108      def test_strings(self):
     109          'Verify the pretty-printing of unicode strings'
     110          # We cannot simply call locale.getpreferredencoding() here,
     111          # as GDB might have been linked against a different version
     112          # of Python with a different encoding and coercion policy
     113          # with respect to PEP 538 and PEP 540.
     114          stdout, stderr = run_gdb(
     115              '--eval-command',
     116              'python import locale; print(locale.getpreferredencoding())')
     117  
     118          encoding = stdout
     119          if stderr or not encoding:
     120              raise RuntimeError(
     121                  f'unable to determine the Python locale preferred encoding '
     122                  f'of embedded Python in GDB\n'
     123                  f'stdout={stdout!r}\n'
     124                  f'stderr={stderr!r}')
     125  
     126          def check_repr(text):
     127              try:
     128                  text.encode(encoding)
     129              except UnicodeEncodeError:
     130                  self.assertGdbRepr(text, ascii(text))
     131              else:
     132                  self.assertGdbRepr(text)
     133  
     134          self.assertGdbRepr('')
     135          self.assertGdbRepr('And now for something hopefully the same')
     136          self.assertGdbRepr('string with embedded NUL here \0 and then some more text')
     137  
     138          # Test printing a single character:
     139          #    U+2620 SKULL AND CROSSBONES
     140          check_repr('\u2620')
     141  
     142          # Test printing a Japanese unicode string
     143          # (I believe this reads "mojibake", using 3 characters from the CJK
     144          # Unified Ideographs area, followed by U+3051 HIRAGANA LETTER KE)
     145          check_repr('\u6587\u5b57\u5316\u3051')
     146  
     147          # Test a character outside the BMP:
     148          #    U+1D121 MUSICAL SYMBOL C CLEF
     149          # This is:
     150          # UTF-8: 0xF0 0x9D 0x84 0xA1
     151          # UTF-16: 0xD834 0xDD21
     152          check_repr(chr(0x1D121))
     153  
     154      def test_tuples(self):
     155          'Verify the pretty-printing of tuples'
     156          self.assertGdbRepr(tuple(), '()')
     157          self.assertGdbRepr((1,), '(1,)')
     158          self.assertGdbRepr(('foo', 'bar', 'baz'))
     159  
     160      @support.requires_resource('cpu')
     161      def test_sets(self):
     162          'Verify the pretty-printing of sets'
     163          if GDB_VERSION < (7, 3):
     164              self.skipTest("pretty-printing of sets needs gdb 7.3 or later")
     165          self.assertGdbRepr(set(), "set()")
     166          self.assertGdbRepr(set(['a']), "{'a'}")
     167          # PYTHONHASHSEED is need to get the exact frozenset item order
     168          if not sys.flags.ignore_environment:
     169              self.assertGdbRepr(set(['a', 'b']), "{'a', 'b'}")
     170              self.assertGdbRepr(set([4, 5, 6]), "{4, 5, 6}")
     171  
     172          # Ensure that we handle sets containing the "dummy" key value,
     173          # which happens on deletion:
     174          gdb_repr, gdb_output = self.get_gdb_repr('''s = set(['a','b'])
     175  s.remove('a')
     176  id(s)''')
     177          self.assertEqual(gdb_repr, "{'b'}")
     178  
     179      @support.requires_resource('cpu')
     180      def test_frozensets(self):
     181          'Verify the pretty-printing of frozensets'
     182          if GDB_VERSION < (7, 3):
     183              self.skipTest("pretty-printing of frozensets needs gdb 7.3 or later")
     184          self.assertGdbRepr(frozenset(), "frozenset()")
     185          self.assertGdbRepr(frozenset(['a']), "frozenset({'a'})")
     186          # PYTHONHASHSEED is need to get the exact frozenset item order
     187          if not sys.flags.ignore_environment:
     188              self.assertGdbRepr(frozenset(['a', 'b']), "frozenset({'a', 'b'})")
     189              self.assertGdbRepr(frozenset([4, 5, 6]), "frozenset({4, 5, 6})")
     190  
     191      def test_exceptions(self):
     192          # Test a RuntimeError
     193          gdb_repr, gdb_output = self.get_gdb_repr('''
     194  try:
     195      raise RuntimeError("I am an error")
     196  except RuntimeError as e:
     197      id(e)
     198  ''')
     199          self.assertEqual(gdb_repr,
     200                           "RuntimeError('I am an error',)")
     201  
     202  
     203          # Test division by zero:
     204          gdb_repr, gdb_output = self.get_gdb_repr('''
     205  try:
     206      a = 1 / 0
     207  except ZeroDivisionError as e:
     208      id(e)
     209  ''')
     210          self.assertEqual(gdb_repr,
     211                           "ZeroDivisionError('division by zero',)")
     212  
     213      def test_modern_class(self):
     214          'Verify the pretty-printing of new-style class instances'
     215          gdb_repr, gdb_output = self.get_gdb_repr('''
     216  class Foo:
     217      pass
     218  foo = Foo()
     219  foo.an_int = 42
     220  id(foo)''')
     221          m = re.match(r'<Foo\(an_int=42\) at remote 0x-?[0-9a-f]+>', gdb_repr)
     222          self.assertTrue(m,
     223                          msg='Unexpected new-style class rendering %r' % gdb_repr)
     224  
     225      def test_subclassing_list(self):
     226          'Verify the pretty-printing of an instance of a list subclass'
     227          gdb_repr, gdb_output = self.get_gdb_repr('''
     228  class Foo(list):
     229      pass
     230  foo = Foo()
     231  foo += [1, 2, 3]
     232  foo.an_int = 42
     233  id(foo)''')
     234          m = re.match(r'<Foo\(an_int=42\) at remote 0x-?[0-9a-f]+>', gdb_repr)
     235  
     236          self.assertTrue(m,
     237                          msg='Unexpected new-style class rendering %r' % gdb_repr)
     238  
     239      def test_subclassing_tuple(self):
     240          'Verify the pretty-printing of an instance of a tuple subclass'
     241          # This should exercise the negative tp_dictoffset code in the
     242          # new-style class support
     243          gdb_repr, gdb_output = self.get_gdb_repr('''
     244  class Foo(tuple):
     245      pass
     246  foo = Foo((1, 2, 3))
     247  foo.an_int = 42
     248  id(foo)''')
     249          m = re.match(r'<Foo\(an_int=42\) at remote 0x-?[0-9a-f]+>', gdb_repr)
     250  
     251          self.assertTrue(m,
     252                          msg='Unexpected new-style class rendering %r' % gdb_repr)
     253  
     254      def assertSane(self, source, corruption, exprepr=None):
     255          '''Run Python under gdb, corrupting variables in the inferior process
     256          immediately before taking a backtrace.
     257  
     258          Verify that the variable's representation is the expected failsafe
     259          representation'''
     260          if corruption:
     261              cmds_after_breakpoint=[corruption, 'backtrace']
     262          else:
     263              cmds_after_breakpoint=['backtrace']
     264  
     265          gdb_repr, gdb_output = \
     266              self.get_gdb_repr(source,
     267                                cmds_after_breakpoint=cmds_after_breakpoint)
     268          if exprepr:
     269              if gdb_repr == exprepr:
     270                  # gdb managed to print the value in spite of the corruption;
     271                  # this is good (see http://bugs.python.org/issue8330)
     272                  return
     273  
     274          # Match anything for the type name; 0xDEADBEEF could point to
     275          # something arbitrary (see  http://bugs.python.org/issue8330)
     276          pattern = '<.* at remote 0x-?[0-9a-f]+>'
     277  
     278          m = re.match(pattern, gdb_repr)
     279          if not m:
     280              self.fail('Unexpected gdb representation: %r\n%s' % \
     281                            (gdb_repr, gdb_output))
     282  
     283      def test_NULL_ptr(self):
     284          'Ensure that a NULL PyObject* is handled gracefully'
     285          gdb_repr, gdb_output = (
     286              self.get_gdb_repr('id(42)',
     287                                cmds_after_breakpoint=['set variable v=0',
     288                                                       'backtrace'])
     289              )
     290  
     291          self.assertEqual(gdb_repr, '0x0')
     292  
     293      def test_NULL_ob_type(self):
     294          'Ensure that a PyObject* with NULL ob_type is handled gracefully'
     295          self.assertSane('id(42)',
     296                          'set v->ob_type=0')
     297  
     298      def test_corrupt_ob_type(self):
     299          'Ensure that a PyObject* with a corrupt ob_type is handled gracefully'
     300          self.assertSane('id(42)',
     301                          'set v->ob_type=0xDEADBEEF',
     302                          exprepr='42')
     303  
     304      def test_corrupt_tp_flags(self):
     305          'Ensure that a PyObject* with a type with corrupt tp_flags is handled'
     306          self.assertSane('id(42)',
     307                          'set v->ob_type->tp_flags=0x0',
     308                          exprepr='42')
     309  
     310      def test_corrupt_tp_name(self):
     311          'Ensure that a PyObject* with a type with corrupt tp_name is handled'
     312          self.assertSane('id(42)',
     313                          'set v->ob_type->tp_name=0xDEADBEEF',
     314                          exprepr='42')
     315  
     316      def test_builtins_help(self):
     317          'Ensure that the new-style class _Helper in site.py can be handled'
     318  
     319          if sys.flags.no_site:
     320              self.skipTest("need site module, but -S option was used")
     321  
     322          # (this was the issue causing tracebacks in
     323          #  http://bugs.python.org/issue8032#msg100537 )
     324          gdb_repr, gdb_output = self.get_gdb_repr('id(__builtins__.help)', import_site=True)
     325  
     326          m = re.match(r'<_Helper\(\) at remote 0x-?[0-9a-f]+>', gdb_repr)
     327          self.assertTrue(m,
     328                          msg='Unexpected rendering %r' % gdb_repr)
     329  
     330      def test_selfreferential_list(self):
     331          '''Ensure that a reference loop involving a list doesn't lead proxyval
     332          into an infinite loop:'''
     333          gdb_repr, gdb_output = \
     334              self.get_gdb_repr("a = [3, 4, 5] ; a.append(a) ; id(a)")
     335          self.assertEqual(gdb_repr, '[3, 4, 5, [...]]')
     336  
     337          gdb_repr, gdb_output = \
     338              self.get_gdb_repr("a = [3, 4, 5] ; b = [a] ; a.append(b) ; id(a)")
     339          self.assertEqual(gdb_repr, '[3, 4, 5, [[...]]]')
     340  
     341      def test_selfreferential_dict(self):
     342          '''Ensure that a reference loop involving a dict doesn't lead proxyval
     343          into an infinite loop:'''
     344          gdb_repr, gdb_output = \
     345              self.get_gdb_repr("a = {} ; b = {'bar':a} ; a['foo'] = b ; id(a)")
     346  
     347          self.assertEqual(gdb_repr, "{'foo': {'bar': {...}}}")
     348  
     349      def test_selfreferential_old_style_instance(self):
     350          gdb_repr, gdb_output = \
     351              self.get_gdb_repr('''
     352  class Foo:
     353      pass
     354  foo = Foo()
     355  foo.an_attr = foo
     356  id(foo)''')
     357          self.assertTrue(re.match(r'<Foo\(an_attr=<\.\.\.>\) at remote 0x-?[0-9a-f]+>',
     358                                   gdb_repr),
     359                          'Unexpected gdb representation: %r\n%s' % \
     360                              (gdb_repr, gdb_output))
     361  
     362      def test_selfreferential_new_style_instance(self):
     363          gdb_repr, gdb_output = \
     364              self.get_gdb_repr('''
     365  class Foo(object):
     366      pass
     367  foo = Foo()
     368  foo.an_attr = foo
     369  id(foo)''')
     370          self.assertTrue(re.match(r'<Foo\(an_attr=<\.\.\.>\) at remote 0x-?[0-9a-f]+>',
     371                                   gdb_repr),
     372                          'Unexpected gdb representation: %r\n%s' % \
     373                              (gdb_repr, gdb_output))
     374  
     375          gdb_repr, gdb_output = \
     376              self.get_gdb_repr('''
     377  class Foo(object):
     378      pass
     379  a = Foo()
     380  b = Foo()
     381  a.an_attr = b
     382  b.an_attr = a
     383  id(a)''')
     384          self.assertTrue(re.match(r'<Foo\(an_attr=<Foo\(an_attr=<\.\.\.>\) at remote 0x-?[0-9a-f]+>\) at remote 0x-?[0-9a-f]+>',
     385                                   gdb_repr),
     386                          'Unexpected gdb representation: %r\n%s' % \
     387                              (gdb_repr, gdb_output))
     388  
     389      def test_truncation(self):
     390          'Verify that very long output is truncated'
     391          gdb_repr, gdb_output = self.get_gdb_repr('id(list(range(1000)))')
     392          self.assertEqual(gdb_repr,
     393                           "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, "
     394                           "14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, "
     395                           "27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, "
     396                           "40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, "
     397                           "53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, "
     398                           "66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, "
     399                           "79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, "
     400                           "92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, "
     401                           "104, 105, 106, 107, 108, 109, 110, 111, 112, 113, "
     402                           "114, 115, 116, 117, 118, 119, 120, 121, 122, 123, "
     403                           "124, 125, 126, 127, 128, 129, 130, 131, 132, 133, "
     404                           "134, 135, 136, 137, 138, 139, 140, 141, 142, 143, "
     405                           "144, 145, 146, 147, 148, 149, 150, 151, 152, 153, "
     406                           "154, 155, 156, 157, 158, 159, 160, 161, 162, 163, "
     407                           "164, 165, 166, 167, 168, 169, 170, 171, 172, 173, "
     408                           "174, 175, 176, 177, 178, 179, 180, 181, 182, 183, "
     409                           "184, 185, 186, 187, 188, 189, 190, 191, 192, 193, "
     410                           "194, 195, 196, 197, 198, 199, 200, 201, 202, 203, "
     411                           "204, 205, 206, 207, 208, 209, 210, 211, 212, 213, "
     412                           "214, 215, 216, 217, 218, 219, 220, 221, 222, 223, "
     413                           "224, 225, 226...(truncated)")
     414          self.assertEqual(len(gdb_repr),
     415                           1024 + len('...(truncated)'))
     416  
     417      def test_builtin_method(self):
     418          gdb_repr, gdb_output = self.get_gdb_repr('import sys; id(sys.stdout.readlines)')
     419          self.assertTrue(re.match(r'<built-in method readlines of _io.TextIOWrapper object at remote 0x-?[0-9a-f]+>',
     420                                   gdb_repr),
     421                          'Unexpected gdb representation: %r\n%s' % \
     422                              (gdb_repr, gdb_output))
     423  
     424      def test_frames(self):
     425          gdb_output = self.get_stack_trace('''
     426  import sys
     427  def foo(a, b, c):
     428      return sys._getframe(0)
     429  
     430  f = foo(3, 4, 5)
     431  id(f)''',
     432                                            breakpoint='builtin_id',
     433                                            cmds_after_breakpoint=['print (PyFrameObject*)v']
     434                                            )
     435          self.assertTrue(re.match(r'.*\s+\$1 =\s+Frame 0x-?[0-9a-f]+, for file <string>, line 4, in foo \(a=3.*',
     436                                   gdb_output,
     437                                   re.DOTALL),
     438                          'Unexpected gdb representation: %r\n%s' % (gdb_output, gdb_output))