Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
249 changes: 247 additions & 2 deletions crates/capi/src/descrobject.rs
Original file line number Diff line number Diff line change
@@ -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 {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is part of api... painful

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it is...

pub name: *const c_char,
pub get:
Option<unsafe extern "C" fn(slf: *mut PyObject, closure: *mut c_void) -> *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<PyType>,
vm: &VirtualMachine,
) -> PyResult<PyRef<PyGetSet>> {
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<PyObjectRef> {
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<PyObjectRef> {
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<PyObjectRef> {
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<PyObjectRef> {
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<PyType>,
vm: &VirtualMachine,
) -> PyResult<PyRef<PyMemberDescriptor>> {
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 {
Expand All @@ -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::*;
Expand Down
4 changes: 4 additions & 0 deletions crates/vm/src/builtins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading