#![deny(clippy::undocumented_unsafe_blocks)]
use crate::ffi_ptr_ext::FfiPtrExt;
use crate::py_result_ext::PyResultExt;
use crate::{ffi, PyAny};
use crate::{Bound, Python};
use crate::{PyErr, PyResult};
use std::ffi::{c_char, c_int, c_void};
use std::ffi::{CStr, CString};
use std::mem::offset_of;
use std::ptr::{self, NonNull};
#[repr(transparent)]
pub struct PyCapsule(PyAny);
pyobject_native_type_core!(PyCapsule, pyobject_native_static_type_object!(ffi::PyCapsule_Type), "types", "CapsuleType", #checkfunction=ffi::PyCapsule_CheckExact);
impl PyCapsule {
pub fn new<T: 'static + Send + AssertNotZeroSized>(
py: Python<'_>,
value: T,
name: Option<CString>,
) -> PyResult<Bound<'_, Self>> {
Self::new_with_destructor(py, value, name, |_, _| {})
}
pub fn new_with_destructor<
T: 'static + Send + AssertNotZeroSized,
F: FnOnce(T, *mut c_void) + Send,
>(
py: Python<'_>,
value: T,
name: Option<CString>,
destructor: F,
) -> PyResult<Bound<'_, Self>> {
AssertNotZeroSized::assert_not_zero_sized(&value);
debug_assert_eq!(offset_of!(CapsuleContents::<T, F>, value), 0);
let name_ptr = name.as_ref().map_or(std::ptr::null(), |name| name.as_ptr());
let val = Box::into_raw(Box::new(CapsuleContents {
value,
destructor,
name,
}));
unsafe {
ffi::PyCapsule_New(val.cast(), name_ptr, Some(capsule_destructor::<T, F>))
.assume_owned_or_err(py)
.cast_into_unchecked()
}
}
pub unsafe fn new_with_pointer<'py>(
py: Python<'py>,
pointer: NonNull<c_void>,
name: &'static CStr,
) -> PyResult<Bound<'py, Self>> {
unsafe { Self::new_with_pointer_and_destructor(py, pointer, name, None) }
}
pub unsafe fn new_with_pointer_and_destructor<'py>(
py: Python<'py>,
pointer: NonNull<c_void>,
name: &'static CStr,
destructor: Option<ffi::PyCapsule_Destructor>,
) -> PyResult<Bound<'py, Self>> {
let name_ptr = name.as_ptr();
unsafe {
ffi::PyCapsule_New(pointer.as_ptr(), name_ptr, destructor)
.assume_owned_or_err(py)
.cast_into_unchecked()
}
}
pub unsafe fn import<'py, T>(py: Python<'py>, name: &CStr) -> PyResult<&'py T> {
let ptr = unsafe { ffi::PyCapsule_Import(name.as_ptr(), false as c_int) };
if ptr.is_null() {
Err(PyErr::fetch(py))
} else {
Ok(unsafe { &*ptr.cast::<T>() })
}
}
}
#[doc(alias = "PyCapsule")]
pub trait PyCapsuleMethods<'py>: crate::sealed::Sealed {
fn set_context(&self, context: *mut c_void) -> PyResult<()>;
fn context(&self) -> PyResult<*mut c_void>;
#[deprecated(since = "0.27.0", note = "to be removed, see `pointer_checked()`")]
unsafe fn reference<T>(&self) -> &T;
#[deprecated(since = "0.27.0", note = "use `pointer_checked()` instead")]
fn pointer(&self) -> *mut c_void;
fn pointer_checked(&self, name: Option<&CStr>) -> PyResult<NonNull<c_void>>;
#[deprecated(since = "0.27.0", note = "use `is_valid_checked()` instead")]
fn is_valid(&self) -> bool;
fn is_valid_checked(&self, name: Option<&CStr>) -> bool;
fn name(&self) -> PyResult<Option<CapsuleName>>;
}
impl<'py> PyCapsuleMethods<'py> for Bound<'py, PyCapsule> {
#[allow(clippy::not_unsafe_ptr_arg_deref)]
fn set_context(&self, context: *mut c_void) -> PyResult<()> {
let result = unsafe { ffi::PyCapsule_SetContext(self.as_ptr(), context) };
if result != 0 {
Err(PyErr::fetch(self.py()))
} else {
Ok(())
}
}
fn context(&self) -> PyResult<*mut c_void> {
let ctx = unsafe { ffi::PyCapsule_GetContext(self.as_ptr()) };
if ctx.is_null() {
ensure_no_error(self.py())?
}
Ok(ctx)
}
#[allow(deprecated)]
unsafe fn reference<T>(&self) -> &T {
unsafe { &*self.pointer().cast() }
}
fn pointer(&self) -> *mut c_void {
unsafe {
let ptr = ffi::PyCapsule_GetPointer(self.as_ptr(), name_ptr_ignore_error(self));
if ptr.is_null() {
ffi::PyErr_Clear();
}
ptr
}
}
fn pointer_checked(&self, name: Option<&CStr>) -> PyResult<NonNull<c_void>> {
let ptr = unsafe { ffi::PyCapsule_GetPointer(self.as_ptr(), name_ptr(name)) };
NonNull::new(ptr).ok_or_else(|| PyErr::fetch(self.py()))
}
fn is_valid(&self) -> bool {
let r = unsafe { ffi::PyCapsule_IsValid(self.as_ptr(), name_ptr_ignore_error(self)) };
r != 0
}
fn is_valid_checked(&self, name: Option<&CStr>) -> bool {
let r = unsafe { ffi::PyCapsule_IsValid(self.as_ptr(), name_ptr(name)) };
r != 0
}
fn name(&self) -> PyResult<Option<CapsuleName>> {
let name = unsafe { ffi::PyCapsule_GetName(self.as_ptr()) };
match NonNull::new(name.cast_mut()) {
Some(name) => Ok(Some(CapsuleName { ptr: name })),
None => {
ensure_no_error(self.py())?;
Ok(None)
}
}
}
}
#[derive(Clone, Copy)]
pub struct CapsuleName {
ptr: NonNull<c_char>,
}
impl CapsuleName {
pub unsafe fn as_cstr<'a>(self) -> &'a CStr {
unsafe { CStr::from_ptr(self.as_ptr()) }
}
pub fn as_ptr(self) -> *const c_char {
self.ptr.as_ptr().cast_const()
}
}
#[repr(C)]
struct CapsuleContents<T: 'static + Send, D: FnOnce(T, *mut c_void) + Send> {
value: T,
destructor: D,
name: Option<CString>,
}
unsafe extern "C" fn capsule_destructor<T: 'static + Send, F: FnOnce(T, *mut c_void) + Send>(
capsule: *mut ffi::PyObject,
) {
unsafe fn get_pointer_ctx(capsule: *mut ffi::PyObject) -> (*mut c_void, *mut c_void) {
let name = unsafe { ffi::PyCapsule_GetName(capsule) };
let ptr = unsafe { ffi::PyCapsule_GetPointer(capsule, name) };
let ctx = unsafe { ffi::PyCapsule_GetContext(capsule) };
(ptr, ctx)
}
let (ptr, ctx) = unsafe { get_pointer_ctx(capsule) };
let CapsuleContents::<T, F> {
value, destructor, ..
} = *unsafe { Box::from_raw(ptr.cast()) };
destructor(value, ctx);
}
#[doc(hidden)]
pub trait AssertNotZeroSized: Sized {
const _CONDITION: usize = (std::mem::size_of::<Self>() == 0) as usize;
const _CHECK: &'static str =
["PyCapsule value type T must not be zero-sized!"][Self::_CONDITION];
#[allow(path_statements, clippy::no_effect)]
fn assert_not_zero_sized(&self) {
<Self as AssertNotZeroSized>::_CHECK;
}
}
impl<T> AssertNotZeroSized for T {}
fn ensure_no_error(py: Python<'_>) -> PyResult<()> {
if let Some(err) = PyErr::take(py) {
Err(err)
} else {
Ok(())
}
}
fn name_ptr_ignore_error(slf: &Bound<'_, PyCapsule>) -> *const c_char {
let ptr = unsafe { ffi::PyCapsule_GetName(slf.as_ptr()) };
if ptr.is_null() {
unsafe { ffi::PyErr_Clear() };
}
ptr
}
fn name_ptr(name: Option<&CStr>) -> *const c_char {
match name {
Some(name) => name.as_ptr(),
None => ptr::null(),
}
}
#[cfg(test)]
mod tests {
use crate::prelude::PyModule;
use crate::types::capsule::PyCapsuleMethods;
use crate::types::module::PyModuleMethods;
use crate::{types::PyCapsule, Py, PyResult, Python};
use std::ffi::{c_void, CStr};
use std::ptr::NonNull;
use std::sync::mpsc::{channel, Sender};
const NAME: &CStr = c"foo";
#[test]
fn test_pycapsule_struct() {
#[repr(C)]
struct Foo {
pub val: u32,
}
impl Foo {
fn get_val(&self) -> u32 {
self.val
}
}
Python::attach(|py| {
let foo = Foo { val: 123 };
let cap = PyCapsule::new(py, foo, Some(NAME.to_owned())).unwrap();
assert!(cap.is_valid_checked(Some(NAME)));
let foo_capi = cap.pointer_checked(Some(NAME)).unwrap().cast::<Foo>();
assert_eq!(unsafe { foo_capi.as_ref() }.val, 123);
assert_eq!(unsafe { foo_capi.as_ref() }.get_val(), 123);
assert_eq!(
unsafe { CStr::from_ptr(cap.name().unwrap().unwrap().as_ptr()) },
NAME
);
assert_eq!(unsafe { cap.name().unwrap().unwrap().as_cstr() }, NAME)
})
}
#[test]
fn test_pycapsule_func() {
fn foo(x: u32) -> u32 {
x
}
let cap: Py<PyCapsule> = Python::attach(|py| {
let cap = PyCapsule::new(py, foo as fn(u32) -> u32, Some(NAME.to_owned())).unwrap();
cap.into()
});
Python::attach(move |py| {
let f = cap
.bind(py)
.pointer_checked(Some(NAME))
.unwrap()
.cast::<fn(u32) -> u32>();
assert_eq!(unsafe { f.as_ref() }(123), 123);
});
}
#[test]
fn test_pycapsule_context() {
Python::attach(|py| {
let cap = PyCapsule::new(py, 0, Some(NAME.to_owned())).unwrap();
let c = cap.context().unwrap();
assert!(c.is_null());
let ctx = Box::new(123_u32);
cap.set_context(Box::into_raw(ctx).cast()).unwrap();
let ctx_ptr: *mut c_void = cap.context().unwrap();
let ctx = unsafe { *Box::from_raw(ctx_ptr.cast::<u32>()) };
assert_eq!(ctx, 123);
})
}
#[test]
fn test_pycapsule_import() {
#[repr(C)]
struct Foo {
pub val: u32,
}
Python::attach(|py| {
let foo = Foo { val: 123 };
let name = c"builtins.capsule";
let capsule = PyCapsule::new(py, foo, Some(name.to_owned())).unwrap();
let module = PyModule::import(py, "builtins").unwrap();
module.add("capsule", capsule).unwrap();
let result: PyResult<&Foo> = unsafe { PyCapsule::import(py, c"builtins.non_existent") };
assert!(result.is_err());
let cap: &Foo = unsafe { PyCapsule::import(py, name) }.unwrap();
assert_eq!(cap.val, 123);
})
}
#[test]
fn test_vec_storage() {
let cap: Py<PyCapsule> = Python::attach(|py| {
let stuff: Vec<u8> = vec![1, 2, 3, 4];
let cap = PyCapsule::new(py, stuff, Some(NAME.to_owned())).unwrap();
cap.into()
});
Python::attach(move |py| {
let stuff = cap
.bind(py)
.pointer_checked(Some(NAME))
.unwrap()
.cast::<Vec<u8>>();
assert_eq!(unsafe { stuff.as_ref() }, &[1, 2, 3, 4]);
})
}
#[test]
fn test_vec_context() {
let context: Vec<u8> = vec![1, 2, 3, 4];
let cap: Py<PyCapsule> = Python::attach(|py| {
let cap = PyCapsule::new(py, 0, Some(NAME.to_owned())).unwrap();
cap.set_context(Box::into_raw(Box::new(&context)).cast())
.unwrap();
cap.into()
});
Python::attach(move |py| {
let ctx_ptr: *mut c_void = cap.bind(py).context().unwrap();
let ctx = unsafe { *Box::from_raw(ctx_ptr.cast::<&Vec<u8>>()) };
assert_eq!(ctx, &vec![1_u8, 2, 3, 4]);
})
}
#[test]
fn test_pycapsule_destructor() {
let (tx, rx) = channel::<bool>();
fn destructor(_val: u32, ctx: *mut c_void) {
assert!(!ctx.is_null());
let context = unsafe { *Box::from_raw(ctx.cast::<Sender<bool>>()) };
context.send(true).unwrap();
}
Python::attach(move |py| {
let cap =
PyCapsule::new_with_destructor(py, 0, Some(NAME.to_owned()), destructor).unwrap();
cap.set_context(Box::into_raw(Box::new(tx)).cast()).unwrap();
});
assert_eq!(rx.recv(), Ok(true));
}
#[test]
fn test_pycapsule_no_name() {
Python::attach(|py| {
let cap = PyCapsule::new(py, 0usize, None).unwrap();
assert_eq!(
unsafe { cap.pointer_checked(None).unwrap().cast::<usize>().as_ref() },
&0usize
);
assert!(cap.name().unwrap().is_none());
assert_eq!(cap.context().unwrap(), std::ptr::null_mut());
});
}
#[test]
fn test_pycapsule_new_with_pointer() {
extern "C" fn dummy_handler(_: *mut c_void) -> *mut c_void {
std::ptr::null_mut()
}
let fn_ptr =
NonNull::new(dummy_handler as *mut c_void).expect("function pointer is non-null");
Python::attach(|py| {
let capsule =
unsafe { PyCapsule::new_with_pointer(py, fn_ptr, c"test.dummy_handler") }.unwrap();
let retrieved_ptr = capsule
.pointer_checked(Some(c"test.dummy_handler"))
.unwrap();
assert_eq!(retrieved_ptr.as_ptr(), fn_ptr.as_ptr());
});
}
#[test]
fn test_pycapsule_new_with_pointer_and_destructor() {
use std::sync::mpsc::{channel, TryRecvError};
let (tx, rx) = channel::<bool>();
unsafe extern "C" fn destructor_fn(capsule: *mut crate::ffi::PyObject) {
unsafe {
let ctx = crate::ffi::PyCapsule_GetContext(capsule);
if !ctx.is_null() {
let sender: Box<Sender<bool>> = Box::from_raw(ctx.cast());
let _ = sender.send(true);
}
}
}
let dummy_ptr =
NonNull::new(0xDEADBEEF as *mut c_void).expect("function pointer is non-null");
Python::attach(|py| {
let capsule = unsafe {
PyCapsule::new_with_pointer_and_destructor(
py,
dummy_ptr,
c"test.destructor_capsule",
Some(destructor_fn),
)
}
.unwrap();
let sender_box = Box::new(tx);
capsule
.set_context(Box::into_raw(sender_box).cast())
.unwrap();
assert_eq!(rx.try_recv(), Err(TryRecvError::Empty));
});
assert_eq!(rx.recv(), Ok(true));
}
#[test]
fn test_pycapsule_pointer_checked_wrong_name() {
Python::attach(|py| {
let cap = PyCapsule::new(py, 123u32, Some(c"correct.name".to_owned())).unwrap();
let result = cap.pointer_checked(Some(c"wrong.name"));
assert!(result.is_err());
let result = cap.pointer_checked(None);
assert!(result.is_err());
});
}
#[test]
fn test_pycapsule_pointer_checked_none_vs_some() {
Python::attach(|py| {
let cap_no_name = PyCapsule::new(py, 123u32, None).unwrap();
assert!(cap_no_name.pointer_checked(None).is_ok());
let result = cap_no_name.pointer_checked(Some(c"some.name"));
assert!(result.is_err());
});
}
#[test]
fn test_pycapsule_is_valid_checked_wrong_name() {
Python::attach(|py| {
let cap = PyCapsule::new(py, 123u32, Some(c"correct.name".to_owned())).unwrap();
assert!(cap.is_valid_checked(Some(c"correct.name")));
assert!(!cap.is_valid_checked(Some(c"wrong.name")));
assert!(!cap.is_valid_checked(None));
});
}
#[test]
fn test_pycapsule_is_valid_checked_no_name() {
Python::attach(|py| {
let cap = PyCapsule::new(py, 123u32, None).unwrap();
assert!(cap.is_valid_checked(None));
assert!(!cap.is_valid_checked(Some(c"any.name")));
});
}
#[test]
fn test_pycapsule_context_on_invalid_capsule() {
Python::attach(|py| {
let cap = PyCapsule::new(py, 123u32, Some(NAME.to_owned())).unwrap();
unsafe {
crate::ffi::PyCapsule_SetPointer(cap.as_ptr(), std::ptr::null_mut());
}
let result = cap.context();
assert!(result.is_err());
});
}
#[test]
fn test_pycapsule_import_wrong_module() {
Python::attach(|py| {
let result: PyResult<&u32> =
unsafe { PyCapsule::import(py, c"nonexistent_module.capsule") };
assert!(result.is_err());
});
}
#[test]
fn test_pycapsule_import_wrong_attribute() {
Python::attach(|py| {
let cap = PyCapsule::new(py, 123u32, Some(c"builtins.test_cap".to_owned())).unwrap();
let module = crate::prelude::PyModule::import(py, "builtins").unwrap();
module.add("test_cap", cap).unwrap();
let result: PyResult<&u32> =
unsafe { PyCapsule::import(py, c"builtins.wrong_attribute") };
assert!(result.is_err());
});
}
}