diff --git a/Lib/dis.py b/Lib/dis.py index d60507ae473453..360b356378b7a4 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -352,6 +352,10 @@ def _get_jump_target(op, arg, offset): arg = -arg target = offset + 2 + arg*2 target += 2 * caches + # FOR_ITER uses JUMPBY(oparg + 1) at runtime to land on POP_ITER, + # skipping END_FOR. Add the extra 2 bytes to match the actual target. + if deop == FOR_ITER: + target += 2 elif deop in hasjabs: target = arg*2 else: @@ -569,6 +573,8 @@ def offset_from_jump_arg(self, op, arg, offset): argval = offset + 2 + signed_arg*2 caches = _get_cache_size(_all_opname[deop]) argval += 2 * caches + if deop == FOR_ITER: + argval += 2 return argval return None diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index 8be104585814f4..11d2c7b884aa37 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -180,8 +180,8 @@ def bug708901(): %3d JUMP_BACKWARD 5 (to L1) -%3d L2: END_FOR - POP_ITER +%3d END_FOR + L2: POP_ITER LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE """ % (bug708901.__code__.co_firstlineno, @@ -849,8 +849,8 @@ def foo(x): L1: FOR_ITER 3 (to L2) LIST_APPEND 3 JUMP_BACKWARD 5 (to L1) - L2: END_FOR - POP_ITER + END_FOR + L2: POP_ITER RETURN_VALUE L3: PUSH_NULL LOAD_FAST_BORROW 0 (x) @@ -893,8 +893,8 @@ def foo(x): RESUME 9 POP_TOP JUMP_BACKWARD 17 (to L2) - L3: END_FOR - POP_ITER + END_FOR + L3: POP_ITER LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE @@ -947,8 +947,8 @@ def loop_test(): POP_TOP JUMP_BACKWARD_{: <6} 16 (to L1) -%3d L2: END_FOR - POP_ITER +%3d END_FOR + L2: POP_ITER LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE """ % (loop_test.__code__.co_firstlineno, @@ -1858,7 +1858,7 @@ def _prepare_test_cases(): make_inst(opname='LOAD_SMALL_INT', arg=10, argval=10, argrepr='', offset=14, start_offset=14, starts_line=False, line_number=3), make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=16, start_offset=16, starts_line=False, line_number=3, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), make_inst(opname='GET_ITER', arg=0, argval=0, argrepr='', offset=24, start_offset=24, starts_line=False, line_number=3, cache_info=[('counter', 1, b'\x00\x00')]), - make_inst(opname='FOR_ITER', arg=33, argval=98, argrepr='to L4', offset=28, start_offset=28, starts_line=False, line_number=3, label=1, cache_info=[('counter', 1, b'\x00\x00')]), + make_inst(opname='FOR_ITER', arg=33, argval=100, argrepr='to L4', offset=28, start_offset=28, starts_line=False, line_number=3, label=1, cache_info=[('counter', 1, b'\x00\x00')]), make_inst(opname='STORE_FAST', arg=0, argval='i', argrepr='i', offset=32, start_offset=32, starts_line=False, line_number=3), make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=34, start_offset=34, starts_line=True, line_number=4, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), make_inst(opname='LOAD_FAST_BORROW', arg=0, argval='i', argrepr='i', offset=44, start_offset=44, starts_line=False, line_number=4), @@ -1879,8 +1879,8 @@ def _prepare_test_cases(): make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=92, start_offset=92, starts_line=True, line_number=8, label=3), make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=94, start_offset=94, starts_line=False, line_number=8), make_inst(opname='JUMP_FORWARD', arg=13, argval=124, argrepr='to L5', offset=96, start_offset=96, starts_line=False, line_number=8), - make_inst(opname='END_FOR', arg=None, argval=None, argrepr='', offset=98, start_offset=98, starts_line=True, line_number=3, label=4), - make_inst(opname='POP_ITER', arg=None, argval=None, argrepr='', offset=100, start_offset=100, starts_line=False, line_number=3), + make_inst(opname='END_FOR', arg=None, argval=None, argrepr='', offset=98, start_offset=98, starts_line=True, line_number=3), + make_inst(opname='POP_ITER', arg=None, argval=None, argrepr='', offset=100, start_offset=100, starts_line=False, line_number=3, label=4), make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=102, start_offset=102, starts_line=True, line_number=10, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), make_inst(opname='LOAD_CONST', arg=1, argval='I can haz else clause?', argrepr="'I can haz else clause?'", offset=112, start_offset=112, starts_line=False, line_number=10), make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=114, start_offset=114, starts_line=False, line_number=10, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), @@ -2200,6 +2200,13 @@ def test_jump_target(self): positions=None) self.assertEqual(10 + 2 + 1*2 + 100*2, instruction.jump_target) + # FOR_ITER uses JUMPBY(oparg + 1) at runtime, skipping END_FOR to land + # on POP_ITER; the reported target must reflect the extra +2 bytes. + instruction = make_inst(opname="FOR_ITER", arg=delta, argval=delta, + argrepr='', offset=10, start_offset=10, starts_line=True, line_number=1, label=None, + positions=None) + self.assertEqual(10 + 2 + 1*2 + 100*2 + 2, instruction.jump_target) + def test_argval_argrepr(self): def f(opcode, oparg, offset, *init_args): arg_resolver = dis.ArgResolver(*init_args) diff --git a/Misc/NEWS.d/next/Library/2026-05-07-17-38-44.gh-issue-149498.fix-for-iter-jump-target.rst b/Misc/NEWS.d/next/Library/2026-05-07-17-38-44.gh-issue-149498.fix-for-iter-jump-target.rst new file mode 100644 index 00000000000000..c3058c5f11f254 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-05-07-17-38-44.gh-issue-149498.fix-for-iter-jump-target.rst @@ -0,0 +1,4 @@ +Fix :func:`dis.get_instructions` reporting the wrong ``jump_target`` for +:opcode:`FOR_ITER`. The exhausted-iterator path now correctly points to +:opcode:`POP_ITER` rather than :opcode:`END_FOR`, matching the runtime +behaviour of ``JUMPBY(oparg + 1)``.