Skip to content

Commit dcbc453

Browse files
authored
gh-159739: Keep the instance type alive during ABC subclass checks (#160198)
When an instance advertises a __class__ different from its actual type, ABCMeta.__instancecheck__ performs two subclass checks. The first callback can change the instance's actual type and allow its old type to be collected before the second check, causing a crash. Keep the original type alive through both checks. Backport to 3.15 and 3.14.
1 parent f4f8b93 commit dcbc453

3 files changed

Lines changed: 34 additions & 1 deletion

File tree

‎Lib/test/test_abc.py‎

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@
77
"""Unit tests for abc.py."""
88

99
import unittest
10+
import weakref
1011

1112
import abc
1213
import _py_abc
1314
from inspect import isabstract
14-
from test.support import warnings_helper
15+
from test.support import gc_collect, warnings_helper
1516

1617
def test_factory(abc_ABCMeta, abc_get_cache_token):
1718
class TestLegacyAPI(unittest.TestCase):
@@ -380,6 +381,33 @@ class C(str): pass
380381
self.assertIsSubclass(C, A)
381382
self.assertIsSubclass(C, (A,))
382383

384+
def test_instancecheck_class_changes_in_subclasscheck(self):
385+
class Original:
386+
def __getattribute__(self, name):
387+
if name == '__class__':
388+
return int
389+
return object.__getattribute__(self, name)
390+
391+
class Replacement:
392+
pass
393+
394+
instance = Original()
395+
original = weakref.ref(Original)
396+
del Original
397+
398+
class Meta(abc_ABCMeta):
399+
def __subclasscheck__(cls, candidate):
400+
if candidate is int:
401+
instance.__class__ = Replacement
402+
gc_collect()
403+
self.assertIsNotNone(original())
404+
return False
405+
406+
class Interface(metaclass=Meta):
407+
pass
408+
409+
self.assertNotIsInstance(instance, Interface)
410+
383411
def test_instancecheck_no_class(self):
384412
# gh-153772: __instancecheck__ must fall back to type(instance)
385413
# when the instance has no __class__, matching isinstance().
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix a possible crash in :class:`abc.ABCMeta` instance checks when a subclass-checking callback changes the instance’s class.

‎Modules/_abc.c‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,9 +664,12 @@ _abc__abc_instancecheck_impl(PyObject *module, PyObject *self,
664664
subclass);
665665
goto end;
666666
}
667+
/* A subclass check can change the instance's actual type. */
668+
Py_INCREF(subtype);
667669
result = PyObject_CallMethodOneArg(self, &_Py_ID(__subclasscheck__),
668670
subclass);
669671
if (result == NULL) {
672+
Py_DECREF(subtype);
670673
goto end;
671674
}
672675

@@ -684,6 +687,7 @@ _abc__abc_instancecheck_impl(PyObject *module, PyObject *self,
684687
default:
685688
Py_UNREACHABLE();
686689
}
690+
Py_DECREF(subtype);
687691

688692
end:
689693
Py_XDECREF(impl);

0 commit comments

Comments
 (0)