summaryrefslogtreecommitdiff
path: root/numpy
diff options
context:
space:
mode:
Diffstat (limited to 'numpy')
-rw-r--r--numpy/core/src/umath/_umath_tests.c.src25
-rw-r--r--numpy/core/src/umath/ufunc_object.c15
-rw-r--r--numpy/core/tests/test_umath.py28
3 files changed, 67 insertions, 1 deletions
diff --git a/numpy/core/src/umath/_umath_tests.c.src b/numpy/core/src/umath/_umath_tests.c.src
index ed4c617a4..33d8539d5 100644
--- a/numpy/core/src/umath/_umath_tests.c.src
+++ b/numpy/core/src/umath/_umath_tests.c.src
@@ -58,6 +58,19 @@
*****************************************************************************
*/
+static void
+always_error_loop(
+ char **NPY_UNUSED(args), npy_intp const *NPY_UNUSED(dimensions),
+ npy_intp const *NPY_UNUSED(steps), void *NPY_UNUSED(func))
+{
+ NPY_ALLOW_C_API_DEF
+ NPY_ALLOW_C_API;
+ PyErr_SetString(PyExc_RuntimeError, "How unexpected :)!");
+ NPY_DISABLE_C_API;
+ return;
+}
+
+
char *inner1d_signature = "(i),(i)->()";
/**begin repeat
@@ -348,6 +361,9 @@ defdict = {
*/
+static PyUFuncGenericFunction always_error_functions[] = { always_error_loop };
+static void *always_error_data[] = { (void *)NULL };
+static char always_error_signatures[] = { NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE };
static PyUFuncGenericFunction inner1d_functions[] = { LONG_inner1d, DOUBLE_inner1d };
static void *inner1d_data[] = { (void *)NULL, (void *)NULL };
static char inner1d_signatures[] = { NPY_LONG, NPY_LONG, NPY_LONG, NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE };
@@ -375,6 +391,15 @@ static int
addUfuncs(PyObject *dictionary) {
PyObject *f;
+ f = PyUFunc_FromFuncAndData(always_error_functions, always_error_data,
+ always_error_signatures, 1, 2, 1, PyUFunc_None, "always_error",
+ "simply, broken, ufunc that sets an error (but releases the GIL).",
+ 0);
+ if (f == NULL) {
+ return -1;
+ }
+ PyDict_SetItemString(dictionary, "always_error", f);
+ Py_DECREF(f);
f = PyUFunc_FromFuncAndDataAndSignature(inner1d_functions, inner1d_data,
inner1d_signatures, 2, 2, 1, PyUFunc_None, "inner1d",
"inner on the last dimension and broadcast on the rest \n"
diff --git a/numpy/core/src/umath/ufunc_object.c b/numpy/core/src/umath/ufunc_object.c
index 714afb273..42290e8c9 100644
--- a/numpy/core/src/umath/ufunc_object.c
+++ b/numpy/core/src/umath/ufunc_object.c
@@ -1325,6 +1325,14 @@ try_trivial_single_output_loop(PyArrayMethod_Context *context,
NPY_END_THREADS;
NPY_AUXDATA_FREE(auxdata);
+ /*
+ * An error should only be possible if `res != 0` is already set.
+ * But this is not strictly correct for old-style ufuncs (e.g. `power`
+ * released the GIL but manually set an Exception).
+ */
+ if (PyErr_Occurred()) {
+ res = -1;
+ }
if (res == 0 && !(flags & NPY_METH_NO_FLOATINGPOINT_ERRORS)) {
/* NOTE: We could check float errors even when `res < 0` */
@@ -6162,7 +6170,12 @@ ufunc_at(PyUFuncObject *ufunc, PyObject *args)
Py_XDECREF(array_operands[i]);
}
- if (needs_api && PyErr_Occurred()) {
+ /*
+ * An error should only be possible if needs_api is true, but this is not
+ * strictly correct for old-style ufuncs (e.g. `power` released the GIL
+ * but manually set an Exception).
+ */
+ if (PyErr_Occurred()) {
return NULL;
}
else {
diff --git a/numpy/core/tests/test_umath.py b/numpy/core/tests/test_umath.py
index 4f57c0088..d98afada8 100644
--- a/numpy/core/tests/test_umath.py
+++ b/numpy/core/tests/test_umath.py
@@ -3852,3 +3852,31 @@ def test_outer_exceeds_maxdims():
with assert_raises(ValueError):
np.add.outer(deep, deep)
+def test_bad_legacy_ufunc_silent_errors():
+ # legacy ufuncs can't report errors and NumPy can't check if the GIL
+ # is released. So NumPy has to check after the GIL is released just to
+ # cover all bases. `np.power` uses/used to use this.
+ arr = np.arange(3).astype(np.float64)
+
+ with pytest.raises(RuntimeError, match=r"How unexpected :\)!"):
+ ncu_tests.always_error(arr, arr)
+
+ with pytest.raises(RuntimeError, match=r"How unexpected :\)!"):
+ # not contiguous means the fast-path cannot be taken
+ non_contig = arr.repeat(20).reshape(-1, 6)[:, ::2]
+ ncu_tests.always_error(non_contig, arr)
+
+ with pytest.raises(RuntimeError, match=r"How unexpected :\)!"):
+ ncu_tests.always_error.outer(arr, arr)
+
+ with pytest.raises(RuntimeError, match=r"How unexpected :\)!"):
+ ncu_tests.always_error.reduce(arr)
+
+ with pytest.raises(RuntimeError, match=r"How unexpected :\)!"):
+ ncu_tests.always_error.reduceat(arr, [0, 1])
+
+ with pytest.raises(RuntimeError, match=r"How unexpected :\)!"):
+ ncu_tests.always_error.accumulate(arr)
+
+ with pytest.raises(RuntimeError, match=r"How unexpected :\)!"):
+ ncu_tests.always_error.at(arr, [0, 1, 2], arr)