diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-07-11-14-39-38.gh-issue-151763.nlYxAk.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-11-14-39-38.gh-issue-151763.nlYxAk.rst new file mode 100644 index 00000000000000..4d54c2b82da6cb --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-11-14-39-38.gh-issue-151763.nlYxAk.rst @@ -0,0 +1,2 @@ +Fixed a bug in free-threaded builds where the active exception could be +unexpectedly cleared during subtype deallocation in ``subtype_dealloc()``. diff --git a/Objects/typeobject.c b/Objects/typeobject.c index b77f3e3bce5455..c66f245bdf329a 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -2833,6 +2833,13 @@ subtype_dealloc(PyObject *self) } } + /* Save the current exception: clear_slots() and the dict/base-dealloc + below decref arbitrary attribute values, which may clear + tstate->current_exception (gh-89373). Mirroring slot_tp_finalize(). */ + PyThreadState *tstate = _PyThreadState_GET(); + PyObject *exc = _PyErr_GetRaisedException(tstate); /* preserve in-flight exception */ + + /* Clear slots up to the nearest base with a different tp_dealloc */ base = type; while ((basedealloc = base->tp_dealloc) == subtype_dealloc) { @@ -2882,6 +2889,9 @@ subtype_dealloc(PyObject *self) if (type_needs_decref) { _Py_DECREF_TYPE(type); } + + /* Restore the saved exception (see save above). */ + _PyErr_SetRaisedException(tstate, exc); } static PyTypeObject *solid_base(PyTypeObject *type);