]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.13] gh-143602: Fix duplicate buffer exports in io.BytesIO.write (#143629) (#143872...
authorVictor Stinner <vstinner@python.org>
Fri, 16 Jan 2026 13:23:03 +0000 (14:23 +0100)
committerGitHub <noreply@github.com>
Fri, 16 Jan 2026 13:23:03 +0000 (14:23 +0100)
[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)

(cherry picked from commit 1241432150f6342e3d38c5a80a19c8c157a4ebe8)

Co-authored-by: zhong <60600792+superboy-zjc@users.noreply.github.com>
Lib/_pyio.py
Lib/test/test_memoryio.py
Misc/NEWS.d/next/Library/2026-01-09-12-37-19.gh-issue-143602.V8vQpj.rst [new file with mode: 0644]

index 59f4d06f1f5bf23ce129f7c6e1e0569985f1b672..9ed2e5b2aa0aac2da736f88b7e688d9e4e02bd19 100644 (file)
@@ -944,19 +944,21 @@ 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):
-            # Inserts null bytes between the current end of the file
-            # and the new write position.
-            padding = b'\x00' * (pos - len(self._buffer))
-            self._buffer += padding
-        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):
+                # Inserts null bytes between the current end of the file
+                # and the new write position.
+                padding = b'\x00' * (pos - len(self._buffer))
+                self._buffer += padding
+            self._buffer[pos:pos + n] = view
+            self._pos += n
         return n
 
     def seek(self, pos, whence=0):
index 91b9c87160af5657bd324400fa60849306739341..a1b881dff16244d76ec3786ead32bb634a4da805 100644 (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:
 
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 (file)
index 0000000..0eaec90
--- /dev/null
@@ -0,0 +1,2 @@
+Fix a inconsistency issue in :meth:`~io.RawIOBase.write` that leads to
+unexpected buffer overwrite by deduplicating the buffer exports.