Visitar URL original
gh-104223: Fix issues with inheriting from buffer classes by JelleZijlstra · Pull Request #104227 · python/cpython · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Fix bf_releasebuffer in child classes
  • Loading branch information
JelleZijlstra committed May 6, 2023
commit c717784f82d4eab23f7bd44217553b9989619806
16 changes: 16 additions & 0 deletions Lib/test/test_buffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4588,6 +4588,22 @@ def __buffer__(self, flags):
mv = memoryview(a)
self.assertEqual(mv.tobytes(), b"hello")

def test_inheritance_releasebuffer(self):
rb_call_count = 0
class B(bytearray):
def __buffer__(self, flags):
return super().__buffer__(flags)
def __release_buffer__(self, view):
nonlocal rb_call_count
rb_call_count += 1
super().__release_buffer__(view)

b = B(b"hello")
with memoryview(b) as mv:
self.assertEqual(mv.tobytes(), b"hello")
self.assertEqual(rb_call_count, 0)
self.assertEqual(rb_call_count, 1)


if __name__ == "__main__":
unittest.main()
81 changes: 77 additions & 4 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ typedef struct PySlot_Offset {
static void
slot_bf_releasebuffer(PyObject *self, Py_buffer *buffer);

static void
releasebuffer_call_python(PyObject *self, Py_buffer *buffer);

static PyObject *
slot_tp_new(PyTypeObject *type, PyObject *args, PyObject *kwds);

Expand Down Expand Up @@ -8097,6 +8100,10 @@ wrap_releasebuffer(PyObject *self, PyObject *args, void *wrapped)
return NULL;
}
PyMemoryViewObject *mview = (PyMemoryViewObject *)arg;
if (mview->view.obj == NULL) {
// Already released, ignore
Py_RETURN_NONE;
}
if (mview->view.obj != self) {
PyErr_SetString(PyExc_ValueError,
"memoryview's buffer is not this object");
Expand Down Expand Up @@ -8985,9 +8992,10 @@ bufferwrapper_releasebuf(PyObject *self, Py_buffer *view)
Py_TYPE(bw->mv)->tp_as_buffer->bf_releasebuffer(bw->mv, view);
// We only need to call bf_releasebuffer if it's a Python function. If it's a C
// bf_releasebuf, it will be called when the memoryview is released.
if (Py_TYPE(bw->obj)->tp_as_buffer != NULL
&& Py_TYPE(bw->obj)->tp_as_buffer->bf_releasebuffer == slot_bf_releasebuffer) {
Py_TYPE(bw->obj)->tp_as_buffer->bf_releasebuffer(bw->obj, view);
if (((PyMemoryViewObject *)bw->mv)->view.obj != bw->obj
&& Py_TYPE(bw->obj)->tp_as_buffer != NULL
&& Py_TYPE(bw->obj)->tp_as_buffer->bf_releasebuffer == slot_bf_releasebuffer) {
releasebuffer_call_python(bw->obj, view);
}
}

Expand Down Expand Up @@ -9052,8 +9060,51 @@ slot_bf_getbuffer(PyObject *self, Py_buffer *buffer, int flags)
return -1;
}

static int
releasebuffer_maybe_call_super(PyObject *self, Py_buffer *buffer)
{
PyTypeObject *self_type = Py_TYPE(self);
PyObject *mro = lookup_tp_mro(self_type);
if (mro == NULL) {
return -1;
}

assert(PyTuple_Check(mro));
Py_ssize_t n = PyTuple_GET_SIZE(mro);
Py_ssize_t i;

/* No need to check the last one: it's gonna be skipped anyway. */
for (i = 0; i+1 < n; i++) {
Comment thread
JelleZijlstra marked this conversation as resolved.
Outdated
if ((PyObject *)(self_type) == PyTuple_GET_ITEM(mro, i))
break;
}
i++; /* skip self_type */
if (i >= n)
return -1;

releasebufferproc base_releasebuffer = NULL;
for (; i < n; i++) {
PyObject *obj = PyTuple_GET_ITEM(mro, i);
if (!PyType_Check(obj)) {
continue;
}
PyTypeObject *base_type = (PyTypeObject *)obj;
if (base_type->tp_as_buffer != NULL
&& base_type->tp_as_buffer->bf_releasebuffer != NULL
&& base_type->tp_as_buffer->bf_releasebuffer != slot_bf_releasebuffer) {
base_releasebuffer = base_type->tp_as_buffer->bf_releasebuffer;
break;
}
}

if (base_releasebuffer != NULL) {
base_releasebuffer(self, buffer);
}
return 0;
}

static void
slot_bf_releasebuffer(PyObject *self, Py_buffer *buffer)
releasebuffer_call_python(PyObject *self, Py_buffer *buffer)
{
PyObject *mv;
if (Py_TYPE(buffer->obj) == &_PyBufferWrapper_Type) {
Comment thread
JelleZijlstra marked this conversation as resolved.
Outdated
Expand All @@ -9079,6 +9130,28 @@ slot_bf_releasebuffer(PyObject *self, Py_buffer *buffer)
}
}

/*
* bf_releasebuffer is very delicate, because we need to ensure that
* C bf_releasebuffer slots are called correctly (or we'll leak memory),
* but we cannot trust any __release_buffer__ implemented in Python to
* do so correctly. Therefore, if a base class has a C bf_releasebuffer
* slot, we call it directly here. That is safe because this function
* only gets called from C callers of the bf_releasebuffer slot. Python
* code that calls __release_buffer__ directly instead goes through
* wrap_releasebuffer(), which doesn't call the bf_releasebuffer slot
* directly but instead simply releases the associated memoryview.
*/
static void
slot_bf_releasebuffer(PyObject *self, Py_buffer *buffer)
{
if (releasebuffer_maybe_call_super(self, buffer) < 0) {
if (PyErr_Occurred()) {
PyErr_WriteUnraisable(self);
}
}
releasebuffer_call_python(self, buffer);
}

static PyObject *
slot_am_await(PyObject *self)
{
Expand Down