Skip to content

gh-155907: Raise OSError and KeyboardInterrupt when reading marshal data - #155909

Merged
serhiy-storchaka merged 6 commits into
python:mainfrom
serhiy-storchaka:marshal-read-errors
Sep 4, 2026
Merged

gh-155907: Raise OSError and KeyboardInterrupt when reading marshal data#155909
serhiy-storchaka merged 6 commits into
python:mainfrom
serhiy-storchaka:marshal-read-errors

Conversation

@serhiy-storchaka

@serhiy-storchaka serhiy-storchaka commented Aug 16, 2026

Copy link
Copy Markdown
Member

r_string(), r_byte() and PyMarshal_ReadLastObjectFromFile() did not check ferror() nor call PyErr_CheckSignals(), so a genuine I/O error or a read interrupted by Ctrl-C was reported as EOFError, as if the file had simply ended.

>>> import _testcapi, os
>>> os.mkdir('d')
>>> _testcapi.pymarshal_read_object_from_file('d')
EOFError: EOF read where object expected      # before
IsADirectoryError: [Errno 21] Is a directory  # after

The writing side had the same gap: w_flush() and w_string() discarded the result of fwrite(), so a failed or interrupted write to a FILE* was ignored. PyMarshal_WriteObjectToFile() also never turned the recorded WFERR_* code into an exception, so even an unmarshallable value was ignored.

>>> import _testcapi, marshal
>>> _testcapi.pymarshal_write_object_to_file(b'x' * 100000, '/dev/full', marshal.version)
                                             # before: no error
OSError: [Errno 28] No space left on device  # after
>>> _testcapi.pymarshal_write_object_to_file(object(), 'f', marshal.version)
                                             # before: no error
ValueError: unmarshallable object            # after

The WFERR_* to exception mapping is factored out of PyMarshal_WriteObjectToString() into w_set_error(), and all three writers now use it. This removes the note above PyMarshal_WriteLongToFile() saying that it never sets an exception and that doing so "should be regarded as an API-breaking change": detecting write errors makes it set one, which is what its documentation has promised since gh-105184.

The documentation of these functions listed only EOFError, ValueError and TypeError, which was inaccurate even before this change (MemoryError is raised when the read buffer cannot be allocated), so the per-function lists are replaced by one list for the whole section.

…shal data

Reading marshalled data from a FILE* did not check ferror() nor signals,
so an I/O error or a Ctrl-C was reported as EOFError.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@read-the-docs-community

read-the-docs-community Bot commented Aug 16, 2026

Copy link
Copy Markdown

@serhiy-storchaka

Copy link
Copy Markdown
Member Author

Failure on Windows is expected -- this is a bug #155905.

serhiy-storchaka and others added 3 commits August 29, 2026 19:31
PyMarshal_WriteObjectToFile() and PyMarshal_WriteLongToFile() now set the
error indicator when writing to the underlying FILE* fails or is interrupted
by a signal, instead of ignoring the failure.  PyMarshal_WriteObjectToFile()
now also sets the error indicator when the value cannot be marshalled.

r_byte() no longer replaces an exception raised by a signal handler with
EOFError.

Document which exceptions the marshalling functions can raise.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread Python/marshal.c
Comment thread Python/marshal.c Outdated
Comment thread Doc/c-api/marshal.rst
Previously, in functions taking a :c:expr:`FILE*`,
the reading functions raised :exc:`EOFError`
instead of :exc:`OSError` and :exc:`KeyboardInterrupt`,
and the writing functions ignored I/O errors and interruptions.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

marshal.load() and marshal.dump() error handling also changes and should be documented in Doc/library/marshal.rst.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, they are not affected. They do not use FILE* based C API.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked again your change, you're right and I'm wrong. marshal.load() and marshal.dump() are not affected.

Comment thread Python/marshal.c
Comment thread Python/marshal.c Outdated
Comment thread Lib/test/test_marshal.py Outdated
Preserve errno across PyErr_CheckSignals() when reporting a read error,
rename w_set_error() to w_set_exception(), handle a PyNumber_AsSsize_t()
failure explicitly, and check the error message in the test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@vstinner vstinner left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. I just suggest renaming WFERR_ERROR_SET to WFERR_EXCEPTION_SET.

Comment thread Python/marshal.c Outdated
#define WFERR_NESTEDTOODEEP 2
#define WFERR_NOMEMORY 3
#define WFERR_CODE_NOT_ALLOWED 4
#define WFERR_ERROR_SET 5 /* An exception has already been raised. */

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should rename the constant WFERR_EXCEPTION_SET since it's an "exception set", not an "error set".

Comment thread Doc/c-api/marshal.rst
Previously, in functions taking a :c:expr:`FILE*`,
the reading functions raised :exc:`EOFError`
instead of :exc:`OSError` and :exc:`KeyboardInterrupt`,
and the writing functions ignored I/O errors and interruptions.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked again your change, you're right and I'm wrong. marshal.load() and marshal.dump() are not affected.

@vstinner vstinner removed needs backport to 3.14 bugs and security fixes needs backport to 3.15 pre-release feature fixes, bugs and security fixes labels Sep 4, 2026
@vstinner

vstinner commented Sep 4, 2026

Copy link
Copy Markdown
Member

vstinner removed needs backport to 3.14 needs backport to 3.15 labels

I don't think that it's a good idea to backport this change. Some projects can rely on the current exact exception raised by these C functions. It's ok to change them in Python 3.16, but IMO it's too risky to backport the change to stable versions.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@serhiy-storchaka

Copy link
Copy Markdown
Member Author

Thank you for your review. Initially it was smaller change, but it grew more after discovering more silenced/overridden errors. I plannet to fix the writing part in a separate issue, but is better for documentation if fix them together.

@serhiy-storchaka
serhiy-storchaka merged commit aaae15c into python:main Sep 4, 2026
54 checks passed
@serhiy-storchaka
serhiy-storchaka deleted the marshal-read-errors branch September 4, 2026 13:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants