bpo-46417: Add _PyType_GetSubclasses() function (GH-30761)

Add a new _PyType_GetSubclasses() function to get type's subclasses.

_PyType_GetSubclasses(type) returns a list which holds strong
refererences to subclasses. It is safer than iterating on
type->tp_subclasses which yields weak references and can be modified
in the loop.

_PyType_GetSubclasses(type) now holds a reference to the tp_subclasses
dict while creating the list of subclasses.

set_collection_flag_recursive() of _abc.c now uses
_PyType_GetSubclasses().
This commit is contained in:
Victor Stinner 2022-01-21 23:29:10 +01:00 committed by GitHub
parent 57d1855682
commit 8ee07dda13
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 75 additions and 54 deletions

View File

@ -220,11 +220,12 @@ static inline PyObject **_PyObject_ManagedDictPointer(PyObject *obj)
return ((PyObject **)obj)-3; return ((PyObject **)obj)-3;
} }
PyObject ** _PyObject_DictPointer(PyObject *); extern PyObject ** _PyObject_DictPointer(PyObject *);
int _PyObject_VisitInstanceAttributes(PyObject *self, visitproc visit, void *arg); extern int _PyObject_VisitInstanceAttributes(PyObject *self, visitproc visit, void *arg);
void _PyObject_ClearInstanceAttributes(PyObject *self); extern void _PyObject_ClearInstanceAttributes(PyObject *self);
void _PyObject_FreeInstanceAttributes(PyObject *self); extern void _PyObject_FreeInstanceAttributes(PyObject *self);
int _PyObject_IsInstanceDictEmpty(PyObject *); extern int _PyObject_IsInstanceDictEmpty(PyObject *);
extern PyObject* _PyType_GetSubclasses(PyTypeObject *);
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -4,6 +4,7 @@
#endif #endif
#include "Python.h" #include "Python.h"
#include "pycore_object.h" // _PyType_GetSubclasses()
#include "pycore_moduleobject.h" // _PyModule_GetState() #include "pycore_moduleobject.h" // _PyModule_GetState()
#include "clinic/_abc.c.h" #include "clinic/_abc.c.h"
@ -493,21 +494,20 @@ set_collection_flag_recursive(PyTypeObject *child, unsigned long flag)
{ {
return; return;
} }
child->tp_flags &= ~COLLECTION_FLAGS; child->tp_flags &= ~COLLECTION_FLAGS;
child->tp_flags |= flag; child->tp_flags |= flag;
PyObject *grandchildren = child->tp_subclasses;
PyObject *grandchildren = _PyType_GetSubclasses(child);
if (grandchildren == NULL) { if (grandchildren == NULL) {
return; return;
} }
assert(PyDict_CheckExact(grandchildren));
Py_ssize_t i = 0; for (Py_ssize_t i = 0; i < PyList_GET_SIZE(grandchildren); i++) {
while (PyDict_Next(grandchildren, &i, NULL, &grandchildren)) { PyObject *grandchild = PyList_GET_ITEM(grandchildren, i);
assert(PyWeakref_CheckRef(grandchildren));
PyObject *grandchild = PyWeakref_GET_OBJECT(grandchildren);
if (PyType_Check(grandchild)) {
set_collection_flag_recursive((PyTypeObject *)grandchild, flag); set_collection_flag_recursive((PyTypeObject *)grandchild, flag);
} }
} Py_DECREF(grandchildren);
} }
/*[clinic input] /*[clinic input]

View File

@ -687,27 +687,28 @@ static int recurse_down_subclasses(PyTypeObject *type, PyObject *name,
static int static int
mro_hierarchy(PyTypeObject *type, PyObject *temp) mro_hierarchy(PyTypeObject *type, PyObject *temp)
{ {
int res; PyObject *old_mro;
PyObject *new_mro, *old_mro; int res = mro_internal(type, &old_mro);
PyObject *tuple; if (res <= 0) {
PyObject *subclasses;
Py_ssize_t i, n;
res = mro_internal(type, &old_mro);
if (res <= 0)
/* error / reentrance */ /* error / reentrance */
return res; return res;
new_mro = type->tp_mro; }
PyObject *new_mro = type->tp_mro;
if (old_mro != NULL) PyObject *tuple;
if (old_mro != NULL) {
tuple = PyTuple_Pack(3, type, new_mro, old_mro); tuple = PyTuple_Pack(3, type, new_mro, old_mro);
else }
else {
tuple = PyTuple_Pack(2, type, new_mro); tuple = PyTuple_Pack(2, type, new_mro);
}
if (tuple != NULL) if (tuple != NULL) {
res = PyList_Append(temp, tuple); res = PyList_Append(temp, tuple);
else }
else {
res = -1; res = -1;
}
Py_XDECREF(tuple); Py_XDECREF(tuple);
if (res < 0) { if (res < 0) {
@ -727,16 +728,19 @@ mro_hierarchy(PyTypeObject *type, PyObject *temp)
Finally, this makes things simple avoiding the need to deal Finally, this makes things simple avoiding the need to deal
with dictionary iterators and weak references. with dictionary iterators and weak references.
*/ */
subclasses = type___subclasses___impl(type); PyObject *subclasses = _PyType_GetSubclasses(type);
if (subclasses == NULL) if (subclasses == NULL) {
return -1; return -1;
n = PyList_GET_SIZE(subclasses); }
for (i = 0; i < n; i++) {
Py_ssize_t n = PyList_GET_SIZE(subclasses);
for (Py_ssize_t i = 0; i < n; i++) {
PyTypeObject *subclass = _PyType_CAST(PyList_GET_ITEM(subclasses, i)); PyTypeObject *subclass = _PyType_CAST(PyList_GET_ITEM(subclasses, i));
res = mro_hierarchy(subclass, temp); res = mro_hierarchy(subclass, temp);
if (res < 0) if (res < 0) {
break; break;
} }
}
Py_DECREF(subclasses); Py_DECREF(subclasses);
return res; return res;
@ -4124,6 +4128,42 @@ type_dealloc(PyTypeObject *type)
Py_TYPE(type)->tp_free((PyObject *)type); Py_TYPE(type)->tp_free((PyObject *)type);
} }
PyObject*
_PyType_GetSubclasses(PyTypeObject *self)
{
PyObject *list = PyList_New(0);
if (list == NULL) {
return NULL;
}
// Hold a strong reference to tp_subclasses while iterating on it
PyObject *dict = Py_XNewRef(self->tp_subclasses);
if (dict == NULL) {
return list;
}
assert(PyDict_CheckExact(dict));
Py_ssize_t i = 0;
PyObject *ref; // borrowed ref
while (PyDict_Next(dict, &i, NULL, &ref)) {
assert(PyWeakref_CheckRef(ref));
PyObject *obj = PyWeakref_GET_OBJECT(ref); // borrowed ref
if (obj == Py_None) {
continue;
}
assert(PyType_Check(obj));
if (PyList_Append(list, obj) < 0) {
Py_CLEAR(list);
goto done;
}
}
done:
Py_DECREF(dict);
return list;
}
/*[clinic input] /*[clinic input]
type.__subclasses__ type.__subclasses__
@ -4134,28 +4174,7 @@ static PyObject *
type___subclasses___impl(PyTypeObject *self) type___subclasses___impl(PyTypeObject *self)
/*[clinic end generated code: output=eb5eb54485942819 input=5af66132436f9a7b]*/ /*[clinic end generated code: output=eb5eb54485942819 input=5af66132436f9a7b]*/
{ {
PyObject *list, *raw, *ref; return _PyType_GetSubclasses(self);
Py_ssize_t i;
list = PyList_New(0);
if (list == NULL)
return NULL;
raw = self->tp_subclasses;
if (raw == NULL)
return list;
assert(PyDict_CheckExact(raw));
i = 0;
while (PyDict_Next(raw, &i, NULL, &ref)) {
assert(PyWeakref_CheckRef(ref));
ref = PyWeakref_GET_OBJECT(ref);
if (ref != Py_None) {
if (PyList_Append(list, ref) < 0) {
Py_DECREF(list);
return NULL;
}
}
}
return list;
} }
static PyObject * static PyObject *
@ -4165,6 +4184,7 @@ type_prepare(PyObject *self, PyObject *const *args, Py_ssize_t nargs,
return PyDict_New(); return PyDict_New();
} }
/* /*
Merge the __dict__ of aclass into dict, and recursively also all Merge the __dict__ of aclass into dict, and recursively also all
the __dict__s of aclass's base classes. The order of merging isn't the __dict__s of aclass's base classes. The order of merging isn't