Skip to content

Commit 201e819

Browse files
[3.13] gh-143602: Fix duplicate buffer exports in io.BytesIO.write (#143629) (#143872) (#143878)
[3.14] gh-143602: Fix duplicate buffer exports in io.BytesIO.write (#143629) (#143872) 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. (cherry picked from commit c461aa9) (cherry picked from commit 1241432) Co-authored-by: zhong <60600792+superboy-zjc@users.noreply.github.com>
1 parent d7f11ea commit 201e819

File tree

3 files changed

+37
-11
lines changed

3 files changed

+37
-11
lines changed

Lib/_pyio.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -944,19 +944,21 @@ def write(self, b):
944944
if isinstance(b, str):
945945
raise TypeError("can't write str to binary stream")
946946
with memoryview(b) as view:
947-
n = view.nbytes # Size of any bytes-like object
948947
if self.closed:
949948
raise ValueError("write to closed file")
950-
if n == 0:
951-
return 0
952-
pos = self._pos
953-
if pos > len(self._buffer):
954-
# Inserts null bytes between the current end of the file
955-
# and the new write position.
956-
padding = b'\x00' * (pos - len(self._buffer))
957-
self._buffer += padding
958-
self._buffer[pos:pos + n] = b
959-
self._pos += n
949+
950+
n = view.nbytes # Size of any bytes-like object
951+
if n == 0:
952+
return 0
953+
954+
pos = self._pos
955+
if pos > len(self._buffer):
956+
# Inserts null bytes between the current end of the file
957+
# and the new write position.
958+
padding = b'\x00' * (pos - len(self._buffer))
959+
self._buffer += padding
960+
self._buffer[pos:pos + n] = view
961+
self._pos += n
960962
return n
961963

962964
def seek(self, pos, whence=0):

Lib/test/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)