If the underlying I/O class keeps a reference to the memory, raise BufferError.
Co-authored-by: Victor Stinner <vstinner@python.org>
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(symmetric_difference_update));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(tabsize));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(tag));
+ _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(take_bytes));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(target));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(target_is_directory));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(task));
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)
INIT_ID(symmetric_difference_update), \
INIT_ID(tabsize), \
INIT_ID(tag), \
+ INIT_ID(take_bytes), \
INIT_ID(target), \
INIT_ID(target_is_directory), \
INIT_ID(task), \
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
+ string = &_Py_ID(take_bytes);
+ _PyUnicode_InternStatic(interp, &string);
+ assert(_PyUnicode_CheckConsistency(string, 1));
+ assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(target);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
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(),
--- /dev/null
+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`.
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;
}