Skip to content

Commit d4864c6

Browse files
bpo-24340: Fix estimation of the code stack size. (#5076)
1 parent b4ebaa7 commit d4864c6

File tree

6 files changed

+1504
-1161
lines changed

6 files changed

+1504
-1161
lines changed

Lib/test/test_compile.py

Lines changed: 290 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -672,7 +672,7 @@ def __fspath__(self):
672672
compile("42", PathLike("test_compile_pathlike"), "single")
673673

674674

675-
class TestStackSize(unittest.TestCase):
675+
class TestExpressionStackSize(unittest.TestCase):
676676
# These tests check that the computed stack size for a code object
677677
# stays within reasonable bounds (see issue #21523 for an example
678678
# dysfunction).
@@ -710,5 +710,294 @@ def test_func_and(self):
710710
self.check_stack_size(code)
711711

712712

713+
class TestStackSizeStability(unittest.TestCase):
714+
# Check that repeating certain snippets doesn't increase the stack size
715+
# beyond what a single snippet requires.
716+
717+
def check_stack_size(self, snippet, async_=False):
718+
def compile_snippet(i):
719+
ns = {}
720+
script = """def func():\n""" + i * snippet
721+
if async_:
722+
script = "async " + script
723+
code = compile(script, "<script>", "exec")
724+
exec(code, ns, ns)
725+
return ns['func'].__code__
726+
727+
sizes = [compile_snippet(i).co_stacksize for i in range(2, 5)]
728+
if len(set(sizes)) != 1:
729+
import dis, io
730+
out = io.StringIO()
731+
dis.dis(compile_snippet(1), file=out)
732+
self.fail("stack sizes diverge with # of consecutive snippets: "
733+
"%s\n%s\n%s" % (sizes, snippet, out.getvalue()))
734+
735+
def test_if(self):
736+
snippet = """
737+
if x:
738+
a
739+
"""
740+
self.check_stack_size(snippet)
741+
742+
def test_if_else(self):
743+
snippet = """
744+
if x:
745+
a
746+
elif y:
747+
b
748+
else:
749+
c
750+
"""
751+
self.check_stack_size(snippet)
752+
753+
def test_try_except_bare(self):
754+
snippet = """
755+
try:
756+
a
757+
except:
758+
b
759+
"""
760+
self.check_stack_size(snippet)
761+
762+
def test_try_except_qualified(self):
763+
snippet = """
764+
try:
765+
a
766+
except ImportError:
767+
b
768+
except:
769+
c
770+
else:
771+
d
772+
"""
773+
self.check_stack_size(snippet)
774+
775+
def test_try_except_as(self):
776+
snippet = """
777+
try:
778+
a
779+
except ImportError as e:
780+
b
781+
except:
782+
c
783+
else:
784+
d
785+
"""
786+
self.check_stack_size(snippet)
787+
788+
def test_try_finally(self):
789+
snippet = """
790+
try:
791+
a
792+
finally:
793+
b
794+
"""
795+
self.check_stack_size(snippet)
796+
797+
def test_with(self):
798+
snippet = """
799+
with x as y:
800+
a
801+
"""
802+
self.check_stack_size(snippet)
803+
804+
def test_while_else(self):
805+
snippet = """
806+
while x:
807+
a
808+
else:
809+
b
810+
"""
811+
self.check_stack_size(snippet)
812+
813+
def test_for(self):
814+
snippet = """
815+
for x in y:
816+
a
817+
"""
818+
self.check_stack_size(snippet)
819+
820+
def test_for_else(self):
821+
snippet = """
822+
for x in y:
823+
a
824+
else:
825+
b
826+
"""
827+
self.check_stack_size(snippet)
828+
829+
def test_for_break_continue(self):
830+
snippet = """
831+
for x in y:
832+
if z:
833+
break
834+
elif u:
835+
continue
836+
else:
837+
a
838+
else:
839+
b
840+
"""
841+
self.check_stack_size(snippet)
842+
843+
def test_for_break_continue_inside_try_finally_block(self):
844+
snippet = """
845+
for x in y:
846+
try:
847+
if z:
848+
break
849+
elif u:
850+
continue
851+
else:
852+
a
853+
finally:
854+
f
855+
else:
856+
b
857+
"""
858+
self.check_stack_size(snippet)
859+
860+
def test_for_break_inside_finally_block(self):
861+
snippet = """
862+
for x in y:
863+
try:
864+
t
865+
finally:
866+
if z:
867+
break
868+
else:
869+
a
870+
else:
871+
b
872+
"""
873+
self.check_stack_size(snippet)
874+
875+
def test_for_break_continue_inside_except_block(self):
876+
snippet = """
877+
for x in y:
878+
try:
879+
t
880+
except:
881+
if z:
882+
break
883+
elif u:
884+
continue
885+
else:
886+
a
887+
else:
888+
b
889+
"""
890+
self.check_stack_size(snippet)
891+
892+
def test_for_break_continue_inside_with_block(self):
893+
snippet = """
894+
for x in y:
895+
with c:
896+
if z:
897+
break
898+
elif u:
899+
continue
900+
else:
901+
a
902+
else:
903+
b
904+
"""
905+
self.check_stack_size(snippet)
906+
907+
def test_return_inside_try_finally_block(self):
908+
snippet = """
909+
try:
910+
if z:
911+
return
912+
else:
913+
a
914+
finally:
915+
f
916+
"""
917+
self.check_stack_size(snippet)
918+
919+
def test_return_inside_finally_block(self):
920+
snippet = """
921+
try:
922+
t
923+
finally:
924+
if z:
925+
return
926+
else:
927+
a
928+
"""
929+
self.check_stack_size(snippet)
930+
931+
def test_return_inside_except_block(self):
932+
snippet = """
933+
try:
934+
t
935+
except:
936+
if z:
937+
return
938+
else:
939+
a
940+
"""
941+
self.check_stack_size(snippet)
942+
943+
def test_return_inside_with_block(self):
944+
snippet = """
945+
with c:
946+
if z:
947+
return
948+
else:
949+
a
950+
"""
951+
self.check_stack_size(snippet)
952+
953+
def test_async_with(self):
954+
snippet = """
955+
async with x as y:
956+
a
957+
"""
958+
self.check_stack_size(snippet, async_=True)
959+
960+
def test_async_for(self):
961+
snippet = """
962+
async for x in y:
963+
a
964+
"""
965+
self.check_stack_size(snippet, async_=True)
966+
967+
def test_async_for_else(self):
968+
snippet = """
969+
async for x in y:
970+
a
971+
else:
972+
b
973+
"""
974+
self.check_stack_size(snippet, async_=True)
975+
976+
def test_for_break_continue_inside_async_with_block(self):
977+
snippet = """
978+
for x in y:
979+
async with c:
980+
if z:
981+
break
982+
elif u:
983+
continue
984+
else:
985+
a
986+
else:
987+
b
988+
"""
989+
self.check_stack_size(snippet, async_=True)
990+
991+
def test_return_inside_async_with_block(self):
992+
snippet = """
993+
async with c:
994+
if z:
995+
return
996+
else:
997+
a
998+
"""
999+
self.check_stack_size(snippet, async_=True)
1000+
1001+
7131002
if __name__ == "__main__":
7141003
unittest.main()

Lib/test/test_dis.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -284,18 +284,18 @@ def bug1333982(x=[]):
284284
22 POP_TOP
285285
24 STORE_FAST 0 (e)
286286
26 POP_TOP
287-
28 SETUP_FINALLY 12 (to 42)
287+
28 SETUP_FINALLY 10 (to 40)
288288
289289
%3d 30 LOAD_FAST 0 (e)
290290
32 LOAD_ATTR 1 (__traceback__)
291291
34 STORE_FAST 1 (tb)
292292
36 POP_BLOCK
293-
38 POP_EXCEPT
294-
40 LOAD_CONST 0 (None)
295-
>> 42 LOAD_CONST 0 (None)
296-
44 STORE_FAST 0 (e)
297-
46 DELETE_FAST 0 (e)
298-
48 END_FINALLY
293+
38 LOAD_CONST 0 (None)
294+
>> 40 LOAD_CONST 0 (None)
295+
42 STORE_FAST 0 (e)
296+
44 DELETE_FAST 0 (e)
297+
46 END_FINALLY
298+
48 POP_EXCEPT
299299
50 JUMP_FORWARD 2 (to 54)
300300
>> 52 END_FINALLY
301301
@@ -741,7 +741,7 @@ async def async_def():
741741
Argument count: 0
742742
Kw-only arguments: 0
743743
Number of locals: 2
744-
Stack size: 17
744+
Stack size: 10
745745
Flags: OPTIMIZED, NEWLOCALS, NOFREE, COROUTINE
746746
Constants:
747747
0: None
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed estimation of the code stack size.

0 commit comments

Comments
 (0)