Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Changelog

- Unreleased
- Fix unsoundness when attempting to create a view to a numpy array with strides that are not a multiple of the element size ([#578](https://github.com/PyO3/rust-numpy/pull/578))

- v0.30.0
- `PyReadonlyArray`/`PyReadwriteArray` extraction now returns a `PyErr` which reports the actual dtype/dimensionality mismatch instead of nonsensical `CastError` messages like “'ndarray' is not an instance of 'ndarray'” ([#562](https://github.com/PyO3/rust-numpy/pull/562))
Expand Down
21 changes: 19 additions & 2 deletions src/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ use pyo3::{
types::{DerefToPyAny, PyModule},
};

use crate::borrow::{PyReadonlyArray, PyReadwriteArray};
use crate::cold;
use crate::convert::{ArrayExt, IntoPyArray, NpyIndex, ToNpyDims, ToPyArray};
use crate::dtype::{Element, PyArrayDescrMethods};
Expand All @@ -30,6 +29,10 @@ use crate::error::{
use crate::npyffi::{self, _PyArray_GET_ITEM_DATA, NPY_ORDER, PY_ARRAY_API, npy_intp};
use crate::slice_container::PySliceContainer;
use crate::untyped_array::{PyUntypedArray, PyUntypedArrayMethods};
use crate::{
borrow::{PyReadonlyArray, PyReadwriteArray},
error::UNSUPPORTED_STRIDES_ERR,
};

/// A safe, statically-typed wrapper for NumPy's [`ndarray`][ndarray] class.
///
Expand Down Expand Up @@ -1373,13 +1376,15 @@ where
Some(unsafe { slf.data().offset(offset) })
}

#[track_caller]
fn as_view<T, D, S, F>(slf: &Bound<'_, PyArray<T, D>>, from_shape_ptr: F) -> ArrayBase<S, D>
where
T: Element,
D: Dimension,
S: RawData,
F: FnOnce(StrideShape<D>, *mut T) -> ArrayBase<S, D>,
{
#[track_caller]
fn inner<D: Dimension>(
shape: &[usize],
strides: &[isize],
Expand All @@ -1389,6 +1394,11 @@ where
let shape = D::from_dimension(&Dim(shape)).expect(DIMENSIONALITY_MISMATCH_ERR);

assert!(strides.len() <= 32, "{}", MAX_DIMENSIONALITY_ERR);
assert!(
strides.iter().all(|s| s % itemsize as isize == 0),
"{}",
UNSUPPORTED_STRIDES_ERR
);

let mut new_strides = D::zeros(strides.len());
let mut inverted_axes = 0_u32;
Expand Down Expand Up @@ -1450,7 +1460,10 @@ where
return None;
}

if strides.iter().any(|strides| *strides < 0) {
if strides
.iter()
.any(|stride| *stride < 0 || *stride as usize % mem::size_of::<N>() != 0)
{
return None;
}

Expand Down Expand Up @@ -1574,6 +1587,7 @@ impl<'py, T, D> PyArrayMethods<'py, T, D> for Bound<'py, PyArray<T, D>> {
self.clone().try_into_readwrite()
}

#[track_caller]
unsafe fn as_array(&self) -> ArrayView<'_, T, D>
where
T: Element,
Expand All @@ -1584,6 +1598,7 @@ impl<'py, T, D> PyArrayMethods<'py, T, D> for Bound<'py, PyArray<T, D>> {
})
}

#[track_caller]
unsafe fn as_array_mut(&self) -> ArrayViewMut<'_, T, D>
where
T: Element,
Expand All @@ -1594,6 +1609,7 @@ impl<'py, T, D> PyArrayMethods<'py, T, D> for Bound<'py, PyArray<T, D>> {
})
}

#[track_caller]
fn as_raw_array(&self) -> RawArrayView<T, D>
where
T: Element,
Expand All @@ -1604,6 +1620,7 @@ impl<'py, T, D> PyArrayMethods<'py, T, D> for Bound<'py, PyArray<T, D>> {
})
}

#[track_caller]
fn as_raw_array_mut(&self) -> RawArrayViewMut<T, D>
where
T: Element,
Expand Down
2 changes: 2 additions & 0 deletions src/borrow/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ where

/// Provides an immutable array view of the interior of the NumPy array.
#[inline(always)]
#[track_caller]
pub fn as_array(&self) -> ArrayView<'_, T, D> {
// SAFETY: Global borrow flags ensure aliasing discipline.
unsafe { self.array.as_array() }
Expand Down Expand Up @@ -505,6 +506,7 @@ where

/// Provides a mutable array view of the interior of the NumPy array.
#[inline(always)]
#[track_caller]
pub fn as_array_mut(&mut self) -> ArrayViewMut<'_, T, D> {
// SAFETY: Global borrow flags ensure aliasing discipline.
unsafe { self.array.as_array_mut() }
Expand Down
2 changes: 2 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ use crate::dtype::PyArrayDescr;
///
/// [NPY_MAXDIMS]: https://github.com/numpy/numpy/blob/4c60b3263ac50e5e72f6a909e156314fc3c9cba0/numpy/core/include/numpy/ndarraytypes.h#L40
pub(crate) const MAX_DIMENSIONALITY_ERR: &str = "unexpected dimensionality: NumPy is expected to limit arrays to 32 or fewer dimensions.\nPlease report a bug against the `rust-numpy` crate.";
pub(crate) const UNSUPPORTED_STRIDES_ERR: &str =
"arrays with strides that are not a multiple of the element size are currently unsupported.";

pub(crate) const DIMENSIONALITY_MISMATCH_ERR: &str = "inconsistent dimensionalities: The dimensionality expected by `PyArray` does not match that given by NumPy.\nPlease report a bug against the `rust-numpy` crate.";

Expand Down
17 changes: 17 additions & 0 deletions tests/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,23 @@ fn as_array() {
});
}

#[test]
#[should_panic]
fn test_unsupported_strides_panic() {
Python::attach(|py| {
let field = py.eval(
c"np.array([(1, 0), (2, 0), (3, 0)], dtype=[('num', 'c16'), ('other', 'f8')])['num']",
None,
Some(&get_np_locals(py)),
)?
.cast_into::<PyArray1<numpy::Complex64>>()?;

let _view = field.readonly().as_array(); // unrepresentable -> panic
Ok::<_, pyo3::PyErr>(())
})
.unwrap();
}

#[test]
fn as_raw_array() {
Python::attach(|py| {
Expand Down
Loading