https://github.com/python/cpython/commit/d4f52c445b9ef745bb1d1023be225f99b986e083
commit: d4f52c445b9ef745bb1d1023be225f99b986e083
branch: main
author: Kumar Aditya <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-09-22T11:16:30+05:30
summary:

gh-157526: Call `__instancecheck__` and `__subclasscheck__` without creating a 
bound method (#157670)

files:
M Include/internal/pycore_object.h
M Modules/_testinternalcapi/test_cases.c.h
M Objects/abstract.c
M Objects/typeobject.c
M Python/bytecodes.c
M Python/executor_cases.c.h
M Python/generated_cases.c.h

diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h
index 41786cb267c2e96..3ecc5e9f0554b09 100644
--- a/Include/internal/pycore_object.h
+++ b/Include/internal/pycore_object.h
@@ -957,7 +957,8 @@ extern int _PyObject_IsInstanceDictEmpty(PyObject *);
 
 // Export for 'math' shared extension
 PyAPI_FUNC(PyObject*) _PyObject_LookupSpecial(PyObject *, PyObject *);
-PyAPI_FUNC(int) _PyObject_LookupSpecialMethod(PyObject *attr, _PyStackRef 
*method_and_self);
+PyAPI_FUNC(int) _PyObject_LookupSpecialMethod(PyObject *attr, _PyStackRef 
*method,
+                                              _PyStackRef *self);
 
 // Calls the method named `attr` on `self`, but does not set an exception if
 // the attribute does not exist.
diff --git a/Modules/_testinternalcapi/test_cases.c.h 
b/Modules/_testinternalcapi/test_cases.c.h
index 7a75e80298fcd82..a04770bd83b176e 100644
--- a/Modules/_testinternalcapi/test_cases.c.h
+++ b/Modules/_testinternalcapi/test_cases.c.h
@@ -10357,7 +10357,8 @@
                 ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
                 _PyFrame_SetStackPointer(frame, stack_pointer);
                 _PyFrame_StackPointerValidate(frame);
-                int err = _PyObject_LookupSpecialMethod(name, method_and_self);
+                int err = _PyObject_LookupSpecialMethod(name, 
&method_and_self[0],
+                    &method_and_self[1]);
                 _PyFrame_StackPointerInvalidate(frame);
                 if (err <= 0) {
                     if (err == 0) {
diff --git a/Objects/abstract.c b/Objects/abstract.c
index 28f751965f36b94..da2a0b0c285b52b 100644
--- a/Objects/abstract.c
+++ b/Objects/abstract.c
@@ -9,6 +9,7 @@
 #include "pycore_list.h"          // _PyList_AppendTakeRef()
 #include "pycore_long.h"          // _PyLong_IsNegative()
 #include "pycore_object.h"        // _Py_CheckSlotResult()
+#include "pycore_stackref.h"      // _PyStackRef
 #include "pycore_pybuffer.h"      // _PyBuffer_ReleaseInInterpreterAndRawFree()
 #include "pycore_pyerrors.h"      // _PyErr_Occurred()
 #include "pycore_pystate.h"       // _PyThreadState_GET()
@@ -2635,6 +2636,37 @@ object_isinstance(PyObject *inst, PyObject *cls)
     return retval;
 }
 
+static int
+call_special_method(PyThreadState *tstate, PyObject *cls, PyObject *name,
+                    const char *where, PyObject *arg, PyObject **res)
+{
+    _PyCStackRef cref;
+    _PyThreadState_PushCStackRef(tstate, &cref);
+    _PyStackRef self = PyStackRef_FromPyObjectBorrow(cls);
+    int found = _PyObject_LookupSpecialMethod(name, &cref.ref, &self);
+    if (found > 0) {
+        *res = NULL;
+        if (!_Py_EnterRecursiveCallTstate(tstate, where)) {
+            PyObject *method = PyStackRef_AsPyObjectBorrow(cref.ref);
+            PyObject *args[2] = {PyStackRef_AsPyObjectBorrow(self), arg};
+            if (args[0] != NULL) {
+                /* Unbound method: prepend self. */
+                *res = PyObject_Vectorcall(method, args, 2, NULL);
+            }
+            else {
+                *res = PyObject_Vectorcall(method, args + 1, 1, NULL);
+            }
+            _Py_LeaveRecursiveCallTstate(tstate);
+        }
+        if (*res == NULL) {
+            found = -1;
+        }
+    }
+    PyStackRef_XCLOSE(self);
+    _PyThreadState_PopCStackRef(tstate, &cref);
+    return found;
+}
+
 static int
 object_recursive_isinstance(PyThreadState *tstate, PyObject *inst, PyObject 
*cls)
 {
@@ -2672,26 +2704,16 @@ object_recursive_isinstance(PyThreadState *tstate, 
PyObject *inst, PyObject *cls
         return r;
     }
 
-    PyObject *checker = _PyObject_LookupSpecial(cls, 
&_Py_ID(__instancecheck__));
-    if (checker != NULL) {
-        if (_Py_EnterRecursiveCallTstate(tstate, " in __instancecheck__")) {
-            Py_DECREF(checker);
-            return -1;
-        }
-
-        PyObject *res = PyObject_CallOneArg(checker, inst);
-        _Py_LeaveRecursiveCallTstate(tstate);
-        Py_DECREF(checker);
-
-        if (res == NULL) {
-            return -1;
-        }
+    PyObject *res;
+    int found = call_special_method(tstate, cls, &_Py_ID(__instancecheck__),
+                                    " in __instancecheck__", inst, &res);
+    if (found > 0) {
         int ok = PyObject_IsTrue(res);
         Py_DECREF(res);
 
         return ok;
     }
-    else if (_PyErr_Occurred(tstate)) {
+    else if (found < 0) {
         return -1;
     }
 
@@ -2731,8 +2753,6 @@ recursive_issubclass(PyObject *derived, PyObject *cls)
 static int
 object_issubclass(PyThreadState *tstate, PyObject *derived, PyObject *cls)
 {
-    PyObject *checker;
-
     /* We know what type's __subclasscheck__ does. */
     if (PyType_CheckExact(cls)) {
         /* Quick test for an exact match */
@@ -2763,23 +2783,15 @@ object_issubclass(PyThreadState *tstate, PyObject 
*derived, PyObject *cls)
         return r;
     }
 
-    checker = _PyObject_LookupSpecial(cls, &_Py_ID(__subclasscheck__));
-    if (checker != NULL) {
-        int ok = -1;
-        if (_Py_EnterRecursiveCallTstate(tstate, " in __subclasscheck__")) {
-            Py_DECREF(checker);
-            return ok;
-        }
-        PyObject *res = PyObject_CallOneArg(checker, derived);
-        _Py_LeaveRecursiveCallTstate(tstate);
-        Py_DECREF(checker);
-        if (res != NULL) {
-            ok = PyObject_IsTrue(res);
-            Py_DECREF(res);
-        }
+    PyObject *res;
+    int found = call_special_method(tstate, cls, &_Py_ID(__subclasscheck__),
+                                    " in __subclasscheck__", derived, &res);
+    if (found > 0) {
+        int ok = PyObject_IsTrue(res);
+        Py_DECREF(res);
         return ok;
     }
-    else if (_PyErr_Occurred(tstate)) {
+    else if (found < 0) {
         return -1;
     }
 
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index 30958310227af0e..b9e114260424072 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -2931,17 +2931,22 @@ _PyObject_LookupSpecial(PyObject *self, PyObject *attr)
     return res;
 }
 
-// Lookup the method name `attr` on `self`. On entry, `method_and_self[0]`
-// is null and `method_and_self[1]` is `self`. On exit, `method_and_self[0]`
-// is the method object and `method_and_self[1]` is `self` if the method is
-// not bound.
+// Lookup the method name `attr` on `*self`. On entry, `*method` is null.
+// On exit, `*method` is the method object and `*self` is cleared if the
+// method is bound.
 // Return 1 on success, -1 on error, and 0 if the method is missing.
+//
+// `method` must point to a location that the garbage collector can see,
+// such as the `ref` field of a `_PyCStackRef` or a slot on the interpreter
+// stack. A descriptor may be invoked while `*method` holds the only
+// reference to the method object, and that can trigger a collection.
 int
-_PyObject_LookupSpecialMethod(PyObject *attr, _PyStackRef *method_and_self)
+_PyObject_LookupSpecialMethod(PyObject *attr, _PyStackRef *method,
+                              _PyStackRef *self)
 {
-    PyObject *self = PyStackRef_AsPyObjectBorrow(method_and_self[1]);
-    _PyType_LookupStackRefAndVersion(Py_TYPE(self), attr, &method_and_self[0]);
-    PyObject *method_o = PyStackRef_AsPyObjectBorrow(method_and_self[0]);
+    PyObject *self_o = PyStackRef_AsPyObjectBorrow(*self);
+    _PyType_LookupStackRefAndVersion(Py_TYPE(self_o), attr, method);
+    PyObject *method_o = PyStackRef_AsPyObjectBorrow(*method);
     if (method_o == NULL) {
         return 0;
     }
@@ -2953,14 +2958,14 @@ _PyObject_LookupSpecialMethod(PyObject *attr, 
_PyStackRef *method_and_self)
 
     descrgetfunc f = Py_TYPE(method_o)->tp_descr_get;
     if (f != NULL) {
-        PyObject *func = f(method_o, self, (PyObject *)(Py_TYPE(self)));
+        PyObject *func = f(method_o, self_o, (PyObject *)(Py_TYPE(self_o)));
         if (func == NULL) {
             return -1;
         }
-        PyStackRef_CLEAR(method_and_self[0]); // clear method
-        method_and_self[0] = PyStackRef_FromPyObjectSteal(func);
+        PyStackRef_CLEAR(*method); // clear method
+        *method = PyStackRef_FromPyObjectSteal(func);
     }
-    PyStackRef_CLEAR(method_and_self[1]); // clear self
+    PyStackRef_CLEAR(*self); // clear self
     return 1;
 }
 
diff --git a/Python/bytecodes.c b/Python/bytecodes.c
index fb0cdf4d65e060d..fdfd5a0e6090464 100644
--- a/Python/bytecodes.c
+++ b/Python/bytecodes.c
@@ -4141,7 +4141,8 @@ dummy_func(
 
         op(_LOAD_SPECIAL, (method_and_self[2] -- method_and_self[2])) {
             PyObject *name = _Py_SpecialMethods[oparg].name;
-            int err = _PyObject_LookupSpecialMethod(name, method_and_self);
+            int err = _PyObject_LookupSpecialMethod(name, &method_and_self[0],
+                                                    &method_and_self[1]);
             if (err <= 0) {
                 if (err == 0) {
                     PyObject *owner = 
PyStackRef_AsPyObjectBorrow(method_and_self[1]);
diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h
index 9aad9e003765cf8..e3dc2e781a3bebd 100644
--- a/Python/executor_cases.c.h
+++ b/Python/executor_cases.c.h
@@ -16224,7 +16224,8 @@
             PyObject *name = _Py_SpecialMethods[oparg].name;
             _PyFrame_SetStackPointer(frame, stack_pointer);
             _PyFrame_StackPointerValidate(frame);
-            int err = _PyObject_LookupSpecialMethod(name, method_and_self);
+            int err = _PyObject_LookupSpecialMethod(name, &method_and_self[0],
+                &method_and_self[1]);
             _PyFrame_StackPointerInvalidate(frame);
             if (err <= 0) {
                 if (err == 0) {
diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h
index 77c18b3d61fefc7..48f76ccf59f8bc8 100644
--- a/Python/generated_cases.c.h
+++ b/Python/generated_cases.c.h
@@ -10355,7 +10355,8 @@
                 ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
                 _PyFrame_SetStackPointer(frame, stack_pointer);
                 _PyFrame_StackPointerValidate(frame);
-                int err = _PyObject_LookupSpecialMethod(name, method_and_self);
+                int err = _PyObject_LookupSpecialMethod(name, 
&method_and_self[0],
+                    &method_and_self[1]);
                 _PyFrame_StackPointerInvalidate(frame);
                 if (err <= 0) {
                     if (err == 0) {

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to