https://github.com/python/cpython/commit/c461aa99e2fabbaf5859c0a8a93e08306ee8115d
commit: c461aa99e2fabbaf5859c0a8a93e08306ee8115d
branch: main
author: zhong <[email protected]>
committer: vstinner <[email protected]>
date: 2026-01-15T16:08:55+01:00
summary:
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.
files:
A Misc/NEWS.d/next/Library/2026-01-09-12-37-19.gh-issue-143602.V8vQpj.rst
M Lib/_pyio.py
M Lib/test/test_io/test_memoryio.py
diff --git a/Lib/_pyio.py b/Lib/_pyio.py
index 77c44addabf225..3306c8a274760b 100644
--- a/Lib/_pyio.py
+++ b/Lib/_pyio.py
@@ -952,20 +952,21 @@ 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
- with self._lock:
- 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
- return n
+ n = view.nbytes # Size of any bytes-like object
+ if n == 0:
+ return 0
+
+ with self._lock:
+ 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):
if self.closed:
diff --git a/Lib/test/test_io/test_memoryio.py
b/Lib/test/test_io/test_memoryio.py
index f730e38a5d6485..482b183da23ffa 100644
--- a/Lib/test/test_io/test_memoryio.py
+++ b/Lib/test/test_io/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.
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]