Skip to content

Commit 8ee07dd

Browse files
authored
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().
1 parent 57d1855 commit 8ee07dd

File tree

3 files changed

+75
-54
lines changed

3 files changed

+75
-54
lines changed

Include/internal/pycore_object.h

+6-5
Original file line numberDiff line numberDiff line change
@@ -220,11 +220,12 @@ static inline PyObject **_PyObject_ManagedDictPointer(PyObject *obj)
220220
return ((PyObject **)obj)-3;
221221
}
222222

223-
PyObject ** _PyObject_DictPointer(PyObject *);
224-
int _PyObject_VisitInstanceAttributes(PyObject *self, visitproc visit, void *arg);
225-
void _PyObject_ClearInstanceAttributes(PyObject *self);
226-
void _PyObject_FreeInstanceAttributes(PyObject *self);
227-
int _PyObject_IsInstanceDictEmpty(PyObject *);
223+
extern PyObject ** _PyObject_DictPointer(PyObject *);
224+
extern int _PyObject_VisitInstanceAttributes(PyObject *self, visitproc visit, void *arg);
225+
extern void _PyObject_ClearInstanceAttributes(PyObject *self);
226+
extern void _PyObject_FreeInstanceAttributes(PyObject *self);
227+
extern int _PyObject_IsInstanceDictEmpty(PyObject *);
228+
extern PyObject* _PyType_GetSubclasses(PyTypeObject *);
228229

229230
#ifdef __cplusplus
230231
}

Modules/_abc.c

+9-9
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#endif
55

66
#include "Python.h"
7+
#include "pycore_object.h" // _PyType_GetSubclasses()
78
#include "pycore_moduleobject.h" // _PyModule_GetState()
89
#include "clinic/_abc.c.h"
910

@@ -493,21 +494,20 @@ set_collection_flag_recursive(PyTypeObject *child, unsigned long flag)
493494
{
494495
return;
495496
}
497+
496498
child->tp_flags &= ~COLLECTION_FLAGS;
497499
child->tp_flags |= flag;
498-
PyObject *grandchildren = child->tp_subclasses;
500+
501+
PyObject *grandchildren = _PyType_GetSubclasses(child);
499502
if (grandchildren == NULL) {
500503
return;
501504
}
502-
assert(PyDict_CheckExact(grandchildren));
503-
Py_ssize_t i = 0;
504-
while (PyDict_Next(grandchildren, &i, NULL, &grandchildren)) {
505-
assert(PyWeakref_CheckRef(grandchildren));
506-
PyObject *grandchild = PyWeakref_GET_OBJECT(grandchildren);
507-
if (PyType_Check(grandchild)) {
508-
set_collection_flag_recursive((PyTypeObject *)grandchild, flag);
509-
}
505+
506+
for (Py_ssize_t i = 0; i < PyList_GET_SIZE(grandchildren); i++) {
507+
PyObject *grandchild = PyList_GET_ITEM(grandchildren, i);
508+
set_collection_flag_recursive((PyTypeObject *)grandchild, flag);
510509
}
510+
Py_DECREF(grandchildren);
511511
}
512512

513513
/*[clinic input]

Objects/typeobject.c

+60-40
Original file line numberDiff line numberDiff line change
@@ -687,27 +687,28 @@ static int recurse_down_subclasses(PyTypeObject *type, PyObject *name,
687687
static int
688688
mro_hierarchy(PyTypeObject *type, PyObject *temp)
689689
{
690-
int res;
691-
PyObject *new_mro, *old_mro;
692-
PyObject *tuple;
693-
PyObject *subclasses;
694-
Py_ssize_t i, n;
695-
696-
res = mro_internal(type, &old_mro);
697-
if (res <= 0)
690+
PyObject *old_mro;
691+
int res = mro_internal(type, &old_mro);
692+
if (res <= 0) {
698693
/* error / reentrance */
699694
return res;
700-
new_mro = type->tp_mro;
695+
}
696+
PyObject *new_mro = type->tp_mro;
701697

702-
if (old_mro != NULL)
698+
PyObject *tuple;
699+
if (old_mro != NULL) {
703700
tuple = PyTuple_Pack(3, type, new_mro, old_mro);
704-
else
701+
}
702+
else {
705703
tuple = PyTuple_Pack(2, type, new_mro);
704+
}
706705

707-
if (tuple != NULL)
706+
if (tuple != NULL) {
708707
res = PyList_Append(temp, tuple);
709-
else
708+
}
709+
else {
710710
res = -1;
711+
}
711712
Py_XDECREF(tuple);
712713

713714
if (res < 0) {
@@ -727,15 +728,18 @@ mro_hierarchy(PyTypeObject *type, PyObject *temp)
727728
Finally, this makes things simple avoiding the need to deal
728729
with dictionary iterators and weak references.
729730
*/
730-
subclasses = type___subclasses___impl(type);
731-
if (subclasses == NULL)
731+
PyObject *subclasses = _PyType_GetSubclasses(type);
732+
if (subclasses == NULL) {
732733
return -1;
733-
n = PyList_GET_SIZE(subclasses);
734-
for (i = 0; i < n; i++) {
734+
}
735+
736+
Py_ssize_t n = PyList_GET_SIZE(subclasses);
737+
for (Py_ssize_t i = 0; i < n; i++) {
735738
PyTypeObject *subclass = _PyType_CAST(PyList_GET_ITEM(subclasses, i));
736739
res = mro_hierarchy(subclass, temp);
737-
if (res < 0)
740+
if (res < 0) {
738741
break;
742+
}
739743
}
740744
Py_DECREF(subclasses);
741745

@@ -4124,6 +4128,42 @@ type_dealloc(PyTypeObject *type)
41244128
Py_TYPE(type)->tp_free((PyObject *)type);
41254129
}
41264130

4131+
4132+
PyObject*
4133+
_PyType_GetSubclasses(PyTypeObject *self)
4134+
{
4135+
PyObject *list = PyList_New(0);
4136+
if (list == NULL) {
4137+
return NULL;
4138+
}
4139+
4140+
// Hold a strong reference to tp_subclasses while iterating on it
4141+
PyObject *dict = Py_XNewRef(self->tp_subclasses);
4142+
if (dict == NULL) {
4143+
return list;
4144+
}
4145+
assert(PyDict_CheckExact(dict));
4146+
4147+
Py_ssize_t i = 0;
4148+
PyObject *ref; // borrowed ref
4149+
while (PyDict_Next(dict, &i, NULL, &ref)) {
4150+
assert(PyWeakref_CheckRef(ref));
4151+
PyObject *obj = PyWeakref_GET_OBJECT(ref); // borrowed ref
4152+
if (obj == Py_None) {
4153+
continue;
4154+
}
4155+
assert(PyType_Check(obj));
4156+
if (PyList_Append(list, obj) < 0) {
4157+
Py_CLEAR(list);
4158+
goto done;
4159+
}
4160+
}
4161+
done:
4162+
Py_DECREF(dict);
4163+
return list;
4164+
}
4165+
4166+
41274167
/*[clinic input]
41284168
type.__subclasses__
41294169
@@ -4134,28 +4174,7 @@ static PyObject *
41344174
type___subclasses___impl(PyTypeObject *self)
41354175
/*[clinic end generated code: output=eb5eb54485942819 input=5af66132436f9a7b]*/
41364176
{
4137-
PyObject *list, *raw, *ref;
4138-
Py_ssize_t i;
4139-
4140-
list = PyList_New(0);
4141-
if (list == NULL)
4142-
return NULL;
4143-
raw = self->tp_subclasses;
4144-
if (raw == NULL)
4145-
return list;
4146-
assert(PyDict_CheckExact(raw));
4147-
i = 0;
4148-
while (PyDict_Next(raw, &i, NULL, &ref)) {
4149-
assert(PyWeakref_CheckRef(ref));
4150-
ref = PyWeakref_GET_OBJECT(ref);
4151-
if (ref != Py_None) {
4152-
if (PyList_Append(list, ref) < 0) {
4153-
Py_DECREF(list);
4154-
return NULL;
4155-
}
4156-
}
4157-
}
4158-
return list;
4177+
return _PyType_GetSubclasses(self);
41594178
}
41604179

41614180
static PyObject *
@@ -4165,6 +4184,7 @@ type_prepare(PyObject *self, PyObject *const *args, Py_ssize_t nargs,
41654184
return PyDict_New();
41664185
}
41674186

4187+
41684188
/*
41694189
Merge the __dict__ of aclass into dict, and recursively also all
41704190
the __dict__s of aclass's base classes. The order of merging isn't

0 commit comments

Comments
 (0)