diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst index c7ba74cc8d5..a042c6eee0a 100644 --- a/Doc/c-api/exceptions.rst +++ b/Doc/c-api/exceptions.rst @@ -715,15 +715,21 @@ recursion depth automatically). case, a :exc:`RecursionError` is set and a nonzero value is returned. Otherwise, zero is returned. - *where* should be a string such as ``" in instance check"`` to be - concatenated to the :exc:`RecursionError` message caused by the recursion + *where* should be a UTF-8 encoded string such as ``" in instance check"`` to + be concatenated to the :exc:`RecursionError` message caused by the recursion depth limit. -.. c:function:: void Py_LeaveRecursiveCall() + .. versionchanged:: 3.9 + This function is now also available in the limited API. + +.. c:function:: void Py_LeaveRecursiveCall(void) Ends a :c:func:`Py_EnterRecursiveCall`. Must be called once for each *successful* invocation of :c:func:`Py_EnterRecursiveCall`. + .. versionchanged:: 3.9 + This function is now also available in the limited API. + Properly implementing :c:member:`~PyTypeObject.tp_repr` for container types requires special recursion handling. In addition to protecting the stack, :c:member:`~PyTypeObject.tp_repr` also needs to track objects to prevent cycles. The diff --git a/Doc/whatsnew/3.9.rst b/Doc/whatsnew/3.9.rst index 7d7c502459a..3cac9c5eedb 100644 --- a/Doc/whatsnew/3.9.rst +++ b/Doc/whatsnew/3.9.rst @@ -197,6 +197,12 @@ Optimizations Build and C API Changes ======================= +* Provide :c:func:`Py_EnterRecursiveCall` and :c:func:`Py_LeaveRecursiveCall` + as regular functions for the limited API. Previously, there were defined as + macros, but these macros didn't work with the limited API which cannot access + ``PyThreadState.recursion_depth`` field. Remove ``_Py_CheckRecursionLimit`` + from the stable ABI. + (Contributed by Victor Stinner in :issue:`38644`.) * Add a new public :c:func:`PyObject_CallNoArgs` function to the C API, which calls a callable Python object without any arguments. It is the most efficient way to call a callable Python object without any argument. diff --git a/Include/ceval.h b/Include/ceval.h index 61db777cc4d..8da779ba97b 100644 --- a/Include/ceval.h +++ b/Include/ceval.h @@ -85,41 +85,8 @@ PyAPI_FUNC(int) Py_MakePendingCalls(void); PyAPI_FUNC(void) Py_SetRecursionLimit(int); PyAPI_FUNC(int) Py_GetRecursionLimit(void); -#define Py_EnterRecursiveCall(where) \ - (_Py_MakeRecCheck(PyThreadState_GET()->recursion_depth) && \ - _Py_CheckRecursiveCall(where)) -#define Py_LeaveRecursiveCall() \ - do{ if(_Py_MakeEndRecCheck(PyThreadState_GET()->recursion_depth)) \ - PyThreadState_GET()->overflowed = 0; \ - } while(0) -PyAPI_FUNC(int) _Py_CheckRecursiveCall(const char *where); - -/* Due to the macros in which it's used, _Py_CheckRecursionLimit is in - the stable ABI. It should be removed therefrom when possible. -*/ -PyAPI_DATA(int) _Py_CheckRecursionLimit; - -#ifdef USE_STACKCHECK -/* With USE_STACKCHECK, trigger stack checks in _Py_CheckRecursiveCall() - on every 64th call to Py_EnterRecursiveCall. -*/ -# define _Py_MakeRecCheck(x) \ - (++(x) > _Py_CheckRecursionLimit || \ - ++(PyThreadState_GET()->stackcheck_counter) > 64) -#else -# define _Py_MakeRecCheck(x) (++(x) > _Py_CheckRecursionLimit) -#endif - -/* Compute the "lower-water mark" for a recursion limit. When - * Py_LeaveRecursiveCall() is called with a recursion depth below this mark, - * the overflowed flag is reset to 0. */ -#define _Py_RecursionLimitLowerWaterMark(limit) \ - (((limit) > 200) \ - ? ((limit) - 50) \ - : (3 * ((limit) >> 2))) - -#define _Py_MakeEndRecCheck(x) \ - (--(x) < _Py_RecursionLimitLowerWaterMark(_Py_CheckRecursionLimit)) +PyAPI_FUNC(int) Py_EnterRecursiveCall(const char *where); +PyAPI_FUNC(void) Py_LeaveRecursiveCall(void); #define Py_ALLOW_RECURSION \ do { unsigned char _old = PyThreadState_GET()->recursion_critical;\ @@ -224,6 +191,12 @@ PyAPI_FUNC(int) _PyEval_SliceIndexNotNone(PyObject *, Py_ssize_t *); #define FVS_MASK 0x4 #define FVS_HAVE_SPEC 0x4 +#ifndef Py_LIMITED_API +# define Py_CPYTHON_CEVAL_H +# include "cpython/ceval.h" +# undef Py_CPYTHON_CEVAL_H +#endif + #ifdef __cplusplus } #endif diff --git a/Include/cpython/ceval.h b/Include/cpython/ceval.h new file mode 100644 index 00000000000..61bbc4f69d5 --- /dev/null +++ b/Include/cpython/ceval.h @@ -0,0 +1,50 @@ +#ifndef Py_CPYTHON_CEVAL_H +# error "this header file must not be included directly" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +PyAPI_DATA(int) _Py_CheckRecursionLimit; + +#ifdef USE_STACKCHECK +/* With USE_STACKCHECK macro defined, trigger stack checks in + _Py_CheckRecursiveCall() on every 64th call to Py_EnterRecursiveCall. */ +# define _Py_MakeRecCheck(x) \ + (++(x) > _Py_CheckRecursionLimit || \ + ++(PyThreadState_GET()->stackcheck_counter) > 64) +#else +# define _Py_MakeRecCheck(x) (++(x) > _Py_CheckRecursionLimit) +#endif + +PyAPI_FUNC(int) _Py_CheckRecursiveCall(const char *where); + +#define _Py_EnterRecursiveCall_macro(where) \ + (_Py_MakeRecCheck(PyThreadState_GET()->recursion_depth) && \ + _Py_CheckRecursiveCall(where)) + +#define Py_EnterRecursiveCall(where) _Py_EnterRecursiveCall_macro(where) + + +/* Compute the "lower-water mark" for a recursion limit. When + * Py_LeaveRecursiveCall() is called with a recursion depth below this mark, + * the overflowed flag is reset to 0. */ +#define _Py_RecursionLimitLowerWaterMark(limit) \ + (((limit) > 200) \ + ? ((limit) - 50) \ + : (3 * ((limit) >> 2))) + +#define _Py_MakeEndRecCheck(x) \ + (--(x) < _Py_RecursionLimitLowerWaterMark(_Py_CheckRecursionLimit)) + +#define _Py_LeaveRecursiveCall_macro() \ + do{ if(_Py_MakeEndRecCheck(PyThreadState_GET()->recursion_depth)) \ + PyThreadState_GET()->overflowed = 0; \ + } while(0) + +#define Py_LeaveRecursiveCall() _Py_LeaveRecursiveCall_macro() + +#ifdef __cplusplus +} +#endif diff --git a/Makefile.pre.in b/Makefile.pre.in index 1c0958ec974..3c607d08ae7 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1057,6 +1057,7 @@ PYTHON_HEADERS= \ $(srcdir)/Include/Python-ast.h \ \ $(srcdir)/Include/cpython/abstract.h \ + $(srcdir)/Include/cpython/ceval.h \ $(srcdir)/Include/cpython/dictobject.h \ $(srcdir)/Include/cpython/fileobject.h \ $(srcdir)/Include/cpython/import.h \ diff --git a/Misc/NEWS.d/next/C API/2019-11-04-17-59-46.bpo-38644.euO_RR.rst b/Misc/NEWS.d/next/C API/2019-11-04-17-59-46.bpo-38644.euO_RR.rst new file mode 100644 index 00000000000..b94f505568f --- /dev/null +++ b/Misc/NEWS.d/next/C API/2019-11-04-17-59-46.bpo-38644.euO_RR.rst @@ -0,0 +1,5 @@ +Provide :c:func:`Py_EnterRecursiveCall` and :c:func:`Py_LeaveRecursiveCall` +as regular functions for the limited API. Previously, there were defined as +macros, but these macros didn't work with the limited API which cannot access +``PyThreadState.recursion_depth`` field. Remove ``_Py_CheckRecursionLimit`` +from the stable ABI. diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index 1c055b6a334..b72474060a3 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -127,6 +127,7 @@ + diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters index dbff89fbff6..80908135550 100644 --- a/PCbuild/pythoncore.vcxproj.filters +++ b/PCbuild/pythoncore.vcxproj.filters @@ -84,6 +84,9 @@ Include + + Include + Include diff --git a/Python/ceval.c b/Python/ceval.c index a7d2ea80069..881a7dd629b 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -5632,3 +5632,21 @@ maybe_dtrace_line(PyFrameObject *frame, } *instr_prev = frame->f_lasti; } + + +/* Implement Py_EnterRecursiveCall() and Py_LeaveRecursiveCall() as functions + for the limited API. */ + +#undef Py_EnterRecursiveCall + +int Py_EnterRecursiveCall(const char *where) +{ + return _Py_EnterRecursiveCall_macro(where); +} + +#undef Py_LeaveRecursiveCall + +void Py_LeaveRecursiveCall(void) +{ + _Py_LeaveRecursiveCall_macro(); +}