Skip to content

Commit 0e6af95

Browse files
authored
Merge branch 'main' into deprecate-struct-init/78724
2 parents 69a5eec + ce6bae9 commit 0e6af95

File tree

14 files changed

+105
-14
lines changed

14 files changed

+105
-14
lines changed

Include/internal/pycore_optimizer.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ extern void _PyExecutor_Free(_PyExecutorObject *self);
222222

223223
PyAPI_FUNC(int) _PyDumpExecutors(FILE *out);
224224
#ifdef _Py_TIER2
225-
extern void _Py_ClearExecutorDeletionList(PyInterpreterState *interp);
225+
PyAPI_FUNC(void) _Py_ClearExecutorDeletionList(PyInterpreterState *interp);
226226
#endif
227227

228228
int _PyJit_translate_single_bytecode_to_trace(PyThreadState *tstate, _PyInterpreterFrame *frame, _Py_CODEUNIT *next_instr, int stop_tracing_opcode);
@@ -231,7 +231,7 @@ PyAPI_FUNC(int)
231231
_PyJit_TryInitializeTracing(PyThreadState *tstate, _PyInterpreterFrame *frame,
232232
_Py_CODEUNIT *curr_instr, _Py_CODEUNIT *start_instr,
233233
_Py_CODEUNIT *close_loop_instr, int curr_stackdepth, int chain_depth, _PyExitData *exit,
234-
int oparg);
234+
int oparg, _PyExecutorObject *current_executor);
235235

236236
void _PyJit_FinalizeTracing(PyThreadState *tstate);
237237
void _PyJit_TracerFree(_PyThreadStateImpl *_tstate);

Include/internal/pycore_tstate.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ typedef struct _PyJitTracerInitialState {
3131
struct _PyExitData *exit;
3232
PyCodeObject *code; // Strong
3333
PyFunctionObject *func; // Strong
34+
struct _PyExecutorObject *executor; // Strong
3435
_Py_CODEUNIT *start_instr;
3536
_Py_CODEUNIT *close_loop_instr;
3637
_Py_CODEUNIT *jump_backward_instr;

Lib/test/support/import_helper.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,8 +305,8 @@ def ready_to_import(name=None, source=""):
305305
try:
306306
sys.path.insert(0, tempdir)
307307
yield name, path
308-
sys.path.remove(tempdir)
309308
finally:
309+
sys.path.remove(tempdir)
310310
if old_module is not None:
311311
sys.modules[name] = old_module
312312
else:

Lib/test/test_capi/test_opt.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,19 @@ def f():
139139
exe = get_first_executor(f)
140140
self.assertIsNone(exe)
141141

142+
def test_prev_executor_freed_while_tracing(self):
143+
def f(start, end, way):
144+
for x in range(start, end):
145+
# For the first trace, create a bad branch on purpose to trace into.
146+
# A side exit will form from here on the second trace.
147+
y = way + way
148+
if x >= TIER2_THRESHOLD:
149+
# Invalidate the first trace while tracing the second.
150+
_testinternalcapi.invalidate_executors(f.__code__)
151+
_testinternalcapi.clear_executor_deletion_list()
152+
f(0, TIER2_THRESHOLD, 1)
153+
f(1, TIER2_THRESHOLD + 1, 1.0)
154+
142155

143156
@requires_specialization
144157
@unittest.skipIf(Py_GIL_DISABLED, "optimizer not yet supported in free-threaded builds")

Lib/test/test_crossinterp.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import contextlib
22
import itertools
33
import sys
4+
import traceback
45
import types
56
import unittest
67
import warnings
78

9+
from test import support
810
from test.support import import_helper
911

1012
_testinternalcapi = import_helper.import_module('_testinternalcapi')
@@ -1491,5 +1493,54 @@ def test_builtin_objects(self):
14911493
])
14921494

14931495

1496+
class CaptureExceptionTests(unittest.TestCase):
1497+
1498+
# Prevent crashes with incompatible TracebackException.format().
1499+
# Regression test for https://github.com/python/cpython/issues/143377.
1500+
1501+
def capture_with_formatter(self, exc, formatter):
1502+
with support.swap_attr(traceback.TracebackException, "format", formatter):
1503+
return _interpreters.capture_exception(exc)
1504+
1505+
def test_capture_exception(self):
1506+
captured = _interpreters.capture_exception(ValueError("hello"))
1507+
1508+
self.assertEqual(captured.type.__name__, "ValueError")
1509+
self.assertEqual(captured.type.__qualname__, "ValueError")
1510+
self.assertEqual(captured.type.__module__, "builtins")
1511+
1512+
self.assertEqual(captured.msg, "hello")
1513+
self.assertEqual(captured.formatted, "ValueError: hello")
1514+
1515+
def test_capture_exception_custom_format(self):
1516+
exc = ValueError("good bye!")
1517+
formatter = lambda self: ["hello\n", "world\n"]
1518+
captured = self.capture_with_formatter(exc, formatter)
1519+
self.assertEqual(captured.msg, "good bye!")
1520+
self.assertEqual(captured.formatted, "ValueError: good bye!")
1521+
self.assertEqual(captured.errdisplay, "hello\nworld")
1522+
1523+
@support.subTests("exc_lines", ([], ["x-no-nl"], ["x-no-nl", "y-no-nl"]))
1524+
def test_capture_exception_invalid_format(self, exc_lines):
1525+
formatter = lambda self: exc_lines
1526+
captured = self.capture_with_formatter(ValueError(), formatter)
1527+
self.assertEqual(captured.msg, "")
1528+
self.assertEqual(captured.formatted, "ValueError: ")
1529+
self.assertEqual(captured.errdisplay, "".join(exc_lines))
1530+
1531+
@unittest.skipUnless(
1532+
support.Py_DEBUG,
1533+
"printing subinterpreter unraisable exceptions requires DEBUG build",
1534+
)
1535+
def test_capture_exception_unraisable_exception(self):
1536+
formatter = lambda self: 1
1537+
with support.catch_unraisable_exception() as cm:
1538+
captured = self.capture_with_formatter(ValueError(), formatter)
1539+
self.assertNotHasAttr(captured, "errdisplay")
1540+
self.assertEqual(cm.unraisable.exc_type, TypeError)
1541+
self.assertEqual(str(cm.unraisable.exc_value),
1542+
"can only join an iterable")
1543+
1544+
14941545
if __name__ == '__main__':
14951546
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a crash in :func:`!_interpreters.capture_exception` when
2+
the exception is incorrectly formatted. Patch by Bénédikt Tran.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a reference counting issue in the JIT tracer where the current executor
2+
could be prematurely freed during tracing.

Modules/_testinternalcapi.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1245,6 +1245,14 @@ invalidate_executors(PyObject *self, PyObject *obj)
12451245
Py_RETURN_NONE;
12461246
}
12471247

1248+
static PyObject *
1249+
clear_executor_deletion_list(PyObject *self, PyObject *Py_UNUSED(ignored))
1250+
{
1251+
PyInterpreterState *interp = PyInterpreterState_Get();
1252+
_Py_ClearExecutorDeletionList(interp);
1253+
Py_RETURN_NONE;
1254+
}
1255+
12481256
static PyObject *
12491257
get_exit_executor(PyObject *self, PyObject *arg)
12501258
{
@@ -2562,6 +2570,7 @@ static PyMethodDef module_functions[] = {
25622570
#ifdef _Py_TIER2
25632571
{"add_executor_dependency", add_executor_dependency, METH_VARARGS, NULL},
25642572
{"invalidate_executors", invalidate_executors, METH_O, NULL},
2573+
{"clear_executor_deletion_list", clear_executor_deletion_list, METH_NOARGS, NULL},
25652574
{"get_exit_executor", get_exit_executor, METH_O, NULL},
25662575
#endif
25672576
{"pending_threadfunc", _PyCFunction_CAST(pending_threadfunc),

Python/bytecodes.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2990,7 +2990,7 @@ dummy_func(
29902990
oparg >>= 8;
29912991
insert_exec_at--;
29922992
}
2993-
int succ = _PyJit_TryInitializeTracing(tstate, frame, this_instr, insert_exec_at, next_instr, STACK_LEVEL(), 0, NULL, oparg);
2993+
int succ = _PyJit_TryInitializeTracing(tstate, frame, this_instr, insert_exec_at, next_instr, STACK_LEVEL(), 0, NULL, oparg, NULL);
29942994
if (succ) {
29952995
ENTER_TRACING();
29962996
}
@@ -5525,7 +5525,7 @@ dummy_func(
55255525
// Note: it's safe to use target->op.arg here instead of the oparg given by EXTENDED_ARG.
55265526
// The invariant in the optimizer is the deopt target always points back to the first EXTENDED_ARG.
55275527
// So setting it to anything else is wrong.
5528-
int succ = _PyJit_TryInitializeTracing(tstate, frame, target, target, target, STACK_LEVEL(), chain_depth, exit, target->op.arg);
5528+
int succ = _PyJit_TryInitializeTracing(tstate, frame, target, target, target, STACK_LEVEL(), chain_depth, exit, target->op.arg, previous_executor);
55295529
exit->temperature = restart_backoff_counter(exit->temperature);
55305530
if (succ) {
55315531
GOTO_TIER_ONE_CONTINUE_TRACING(target);

Python/ceval.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1475,7 +1475,7 @@ stop_tracing_and_jit(PyThreadState *tstate, _PyInterpreterFrame *frame)
14751475
tracer->initial_state.jump_backward_instr[1].counter = initial_jump_backoff_counter(&_tstate->policy);
14761476
}
14771477
}
1478-
else {
1478+
else if (tracer->initial_state.executor->vm_data.valid) {
14791479
// Likewise, we hold a strong reference to the executor containing this exit, so the exit is guaranteed
14801480
// to be valid to access.
14811481
if (err <= 0) {

0 commit comments

Comments
 (0)