Skip to content

Commit 363af44

Browse files
author
Stefan Krah
committed
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.
1 parent a5e1dbe commit 363af44

File tree

4 files changed

+90
-29
lines changed

4 files changed

+90
-29
lines changed

Lib/test/test_buffer.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,6 +1007,7 @@ def test_ndarray_getbuf(self):
10071007
# shape, strides, offset
10081008
structure = (
10091009
([], [], 0),
1010+
([1,3,1], [], 0),
10101011
([12], [], 0),
10111012
([12], [-1], 11),
10121013
([6], [2], 0),
@@ -1078,6 +1079,18 @@ def test_ndarray_getbuf(self):
10781079
self.assertRaises(BufferError, ndarray, ex, getbuf=PyBUF_ANY_CONTIGUOUS)
10791080
nd = ndarray(ex, getbuf=PyBUF_SIMPLE)
10801081

1082+
# Issue #22445: New precise contiguity definition.
1083+
for shape in [1,12,1], [7,0,7]:
1084+
for order in 0, ND_FORTRAN:
1085+
ex = ndarray(items, shape=shape, flags=order|ND_WRITABLE)
1086+
self.assertTrue(is_contiguous(ex, 'F'))
1087+
self.assertTrue(is_contiguous(ex, 'C'))
1088+
1089+
for flags in requests:
1090+
nd = ndarray(ex, getbuf=flags)
1091+
self.assertTrue(is_contiguous(nd, 'F'))
1092+
self.assertTrue(is_contiguous(nd, 'C'))
1093+
10811094
def test_ndarray_exceptions(self):
10821095
nd = ndarray([9], [1])
10831096
ndm = ndarray([9], [1], flags=ND_VAREXPORT)

Misc/NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1531,6 +1531,10 @@ Build
15311531
C API
15321532
-----
15331533

1534+
- Issue #22445: PyBuffer_IsContiguous() now implements precise contiguity
1535+
tests, compatible with NumPy's NPY_RELAXED_STRIDES_CHECKING compilation
1536+
flag. Previously the function reported false negatives for corner cases.
1537+
15341538
- Issue #22079: PyType_Ready() now checks that statically allocated type has
15351539
no dynamically allocated bases.
15361540

Modules/_testbuffer.c

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1510,6 +1510,19 @@ ndarray_getbuf(NDArrayObject *self, Py_buffer *view, int flags)
15101510
view->shape = NULL;
15111511
}
15121512

1513+
/* Ascertain that the new buffer has the same contiguity as the exporter */
1514+
if (ND_C_CONTIGUOUS(baseflags) != PyBuffer_IsContiguous(view, 'C') ||
1515+
/* skip cast to 1-d */
1516+
(view->format != NULL && view->shape != NULL &&
1517+
ND_FORTRAN_CONTIGUOUS(baseflags) != PyBuffer_IsContiguous(view, 'F')) ||
1518+
/* cast to 1-d */
1519+
(view->format == NULL && view->shape == NULL &&
1520+
!PyBuffer_IsContiguous(view, 'F'))) {
1521+
PyErr_SetString(PyExc_BufferError,
1522+
"ndarray: contiguity mismatch in getbuf()");
1523+
return -1;
1524+
}
1525+
15131526
view->obj = (PyObject *)self;
15141527
Py_INCREF(view->obj);
15151528
self->head->exports++;
@@ -2206,6 +2219,8 @@ ndarray_add_suboffsets(PyObject *self, PyObject *dummy)
22062219
for (i = 0; i < base->ndim; i++)
22072220
base->suboffsets[i] = -1;
22082221

2222+
nd->head->flags &= ~(ND_C|ND_FORTRAN);
2223+
22092224
Py_RETURN_NONE;
22102225
}
22112226

@@ -2469,13 +2484,12 @@ arraycmp(const Py_ssize_t *a1, const Py_ssize_t *a2, const Py_ssize_t *shape,
24692484
{
24702485
Py_ssize_t i;
24712486

2472-
if (ndim == 1 && shape && shape[0] == 1) {
2473-
/* This is for comparing strides: For example, the array
2474-
[175], shape=[1], strides=[-5] is considered contiguous. */
2475-
return 1;
2476-
}
24772487

24782488
for (i = 0; i < ndim; i++) {
2489+
if (shape && shape[i] <= 1) {
2490+
/* strides can differ if the dimension is less than 2 */
2491+
continue;
2492+
}
24792493
if (a1[i] != a2[i]) {
24802494
return 0;
24812495
}
@@ -2555,30 +2569,35 @@ is_contiguous(PyObject *self, PyObject *args)
25552569
PyObject *obj;
25562570
PyObject *order;
25572571
PyObject *ret = NULL;
2558-
Py_buffer view;
2572+
Py_buffer view, *base;
25592573
char ord;
25602574

25612575
if (!PyArg_ParseTuple(args, "OO", &obj, &order)) {
25622576
return NULL;
25632577
}
25642578

2565-
if (PyObject_GetBuffer(obj, &view, PyBUF_FULL_RO) < 0) {
2566-
PyErr_SetString(PyExc_TypeError,
2567-
"is_contiguous: object does not implement the buffer "
2568-
"protocol");
2579+
ord = get_ascii_order(order);
2580+
if (ord == CHAR_MAX) {
25692581
return NULL;
25702582
}
25712583

2572-
ord = get_ascii_order(order);
2573-
if (ord == CHAR_MAX) {
2574-
goto release;
2584+
if (NDArray_Check(obj)) {
2585+
/* Skip the buffer protocol to check simple etc. buffers directly. */
2586+
base = &((NDArrayObject *)obj)->head->base;
2587+
ret = PyBuffer_IsContiguous(base, ord) ? Py_True : Py_False;
2588+
}
2589+
else {
2590+
if (PyObject_GetBuffer(obj, &view, PyBUF_FULL_RO) < 0) {
2591+
PyErr_SetString(PyExc_TypeError,
2592+
"is_contiguous: object does not implement the buffer "
2593+
"protocol");
2594+
return NULL;
2595+
}
2596+
ret = PyBuffer_IsContiguous(&view, ord) ? Py_True : Py_False;
2597+
PyBuffer_Release(&view);
25752598
}
25762599

2577-
ret = PyBuffer_IsContiguous(&view, ord) ? Py_True : Py_False;
25782600
Py_INCREF(ret);
2579-
2580-
release:
2581-
PyBuffer_Release(&view);
25822601
return ret;
25832602
}
25842603

Objects/abstract.c

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -367,16 +367,35 @@ _IsFortranContiguous(const Py_buffer *view)
367367
Py_ssize_t sd, dim;
368368
int i;
369369

370-
if (view->ndim == 0) return 1;
371-
if (view->strides == NULL) return (view->ndim == 1);
370+
/* 1) len = product(shape) * itemsize
371+
2) itemsize > 0
372+
3) len = 0 <==> exists i: shape[i] = 0 */
373+
if (view->len == 0) return 1;
374+
if (view->strides == NULL) { /* C-contiguous by definition */
375+
/* Trivially F-contiguous */
376+
if (view->ndim <= 1) return 1;
377+
378+
/* ndim > 1 implies shape != NULL */
379+
assert(view->shape != NULL);
380+
381+
/* Effectively 1-d */
382+
sd = 0;
383+
for (i=0; i<view->ndim; i++) {
384+
if (view->shape[i] > 1) sd += 1;
385+
}
386+
return sd <= 1;
387+
}
388+
389+
/* strides != NULL implies both of these */
390+
assert(view->ndim > 0);
391+
assert(view->shape != NULL);
372392

373393
sd = view->itemsize;
374-
if (view->ndim == 1) return (view->shape[0] == 1 ||
375-
sd == view->strides[0]);
376394
for (i=0; i<view->ndim; i++) {
377395
dim = view->shape[i];
378-
if (dim == 0) return 1;
379-
if (view->strides[i] != sd) return 0;
396+
if (dim > 1 && view->strides[i] != sd) {
397+
return 0;
398+
}
380399
sd *= dim;
381400
}
382401
return 1;
@@ -388,16 +407,22 @@ _IsCContiguous(const Py_buffer *view)
388407
Py_ssize_t sd, dim;
389408
int i;
390409

391-
if (view->ndim == 0) return 1;
392-
if (view->strides == NULL) return 1;
410+
/* 1) len = product(shape) * itemsize
411+
2) itemsize > 0
412+
3) len = 0 <==> exists i: shape[i] = 0 */
413+
if (view->len == 0) return 1;
414+
if (view->strides == NULL) return 1; /* C-contiguous by definition */
415+
416+
/* strides != NULL implies both of these */
417+
assert(view->ndim > 0);
418+
assert(view->shape != NULL);
393419

394420
sd = view->itemsize;
395-
if (view->ndim == 1) return (view->shape[0] == 1 ||
396-
sd == view->strides[0]);
397421
for (i=view->ndim-1; i>=0; i--) {
398422
dim = view->shape[i];
399-
if (dim == 0) return 1;
400-
if (view->strides[i] != sd) return 0;
423+
if (dim > 1 && view->strides[i] != sd) {
424+
return 0;
425+
}
401426
sd *= dim;
402427
}
403428
return 1;

0 commit comments

Comments
 (0)