python (3.12.0)
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()