diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index 60886b1a55c16f4..ff791d1b1d68bc5 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -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 jumps in a loop body, including the backward jump + # for the untaken branch of a conditional at the end of the 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.opcode in dis.hasjump] + 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 diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index 3e562a26ad586aa..9879a665bc05000 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -454,53 +454,52 @@ def foo(a: int, b: str) -> str: """ dis_traceback = """\ -%4d RESUME 0 +%4d RESUME 0 -%4d NOP +%4d NOP -%4d L1: LOAD_SMALL_INT 1 - LOAD_SMALL_INT 0 - --> BINARY_OP 11 (/) - POP_TOP +%4d L1: LOAD_SMALL_INT 1 + LOAD_SMALL_INT 0 + --> BINARY_OP 11 (/) + POP_TOP -%4d L2: LOAD_FAST_CHECK 1 (tb) - RETURN_VALUE +%4d L2: LOAD_FAST_CHECK 1 (tb) + RETURN_VALUE - -- L3: PUSH_EXC_INFO + -- L3: PUSH_EXC_INFO -%4d LOAD_GLOBAL 0 (Exception) - CHECK_EXC_MATCH - POP_JUMP_IF_FALSE 24 (to L9) - L4: NOT_TAKEN - L5: STORE_FAST 0 (e) +%4d LOAD_GLOBAL 0 (Exception) + CHECK_EXC_MATCH + POP_JUMP_IF_FALSE 24 (to L7) + NOT_TAKEN + STORE_FAST 0 (e) -%4d L6: LOAD_FAST 0 (e) - LOAD_ATTR 2 (__traceback__) - STORE_FAST 1 (tb) - L7: POP_EXCEPT - LOAD_COMMON_CONSTANT 7 (None) - STORE_FAST 0 (e) - DELETE_FAST 0 (e) +%4d L4: LOAD_FAST 0 (e) + LOAD_ATTR 2 (__traceback__) + STORE_FAST 1 (tb) + L5: POP_EXCEPT + LOAD_COMMON_CONSTANT 7 (None) + STORE_FAST 0 (e) + DELETE_FAST 0 (e) -%4d LOAD_FAST 1 (tb) - RETURN_VALUE +%4d LOAD_FAST 1 (tb) + RETURN_VALUE - -- L8: LOAD_COMMON_CONSTANT 7 (None) - STORE_FAST 0 (e) - DELETE_FAST 0 (e) - RERAISE 1 + -- L6: LOAD_COMMON_CONSTANT 7 (None) + STORE_FAST 0 (e) + DELETE_FAST 0 (e) + RERAISE 1 -%4d L9: RERAISE 0 +%4d L7: RERAISE 0 - -- L10: COPY 3 - POP_EXCEPT - RERAISE 1 + -- L8: COPY 3 + POP_EXCEPT + RERAISE 1 ExceptionTable: L1 to L2 -> L3 [0] - L3 to L4 -> L10 [1] lasti - L5 to L6 -> L10 [1] lasti - L6 to L7 -> L8 [1] lasti - L8 to L10 -> L10 [1] lasti + L3 to L4 -> L8 [1] lasti + L4 to L5 -> L6 [1] lasti + L6 to L8 -> L8 [1] lasti """ % (TRACEBACK_CODE.co_firstlineno, TRACEBACK_CODE.co_firstlineno + 1, TRACEBACK_CODE.co_firstlineno + 2, @@ -569,11 +568,11 @@ def _with(c): %4d L3: PUSH_EXC_INFO WITH_EXCEPT_START TO_BOOL - POP_JUMP_IF_TRUE 2 (to L6) - L4: NOT_TAKEN - L5: RERAISE 2 - L6: POP_TOP - L7: POP_EXCEPT + POP_JUMP_IF_TRUE 2 (to L4) + NOT_TAKEN + RERAISE 2 + L4: POP_TOP + L5: POP_EXCEPT POP_TOP POP_TOP POP_TOP @@ -583,13 +582,12 @@ def _with(c): LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE - -- L8: COPY 3 + -- L6: COPY 3 POP_EXCEPT RERAISE 1 ExceptionTable: L1 to L2 -> L3 [2] lasti - L3 to L4 -> L8 [4] lasti - L5 to L7 -> L8 [4] lasti + L3 to L5 -> L6 [4] lasti """ % (_with.__code__.co_firstlineno, _with.__code__.co_firstlineno + 1, _with.__code__.co_firstlineno + 2, @@ -664,11 +662,11 @@ async def _asyncwith(c): L20: CLEANUP_THROW L21: END_SEND TO_BOOL - POP_JUMP_IF_TRUE 2 (to L24) - L22: NOT_TAKEN - L23: RERAISE 2 - L24: POP_TOP - L25: POP_EXCEPT + POP_JUMP_IF_TRUE 2 (to L22) + NOT_TAKEN + RERAISE 2 + L22: POP_TOP + L23: POP_EXCEPT POP_TOP POP_TOP POP_TOP @@ -678,25 +676,24 @@ async def _asyncwith(c): LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE - -- L26: COPY 3 + -- L24: COPY 3 POP_EXCEPT RERAISE 1 - L27: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + L25: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) RERAISE 1 ExceptionTable: - L1 to L3 -> L27 [0] lasti + L1 to L3 -> L25 [0] lasti L3 to L4 -> L12 [5] - L4 to L6 -> L27 [0] lasti + L4 to L6 -> L25 [0] lasti L6 to L7 -> L16 [2] lasti - L7 to L9 -> L27 [0] lasti + L7 to L9 -> L25 [0] lasti L9 to L10 -> L14 [3] - L10 to L13 -> L27 [0] lasti - L14 to L15 -> L27 [0] lasti - L16 to L18 -> L26 [4] lasti + L10 to L13 -> L25 [0] lasti + L14 to L15 -> L25 [0] lasti + L16 to L18 -> L24 [4] lasti L18 to L19 -> L20 [8] - L19 to L22 -> L26 [4] lasti - L23 to L25 -> L26 [4] lasti - L25 to L27 -> L27 [0] lasti + L19 to L23 -> L24 [4] lasti + L23 to L25 -> L25 [0] lasti """ % (_asyncwith.__code__.co_firstlineno, _asyncwith.__code__.co_firstlineno + 1, _asyncwith.__code__.co_firstlineno + 2, diff --git a/Lib/test/test_monitoring.py b/Lib/test/test_monitoring.py index 5c2d69934b02ea6..d1b2a4380be806d 100644 --- a/Lib/test/test_monitoring.py +++ b/Lib/test/test_monitoring.py @@ -2529,6 +2529,37 @@ def foo(): foo() sys.monitoring.set_local_events(0, foo.__code__, 0) + def test_130279(self): + # gh-130279 + # An exception raised by the BRANCH_LEFT callback of a forward + # branch must be caught by the enclosing try + class Boom(Exception): + pass + + raised = False + + def callback(code, src, dst): + nonlocal raised + if not raised: + raised = True + raise Boom + + def foo(x): + try: + if x: + pass + except Boom: + return "caught" + return "not raised" + + sys.monitoring.register_callback(TEST_TOOL, E.BRANCH_LEFT, callback) + sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, E.BRANCH_LEFT) + try: + self.assertEqual(foo(True), "caught") + finally: + sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0) + sys.monitoring.register_callback(TEST_TOOL, E.BRANCH_LEFT, None) + class TestOptimizer(MonitoringTestBase, unittest.TestCase): diff --git a/Misc/ACKS b/Misc/ACKS index ad1b1119aa243a8..00ed03ff51c98fb 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1144,6 +1144,7 @@ Ivan Levkivskyi Ben Lewis William Lewis Akira Li +Han Li Jiahao Li Robert Li Xuanji Li diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-10-15-12-36-09.gh-issue-130279.N9N-sw.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-15-12-36-09.gh-issue-130279.N9N-sw.rst new file mode 100644 index 000000000000000..996eb7020f53449 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-15-12-36-09.gh-issue-130279.N9N-sw.rst @@ -0,0 +1,2 @@ +Fix bug where compiler did not set the right exception handler for some jump +instructions. diff --git a/Python/flowgraph.c b/Python/flowgraph.c index a5138d1a1fa2846..17489ae5bfb5252 100644 --- a/Python/flowgraph.c +++ b/Python/flowgraph.c @@ -188,7 +188,8 @@ cfg_builder_new_block(cfg_builder *g) } static int -basicblock_addop(basicblock *b, int opcode, int oparg, location loc) +basicblock_addop(basicblock *b, int opcode, int oparg, location loc, + basicblock *except) { assert(IS_WITHIN_OPCODE_RANGE(opcode)); assert(!IS_ASSEMBLER_OPCODE(opcode)); @@ -203,15 +204,16 @@ basicblock_addop(basicblock *b, int opcode, int oparg, location loc) i->i_opcode = opcode; i->i_oparg = oparg; i->i_loc = loc; + i->i_except = except; // memory is already zero initialized assert(i->i_target == NULL); - assert(i->i_except == NULL); return SUCCESS; } static int -basicblock_add_jump(basicblock *b, int opcode, basicblock *target, location loc) +basicblock_add_jump(basicblock *b, int opcode, basicblock *target, location loc, + basicblock *except) { cfg_instr *last = basicblock_last_instr(b); if (last && is_jump(last)) { @@ -219,7 +221,7 @@ basicblock_add_jump(basicblock *b, int opcode, basicblock *target, location loc) } RETURN_IF_ERROR( - basicblock_addop(b, opcode, target->b_label.id, loc)); + basicblock_addop(b, opcode, target->b_label.id, loc, except)); last = basicblock_last_instr(b); assert(last && last->i_opcode == opcode); last->i_target = target; @@ -493,7 +495,7 @@ int _PyCfgBuilder_Addop(cfg_builder *g, int opcode, int oparg, location loc) { RETURN_IF_ERROR(cfg_builder_maybe_start_new_block(g)); - return basicblock_addop(g->g_curblock, opcode, oparg, loc); + return basicblock_addop(g->g_curblock, opcode, oparg, loc, NULL); } @@ -554,7 +556,7 @@ normalize_jumps_in_block(cfg_builder *g, basicblock *b) { bool is_forward = last->i_target->b_visited == 0; if (is_forward) { RETURN_IF_ERROR( - basicblock_addop(b, NOT_TAKEN, 0, last->i_loc)); + basicblock_addop(b, NOT_TAKEN, 0, last->i_loc, last->i_except)); return SUCCESS; } @@ -583,9 +585,11 @@ normalize_jumps_in_block(cfg_builder *g, basicblock *b) { return ERROR; } RETURN_IF_ERROR( - basicblock_addop(backwards_jump, NOT_TAKEN, 0, last->i_loc)); + basicblock_addop(backwards_jump, NOT_TAKEN, 0, last->i_loc, + last->i_except)); RETURN_IF_ERROR( - basicblock_add_jump(backwards_jump, JUMP, target, last->i_loc)); + basicblock_add_jump(backwards_jump, JUMP, target, last->i_loc, + last->i_except)); backwards_jump->b_startdepth = target->b_startdepth; last->i_opcode = reversed_opcode; last->i_target = b->b_next; @@ -1293,7 +1297,8 @@ jump_thread(basicblock *bb, cfg_instr *inst, cfg_instr *target, int opcode) RETURN_IF_ERROR( basicblock_add_jump( - bb, opcode, target->i_target, target->i_loc)); + bb, opcode, target->i_target, target->i_loc, + target->i_except)); return true; } @@ -3547,7 +3552,7 @@ push_cold_blocks_to_end(cfg_builder *g) { b->b_next->b_label.id = next_lbl++; } basicblock_addop(explicit_jump, JUMP_NO_INTERRUPT, b->b_next->b_label.id, - NO_LOCATION); + NO_LOCATION, NULL); explicit_jump->b_cold = 1; explicit_jump->b_next = b->b_next; explicit_jump->b_predecessors = 1;