Skip to content

Commit 66f4fdf

Browse files
authored
BUG: fix incorrect alignment handling and error handling in several code paths (numpy#31615)
Fixes two error paths that are trivially incorrect for OOM scenarios: arr_place uses PyArray_ResolveWritebackIfCopy in an error path, but it probably makes more sense to use PyArray_DiscardWritebackIfCopy, as suggested in the docstring for that function: https://numpy.org/doc/2.4/reference/c-api/array.html#c.PyArray_DiscardWritebackIfCopy PyArray_PutTo and PyArray_PutMask fail to check if PyArray_FromArray fails. Fixes an incorrect assumption in the flatiter assignment code - the result array might be a user-supplied unaligned array. Fixes a logic error in boolean indexing, where if an error is raised during an iteration, the iteration is not halted and it's possible the error could be silently discarded. Adds tests for the latter two cases.
1 parent ce6dd7d commit 66f4fdf

6 files changed

Lines changed: 42 additions & 5 deletions

File tree

numpy/_core/src/multiarray/compiled_base.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@ arr_place(PyObject *NPY_UNUSED(self), PyObject *args, PyObject *kwdict)
414414

415415
fail:
416416
Py_XDECREF(mask);
417-
PyArray_ResolveWritebackIfCopy(array);
417+
PyArray_DiscardWritebackIfCopy(array);
418418
Py_XDECREF(array);
419419
Py_XDECREF(values);
420420
return NULL;

numpy/_core/src/multiarray/item_selection.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,9 @@ PyArray_PutTo(PyArrayObject *self, PyObject* values0, PyObject *indices0,
439439
Py_INCREF(PyArray_DESCR(self));
440440
obj = (PyArrayObject *)PyArray_FromArray(self,
441441
PyArray_DESCR(self), flags);
442+
if (obj == NULL) {
443+
goto fail;
444+
}
442445
copied = 1;
443446
assert(self != obj);
444447
self = obj;
@@ -732,6 +735,9 @@ PyArray_PutMask(PyArrayObject *self, PyObject* values0, PyObject* mask0)
732735
dtype = PyArray_DESCR(self);
733736
Py_INCREF(dtype);
734737
obj = (PyArrayObject *)PyArray_FromArray(self, dtype, flags);
738+
if (obj == NULL) {
739+
goto fail;
740+
}
735741
if (obj != self) {
736742
copied = 1;
737743
}

numpy/_core/src/multiarray/iterators.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -849,8 +849,8 @@ iter_ass_subscript(PyArrayIterObject *self, PyObject *ind, PyObject *val)
849849
/* set up cast to handle single-element copies into arrval */
850850
NPY_ARRAYMETHOD_FLAGS transfer_flags = 0;
851851
npy_intp one = 1;
852-
/* We can assume the newly allocated array is aligned */
853-
int is_aligned = IsUintAligned(self->ao);
852+
/* arrval can be the caller's array, so its alignment must be checked */
853+
int is_aligned = IsUintAligned(self->ao) && IsUintAligned(arrval);
854854
if (PyArray_GetDTypeTransferFunction(
855855
is_aligned, dtype_size, dtype_size, PyArray_DESCR(arrval), dtype, 0,
856856
&cast_info, &transfer_flags) < 0) {

numpy/_core/src/multiarray/mapping.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1076,7 +1076,7 @@ array_boolean_subscript(PyArrayObject *self,
10761076
self_data += subloopsize * self_stride;
10771077
ret_data += subloopsize * itemsize;
10781078
}
1079-
} while (iternext(iter));
1079+
} while (res == 0 && iternext(iter));
10801080

10811081
NPY_END_THREADS;
10821082

@@ -1274,7 +1274,7 @@ array_assign_boolean_subscript(PyArrayObject *self,
12741274
self_data += subloopsize * self_stride;
12751275
v_data += subloopsize * v_stride;
12761276
}
1277-
} while (iternext(iter));
1277+
} while (res == 0 && iternext(iter));
12781278

12791279
if (!(cast_flags & NPY_METH_REQUIRES_PYAPI)) {
12801280
NPY_END_THREADS;

numpy/_core/tests/test_indexing.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1365,6 +1365,22 @@ def test_boolean_indexing_fast_path(self):
13651365
"size of axis is 1 but size of corresponding boolean axis is 2",
13661366
lambda: a[idx])
13671367

1368+
def test_assignment_cast_error_aborts_iteration(self):
1369+
# A failing cast must abort the assignment: no further chunks may
1370+
# be processed and the error must be raised. The iteration used to
1371+
# continue after a failed chunk, relying on every later cast call
1372+
# failing fast on the pending exception to keep the error alive.
1373+
a = np.zeros((40, 30))[:, ::2]
1374+
mask = np.zeros(a.shape, dtype=bool)
1375+
mask[:2] = True
1376+
mask[2:, :5] = True
1377+
vals = np.arange(float(mask.sum())).astype(object)
1378+
vals[29] = object()
1379+
with pytest.raises(TypeError):
1380+
a[mask] = vals
1381+
# rows after the failing chunk are untouched
1382+
assert not a[2:].any()
1383+
13681384

13691385
class TestArrayToIndexDeprecation:
13701386
"""Creating an index from array not 0-D is an error.

numpy/_core/tests/test_multiarray.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6904,6 +6904,21 @@ def test_discontiguous(self):
69046904
assert_(testpassed)
69056905
assert_(b.flat[4] == 12.0)
69066906

6907+
def test_unaligned_values(self):
6908+
# the value array is used as-is, so its (mis)alignment must be
6909+
# honored when assigning through the flat iterator
6910+
n = 8
6911+
buf = np.zeros(n * 8 + 1, dtype=np.uint8)
6912+
unaligned = buf[1:].view(np.float64)
6913+
assert not unaligned.flags.aligned
6914+
unaligned[:] = np.arange(n)
6915+
6916+
b = np.zeros(n)
6917+
b.flat = unaligned
6918+
assert_array_equal(b, unaligned)
6919+
b.flat[::2] = unaligned[:n // 2]
6920+
assert_array_equal(b[::2], unaligned[:n // 2])
6921+
69076922
def test___array__(self):
69086923
a0 = np.arange(20.0)
69096924
a = a0.reshape(4, 5)

0 commit comments

Comments
 (0)