Skip to content

Commit c717784

Browse files
committed
Fix bf_releasebuffer in child classes
1 parent b7fb7a4 commit c717784

File tree

2 files changed

+93
-4
lines changed

2 files changed

+93
-4
lines changed

Lib/test/test_buffer.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4588,6 +4588,22 @@ def __buffer__(self, flags):
45884588
mv = memoryview(a)
45894589
self.assertEqual(mv.tobytes(), b"hello")
45904590

4591+
def test_inheritance_releasebuffer(self):
4592+
rb_call_count = 0
4593+
class B(bytearray):
4594+
def __buffer__(self, flags):
4595+
return super().__buffer__(flags)
4596+
def __release_buffer__(self, view):
4597+
nonlocal rb_call_count
4598+
rb_call_count += 1
4599+
super().__release_buffer__(view)
4600+
4601+
b = B(b"hello")
4602+
with memoryview(b) as mv:
4603+
self.assertEqual(mv.tobytes(), b"hello")
4604+
self.assertEqual(rb_call_count, 0)
4605+
self.assertEqual(rb_call_count, 1)
4606+
45914607

45924608
if __name__ == "__main__":
45934609
unittest.main()

Objects/typeobject.c

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ typedef struct PySlot_Offset {
5959
static void
6060
slot_bf_releasebuffer(PyObject *self, Py_buffer *buffer);
6161

62+
static void
63+
releasebuffer_call_python(PyObject *self, Py_buffer *buffer);
64+
6265
static PyObject *
6366
slot_tp_new(PyTypeObject *type, PyObject *args, PyObject *kwds);
6467

@@ -8097,6 +8100,10 @@ wrap_releasebuffer(PyObject *self, PyObject *args, void *wrapped)
80978100
return NULL;
80988101
}
80998102
PyMemoryViewObject *mview = (PyMemoryViewObject *)arg;
8103+
if (mview->view.obj == NULL) {
8104+
// Already released, ignore
8105+
Py_RETURN_NONE;
8106+
}
81008107
if (mview->view.obj != self) {
81018108
PyErr_SetString(PyExc_ValueError,
81028109
"memoryview's buffer is not this object");
@@ -8985,9 +8992,10 @@ bufferwrapper_releasebuf(PyObject *self, Py_buffer *view)
89858992
Py_TYPE(bw->mv)->tp_as_buffer->bf_releasebuffer(bw->mv, view);
89868993
// We only need to call bf_releasebuffer if it's a Python function. If it's a C
89878994
// bf_releasebuf, it will be called when the memoryview is released.
8988-
if (Py_TYPE(bw->obj)->tp_as_buffer != NULL
8989-
&& Py_TYPE(bw->obj)->tp_as_buffer->bf_releasebuffer == slot_bf_releasebuffer) {
8990-
Py_TYPE(bw->obj)->tp_as_buffer->bf_releasebuffer(bw->obj, view);
8995+
if (((PyMemoryViewObject *)bw->mv)->view.obj != bw->obj
8996+
&& Py_TYPE(bw->obj)->tp_as_buffer != NULL
8997+
&& Py_TYPE(bw->obj)->tp_as_buffer->bf_releasebuffer == slot_bf_releasebuffer) {
8998+
releasebuffer_call_python(bw->obj, view);
89918999
}
89929000
}
89939001

@@ -9052,8 +9060,51 @@ slot_bf_getbuffer(PyObject *self, Py_buffer *buffer, int flags)
90529060
return -1;
90539061
}
90549062

9063+
static int
9064+
releasebuffer_maybe_call_super(PyObject *self, Py_buffer *buffer)
9065+
{
9066+
PyTypeObject *self_type = Py_TYPE(self);
9067+
PyObject *mro = lookup_tp_mro(self_type);
9068+
if (mro == NULL) {
9069+
return -1;
9070+
}
9071+
9072+
assert(PyTuple_Check(mro));
9073+
Py_ssize_t n = PyTuple_GET_SIZE(mro);
9074+
Py_ssize_t i;
9075+
9076+
/* No need to check the last one: it's gonna be skipped anyway. */
9077+
for (i = 0; i+1 < n; i++) {
9078+
if ((PyObject *)(self_type) == PyTuple_GET_ITEM(mro, i))
9079+
break;
9080+
}
9081+
i++; /* skip self_type */
9082+
if (i >= n)
9083+
return -1;
9084+
9085+
releasebufferproc base_releasebuffer = NULL;
9086+
for (; i < n; i++) {
9087+
PyObject *obj = PyTuple_GET_ITEM(mro, i);
9088+
if (!PyType_Check(obj)) {
9089+
continue;
9090+
}
9091+
PyTypeObject *base_type = (PyTypeObject *)obj;
9092+
if (base_type->tp_as_buffer != NULL
9093+
&& base_type->tp_as_buffer->bf_releasebuffer != NULL
9094+
&& base_type->tp_as_buffer->bf_releasebuffer != slot_bf_releasebuffer) {
9095+
base_releasebuffer = base_type->tp_as_buffer->bf_releasebuffer;
9096+
break;
9097+
}
9098+
}
9099+
9100+
if (base_releasebuffer != NULL) {
9101+
base_releasebuffer(self, buffer);
9102+
}
9103+
return 0;
9104+
}
9105+
90559106
static void
9056-
slot_bf_releasebuffer(PyObject *self, Py_buffer *buffer)
9107+
releasebuffer_call_python(PyObject *self, Py_buffer *buffer)
90579108
{
90589109
PyObject *mv;
90599110
if (Py_TYPE(buffer->obj) == &_PyBufferWrapper_Type) {
@@ -9079,6 +9130,28 @@ slot_bf_releasebuffer(PyObject *self, Py_buffer *buffer)
90799130
}
90809131
}
90819132

9133+
/*
9134+
* bf_releasebuffer is very delicate, because we need to ensure that
9135+
* C bf_releasebuffer slots are called correctly (or we'll leak memory),
9136+
* but we cannot trust any __release_buffer__ implemented in Python to
9137+
* do so correctly. Therefore, if a base class has a C bf_releasebuffer
9138+
* slot, we call it directly here. That is safe because this function
9139+
* only gets called from C callers of the bf_releasebuffer slot. Python
9140+
* code that calls __release_buffer__ directly instead goes through
9141+
* wrap_releasebuffer(), which doesn't call the bf_releasebuffer slot
9142+
* directly but instead simply releases the associated memoryview.
9143+
*/
9144+
static void
9145+
slot_bf_releasebuffer(PyObject *self, Py_buffer *buffer)
9146+
{
9147+
if (releasebuffer_maybe_call_super(self, buffer) < 0) {
9148+
if (PyErr_Occurred()) {
9149+
PyErr_WriteUnraisable(self);
9150+
}
9151+
}
9152+
releasebuffer_call_python(self, buffer);
9153+
}
9154+
90829155
static PyObject *
90839156
slot_am_await(PyObject *self)
90849157
{

0 commit comments

Comments
 (0)