https://github.com/python/cpython/commit/036d309cf542986a39eeba5ca805b2ec7619731d
commit: 036d309cf542986a39eeba5ca805b2ec7619731d
branch: main
author: Han <[email protected]>
committer: iritkatriel <[email protected]>
date: 2026-10-19T09:10:15+01:00
summary:
gh-130279: Fix missing exception handlers for jumps in loops (#159460)
files:
A
Misc/NEWS.d/next/Core_and_Builtins/2026-10-15-12-36-09.gh-issue-130279.N9N-sw.rst
M Lib/test/test_compile.py
M Lib/test/test_dis.py
M Lib/test/test_monitoring.py
M Misc/ACKS
M Python/flowgraph.c
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;
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]