Skip to content

Commit

Permalink
BUG: Raise recursion and memory error during dimension discovery
Browse files Browse the repository at this point in the history
This mitigates gh-17785 on the 1.19.x branch. Note that the actual
problem seems to have more layers, so that the bad code will still
cause crashes on certain systems or call contexts (I am not sure).

Pre 1.19.x did not check for `__array__` in the dimension discovery
code, so was unaffected by the issue in gh-17785.
  • Loading branch information
seberg committed Dec 17, 2020
1 parent 672a6c8 commit 6f9223c
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 5 deletions.
39 changes: 34 additions & 5 deletions numpy/core/src/multiarray/ctors.c
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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();
}

Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down
31 changes: 31 additions & 0 deletions numpy/core/tests/test_multiarray.py
Expand Up @@ -818,6 +818,37 @@ def __array__(self, dtype=None):

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']
Expand Down

0 comments on commit 6f9223c

Please sign in to comment.