summaryrefslogtreecommitdiff
path: root/numpy
diff options
context:
space:
mode:
authorSebastian Berg <sebastian@sipsolutions.net>2020-11-20 16:25:52 -0600
committerSebastian Berg <sebastian@sipsolutions.net>2020-11-24 15:03:04 -0600
commitb17dd63a01585e001e6a75daf89ea0bbbdd8fce4 (patch)
tree4e36ec19bacee6d829aec502525d6872ff70ab9a /numpy
parentb88b2c0c19851810d4ee07f03a7734b6e19dbdaa (diff)
downloadnumpy-b17dd63a01585e001e6a75daf89ea0bbbdd8fce4.tar.gz
BUG: Ignore fewer errors during array-coercion
This changes it so that we only ignore attribute errors on looking up `__array__` and propagate errors when checking for sequences `len(obj)` if those errors are either RecursionError or MemoryError (we consider them unrecoverable). Also adds test for bad recursive array-like with sequence as reported in gh-17785. The test might be flaky/more complicated in which case it should probably just be deleted.
Diffstat (limited to 'numpy')
-rw-r--r--numpy/core/src/multiarray/array_coercion.c18
-rw-r--r--numpy/core/src/multiarray/ctors.c4
-rw-r--r--numpy/core/tests/test_array_coercion.py30
3 files changed, 48 insertions, 4 deletions
diff --git a/numpy/core/src/multiarray/array_coercion.c b/numpy/core/src/multiarray/array_coercion.c
index 53d891049..603e9d93b 100644
--- a/numpy/core/src/multiarray/array_coercion.c
+++ b/numpy/core/src/multiarray/array_coercion.c
@@ -979,14 +979,28 @@ PyArray_DiscoverDTypeAndShape_Recursive(
* and to handle it recursively. That is, unless we have hit the
* dimension limit.
*/
- npy_bool is_sequence = (PySequence_Check(obj) && PySequence_Size(obj) >= 0);
+ npy_bool is_sequence = PySequence_Check(obj);
+ if (is_sequence) {
+ is_sequence = PySequence_Size(obj) >= 0;
+ if (NPY_UNLIKELY(!is_sequence)) {
+ /* NOTE: This should likely just raise all errors */
+ if (PyErr_ExceptionMatches(PyExc_RecursionError) ||
+ PyErr_ExceptionMatches(PyExc_MemoryError)) {
+ /*
+ * Consider these unrecoverable errors, continuing execution
+ * might crash the interpreter.
+ */
+ return -1;
+ }
+ PyErr_Clear();
+ }
+ }
if (NPY_UNLIKELY(*flags & DISCOVER_TUPLES_AS_ELEMENTS) &&
PyTuple_Check(obj)) {
is_sequence = NPY_FALSE;
}
if (curr_dims == max_dims || !is_sequence) {
/* Clear any PySequence_Size error which would corrupts further calls */
- PyErr_Clear();
max_dims = handle_scalar(
obj, curr_dims, &max_dims, out_descr, out_shape, fixed_DType,
flags, NULL);
diff --git a/numpy/core/src/multiarray/ctors.c b/numpy/core/src/multiarray/ctors.c
index ff262369b..4ed3deab8 100644
--- a/numpy/core/src/multiarray/ctors.c
+++ b/numpy/core/src/multiarray/ctors.c
@@ -2122,7 +2122,7 @@ PyArray_FromInterface(PyObject *origin)
if (iface == NULL) {
if (PyErr_Occurred()) {
- PyErr_Clear(); /* TODO[gh-14801]: propagate crashes during attribute access? */
+ return NULL;
}
return Py_NotImplemented;
}
@@ -2390,7 +2390,7 @@ 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? */
+ return NULL;
}
return Py_NotImplemented;
}
diff --git a/numpy/core/tests/test_array_coercion.py b/numpy/core/tests/test_array_coercion.py
index 78def9360..b966ee7b0 100644
--- a/numpy/core/tests/test_array_coercion.py
+++ b/numpy/core/tests/test_array_coercion.py
@@ -689,3 +689,33 @@ class TestArrayLikes:
np.array(arr)
with pytest.raises(MemoryError):
np.array([arr])
+
+ @pytest.mark.parametrize("attribute",
+ ["__array_interface__", "__array__", "__array_struct__"])
+ def test_bad_array_like_attributes(self, attribute):
+ # Check that errors during attribute retrieval are raised unless
+ # they are Attribute errors.
+
+ class BadInterface:
+ def __getattr__(self, attr):
+ if attr == attribute:
+ raise RuntimeError
+ super().__getattr__(attr)
+
+ with pytest.raises(RuntimeError):
+ 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())
+