Skip to content

Commit f63d378

Browse files
authored
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.
1 parent b0edf3b commit f63d378

File tree

4 files changed

+35
-20
lines changed

4 files changed

+35
-20
lines changed

Include/internal/pycore_pystate.h

+4
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ _Py_ThreadCanHandleSignals(PyInterpreterState *interp)
6767
extern _Py_thread_local PyThreadState *_Py_tss_tstate;
6868
#endif
6969

70+
#ifndef NDEBUG
71+
extern int _PyThreadState_CheckConsistency(PyThreadState *tstate);
72+
#endif
73+
7074
// Export for most shared extensions, used via _PyThreadState_GET() static
7175
// inline function.
7276
PyAPI_FUNC(PyThreadState *) _PyThreadState_GetCurrent(void);

Modules/_threadmodule.c

+5-2
Original file line numberDiff line numberDiff line change
@@ -1074,9 +1074,12 @@ static void
10741074
thread_run(void *boot_raw)
10751075
{
10761076
struct bootstate *boot = (struct bootstate *) boot_raw;
1077-
PyThreadState *tstate;
1077+
PyThreadState *tstate = boot->tstate;
1078+
1079+
// gh-104690: If Python is being finalized and PyInterpreterState_Delete()
1080+
// was called, tstate becomes a dangling pointer.
1081+
assert(_PyThreadState_CheckConsistency(tstate));
10781082

1079-
tstate = boot->tstate;
10801083
_PyThreadState_Bind(tstate);
10811084
PyEval_AcquireThread(tstate);
10821085
tstate->interp->threads.count++;

Python/ceval_gil.c

+8-18
Original file line numberDiff line numberDiff line change
@@ -163,16 +163,6 @@ UNSIGNAL_ASYNC_EXC(PyInterpreterState *interp)
163163
COMPUTE_EVAL_BREAKER(interp, ceval, ceval2);
164164
}
165165

166-
#ifndef NDEBUG
167-
/* Ensure that tstate is valid */
168-
static int
169-
is_tstate_valid(PyThreadState *tstate)
170-
{
171-
assert(!_PyMem_IsPtrFreed(tstate));
172-
assert(!_PyMem_IsPtrFreed(tstate->interp));
173-
return 1;
174-
}
175-
#endif
176166

177167
/*
178168
* Implementation of the Global Interpreter Lock (GIL).
@@ -325,7 +315,7 @@ drop_gil(struct _ceval_state *ceval, PyThreadState *tstate)
325315
/* Not switched yet => wait */
326316
if (((PyThreadState*)_Py_atomic_load_relaxed(&gil->last_holder)) == tstate)
327317
{
328-
assert(is_tstate_valid(tstate));
318+
assert(_PyThreadState_CheckConsistency(tstate));
329319
RESET_GIL_DROP_REQUEST(tstate->interp);
330320
/* NOTE: if COND_WAIT does not atomically start waiting when
331321
releasing the mutex, another thread can run through, take
@@ -386,7 +376,7 @@ take_gil(PyThreadState *tstate)
386376
PyThread_exit_thread();
387377
}
388378

389-
assert(is_tstate_valid(tstate));
379+
assert(_PyThreadState_CheckConsistency(tstate));
390380
PyInterpreterState *interp = tstate->interp;
391381
struct _ceval_state *ceval = &interp->ceval;
392382
struct _gil_runtime_state *gil = ceval->gil;
@@ -427,7 +417,7 @@ take_gil(PyThreadState *tstate)
427417
}
428418
PyThread_exit_thread();
429419
}
430-
assert(is_tstate_valid(tstate));
420+
assert(_PyThreadState_CheckConsistency(tstate));
431421

432422
SET_GIL_DROP_REQUEST(interp);
433423
drop_requested = 1;
@@ -466,7 +456,7 @@ take_gil(PyThreadState *tstate)
466456
drop_gil(ceval, tstate);
467457
PyThread_exit_thread();
468458
}
469-
assert(is_tstate_valid(tstate));
459+
assert(_PyThreadState_CheckConsistency(tstate));
470460

471461
if (_Py_atomic_load_relaxed(&ceval->gil_drop_request)) {
472462
RESET_GIL_DROP_REQUEST(interp);
@@ -679,7 +669,7 @@ PyEval_AcquireThread(PyThreadState *tstate)
679669
void
680670
PyEval_ReleaseThread(PyThreadState *tstate)
681671
{
682-
assert(is_tstate_valid(tstate));
672+
assert(_PyThreadState_CheckConsistency(tstate));
683673

684674
PyThreadState *new_tstate = _PyThreadState_SwapNoGIL(NULL);
685675
if (new_tstate != tstate) {
@@ -877,7 +867,7 @@ Py_AddPendingCall(int (*func)(void *), void *arg)
877867
static int
878868
handle_signals(PyThreadState *tstate)
879869
{
880-
assert(is_tstate_valid(tstate));
870+
assert(_PyThreadState_CheckConsistency(tstate));
881871
if (!_Py_ThreadCanHandleSignals(tstate->interp)) {
882872
return 0;
883873
}
@@ -983,7 +973,7 @@ void
983973
_Py_FinishPendingCalls(PyThreadState *tstate)
984974
{
985975
assert(PyGILState_Check());
986-
assert(is_tstate_valid(tstate));
976+
assert(_PyThreadState_CheckConsistency(tstate));
987977

988978
if (make_pending_calls(tstate->interp) < 0) {
989979
PyObject *exc = _PyErr_GetRaisedException(tstate);
@@ -1024,7 +1014,7 @@ Py_MakePendingCalls(void)
10241014
assert(PyGILState_Check());
10251015

10261016
PyThreadState *tstate = _PyThreadState_GET();
1027-
assert(is_tstate_valid(tstate));
1017+
assert(_PyThreadState_CheckConsistency(tstate));
10281018

10291019
/* Only execute pending calls on the main thread. */
10301020
if (!_Py_IsMainThread() || !_Py_IsMainInterpreter(tstate->interp)) {

Python/pystate.c

+18
Original file line numberDiff line numberDiff line change
@@ -2890,6 +2890,24 @@ _PyThreadState_PopFrame(PyThreadState *tstate, _PyInterpreterFrame * frame)
28902890
}
28912891

28922892

2893+
#ifndef NDEBUG
2894+
// Check that a Python thread state valid. In practice, this function is used
2895+
// on a Python debug build to check if 'tstate' is a dangling pointer, if the
2896+
// PyThreadState memory has been freed.
2897+
//
2898+
// Usage:
2899+
//
2900+
// assert(_PyThreadState_CheckConsistency(tstate));
2901+
int
2902+
_PyThreadState_CheckConsistency(PyThreadState *tstate)
2903+
{
2904+
assert(!_PyMem_IsPtrFreed(tstate));
2905+
assert(!_PyMem_IsPtrFreed(tstate->interp));
2906+
return 1;
2907+
}
2908+
#endif
2909+
2910+
28932911
#ifdef __cplusplus
28942912
}
28952913
#endif

0 commit comments

Comments
 (0)