Skip to content

Commit 71dd405

Browse files
committed
buffer: tidy up exceptions
1 parent 4713b46 commit 71dd405

2 files changed

Lines changed: 45 additions & 47 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
2626
- The `auto-initialize` feature is no longer enabled by default. [#1443](https://github.com/PyO3/pyo3/pull/1443)
2727
- Change `PyCFunction::new()` and `PyCFunction::new_with_keywords()` to take `&'static str` arguments rather than implicitly copying (and leaking) them. [#1450](https://github.com/PyO3/pyo3/pull/1450)
2828
- Deprecate `PyModule` methods `call`, `call0`, `call1` and `get`. [#1492](https://github.com/PyO3/pyo3/pull/1492)
29+
- Add length information to `PyBufferError`s raised from `PyBuffer::copy_to_slice` and `PyBuffer::copy_from_slice`. [#1534](https://github.com/PyO3/pyo3/pull/1534)
2930

3031
### Removed
3132
- Remove deprecated exception names `BaseException` etc. [#1426](https://github.com/PyO3/pyo3/pull/1426)

src/buffer.rs

Lines changed: 44 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
// DEALINGS IN THE SOFTWARE.
1818

1919
//! `PyBuffer` implementation
20-
use crate::err::{self, PyResult};
21-
use crate::{exceptions, ffi, AsPyPointer, FromPyObject, PyAny, PyNativeType, Python};
20+
use crate::{
21+
err, exceptions::PyBufferError, ffi, AsPyPointer, FromPyObject, PyAny, PyNativeType, PyResult,
22+
Python,
23+
};
2224
use std::ffi::CStr;
2325
use std::marker::PhantomData;
2426
use std::os::raw;
@@ -147,19 +149,6 @@ pub unsafe trait Element: Copy {
147149
fn is_compatible_format(format: &CStr) -> bool;
148150
}
149151

150-
fn validate(b: &ffi::Py_buffer) -> PyResult<()> {
151-
// shape and stride information must be provided when we use PyBUF_FULL_RO
152-
if b.shape.is_null() {
153-
return Err(exceptions::PyBufferError::new_err("Shape is Null"));
154-
}
155-
if b.strides.is_null() {
156-
return Err(exceptions::PyBufferError::new_err(
157-
"PyBuffer: Strides is Null",
158-
));
159-
}
160-
Ok(())
161-
}
162-
163152
impl<'source, T: Element> FromPyObject<'source> for PyBuffer<T> {
164153
fn extract(obj: &PyAny) -> PyResult<PyBuffer<T>> {
165154
Self::get(obj)
@@ -169,25 +158,37 @@ impl<'source, T: Element> FromPyObject<'source> for PyBuffer<T> {
169158
impl<T: Element> PyBuffer<T> {
170159
/// Get the underlying buffer from the specified python object.
171160
pub fn get(obj: &PyAny) -> PyResult<PyBuffer<T>> {
172-
unsafe {
173-
let mut buf = Box::pin(ffi::Py_buffer::new());
161+
// TODO: use nightly API Box::new_uninit() once stable
162+
let mut buf = Box::new(mem::MaybeUninit::uninit());
163+
let buf: Box<ffi::Py_buffer> = unsafe {
174164
err::error_on_minusone(
175165
obj.py(),
176-
ffi::PyObject_GetBuffer(obj.as_ptr(), &mut *buf, ffi::PyBUF_FULL_RO),
166+
ffi::PyObject_GetBuffer(obj.as_ptr(), buf.as_mut_ptr(), ffi::PyBUF_FULL_RO),
177167
)?;
178-
validate(&buf)?;
179-
let buf = PyBuffer(buf, PhantomData);
180-
// Type Check
181-
if mem::size_of::<T>() == buf.item_size()
182-
&& (buf.0.buf as usize) % mem::align_of::<T>() == 0
183-
&& T::is_compatible_format(buf.format())
184-
{
185-
Ok(buf)
186-
} else {
187-
Err(exceptions::PyBufferError::new_err(
188-
"Incompatible type as buffer",
189-
))
190-
}
168+
// Safety: buf is initialized by PyObject_GetBuffer.
169+
// TODO: use nightly API Box::assume_init() once stable
170+
mem::transmute(buf)
171+
};
172+
// Create PyBuffer immediately so that if validation checks fail, the PyBuffer::drop code
173+
// will call PyBuffer_Release (thus avoiding any leaks).
174+
let buf = PyBuffer(Pin::from(buf), PhantomData);
175+
176+
if buf.0.shape.is_null() {
177+
Err(PyBufferError::new_err("shape is null"))
178+
} else if buf.0.strides.is_null() {
179+
Err(PyBufferError::new_err("strides is null"))
180+
} else if mem::size_of::<T>() != buf.item_size() || !T::is_compatible_format(buf.format()) {
181+
Err(PyBufferError::new_err(format!(
182+
"buffer contents are not compatible with {}",
183+
std::any::type_name::<T>()
184+
)))
185+
} else if buf.0.buf.align_offset(mem::align_of::<T>()) != 0 {
186+
Err(PyBufferError::new_err(format!(
187+
"buffer contents are insufficiently aligned for {}",
188+
std::any::type_name::<T>()
189+
)))
190+
} else {
191+
Ok(buf)
191192
}
192193
}
193194

@@ -441,9 +442,11 @@ impl<T: Element> PyBuffer<T> {
441442

442443
fn copy_to_slice_impl(&self, py: Python, target: &mut [T], fort: u8) -> PyResult<()> {
443444
if mem::size_of_val(target) != self.len_bytes() {
444-
return Err(exceptions::PyBufferError::new_err(
445-
"Slice length does not match buffer length.",
446-
));
445+
return Err(PyBufferError::new_err(format!(
446+
"slice to copy to (of length {}) does not match buffer length of {}",
447+
target.len(),
448+
self.item_count()
449+
)));
447450
}
448451
unsafe {
449452
err::error_on_minusone(
@@ -525,12 +528,13 @@ impl<T: Element> PyBuffer<T> {
525528

526529
fn copy_from_slice_impl(&self, py: Python, source: &[T], fort: u8) -> PyResult<()> {
527530
if self.readonly() {
528-
return buffer_readonly_error();
529-
}
530-
if mem::size_of_val(source) != self.len_bytes() {
531-
return Err(exceptions::PyBufferError::new_err(
532-
"Slice length does not match buffer length.",
533-
));
531+
return Err(PyBufferError::new_err("cannot write to read-only buffer"));
532+
} else if mem::size_of_val(source) != self.len_bytes() {
533+
return Err(PyBufferError::new_err(format!(
534+
"slice to copy from (of length {}) does not match buffer length of {}",
535+
source.len(),
536+
self.item_count()
537+
)));
534538
}
535539
unsafe {
536540
err::error_on_minusone(
@@ -562,13 +566,6 @@ impl<T: Element> PyBuffer<T> {
562566
}
563567
}
564568

565-
#[inline(always)]
566-
fn buffer_readonly_error() -> PyResult<()> {
567-
Err(exceptions::PyBufferError::new_err(
568-
"Cannot write to read-only buffer.",
569-
))
570-
}
571-
572569
impl<T> Drop for PyBuffer<T> {
573570
fn drop(&mut self) {
574571
Python::with_gil(|_| unsafe { ffi::PyBuffer_Release(&mut *self.0) });

0 commit comments

Comments
 (0)