diff --git a/crates/capi/src/descrobject.rs b/crates/capi/src/descrobject.rs index b0d24667dc7..ecc0b53f82a 100644 --- a/crates/capi/src/descrobject.rs +++ b/crates/capi/src/descrobject.rs @@ -1,7 +1,201 @@ use crate::PyObject; +use crate::methodobject::{PyMethodDef, build_method_def}; +use crate::object::PyTypeObject; use crate::pystate::with_vm; -use rustpython_vm::PyPayload; -use rustpython_vm::builtins::PyMappingProxy; +use core::ffi::{CStr, c_char, c_int, c_void}; +use core::ptr::NonNull; +use rustpython_vm::builtins::{ + DescriptorMemberDef, MemberGetter, MemberKind, MemberSetter, PyDescriptorOwned, PyGetSet, + PyMappingProxy, PyMemberDescriptor, PyType, +}; +use rustpython_vm::common::lock::PyRwLock; +use rustpython_vm::function::PySetterValue; +use rustpython_vm::{Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine}; + +#[repr(C)] +pub struct PyGetSetDef { + pub name: *const c_char, + pub get: + Option *mut PyObject>, + pub set: Option< + unsafe extern "C" fn( + slf: *mut PyObject, + value: *mut PyObject, + closure: *mut c_void, + ) -> c_int, + >, + pub doc: *const c_char, + pub closure: *mut c_void, +} + +impl PyGetSetDef { + pub(crate) fn build( + &self, + ty: &'static Py, + vm: &VirtualMachine, + ) -> PyResult> { + let name = unsafe { CStr::from_ptr(self.name) } + .to_str() + .map_err(|_| vm.new_system_error("PyGetSetDef name was not valid UTF-8"))?; + let closure = self.closure as usize; + + let descriptor = match (self.get, self.set) { + (Some(get), Some(set)) => vm.ctx.new_static_getset( + name, + ty, + move |obj: PyObjectRef, vm: &VirtualMachine| -> PyResult { + unsafe { + let closure = closure as *mut c_void; + let ret_ptr = get(obj.as_raw().cast_mut(), closure); + let ret_ptr = NonNull::new(ret_ptr).ok_or_else(|| { + vm.take_raised_exception().unwrap_or_else(|| { + vm.new_system_error( + "Native function returned NULL, but there was no exception set", + ) + }) + })?; + Ok(PyObjectRef::from_raw(ret_ptr)) + } + }, + move |obj: PyObjectRef, value: PySetterValue, vm: &VirtualMachine| unsafe { + let closure = closure as *mut c_void; + let value = value.unwrap_or_none(vm); + let result = set(obj.as_raw().cast_mut(), value.as_raw().cast_mut(), closure); + if result == 0 { + Ok(()) + } else { + Err(vm.take_raised_exception().unwrap_or_else(|| { + vm.new_system_error( + "C setter returned error but did not set an exception", + ) + })) + } + }, + ), + (Some(get), None) => vm.ctx.new_readonly_getset( + name, + ty, + move |obj: PyObjectRef, vm: &VirtualMachine| -> PyResult { + unsafe { + let closure = closure as *mut c_void; + let ret_ptr = get(obj.as_raw().cast_mut(), closure); + let ret_ptr = NonNull::new(ret_ptr).ok_or_else(|| { + vm.take_raised_exception().unwrap_or_else(|| { + vm.new_system_error( + "Native function returned NULL, but there was no exception set", + ) + }) + })?; + Ok(PyObjectRef::from_raw(ret_ptr)) + } + }, + ), + (None, Some(set)) => vm.ctx.new_static_getset( + name, + ty, + move |_obj: PyObjectRef, vm: &VirtualMachine| -> PyResult { + Err(vm.new_attribute_error("unreadable attribute")) + }, + move |obj: PyObjectRef, value: PySetterValue, vm: &VirtualMachine| unsafe { + let closure = closure as *mut c_void; + let value = value.unwrap_or_none(vm); + let result = set(obj.as_raw().cast_mut(), value.as_raw().cast_mut(), closure); + if result == 0 { + Ok(()) + } else { + Err(vm.take_raised_exception().unwrap_or_else(|| { + vm.new_system_error( + "C setter returned error but did not set an exception", + ) + })) + } + }, + ), + (None, None) => vm.ctx.new_readonly_getset( + name, + ty, + move |_obj: PyObjectRef, vm: &VirtualMachine| -> PyResult { + Err(vm.new_attribute_error("unreadable attribute")) + }, + ), + }; + + Ok(descriptor) + } +} + +#[repr(C)] +pub struct PyMemberDef { + pub name: *const c_char, + pub type_code: c_int, + pub offset: isize, + pub flags: c_int, + pub doc: *const c_char, +} + +impl PyMemberDef { + const PY_READONLY: c_int = 1; + const PY_RELATIVE_OFFSET: c_int = 8; + + pub(crate) fn build( + &self, + ty: &Py, + vm: &VirtualMachine, + ) -> PyResult> { + let name = unsafe { CStr::from_ptr(self.name) } + .to_str() + .map_err(|_| vm.new_system_error("PyMemberDef name was not valid UTF-8"))?; + let kind = match self.type_code { + 6 => MemberKind::Object, + 16 => MemberKind::ObjectEx, + 14 => MemberKind::Bool, + _ => { + return Err(vm.new_system_error(format!( + "PyDescr_NewMember does not support member type code {}", + self.type_code + ))); + } + }; + if self.offset < 0 { + return Err(vm.new_system_error("PyDescr_NewMember does not support negative offsets")); + } + if self.flags & Self::PY_RELATIVE_OFFSET != 0 { + return Err( + vm.new_system_error("PyDescr_NewMember does not support Py_RELATIVE_OFFSET") + ); + } + + let doc = NonNull::new(self.doc.cast_mut()) + .map(|doc| { + unsafe { CStr::from_ptr(doc.as_ptr()) } + .to_str() + .map(|s| s.to_owned()) + .map_err(|_| vm.new_system_error("PyMemberDef doc was not valid UTF-8")) + }) + .transpose()?; + + let descriptor = PyMemberDescriptor { + common: PyDescriptorOwned { + typ: ty.to_owned(), + name: vm.ctx.intern_str(name), + qualname: PyRwLock::new(None), + }, + member: DescriptorMemberDef { + name: name.to_owned(), + kind, + getter: MemberGetter::Offset(self.offset as usize), + setter: if self.flags & Self::PY_READONLY != 0 { + MemberSetter::Setter(None) + } else { + MemberSetter::Offset(self.offset as usize) + }, + doc, + }, + }; + + Ok(descriptor.into_ref(&vm.ctx)) + } +} #[unsafe(no_mangle)] pub unsafe extern "C" fn PyDictProxy_New(mapping: *mut PyObject) -> *mut PyObject { @@ -11,6 +205,57 @@ pub unsafe extern "C" fn PyDictProxy_New(mapping: *mut PyObject) -> *mut PyObjec }) } +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyDescr_NewMethod( + typ: *mut PyTypeObject, + method: *mut PyMethodDef, +) -> *mut PyObject { + with_vm(|vm| { + let method = build_method_def(vm, unsafe { &*method }, true)?; + Ok(method.build_method(unsafe { &*typ }, vm)) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyDescr_NewClassMethod( + typ: *mut PyTypeObject, + method: *mut PyMethodDef, +) -> *mut PyObject { + with_vm(|vm| { + let method = build_method_def(vm, unsafe { &*method }, true)?; + Ok(method.build_method(unsafe { &*typ }, vm)) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyDescr_NewGetSet( + typ: *mut PyTypeObject, + getset: *mut PyGetSetDef, +) -> *mut PyObject { + with_vm(|vm| unsafe { &*getset }.build(unsafe { &*typ }, vm)) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyDescr_NewMember( + typ: *mut PyTypeObject, + member: *mut PyMemberDef, +) -> *mut PyObject { + with_vm(|vm| Ok(unsafe { &*member }.build(unsafe { &*typ }, vm))) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyWrapper_New(descr: *mut PyObject, obj: *mut PyObject) -> *mut PyObject { + with_vm(|vm| { + let descr = unsafe { &*descr }; + let obj = unsafe { &*obj }; + vm.call_special_method( + descr, + vm.ctx.names.__get__, + (obj.to_owned(), obj.class().to_owned()), + ) + }) +} + #[cfg(test)] mod tests { use pyo3::prelude::*; diff --git a/crates/vm/src/builtins/mod.rs b/crates/vm/src/builtins/mod.rs index ffc01b00f29..f08a2b46721 100644 --- a/crates/vm/src/builtins/mod.rs +++ b/crates/vm/src/builtins/mod.rs @@ -99,6 +99,10 @@ pub use zip::PyZip; pub(crate) mod union_; pub use union_::{PyUnion, make_union}; pub(crate) mod descriptor; +pub use descriptor::{ + MemberGetter, MemberKind, MemberSetter, PyDescriptorOwned, PyMemberDef as DescriptorMemberDef, + PyMemberDescriptor, +}; pub use float::float_from_string as parse_float_from_string; pub use float::try_to_bigint as try_f64_to_bigint;