summaryrefslogtreecommitdiff
path: root/numpy/core/src/multiarray
diff options
context:
space:
mode:
authorSebastian Berg <sebastian@sipsolutions.net>2020-06-05 13:29:05 -0500
committerSebastian Berg <sebastian@sipsolutions.net>2022-06-09 11:24:02 -0700
commita482ae5111799602b7eb2f52f521df3c39ded919 (patch)
tree6dfa2568061884e2ccf3225f0e7aa2edf2f9d970 /numpy/core/src/multiarray
parent3523b578f60b4b584f951f0ffaf89703bc3e07b3 (diff)
downloadnumpy-a482ae5111799602b7eb2f52f521df3c39ded919.tar.gz
BUG: Do not allow nditer to reduce the mask
Previously this was not checked correctly when `op_axes` were provided. Note that we may want to disallow writing to a mask completely. This also continues some maintanence started earlier, which was missing here due to the mask confusion.
Diffstat (limited to 'numpy/core/src/multiarray')
-rw-r--r--numpy/core/src/multiarray/nditer_constr.c96
1 files changed, 38 insertions, 58 deletions
diff --git a/numpy/core/src/multiarray/nditer_constr.c b/numpy/core/src/multiarray/nditer_constr.c
index f82a9624e..ea2e43895 100644
--- a/numpy/core/src/multiarray/nditer_constr.c
+++ b/numpy/core/src/multiarray/nditer_constr.c
@@ -1410,9 +1410,9 @@ check_mask_for_writemasked_reduction(NpyIter *iter, int iop)
static int
npyiter_check_reduce_ok_and_set_flags(
NpyIter *iter, npy_uint32 flags, npyiter_opitflags *op_itflags,
- int dim) {
+ int iop, int maskop, int dim) {
/* If it's writeable, this means a reduction */
- if (*op_itflags & NPY_OP_ITFLAG_WRITE) {
+ if (op_itflags[iop] & NPY_OP_ITFLAG_WRITE) {
if (!(flags & NPY_ITER_REDUCE_OK)) {
PyErr_Format(PyExc_ValueError,
"output operand requires a reduction along dimension %d, "
@@ -1420,17 +1420,35 @@ npyiter_check_reduce_ok_and_set_flags(
"does not match the expected output shape.", dim);
return 0;
}
- if (!(*op_itflags & NPY_OP_ITFLAG_READ)) {
+ if (!(op_itflags[iop] & NPY_OP_ITFLAG_READ)) {
PyErr_SetString(PyExc_ValueError,
"output operand requires a reduction, but is flagged as "
"write-only, not read-write");
return 0;
}
+ /*
+ * The ARRAYMASK can't be a reduction, because
+ * it would be possible to write back to the
+ * array once when the ARRAYMASK says 'True',
+ * then have the reduction on the ARRAYMASK
+ * later flip to 'False', indicating that the
+ * write back should never have been done,
+ * and violating the strict masking semantics
+ */
+ if (iop == maskop) {
+ PyErr_SetString(PyExc_ValueError,
+ "output operand requires a "
+ "reduction, but is flagged as "
+ "the ARRAYMASK operand which "
+ "is not permitted to be the "
+ "result of a reduction");
+ return 0;
+ }
NPY_IT_DBG_PRINT("Iterator: Indicating that a reduction is"
"occurring\n");
NIT_ITFLAGS(iter) |= NPY_ITFLAG_REDUCE;
- *op_itflags |= NPY_OP_ITFLAG_REDUCE;
+ op_itflags[iop] |= NPY_OP_ITFLAG_REDUCE;
}
return 1;
}
@@ -1613,42 +1631,9 @@ npyiter_fill_axisdata(NpyIter *iter, npy_uint32 flags, npyiter_opitflags *op_itf
goto operand_different_than_broadcast;
}
/* If it's writeable, this means a reduction */
- if (op_itflags[iop] & NPY_OP_ITFLAG_WRITE) {
- if (!(flags & NPY_ITER_REDUCE_OK)) {
- PyErr_SetString(PyExc_ValueError,
- "output operand requires a "
- "reduction, but reduction is "
- "not enabled");
- return 0;
- }
- if (!(op_itflags[iop] & NPY_OP_ITFLAG_READ)) {
- PyErr_SetString(PyExc_ValueError,
- "output operand requires a "
- "reduction, but is flagged as "
- "write-only, not read-write");
- return 0;
- }
- /*
- * The ARRAYMASK can't be a reduction, because
- * it would be possible to write back to the
- * array once when the ARRAYMASK says 'True',
- * then have the reduction on the ARRAYMASK
- * later flip to 'False', indicating that the
- * write back should never have been done,
- * and violating the strict masking semantics
- */
- if (iop == maskop) {
- PyErr_SetString(PyExc_ValueError,
- "output operand requires a "
- "reduction, but is flagged as "
- "the ARRAYMASK operand which "
- "is not permitted to be the "
- "result of a reduction");
- return 0;
- }
-
- NIT_ITFLAGS(iter) |= NPY_ITFLAG_REDUCE;
- op_itflags[iop] |= NPY_OP_ITFLAG_REDUCE;
+ if (!npyiter_check_reduce_ok_and_set_flags(
+ iter, flags, op_itflags, iop, maskop, idim)) {
+ return 0;
}
}
else {
@@ -1697,7 +1682,7 @@ npyiter_fill_axisdata(NpyIter *iter, npy_uint32 flags, npyiter_opitflags *op_itf
goto operand_different_than_broadcast;
}
if (!npyiter_check_reduce_ok_and_set_flags(
- iter, flags, &op_itflags[iop], i)) {
+ iter, flags, op_itflags, iop, maskop, i)) {
return 0;
}
}
@@ -1707,8 +1692,14 @@ npyiter_fill_axisdata(NpyIter *iter, npy_uint32 flags, npyiter_opitflags *op_itf
}
else {
strides[iop] = 0;
+ /*
+ * If deleting this axis produces a reduction, but
+ * reduction wasn't enabled, throw an error.
+ * NOTE: We currently always allow new-axis if the iteration
+ * size is 1 (thus allowing broadcasting sometimes).
+ */
if (!npyiter_check_reduce_ok_and_set_flags(
- iter, flags, &op_itflags[iop], i)) {
+ iter, flags, op_itflags, iop, maskop, i)) {
return 0;
}
}
@@ -2545,6 +2536,11 @@ npyiter_new_temp_array(NpyIter *iter, PyTypeObject *subtype,
i = npyiter_undo_iter_axis_perm(idim, ndim, perm, NULL);
i = npyiter_get_op_axis(op_axes[i], &reduction_axis);
+ /*
+ * If i < 0, this is a new axis (the operand does not have it)
+ * so we can ignore it here. The iterator setup will have
+ * ensured already that a potential reduction/broadcast is valid.
+ */
if (i >= 0) {
NPY_IT_DBG_PRINT3("Iterator: Setting allocated stride %d "
"for iterator dimension %d to %d\n", (int)i,
@@ -2575,22 +2571,6 @@ npyiter_new_temp_array(NpyIter *iter, PyTypeObject *subtype,
stride *= shape[i];
}
}
- else {
- if (shape == NULL) {
- /*
- * If deleting this axis produces a reduction, but
- * reduction wasn't enabled, throw an error.
- * NOTE: We currently always allow new-axis if the iteration
- * size is 1 (thus allowing broadcasting sometimes).
- */
- if (!reduction_axis && NAD_SHAPE(axisdata) != 1) {
- if (!npyiter_check_reduce_ok_and_set_flags(
- iter, flags, op_itflags, i)) {
- return NULL;
- }
- }
- }
- }
}
}
else {