Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions Lib/test/test_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -1583,6 +1583,28 @@ def f():

self.check_line_numbers(f.__code__, 'JUMP_BACKWARD')

def test_backward_jump_conditional_in_loop_in_try(self):
# gh-130279: The backward jump for the untaken branch of a
# conditional at the end of a loop body must be covered by the
# exception handler of the enclosing try.
def f(items):
try:
for x in items:
if x < 0:
raise ValueError(x)
except KeyboardInterrupt:
return 'handled'

bytecode = dis.Bytecode(f)
jumps = [instr for instr in bytecode
if instr.opname == 'JUMP_BACKWARD']
self.assertTrue(jumps)
for instr in jumps:
with self.subTest(instr=instr):
self.assertTrue(
any(entry.start <= instr.offset < entry.end
for entry in bytecode.exception_entries))

def test_big_dict_literal(self):
# The compiler has a flushing point in "compiler_dict" that calls compiles
# a portion of the dictionary literal when the loop that iterates over the items
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Fix exceptions raised at the backward jump of a loop (such as
:exc:`KeyboardInterrupt`) skipping the enclosing :keyword:`try` or
:keyword:`with` statement when the loop body ends with an :keyword:`if`
statement whose condition is false.
10 changes: 10 additions & 0 deletions Python/flowgraph.c
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,13 @@ normalize_jumps_in_block(cfg_builder *g, basicblock *b) {
basicblock_addop(backwards_jump, NOT_TAKEN, 0, last->i_loc));
RETURN_IF_ERROR(
basicblock_add_jump(backwards_jump, JUMP, target, last->i_loc));
/* The new instructions are covered by the same exception handler as
* the conditional jump, since the backward jump can raise when it
* checks the eval breaker.
*/
for (int i = 0; i < backwards_jump->b_iused; i++) {
backwards_jump->b_instr[i].i_except = last->i_except;
}
backwards_jump->b_startdepth = target->b_startdepth;
last->i_opcode = reversed_opcode;
last->i_target = b->b_next;
Expand Down Expand Up @@ -1288,12 +1295,15 @@ jump_thread(basicblock *bb, cfg_instr *inst, cfg_instr *target, int opcode)
if (inst->i_target != target->i_target) {
/* Change inst to NOP and append a jump to target->i_target. The
* NOP will be removed later if it's not needed for the lineno.
* The new jump keeps the exception handler of inst.
*/
basicblock *except = inst->i_except;
INSTR_SET_OP0(inst, NOP);

RETURN_IF_ERROR(
basicblock_add_jump(
bb, opcode, target->i_target, target->i_loc));
basicblock_last_instr(bb)->i_except = except;

return true;
}
Expand Down
Loading