From 69fc75b22d087bee98edb11fd8b59b98d8569850 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Mon, 2 Mar 2026 10:18:45 +0900 Subject: [PATCH] Skip __class__ lookup in object_isinstance for standard getattro (#7303) In object_isinstance(), when is_subtype() returns false, the __class__ attribute lookup via get_attribute_opt is redundant for objects using standard __getattribute__, since __class__ is a data descriptor on object that always returns obj.class(). --- crates/vm/src/protocol/object.rs | 37 +++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/crates/vm/src/protocol/object.rs b/crates/vm/src/protocol/object.rs index 78ac99039..75b34c29c 100644 --- a/crates/vm/src/protocol/object.rs +++ b/crates/vm/src/protocol/object.rs @@ -4,8 +4,8 @@ use crate::{ AsObject, Py, PyObject, PyObjectRef, PyRef, PyResult, TryFromObject, VirtualMachine, builtins::{ - PyBytes, PyDict, PyDictRef, PyGenericAlias, PyInt, PyList, PyStr, PyTuple, PyTupleRef, - PyType, PyTypeRef, PyUtf8Str, pystr::AsPyStr, + PyBaseObject, PyBytes, PyDict, PyDictRef, PyGenericAlias, PyInt, PyList, PyStr, PyTuple, + PyTupleRef, PyType, PyTypeRef, PyUtf8Str, pystr::AsPyStr, }, common::{hash::PyHash, str::to_ascii}, convert::{ToPyObject, ToPyResult}, @@ -565,9 +565,12 @@ impl PyObject { // PyType_Check(cls) - cls is a type object let mut retval = self.class().is_subtype(cls); if !retval { - // Check __class__ attribute, only masking AttributeError - if let Some(i_cls) = - vm.get_attribute_opt(self.to_owned(), identifier!(vm, __class__))? + // __class__ is a data descriptor on object that always returns + // obj.class() under standard __getattribute__. Only do the + // expensive attribute lookup when getattro is overridden. + if !self.has_standard_getattro() + && let Some(i_cls) = + vm.get_attribute_opt(self.to_owned(), identifier!(vm, __class__))? && let Ok(i_cls_type) = PyTypeRef::try_from_object(vm, i_cls) && !i_cls_type.is(self.class()) { @@ -584,14 +587,15 @@ impl PyObject { ) })?; - // Get __class__ attribute and check, only masking AttributeError - if let Some(i_cls) = - vm.get_attribute_opt(self.to_owned(), identifier!(vm, __class__))? - { - i_cls.abstract_issubclass(cls, vm) + let i_cls: PyObjectRef = if self.has_standard_getattro() { + self.class().to_owned().into() } else { - Ok(false) - } + match vm.get_attribute_opt(self.to_owned(), identifier!(vm, __class__))? { + Some(cls) => cls, + None => return Ok(false), + } + }; + i_cls.abstract_issubclass(cls, vm) } } @@ -769,6 +773,15 @@ impl PyObject { Err(vm.new_type_error(format!("'{}' does not support item deletion", self.class()))) } + /// Returns true if the object uses the standard `__getattribute__` + /// (i.e. `object.__getattribute__`), meaning attribute access follows + /// the normal descriptor protocol without custom interception. + #[inline] + fn has_standard_getattro(&self) -> bool { + let getattro = self.class().slots.getattro.load().unwrap(); + getattro as usize == PyBaseObject::getattro as *const () as usize + } + /// Equivalent to CPython's _PyObject_LookupSpecial /// Looks up a special method in the type's MRO without checking instance dict. /// Returns None if not found (masking AttributeError like CPython).