Skip to content

Commit f548176

Browse files
fix try except block bug
1 parent d3b7b93 commit f548176

File tree

3 files changed

+63
-0
lines changed

3 files changed

+63
-0
lines changed

Lib/test/test_compile.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1633,6 +1633,56 @@ def test_remove_redundant_nop_edge_case(self):
16331633
def f():
16341634
a if (1 if b else c) else d
16351635

1636+
def test_while_continue_try_except_exception_table(self):
1637+
# The try region's exception table must include the backward jump emitted
1638+
# for 'continue', so pending exceptions (e.g. KeyboardInterrupt) and
1639+
# tracing behave like other try-body instructions.
1640+
src = textwrap.dedent('''\
1641+
def f():
1642+
while True:
1643+
try:
1644+
continue
1645+
except KeyboardInterrupt:
1646+
break
1647+
''')
1648+
f_code = compile(src, '<string>', 'exec').co_consts[0]
1649+
jump_offsets = [
1650+
i.offset for i in dis.get_instructions(f_code)
1651+
if i.opname == 'JUMP_BACKWARD'
1652+
]
1653+
self.assertEqual(len(jump_offsets), 1)
1654+
joff = jump_offsets[0]
1655+
entries = dis._parse_exception_table(f_code)
1656+
self.assertTrue(
1657+
any(e.start <= joff < e.end for e in entries),
1658+
f'offset {joff} not covered by {entries!r}',
1659+
)
1660+
1661+
def test_try_literal_stmt_exception_table(self):
1662+
# Like try + continue, a try body that is only a literal statement must
1663+
# not leave the "invisible" result-discard outside the exception table.
1664+
src = textwrap.dedent('''\
1665+
def f():
1666+
try:
1667+
42
1668+
except:
1669+
pass
1670+
''')
1671+
f_code = compile(src, '<string>', 'exec').co_consts[0]
1672+
body_offsets = [
1673+
i.offset for i in dis.get_instructions(f_code)
1674+
if i.positions is not None
1675+
and i.positions.lineno == 3
1676+
and i.opname not in ('RESUME', 'NOP')
1677+
]
1678+
self.assertNotEqual(body_offsets, [])
1679+
entries = dis._parse_exception_table(f_code)
1680+
for off in body_offsets:
1681+
self.assertTrue(
1682+
any(e.start <= off < e.end for e in entries),
1683+
f'offset {off} not covered by {entries!r}',
1684+
)
1685+
16361686
def test_lineno_propagation_empty_blocks(self):
16371687
# Smoke test. See gh-138714.
16381688
def f():
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix exception table coverage for ``try`` blocks: pseudo ``SETUP_FINALLY`` and
2+
``POP_BLOCK`` instructions are now labeled with the active handler, so
3+
``continue`` inside ``try``/``except`` and other minimal try bodies handle
4+
pending exceptions and tracing like the rest of the protected region.

Python/flowgraph.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -932,8 +932,17 @@ label_exception_targets(basicblock *entryblock) {
932932
todo++;
933933
}
934934
handler = push_except_block(except_stack, instr);
935+
/* Exception coverage for this instruction must match the try
936+
* region it opens, so tracing and pending exceptions while
937+
* executing it are handled like the following protected
938+
* instructions. */
939+
instr->i_except = handler;
935940
}
936941
else if (instr->i_opcode == POP_BLOCK) {
942+
/* POP_BLOCK ends a protected region but must still be covered by
943+
* that region's handler until the pop completes (e.g. continue
944+
* inside try). */
945+
instr->i_except = except_stack_top(except_stack);
937946
handler = pop_except_block(except_stack);
938947
INSTR_SET_OP0(instr, NOP);
939948
}

0 commit comments

Comments
 (0)