Bug description
The pure Python implementation of BytesIO.readinto() and readinto1() consumes input before rejecting a read-only destination buffer. The C implementation rejects the same argument without advancing the stream.
import io
import _pyio
for module in (io, _pyio):
stream = module.BytesIO(b"abcdef")
try:
stream.readinto(b"xxx")
except TypeError:
print(module.__name__, stream.tell(), stream.read())
Output on main:
io 0 b'abcdef'
_pyio 3 b'def'
The same happens with readinto1() and with a read-only memoryview. The destination is invalid because readinto() requires a writable bytes-like object, but rejecting it should not consume the input first.
The cause is in _pyio.BufferedIOBase._readinto(): it calls self.read() or self.read1() before the assignment to the destination memoryview raises TypeError. BytesIO inherits this implementation. Checking the memoryview's readonly flag before reading avoids changing the stream state and matches the C implementation.
I have prepared a focused fix and a regression test shared by the C and pure Python BytesIO implementations. The test covers both methods, bytes and memoryview destinations, empty destinations, and positions at the beginning, near EOF, and beyond EOF. On the original implementation the C test passes and the Python test fails eight subcases because the stream position changes. Both pass with the fix, and the full test_io package passes locally.
CPython versions tested on
Main at 2639fd65ff8e0c1949c480a8e670fe9c2467a1f8, locally built CPython 3.16.0a0 debug.
Operating systems tested on
Windows x64.
Linked PRs
Bug description
The pure Python implementation of BytesIO.readinto() and readinto1() consumes input before rejecting a read-only destination buffer. The C implementation rejects the same argument without advancing the stream.
Output on main:
The same happens with readinto1() and with a read-only memoryview. The destination is invalid because readinto() requires a writable bytes-like object, but rejecting it should not consume the input first.
The cause is in _pyio.BufferedIOBase._readinto(): it calls self.read() or self.read1() before the assignment to the destination memoryview raises TypeError. BytesIO inherits this implementation. Checking the memoryview's readonly flag before reading avoids changing the stream state and matches the C implementation.
I have prepared a focused fix and a regression test shared by the C and pure Python BytesIO implementations. The test covers both methods, bytes and memoryview destinations, empty destinations, and positions at the beginning, near EOF, and beyond EOF. On the original implementation the C test passes and the Python test fails eight subcases because the stream position changes. Both pass with the fix, and the full test_io package passes locally.
CPython versions tested on
Main at 2639fd65ff8e0c1949c480a8e670fe9c2467a1f8, locally built CPython 3.16.0a0 debug.
Operating systems tested on
Windows x64.
Linked PRs