From 81febe6fc921f8e1913f2079150884b9ccde7ab9 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 22 Oct 2026 12:37:22 -0700 Subject: [PATCH] [3.15] gh-159739: Keep the instance type alive during ABC subclass checks (GH-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. (cherry picked from commit dcbc45356ea7ccc8038f40dd293f0e338f5ff977) Co-authored-by: Jelle Zijlstra --- Lib/test/test_abc.py | 29 +++++++++++++++++++ ...-10-21-21-26-10.gh-issue-159739.yBc6Ei.rst | 1 + Modules/_abc.c | 4 +++ 3 files changed, 34 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-10-21-21-26-10.gh-issue-159739.yBc6Ei.rst diff --git a/Lib/test/test_abc.py b/Lib/test/test_abc.py index bdfad263db6fc07..f3e040ff051f211 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 000000000000000..e299599e9763f0e --- /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 bca9066d340ee43..054cff517115f50 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);