Skip to content

Navigation Menu

Sign in
Appearance settings
Sign up
Appearance settings
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 34 additions & 5 deletions 39 numpy/core/src/multiarray/ctors.c
Original file line number Diff line number Diff line change
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();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using PyErr_WriteUnraisable(NULL) in place here (and everywhere) might help smoke out the stack overflow

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I did that in most places, but not all. Somehow the places I considered irrelevant (because execution should hit another later) are relevant (or I missed one). I guess creating a nested array with a depth related to the recursion limit, is somehow just too much... Failing hard would likely solve it (i.e. in my python "crash" example, you still need the try/except RecursionError:)

I will first give it one more shot on master later (which is simpler), making the equivalent paths there more strict.

}
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 31 numpy/core/tests/test_multiarray.py
Original file line number Diff line number Diff line change
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
Morty Proxy This is a proxified and sanitized view of the page, visit original site.