Skip to content

Commit f7d17ac

Browse files
authored
[3.14] gh-140011: Delete importdl assertion that prevents importing embedded modules from packages (GH-141605) (#141986)
gh-140011: Delete importdl assertion that prevents importing embedded modules from packages (GH-141605) (cherry picked from commit 27f62eb)
1 parent d8a72b4 commit f7d17ac

File tree

3 files changed

+211
-2
lines changed

3 files changed

+211
-2
lines changed

Lib/test/test_embed.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,26 @@ def test_repeated_init_and_inittab(self):
239239
lines = "\n".join(lines) + "\n"
240240
self.assertEqual(out, lines)
241241

242+
def test_inittab_submodule_multiphase(self):
243+
out, err = self.run_embedded_interpreter("test_inittab_submodule_multiphase")
244+
self.assertEqual(err, "")
245+
self.assertEqual(out,
246+
"<module 'mp_pkg.mp_submod' (built-in)>\n"
247+
"<module 'mp_pkg.mp_submod' (built-in)>\n"
248+
"Hello from sub-module\n"
249+
"mp_pkg.mp_submod.mp_submod_exec_slot_ran='yes'\n"
250+
"mp_pkg.mp_pkg_exec_slot_ran='yes'\n"
251+
)
252+
253+
def test_inittab_submodule_singlephase(self):
254+
out, err = self.run_embedded_interpreter("test_inittab_submodule_singlephase")
255+
self.assertEqual(self._nogil_filtered_err(err, "sp_pkg"), "")
256+
self.assertEqual(out,
257+
"<module 'sp_pkg.sp_submod' (built-in)>\n"
258+
"<module 'sp_pkg.sp_submod' (built-in)>\n"
259+
"Hello from sub-module\n"
260+
)
261+
242262
def test_forced_io_encoding(self):
243263
# Checks forced configuration of embedded interpreter IO streams
244264
env = dict(os.environ, PYTHONIOENCODING="utf-8:surrogateescape")
@@ -516,6 +536,24 @@ def test_getargs_reset_static_parser(self):
516536
out, err = self.run_embedded_interpreter("test_repeated_init_exec", code)
517537
self.assertEqual(out, '1\n2\n3\n' * INIT_LOOPS)
518538

539+
@staticmethod
540+
def _nogil_filtered_err(err: str, mod_name: str) -> str:
541+
if not support.Py_GIL_DISABLED:
542+
return err
543+
544+
# the test imports a singlephase init extension, so it emits a warning
545+
# under the free-threaded build
546+
expected_runtime_warning = (
547+
"RuntimeWarning: The global interpreter lock (GIL)"
548+
f" has been enabled to load module '{mod_name}'"
549+
)
550+
filtered_err_lines = [
551+
line
552+
for line in err.strip().splitlines()
553+
if expected_runtime_warning not in line
554+
]
555+
return "\n".join(filtered_err_lines)
556+
519557

520558
def config_dev_mode(preconfig, config):
521559
preconfig['allocator'] = PYMEM_ALLOCATOR_DEBUG

Programs/_testembed.c

Lines changed: 173 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2276,6 +2276,177 @@ static int test_repeated_init_and_inittab(void)
22762276
return 0;
22772277
}
22782278

2279+
/// Multi-phase initialization package & submodule ///
2280+
2281+
int
2282+
mp_pkg_exec(PyObject *mod)
2283+
{
2284+
// make this a namespace package
2285+
// empty list = namespace package
2286+
if (PyModule_Add(mod, "__path__", PyList_New(0)) < 0) {
2287+
return -1;
2288+
}
2289+
if (PyModule_AddStringConstant(mod, "mp_pkg_exec_slot_ran", "yes") < 0) {
2290+
return -1;
2291+
}
2292+
return 0;
2293+
}
2294+
2295+
static PyModuleDef_Slot mp_pkg_slots[] = {
2296+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
2297+
{Py_mod_exec, mp_pkg_exec},
2298+
{0, NULL}
2299+
};
2300+
2301+
static struct PyModuleDef mp_pkg_def = {
2302+
PyModuleDef_HEAD_INIT,
2303+
.m_name = "mp_pkg",
2304+
.m_size = 0,
2305+
.m_slots = mp_pkg_slots,
2306+
};
2307+
2308+
PyMODINIT_FUNC
2309+
PyInit_mp_pkg(void)
2310+
{
2311+
return PyModuleDef_Init(&mp_pkg_def);
2312+
}
2313+
2314+
static PyObject *
2315+
submod_greet(PyObject *self, PyObject *Py_UNUSED(ignored))
2316+
{
2317+
return PyUnicode_FromString("Hello from sub-module");
2318+
}
2319+
2320+
static PyMethodDef submod_methods[] = {
2321+
{"greet", submod_greet, METH_NOARGS, NULL},
2322+
{NULL},
2323+
};
2324+
2325+
int
2326+
mp_submod_exec(PyObject *mod)
2327+
{
2328+
return PyModule_AddStringConstant(mod, "mp_submod_exec_slot_ran", "yes");
2329+
}
2330+
2331+
static PyModuleDef_Slot mp_submod_slots[] = {
2332+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
2333+
{Py_mod_exec, mp_submod_exec},
2334+
{0, NULL}
2335+
};
2336+
2337+
static struct PyModuleDef mp_submod_def = {
2338+
PyModuleDef_HEAD_INIT,
2339+
.m_name = "mp_pkg.mp_submod",
2340+
.m_size = 0,
2341+
.m_methods = submod_methods,
2342+
.m_slots = mp_submod_slots,
2343+
};
2344+
2345+
PyMODINIT_FUNC
2346+
PyInit_mp_submod(void)
2347+
{
2348+
return PyModuleDef_Init(&mp_submod_def);
2349+
}
2350+
2351+
static int
2352+
test_inittab_submodule_multiphase(void)
2353+
{
2354+
wchar_t* argv[] = {
2355+
PROGRAM_NAME,
2356+
L"-c",
2357+
L"import sys;"
2358+
L"import mp_pkg.mp_submod;"
2359+
L"print(mp_pkg.mp_submod);"
2360+
L"print(sys.modules['mp_pkg.mp_submod']);"
2361+
L"print(mp_pkg.mp_submod.greet());"
2362+
L"print(f'{mp_pkg.mp_submod.mp_submod_exec_slot_ran=}');"
2363+
L"print(f'{mp_pkg.mp_pkg_exec_slot_ran=}');"
2364+
};
2365+
PyConfig config;
2366+
if (PyImport_AppendInittab("mp_pkg",
2367+
&PyInit_mp_pkg) != 0) {
2368+
fprintf(stderr, "PyImport_AppendInittab() failed\n");
2369+
return 1;
2370+
}
2371+
if (PyImport_AppendInittab("mp_pkg.mp_submod",
2372+
&PyInit_mp_submod) != 0) {
2373+
fprintf(stderr, "PyImport_AppendInittab() failed\n");
2374+
return 1;
2375+
}
2376+
PyConfig_InitPythonConfig(&config);
2377+
config.isolated = 1;
2378+
config_set_argv(&config, Py_ARRAY_LENGTH(argv), argv);
2379+
init_from_config_clear(&config);
2380+
return Py_RunMain();
2381+
}
2382+
2383+
/// Single-phase initialization package & submodule ///
2384+
2385+
static struct PyModuleDef sp_pkg_def = {
2386+
PyModuleDef_HEAD_INIT,
2387+
.m_name = "sp_pkg",
2388+
.m_size = 0,
2389+
};
2390+
2391+
PyMODINIT_FUNC
2392+
PyInit_sp_pkg(void)
2393+
{
2394+
PyObject *mod = PyModule_Create(&sp_pkg_def);
2395+
if (mod == NULL) {
2396+
return NULL;
2397+
}
2398+
// make this a namespace package
2399+
// empty list = namespace package
2400+
if (PyModule_Add(mod, "__path__", PyList_New(0)) < 0) {
2401+
Py_DECREF(mod);
2402+
return NULL;
2403+
}
2404+
return mod;
2405+
}
2406+
2407+
static struct PyModuleDef sp_submod_def = {
2408+
PyModuleDef_HEAD_INIT,
2409+
.m_name = "sp_pkg.sp_submod",
2410+
.m_size = 0,
2411+
.m_methods = submod_methods,
2412+
};
2413+
2414+
PyMODINIT_FUNC
2415+
PyInit_sp_submod(void)
2416+
{
2417+
return PyModule_Create(&sp_submod_def);
2418+
}
2419+
2420+
static int
2421+
test_inittab_submodule_singlephase(void)
2422+
{
2423+
wchar_t* argv[] = {
2424+
PROGRAM_NAME,
2425+
L"-c",
2426+
L"import sys;"
2427+
L"import sp_pkg.sp_submod;"
2428+
L"print(sp_pkg.sp_submod);"
2429+
L"print(sys.modules['sp_pkg.sp_submod']);"
2430+
L"print(sp_pkg.sp_submod.greet());"
2431+
};
2432+
PyConfig config;
2433+
if (PyImport_AppendInittab("sp_pkg",
2434+
&PyInit_sp_pkg) != 0) {
2435+
fprintf(stderr, "PyImport_AppendInittab() failed\n");
2436+
return 1;
2437+
}
2438+
if (PyImport_AppendInittab("sp_pkg.sp_submod",
2439+
&PyInit_sp_submod) != 0) {
2440+
fprintf(stderr, "PyImport_AppendInittab() failed\n");
2441+
return 1;
2442+
}
2443+
PyConfig_InitPythonConfig(&config);
2444+
config.isolated = 1;
2445+
config_set_argv(&config, Py_ARRAY_LENGTH(argv), argv);
2446+
init_from_config_clear(&config);
2447+
return Py_RunMain();
2448+
}
2449+
22792450
static void wrap_allocator(PyMemAllocatorEx *allocator);
22802451
static void unwrap_allocator(PyMemAllocatorEx *allocator);
22812452

@@ -2433,7 +2604,8 @@ static struct TestCase TestCases[] = {
24332604
{"test_frozenmain", test_frozenmain},
24342605
#endif
24352606
{"test_get_incomplete_frame", test_get_incomplete_frame},
2436-
2607+
{"test_inittab_submodule_multiphase", test_inittab_submodule_multiphase},
2608+
{"test_inittab_submodule_singlephase", test_inittab_submodule_singlephase},
24372609
{NULL, NULL}
24382610
};
24392611

Python/importdl.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,6 @@ _Py_ext_module_loader_info_init_for_builtin(
175175
PyObject *name)
176176
{
177177
assert(PyUnicode_Check(name));
178-
assert(PyUnicode_FindChar(name, '.', 0, PyUnicode_GetLength(name), -1) == -1);
179178
assert(PyUnicode_GetLength(name) > 0);
180179

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

0 commit comments

Comments
 (0)