Skip to content

Commit

Permalink
pythongh-105144: abc: Suppress errors raised by unrelated other subcl…
Browse files Browse the repository at this point in the history
…asses

A more invasive solution for python#105144; perhaps better kept for 3.13 only.
  • Loading branch information
JelleZijlstra committed May 31, 2023
1 parent df396b5 commit 2008aa5
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 11 deletions.
18 changes: 12 additions & 6 deletions Lib/_py_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
38 changes: 36 additions & 2 deletions Lib/test/test_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__"
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
6 changes: 3 additions & 3 deletions Modules/_abc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}

Expand Down Expand Up @@ -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) {
Expand Down

0 comments on commit 2008aa5

Please sign in to comment.