https://github.com/python/cpython/commit/e325fae357821165d30fcca10ad4941d3c1e5201
commit: e325fae357821165d30fcca10ad4941d3c1e5201
branch: 3.15
author: Miss Islington (bot) <[email protected]>
committer: hugovk <[email protected]>
date: 2026-09-12T21:47:23+03:00
summary:

[3.15] gh-156126: Fix crash in -X importtime with unencodable module names 
(GH-156137) (#156259)

Co-authored-by: Kirill Podoprigora <[email protected]>

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 bf8ac2bbac309fe..1244dbca02e2c76 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 000000000000000..d8cfefb86b06dd2
--- /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 95f5c20bf4894a8..dc55582792a9c5d 100644
--- a/Python/import.c
+++ b/Python/import.c
@@ -288,6 +288,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)
 {
@@ -325,8 +338,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
     }
 
@@ -4118,10 +4134,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]

Reply via email to