From 6f9223c604c501bec9357b7cbff9211996fe834e Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Mon, 16 Nov 2020 12:34:58 -0600 Subject: [PATCH] BUG: Raise recursion and memory error during dimension discovery 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. --- numpy/core/src/multiarray/ctors.c | 39 +++++++++++++++++++++++++---- 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 be0b0b8b0266..d347866de125 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 eef0dd8a43ea..6600cc61869d 100644 --- a/numpy/core/tests/test_multiarray.py +++ b/numpy/core/tests/test_multiarray.py @@ -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']