summaryrefslogtreecommitdiff
path: root/numpy/core
diff options
context:
space:
mode:
Diffstat (limited to 'numpy/core')
-rw-r--r--numpy/core/src/multiarray/buffer.c105
-rw-r--r--numpy/core/tests/test_multiarray.py24
2 files changed, 86 insertions, 43 deletions
diff --git a/numpy/core/src/multiarray/buffer.c b/numpy/core/src/multiarray/buffer.c
index af40cdc2c..e676682de 100644
--- a/numpy/core/src/multiarray/buffer.c
+++ b/numpy/core/src/multiarray/buffer.c
@@ -456,7 +456,7 @@ static PyObject *_buffer_info_cache = NULL;
/* Fill in the info structure */
static _buffer_info_t*
-_buffer_info_new(PyObject *obj)
+_buffer_info_new(PyObject *obj, npy_bool f_contiguous)
{
/*
* Note that the buffer info is cached as PyLongObjects making them appear
@@ -504,9 +504,43 @@ _buffer_info_new(PyObject *obj)
info->shape = (npy_intp *)((char *)info + sizeof(_buffer_info_t));
assert((size_t)info->shape % sizeof(npy_intp) == 0);
info->strides = info->shape + PyArray_NDIM(arr);
- for (k = 0; k < PyArray_NDIM(arr); ++k) {
- info->shape[k] = PyArray_DIMS(arr)[k];
- info->strides[k] = PyArray_STRIDES(arr)[k];
+
+#if NPY_RELAXED_STRIDES_CHECKING
+ /*
+ * When NPY_RELAXED_STRIDES_CHECKING is used, some buffer users
+ * may expect a contiguous buffer to have well formatted strides
+ * also when a dimension is 1, but we do not guarantee this
+ * internally. Thus, recalculate strides for contiguous arrays.
+ * (This is unnecessary, but has no effect in the case where
+ * NPY_RELAXED_STRIDES CHECKING is disabled.)
+ */
+ if (PyArray_IS_C_CONTIGUOUS(arr) && !(
+ f_contiguous && PyArray_IS_F_CONTIGUOUS(arr))) {
+ Py_ssize_t sd = PyArray_ITEMSIZE(arr);
+ for (k = info->ndim-1; k >= 0; --k) {
+ info->shape[k] = PyArray_DIMS(arr)[k];
+ info->strides[k] = sd;
+ sd *= info->shape[k];
+ }
+ }
+ else if (PyArray_IS_F_CONTIGUOUS(arr)) {
+ Py_ssize_t sd = PyArray_ITEMSIZE(arr);
+ for (k = 0; k < info->ndim; ++k) {
+ info->shape[k] = PyArray_DIMS(arr)[k];
+ info->strides[k] = sd;
+ sd *= info->shape[k];
+ }
+ }
+ else {
+#else /* NPY_RELAXED_STRIDES_CHECKING */
+ /* We can always use the arrays strides directly */
+ {
+#endif
+
+ for (k = 0; k < PyArray_NDIM(arr); ++k) {
+ info->shape[k] = PyArray_DIMS(arr)[k];
+ info->strides[k] = PyArray_STRIDES(arr)[k];
+ }
}
}
Py_INCREF(descr);
@@ -565,7 +599,7 @@ _buffer_info_free(_buffer_info_t *info)
/* Get buffer info from the global dictionary */
static _buffer_info_t*
-_buffer_get_info(PyObject *obj)
+_buffer_get_info(PyObject *obj, npy_bool f_contiguous)
{
PyObject *key = NULL, *item_list = NULL, *item = NULL;
_buffer_info_t *info = NULL, *old_info = NULL;
@@ -578,7 +612,7 @@ _buffer_get_info(PyObject *obj)
}
/* Compute information */
- info = _buffer_info_new(obj);
+ info = _buffer_info_new(obj, f_contiguous);
if (info == NULL) {
return NULL;
}
@@ -591,15 +625,35 @@ _buffer_get_info(PyObject *obj)
item_list = PyDict_GetItem(_buffer_info_cache, key);
if (item_list != NULL) {
+ Py_ssize_t item_list_length = PyList_GET_SIZE(item_list);
Py_INCREF(item_list);
- if (PyList_GET_SIZE(item_list) > 0) {
- item = PyList_GetItem(item_list, PyList_GET_SIZE(item_list) - 1);
+ if (item_list_length > 0) {
+ item = PyList_GetItem(item_list, item_list_length - 1);
old_info = (_buffer_info_t*)PyLong_AsVoidPtr(item);
-
if (_buffer_info_cmp(info, old_info) == 0) {
_buffer_info_free(info);
info = old_info;
}
+ else {
+ if (item_list_length > 1 && info->ndim > 1) {
+ /*
+ * Some arrays are C- and F-contiguous and if they have more
+ * than one dimension, the buffer-info may differ between
+ * the two due to RELAXED_STRIDES_CHECKING.
+ * If we export both buffers, the first stored one may be
+ * the one for the other contiguity, so check both.
+ * This is generally very unlikely in all other cases, since
+ * in all other cases the first one will match unless array
+ * metadata was modified in-place (which is discouraged).
+ */
+ item = PyList_GetItem(item_list, item_list_length - 2);
+ old_info = (_buffer_info_t*)PyLong_AsVoidPtr(item);
+ if (_buffer_info_cmp(info, old_info) == 0) {
+ _buffer_info_free(info);
+ info = old_info;
+ }
+ }
+ }
}
}
else {
@@ -706,7 +760,7 @@ array_getbuffer(PyObject *obj, Py_buffer *view, int flags)
}
/* Fill in information */
- info = _buffer_get_info(obj);
+ info = _buffer_get_info(obj, (flags & PyBUF_F_CONTIGUOUS) == PyBUF_F_CONTIGUOUS);
if (info == NULL) {
goto fail;
}
@@ -742,35 +796,6 @@ array_getbuffer(PyObject *obj, Py_buffer *view, int flags)
}
if ((flags & PyBUF_STRIDES) == PyBUF_STRIDES) {
view->strides = info->strides;
-
-#ifdef NPY_RELAXED_STRIDES_CHECKING
- /*
- * If NPY_RELAXED_STRIDES_CHECKING is on, the array may be
- * contiguous, but it won't look that way to Python when it
- * tries to determine contiguity by looking at the strides
- * (since one of the elements may be -1). In that case, just
- * regenerate strides from shape.
- */
- if (PyArray_CHKFLAGS(self, NPY_ARRAY_C_CONTIGUOUS) &&
- !((flags & PyBUF_F_CONTIGUOUS) == PyBUF_F_CONTIGUOUS)) {
- Py_ssize_t sd = view->itemsize;
- int i;
-
- for (i = view->ndim-1; i >= 0; --i) {
- view->strides[i] = sd;
- sd *= view->shape[i];
- }
- }
- else if (PyArray_CHKFLAGS(self, NPY_ARRAY_F_CONTIGUOUS)) {
- Py_ssize_t sd = view->itemsize;
- int i;
-
- for (i = 0; i < view->ndim; ++i) {
- view->strides[i] = sd;
- sd *= view->shape[i];
- }
- }
-#endif
}
else {
view->strides = NULL;
@@ -800,7 +825,7 @@ void_getbuffer(PyObject *self, Py_buffer *view, int flags)
}
/* Fill in information */
- info = _buffer_get_info(self);
+ info = _buffer_get_info(self, 0);
if (info == NULL) {
goto fail;
}
diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py
index 6f8af1757..d46e4ce9b 100644
--- a/numpy/core/tests/test_multiarray.py
+++ b/numpy/core/tests/test_multiarray.py
@@ -7178,9 +7178,10 @@ class TestNewBufferProtocol:
x3 = np.arange(dt3.itemsize, dtype=np.int8).view(dt3)
self._check_roundtrip(x3)
- def test_relaxed_strides(self):
- # Test that relaxed strides are converted to non-relaxed
- c = np.ones((1, 10, 10), dtype='i8')
+ @pytest.mark.valgrind_error(reason="leaks buffer info cache temporarily.")
+ def test_relaxed_strides(self, c=np.ones((1, 10, 10), dtype='i8')):
+ # Note: c defined as parameter so that it is persistent and leak
+ # checks will notice gh-16934 (buffer info cache leak).
# Check for NPY_RELAXED_STRIDES_CHECKING:
if np.ones((10, 1), order="C").flags.f_contiguous:
@@ -7205,6 +7206,23 @@ class TestNewBufferProtocol:
arr, ['C_CONTIGUOUS'])
assert_(strides[-1] == 8)
+ @pytest.mark.valgrind_error(reason="leaks buffer info cache temporarily.")
+ @pytest.mark.skipif(not np.ones((10, 1), order="C").flags.f_contiguous,
+ reason="Test is unnecessary (but fails) without relaxed strides.")
+ def test_relaxed_strides_buffer_info_leak(self, arr=np.ones((1, 10))):
+ """Test that alternating export of C- and F-order buffers from
+ an array which is both C- and F-order when relaxed strides is
+ active works.
+ This test defines array in the signature to ensure leaking more
+ references every time the test is run (catching the leak with
+ pytest-leaks).
+ """
+ for i in range(10):
+ _, s = _multiarray_tests.get_buffer_info(arr, ['F_CONTIGUOUS'])
+ assert s == (8, 8)
+ _, s = _multiarray_tests.get_buffer_info(arr, ['C_CONTIGUOUS'])
+ assert s == (80, 8)
+
def test_out_of_order_fields(self):
dt = np.dtype(dict(
formats=['<i4', '<i4'],