Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 5 additions & 10 deletions Doc/c-api/marshal.rst
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,7 @@ The following functions allow marshalled values to be read back in.
for reading. Only a 32-bit value can be read in using this function,
regardless of the native size of :c:expr:`long`.

On error, sets the appropriate exception (:exc:`EOFError`) and returns
``-1``.
On error, sets the appropriate exception and returns ``-1``.


.. c:function:: int PyMarshal_ReadShortFromFile(FILE *file)
Expand All @@ -62,17 +61,15 @@ The following functions allow marshalled values to be read back in.
for reading. Only a 16-bit value can be read in using this function,
regardless of the native size of :c:expr:`short`.

On error, sets the appropriate exception (:exc:`EOFError`) and returns
``-1``.
On error, sets the appropriate exception and returns ``-1``.


.. c:function:: PyObject* PyMarshal_ReadObjectFromFile(FILE *file)

Return a Python object from the data stream in a :c:expr:`FILE*` opened for
reading.

On error, sets the appropriate exception (:exc:`EOFError`, :exc:`ValueError`
or :exc:`TypeError`) and returns ``NULL``.
On error, sets the appropriate exception and returns ``NULL``.


.. c:function:: PyObject* PyMarshal_ReadLastObjectFromFile(FILE *file)
Expand All @@ -85,15 +82,13 @@ The following functions allow marshalled values to be read back in.
file. Only use this variant if you are certain that you won't be reading
anything else from the file.

On error, sets the appropriate exception (:exc:`EOFError`, :exc:`ValueError`
or :exc:`TypeError`) and returns ``NULL``.
On error, sets the appropriate exception and returns ``NULL``.


.. c:function:: PyObject* PyMarshal_ReadObjectFromString(const char *data, Py_ssize_t len)

Return a Python object from the data stream in a byte buffer
containing *len* bytes pointed to by *data*.

On error, sets the appropriate exception (:exc:`EOFError`, :exc:`ValueError`
or :exc:`TypeError`) and returns ``NULL``.
On error, sets the appropriate exception and returns ``NULL``.

13 changes: 13 additions & 0 deletions Lib/test/test_marshal.py
Original file line number Diff line number Diff line change
Expand Up @@ -784,6 +784,19 @@ def test_slice(self):
@unittest.skipUnless(_testcapi, 'requires _testcapi')
class CAPI_TestCase(unittest.TestCase, HelperMixin):

def test_read_from_file_error(self):
# A read error is reported as OSError, not EOFError.
# A directory cannot be read (on some platforms it cannot even
# be opened, which is reported as OSError as well).
os.mkdir(os_helper.TESTFN)
self.addCleanup(os_helper.rmdir, os_helper.TESTFN)
for func in (_testcapi.pymarshal_read_short_from_file,
_testcapi.pymarshal_read_long_from_file,
_testcapi.pymarshal_read_object_from_file,
_testcapi.pymarshal_read_last_object_from_file):
with self.subTest(func=func.__name__):
self.assertRaises(OSError, func, os_helper.TESTFN)

def test_write_long_to_file(self):
for v in range(marshal.version + 1):
_testcapi.pymarshal_write_long_to_file(0x12345678, os_helper.TESTFN, v)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
:c:func:`PyMarshal_ReadObjectFromFile` and other functions reading marshalled
data from a :c:expr:`FILE*` now raise :exc:`OSError` for I/O errors and
:exc:`KeyboardInterrupt` for interrupted reading, instead of :exc:`EOFError`.
44 changes: 32 additions & 12 deletions Python/marshal.c
Original file line number Diff line number Diff line change
Expand Up @@ -872,6 +872,12 @@ r_string(Py_ssize_t n, RFILE *p)
if (!p->readable) {
assert(p->fp != NULL);
read = fread(p->buf, 1, n, p->fp);
if (read != n) {
assert(read < n);
if (!PyErr_CheckSignals() && ferror(p->fp)) {
PyErr_SetFromErrno(PyExc_OSError);
}
}
}
else {
PyObject *res, *mview;
Expand All @@ -884,21 +890,23 @@ r_string(Py_ssize_t n, RFILE *p)
return NULL;

res = _PyObject_CallMethod(p->readable, &_Py_ID(readinto), "N", mview);
if (res != NULL) {
read = PyNumber_AsSsize_t(res, PyExc_ValueError);
Py_DECREF(res);
if (res == NULL) {
return NULL;
}
read = PyNumber_AsSsize_t(res, PyExc_ValueError);
Py_DECREF(res);
if (read > n) {
PyErr_Format(PyExc_ValueError,
"read() returned too much data: "
"%zd bytes requested, %zd returned",
n, read);
return NULL;
}
}
if (read != n) {
if (!PyErr_Occurred()) {
if (read > n)
PyErr_Format(PyExc_ValueError,
"read() returned too much data: "
"%zd bytes requested, %zd returned",
n, read);
else
PyErr_SetString(PyExc_EOFError,
"EOF read where not expected");
PyErr_SetString(PyExc_EOFError,
"EOF read where not expected");
}
return NULL;
}
Expand All @@ -919,6 +927,10 @@ r_byte(RFILE *p)
if (c != EOF) {
return c;
}
if (!PyErr_CheckSignals() && ferror(p->fp)) {
PyErr_SetFromErrno(PyExc_OSError);
return EOF;
}
}
else {
const char *ptr = r_string(1, p);
Expand Down Expand Up @@ -1841,8 +1853,16 @@ PyMarshal_ReadLastObjectFromFile(FILE *fp)
if (filesize > 0 && filesize <= REASONABLE_FILE_LIMIT) {
char* pBuf = (char *)PyMem_Malloc(filesize);
if (pBuf != NULL) {
PyObject *v = NULL;
size_t n = fread(pBuf, 1, (size_t)filesize, fp);
PyObject* v = PyMarshal_ReadObjectFromString(pBuf, n);
if (!PyErr_CheckSignals()) {
if (ferror(fp)) {
PyErr_SetFromErrno(PyExc_OSError);
}
else {
v = PyMarshal_ReadObjectFromString(pBuf, n);
}
}
PyMem_Free(pBuf);
return v;
}
Expand Down
Loading