diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index f2b1db1dc..8a7f750cd 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -779,13 +779,21 @@ fn descr_get_wrapper( cls: Option<&PyObject>, vm: &VirtualMachine, ) -> PyResult { - // A descriptor whose `__get__` is the descriptor itself resolves it by - // fetching `__get__` again, and none of that pushes a Python frame. + // slot_tp_descr_get calls the raw type attribute with all three arguments. + // Binding it first would omit self for builtin functions and incorrectly + // invoke the descriptor protocol of static/class/custom __get__ objects. + // Keep both the callable and self owned across user code that deletes them. vm.with_recursion(" while calling a Python object", || { - vm.call_special_method( - zelf, - identifier!(vm, __get__), - (obj.map(PyObject::to_owned), cls.map(PyObject::to_owned)), + let Some(get) = zelf.class().get_attr(identifier!(vm, __get__)) else { + return Ok(zelf.to_owned()); + }; + get.call( + ( + zelf.to_owned(), + obj.map(PyObject::to_owned), + cls.map(PyObject::to_owned), + ), + vm, ) }) } diff --git a/extra_tests/snippets/builtin_object.py b/extra_tests/snippets/builtin_object.py index 0b90c44a3..9d343eafe 100644 --- a/extra_tests/snippets/builtin_object.py +++ b/extra_tests/snippets/builtin_object.py @@ -161,3 +161,35 @@ except RuntimeError as outer: with assert_raises(ValueError) as caught: RaisingAttributeHook().absent assert caught.exception.__context__ is outer + + +# The descriptor slot calls the raw __get__ attribute without binding it first. +class RawGetDescriptor: + __get__ = staticmethod(lambda *args: args) + + +class RawGetOwner: + value = RawGetDescriptor() + + +raw_descriptor = vars(RawGetOwner)["value"] +raw_instance = RawGetOwner() +assert raw_instance.value == (raw_descriptor, raw_instance, RawGetOwner) +assert RawGetOwner.value == (raw_descriptor, None, RawGetOwner) + + +class CallableGet: + def __get__(self, instance, owner): + raise AssertionError("__get__ must not be bound before the slot call") + + def __call__(self, *args): + return args + + +RawGetDescriptor.__get__ = CallableGet() +assert raw_instance.value == (raw_descriptor, raw_instance, RawGetOwner) +RawGetDescriptor.__get__ = classmethod(lambda *args: args) +with assert_raises(TypeError): + raw_instance.value +RawGetDescriptor.__get__ = lambda self, instance, owner: (self, instance, owner) +assert raw_instance.value == (raw_descriptor, raw_instance, RawGetOwner)