Skip to content

Commit f904fce

Browse files
committed
gh-60107: Remove a copy from RawIOBase.read
If the underlying I/O class keeps a reference to the memory raise BufferError.
1 parent a486d45 commit f904fce

3 files changed

Lines changed: 34 additions & 1 deletion

File tree

Lib/test/test_io/test_general.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -724,6 +724,26 @@ def test_RawIOBase_readall(self):
724724
rawio = self.MockRawIOWithoutRead((b"abc", b"d", b"efg"))
725725
self.assertEqual(rawio.readall(), b"abcdefg")
726726

727+
# gh-60107: Ensure a "Raw I/O" which keeps a reference to the
728+
# mutable memory doesn't allow making a mutable bytes.
729+
class RawIOKeepsReference(self.MockRawIOWithoutRead):
730+
def __init__(self, *args, **kwargs):
731+
self.buf = None
732+
super().__init__(*args, **kwargs)
733+
734+
def readinto(self, buf):
735+
# buf is the bytearray so keeping a reference to it doesn't keep
736+
# the memory alive; a memoryview does.
737+
self.buf = memoryview(buf)
738+
buf[0:4] = self._read_stack.pop()
739+
return 3
740+
741+
with self.assertRaises(BufferError):
742+
rawio = RawIOKeepsReference([b"1234"])
743+
rawio.read(4)
744+
745+
746+
727747
def test_BufferedIOBase_readinto(self):
728748
# Exercise the default BufferedIOBase.readinto() and readinto1()
729749
# implementations (which call read() or read1() internally).
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Remove a copy from :meth:`io.RawIOBase.read`. If the underlying I/O class
2+
keeps a reference to the mutable memory raise a :exc:`BufferError`.

Modules/_io/iobase.c

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -953,7 +953,18 @@ _io__RawIOBase_read_impl(PyObject *self, Py_ssize_t n)
953953
return NULL;
954954
}
955955

956-
res = PyBytes_FromStringAndSize(PyByteArray_AsString(b), bytes_filled);
956+
res = PyObject_CallMethod(b, "resize", "i", bytes_filled);
957+
if (res != Py_None) {
958+
Py_DECREF(b);
959+
if (res != NULL) {
960+
PyErr_Format(PyExc_ValueError,
961+
"resize returned unexpected value %R",
962+
res);
963+
Py_DECREF(res);
964+
}
965+
return res;
966+
}
967+
res = PyObject_CallMethod(b, "take_bytes", NULL);
957968
Py_DECREF(b);
958969
return res;
959970
}

0 commit comments

Comments
 (0)