GH-91079: Decouple C stack overflow checks from Python recursion checks. (GH-96510)

This commit is contained in:
Mark Shannon 2022-10-05 01:34:03 +01:00 committed by GitHub
parent 0ff8fd6583
commit 76449350b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 165 additions and 99 deletions

View File

@ -95,8 +95,10 @@ struct _ts {
/* Was this thread state statically allocated? */ /* Was this thread state statically allocated? */
int _static; int _static;
int recursion_remaining; int py_recursion_remaining;
int recursion_limit; int py_recursion_limit;
int c_recursion_remaining;
int recursion_headroom; /* Allow 50 more calls to handle any errors. */ int recursion_headroom; /* Allow 50 more calls to handle any errors. */
/* 'tracing' keeps track of the execution depth when tracing/profiling. /* 'tracing' keeps track of the execution depth when tracing/profiling.
@ -202,6 +204,16 @@ struct _ts {
_PyCFrame root_cframe; _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 */ /* other API */

View File

@ -12,15 +12,8 @@ extern "C" {
struct pyruntimestate; struct pyruntimestate;
struct _ceval_runtime_state; 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 #ifndef Py_DEFAULT_RECURSION_LIMIT
# ifdef __wasi__ # define Py_DEFAULT_RECURSION_LIMIT 1000
# define Py_DEFAULT_RECURSION_LIMIT 600
# else
# define Py_DEFAULT_RECURSION_LIMIT 1000
# endif
#endif #endif
#include "pycore_interp.h" // PyInterpreterState.eval_frame #include "pycore_interp.h" // PyInterpreterState.eval_frame
@ -118,12 +111,12 @@ extern void _PyEval_DeactivateOpCache(void);
/* With USE_STACKCHECK macro defined, trigger stack checks in /* With USE_STACKCHECK macro defined, trigger stack checks in
_Py_CheckRecursiveCall() on every 64th call to _Py_EnterRecursiveCall. */ _Py_CheckRecursiveCall() on every 64th call to _Py_EnterRecursiveCall. */
static inline int _Py_MakeRecCheck(PyThreadState *tstate) { static inline int _Py_MakeRecCheck(PyThreadState *tstate) {
return (tstate->recursion_remaining-- <= 0 return (tstate->c_recursion_remaining-- <= 0
|| (tstate->recursion_remaining & 63) == 0); || (tstate->c_recursion_remaining & 63) == 0);
} }
#else #else
static inline int _Py_MakeRecCheck(PyThreadState *tstate) { static inline int _Py_MakeRecCheck(PyThreadState *tstate) {
return tstate->recursion_remaining-- <= 0; return tstate->c_recursion_remaining-- <= 0;
} }
#endif #endif
@ -131,6 +124,9 @@ PyAPI_FUNC(int) _Py_CheckRecursiveCall(
PyThreadState *tstate, PyThreadState *tstate,
const char *where); const char *where);
int _Py_CheckRecursiveCallPy(
PyThreadState *tstate);
static inline int _Py_EnterRecursiveCallTstate(PyThreadState *tstate, static inline int _Py_EnterRecursiveCallTstate(PyThreadState *tstate,
const char *where) { const char *where) {
return (_Py_MakeRecCheck(tstate) && _Py_CheckRecursiveCall(tstate, where)); return (_Py_MakeRecCheck(tstate) && _Py_CheckRecursiveCall(tstate, where));
@ -142,7 +138,7 @@ static inline int _Py_EnterRecursiveCall(const char *where) {
} }
static inline void _Py_LeaveRecursiveCallTstate(PyThreadState *tstate) { static inline void _Py_LeaveRecursiveCallTstate(PyThreadState *tstate) {
tstate->recursion_remaining++; tstate->c_recursion_remaining++;
} }
static inline void _Py_LeaveRecursiveCall(void) { static inline void _Py_LeaveRecursiveCall(void) {
@ -157,6 +153,7 @@ extern PyObject* _Py_MakeCoro(PyFunctionObject *func);
extern int _Py_HandlePending(PyThreadState *tstate); extern int _Py_HandlePending(PyThreadState *tstate);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@ -68,7 +68,7 @@ extern "C" {
#define _PyThreadState_INIT \ #define _PyThreadState_INIT \
{ \ { \
._static = 1, \ ._static = 1, \
.recursion_limit = Py_DEFAULT_RECURSION_LIMIT, \ .py_recursion_limit = Py_DEFAULT_RECURSION_LIMIT, \
.context_ver = 1, \ .context_ver = 1, \
} }

View File

@ -60,7 +60,7 @@ __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", "Py_DEBUG", "EXCEEDS_RECURSION_LIMIT",
] ]
@ -2352,3 +2352,6 @@ def adjust_int_max_str_digits(max_digits):
yield yield
finally: finally:
sys.set_int_max_str_digits(current) sys.set_int_max_str_digits(current)
#For recursion tests, easily exceeds default recursion limit
EXCEEDS_RECURSION_LIMIT = 5000

View File

@ -825,9 +825,9 @@ class AST_Tests(unittest.TestCase):
@support.cpython_only @support.cpython_only
def test_ast_recursion_limit(self): def test_ast_recursion_limit(self):
fail_depth = sys.getrecursionlimit() * 3 fail_depth = support.EXCEEDS_RECURSION_LIMIT
crash_depth = sys.getrecursionlimit() * 300 crash_depth = 100_000
success_depth = int(fail_depth * 0.75) success_depth = 1200
def check_limit(prefix, repeated): def check_limit(prefix, repeated):
expect_ok = prefix + repeated * success_depth expect_ok = prefix + repeated * success_depth

View File

@ -864,6 +864,44 @@ class TestErrorMessagesUseQualifiedName(unittest.TestCase):
with self.check_raises_type_error(msg): with self.check_raises_type_error(msg):
A().method_two_args("x", "y", x="oops") 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__": if __name__ == "__main__":
unittest.main() unittest.main()

View File

@ -545,7 +545,7 @@ class TestNamedTuple(unittest.TestCase):
self.assertEqual(Dot(1)._replace(d=999), (999,)) self.assertEqual(Dot(1)._replace(d=999), (999,))
self.assertEqual(Dot(1)._fields, ('d',)) self.assertEqual(Dot(1)._fields, ('d',))
n = 5000 n = support.EXCEEDS_RECURSION_LIMIT
names = list(set(''.join([choice(string.ascii_letters) names = list(set(''.join([choice(string.ascii_letters)
for j in range(10)]) for i in range(n))) for j in range(10)]) for i in range(n)))
n = len(names) n = len(names)

View File

@ -111,8 +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):
# default: 1000 * 2.5 = 2500 repetitions repeat = 2000
repeat = int(sys.getrecursionlimit() * 2.5)
longexpr = 'x = x or ' + '-x' * repeat longexpr = 'x = x or ' + '-x' * repeat
g = {} g = {}
code = ''' code = '''

View File

@ -140,11 +140,11 @@ class RebindBuiltinsTests(unittest.TestCase):
def __missing__(self, key): def __missing__(self, key):
return int(key.removeprefix("_number_")) return int(key.removeprefix("_number_"))
# 1,000 on most systems # Need more than 256 variables to use EXTENDED_ARGS
limit = sys.getrecursionlimit() variables = 400
code = "lambda: " + "+".join(f"_number_{i}" for i in range(limit)) code = "lambda: " + "+".join(f"_number_{i}" for i in range(variables))
sum_func = eval(code, MyGlobals()) sum_func = eval(code, MyGlobals())
expected = sum(range(limit)) expected = sum(range(variables))
# Warm up the the function for quickening (PEP 659) # Warm up the the function for quickening (PEP 659)
for _ in range(30): for _ in range(30):
self.assertEqual(sum_func(), expected) self.assertEqual(sum_func(), expected)

View File

@ -1372,6 +1372,7 @@ class ExceptionTests(unittest.TestCase):
code = """if 1: code = """if 1:
import sys import sys
from _testinternalcapi import get_recursion_depth from _testinternalcapi import get_recursion_depth
from test import support
class MyException(Exception): pass class MyException(Exception): pass
@ -1399,13 +1400,8 @@ class ExceptionTests(unittest.TestCase):
generator = gen() generator = gen()
next(generator) next(generator)
recursionlimit = sys.getrecursionlimit() recursionlimit = sys.getrecursionlimit()
depth = get_recursion_depth()
try: try:
# Upon the last recursive invocation of recurse(), recurse(support.EXCEEDS_RECURSION_LIMIT)
# 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)
finally: finally:
sys.setrecursionlimit(recursionlimit) sys.setrecursionlimit(recursionlimit)
print('Done.') print('Done.')

View File

@ -8,7 +8,7 @@ import typing
from test import support from test import support
class TestIsInstanceExceptions(unittest.TestCase): class TestIsInstanceExceptions(unittest.TestCase):
# Test to make sure that an AttributeError when accessing the instance's # 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's bases is masked. This was actually a bug in Python 2.2 and
@ -97,7 +97,7 @@ class TestIsInstanceExceptions(unittest.TestCase):
class D: pass class D: pass
self.assertRaises(RuntimeError, isinstance, c, D) self.assertRaises(RuntimeError, isinstance, c, D)
# These tests are similar to above, but tickle certain code paths in # These tests are similar to above, but tickle certain code paths in
# issubclass() instead of isinstance() -- really PyObject_IsSubclass() # issubclass() instead of isinstance() -- really PyObject_IsSubclass()
# vs. PyObject_IsInstance(). # vs. PyObject_IsInstance().
@ -147,7 +147,7 @@ class TestIsSubclassExceptions(unittest.TestCase):
self.assertRaises(TypeError, issubclass, B, C()) self.assertRaises(TypeError, issubclass, B, C())
# meta classes for creating abstract classes and instances # meta classes for creating abstract classes and instances
class AbstractClass(object): class AbstractClass(object):
def __init__(self, bases): def __init__(self, bases):
@ -179,7 +179,7 @@ class Super:
class Child(Super): class Child(Super):
pass pass
class TestIsInstanceIsSubclass(unittest.TestCase): class TestIsInstanceIsSubclass(unittest.TestCase):
# Tests to ensure that isinstance and issubclass work on abstract # Tests to ensure that isinstance and issubclass work on abstract
# classes and instances. Before the 2.2 release, TypeErrors were # classes and instances. Before the 2.2 release, TypeErrors were
@ -353,10 +353,10 @@ def blowstack(fxn, arg, compare_to):
# Make sure that calling isinstance with a deeply nested tuple for its # Make sure that calling isinstance with a deeply nested tuple for its
# argument will raise RecursionError eventually. # argument will raise RecursionError eventually.
tuple_arg = (compare_to,) tuple_arg = (compare_to,)
for cnt in range(sys.getrecursionlimit()+5): for cnt in range(support.EXCEEDS_RECURSION_LIMIT):
tuple_arg = (tuple_arg,) tuple_arg = (tuple_arg,)
fxn(arg, tuple_arg) fxn(arg, tuple_arg)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -117,7 +117,8 @@ class CodeTestCase(unittest.TestCase):
def test_many_codeobjects(self): def test_many_codeobjects(self):
# Issue2957: bad recursion count on code objects # 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 codes = (ExceptionTestCase.test_exceptions.__code__,) * count
marshal.loads(marshal.dumps(codes)) marshal.loads(marshal.dumps(codes))

View File

@ -0,0 +1,3 @@
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.

View File

@ -44,9 +44,7 @@ get_recursion_depth(PyObject *self, PyObject *Py_UNUSED(args))
{ {
PyThreadState *tstate = _PyThreadState_GET(); PyThreadState *tstate = _PyThreadState_GET();
/* subtract one to ignore the frame of the get_recursion_depth() call */ return PyLong_FromLong(tstate->py_recursion_limit - tstate->py_recursion_remaining);
return PyLong_FromLong(tstate->recursion_limit - tstate->recursion_remaining - 1);
} }

View File

@ -1380,7 +1380,6 @@ PyObject* PyAST_mod2obj(mod_ty t)
return NULL; return NULL;
} }
int recursion_limit = Py_GetRecursionLimit();
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 = 3;
@ -1388,11 +1387,9 @@ PyObject* PyAST_mod2obj(mod_ty t)
if (!tstate) { if (!tstate) {
return 0; return 0;
} }
state->recursion_limit = (recursion_limit < INT_MAX / COMPILER_STACK_FRAME_SCALE) ? state->recursion_limit = C_RECURSION_LIMIT * COMPILER_STACK_FRAME_SCALE;
recursion_limit * COMPILER_STACK_FRAME_SCALE : recursion_limit; int recursion_depth = C_RECURSION_LIMIT - tstate->c_recursion_remaining;
int recursion_depth = tstate->recursion_limit - tstate->recursion_remaining; starting_recursion_depth = recursion_depth * COMPILER_STACK_FRAME_SCALE;
starting_recursion_depth = (recursion_depth < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
recursion_depth * COMPILER_STACK_FRAME_SCALE : recursion_depth;
state->recursion_depth = starting_recursion_depth; state->recursion_depth = starting_recursion_depth;
PyObject *result = ast2obj_mod(state, t); PyObject *result = ast2obj_mod(state, t);

9
Python/Python-ast.c generated
View File

@ -12315,7 +12315,6 @@ PyObject* PyAST_mod2obj(mod_ty t)
return NULL; return NULL;
} }
int recursion_limit = Py_GetRecursionLimit();
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 = 3;
@ -12323,11 +12322,9 @@ PyObject* PyAST_mod2obj(mod_ty t)
if (!tstate) { if (!tstate) {
return 0; return 0;
} }
state->recursion_limit = (recursion_limit < INT_MAX / COMPILER_STACK_FRAME_SCALE) ? state->recursion_limit = C_RECURSION_LIMIT * COMPILER_STACK_FRAME_SCALE;
recursion_limit * COMPILER_STACK_FRAME_SCALE : recursion_limit; int recursion_depth = C_RECURSION_LIMIT - tstate->c_recursion_remaining;
int recursion_depth = tstate->recursion_limit - tstate->recursion_remaining; starting_recursion_depth = recursion_depth * COMPILER_STACK_FRAME_SCALE;
starting_recursion_depth = (recursion_depth < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
recursion_depth * COMPILER_STACK_FRAME_SCALE : recursion_depth;
state->recursion_depth = starting_recursion_depth; state->recursion_depth = starting_recursion_depth;
PyObject *result = ast2obj_mod(state, t); PyObject *result = ast2obj_mod(state, t);

View File

@ -975,7 +975,6 @@ _PyAST_Validate(mod_ty mod)
int res = -1; int res = -1;
struct validator state; struct validator state;
PyThreadState *tstate; PyThreadState *tstate;
int recursion_limit = Py_GetRecursionLimit();
int starting_recursion_depth; int starting_recursion_depth;
/* Setup recursion depth check counters */ /* Setup recursion depth check counters */
@ -984,12 +983,10 @@ _PyAST_Validate(mod_ty mod)
return 0; return 0;
} }
/* Be careful here to prevent overflow. */ /* Be careful here to prevent overflow. */
int recursion_depth = tstate->recursion_limit - tstate->recursion_remaining; int recursion_depth = C_RECURSION_LIMIT - tstate->c_recursion_remaining;
starting_recursion_depth = (recursion_depth< INT_MAX / COMPILER_STACK_FRAME_SCALE) ? starting_recursion_depth = recursion_depth * COMPILER_STACK_FRAME_SCALE;
recursion_depth * COMPILER_STACK_FRAME_SCALE : recursion_depth;
state.recursion_depth = starting_recursion_depth; state.recursion_depth = starting_recursion_depth;
state.recursion_limit = (recursion_limit < INT_MAX / COMPILER_STACK_FRAME_SCALE) ? state.recursion_limit = C_RECURSION_LIMIT * COMPILER_STACK_FRAME_SCALE;
recursion_limit * COMPILER_STACK_FRAME_SCALE : recursion_limit;
switch (mod->kind) { switch (mod->kind) {
case Module_kind: case Module_kind:

View File

@ -1080,7 +1080,6 @@ int
_PyAST_Optimize(mod_ty mod, PyArena *arena, _PyASTOptimizeState *state) _PyAST_Optimize(mod_ty mod, PyArena *arena, _PyASTOptimizeState *state)
{ {
PyThreadState *tstate; PyThreadState *tstate;
int recursion_limit = Py_GetRecursionLimit();
int starting_recursion_depth; int starting_recursion_depth;
/* Setup recursion depth check counters */ /* Setup recursion depth check counters */
@ -1089,12 +1088,10 @@ _PyAST_Optimize(mod_ty mod, PyArena *arena, _PyASTOptimizeState *state)
return 0; return 0;
} }
/* Be careful here to prevent overflow. */ /* Be careful here to prevent overflow. */
int recursion_depth = tstate->recursion_limit - tstate->recursion_remaining; int recursion_depth = C_RECURSION_LIMIT - tstate->c_recursion_remaining;
starting_recursion_depth = (recursion_depth < INT_MAX / COMPILER_STACK_FRAME_SCALE) ? starting_recursion_depth = recursion_depth * COMPILER_STACK_FRAME_SCALE;
recursion_depth * COMPILER_STACK_FRAME_SCALE : recursion_depth;
state->recursion_depth = starting_recursion_depth; state->recursion_depth = starting_recursion_depth;
state->recursion_limit = (recursion_limit < INT_MAX / COMPILER_STACK_FRAME_SCALE) ? state->recursion_limit = C_RECURSION_LIMIT * COMPILER_STACK_FRAME_SCALE;
recursion_limit * COMPILER_STACK_FRAME_SCALE : recursion_limit;
int ret = astfold_mod(mod, arena, state); int ret = astfold_mod(mod, arena, state);
assert(ret || PyErr_Occurred()); assert(ret || PyErr_Occurred());

View File

@ -257,9 +257,9 @@ Py_SetRecursionLimit(int new_limit)
PyInterpreterState *interp = _PyInterpreterState_GET(); PyInterpreterState *interp = _PyInterpreterState_GET();
interp->ceval.recursion_limit = new_limit; interp->ceval.recursion_limit = new_limit;
for (PyThreadState *p = interp->threads.head; p != NULL; p = p->next) { for (PyThreadState *p = interp->threads.head; p != NULL; p = p->next) {
int depth = p->recursion_limit - p->recursion_remaining; int depth = p->py_recursion_limit - p->py_recursion_remaining;
p->recursion_limit = new_limit; p->py_recursion_limit = new_limit;
p->recursion_remaining = new_limit - depth; p->py_recursion_remaining = new_limit - depth;
} }
} }
@ -268,35 +268,27 @@ Py_SetRecursionLimit(int new_limit)
int int
_Py_CheckRecursiveCall(PyThreadState *tstate, const char *where) _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 #ifdef USE_STACKCHECK
if (PyOS_CheckStack()) { if (PyOS_CheckStack()) {
++tstate->recursion_remaining; ++tstate->c_recursion_remaining;
_PyErr_SetString(tstate, PyExc_MemoryError, "Stack overflow"); _PyErr_SetString(tstate, PyExc_MemoryError, "Stack overflow");
return -1; return -1;
} }
#endif #endif
if (tstate->recursion_headroom) { if (tstate->recursion_headroom) {
if (tstate->recursion_remaining < -50) { if (tstate->c_recursion_remaining < -50) {
/* Overflowing while handling an overflow. Give up. */ /* Overflowing while handling an overflow. Give up. */
Py_FatalError("Cannot recover from stack overflow."); Py_FatalError("Cannot recover from stack overflow.");
} }
} }
else { else {
if (tstate->recursion_remaining <= 0) { if (tstate->c_recursion_remaining <= 0) {
tstate->recursion_headroom++; tstate->recursion_headroom++;
_PyErr_Format(tstate, PyExc_RecursionError, _PyErr_Format(tstate, PyExc_RecursionError,
"maximum recursion depth exceeded%s", "maximum recursion depth exceeded%s",
where); where);
tstate->recursion_headroom--; tstate->recursion_headroom--;
++tstate->recursion_remaining; ++tstate->c_recursion_remaining;
return -1; return -1;
} }
} }
@ -983,6 +975,39 @@ pop_frame(PyThreadState *tstate, _PyInterpreterFrame *frame)
return prev_frame; 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, /* It is only between the KW_NAMES instruction and the following CALL,
* that this has any meaning. * that this has any meaning.
*/ */
@ -1037,10 +1062,15 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
frame->previous = prev_cframe->current_frame; frame->previous = prev_cframe->current_frame;
cframe.current_frame = frame; cframe.current_frame = frame;
if (_Py_EnterRecursiveCallTstate(tstate, "")) {
tstate->c_recursion_remaining--;
tstate->py_recursion_remaining--;
goto exit_unwind;
}
/* support for generator.throw() */ /* support for generator.throw() */
if (throwflag) { if (throwflag) {
if (_Py_EnterRecursiveCallTstate(tstate, "")) { if (_Py_EnterRecursivePy(tstate)) {
tstate->recursion_remaining--;
goto exit_unwind; goto exit_unwind;
} }
TRACE_FUNCTION_THROW_ENTRY(); TRACE_FUNCTION_THROW_ENTRY();
@ -1079,8 +1109,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
start_frame: start_frame:
if (_Py_EnterRecursiveCallTstate(tstate, "")) { if (_Py_EnterRecursivePy(tstate)) {
tstate->recursion_remaining--;
goto exit_unwind; goto exit_unwind;
} }
@ -1830,12 +1859,13 @@ handle_eval_breaker:
_PyFrame_SetStackPointer(frame, stack_pointer); _PyFrame_SetStackPointer(frame, stack_pointer);
TRACE_FUNCTION_EXIT(); TRACE_FUNCTION_EXIT();
DTRACE_FUNCTION_EXIT(); DTRACE_FUNCTION_EXIT();
_Py_LeaveRecursiveCallTstate(tstate); _Py_LeaveRecursiveCallPy(tstate);
if (!frame->is_entry) { if (!frame->is_entry) {
frame = cframe.current_frame = pop_frame(tstate, frame); frame = cframe.current_frame = pop_frame(tstate, frame);
_PyFrame_StackPush(frame, retval); _PyFrame_StackPush(frame, retval);
goto resume_frame; goto resume_frame;
} }
_Py_LeaveRecursiveCallTstate(tstate);
/* Restore previous cframe and return. */ /* Restore previous cframe and return. */
tstate->cframe = cframe.previous; tstate->cframe = cframe.previous;
tstate->cframe->use_tracing = cframe.use_tracing; tstate->cframe->use_tracing = cframe.use_tracing;
@ -2046,6 +2076,7 @@ handle_eval_breaker:
_PyFrame_SetStackPointer(frame, stack_pointer); _PyFrame_SetStackPointer(frame, stack_pointer);
TRACE_FUNCTION_EXIT(); TRACE_FUNCTION_EXIT();
DTRACE_FUNCTION_EXIT(); DTRACE_FUNCTION_EXIT();
_Py_LeaveRecursiveCallPy(tstate);
_Py_LeaveRecursiveCallTstate(tstate); _Py_LeaveRecursiveCallTstate(tstate);
/* Restore previous cframe and return. */ /* Restore previous cframe and return. */
tstate->cframe = cframe.previous; tstate->cframe = cframe.previous;
@ -4800,7 +4831,7 @@ handle_eval_breaker:
assert(frame->frame_obj == NULL); assert(frame->frame_obj == NULL);
gen->gi_frame_state = FRAME_CREATED; gen->gi_frame_state = FRAME_CREATED;
gen_frame->owner = FRAME_OWNED_BY_GENERATOR; gen_frame->owner = FRAME_OWNED_BY_GENERATOR;
_Py_LeaveRecursiveCallTstate(tstate); _Py_LeaveRecursiveCallPy(tstate);
if (!frame->is_entry) { if (!frame->is_entry) {
_PyInterpreterFrame *prev = frame->previous; _PyInterpreterFrame *prev = frame->previous;
_PyThreadState_PopFrame(tstate, frame); _PyThreadState_PopFrame(tstate, frame);
@ -4808,6 +4839,7 @@ handle_eval_breaker:
_PyFrame_StackPush(frame, (PyObject *)gen); _PyFrame_StackPush(frame, (PyObject *)gen);
goto resume_frame; goto resume_frame;
} }
_Py_LeaveRecursiveCallTstate(tstate);
/* Make sure that frame is in a valid state */ /* Make sure that frame is in a valid state */
frame->stacktop = 0; frame->stacktop = 0;
frame->f_locals = NULL; frame->f_locals = NULL;
@ -5178,12 +5210,13 @@ exception_unwind:
exit_unwind: exit_unwind:
assert(_PyErr_Occurred(tstate)); assert(_PyErr_Occurred(tstate));
_Py_LeaveRecursiveCallTstate(tstate); _Py_LeaveRecursiveCallPy(tstate);
if (frame->is_entry) { if (frame->is_entry) {
/* Restore previous cframe and exit */ /* Restore previous cframe and exit */
tstate->cframe = cframe.previous; tstate->cframe = cframe.previous;
tstate->cframe->use_tracing = cframe.use_tracing; tstate->cframe->use_tracing = cframe.use_tracing;
assert(tstate->cframe->current_frame == frame->previous); assert(tstate->cframe->current_frame == frame->previous);
_Py_LeaveRecursiveCallTstate(tstate);
return NULL; return NULL;
} }
frame = cframe.current_frame = pop_frame(tstate, frame); frame = cframe.current_frame = pop_frame(tstate, frame);
@ -5755,11 +5788,11 @@ _PyEvalFrameClearAndPop(PyThreadState *tstate, _PyInterpreterFrame * frame)
// _PyThreadState_PopFrame, since f_code is already cleared at that point: // _PyThreadState_PopFrame, since f_code is already cleared at that point:
assert((PyObject **)frame + frame->f_code->co_framesize == assert((PyObject **)frame + frame->f_code->co_framesize ==
tstate->datastack_top); tstate->datastack_top);
tstate->recursion_remaining--; tstate->c_recursion_remaining--;
assert(frame->frame_obj == NULL || frame->frame_obj->f_frame == frame); assert(frame->frame_obj == NULL || frame->frame_obj->f_frame == frame);
assert(frame->owner == FRAME_OWNED_BY_THREAD); assert(frame->owner == FRAME_OWNED_BY_THREAD);
_PyFrame_Clear(frame); _PyFrame_Clear(frame);
tstate->recursion_remaining++; tstate->c_recursion_remaining++;
_PyThreadState_PopFrame(tstate, frame); _PyThreadState_PopFrame(tstate, frame);
} }

View File

@ -792,8 +792,9 @@ init_threadstate(PyThreadState *tstate,
tstate->native_thread_id = PyThread_get_thread_native_id(); tstate->native_thread_id = PyThread_get_thread_native_id();
#endif #endif
tstate->recursion_limit = interp->ceval.recursion_limit, tstate->py_recursion_limit = interp->ceval.recursion_limit,
tstate->recursion_remaining = 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; tstate->exc_info = &tstate->exc_state;

View File

@ -278,7 +278,6 @@ _PySymtable_Build(mod_ty mod, PyObject *filename, PyFutureFeatures *future)
asdl_stmt_seq *seq; asdl_stmt_seq *seq;
int i; int i;
PyThreadState *tstate; PyThreadState *tstate;
int recursion_limit = Py_GetRecursionLimit();
int starting_recursion_depth; int starting_recursion_depth;
if (st == NULL) if (st == NULL)
@ -298,12 +297,10 @@ _PySymtable_Build(mod_ty mod, PyObject *filename, PyFutureFeatures *future)
return NULL; return NULL;
} }
/* Be careful here to prevent overflow. */ /* Be careful here to prevent overflow. */
int recursion_depth = tstate->recursion_limit - tstate->recursion_remaining; int recursion_depth = C_RECURSION_LIMIT - tstate->c_recursion_remaining;
starting_recursion_depth = (recursion_depth < INT_MAX / COMPILER_STACK_FRAME_SCALE) ? starting_recursion_depth = recursion_depth * COMPILER_STACK_FRAME_SCALE;
recursion_depth * COMPILER_STACK_FRAME_SCALE : recursion_depth;
st->recursion_depth = starting_recursion_depth; st->recursion_depth = starting_recursion_depth;
st->recursion_limit = (recursion_limit < INT_MAX / COMPILER_STACK_FRAME_SCALE) ? st->recursion_limit = C_RECURSION_LIMIT * COMPILER_STACK_FRAME_SCALE;
recursion_limit * COMPILER_STACK_FRAME_SCALE : recursion_limit;
/* Make the initial symbol information gathering pass */ /* Make the initial symbol information gathering pass */
if (!symtable_enter_block(st, &_Py_ID(top), ModuleBlock, (void *)mod, 0, 0, 0, 0)) { if (!symtable_enter_block(st, &_Py_ID(top), ModuleBlock, (void *)mod, 0, 0, 0, 0)) {

View File

@ -1218,7 +1218,7 @@ sys_setrecursionlimit_impl(PyObject *module, int new_limit)
/* Reject too low new limit if the current recursion depth is higher than /* Reject too low new limit if the current recursion depth is higher than
the new low-water mark. */ 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) { if (depth >= new_limit) {
_PyErr_Format(tstate, PyExc_RecursionError, _PyErr_Format(tstate, PyExc_RecursionError,
"cannot set the recursion limit to %i at " "cannot set the recursion limit to %i at "