aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorMark Shannon <mark@hotpy.org>2023-08-04 10:10:29 +0100
committerGitHub <noreply@github.com>2023-08-04 10:10:29 +0100
commitfa45958450aa3489607daf9855ca0474a2a20878 (patch)
tree0e7a9b21503a7cf08691b99990ee6a538aa931b5
parent0bd784b355edf0d1911a7830db5d41c84171e367 (diff)
downloadcpython-fa45958450aa3489607daf9855ca0474a2a20878.tar.gz
cpython-fa45958450aa3489607daf9855ca0474a2a20878.zip
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.
-rw-r--r--Doc/whatsnew/3.12.rst5
-rw-r--r--Include/cpython/pystate.h3
-rw-r--r--Lib/test/list_tests.py4
-rw-r--r--Lib/test/mapping_tests.py3
-rw-r--r--Lib/test/support/__init__.py10
-rw-r--r--Lib/test/test_ast.py1
-rw-r--r--Lib/test/test_call.py3
-rw-r--r--Lib/test/test_compile.py21
-rw-r--r--Lib/test/test_dict.py4
-rw-r--r--Lib/test/test_dictviews.py3
-rw-r--r--Lib/test/test_exception_group.py4
-rw-r--r--Lib/test/test_zlib.py7
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2023-07-30-05-20-16.gh-issue-107263.q0IU2M.rst3
-rwxr-xr-xParser/asdl_c.py2
-rw-r--r--Python/Python-ast.c2
-rw-r--r--Python/ast.c2
-rw-r--r--Python/ast_opt.c2
-rw-r--r--Python/bytecodes.c2
-rw-r--r--Python/ceval.c8
-rw-r--r--Python/generated_cases.c.h2
-rw-r--r--Python/symtable.c11
21 files changed, 57 insertions, 45 deletions
diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst
index 3c39476ced9..6553013552d 100644
--- a/Doc/whatsnew/3.12.rst
+++ b/Doc/whatsnew/3.12.rst
@@ -801,6 +801,11 @@ sys
exception instance, rather than to a ``(typ, exc, tb)`` tuple.
(Contributed by Irit Katriel in :gh:`103176`.)
+* :func:`sys.setrecursionlimit` and :func:`sys.getrecursionlimit`.
+ The recursion limit now applies only to Python code. Builtin functions do
+ not use the recursion limit, but are protected by a different mechanism
+ that prevents recursion from causing a virtual machine crash.
+
tempfile
--------
diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h
index 30de4ee4b6c..56e473cc5e4 100644
--- a/Include/cpython/pystate.h
+++ b/Include/cpython/pystate.h
@@ -222,7 +222,8 @@ struct _ts {
# ifdef __wasi__
# define C_RECURSION_LIMIT 500
# else
-# define C_RECURSION_LIMIT 800
+ // This value is duplicated in Lib/test/support/__init__.py
+# define C_RECURSION_LIMIT 1500
# endif
#endif
diff --git a/Lib/test/list_tests.py b/Lib/test/list_tests.py
index fe3ee80b8d4..b1ef332522d 100644
--- a/Lib/test/list_tests.py
+++ b/Lib/test/list_tests.py
@@ -6,7 +6,7 @@ import sys
from functools import cmp_to_key
from test import seq_tests
-from test.support import ALWAYS_EQ, NEVER_EQ
+from test.support import ALWAYS_EQ, NEVER_EQ, C_RECURSION_LIMIT
class CommonTest(seq_tests.CommonTest):
@@ -61,7 +61,7 @@ class CommonTest(seq_tests.CommonTest):
def test_repr_deep(self):
a = self.type2test([])
- for i in range(sys.getrecursionlimit() + 100):
+ for i in range(C_RECURSION_LIMIT + 1):
a = self.type2test([a])
self.assertRaises(RecursionError, repr, a)
diff --git a/Lib/test/mapping_tests.py b/Lib/test/mapping_tests.py
index 613206a0855..5492bbf86d1 100644
--- a/Lib/test/mapping_tests.py
+++ b/Lib/test/mapping_tests.py
@@ -2,6 +2,7 @@
import unittest
import collections
import sys
+from test.support import C_RECURSION_LIMIT
class BasicTestMappingProtocol(unittest.TestCase):
@@ -624,7 +625,7 @@ class TestHashMappingProtocol(TestMappingProtocol):
def test_repr_deep(self):
d = self._empty_mapping()
- for i in range(sys.getrecursionlimit() + 100):
+ for i in range(C_RECURSION_LIMIT + 1):
d0 = d
d = self._empty_mapping()
d[1] = d0
diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
index 2a59911e54d..64c66d8e25d 100644
--- a/Lib/test/support/__init__.py
+++ b/Lib/test/support/__init__.py
@@ -64,7 +64,8 @@ __all__ = [
"run_with_tz", "PGO", "missing_compiler_executable",
"ALWAYS_EQ", "NEVER_EQ", "LARGEST", "SMALLEST",
"LOOPBACK_TIMEOUT", "INTERNET_TIMEOUT", "SHORT_TIMEOUT", "LONG_TIMEOUT",
- "Py_DEBUG", "EXCEEDS_RECURSION_LIMIT",
+ "Py_DEBUG", "EXCEEDS_RECURSION_LIMIT", "C_RECURSION_LIMIT",
+ "skip_on_s390x",
]
@@ -2460,3 +2461,10 @@ def adjust_int_max_str_digits(max_digits):
#For recursion tests, easily exceeds default recursion limit
EXCEEDS_RECURSION_LIMIT = 5000
+
+# The default C recursion limit (from Include/cpython/pystate.h).
+C_RECURSION_LIMIT = 1500
+
+#Windows doesn't have os.uname() but it doesn't support s390x.
+skip_on_s390x = unittest.skipIf(hasattr(os, 'uname') and os.uname().machine == 's390x',
+ 'skipped on s390x')
diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py
index a03fa4c7187..5346b39043f 100644
--- a/Lib/test/test_ast.py
+++ b/Lib/test/test_ast.py
@@ -1084,6 +1084,7 @@ class AST_Tests(unittest.TestCase):
return self
enum._test_simple_enum(_Precedence, ast._Precedence)
+ @unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI")
@support.cpython_only
def test_ast_recursion_limit(self):
fail_depth = support.EXCEEDS_RECURSION_LIMIT
diff --git a/Lib/test/test_call.py b/Lib/test/test_call.py
index 09a531f8cc6..c3c3b1853b5 100644
--- a/Lib/test/test_call.py
+++ b/Lib/test/test_call.py
@@ -1,5 +1,5 @@
import unittest
-from test.support import cpython_only, requires_limited_api
+from test.support import cpython_only, requires_limited_api, skip_on_s390x
try:
import _testcapi
except ImportError:
@@ -918,6 +918,7 @@ class TestErrorMessagesUseQualifiedName(unittest.TestCase):
@cpython_only
class TestRecursion(unittest.TestCase):
+ @skip_on_s390x
def test_super_deep(self):
def recurse(n):
diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py
index 85ce0a4b39d..770184c5ef9 100644
--- a/Lib/test/test_compile.py
+++ b/Lib/test/test_compile.py
@@ -11,10 +11,9 @@ import textwrap
import warnings
from test import support
from test.support import (script_helper, requires_debug_ranges,
- requires_specialization)
+ requires_specialization, C_RECURSION_LIMIT)
from test.support.os_helper import FakePath
-
class TestSpecifics(unittest.TestCase):
def compile_single(self, source):
@@ -112,7 +111,7 @@ class TestSpecifics(unittest.TestCase):
@unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI")
def test_extended_arg(self):
- repeat = 2000
+ repeat = int(C_RECURSION_LIMIT * 0.9)
longexpr = 'x = x or ' + '-x' * repeat
g = {}
code = textwrap.dedent('''
@@ -558,16 +557,12 @@ class TestSpecifics(unittest.TestCase):
@support.cpython_only
@unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI")
def test_compiler_recursion_limit(self):
- # Expected limit is sys.getrecursionlimit() * the scaling factor
- # in symtable.c (currently 3)
- # We expect to fail *at* that limit, because we use up some of
- # the stack depth limit in the test suite code
- # So we check the expected limit and 75% of that
- # XXX (ncoghlan): duplicating the scaling factor here is a little
- # ugly. Perhaps it should be exposed somewhere...
- fail_depth = sys.getrecursionlimit() * 3
- crash_depth = sys.getrecursionlimit() * 300
- success_depth = int(fail_depth * 0.75)
+ # Expected limit is C_RECURSION_LIMIT * 2
+ # Duplicating the limit here is a little ugly.
+ # Perhaps it should be exposed somewhere...
+ fail_depth = C_RECURSION_LIMIT * 2 + 1
+ crash_depth = C_RECURSION_LIMIT * 100
+ success_depth = int(C_RECURSION_LIMIT * 1.8)
def check_limit(prefix, repeated, mode="single"):
expect_ok = prefix + repeated * success_depth
diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py
index 79638340059..fbc6ce8282d 100644
--- a/Lib/test/test_dict.py
+++ b/Lib/test/test_dict.py
@@ -8,7 +8,7 @@ import sys
import unittest
import weakref
from test import support
-from test.support import import_helper
+from test.support import import_helper, C_RECURSION_LIMIT
class DictTest(unittest.TestCase):
@@ -596,7 +596,7 @@ class DictTest(unittest.TestCase):
def test_repr_deep(self):
d = {}
- for i in range(sys.getrecursionlimit() + 100):
+ for i in range(C_RECURSION_LIMIT + 1):
d = {1: d}
self.assertRaises(RecursionError, repr, d)
diff --git a/Lib/test/test_dictviews.py b/Lib/test/test_dictviews.py
index 924f4a6829e..2bd9d6eef8c 100644
--- a/Lib/test/test_dictviews.py
+++ b/Lib/test/test_dictviews.py
@@ -3,6 +3,7 @@ import copy
import pickle
import sys
import unittest
+from test.support import C_RECURSION_LIMIT
class DictSetTest(unittest.TestCase):
@@ -279,7 +280,7 @@ class DictSetTest(unittest.TestCase):
def test_deeply_nested_repr(self):
d = {}
- for i in range(sys.getrecursionlimit() + 100):
+ for i in range(C_RECURSION_LIMIT//2 + 100):
d = {42: d.values()}
self.assertRaises(RecursionError, repr, d)
diff --git a/Lib/test/test_exception_group.py b/Lib/test/test_exception_group.py
index 2658e027ff3..a02d54da35e 100644
--- a/Lib/test/test_exception_group.py
+++ b/Lib/test/test_exception_group.py
@@ -1,7 +1,7 @@
import collections.abc
import types
import unittest
-
+from test.support import C_RECURSION_LIMIT
class TestExceptionGroupTypeHierarchy(unittest.TestCase):
def test_exception_group_types(self):
@@ -460,7 +460,7 @@ class ExceptionGroupSplitTests(ExceptionGroupTestBase):
class DeepRecursionInSplitAndSubgroup(unittest.TestCase):
def make_deep_eg(self):
e = TypeError(1)
- for i in range(2000):
+ for i in range(C_RECURSION_LIMIT + 1):
e = ExceptionGroup('eg', [e])
return e
diff --git a/Lib/test/test_zlib.py b/Lib/test/test_zlib.py
index 3dac70eb128..2113757254c 100644
--- a/Lib/test/test_zlib.py
+++ b/Lib/test/test_zlib.py
@@ -7,7 +7,7 @@ import os
import pickle
import random
import sys
-from test.support import bigmemtest, _1G, _4G
+from test.support import bigmemtest, _1G, _4G, skip_on_s390x
zlib = import_helper.import_module('zlib')
@@ -44,10 +44,7 @@ requires_Decompress_copy = unittest.skipUnless(
# zlib.decompress(func1(data)) == zlib.decompress(func2(data)) == data
#
# Make the assumption that s390x always has an accelerator to simplify the skip
-# condition. Windows doesn't have os.uname() but it doesn't support s390x.
-skip_on_s390x = unittest.skipIf(hasattr(os, 'uname') and os.uname().machine == 's390x',
- 'skipped on s390x')
-
+# condition.
class VersionTestCase(unittest.TestCase):
diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-07-30-05-20-16.gh-issue-107263.q0IU2M.rst b/Misc/NEWS.d/next/Core and Builtins/2023-07-30-05-20-16.gh-issue-107263.q0IU2M.rst
new file mode 100644
index 00000000000..fb0940b456d
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2023-07-30-05-20-16.gh-issue-107263.q0IU2M.rst
@@ -0,0 +1,3 @@
+Increase C recursion limit for functions other than the main interpreter
+from 800 to 1500. This should allow functions like ``list.__repr__`` and
+``json.dumps`` to handle all the inputs that they could prior to 3.12
diff --git a/Parser/asdl_c.py b/Parser/asdl_c.py
index f159b573ce4..2a36610527f 100755
--- a/Parser/asdl_c.py
+++ b/Parser/asdl_c.py
@@ -1393,7 +1393,7 @@ PyObject* PyAST_mod2obj(mod_ty t)
int starting_recursion_depth;
/* Be careful here to prevent overflow. */
- int COMPILER_STACK_FRAME_SCALE = 3;
+ int COMPILER_STACK_FRAME_SCALE = 2;
PyThreadState *tstate = _PyThreadState_GET();
if (!tstate) {
return 0;
diff --git a/Python/Python-ast.c b/Python/Python-ast.c
index 412de793974..8047b1259c5 100644
--- a/Python/Python-ast.c
+++ b/Python/Python-ast.c
@@ -13073,7 +13073,7 @@ PyObject* PyAST_mod2obj(mod_ty t)
int starting_recursion_depth;
/* Be careful here to prevent overflow. */
- int COMPILER_STACK_FRAME_SCALE = 3;
+ int COMPILER_STACK_FRAME_SCALE = 2;
PyThreadState *tstate = _PyThreadState_GET();
if (!tstate) {
return 0;
diff --git a/Python/ast.c b/Python/ast.c
index 68600ce683b..74c97f948d1 100644
--- a/Python/ast.c
+++ b/Python/ast.c
@@ -1029,7 +1029,7 @@ validate_type_params(struct validator *state, asdl_type_param_seq *tps)
/* See comments in symtable.c. */
-#define COMPILER_STACK_FRAME_SCALE 3
+#define COMPILER_STACK_FRAME_SCALE 2
int
_PyAST_Validate(mod_ty mod)
diff --git a/Python/ast_opt.c b/Python/ast_opt.c
index ad1e312b084..82e7559e5b6 100644
--- a/Python/ast_opt.c
+++ b/Python/ast_opt.c
@@ -1112,7 +1112,7 @@ astfold_type_param(type_param_ty node_, PyArena *ctx_, _PyASTOptimizeState *stat
#undef CALL_SEQ
/* See comments in symtable.c. */
-#define COMPILER_STACK_FRAME_SCALE 3
+#define COMPILER_STACK_FRAME_SCALE 2
int
_PyAST_Optimize(mod_ty mod, PyArena *arena, int optimize, int ff_features)
diff --git a/Python/bytecodes.c b/Python/bytecodes.c
index 0bea7b59597..90e26d3c86b 100644
--- a/Python/bytecodes.c
+++ b/Python/bytecodes.c
@@ -741,7 +741,7 @@ dummy_func(
tstate->cframe = cframe.previous;
assert(tstate->cframe->current_frame == frame->previous);
assert(!_PyErr_Occurred(tstate));
- _Py_LeaveRecursiveCallTstate(tstate);
+ tstate->c_recursion_remaining += PY_EVAL_C_STACK_UNITS;
return retval;
}
diff --git a/Python/ceval.c b/Python/ceval.c
index 369b9a69152..b85e9677747 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -624,6 +624,11 @@ extern const struct _PyCode_DEF(8) _Py_InitCleanup;
# pragma warning(disable:4102)
#endif
+
+/* _PyEval_EvalFrameDefault() is a *big* function,
+ * so consume 3 units of C stack */
+#define PY_EVAL_C_STACK_UNITS 2
+
PyObject* _Py_HOT_FUNCTION
_PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int throwflag)
{
@@ -676,6 +681,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
frame->previous = &entry_frame;
cframe.current_frame = frame;
+ tstate->c_recursion_remaining -= (PY_EVAL_C_STACK_UNITS - 1);
if (_Py_EnterRecursiveCallTstate(tstate, "")) {
tstate->c_recursion_remaining--;
tstate->py_recursion_remaining--;
@@ -907,7 +913,7 @@ exit_unwind:
/* Restore previous cframe and exit */
tstate->cframe = cframe.previous;
assert(tstate->cframe->current_frame == frame->previous);
- _Py_LeaveRecursiveCallTstate(tstate);
+ tstate->c_recursion_remaining += PY_EVAL_C_STACK_UNITS;
return NULL;
}
diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h
index e43b3dedf14..9fa549a1203 100644
--- a/Python/generated_cases.c.h
+++ b/Python/generated_cases.c.h
@@ -903,7 +903,7 @@
tstate->cframe = cframe.previous;
assert(tstate->cframe->current_frame == frame->previous);
assert(!_PyErr_Occurred(tstate));
- _Py_LeaveRecursiveCallTstate(tstate);
+ tstate->c_recursion_remaining += PY_EVAL_C_STACK_UNITS;
return retval;
}
diff --git a/Python/symtable.c b/Python/symtable.c
index 04be3192d6c..e9adbd5d29b 100644
--- a/Python/symtable.c
+++ b/Python/symtable.c
@@ -282,17 +282,10 @@ symtable_new(void)
return NULL;
}
-/* When compiling the use of C stack is probably going to be a lot
- lighter than when executing Python code but still can overflow
- and causing a Python crash if not checked (e.g. eval("()"*300000)).
- Using the current recursion limit for the compiler seems too
- restrictive (it caused at least one test to fail) so a factor is
- used to allow deeper recursion when compiling an expression.
-
- Using a scaling factor means this should automatically adjust when
+/* Using a scaling factor means this should automatically adjust when
the recursion limit is adjusted for small or large C stack allocations.
*/
-#define COMPILER_STACK_FRAME_SCALE 3
+#define COMPILER_STACK_FRAME_SCALE 2
struct symtable *
_PySymtable_Build(mod_ty mod, PyObject *filename, PyFutureFeatures *future)