bpo-38644: Add Py_EnterRecursiveCall() to the limited API (GH-17046)

Provide Py_EnterRecursiveCall() and 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.

Add Include/cpython/ceval.h header file.
This commit is contained in:
Victor Stinner 2019-11-04 19:48:34 +01:00 committed by GitHub
parent 6552563b3d
commit f4b1e3d7c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 101 additions and 38 deletions

View File

@ -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

View File

@ -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.

View File

@ -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

50
Include/cpython/ceval.h Normal file
View File

@ -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

View File

@ -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 \

View File

@ -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.

View File

@ -127,6 +127,7 @@
<ClInclude Include="..\Include\complexobject.h" />
<ClInclude Include="..\Include\context.h" />
<ClInclude Include="..\Include\cpython\abstract.h" />
<ClInclude Include="..\Include\cpython\ceval.h" />
<ClInclude Include="..\Include\cpython\dictobject.h" />
<ClInclude Include="..\Include\cpython\fileobject.h" />
<ClInclude Include="..\Include\cpython\import.h" />

View File

@ -84,6 +84,9 @@
<ClInclude Include="..\Include\cpython\abstract.h">
<Filter>Include</Filter>
</ClInclude>
<ClInclude Include="..\Include\cpython\ceval.h">
<Filter>Include</Filter>
</ClInclude>
<ClInclude Include="..\Include\cpython\dictobject.h">
<Filter>Include</Filter>
</ClInclude>

View File

@ -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();
}