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..b398c10075ded12 100644 --- a/Lib/test/test_interpreters/test_api.py +++ b/Lib/test/test_interpreters/test_api.py @@ -1873,6 +1873,20 @@ 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 + script = dedent(r""" + import __main__ + from concurrent import interpreters + + __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): 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..f8c38d59f0b6a0c --- /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 :meth:`concurrent.interpreters.Interpreter.call` when +``__main__.__file__`` contains lone surrogates. diff --git a/Objects/moduleobject.c b/Objects/moduleobject.c index b8cd6025c20ba56..0d3b9f19998ba10 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,16 +991,25 @@ _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) { + 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"); } 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; }