From 53bcc4f722f881ed51524d10fcc1a7a58477afc6 Mon Sep 17 00:00:00 2001 From: zhong <60600792+superboy-zjc@users.noreply.github.com> Date: Thu, 15 Jan 2026 07:08:55 -0800 Subject: [PATCH] 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) --- Lib/_pyio.py | 20 +++++++++-------- Lib/test/test_memoryio.py | 22 +++++++++++++++++++ ...-01-09-12-37-19.gh-issue-143602.V8vQpj.rst | 2 ++ 3 files changed, 35 insertions(+), 9 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-01-09-12-37-19.gh-issue-143602.V8vQpj.rst diff --git a/Lib/_pyio.py b/Lib/_pyio.py index 23e81e850dad04..116ce4f37ecc1a 100644 --- a/Lib/_pyio.py +++ b/Lib/_pyio.py @@ -938,17 +938,19 @@ def write(self, b): 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): diff --git a/Lib/test/test_memoryio.py b/Lib/test/test_memoryio.py index f730e38a5d6485..482b183da23ffa 100644 --- a/Lib/test/test_memoryio.py +++ b/Lib/test/test_memoryio.py @@ -629,6 +629,28 @@ def __buffer__(self, flags): 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: diff --git a/Misc/NEWS.d/next/Library/2026-01-09-12-37-19.gh-issue-143602.V8vQpj.rst b/Misc/NEWS.d/next/Library/2026-01-09-12-37-19.gh-issue-143602.V8vQpj.rst new file mode 100644 index 00000000000000..0eaec9029221ba --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-01-09-12-37-19.gh-issue-143602.V8vQpj.rst @@ -0,0 +1,2 @@ +Fix a inconsistency issue in :meth:`~io.RawIOBase.write` that leads to +unexpected buffer overwrite by deduplicating the buffer exports.