bpo-46417: Cleanup typeobject.c code (GH-30795)

* Add comment to recurse_down_subclasses() explaining why it's safe
  to use a borrowed reference to tp_subclasses.
* remove_all_subclasses() no longer accept NULL cases
* type_set_bases() now relies on the fact that new_bases is not NULL.
* type_dealloc_common() avoids PyErr_Fetch/PyErr_Restore if tp_bases
  is NULL.
* remove_all_subclasses() makes sure that no exception is raised.
* Don't test at runtime if tp_mro only contains types: rely on
  _PyType_CAST() assertion for that.
* _PyStaticType_Dealloc() no longer clears tp_subclasses which is
  already NULL.
* mro_hierarchy() avoids calling _PyType_GetSubclasses() if
  tp_subclasses is NULL.

Coding style:

* Use Py_NewRef().
* Add braces and move variable declarations to the first variable
  assignement.
* Rename a few variables and parameters to use better names.
This commit is contained in:
Victor Stinner 2022-01-22 18:56:11 +01:00 committed by GitHub
parent 500c146387
commit 3a4c15bb98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 165 additions and 160 deletions

View File

@ -328,24 +328,26 @@ PyType_Modified(PyTypeObject *type)
We don't assign new version tags eagerly, but only as We don't assign new version tags eagerly, but only as
needed. needed.
*/ */
PyObject *raw, *ref; if (!_PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG)) {
Py_ssize_t i;
if (!_PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG))
return; return;
}
raw = type->tp_subclasses; PyObject *subclasses = type->tp_subclasses;
if (raw != NULL) { if (subclasses != NULL) {
assert(PyDict_CheckExact(raw)); assert(PyDict_CheckExact(subclasses));
i = 0;
while (PyDict_Next(raw, &i, NULL, &ref)) { Py_ssize_t i = 0;
PyObject *ref;
while (PyDict_Next(subclasses, &i, NULL, &ref)) {
assert(PyWeakref_CheckRef(ref)); assert(PyWeakref_CheckRef(ref));
ref = PyWeakref_GET_OBJECT(ref); PyObject *obj = PyWeakref_GET_OBJECT(ref);
if (ref != Py_None) { if (obj == Py_None) {
PyType_Modified(_PyType_CAST(ref)); continue;
} }
PyType_Modified(_PyType_CAST(obj));
} }
} }
type->tp_flags &= ~Py_TPFLAGS_VALID_VERSION_TAG; type->tp_flags &= ~Py_TPFLAGS_VALID_VERSION_TAG;
type->tp_version_tag = 0; /* 0 is not a valid version tag */ type->tp_version_tag = 0; /* 0 is not a valid version tag */
} }
@ -409,13 +411,12 @@ assign_version_tag(struct type_cache *cache, PyTypeObject *type)
must first be done on all super classes. Return 0 if this must first be done on all super classes. Return 0 if this
cannot be done, 1 if Py_TPFLAGS_VALID_VERSION_TAG. cannot be done, 1 if Py_TPFLAGS_VALID_VERSION_TAG.
*/ */
Py_ssize_t i, n; if (_PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG)) {
PyObject *bases;
if (_PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG))
return 1; return 1;
if (!_PyType_HasFeature(type, Py_TPFLAGS_READY)) }
if (!_PyType_HasFeature(type, Py_TPFLAGS_READY)) {
return 0; return 0;
}
if (next_version_tag == 0) { if (next_version_tag == 0) {
/* We have run out of version numbers */ /* We have run out of version numbers */
@ -424,9 +425,9 @@ assign_version_tag(struct type_cache *cache, PyTypeObject *type)
type->tp_version_tag = next_version_tag++; type->tp_version_tag = next_version_tag++;
assert (type->tp_version_tag != 0); assert (type->tp_version_tag != 0);
bases = type->tp_bases; PyObject *bases = type->tp_bases;
n = PyTuple_GET_SIZE(bases); Py_ssize_t n = PyTuple_GET_SIZE(bases);
for (i = 0; i < n; i++) { for (Py_ssize_t i = 0; i < n; i++) {
PyObject *b = PyTuple_GET_ITEM(bases, i); PyObject *b = PyTuple_GET_ITEM(bases, i);
if (!assign_version_tag(cache, _PyType_CAST(b))) if (!assign_version_tag(cache, _PyType_CAST(b)))
return 0; return 0;
@ -679,7 +680,7 @@ static void remove_all_subclasses(PyTypeObject *type, PyObject *bases);
static void update_all_slots(PyTypeObject *); static void update_all_slots(PyTypeObject *);
typedef int (*update_callback)(PyTypeObject *, void *); typedef int (*update_callback)(PyTypeObject *, void *);
static int update_subclasses(PyTypeObject *type, PyObject *name, static int update_subclasses(PyTypeObject *type, PyObject *attr_name,
update_callback callback, void *data); update_callback callback, void *data);
static int recurse_down_subclasses(PyTypeObject *type, PyObject *name, static int recurse_down_subclasses(PyTypeObject *type, PyObject *name,
update_callback callback, void *data); update_callback callback, void *data);
@ -718,30 +719,33 @@ mro_hierarchy(PyTypeObject *type, PyObject *temp)
} }
Py_XDECREF(old_mro); Py_XDECREF(old_mro);
/* Obtain a copy of subclasses list to iterate over. // Avoid creating an empty list if there is no subclass
if (type->tp_subclasses != NULL) {
/* Obtain a copy of subclasses list to iterate over.
Otherwise type->tp_subclasses might be altered Otherwise type->tp_subclasses might be altered
in the middle of the loop, for example, through a custom mro(), in the middle of the loop, for example, through a custom mro(),
by invoking type_set_bases on some subclass of the type by invoking type_set_bases on some subclass of the type
which in turn calls remove_subclass/add_subclass on this type. which in turn calls remove_subclass/add_subclass on this type.
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.
*/ */
PyObject *subclasses = _PyType_GetSubclasses(type); PyObject *subclasses = _PyType_GetSubclasses(type);
if (subclasses == NULL) { if (subclasses == NULL) {
return -1; return -1;
}
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));
res = mro_hierarchy(subclass, temp);
if (res < 0) {
break;
} }
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));
res = mro_hierarchy(subclass, temp);
if (res < 0) {
break;
}
}
Py_DECREF(subclasses);
} }
Py_DECREF(subclasses);
return res; return res;
} }
@ -749,14 +753,12 @@ mro_hierarchy(PyTypeObject *type, PyObject *temp)
static int static int
type_set_bases(PyTypeObject *type, PyObject *new_bases, void *context) type_set_bases(PyTypeObject *type, PyObject *new_bases, void *context)
{ {
int res = 0; // Check arguments
PyObject *temp; if (!check_set_special_type_attr(type, new_bases, "__bases__")) {
PyObject *old_bases;
PyTypeObject *new_base, *old_base;
Py_ssize_t i;
if (!check_set_special_type_attr(type, new_bases, "__bases__"))
return -1; return -1;
}
assert(new_bases != NULL);
if (!PyTuple_Check(new_bases)) { if (!PyTuple_Check(new_bases)) {
PyErr_Format(PyExc_TypeError, PyErr_Format(PyExc_TypeError,
"can only assign tuple to %s.__bases__, not %s", "can only assign tuple to %s.__bases__, not %s",
@ -769,7 +771,8 @@ type_set_bases(PyTypeObject *type, PyObject *new_bases, void *context)
type->tp_name); type->tp_name);
return -1; return -1;
} }
for (i = 0; i < PyTuple_GET_SIZE(new_bases); i++) { Py_ssize_t n = PyTuple_GET_SIZE(new_bases);
for (Py_ssize_t i = 0; i < n; i++) {
PyObject *ob = PyTuple_GET_ITEM(new_bases, i); PyObject *ob = PyTuple_GET_ITEM(new_bases, i);
if (!PyType_Check(ob)) { if (!PyType_Check(ob)) {
PyErr_Format(PyExc_TypeError, PyErr_Format(PyExc_TypeError,
@ -789,39 +792,42 @@ type_set_bases(PyTypeObject *type, PyObject *new_bases, void *context)
below), which in turn may cause an inheritance cycle below), which in turn may cause an inheritance cycle
through tp_base chain. And this is definitely through tp_base chain. And this is definitely
not what you want to ever happen. */ not what you want to ever happen. */
(base->tp_mro != NULL && type_is_subtype_base_chain(base, type))) { (base->tp_mro != NULL && type_is_subtype_base_chain(base, type)))
{
PyErr_SetString(PyExc_TypeError, PyErr_SetString(PyExc_TypeError,
"a __bases__ item causes an inheritance cycle"); "a __bases__ item causes an inheritance cycle");
return -1; return -1;
} }
} }
new_base = best_base(new_bases); // Compute the new MRO and the new base class
PyTypeObject *new_base = best_base(new_bases);
if (new_base == NULL) if (new_base == NULL)
return -1; return -1;
if (!compatible_for_assignment(type->tp_base, new_base, "__bases__")) if (!compatible_for_assignment(type->tp_base, new_base, "__bases__")) {
return -1; return -1;
}
Py_INCREF(new_bases); PyObject *old_bases = type->tp_bases;
Py_INCREF(new_base); assert(old_bases != NULL);
PyTypeObject *old_base = type->tp_base;
old_bases = type->tp_bases; type->tp_bases = Py_NewRef(new_bases);
old_base = type->tp_base; type->tp_base = (PyTypeObject *)Py_NewRef(new_base);
type->tp_bases = new_bases; PyObject *temp = PyList_New(0);
type->tp_base = new_base; if (temp == NULL) {
temp = PyList_New(0);
if (temp == NULL)
goto bail; goto bail;
if (mro_hierarchy(type, temp) < 0) }
if (mro_hierarchy(type, temp) < 0) {
goto undo; goto undo;
}
Py_DECREF(temp); Py_DECREF(temp);
/* Take no action in case if type->tp_bases has been replaced /* Take no action in case if type->tp_bases has been replaced
through reentrance. */ through reentrance. */
int res;
if (type->tp_bases == new_bases) { if (type->tp_bases == new_bases) {
/* any base that was in __bases__ but now isn't, we /* any base that was in __bases__ but now isn't, we
need to remove |type| from its tp_subclasses. need to remove |type| from its tp_subclasses.
@ -834,6 +840,9 @@ type_set_bases(PyTypeObject *type, PyObject *new_bases, void *context)
res = add_all_subclasses(type, new_bases); res = add_all_subclasses(type, new_bases);
update_all_slots(type); update_all_slots(type);
} }
else {
res = 0;
}
Py_DECREF(old_bases); Py_DECREF(old_bases);
Py_DECREF(old_base); Py_DECREF(old_base);
@ -842,7 +851,8 @@ type_set_bases(PyTypeObject *type, PyObject *new_bases, void *context)
return res; return res;
undo: undo:
for (i = PyList_GET_SIZE(temp) - 1; i >= 0; i--) { n = PyList_GET_SIZE(temp);
for (Py_ssize_t i = n - 1; i >= 0; i--) {
PyTypeObject *cls; PyTypeObject *cls;
PyObject *new_mro, *old_mro = NULL; PyObject *new_mro, *old_mro = NULL;
@ -1413,8 +1423,9 @@ subtype_dealloc(PyObject *self)
and if self is tracked at that point, it will look like trash to GC and GC and if self is tracked at that point, it will look like trash to GC and GC
will try to delete self again. will try to delete self again.
*/ */
if (type->tp_weaklistoffset && !base->tp_weaklistoffset) if (type->tp_weaklistoffset && !base->tp_weaklistoffset) {
PyObject_ClearWeakRefs(self); PyObject_ClearWeakRefs(self);
}
if (type->tp_del) { if (type->tp_del) {
_PyObject_GC_TRACK(self); _PyObject_GC_TRACK(self);
@ -1929,20 +1940,14 @@ pmerge(PyObject *acc, PyObject **to_merge, Py_ssize_t to_merge_size)
static PyObject * static PyObject *
mro_implementation(PyTypeObject *type) mro_implementation(PyTypeObject *type)
{ {
PyObject *result;
PyObject *bases;
PyObject **to_merge;
Py_ssize_t i, n;
if (!_PyType_IsReady(type)) { if (!_PyType_IsReady(type)) {
if (PyType_Ready(type) < 0) if (PyType_Ready(type) < 0)
return NULL; return NULL;
} }
bases = type->tp_bases; PyObject *bases = type->tp_bases;
assert(PyTuple_Check(bases)); Py_ssize_t n = PyTuple_GET_SIZE(bases);
n = PyTuple_GET_SIZE(bases); for (Py_ssize_t i = 0; i < n; i++) {
for (i = 0; i < n; i++) {
PyTypeObject *base = _PyType_CAST(PyTuple_GET_ITEM(bases, i)); PyTypeObject *base = _PyType_CAST(PyTuple_GET_ITEM(bases, i));
if (base->tp_mro == NULL) { if (base->tp_mro == NULL) {
PyErr_Format(PyExc_TypeError, PyErr_Format(PyExc_TypeError,
@ -1959,13 +1964,14 @@ mro_implementation(PyTypeObject *type)
*/ */
PyTypeObject *base = _PyType_CAST(PyTuple_GET_ITEM(bases, 0)); PyTypeObject *base = _PyType_CAST(PyTuple_GET_ITEM(bases, 0));
Py_ssize_t k = PyTuple_GET_SIZE(base->tp_mro); Py_ssize_t k = PyTuple_GET_SIZE(base->tp_mro);
result = PyTuple_New(k + 1); PyObject *result = PyTuple_New(k + 1);
if (result == NULL) { if (result == NULL) {
return NULL; return NULL;
} }
Py_INCREF(type); Py_INCREF(type);
PyTuple_SET_ITEM(result, 0, (PyObject *) type); PyTuple_SET_ITEM(result, 0, (PyObject *) type);
for (i = 0; i < k; i++) { for (Py_ssize_t i = 0; i < k; i++) {
PyObject *cls = PyTuple_GET_ITEM(base->tp_mro, i); PyObject *cls = PyTuple_GET_ITEM(base->tp_mro, i);
Py_INCREF(cls); Py_INCREF(cls);
PyTuple_SET_ITEM(result, i + 1, cls); PyTuple_SET_ITEM(result, i + 1, cls);
@ -1986,20 +1992,19 @@ mro_implementation(PyTypeObject *type)
linearization implied by a base class. The last element of linearization implied by a base class. The last element of
to_merge is the declared tuple of bases. to_merge is the declared tuple of bases.
*/ */
PyObject **to_merge = PyMem_New(PyObject *, n + 1);
to_merge = PyMem_New(PyObject *, n + 1);
if (to_merge == NULL) { if (to_merge == NULL) {
PyErr_NoMemory(); PyErr_NoMemory();
return NULL; return NULL;
} }
for (i = 0; i < n; i++) { for (Py_ssize_t i = 0; i < n; i++) {
PyTypeObject *base = _PyType_CAST(PyTuple_GET_ITEM(bases, i)); PyTypeObject *base = _PyType_CAST(PyTuple_GET_ITEM(bases, i));
to_merge[i] = base->tp_mro; to_merge[i] = base->tp_mro;
} }
to_merge[n] = bases; to_merge[n] = bases;
result = PyList_New(1); PyObject *result = PyList_New(1);
if (result == NULL) { if (result == NULL) {
PyMem_Free(to_merge); PyMem_Free(to_merge);
return NULL; return NULL;
@ -2010,8 +2015,8 @@ mro_implementation(PyTypeObject *type)
if (pmerge(result, to_merge, n + 1) < 0) { if (pmerge(result, to_merge, n + 1) < 0) {
Py_CLEAR(result); Py_CLEAR(result);
} }
PyMem_Free(to_merge); PyMem_Free(to_merge);
return result; return result;
} }
@ -2651,20 +2656,20 @@ type_new_slots_bases(type_new_ctx *ctx)
(ctx->may_add_weak && ctx->add_weak == 0))) (ctx->may_add_weak && ctx->add_weak == 0)))
{ {
for (Py_ssize_t i = 0; i < nbases; i++) { for (Py_ssize_t i = 0; i < nbases; i++) {
PyObject *base = PyTuple_GET_ITEM(ctx->bases, i); PyObject *obj = PyTuple_GET_ITEM(ctx->bases, i);
if (base == (PyObject *)ctx->base) { if (obj == (PyObject *)ctx->base) {
/* Skip primary base */ /* Skip primary base */
continue; continue;
} }
PyTypeObject *type = _PyType_CAST(base); PyTypeObject *base = _PyType_CAST(obj);
if (ctx->may_add_dict && ctx->add_dict == 0 && if (ctx->may_add_dict && ctx->add_dict == 0 &&
type->tp_dictoffset != 0) base->tp_dictoffset != 0)
{ {
ctx->add_dict++; ctx->add_dict++;
} }
if (ctx->may_add_weak && ctx->add_weak == 0 && if (ctx->may_add_weak && ctx->add_weak == 0 &&
type->tp_weaklistoffset != 0) base->tp_weaklistoffset != 0)
{ {
ctx->add_weak++; ctx->add_weak++;
} }
@ -3739,8 +3744,8 @@ _PyType_GetModuleByDef(PyTypeObject *type, struct PyModuleDef *def)
// to check i < PyTuple_GET_SIZE(mro) at the first loop iteration. // to check i < PyTuple_GET_SIZE(mro) at the first loop iteration.
assert(PyTuple_GET_SIZE(mro) >= 1); assert(PyTuple_GET_SIZE(mro) >= 1);
Py_ssize_t i = 0; Py_ssize_t n = PyTuple_GET_SIZE(mro);
do { for (Py_ssize_t i = 0; i < n; i++) {
PyObject *super = PyTuple_GET_ITEM(mro, i); PyObject *super = PyTuple_GET_ITEM(mro, i);
// _PyType_GetModuleByDef() must only be called on a heap type created // _PyType_GetModuleByDef() must only be called on a heap type created
// by PyType_FromModuleAndSpec() or on its subclasses. // by PyType_FromModuleAndSpec() or on its subclasses.
@ -3753,8 +3758,7 @@ _PyType_GetModuleByDef(PyTypeObject *type, struct PyModuleDef *def)
if (module && _PyModule_GetDef(module) == def) { if (module && _PyModule_GetDef(module) == def) {
return module; return module;
} }
i++; }
} while (i < PyTuple_GET_SIZE(mro));
PyErr_Format( PyErr_Format(
PyExc_TypeError, PyExc_TypeError,
@ -3770,10 +3774,7 @@ _PyType_GetModuleByDef(PyTypeObject *type, struct PyModuleDef *def)
static PyObject * static PyObject *
find_name_in_mro(PyTypeObject *type, PyObject *name, int *error) find_name_in_mro(PyTypeObject *type, PyObject *name, int *error)
{ {
Py_ssize_t i, n;
PyObject *mro, *res, *base, *dict;
Py_hash_t hash; Py_hash_t hash;
if (!PyUnicode_CheckExact(name) || if (!PyUnicode_CheckExact(name) ||
(hash = ((PyASCIIObject *) name)->hash) == -1) (hash = ((PyASCIIObject *) name)->hash) == -1)
{ {
@ -3785,8 +3786,7 @@ find_name_in_mro(PyTypeObject *type, PyObject *name, int *error)
} }
/* Look in tp_dict of types in MRO */ /* Look in tp_dict of types in MRO */
mro = type->tp_mro; PyObject *mro = type->tp_mro;
if (mro == NULL) { if (mro == NULL) {
if ((type->tp_flags & Py_TPFLAGS_READYING) == 0) { if ((type->tp_flags & Py_TPFLAGS_READYING) == 0) {
if (PyType_Ready(type) < 0) { if (PyType_Ready(type) < 0) {
@ -3801,20 +3801,19 @@ find_name_in_mro(PyTypeObject *type, PyObject *name, int *error)
} }
} }
res = NULL; PyObject *res = NULL;
/* Keep a strong reference to mro because type->tp_mro can be replaced /* Keep a strong reference to mro because type->tp_mro can be replaced
during dict lookup, e.g. when comparing to non-string keys. */ during dict lookup, e.g. when comparing to non-string keys. */
Py_INCREF(mro); Py_INCREF(mro);
assert(PyTuple_Check(mro)); Py_ssize_t n = PyTuple_GET_SIZE(mro);
n = PyTuple_GET_SIZE(mro); for (Py_ssize_t i = 0; i < n; i++) {
for (i = 0; i < n; i++) { PyObject *base = PyTuple_GET_ITEM(mro, i);
base = PyTuple_GET_ITEM(mro, i); PyObject *dict = _PyType_CAST(base)->tp_dict;
assert(PyType_Check(base));
dict = _PyType_CAST(base)->tp_dict;
assert(dict && PyDict_Check(dict)); assert(dict && PyDict_Check(dict));
res = _PyDict_GetItem_KnownHash(dict, name, hash); res = _PyDict_GetItem_KnownHash(dict, name, hash);
if (res != NULL) if (res != NULL) {
break; break;
}
if (PyErr_Occurred()) { if (PyErr_Occurred()) {
*error = -1; *error = -1;
goto done; goto done;
@ -4066,10 +4065,12 @@ _PyDictKeys_DecRef(PyDictKeysObject *keys);
static void static void
type_dealloc_common(PyTypeObject *type) type_dealloc_common(PyTypeObject *type)
{ {
PyObject *tp, *val, *tb; if (type->tp_bases != NULL) {
PyErr_Fetch(&tp, &val, &tb); PyObject *tp, *val, *tb;
remove_all_subclasses(type, type->tp_bases); PyErr_Fetch(&tp, &val, &tb);
PyErr_Restore(tp, val, tb); remove_all_subclasses(type, type->tp_bases);
PyErr_Restore(tp, val, tb);
}
PyObject_ClearWeakRefs((PyObject *)type); PyObject_ClearWeakRefs((PyObject *)type);
} }
@ -4089,7 +4090,7 @@ _PyStaticType_Dealloc(PyTypeObject *type)
Py_CLEAR(type->tp_bases); Py_CLEAR(type->tp_bases);
Py_CLEAR(type->tp_mro); Py_CLEAR(type->tp_mro);
Py_CLEAR(type->tp_cache); Py_CLEAR(type->tp_cache);
Py_CLEAR(type->tp_subclasses); // type->tp_subclasses is NULL
type->tp_flags &= ~Py_TPFLAGS_READY; type->tp_flags &= ~Py_TPFLAGS_READY;
} }
@ -4154,6 +4155,7 @@ _PyType_GetSubclasses(PyTypeObject *self)
continue; continue;
} }
assert(PyType_Check(obj)); assert(PyType_Check(obj));
if (PyList_Append(list, obj) < 0) { if (PyList_Append(list, obj) < 0) {
Py_DECREF(list); Py_DECREF(list);
return NULL; return NULL;
@ -6227,7 +6229,7 @@ type_ready_mro(PyTypeObject *type)
Py_ssize_t n = PyTuple_GET_SIZE(mro); Py_ssize_t n = PyTuple_GET_SIZE(mro);
for (Py_ssize_t i = 0; i < n; i++) { for (Py_ssize_t i = 0; i < n; i++) {
PyTypeObject *base = _PyType_CAST(PyTuple_GET_ITEM(mro, i)); PyTypeObject *base = _PyType_CAST(PyTuple_GET_ITEM(mro, i));
if (PyType_Check(base) && (base->tp_flags & Py_TPFLAGS_HEAPTYPE)) { if (base->tp_flags & Py_TPFLAGS_HEAPTYPE) {
PyErr_Format(PyExc_TypeError, PyErr_Format(PyExc_TypeError,
"type '%.100s' is not dynamically allocated but " "type '%.100s' is not dynamically allocated but "
"its base type '%.100s' is dynamically allocated", "its base type '%.100s' is dynamically allocated",
@ -6515,15 +6517,15 @@ add_subclass(PyTypeObject *base, PyTypeObject *type)
// Only get tp_subclasses after creating the key and value. // Only get tp_subclasses after creating the key and value.
// PyWeakref_NewRef() can trigger a garbage collection which can execute // PyWeakref_NewRef() can trigger a garbage collection which can execute
// arbitrary Python code and so modify base->tp_subclasses. // arbitrary Python code and so modify base->tp_subclasses.
PyObject *dict = base->tp_subclasses; PyObject *subclasses = base->tp_subclasses;
if (dict == NULL) { if (subclasses == NULL) {
base->tp_subclasses = dict = PyDict_New(); base->tp_subclasses = subclasses = PyDict_New();
if (dict == NULL) if (subclasses == NULL)
return -1; return -1;
} }
assert(PyDict_CheckExact(dict)); assert(PyDict_CheckExact(subclasses));
int result = PyDict_SetItem(dict, key, ref); int result = PyDict_SetItem(subclasses, key, ref);
Py_DECREF(ref); Py_DECREF(ref);
Py_DECREF(key); Py_DECREF(key);
return result; return result;
@ -6532,35 +6534,30 @@ add_subclass(PyTypeObject *base, PyTypeObject *type)
static int static int
add_all_subclasses(PyTypeObject *type, PyObject *bases) add_all_subclasses(PyTypeObject *type, PyObject *bases)
{ {
Py_ssize_t n = PyTuple_GET_SIZE(bases);
int res = 0; int res = 0;
for (Py_ssize_t i = 0; i < n; i++) {
if (bases) { PyObject *obj = PyTuple_GET_ITEM(bases, i);
Py_ssize_t i; // bases tuple must only contain types
for (i = 0; i < PyTuple_GET_SIZE(bases); i++) { PyTypeObject *base = _PyType_CAST(obj);
PyObject *base = PyTuple_GET_ITEM(bases, i); if (add_subclass(base, type) < 0) {
if (PyType_Check(base) && res = -1;
add_subclass((PyTypeObject*)base, type) < 0)
{
res = -1;
}
} }
} }
return res; return res;
} }
static void static void
remove_subclass(PyTypeObject *base, PyTypeObject *type) remove_subclass(PyTypeObject *base, PyTypeObject *type)
{ {
PyObject *dict, *key; PyObject *subclasses = base->tp_subclasses; // borrowed ref
if (subclasses == NULL) {
dict = base->tp_subclasses;
if (dict == NULL) {
return; return;
} }
assert(PyDict_CheckExact(dict)); assert(PyDict_CheckExact(subclasses));
key = PyLong_FromVoidPtr((void *) type);
if (key == NULL || PyDict_DelItem(dict, key)) { PyObject *key = PyLong_FromVoidPtr((void *) type);
if (key == NULL || PyDict_DelItem(subclasses, key)) {
/* This can happen if the type initialization errored out before /* This can happen if the type initialization errored out before
the base subclasses were updated (e.g. a non-str __qualname__ the base subclasses were updated (e.g. a non-str __qualname__
was passed in the type dict). */ was passed in the type dict). */
@ -6568,7 +6565,7 @@ remove_subclass(PyTypeObject *base, PyTypeObject *type)
} }
Py_XDECREF(key); Py_XDECREF(key);
if (PyDict_Size(dict) == 0) { if (PyDict_Size(subclasses) == 0) {
// Delete the dictionary to save memory. _PyStaticType_Dealloc() // Delete the dictionary to save memory. _PyStaticType_Dealloc()
// callers also test if tp_subclasses is NULL to check if a static type // callers also test if tp_subclasses is NULL to check if a static type
// has no subclass. // has no subclass.
@ -6579,15 +6576,17 @@ remove_subclass(PyTypeObject *base, PyTypeObject *type)
static void static void
remove_all_subclasses(PyTypeObject *type, PyObject *bases) remove_all_subclasses(PyTypeObject *type, PyObject *bases)
{ {
if (bases) { assert(bases != NULL);
Py_ssize_t i; // remove_subclass() can clear the current exception
for (i = 0; i < PyTuple_GET_SIZE(bases); i++) { assert(!PyErr_Occurred());
PyObject *base = PyTuple_GET_ITEM(bases, i);
if (PyType_Check(base)) { for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(bases); i++) {
remove_subclass((PyTypeObject*) base, type); PyObject *base = PyTuple_GET_ITEM(bases, i);
} if (PyType_Check(base)) {
remove_subclass((PyTypeObject*) base, type);
} }
} }
assert(!PyErr_Occurred());
} }
static int static int
@ -8466,9 +8465,9 @@ static int
update_slots_callback(PyTypeObject *type, void *data) update_slots_callback(PyTypeObject *type, void *data)
{ {
slotdef **pp = (slotdef **)data; slotdef **pp = (slotdef **)data;
for (; *pp; pp++) {
for (; *pp; pp++)
update_one_slot(type, *pp); update_one_slot(type, *pp);
}
return 0; return 0;
} }
@ -8654,29 +8653,33 @@ type_new_init_subclass(PyTypeObject *type, PyObject *kwds)
/* recurse_down_subclasses() and update_subclasses() are mutually /* recurse_down_subclasses() and update_subclasses() are mutually
recursive functions to call a callback for all subclasses, recursive functions to call a callback for all subclasses,
but refraining from recursing into subclasses that define 'name'. */ but refraining from recursing into subclasses that define 'attr_name'. */
static int static int
update_subclasses(PyTypeObject *type, PyObject *name, update_subclasses(PyTypeObject *type, PyObject *attr_name,
update_callback callback, void *data) update_callback callback, void *data)
{ {
if (callback(type, data) < 0) if (callback(type, data) < 0) {
return -1; return -1;
return recurse_down_subclasses(type, name, callback, data); }
return recurse_down_subclasses(type, attr_name, callback, data);
} }
static int static int
recurse_down_subclasses(PyTypeObject *type, PyObject *name, recurse_down_subclasses(PyTypeObject *type, PyObject *attr_name,
update_callback callback, void *data) update_callback callback, void *data)
{ {
PyObject *ref, *subclasses, *dict; // It is safe to use a borrowed reference because update_subclasses() is
Py_ssize_t i; // only used with update_slots_callback() which doesn't modify
// tp_subclasses.
subclasses = type->tp_subclasses; PyObject *subclasses = type->tp_subclasses; // borrowed ref
if (subclasses == NULL) if (subclasses == NULL) {
return 0; return 0;
}
assert(PyDict_CheckExact(subclasses)); assert(PyDict_CheckExact(subclasses));
i = 0;
Py_ssize_t i = 0;
PyObject *ref;
while (PyDict_Next(subclasses, &i, NULL, &ref)) { while (PyDict_Next(subclasses, &i, NULL, &ref)) {
assert(PyWeakref_CheckRef(ref)); assert(PyWeakref_CheckRef(ref));
PyObject *obj = PyWeakref_GET_OBJECT(ref); PyObject *obj = PyWeakref_GET_OBJECT(ref);
@ -8687,18 +8690,20 @@ recurse_down_subclasses(PyTypeObject *type, PyObject *name,
PyTypeObject *subclass = _PyType_CAST(obj); PyTypeObject *subclass = _PyType_CAST(obj);
/* Avoid recursing down into unaffected classes */ /* Avoid recursing down into unaffected classes */
dict = subclass->tp_dict; PyObject *dict = subclass->tp_dict;
if (dict != NULL && PyDict_Check(dict)) { if (dict != NULL && PyDict_Check(dict)) {
int r = PyDict_Contains(dict, name); int r = PyDict_Contains(dict, attr_name);
if (r > 0) {
continue;
}
if (r < 0) { if (r < 0) {
return -1; return -1;
} }
if (r > 0) {
continue;
}
} }
if (update_subclasses(subclass, name, callback, data) < 0)
if (update_subclasses(subclass, attr_name, callback, data) < 0) {
return -1; return -1;
}
} }
return 0; return 0;
} }