https://github.com/python/cpython/commit/e370c8db52d8b0983158ba6261ab227d8158b8b4
commit: e370c8db52d8b0983158ba6261ab227d8158b8b4
branch: main
author: Ken Jin <[email protected]>
committer: markshannon <[email protected]>
date: 2026-01-14T12:23:14Z
summary:
gh-143123: Protect against recursive tracer calls/finalization (GH-143126)
* Stronger check for recursive traces
* Add a stop_tracing field
* Stop early when tracing exceptions
files:
A
Misc/NEWS.d/next/Core_and_Builtins/2025-12-23-23-36-41.gh-issue-143123.-51gt_.rst
M Include/internal/pycore_optimizer.h
M Include/internal/pycore_tstate.h
M Lib/test/test_capi/test_opt.py
M Python/bytecodes.c
M Python/ceval.c
M Python/ceval_macros.h
M Python/generated_cases.c.h
M Python/optimizer.c
diff --git a/Include/internal/pycore_optimizer.h
b/Include/internal/pycore_optimizer.h
index 80a22e6bd12c64..b4d19eb69511d2 100644
--- a/Include/internal/pycore_optimizer.h
+++ b/Include/internal/pycore_optimizer.h
@@ -233,7 +233,7 @@ _PyJit_TryInitializeTracing(PyThreadState *tstate,
_PyInterpreterFrame *frame,
_Py_CODEUNIT *close_loop_instr, int curr_stackdepth, int chain_depth,
_PyExitData *exit,
int oparg, _PyExecutorObject *current_executor);
-void _PyJit_FinalizeTracing(PyThreadState *tstate);
+void _PyJit_FinalizeTracing(PyThreadState *tstate, int err);
void _PyJit_TracerFree(_PyThreadStateImpl *_tstate);
void _PyJit_Tracer_InvalidateDependency(PyThreadState *old_tstate, void *obj);
diff --git a/Include/internal/pycore_tstate.h b/Include/internal/pycore_tstate.h
index 518fd94a31ae5e..25f4f6ed7078df 100644
--- a/Include/internal/pycore_tstate.h
+++ b/Include/internal/pycore_tstate.h
@@ -54,6 +54,7 @@ typedef struct _PyJitTracerTranslatorState {
} _PyJitTracerTranslatorState;
typedef struct _PyJitTracerState {
+ bool is_tracing;
_PyJitTracerInitialState initial_state;
_PyJitTracerPreviousState prev_state;
_PyJitTracerTranslatorState translator_state;
diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py
index 757e5e6ef53574..c0aecb8d841224 100644
--- a/Lib/test/test_capi/test_opt.py
+++ b/Lib/test/test_capi/test_opt.py
@@ -3747,6 +3747,51 @@ async def async_for_driver():
"""), PYTHON_JIT="1")
self.assertEqual(result[0].rc, 0, result)
+ def test_143358(self):
+ # https://github.com/python/cpython/issues/143358
+
+ result = script_helper.run_python_until_end('-c', textwrap.dedent(f"""
+ def f1():
+
+ class EvilIterator:
+
+ def __init__(self):
+ self._items = [1, 2]
+ self._index = 1
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ if not len(self._items) % 13:
+ self._items.clear()
+
+ for i_loop_9279 in range(10):
+ self._items.extend([1, "", None])
+
+ if not len(self._items) % 11:
+ return 'unexpected_type_from_iterator'
+
+ if self._index >= len(self._items):
+ raise StopIteration
+
+ item = self._items[self._index]
+ self._index += 1
+ return item
+
+ evil_iter = EvilIterator()
+
+ large_num = 2**31
+ for _ in range(400):
+ try:
+ _ = [x + y for x in evil_iter for y in evil_iter if
evil_iter._items.append(x) or large_num]
+ except TypeError:
+ pass
+
+ f1()
+ """), PYTHON_JIT="1", PYTHON_JIT_JUMP_BACKWARD_INITIAL_VALUE="64")
+ self.assertEqual(result[0].rc, 0, result)
+
def global_identity(x):
return x
diff --git
a/Misc/NEWS.d/next/Core_and_Builtins/2025-12-23-23-36-41.gh-issue-143123.-51gt_.rst
b/Misc/NEWS.d/next/Core_and_Builtins/2025-12-23-23-36-41.gh-issue-143123.-51gt_.rst
new file mode 100644
index 00000000000000..04523bdb615bfe
--- /dev/null
+++
b/Misc/NEWS.d/next/Core_and_Builtins/2025-12-23-23-36-41.gh-issue-143123.-51gt_.rst
@@ -0,0 +1 @@
+Protect the JIT against recursive tracing.
diff --git a/Python/bytecodes.c b/Python/bytecodes.c
index 273865bd366935..66f322c2ef7757 100644
--- a/Python/bytecodes.c
+++ b/Python/bytecodes.c
@@ -5620,6 +5620,9 @@ dummy_func(
#else
assert(_PyErr_Occurred(tstate));
#endif
+ SAVE_STACK();
+ STOP_TRACING();
+ RELOAD_STACK();
/* Log traceback info. */
assert(frame->owner != FRAME_OWNED_BY_INTERPRETER);
@@ -5634,6 +5637,9 @@ dummy_func(
}
spilled label(exception_unwind) {
+ SAVE_STACK();
+ STOP_TRACING();
+ RELOAD_STACK();
/* We can't use frame->instr_ptr here, as RERAISE may have set it
*/
int offset = INSTR_OFFSET()-1;
int level, handler, lasti;
diff --git a/Python/ceval.c b/Python/ceval.c
index e67ff082ef9fac..2f83b2e399f4b9 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -1460,32 +1460,7 @@ stop_tracing_and_jit(PyThreadState *tstate,
_PyInterpreterFrame *frame)
if (!_PyErr_Occurred(tstate) && !_is_sys_tracing) {
err = _PyOptimizer_Optimize(frame, tstate);
}
- _PyThreadStateImpl *_tstate = (_PyThreadStateImpl *)tstate;
- // Deal with backoffs
- _PyJitTracerState *tracer = _tstate->jit_tracer_state;
- assert(tracer != NULL);
- _PyExitData *exit = tracer->initial_state.exit;
- if (exit == NULL) {
- // We hold a strong reference to the code object, so the instruction
won't be freed.
- if (err <= 0) {
- _Py_BackoffCounter counter =
tracer->initial_state.jump_backward_instr[1].counter;
- tracer->initial_state.jump_backward_instr[1].counter =
restart_backoff_counter(counter);
- }
- else {
- tracer->initial_state.jump_backward_instr[1].counter =
initial_jump_backoff_counter(&_tstate->policy);
- }
- }
- else if (tracer->initial_state.executor->vm_data.valid) {
- // Likewise, we hold a strong reference to the executor containing
this exit, so the exit is guaranteed
- // to be valid to access.
- if (err <= 0) {
- exit->temperature = restart_backoff_counter(exit->temperature);
- }
- else {
- exit->temperature =
initial_temperature_backoff_counter(&_tstate->policy);
- }
- }
- _PyJit_FinalizeTracing(tstate);
+ _PyJit_FinalizeTracing(tstate, err);
return err;
}
#endif
diff --git a/Python/ceval_macros.h b/Python/ceval_macros.h
index c6621a08999e4a..3b4b3253b3638c 100644
--- a/Python/ceval_macros.h
+++ b/Python/ceval_macros.h
@@ -156,6 +156,19 @@
# define LEAVE_TRACING() tracing_mode = 0
#endif
+#if _Py_TIER2
+#define STOP_TRACING() \
+ do { \
+ if (IS_JIT_TRACING()) { \
+ LEAVE_TRACING(); \
+ _PyJit_FinalizeTracing(tstate, 0); \
+ } \
+ } while (0);
+#else
+#define STOP_TRACING() ((void)(0));
+#endif
+
+
/* PRE_DISPATCH_GOTO() does lltrace if enabled. Normally a no-op */
#ifdef Py_DEBUG
#define PRE_DISPATCH_GOTO() if (frame->lltrace >= 5) { \
diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h
index efcadc19d997f3..b9b493130d732f 100644
--- a/Python/generated_cases.c.h
+++ b/Python/generated_cases.c.h
@@ -12368,7 +12368,9 @@ JUMP_TO_LABEL(error);
#else
assert(_PyErr_Occurred(tstate));
#endif
-
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ STOP_TRACING();
+ stack_pointer = _PyFrame_GetStackPointer(frame);
assert(frame->owner != FRAME_OWNED_BY_INTERPRETER);
if (!_PyFrame_IsIncomplete(frame)) {
_PyFrame_SetStackPointer(frame, stack_pointer);
@@ -12387,6 +12389,7 @@ JUMP_TO_LABEL(error);
LABEL(exception_unwind)
{
+ STOP_TRACING();
int offset = INSTR_OFFSET()-1;
int level, handler, lasti;
int handled = get_exception_handler(_PyFrame_GetCode(frame),
offset, &level, &handler, &lasti);
diff --git a/Python/optimizer.c b/Python/optimizer.c
index d24e29b2b298e0..a2a1feb8b9e147 100644
--- a/Python/optimizer.c
+++ b/Python/optimizer.c
@@ -1030,11 +1030,11 @@ _PyJit_TryInitializeTracing(
// Don't error, just go to next instruction.
return 0;
}
+ _tstate->jit_tracer_state->is_tracing = false;
}
_PyJitTracerState *tracer = _tstate->jit_tracer_state;
// A recursive trace.
- // Don't trace into the inner call because it will stomp on the previous
trace, causing endless retraces.
- if (tracer->prev_state.code_curr_size > CODE_SIZE_EMPTY) {
+ if (tracer->is_tracing) {
return 0;
}
if (oparg > 0xFFFF) {
@@ -1086,20 +1086,45 @@ _PyJit_TryInitializeTracing(
close_loop_instr[1].counter = trigger_backoff_counter();
}
_Py_BloomFilter_Init(&tracer->prev_state.dependencies);
+ tracer->is_tracing = true;
return 1;
}
Py_NO_INLINE void
-_PyJit_FinalizeTracing(PyThreadState *tstate)
+_PyJit_FinalizeTracing(PyThreadState *tstate, int err)
{
_PyThreadStateImpl *_tstate = (_PyThreadStateImpl *)tstate;
_PyJitTracerState *tracer = _tstate->jit_tracer_state;
+ // Deal with backoffs
+ assert(tracer != NULL);
+ _PyExitData *exit = tracer->initial_state.exit;
+ if (exit == NULL) {
+ // We hold a strong reference to the code object, so the instruction
won't be freed.
+ if (err <= 0) {
+ _Py_BackoffCounter counter =
tracer->initial_state.jump_backward_instr[1].counter;
+ tracer->initial_state.jump_backward_instr[1].counter =
restart_backoff_counter(counter);
+ }
+ else {
+ tracer->initial_state.jump_backward_instr[1].counter =
initial_jump_backoff_counter(&_tstate->policy);
+ }
+ }
+ else if (tracer->initial_state.executor->vm_data.valid) {
+ // Likewise, we hold a strong reference to the executor containing
this exit, so the exit is guaranteed
+ // to be valid to access.
+ if (err <= 0) {
+ exit->temperature = restart_backoff_counter(exit->temperature);
+ }
+ else {
+ exit->temperature =
initial_temperature_backoff_counter(&_tstate->policy);
+ }
+ }
Py_CLEAR(tracer->initial_state.code);
Py_CLEAR(tracer->initial_state.func);
Py_CLEAR(tracer->initial_state.executor);
Py_CLEAR(tracer->prev_state.instr_code);
tracer->prev_state.code_curr_size = CODE_SIZE_EMPTY;
tracer->prev_state.code_max_size = UOP_MAX_TRACE_LENGTH/2 - 1;
+ tracer->is_tracing = false;
}
void
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]