Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BUG: Ignore fewer errors during array-coercion #17817

Merged
merged 1 commit into from Dec 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 16 additions & 2 deletions numpy/core/src/multiarray/array_coercion.c
Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions numpy/core/src/multiarray/ctors.c
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down
30 changes: 30 additions & 0 deletions numpy/core/tests/test_array_coercion.py
Expand Up @@ -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())