[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 c461aa99e2fabbaf5859c0a8a93e08306ee8115d)

Co-authored-by: zhong <60600792+superboy-zjc@users.noreply.github.com>
This commit is contained in:
Victor Stinner 2026-01-15 17:02:46 +01:00 committed by GitHub
parent bbd11568a7
commit 1241432150
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 35 additions and 9 deletions

View File

@ -938,17 +938,19 @@ class BytesIO(BufferedIOBase):
if isinstance(b, str):
raise TypeError("can't write str to binary stream")
with memoryview(b) as view:
n = view.nbytes # Size of any bytes-like object
if self.closed:
raise ValueError("write to closed file")
if n == 0:
return 0
pos = self._pos
if pos > len(self._buffer):
# Pad buffer to pos with null bytes.
self._buffer.resize(pos)
self._buffer[pos:pos + n] = b
self._pos += n
n = view.nbytes # Size of any bytes-like object
if n == 0:
return 0
pos = self._pos
if pos > len(self._buffer):
# Pad buffer to pos with null bytes.
self._buffer.resize(pos)
self._buffer[pos:pos + n] = view
self._pos += n
return n
def seek(self, pos, whence=0):

View File

@ -629,6 +629,28 @@ class PyBytesIOTest(MemoryTestMixin, MemorySeekTestMixin, unittest.TestCase):
memio = self.ioclass()
self.assertRaises(BufferError, memio.writelines, [B()])
def test_write_mutating_buffer(self):
# Test that buffer is exported only once during write().
# See: https://github.com/python/cpython/issues/143602.
class B:
count = 0
def __buffer__(self, flags):
self.count += 1
if self.count == 1:
return memoryview(b"AAA")
else:
return memoryview(b"BBBBBBBBB")
memio = self.ioclass(b'0123456789')
memio.seek(2)
b = B()
n = memio.write(b)
self.assertEqual(b.count, 1)
self.assertEqual(n, 3)
self.assertEqual(memio.getvalue(), b"01AAA56789")
self.assertEqual(memio.tell(), 5)
class TextIOTestMixin:

View File

@ -0,0 +1,2 @@
Fix a inconsistency issue in :meth:`~io.RawIOBase.write` that leads to
unexpected buffer overwrite by deduplicating the buffer exports.