/* Was this thread state statically allocated? */
int _static;
- int recursion_remaining;
- int recursion_limit;
+ int py_recursion_remaining;
+ int py_recursion_limit;
+
+ int c_recursion_remaining;
int recursion_headroom; /* Allow 50 more calls to handle any errors. */
/* 'tracing' keeps track of the execution depth when tracing/profiling.
_PyCFrame root_cframe;
};
+/* WASI has limited call stack. Python's recursion limit depends on code
+ layout, optimization, and WASI runtime. Wasmtime can handle about 700
+ recursions, sometimes less. 500 is a more conservative limit. */
+#ifndef C_RECURSION_LIMIT
+# ifdef __wasi__
+# define C_RECURSION_LIMIT 500
+# else
+# define C_RECURSION_LIMIT 800
+# endif
+#endif
/* other API */
struct pyruntimestate;
struct _ceval_runtime_state;
-/* WASI has limited call stack. Python's recursion limit depends on code
- layout, optimization, and WASI runtime. Wasmtime can handle about 700-750
- recursions, sometimes less. 600 is a more conservative limit. */
#ifndef Py_DEFAULT_RECURSION_LIMIT
-# ifdef __wasi__
-# define Py_DEFAULT_RECURSION_LIMIT 600
-# else
-# define Py_DEFAULT_RECURSION_LIMIT 1000
-# endif
+# define Py_DEFAULT_RECURSION_LIMIT 1000
#endif
#include "pycore_interp.h" // PyInterpreterState.eval_frame
/* With USE_STACKCHECK macro defined, trigger stack checks in
_Py_CheckRecursiveCall() on every 64th call to _Py_EnterRecursiveCall. */
static inline int _Py_MakeRecCheck(PyThreadState *tstate) {
- return (tstate->recursion_remaining-- <= 0
- || (tstate->recursion_remaining & 63) == 0);
+ return (tstate->c_recursion_remaining-- <= 0
+ || (tstate->c_recursion_remaining & 63) == 0);
}
#else
static inline int _Py_MakeRecCheck(PyThreadState *tstate) {
- return tstate->recursion_remaining-- <= 0;
+ return tstate->c_recursion_remaining-- <= 0;
}
#endif
PyThreadState *tstate,
const char *where);
+int _Py_CheckRecursiveCallPy(
+ PyThreadState *tstate);
+
static inline int _Py_EnterRecursiveCallTstate(PyThreadState *tstate,
const char *where) {
return (_Py_MakeRecCheck(tstate) && _Py_CheckRecursiveCall(tstate, where));
}
static inline void _Py_LeaveRecursiveCallTstate(PyThreadState *tstate) {
- tstate->recursion_remaining++;
+ tstate->c_recursion_remaining++;
}
static inline void _Py_LeaveRecursiveCall(void) {
extern int _Py_HandlePending(PyThreadState *tstate);
+
#ifdef __cplusplus
}
#endif
#define _PyThreadState_INIT \
{ \
._static = 1, \
- .recursion_limit = Py_DEFAULT_RECURSION_LIMIT, \
+ .py_recursion_limit = Py_DEFAULT_RECURSION_LIMIT, \
.context_ver = 1, \
}
"run_with_tz", "PGO", "missing_compiler_executable",
"ALWAYS_EQ", "NEVER_EQ", "LARGEST", "SMALLEST",
"LOOPBACK_TIMEOUT", "INTERNET_TIMEOUT", "SHORT_TIMEOUT", "LONG_TIMEOUT",
- "Py_DEBUG",
+ "Py_DEBUG", "EXCEEDS_RECURSION_LIMIT",
]
yield
finally:
sys.set_int_max_str_digits(current)
+
+#For recursion tests, easily exceeds default recursion limit
+EXCEEDS_RECURSION_LIMIT = 5000
@support.cpython_only
def test_ast_recursion_limit(self):
- fail_depth = sys.getrecursionlimit() * 3
- crash_depth = sys.getrecursionlimit() * 300
- success_depth = int(fail_depth * 0.75)
+ fail_depth = support.EXCEEDS_RECURSION_LIMIT
+ crash_depth = 100_000
+ success_depth = 1200
def check_limit(prefix, repeated):
expect_ok = prefix + repeated * success_depth
with self.check_raises_type_error(msg):
A().method_two_args("x", "y", x="oops")
+@cpython_only
+class TestRecursion(unittest.TestCase):
+
+ def test_super_deep(self):
+
+ def recurse(n):
+ if n:
+ recurse(n-1)
+
+ def py_recurse(n, m):
+ if n:
+ py_recurse(n-1, m)
+ else:
+ c_py_recurse(m-1)
+
+ def c_recurse(n):
+ if n:
+ _testcapi.pyobject_fastcall(c_recurse, (n-1,))
+
+ def c_py_recurse(m):
+ if m:
+ _testcapi.pyobject_fastcall(py_recurse, (1000, m))
+
+ depth = sys.getrecursionlimit()
+ sys.setrecursionlimit(100_000)
+ try:
+ recurse(90_000)
+ with self.assertRaises(RecursionError):
+ recurse(101_000)
+ c_recurse(100)
+ with self.assertRaises(RecursionError):
+ c_recurse(90_000)
+ c_py_recurse(90)
+ with self.assertRaises(RecursionError):
+ c_py_recurse(100_000)
+ finally:
+ sys.setrecursionlimit(depth)
+
if __name__ == "__main__":
unittest.main()
self.assertEqual(Dot(1)._replace(d=999), (999,))
self.assertEqual(Dot(1)._fields, ('d',))
- n = 5000
+ n = support.EXCEEDS_RECURSION_LIMIT
names = list(set(''.join([choice(string.ascii_letters)
for j in range(10)]) for i in range(n)))
n = len(names)
@unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI")
def test_extended_arg(self):
- # default: 1000 * 2.5 = 2500 repetitions
- repeat = int(sys.getrecursionlimit() * 2.5)
+ repeat = 2000
longexpr = 'x = x or ' + '-x' * repeat
g = {}
code = '''
def __missing__(self, key):
return int(key.removeprefix("_number_"))
- # 1,000 on most systems
- limit = sys.getrecursionlimit()
- code = "lambda: " + "+".join(f"_number_{i}" for i in range(limit))
+ # Need more than 256 variables to use EXTENDED_ARGS
+ variables = 400
+ code = "lambda: " + "+".join(f"_number_{i}" for i in range(variables))
sum_func = eval(code, MyGlobals())
- expected = sum(range(limit))
+ expected = sum(range(variables))
# Warm up the the function for quickening (PEP 659)
for _ in range(30):
self.assertEqual(sum_func(), expected)
code = """if 1:
import sys
from _testinternalcapi import get_recursion_depth
+ from test import support
class MyException(Exception): pass
generator = gen()
next(generator)
recursionlimit = sys.getrecursionlimit()
- depth = get_recursion_depth()
try:
- # Upon the last recursive invocation of recurse(),
- # tstate->recursion_depth is equal to (recursion_limit - 1)
- # and is equal to recursion_limit when _gen_throw() calls
- # PyErr_NormalizeException().
- recurse(setrecursionlimit(depth + 2) - depth)
+ recurse(support.EXCEEDS_RECURSION_LIMIT)
finally:
sys.setrecursionlimit(recursionlimit)
print('Done.')
from test import support
-\f
+
class TestIsInstanceExceptions(unittest.TestCase):
# Test to make sure that an AttributeError when accessing the instance's
# class's bases is masked. This was actually a bug in Python 2.2 and
class D: pass
self.assertRaises(RuntimeError, isinstance, c, D)
-\f
+
# These tests are similar to above, but tickle certain code paths in
# issubclass() instead of isinstance() -- really PyObject_IsSubclass()
# vs. PyObject_IsInstance().
self.assertRaises(TypeError, issubclass, B, C())
-\f
+
# meta classes for creating abstract classes and instances
class AbstractClass(object):
def __init__(self, bases):
class Child(Super):
pass
-\f
+
class TestIsInstanceIsSubclass(unittest.TestCase):
# Tests to ensure that isinstance and issubclass work on abstract
# classes and instances. Before the 2.2 release, TypeErrors were
# Make sure that calling isinstance with a deeply nested tuple for its
# argument will raise RecursionError eventually.
tuple_arg = (compare_to,)
- for cnt in range(sys.getrecursionlimit()+5):
+ for cnt in range(support.EXCEEDS_RECURSION_LIMIT):
tuple_arg = (tuple_arg,)
fxn(arg, tuple_arg)
-\f
+
if __name__ == '__main__':
unittest.main()
def test_many_codeobjects(self):
# Issue2957: bad recursion count on code objects
- count = 5000 # more than MAX_MARSHAL_STACK_DEPTH
+ # more than MAX_MARSHAL_STACK_DEPTH
+ count = support.EXCEEDS_RECURSION_LIMIT
codes = (ExceptionTestCase.test_exceptions.__code__,) * count
marshal.loads(marshal.dumps(codes))
--- /dev/null
+Separate Python recursion checking from C recursion checking which reduces
+the chance of C stack overflow and allows the recursion limit to be
+increased safely.
{
PyThreadState *tstate = _PyThreadState_GET();
- /* subtract one to ignore the frame of the get_recursion_depth() call */
-
- return PyLong_FromLong(tstate->recursion_limit - tstate->recursion_remaining - 1);
+ return PyLong_FromLong(tstate->py_recursion_limit - tstate->py_recursion_remaining);
}
return NULL;
}
- int recursion_limit = Py_GetRecursionLimit();
int starting_recursion_depth;
/* Be careful here to prevent overflow. */
int COMPILER_STACK_FRAME_SCALE = 3;
if (!tstate) {
return 0;
}
- state->recursion_limit = (recursion_limit < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
- recursion_limit * COMPILER_STACK_FRAME_SCALE : recursion_limit;
- int recursion_depth = tstate->recursion_limit - tstate->recursion_remaining;
- starting_recursion_depth = (recursion_depth < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
- recursion_depth * COMPILER_STACK_FRAME_SCALE : recursion_depth;
+ state->recursion_limit = C_RECURSION_LIMIT * COMPILER_STACK_FRAME_SCALE;
+ int recursion_depth = C_RECURSION_LIMIT - tstate->c_recursion_remaining;
+ starting_recursion_depth = recursion_depth * COMPILER_STACK_FRAME_SCALE;
state->recursion_depth = starting_recursion_depth;
PyObject *result = ast2obj_mod(state, t);
return NULL;
}
- int recursion_limit = Py_GetRecursionLimit();
int starting_recursion_depth;
/* Be careful here to prevent overflow. */
int COMPILER_STACK_FRAME_SCALE = 3;
if (!tstate) {
return 0;
}
- state->recursion_limit = (recursion_limit < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
- recursion_limit * COMPILER_STACK_FRAME_SCALE : recursion_limit;
- int recursion_depth = tstate->recursion_limit - tstate->recursion_remaining;
- starting_recursion_depth = (recursion_depth < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
- recursion_depth * COMPILER_STACK_FRAME_SCALE : recursion_depth;
+ state->recursion_limit = C_RECURSION_LIMIT * COMPILER_STACK_FRAME_SCALE;
+ int recursion_depth = C_RECURSION_LIMIT - tstate->c_recursion_remaining;
+ starting_recursion_depth = recursion_depth * COMPILER_STACK_FRAME_SCALE;
state->recursion_depth = starting_recursion_depth;
PyObject *result = ast2obj_mod(state, t);
int res = -1;
struct validator state;
PyThreadState *tstate;
- int recursion_limit = Py_GetRecursionLimit();
int starting_recursion_depth;
/* Setup recursion depth check counters */
return 0;
}
/* Be careful here to prevent overflow. */
- int recursion_depth = tstate->recursion_limit - tstate->recursion_remaining;
- starting_recursion_depth = (recursion_depth< INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
- recursion_depth * COMPILER_STACK_FRAME_SCALE : recursion_depth;
+ int recursion_depth = C_RECURSION_LIMIT - tstate->c_recursion_remaining;
+ starting_recursion_depth = recursion_depth * COMPILER_STACK_FRAME_SCALE;
state.recursion_depth = starting_recursion_depth;
- state.recursion_limit = (recursion_limit < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
- recursion_limit * COMPILER_STACK_FRAME_SCALE : recursion_limit;
+ state.recursion_limit = C_RECURSION_LIMIT * COMPILER_STACK_FRAME_SCALE;
switch (mod->kind) {
case Module_kind:
_PyAST_Optimize(mod_ty mod, PyArena *arena, _PyASTOptimizeState *state)
{
PyThreadState *tstate;
- int recursion_limit = Py_GetRecursionLimit();
int starting_recursion_depth;
/* Setup recursion depth check counters */
return 0;
}
/* Be careful here to prevent overflow. */
- int recursion_depth = tstate->recursion_limit - tstate->recursion_remaining;
- starting_recursion_depth = (recursion_depth < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
- recursion_depth * COMPILER_STACK_FRAME_SCALE : recursion_depth;
+ int recursion_depth = C_RECURSION_LIMIT - tstate->c_recursion_remaining;
+ starting_recursion_depth = recursion_depth * COMPILER_STACK_FRAME_SCALE;
state->recursion_depth = starting_recursion_depth;
- state->recursion_limit = (recursion_limit < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
- recursion_limit * COMPILER_STACK_FRAME_SCALE : recursion_limit;
+ state->recursion_limit = C_RECURSION_LIMIT * COMPILER_STACK_FRAME_SCALE;
int ret = astfold_mod(mod, arena, state);
assert(ret || PyErr_Occurred());
PyInterpreterState *interp = _PyInterpreterState_GET();
interp->ceval.recursion_limit = new_limit;
for (PyThreadState *p = interp->threads.head; p != NULL; p = p->next) {
- int depth = p->recursion_limit - p->recursion_remaining;
- p->recursion_limit = new_limit;
- p->recursion_remaining = new_limit - depth;
+ int depth = p->py_recursion_limit - p->py_recursion_remaining;
+ p->py_recursion_limit = new_limit;
+ p->py_recursion_remaining = new_limit - depth;
}
}
int
_Py_CheckRecursiveCall(PyThreadState *tstate, const char *where)
{
- /* Check against global limit first. */
- int depth = tstate->recursion_limit - tstate->recursion_remaining;
- if (depth < tstate->interp->ceval.recursion_limit) {
- tstate->recursion_limit = tstate->interp->ceval.recursion_limit;
- tstate->recursion_remaining = tstate->recursion_limit - depth;
- assert(tstate->recursion_remaining > 0);
- return 0;
- }
#ifdef USE_STACKCHECK
if (PyOS_CheckStack()) {
- ++tstate->recursion_remaining;
+ ++tstate->c_recursion_remaining;
_PyErr_SetString(tstate, PyExc_MemoryError, "Stack overflow");
return -1;
}
#endif
if (tstate->recursion_headroom) {
- if (tstate->recursion_remaining < -50) {
+ if (tstate->c_recursion_remaining < -50) {
/* Overflowing while handling an overflow. Give up. */
Py_FatalError("Cannot recover from stack overflow.");
}
}
else {
- if (tstate->recursion_remaining <= 0) {
+ if (tstate->c_recursion_remaining <= 0) {
tstate->recursion_headroom++;
_PyErr_Format(tstate, PyExc_RecursionError,
"maximum recursion depth exceeded%s",
where);
tstate->recursion_headroom--;
- ++tstate->recursion_remaining;
+ ++tstate->c_recursion_remaining;
return -1;
}
}
return prev_frame;
}
+
+int _Py_CheckRecursiveCallPy(
+ PyThreadState *tstate)
+{
+ if (tstate->recursion_headroom) {
+ if (tstate->py_recursion_remaining < -50) {
+ /* Overflowing while handling an overflow. Give up. */
+ Py_FatalError("Cannot recover from Python stack overflow.");
+ }
+ }
+ else {
+ if (tstate->py_recursion_remaining <= 0) {
+ tstate->recursion_headroom++;
+ _PyErr_Format(tstate, PyExc_RecursionError,
+ "maximum recursion depth exceeded");
+ tstate->recursion_headroom--;
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static inline int _Py_EnterRecursivePy(PyThreadState *tstate) {
+ return (tstate->py_recursion_remaining-- <= 0) &&
+ _Py_CheckRecursiveCallPy(tstate);
+}
+
+
+static inline void _Py_LeaveRecursiveCallPy(PyThreadState *tstate) {
+ tstate->py_recursion_remaining++;
+}
+
+
/* It is only between the KW_NAMES instruction and the following CALL,
* that this has any meaning.
*/
frame->previous = prev_cframe->current_frame;
cframe.current_frame = frame;
+ if (_Py_EnterRecursiveCallTstate(tstate, "")) {
+ tstate->c_recursion_remaining--;
+ tstate->py_recursion_remaining--;
+ goto exit_unwind;
+ }
+
/* support for generator.throw() */
if (throwflag) {
- if (_Py_EnterRecursiveCallTstate(tstate, "")) {
- tstate->recursion_remaining--;
+ if (_Py_EnterRecursivePy(tstate)) {
goto exit_unwind;
}
TRACE_FUNCTION_THROW_ENTRY();
start_frame:
- if (_Py_EnterRecursiveCallTstate(tstate, "")) {
- tstate->recursion_remaining--;
+ if (_Py_EnterRecursivePy(tstate)) {
goto exit_unwind;
}
_PyFrame_SetStackPointer(frame, stack_pointer);
TRACE_FUNCTION_EXIT();
DTRACE_FUNCTION_EXIT();
- _Py_LeaveRecursiveCallTstate(tstate);
+ _Py_LeaveRecursiveCallPy(tstate);
if (!frame->is_entry) {
frame = cframe.current_frame = pop_frame(tstate, frame);
_PyFrame_StackPush(frame, retval);
goto resume_frame;
}
+ _Py_LeaveRecursiveCallTstate(tstate);
/* Restore previous cframe and return. */
tstate->cframe = cframe.previous;
tstate->cframe->use_tracing = cframe.use_tracing;
_PyFrame_SetStackPointer(frame, stack_pointer);
TRACE_FUNCTION_EXIT();
DTRACE_FUNCTION_EXIT();
+ _Py_LeaveRecursiveCallPy(tstate);
_Py_LeaveRecursiveCallTstate(tstate);
/* Restore previous cframe and return. */
tstate->cframe = cframe.previous;
assert(frame->frame_obj == NULL);
gen->gi_frame_state = FRAME_CREATED;
gen_frame->owner = FRAME_OWNED_BY_GENERATOR;
- _Py_LeaveRecursiveCallTstate(tstate);
+ _Py_LeaveRecursiveCallPy(tstate);
if (!frame->is_entry) {
_PyInterpreterFrame *prev = frame->previous;
_PyThreadState_PopFrame(tstate, frame);
_PyFrame_StackPush(frame, (PyObject *)gen);
goto resume_frame;
}
+ _Py_LeaveRecursiveCallTstate(tstate);
/* Make sure that frame is in a valid state */
frame->stacktop = 0;
frame->f_locals = NULL;
exit_unwind:
assert(_PyErr_Occurred(tstate));
- _Py_LeaveRecursiveCallTstate(tstate);
+ _Py_LeaveRecursiveCallPy(tstate);
if (frame->is_entry) {
/* Restore previous cframe and exit */
tstate->cframe = cframe.previous;
tstate->cframe->use_tracing = cframe.use_tracing;
assert(tstate->cframe->current_frame == frame->previous);
+ _Py_LeaveRecursiveCallTstate(tstate);
return NULL;
}
frame = cframe.current_frame = pop_frame(tstate, frame);
// _PyThreadState_PopFrame, since f_code is already cleared at that point:
assert((PyObject **)frame + frame->f_code->co_framesize ==
tstate->datastack_top);
- tstate->recursion_remaining--;
+ tstate->c_recursion_remaining--;
assert(frame->frame_obj == NULL || frame->frame_obj->f_frame == frame);
assert(frame->owner == FRAME_OWNED_BY_THREAD);
_PyFrame_Clear(frame);
- tstate->recursion_remaining++;
+ tstate->c_recursion_remaining++;
_PyThreadState_PopFrame(tstate, frame);
}
tstate->native_thread_id = PyThread_get_thread_native_id();
#endif
- tstate->recursion_limit = interp->ceval.recursion_limit,
- tstate->recursion_remaining = interp->ceval.recursion_limit,
+ tstate->py_recursion_limit = interp->ceval.recursion_limit,
+ tstate->py_recursion_remaining = interp->ceval.recursion_limit,
+ tstate->c_recursion_remaining = C_RECURSION_LIMIT;
tstate->exc_info = &tstate->exc_state;
asdl_stmt_seq *seq;
int i;
PyThreadState *tstate;
- int recursion_limit = Py_GetRecursionLimit();
int starting_recursion_depth;
if (st == NULL)
return NULL;
}
/* Be careful here to prevent overflow. */
- int recursion_depth = tstate->recursion_limit - tstate->recursion_remaining;
- starting_recursion_depth = (recursion_depth < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
- recursion_depth * COMPILER_STACK_FRAME_SCALE : recursion_depth;
+ int recursion_depth = C_RECURSION_LIMIT - tstate->c_recursion_remaining;
+ starting_recursion_depth = recursion_depth * COMPILER_STACK_FRAME_SCALE;
st->recursion_depth = starting_recursion_depth;
- st->recursion_limit = (recursion_limit < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
- recursion_limit * COMPILER_STACK_FRAME_SCALE : recursion_limit;
+ st->recursion_limit = C_RECURSION_LIMIT * COMPILER_STACK_FRAME_SCALE;
/* Make the initial symbol information gathering pass */
if (!symtable_enter_block(st, &_Py_ID(top), ModuleBlock, (void *)mod, 0, 0, 0, 0)) {
/* Reject too low new limit if the current recursion depth is higher than
the new low-water mark. */
- int depth = tstate->recursion_limit - tstate->recursion_remaining;
+ int depth = tstate->py_recursion_limit - tstate->py_recursion_remaining;
if (depth >= new_limit) {
_PyErr_Format(tstate, PyExc_RecursionError,
"cannot set the recursion limit to %i at "