From 14c66bdb31df0a9bea0c9859d774e04355819471 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 6 Oct 2025 17:31:17 +0200 Subject: [PATCH 01/11] gh-139653: Add PyUnstable_ThreadState_SetStack() Add PyUnstable_ThreadState_SetStack() and PyUnstable_ThreadState_ResetStack() functions to set the stack base address and stack size of a Python thread state. --- Doc/c-api/init.rst | 26 +++++++ Doc/whatsnew/3.14.rst | 5 ++ Include/cpython/pystate.h | 10 +++ Include/internal/pycore_tstate.h | 4 + ...-10-06-22-17-47.gh-issue-139653.6-1MOd.rst | 3 + Modules/_testinternalcapi.c | 50 ++++++++++++ Python/ceval.c | 77 +++++++++++++++++-- Python/pystate.c | 3 + 8 files changed, 171 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/C_API/2025-10-06-22-17-47.gh-issue-139653.6-1MOd.rst diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index ccf85e627f9b5f..ae16bc6990c170 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -1353,6 +1353,32 @@ All of the following functions must be called after :c:func:`Py_Initialize`. .. versionadded:: 3.11 +.. c:function:: int PyUnstable_ThreadState_SetStack(PyThreadState *tstate, void *stack_start_addr, size_t stack_size) + + Set the stack start address and stack size of a Python thread state. + + *stack_size* must be greater than ``0``. + + On success, return ``0``. + On failure, set an exception and return ``-1``. + + .. seealso:: + The :c:func:`PyUnstable_ThreadState_ResetStack` function. + + .. versionadded:: next + + +.. c:function:: void PyUnstable_ThreadState_ResetStack(PyThreadState *tstate) + + Reset the stack start address and stack size of a Python thread state to + the operating system defaults. + + .. seealso:: + The :c:func:`PyUnstable_ThreadState_SetStack` function. + + .. versionadded:: next + + .. c:function:: PyInterpreterState* PyInterpreterState_Get(void) Get the current interpreter. diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index b174333760e76a..9e9c757a5f5df8 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -2994,6 +2994,11 @@ New features in the C API as arguments to C API functions. (Contributed by Sam Gross in :gh:`133164`.) +* Add :c:func:`PyUnstable_ThreadState_SetStack` and + :c:func:`PyUnstable_ThreadState_ResetStack` functions to set the stack base + address and stack size of a Python thread state. + (Contributed by Victor Stinner in :gh:`139653`.) + Limited C API changes --------------------- diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index ac8798ff6129a0..62a2fdda52ca75 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -252,6 +252,16 @@ PyAPI_FUNC(int) PyGILState_Check(void); */ PyAPI_FUNC(PyObject*) _PyThread_CurrentFrames(void); +// Set the stack start address and stack size of a Python thread state +PyAPI_FUNC(int) PyUnstable_ThreadState_SetStack( + PyThreadState *tstate, + void *stack_start_addr, // Stack start address + size_t stack_size); // Stack size (in bytes) + +// Reset the stack start address and stack size of a Python thread state +PyAPI_FUNC(void) PyUnstable_ThreadState_ResetStack( + PyThreadState *tstate); + /* Routines for advanced debuggers, requested by David Beazley. Don't use unless you know what you are doing! */ PyAPI_FUNC(PyInterpreterState *) PyInterpreterState_Main(void); diff --git a/Include/internal/pycore_tstate.h b/Include/internal/pycore_tstate.h index bad968428c73a1..036eb9028984df 100644 --- a/Include/internal/pycore_tstate.h +++ b/Include/internal/pycore_tstate.h @@ -37,6 +37,10 @@ typedef struct _PyThreadStateImpl { uintptr_t c_stack_soft_limit; uintptr_t c_stack_hard_limit; + // PyUnstable_ThreadState_ResetStack() values + uintptr_t c_stack_init_base; + uintptr_t c_stack_init_top; + PyObject *asyncio_running_loop; // Strong reference PyObject *asyncio_running_task; // Strong reference diff --git a/Misc/NEWS.d/next/C_API/2025-10-06-22-17-47.gh-issue-139653.6-1MOd.rst b/Misc/NEWS.d/next/C_API/2025-10-06-22-17-47.gh-issue-139653.6-1MOd.rst new file mode 100644 index 00000000000000..f50a976574038f --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2025-10-06-22-17-47.gh-issue-139653.6-1MOd.rst @@ -0,0 +1,3 @@ +Add :c:func:`PyUnstable_ThreadState_SetStack` and +:c:func:`PyUnstable_ThreadState_ResetStack` functions to set the stack base +address and stack size of a Python thread state. Patch by Victor Stinner. diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index c2647d405e25bc..182402e7106ed6 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -2418,6 +2418,55 @@ set_vectorcall_nop(PyObject *self, PyObject *func) Py_RETURN_NONE; } +static void +check_threadstate_set_stack(PyThreadState *tstate, void *start, size_t size) +{ + assert(PyUnstable_ThreadState_SetStack(tstate, start, size) == 0); + assert(!PyErr_Occurred()); + + _PyThreadStateImpl *ts = (_PyThreadStateImpl *)tstate; + assert(ts->c_stack_hard_limit == (uintptr_t)start + _PyOS_STACK_MARGIN_BYTES); + assert(ts->c_stack_top == (uintptr_t)start + size); + assert(ts->c_stack_soft_limit >= ts->c_stack_hard_limit); + assert(ts->c_stack_soft_limit < ts->c_stack_top); +} + + +static PyObject * +test_threadstate_set_stack(PyObject *self, PyObject *Py_UNUSED(args)) +{ + PyThreadState *tstate = PyThreadState_GET(); + _PyThreadStateImpl *ts = (_PyThreadStateImpl *)tstate; + assert(!PyErr_Occurred()); + + uintptr_t init_base = ts->c_stack_init_base; + size_t init_top = ts->c_stack_init_top; + + // Test the minimum stack size + size_t size = _PyOS_STACK_MARGIN_BYTES * 3; + void *start = (void*)(_Py_get_machine_stack_pointer() - size); + check_threadstate_set_stack(tstate, start, size); + + // Test a larger size + size = 7654321; + start = (void*)(_Py_get_machine_stack_pointer() - size); + check_threadstate_set_stack(tstate, start, size); + + // Test invalid size (too small) + size = 5; + start = (void*)(_Py_get_machine_stack_pointer() - size); + assert(PyUnstable_ThreadState_SetStack(tstate, start, size) == -1); + assert(PyErr_ExceptionMatches(PyExc_ValueError)); + PyErr_Clear(); + + // Test PyUnstable_ThreadState_ResetStack() + PyUnstable_ThreadState_ResetStack(tstate); + assert(ts->c_stack_init_base == init_base); + assert(ts->c_stack_init_top == init_top); + + Py_RETURN_NONE; +} + static PyMethodDef module_functions[] = { {"get_configs", get_configs, METH_NOARGS}, {"get_recursion_depth", get_recursion_depth, METH_NOARGS}, @@ -2527,6 +2576,7 @@ static PyMethodDef module_functions[] = { #endif {"simple_pending_call", simple_pending_call, METH_O}, {"set_vectorcall_nop", set_vectorcall_nop, METH_O}, + {"test_threadstate_set_stack", test_threadstate_set_stack, METH_NOARGS}, {NULL, NULL} /* sentinel */ }; diff --git a/Python/ceval.c b/Python/ceval.c index 1b52128c858ecb..5adce2fa6c2b54 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -439,7 +439,7 @@ int pthread_attr_destroy(pthread_attr_t *a) #endif static void -hardware_stack_limits(uintptr_t *top, uintptr_t *base) +hardware_stack_limits(uintptr_t *base, uintptr_t *top) { #ifdef WIN32 ULONG_PTR low, high; @@ -482,23 +482,86 @@ hardware_stack_limits(uintptr_t *top, uintptr_t *base) #endif } -void -_Py_InitializeRecursionLimits(PyThreadState *tstate) +static void +tstate_set_stack(PyThreadState *tstate, + uintptr_t base, uintptr_t top) { - uintptr_t top; - uintptr_t base; - hardware_stack_limits(&top, &base); + assert(base < top); + assert((top - base) >= (_PyOS_STACK_MARGIN_BYTES * 3)); + #ifdef _Py_THREAD_SANITIZER // Thread sanitizer crashes if we use more than half the stack. uintptr_t stacksize = top - base; - base += stacksize/2; + base += stacksize / 2; #endif _PyThreadStateImpl *_tstate = (_PyThreadStateImpl *)tstate; _tstate->c_stack_top = top; _tstate->c_stack_hard_limit = base + _PyOS_STACK_MARGIN_BYTES; _tstate->c_stack_soft_limit = base + _PyOS_STACK_MARGIN_BYTES * 2; + +#ifndef NDEBUG + // Sanity checks + _PyThreadStateImpl *ts = (_PyThreadStateImpl *)tstate; + assert(ts->c_stack_hard_limit <= ts->c_stack_soft_limit); + assert(ts->c_stack_soft_limit < ts->c_stack_top); +#endif +} + + +void +_Py_InitializeRecursionLimits(PyThreadState *tstate) +{ + uintptr_t base, top; + hardware_stack_limits(&base, &top); + assert(top != 0); + + tstate_set_stack(tstate, base, top); + _PyThreadStateImpl *ts = (_PyThreadStateImpl *)tstate; + ts->c_stack_init_base = base; + ts->c_stack_init_top = top; + + // Test the stack pointer +#if !defined(NDEBUG) && !defined(__wasi__) + uintptr_t here_addr = _Py_get_machine_stack_pointer(); + assert(ts->c_stack_soft_limit < here_addr); + assert(here_addr < ts->c_stack_top); +#endif +} + + +int +PyUnstable_ThreadState_SetStack(PyThreadState *tstate, + void *stack_start_addr, size_t stack_size) +{ + if (stack_size < (_PyOS_STACK_MARGIN_BYTES * 3)) { + PyErr_Format(PyExc_ValueError, + "stack_size must be at least %zu bytes", + _PyOS_STACK_MARGIN_BYTES * 3); + return -1; + } + + uintptr_t base = (uintptr_t)stack_start_addr; + uintptr_t top = base + stack_size; + tstate_set_stack(tstate, base, top); + return 0; } + +void +PyUnstable_ThreadState_ResetStack(PyThreadState *tstate) +{ + _PyThreadStateImpl *ts = (_PyThreadStateImpl *)tstate; + if (ts->c_stack_init_top != 0) { + tstate_set_stack(tstate, + ts->c_stack_init_base, + ts->c_stack_init_top); + return; + } + + _Py_InitializeRecursionLimits(tstate); +} + + /* The function _Py_EnterRecursiveCallTstate() only calls _Py_CheckRecursiveCall() if the recursion_depth reaches recursion_limit. */ int diff --git a/Python/pystate.c b/Python/pystate.c index dbed609f29aa07..02457c901de9d9 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1490,6 +1490,9 @@ init_threadstate(_PyThreadStateImpl *_tstate, _tstate->c_stack_top = 0; _tstate->c_stack_hard_limit = 0; + _tstate->c_stack_init_base = 0; + _tstate->c_stack_init_top = 0; + _tstate->asyncio_running_loop = NULL; _tstate->asyncio_running_task = NULL; From 5d157ffa1b9690b1165978b525acdf0995795dde Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 7 Oct 2025 15:51:47 +0200 Subject: [PATCH 02/11] Update the doc --- Doc/c-api/init.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index ae16bc6990c170..e6d33b842348f3 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -1357,8 +1357,6 @@ All of the following functions must be called after :c:func:`Py_Initialize`. Set the stack start address and stack size of a Python thread state. - *stack_size* must be greater than ``0``. - On success, return ``0``. On failure, set an exception and return ``-1``. From b0d1cf38879ed3e1cf7bbda08f1a925eae012a9b Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 7 Oct 2025 18:16:37 +0200 Subject: [PATCH 03/11] Fix TSan: use larger minimum stack size --- Include/internal/pycore_pythonrun.h | 6 ++++++ Modules/_testinternalcapi.c | 6 +++--- Python/ceval.c | 6 +++--- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Include/internal/pycore_pythonrun.h b/Include/internal/pycore_pythonrun.h index c2832098ddb3e7..b232429c4d09c3 100644 --- a/Include/internal/pycore_pythonrun.h +++ b/Include/internal/pycore_pythonrun.h @@ -54,6 +54,12 @@ extern const char* _Py_SourceAsString( # define _PyOS_STACK_MARGIN_SHIFT (_PyOS_LOG2_STACK_MARGIN + 2) #endif +#ifdef _Py_THREAD_SANITIZER +# define _PyOS_MIN_STACK_SIZE (_PyOS_STACK_MARGIN_BYTES * 6) +#else +# define _PyOS_MIN_STACK_SIZE (_PyOS_STACK_MARGIN_BYTES * 3) +#endif + #ifdef __cplusplus } diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 182402e7106ed6..56dea1b2252beb 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -2425,9 +2425,8 @@ check_threadstate_set_stack(PyThreadState *tstate, void *start, size_t size) assert(!PyErr_Occurred()); _PyThreadStateImpl *ts = (_PyThreadStateImpl *)tstate; - assert(ts->c_stack_hard_limit == (uintptr_t)start + _PyOS_STACK_MARGIN_BYTES); assert(ts->c_stack_top == (uintptr_t)start + size); - assert(ts->c_stack_soft_limit >= ts->c_stack_hard_limit); + assert(ts->c_stack_hard_limit <= ts->c_stack_soft_limit); assert(ts->c_stack_soft_limit < ts->c_stack_top); } @@ -2443,12 +2442,13 @@ test_threadstate_set_stack(PyObject *self, PyObject *Py_UNUSED(args)) size_t init_top = ts->c_stack_init_top; // Test the minimum stack size - size_t size = _PyOS_STACK_MARGIN_BYTES * 3; + size_t size = _PyOS_MIN_STACK_SIZE; void *start = (void*)(_Py_get_machine_stack_pointer() - size); check_threadstate_set_stack(tstate, start, size); // Test a larger size size = 7654321; + assert(size > _PyOS_MIN_STACK_SIZE); start = (void*)(_Py_get_machine_stack_pointer() - size); check_threadstate_set_stack(tstate, start, size); diff --git a/Python/ceval.c b/Python/ceval.c index 5adce2fa6c2b54..4bb0676a2d6f13 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -487,7 +487,7 @@ tstate_set_stack(PyThreadState *tstate, uintptr_t base, uintptr_t top) { assert(base < top); - assert((top - base) >= (_PyOS_STACK_MARGIN_BYTES * 3)); + assert((top - base) >= _PyOS_MIN_STACK_SIZE); #ifdef _Py_THREAD_SANITIZER // Thread sanitizer crashes if we use more than half the stack. @@ -533,10 +533,10 @@ int PyUnstable_ThreadState_SetStack(PyThreadState *tstate, void *stack_start_addr, size_t stack_size) { - if (stack_size < (_PyOS_STACK_MARGIN_BYTES * 3)) { + if (stack_size < _PyOS_MIN_STACK_SIZE) { PyErr_Format(PyExc_ValueError, "stack_size must be at least %zu bytes", - _PyOS_STACK_MARGIN_BYTES * 3); + _PyOS_MIN_STACK_SIZE); return -1; } From b8672c88d40a9f407ada6eaae2299dab9cf36aea Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 13 Oct 2025 18:35:28 +0200 Subject: [PATCH 04/11] Update Doc/c-api/init.rst Co-authored-by: Petr Viktorin --- Doc/c-api/init.rst | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index e6d33b842348f3..b3d1f00ef0ac72 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -1360,8 +1360,16 @@ All of the following functions must be called after :c:func:`Py_Initialize`. On success, return ``0``. On failure, set an exception and return ``-1``. - .. seealso:: - The :c:func:`PyUnstable_ThreadState_ResetStack` function. + CPython implements :ref:`recursion control ` for C code by raising + :py:exc:`RecursionError` when it notices that the machine execution stack is close + to overflow. + For this, it needs to know the location of the current thread's stack, which it + normally gets from the operating system. + When the stack is changed, for example using context switching techniques like the + Boost library's ``boost::context``, you must call + :c:func:`~PyUnstable_ThreadState_SetStack` to inform CPython of the change. + + See :c:func:`PyUnstable_ThreadState_ResetStack` for undoing this operation. .. versionadded:: next From 82906790a52689ad5c9cafee8901f32015524fab Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 13 Oct 2025 18:35:40 +0200 Subject: [PATCH 05/11] Update Doc/c-api/init.rst Co-authored-by: Petr Viktorin --- Doc/c-api/init.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index b3d1f00ef0ac72..e8ee2c0ec405bd 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -1379,8 +1379,7 @@ All of the following functions must be called after :c:func:`Py_Initialize`. Reset the stack start address and stack size of a Python thread state to the operating system defaults. - .. seealso:: - The :c:func:`PyUnstable_ThreadState_SetStack` function. + See :c:func:`PyUnstable_ThreadState_SetStack` for an explanation. .. versionadded:: next From b85c8b85dc8eb0f391bf30677ee68e3a3bde4625 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 13 Oct 2025 18:42:46 +0200 Subject: [PATCH 06/11] Mention Py_EnterRecursiveCall() --- Doc/c-api/init.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index e8ee2c0ec405bd..884080ab6b7359 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -1362,7 +1362,7 @@ All of the following functions must be called after :c:func:`Py_Initialize`. CPython implements :ref:`recursion control ` for C code by raising :py:exc:`RecursionError` when it notices that the machine execution stack is close - to overflow. + to overflow. See for example the :c:func:`Py_EnterRecursiveCall` function. For this, it needs to know the location of the current thread's stack, which it normally gets from the operating system. When the stack is changed, for example using context switching techniques like the From da8cbf017fa772746a4d4be1881ff1bc13f267c3 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 13 Oct 2025 18:44:15 +0200 Subject: [PATCH 07/11] Py_EnterRecursiveCall(): add seealso --- Doc/c-api/exceptions.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst index 3ff4631a8e53c4..b9105c00ab849b 100644 --- a/Doc/c-api/exceptions.rst +++ b/Doc/c-api/exceptions.rst @@ -939,6 +939,9 @@ because the :ref:`call protocol ` takes care of recursion handling. be concatenated to the :exc:`RecursionError` message caused by the recursion depth limit. + .. seealso:: + The :c:func:`PyUnstable_ThreadState_SetStack` function. + .. versionchanged:: 3.9 This function is now also available in the :ref:`limited API `. From 2f7b92e9819616c27c910f6007f88cce48da9085 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 15 Oct 2025 15:41:43 +0200 Subject: [PATCH 08/11] Update Doc/c-api/init.rst Co-authored-by: Petr Viktorin --- Doc/c-api/init.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index 884080ab6b7359..2dff2b3cb3dd0a 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -1369,6 +1369,11 @@ All of the following functions must be called after :c:func:`Py_Initialize`. Boost library's ``boost::context``, you must call :c:func:`~PyUnstable_ThreadState_SetStack` to inform CPython of the change. + Call :c:func:`~PyUnstable_ThreadState_SetStack` either before + or after changing the stack. + Do not call any other Python C API between the call and the stack + change. + See :c:func:`PyUnstable_ThreadState_ResetStack` for undoing this operation. .. versionadded:: next From 34d9153daa6be5332d006ee9275b49302ee98b87 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 5 Nov 2025 17:28:05 +0100 Subject: [PATCH 09/11] Rename to PyUnstable_ThreadState_SetStackProtection() --- Doc/c-api/exceptions.rst | 2 +- Doc/c-api/init.rst | 12 ++++++------ Doc/whatsnew/3.14.rst | 4 ++-- Include/cpython/pystate.h | 4 ++-- Include/internal/pycore_tstate.h | 2 +- .../2025-10-06-22-17-47.gh-issue-139653.6-1MOd.rst | 4 ++-- Modules/_testinternalcapi.c | 8 ++++---- Python/ceval.c | 4 ++-- 8 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst index b9105c00ab849b..45fb7d091052df 100644 --- a/Doc/c-api/exceptions.rst +++ b/Doc/c-api/exceptions.rst @@ -940,7 +940,7 @@ because the :ref:`call protocol ` takes care of recursion handling. depth limit. .. seealso:: - The :c:func:`PyUnstable_ThreadState_SetStack` function. + The :c:func:`PyUnstable_ThreadState_SetStackProtection` function. .. versionchanged:: 3.9 This function is now also available in the :ref:`limited API `. diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index 2dff2b3cb3dd0a..60aecaccd36a89 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -1353,7 +1353,7 @@ All of the following functions must be called after :c:func:`Py_Initialize`. .. versionadded:: 3.11 -.. c:function:: int PyUnstable_ThreadState_SetStack(PyThreadState *tstate, void *stack_start_addr, size_t stack_size) +.. c:function:: int PyUnstable_ThreadState_SetStackProtection(PyThreadState *tstate, void *stack_start_addr, size_t stack_size) Set the stack start address and stack size of a Python thread state. @@ -1367,24 +1367,24 @@ All of the following functions must be called after :c:func:`Py_Initialize`. normally gets from the operating system. When the stack is changed, for example using context switching techniques like the Boost library's ``boost::context``, you must call - :c:func:`~PyUnstable_ThreadState_SetStack` to inform CPython of the change. + :c:func:`~PyUnstable_ThreadState_SetStackProtection` to inform CPython of the change. - Call :c:func:`~PyUnstable_ThreadState_SetStack` either before + Call :c:func:`~PyUnstable_ThreadState_SetStackProtection` either before or after changing the stack. Do not call any other Python C API between the call and the stack change. - See :c:func:`PyUnstable_ThreadState_ResetStack` for undoing this operation. + See :c:func:`PyUnstable_ThreadState_ResetStackProtection` for undoing this operation. .. versionadded:: next -.. c:function:: void PyUnstable_ThreadState_ResetStack(PyThreadState *tstate) +.. c:function:: void PyUnstable_ThreadState_ResetStackProtection(PyThreadState *tstate) Reset the stack start address and stack size of a Python thread state to the operating system defaults. - See :c:func:`PyUnstable_ThreadState_SetStack` for an explanation. + See :c:func:`PyUnstable_ThreadState_SetStackProtection` for an explanation. .. versionadded:: next diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 9e9c757a5f5df8..95aa8e608f618a 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -2994,8 +2994,8 @@ New features in the C API as arguments to C API functions. (Contributed by Sam Gross in :gh:`133164`.) -* Add :c:func:`PyUnstable_ThreadState_SetStack` and - :c:func:`PyUnstable_ThreadState_ResetStack` functions to set the stack base +* Add :c:func:`PyUnstable_ThreadState_SetStackProtection` and + :c:func:`PyUnstable_ThreadState_ResetStackProtection` functions to set the stack base address and stack size of a Python thread state. (Contributed by Victor Stinner in :gh:`139653`.) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index 62a2fdda52ca75..3342b05254e083 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -253,13 +253,13 @@ PyAPI_FUNC(int) PyGILState_Check(void); PyAPI_FUNC(PyObject*) _PyThread_CurrentFrames(void); // Set the stack start address and stack size of a Python thread state -PyAPI_FUNC(int) PyUnstable_ThreadState_SetStack( +PyAPI_FUNC(int) PyUnstable_ThreadState_SetStackProtection( PyThreadState *tstate, void *stack_start_addr, // Stack start address size_t stack_size); // Stack size (in bytes) // Reset the stack start address and stack size of a Python thread state -PyAPI_FUNC(void) PyUnstable_ThreadState_ResetStack( +PyAPI_FUNC(void) PyUnstable_ThreadState_ResetStackProtection( PyThreadState *tstate); /* Routines for advanced debuggers, requested by David Beazley. diff --git a/Include/internal/pycore_tstate.h b/Include/internal/pycore_tstate.h index 036eb9028984df..e0b77cc3e85af0 100644 --- a/Include/internal/pycore_tstate.h +++ b/Include/internal/pycore_tstate.h @@ -37,7 +37,7 @@ typedef struct _PyThreadStateImpl { uintptr_t c_stack_soft_limit; uintptr_t c_stack_hard_limit; - // PyUnstable_ThreadState_ResetStack() values + // PyUnstable_ThreadState_ResetStackProtection() values uintptr_t c_stack_init_base; uintptr_t c_stack_init_top; diff --git a/Misc/NEWS.d/next/C_API/2025-10-06-22-17-47.gh-issue-139653.6-1MOd.rst b/Misc/NEWS.d/next/C_API/2025-10-06-22-17-47.gh-issue-139653.6-1MOd.rst index f50a976574038f..d497dabee06fcd 100644 --- a/Misc/NEWS.d/next/C_API/2025-10-06-22-17-47.gh-issue-139653.6-1MOd.rst +++ b/Misc/NEWS.d/next/C_API/2025-10-06-22-17-47.gh-issue-139653.6-1MOd.rst @@ -1,3 +1,3 @@ -Add :c:func:`PyUnstable_ThreadState_SetStack` and -:c:func:`PyUnstable_ThreadState_ResetStack` functions to set the stack base +Add :c:func:`PyUnstable_ThreadState_SetStackProtection` and +:c:func:`PyUnstable_ThreadState_ResetStackProtection` functions to set the stack base address and stack size of a Python thread state. Patch by Victor Stinner. diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 56dea1b2252beb..5850ca22a8c411 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -2421,7 +2421,7 @@ set_vectorcall_nop(PyObject *self, PyObject *func) static void check_threadstate_set_stack(PyThreadState *tstate, void *start, size_t size) { - assert(PyUnstable_ThreadState_SetStack(tstate, start, size) == 0); + assert(PyUnstable_ThreadState_SetStackProtection(tstate, start, size) == 0); assert(!PyErr_Occurred()); _PyThreadStateImpl *ts = (_PyThreadStateImpl *)tstate; @@ -2455,12 +2455,12 @@ test_threadstate_set_stack(PyObject *self, PyObject *Py_UNUSED(args)) // Test invalid size (too small) size = 5; start = (void*)(_Py_get_machine_stack_pointer() - size); - assert(PyUnstable_ThreadState_SetStack(tstate, start, size) == -1); + assert(PyUnstable_ThreadState_SetStackProtection(tstate, start, size) == -1); assert(PyErr_ExceptionMatches(PyExc_ValueError)); PyErr_Clear(); - // Test PyUnstable_ThreadState_ResetStack() - PyUnstable_ThreadState_ResetStack(tstate); + // Test PyUnstable_ThreadState_ResetStackProtection() + PyUnstable_ThreadState_ResetStackProtection(tstate); assert(ts->c_stack_init_base == init_base); assert(ts->c_stack_init_top == init_top); diff --git a/Python/ceval.c b/Python/ceval.c index 4bb0676a2d6f13..7359b68ec58be4 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -530,7 +530,7 @@ _Py_InitializeRecursionLimits(PyThreadState *tstate) int -PyUnstable_ThreadState_SetStack(PyThreadState *tstate, +PyUnstable_ThreadState_SetStackProtection(PyThreadState *tstate, void *stack_start_addr, size_t stack_size) { if (stack_size < _PyOS_MIN_STACK_SIZE) { @@ -548,7 +548,7 @@ PyUnstable_ThreadState_SetStack(PyThreadState *tstate, void -PyUnstable_ThreadState_ResetStack(PyThreadState *tstate) +PyUnstable_ThreadState_ResetStackProtection(PyThreadState *tstate) { _PyThreadStateImpl *ts = (_PyThreadStateImpl *)tstate; if (ts->c_stack_init_top != 0) { From f7e348b928fc263674aa987b927b18870a774837 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 5 Nov 2025 17:31:27 +0100 Subject: [PATCH 10/11] Rename the unit test --- Modules/_testinternalcapi.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 32969c729e6abd..6514ca7f3cd6de 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -2448,7 +2448,8 @@ module_get_gc_hooks(PyObject *self, PyObject *arg) static void -check_threadstate_set_stack(PyThreadState *tstate, void *start, size_t size) +check_threadstate_set_stack_protection(PyThreadState *tstate, + void *start, size_t size) { assert(PyUnstable_ThreadState_SetStackProtection(tstate, start, size) == 0); assert(!PyErr_Occurred()); @@ -2461,7 +2462,7 @@ check_threadstate_set_stack(PyThreadState *tstate, void *start, size_t size) static PyObject * -test_threadstate_set_stack(PyObject *self, PyObject *Py_UNUSED(args)) +test_threadstate_set_stack_protection(PyObject *self, PyObject *Py_UNUSED(args)) { PyThreadState *tstate = PyThreadState_GET(); _PyThreadStateImpl *ts = (_PyThreadStateImpl *)tstate; @@ -2473,13 +2474,13 @@ test_threadstate_set_stack(PyObject *self, PyObject *Py_UNUSED(args)) // Test the minimum stack size size_t size = _PyOS_MIN_STACK_SIZE; void *start = (void*)(_Py_get_machine_stack_pointer() - size); - check_threadstate_set_stack(tstate, start, size); + check_threadstate_set_stack_protection(tstate, start, size); // Test a larger size size = 7654321; assert(size > _PyOS_MIN_STACK_SIZE); start = (void*)(_Py_get_machine_stack_pointer() - size); - check_threadstate_set_stack(tstate, start, size); + check_threadstate_set_stack_protection(tstate, start, size); // Test invalid size (too small) size = 5; @@ -2607,7 +2608,8 @@ static PyMethodDef module_functions[] = { {"simple_pending_call", simple_pending_call, METH_O}, {"set_vectorcall_nop", set_vectorcall_nop, METH_O}, {"module_get_gc_hooks", module_get_gc_hooks, METH_O}, - {"test_threadstate_set_stack", test_threadstate_set_stack, METH_NOARGS}, + {"test_threadstate_set_stack_protection", + test_threadstate_set_stack_protection, METH_NOARGS}, {NULL, NULL} /* sentinel */ }; From 0a8ba2d77ba2834ced13df3a1e3dc3badadac9db Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 5 Nov 2025 17:35:45 +0100 Subject: [PATCH 11/11] Repeat "protection" in the documentation --- Doc/c-api/init.rst | 7 ++++--- Doc/whatsnew/3.14.rst | 5 ----- Doc/whatsnew/3.15.rst | 6 ++++++ Include/cpython/pystate.h | 6 ++++-- .../C_API/2025-10-06-22-17-47.gh-issue-139653.6-1MOd.rst | 5 +++-- 5 files changed, 17 insertions(+), 12 deletions(-) diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index 2c3a29a096a279..18ee16118070eb 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -1368,7 +1368,8 @@ All of the following functions must be called after :c:func:`Py_Initialize`. .. c:function:: int PyUnstable_ThreadState_SetStackProtection(PyThreadState *tstate, void *stack_start_addr, size_t stack_size) - Set the stack start address and stack size of a Python thread state. + Set the stack protection start address and stack protection size + of a Python thread state. On success, return ``0``. On failure, set an exception and return ``-1``. @@ -1394,8 +1395,8 @@ All of the following functions must be called after :c:func:`Py_Initialize`. .. c:function:: void PyUnstable_ThreadState_ResetStackProtection(PyThreadState *tstate) - Reset the stack start address and stack size of a Python thread state to - the operating system defaults. + Reset the stack protection start address and stack protection size + of a Python thread state to the operating system defaults. See :c:func:`PyUnstable_ThreadState_SetStackProtection` for an explanation. diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 2e641e13f60638..1a2fbda0c4ce81 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -2995,11 +2995,6 @@ New features in the C API as arguments to C API functions. (Contributed by Sam Gross in :gh:`133164`.) -* Add :c:func:`PyUnstable_ThreadState_SetStackProtection` and - :c:func:`PyUnstable_ThreadState_ResetStackProtection` functions to set the stack base - address and stack size of a Python thread state. - (Contributed by Victor Stinner in :gh:`139653`.) - Limited C API changes --------------------- diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 5379ac3abba227..21a9c9c371709f 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -950,6 +950,12 @@ New features * Add :c:func:`PyTuple_FromArray` to create a :class:`tuple` from an array. (Contributed by Victor Stinner in :gh:`111489`.) +* Add :c:func:`PyUnstable_ThreadState_SetStackProtection` and + :c:func:`PyUnstable_ThreadState_ResetStackProtection` functions to set + the stack protection base address and stack protection size of a Python + thread state. + (Contributed by Victor Stinner in :gh:`139653`.) + Changed C APIs -------------- diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index 8902cb8500b044..c53abe43ebe65c 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -276,13 +276,15 @@ PyAPI_FUNC(int) PyGILState_Check(void); */ PyAPI_FUNC(PyObject*) _PyThread_CurrentFrames(void); -// Set the stack start address and stack size of a Python thread state +// Set the stack protection start address and stack protection size +// of a Python thread state PyAPI_FUNC(int) PyUnstable_ThreadState_SetStackProtection( PyThreadState *tstate, void *stack_start_addr, // Stack start address size_t stack_size); // Stack size (in bytes) -// Reset the stack start address and stack size of a Python thread state +// Reset the stack protection start address and stack protection size +// of a Python thread state PyAPI_FUNC(void) PyUnstable_ThreadState_ResetStackProtection( PyThreadState *tstate); diff --git a/Misc/NEWS.d/next/C_API/2025-10-06-22-17-47.gh-issue-139653.6-1MOd.rst b/Misc/NEWS.d/next/C_API/2025-10-06-22-17-47.gh-issue-139653.6-1MOd.rst index d497dabee06fcd..cd3d5262fa0f3a 100644 --- a/Misc/NEWS.d/next/C_API/2025-10-06-22-17-47.gh-issue-139653.6-1MOd.rst +++ b/Misc/NEWS.d/next/C_API/2025-10-06-22-17-47.gh-issue-139653.6-1MOd.rst @@ -1,3 +1,4 @@ Add :c:func:`PyUnstable_ThreadState_SetStackProtection` and -:c:func:`PyUnstable_ThreadState_ResetStackProtection` functions to set the stack base -address and stack size of a Python thread state. Patch by Victor Stinner. +:c:func:`PyUnstable_ThreadState_ResetStackProtection` functions to set the +stack protection base address and stack protection size of a Python thread +state. Patch by Victor Stinner.