diff --git a/Lib/test/test_abc.py b/Lib/test/test_abc.py index bdfad263db6fc0..f3e040ff051f21 100644 --- a/Lib/test/test_abc.py +++ b/Lib/test/test_abc.py @@ -7,10 +7,12 @@ """Unit tests for abc.py.""" import unittest +import weakref import abc import _py_abc from inspect import isabstract +from test.support import gc_collect def test_factory(abc_ABCMeta, abc_get_cache_token): class TestLegacyAPI(unittest.TestCase): @@ -349,6 +351,33 @@ class C(str): pass self.assertIsSubclass(C, A) self.assertIsSubclass(C, (A,)) + def test_instancecheck_class_changes_in_subclasscheck(self): + class Original: + def __getattribute__(self, name): + if name == '__class__': + return int + return object.__getattribute__(self, name) + + class Replacement: + pass + + instance = Original() + original = weakref.ref(Original) + del Original + + class Meta(abc_ABCMeta): + def __subclasscheck__(cls, candidate): + if candidate is int: + instance.__class__ = Replacement + gc_collect() + self.assertIsNotNone(original()) + return False + + class Interface(metaclass=Meta): + pass + + self.assertNotIsInstance(instance, Interface) + def test_instancecheck_no_class(self): # gh-153772: __instancecheck__ must fall back to type(instance) # when the instance has no __class__, matching isinstance(). diff --git a/Misc/NEWS.d/next/Library/2026-10-21-21-26-10.gh-issue-159739.yBc6Ei.rst b/Misc/NEWS.d/next/Library/2026-10-21-21-26-10.gh-issue-159739.yBc6Ei.rst new file mode 100644 index 00000000000000..e299599e9763f0 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-21-21-26-10.gh-issue-159739.yBc6Ei.rst @@ -0,0 +1 @@ +Fix a possible crash in :class:`abc.ABCMeta` instance checks when a subclass-checking callback changes the instance’s class. diff --git a/Modules/_abc.c b/Modules/_abc.c index bca9066d340ee4..054cff517115f5 100644 --- a/Modules/_abc.c +++ b/Modules/_abc.c @@ -664,9 +664,12 @@ _abc__abc_instancecheck_impl(PyObject *module, PyObject *self, subclass); goto end; } + /* A subclass check can change the instance's actual type. */ + Py_INCREF(subtype); result = PyObject_CallMethodOneArg(self, &_Py_ID(__subclasscheck__), subclass); if (result == NULL) { + Py_DECREF(subtype); goto end; } @@ -684,6 +687,7 @@ _abc__abc_instancecheck_impl(PyObject *module, PyObject *self, default: Py_UNREACHABLE(); } + Py_DECREF(subtype); end: Py_XDECREF(impl);