diff --git a/Lib/test/test_ctypes/test_pep3118.py b/Lib/test/test_ctypes/test_pep3118.py index 11a0744f5a8e36..a9f8a5a8af37c9 100644 --- a/Lib/test/test_ctypes/test_pep3118.py +++ b/Lib/test/test_ctypes/test_pep3118.py @@ -1,6 +1,7 @@ import re import sys import unittest +from test.support import import_helper from ctypes import (CFUNCTYPE, POINTER, sizeof, Union, Structure, LittleEndianStructure, BigEndianStructure, c_char, c_byte, c_ubyte, @@ -8,6 +9,8 @@ c_long, c_ulong, c_longlong, c_ulonglong, c_uint64, c_bool, c_float, c_double, c_longdouble, py_object) +_testbuffer = import_helper.import_module('_testbuffer') + if sys.byteorder == "little": THIS_ENDIAN = "<" @@ -28,7 +31,7 @@ def normalize(format): class Test(unittest.TestCase): def test_native_types(self): - for tp, fmt, shape, itemtp in native_types: + for tp, fmt, shape, stride, itemtp in native_types: ob = tp() v = memoryview(ob) self.assertEqual(normalize(v.format), normalize(fmt)) @@ -38,10 +41,7 @@ def test_native_types(self): self.assertRaises(TypeError, len, v) self.assertEqual(v.itemsize, sizeof(itemtp)) self.assertEqual(v.shape, shape) - # XXX Issue #12851: PyCData_NewGetBuffer() must provide strides - # if requested. memoryview currently reconstructs missing - # stride information, so this assert will fail. - # self.assertEqual(v.strides, ()) + self.assertEqual(v.strides, stride) # they are always read/write self.assertFalse(v.readonly) @@ -51,8 +51,47 @@ def test_native_types(self): n = n * dim self.assertEqual(n * v.itemsize, len(v.tobytes())) + def test_native_types_shape_strides(self): + # memoryview fills in the shape and the strides which the exporter + # does not provide, and always requests all of them, so check what + # ctypes exports itself. + for tp, fmt, shape, stride, itemtp in native_types: + with self.subTest(tp=tp): + v = _testbuffer.ndarray(tp(), getbuf=_testbuffer.PyBUF_FULL_RO) + self.assertEqual(v.shape, shape) + self.assertEqual(v.strides, stride) + + def test_flags(self): + ob = (c_int * 3 * 2)() + + v = _testbuffer.ndarray(ob, getbuf=_testbuffer.PyBUF_SIMPLE) + # ndim > 1 implies shape != NULL, so a flat buffer is exported. + self.assertEqual(v.ndim, 1) + self.assertEqual(v.shape, ()) + self.assertEqual(v.strides, ()) + + v = _testbuffer.ndarray(ob, getbuf=_testbuffer.PyBUF_ND) + self.assertEqual(v.shape, (2, 3)) + self.assertEqual(v.strides, ()) + + v = _testbuffer.ndarray(ob, getbuf=_testbuffer.PyBUF_STRIDES) + self.assertEqual(v.shape, (2, 3)) + self.assertEqual(v.strides, (12, 4)) + + def test_fortran_contiguous(self): + # A multidimensional array is C contiguous, but not Fortran + # contiguous. A one-dimensional array is contiguous in both orders. + ob = (c_int * 3 * 2)() + with self.assertRaises(BufferError): + _testbuffer.ndarray(ob, getbuf=_testbuffer.PyBUF_F_CONTIGUOUS) + _testbuffer.ndarray(ob, getbuf=_testbuffer.PyBUF_C_CONTIGUOUS) + + ob = (c_int * 3)() + _testbuffer.ndarray(ob, getbuf=_testbuffer.PyBUF_F_CONTIGUOUS) + _testbuffer.ndarray(ob, getbuf=_testbuffer.PyBUF_C_CONTIGUOUS) + def test_endian_types(self): - for tp, fmt, shape, itemtp in endian_types: + for tp, fmt, shape, stride, itemtp in endian_types: ob = tp() v = memoryview(ob) self.assertEqual(v.format, fmt) @@ -62,8 +101,7 @@ def test_endian_types(self): self.assertRaises(TypeError, len, v) self.assertEqual(v.itemsize, sizeof(itemtp)) self.assertEqual(v.shape, shape) - # XXX Issue #12851 - # self.assertEqual(v.strides, ()) + self.assertEqual(v.strides, stride) # they are always read/write self.assertFalse(v.readonly) @@ -156,78 +194,78 @@ class Complete(Structure): native_types = [ - # type format shape calc itemsize + # type format shape stride calc itemsize ## simple types - (c_char, "l:x:>l:y:}".replace('l', s_long), (), BEPoint), - (LEPoint * 1, "T{l:x:>l:y:}".replace('l', s_long), (), POINTER(BEPoint)), - (POINTER(LEPoint), "&T{l:x:>l:y:}".replace('l', s_long), (), (), BEPoint), + (LEPoint * 1, "T{l:x:>l:y:}".replace('l', s_long), (), (), POINTER(BEPoint)), + (POINTER(LEPoint), "&T{ffi_type_pointer.elements = NULL; PyMem_Free(info->format); info->format = NULL; + /* The strides point into the shape allocation. */ PyMem_Free(info->shape); info->shape = NULL; + info->strides = NULL; ctype_clear_stginfo(info); } @@ -1785,7 +1787,7 @@ PyCArrayType_init(PyObject *self, PyObject *args, PyObject *kwds) if (stginfo->format == NULL) goto error; stginfo->ndim = iteminfo->ndim + 1; - stginfo->shape = PyMem_Malloc(sizeof(Py_ssize_t) * stginfo->ndim); + stginfo->shape = PyMem_Malloc(sizeof(Py_ssize_t) * stginfo->ndim * 2); if (stginfo->shape == NULL) { PyErr_NoMemory(); goto error; @@ -1795,6 +1797,15 @@ PyCArrayType_init(PyObject *self, PyObject *args, PyObject *kwds) memmove(&stginfo->shape[1], iteminfo->shape, sizeof(Py_ssize_t) * (stginfo->ndim - 1)); } + stginfo->strides = stginfo->shape + stginfo->ndim; + if (stginfo->ndim > 1) { + memmove(&stginfo->strides[1], iteminfo->strides, + sizeof(Py_ssize_t) * (stginfo->ndim - 1)); + stginfo->strides[0] = stginfo->strides[1] * stginfo->shape[1]; + } + else { + stginfo->strides[0] = iteminfo->size; + } itemsize = iteminfo->size; if (itemsize != 0 && length > PY_SSIZE_T_MAX / itemsize) { @@ -3125,16 +3136,38 @@ PyCData_NewGetBuffer(PyObject *myself, Py_buffer *view, int flags) } assert(item_info); + if (info->ndim > 1 && (flags & PyBUF_F_CONTIGUOUS) == PyBUF_F_CONTIGUOUS) { + PyErr_SetString(PyExc_BufferError, + "ctypes array is not Fortran contiguous"); + return -1; + } + view->buf = self->b_ptr; view->obj = Py_NewRef(myself); view->len = self->b_size; view->readonly = 0; - /* use default format character if not set */ - view->format = info->format ? info->format : "B"; - view->ndim = info->ndim; - view->shape = info->shape; + if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT) { + /* use default format character if not set */ + view->format = info->format ? info->format : "B"; + } + else { + view->format = NULL; + } + if ((flags & PyBUF_ND) == PyBUF_ND) { + view->ndim = info->ndim; + view->shape = info->shape; + } + else { + /* The buffer is C contiguous, so it can be exposed as flat. + Keep ndim <= 1: ndim > 1 implies shape != NULL, see + PyBuffer_IsContiguous(). */ + view->ndim = info->ndim ? 1 : 0; + view->shape = NULL; + } view->itemsize = item_info->size; - view->strides = NULL; + /* PyBUF_STRIDES implies PyBUF_ND. */ + view->strides = ((flags & PyBUF_STRIDES) == PyBUF_STRIDES) + ? info->strides : NULL; view->suboffsets = NULL; view->internal = NULL; return 0; diff --git a/Modules/_ctypes/ctypes.h b/Modules/_ctypes/ctypes.h index 248559aa364a19..26aa2e82bd9896 100644 --- a/Modules/_ctypes/ctypes.h +++ b/Modules/_ctypes/ctypes.h @@ -413,7 +413,7 @@ typedef struct { char *format; int ndim; Py_ssize_t *shape; - /* Py_ssize_t *strides; */ /* unused in ctypes */ + Py_ssize_t *strides; /* offset from *shape, not necessary to PyMem_Free */ /* Py_ssize_t *suboffsets; */ /* unused in ctypes */ } StgInfo; diff --git a/Modules/_ctypes/stgdict.c b/Modules/_ctypes/stgdict.c index ab955a0b824a2f..68f6abc4efc391 100644 --- a/Modules/_ctypes/stgdict.c +++ b/Modules/_ctypes/stgdict.c @@ -49,14 +49,21 @@ PyCStgInfo_clone(StgInfo *dst_info, StgInfo *src_info) } strcpy(dst_info->format, src_info->format); } + dst_info->strides = NULL; if (src_info->shape) { - dst_info->shape = PyMem_Malloc(sizeof(Py_ssize_t) * src_info->ndim); + dst_info->shape = PyMem_Malloc( + sizeof(Py_ssize_t) * src_info->ndim * 2); if (dst_info->shape == NULL) { PyErr_NoMemory(); return -1; } memcpy(dst_info->shape, src_info->shape, sizeof(Py_ssize_t) * src_info->ndim); + if (src_info->strides) { + dst_info->strides = dst_info->shape + src_info->ndim; + memcpy(dst_info->strides, src_info->strides, + sizeof(Py_ssize_t) * src_info->ndim); + } } if (src_info->ffi_type_pointer.elements == NULL)