gh-156126: Fix crash in -X importtime with unencodable module names (#156137)

Eclips4 <[email protected]>
Newsgroups gmane.comp.python.cvs
Message-ID <[email protected]>
https://github.com/python/cpython/commit/3a5aa685885d8a2103f7d8de17481be64bc84dfb
commit: 3a5aa685885d8a2103f7d8de17481be64bc84dfb
branch: main
author: Kirill Podoprigora <[email protected]>
committer: Eclips4 <[email protected]>
date: 2026-08-23T10:32:56+03:00
summary:

gh-156126: Fix crash in -X importtime with unencodable module names (#156137)

files:
A Misc/NEWS.d/next/Core_and_Builtins/2026-08-20-22-13-20.gh-issue-156126.pXm4Qr.rst
M Lib/test/test_cmd_line.py
M Python/import.c

diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py
index 25d6d1a248b457..555cebeadc6053 100644
--- a/Lib/test/test_cmd_line.py
+++ b/Lib/test/test_cmd_line.py
@@ -1267,6 +1267,24 @@ def test_import_time(self):
         assert_python_failure('-X', 'importtime=-1', '-c', code)
         assert_python_failure('-X', 'importtime=3', '-c', code)
 
+    def test_import_time_unencodable_module_name(self):
+        code = textwrap.dedent("""
+            import sys, types
+            name = 'mod\\ud800'
+            sys.modules[name] = types.ModuleType(name)
+            __import__(name)
+            try:
+                __import__('nonexistent\\ud800')
+            except ModuleNotFoundError:
+                pass
+        """)
+        res = assert_python_ok('-X', 'importtime=2', '-c', code)
+        res_err = res.err.decode('utf-8')
+        self.assertRegex(res_err,
+                         r'import time: cached\s* \| cached\s* \| mod\\ud800')
+        self.assertRegex(res_err,
+                         r'import time: \s*\d+ \| \s*\d+ \| \s*nonexistent\\ud800')
+
     def res2int(self, res):
         out = res.out.strip().decode("utf-8")
         return tuple(int(i) for i in out.split())
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-08-20-22-13-20.gh-issue-156126.pXm4Qr.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-20-22-13-20.gh-issue-156126.pXm4Qr.rst
new file mode 100644
index 00000000000000..d8cfefb86b06dd
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-20-22-13-20.gh-issue-156126.pXm4Qr.rst
@@ -0,0 +1,3 @@
+Fix a crash when importing a module whose name contains characters that
+cannot be encoded to UTF-8 (such as lone surrogates) while :option:`-X
+importtime <-X>` is enabled.
diff --git a/Python/import.c b/Python/import.c
index 47d5296a2fec9b..037f15d4ca2baf 100644
--- a/Python/import.c
+++ b/Python/import.c
@@ -286,6 +286,19 @@ _PyImport_ClearLazyModules(PyInterpreterState *interp)
     Py_CLEAR(LAZY_PENDING_SUBMODULES(interp));
 }
 
+static PyObject *
+get_importtime_name(PyObject *name)
+{
+    PyObject *exc = PyErr_GetRaisedException();
+    PyObject *encoded = PyUnicode_AsEncodedString(name, "utf-8",
+                                                  "backslashreplace");
+    if (encoded == NULL) {
+        PyErr_Clear();
+    }
+    PyErr_SetRaisedException(exc);
+    return encoded;
+}
+
 static int
 import_ensure_initialized(PyInterpreterState *interp, PyObject *mod, PyObject *name)
 {
@@ -323,8 +336,11 @@ import_ensure_initialized(PyInterpreterState *interp, PyObject *mod, PyObject *n
     if (_PyInterpreterState_GetConfig(interp)->import_time == 2) {
         _IMPORT_TIME_HEADER(interp);
 #define import_level FIND_AND_LOAD(interp).import_level
+        PyObject *encoded_name = get_importtime_name(name);
         fprintf(stderr, "import time: cached    | cached     | %*s\n",
-                import_level*2, PyUnicode_AsUTF8(name));
+                import_level*2,
+                encoded_name != NULL ? PyBytes_AS_STRING(encoded_name) : "?");
+        Py_XDECREF(encoded_name);
 #undef import_level
     }
 
@@ -4121,10 +4137,13 @@ import_find_and_load_with_name(PyThreadState *tstate, PyObject *abs_name,
         PyTime_t cum = t2 - t1;
 
         import_level--;
+        PyObject *encoded_name = get_importtime_name(abs_name);
         fprintf(stderr, "import time: %9ld | %10ld | %*s%s\n",
                 (long)_PyTime_AsMicroseconds(cum - accumulated, _PyTime_ROUND_CEILING),
                 (long)_PyTime_AsMicroseconds(cum, _PyTime_ROUND_CEILING),
-                import_level*2, "", PyUnicode_AsUTF8(abs_name));
+                import_level*2, "",
+                encoded_name != NULL ? PyBytes_AS_STRING(encoded_name) : "?");
+        Py_XDECREF(encoded_name);
 
         accumulated = accumulated_copy + cum;
     }

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.