]> granicus.if.org Git - python/commitdiff
Issue #25498: Fix GC crash due to ctypes objects wrapping a memoryview
authorMartin Panter <vadmium+py@gmail.com>
Fri, 13 Nov 2015 21:43:39 +0000 (21:43 +0000)
committerMartin Panter <vadmium+py@gmail.com>
Fri, 13 Nov 2015 21:43:39 +0000 (21:43 +0000)
This was a regression caused by revision 1da9630e9b7f.  Based on patch by
Eryksun.

Lib/ctypes/test/test_frombuffer.py
Misc/ACKS
Misc/NEWS
Modules/_ctypes/_ctypes.c

index 6aa2d1c5917b2a5147ef4029599d14d1046f40ab..86954fe61e7898a3743c76702682c024afe2fd8b 100644 (file)
@@ -38,11 +38,32 @@ class Test(unittest.TestCase):
         del a; gc.collect(); gc.collect(); gc.collect()
         self.assertEqual(x[:], expected)
 
-        with self.assertRaises(TypeError):
+        with self.assertRaisesRegex(TypeError, "not writable"):
             (c_char * 16).from_buffer(b"a" * 16)
-        with self.assertRaises(TypeError):
+        with self.assertRaisesRegex(TypeError, "not writable"):
+            (c_char * 16).from_buffer(memoryview(b"a" * 16))
+        with self.assertRaisesRegex(TypeError, "not C contiguous"):
+            (c_char * 16).from_buffer(memoryview(bytearray(b"a" * 16))[::-1])
+        msg = "does not have the buffer interface"
+        with self.assertRaisesRegex(TypeError, msg):
             (c_char * 16).from_buffer("a" * 16)
 
+    def test_fortran_contiguous(self):
+        try:
+            import _testbuffer
+        except ImportError as err:
+            self.skipTest(str(err))
+        flags = _testbuffer.ND_WRITABLE | _testbuffer.ND_FORTRAN
+        array = _testbuffer.ndarray(
+            [97] * 16, format="B", shape=[4, 4], flags=flags)
+        with self.assertRaisesRegex(TypeError, "not C contiguous"):
+            (c_char * 16).from_buffer(array)
+        array = memoryview(array)
+        self.assertTrue(array.f_contiguous)
+        self.assertFalse(array.c_contiguous)
+        with self.assertRaisesRegex(TypeError, "not C contiguous"):
+            (c_char * 16).from_buffer(array)
+
     def test_from_buffer_with_offset(self):
         a = array.array("i", range(16))
         x = (c_int * 15).from_buffer(a, sizeof(c_int))
@@ -55,6 +76,12 @@ class Test(unittest.TestCase):
         with self.assertRaises(ValueError):
             (c_int * 1).from_buffer(a, 16 * sizeof(c_int))
 
+    def test_from_buffer_memoryview(self):
+        a = [c_char.from_buffer(memoryview(bytearray(b'a')))]
+        a.append(a)
+        del a
+        gc.collect()  # Should not crash
+
     def test_from_buffer_copy(self):
         a = array.array("i", range(16))
         x = (c_int * 16).from_buffer_copy(a)
index abf73045c0288da69c1af5ae793bfd3e25d84871..c9d69e2f36d27864471982900623d2846a6491d0 100644 (file)
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -391,6 +391,7 @@ Gökcen Eraslan
 Stoffel Erasmus
 Jürgen A. Erhard
 Michael Ernst
+Eryksun
 Ben Escoto
 Andy Eskilsson
 André Espaze
index 4e5ea1901b66ee86435db66d7d6ba18d992615bd..aacd51ea079036b78efcca8f8acad0e2ef590f1c 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -103,6 +103,10 @@ Core and Builtins
 Library
 -------
 
+- Issue #25498: Fix a crash when garbage-collecting ctypes objects created
+  by wrapping a memoryview.  This was a regression made in 3.4.3.  Based
+  on patch by Eryksun.
+
 - Issue #18010: Fix the pydoc web server's module search function to handle
   exceptions from importing packages.
 
index 2e5939c2571a5c828c2ca658757d19dc3907f6ed..2b27ed36db1241e9dfa9804ce676b4dae9d1367d 100644 (file)
@@ -463,45 +463,65 @@ KeepRef(CDataObject *target, Py_ssize_t index, PyObject *keep);
 static PyObject *
 CDataType_from_buffer(PyObject *type, PyObject *args)
 {
-    Py_buffer buffer;
+    PyObject *obj;
+    PyObject *mv;
+    PyObject *result;
+    Py_buffer *buffer;
     Py_ssize_t offset = 0;
-    PyObject *result, *mv;
+
     StgDictObject *dict = PyType_stgdict(type);
     assert (dict);
 
-    if (!PyArg_ParseTuple(args, "w*|n:from_buffer", &buffer, &offset))
+    if (!PyArg_ParseTuple(args, "O|n:from_buffer", &obj, &offset))
         return NULL;
 
+    mv = PyMemoryView_FromObject(obj);
+    if (mv == NULL)
+        return NULL;
+
+    buffer = PyMemoryView_GET_BUFFER(mv);
+
+    if (buffer->readonly) {
+        PyErr_SetString(PyExc_TypeError,
+            "underlying buffer is not writable");
+        Py_DECREF(mv);
+        return NULL;
+    }
+
+    if (!PyBuffer_IsContiguous(buffer, 'C')) {
+        PyErr_SetString(PyExc_TypeError,
+            "underlying buffer is not C contiguous");
+        Py_DECREF(mv);
+        return NULL;
+    }
+
     if (offset < 0) {
         PyErr_SetString(PyExc_ValueError,
                         "offset cannot be negative");
-        PyBuffer_Release(&buffer);
+        Py_DECREF(mv);
         return NULL;
     }
-    if (dict->size > buffer.len - offset) {
+
+    if (dict->size > buffer->len - offset) {
         PyErr_Format(PyExc_ValueError,
-                     "Buffer size too small (%zd instead of at least %zd bytes)",
-                     buffer.len, dict->size + offset);
-        PyBuffer_Release(&buffer);
+                     "Buffer size too small "
+                     "(%zd instead of at least %zd bytes)",
+                     buffer->len, dict->size + offset);
+        Py_DECREF(mv);
         return NULL;
     }
 
-    result = PyCData_AtAddress(type, (char *)buffer.buf + offset);
+    result = PyCData_AtAddress(type, (char *)buffer->buf + offset);
     if (result == NULL) {
-        PyBuffer_Release(&buffer);
+        Py_DECREF(mv);
         return NULL;
     }
 
-    mv = PyMemoryView_FromBuffer(&buffer);
-    if (mv == NULL) {
-        PyBuffer_Release(&buffer);
+    if (-1 == KeepRef((CDataObject *)result, -1, mv)) {
+        Py_DECREF(result);
         return NULL;
     }
-    /* Hack the memoryview so that it will release the buffer. */
-    ((PyMemoryViewObject *)mv)->mbuf->master.obj = buffer.obj;
-    ((PyMemoryViewObject *)mv)->view.obj = buffer.obj;
-    if (-1 == KeepRef((CDataObject *)result, -1, mv))
-        result = NULL;
+
     return result;
 }