Skip to content

Commit 385688d

Browse files
authored
[3.13] gh-140011: Delete importdl assertion that prevents importing embedded modules from packages (GH-141605) (#141987)
gh-140011: Delete importdl assertion that prevents importing embedded modules from packages (GH-141605) (cherry picked from commit 27f62eb)
1 parent 09ceaa6 commit 385688d

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
@@ -230,6 +230,26 @@ def test_repeated_init_and_inittab(self):
230230
lines = "\n".join(lines) + "\n"
231231
self.assertEqual(out, lines)
232232

233+
def test_inittab_submodule_multiphase(self):
234+
out, err = self.run_embedded_interpreter("test_inittab_submodule_multiphase")
235+
self.assertEqual(err, "")
236+
self.assertEqual(out,
237+
"<module 'mp_pkg.mp_submod' (built-in)>\n"
238+
"<module 'mp_pkg.mp_submod' (built-in)>\n"
239+
"Hello from sub-module\n"
240+
"mp_pkg.mp_submod.mp_submod_exec_slot_ran='yes'\n"
241+
"mp_pkg.mp_pkg_exec_slot_ran='yes'\n"
242+
)
243+
244+
def test_inittab_submodule_singlephase(self):
245+
out, err = self.run_embedded_interpreter("test_inittab_submodule_singlephase")
246+
self.assertEqual(self._nogil_filtered_err(err, "sp_pkg"), "")
247+
self.assertEqual(out,
248+
"<module 'sp_pkg.sp_submod' (built-in)>\n"
249+
"<module 'sp_pkg.sp_submod' (built-in)>\n"
250+
"Hello from sub-module\n"
251+
)
252+
233253
def test_forced_io_encoding(self):
234254
# Checks forced configuration of embedded interpreter IO streams
235255
env = dict(os.environ, PYTHONIOENCODING="utf-8:surrogateescape")
@@ -492,6 +512,24 @@ def test_getargs_reset_static_parser(self):
492512
out, err = self.run_embedded_interpreter("test_repeated_init_exec", code)
493513
self.assertEqual(out, '1\n2\n3\n' * INIT_LOOPS)
494514

515+
@staticmethod
516+
def _nogil_filtered_err(err: str, mod_name: str) -> str:
517+
if not support.Py_GIL_DISABLED:
518+
return err
519+
520+
# the test imports a singlephase init extension, so it emits a warning
521+
# under the free-threaded build
522+
expected_runtime_warning = (
523+
"RuntimeWarning: The global interpreter lock (GIL)"
524+
f" has been enabled to load module '{mod_name}'"
525+
)
526+
filtered_err_lines = [
527+
line
528+
for line in err.strip().splitlines()
529+
if expected_runtime_warning not in line
530+
]
531+
return "\n".join(filtered_err_lines)
532+
495533

496534
@unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi")
497535
class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):

Programs/_testembed.c

Lines changed: 173 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2125,6 +2125,177 @@ static int test_repeated_init_and_inittab(void)
21252125
return 0;
21262126
}
21272127

2128+
/// Multi-phase initialization package & submodule ///
2129+
2130+
int
2131+
mp_pkg_exec(PyObject *mod)
2132+
{
2133+
// make this a namespace package
2134+
// empty list = namespace package
2135+
if (PyModule_Add(mod, "__path__", PyList_New(0)) < 0) {
2136+
return -1;
2137+
}
2138+
if (PyModule_AddStringConstant(mod, "mp_pkg_exec_slot_ran", "yes") < 0) {
2139+
return -1;
2140+
}
2141+
return 0;
2142+
}
2143+
2144+
static PyModuleDef_Slot mp_pkg_slots[] = {
2145+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
2146+
{Py_mod_exec, mp_pkg_exec},
2147+
{0, NULL}
2148+
};
2149+
2150+
static struct PyModuleDef mp_pkg_def = {
2151+
PyModuleDef_HEAD_INIT,
2152+
.m_name = "mp_pkg",
2153+
.m_size = 0,
2154+
.m_slots = mp_pkg_slots,
2155+
};
2156+
2157+
PyMODINIT_FUNC
2158+
PyInit_mp_pkg(void)
2159+
{
2160+
return PyModuleDef_Init(&mp_pkg_def);
2161+
}
2162+
2163+
static PyObject *
2164+
submod_greet(PyObject *self, PyObject *Py_UNUSED(ignored))
2165+
{
2166+
return PyUnicode_FromString("Hello from sub-module");
2167+
}
2168+
2169+
static PyMethodDef submod_methods[] = {
2170+
{"greet", submod_greet, METH_NOARGS, NULL},
2171+
{NULL},
2172+
};
2173+
2174+
int
2175+
mp_submod_exec(PyObject *mod)
2176+
{
2177+
return PyModule_AddStringConstant(mod, "mp_submod_exec_slot_ran", "yes");
2178+
}
2179+
2180+
static PyModuleDef_Slot mp_submod_slots[] = {
2181+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
2182+
{Py_mod_exec, mp_submod_exec},
2183+
{0, NULL}
2184+
};
2185+
2186+
static struct PyModuleDef mp_submod_def = {
2187+
PyModuleDef_HEAD_INIT,
2188+
.m_name = "mp_pkg.mp_submod",
2189+
.m_size = 0,
2190+
.m_methods = submod_methods,
2191+
.m_slots = mp_submod_slots,
2192+
};
2193+
2194+
PyMODINIT_FUNC
2195+
PyInit_mp_submod(void)
2196+
{
2197+
return PyModuleDef_Init(&mp_submod_def);
2198+
}
2199+
2200+
static int
2201+
test_inittab_submodule_multiphase(void)
2202+
{
2203+
wchar_t* argv[] = {
2204+
PROGRAM_NAME,
2205+
L"-c",
2206+
L"import sys;"
2207+
L"import mp_pkg.mp_submod;"
2208+
L"print(mp_pkg.mp_submod);"
2209+
L"print(sys.modules['mp_pkg.mp_submod']);"
2210+
L"print(mp_pkg.mp_submod.greet());"
2211+
L"print(f'{mp_pkg.mp_submod.mp_submod_exec_slot_ran=}');"
2212+
L"print(f'{mp_pkg.mp_pkg_exec_slot_ran=}');"
2213+
};
2214+
PyConfig config;
2215+
if (PyImport_AppendInittab("mp_pkg",
2216+
&PyInit_mp_pkg) != 0) {
2217+
fprintf(stderr, "PyImport_AppendInittab() failed\n");
2218+
return 1;
2219+
}
2220+
if (PyImport_AppendInittab("mp_pkg.mp_submod",
2221+
&PyInit_mp_submod) != 0) {
2222+
fprintf(stderr, "PyImport_AppendInittab() failed\n");
2223+
return 1;
2224+
}
2225+
PyConfig_InitPythonConfig(&config);
2226+
config.isolated = 1;
2227+
config_set_argv(&config, Py_ARRAY_LENGTH(argv), argv);
2228+
init_from_config_clear(&config);
2229+
return Py_RunMain();
2230+
}
2231+
2232+
/// Single-phase initialization package & submodule ///
2233+
2234+
static struct PyModuleDef sp_pkg_def = {
2235+
PyModuleDef_HEAD_INIT,
2236+
.m_name = "sp_pkg",
2237+
.m_size = 0,
2238+
};
2239+
2240+
PyMODINIT_FUNC
2241+
PyInit_sp_pkg(void)
2242+
{
2243+
PyObject *mod = PyModule_Create(&sp_pkg_def);
2244+
if (mod == NULL) {
2245+
return NULL;
2246+
}
2247+
// make this a namespace package
2248+
// empty list = namespace package
2249+
if (PyModule_Add(mod, "__path__", PyList_New(0)) < 0) {
2250+
Py_DECREF(mod);
2251+
return NULL;
2252+
}
2253+
return mod;
2254+
}
2255+
2256+
static struct PyModuleDef sp_submod_def = {
2257+
PyModuleDef_HEAD_INIT,
2258+
.m_name = "sp_pkg.sp_submod",
2259+
.m_size = 0,
2260+
.m_methods = submod_methods,
2261+
};
2262+
2263+
PyMODINIT_FUNC
2264+
PyInit_sp_submod(void)
2265+
{
2266+
return PyModule_Create(&sp_submod_def);
2267+
}
2268+
2269+
static int
2270+
test_inittab_submodule_singlephase(void)
2271+
{
2272+
wchar_t* argv[] = {
2273+
PROGRAM_NAME,
2274+
L"-c",
2275+
L"import sys;"
2276+
L"import sp_pkg.sp_submod;"
2277+
L"print(sp_pkg.sp_submod);"
2278+
L"print(sys.modules['sp_pkg.sp_submod']);"
2279+
L"print(sp_pkg.sp_submod.greet());"
2280+
};
2281+
PyConfig config;
2282+
if (PyImport_AppendInittab("sp_pkg",
2283+
&PyInit_sp_pkg) != 0) {
2284+
fprintf(stderr, "PyImport_AppendInittab() failed\n");
2285+
return 1;
2286+
}
2287+
if (PyImport_AppendInittab("sp_pkg.sp_submod",
2288+
&PyInit_sp_submod) != 0) {
2289+
fprintf(stderr, "PyImport_AppendInittab() failed\n");
2290+
return 1;
2291+
}
2292+
PyConfig_InitPythonConfig(&config);
2293+
config.isolated = 1;
2294+
config_set_argv(&config, Py_ARRAY_LENGTH(argv), argv);
2295+
init_from_config_clear(&config);
2296+
return Py_RunMain();
2297+
}
2298+
21282299
static void wrap_allocator(PyMemAllocatorEx *allocator);
21292300
static void unwrap_allocator(PyMemAllocatorEx *allocator);
21302301

@@ -2280,7 +2451,8 @@ static struct TestCase TestCases[] = {
22802451
{"test_frozenmain", test_frozenmain},
22812452
#endif
22822453
{"test_get_incomplete_frame", test_get_incomplete_frame},
2283-
2454+
{"test_inittab_submodule_multiphase", test_inittab_submodule_multiphase},
2455+
{"test_inittab_submodule_singlephase", test_inittab_submodule_singlephase},
22842456
{NULL, NULL}
22852457
};
22862458

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)