From 1d42cee941c13ac3a0e85ce6cc139824060d7e3c Mon Sep 17 00:00:00 2001 From: tonyyuyiding Date: Mon, 21 Sep 2026 15:27:32 +0800 Subject: [PATCH 1/2] gh-157875: Fix reference leak when a JIT trace is rewound --- Lib/test/test_capi/test_opt.py | 20 +++++++++++++++++++ ...-09-21-15-11-11.gh-issue-157875.KLAOhf.rst | 1 + Python/optimizer.c | 3 +++ 3 files changed, 24 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-09-21-15-11-11.gh-issue-157875.KLAOhf.rst diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 36efab518781410..db9fa994feb5d12 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -7,6 +7,7 @@ import gc import os import types +import weakref import _opcode @@ -624,6 +625,25 @@ def testfunc(n, m): self.assertIn("_FOR_ITER_TIER_TWO", uops) self.assertNotIn("_ITER_NEXT_INLINE", uops) + def test_trace_rewind_decref(self): + # gh-157875: trace rewind should not leak reference + def run(): + class C: + def __iter__(self): + return self + def __next__(self): + raise StopIteration + + obj = C() + for _ in range(TIER2_THRESHOLD): + for _ in obj: + pass + return weakref.ref(C) + + ref = run() + gc.collect() + self.assertIsNone(ref()) + @requires_specialization @unittest.skipIf(Py_GIL_DISABLED, "optimizer not yet supported in free-threaded builds") diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-09-21-15-11-11.gh-issue-157875.KLAOhf.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-21-15-11-11.gh-issue-157875.KLAOhf.rst new file mode 100644 index 000000000000000..0918d53b5d369c4 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-21-15-11-11.gh-issue-157875.KLAOhf.rst @@ -0,0 +1 @@ +Fix a reference leak when a JIT trace is rewound. diff --git a/Python/optimizer.c b/Python/optimizer.c index e05adb344c8d06d..4ae72304c162068 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -789,6 +789,9 @@ _PyJit_translate_single_bytecode_to_trace( { _PyUOpInstruction *curr = uop_buffer_last(trace); while (curr->opcode != _SET_IP && uop_buffer_length(trace) > 2) { + if (_PyUop_Flags[curr->opcode] & HAS_RECORDS_VALUE_FLAG) { + Py_XDECREF((PyObject *)(uintptr_t)curr->operand0); + } trace->next--; curr = uop_buffer_last(trace); } From 22598ed3fe4ba32228ba0aaa4a21d68c0f2990aa Mon Sep 17 00:00:00 2001 From: tonyyuyiding Date: Mon, 21 Sep 2026 15:43:33 +0800 Subject: [PATCH 2/2] update test name --- Lib/test/test_capi/test_opt.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index db9fa994feb5d12..c078944a96184d6 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -625,8 +625,7 @@ def testfunc(n, m): self.assertIn("_FOR_ITER_TIER_TWO", uops) self.assertNotIn("_ITER_NEXT_INLINE", uops) - def test_trace_rewind_decref(self): - # gh-157875: trace rewind should not leak reference + def test_157875_trace_rewind_ref_leak(self): def run(): class C: def __iter__(self):