summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCharles Harris <charlesr.harris@gmail.com>2020-12-17 17:16:44 -0700
committerGitHub <noreply@github.com>2020-12-17 17:16:44 -0700
commit8ab99be58a99aab0c86f47afaa38d65739d22b3b (patch)
treebdf024f4bf638226a5065a7edd5e3b326f54ee15
parenteeaf12d118ca8b72133768ce00fdde0b3fda6a16 (diff)
parent6f9223c604c501bec9357b7cbff9211996fe834e (diff)
downloadnumpy-8ab99be58a99aab0c86f47afaa38d65739d22b3b.tar.gz
Merge pull request #17786 from seberg/bad-array-recursion-error
BUG: Raise recursion error during dimension discovery
-rw-r--r--numpy/core/src/multiarray/ctors.c39
-rw-r--r--numpy/core/tests/test_multiarray.py31
2 files changed, 65 insertions, 5 deletions
diff --git a/numpy/core/src/multiarray/ctors.c b/numpy/core/src/multiarray/ctors.c
index 6e6f67e7d..851244adf 100644
--- a/numpy/core/src/multiarray/ctors.c
+++ b/numpy/core/src/multiarray/ctors.c
@@ -741,7 +741,13 @@ discover_dimensions(PyObject *obj, int *maxndim, npy_intp *d, int check_it,
if (!PySequence_Check(obj) ||
PySequence_Length(obj) < 0) {
*maxndim = 0;
- PyErr_Clear();
+ if (PyErr_Occurred()) {
+ if (PyErr_ExceptionMatches(PyExc_RecursionError) ||
+ PyErr_ExceptionMatches(PyExc_MemoryError)) {
+ return -1;
+ }
+ PyErr_Clear();
+ }
return 0;
}
@@ -780,7 +786,14 @@ discover_dimensions(PyObject *obj, int *maxndim, npy_intp *d, int check_it,
return 0;
}
else if (PyErr_Occurred()) {
- /* TODO[gh-14801]: propagate crashes during attribute access? */
+ /*
+ * Clear all but clearly fatal errors (previously cleared all).
+ * 1.20+ does not clear any errors here.
+ */
+ if (PyErr_ExceptionMatches(PyExc_RecursionError) ||
+ PyErr_ExceptionMatches(PyExc_MemoryError)) {
+ return -1;
+ }
PyErr_Clear();
}
@@ -1740,7 +1753,8 @@ PyArray_GetArrayParamsFromObject_int(PyObject *op,
else {
*out_dtype = NULL;
if (PyArray_DTypeFromObject(op, NPY_MAXDIMS, out_dtype) < 0) {
- if (PyErr_ExceptionMatches(PyExc_MemoryError)) {
+ if (PyErr_ExceptionMatches(PyExc_MemoryError) ||
+ PyErr_ExceptionMatches(PyExc_RecursionError)) {
return -1;
}
/* Return NPY_OBJECT for most exceptions */
@@ -2404,7 +2418,14 @@ PyArray_FromInterface(PyObject *origin)
"__array_interface__");
if (iface == NULL) {
if (PyErr_Occurred()) {
- PyErr_Clear(); /* TODO[gh-14801]: propagate crashes during attribute access? */
+ /*
+ * Clear all but clearly fatal errors (previously cleared all).
+ * 1.20+ does not clear any errors here.
+ */
+ if (PyErr_ExceptionMatches(PyExc_RecursionError) ||
+ PyErr_ExceptionMatches(PyExc_MemoryError)) {
+ return NULL;
+ }
}
return Py_NotImplemented;
}
@@ -2672,7 +2693,15 @@ PyArray_FromArrayAttr(PyObject *op, PyArray_Descr *typecode, PyObject *context)
array_meth = PyArray_LookupSpecial_OnInstance(op, "__array__");
if (array_meth == NULL) {
if (PyErr_Occurred()) {
- PyErr_Clear(); /* TODO[gh-14801]: propagate crashes during attribute access? */
+ /*
+ * Clear all but clearly fatal errors (previously cleared all).
+ * 1.20+ does not clear any errors here.
+ */
+ if (PyErr_ExceptionMatches(PyExc_RecursionError) ||
+ PyErr_ExceptionMatches(PyExc_MemoryError)) {
+ return NULL;
+ }
+ PyErr_Clear();
}
return Py_NotImplemented;
}
diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py
index f7c7b0ad9..2d087f7ca 100644
--- a/numpy/core/tests/test_multiarray.py
+++ b/numpy/core/tests/test_multiarray.py
@@ -818,6 +818,37 @@ class TestCreation:
assert_raises(ValueError, np.array, x())
+ @pytest.mark.parametrize("error", [RecursionError, MemoryError])
+ @pytest.mark.parametrize("attribute",
+ ["__array_interface__", "__array__", "__array_struct__"])
+ def test_bad_array_like_attributes(self, attribute, error):
+ # Check that errors during attribute retrieval are raised unless
+ # they are Attribute errors.
+
+ class BadInterface:
+ def __getattr__(self, attr):
+ if attr == attribute:
+ raise error
+ super().__getattr__(attr)
+
+ with pytest.raises(error):
+ np.array(BadInterface())
+
+ @pytest.mark.parametrize("error", [RecursionError, MemoryError])
+ def test_bad_array_like_bad_length(self, error):
+ # RecursionError and MemoryError are considered "critical" in
+ # sequences. We could expand this more generally though. (NumPy 1.20)
+ class BadSequence:
+ def __len__(self):
+ raise error
+ def __getitem__(self):
+ # must have getitem to be a Sequence
+ return 1
+
+ with pytest.raises(error):
+ np.array(BadSequence())
+
+
def test_from_string(self):
types = np.typecodes['AllInteger'] + np.typecodes['Float']
nstr = ['123', '123']