Skip to content

Commit 3a5aa68

Browse files
authored
gh-156126: Fix crash in -X importtime with unencodable module names (#156137)
1 parent 526b2e0 commit 3a5aa68

3 files changed

Lines changed: 42 additions & 2 deletions

File tree

Lib/test/test_cmd_line.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1267,6 +1267,24 @@ def test_import_time(self):
12671267
assert_python_failure('-X', 'importtime=-1', '-c', code)
12681268
assert_python_failure('-X', 'importtime=3', '-c', code)
12691269

1270+
def test_import_time_unencodable_module_name(self):
1271+
code = textwrap.dedent("""
1272+
import sys, types
1273+
name = 'mod\\ud800'
1274+
sys.modules[name] = types.ModuleType(name)
1275+
__import__(name)
1276+
try:
1277+
__import__('nonexistent\\ud800')
1278+
except ModuleNotFoundError:
1279+
pass
1280+
""")
1281+
res = assert_python_ok('-X', 'importtime=2', '-c', code)
1282+
res_err = res.err.decode('utf-8')
1283+
self.assertRegex(res_err,
1284+
r'import time: cached\s* \| cached\s* \| mod\\ud800')
1285+
self.assertRegex(res_err,
1286+
r'import time: \s*\d+ \| \s*\d+ \| \s*nonexistent\\ud800')
1287+
12701288
def res2int(self, res):
12711289
out = res.out.strip().decode("utf-8")
12721290
return tuple(int(i) for i in out.split())
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix a crash when importing a module whose name contains characters that
2+
cannot be encoded to UTF-8 (such as lone surrogates) while :option:`-X
3+
importtime <-X>` is enabled.

Python/import.c

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,19 @@ _PyImport_ClearLazyModules(PyInterpreterState *interp)
286286
Py_CLEAR(LAZY_PENDING_SUBMODULES(interp));
287287
}
288288

289+
static PyObject *
290+
get_importtime_name(PyObject *name)
291+
{
292+
PyObject *exc = PyErr_GetRaisedException();
293+
PyObject *encoded = PyUnicode_AsEncodedString(name, "utf-8",
294+
"backslashreplace");
295+
if (encoded == NULL) {
296+
PyErr_Clear();
297+
}
298+
PyErr_SetRaisedException(exc);
299+
return encoded;
300+
}
301+
289302
static int
290303
import_ensure_initialized(PyInterpreterState *interp, PyObject *mod, PyObject *name)
291304
{
@@ -323,8 +336,11 @@ import_ensure_initialized(PyInterpreterState *interp, PyObject *mod, PyObject *n
323336
if (_PyInterpreterState_GetConfig(interp)->import_time == 2) {
324337
_IMPORT_TIME_HEADER(interp);
325338
#define import_level FIND_AND_LOAD(interp).import_level
339+
PyObject *encoded_name = get_importtime_name(name);
326340
fprintf(stderr, "import time: cached | cached | %*s\n",
327-
import_level*2, PyUnicode_AsUTF8(name));
341+
import_level*2,
342+
encoded_name != NULL ? PyBytes_AS_STRING(encoded_name) : "?");
343+
Py_XDECREF(encoded_name);
328344
#undef import_level
329345
}
330346

@@ -4121,10 +4137,13 @@ import_find_and_load_with_name(PyThreadState *tstate, PyObject *abs_name,
41214137
PyTime_t cum = t2 - t1;
41224138

41234139
import_level--;
4140+
PyObject *encoded_name = get_importtime_name(abs_name);
41244141
fprintf(stderr, "import time: %9ld | %10ld | %*s%s\n",
41254142
(long)_PyTime_AsMicroseconds(cum - accumulated, _PyTime_ROUND_CEILING),
41264143
(long)_PyTime_AsMicroseconds(cum, _PyTime_ROUND_CEILING),
4127-
import_level*2, "", PyUnicode_AsUTF8(abs_name));
4144+
import_level*2, "",
4145+
encoded_name != NULL ? PyBytes_AS_STRING(encoded_name) : "?");
4146+
Py_XDECREF(encoded_name);
41284147

41294148
accumulated = accumulated_copy + cum;
41304149
}

0 commit comments

Comments
 (0)