gh-104690: thread_run() checks for tstate dangling pointer (#109056)

thread_run() of _threadmodule.c now calls
_PyThreadState_CheckConsistency() to check if tstate is a dangling
pointer when Python is built in debug mode.

Rename ceval_gil.c is_tstate_valid() to
_PyThreadState_CheckConsistency() to reuse it in _threadmodule.c.
This commit is contained in:
Victor Stinner 2023-09-08 11:50:46 +02:00 committed by GitHub
parent b0edf3b98e
commit f63d37877a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 35 additions and 20 deletions

View File

@ -67,6 +67,10 @@ _Py_ThreadCanHandleSignals(PyInterpreterState *interp)
extern _Py_thread_local PyThreadState *_Py_tss_tstate; extern _Py_thread_local PyThreadState *_Py_tss_tstate;
#endif #endif
#ifndef NDEBUG
extern int _PyThreadState_CheckConsistency(PyThreadState *tstate);
#endif
// Export for most shared extensions, used via _PyThreadState_GET() static // Export for most shared extensions, used via _PyThreadState_GET() static
// inline function. // inline function.
PyAPI_FUNC(PyThreadState *) _PyThreadState_GetCurrent(void); PyAPI_FUNC(PyThreadState *) _PyThreadState_GetCurrent(void);

View File

@ -1074,9 +1074,12 @@ static void
thread_run(void *boot_raw) thread_run(void *boot_raw)
{ {
struct bootstate *boot = (struct bootstate *) boot_raw; struct bootstate *boot = (struct bootstate *) boot_raw;
PyThreadState *tstate; PyThreadState *tstate = boot->tstate;
// gh-104690: If Python is being finalized and PyInterpreterState_Delete()
// was called, tstate becomes a dangling pointer.
assert(_PyThreadState_CheckConsistency(tstate));
tstate = boot->tstate;
_PyThreadState_Bind(tstate); _PyThreadState_Bind(tstate);
PyEval_AcquireThread(tstate); PyEval_AcquireThread(tstate);
tstate->interp->threads.count++; tstate->interp->threads.count++;

View File

@ -163,16 +163,6 @@ UNSIGNAL_ASYNC_EXC(PyInterpreterState *interp)
COMPUTE_EVAL_BREAKER(interp, ceval, ceval2); COMPUTE_EVAL_BREAKER(interp, ceval, ceval2);
} }
#ifndef NDEBUG
/* Ensure that tstate is valid */
static int
is_tstate_valid(PyThreadState *tstate)
{
assert(!_PyMem_IsPtrFreed(tstate));
assert(!_PyMem_IsPtrFreed(tstate->interp));
return 1;
}
#endif
/* /*
* Implementation of the Global Interpreter Lock (GIL). * Implementation of the Global Interpreter Lock (GIL).
@ -325,7 +315,7 @@ drop_gil(struct _ceval_state *ceval, PyThreadState *tstate)
/* Not switched yet => wait */ /* Not switched yet => wait */
if (((PyThreadState*)_Py_atomic_load_relaxed(&gil->last_holder)) == tstate) if (((PyThreadState*)_Py_atomic_load_relaxed(&gil->last_holder)) == tstate)
{ {
assert(is_tstate_valid(tstate)); assert(_PyThreadState_CheckConsistency(tstate));
RESET_GIL_DROP_REQUEST(tstate->interp); RESET_GIL_DROP_REQUEST(tstate->interp);
/* NOTE: if COND_WAIT does not atomically start waiting when /* NOTE: if COND_WAIT does not atomically start waiting when
releasing the mutex, another thread can run through, take releasing the mutex, another thread can run through, take
@ -386,7 +376,7 @@ take_gil(PyThreadState *tstate)
PyThread_exit_thread(); PyThread_exit_thread();
} }
assert(is_tstate_valid(tstate)); assert(_PyThreadState_CheckConsistency(tstate));
PyInterpreterState *interp = tstate->interp; PyInterpreterState *interp = tstate->interp;
struct _ceval_state *ceval = &interp->ceval; struct _ceval_state *ceval = &interp->ceval;
struct _gil_runtime_state *gil = ceval->gil; struct _gil_runtime_state *gil = ceval->gil;
@ -427,7 +417,7 @@ take_gil(PyThreadState *tstate)
} }
PyThread_exit_thread(); PyThread_exit_thread();
} }
assert(is_tstate_valid(tstate)); assert(_PyThreadState_CheckConsistency(tstate));
SET_GIL_DROP_REQUEST(interp); SET_GIL_DROP_REQUEST(interp);
drop_requested = 1; drop_requested = 1;
@ -466,7 +456,7 @@ _ready:
drop_gil(ceval, tstate); drop_gil(ceval, tstate);
PyThread_exit_thread(); PyThread_exit_thread();
} }
assert(is_tstate_valid(tstate)); assert(_PyThreadState_CheckConsistency(tstate));
if (_Py_atomic_load_relaxed(&ceval->gil_drop_request)) { if (_Py_atomic_load_relaxed(&ceval->gil_drop_request)) {
RESET_GIL_DROP_REQUEST(interp); RESET_GIL_DROP_REQUEST(interp);
@ -679,7 +669,7 @@ PyEval_AcquireThread(PyThreadState *tstate)
void void
PyEval_ReleaseThread(PyThreadState *tstate) PyEval_ReleaseThread(PyThreadState *tstate)
{ {
assert(is_tstate_valid(tstate)); assert(_PyThreadState_CheckConsistency(tstate));
PyThreadState *new_tstate = _PyThreadState_SwapNoGIL(NULL); PyThreadState *new_tstate = _PyThreadState_SwapNoGIL(NULL);
if (new_tstate != tstate) { if (new_tstate != tstate) {
@ -877,7 +867,7 @@ Py_AddPendingCall(int (*func)(void *), void *arg)
static int static int
handle_signals(PyThreadState *tstate) handle_signals(PyThreadState *tstate)
{ {
assert(is_tstate_valid(tstate)); assert(_PyThreadState_CheckConsistency(tstate));
if (!_Py_ThreadCanHandleSignals(tstate->interp)) { if (!_Py_ThreadCanHandleSignals(tstate->interp)) {
return 0; return 0;
} }
@ -983,7 +973,7 @@ void
_Py_FinishPendingCalls(PyThreadState *tstate) _Py_FinishPendingCalls(PyThreadState *tstate)
{ {
assert(PyGILState_Check()); assert(PyGILState_Check());
assert(is_tstate_valid(tstate)); assert(_PyThreadState_CheckConsistency(tstate));
if (make_pending_calls(tstate->interp) < 0) { if (make_pending_calls(tstate->interp) < 0) {
PyObject *exc = _PyErr_GetRaisedException(tstate); PyObject *exc = _PyErr_GetRaisedException(tstate);
@ -1024,7 +1014,7 @@ Py_MakePendingCalls(void)
assert(PyGILState_Check()); assert(PyGILState_Check());
PyThreadState *tstate = _PyThreadState_GET(); PyThreadState *tstate = _PyThreadState_GET();
assert(is_tstate_valid(tstate)); assert(_PyThreadState_CheckConsistency(tstate));
/* Only execute pending calls on the main thread. */ /* Only execute pending calls on the main thread. */
if (!_Py_IsMainThread() || !_Py_IsMainInterpreter(tstate->interp)) { if (!_Py_IsMainThread() || !_Py_IsMainInterpreter(tstate->interp)) {

View File

@ -2890,6 +2890,24 @@ _PyThreadState_PopFrame(PyThreadState *tstate, _PyInterpreterFrame * frame)
} }
#ifndef NDEBUG
// Check that a Python thread state valid. In practice, this function is used
// on a Python debug build to check if 'tstate' is a dangling pointer, if the
// PyThreadState memory has been freed.
//
// Usage:
//
// assert(_PyThreadState_CheckConsistency(tstate));
int
_PyThreadState_CheckConsistency(PyThreadState *tstate)
{
assert(!_PyMem_IsPtrFreed(tstate));
assert(!_PyMem_IsPtrFreed(tstate->interp));
return 1;
}
#endif
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif