Skip to content

Commit b979c5a

Browse files
committed
py/compile: Fix potential Py-stack overflow in try-finally with return.
If a return is executed within the try block of a try-finally then the return value is stored on the top of the Python stack during the execution of the finally block. In this case the Python stack is one larger than it normally would be in the finally block. Prior to this commit, the compiler was not taking this case into account and could have a Python stack overflow if the Python stack used by the finally block was more than that used elsewhere in the function. In such a scenario the last argument of the function would be clobbered by the top-most temporary value used in the deepest Python expression/statement. This commit fixes that case by making sure enough Python stack is allocated to the function. Fixes issue micropython#13562. Signed-off-by: Damien George <[email protected]>
1 parent f53ee9f commit b979c5a

File tree

2 files changed

+21
-0
lines changed

2 files changed

+21
-0
lines changed

py/compile.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1650,9 +1650,11 @@ STATIC void compile_try_except(compiler_t *comp, mp_parse_node_t pn_body, int n_
16501650
if (qstr_exception_local != 0) {
16511651
EMIT_ARG(load_const_tok, MP_TOKEN_KW_NONE);
16521652
EMIT_ARG(label_assign, l3);
1653+
EMIT_ARG(adjust_stack_size, 1); // stack adjust for possible return value
16531654
EMIT_ARG(load_const_tok, MP_TOKEN_KW_NONE);
16541655
compile_store_id(comp, qstr_exception_local);
16551656
compile_delete_id(comp, qstr_exception_local);
1657+
EMIT_ARG(adjust_stack_size, -1);
16561658
compile_decrease_except_level(comp);
16571659
}
16581660

@@ -1682,9 +1684,18 @@ STATIC void compile_try_finally(compiler_t *comp, mp_parse_node_t pn_body, int n
16821684
} else {
16831685
compile_try_except(comp, pn_body, n_except, pn_except, pn_else);
16841686
}
1687+
1688+
// If the code reaches this point then the try part of the try-finally exited normally.
1689+
// This is indicated to the runtime by None sitting on the stack.
16851690
EMIT_ARG(load_const_tok, MP_TOKEN_KW_NONE);
1691+
1692+
// Compile the finally block.
1693+
// The stack needs to be adjusted by 1 to account for the possibility that the finally is
1694+
// being executed as part of a return, and the return value is on the top of the stack.
16861695
EMIT_ARG(label_assign, l_finally_block);
1696+
EMIT_ARG(adjust_stack_size, 1);
16871697
compile_node(comp, pn_finally);
1698+
EMIT_ARG(adjust_stack_size, -1);
16881699

16891700
compile_decrease_except_level(comp);
16901701
}

tests/basics/try_finally_return.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,13 @@ def f():
7070
finally:
7171
print('finally 1')
7272
print(f())
73+
74+
# the finally block uses a lot of Python stack and then a local is accessed
75+
# (tests that the use of the stack doesn't clobber the local)
76+
def f(x):
77+
try:
78+
return x
79+
finally:
80+
print(2, 3, 4, 5, 6)
81+
print(x)
82+
print(f(1))

0 commit comments

Comments
 (0)