pymethods: support buffer protocol

This commit is contained in:
David Hewitt 2021-12-21 07:01:11 +00:00
parent 2503a2dd5e
commit cf965155f4
15 changed files with 290 additions and 136 deletions

View File

@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- All PyO3 proc-macros except the deprecated `#[pyproto]` now accept a supplemental attribute `#[pyo3(crate = "some::path")]` that specifies - All PyO3 proc-macros except the deprecated `#[pyproto]` now accept a supplemental attribute `#[pyo3(crate = "some::path")]` that specifies
where to find the `pyo3` crate, in case it has been renamed or is re-exported and not found at the crate root. [#2022](https://github.com/PyO3/pyo3/pull/2022) where to find the `pyo3` crate, in case it has been renamed or is re-exported and not found at the crate root. [#2022](https://github.com/PyO3/pyo3/pull/2022)
- Expose `pyo3-build-config` APIs for cross-compiling and Python configuration discovery for use in other projects. [#1996](https://github.com/PyO3/pyo3/pull/1996) - Expose `pyo3-build-config` APIs for cross-compiling and Python configuration discovery for use in other projects. [#1996](https://github.com/PyO3/pyo3/pull/1996)
- Add buffer magic methods `__getbuffer__` and `__releasebuffer__` to `#[pymethods]`. [#2067](https://github.com/PyO3/pyo3/pull/2067)
- Accept paths in `wrap_pyfunction` and `wrap_pymodule`. [#2081](https://github.com/PyO3/pyo3/pull/2081) - Accept paths in `wrap_pyfunction` and `wrap_pymodule`. [#2081](https://github.com/PyO3/pyo3/pull/2081)
### Changed ### Changed

View File

@ -899,12 +899,6 @@ impl pyo3::class::impl_::PyClassImpl for MyClass {
visitor(collector.buffer_protocol_slots()); visitor(collector.buffer_protocol_slots());
visitor(collector.methods_protocol_slots()); visitor(collector.methods_protocol_slots());
} }
fn get_buffer() -> Option<&'static pyo3::class::impl_::PyBufferProcs> {
use pyo3::class::impl_::*;
let collector = PyClassImplCollector::<Self>::new();
collector.buffer_procs()
}
} }
# Python::with_gil(|py| { # Python::with_gil(|py| {
# let cls = py.get_type::<MyClass>(); # let cls = py.get_type::<MyClass>();

View File

@ -175,7 +175,8 @@ TODO; see [#1884](https://github.com/PyO3/pyo3/issues/1884)
#### Buffer objects #### Buffer objects
TODO; see [#1884](https://github.com/PyO3/pyo3/issues/1884) - `__getbuffer__(<self>, *mut ffi::Py_buffer, flags) -> ()`
- `__releasebuffer__(<self>, *mut ffi::Py_buffer)` (no return value, not even `PyResult`)
#### Garbage Collector Integration #### Garbage Collector Integration

View File

@ -229,6 +229,7 @@ pub struct FnSpec<'a> {
pub convention: CallingConvention, pub convention: CallingConvention,
pub text_signature: Option<TextSignatureAttribute>, pub text_signature: Option<TextSignatureAttribute>,
pub krate: syn::Path, pub krate: syn::Path,
pub unsafety: Option<syn::Token![unsafe]>,
} }
pub fn get_return_info(output: &syn::ReturnType) -> syn::Type { pub fn get_return_info(output: &syn::ReturnType) -> syn::Type {
@ -316,6 +317,7 @@ impl<'a> FnSpec<'a> {
deprecations, deprecations,
text_signature, text_signature,
krate, krate,
unsafety: sig.unsafety,
}) })
} }

View File

@ -847,12 +847,8 @@ impl<'a> PyClassImplsBuilder<'a> {
#methods_protos #methods_protos
} }
fn get_buffer() -> ::std::option::Option<&'static _pyo3::class::impl_::PyBufferProcs> {
use _pyo3::class::impl_::*;
let collector = PyClassImplCollector::<Self>::new();
collector.buffer_procs()
}
#dict_offset #dict_offset
#weaklist_offset #weaklist_offset
} }

View File

@ -431,6 +431,7 @@ pub fn impl_wrap_pyfunction(
deprecations: options.deprecations, deprecations: options.deprecations,
text_signature: options.text_signature, text_signature: options.text_signature,
krate: krate.clone(), krate: krate.clone(),
unsafety: func.sig.unsafety,
}; };
let wrapper_ident = format_ident!("__pyo3_raw_{}", spec.name); let wrapper_ident = format_ident!("__pyo3_raw_{}", spec.name);

View File

@ -103,7 +103,7 @@ pub fn gen_py_method(
ensure_no_forbidden_protocol_attributes(spec, &method.method_name)?; ensure_no_forbidden_protocol_attributes(spec, &method.method_name)?;
match proto_kind { match proto_kind {
PyMethodProtoKind::Slot(slot_def) => { PyMethodProtoKind::Slot(slot_def) => {
let slot = slot_def.generate_type_slot(cls, spec)?; let slot = slot_def.generate_type_slot(cls, spec, &method.method_name)?;
GeneratedPyMethod::Proto(slot) GeneratedPyMethod::Proto(slot)
} }
PyMethodProtoKind::Call => { PyMethodProtoKind::Call => {
@ -556,6 +556,14 @@ const __IOR__: SlotDef = SlotDef::new("Py_nb_inplace_or", "binaryfunc")
.arguments(&[Ty::Object]) .arguments(&[Ty::Object])
.extract_error_mode(ExtractErrorMode::NotImplemented) .extract_error_mode(ExtractErrorMode::NotImplemented)
.return_self(); .return_self();
const __GETBUFFER__: SlotDef = SlotDef::new("Py_bf_getbuffer", "getbufferproc")
.arguments(&[Ty::PyBuffer, Ty::Int])
.ret_ty(Ty::Int)
.require_unsafe();
const __RELEASEBUFFER__: SlotDef = SlotDef::new("Py_bf_releasebuffer", "releasebufferproc")
.arguments(&[Ty::PyBuffer])
.ret_ty(Ty::Void)
.require_unsafe();
fn pyproto(method_name: &str) -> Option<&'static SlotDef> { fn pyproto(method_name: &str) -> Option<&'static SlotDef> {
match method_name { match method_name {
@ -594,6 +602,8 @@ fn pyproto(method_name: &str) -> Option<&'static SlotDef> {
"__iand__" => Some(&__IAND__), "__iand__" => Some(&__IAND__),
"__ixor__" => Some(&__IXOR__), "__ixor__" => Some(&__IXOR__),
"__ior__" => Some(&__IOR__), "__ior__" => Some(&__IOR__),
"__getbuffer__" => Some(&__GETBUFFER__),
"__releasebuffer__" => Some(&__RELEASEBUFFER__),
_ => None, _ => None,
} }
} }
@ -608,6 +618,7 @@ enum Ty {
PyHashT, PyHashT,
PySsizeT, PySsizeT,
Void, Void,
PyBuffer,
} }
impl Ty { impl Ty {
@ -619,6 +630,7 @@ impl Ty {
Ty::PyHashT => quote! { _pyo3::ffi::Py_hash_t }, Ty::PyHashT => quote! { _pyo3::ffi::Py_hash_t },
Ty::PySsizeT => quote! { _pyo3::ffi::Py_ssize_t }, Ty::PySsizeT => quote! { _pyo3::ffi::Py_ssize_t },
Ty::Void => quote! { () }, Ty::Void => quote! { () },
Ty::PyBuffer => quote! { *mut _pyo3::ffi::Py_buffer },
} }
} }
@ -680,7 +692,8 @@ impl Ty {
let #ident = #extract; let #ident = #extract;
} }
} }
Ty::Int | Ty::PyHashT | Ty::PySsizeT | Ty::Void => todo!(), // Just pass other types through unmodified
Ty::PyBuffer | Ty::Int | Ty::PyHashT | Ty::PySsizeT | Ty::Void => quote! {},
} }
} }
} }
@ -752,6 +765,7 @@ struct SlotDef {
before_call_method: Option<TokenGenerator>, before_call_method: Option<TokenGenerator>,
extract_error_mode: ExtractErrorMode, extract_error_mode: ExtractErrorMode,
return_mode: Option<ReturnMode>, return_mode: Option<ReturnMode>,
require_unsafe: bool,
} }
const NO_ARGUMENTS: &[Ty] = &[]; const NO_ARGUMENTS: &[Ty] = &[];
@ -766,6 +780,7 @@ impl SlotDef {
before_call_method: None, before_call_method: None,
extract_error_mode: ExtractErrorMode::Raise, extract_error_mode: ExtractErrorMode::Raise,
return_mode: None, return_mode: None,
require_unsafe: false,
} }
} }
@ -799,7 +814,17 @@ impl SlotDef {
self self
} }
fn generate_type_slot(&self, cls: &syn::Type, spec: &FnSpec) -> Result<TokenStream> { const fn require_unsafe(mut self) -> Self {
self.require_unsafe = true;
self
}
fn generate_type_slot(
&self,
cls: &syn::Type,
spec: &FnSpec,
method_name: &str,
) -> Result<TokenStream> {
let SlotDef { let SlotDef {
slot, slot,
func_ty, func_ty,
@ -808,7 +833,14 @@ impl SlotDef {
extract_error_mode, extract_error_mode,
ret_ty, ret_ty,
return_mode, return_mode,
require_unsafe,
} = self; } = self;
if *require_unsafe {
ensure_spanned!(
spec.unsafety.is_some(),
spec.name.span() => format!("`{}` must be `unsafe fn`", method_name)
);
}
let py = syn::Ident::new("_py", Span::call_site()); let py = syn::Ident::new("_py", Span::call_site());
let method_arguments = generate_method_arguments(arguments); let method_arguments = generate_method_arguments(arguments);
let ret_ty = ret_ty.ffi_type(); let ret_ty = ret_ty.ffi_type();

View File

@ -134,31 +134,6 @@ fn impl_proto_methods(
let slots_trait = proto.slots_trait(); let slots_trait = proto.slots_trait();
let slots_trait_slots = proto.slots_trait_slots(); let slots_trait_slots = proto.slots_trait_slots();
let mut maybe_buffer_methods = None;
let build_config = pyo3_build_config::get();
const PY39: pyo3_build_config::PythonVersion =
pyo3_build_config::PythonVersion { major: 3, minor: 9 };
if build_config.version <= PY39 && proto.name == "Buffer" {
maybe_buffer_methods = Some(quote! {
impl _pyo3::class::impl_::PyBufferProtocolProcs<#ty>
for _pyo3::class::impl_::PyClassImplCollector<#ty>
{
fn buffer_procs(
self
) -> ::std::option::Option<&'static _pyo3::class::impl_::PyBufferProcs> {
static PROCS: _pyo3::class::impl_::PyBufferProcs
= _pyo3::class::impl_::PyBufferProcs {
bf_getbuffer: ::std::option::Option::Some(_pyo3::class::buffer::getbuffer::<#ty>),
bf_releasebuffer: ::std::option::Option::Some(_pyo3::class::buffer::releasebuffer::<#ty>),
};
::std::option::Option::Some(&PROCS)
}
}
});
}
let mut tokens = proto let mut tokens = proto
.slot_defs(method_names) .slot_defs(method_names)
.map(|def| { .map(|def| {
@ -178,8 +153,6 @@ fn impl_proto_methods(
} }
quote! { quote! {
#maybe_buffer_methods
impl _pyo3::class::impl_::#slots_trait<#ty> impl _pyo3::class::impl_::#slots_trait<#ty>
for _pyo3::class::impl_::PyClassImplCollector<#ty> for _pyo3::class::impl_::PyClassImplCollector<#ty>
{ {

View File

@ -85,9 +85,6 @@ pub trait PyClassImpl: Sized {
fn get_free() -> Option<ffi::freefunc> { fn get_free() -> Option<ffi::freefunc> {
None None
} }
fn get_buffer() -> Option<&'static PyBufferProcs> {
None
}
#[inline] #[inline]
fn dict_offset() -> Option<ffi::Py_ssize_t> { fn dict_offset() -> Option<ffi::Py_ssize_t> {
None None
@ -685,25 +682,6 @@ methods_trait!(PyDescrProtocolMethods, descr_protocol_methods);
methods_trait!(PyMappingProtocolMethods, mapping_protocol_methods); methods_trait!(PyMappingProtocolMethods, mapping_protocol_methods);
methods_trait!(PyNumberProtocolMethods, number_protocol_methods); methods_trait!(PyNumberProtocolMethods, number_protocol_methods);
// On Python < 3.9 setting the buffer protocol using slots doesn't work, so these procs are used
// on those versions to set the slots manually (on the limited API).
#[cfg(not(Py_LIMITED_API))]
pub use ffi::PyBufferProcs;
#[cfg(Py_LIMITED_API)]
pub struct PyBufferProcs;
pub trait PyBufferProtocolProcs<T> {
fn buffer_procs(self) -> Option<&'static PyBufferProcs>;
}
impl<T> PyBufferProtocolProcs<T> for &'_ PyClassImplCollector<T> {
fn buffer_procs(self) -> Option<&'static PyBufferProcs> {
None
}
}
// Thread checkers // Thread checkers
#[doc(hidden)] #[doc(hidden)]

View File

@ -1,6 +1,6 @@
//! `PyClass` and related traits. //! `PyClass` and related traits.
use crate::{ use crate::{
class::impl_::{fallback_new, tp_dealloc, PyBufferProcs, PyClassImpl}, class::impl_::{fallback_new, tp_dealloc, PyClassImpl},
ffi, ffi,
impl_::pyclass::{PyClassDict, PyClassWeakRef}, impl_::pyclass::{PyClassDict, PyClassWeakRef},
PyCell, PyErr, PyMethodDefType, PyNativeType, PyResult, PyTypeInfo, Python, PyCell, PyErr, PyMethodDefType, PyNativeType, PyResult, PyTypeInfo, Python,
@ -55,7 +55,6 @@ where
&T::for_each_proto_slot, &T::for_each_proto_slot,
T::IS_GC, T::IS_GC,
T::IS_BASETYPE, T::IS_BASETYPE,
T::get_buffer(),
) )
} { } {
Ok(type_object) => type_object, Ok(type_object) => type_object,
@ -81,7 +80,6 @@ unsafe fn create_type_object_impl(
for_each_proto_slot: &dyn Fn(&mut dyn FnMut(&[ffi::PyType_Slot])), for_each_proto_slot: &dyn Fn(&mut dyn FnMut(&[ffi::PyType_Slot])),
is_gc: bool, is_gc: bool,
is_basetype: bool, is_basetype: bool,
buffer_procs: Option<&PyBufferProcs>,
) -> PyResult<*mut ffi::PyTypeObject> { ) -> PyResult<*mut ffi::PyTypeObject> {
let mut slots = Vec::new(); let mut slots = Vec::new();
@ -130,10 +128,26 @@ unsafe fn create_type_object_impl(
// protocol methods // protocol methods
let mut has_gc_methods = false; let mut has_gc_methods = false;
// Before Python 3.9, need to patch in buffer methods manually (they don't work in slots)
#[cfg(all(not(Py_3_9), not(Py_LIMITED_API)))]
let mut buffer_procs: ffi::PyBufferProcs = Default::default();
for_each_proto_slot(&mut |proto_slots| { for_each_proto_slot(&mut |proto_slots| {
has_gc_methods |= proto_slots for slot in proto_slots {
.iter() has_gc_methods |= slot.slot == ffi::Py_tp_clear || slot.slot == ffi::Py_tp_traverse;
.any(|slot| slot.slot == ffi::Py_tp_clear || slot.slot == ffi::Py_tp_traverse);
#[cfg(all(not(Py_3_9), not(Py_LIMITED_API)))]
if slot.slot == ffi::Py_bf_getbuffer {
// Safety: slot.pfunc is a valid function pointer
buffer_procs.bf_getbuffer = Some(std::mem::transmute(slot.pfunc));
}
#[cfg(all(not(Py_3_9), not(Py_LIMITED_API)))]
if slot.slot == ffi::Py_bf_releasebuffer {
// Safety: slot.pfunc is a valid function pointer
buffer_procs.bf_releasebuffer = Some(std::mem::transmute(slot.pfunc));
}
}
slots.extend_from_slice(proto_slots); slots.extend_from_slice(proto_slots);
}); });
@ -153,8 +167,11 @@ unsafe fn create_type_object_impl(
tp_init_additional( tp_init_additional(
type_object as _, type_object as _,
tp_doc, tp_doc,
buffer_procs, #[cfg(all(not(Py_3_9), not(Py_LIMITED_API)))]
&buffer_procs,
#[cfg(not(Py_3_9))]
dict_offset, dict_offset,
#[cfg(not(Py_3_9))]
weaklist_offset, weaklist_offset,
); );
Ok(type_object as _) Ok(type_object as _)
@ -169,12 +186,12 @@ fn type_object_creation_failed(py: Python, e: PyErr, name: &'static str) -> ! {
/// Additional type initializations necessary before Python 3.10 /// Additional type initializations necessary before Python 3.10
#[cfg(all(not(Py_LIMITED_API), not(Py_3_10)))] #[cfg(all(not(Py_LIMITED_API), not(Py_3_10)))]
fn tp_init_additional( unsafe fn tp_init_additional(
type_object: *mut ffi::PyTypeObject, type_object: *mut ffi::PyTypeObject,
_tp_doc: &str, _tp_doc: &str,
_buffer_procs: Option<&PyBufferProcs>, #[cfg(not(Py_3_9))] buffer_procs: &ffi::PyBufferProcs,
_dict_offset: Option<ffi::Py_ssize_t>, #[cfg(not(Py_3_9))] dict_offset: Option<ffi::Py_ssize_t>,
_weaklist_offset: Option<ffi::Py_ssize_t>, #[cfg(not(Py_3_9))] weaklist_offset: Option<ffi::Py_ssize_t>,
) { ) {
// Just patch the type objects for the things there's no // Just patch the type objects for the things there's no
// PyType_FromSpec API for... there's no reason this should work, // PyType_FromSpec API for... there's no reason this should work,
@ -184,16 +201,14 @@ fn tp_init_additional(
#[cfg(all(not(PyPy), not(Py_3_10)))] #[cfg(all(not(PyPy), not(Py_3_10)))]
{ {
if _tp_doc != "\0" { if _tp_doc != "\0" {
unsafe { // Until CPython 3.10, tp_doc was treated specially for
// Until CPython 3.10, tp_doc was treated specially for // heap-types, and it removed the text_signature value from it.
// heap-types, and it removed the text_signature value from it. // We go in after the fact and replace tp_doc with something
// We go in after the fact and replace tp_doc with something // that _does_ include the text_signature value!
// that _does_ include the text_signature value! ffi::PyObject_Free((*type_object).tp_doc as _);
ffi::PyObject_Free((*type_object).tp_doc as _); let data = ffi::PyObject_Malloc(_tp_doc.len());
let data = ffi::PyObject_Malloc(_tp_doc.len()); data.copy_from(_tp_doc.as_ptr() as _, _tp_doc.len());
data.copy_from(_tp_doc.as_ptr() as _, _tp_doc.len()); (*type_object).tp_doc = data as _;
(*type_object).tp_doc = data as _;
}
} }
} }
@ -201,23 +216,15 @@ fn tp_init_additional(
// Python 3.9, so on older versions we must manually fixup the type object. // Python 3.9, so on older versions we must manually fixup the type object.
#[cfg(not(Py_3_9))] #[cfg(not(Py_3_9))]
{ {
if let Some(buffer) = _buffer_procs { (*(*type_object).tp_as_buffer).bf_getbuffer = buffer_procs.bf_getbuffer;
unsafe { (*(*type_object).tp_as_buffer).bf_releasebuffer = buffer_procs.bf_releasebuffer;
(*(*type_object).tp_as_buffer).bf_getbuffer = buffer.bf_getbuffer;
(*(*type_object).tp_as_buffer).bf_releasebuffer = buffer.bf_releasebuffer; if let Some(dict_offset) = dict_offset {
} (*type_object).tp_dictoffset = dict_offset;
} }
if let Some(dict_offset) = _dict_offset { if let Some(weaklist_offset) = weaklist_offset {
unsafe { (*type_object).tp_weaklistoffset = weaklist_offset;
(*type_object).tp_dictoffset = dict_offset;
}
}
if let Some(weaklist_offset) = _weaklist_offset {
unsafe {
(*type_object).tp_weaklistoffset = weaklist_offset;
}
} }
} }
} }
@ -226,9 +233,9 @@ fn tp_init_additional(
fn tp_init_additional( fn tp_init_additional(
_type_object: *mut ffi::PyTypeObject, _type_object: *mut ffi::PyTypeObject,
_tp_doc: &str, _tp_doc: &str,
_buffer_procs: Option<&PyBufferProcs>, #[cfg(all(not(Py_3_9), not(Py_LIMITED_API)))] _buffer_procs: &ffi::PyBufferProcs,
_dict_offset: Option<ffi::Py_ssize_t>, #[cfg(not(Py_3_9))] _dict_offset: Option<ffi::Py_ssize_t>,
_weaklist_offset: Option<ffi::Py_ssize_t>, #[cfg(not(Py_3_9))] _weaklist_offset: Option<ffi::Py_ssize_t>,
) { ) {
} }
@ -290,6 +297,7 @@ fn py_class_method_defs(
}); });
if !defs.is_empty() { if !defs.is_empty() {
// Safety: Python expects a zeroed entry to mark the end of the defs
defs.push(unsafe { std::mem::zeroed() }); defs.push(unsafe { std::mem::zeroed() });
} }
@ -329,6 +337,7 @@ fn py_class_members(
} }
if !members.is_empty() { if !members.is_empty() {
// Safety: Python expects a zeroed entry to mark the end of the defs
members.push(unsafe { std::mem::zeroed() }); members.push(unsafe { std::mem::zeroed() });
} }
@ -370,6 +379,7 @@ fn py_class_properties(
push_dict_getset(&mut props, is_dummy); push_dict_getset(&mut props, is_dummy);
if !props.is_empty() { if !props.is_empty() {
// Safety: Python expects a zeroed entry to mark the end of the defs
props.push(unsafe { std::mem::zeroed() }); props.push(unsafe { std::mem::zeroed() });
} }
props props

View File

@ -2,13 +2,12 @@
#![cfg(not(Py_LIMITED_API))] #![cfg(not(Py_LIMITED_API))]
use pyo3::buffer::PyBuffer; use pyo3::buffer::PyBuffer;
use pyo3::class::PyBufferProtocol;
use pyo3::exceptions::PyBufferError; use pyo3::exceptions::PyBufferError;
use pyo3::ffi; use pyo3::ffi;
use pyo3::prelude::*; use pyo3::prelude::*;
use pyo3::types::IntoPyDict; use pyo3::types::IntoPyDict;
use pyo3::AsPyPointer; use pyo3::AsPyPointer;
use std::ffi::CStr; use std::ffi::CString;
use std::os::raw::{c_int, c_void}; use std::os::raw::{c_int, c_void};
use std::ptr; use std::ptr;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
@ -22,9 +21,13 @@ struct TestBufferClass {
drop_called: Arc<AtomicBool>, drop_called: Arc<AtomicBool>,
} }
#[pyproto] #[pymethods]
impl PyBufferProtocol for TestBufferClass { impl TestBufferClass {
fn bf_getbuffer(slf: PyRefMut<Self>, view: *mut ffi::Py_buffer, flags: c_int) -> PyResult<()> { unsafe fn __getbuffer__(
mut slf: PyRefMut<Self>,
view: *mut ffi::Py_buffer,
flags: c_int,
) -> PyResult<()> {
if view.is_null() { if view.is_null() {
return Err(PyBufferError::new_err("View is null")); return Err(PyBufferError::new_err("View is null"));
} }
@ -33,43 +36,43 @@ impl PyBufferProtocol for TestBufferClass {
return Err(PyBufferError::new_err("Object is not writable")); return Err(PyBufferError::new_err("Object is not writable"));
} }
unsafe { (*view).obj = ffi::_Py_NewRef(slf.as_ptr());
(*view).obj = ffi::_Py_NewRef(slf.as_ptr());
}
let bytes = &slf.vec; (*view).buf = slf.vec.as_mut_ptr() as *mut c_void;
(*view).len = slf.vec.len() as isize;
(*view).readonly = 1;
(*view).itemsize = 1;
unsafe { (*view).format = if (flags & ffi::PyBUF_FORMAT) == ffi::PyBUF_FORMAT {
(*view).buf = bytes.as_ptr() as *mut c_void; let msg = CString::new("B").unwrap();
(*view).len = bytes.len() as isize; msg.into_raw()
(*view).readonly = 1; } else {
(*view).itemsize = 1; ptr::null_mut()
};
(*view).format = ptr::null_mut(); (*view).ndim = 1;
if (flags & ffi::PyBUF_FORMAT) == ffi::PyBUF_FORMAT { (*view).shape = if (flags & ffi::PyBUF_ND) == ffi::PyBUF_ND {
let msg = CStr::from_bytes_with_nul(b"B\0").unwrap(); &mut (*view).len
(*view).format = msg.as_ptr() as *mut _; } else {
} ptr::null_mut()
};
(*view).ndim = 1; (*view).strides = if (flags & ffi::PyBUF_STRIDES) == ffi::PyBUF_STRIDES {
(*view).shape = ptr::null_mut(); &mut (*view).itemsize
if (flags & ffi::PyBUF_ND) == ffi::PyBUF_ND { } else {
(*view).shape = &mut (*view).len; ptr::null_mut()
} };
(*view).strides = ptr::null_mut(); (*view).suboffsets = ptr::null_mut();
if (flags & ffi::PyBUF_STRIDES) == ffi::PyBUF_STRIDES { (*view).internal = ptr::null_mut();
(*view).strides = &mut (*view).itemsize;
}
(*view).suboffsets = ptr::null_mut();
(*view).internal = ptr::null_mut();
}
Ok(()) Ok(())
} }
fn bf_releasebuffer(_slf: PyRefMut<Self>, _view: *mut ffi::Py_buffer) {} unsafe fn __releasebuffer__(&self, view: *mut ffi::Py_buffer) {
// Release memory held by the format string
drop(CString::from_raw((*view).format));
}
} }
impl Drop for TestBufferClass { impl Drop for TestBufferClass {

View File

@ -0,0 +1,132 @@
#![cfg(feature = "macros")]
#![cfg(not(Py_LIMITED_API))]
use pyo3::buffer::PyBuffer;
use pyo3::class::PyBufferProtocol;
use pyo3::exceptions::PyBufferError;
use pyo3::ffi;
use pyo3::prelude::*;
use pyo3::types::IntoPyDict;
use pyo3::AsPyPointer;
use std::ffi::CStr;
use std::os::raw::{c_int, c_void};
use std::ptr;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
mod common;
#[pyclass]
struct TestBufferClass {
vec: Vec<u8>,
drop_called: Arc<AtomicBool>,
}
#[pyproto]
impl PyBufferProtocol for TestBufferClass {
fn bf_getbuffer(slf: PyRefMut<Self>, view: *mut ffi::Py_buffer, flags: c_int) -> PyResult<()> {
if view.is_null() {
return Err(PyBufferError::new_err("View is null"));
}
if (flags & ffi::PyBUF_WRITABLE) == ffi::PyBUF_WRITABLE {
return Err(PyBufferError::new_err("Object is not writable"));
}
unsafe {
(*view).obj = ffi::_Py_NewRef(slf.as_ptr());
}
let bytes = &slf.vec;
unsafe {
(*view).buf = bytes.as_ptr() as *mut c_void;
(*view).len = bytes.len() as isize;
(*view).readonly = 1;
(*view).itemsize = 1;
(*view).format = ptr::null_mut();
if (flags & ffi::PyBUF_FORMAT) == ffi::PyBUF_FORMAT {
let msg = CStr::from_bytes_with_nul(b"B\0").unwrap();
(*view).format = msg.as_ptr() as *mut _;
}
(*view).ndim = 1;
(*view).shape = ptr::null_mut();
if (flags & ffi::PyBUF_ND) == ffi::PyBUF_ND {
(*view).shape = &mut (*view).len;
}
(*view).strides = ptr::null_mut();
if (flags & ffi::PyBUF_STRIDES) == ffi::PyBUF_STRIDES {
(*view).strides = &mut (*view).itemsize;
}
(*view).suboffsets = ptr::null_mut();
(*view).internal = ptr::null_mut();
}
Ok(())
}
fn bf_releasebuffer(_slf: PyRefMut<Self>, _view: *mut ffi::Py_buffer) {}
}
impl Drop for TestBufferClass {
fn drop(&mut self) {
print!("dropped");
self.drop_called.store(true, Ordering::Relaxed);
}
}
#[test]
fn test_buffer() {
let drop_called = Arc::new(AtomicBool::new(false));
{
let gil = Python::acquire_gil();
let py = gil.python();
let instance = Py::new(
py,
TestBufferClass {
vec: vec![b' ', b'2', b'3'],
drop_called: drop_called.clone(),
},
)
.unwrap();
let env = [("ob", instance)].into_py_dict(py);
py_assert!(py, *env, "bytes(ob) == b' 23'");
}
assert!(drop_called.load(Ordering::Relaxed));
}
#[test]
fn test_buffer_referenced() {
let drop_called = Arc::new(AtomicBool::new(false));
let buf = {
let input = vec![b' ', b'2', b'3'];
let gil = Python::acquire_gil();
let py = gil.python();
let instance: PyObject = TestBufferClass {
vec: input.clone(),
drop_called: drop_called.clone(),
}
.into_py(py);
let buf = PyBuffer::<u8>::get(instance.as_ref(py)).unwrap();
assert_eq!(buf.to_vec(py).unwrap(), input);
drop(instance);
buf
};
assert!(!drop_called.load(Ordering::Relaxed));
{
let _py = Python::acquire_gil().python();
drop(buf);
}
assert!(drop_called.load(Ordering::Relaxed));
}

View File

@ -25,6 +25,8 @@ fn _test_compile_errors() {
t.compile_fail("tests/ui/invalid_pyclass_item.rs"); t.compile_fail("tests/ui/invalid_pyclass_item.rs");
t.compile_fail("tests/ui/invalid_pyfunctions.rs"); t.compile_fail("tests/ui/invalid_pyfunctions.rs");
t.compile_fail("tests/ui/invalid_pymethods.rs"); t.compile_fail("tests/ui/invalid_pymethods.rs");
#[cfg(not(Py_LIMITED_API))]
t.compile_fail("tests/ui/invalid_pymethods_buffer.rs");
t.compile_fail("tests/ui/invalid_pymethod_names.rs"); t.compile_fail("tests/ui/invalid_pymethod_names.rs");
t.compile_fail("tests/ui/invalid_pymodule_args.rs"); t.compile_fail("tests/ui/invalid_pymodule_args.rs");
t.compile_fail("tests/ui/missing_clone.rs"); t.compile_fail("tests/ui/missing_clone.rs");

View File

@ -0,0 +1,18 @@
use pyo3::prelude::*;
#[pyclass]
struct MyClass {}
#[pymethods]
impl MyClass {
#[pyo3(name = "__getbuffer__")]
fn getbuffer_must_be_unsafe(&self, _view: *mut pyo3::ffi::Py_buffer, _flags: std::os::raw::c_int) {}
}
#[pymethods]
impl MyClass {
#[pyo3(name = "__releasebuffer__")]
fn releasebuffer_must_be_unsafe(&self, _view: *mut pyo3::ffi::Py_buffer) {}
}
fn main() {}

View File

@ -0,0 +1,11 @@
error: `__getbuffer__` must be `unsafe fn`
--> tests/ui/invalid_pymethods_buffer.rs:9:8
|
9 | fn getbuffer_must_be_unsafe(&self, _view: *mut pyo3::ffi::Py_buffer, _flags: std::os::raw::c_int) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^
error: `__releasebuffer__` must be `unsafe fn`
--> tests/ui/invalid_pymethods_buffer.rs:15:8
|
15 | fn releasebuffer_must_be_unsafe(&self, _view: *mut pyo3::ffi::Py_buffer) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^