Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 1bb6515

Browse files
committed
Issue #25498: Fix GC crash due to ctypes objects wrapping a memoryview
This was a regression caused by revision 1da9630e9b7f. Based on patch by Eryksun.
1 parent abe9625 commit 1bb6515

4 files changed

Lines changed: 72 additions & 20 deletions

File tree

Lib/ctypes/test/test_frombuffer.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,32 @@ def test_from_buffer(self):
3838
del a; gc.collect(); gc.collect(); gc.collect()
3939
self.assertEqual(x[:], expected)
4040

41-
with self.assertRaises(TypeError):
41+
with self.assertRaisesRegex(TypeError, "not writable"):
4242
(c_char * 16).from_buffer(b"a" * 16)
43-
with self.assertRaises(TypeError):
43+
with self.assertRaisesRegex(TypeError, "not writable"):
44+
(c_char * 16).from_buffer(memoryview(b"a" * 16))
45+
with self.assertRaisesRegex(TypeError, "not C contiguous"):
46+
(c_char * 16).from_buffer(memoryview(bytearray(b"a" * 16))[::-1])
47+
msg = "does not have the buffer interface"
48+
with self.assertRaisesRegex(TypeError, msg):
4449
(c_char * 16).from_buffer("a" * 16)
4550

51+
def test_fortran_contiguous(self):
52+
try:
53+
import _testbuffer
54+
except ImportError as err:
55+
self.skipTest(str(err))
56+
flags = _testbuffer.ND_WRITABLE | _testbuffer.ND_FORTRAN
57+
array = _testbuffer.ndarray(
58+
[97] * 16, format="B", shape=[4, 4], flags=flags)
59+
with self.assertRaisesRegex(TypeError, "not C contiguous"):
60+
(c_char * 16).from_buffer(array)
61+
array = memoryview(array)
62+
self.assertTrue(array.f_contiguous)
63+
self.assertFalse(array.c_contiguous)
64+
with self.assertRaisesRegex(TypeError, "not C contiguous"):
65+
(c_char * 16).from_buffer(array)
66+
4667
def test_from_buffer_with_offset(self):
4768
a = array.array("i", range(16))
4869
x = (c_int * 15).from_buffer(a, sizeof(c_int))
@@ -55,6 +76,12 @@ def test_from_buffer_with_offset(self):
5576
with self.assertRaises(ValueError):
5677
(c_int * 1).from_buffer(a, 16 * sizeof(c_int))
5778

79+
def test_from_buffer_memoryview(self):
80+
a = [c_char.from_buffer(memoryview(bytearray(b'a')))]
81+
a.append(a)
82+
del a
83+
gc.collect() # Should not crash
84+
5885
def test_from_buffer_copy(self):
5986
a = array.array("i", range(16))
6087
x = (c_int * 16).from_buffer_copy(a)

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,7 @@ Gökcen Eraslan
391391
Stoffel Erasmus
392392
Jürgen A. Erhard
393393
Michael Ernst
394+
Eryksun
394395
Ben Escoto
395396
Andy Eskilsson
396397
André Espaze

Misc/NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ Core and Builtins
103103
Library
104104
-------
105105

106+
- Issue #25498: Fix a crash when garbage-collecting ctypes objects created
107+
by wrapping a memoryview. This was a regression made in 3.4.3. Based
108+
on patch by Eryksun.
109+
106110
- Issue #18010: Fix the pydoc web server's module search function to handle
107111
exceptions from importing packages.
108112

Modules/_ctypes/_ctypes.c

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -463,45 +463,65 @@ KeepRef(CDataObject *target, Py_ssize_t index, PyObject *keep);
463463
static PyObject *
464464
CDataType_from_buffer(PyObject *type, PyObject *args)
465465
{
466-
Py_buffer buffer;
466+
PyObject *obj;
467+
PyObject *mv;
468+
PyObject *result;
469+
Py_buffer *buffer;
467470
Py_ssize_t offset = 0;
468-
PyObject *result, *mv;
471+
469472
StgDictObject *dict = PyType_stgdict(type);
470473
assert (dict);
471474

472-
if (!PyArg_ParseTuple(args, "w*|n:from_buffer", &buffer, &offset))
475+
if (!PyArg_ParseTuple(args, "O|n:from_buffer", &obj, &offset))
473476
return NULL;
474477

478+
mv = PyMemoryView_FromObject(obj);
479+
if (mv == NULL)
480+
return NULL;
481+
482+
buffer = PyMemoryView_GET_BUFFER(mv);
483+
484+
if (buffer->readonly) {
485+
PyErr_SetString(PyExc_TypeError,
486+
"underlying buffer is not writable");
487+
Py_DECREF(mv);
488+
return NULL;
489+
}
490+
491+
if (!PyBuffer_IsContiguous(buffer, 'C')) {
492+
PyErr_SetString(PyExc_TypeError,
493+
"underlying buffer is not C contiguous");
494+
Py_DECREF(mv);
495+
return NULL;
496+
}
497+
475498
if (offset < 0) {
476499
PyErr_SetString(PyExc_ValueError,
477500
"offset cannot be negative");
478-
PyBuffer_Release(&buffer);
501+
Py_DECREF(mv);
479502
return NULL;
480503
}
481-
if (dict->size > buffer.len - offset) {
504+
505+
if (dict->size > buffer->len - offset) {
482506
PyErr_Format(PyExc_ValueError,
483-
"Buffer size too small (%zd instead of at least %zd bytes)",
484-
buffer.len, dict->size + offset);
485-
PyBuffer_Release(&buffer);
507+
"Buffer size too small "
508+
"(%zd instead of at least %zd bytes)",
509+
buffer->len, dict->size + offset);
510+
Py_DECREF(mv);
486511
return NULL;
487512
}
488513

489-
result = PyCData_AtAddress(type, (char *)buffer.buf + offset);
514+
result = PyCData_AtAddress(type, (char *)buffer->buf + offset);
490515
if (result == NULL) {
491-
PyBuffer_Release(&buffer);
516+
Py_DECREF(mv);
492517
return NULL;
493518
}
494519

495-
mv = PyMemoryView_FromBuffer(&buffer);
496-
if (mv == NULL) {
497-
PyBuffer_Release(&buffer);
520+
if (-1 == KeepRef((CDataObject *)result, -1, mv)) {
521+
Py_DECREF(result);
498522
return NULL;
499523
}
500-
/* Hack the memoryview so that it will release the buffer. */
501-
((PyMemoryViewObject *)mv)->mbuf->master.obj = buffer.obj;
502-
((PyMemoryViewObject *)mv)->view.obj = buffer.obj;
503-
if (-1 == KeepRef((CDataObject *)result, -1, mv))
504-
result = NULL;
524+
505525
return result;
506526
}
507527

0 commit comments

Comments
 (0)