Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 405eacc

Browse filesBrowse files
gh-104223: Fix issues with inheriting from buffer classes (#104227)
Co-authored-by: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com>
1 parent 874010c commit 405eacc
Copy full SHA for 405eacc

File tree

6 files changed

+334
-13
lines changed
Filter options

6 files changed

+334
-13
lines changed

‎Include/cpython/memoryobject.h

Copy file name to clipboardExpand all lines: Include/cpython/memoryobject.h
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ typedef struct {
2424
#define _Py_MEMORYVIEW_FORTRAN 0x004 /* Fortran contiguous layout */
2525
#define _Py_MEMORYVIEW_SCALAR 0x008 /* scalar: ndim = 0 */
2626
#define _Py_MEMORYVIEW_PIL 0x010 /* PIL-style layout */
27+
#define _Py_MEMORYVIEW_RESTRICTED 0x020 /* Disallow new references to the memoryview's buffer */
2728

2829
typedef struct {
2930
PyObject_VAR_HEAD

‎Include/internal/pycore_memoryobject.h

Copy file name to clipboardExpand all lines: Include/internal/pycore_memoryobject.h
+2-1Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ extern "C" {
99
#endif
1010

1111
PyObject *
12-
PyMemoryView_FromObjectAndFlags(PyObject *v, int flags);
12+
_PyMemoryView_FromBufferProc(PyObject *v, int flags,
13+
getbufferproc bufferproc);
1314

1415
#ifdef __cplusplus
1516
}

‎Lib/test/test_buffer.py

Copy file name to clipboardExpand all lines: Lib/test/test_buffer.py
+170Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4579,6 +4579,176 @@ def test_c_buffer(self):
45794579
buf.__release_buffer__(mv)
45804580
self.assertEqual(buf.references, 0)
45814581

4582+
def test_inheritance(self):
4583+
class A(bytearray):
4584+
def __buffer__(self, flags):
4585+
return super().__buffer__(flags)
4586+
4587+
a = A(b"hello")
4588+
mv = memoryview(a)
4589+
self.assertEqual(mv.tobytes(), b"hello")
4590+
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+
4607+
def test_inherit_but_return_something_else(self):
4608+
class A(bytearray):
4609+
def __buffer__(self, flags):
4610+
return memoryview(b"hello")
4611+
4612+
a = A(b"hello")
4613+
with memoryview(a) as mv:
4614+
self.assertEqual(mv.tobytes(), b"hello")
4615+
4616+
rb_call_count = 0
4617+
rb_raised = False
4618+
class B(bytearray):
4619+
def __buffer__(self, flags):
4620+
return memoryview(b"hello")
4621+
def __release_buffer__(self, view):
4622+
nonlocal rb_call_count
4623+
rb_call_count += 1
4624+
try:
4625+
super().__release_buffer__(view)
4626+
except ValueError:
4627+
nonlocal rb_raised
4628+
rb_raised = True
4629+
4630+
b = B(b"hello")
4631+
with memoryview(b) as mv:
4632+
self.assertEqual(mv.tobytes(), b"hello")
4633+
self.assertEqual(rb_call_count, 0)
4634+
self.assertEqual(rb_call_count, 1)
4635+
self.assertIs(rb_raised, True)
4636+
4637+
def test_override_only_release(self):
4638+
class C(bytearray):
4639+
def __release_buffer__(self, buffer):
4640+
super().__release_buffer__(buffer)
4641+
4642+
c = C(b"hello")
4643+
with memoryview(c) as mv:
4644+
self.assertEqual(mv.tobytes(), b"hello")
4645+
4646+
def test_release_saves_reference(self):
4647+
smuggled_buffer = None
4648+
4649+
class C(bytearray):
4650+
def __release_buffer__(s, buffer: memoryview):
4651+
with self.assertRaises(ValueError):
4652+
memoryview(buffer)
4653+
with self.assertRaises(ValueError):
4654+
buffer.cast("b")
4655+
with self.assertRaises(ValueError):
4656+
buffer.toreadonly()
4657+
with self.assertRaises(ValueError):
4658+
buffer[:1]
4659+
with self.assertRaises(ValueError):
4660+
buffer.__buffer__(0)
4661+
nonlocal smuggled_buffer
4662+
smuggled_buffer = buffer
4663+
self.assertEqual(buffer.tobytes(), b"hello")
4664+
super().__release_buffer__(buffer)
4665+
4666+
c = C(b"hello")
4667+
with memoryview(c) as mv:
4668+
self.assertEqual(mv.tobytes(), b"hello")
4669+
c.clear()
4670+
with self.assertRaises(ValueError):
4671+
smuggled_buffer.tobytes()
4672+
4673+
def test_release_saves_reference_no_subclassing(self):
4674+
ba = bytearray(b"hello")
4675+
4676+
class C:
4677+
def __buffer__(self, flags):
4678+
return memoryview(ba)
4679+
4680+
def __release_buffer__(self, buffer):
4681+
self.buffer = buffer
4682+
4683+
c = C()
4684+
with memoryview(c) as mv:
4685+
self.assertEqual(mv.tobytes(), b"hello")
4686+
self.assertEqual(c.buffer.tobytes(), b"hello")
4687+
4688+
with self.assertRaises(BufferError):
4689+
ba.clear()
4690+
c.buffer.release()
4691+
ba.clear()
4692+
4693+
def test_multiple_inheritance_buffer_last(self):
4694+
class A:
4695+
def __buffer__(self, flags):
4696+
return memoryview(b"hello A")
4697+
4698+
class B(A, bytearray):
4699+
def __buffer__(self, flags):
4700+
return super().__buffer__(flags)
4701+
4702+
b = B(b"hello")
4703+
with memoryview(b) as mv:
4704+
self.assertEqual(mv.tobytes(), b"hello A")
4705+
4706+
class Releaser:
4707+
def __release_buffer__(self, buffer):
4708+
self.buffer = buffer
4709+
4710+
class C(Releaser, bytearray):
4711+
def __buffer__(self, flags):
4712+
return super().__buffer__(flags)
4713+
4714+
c = C(b"hello C")
4715+
with memoryview(c) as mv:
4716+
self.assertEqual(mv.tobytes(), b"hello C")
4717+
c.clear()
4718+
with self.assertRaises(ValueError):
4719+
c.buffer.tobytes()
4720+
4721+
def test_multiple_inheritance_buffer_last(self):
4722+
class A:
4723+
def __buffer__(self, flags):
4724+
raise RuntimeError("should not be called")
4725+
4726+
def __release_buffer__(self, buffer):
4727+
raise RuntimeError("should not be called")
4728+
4729+
class B(bytearray, A):
4730+
def __buffer__(self, flags):
4731+
return super().__buffer__(flags)
4732+
4733+
b = B(b"hello")
4734+
with memoryview(b) as mv:
4735+
self.assertEqual(mv.tobytes(), b"hello")
4736+
4737+
class Releaser:
4738+
buffer = None
4739+
def __release_buffer__(self, buffer):
4740+
self.buffer = buffer
4741+
4742+
class C(bytearray, Releaser):
4743+
def __buffer__(self, flags):
4744+
return super().__buffer__(flags)
4745+
4746+
c = C(b"hello")
4747+
with memoryview(c) as mv:
4748+
self.assertEqual(mv.tobytes(), b"hello")
4749+
c.clear()
4750+
self.assertIs(c.buffer, None)
4751+
45824752

45834753
if __name__ == "__main__":
45844754
unittest.main()

‎Objects/bytearrayobject.c

Copy file name to clipboardExpand all lines: Objects/bytearrayobject.c
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ static void
6161
bytearray_releasebuffer(PyByteArrayObject *obj, Py_buffer *view)
6262
{
6363
obj->ob_exports--;
64+
assert(obj->ob_exports >= 0);
6465
}
6566

6667
static int

‎Objects/memoryobject.c

Copy file name to clipboardExpand all lines: Objects/memoryobject.c
+44-1Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,20 @@ PyTypeObject _PyManagedBuffer_Type = {
193193
return -1; \
194194
}
195195

196+
#define CHECK_RESTRICTED(mv) \
197+
if (((PyMemoryViewObject *)(mv))->flags & _Py_MEMORYVIEW_RESTRICTED) { \
198+
PyErr_SetString(PyExc_ValueError, \
199+
"cannot create new view on restricted memoryview"); \
200+
return NULL; \
201+
}
202+
203+
#define CHECK_RESTRICTED_INT(mv) \
204+
if (((PyMemoryViewObject *)(mv))->flags & _Py_MEMORYVIEW_RESTRICTED) { \
205+
PyErr_SetString(PyExc_ValueError, \
206+
"cannot create new view on restricted memoryview"); \
207+
return -1; \
208+
}
209+
196210
/* See gh-92888. These macros signal that we need to check the memoryview
197211
again due to possible read after frees. */
198212
#define CHECK_RELEASED_AGAIN(mv) CHECK_RELEASED(mv)
@@ -781,14 +795,15 @@ PyMemoryView_FromBuffer(const Py_buffer *info)
781795
using the given flags.
782796
If the object is a memoryview, the new memoryview must be registered
783797
with the same managed buffer. Otherwise, a new managed buffer is created. */
784-
PyObject *
798+
static PyObject *
785799
PyMemoryView_FromObjectAndFlags(PyObject *v, int flags)
786800
{
787801
_PyManagedBufferObject *mbuf;
788802

789803
if (PyMemoryView_Check(v)) {
790804
PyMemoryViewObject *mv = (PyMemoryViewObject *)v;
791805
CHECK_RELEASED(mv);
806+
CHECK_RESTRICTED(mv);
792807
return mbuf_add_view(mv->mbuf, &mv->view);
793808
}
794809
else if (PyObject_CheckBuffer(v)) {
@@ -806,6 +821,30 @@ PyMemoryView_FromObjectAndFlags(PyObject *v, int flags)
806821
Py_TYPE(v)->tp_name);
807822
return NULL;
808823
}
824+
825+
/* Create a memoryview from an object that implements the buffer protocol,
826+
using the given flags.
827+
If the object is a memoryview, the new memoryview must be registered
828+
with the same managed buffer. Otherwise, a new managed buffer is created. */
829+
PyObject *
830+
_PyMemoryView_FromBufferProc(PyObject *v, int flags, getbufferproc bufferproc)
831+
{
832+
_PyManagedBufferObject *mbuf = mbuf_alloc();
833+
if (mbuf == NULL)
834+
return NULL;
835+
836+
int res = bufferproc(v, &mbuf->master, flags);
837+
if (res < 0) {
838+
mbuf->master.obj = NULL;
839+
Py_DECREF(mbuf);
840+
return NULL;
841+
}
842+
843+
PyObject *ret = mbuf_add_view(mbuf, NULL);
844+
Py_DECREF(mbuf);
845+
return ret;
846+
}
847+
809848
/* Create a memoryview from an object that implements the buffer protocol.
810849
If the object is a memoryview, the new memoryview must be registered
811850
with the same managed buffer. Otherwise, a new managed buffer is created. */
@@ -1397,6 +1436,7 @@ memoryview_cast_impl(PyMemoryViewObject *self, PyObject *format,
13971436
Py_ssize_t ndim = 1;
13981437

13991438
CHECK_RELEASED(self);
1439+
CHECK_RESTRICTED(self);
14001440

14011441
if (!MV_C_CONTIGUOUS(self->flags)) {
14021442
PyErr_SetString(PyExc_TypeError,
@@ -1452,6 +1492,7 @@ memoryview_toreadonly_impl(PyMemoryViewObject *self)
14521492
/*[clinic end generated code: output=2c7e056f04c99e62 input=dc06d20f19ba236f]*/
14531493
{
14541494
CHECK_RELEASED(self);
1495+
CHECK_RESTRICTED(self);
14551496
/* Even if self is already readonly, we still need to create a new
14561497
* object for .release() to work correctly.
14571498
*/
@@ -1474,6 +1515,7 @@ memory_getbuf(PyMemoryViewObject *self, Py_buffer *view, int flags)
14741515
int baseflags = self->flags;
14751516

14761517
CHECK_RELEASED_INT(self);
1518+
CHECK_RESTRICTED_INT(self);
14771519

14781520
/* start with complete information */
14791521
*view = *base;
@@ -2535,6 +2577,7 @@ memory_subscript(PyMemoryViewObject *self, PyObject *key)
25352577
return memory_item(self, index);
25362578
}
25372579
else if (PySlice_Check(key)) {
2580+
CHECK_RESTRICTED(self);
25382581
PyMemoryViewObject *sliced;
25392582

25402583
sliced = (PyMemoryViewObject *)mbuf_add_view(self->mbuf, view);

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.