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]

Reply via email to