From ed134d5ada340613988184906e77f07e6ea2d241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jouni=20K=2E=20Sepp=C3=A4nen?= Date: Thu, 15 Oct 2015 07:44:37 +0300 Subject: [PATCH 1/4] Throw errors when indexing into empty array_view objects Should catch issues like #5185 while not adding a big perf penalty --- src/numpy_cpp.h | 65 ++++++++++++++++++++++++++++++++++++++++++--- src/py_exceptions.h | 14 +++++++--- 2 files changed, 72 insertions(+), 7 deletions(-) diff --git a/src/numpy_cpp.h b/src/numpy_cpp.h index 0bc2d5c03854..7480503cf462 100644 --- a/src/numpy_cpp.h +++ b/src/numpy_cpp.h @@ -27,6 +27,13 @@ #include #include +#include + +#if defined(__GNUC__) || defined(__clang__) +#define unlikely(x) __builtin_expect(!!(x), 0) +#else +#define unlikely(x) (x) +#endif namespace numpy { @@ -235,6 +242,9 @@ class array_view_accessors T &operator()(npy_intp i) { AVC *self = static_cast(this); + if (unlikely(self->m_empty)) { + throw std::out_of_range("indexing into an array_view with no data"); + } return *reinterpret_cast(self->m_data + self->m_strides[0] * i); } @@ -242,6 +252,9 @@ class array_view_accessors const T &operator()(npy_intp i) const { const AVC *self = static_cast(this); + if (unlikely(self->m_empty)) { + throw std::out_of_range("indexing into an array_view with no data"); + } return *reinterpret_cast(self->m_data + self->m_strides[0] * i); } @@ -249,6 +262,9 @@ class array_view_accessors T &operator[](npy_intp i) { AVC *self = static_cast(this); + if (unlikely(self->m_empty)) { + throw std::out_of_range("indexing into an array_view with no data"); + } return *reinterpret_cast(self->m_data + self->m_strides[0] * i); } @@ -256,6 +272,9 @@ class array_view_accessors const T &operator[](npy_intp i) const { const AVC *self = static_cast(this); + if (unlikely(self->m_empty)) { + throw std::out_of_range("indexing into an array_view with no data"); + } return *reinterpret_cast(self->m_data + self->m_strides[0] * i); } @@ -271,6 +290,9 @@ class array_view_accessors T &operator()(npy_intp i, npy_intp j) { AVC *self = static_cast(this); + if (unlikely(self->m_empty)) { + throw std::out_of_range("indexing into an array_view with no data"); + } return *reinterpret_cast(self->m_data + self->m_strides[0] * i + self->m_strides[1] * j); @@ -279,6 +301,9 @@ class array_view_accessors const T &operator()(npy_intp i, npy_intp j) const { const AVC *self = static_cast(this); + if (unlikely(self->m_empty)) { + throw std::out_of_range("indexing into an array_view with no data"); + } return *reinterpret_cast(self->m_data + self->m_strides[0] * i + self->m_strides[1] * j); @@ -287,6 +312,9 @@ class array_view_accessors sub_t operator[](npy_intp i) const { const AVC *self = static_cast(this); + if (unlikely(self->m_empty)) { + throw std::out_of_range("indexing into an array_view with no data"); + } return sub_t(self->m_arr, self->m_data + self->m_strides[0] * i, @@ -305,6 +333,9 @@ class array_view_accessors T &operator()(npy_intp i, npy_intp j, npy_intp k) { AVC *self = static_cast(this); + if (unlikely(self->m_empty)) { + throw std::out_of_range("indexing into an array_view with no data"); + } return *reinterpret_cast(self->m_data + self->m_strides[0] * i + self->m_strides[1] * j + self->m_strides[2] * k); @@ -313,6 +344,9 @@ class array_view_accessors const T &operator()(npy_intp i, npy_intp j, npy_intp k) const { const AVC *self = static_cast(this); + if (unlikely(self->m_empty)) { + throw std::out_of_range("indexing into an array_view with no data"); + } return *reinterpret_cast(self->m_data + self->m_strides[0] * i + self->m_strides[1] * j + self->m_strides[2] * k); @@ -321,6 +355,9 @@ class array_view_accessors sub_t operator[](npy_intp i) const { const AVC *self = static_cast(this); + if (unlikely(self->m_empty)) { + throw std::out_of_range("indexing into an array_view with no data"); + } return sub_t(self->m_arr, self->m_data + self->m_strides[0] * i, @@ -349,6 +386,9 @@ class array_view : public detail::array_view_accessors npy_intp *m_shape; npy_intp *m_strides; char *m_data; + // Flag for a limited kind of bounds checking, + // not the same as the empty() method which checks the outer dimension + bool m_empty; public: typedef T value_type; @@ -361,6 +401,7 @@ class array_view : public detail::array_view_accessors { m_shape = zeros; m_strides = zeros; + m_empty = true; } array_view(PyObject *arr, bool contiguous = false) : m_arr(NULL), m_data(NULL) @@ -377,6 +418,7 @@ class array_view : public detail::array_view_accessors m_data = other.m_data; m_shape = other.m_shape; m_strides = other.m_strides; + m_empty = other.m_empty; } array_view(PyArrayObject *arr, char *data, npy_intp *shape, npy_intp *strides) @@ -386,6 +428,12 @@ class array_view : public detail::array_view_accessors m_data = data; m_shape = shape; m_strides = strides; + m_empty = (ND == 0); + for (size_t i = 0; i < ND; i++) { + if (shape[i] == 0) { + m_empty = true; + } + } } array_view(npy_intp shape[ND]) : m_arr(NULL), m_shape(NULL), m_strides(NULL), m_data(NULL) @@ -416,6 +464,7 @@ class array_view : public detail::array_view_accessors m_data = other.m_data; m_shape = other.m_shape; m_strides = other.m_strides; + m_empty = other.m_empty; } return *this; } @@ -430,6 +479,7 @@ class array_view : public detail::array_view_accessors m_data = NULL; m_shape = zeros; m_strides = zeros; + m_empty = true; } else { if (contiguous) { tmp = (PyArrayObject *)PyArray_ContiguousFromAny(arr, type_num_of::value, 0, ND); @@ -446,10 +496,11 @@ class array_view : public detail::array_view_accessors m_data = NULL; m_shape = zeros; m_strides = zeros; - if (PyArray_NDIM(tmp) == 0 && ND == 0) { - m_arr = tmp; - return 1; - } + if (PyArray_NDIM(tmp) == 0 && ND == 0) { + m_arr = tmp; + m_empty = true; + return 1; + } } if (PyArray_NDIM(tmp) != ND) { PyErr_Format(PyExc_ValueError, @@ -466,6 +517,12 @@ class array_view : public detail::array_view_accessors m_shape = PyArray_DIMS(m_arr); m_strides = PyArray_STRIDES(m_arr); m_data = (char *)PyArray_BYTES(tmp); + m_empty = (ND == 0); + for (size_t i = 0; i < ND; i++) { + if (m_shape[i] == 0) { + m_empty = true; + } + } } return 1; diff --git a/src/py_exceptions.h b/src/py_exceptions.h index 82dd94dedcab..765017bf3c0b 100644 --- a/src/py_exceptions.h +++ b/src/py_exceptions.h @@ -32,7 +32,7 @@ class exception : public std::exception } \ catch (const std::bad_alloc) \ { \ - PyErr_Format(PyExc_MemoryError, "In %s: Out of memory", (name)); \ + PyErr_Format(PyExc_MemoryError, "In %s: Out of memory", (name)); \ { \ cleanup; \ } \ @@ -40,7 +40,15 @@ class exception : public std::exception } \ catch (const std::overflow_error &e) \ { \ - PyErr_Format(PyExc_OverflowError, "In %s: %s", (name), e.what()); \ + PyErr_Format(PyExc_OverflowError, "In %s: %s", (name), e.what()); \ + { \ + cleanup; \ + } \ + return (errorcode); \ + } \ + catch (const std::out_of_range &e) \ + { \ + PyErr_Format(PyExc_IndexError, "In %s: %s", (name), e.what()); \ { \ cleanup; \ } \ @@ -48,7 +56,7 @@ class exception : public std::exception } \ catch (char const *e) \ { \ - PyErr_Format(PyExc_RuntimeError, "In %s: %s", (name), e); \ + PyErr_Format(PyExc_RuntimeError, "In %s: %s", (name), e); \ { \ cleanup; \ } \ From aa38c5b0e338a708db573e47632019bb0bfe3248 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jouni=20K=2E=20Sepp=C3=A4nen?= Date: Thu, 15 Oct 2015 09:28:28 +0300 Subject: [PATCH 2/4] Use null data instead of a separate flag to mark empty array views --- src/numpy_cpp.h | 57 +++++++++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/src/numpy_cpp.h b/src/numpy_cpp.h index 7480503cf462..44c7a3566d68 100644 --- a/src/numpy_cpp.h +++ b/src/numpy_cpp.h @@ -242,7 +242,7 @@ class array_view_accessors T &operator()(npy_intp i) { AVC *self = static_cast(this); - if (unlikely(self->m_empty)) { + if (unlikely(self->m_data == NULL)) { throw std::out_of_range("indexing into an array_view with no data"); } @@ -252,7 +252,7 @@ class array_view_accessors const T &operator()(npy_intp i) const { const AVC *self = static_cast(this); - if (unlikely(self->m_empty)) { + if (unlikely(self->m_data == NULL)) { throw std::out_of_range("indexing into an array_view with no data"); } @@ -262,7 +262,7 @@ class array_view_accessors T &operator[](npy_intp i) { AVC *self = static_cast(this); - if (unlikely(self->m_empty)) { + if (unlikely(self->m_data == NULL)) { throw std::out_of_range("indexing into an array_view with no data"); } @@ -272,7 +272,7 @@ class array_view_accessors const T &operator[](npy_intp i) const { const AVC *self = static_cast(this); - if (unlikely(self->m_empty)) { + if (unlikely(self->m_data == NULL)) { throw std::out_of_range("indexing into an array_view with no data"); } @@ -290,7 +290,7 @@ class array_view_accessors T &operator()(npy_intp i, npy_intp j) { AVC *self = static_cast(this); - if (unlikely(self->m_empty)) { + if (unlikely(self->m_data == NULL)) { throw std::out_of_range("indexing into an array_view with no data"); } @@ -301,7 +301,7 @@ class array_view_accessors const T &operator()(npy_intp i, npy_intp j) const { const AVC *self = static_cast(this); - if (unlikely(self->m_empty)) { + if (unlikely(self->m_data == NULL)) { throw std::out_of_range("indexing into an array_view with no data"); } @@ -312,7 +312,7 @@ class array_view_accessors sub_t operator[](npy_intp i) const { const AVC *self = static_cast(this); - if (unlikely(self->m_empty)) { + if (unlikely(self->m_data == NULL)) { throw std::out_of_range("indexing into an array_view with no data"); } @@ -333,7 +333,7 @@ class array_view_accessors T &operator()(npy_intp i, npy_intp j, npy_intp k) { AVC *self = static_cast(this); - if (unlikely(self->m_empty)) { + if (unlikely(self->m_data == NULL)) { throw std::out_of_range("indexing into an array_view with no data"); } @@ -344,7 +344,7 @@ class array_view_accessors const T &operator()(npy_intp i, npy_intp j, npy_intp k) const { const AVC *self = static_cast(this); - if (unlikely(self->m_empty)) { + if (unlikely(self->m_data == NULL)) { throw std::out_of_range("indexing into an array_view with no data"); } @@ -355,7 +355,7 @@ class array_view_accessors sub_t operator[](npy_intp i) const { const AVC *self = static_cast(this); - if (unlikely(self->m_empty)) { + if (unlikely(self->m_data == NULL)) { throw std::out_of_range("indexing into an array_view with no data"); } @@ -386,9 +386,6 @@ class array_view : public detail::array_view_accessors npy_intp *m_shape; npy_intp *m_strides; char *m_data; - // Flag for a limited kind of bounds checking, - // not the same as the empty() method which checks the outer dimension - bool m_empty; public: typedef T value_type; @@ -401,7 +398,6 @@ class array_view : public detail::array_view_accessors { m_shape = zeros; m_strides = zeros; - m_empty = true; } array_view(PyObject *arr, bool contiguous = false) : m_arr(NULL), m_data(NULL) @@ -418,22 +414,26 @@ class array_view : public detail::array_view_accessors m_data = other.m_data; m_shape = other.m_shape; m_strides = other.m_strides; - m_empty = other.m_empty; } array_view(PyArrayObject *arr, char *data, npy_intp *shape, npy_intp *strides) { - m_arr = arr; - Py_XINCREF(arr); - m_data = data; - m_shape = shape; - m_strides = strides; - m_empty = (ND == 0); + bool empty = (ND == 0); for (size_t i = 0; i < ND; i++) { if (shape[i] == 0) { - m_empty = true; + empty = true; } } + + m_arr = arr; + Py_XINCREF(arr); + if (empty) { + m_data = NULL; + } else { + m_data = data; + } + m_shape = shape; + m_strides = strides; } array_view(npy_intp shape[ND]) : m_arr(NULL), m_shape(NULL), m_strides(NULL), m_data(NULL) @@ -464,7 +464,6 @@ class array_view : public detail::array_view_accessors m_data = other.m_data; m_shape = other.m_shape; m_strides = other.m_strides; - m_empty = other.m_empty; } return *this; } @@ -479,7 +478,6 @@ class array_view : public detail::array_view_accessors m_data = NULL; m_shape = zeros; m_strides = zeros; - m_empty = true; } else { if (contiguous) { tmp = (PyArrayObject *)PyArray_ContiguousFromAny(arr, type_num_of::value, 0, ND); @@ -498,7 +496,6 @@ class array_view : public detail::array_view_accessors m_strides = zeros; if (PyArray_NDIM(tmp) == 0 && ND == 0) { m_arr = tmp; - m_empty = true; return 1; } } @@ -516,13 +513,17 @@ class array_view : public detail::array_view_accessors m_arr = tmp; m_shape = PyArray_DIMS(m_arr); m_strides = PyArray_STRIDES(m_arr); - m_data = (char *)PyArray_BYTES(tmp); - m_empty = (ND == 0); + bool empty = (ND == 0); for (size_t i = 0; i < ND; i++) { if (m_shape[i] == 0) { - m_empty = true; + empty = true; } } + if (empty) { + m_data = NULL; + } else { + m_data = (char *)PyArray_BYTES(tmp); + } } return 1; From cae71f6a13f1c35e40fd8ecf1bda34cb5a7bd116 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jouni=20K=2E=20Sepp=C3=A4nen?= Date: Thu, 15 Oct 2015 15:28:32 +0300 Subject: [PATCH 3/4] Make array_view.size return 0 for empty arrays --- src/numpy_cpp.h | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/numpy_cpp.h b/src/numpy_cpp.h index 44c7a3566d68..ff1b0033db58 100644 --- a/src/numpy_cpp.h +++ b/src/numpy_cpp.h @@ -539,7 +539,17 @@ class array_view : public detail::array_view_accessors size_t size() const { - return (size_t)dim(0); + bool empty = (ND == 0); + for (size_t i = 0; i < ND; i++) { + if (m_shape[i] == 0) { + empty = true; + } + } + if (empty) { + return 0; + } else { + return (size_t)dim(0); + } } bool empty() const From 8698b5ee9363c4b9bd37537a4b0e6882bdf16cb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jouni=20K=2E=20Sepp=C3=A4nen?= Date: Thu, 15 Oct 2015 18:41:55 +0300 Subject: [PATCH 4/4] Use m_data==NULL as the condition in size() --- src/numpy_cpp.h | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/numpy_cpp.h b/src/numpy_cpp.h index ff1b0033db58..1629688792bd 100644 --- a/src/numpy_cpp.h +++ b/src/numpy_cpp.h @@ -539,13 +539,7 @@ class array_view : public detail::array_view_accessors size_t size() const { - bool empty = (ND == 0); - for (size_t i = 0; i < ND; i++) { - if (m_shape[i] == 0) { - empty = true; - } - } - if (empty) { + if (m_data == NULL) { return 0; } else { return (size_t)dim(0);