Skip to content

Commit 0d308eb

Browse files
committed
gh-149498: Fix dis.FOR_ITER reporting wrong exhausted-path jump target
FOR_ITER uses JUMPBY(oparg + 1) at runtime, landing on POP_ITER (past END_FOR). dis was computing the target as NIP + oparg*2, landing on END_FOR instead. Add the extra 2 bytes for FOR_ITER in both _get_jump_target and ArgResolver.offset_from_jump_arg so that jump_target, findlabels, and argrepr all reflect the actual runtime behaviour.
1 parent f5c7535 commit 0d308eb

3 files changed

Lines changed: 28 additions & 11 deletions

File tree

Lib/dis.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,10 @@ def _get_jump_target(op, arg, offset):
352352
arg = -arg
353353
target = offset + 2 + arg*2
354354
target += 2 * caches
355+
# FOR_ITER uses JUMPBY(oparg + 1) at runtime to land on POP_ITER,
356+
# skipping END_FOR. Add the extra 2 bytes to match the actual target.
357+
if deop == FOR_ITER:
358+
target += 2
355359
elif deop in hasjabs:
356360
target = arg*2
357361
else:
@@ -569,6 +573,8 @@ def offset_from_jump_arg(self, op, arg, offset):
569573
argval = offset + 2 + signed_arg*2
570574
caches = _get_cache_size(_all_opname[deop])
571575
argval += 2 * caches
576+
if deop == FOR_ITER:
577+
argval += 2
572578
return argval
573579
return None
574580

Lib/test/test_dis.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,8 @@ def bug708901():
180180
181181
%3d JUMP_BACKWARD 5 (to L1)
182182
183-
%3d L2: END_FOR
184-
POP_ITER
183+
%3d END_FOR
184+
L2: POP_ITER
185185
LOAD_COMMON_CONSTANT 7 (None)
186186
RETURN_VALUE
187187
""" % (bug708901.__code__.co_firstlineno,
@@ -849,8 +849,8 @@ def foo(x):
849849
L1: FOR_ITER 3 (to L2)
850850
LIST_APPEND 3
851851
JUMP_BACKWARD 5 (to L1)
852-
L2: END_FOR
853-
POP_ITER
852+
END_FOR
853+
L2: POP_ITER
854854
RETURN_VALUE
855855
L3: PUSH_NULL
856856
LOAD_FAST_BORROW 0 (x)
@@ -893,8 +893,8 @@ def foo(x):
893893
RESUME 9
894894
POP_TOP
895895
JUMP_BACKWARD 17 (to L2)
896-
L3: END_FOR
897-
POP_ITER
896+
END_FOR
897+
L3: POP_ITER
898898
LOAD_COMMON_CONSTANT 7 (None)
899899
RETURN_VALUE
900900
@@ -947,8 +947,8 @@ def loop_test():
947947
POP_TOP
948948
JUMP_BACKWARD_{: <6} 16 (to L1)
949949
950-
%3d L2: END_FOR
951-
POP_ITER
950+
%3d END_FOR
951+
L2: POP_ITER
952952
LOAD_COMMON_CONSTANT 7 (None)
953953
RETURN_VALUE
954954
""" % (loop_test.__code__.co_firstlineno,
@@ -1858,7 +1858,7 @@ def _prepare_test_cases():
18581858
make_inst(opname='LOAD_SMALL_INT', arg=10, argval=10, argrepr='', offset=14, start_offset=14, starts_line=False, line_number=3),
18591859
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')]),
18601860
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')]),
1861-
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')]),
1861+
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')]),
18621862
make_inst(opname='STORE_FAST', arg=0, argval='i', argrepr='i', offset=32, start_offset=32, starts_line=False, line_number=3),
18631863
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')]),
18641864
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():
18791879
make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=92, start_offset=92, starts_line=True, line_number=8, label=3),
18801880
make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=94, start_offset=94, starts_line=False, line_number=8),
18811881
make_inst(opname='JUMP_FORWARD', arg=13, argval=124, argrepr='to L5', offset=96, start_offset=96, starts_line=False, line_number=8),
1882-
make_inst(opname='END_FOR', arg=None, argval=None, argrepr='', offset=98, start_offset=98, starts_line=True, line_number=3, label=4),
1883-
make_inst(opname='POP_ITER', arg=None, argval=None, argrepr='', offset=100, start_offset=100, starts_line=False, line_number=3),
1882+
make_inst(opname='END_FOR', arg=None, argval=None, argrepr='', offset=98, start_offset=98, starts_line=True, line_number=3),
1883+
make_inst(opname='POP_ITER', arg=None, argval=None, argrepr='', offset=100, start_offset=100, starts_line=False, line_number=3, label=4),
18841884
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')]),
18851885
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),
18861886
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):
22002200
positions=None)
22012201
self.assertEqual(10 + 2 + 1*2 + 100*2, instruction.jump_target)
22022202

2203+
# FOR_ITER uses JUMPBY(oparg + 1) at runtime, skipping END_FOR to land
2204+
# on POP_ITER; the reported target must reflect the extra +2 bytes.
2205+
instruction = make_inst(opname="FOR_ITER", arg=delta, argval=delta,
2206+
argrepr='', offset=10, start_offset=10, starts_line=True, line_number=1, label=None,
2207+
positions=None)
2208+
self.assertEqual(10 + 2 + 1*2 + 100*2 + 2, instruction.jump_target)
2209+
22032210
def test_argval_argrepr(self):
22042211
def f(opcode, oparg, offset, *init_args):
22052212
arg_resolver = dis.ArgResolver(*init_args)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix :func:`dis.get_instructions` reporting the wrong ``jump_target`` for
2+
:opcode:`FOR_ITER`. The exhausted-iterator path now correctly points to
3+
:opcode:`POP_ITER` rather than :opcode:`END_FOR`, matching the runtime
4+
behaviour of ``JUMPBY(oparg + 1)``.

0 commit comments

Comments
 (0)