Repository: numba/numba

Issue:
No out of bounds check during advanced array indexing
Numba ignores any indices that are out of bounds for a given array shape. This behavior is present in both basic and advanced indexing. 

```python 
import numpy as np
import numba

@numba.njit
def something():
    arr = np.ones((3,3))
    arr[100] = 0
    arr[:, np.array([2,3,5,100])] = 0
    return arr

print(something()) # Does not raise error, ignores the out of bound indices completely.
print(something.py_func()) # Raises out of bounds error
```

Not sure if we should be considering this as a bug or a feature :-) 

Should be easily fixed by adding a bounds check during indexing during function runtime.



Discussion:
@kc611 thank you for reporting this. IIRC there is a keyword argument that can be passed to the `njit` decorator:

https://numba.readthedocs.io/en/stable/reference/pysemantics.html?highlight=boundscheck#bounds-checking

Is this perhaps what you are looking for?
Thank you that was exactly what I was looking for. 
So, uh it does work for basic indexing, same cannot be said for advanced indexing:

```python
import numpy as np
import numba

@numba.njit(boundscheck=True)
def something():
    arr = np.ones((3,3))
    arr[:, np.array([2,3,5,100])] = 0
    return arr

print(something()) # Does not raise error, ignores the out of bound indices completely.
print(something.py_func()) # Raises out of bounds error
```

Note: The planning for a new implementation of advanced indexing is already underway, so this needs to handled/labelled correctly such that nobody takes this up till the respective PR is made. 
@kc611 suggest adding:
```diff
diff --git a/numba/np/arrayobj.py b/numba/np/arrayobj.py
index 850bd1d..5b1d48e 100644
--- a/numba/np/arrayobj.py
+++ b/numba/np/arrayobj.py
@@ -1617,7 +1617,8 @@ def fancy_setslice(context, builder, sig, args, index_types, indices):
     dest_ptr = cgutils.get_item_pointer2(context, builder, dest_data,
                                          dest_shapes, dest_strides,
                                          aryty.layout, dest_indices,
-                                         wraparound=False)
+                                         wraparound=False,
+                                         boundscheck=context.enable_boundscheck)
     store_item(context, builder, aryty, val, dest_ptr)
```
see if that helps?

Marking this as a good first issue for any newcomer. 

Resolving patch:
diff --git a/numba/np/arrayobj.py b/numba/np/arrayobj.py
--- a/numba/np/arrayobj.py
+++ b/numba/np/arrayobj.py
@@ -1627,7 +1627,8 @@ def src_cleanup():
     dest_ptr = cgutils.get_item_pointer2(context, builder, dest_data,
                                          dest_shapes, dest_strides,
                                          aryty.layout, dest_indices,
-                                         wraparound=False)
+                                         wraparound=False,
+                                         boundscheck=context.enable_boundscheck)
     store_item(context, builder, aryty, val, dest_ptr)
 
     indexer.end_loops()


Test patch:
diff --git a/numba/tests/test_boundscheck.py b/numba/tests/test_boundscheck.py
--- a/numba/tests/test_boundscheck.py
+++ b/numba/tests/test_boundscheck.py
@@ -26,6 +26,12 @@ def fancy_array_access(x):
     return x[a]
 
 
+def fancy_array_modify(x):
+    a = np.array([1, 2, 3])
+    x[a] = 0
+    return x
+
+
 class TestBoundsCheckNoError(MemoryLeakMixin, unittest.TestCase):
     def setUp(self):
         self.old_boundscheck = config.BOUNDSCHECK
@@ -204,6 +210,26 @@ def test_fancy_indexing_boundscheck(self):
         with self.assertRaises(IndexError):
             boundscheck(a)
 
+    def test_fancy_indexing_with_modification_boundscheck(self):
+        a = np.arange(3)
+        b = np.arange(4)
+
+        # Check the numpy behavior to ensure the test is correct.
+        with self.assertRaises(IndexError):
+            # TODO: When we raise the same error message as numpy, test that
+            # they are the same
+            fancy_array_modify(a)
+        fancy_array_modify(b)
+
+        at = typeof(a)
+        rt = at.dtype[:]
+        c_boundscheck = compile_isolated(fancy_array_modify, [at],
+                                         return_type=rt,
+                                         flags=BOUNDSCHECK_FLAGS)
+        boundscheck = c_boundscheck.entry_point
+        with self.assertRaises(IndexError):
+            boundscheck(a)
+
     def tearDown(self):
         config.BOUNDSCHECK = self.old_boundscheck