Skip to content

Commit 27f62eb

Browse files
authored
gh-140011: Delete importdl assertion that prevents importing embedded modules from packages (GH-141605)
1 parent d7f0214 commit 27f62eb

File tree

3 files changed

+212
-16
lines changed

3 files changed

+212
-16
lines changed

Lib/test/test_embed.py

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -241,21 +241,7 @@ def test_repeated_init_and_inittab(self):
241241

242242
def test_create_module_from_initfunc(self):
243243
out, err = self.run_embedded_interpreter("test_create_module_from_initfunc")
244-
if support.Py_GIL_DISABLED:
245-
# the test imports a singlephase init extension, so it emits a warning
246-
# under the free-threaded build
247-
expected_runtime_warning = (
248-
"RuntimeWarning: The global interpreter lock (GIL)"
249-
" has been enabled to load module 'embedded_ext'"
250-
)
251-
filtered_err_lines = [
252-
line
253-
for line in err.strip().splitlines()
254-
if expected_runtime_warning not in line
255-
]
256-
self.assertEqual(filtered_err_lines, [])
257-
else:
258-
self.assertEqual(err, "")
244+
self.assertEqual(self._nogil_filtered_err(err, "embedded_ext"), "")
259245
self.assertEqual(out,
260246
"<module 'my_test_extension' (static-extension)>\n"
261247
"my_test_extension.executed='yes'\n"
@@ -264,6 +250,26 @@ def test_create_module_from_initfunc(self):
264250
"embedded_ext.executed='yes'\n"
265251
)
266252

253+
def test_inittab_submodule_multiphase(self):
254+
out, err = self.run_embedded_interpreter("test_inittab_submodule_multiphase")
255+
self.assertEqual(err, "")
256+
self.assertEqual(out,
257+
"<module 'mp_pkg.mp_submod' (built-in)>\n"
258+
"<module 'mp_pkg.mp_submod' (built-in)>\n"
259+
"Hello from sub-module\n"
260+
"mp_pkg.mp_submod.mp_submod_exec_slot_ran='yes'\n"
261+
"mp_pkg.mp_pkg_exec_slot_ran='yes'\n"
262+
)
263+
264+
def test_inittab_submodule_singlephase(self):
265+
out, err = self.run_embedded_interpreter("test_inittab_submodule_singlephase")
266+
self.assertEqual(self._nogil_filtered_err(err, "sp_pkg"), "")
267+
self.assertEqual(out,
268+
"<module 'sp_pkg.sp_submod' (built-in)>\n"
269+
"<module 'sp_pkg.sp_submod' (built-in)>\n"
270+
"Hello from sub-module\n"
271+
)
272+
267273
def test_forced_io_encoding(self):
268274
# Checks forced configuration of embedded interpreter IO streams
269275
env = dict(os.environ, PYTHONIOENCODING="utf-8:surrogateescape")
@@ -541,6 +547,24 @@ def test_getargs_reset_static_parser(self):
541547
out, err = self.run_embedded_interpreter("test_repeated_init_exec", code)
542548
self.assertEqual(out, '1\n2\n3\n' * INIT_LOOPS)
543549

550+
@staticmethod
551+
def _nogil_filtered_err(err: str, mod_name: str) -> str:
552+
if not support.Py_GIL_DISABLED:
553+
return err
554+
555+
# the test imports a singlephase init extension, so it emits a warning
556+
# under the free-threaded build
557+
expected_runtime_warning = (
558+
"RuntimeWarning: The global interpreter lock (GIL)"
559+
f" has been enabled to load module '{mod_name}'"
560+
)
561+
filtered_err_lines = [
562+
line
563+
for line in err.strip().splitlines()
564+
if expected_runtime_warning not in line
565+
]
566+
return "\n".join(filtered_err_lines)
567+
544568

545569
def config_dev_mode(preconfig, config):
546570
preconfig['allocator'] = PYMEM_ALLOCATOR_DEBUG

Programs/_testembed.c

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2323,6 +2323,177 @@ test_create_module_from_initfunc(void)
23232323
return Py_RunMain();
23242324
}
23252325

2326+
/// Multi-phase initialization package & submodule ///
2327+
2328+
int
2329+
mp_pkg_exec(PyObject *mod)
2330+
{
2331+
// make this a namespace package
2332+
// empty list = namespace package
2333+
if (PyModule_Add(mod, "__path__", PyList_New(0)) < 0) {
2334+
return -1;
2335+
}
2336+
if (PyModule_AddStringConstant(mod, "mp_pkg_exec_slot_ran", "yes") < 0) {
2337+
return -1;
2338+
}
2339+
return 0;
2340+
}
2341+
2342+
static PyModuleDef_Slot mp_pkg_slots[] = {
2343+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
2344+
{Py_mod_exec, mp_pkg_exec},
2345+
{0, NULL}
2346+
};
2347+
2348+
static struct PyModuleDef mp_pkg_def = {
2349+
PyModuleDef_HEAD_INIT,
2350+
.m_name = "mp_pkg",
2351+
.m_size = 0,
2352+
.m_slots = mp_pkg_slots,
2353+
};
2354+
2355+
PyMODINIT_FUNC
2356+
PyInit_mp_pkg(void)
2357+
{
2358+
return PyModuleDef_Init(&mp_pkg_def);
2359+
}
2360+
2361+
static PyObject *
2362+
submod_greet(PyObject *self, PyObject *Py_UNUSED(ignored))
2363+
{
2364+
return PyUnicode_FromString("Hello from sub-module");
2365+
}
2366+
2367+
static PyMethodDef submod_methods[] = {
2368+
{"greet", submod_greet, METH_NOARGS, NULL},
2369+
{NULL},
2370+
};
2371+
2372+
int
2373+
mp_submod_exec(PyObject *mod)
2374+
{
2375+
return PyModule_AddStringConstant(mod, "mp_submod_exec_slot_ran", "yes");
2376+
}
2377+
2378+
static PyModuleDef_Slot mp_submod_slots[] = {
2379+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
2380+
{Py_mod_exec, mp_submod_exec},
2381+
{0, NULL}
2382+
};
2383+
2384+
static struct PyModuleDef mp_submod_def = {
2385+
PyModuleDef_HEAD_INIT,
2386+
.m_name = "mp_pkg.mp_submod",
2387+
.m_size = 0,
2388+
.m_methods = submod_methods,
2389+
.m_slots = mp_submod_slots,
2390+
};
2391+
2392+
PyMODINIT_FUNC
2393+
PyInit_mp_submod(void)
2394+
{
2395+
return PyModuleDef_Init(&mp_submod_def);
2396+
}
2397+
2398+
static int
2399+
test_inittab_submodule_multiphase(void)
2400+
{
2401+
wchar_t* argv[] = {
2402+
PROGRAM_NAME,
2403+
L"-c",
2404+
L"import sys;"
2405+
L"import mp_pkg.mp_submod;"
2406+
L"print(mp_pkg.mp_submod);"
2407+
L"print(sys.modules['mp_pkg.mp_submod']);"
2408+
L"print(mp_pkg.mp_submod.greet());"
2409+
L"print(f'{mp_pkg.mp_submod.mp_submod_exec_slot_ran=}');"
2410+
L"print(f'{mp_pkg.mp_pkg_exec_slot_ran=}');"
2411+
};
2412+
PyConfig config;
2413+
if (PyImport_AppendInittab("mp_pkg",
2414+
&PyInit_mp_pkg) != 0) {
2415+
fprintf(stderr, "PyImport_AppendInittab() failed\n");
2416+
return 1;
2417+
}
2418+
if (PyImport_AppendInittab("mp_pkg.mp_submod",
2419+
&PyInit_mp_submod) != 0) {
2420+
fprintf(stderr, "PyImport_AppendInittab() failed\n");
2421+
return 1;
2422+
}
2423+
PyConfig_InitPythonConfig(&config);
2424+
config.isolated = 1;
2425+
config_set_argv(&config, Py_ARRAY_LENGTH(argv), argv);
2426+
init_from_config_clear(&config);
2427+
return Py_RunMain();
2428+
}
2429+
2430+
/// Single-phase initialization package & submodule ///
2431+
2432+
static struct PyModuleDef sp_pkg_def = {
2433+
PyModuleDef_HEAD_INIT,
2434+
.m_name = "sp_pkg",
2435+
.m_size = 0,
2436+
};
2437+
2438+
PyMODINIT_FUNC
2439+
PyInit_sp_pkg(void)
2440+
{
2441+
PyObject *mod = PyModule_Create(&sp_pkg_def);
2442+
if (mod == NULL) {
2443+
return NULL;
2444+
}
2445+
// make this a namespace package
2446+
// empty list = namespace package
2447+
if (PyModule_Add(mod, "__path__", PyList_New(0)) < 0) {
2448+
Py_DECREF(mod);
2449+
return NULL;
2450+
}
2451+
return mod;
2452+
}
2453+
2454+
static struct PyModuleDef sp_submod_def = {
2455+
PyModuleDef_HEAD_INIT,
2456+
.m_name = "sp_pkg.sp_submod",
2457+
.m_size = 0,
2458+
.m_methods = submod_methods,
2459+
};
2460+
2461+
PyMODINIT_FUNC
2462+
PyInit_sp_submod(void)
2463+
{
2464+
return PyModule_Create(&sp_submod_def);
2465+
}
2466+
2467+
static int
2468+
test_inittab_submodule_singlephase(void)
2469+
{
2470+
wchar_t* argv[] = {
2471+
PROGRAM_NAME,
2472+
L"-c",
2473+
L"import sys;"
2474+
L"import sp_pkg.sp_submod;"
2475+
L"print(sp_pkg.sp_submod);"
2476+
L"print(sys.modules['sp_pkg.sp_submod']);"
2477+
L"print(sp_pkg.sp_submod.greet());"
2478+
};
2479+
PyConfig config;
2480+
if (PyImport_AppendInittab("sp_pkg",
2481+
&PyInit_sp_pkg) != 0) {
2482+
fprintf(stderr, "PyImport_AppendInittab() failed\n");
2483+
return 1;
2484+
}
2485+
if (PyImport_AppendInittab("sp_pkg.sp_submod",
2486+
&PyInit_sp_submod) != 0) {
2487+
fprintf(stderr, "PyImport_AppendInittab() failed\n");
2488+
return 1;
2489+
}
2490+
PyConfig_InitPythonConfig(&config);
2491+
config.isolated = 1;
2492+
config_set_argv(&config, Py_ARRAY_LENGTH(argv), argv);
2493+
init_from_config_clear(&config);
2494+
return Py_RunMain();
2495+
}
2496+
23262497
static void wrap_allocator(PyMemAllocatorEx *allocator);
23272498
static void unwrap_allocator(PyMemAllocatorEx *allocator);
23282499

@@ -2507,6 +2678,8 @@ static struct TestCase TestCases[] = {
25072678
{"test_get_incomplete_frame", test_get_incomplete_frame},
25082679
{"test_gilstate_after_finalization", test_gilstate_after_finalization},
25092680
{"test_create_module_from_initfunc", test_create_module_from_initfunc},
2681+
{"test_inittab_submodule_multiphase", test_inittab_submodule_multiphase},
2682+
{"test_inittab_submodule_singlephase", test_inittab_submodule_singlephase},
25102683
{NULL, NULL}
25112684
};
25122685

Python/importdl.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,6 @@ _Py_ext_module_loader_info_init_for_builtin(
156156
PyObject *name)
157157
{
158158
assert(PyUnicode_Check(name));
159-
assert(PyUnicode_FindChar(name, '.', 0, PyUnicode_GetLength(name), -1) == -1);
160159
assert(PyUnicode_GetLength(name) > 0);
161160

162161
PyObject *name_encoded = PyUnicode_AsEncodedString(name, "ascii", NULL);

0 commit comments

Comments
 (0)