mirror of
https://github.com/python/cpython.git
synced 2025-08-04 08:59:19 +00:00
[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 fa45958450
)
Co-authored-by: Mark Shannon <mark@hotpy.org>
This commit is contained in:
parent
58af2293c5
commit
98902d6c05
21 changed files with 57 additions and 45 deletions
|
@ -802,6 +802,11 @@ sys
|
||||||
exception instance, rather than to a ``(typ, exc, tb)`` tuple.
|
exception instance, rather than to a ``(typ, exc, tb)`` tuple.
|
||||||
(Contributed by Irit Katriel in :gh:`103176`.)
|
(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
|
tempfile
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
|
|
@ -255,7 +255,8 @@ struct _ts {
|
||||||
# ifdef __wasi__
|
# ifdef __wasi__
|
||||||
# define C_RECURSION_LIMIT 500
|
# define C_RECURSION_LIMIT 500
|
||||||
# else
|
# else
|
||||||
# define C_RECURSION_LIMIT 800
|
// This value is duplicated in Lib/test/support/__init__.py
|
||||||
|
# define C_RECURSION_LIMIT 1500
|
||||||
# endif
|
# endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import sys
|
||||||
from functools import cmp_to_key
|
from functools import cmp_to_key
|
||||||
|
|
||||||
from test import seq_tests
|
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):
|
class CommonTest(seq_tests.CommonTest):
|
||||||
|
@ -61,7 +61,7 @@ class CommonTest(seq_tests.CommonTest):
|
||||||
|
|
||||||
def test_repr_deep(self):
|
def test_repr_deep(self):
|
||||||
a = self.type2test([])
|
a = self.type2test([])
|
||||||
for i in range(sys.getrecursionlimit() + 100):
|
for i in range(C_RECURSION_LIMIT + 1):
|
||||||
a = self.type2test([a])
|
a = self.type2test([a])
|
||||||
self.assertRaises(RecursionError, repr, a)
|
self.assertRaises(RecursionError, repr, a)
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import unittest
|
import unittest
|
||||||
import collections
|
import collections
|
||||||
import sys
|
import sys
|
||||||
|
from test.support import C_RECURSION_LIMIT
|
||||||
|
|
||||||
|
|
||||||
class BasicTestMappingProtocol(unittest.TestCase):
|
class BasicTestMappingProtocol(unittest.TestCase):
|
||||||
|
@ -624,7 +625,7 @@ class TestHashMappingProtocol(TestMappingProtocol):
|
||||||
|
|
||||||
def test_repr_deep(self):
|
def test_repr_deep(self):
|
||||||
d = self._empty_mapping()
|
d = self._empty_mapping()
|
||||||
for i in range(sys.getrecursionlimit() + 100):
|
for i in range(C_RECURSION_LIMIT + 1):
|
||||||
d0 = d
|
d0 = d
|
||||||
d = self._empty_mapping()
|
d = self._empty_mapping()
|
||||||
d[1] = d0
|
d[1] = d0
|
||||||
|
|
|
@ -64,7 +64,8 @@ __all__ = [
|
||||||
"run_with_tz", "PGO", "missing_compiler_executable",
|
"run_with_tz", "PGO", "missing_compiler_executable",
|
||||||
"ALWAYS_EQ", "NEVER_EQ", "LARGEST", "SMALLEST",
|
"ALWAYS_EQ", "NEVER_EQ", "LARGEST", "SMALLEST",
|
||||||
"LOOPBACK_TIMEOUT", "INTERNET_TIMEOUT", "SHORT_TIMEOUT", "LONG_TIMEOUT",
|
"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
|
#For recursion tests, easily exceeds default recursion limit
|
||||||
EXCEEDS_RECURSION_LIMIT = 5000
|
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')
|
||||||
|
|
|
@ -1084,6 +1084,7 @@ class AST_Tests(unittest.TestCase):
|
||||||
return self
|
return self
|
||||||
enum._test_simple_enum(_Precedence, ast._Precedence)
|
enum._test_simple_enum(_Precedence, ast._Precedence)
|
||||||
|
|
||||||
|
@unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI")
|
||||||
@support.cpython_only
|
@support.cpython_only
|
||||||
def test_ast_recursion_limit(self):
|
def test_ast_recursion_limit(self):
|
||||||
fail_depth = support.EXCEEDS_RECURSION_LIMIT
|
fail_depth = support.EXCEEDS_RECURSION_LIMIT
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import unittest
|
import unittest
|
||||||
from test.support import cpython_only, requires_limited_api
|
from test.support import cpython_only, requires_limited_api, skip_on_s390x
|
||||||
try:
|
try:
|
||||||
import _testcapi
|
import _testcapi
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -931,6 +931,7 @@ class TestErrorMessagesUseQualifiedName(unittest.TestCase):
|
||||||
@cpython_only
|
@cpython_only
|
||||||
class TestRecursion(unittest.TestCase):
|
class TestRecursion(unittest.TestCase):
|
||||||
|
|
||||||
|
@skip_on_s390x
|
||||||
def test_super_deep(self):
|
def test_super_deep(self):
|
||||||
|
|
||||||
def recurse(n):
|
def recurse(n):
|
||||||
|
|
|
@ -11,10 +11,9 @@ import textwrap
|
||||||
import warnings
|
import warnings
|
||||||
from test import support
|
from test import support
|
||||||
from test.support import (script_helper, requires_debug_ranges,
|
from test.support import (script_helper, requires_debug_ranges,
|
||||||
requires_specialization)
|
requires_specialization, C_RECURSION_LIMIT)
|
||||||
from test.support.os_helper import FakePath
|
from test.support.os_helper import FakePath
|
||||||
|
|
||||||
|
|
||||||
class TestSpecifics(unittest.TestCase):
|
class TestSpecifics(unittest.TestCase):
|
||||||
|
|
||||||
def compile_single(self, source):
|
def compile_single(self, source):
|
||||||
|
@ -112,7 +111,7 @@ class TestSpecifics(unittest.TestCase):
|
||||||
|
|
||||||
@unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI")
|
@unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI")
|
||||||
def test_extended_arg(self):
|
def test_extended_arg(self):
|
||||||
repeat = 2000
|
repeat = int(C_RECURSION_LIMIT * 0.9)
|
||||||
longexpr = 'x = x or ' + '-x' * repeat
|
longexpr = 'x = x or ' + '-x' * repeat
|
||||||
g = {}
|
g = {}
|
||||||
code = textwrap.dedent('''
|
code = textwrap.dedent('''
|
||||||
|
@ -558,16 +557,12 @@ class TestSpecifics(unittest.TestCase):
|
||||||
@support.cpython_only
|
@support.cpython_only
|
||||||
@unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI")
|
@unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI")
|
||||||
def test_compiler_recursion_limit(self):
|
def test_compiler_recursion_limit(self):
|
||||||
# Expected limit is sys.getrecursionlimit() * the scaling factor
|
# Expected limit is C_RECURSION_LIMIT * 2
|
||||||
# in symtable.c (currently 3)
|
# Duplicating the limit here is a little ugly.
|
||||||
# We expect to fail *at* that limit, because we use up some of
|
# Perhaps it should be exposed somewhere...
|
||||||
# the stack depth limit in the test suite code
|
fail_depth = C_RECURSION_LIMIT * 2 + 1
|
||||||
# So we check the expected limit and 75% of that
|
crash_depth = C_RECURSION_LIMIT * 100
|
||||||
# XXX (ncoghlan): duplicating the scaling factor here is a little
|
success_depth = int(C_RECURSION_LIMIT * 1.8)
|
||||||
# ugly. Perhaps it should be exposed somewhere...
|
|
||||||
fail_depth = sys.getrecursionlimit() * 3
|
|
||||||
crash_depth = sys.getrecursionlimit() * 300
|
|
||||||
success_depth = int(fail_depth * 0.75)
|
|
||||||
|
|
||||||
def check_limit(prefix, repeated, mode="single"):
|
def check_limit(prefix, repeated, mode="single"):
|
||||||
expect_ok = prefix + repeated * success_depth
|
expect_ok = prefix + repeated * success_depth
|
||||||
|
|
|
@ -8,7 +8,7 @@ import sys
|
||||||
import unittest
|
import unittest
|
||||||
import weakref
|
import weakref
|
||||||
from test import support
|
from test import support
|
||||||
from test.support import import_helper
|
from test.support import import_helper, C_RECURSION_LIMIT
|
||||||
|
|
||||||
|
|
||||||
class DictTest(unittest.TestCase):
|
class DictTest(unittest.TestCase):
|
||||||
|
@ -596,7 +596,7 @@ class DictTest(unittest.TestCase):
|
||||||
|
|
||||||
def test_repr_deep(self):
|
def test_repr_deep(self):
|
||||||
d = {}
|
d = {}
|
||||||
for i in range(sys.getrecursionlimit() + 100):
|
for i in range(C_RECURSION_LIMIT + 1):
|
||||||
d = {1: d}
|
d = {1: d}
|
||||||
self.assertRaises(RecursionError, repr, d)
|
self.assertRaises(RecursionError, repr, d)
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import copy
|
||||||
import pickle
|
import pickle
|
||||||
import sys
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
|
from test.support import C_RECURSION_LIMIT
|
||||||
|
|
||||||
class DictSetTest(unittest.TestCase):
|
class DictSetTest(unittest.TestCase):
|
||||||
|
|
||||||
|
@ -279,7 +280,7 @@ class DictSetTest(unittest.TestCase):
|
||||||
|
|
||||||
def test_deeply_nested_repr(self):
|
def test_deeply_nested_repr(self):
|
||||||
d = {}
|
d = {}
|
||||||
for i in range(sys.getrecursionlimit() + 100):
|
for i in range(C_RECURSION_LIMIT//2 + 100):
|
||||||
d = {42: d.values()}
|
d = {42: d.values()}
|
||||||
self.assertRaises(RecursionError, repr, d)
|
self.assertRaises(RecursionError, repr, d)
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import collections.abc
|
import collections.abc
|
||||||
import types
|
import types
|
||||||
import unittest
|
import unittest
|
||||||
|
from test.support import C_RECURSION_LIMIT
|
||||||
|
|
||||||
class TestExceptionGroupTypeHierarchy(unittest.TestCase):
|
class TestExceptionGroupTypeHierarchy(unittest.TestCase):
|
||||||
def test_exception_group_types(self):
|
def test_exception_group_types(self):
|
||||||
|
@ -433,7 +433,7 @@ class ExceptionGroupSplitTests(ExceptionGroupTestBase):
|
||||||
class DeepRecursionInSplitAndSubgroup(unittest.TestCase):
|
class DeepRecursionInSplitAndSubgroup(unittest.TestCase):
|
||||||
def make_deep_eg(self):
|
def make_deep_eg(self):
|
||||||
e = TypeError(1)
|
e = TypeError(1)
|
||||||
for i in range(2000):
|
for i in range(C_RECURSION_LIMIT + 1):
|
||||||
e = ExceptionGroup('eg', [e])
|
e = ExceptionGroup('eg', [e])
|
||||||
return e
|
return e
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import os
|
||||||
import pickle
|
import pickle
|
||||||
import random
|
import random
|
||||||
import sys
|
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')
|
zlib = import_helper.import_module('zlib')
|
||||||
|
@ -44,10 +44,7 @@ requires_Decompress_copy = unittest.skipUnless(
|
||||||
# zlib.decompress(func1(data)) == zlib.decompress(func2(data)) == data
|
# zlib.decompress(func1(data)) == zlib.decompress(func2(data)) == data
|
||||||
#
|
#
|
||||||
# Make the assumption that s390x always has an accelerator to simplify the skip
|
# 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.
|
# condition.
|
||||||
skip_on_s390x = unittest.skipIf(hasattr(os, 'uname') and os.uname().machine == 's390x',
|
|
||||||
'skipped on s390x')
|
|
||||||
|
|
||||||
|
|
||||||
class VersionTestCase(unittest.TestCase):
|
class VersionTestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -1393,7 +1393,7 @@ PyObject* PyAST_mod2obj(mod_ty t)
|
||||||
|
|
||||||
int starting_recursion_depth;
|
int starting_recursion_depth;
|
||||||
/* Be careful here to prevent overflow. */
|
/* Be careful here to prevent overflow. */
|
||||||
int COMPILER_STACK_FRAME_SCALE = 3;
|
int COMPILER_STACK_FRAME_SCALE = 2;
|
||||||
PyThreadState *tstate = _PyThreadState_GET();
|
PyThreadState *tstate = _PyThreadState_GET();
|
||||||
if (!tstate) {
|
if (!tstate) {
|
||||||
return 0;
|
return 0;
|
||||||
|
|
2
Python/Python-ast.c
generated
2
Python/Python-ast.c
generated
|
@ -13074,7 +13074,7 @@ PyObject* PyAST_mod2obj(mod_ty t)
|
||||||
|
|
||||||
int starting_recursion_depth;
|
int starting_recursion_depth;
|
||||||
/* Be careful here to prevent overflow. */
|
/* Be careful here to prevent overflow. */
|
||||||
int COMPILER_STACK_FRAME_SCALE = 3;
|
int COMPILER_STACK_FRAME_SCALE = 2;
|
||||||
PyThreadState *tstate = _PyThreadState_GET();
|
PyThreadState *tstate = _PyThreadState_GET();
|
||||||
if (!tstate) {
|
if (!tstate) {
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -1029,7 +1029,7 @@ validate_type_params(struct validator *state, asdl_type_param_seq *tps)
|
||||||
|
|
||||||
|
|
||||||
/* See comments in symtable.c. */
|
/* See comments in symtable.c. */
|
||||||
#define COMPILER_STACK_FRAME_SCALE 3
|
#define COMPILER_STACK_FRAME_SCALE 2
|
||||||
|
|
||||||
int
|
int
|
||||||
_PyAST_Validate(mod_ty mod)
|
_PyAST_Validate(mod_ty mod)
|
||||||
|
|
|
@ -1103,7 +1103,7 @@ astfold_type_param(type_param_ty node_, PyArena *ctx_, _PyASTOptimizeState *stat
|
||||||
#undef CALL_SEQ
|
#undef CALL_SEQ
|
||||||
|
|
||||||
/* See comments in symtable.c. */
|
/* See comments in symtable.c. */
|
||||||
#define COMPILER_STACK_FRAME_SCALE 3
|
#define COMPILER_STACK_FRAME_SCALE 2
|
||||||
|
|
||||||
int
|
int
|
||||||
_PyAST_Optimize(mod_ty mod, PyArena *arena, _PyASTOptimizeState *state)
|
_PyAST_Optimize(mod_ty mod, PyArena *arena, _PyASTOptimizeState *state)
|
||||||
|
|
|
@ -635,7 +635,7 @@ dummy_func(
|
||||||
tstate->cframe = cframe.previous;
|
tstate->cframe = cframe.previous;
|
||||||
assert(tstate->cframe->current_frame == frame->previous);
|
assert(tstate->cframe->current_frame == frame->previous);
|
||||||
assert(!_PyErr_Occurred(tstate));
|
assert(!_PyErr_Occurred(tstate));
|
||||||
_Py_LeaveRecursiveCallTstate(tstate);
|
tstate->c_recursion_remaining += PY_EVAL_C_STACK_UNITS;
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -637,6 +637,11 @@ static inline void _Py_LeaveRecursiveCallPy(PyThreadState *tstate) {
|
||||||
# pragma warning(disable:4102)
|
# pragma warning(disable:4102)
|
||||||
#endif
|
#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
|
PyObject* _Py_HOT_FUNCTION
|
||||||
_PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int throwflag)
|
_PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int throwflag)
|
||||||
{
|
{
|
||||||
|
@ -691,6 +696,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
|
||||||
frame->previous = &entry_frame;
|
frame->previous = &entry_frame;
|
||||||
cframe.current_frame = frame;
|
cframe.current_frame = frame;
|
||||||
|
|
||||||
|
tstate->c_recursion_remaining -= (PY_EVAL_C_STACK_UNITS - 1);
|
||||||
if (_Py_EnterRecursiveCallTstate(tstate, "")) {
|
if (_Py_EnterRecursiveCallTstate(tstate, "")) {
|
||||||
tstate->c_recursion_remaining--;
|
tstate->c_recursion_remaining--;
|
||||||
tstate->py_recursion_remaining--;
|
tstate->py_recursion_remaining--;
|
||||||
|
@ -990,7 +996,7 @@ exit_unwind:
|
||||||
/* Restore previous cframe and exit */
|
/* Restore previous cframe and exit */
|
||||||
tstate->cframe = cframe.previous;
|
tstate->cframe = cframe.previous;
|
||||||
assert(tstate->cframe->current_frame == frame->previous);
|
assert(tstate->cframe->current_frame == frame->previous);
|
||||||
_Py_LeaveRecursiveCallTstate(tstate);
|
tstate->c_recursion_remaining += PY_EVAL_C_STACK_UNITS;
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
2
Python/generated_cases.c.h
generated
2
Python/generated_cases.c.h
generated
|
@ -922,7 +922,7 @@
|
||||||
tstate->cframe = cframe.previous;
|
tstate->cframe = cframe.previous;
|
||||||
assert(tstate->cframe->current_frame == frame->previous);
|
assert(tstate->cframe->current_frame == frame->previous);
|
||||||
assert(!_PyErr_Occurred(tstate));
|
assert(!_PyErr_Occurred(tstate));
|
||||||
_Py_LeaveRecursiveCallTstate(tstate);
|
tstate->c_recursion_remaining += PY_EVAL_C_STACK_UNITS;
|
||||||
return retval;
|
return retval;
|
||||||
#line 928 "Python/generated_cases.c.h"
|
#line 928 "Python/generated_cases.c.h"
|
||||||
}
|
}
|
||||||
|
|
|
@ -282,17 +282,10 @@ symtable_new(void)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* When compiling the use of C stack is probably going to be a lot
|
/* Using a scaling factor means this should automatically adjust when
|
||||||
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
|
|
||||||
the recursion limit is adjusted for small or large C stack allocations.
|
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 *
|
struct symtable *
|
||||||
_PySymtable_Build(mod_ty mod, PyObject *filename, PyFutureFeatures *future)
|
_PySymtable_Build(mod_ty mod, PyObject *filename, PyFutureFeatures *future)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue