From 57c3e7cd59e9673799c6244ebcaa57f0cc272725 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 11 Nov 2021 13:40:01 +0000 Subject: [PATCH 1/6] Make recursion checks more efficient. --- Include/cpython/pystate.h | 3 +-- Include/internal/pycore_ceval.h | 8 ++++---- Modules/_testinternalcapi.c | 3 ++- Python/ast.c | 6 ++++-- Python/ast_opt.c | 6 ++++-- Python/ceval.c | 27 ++++++++++++++------------- Python/pystate.c | 3 +-- Python/symtable.c | 6 ++++-- Python/sysmodule.c | 4 ++-- 9 files changed, 36 insertions(+), 30 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index cf69c72028a3ac..a3a1f3ccea90c8 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -79,9 +79,8 @@ struct _ts { struct _ts *next; PyInterpreterState *interp; - int recursion_depth; + int recursion_spare; 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 diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h index 53580b99d33ac6..e35a3ef7c02428 100644 --- a/Include/internal/pycore_ceval.h +++ b/Include/internal/pycore_ceval.h @@ -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_spare-- <= 0 + || (tstate->recursion_spare & 63) == 0); } #else static inline int _Py_MakeRecCheck(PyThreadState *tstate) { - return (++tstate->recursion_depth > tstate->interp->ceval.recursion_limit); + return tstate->recursion_spare-- <= 0; } #endif @@ -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_spare++; } static inline void _Py_LeaveRecursiveCall_inline(void) { diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 1f205b873beaf6..487e480b7f8005 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -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->interp->ceval.recursion_limit - tstate->recursion_spare - 1); } diff --git a/Python/ast.c b/Python/ast.c index 2113124dbd51c2..cbe2a7f754036e 100644 --- a/Python/ast.c +++ b/Python/ast.c @@ -4,6 +4,7 @@ */ #include "Python.h" #include "pycore_ast.h" // asdl_stmt_seq +#include "pycore_interp.h" // recursion limit #include "pycore_pystate.h" // _PyThreadState_GET() #include @@ -933,8 +934,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->interp->ceval.recursion_limit - tstate->recursion_spare; + 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; diff --git a/Python/ast_opt.c b/Python/ast_opt.c index f6506cef035cd4..11d6f0642db428 100644 --- a/Python/ast_opt.c +++ b/Python/ast_opt.c @@ -2,6 +2,7 @@ #include "Python.h" #include "pycore_ast.h" // _PyAST_GetDocString() #include "pycore_compile.h" // _PyASTOptimizeState +#include "pycore_interp.h" // recursion limit #include "pycore_pystate.h" // _PyThreadState_GET() #include "pycore_format.h" // F_LJUST @@ -1098,8 +1099,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->interp->ceval.recursion_limit - tstate->recursion_spare; + 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; diff --git a/Python/ceval.c b/Python/ceval.c index c9f6638afa0e46..f3f0612750962b 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -787,8 +787,12 @@ Py_GetRecursionLimit(void) void Py_SetRecursionLimit(int new_limit) { - PyThreadState *tstate = _PyThreadState_GET(); - tstate->interp->ceval.recursion_limit = new_limit; + PyInterpreterState *interp = _PyInterpreterState_GET(); + int old_limit = interp->ceval.recursion_limit; + interp->ceval.recursion_limit = new_limit; + for (PyThreadState *p = interp->tstate_head; p != NULL; p = p->next) { + p->recursion_spare += new_limit - old_limit; + } } /* The function _Py_EnterRecursiveCall() only calls _Py_CheckRecursiveCall() @@ -799,30 +803,27 @@ Py_SetRecursionLimit(int new_limit) int _Py_CheckRecursiveCall(PyThreadState *tstate, const char *where) { - int recursion_limit = tstate->interp->ceval.recursion_limit; - #ifdef USE_STACKCHECK - tstate->stackcheck_counter = 0; if (PyOS_CheckStack()) { - --tstate->recursion_depth; + ++tstate->recursion_spare; _PyErr_SetString(tstate, PyExc_MemoryError, "Stack overflow"); return -1; } #endif if (tstate->recursion_headroom) { - if (tstate->recursion_depth > recursion_limit + 50) { + if (tstate->recursion_spare < -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_spare <= 0) { tstate->recursion_headroom++; _PyErr_Format(tstate, PyExc_RecursionError, "maximum recursion depth exceeded%s", where); tstate->recursion_headroom--; - --tstate->recursion_depth; + ++tstate->recursion_spare; return -1; } } @@ -1567,7 +1568,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr start_frame: if (_Py_EnterRecursiveCall(tstate, "")) { - tstate->recursion_depth++; + tstate->recursion_spare--; goto exit_eval_frame; } @@ -5922,13 +5923,13 @@ _PyEvalFramePushAndInit(PyThreadState *tstate, PyFrameConstructor *con, static int _PyEvalFrameClearAndPop(PyThreadState *tstate, InterpreterFrame * frame) { - ++tstate->recursion_depth; + --tstate->recursion_spare; assert(frame->frame_obj == NULL || frame->frame_obj->f_own_locals_memory == 0); if (_PyFrame_Clear(frame, 0)) { - --tstate->recursion_depth; + ++tstate->recursion_spare; return -1; } - --tstate->recursion_depth; + ++tstate->recursion_spare; _PyThreadState_PopFrame(tstate, frame); return 0; } diff --git a/Python/pystate.c b/Python/pystate.c index a9ed08a7e34be3..c40f4be3ec7f11 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -636,9 +636,8 @@ new_threadstate(PyInterpreterState *interp, int init) tstate->interp = interp; - tstate->recursion_depth = 0; + tstate->recursion_spare = 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; diff --git a/Python/symtable.c b/Python/symtable.c index 64c1635fba7603..49de1ba8c09ebc 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -1,6 +1,7 @@ #include "Python.h" #include "pycore_ast.h" // identifier, stmt_ty #include "pycore_compile.h" // _Py_Mangle(), _PyFuture_FromAST() +#include "pycore_interp.h" // recursion limit #include "pycore_parser.h" // _PyParser_ASTFromString() #include "pycore_pystate.h" // _PyThreadState_GET() #include "pycore_symtable.h" // PySTEntryObject @@ -298,8 +299,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->interp->ceval.recursion_limit - tstate->recursion_spare; + 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; diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 27937a03e89689..61498e4208da9a 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -1196,11 +1196,11 @@ sys_setrecursionlimit_impl(PyObject *module, int new_limit) 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) { + if (tstate->interp->ceval.recursion_limit - tstate->recursion_spare > 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, tstate->interp->ceval.recursion_limit - tstate->recursion_spare); return NULL; } From 5cd54a885bd01a53d5505eca7e5c867a34b32769 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 11 Nov 2021 17:13:18 +0000 Subject: [PATCH 2/6] Change 'recursion_spare' to 'recursion_remaining' --- Include/cpython/pystate.h | 2 +- Include/internal/pycore_ceval.h | 8 ++++---- Modules/_testinternalcapi.c | 2 +- Python/ast.c | 2 +- Python/ast_opt.c | 2 +- Python/ceval.c | 18 +++++++++--------- Python/pystate.c | 2 +- Python/symtable.c | 2 +- Python/sysmodule.c | 4 ++-- 9 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index a3a1f3ccea90c8..affb08e1ea056b 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -79,7 +79,7 @@ struct _ts { struct _ts *next; PyInterpreterState *interp; - int recursion_spare; + int recursion_remaining; int recursion_headroom; /* Allow 50 more calls to handle any errors. */ /* 'tracing' keeps track of the execution depth when tracing/profiling. diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h index e35a3ef7c02428..c2251b04be65d4 100644 --- a/Include/internal/pycore_ceval.h +++ b/Include/internal/pycore_ceval.h @@ -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_spare-- <= 0 - || (tstate->recursion_spare & 63) == 0); + return (tstate->recursion_remaining-- <= 0 + || (tstate->recursion_remaining & 63) == 0); } #else static inline int _Py_MakeRecCheck(PyThreadState *tstate) { - return tstate->recursion_spare-- <= 0; + return tstate->recursion_remaining-- <= 0; } #endif @@ -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_spare++; + tstate->recursion_remaining++; } static inline void _Py_LeaveRecursiveCall_inline(void) { diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 487e480b7f8005..551cd4c58a5c56 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -38,7 +38,7 @@ get_recursion_depth(PyObject *self, PyObject *Py_UNUSED(args)) /* subtract one to ignore the frame of the get_recursion_depth() call */ - return PyLong_FromLong(tstate->interp->ceval.recursion_limit - tstate->recursion_spare - 1); + return PyLong_FromLong(tstate->interp->ceval.recursion_limit - tstate->recursion_remaining - 1); } diff --git a/Python/ast.c b/Python/ast.c index cbe2a7f754036e..e7d7d1403c9d62 100644 --- a/Python/ast.c +++ b/Python/ast.c @@ -934,7 +934,7 @@ _PyAST_Validate(mod_ty mod) return 0; } /* Be careful here to prevent overflow. */ - int recursion_depth = tstate->interp->ceval.recursion_limit - tstate->recursion_spare; + int recursion_depth = tstate->interp->ceval.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; diff --git a/Python/ast_opt.c b/Python/ast_opt.c index 11d6f0642db428..a1ae994ffd40ff 100644 --- a/Python/ast_opt.c +++ b/Python/ast_opt.c @@ -1099,7 +1099,7 @@ _PyAST_Optimize(mod_ty mod, PyArena *arena, _PyASTOptimizeState *state) return 0; } /* Be careful here to prevent overflow. */ - int recursion_depth = tstate->interp->ceval.recursion_limit - tstate->recursion_spare; + int recursion_depth = tstate->interp->ceval.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; diff --git a/Python/ceval.c b/Python/ceval.c index f3f0612750962b..83343a23546a96 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -791,7 +791,7 @@ Py_SetRecursionLimit(int new_limit) int old_limit = interp->ceval.recursion_limit; interp->ceval.recursion_limit = new_limit; for (PyThreadState *p = interp->tstate_head; p != NULL; p = p->next) { - p->recursion_spare += new_limit - old_limit; + p->recursion_remaining += new_limit - old_limit; } } @@ -805,25 +805,25 @@ _Py_CheckRecursiveCall(PyThreadState *tstate, const char *where) { #ifdef USE_STACKCHECK if (PyOS_CheckStack()) { - ++tstate->recursion_spare; + ++tstate->recursion_remaining; _PyErr_SetString(tstate, PyExc_MemoryError, "Stack overflow"); return -1; } #endif if (tstate->recursion_headroom) { - if (tstate->recursion_spare < -50) { + if (tstate->recursion_remaining < -50) { /* Overflowing while handling an overflow. Give up. */ Py_FatalError("Cannot recover from stack overflow."); } } else { - if (tstate->recursion_spare <= 0) { + if (tstate->recursion_remaining <= 0) { tstate->recursion_headroom++; _PyErr_Format(tstate, PyExc_RecursionError, "maximum recursion depth exceeded%s", where); tstate->recursion_headroom--; - ++tstate->recursion_spare; + ++tstate->recursion_remaining; return -1; } } @@ -1568,7 +1568,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr start_frame: if (_Py_EnterRecursiveCall(tstate, "")) { - tstate->recursion_spare--; + tstate->recursion_remaining--; goto exit_eval_frame; } @@ -5923,13 +5923,13 @@ _PyEvalFramePushAndInit(PyThreadState *tstate, PyFrameConstructor *con, static int _PyEvalFrameClearAndPop(PyThreadState *tstate, InterpreterFrame * frame) { - --tstate->recursion_spare; + --tstate->recursion_remaining; assert(frame->frame_obj == NULL || frame->frame_obj->f_own_locals_memory == 0); if (_PyFrame_Clear(frame, 0)) { - ++tstate->recursion_spare; + ++tstate->recursion_remaining; return -1; } - ++tstate->recursion_spare; + ++tstate->recursion_remaining; _PyThreadState_PopFrame(tstate, frame); return 0; } diff --git a/Python/pystate.c b/Python/pystate.c index c40f4be3ec7f11..39136b0e6351a3 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -636,7 +636,7 @@ new_threadstate(PyInterpreterState *interp, int init) tstate->interp = interp; - tstate->recursion_spare = interp->ceval.recursion_limit; + tstate->recursion_remaining = interp->ceval.recursion_limit; tstate->recursion_headroom = 0; tstate->tracing = 0; tstate->root_cframe.use_tracing = 0; diff --git a/Python/symtable.c b/Python/symtable.c index 49de1ba8c09ebc..b0ee60e78214cf 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -299,7 +299,7 @@ _PySymtable_Build(mod_ty mod, PyObject *filename, PyFutureFeatures *future) return NULL; } /* Be careful here to prevent overflow. */ - int recursion_depth = tstate->interp->ceval.recursion_limit - tstate->recursion_spare; + int recursion_depth = tstate->interp->ceval.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; diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 61498e4208da9a..6a4d92b4a11406 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -1196,11 +1196,11 @@ sys_setrecursionlimit_impl(PyObject *module, int new_limit) 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->interp->ceval.recursion_limit - tstate->recursion_spare > new_limit) { + if (tstate->interp->ceval.recursion_limit - tstate->recursion_remaining > 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->interp->ceval.recursion_limit - tstate->recursion_spare); + new_limit, tstate->interp->ceval.recursion_limit - tstate->recursion_remaining); return NULL; } From 84ee4cd87d220474bb167b736dea1cccee237743 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 11 Nov 2021 17:14:42 +0000 Subject: [PATCH 3/6] Add NEWS --- .../Core and Builtins/2021-11-11-17-14-21.bpo-45753.nEBFcC.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2021-11-11-17-14-21.bpo-45753.nEBFcC.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-11-11-17-14-21.bpo-45753.nEBFcC.rst b/Misc/NEWS.d/next/Core and Builtins/2021-11-11-17-14-21.bpo-45753.nEBFcC.rst new file mode 100644 index 00000000000000..327f00d68911b0 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2021-11-11-17-14-21.bpo-45753.nEBFcC.rst @@ -0,0 +1,2 @@ +Make recursion checks a bit more efficient by tracking amount of calls left +before overflow. From 1eca1ffa92fbec6f75ed278eed27c7d5ea6249a2 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Fri, 12 Nov 2021 10:52:23 +0000 Subject: [PATCH 4/6] Lazily initialize remaining recursion. --- Python/ceval.c | 12 ++++++++---- Python/pystate.c | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Python/ceval.c b/Python/ceval.c index 83343a23546a96..0d335000618f62 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -791,18 +791,22 @@ Py_SetRecursionLimit(int new_limit) int old_limit = interp->ceval.recursion_limit; interp->ceval.recursion_limit = new_limit; for (PyThreadState *p = interp->tstate_head; p != NULL; p = p->next) { + if (p->recursion_remaining <= INT_MIN/2) { + p->recursion_remaining = old_limit; + } p->recursion_remaining += new_limit - old_limit; } } /* 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) { + if (tstate->recursion_remaining <= INT_MIN/2) { + tstate->recursion_remaining = tstate->interp->ceval.recursion_limit; + return 0; + } #ifdef USE_STACKCHECK if (PyOS_CheckStack()) { ++tstate->recursion_remaining; diff --git a/Python/pystate.c b/Python/pystate.c index 39136b0e6351a3..1777bdf2ee1d51 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -636,7 +636,7 @@ new_threadstate(PyInterpreterState *interp, int init) tstate->interp = interp; - tstate->recursion_remaining = interp->ceval.recursion_limit; + tstate->recursion_remaining = INT_MIN/2; tstate->recursion_headroom = 0; tstate->tracing = 0; tstate->root_cframe.use_tracing = 0; From 0c65e85b29c719c4ccf1ba59234de31d6345c138 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Mon, 15 Nov 2021 11:13:36 +0000 Subject: [PATCH 5/6] Keep per-thread recursion and limit and check against global limit on reaching limit. --- Include/cpython/pystate.h | 1 + Modules/_testinternalcapi.c | 2 +- Python/ast.c | 3 +-- Python/ast_opt.c | 3 +-- Python/ceval.c | 16 +++++++++------- Python/pystate.c | 3 ++- Python/sysmodule.c | 16 +++++----------- 7 files changed, 20 insertions(+), 24 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index affb08e1ea056b..9ac0a298baab4e 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -80,6 +80,7 @@ struct _ts { PyInterpreterState *interp; int recursion_remaining; + int recursion_limit; int recursion_headroom; /* Allow 50 more calls to handle any errors. */ /* 'tracing' keeps track of the execution depth when tracing/profiling. diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 551cd4c58a5c56..c230ba28d61115 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -38,7 +38,7 @@ get_recursion_depth(PyObject *self, PyObject *Py_UNUSED(args)) /* subtract one to ignore the frame of the get_recursion_depth() call */ - return PyLong_FromLong(tstate->interp->ceval.recursion_limit - tstate->recursion_remaining - 1); + return PyLong_FromLong(tstate->recursion_limit - tstate->recursion_remaining - 1); } diff --git a/Python/ast.c b/Python/ast.c index e7d7d1403c9d62..0c3121d3ee7b66 100644 --- a/Python/ast.c +++ b/Python/ast.c @@ -4,7 +4,6 @@ */ #include "Python.h" #include "pycore_ast.h" // asdl_stmt_seq -#include "pycore_interp.h" // recursion limit #include "pycore_pystate.h" // _PyThreadState_GET() #include @@ -934,7 +933,7 @@ _PyAST_Validate(mod_ty mod) return 0; } /* Be careful here to prevent overflow. */ - int recursion_depth = tstate->interp->ceval.recursion_limit - tstate->recursion_remaining; + 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; diff --git a/Python/ast_opt.c b/Python/ast_opt.c index a1ae994ffd40ff..356f60e2d53992 100644 --- a/Python/ast_opt.c +++ b/Python/ast_opt.c @@ -2,7 +2,6 @@ #include "Python.h" #include "pycore_ast.h" // _PyAST_GetDocString() #include "pycore_compile.h" // _PyASTOptimizeState -#include "pycore_interp.h" // recursion limit #include "pycore_pystate.h" // _PyThreadState_GET() #include "pycore_format.h" // F_LJUST @@ -1099,7 +1098,7 @@ _PyAST_Optimize(mod_ty mod, PyArena *arena, _PyASTOptimizeState *state) return 0; } /* Be careful here to prevent overflow. */ - int recursion_depth = tstate->interp->ceval.recursion_limit - tstate->recursion_remaining; + 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; diff --git a/Python/ceval.c b/Python/ceval.c index 0d335000618f62..383ea06198366d 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -788,13 +788,11 @@ void Py_SetRecursionLimit(int new_limit) { PyInterpreterState *interp = _PyInterpreterState_GET(); - int old_limit = interp->ceval.recursion_limit; interp->ceval.recursion_limit = new_limit; for (PyThreadState *p = interp->tstate_head; p != NULL; p = p->next) { - if (p->recursion_remaining <= INT_MIN/2) { - p->recursion_remaining = old_limit; - } - p->recursion_remaining += new_limit - old_limit; + int depth = p->recursion_limit - p->recursion_remaining; + p->recursion_limit = new_limit; + p->recursion_remaining = new_limit - depth; } } @@ -803,8 +801,12 @@ Py_SetRecursionLimit(int new_limit) int _Py_CheckRecursiveCall(PyThreadState *tstate, const char *where) { - if (tstate->recursion_remaining <= INT_MIN/2) { - tstate->recursion_remaining = 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 diff --git a/Python/pystate.c b/Python/pystate.c index 1777bdf2ee1d51..8df28078f2a4e1 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -636,7 +636,8 @@ new_threadstate(PyInterpreterState *interp, int init) tstate->interp = interp; - tstate->recursion_remaining = INT_MIN/2; + tstate->recursion_limit = interp->ceval.recursion_limit; + tstate->recursion_remaining = interp->ceval.recursion_limit; tstate->recursion_headroom = 0; tstate->tracing = 0; tstate->root_cframe.use_tracing = 0; diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 6a4d92b4a11406..0c47b163c50c9b 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -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->interp->ceval.recursion_limit - tstate->recursion_remaining > 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->interp->ceval.recursion_limit - tstate->recursion_remaining); + new_limit, depth); return NULL; } From 86f296015c64baa98480615a4f3c6ce61afae223 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Mon, 15 Nov 2021 12:13:38 +0000 Subject: [PATCH 6/6] Fix off-by-one error. --- Python/symtable.c | 3 +-- Python/sysmodule.c | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Python/symtable.c b/Python/symtable.c index b0ee60e78214cf..dc5426cf3b4267 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -1,7 +1,6 @@ #include "Python.h" #include "pycore_ast.h" // identifier, stmt_ty #include "pycore_compile.h" // _Py_Mangle(), _PyFuture_FromAST() -#include "pycore_interp.h" // recursion limit #include "pycore_parser.h" // _PyParser_ASTFromString() #include "pycore_pystate.h" // _PyThreadState_GET() #include "pycore_symtable.h" // PySTEntryObject @@ -299,7 +298,7 @@ _PySymtable_Build(mod_ty mod, PyObject *filename, PyFutureFeatures *future) return NULL; } /* Be careful here to prevent overflow. */ - int recursion_depth = tstate->interp->ceval.recursion_limit - tstate->recursion_remaining; + 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; diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 0c47b163c50c9b..3e2091e70ab8a6 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -1190,7 +1190,7 @@ sys_setrecursionlimit_impl(PyObject *module, int 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) { + 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",