1  import re
       2  import textwrap
       3  import unittest
       4  
       5  
       6  from test import support
       7  from test.support import import_helper, requires_subprocess
       8  from test.support.script_helper import assert_python_failure, assert_python_ok
       9  
      10  
      11  # Skip this test if the _testcapi module isn't available.
      12  _testcapi = import_helper.import_module('_testcapi')
      13  
      14  @requires_subprocess()
      15  class ESC[4;38;5;81mPyMemDebugTests(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
      16      PYTHONMALLOC = 'debug'
      17      # '0x04c06e0' or '04C06E0'
      18      PTR_REGEX = r'(?:0x)?[0-9a-fA-F]+'
      19  
      20      def check(self, code):
      21          with support.SuppressCrashReport():
      22              out = assert_python_failure(
      23                  '-c', code,
      24                  PYTHONMALLOC=self.PYTHONMALLOC,
      25                  # FreeBSD: instruct jemalloc to not fill freed() memory
      26                  # with junk byte 0x5a, see JEMALLOC(3)
      27                  MALLOC_CONF="junk:false",
      28              )
      29          stderr = out.err
      30          return stderr.decode('ascii', 'replace')
      31  
      32      def test_buffer_overflow(self):
      33          out = self.check('import _testcapi; _testcapi.pymem_buffer_overflow()')
      34          regex = (r"Debug memory block at address p={ptr}: API 'm'\n"
      35                   r"    16 bytes originally requested\n"
      36                   r"    The [0-9] pad bytes at p-[0-9] are FORBIDDENBYTE, as expected.\n"
      37                   r"    The [0-9] pad bytes at tail={ptr} are not all FORBIDDENBYTE \(0x[0-9a-f]{{2}}\):\n"
      38                   r"        at tail\+0: 0x78 \*\*\* OUCH\n"
      39                   r"        at tail\+1: 0xfd\n"
      40                   r"        at tail\+2: 0xfd\n"
      41                   r"        .*\n"
      42                   r"(    The block was made by call #[0-9]+ to debug malloc/realloc.\n)?"
      43                   r"    Data at p: cd cd cd .*\n"
      44                   r"\n"
      45                   r"Enable tracemalloc to get the memory block allocation traceback\n"
      46                   r"\n"
      47                   r"Fatal Python error: _PyMem_DebugRawFree: bad trailing pad byte")
      48          regex = regex.format(ptr=self.PTR_REGEX)
      49          regex = re.compile(regex, flags=re.DOTALL)
      50          self.assertRegex(out, regex)
      51  
      52      def test_api_misuse(self):
      53          out = self.check('import _testcapi; _testcapi.pymem_api_misuse()')
      54          regex = (r"Debug memory block at address p={ptr}: API 'm'\n"
      55                   r"    16 bytes originally requested\n"
      56                   r"    The [0-9] pad bytes at p-[0-9] are FORBIDDENBYTE, as expected.\n"
      57                   r"    The [0-9] pad bytes at tail={ptr} are FORBIDDENBYTE, as expected.\n"
      58                   r"(    The block was made by call #[0-9]+ to debug malloc/realloc.\n)?"
      59                   r"    Data at p: cd cd cd .*\n"
      60                   r"\n"
      61                   r"Enable tracemalloc to get the memory block allocation traceback\n"
      62                   r"\n"
      63                   r"Fatal Python error: _PyMem_DebugRawFree: bad ID: Allocated using API 'm', verified using API 'r'\n")
      64          regex = regex.format(ptr=self.PTR_REGEX)
      65          self.assertRegex(out, regex)
      66  
      67      def check_malloc_without_gil(self, code):
      68          out = self.check(code)
      69          expected = ('Fatal Python error: _PyMem_DebugMalloc: '
      70                      'Python memory allocator called without holding the GIL')
      71          self.assertIn(expected, out)
      72  
      73      def test_pymem_malloc_without_gil(self):
      74          # Debug hooks must raise an error if PyMem_Malloc() is called
      75          # without holding the GIL
      76          code = 'import _testcapi; _testcapi.pymem_malloc_without_gil()'
      77          self.check_malloc_without_gil(code)
      78  
      79      def test_pyobject_malloc_without_gil(self):
      80          # Debug hooks must raise an error if PyObject_Malloc() is called
      81          # without holding the GIL
      82          code = 'import _testcapi; _testcapi.pyobject_malloc_without_gil()'
      83          self.check_malloc_without_gil(code)
      84  
      85      def check_pyobject_is_freed(self, func_name):
      86          code = textwrap.dedent(f'''
      87              import gc, os, sys, _testcapi
      88              # Disable the GC to avoid crash on GC collection
      89              gc.disable()
      90              try:
      91                  _testcapi.{func_name}()
      92                  # Exit immediately to avoid a crash while deallocating
      93                  # the invalid object
      94                  os._exit(0)
      95              except _testcapi.error:
      96                  os._exit(1)
      97          ''')
      98          assert_python_ok(
      99              '-c', code,
     100              PYTHONMALLOC=self.PYTHONMALLOC,
     101              MALLOC_CONF="junk:false",
     102          )
     103  
     104      def test_pyobject_null_is_freed(self):
     105          self.check_pyobject_is_freed('check_pyobject_null_is_freed')
     106  
     107      def test_pyobject_uninitialized_is_freed(self):
     108          self.check_pyobject_is_freed('check_pyobject_uninitialized_is_freed')
     109  
     110      def test_pyobject_forbidden_bytes_is_freed(self):
     111          self.check_pyobject_is_freed('check_pyobject_forbidden_bytes_is_freed')
     112  
     113      def test_pyobject_freed_is_freed(self):
     114          self.check_pyobject_is_freed('check_pyobject_freed_is_freed')
     115  
     116      def test_set_nomemory(self):
     117          code = """if 1:
     118              import _testcapi
     119  
     120              class C(): pass
     121  
     122              # The first loop tests both functions and that remove_mem_hooks()
     123              # can be called twice in a row. The second loop checks a call to
     124              # set_nomemory() after a call to remove_mem_hooks(). The third
     125              # loop checks the start and stop arguments of set_nomemory().
     126              for outer_cnt in range(1, 4):
     127                  start = 10 * outer_cnt
     128                  for j in range(100):
     129                      if j == 0:
     130                          if outer_cnt != 3:
     131                              _testcapi.set_nomemory(start)
     132                          else:
     133                              _testcapi.set_nomemory(start, start + 1)
     134                      try:
     135                          C()
     136                      except MemoryError as e:
     137                          if outer_cnt != 3:
     138                              _testcapi.remove_mem_hooks()
     139                          print('MemoryError', outer_cnt, j)
     140                          _testcapi.remove_mem_hooks()
     141                          break
     142          """
     143          rc, out, err = assert_python_ok('-c', code)
     144          lines = out.splitlines()
     145          for i, line in enumerate(lines, 1):
     146              self.assertIn(b'MemoryError', out)
     147              *_, count = line.split(b' ')
     148              count = int(count)
     149              self.assertLessEqual(count, i*5)
     150              self.assertGreaterEqual(count, i*5-2)
     151  
     152  
     153  class ESC[4;38;5;81mPyMemMallocDebugTests(ESC[4;38;5;149mPyMemDebugTests):
     154      PYTHONMALLOC = 'malloc_debug'
     155  
     156  
     157  @unittest.skipUnless(support.with_pymalloc(), 'need pymalloc')
     158  class ESC[4;38;5;81mPyMemPymallocDebugTests(ESC[4;38;5;149mPyMemDebugTests):
     159      PYTHONMALLOC = 'pymalloc_debug'
     160  
     161  
     162  @unittest.skipUnless(support.Py_DEBUG, 'need Py_DEBUG')
     163  class ESC[4;38;5;81mPyMemDefaultTests(ESC[4;38;5;149mPyMemDebugTests):
     164      # test default allocator of Python compiled in debug mode
     165      PYTHONMALLOC = ''
     166  
     167  
     168  if __name__ == "__main__":
     169      unittest.main()