From fd326c95831c31159b8e3b427545ff4017933609 Mon Sep 17 00:00:00 2001 From: An Long Date: Sat, 15 Aug 2026 20:51:08 +0900 Subject: [PATCH] gh-155823: Fix stale stack on _SEND_VIRTUAL_TIER_TWO exhausted exit --- Lib/test/test_capi/test_opt.py | 14 ++++++++++++++ .../2026-08-15-20-31-20.gh-issue-155823.OhNmzi.rst | 4 ++++ Python/bytecodes.c | 2 -- Python/executor_cases.c.h | 10 +++------- 4 files changed, 21 insertions(+), 9 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-08-15-20-31-20.gh-issue-155823.OhNmzi.rst diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 36efab51878141..ce3dccf40925ba 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -5599,6 +5599,20 @@ def testfunc(n): self.assertIn("_FOR_ITER_GEN_FRAME", uops) self.assertIn("_SEND_VIRTUAL_TIER_TWO", uops) + def test_send_virtual_exhausted(self): + # gh-155823: warm up on a non-empty list, then take the exhausted exit. + def gen(x): + yield from x + def testfunc(n, x): + total = 0 + for _ in range(n): + for v in gen(x): + total += v + return total + + testfunc(TIER2_THRESHOLD * 10, [1]) + self.assertEqual(testfunc(1000, []), 0) + def test_binary_op_subscr_init_frame(self): class B: def __getitem__(self, other): diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-08-15-20-31-20.gh-issue-155823.OhNmzi.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-15-20-31-20.gh-issue-155823.OhNmzi.rst new file mode 100644 index 00000000000000..7dfbaf02575fd6 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-15-20-31-20.gh-issue-155823.OhNmzi.rst @@ -0,0 +1,4 @@ +Fix a JIT bug where the side exit taken when a virtual iterator is exhausted +left a stale value on the stack, so the tier one interpreter resumed with +corrupted state and ``yield from`` raised :exc:`AttributeError` instead of +finishing normally. diff --git a/Python/bytecodes.c b/Python/bytecodes.c index fb0cdf4d65e060..c9fdde28510415 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -1785,8 +1785,6 @@ dummy_func( if (index < 0) { ERROR_NO_POP(); } - next = none; - DEAD(none); EXIT_IF(true); } DEAD(none); diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 9aad9e003765cf..66ad47c9412fb7 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -9175,7 +9175,6 @@ SET_CURRENT_CACHED_VALUES(0); JUMP_TO_ERROR(); } - next = none; if (true) { UOP_STAT_INC(uopcode, miss); SET_CURRENT_CACHED_VALUES(0); @@ -9219,10 +9218,9 @@ SET_CURRENT_CACHED_VALUES(0); JUMP_TO_ERROR(); } - next = none; if (true) { UOP_STAT_INC(uopcode, miss); - _tos_cache0 = stack_pointer[0]; + _tos_cache0 = none; SET_CURRENT_CACHED_VALUES(1); JUMP_TO_JUMP_TARGET(); } @@ -9266,10 +9264,9 @@ SET_CURRENT_CACHED_VALUES(0); JUMP_TO_ERROR(); } - next = none; if (true) { UOP_STAT_INC(uopcode, miss); - _tos_cache1 = stack_pointer[1]; + _tos_cache1 = none; _tos_cache0 = null_or_index; SET_CURRENT_CACHED_VALUES(2); JUMP_TO_JUMP_TARGET(); @@ -9316,10 +9313,9 @@ SET_CURRENT_CACHED_VALUES(0); JUMP_TO_ERROR(); } - next = none; if (true) { UOP_STAT_INC(uopcode, miss); - _tos_cache2 = stack_pointer[2]; + _tos_cache2 = none; _tos_cache1 = null_or_index; _tos_cache0 = iter; SET_CURRENT_CACHED_VALUES(3);