Thanks to visit codestin.com
Credit goes to github.com

Skip to content

bpo-45753: Make recursion checks more efficient. #29524

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Nov 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Include/cpython/pystate.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,9 @@ struct _ts {
struct _ts *next;
PyInterpreterState *interp;

int recursion_depth;
int recursion_remaining;
int recursion_limit;
int recursion_headroom; /* Allow 50 more calls to handle any errors. */
int stackcheck_counter;

/* 'tracing' keeps track of the execution depth when tracing/profiling.
This is to prevent the actual trace/profile code from being recorded in
Expand Down
8 changes: 4 additions & 4 deletions Include/internal/pycore_ceval.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,12 @@ extern void _PyEval_DeactivateOpCache(void);
/* 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_depth > tstate->interp->ceval.recursion_limit
|| ++tstate->stackcheck_counter > 64);
return (tstate->recursion_remaining-- <= 0
|| (tstate->recursion_remaining & 63) == 0);
}
#else
static inline int _Py_MakeRecCheck(PyThreadState *tstate) {
return (++tstate->recursion_depth > tstate->interp->ceval.recursion_limit);
return tstate->recursion_remaining-- <= 0;
}
#endif

Expand All @@ -101,7 +101,7 @@ static inline int _Py_EnterRecursiveCall_inline(const char *where) {
#define Py_EnterRecursiveCall(where) _Py_EnterRecursiveCall_inline(where)

static inline void _Py_LeaveRecursiveCall(PyThreadState *tstate) {
tstate->recursion_depth--;
tstate->recursion_remaining++;
}

static inline void _Py_LeaveRecursiveCall_inline(void) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Make recursion checks a bit more efficient by tracking amount of calls left
before overflow.
3 changes: 2 additions & 1 deletion Modules/_testinternalcapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ get_recursion_depth(PyObject *self, PyObject *Py_UNUSED(args))
PyThreadState *tstate = _PyThreadState_GET();

/* subtract one to ignore the frame of the get_recursion_depth() call */
return PyLong_FromLong(tstate->recursion_depth - 1);

return PyLong_FromLong(tstate->recursion_limit - tstate->recursion_remaining - 1);
}


Expand Down
5 changes: 3 additions & 2 deletions Python/ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -933,8 +933,9 @@ _PyAST_Validate(mod_ty mod)
return 0;
}
/* Be careful here to prevent overflow. */
starting_recursion_depth = (tstate->recursion_depth < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
tstate->recursion_depth * COMPILER_STACK_FRAME_SCALE : tstate->recursion_depth;
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_depth = starting_recursion_depth;
state.recursion_limit = (recursion_limit < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
recursion_limit * COMPILER_STACK_FRAME_SCALE : recursion_limit;
Expand Down
5 changes: 3 additions & 2 deletions Python/ast_opt.c
Original file line number Diff line number Diff line change
Expand Up @@ -1098,8 +1098,9 @@ _PyAST_Optimize(mod_ty mod, PyArena *arena, _PyASTOptimizeState *state)
return 0;
}
/* Be careful here to prevent overflow. */
starting_recursion_depth = (tstate->recursion_depth < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
tstate->recursion_depth * COMPILER_STACK_FRAME_SCALE : tstate->recursion_depth;
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_depth = starting_recursion_depth;
state->recursion_limit = (recursion_limit < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
recursion_limit * COMPILER_STACK_FRAME_SCALE : recursion_limit;
Expand Down
41 changes: 24 additions & 17 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -787,42 +787,49 @@ Py_GetRecursionLimit(void)
void
Py_SetRecursionLimit(int new_limit)
{
PyThreadState *tstate = _PyThreadState_GET();
tstate->interp->ceval.recursion_limit = new_limit;
PyInterpreterState *interp = _PyInterpreterState_GET();
interp->ceval.recursion_limit = new_limit;
for (PyThreadState *p = interp->tstate_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;
}
}

/* The function _Py_EnterRecursiveCall() only calls _Py_CheckRecursiveCall()
if the recursion_depth reaches recursion_limit.
If USE_STACKCHECK, the macro decrements recursion_limit
to guarantee that _Py_CheckRecursiveCall() is regularly called.
Without USE_STACKCHECK, there is no need for this. */
if the recursion_depth reaches recursion_limit. */
int
_Py_CheckRecursiveCall(PyThreadState *tstate, const char *where)
{
int recursion_limit = tstate->interp->ceval.recursion_limit;

/* 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
tstate->stackcheck_counter = 0;
if (PyOS_CheckStack()) {
--tstate->recursion_depth;
++tstate->recursion_remaining;
_PyErr_SetString(tstate, PyExc_MemoryError, "Stack overflow");
return -1;
}
#endif
if (tstate->recursion_headroom) {
if (tstate->recursion_depth > recursion_limit + 50) {
if (tstate->recursion_remaining < -50) {
/* Overflowing while handling an overflow. Give up. */
Py_FatalError("Cannot recover from stack overflow.");
}
}
else {
if (tstate->recursion_depth > recursion_limit) {
if (tstate->recursion_remaining <= 0) {
tstate->recursion_headroom++;
_PyErr_Format(tstate, PyExc_RecursionError,
"maximum recursion depth exceeded%s",
where);
tstate->recursion_headroom--;
--tstate->recursion_depth;
++tstate->recursion_remaining;
return -1;
}
}
Expand Down Expand Up @@ -1567,7 +1574,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr

start_frame:
if (_Py_EnterRecursiveCall(tstate, "")) {
tstate->recursion_depth++;
tstate->recursion_remaining--;
goto exit_eval_frame;
}

Expand Down Expand Up @@ -5922,13 +5929,13 @@ _PyEvalFramePushAndInit(PyThreadState *tstate, PyFrameConstructor *con,
static int
_PyEvalFrameClearAndPop(PyThreadState *tstate, InterpreterFrame * frame)
{
++tstate->recursion_depth;
--tstate->recursion_remaining;
assert(frame->frame_obj == NULL || frame->frame_obj->f_own_locals_memory == 0);
if (_PyFrame_Clear(frame, 0)) {
--tstate->recursion_depth;
++tstate->recursion_remaining;
return -1;
}
--tstate->recursion_depth;
++tstate->recursion_remaining;
_PyThreadState_PopFrame(tstate, frame);
return 0;
}
Expand Down
4 changes: 2 additions & 2 deletions Python/pystate.c
Original file line number Diff line number Diff line change
Expand Up @@ -636,9 +636,9 @@ new_threadstate(PyInterpreterState *interp, int init)

tstate->interp = interp;

tstate->recursion_depth = 0;
tstate->recursion_limit = interp->ceval.recursion_limit;
tstate->recursion_remaining = interp->ceval.recursion_limit;
tstate->recursion_headroom = 0;
tstate->stackcheck_counter = 0;
tstate->tracing = 0;
tstate->root_cframe.use_tracing = 0;
tstate->root_cframe.current_frame = NULL;
Expand Down
5 changes: 3 additions & 2 deletions Python/symtable.c
Original file line number Diff line number Diff line change
Expand Up @@ -298,8 +298,9 @@ _PySymtable_Build(mod_ty mod, PyObject *filename, PyFutureFeatures *future)
return NULL;
}
/* Be careful here to prevent overflow. */
starting_recursion_depth = (tstate->recursion_depth < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
tstate->recursion_depth * COMPILER_STACK_FRAME_SCALE : tstate->recursion_depth;
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;
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;
Expand Down
16 changes: 5 additions & 11 deletions Python/sysmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -1187,20 +1187,14 @@ sys_setrecursionlimit_impl(PyObject *module, int new_limit)
return NULL;
}

/* Issue #25274: When the recursion depth hits the recursion limit in
_Py_CheckRecursiveCall(), the overflowed flag of the thread state is
set to 1 and a RecursionError is raised. The overflowed flag is reset
to 0 when the recursion depth goes below the low-water mark: see
Py_LeaveRecursiveCall().

Reject too low new limit if the current recursion depth is higher than
the new low-water mark. Otherwise it may not be possible anymore to
reset the overflowed flag to 0. */
if (tstate->recursion_depth >= new_limit) {
/* 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;
if (depth >= new_limit) {
_PyErr_Format(tstate, PyExc_RecursionError,
"cannot set the recursion limit to %i at "
"the recursion depth %i: the limit is too low",
new_limit, tstate->recursion_depth);
new_limit, depth);
return NULL;
}

Expand Down