]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-143123: Protect against recursive tracer calls/finalization (GH-143126)
authorKen Jin <kenjin@python.org>
Wed, 14 Jan 2026 12:23:14 +0000 (20:23 +0800)
committerGitHub <noreply@github.com>
Wed, 14 Jan 2026 12:23:14 +0000 (12:23 +0000)
* Stronger check for recursive traces

* Add a stop_tracing field

* Stop early when tracing exceptions

Include/internal/pycore_optimizer.h
Include/internal/pycore_tstate.h
Lib/test/test_capi/test_opt.py
Misc/NEWS.d/next/Core_and_Builtins/2025-12-23-23-36-41.gh-issue-143123.-51gt_.rst [new file with mode: 0644]
Python/bytecodes.c
Python/ceval.c
Python/ceval_macros.h
Python/generated_cases.c.h
Python/optimizer.c

index 80a22e6bd12c649d16fc2c9eb4ffa0576f7686ee..b4d19eb69511d2f8c3dcf35513709dc5d9a6f72b 100644 (file)
@@ -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);
index 518fd94a31ae5ebaf15653e02e4a0f6d38bdd652..25f4f6ed7078dfadc2417f2288870bcf38cbed40 100644 (file)
@@ -54,6 +54,7 @@ typedef struct _PyJitTracerTranslatorState {
 } _PyJitTracerTranslatorState;
 
 typedef struct _PyJitTracerState {
+    bool is_tracing;
     _PyJitTracerInitialState initial_state;
     _PyJitTracerPreviousState prev_state;
     _PyJitTracerTranslatorState translator_state;
index 757e5e6ef53574a6486a37e0ca03f1c8a823e603..c0aecb8d841224969f52503bdef247664bac1c6d 100644 (file)
@@ -3747,6 +3747,51 @@ class TestUopsOptimization(unittest.TestCase):
         """), 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 (file)
index 0000000..04523bd
--- /dev/null
@@ -0,0 +1 @@
+Protect the JIT against recursive tracing.
index 273865bd366935dd677fe0847e8620d4e282dc64..66f322c2ef775791d0195af49a2f8935f310c35f 100644 (file)
@@ -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;
index e67ff082ef9fac8dd434e886dc9a13b239b504de..2f83b2e399f4b9a7177c1afeb12d1a7f4790920f 100644 (file)
@@ -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
index c6621a08999e4a9b80f655153a36cc3ec957a6e1..3b4b3253b3638c2d259f4100bc3041c1bd456557 100644 (file)
 #  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) { \
index efcadc19d997f381d822a96d682b2051c62ad5a0..b9b493130d732f94629ac9edc1223d5bd5f7c95f 100644 (file)
@@ -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);
index d24e29b2b298e0bee6dee8fcc56bb040e9dea692..a2a1feb8b9e147485c45316903e6f42d6d240837 100644 (file)
@@ -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