diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 36efab518781410..6721439a2fb2f45 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -12,7 +12,8 @@ from test.support import (script_helper, requires_specialization, import_helper, Py_GIL_DISABLED, requires_jit_enabled, - reset_code, SHORT_TIMEOUT, isolation) + reset_code, SHORT_TIMEOUT, isolation, + threading_helper) _testinternalcapi = import_helper.import_module("_testinternalcapi") @@ -6268,6 +6269,34 @@ def exhaust(iterator): # A different iterator type must not link that executor to itself. exhaust(map(bool, values)) + @isolation.runInSubprocess( + env={'PYTHON_JIT': '1', 'PYTHON_JIT_STRESS': '1'}, + timeout=SHORT_TIMEOUT) + def test_157740_stale_oparg_executor_race(self): + import json + import threading + + sys.setswitchinterval(1e-6) + + def worker(data, index): + while data: + for d in list(data): + d[index] = index + + for _ in range(25): + # Race executor installation against tracing the same code. + worker.__code__ = worker.__code__.replace() + data = [{}, {}] + threads = [threading.Thread(target=worker, args=(data, i), + daemon=True) + for i in range(4)] + with threading_helper.start_threads(threads, unlock=data.clear): + for _ in range(200): + try: + json.dumps(data) + except Exception: + pass + def global_identity(x): return x diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-09-20-11-58-15.gh-issue-157740.S3wLtW.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-20-11-58-15.gh-issue-157740.S3wLtW.rst new file mode 100644 index 000000000000000..00b2afbb9e34c4e --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-20-11-58-15.gh-issue-157740.S3wLtW.rst @@ -0,0 +1 @@ +Fix a crash when multiple threads execute the same code with the JIT enabled. A stale ``oparg`` value could be used as an executor index, causing an out-of-bounds read. diff --git a/Python/optimizer.c b/Python/optimizer.c index e05adb344c8d06d..48b2b570f2588b6 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -706,7 +706,7 @@ _PyJit_translate_single_bytecode_to_trace( } if (opcode == ENTER_EXECUTOR) { - _PyExecutorObject *executor = old_code->co_executors->executors[oparg & 255]; + _PyExecutorObject *executor = old_code->co_executors->executors[this_instr->op.arg]; opcode = executor->vm_data.opcode; oparg = (oparg & ~255) | executor->vm_data.oparg; }