Skip to content
1 change: 1 addition & 0 deletions Include/internal/pycore_global_objects_fini_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Include/internal/pycore_global_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -789,6 +789,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(symmetric_difference_update)
STRUCT_FOR_ID(tabsize)
STRUCT_FOR_ID(tag)
STRUCT_FOR_ID(take_bytes)
STRUCT_FOR_ID(target)
STRUCT_FOR_ID(target_is_directory)
STRUCT_FOR_ID(task)
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_runtime_init_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Include/internal/pycore_unicodeobject_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions Lib/test/test_io/test_general.py
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,25 @@ def readinto(self, b):
with self.assertRaises(ValueError):
Misbehaved(bad_size).read()

def test_RawIOBase_read_gh60107(self):
# gh-60107: Ensure a "Raw I/O" which keeps a reference to the
# mutable memory doesn't allow making a mutable bytes.
class RawIOKeepsReference(self.MockRawIOWithoutRead):
def __init__(self, *args, **kwargs):
self.buf = None
super().__init__(*args, **kwargs)

def readinto(self, buf):
# buf is the bytearray so keeping a reference to it doesn't keep
# the memory alive; a memoryview does.
self.buf = memoryview(buf)
buf[0:4] = self._read_stack.pop()
return 3

with self.assertRaises(BufferError):
rawio = RawIOKeepsReference([b"1234"])
rawio.read(4)

def test_types_have_dict(self):
test = (
self.IOBase(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Remove a copy from :meth:`io.RawIOBase.read`. If the underlying I/O class
keeps a reference to the mutable memory, raise a :exc:`BufferError`.
22 changes: 11 additions & 11 deletions Modules/_io/iobase.c
Original file line number Diff line number Diff line change
Expand Up @@ -927,33 +927,33 @@ _io__RawIOBase_read_impl(PyObject *self, Py_ssize_t n)
return PyObject_CallMethodNoArgs(self, &_Py_ID(readall));
}

/* TODO: allocate a bytes object directly instead and manually construct
a writable memoryview pointing to it. */
b = PyByteArray_FromStringAndSize(NULL, n);
if (b == NULL)
if (b == NULL) {
return NULL;
}

res = PyObject_CallMethodObjArgs(self, &_Py_ID(readinto), b, NULL);
if (res == NULL || res == Py_None) {
Py_DECREF(b);
return res;
goto cleanup;
}

Py_ssize_t bytes_filled = PyNumber_AsSsize_t(res, PyExc_ValueError);
Py_DECREF(res);
Py_CLEAR(res);
if (bytes_filled == -1 && PyErr_Occurred()) {
Py_DECREF(b);
return NULL;
goto cleanup;
}
if (bytes_filled < 0 || bytes_filled > n) {
Py_DECREF(b);
PyErr_Format(PyExc_ValueError,
"readinto returned %zd outside buffer size %zd",
bytes_filled, n);
return NULL;
goto cleanup;
}
if (PyByteArray_Resize(b, bytes_filled) < 0) {
goto cleanup;
}
res = PyObject_CallMethodNoArgs(b, &_Py_ID(take_bytes));

res = PyBytes_FromStringAndSize(PyByteArray_AsString(b), bytes_filled);
cleanup:
Py_DECREF(b);
return res;
}
Expand Down
Loading