Skip to content

Commit 1241432

Browse files
[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) Co-authored-by: zhong <60600792+superboy-zjc@users.noreply.github.com>
1 parent bbd1156 commit 1241432

File tree

3 files changed

+35
-9
lines changed

3 files changed

+35
-9
lines changed

Lib/_pyio.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -938,17 +938,19 @@ def write(self, b):
938938
if isinstance(b, str):
939939
raise TypeError("can't write str to binary stream")
940940
with memoryview(b) as view:
941-
n = view.nbytes # Size of any bytes-like object
942941
if self.closed:
943942
raise ValueError("write to closed file")
944-
if n == 0:
945-
return 0
946-
pos = self._pos
947-
if pos > len(self._buffer):
948-
# Pad buffer to pos with null bytes.
949-
self._buffer.resize(pos)
950-
self._buffer[pos:pos + n] = b
951-
self._pos += n
943+
944+
n = view.nbytes # Size of any bytes-like object
945+
if n == 0:
946+
return 0
947+
948+
pos = self._pos
949+
if pos > len(self._buffer):
950+
# Pad buffer to pos with null bytes.
951+
self._buffer.resize(pos)
952+
self._buffer[pos:pos + n] = view
953+
self._pos += n
952954
return n
953955

954956
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)