Skip to content

Commit ef61c52

Browse files
bpo-37830: Fix compilation of break and continue in finally. (GH-15320)
Fix compilation of "break" and "continue" in the "finally" block when the corresponding "try" block contains "return" with a non-constant value.
1 parent e9c90aa commit ef61c52

File tree

11 files changed

+305
-194
lines changed

11 files changed

+305
-194
lines changed

Lib/importlib/_bootstrap_external.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ def _write_atomic(path, data, mode=0o666):
270270
# comprehensions #35224)
271271
# Python 3.8b2 3412 (Swap the position of positional args and positional
272272
# only args in ast.arguments #37593)
273+
# Python 3.8b4 3413 (Fix "break" and "continue" in "finally" #37830)
273274
#
274275
# MAGIC must change whenever the bytecode emitted by the compiler may no
275276
# longer be understood by older implementations of the eval loop (usually
@@ -278,7 +279,7 @@ def _write_atomic(path, data, mode=0o666):
278279
# Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array
279280
# in PC/launcher.c must also be updated.
280281

281-
MAGIC_NUMBER = (3412).to_bytes(2, 'little') + b'\r\n'
282+
MAGIC_NUMBER = (3413).to_bytes(2, 'little') + b'\r\n'
282283
_RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c
283284

284285
_PYCACHE = '__pycache__'

Lib/test/test_dis.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -980,7 +980,7 @@ def jumpy():
980980
Instruction(opname='SETUP_FINALLY', opcode=122, arg=70, argval=174, argrepr='to 174', offset=102, starts_line=20, is_jump_target=True),
981981
Instruction(opname='SETUP_FINALLY', opcode=122, arg=12, argval=118, argrepr='to 118', offset=104, starts_line=None, is_jump_target=False),
982982
Instruction(opname='LOAD_CONST', opcode=100, arg=5, argval=1, argrepr='1', offset=106, starts_line=21, is_jump_target=False),
983-
Instruction(opname='LOAD_CONST', opcode=100, arg=7, argval=0, argrepr='0', offset=108, starts_line=None, is_jump_target=False),
983+
Instruction(opname='LOAD_CONST', opcode=100, arg=8, argval=0, argrepr='0', offset=108, starts_line=None, is_jump_target=False),
984984
Instruction(opname='BINARY_TRUE_DIVIDE', opcode=27, arg=None, argval=None, argrepr='', offset=110, starts_line=None, is_jump_target=False),
985985
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=112, starts_line=None, is_jump_target=False),
986986
Instruction(opname='POP_BLOCK', opcode=87, arg=None, argval=None, argrepr='', offset=114, starts_line=None, is_jump_target=False),
@@ -993,7 +993,7 @@ def jumpy():
993993
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=128, starts_line=None, is_jump_target=False),
994994
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=130, starts_line=None, is_jump_target=False),
995995
Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=132, starts_line=23, is_jump_target=False),
996-
Instruction(opname='LOAD_CONST', opcode=100, arg=8, argval='Here we go, here we go, here we go...', argrepr="'Here we go, here we go, here we go...'", offset=134, starts_line=None, is_jump_target=False),
996+
Instruction(opname='LOAD_CONST', opcode=100, arg=9, argval='Here we go, here we go, here we go...', argrepr="'Here we go, here we go, here we go...'", offset=134, starts_line=None, is_jump_target=False),
997997
Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=136, starts_line=None, is_jump_target=False),
998998
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=138, starts_line=None, is_jump_target=False),
999999
Instruction(opname='POP_EXCEPT', opcode=89, arg=None, argval=None, argrepr='', offset=140, starts_line=None, is_jump_target=False),
@@ -1003,7 +1003,7 @@ def jumpy():
10031003
Instruction(opname='SETUP_WITH', opcode=143, arg=14, argval=164, argrepr='to 164', offset=148, starts_line=None, is_jump_target=False),
10041004
Instruction(opname='STORE_FAST', opcode=125, arg=1, argval='dodgy', argrepr='dodgy', offset=150, starts_line=None, is_jump_target=False),
10051005
Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=152, starts_line=26, is_jump_target=False),
1006-
Instruction(opname='LOAD_CONST', opcode=100, arg=9, argval='Never reach this', argrepr="'Never reach this'", offset=154, starts_line=None, is_jump_target=False),
1006+
Instruction(opname='LOAD_CONST', opcode=100, arg=10, argval='Never reach this', argrepr="'Never reach this'", offset=154, starts_line=None, is_jump_target=False),
10071007
Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=156, starts_line=None, is_jump_target=False),
10081008
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=158, starts_line=None, is_jump_target=False),
10091009
Instruction(opname='POP_BLOCK', opcode=87, arg=None, argval=None, argrepr='', offset=160, starts_line=None, is_jump_target=False),
@@ -1014,7 +1014,7 @@ def jumpy():
10141014
Instruction(opname='POP_BLOCK', opcode=87, arg=None, argval=None, argrepr='', offset=170, starts_line=None, is_jump_target=True),
10151015
Instruction(opname='BEGIN_FINALLY', opcode=53, arg=None, argval=None, argrepr='', offset=172, starts_line=None, is_jump_target=False),
10161016
Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=174, starts_line=28, is_jump_target=True),
1017-
Instruction(opname='LOAD_CONST', opcode=100, arg=10, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=176, starts_line=None, is_jump_target=False),
1017+
Instruction(opname='LOAD_CONST', opcode=100, arg=7, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=176, starts_line=None, is_jump_target=False),
10181018
Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=178, starts_line=None, is_jump_target=False),
10191019
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=180, starts_line=None, is_jump_target=False),
10201020
Instruction(opname='END_FINALLY', opcode=88, arg=None, argval=None, argrepr='', offset=182, starts_line=None, is_jump_target=False),

Lib/test/test_grammar.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -991,6 +991,60 @@ def g3():
991991
return 4
992992
self.assertEqual(g3(), 4)
993993

994+
def test_break_in_finally_after_return(self):
995+
# See issue #37830
996+
def g1(x):
997+
for count in [0, 1]:
998+
count2 = 0
999+
while count2 < 20:
1000+
count2 += 10
1001+
try:
1002+
return count + count2
1003+
finally:
1004+
if x:
1005+
break
1006+
return 'end', count, count2
1007+
self.assertEqual(g1(False), 10)
1008+
self.assertEqual(g1(True), ('end', 1, 10))
1009+
1010+
def g2(x):
1011+
for count in [0, 1]:
1012+
for count2 in [10, 20]:
1013+
try:
1014+
return count + count2
1015+
finally:
1016+
if x:
1017+
break
1018+
return 'end', count, count2
1019+
self.assertEqual(g2(False), 10)
1020+
self.assertEqual(g2(True), ('end', 1, 10))
1021+
1022+
def test_continue_in_finally_after_return(self):
1023+
# See issue #37830
1024+
def g1(x):
1025+
count = 0
1026+
while count < 100:
1027+
count += 1
1028+
try:
1029+
return count
1030+
finally:
1031+
if x:
1032+
continue
1033+
return 'end', count
1034+
self.assertEqual(g1(False), 1)
1035+
self.assertEqual(g1(True), ('end', 100))
1036+
1037+
def g2(x):
1038+
for count in [0, 1]:
1039+
try:
1040+
return count
1041+
finally:
1042+
if x:
1043+
continue
1044+
return 'end', count
1045+
self.assertEqual(g2(False), 0)
1046+
self.assertEqual(g2(True), ('end', 1))
1047+
9941048
def test_yield(self):
9951049
# Allowed as standalone statement
9961050
def g(): yield 1

Lib/test/test_importlib/test_util.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -861,7 +861,7 @@ def test_magic_number(self):
861861
in advance. Such exceptional releases will then require an
862862
adjustment to this test case.
863863
"""
864-
EXPECTED_MAGIC_NUMBER = 3410
864+
EXPECTED_MAGIC_NUMBER = 3413
865865
actual = int.from_bytes(importlib.util.MAGIC_NUMBER[:2], 'little')
866866

867867
msg = (
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fixed compilation of :keyword:`break` and :keyword:`continue` in the
2+
:keyword:`finally` block when the corresponding :keyword:`try` block
3+
contains :keyword:`return` with a non-constant value.

Objects/frameobject.c

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno, void *Py_UNUSED(ignore
233233
* the 'finally' blocks. */
234234
memset(blockstack, '\0', sizeof(blockstack));
235235
blockstack_top = 0;
236+
unsigned char prevop = NOP;
236237
for (addr = 0; addr < code_len; addr += sizeof(_Py_CODEUNIT)) {
237238
unsigned char op = code[addr];
238239
switch (op) {
@@ -259,17 +260,24 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno, void *Py_UNUSED(ignore
259260
"can't jump into the middle of a block");
260261
return -1;
261262
}
263+
int in_for_loop = op == FOR_ITER || code[target_addr] == END_ASYNC_FOR;
262264
if (first_in && !second_in) {
263-
if (op != FOR_ITER && code[target_addr] != END_ASYNC_FOR) {
264-
delta_iblock++;
265+
if (!delta_iblock) {
266+
if (in_for_loop) {
267+
/* Pop the iterators of any 'for' and 'async for' loop
268+
* we're jumping out of. */
269+
delta++;
270+
}
271+
else if (prevop == LOAD_CONST) {
272+
/* Pops None pushed before SETUP_FINALLY. */
273+
delta++;
274+
}
265275
}
266-
else if (!delta_iblock) {
267-
/* Pop the iterators of any 'for' and 'async for' loop
268-
* we're jumping out of. */
269-
delta++;
276+
if (!in_for_loop) {
277+
delta_iblock++;
270278
}
271279
}
272-
if (op != FOR_ITER && code[target_addr] != END_ASYNC_FOR) {
280+
if (!in_for_loop) {
273281
blockstack[blockstack_top++] = target_addr;
274282
}
275283
break;
@@ -293,6 +301,7 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno, void *Py_UNUSED(ignore
293301
break;
294302
}
295303
}
304+
prevop = op;
296305
}
297306

298307
/* Verify that the blockstack tracking code didn't get lost. */

PC/launcher.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1139,7 +1139,7 @@ static PYC_MAGIC magic_values[] = {
11391139
{ 3320, 3351, L"3.5" },
11401140
{ 3360, 3379, L"3.6" },
11411141
{ 3390, 3399, L"3.7" },
1142-
{ 3400, 3410, L"3.8" },
1142+
{ 3400, 3419, L"3.8" },
11431143
{ 0 }
11441144
};
11451145

Python/compile.c

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ It's called a frame block to distinguish it from a basic block in the
8181
compiler IR.
8282
*/
8383

84-
enum fblocktype { WHILE_LOOP, FOR_LOOP, EXCEPT, FINALLY_TRY, FINALLY_END,
84+
enum fblocktype { WHILE_LOOP, FOR_LOOP, EXCEPT, FINALLY_TRY, FINALLY_TRY2, FINALLY_END,
8585
WITH, ASYNC_WITH, HANDLER_CLEANUP };
8686

8787
struct fblockinfo {
@@ -1664,7 +1664,12 @@ compiler_unwind_fblock(struct compiler *c, struct fblockinfo *info,
16641664
return 1;
16651665

16661666
case FINALLY_END:
1667+
info->fb_exit = NULL;
16671668
ADDOP_I(c, POP_FINALLY, preserve_tos);
1669+
if (preserve_tos) {
1670+
ADDOP(c, ROT_TWO);
1671+
}
1672+
ADDOP(c, POP_TOP);
16681673
return 1;
16691674

16701675
case FOR_LOOP:
@@ -1684,6 +1689,19 @@ compiler_unwind_fblock(struct compiler *c, struct fblockinfo *info,
16841689
ADDOP_JREL(c, CALL_FINALLY, info->fb_exit);
16851690
return 1;
16861691

1692+
case FINALLY_TRY2:
1693+
ADDOP(c, POP_BLOCK);
1694+
if (preserve_tos) {
1695+
ADDOP(c, ROT_TWO);
1696+
ADDOP(c, POP_TOP);
1697+
ADDOP_JREL(c, CALL_FINALLY, info->fb_exit);
1698+
}
1699+
else {
1700+
ADDOP_JREL(c, CALL_FINALLY, info->fb_exit);
1701+
ADDOP(c, POP_TOP);
1702+
}
1703+
return 1;
1704+
16871705
case WITH:
16881706
case ASYNC_WITH:
16891707
ADDOP(c, POP_BLOCK);
@@ -2869,17 +2887,47 @@ compiler_continue(struct compiler *c)
28692887
static int
28702888
compiler_try_finally(struct compiler *c, stmt_ty s)
28712889
{
2872-
basicblock *body, *end;
2890+
basicblock *start, *newcurblock, *body, *end;
2891+
int break_finally = 1;
28732892

28742893
body = compiler_new_block(c);
28752894
end = compiler_new_block(c);
28762895
if (body == NULL || end == NULL)
28772896
return 0;
28782897

2898+
start = c->u->u_curblock;
2899+
2900+
/* `finally` block. Compile it first to determine if any of "break",
2901+
"continue" or "return" are used in it. */
2902+
compiler_use_next_block(c, end);
2903+
if (!compiler_push_fblock(c, FINALLY_END, end, end))
2904+
return 0;
2905+
VISIT_SEQ(c, stmt, s->v.Try.finalbody);
2906+
ADDOP(c, END_FINALLY);
2907+
break_finally = (c->u->u_fblock[c->u->u_nfblocks - 1].fb_exit == NULL);
2908+
if (break_finally) {
2909+
/* Pops a placeholder. See below */
2910+
ADDOP(c, POP_TOP);
2911+
}
2912+
compiler_pop_fblock(c, FINALLY_END, end);
2913+
2914+
newcurblock = c->u->u_curblock;
2915+
c->u->u_curblock = start;
2916+
start->b_next = NULL;
2917+
28792918
/* `try` block */
2919+
c->u->u_lineno_set = 0;
2920+
c->u->u_lineno = s->lineno;
2921+
c->u->u_col_offset = s->col_offset;
2922+
if (break_finally) {
2923+
/* Pushes a placeholder for the value of "return" in the "try" block
2924+
to balance the stack for "break", "continue" and "return" in
2925+
the "finally" block. */
2926+
ADDOP_LOAD_CONST(c, Py_None);
2927+
}
28802928
ADDOP_JREL(c, SETUP_FINALLY, end);
28812929
compiler_use_next_block(c, body);
2882-
if (!compiler_push_fblock(c, FINALLY_TRY, body, end))
2930+
if (!compiler_push_fblock(c, break_finally ? FINALLY_TRY2 : FINALLY_TRY, body, end))
28832931
return 0;
28842932
if (s->v.Try.handlers && asdl_seq_LEN(s->v.Try.handlers)) {
28852933
if (!compiler_try_except(c, s))
@@ -2890,15 +2938,11 @@ compiler_try_finally(struct compiler *c, stmt_ty s)
28902938
}
28912939
ADDOP(c, POP_BLOCK);
28922940
ADDOP(c, BEGIN_FINALLY);
2893-
compiler_pop_fblock(c, FINALLY_TRY, body);
2941+
compiler_pop_fblock(c, break_finally ? FINALLY_TRY2 : FINALLY_TRY, body);
2942+
2943+
c->u->u_curblock->b_next = end;
2944+
c->u->u_curblock = newcurblock;
28942945

2895-
/* `finally` block */
2896-
compiler_use_next_block(c, end);
2897-
if (!compiler_push_fblock(c, FINALLY_END, end, NULL))
2898-
return 0;
2899-
VISIT_SEQ(c, stmt, s->v.Try.finalbody);
2900-
ADDOP(c, END_FINALLY);
2901-
compiler_pop_fblock(c, FINALLY_END, end);
29022946
return 1;
29032947
}
29042948

0 commit comments

Comments
 (0)