diff --git a/CHANGELOG.md b/CHANGELOG.md index 97fd31e5a50..d109d977d96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -89,6 +89,7 @@ This release is compatible with NumPy 2.5. * Fixed `dpnp.ndarray.view` ignoring the USM element offset of a sliced array, which also caused `dpnp.einsum` to silently return wrong results for a single sliced operand with no summed index [#3037](https://github.com/IntelPython/dpnp/pull/3037) * Fixed `dpnp.all` and `dpnp.any` aborting when reducing over an empty axis (e.g. an array with a zero-length dimension) [#3021](https://github.com/IntelPython/dpnp/pull/3021) * Released the GIL before the blocking OneMKL DFT calls in the FFT extension [#3040](https://github.com/IntelPython/dpnp/pull/3040) +* Fixed `dpnp.insert` silently ignoring out-of-bounds negative indices in a multi-element `obj`, so a mix of in-bounds and out-of-bounds indices now consistently raises `IndexError` [#3041](https://github.com/IntelPython/dpnp/pull/3041) ### Security diff --git a/dpnp/dpnp_iface_manipulation.py b/dpnp/dpnp_iface_manipulation.py index b2046ffc494..53cbd231a89 100644 --- a/dpnp/dpnp_iface_manipulation.py +++ b/dpnp/dpnp_iface_manipulation.py @@ -254,6 +254,15 @@ def _insert_array_indices(parameters, indices, values, obj): # Can safely cast the empty list to intp indices = indices.astype(dpnp.intp) + if indices.size > 0: + min_idx = int(indices.min()) + max_idx = int(indices.max()) + if min_idx < -n or max_idx > n: + oob = min_idx if min_idx < -n else max_idx + raise IndexError( + f"index {oob} is out of bounds for axis {axis} with size {n}" + ) + indices[indices < 0] += n numnew = len(indices) diff --git a/dpnp/tests/test_manipulation.py b/dpnp/tests/test_manipulation.py index 3dbd9691d4c..7245c6712a3 100644 --- a/dpnp/tests/test_manipulation.py +++ b/dpnp/tests/test_manipulation.py @@ -815,11 +815,32 @@ def test_error(self): with pytest.raises(TypeError): dpnp.insert(a, [], 2, axis="nonsense") - @pytest.mark.parametrize("idx", [4, -4]) - def test_index_out_of_bounds(self, idx): - a = dpnp.array([0, 1, 2]) + @testing.with_requires("numpy>=2.6") + @pytest.mark.parametrize("xp", [numpy, dpnp]) + @pytest.mark.parametrize( + "idx, values", + [ + # single-element obj -> singleton path + ([4], [3, 4]), + ([-4], [3, 4]), + # multi-element obj -> array path + ([-6, 0], [9, 8]), + ([0, 6], [9, 8]), + ([4, 4], [3, 4]), + ([-4, -5], [3, 4]), + ], + ) + def test_index_out_of_bounds(self, xp, idx, values): + a = xp.array([0, 1, 2]) + with pytest.raises(IndexError, match="out of bounds"): + xp.insert(a, idx, values) + + @pytest.mark.parametrize("xp", [numpy, dpnp]) + @pytest.mark.parametrize("axis", [0, 1]) + def test_index_out_of_bounds_ndim(self, xp, axis): + a = xp.ones((3, 3)) with pytest.raises(IndexError, match="out of bounds"): - dpnp.insert(a, [idx], [3, 4]) + xp.insert(a, [5, 0], 9, axis=axis) # array_split has more comprehensive test of splitting.