diff --git a/Lib/_py_abc.py b/Lib/_py_abc.py index c870ae9048b4f1..d79add72ed5014 100644 --- a/Lib/_py_abc.py +++ b/Lib/_py_abc.py @@ -134,14 +134,20 @@ def __subclasscheck__(cls, subclass): return True # Check if it's a subclass of a registered class (recursive) for rcls in cls._abc_registry: - if issubclass(subclass, rcls): - cls._abc_cache.add(subclass) - return True + try: + if issubclass(subclass, rcls): + cls._abc_cache.add(subclass) + return True + except Exception: + pass # Check if it's a subclass of a subclass (recursive) for scls in cls.__subclasses__(): - if issubclass(subclass, scls): - cls._abc_cache.add(subclass) - return True + try: + if issubclass(subclass, scls): + cls._abc_cache.add(subclass) + return True + except Exception: + pass # No dice; update negative cache cls._abc_negative_cache.add(subclass) return False diff --git a/Lib/test/test_abc.py b/Lib/test/test_abc.py index 86f31a9acb4d55..605ec03dc593bd 100644 --- a/Lib/test/test_abc.py +++ b/Lib/test/test_abc.py @@ -435,17 +435,22 @@ class C: None, lambda x: [], lambda: 42, - lambda: [42], ] for i, func in enumerate(bogus_subclasses): class S(metaclass=abc_ABCMeta): __subclasses__ = func - with self.subTest(i=i): + with self.subTest(i=i, func=func): with self.assertRaises(TypeError): issubclass(int, S) + # If __subclasses__ contains non-classes, we suppress the error instead. + class S(metaclass=abc_ABCMeta): + __subclasses__ = lambda: [42] + + self.assertIs(issubclass(int, S), False) + # Also check that issubclass() propagates exceptions raised by # __subclasses__. exc_msg = "exception from __subclasses__" @@ -459,6 +464,35 @@ class S(metaclass=abc_ABCMeta): with self.assertRaisesRegex(Exception, exc_msg): issubclass(int, S) + def test_subclass_with_broken_subclasscheck(self): + class A(metaclass=abc_ABCMeta): + pass + + class Unrelated: pass + + self.assertIs(issubclass(Unrelated, A), False) + + class BrokenMeta(abc_ABCMeta): + is_broken = True + def __subclasscheck__(cls, subclass): + if not BrokenMeta.is_broken: + return super().__subclasscheck__(subclass) + raise Exception("broken") + + class Broken(A, metaclass=BrokenMeta): + pass + + self.assertIs(issubclass(Unrelated, A), False) + + class RegisteredBroken(metaclass=BrokenMeta): + pass + + BrokenMeta.is_broken = False + A.register(RegisteredBroken) + BrokenMeta.is_broken = True + + self.assertIs(issubclass(Unrelated, A), False) + def test_subclasshook(self): class A(metaclass=abc.ABCMeta): @classmethod diff --git a/Misc/NEWS.d/next/Library/2023-05-31-09-55-16.gh-issue-105144.fTqfGv.rst b/Misc/NEWS.d/next/Library/2023-05-31-09-55-16.gh-issue-105144.fTqfGv.rst new file mode 100644 index 00000000000000..d39e56bff893ee --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-05-31-09-55-16.gh-issue-105144.fTqfGv.rst @@ -0,0 +1,3 @@ +Subclass checks on :class:`abc.ABC` subclasses no longer propagate errors +raised by subclass checks on unrelated base classes of the ABC. Patch by +Jelle Zijlstra. diff --git a/Modules/_abc.c b/Modules/_abc.c index d3e405dadb664a..870cc70a235e03 100644 --- a/Modules/_abc.c +++ b/Modules/_abc.c @@ -786,7 +786,8 @@ _abc__abc_subclasscheck_impl(PyObject *module, PyObject *self, goto end; } if (r < 0) { - goto end; + // Ignore subclasses that throw on issubclass(). + PyErr_Clear(); } } @@ -856,8 +857,7 @@ subclasscheck_check_registry(_abc_data *impl, PyObject *subclass, int r = PyObject_IsSubclass(subclass, rkey); Py_DECREF(rkey); if (r < 0) { - ret = -1; - break; + PyErr_Clear(); } if (r > 0) { if (_add_to_weak_set(&impl->_abc_cache, subclass) < 0) {