From da8199f8842c2830fa4015138725849863523de4 Mon Sep 17 00:00:00 2001 From: Dino Viehland Date: Thu, 11 Dec 2025 00:54:29 -0800 Subject: [PATCH] gh-123241: Don't modify ref count during visitation (GH-142232) --- Include/internal/pycore_typeobject.h | 5 +++ ...-12-10-11-20-05.gh-issue-123241.oYg2n7.rst | 2 + Modules/_ctypes/ctypes.h | 14 +++---- Objects/typeobject.c | 42 +++++++++++++------ 4 files changed, 41 insertions(+), 22 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-12-10-11-20-05.gh-issue-123241.oYg2n7.rst diff --git a/Include/internal/pycore_typeobject.h b/Include/internal/pycore_typeobject.h index 3661f171e2b0..abaa60890b55 100644 --- a/Include/internal/pycore_typeobject.h +++ b/Include/internal/pycore_typeobject.h @@ -155,6 +155,11 @@ extern int _PyType_CacheGetItemForSpecialization(PyHeapTypeObject *ht, PyObject // Precalculates count of non-unique slots and fills wrapperbase.name_count. extern int _PyType_InitSlotDefs(PyInterpreterState *interp); +// Like PyType_GetBaseByToken, but does not modify refcounts. +// Cannot fail; arguments must be valid. +PyAPI_FUNC(int) +_PyType_GetBaseByToken_Borrow(PyTypeObject *type, void *token, PyTypeObject **result); + #ifdef __cplusplus } #endif diff --git a/Misc/NEWS.d/next/Library/2025-12-10-11-20-05.gh-issue-123241.oYg2n7.rst b/Misc/NEWS.d/next/Library/2025-12-10-11-20-05.gh-issue-123241.oYg2n7.rst new file mode 100644 index 000000000000..871a03a6fd10 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-12-10-11-20-05.gh-issue-123241.oYg2n7.rst @@ -0,0 +1,2 @@ +Avoid reference count operations in garbage collection of :mod:`ctypes` +objects. diff --git a/Modules/_ctypes/ctypes.h b/Modules/_ctypes/ctypes.h index 9f995bf24080..478daecad55b 100644 --- a/Modules/_ctypes/ctypes.h +++ b/Modules/_ctypes/ctypes.h @@ -608,7 +608,8 @@ PyStgInfo_FromAny(ctypes_state *state, PyObject *obj, StgInfo **result) return _stginfo_from_type(state, Py_TYPE(obj), result); } -/* A variant of PyStgInfo_FromType that doesn't need the state, +/* A variant of PyStgInfo_FromType that doesn't need the state + * and doesn't modify any refcounts, * so it can be called from finalization functions when the module * state is torn down. */ @@ -616,17 +617,12 @@ static inline StgInfo * _PyStgInfo_FromType_NoState(PyObject *type) { PyTypeObject *PyCType_Type; - if (PyType_GetBaseByToken(Py_TYPE(type), &pyctype_type_spec, &PyCType_Type) < 0) { - return NULL; - } - if (PyCType_Type == NULL) { - PyErr_Format(PyExc_TypeError, "expected a ctypes type, got '%N'", type); + if (_PyType_GetBaseByToken_Borrow(Py_TYPE(type), &pyctype_type_spec, &PyCType_Type) < 0 || + PyCType_Type == NULL) { return NULL; } - StgInfo *info = PyObject_GetTypeData(type, PyCType_Type); - Py_DECREF(PyCType_Type); - return info; + return PyObject_GetTypeData(type, PyCType_Type); } // Initialize StgInfo on a newly created type diff --git a/Objects/typeobject.c b/Objects/typeobject.c index cbe0215359e2..7f5149aeece1 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5877,23 +5877,15 @@ get_base_by_token_recursive(PyObject *bases, void *token) } int -PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result) +_PyType_GetBaseByToken_Borrow(PyTypeObject *type, void *token, PyTypeObject **result) { + assert(token != NULL); + assert(PyType_Check(type)); + if (result != NULL) { *result = NULL; } - if (token == NULL) { - PyErr_Format(PyExc_SystemError, - "PyType_GetBaseByToken called with token=NULL"); - return -1; - } - if (!PyType_Check(type)) { - PyErr_Format(PyExc_TypeError, - "expected a type, got a '%T' object", type); - return -1; - } - if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) { // No static type has a heaptype superclass, // which is ensured by type_ready_mro(). @@ -5902,7 +5894,7 @@ PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result) if (((PyHeapTypeObject*)type)->ht_token == token) { found: if (result != NULL) { - *result = (PyTypeObject *)Py_NewRef(type); + *result = type; } return 1; } @@ -5936,6 +5928,30 @@ found: return 0; } +int +PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result) +{ + if (result != NULL) { + *result = NULL; + } + if (token == NULL) { + PyErr_Format(PyExc_SystemError, + "PyType_GetBaseByToken called with token=NULL"); + return -1; + } + if (!PyType_Check(type)) { + PyErr_Format(PyExc_TypeError, + "expected a type, got a '%T' object", type); + return -1; + } + + int res = _PyType_GetBaseByToken_Borrow(type, token, result); + if (res > 0 && result) { + Py_INCREF(*result); + } + return res; +} + void * PyObject_GetTypeData(PyObject *obj, PyTypeObject *cls) -- 2.47.3