diff -r e961688ae714 Lib/test/test_memoryview.py --- a/Lib/test/test_memoryview.py Fri Oct 01 17:40:20 2010 +0200 +++ b/Lib/test/test_memoryview.py Fri Oct 01 20:56:47 2010 +0200 @@ -381,6 +381,17 @@ class ArrayMemorySliceSliceTest(unittest pass +class ExporterTests(unittest.TestCase): + + def test_malloced_shape_and_strides(self): + _testcapi = test.support.import_module("_testcapi") + obj = _testcapi.shapedmemoryviewtester() + m = memoryview(obj) + self.assertEqual(m.tobytes(), b"some C string\x00") + #self.assertEqual(m[5:-1].tobytes(), b"C string") # crashes + m.release() + + def test_main(): test.support.run_unittest(__name__) diff -r e961688ae714 Modules/_testcapimodule.c --- a/Modules/_testcapimodule.c Fri Oct 01 17:40:20 2010 +0200 +++ b/Modules/_testcapimodule.c Fri Oct 01 20:56:47 2010 +0200 @@ -373,6 +373,92 @@ test_broken_memoryview(PyObject* self) } +/* Issue #9990: memoryview() should not alter dynamically-allocated + * shape and strides pointers. + */ + +static int +dynamic_getbuffer(PyObject *obj, Py_buffer *view, int flags) +{ + static char data[] = "some C string"; + static char format[] = "B"; + Py_ssize_t *shape; + + /* Shared container for shape and strides */ + shape = (Py_ssize_t *) PyMem_Malloc(sizeof(Py_ssize_t) * 2); + shape[0] = sizeof data; + shape[1] = 1; + + view->obj = obj; + Py_INCREF(obj); + view->buf = data; + view->len = sizeof data; + view->readonly = 1; + view->format = format; + view->ndim = 1; + view->shape = shape; + view->strides = shape + 1; + view->suboffsets = 0; + view->itemsize = 1; + view->internal = shape; + return 0; +} + +static void +dynamic_releasebuffer(PyObject *obj, Py_buffer *view) +{ + if (view->shape) { + PyMem_Free(view->shape); + } +} + +static PyBufferProcs shapedmemoryviewtester_as_buffer = { + dynamic_getbuffer, /* bf_getbuffer */ + dynamic_releasebuffer /* bf_releasebuffer */ +}; + +static PyTypeObject ShapedMemoryViewTester_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "shapedmemoryviewtester", /* Name of this type */ + sizeof(PyObject), /* Basic object size */ + 0, /* Item size for varobject */ + (destructor)PyObject_Del, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + &shapedmemoryviewtester_as_buffer,/* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + 0, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + PyType_GenericNew, /* tp_new */ +}; + + /* Tests of PyLong_{As, From}{Unsigned,}Long(), and (#ifdef HAVE_LONG_LONG) PyLong_{As, From}{Unsigned,}LongLong(). @@ -2468,8 +2554,13 @@ PyInit__testcapi(void) Py_TYPE(&_HashInheritanceTester_Type)=&PyType_Type; Py_TYPE(&_MemoryViewTester_Type)=&PyType_Type; + PyType_Ready(&ShapedMemoryViewTester_Type); + PyModule_AddObject(m, "shapedmemoryviewtester", + (PyObject *) &ShapedMemoryViewTester_Type); + Py_TYPE(&test_structmembersType)=&PyType_Type; Py_INCREF(&test_structmembersType); + /* don't use a name starting with "test", since we don't want test_capi to automatically call this */ PyModule_AddObject(m, "_test_structmembersType", (PyObject *)&test_structmembersType); diff -r e961688ae714 Objects/memoryobject.c --- a/Objects/memoryobject.c Fri Oct 01 17:40:20 2010 +0200 +++ b/Objects/memoryobject.c Fri Oct 01 20:56:47 2010 +0200 @@ -34,17 +34,25 @@ get_shape0(Py_buffer *buf) } static void +fixup_inline_pointer(void **ptr, Py_buffer *dest, Py_buffer *src) +{ + if (*ptr != NULL && *ptr >= (void *) src && + *ptr < ((void *) src + sizeof(Py_buffer))) { + *ptr = *ptr + ((void *) dest - (void *) src); + } +} + +static void dup_buffer(Py_buffer *dest, Py_buffer *src) { *dest = *src; - if (src->ndim == 1 && src->shape != NULL) { - dest->shape = &(dest->smalltable[0]); - dest->shape[0] = get_shape0(src); - } - if (src->ndim == 1 && src->strides != NULL) { - dest->strides = &(dest->smalltable[1]); - dest->strides[0] = src->strides[0]; - } + /* This code is because we need to copy a whole Py_buffer struct, + instead of being able to store its pointer. And we must then + take into account that simple exporters will use "inline" shape and + strides tables, while more complex exporters will manage their + allocation by hand. */ + fixup_inline_pointer((void **) &dest->shape, dest, src); + fixup_inline_pointer((void **) &dest->strides, dest, src); } static int @@ -55,9 +63,16 @@ memory_getbuf(PyMemoryViewObject *self, /* XXX for whatever reason fixing the flags seems necessary */ if (self->view.readonly) flags &= ~PyBUF_WRITABLE; - if (self->view.obj != NULL) + if (self->view.obj != NULL) { res = PyObject_GetBuffer(self->view.obj, view, flags); - if (view) + if (res == 0 && view != NULL) { + /* Necessary in the case our memoryview is a slice, which the + original object (and the buffer protocol) has no knowledge of. */ + view->buf = self->view.buf; + view->len = self->view.len; + } + } + else dup_buffer(view, &self->view); return res; } @@ -78,8 +93,7 @@ PyMemoryView_FromBuffer(Py_buffer *info) { PyMemoryViewObject *mview; - mview = (PyMemoryViewObject *) - PyObject_GC_New(PyMemoryViewObject, &PyMemoryView_Type); + mview = PyObject_GC_New(PyMemoryViewObject, &PyMemoryView_Type); if (mview == NULL) return NULL; mview->base = NULL; @@ -94,7 +108,6 @@ PyObject * PyMemoryView_FromObject(PyObject *base) { PyMemoryViewObject *mview; - Py_buffer view; if (!PyObject_CheckBuffer(base)) { PyErr_SetString(PyExc_TypeError, @@ -103,17 +116,17 @@ PyMemoryView_FromObject(PyObject *base) return NULL; } - if (PyObject_GetBuffer(base, &view, PyBUF_FULL_RO) < 0) + mview = PyObject_GC_New(PyMemoryViewObject, &PyMemoryView_Type); + if (mview == NULL) return NULL; - mview = (PyMemoryViewObject *)PyMemoryView_FromBuffer(&view); - if (mview == NULL) { - PyBuffer_Release(&view); + if (PyObject_GetBuffer(base, &mview->view, PyBUF_FULL_RO) < 0) { + PyObject_GC_Del(mview); return NULL; } - mview->base = base; Py_INCREF(base); + _PyObject_GC_TRACK(mview); return (PyObject *)mview; }