Skip to content

Commit c461aa9

Browse files
authored
gh-143602: Fix duplicate buffer exports in io.BytesIO.write (#143629)
Fix an inconsistency issue in io.BytesIO.write() where the buffer was exported twice, which could lead to unexpected data overwrites and position drift when the buffer changes between exports.
1 parent 3514ba2 commit c461aa9

File tree

3 files changed

+36
-11
lines changed

3 files changed

+36
-11
lines changed

Lib/_pyio.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -952,20 +952,21 @@ def write(self, b):
952952
if isinstance(b, str):
953953
raise TypeError("can't write str to binary stream")
954954
with memoryview(b) as view:
955-
n = view.nbytes # Size of any bytes-like object
956955
if self.closed:
957956
raise ValueError("write to closed file")
958-
if n == 0:
959-
return 0
960957

961-
with self._lock:
962-
pos = self._pos
963-
if pos > len(self._buffer):
964-
# Pad buffer to pos with null bytes.
965-
self._buffer.resize(pos)
966-
self._buffer[pos:pos + n] = b
967-
self._pos += n
968-
return n
958+
n = view.nbytes # Size of any bytes-like object
959+
if n == 0:
960+
return 0
961+
962+
with self._lock:
963+
pos = self._pos
964+
if pos > len(self._buffer):
965+
# Pad buffer to pos with null bytes.
966+
self._buffer.resize(pos)
967+
self._buffer[pos:pos + n] = view
968+
self._pos += n
969+
return n
969970

970971
def seek(self, pos, whence=0):
971972
if self.closed:

Lib/test/test_io/test_memoryio.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,28 @@ def __buffer__(self, flags):
629629
memio = self.ioclass()
630630
self.assertRaises(BufferError, memio.writelines, [B()])
631631

632+
def test_write_mutating_buffer(self):
633+
# Test that buffer is exported only once during write().
634+
# See: https://github.com/python/cpython/issues/143602.
635+
class B:
636+
count = 0
637+
def __buffer__(self, flags):
638+
self.count += 1
639+
if self.count == 1:
640+
return memoryview(b"AAA")
641+
else:
642+
return memoryview(b"BBBBBBBBB")
643+
644+
memio = self.ioclass(b'0123456789')
645+
memio.seek(2)
646+
b = B()
647+
n = memio.write(b)
648+
649+
self.assertEqual(b.count, 1)
650+
self.assertEqual(n, 3)
651+
self.assertEqual(memio.getvalue(), b"01AAA56789")
652+
self.assertEqual(memio.tell(), 5)
653+
632654

633655
class TextIOTestMixin:
634656

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a inconsistency issue in :meth:`~io.RawIOBase.write` that leads to
2+
unexpected buffer overwrite by deduplicating the buffer exports.

0 commit comments

Comments
 (0)