diff --git a/Lib/test/test_buffer.py b/Lib/test/test_buffer.py index 1cdc771a9aa..0976fa9e34d 100644 --- a/Lib/test/test_buffer.py +++ b/Lib/test/test_buffer.py @@ -1007,6 +1007,7 @@ class TestBufferProtocol(unittest.TestCase): # shape, strides, offset structure = ( ([], [], 0), + ([1,3,1], [], 0), ([12], [], 0), ([12], [-1], 11), ([6], [2], 0), @@ -1078,6 +1079,18 @@ class TestBufferProtocol(unittest.TestCase): self.assertRaises(BufferError, ndarray, ex, getbuf=PyBUF_ANY_CONTIGUOUS) nd = ndarray(ex, getbuf=PyBUF_SIMPLE) + # Issue #22445: New precise contiguity definition. + for shape in [1,12,1], [7,0,7]: + for order in 0, ND_FORTRAN: + ex = ndarray(items, shape=shape, flags=order|ND_WRITABLE) + self.assertTrue(is_contiguous(ex, 'F')) + self.assertTrue(is_contiguous(ex, 'C')) + + for flags in requests: + nd = ndarray(ex, getbuf=flags) + self.assertTrue(is_contiguous(nd, 'F')) + self.assertTrue(is_contiguous(nd, 'C')) + def test_ndarray_exceptions(self): nd = ndarray([9], [1]) ndm = ndarray([9], [1], flags=ND_VAREXPORT) diff --git a/Misc/NEWS b/Misc/NEWS index b725c5d811f..9552e29586b 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -1531,6 +1531,10 @@ Build C API ----- +- Issue #22445: PyBuffer_IsContiguous() now implements precise contiguity + tests, compatible with NumPy's NPY_RELAXED_STRIDES_CHECKING compilation + flag. Previously the function reported false negatives for corner cases. + - Issue #22079: PyType_Ready() now checks that statically allocated type has no dynamically allocated bases. diff --git a/Modules/_testbuffer.c b/Modules/_testbuffer.c index 0c6ef16f175..a563a04cd75 100644 --- a/Modules/_testbuffer.c +++ b/Modules/_testbuffer.c @@ -1510,6 +1510,19 @@ ndarray_getbuf(NDArrayObject *self, Py_buffer *view, int flags) view->shape = NULL; } + /* Ascertain that the new buffer has the same contiguity as the exporter */ + if (ND_C_CONTIGUOUS(baseflags) != PyBuffer_IsContiguous(view, 'C') || + /* skip cast to 1-d */ + (view->format != NULL && view->shape != NULL && + ND_FORTRAN_CONTIGUOUS(baseflags) != PyBuffer_IsContiguous(view, 'F')) || + /* cast to 1-d */ + (view->format == NULL && view->shape == NULL && + !PyBuffer_IsContiguous(view, 'F'))) { + PyErr_SetString(PyExc_BufferError, + "ndarray: contiguity mismatch in getbuf()"); + return -1; + } + view->obj = (PyObject *)self; Py_INCREF(view->obj); self->head->exports++; @@ -2206,6 +2219,8 @@ ndarray_add_suboffsets(PyObject *self, PyObject *dummy) for (i = 0; i < base->ndim; i++) base->suboffsets[i] = -1; + nd->head->flags &= ~(ND_C|ND_FORTRAN); + Py_RETURN_NONE; } @@ -2469,13 +2484,12 @@ arraycmp(const Py_ssize_t *a1, const Py_ssize_t *a2, const Py_ssize_t *shape, { Py_ssize_t i; - if (ndim == 1 && shape && shape[0] == 1) { - /* This is for comparing strides: For example, the array - [175], shape=[1], strides=[-5] is considered contiguous. */ - return 1; - } for (i = 0; i < ndim; i++) { + if (shape && shape[i] <= 1) { + /* strides can differ if the dimension is less than 2 */ + continue; + } if (a1[i] != a2[i]) { return 0; } @@ -2555,30 +2569,35 @@ is_contiguous(PyObject *self, PyObject *args) PyObject *obj; PyObject *order; PyObject *ret = NULL; - Py_buffer view; + Py_buffer view, *base; char ord; if (!PyArg_ParseTuple(args, "OO", &obj, &order)) { return NULL; } - if (PyObject_GetBuffer(obj, &view, PyBUF_FULL_RO) < 0) { - PyErr_SetString(PyExc_TypeError, - "is_contiguous: object does not implement the buffer " - "protocol"); + ord = get_ascii_order(order); + if (ord == CHAR_MAX) { return NULL; } - ord = get_ascii_order(order); - if (ord == CHAR_MAX) { - goto release; + if (NDArray_Check(obj)) { + /* Skip the buffer protocol to check simple etc. buffers directly. */ + base = &((NDArrayObject *)obj)->head->base; + ret = PyBuffer_IsContiguous(base, ord) ? Py_True : Py_False; + } + else { + if (PyObject_GetBuffer(obj, &view, PyBUF_FULL_RO) < 0) { + PyErr_SetString(PyExc_TypeError, + "is_contiguous: object does not implement the buffer " + "protocol"); + return NULL; + } + ret = PyBuffer_IsContiguous(&view, ord) ? Py_True : Py_False; + PyBuffer_Release(&view); } - ret = PyBuffer_IsContiguous(&view, ord) ? Py_True : Py_False; Py_INCREF(ret); - -release: - PyBuffer_Release(&view); return ret; } diff --git a/Objects/abstract.c b/Objects/abstract.c index 9ab60f23ca2..92d1ac88ba0 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -367,16 +367,35 @@ _IsFortranContiguous(const Py_buffer *view) Py_ssize_t sd, dim; int i; - if (view->ndim == 0) return 1; - if (view->strides == NULL) return (view->ndim == 1); + /* 1) len = product(shape) * itemsize + 2) itemsize > 0 + 3) len = 0 <==> exists i: shape[i] = 0 */ + if (view->len == 0) return 1; + if (view->strides == NULL) { /* C-contiguous by definition */ + /* Trivially F-contiguous */ + if (view->ndim <= 1) return 1; + + /* ndim > 1 implies shape != NULL */ + assert(view->shape != NULL); + + /* Effectively 1-d */ + sd = 0; + for (i=0; indim; i++) { + if (view->shape[i] > 1) sd += 1; + } + return sd <= 1; + } + + /* strides != NULL implies both of these */ + assert(view->ndim > 0); + assert(view->shape != NULL); sd = view->itemsize; - if (view->ndim == 1) return (view->shape[0] == 1 || - sd == view->strides[0]); for (i=0; indim; i++) { dim = view->shape[i]; - if (dim == 0) return 1; - if (view->strides[i] != sd) return 0; + if (dim > 1 && view->strides[i] != sd) { + return 0; + } sd *= dim; } return 1; @@ -388,16 +407,22 @@ _IsCContiguous(const Py_buffer *view) Py_ssize_t sd, dim; int i; - if (view->ndim == 0) return 1; - if (view->strides == NULL) return 1; + /* 1) len = product(shape) * itemsize + 2) itemsize > 0 + 3) len = 0 <==> exists i: shape[i] = 0 */ + if (view->len == 0) return 1; + if (view->strides == NULL) return 1; /* C-contiguous by definition */ + + /* strides != NULL implies both of these */ + assert(view->ndim > 0); + assert(view->shape != NULL); sd = view->itemsize; - if (view->ndim == 1) return (view->shape[0] == 1 || - sd == view->strides[0]); for (i=view->ndim-1; i>=0; i--) { dim = view->shape[i]; - if (dim == 0) return 1; - if (view->strides[i] != sd) return 0; + if (dim > 1 && view->strides[i] != sd) { + return 0; + } sd *= dim; } return 1;