[Python-checkins] bpo-46417: Cleanup typeobject.c code (GH-30795)

vstinner webhook-mailer at python.org
Sat Jan 22 12:56:15 EST 2022


https://github.com/python/cpython/commit/3a4c15bb9815b6f4652621fe6043ae18e0d202b3
commit: 3a4c15bb9815b6f4652621fe6043ae18e0d202b3
branch: main
author: Victor Stinner <vstinner at python.org>
committer: vstinner <vstinner at python.org>
date: 2022-01-22T18:56:11+01:00
summary:

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.

files:
M Objects/typeobject.c

diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index b3c305e0bf430..bf62b5389257f 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -328,24 +328,26 @@ PyType_Modified(PyTypeObject *type)
        We don't assign new version tags eagerly, but only as
        needed.
      */
-    PyObject *raw, *ref;
-    Py_ssize_t i;
-
-    if (!_PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG))
+    if (!_PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG)) {
         return;
+    }
+
+    PyObject *subclasses = type->tp_subclasses;
+    if (subclasses != NULL) {
+        assert(PyDict_CheckExact(subclasses));
 
-    raw = type->tp_subclasses;
-    if (raw != NULL) {
-        assert(PyDict_CheckExact(raw));
-        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));
-            ref = PyWeakref_GET_OBJECT(ref);
-            if (ref != Py_None) {
-                PyType_Modified(_PyType_CAST(ref));
+            PyObject *obj = PyWeakref_GET_OBJECT(ref);
+            if (obj == Py_None) {
+                continue;
             }
+            PyType_Modified(_PyType_CAST(obj));
         }
     }
+
     type->tp_flags &= ~Py_TPFLAGS_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
        cannot be done, 1 if Py_TPFLAGS_VALID_VERSION_TAG.
     */
-    Py_ssize_t i, n;
-    PyObject *bases;
-
-    if (_PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG))
+    if (_PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG)) {
         return 1;
-    if (!_PyType_HasFeature(type, Py_TPFLAGS_READY))
+    }
+    if (!_PyType_HasFeature(type, Py_TPFLAGS_READY)) {
         return 0;
+    }
 
     if (next_version_tag == 0) {
         /* 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++;
     assert (type->tp_version_tag != 0);
 
-    bases = type->tp_bases;
-    n = PyTuple_GET_SIZE(bases);
-    for (i = 0; i < n; i++) {
+    PyObject *bases = type->tp_bases;
+    Py_ssize_t n = PyTuple_GET_SIZE(bases);
+    for (Py_ssize_t i = 0; i < n; i++) {
         PyObject *b = PyTuple_GET_ITEM(bases, i);
         if (!assign_version_tag(cache, _PyType_CAST(b)))
             return 0;
@@ -679,7 +680,7 @@ static void remove_all_subclasses(PyTypeObject *type, PyObject *bases);
 static void update_all_slots(PyTypeObject *);
 
 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);
 static int recurse_down_subclasses(PyTypeObject *type, PyObject *name,
                                    update_callback callback, void *data);
@@ -718,30 +719,33 @@ mro_hierarchy(PyTypeObject *type, PyObject *temp)
     }
     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
-       in the middle of the loop, for example, through a custom mro(),
-       by invoking type_set_bases on some subclass of the type
-       which in turn calls remove_subclass/add_subclass on this type.
+           Otherwise type->tp_subclasses might be altered
+           in the middle of the loop, for example, through a custom mro(),
+           by invoking type_set_bases on some subclass of the type
+           which in turn calls remove_subclass/add_subclass on this type.
 
-       Finally, this makes things simple avoiding the need to deal
-       with dictionary iterators and weak references.
-    */
-    PyObject *subclasses = _PyType_GetSubclasses(type);
-    if (subclasses == NULL) {
-        return -1;
-    }
+           Finally, this makes things simple avoiding the need to deal
+           with dictionary iterators and weak references.
+        */
+        PyObject *subclasses = _PyType_GetSubclasses(type);
+        if (subclasses == NULL) {
+            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;
 }
@@ -749,14 +753,12 @@ mro_hierarchy(PyTypeObject *type, PyObject *temp)
 static int
 type_set_bases(PyTypeObject *type, PyObject *new_bases, void *context)
 {
-    int res = 0;
-    PyObject *temp;
-    PyObject *old_bases;
-    PyTypeObject *new_base, *old_base;
-    Py_ssize_t i;
-
-    if (!check_set_special_type_attr(type, new_bases, "__bases__"))
+    // Check arguments
+    if (!check_set_special_type_attr(type, new_bases, "__bases__")) {
         return -1;
+    }
+    assert(new_bases != NULL);
+
     if (!PyTuple_Check(new_bases)) {
         PyErr_Format(PyExc_TypeError,
              "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);
         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);
         if (!PyType_Check(ob)) {
             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
                through tp_base chain.  And this is definitely
                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,
                             "a __bases__ item causes an inheritance cycle");
             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)
         return -1;
 
-    if (!compatible_for_assignment(type->tp_base, new_base, "__bases__"))
+    if (!compatible_for_assignment(type->tp_base, new_base, "__bases__")) {
         return -1;
+    }
 
-    Py_INCREF(new_bases);
-    Py_INCREF(new_base);
-
-    old_bases = type->tp_bases;
-    old_base = type->tp_base;
+    PyObject *old_bases = type->tp_bases;
+    assert(old_bases != NULL);
+    PyTypeObject *old_base = type->tp_base;
 
-    type->tp_bases = new_bases;
-    type->tp_base = new_base;
+    type->tp_bases = Py_NewRef(new_bases);
+    type->tp_base = (PyTypeObject *)Py_NewRef(new_base);
 
-    temp = PyList_New(0);
-    if (temp == NULL)
+    PyObject *temp = PyList_New(0);
+    if (temp == NULL) {
         goto bail;
-    if (mro_hierarchy(type, temp) < 0)
+    }
+    if (mro_hierarchy(type, temp) < 0) {
         goto undo;
+    }
     Py_DECREF(temp);
 
     /* Take no action in case if type->tp_bases has been replaced
        through reentrance.  */
+    int res;
     if (type->tp_bases == new_bases) {
         /* any base that was in __bases__ but now isn't, we
            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);
         update_all_slots(type);
     }
+    else {
+        res = 0;
+    }
 
     Py_DECREF(old_bases);
     Py_DECREF(old_base);
@@ -842,7 +851,8 @@ type_set_bases(PyTypeObject *type, PyObject *new_bases, void *context)
     return res;
 
   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;
         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
       will try to delete self again.
     */
-    if (type->tp_weaklistoffset && !base->tp_weaklistoffset)
+    if (type->tp_weaklistoffset && !base->tp_weaklistoffset) {
         PyObject_ClearWeakRefs(self);
+    }
 
     if (type->tp_del) {
         _PyObject_GC_TRACK(self);
@@ -1929,20 +1940,14 @@ pmerge(PyObject *acc, PyObject **to_merge, Py_ssize_t to_merge_size)
 static PyObject *
 mro_implementation(PyTypeObject *type)
 {
-    PyObject *result;
-    PyObject *bases;
-    PyObject **to_merge;
-    Py_ssize_t i, n;
-
     if (!_PyType_IsReady(type)) {
         if (PyType_Ready(type) < 0)
             return NULL;
     }
 
-    bases = type->tp_bases;
-    assert(PyTuple_Check(bases));
-    n = PyTuple_GET_SIZE(bases);
-    for (i = 0; i < n; i++) {
+    PyObject *bases = type->tp_bases;
+    Py_ssize_t n = PyTuple_GET_SIZE(bases);
+    for (Py_ssize_t i = 0; i < n; i++) {
         PyTypeObject *base = _PyType_CAST(PyTuple_GET_ITEM(bases, i));
         if (base->tp_mro == NULL) {
             PyErr_Format(PyExc_TypeError,
@@ -1959,13 +1964,14 @@ mro_implementation(PyTypeObject *type)
          */
         PyTypeObject *base = _PyType_CAST(PyTuple_GET_ITEM(bases, 0));
         Py_ssize_t k = PyTuple_GET_SIZE(base->tp_mro);
-        result = PyTuple_New(k + 1);
+        PyObject *result = PyTuple_New(k + 1);
         if (result == NULL) {
             return NULL;
         }
+
         Py_INCREF(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);
             Py_INCREF(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
        to_merge is the declared tuple of bases.
     */
-
-    to_merge = PyMem_New(PyObject *, n + 1);
+    PyObject **to_merge = PyMem_New(PyObject *, n + 1);
     if (to_merge == NULL) {
         PyErr_NoMemory();
         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));
         to_merge[i] = base->tp_mro;
     }
     to_merge[n] = bases;
 
-    result = PyList_New(1);
+    PyObject *result = PyList_New(1);
     if (result == NULL) {
         PyMem_Free(to_merge);
         return NULL;
@@ -2010,8 +2015,8 @@ mro_implementation(PyTypeObject *type)
     if (pmerge(result, to_merge, n + 1) < 0) {
         Py_CLEAR(result);
     }
-
     PyMem_Free(to_merge);
+
     return result;
 }
 
@@ -2651,20 +2656,20 @@ type_new_slots_bases(type_new_ctx *ctx)
          (ctx->may_add_weak && ctx->add_weak == 0)))
     {
         for (Py_ssize_t i = 0; i < nbases; i++) {
-            PyObject *base = PyTuple_GET_ITEM(ctx->bases, i);
-            if (base == (PyObject *)ctx->base) {
+            PyObject *obj = PyTuple_GET_ITEM(ctx->bases, i);
+            if (obj == (PyObject *)ctx->base) {
                 /* Skip primary base */
                 continue;
             }
-            PyTypeObject *type = _PyType_CAST(base);
+            PyTypeObject *base = _PyType_CAST(obj);
 
             if (ctx->may_add_dict && ctx->add_dict == 0 &&
-                type->tp_dictoffset != 0)
+                base->tp_dictoffset != 0)
             {
                 ctx->add_dict++;
             }
             if (ctx->may_add_weak && ctx->add_weak == 0 &&
-                type->tp_weaklistoffset != 0)
+                base->tp_weaklistoffset != 0)
             {
                 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.
     assert(PyTuple_GET_SIZE(mro) >= 1);
 
-    Py_ssize_t i = 0;
-    do {
+    Py_ssize_t n = PyTuple_GET_SIZE(mro);
+    for (Py_ssize_t i = 0; i < n; i++) {
         PyObject *super = PyTuple_GET_ITEM(mro, i);
         // _PyType_GetModuleByDef() must only be called on a heap type created
         // by PyType_FromModuleAndSpec() or on its subclasses.
@@ -3753,8 +3758,7 @@ _PyType_GetModuleByDef(PyTypeObject *type, struct PyModuleDef *def)
         if (module && _PyModule_GetDef(module) == def) {
             return module;
         }
-        i++;
-    } while (i < PyTuple_GET_SIZE(mro));
+    }
 
     PyErr_Format(
         PyExc_TypeError,
@@ -3770,10 +3774,7 @@ _PyType_GetModuleByDef(PyTypeObject *type, struct PyModuleDef *def)
 static PyObject *
 find_name_in_mro(PyTypeObject *type, PyObject *name, int *error)
 {
-    Py_ssize_t i, n;
-    PyObject *mro, *res, *base, *dict;
     Py_hash_t hash;
-
     if (!PyUnicode_CheckExact(name) ||
         (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 */
-    mro = type->tp_mro;
-
+    PyObject *mro = type->tp_mro;
     if (mro == NULL) {
         if ((type->tp_flags & Py_TPFLAGS_READYING) == 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
        during dict lookup, e.g. when comparing to non-string keys. */
     Py_INCREF(mro);
-    assert(PyTuple_Check(mro));
-    n = PyTuple_GET_SIZE(mro);
-    for (i = 0; i < n; i++) {
-        base = PyTuple_GET_ITEM(mro, i);
-        assert(PyType_Check(base));
-        dict = _PyType_CAST(base)->tp_dict;
+    Py_ssize_t n = PyTuple_GET_SIZE(mro);
+    for (Py_ssize_t i = 0; i < n; i++) {
+        PyObject *base = PyTuple_GET_ITEM(mro, i);
+        PyObject *dict = _PyType_CAST(base)->tp_dict;
         assert(dict && PyDict_Check(dict));
         res = _PyDict_GetItem_KnownHash(dict, name, hash);
-        if (res != NULL)
+        if (res != NULL) {
             break;
+        }
         if (PyErr_Occurred()) {
             *error = -1;
             goto done;
@@ -4066,10 +4065,12 @@ _PyDictKeys_DecRef(PyDictKeysObject *keys);
 static void
 type_dealloc_common(PyTypeObject *type)
 {
-    PyObject *tp, *val, *tb;
-    PyErr_Fetch(&tp, &val, &tb);
-    remove_all_subclasses(type, type->tp_bases);
-    PyErr_Restore(tp, val, tb);
+    if (type->tp_bases != NULL) {
+        PyObject *tp, *val, *tb;
+        PyErr_Fetch(&tp, &val, &tb);
+        remove_all_subclasses(type, type->tp_bases);
+        PyErr_Restore(tp, val, tb);
+    }
 
     PyObject_ClearWeakRefs((PyObject *)type);
 }
@@ -4089,7 +4090,7 @@ _PyStaticType_Dealloc(PyTypeObject *type)
     Py_CLEAR(type->tp_bases);
     Py_CLEAR(type->tp_mro);
     Py_CLEAR(type->tp_cache);
-    Py_CLEAR(type->tp_subclasses);
+    // type->tp_subclasses is NULL
 
     type->tp_flags &= ~Py_TPFLAGS_READY;
 }
@@ -4154,6 +4155,7 @@ _PyType_GetSubclasses(PyTypeObject *self)
             continue;
         }
         assert(PyType_Check(obj));
+
         if (PyList_Append(list, obj) < 0) {
             Py_DECREF(list);
             return NULL;
@@ -6227,7 +6229,7 @@ type_ready_mro(PyTypeObject *type)
         Py_ssize_t n = PyTuple_GET_SIZE(mro);
         for (Py_ssize_t i = 0; i < n; 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,
                              "type '%.100s' is not dynamically allocated but "
                              "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.
     // PyWeakref_NewRef() can trigger a garbage collection which can execute
     // arbitrary Python code and so modify base->tp_subclasses.
-    PyObject *dict = base->tp_subclasses;
-    if (dict == NULL) {
-        base->tp_subclasses = dict = PyDict_New();
-        if (dict == NULL)
+    PyObject *subclasses = base->tp_subclasses;
+    if (subclasses == NULL) {
+        base->tp_subclasses = subclasses = PyDict_New();
+        if (subclasses == NULL)
             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(key);
     return result;
@@ -6532,35 +6534,30 @@ add_subclass(PyTypeObject *base, PyTypeObject *type)
 static int
 add_all_subclasses(PyTypeObject *type, PyObject *bases)
 {
+    Py_ssize_t n = PyTuple_GET_SIZE(bases);
     int res = 0;
-
-    if (bases) {
-        Py_ssize_t i;
-        for (i = 0; i < PyTuple_GET_SIZE(bases); i++) {
-            PyObject *base = PyTuple_GET_ITEM(bases, i);
-            if (PyType_Check(base) &&
-                add_subclass((PyTypeObject*)base, type) < 0)
-            {
-                res = -1;
-            }
+    for (Py_ssize_t i = 0; i < n; i++) {
+        PyObject *obj = PyTuple_GET_ITEM(bases, i);
+        // bases tuple must only contain types
+        PyTypeObject *base = _PyType_CAST(obj);
+        if (add_subclass(base, type) < 0) {
+            res = -1;
         }
     }
-
     return res;
 }
 
 static void
 remove_subclass(PyTypeObject *base, PyTypeObject *type)
 {
-    PyObject *dict, *key;
-
-    dict = base->tp_subclasses;
-    if (dict == NULL) {
+    PyObject *subclasses = base->tp_subclasses;  // borrowed ref
+    if (subclasses == NULL) {
         return;
     }
-    assert(PyDict_CheckExact(dict));
-    key = PyLong_FromVoidPtr((void *) type);
-    if (key == NULL || PyDict_DelItem(dict, key)) {
+    assert(PyDict_CheckExact(subclasses));
+
+    PyObject *key = PyLong_FromVoidPtr((void *) type);
+    if (key == NULL || PyDict_DelItem(subclasses, key)) {
         /* This can happen if the type initialization errored out before
            the base subclasses were updated (e.g. a non-str __qualname__
            was passed in the type dict). */
@@ -6568,7 +6565,7 @@ remove_subclass(PyTypeObject *base, PyTypeObject *type)
     }
     Py_XDECREF(key);
 
-    if (PyDict_Size(dict) == 0) {
+    if (PyDict_Size(subclasses) == 0) {
         // Delete the dictionary to save memory. _PyStaticType_Dealloc()
         // callers also test if tp_subclasses is NULL to check if a static type
         // has no subclass.
@@ -6579,15 +6576,17 @@ remove_subclass(PyTypeObject *base, PyTypeObject *type)
 static void
 remove_all_subclasses(PyTypeObject *type, PyObject *bases)
 {
-    if (bases) {
-        Py_ssize_t i;
-        for (i = 0; i < PyTuple_GET_SIZE(bases); i++) {
-            PyObject *base = PyTuple_GET_ITEM(bases, i);
-            if (PyType_Check(base)) {
-                remove_subclass((PyTypeObject*) base, type);
-            }
+    assert(bases != NULL);
+    // remove_subclass() can clear the current exception
+    assert(!PyErr_Occurred());
+
+    for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(bases); i++) {
+        PyObject *base = PyTuple_GET_ITEM(bases, i);
+        if (PyType_Check(base)) {
+            remove_subclass((PyTypeObject*) base, type);
         }
     }
+    assert(!PyErr_Occurred());
 }
 
 static int
@@ -8466,9 +8465,9 @@ static int
 update_slots_callback(PyTypeObject *type, void *data)
 {
     slotdef **pp = (slotdef **)data;
-
-    for (; *pp; pp++)
+    for (; *pp; pp++) {
         update_one_slot(type, *pp);
+    }
     return 0;
 }
 
@@ -8654,29 +8653,33 @@ type_new_init_subclass(PyTypeObject *type, PyObject *kwds)
 
 /* recurse_down_subclasses() and update_subclasses() are mutually
    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
-update_subclasses(PyTypeObject *type, PyObject *name,
+update_subclasses(PyTypeObject *type, PyObject *attr_name,
                   update_callback callback, void *data)
 {
-    if (callback(type, data) < 0)
+    if (callback(type, data) < 0) {
         return -1;
-    return recurse_down_subclasses(type, name, callback, data);
+    }
+    return recurse_down_subclasses(type, attr_name, callback, data);
 }
 
 static int
-recurse_down_subclasses(PyTypeObject *type, PyObject *name,
+recurse_down_subclasses(PyTypeObject *type, PyObject *attr_name,
                         update_callback callback, void *data)
 {
-    PyObject *ref, *subclasses, *dict;
-    Py_ssize_t i;
-
-    subclasses = type->tp_subclasses;
-    if (subclasses == NULL)
+    // It is safe to use a borrowed reference because update_subclasses() is
+    // only used with update_slots_callback() which doesn't modify
+    // tp_subclasses.
+    PyObject *subclasses = type->tp_subclasses;  // borrowed ref
+    if (subclasses == NULL) {
         return 0;
+    }
     assert(PyDict_CheckExact(subclasses));
-    i = 0;
+
+    Py_ssize_t i = 0;
+    PyObject *ref;
     while (PyDict_Next(subclasses, &i, NULL, &ref)) {
         assert(PyWeakref_CheckRef(ref));
         PyObject *obj = PyWeakref_GET_OBJECT(ref);
@@ -8687,18 +8690,20 @@ recurse_down_subclasses(PyTypeObject *type, PyObject *name,
         PyTypeObject *subclass = _PyType_CAST(obj);
 
         /* Avoid recursing down into unaffected classes */
-        dict = subclass->tp_dict;
+        PyObject *dict = subclass->tp_dict;
         if (dict != NULL && PyDict_Check(dict)) {
-            int r = PyDict_Contains(dict, name);
-            if (r > 0) {
-                continue;
-            }
+            int r = PyDict_Contains(dict, attr_name);
             if (r < 0) {
                 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 0;
 }



More information about the Python-checkins mailing list