diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 06271a0e273275..bb973a9ba164be 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -5554,6 +5554,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 f841e12793a800..918c5428c76cc6 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 dc5c68794e8a5f..31ef230880238b 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -9020,7 +9020,6 @@ SET_CURRENT_CACHED_VALUES(0); JUMP_TO_ERROR(); } - next = none; if (true) { UOP_STAT_INC(uopcode, miss); SET_CURRENT_CACHED_VALUES(0); @@ -9064,10 +9063,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(); } @@ -9111,10 +9109,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(); @@ -9161,10 +9158,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);