]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.14] gh-143602: Fix duplicate buffer exports in io.BytesIO.write (#143629) (#143872)
authorVictor Stinner <vstinner@python.org>
Thu, 15 Jan 2026 16:02:46 +0000 (17:02 +0100)
committerGitHub <noreply@github.com>
Thu, 15 Jan 2026 16:02:46 +0000 (16:02 +0000)
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>
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 23e81e850dad04b66ba48200730fb744ea6e37fd..116ce4f37ecc1af09e988b203c13a038aa8b3c95 100644 (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):
index f730e38a5d6485c99cd37682a01f8828bc5cca8c..482b183da23ffa259664e52592021d5c3c12d34d 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.