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
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;
}
raw = type->tp_subclasses;
if (raw != NULL) {
assert(PyDict_CheckExact(raw));
i = 0;
while (PyDict_Next(raw, &i, NULL, &ref)) {
PyObject *subclasses = type->tp_subclasses;
if (subclasses != NULL) {
assert(PyDict_CheckExact(subclasses));
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;
}
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;
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_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);
PyObject *old_bases = type->tp_bases;
assert(old_bases != NULL);
PyTypeObject *old_base = type->tp_base;
old_bases = type->tp_bases;
old_base = type->tp_base;
type->tp_bases = Py_NewRef(new_bases);
type->tp_base = (PyTypeObject *)Py_NewRef(new_base);
type->tp_bases = new_bases;
type->tp_base = 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;
}