Skip to content

Commit 01df239

Browse files
committed
Strengthen JIT unwind tests
1 parent ef9ea52 commit 01df239

1 file changed

Lines changed: 79 additions & 12 deletions

File tree

Lib/test/test_frame_pointer_unwind.py

Lines changed: 79 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
raise unittest.SkipTest("test requires subprocess support")
1818

1919

20+
STACK_DEPTH = 10
21+
22+
2023
def _frame_pointers_expected(machine):
2124
cflags = " ".join(
2225
value for value in (
@@ -89,7 +92,7 @@ def build_stack(n, unwinder, warming_up_caller=False):
8992
result = operator.call(build_stack, n - 1, unwinder, warming_up)
9093
return result
9194

92-
stack = build_stack(10, unwinder)
95+
stack = build_stack(STACK_DEPTH, unwinder)
9396
return stack
9497

9598

@@ -112,9 +115,7 @@ def _classify_stack(stack, jit_enabled):
112115
return annotated, python_frames, jit_frames, other_frames
113116

114117

115-
def _annotate_unwind(unwinder_name="manual_frame_pointer_unwind"):
116-
unwinder = getattr(_testinternalcapi, unwinder_name)
117-
stack = _build_stack_and_unwind(unwinder)
118+
def _summarize_unwind(stack, unwinder_name):
118119
jit_enabled = hasattr(sys, "_jit") and sys._jit.is_enabled()
119120
jit_backend = _testinternalcapi.get_jit_backend()
120121
ranges = _testinternalcapi.get_jit_code_ranges() if jit_enabled else []
@@ -127,20 +128,44 @@ def _annotate_unwind(unwinder_name="manual_frame_pointer_unwind"):
127128
)
128129
for idx, addr, tag in annotated:
129130
print(f"#{idx:02d} {addr:#x} -> {tag}")
130-
return json.dumps({
131+
return {
131132
"length": len(stack),
132133
"python_frames": python_frames,
133134
"jit_frames": jit_frames,
134135
"other_frames": other_frames,
135136
"jit_backend": jit_backend,
136137
"unwinder": unwinder_name,
138+
}
139+
140+
141+
def _annotate_unwind(unwinder_name="manual_frame_pointer_unwind"):
142+
unwinder = getattr(_testinternalcapi, unwinder_name)
143+
stack = _build_stack_and_unwind(unwinder)
144+
return json.dumps(_summarize_unwind(stack, unwinder_name))
145+
146+
147+
def _annotate_unwind_after_executor_free(unwinder_name="gnu_backtrace_unwind"):
148+
# The first unwind runs at the bottom of _build_stack_and_unwind(), while
149+
# the recursive helper may be executing in JIT code. After it returns, this
150+
# helper is back in normal test code; clearing executor caches should remove
151+
# the old JIT ranges, so the second unwind must not report stale JIT frames.
152+
live = json.loads(_annotate_unwind(unwinder_name))
153+
154+
sys._clear_internal_caches()
155+
_testinternalcapi.clear_executor_deletion_list()
156+
157+
unwinder = getattr(_testinternalcapi, unwinder_name)
158+
after_free = _summarize_unwind(unwinder(), unwinder_name)
159+
return json.dumps({
160+
"live": live,
161+
"after_free": after_free,
137162
})
138163

139164

140-
def _unwind_result(unwinder_name, **env):
165+
def _run_unwind_helper(helper_name, unwinder_name, **env):
141166
code = (
142-
"from test.test_frame_pointer_unwind import _annotate_unwind; "
143-
f"print(_annotate_unwind({unwinder_name!r}));"
167+
f"from test.test_frame_pointer_unwind import {helper_name}; "
168+
f"print({helper_name}({unwinder_name!r}));"
144169
)
145170
run_env = os.environ.copy()
146171
run_env.update(env)
@@ -168,6 +193,15 @@ def _unwind_result(unwinder_name, **env):
168193
) from exc
169194

170195

196+
def _unwind_result(unwinder_name, **env):
197+
return _run_unwind_helper("_annotate_unwind", unwinder_name, **env)
198+
199+
200+
def _unwind_after_executor_free_result(unwinder_name, **env):
201+
return _run_unwind_helper(
202+
"_annotate_unwind_after_executor_free", unwinder_name, **env)
203+
204+
171205
@support.requires_gil_enabled("test requires the GIL enabled")
172206
@unittest.skipIf(support.is_wasi, "test not supported on WASI")
173207
class FramePointerUnwindTests(unittest.TestCase):
@@ -204,9 +238,9 @@ def test_manual_unwind_respects_frame_pointers(self):
204238
python_frames = result.get("python_frames", 0)
205239
jit_backend = result.get("jit_backend")
206240
if self.frame_pointers_expected:
207-
self.assertGreater(
241+
self.assertGreaterEqual(
208242
python_frames,
209-
0,
243+
STACK_DEPTH,
210244
f"expected to find Python frames on {self.machine} with env {env}",
211245
)
212246
if using_jit:
@@ -269,9 +303,9 @@ def test_gnu_backtrace_unwinds_through_jit_frames(self):
269303
jit_frames = result.get("jit_frames", 0)
270304
jit_backend = result.get("jit_backend")
271305

272-
self.assertGreater(
306+
self.assertGreaterEqual(
273307
python_frames,
274-
0,
308+
STACK_DEPTH,
275309
f"expected to find Python frames in GNU backtrace with env {env}",
276310
)
277311
if using_jit and jit_backend == "jit":
@@ -287,6 +321,39 @@ def test_gnu_backtrace_unwinds_through_jit_frames(self):
287321
f"unexpected JIT frames counted in GNU backtrace with env {env}",
288322
)
289323

324+
def test_gnu_backtrace_jit_frames_disappear_after_executor_free(self):
325+
if not (hasattr(sys, "_jit") and sys._jit.is_available()):
326+
self.skipTest("JIT is not available")
327+
328+
result = _unwind_after_executor_free_result(
329+
"gnu_backtrace_unwind", PYTHON_JIT="1")
330+
live = result["live"]
331+
if live.get("jit_backend") != "jit":
332+
self.skipTest("JIT backend is not active")
333+
334+
self.assertGreaterEqual(
335+
live.get("python_frames", 0),
336+
STACK_DEPTH,
337+
"expected live GNU backtrace to include recursive Python frames",
338+
)
339+
self.assertGreater(
340+
live.get("jit_frames", 0),
341+
0,
342+
"expected live GNU backtrace to include JIT frames",
343+
)
344+
345+
after_free = result["after_free"]
346+
self.assertGreater(
347+
after_free.get("python_frames", 0),
348+
0,
349+
"expected GNU backtrace after executor free to include Python frames",
350+
)
351+
self.assertEqual(
352+
after_free.get("jit_frames", 0),
353+
0,
354+
"unexpected JIT frames in GNU backtrace after executor free",
355+
)
356+
290357

291358
if __name__ == "__main__":
292359
unittest.main()

0 commit comments

Comments
 (0)