From ca1f8423abf4d224de326d37bd9c4d16a2c18842 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Tue, 25 Aug 2026 15:27:03 +0200 Subject: [PATCH 1/3] Fix dpnp.insert ignoring out-of-bounds negative indices in multi-element obj Backport of numpy#31782. The multi-element index path in _insert_array_indices normalized negative indices without any bounds check, so an out-of-bounds negative index mixed with in-bounds ones silently produced wrong results instead of raising. Add the same bounds validation numpy uses so any out-of-bounds index raises IndexError, and extend the test to cover the mixed case. --- CHANGELOG.md | 1 + dpnp/dpnp_iface_manipulation.py | 9 +++++++++ dpnp/tests/test_manipulation.py | 13 ++++++++++--- 3 files changed, 20 insertions(+), 3 deletions(-) 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..eba3e70b643 100644 --- a/dpnp/tests/test_manipulation.py +++ b/dpnp/tests/test_manipulation.py @@ -815,11 +815,18 @@ 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): + @pytest.mark.parametrize( + "idx, values", + [ + ([4], [3, 4]), + ([-4], [3, 4]), + ([-6, 0], [9, 8]), + ], + ) + def test_index_out_of_bounds(self, idx, values): a = dpnp.array([0, 1, 2]) with pytest.raises(IndexError, match="out of bounds"): - dpnp.insert(a, [idx], [3, 4]) + dpnp.insert(a, idx, values) # array_split has more comprehensive test of splitting. From 9978b5573e2b959513762496691694f7936b208c Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Tue, 25 Aug 2026 15:48:54 +0200 Subject: [PATCH 2/3] Extend insert out-of-bounds tests to cover the array path and ND axis Parametrize the out-of-bounds test over both numpy and dpnp and add multi-element cases that exercise the newly added bounds check in _insert_array_indices, including the positive (max > n) branch, plus an ND case validating axis/size reporting for a non-zero axis. --- dpnp/tests/test_manipulation.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/dpnp/tests/test_manipulation.py b/dpnp/tests/test_manipulation.py index eba3e70b643..e8265b36774 100644 --- a/dpnp/tests/test_manipulation.py +++ b/dpnp/tests/test_manipulation.py @@ -815,18 +815,32 @@ def test_error(self): with pytest.raises(TypeError): dpnp.insert(a, [], 2, axis="nonsense") + @testing.with_requires("numpy>=2.5.3") + @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, idx, values): - a = dpnp.array([0, 1, 2]) + 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, values) + xp.insert(a, [5, 0], 9, axis=axis) # array_split has more comprehensive test of splitting. From 97b4495405f4633928824431d0606f16937c4dbe Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Wed, 26 Aug 2026 19:57:12 +0200 Subject: [PATCH 3/3] Update fixture with proper expecting numpy release version --- dpnp/tests/test_manipulation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dpnp/tests/test_manipulation.py b/dpnp/tests/test_manipulation.py index e8265b36774..7245c6712a3 100644 --- a/dpnp/tests/test_manipulation.py +++ b/dpnp/tests/test_manipulation.py @@ -815,7 +815,7 @@ def test_error(self): with pytest.raises(TypeError): dpnp.insert(a, [], 2, axis="nonsense") - @testing.with_requires("numpy>=2.5.3") + @testing.with_requires("numpy>=2.6") @pytest.mark.parametrize("xp", [numpy, dpnp]) @pytest.mark.parametrize( "idx, values",