diff options
author | Charles Harris <charlesr.harris@gmail.com> | 2020-12-17 17:16:44 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-12-17 17:16:44 -0700 |
commit | 8ab99be58a99aab0c86f47afaa38d65739d22b3b (patch) | |
tree | bdf024f4bf638226a5065a7edd5e3b326f54ee15 | |
parent | eeaf12d118ca8b72133768ce00fdde0b3fda6a16 (diff) | |
parent | 6f9223c604c501bec9357b7cbff9211996fe834e (diff) | |
download | numpy-8ab99be58a99aab0c86f47afaa38d65739d22b3b.tar.gz |
Merge pull request #17786 from seberg/bad-array-recursion-error
BUG: Raise recursion error during dimension discovery
-rw-r--r-- | numpy/core/src/multiarray/ctors.c | 39 | ||||
-rw-r--r-- | numpy/core/tests/test_multiarray.py | 31 |
2 files changed, 65 insertions, 5 deletions
diff --git a/numpy/core/src/multiarray/ctors.c b/numpy/core/src/multiarray/ctors.c index 6e6f67e7d..851244adf 100644 --- a/numpy/core/src/multiarray/ctors.c +++ b/numpy/core/src/multiarray/ctors.c @@ -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; } @@ -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(); } @@ -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 */ @@ -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; } @@ -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; } diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py index f7c7b0ad9..2d087f7ca 100644 --- a/numpy/core/tests/test_multiarray.py +++ b/numpy/core/tests/test_multiarray.py @@ -818,6 +818,37 @@ class TestCreation: 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'] |