Skip to content

Commit 609baf6

Browse files
committed
gh-92810: Apply fixes
Signed-off-by: Martynov Maxim <[email protected]>
1 parent e78a067 commit 609baf6

File tree

4 files changed

+34
-122
lines changed

4 files changed

+34
-122
lines changed

Lib/_py_abc.py

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ def __new__(mcls, name, bases, namespace, /, **kwargs):
5151
cls._abc_cache = WeakSet()
5252
cls._abc_negative_cache = WeakSet()
5353
cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter
54+
cls._prevent_recursion = 0
5455
return cls
5556

5657
def register(cls, subclass):
@@ -154,19 +155,6 @@ def __subclasscheck__(cls, subclass):
154155
if issubclass(subclass, rcls):
155156
cls._abc_cache.add(subclass)
156157
return True
157-
158-
# Check if it's a subclass of a subclass (recursive).
159-
# >>> class Ancestor: __subclasses__ = lambda: [Other]
160-
# >>> class Other: pass
161-
# >>> isinstance(Other, Ancestor) is True
162-
# Do not iterate over cls.__subclasses__() because it returns the entire class tree,
163-
# not just direct children, which leads to O(n^2) lookup.
164-
original_subclasses = getattr(cls, "__dict__", {}).get("__subclasses__", _UNSET)
165-
if original_subclasses is not _UNSET:
166-
for scls in original_subclasses():
167-
if issubclass(subclass, scls):
168-
cls._abc_cache.add(subclass)
169-
return True
170158
# No dice; update negative cache
171159
cls._abc_negative_cache.add(subclass)
172160
return False

Lib/test/test_abc.py

Lines changed: 0 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -411,34 +411,6 @@ class MyInt(int):
411411
self.assertIsInstance(42, A)
412412
self.assertIsInstance(42, (A,))
413413

414-
def test_subclasses(self):
415-
class A:
416-
pass
417-
418-
class B:
419-
pass
420-
421-
class C:
422-
pass
423-
424-
class Sup(metaclass=abc_ABCMeta):
425-
__subclasses__ = lambda: [A, B]
426-
427-
self.assertIsSubclass(A, Sup)
428-
self.assertIsSubclass(A, (Sup,))
429-
self.assertIsInstance(A(), Sup)
430-
self.assertIsInstance(A(), (Sup,))
431-
432-
self.assertIsSubclass(B, Sup)
433-
self.assertIsSubclass(B, (Sup,))
434-
self.assertIsInstance(B(), Sup)
435-
self.assertIsInstance(B(), (Sup,))
436-
437-
self.assertNotIsSubclass(C, Sup)
438-
self.assertNotIsSubclass(C, (Sup,))
439-
self.assertNotIsInstance(C(), Sup)
440-
self.assertNotIsInstance(C(), (Sup,))
441-
442414
def test_subclasses_bad_arguments(self):
443415
class A(metaclass=abc_ABCMeta):
444416
pass
@@ -457,37 +429,6 @@ class C:
457429
with self.assertRaises(TypeError):
458430
issubclass(C(), A)
459431

460-
# bpo-34441: Check that issubclass() doesn't crash on bogus
461-
# classes.
462-
bogus_subclasses = [
463-
None,
464-
lambda x: [],
465-
lambda: 42,
466-
lambda: [42],
467-
]
468-
469-
for i, func in enumerate(bogus_subclasses):
470-
class S(metaclass=abc_ABCMeta):
471-
__subclasses__ = func
472-
473-
with self.subTest(i=i):
474-
with self.assertRaises(TypeError):
475-
issubclass(int, S)
476-
477-
# Also check that issubclass() propagates exceptions raised by
478-
# __subclasses__.
479-
class CustomError(Exception): ...
480-
exc_msg = "exception from __subclasses__"
481-
482-
def raise_exc():
483-
raise CustomError(exc_msg)
484-
485-
class S(metaclass=abc_ABCMeta):
486-
__subclasses__ = raise_exc
487-
488-
with self.assertRaisesRegex(CustomError, exc_msg):
489-
issubclass(int, S)
490-
491432
def test_subclasshook(self):
492433
class A(metaclass=abc.ABCMeta):
493434
@classmethod

Lib/test/test_isinstance.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,28 @@ class B:
351351
with support.infinite_recursion(25):
352352
self.assertRaises(RecursionError, issubclass, X(), int)
353353

354+
def test_override_subclasses(self):
355+
class A: pass
356+
class B: pass
357+
358+
class Parent1:
359+
@classmethod
360+
def __subclasses__(self):
361+
return [A, B]
362+
363+
class Parent2:
364+
__subclasses__ = lambda: [A, B]
365+
366+
self.assertNotIsInstance(A(), Parent1)
367+
self.assertNotIsInstance(B(), Parent1)
368+
self.assertNotIsSubclass(A, Parent1)
369+
self.assertNotIsSubclass(B, Parent1)
370+
371+
self.assertNotIsInstance(A(), Parent2)
372+
self.assertNotIsInstance(B(), Parent2)
373+
self.assertNotIsSubclass(A, Parent2)
374+
self.assertNotIsSubclass(B, Parent2)
375+
354376

355377
def blowstack(fxn, arg, compare_to):
356378
# Make sure that calling isinstance with a deeply nested tuple for its

Modules/_abc.c

Lines changed: 11 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -603,21 +603,19 @@ _abc__abc_register_impl(PyObject *module, PyObject *self, PyObject *subclass)
603603
return NULL;
604604
}
605605

606-
if (!PyTuple_Check(mro)) {
607-
PyErr_SetString(PyExc_TypeError, "__mro__ is not tuple");
606+
if (!PyTuple_CheckExact(mro)) {
607+
PyErr_SetString(PyExc_TypeError, "__mro__ must be an exact tuple");
608608
goto error;
609609
}
610610

611611
for (Py_ssize_t pos = 0; pos < PyTuple_GET_SIZE(mro); pos++) {
612612
PyObject *base_class = PyTuple_GET_ITEM(mro, pos); // borrowed
613613
PyObject *base_class_data;
614614

615-
if (PyObject_GetOptionalAttr(base_class, &_Py_ID(_abc_impl),
616-
&base_class_data) < 0) {
617-
goto error;
618-
}
619-
620-
if (PyErr_Occurred()) {
615+
if (PyObject_GetOptionalAttr(base_class,
616+
&_Py_ID(_abc_impl),
617+
&base_class_data) < 0)
618+
{
621619
goto error;
622620
}
623621

@@ -627,8 +625,11 @@ _abc__abc_register_impl(PyObject *module, PyObject *self, PyObject *subclass)
627625
}
628626

629627
_abc_data *base_class_state = _abc_data_CAST(base_class_data);
630-
if (_add_to_weak_set(base_class_state, &base_class_state->_abc_registry, subclass) < 0) {
631-
Py_DECREF(base_class_data);
628+
int res = _add_to_weak_set(base_class_state,
629+
&base_class_state->_abc_registry,
630+
subclass);
631+
Py_DECREF(base_class_data);
632+
if (res < 0) {
632633
goto error;
633634
}
634635
}
@@ -763,7 +764,6 @@ _abc__abc_subclasscheck_impl(PyObject *module, PyObject *self,
763764

764765
PyObject *ok, *subclasses = NULL, *result = NULL;
765766
_abcmodule_state *state = NULL;
766-
Py_ssize_t pos;
767767
int incache;
768768
_abc_data *impl = _get_impl(module, self);
769769
if (impl == NULL) {
@@ -850,45 +850,6 @@ _abc__abc_subclasscheck_impl(PyObject *module, PyObject *self,
850850
goto end;
851851
}
852852

853-
/* 6. Check if it's a subclass of a subclass (recursive).
854-
>>> class Ancestor: __subclasses__ = lambda: [Other]
855-
>>> class Other: pass
856-
>>> isinstance(Other, Ancestor) is True
857-
858-
Do not iterate over cls.__subclasses__() because it returns the entire class tree,
859-
not just direct children, which leads to O(n^2) lookup.
860-
*/
861-
PyObject *dict = _PyType_GetDict(cls); // borrowed
862-
PyObject *subclasses_own_method = PyDict_GetItemString(dict, "__subclasses__"); // borrowed
863-
if (subclasses_own_method) {
864-
subclasses = PyObject_CallNoArgs(subclasses_own_method);
865-
if (subclasses == NULL) {
866-
goto end;
867-
}
868-
if (!PyList_Check(subclasses)) {
869-
PyErr_SetString(PyExc_TypeError, "__subclasses__() must return a list");
870-
goto end;
871-
}
872-
for (pos = 0; pos < PyList_GET_SIZE(subclasses); pos++) {
873-
PyObject *scls = PyList_GetItemRef(subclasses, pos);
874-
if (scls == NULL) {
875-
goto end;
876-
}
877-
int r = PyObject_IsSubclass(subclass, scls);
878-
Py_DECREF(scls);
879-
if (r > 0) {
880-
if (_add_to_weak_set(impl, &impl->_abc_cache, subclass) < 0) {
881-
goto end;
882-
}
883-
result = Py_True;
884-
goto end;
885-
}
886-
if (r < 0) {
887-
goto end;
888-
}
889-
}
890-
}
891-
892853
/* No dice; update negative cache. */
893854
if (_add_to_weak_set(impl, &impl->_abc_negative_cache, subclass) < 0) {
894855
goto end;

0 commit comments

Comments
 (0)