diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bc89e7b7be..44030b72063 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,7 @@ This release is compatible with NumPy 2.5. * Fixed `__array_namespace_info__().devices()` and `.default_device()` to return Python array API compatible device objects [#2979](https://github.com/IntelPython/dpnp/pull/2979) * Fixed `dpnp.interp` with an empty input array `x` to return an empty array with the correct dtype [#2985](https://github.com/IntelPython/dpnp/pull/2985) * Fixed `dpnp.interp` returning `nan` when querying at an exact knot point whose adjacent `fp` value is `inf` [#2986](https://github.com/IntelPython/dpnp/pull/2986) +* Fixed missing strides validation in `dpnp.tensor.usm_ndarray` constructor when allocating new memory [#2927](https://github.com/IntelPython/dpnp/pull/2927) ### Security diff --git a/dpnp/tensor/_usmarray.pyx b/dpnp/tensor/_usmarray.pyx index bb03c583192..9e9e9861615 100644 --- a/dpnp/tensor/_usmarray.pyx +++ b/dpnp/tensor/_usmarray.pyx @@ -378,12 +378,11 @@ cdef class usm_ndarray: if (typenum < 0): if typenum == -2: raise ValueError( - "Data type '" + str(dtype) + - "' can only have native byteorder." + f"Data type '{dtype}' can only have native byteorder." ) elif typenum == -1: raise ValueError( - "Data type '" + str(dtype) + "' is not understood." + f"Data type '{dtype}' is not understood." ) raise TypeError( f"Expected string or a dtype object, got {type(dtype)}" @@ -391,7 +390,7 @@ cdef class usm_ndarray: itemsize = type_bytesize(typenum) if (itemsize < 1): raise TypeError( - "dtype=" + np.dtype(dtype).name + " is not supported." + f"dtype={np.dtype(dtype).name} is not supported." ) # allocate host C-arrays for shape, strides err = _from_input_shape_strides( @@ -406,11 +405,11 @@ cdef class usm_ndarray: "array failed.") elif err == ERROR_INCORRECT_ORDER: raise ValueError( - "Unsupported order='{}' given. " - "Supported values are 'C' or 'F'.".format(order)) + f"Unsupported order='{order}' given. " + "Supported values are 'C' or 'F'.") elif err == ERROR_UNEXPECTED_STRIDES: raise ValueError( - "strides={} is not understood".format(strides)) + f"strides={strides} is not understood") else: raise InternalUSMArrayError( " .. while processing shape and strides.") @@ -421,6 +420,21 @@ cdef class usm_ndarray: elif isinstance(buffer, (str, bytes)): if isinstance(buffer, bytes): buffer = buffer.decode("UTF-8") + if strides is not None and ary_min_displacement < 0: + self._cleanup() + raise ValueError( + f"strides={strides} result in a negative memory " + "displacement and are not allowed when allocating " + "new memory") + if strides is not None and ( + (ary_max_displacement - ary_min_displacement + 1) > ary_nelems + ): + self._cleanup() + raise ValueError( + f"strides={strides} is incompatible with " + f"shape={shape} when allocating new memory because " + "the memory footprint exceeds the number of elements" + ) _offset = -ary_min_displacement if (buffer == "shared"): _buffer = dpmem.MemoryUSMShared(ary_nbytes, @@ -434,10 +448,9 @@ cdef class usm_ndarray: else: self._cleanup() raise ValueError( - "buffer='{}' is not understood. " + f"buffer='{buffer}' is not understood. " "Recognized values are 'device', 'shared', 'host', " "an instance of `MemoryUSM*` object, or a usm_ndarray" - "".format(buffer) ) elif isinstance(buffer, usm_ndarray): if not buffer.flags.writable: @@ -445,13 +458,14 @@ cdef class usm_ndarray: _buffer = buffer.usm_data else: self._cleanup() - raise ValueError("buffer='{}' was not understood.".format(buffer)) + raise ValueError(f"buffer='{buffer}' was not understood.") if (shape_to_elem_count(nd, shape_ptr) > 0 and (_offset + ary_min_displacement < 0 or (_offset + ary_max_displacement + 1) * itemsize > _buffer.nbytes)): self._cleanup() - raise ValueError(("buffer='{}' can not accommodate " - "the requested array.").format(buffer)) + raise ValueError( + f"buffer='{buffer}' can not accommodate the requested array." + ) is_fp64 = (typenum == UAR_DOUBLE or typenum == UAR_CDOUBLE) is_fp16 = (typenum == UAR_HALF) if (is_fp64 or is_fp16): @@ -640,9 +654,8 @@ cdef class usm_ndarray: if (not isinstance(self.base_, dpmem._memory._Memory)): raise InternalUSMArrayError( "Invalid instance of usm_ndarray encountered. " - "Private field base_ has an unexpected type {}.".format( - type(self.base_) - ) + "Private field base_ has an unexpected type " + f"{type(self.base_)}." ) ary_iface = self.base_.__sycl_usm_array_interface__ mem_ptr = ( ary_iface["data"][0]) @@ -803,7 +816,7 @@ cdef class usm_ndarray: self.strides_ = strides_ptr else: raise InternalUSMArrayError( - "Encountered in shape setter, error code {err}".format(err) + f"Encountered in shape setter, error code {err}" ) @property @@ -1905,7 +1918,7 @@ cdef api object UsmNDArray_MakeSimpleFromPtr( cdef int itemsize = type_bytesize(typenum) if (itemsize < 1): raise ValueError( - "dtype with typenum=" + str(typenum) + " is not supported." + f"dtype with typenum={typenum} is not supported." ) cdef size_t nbytes = ( itemsize) * nelems cdef c_dpmem._Memory mobj @@ -1961,7 +1974,7 @@ cdef api object UsmNDArray_MakeFromPtr( if (itemsize < 1): raise ValueError( - "dtype with typenum=" + str(typenum) + " is not supported." + f"dtype with typenum={typenum} is not supported." ) if (nd < 0): raise ValueError("Dimensionality must be non-negative") diff --git a/dpnp/tests/tensor/test_usm_ndarray_ctor.py b/dpnp/tests/tensor/test_usm_ndarray_ctor.py index 8494ca9181d..98bfc477025 100644 --- a/dpnp/tests/tensor/test_usm_ndarray_ctor.py +++ b/dpnp/tests/tensor/test_usm_ndarray_ctor.py @@ -123,9 +123,6 @@ def test_usm_ndarray_flags(): f = dpt.usm_ndarray((5, 0, 1), dtype="i4", strides=(1, 0, 1)).flags assert f.fc assert f.forc - assert not dpt.usm_ndarray( - (5, 1, 1), dtype="i4", strides=(2, 0, 1) - ).flags.forc x = dpt.empty(5, dtype="u2") assert x.flags.writable is True @@ -1085,6 +1082,19 @@ def test_ctor_invalid(): dpt.usm_ndarray((4,), dtype="u1", buffer=m, strides={"not": "valid"}) +def test_ctor_invalid_strides(): + try: + dpt.usm_ndarray((1,), dtype="i4") + except dpctl.SyclDeviceCreationError: + pytest.skip("No SYCL devices available") + # negative displacement + with pytest.raises(ValueError): + dpt.usm_ndarray((2, 3, 4), dtype="i4", strides=(-1, 1, 1)) + # oversized memory footprint + with pytest.raises(ValueError): + dpt.usm_ndarray((2, 3, 4), dtype="i4", strides=(1, 16, 128)) + + def test_reshape(): try: X = dpt.usm_ndarray((5, 5), "i4")