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
19 changes: 19 additions & 0 deletions Lib/test/test_interpreters/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1683,6 +1683,25 @@ def get_count():
self.assertEqual(after, 0)
self.assertEqual(counts, [0, 1, 4])

def test_surrogate_filename_in___main__(self):
interp = interpreters.create()
import __main__
orig_file = getattr(__main__, '__file__', None)
try:
for surrogate in ('\ud800', '\udcff'):
with self.subTest(surrogate=ascii(surrogate)):
__main__.__file__ = f'my_script_{surrogate}.py'
res = interp.call(lambda x: x, [1])
self.assertEqual(res, [1])
finally:
if orig_file is None:
try:
del __main__.__file__
except AttributeError:
pass
else:
__main__.__file__ = orig_file

def test_raises(self):
interp = interpreters.create()
with self.assertRaises(ExecutionFailed):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix crash when module filenames containing lone surrogates are used during
cross-interpreter unpickling.
19 changes: 11 additions & 8 deletions Objects/moduleobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -991,14 +991,17 @@ _PyModule_GetFilenameUTF8(PyObject *mod, char *buffer, Py_ssize_t maxlen)
size = 0;
}
else {
const char *filename = PyUnicode_AsUTF8AndSize(filenameobj, &size);
assert(size >= 0);
if (size > maxlen) {
size = -1;
PyErr_SetString(PyExc_ValueError, "__file__ too long");
}
else {
(void)strcpy(buffer, filename);
PyObject *bytes = PyUnicode_EncodeFSDefault(filenameobj);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve a decodable path for re-executing main

On POSIX, a real filename containing an undecodable byte is exposed as (for example) \udcff; PyUnicode_EncodeFSDefault() converts that surrogate back to raw 0xff, not UTF-8. When a __main__ function that uses globals is unpickled in the target interpreter, runpy_run_path() later passes this buffer through Py_BuildValue("sOs") (Python/crossinterp.c:44), which strictly decodes it as UTF-8 and raises UnicodeDecodeError. Thus cross-interpreter calls from scripts with surrogate-escaped filenames still fail when they need the existing re-execution path; retain a Unicode/UTF-8-surrogatepass representation through that handoff (or pass a Unicode filename object) instead of filesystem bytes.

Useful? React with 👍 / 👎.

if (bytes != NULL) {
size = PyBytes_GET_SIZE(bytes);
if (size > maxlen) {
size = -1;
PyErr_SetString(PyExc_ValueError, "__file__ too long");
}
else {
memcpy(buffer, PyBytes_AS_STRING(bytes), size + 1);
}
Py_DECREF(bytes);
}
}
Py_DECREF(filenameobj);
Expand Down
Loading