Skip to content

Commit fc7df0e

Browse files
authored
bpo-32999: Fix ABC.__subclasscheck__ crash (GH-6002)
1 parent bc3f228 commit fc7df0e

File tree

3 files changed

+45
-12
lines changed

3 files changed

+45
-12
lines changed

Lib/test/test_abc.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,24 @@ class MyInt(int):
392392
self.assertIsInstance(42, A)
393393
self.assertIsInstance(42, (A,))
394394

395+
def test_issubclass_bad_arguments(self):
396+
class A(metaclass=abc_ABCMeta):
397+
pass
398+
399+
with self.assertRaises(TypeError):
400+
issubclass({}, A) # unhashable
401+
402+
with self.assertRaises(TypeError):
403+
issubclass(42, A) # No __mro__
404+
405+
# Python version supports any iterable as __mro__.
406+
# But it's implementation detail and don't emulate it in C version.
407+
class C:
408+
__mro__ = 42 # __mro__ is not tuple
409+
410+
with self.assertRaises(TypeError):
411+
issubclass(C(), A)
412+
395413
def test_all_new_methods_are_called(self):
396414
class A(metaclass=abc_ABCMeta):
397415
pass
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix C implemetation of ``ABC.__subclasscheck__(cls, subclass)`` crashed when
2+
``subclass`` is not a type object.

Modules/_abc.c

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ _Py_IDENTIFIER(__abstractmethods__);
1616
_Py_IDENTIFIER(__class__);
1717
_Py_IDENTIFIER(__dict__);
1818
_Py_IDENTIFIER(__bases__);
19+
_Py_IDENTIFIER(__mro__);
1920
_Py_IDENTIFIER(_abc_impl);
2021
_Py_IDENTIFIER(__subclasscheck__);
2122
_Py_IDENTIFIER(__subclasshook__);
@@ -568,7 +569,7 @@ _abc__abc_subclasscheck_impl(PyObject *module, PyObject *self,
568569
PyObject *subclass)
569570
/*[clinic end generated code: output=b56c9e4a530e3894 input=1d947243409d10b8]*/
570571
{
571-
PyObject *ok, *mro, *subclasses = NULL, *result = NULL;
572+
PyObject *ok, *mro = NULL, *subclasses = NULL, *result = NULL;
572573
Py_ssize_t pos;
573574
int incache;
574575
_abc_data *impl = _get_impl(self);
@@ -637,20 +638,31 @@ _abc__abc_subclasscheck_impl(PyObject *module, PyObject *self,
637638
}
638639
Py_DECREF(ok);
639640

640-
/* 4. Check if it's a direct subclass. */
641-
mro = ((PyTypeObject *)subclass)->tp_mro;
642-
assert(PyTuple_Check(mro));
643-
for (pos = 0; pos < PyTuple_GET_SIZE(mro); pos++) {
644-
PyObject *mro_item = PyTuple_GET_ITEM(mro, pos);
645-
if (mro_item == NULL) {
641+
/* 4. Check if it's a direct subclass.
642+
*
643+
* if cls in getattr(subclass, '__mro__', ()):
644+
* cls._abc_cache.add(subclass)
645+
* return True
646+
*/
647+
if (_PyObject_LookupAttrId(subclass, &PyId___mro__, &mro) < 0) {
648+
goto end;
649+
}
650+
if (mro != NULL) {
651+
if (!PyTuple_Check(mro)) {
652+
// Python version supports non-tuple iterable. Keep it as
653+
// implementation detail.
654+
PyErr_SetString(PyExc_TypeError, "__mro__ is not a tuple");
646655
goto end;
647656
}
648-
if ((PyObject *)self == mro_item) {
649-
if (_add_to_weak_set(&impl->_abc_cache, subclass) < 0) {
657+
for (pos = 0; pos < PyTuple_GET_SIZE(mro); pos++) {
658+
PyObject *mro_item = PyTuple_GET_ITEM(mro, pos);
659+
if ((PyObject *)self == mro_item) {
660+
if (_add_to_weak_set(&impl->_abc_cache, subclass) < 0) {
661+
goto end;
662+
}
663+
result = Py_True;
650664
goto end;
651665
}
652-
result = Py_True;
653-
goto end;
654666
}
655667
}
656668

@@ -690,7 +702,8 @@ _abc__abc_subclasscheck_impl(PyObject *module, PyObject *self,
690702
result = Py_False;
691703

692704
end:
693-
Py_XDECREF(impl);
705+
Py_DECREF(impl);
706+
Py_XDECREF(mro);
694707
Py_XDECREF(subclasses);
695708
Py_XINCREF(result);
696709
return result;

0 commit comments

Comments
 (0)