diff --git a/numpy/core/src/multiarray/array_coercion.c b/numpy/core/src/multiarray/array_coercion.c index 53d891049e23..603e9d93be6f 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 f6031e370fe7..58571b678aef 100644 --- a/numpy/core/src/multiarray/ctors.c +++ b/numpy/core/src/multiarray/ctors.c @@ -2124,7 +2124,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; } @@ -2392,7 +2392,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 78def936022d..b966ee7b096e 100644 --- a/numpy/core/tests/test_array_coercion.py +++ b/numpy/core/tests/test_array_coercion.py @@ -689,3 +689,33 @@ def test_too_large_array_error_paths(self): 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()) +