Skip to content

Commit 98902d6

Browse files
[3.12] GH-107263: Increase C stack limit for most functions, except _PyEval_EvalFrameDefault() (GH-107535) (#107618)
GH-107263: Increase C stack limit for most functions, except `_PyEval_EvalFrameDefault()` (GH-107535) * Set C recursion limit to 1500, set cost of eval loop to 2 frames, and compiler mutliply to 2. (cherry picked from commit fa45958) Co-authored-by: Mark Shannon <[email protected]>
1 parent 58af229 commit 98902d6

21 files changed

+57
-45
lines changed

Doc/whatsnew/3.12.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -802,6 +802,11 @@ sys
802802
exception instance, rather than to a ``(typ, exc, tb)`` tuple.
803803
(Contributed by Irit Katriel in :gh:`103176`.)
804804

805+
* :func:`sys.setrecursionlimit` and :func:`sys.getrecursionlimit`.
806+
The recursion limit now applies only to Python code. Builtin functions do
807+
not use the recursion limit, but are protected by a different mechanism
808+
that prevents recursion from causing a virtual machine crash.
809+
805810
tempfile
806811
--------
807812

Include/cpython/pystate.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,8 @@ struct _ts {
255255
# ifdef __wasi__
256256
# define C_RECURSION_LIMIT 500
257257
# else
258-
# define C_RECURSION_LIMIT 800
258+
// This value is duplicated in Lib/test/support/__init__.py
259+
# define C_RECURSION_LIMIT 1500
259260
# endif
260261
#endif
261262

Lib/test/list_tests.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from functools import cmp_to_key
77

88
from test import seq_tests
9-
from test.support import ALWAYS_EQ, NEVER_EQ
9+
from test.support import ALWAYS_EQ, NEVER_EQ, C_RECURSION_LIMIT
1010

1111

1212
class CommonTest(seq_tests.CommonTest):
@@ -61,7 +61,7 @@ def test_repr(self):
6161

6262
def test_repr_deep(self):
6363
a = self.type2test([])
64-
for i in range(sys.getrecursionlimit() + 100):
64+
for i in range(C_RECURSION_LIMIT + 1):
6565
a = self.type2test([a])
6666
self.assertRaises(RecursionError, repr, a)
6767

Lib/test/mapping_tests.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import unittest
33
import collections
44
import sys
5+
from test.support import C_RECURSION_LIMIT
56

67

78
class BasicTestMappingProtocol(unittest.TestCase):
@@ -624,7 +625,7 @@ def __repr__(self):
624625

625626
def test_repr_deep(self):
626627
d = self._empty_mapping()
627-
for i in range(sys.getrecursionlimit() + 100):
628+
for i in range(C_RECURSION_LIMIT + 1):
628629
d0 = d
629630
d = self._empty_mapping()
630631
d[1] = d0

Lib/test/support/__init__.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@
6464
"run_with_tz", "PGO", "missing_compiler_executable",
6565
"ALWAYS_EQ", "NEVER_EQ", "LARGEST", "SMALLEST",
6666
"LOOPBACK_TIMEOUT", "INTERNET_TIMEOUT", "SHORT_TIMEOUT", "LONG_TIMEOUT",
67-
"Py_DEBUG", "EXCEEDS_RECURSION_LIMIT",
67+
"Py_DEBUG", "EXCEEDS_RECURSION_LIMIT", "C_RECURSION_LIMIT",
68+
"skip_on_s390x",
6869
]
6970

7071

@@ -2460,3 +2461,10 @@ def adjust_int_max_str_digits(max_digits):
24602461

24612462
#For recursion tests, easily exceeds default recursion limit
24622463
EXCEEDS_RECURSION_LIMIT = 5000
2464+
2465+
# The default C recursion limit (from Include/cpython/pystate.h).
2466+
C_RECURSION_LIMIT = 1500
2467+
2468+
#Windows doesn't have os.uname() but it doesn't support s390x.
2469+
skip_on_s390x = unittest.skipIf(hasattr(os, 'uname') and os.uname().machine == 's390x',
2470+
'skipped on s390x')

Lib/test/test_ast.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1084,6 +1084,7 @@ def next(self):
10841084
return self
10851085
enum._test_simple_enum(_Precedence, ast._Precedence)
10861086

1087+
@unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI")
10871088
@support.cpython_only
10881089
def test_ast_recursion_limit(self):
10891090
fail_depth = support.EXCEEDS_RECURSION_LIMIT

Lib/test/test_call.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import unittest
2-
from test.support import cpython_only, requires_limited_api
2+
from test.support import cpython_only, requires_limited_api, skip_on_s390x
33
try:
44
import _testcapi
55
except ImportError:
@@ -931,6 +931,7 @@ def test_multiple_values(self):
931931
@cpython_only
932932
class TestRecursion(unittest.TestCase):
933933

934+
@skip_on_s390x
934935
def test_super_deep(self):
935936

936937
def recurse(n):

Lib/test/test_compile.py

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,9 @@
1111
import warnings
1212
from test import support
1313
from test.support import (script_helper, requires_debug_ranges,
14-
requires_specialization)
14+
requires_specialization, C_RECURSION_LIMIT)
1515
from test.support.os_helper import FakePath
1616

17-
1817
class TestSpecifics(unittest.TestCase):
1918

2019
def compile_single(self, source):
@@ -112,7 +111,7 @@ def __getitem__(self, key):
112111

113112
@unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI")
114113
def test_extended_arg(self):
115-
repeat = 2000
114+
repeat = int(C_RECURSION_LIMIT * 0.9)
116115
longexpr = 'x = x or ' + '-x' * repeat
117116
g = {}
118117
code = textwrap.dedent('''
@@ -558,16 +557,12 @@ def test_yet_more_evil_still_undecodable(self):
558557
@support.cpython_only
559558
@unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI")
560559
def test_compiler_recursion_limit(self):
561-
# Expected limit is sys.getrecursionlimit() * the scaling factor
562-
# in symtable.c (currently 3)
563-
# We expect to fail *at* that limit, because we use up some of
564-
# the stack depth limit in the test suite code
565-
# So we check the expected limit and 75% of that
566-
# XXX (ncoghlan): duplicating the scaling factor here is a little
567-
# ugly. Perhaps it should be exposed somewhere...
568-
fail_depth = sys.getrecursionlimit() * 3
569-
crash_depth = sys.getrecursionlimit() * 300
570-
success_depth = int(fail_depth * 0.75)
560+
# Expected limit is C_RECURSION_LIMIT * 2
561+
# Duplicating the limit here is a little ugly.
562+
# Perhaps it should be exposed somewhere...
563+
fail_depth = C_RECURSION_LIMIT * 2 + 1
564+
crash_depth = C_RECURSION_LIMIT * 100
565+
success_depth = int(C_RECURSION_LIMIT * 1.8)
571566

572567
def check_limit(prefix, repeated, mode="single"):
573568
expect_ok = prefix + repeated * success_depth

Lib/test/test_dict.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import unittest
99
import weakref
1010
from test import support
11-
from test.support import import_helper
11+
from test.support import import_helper, C_RECURSION_LIMIT
1212

1313

1414
class DictTest(unittest.TestCase):
@@ -596,7 +596,7 @@ def __repr__(self):
596596

597597
def test_repr_deep(self):
598598
d = {}
599-
for i in range(sys.getrecursionlimit() + 100):
599+
for i in range(C_RECURSION_LIMIT + 1):
600600
d = {1: d}
601601
self.assertRaises(RecursionError, repr, d)
602602

Lib/test/test_dictviews.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import pickle
44
import sys
55
import unittest
6+
from test.support import C_RECURSION_LIMIT
67

78
class DictSetTest(unittest.TestCase):
89

@@ -279,7 +280,7 @@ def test_recursive_repr(self):
279280

280281
def test_deeply_nested_repr(self):
281282
d = {}
282-
for i in range(sys.getrecursionlimit() + 100):
283+
for i in range(C_RECURSION_LIMIT//2 + 100):
283284
d = {42: d.values()}
284285
self.assertRaises(RecursionError, repr, d)
285286

0 commit comments

Comments
 (0)