From 69cb7771c08f6baba2c73f64293cf0c95793a6db Mon Sep 17 00:00:00 2001 From: Ivy Xu Date: Sat, 22 Aug 2026 14:49:22 +0800 Subject: [PATCH 1/4] Fix crash in `Interpreter.call()` with lone surrogate in `__main__.__file__` --- Include/internal/pycore_moduleobject.h | 2 +- Lib/test/test_interpreters/test_api.py | 7 +++++++ ...2026-08-22-14-40-04.gh-issue-156122.J1b673.rst | 2 ++ Objects/moduleobject.c | 15 ++++++++++++--- Python/crossinterp.c | 2 +- 5 files changed, 23 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-08-22-14-40-04.gh-issue-156122.J1b673.rst diff --git a/Include/internal/pycore_moduleobject.h b/Include/internal/pycore_moduleobject.h index 5bcfd17cec46271..72139b5bbf3d1d4 100644 --- a/Include/internal/pycore_moduleobject.h +++ b/Include/internal/pycore_moduleobject.h @@ -72,7 +72,7 @@ static inline PyObject* _PyModule_GetDict(PyObject *mod) { } extern PyObject * _PyModule_GetFilenameObject(PyObject *); -extern Py_ssize_t _PyModule_GetFilenameUTF8( +extern Py_ssize_t _PyModule_GetFilename( PyObject *module, char *buffer, Py_ssize_t maxlen); diff --git a/Lib/test/test_interpreters/test_api.py b/Lib/test/test_interpreters/test_api.py index 13d23af5aceb475..c878ee064126022 100644 --- a/Lib/test/test_interpreters/test_api.py +++ b/Lib/test/test_interpreters/test_api.py @@ -1873,6 +1873,13 @@ def test_call_in_thread(self): t.join() self.assertIsNotNone(ctx.caught) + def test_call_with_surrogate_in_main_filename(self): + # https://github.com/python/cpython/issues/156122 + import __main__ + __main__.__file__ = "bad\ud800.py" + + interp = interpreters.create() + interp.call(lambda x: x, [1]) class TestIsShareable(TestBase): diff --git a/Misc/NEWS.d/next/Library/2026-08-22-14-40-04.gh-issue-156122.J1b673.rst b/Misc/NEWS.d/next/Library/2026-08-22-14-40-04.gh-issue-156122.J1b673.rst new file mode 100644 index 000000000000000..7311c13924e6965 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-22-14-40-04.gh-issue-156122.J1b673.rst @@ -0,0 +1,2 @@ +Fix a crash in :method:`concurrent.interpreters.Interpreter.call` when +``__main__.__file__`` contains lone surrogates. diff --git a/Objects/moduleobject.c b/Objects/moduleobject.c index b8cd6025c20ba56..bcc8f60dcc47b10 100644 --- a/Objects/moduleobject.c +++ b/Objects/moduleobject.c @@ -975,7 +975,7 @@ PyModule_GetFilename(PyObject *m) } Py_ssize_t -_PyModule_GetFilenameUTF8(PyObject *mod, char *buffer, Py_ssize_t maxlen) +_PyModule_GetFilename(PyObject *mod, char *buffer, Py_ssize_t maxlen) { // We "return" an empty string for an invalid module // and for a missing, empty, or invalid filename. @@ -991,8 +991,15 @@ _PyModule_GetFilenameUTF8(PyObject *mod, char *buffer, Py_ssize_t maxlen) size = 0; } else { - const char *filename = PyUnicode_AsUTF8AndSize(filenameobj, &size); - assert(size >= 0); + char *filename; + PyObject *bytes = PyUnicode_EncodeFSDefault(filenameobj); + if (bytes == NULL) { + goto exit; + } + if (PyBytes_AsStringAndSize(bytes, &filename, &size) < 0) { + Py_DECREF(bytes); + goto exit; + } if (size > maxlen) { size = -1; PyErr_SetString(PyExc_ValueError, "__file__ too long"); @@ -1000,7 +1007,9 @@ _PyModule_GetFilenameUTF8(PyObject *mod, char *buffer, Py_ssize_t maxlen) else { (void)strcpy(buffer, filename); } + Py_DECREF(bytes); } +exit: Py_DECREF(filenameobj); return size; } diff --git a/Python/crossinterp.c b/Python/crossinterp.c index ed77c1be646e275..0fe83728f973fba 100644 --- a/Python/crossinterp.c +++ b/Python/crossinterp.c @@ -28,7 +28,7 @@ _Py_GetMainfile(char *buffer, size_t maxlen) Py_XDECREF(module); return -1; } - Py_ssize_t size = _PyModule_GetFilenameUTF8(module, buffer, maxlen); + Py_ssize_t size = _PyModule_GetFilename(module, buffer, maxlen); Py_DECREF(module); return size; } From fb914f988bbd711a57928b7fd47b39a9b2fc8d88 Mon Sep 17 00:00:00 2001 From: Ivy Xu Date: Sat, 22 Aug 2026 15:17:40 +0800 Subject: [PATCH 2/4] fix news entry --- .../next/Library/2026-08-22-14-40-04.gh-issue-156122.J1b673.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2026-08-22-14-40-04.gh-issue-156122.J1b673.rst b/Misc/NEWS.d/next/Library/2026-08-22-14-40-04.gh-issue-156122.J1b673.rst index 7311c13924e6965..f8c38d59f0b6a0c 100644 --- a/Misc/NEWS.d/next/Library/2026-08-22-14-40-04.gh-issue-156122.J1b673.rst +++ b/Misc/NEWS.d/next/Library/2026-08-22-14-40-04.gh-issue-156122.J1b673.rst @@ -1,2 +1,2 @@ -Fix a crash in :method:`concurrent.interpreters.Interpreter.call` when +Fix a crash in :meth:`concurrent.interpreters.Interpreter.call` when ``__main__.__file__`` contains lone surrogates. From 343226977e1b48dd5a045c044133b757b7a6d042 Mon Sep 17 00:00:00 2001 From: Ivy Xu Date: Sat, 22 Aug 2026 16:32:31 +0800 Subject: [PATCH 3/4] fix test --- Lib/test/test_interpreters/test_api.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_interpreters/test_api.py b/Lib/test/test_interpreters/test_api.py index c878ee064126022..b398c10075ded12 100644 --- a/Lib/test/test_interpreters/test_api.py +++ b/Lib/test/test_interpreters/test_api.py @@ -1875,11 +1875,18 @@ def test_call_in_thread(self): def test_call_with_surrogate_in_main_filename(self): # https://github.com/python/cpython/issues/156122 - import __main__ - __main__.__file__ = "bad\ud800.py" + script = dedent(r""" + import __main__ + from concurrent import interpreters - interp = interpreters.create() - interp.call(lambda x: x, [1]) + __main__.__file__ = "bad\ud800.py" + interp = interpreters.create() + interp.call(lambda x: x, [1]) + """) + with os_helper.temp_dir() as tempdir: + filename = script_helper.make_script(tempdir, 'my-script', script) + res = script_helper.assert_python_ok(filename) + self.assertEqual(res.out, b'') class TestIsShareable(TestBase): From df5389e0b7d8f761f76a53af1fd1ffc10633b2c3 Mon Sep 17 00:00:00 2001 From: Ivy Xu Date: Sat, 22 Aug 2026 18:48:11 +0800 Subject: [PATCH 4/4] address review --- Objects/moduleobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/moduleobject.c b/Objects/moduleobject.c index bcc8f60dcc47b10..0d3b9f19998ba10 100644 --- a/Objects/moduleobject.c +++ b/Objects/moduleobject.c @@ -1000,7 +1000,7 @@ _PyModule_GetFilename(PyObject *mod, char *buffer, Py_ssize_t maxlen) Py_DECREF(bytes); goto exit; } - if (size > maxlen) { + if (size >= maxlen) { size = -1; PyErr_SetString(PyExc_ValueError, "__file__ too long"); }