Skip to content

Commit e8a9aa5

Browse files
committed
Pin the managed buffer, not the view, during memoryview operations
1 parent e0b6fa5 commit e8a9aa5

3 files changed

Lines changed: 166 additions & 158 deletions

File tree

‎Include/cpython/memoryobject.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ typedef struct {
2929
_PyManagedBufferObject *mbuf; /* managed buffer */
3030
Py_hash_t hash; /* hash value for read-only views */
3131
int flags; /* state flags */
32-
Py_ssize_t exports; /* buffer re-exports and active operations */
32+
Py_ssize_t exports; /* number of buffer re-exports */
3333
Py_buffer view; /* private copy of the exporter's view */
3434
PyObject *weakreflist;
3535
Py_ssize_t ob_array[1]; /* shape, strides, suboffsets */

‎Lib/test/test_memoryview.py‎

Lines changed: 58 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -561,25 +561,28 @@ def _check_contents(self, tp, obj, contents):
561561
class BytesMemoryviewTest(unittest.TestCase,
562562
BaseMemoryviewTests, BaseBytesMemoryTests):
563563

564-
def test_comparison_blocks_release_during_buffer_callback(self):
564+
def test_comparison_release_during_buffer_callback(self):
565+
# Releasing the view from inside the other operand's __buffer__
566+
# succeeds, but the buffer stays pinned until the comparison ends:
567+
# the exporter cannot be resized under the running comparison.
565568
data = bytearray(b'abc')
566569
view = memoryview(data)
567570
assert_raises = self.assertRaises
568571

569572
class Other:
570573
def __buffer__(self, flags):
571-
with assert_raises(BufferError):
572-
view.release()
574+
view.release()
573575
with assert_raises(BufferError):
574576
data.clear()
575577
return memoryview(data)
576578

577-
self.assertTrue(view == Other())
578-
self.assertEqual(view.tobytes(), b'abc')
579-
view.release()
579+
view == Other()
580+
with self.assertRaisesRegex(ValueError, "released"):
581+
view.tobytes()
580582
data.clear()
581583

582584
def test_comparison_releases_buffer_after_access(self):
585+
# __release_buffer__ releasing the very view being compared.
583586
data = bytearray(b'abc')
584587
view = memoryview(data)
585588

@@ -596,6 +599,7 @@ def __release_buffer__(self, buffer):
596599
data.clear()
597600

598601
def test_assignment_releases_buffer_after_access(self):
602+
# __release_buffer__ releasing the very view being assigned to.
599603
data = bytearray(b'abc')
600604
view = memoryview(data)
601605

@@ -755,6 +759,8 @@ def test_gc_cleared_view(self):
755759
with self.assertRaisesRegex(ValueError, "released"):
756760
view.tobytes()
757761

762+
# Clearing the view while an operation is in progress must not
763+
# free the buffer under that operation, nor crash when it ends.
758764
data = bytearray(b"abc")
759765
view = memoryview(data)
760766

@@ -764,7 +770,9 @@ def __buffer__(self, flags):
764770
return memoryview(data)
765771

766772
self.assertTrue(view == Other())
767-
self.assertEqual(view.tobytes(), b"abc")
773+
with self.assertRaisesRegex(ValueError, "released"):
774+
view.tobytes()
775+
data.clear()
768776

769777
def test_cast_order(self):
770778
# gh-78959: cast(order=...) selects the result memory layout.
@@ -886,11 +894,15 @@ def test_pickle(self):
886894
with self.assertRaises(TypeError):
887895
pickle.dumps(m, proto)
888896

889-
def test_release_during_operation(self):
890-
# gh-92888: A callback cannot release a view while an operation uses it.
897+
def test_use_released_memory(self):
898+
# gh-92888: Previously it was possible to use a memoryview even after
899+
# backing buffer is freed in certain cases. This tests that those
900+
# cases raise an exception.
891901
size = 128
892902
def release():
893903
m.release()
904+
nonlocal ba
905+
ba = bytearray(size)
894906
class MyIndex:
895907
def __index__(self):
896908
release()
@@ -904,90 +916,85 @@ def __bool__(self):
904916
release()
905917
return True
906918

919+
ba = None
907920
m = memoryview(bytearray(b'\xff'*size))
908-
with self.assertRaises(BufferError):
921+
with self.assertRaises(ValueError):
909922
m[MyIndex()]
910923

924+
ba = None
911925
m = memoryview(bytearray(b'\xff'*size))
912-
with self.assertRaises(BufferError):
913-
m[:MyIndex()]
926+
self.assertEqual(list(m[:MyIndex()]), [255] * 4)
914927

928+
ba = None
915929
m = memoryview(bytearray(b'\xff'*size))
916-
with self.assertRaises(BufferError):
917-
m[MyIndex():8]
930+
self.assertEqual(list(m[MyIndex():8]), [255] * 4)
918931

932+
ba = None
919933
m = memoryview(bytearray(b'\xff'*size)).cast('B', (64, 2))
920-
with self.assertRaises(BufferError):
934+
with self.assertRaisesRegex(ValueError, "operation forbidden"):
921935
m[MyIndex(), 0]
922936

937+
ba = None
923938
m = memoryview(bytearray(b'\xff'*size)).cast('B', (2, 64))
924-
with self.assertRaises(BufferError):
939+
with self.assertRaisesRegex(ValueError, "operation forbidden"):
925940
m[0, MyIndex()]
926941

942+
ba = None
927943
m = memoryview(bytearray(b'\xff'*size))
928-
with self.assertRaises(BufferError):
944+
with self.assertRaisesRegex(ValueError, "operation forbidden"):
929945
m[MyIndex()] = 42
930-
self.assertEqual(m.tobytes(), b'\xff'*size)
946+
self.assertEqual(ba[:8], b'\0'*8)
931947

948+
ba = None
932949
m = memoryview(bytearray(b'\xff'*size))
933-
with self.assertRaises(BufferError):
950+
with self.assertRaisesRegex(ValueError, "operation forbidden"):
934951
m[:MyIndex()] = b'spam'
935-
self.assertEqual(m.tobytes(), b'\xff'*size)
952+
self.assertEqual(ba[:8], b'\0'*8)
936953

954+
ba = None
937955
m = memoryview(bytearray(b'\xff'*size))
938-
with self.assertRaises(BufferError):
956+
with self.assertRaisesRegex(ValueError, "operation forbidden"):
939957
m[MyIndex():8] = b'spam'
940-
self.assertEqual(m.tobytes(), b'\xff'*size)
958+
self.assertEqual(ba[:8], b'\0'*8)
941959

960+
ba = None
942961
m = memoryview(bytearray(b'\xff'*size)).cast('B', (64, 2))
943-
with self.assertRaises(BufferError):
962+
with self.assertRaisesRegex(ValueError, "operation forbidden"):
944963
m[MyIndex(), 0] = 42
945-
self.assertEqual(m.tobytes(), b'\xff'*size)
964+
self.assertEqual(ba[8:16], b'\0'*8)
965+
ba = None
946966
m = memoryview(bytearray(b'\xff'*size)).cast('B', (2, 64))
947-
with self.assertRaises(BufferError):
967+
with self.assertRaisesRegex(ValueError, "operation forbidden"):
948968
m[0, MyIndex()] = 42
949-
self.assertEqual(m.tobytes(), b'\xff'*size)
969+
self.assertEqual(ba[:8], b'\0'*8)
950970

971+
ba = None
951972
m = memoryview(bytearray(b'\xff'*size))
952-
with self.assertRaises(BufferError):
973+
with self.assertRaisesRegex(ValueError, "operation forbidden"):
953974
m[0] = MyIndex()
954-
self.assertEqual(m.tobytes(), b'\xff'*size)
975+
self.assertEqual(ba[:8], b'\0'*8)
955976

956977
for fmt in 'bhilqnBHILQN':
957978
with self.subTest(fmt=fmt):
979+
ba = None
958980
m = memoryview(bytearray(b'\xff'*size)).cast(fmt)
959-
with self.assertRaises(BufferError):
981+
with self.assertRaisesRegex(ValueError, "operation forbidden"):
960982
m[0] = MyIndex()
961-
self.assertEqual(m.tobytes(), b'\xff'*size)
983+
self.assertEqual(ba[:8], b'\0'*8)
962984

963985
for fmt in 'fd':
964986
with self.subTest(fmt=fmt):
987+
ba = None
965988
m = memoryview(bytearray(b'\xff'*size)).cast(fmt)
966-
with self.assertRaises(BufferError):
989+
with self.assertRaisesRegex(ValueError, "operation forbidden"):
967990
m[0] = MyFloat()
968-
self.assertEqual(m.tobytes(), b'\xff'*size)
991+
self.assertEqual(ba[:8], b'\0'*8)
969992

993+
ba = None
970994
m = memoryview(bytearray(b'\xff'*size)).cast('?')
971-
with self.assertRaises(BufferError):
995+
with self.assertRaisesRegex(ValueError, "operation forbidden"):
972996
m[0] = MyBool()
973-
self.assertEqual(m.tobytes(), b'\xff'*size)
974-
975-
data = bytearray(b'abc')
976-
m = memoryview(data).cast('c')
977-
assert_raises = self.assertRaises
978-
979-
class CatchingIndex:
980-
def __index__(self):
981-
with assert_raises(BufferError):
982-
release()
983-
with assert_raises(BufferError):
984-
data.clear()
985-
return 0
986-
987-
m[CatchingIndex()] = b'x'
988-
self.assertEqual(data, b'xbc')
989-
m.release()
990-
data.clear()
997+
self.assertEqual(ba[:8], b'\0'*8)
991998

992999
def test_buffer_reference_loop(self):
9931000
m = memoryview(b'abc').__buffer__(0)

0 commit comments

Comments
 (0)