Skip to content

Commit 18c935f

Browse files
encukouitamaro
andauthored
gh-157384: Unify _Py_ext_module_loader_info initialization (GH-157758)
- Unify `_Py_ext_module_loader_info_init*` functions. The “core” one was entirely unused, and the difference between “builtin” and “extension” can be expressed in arguments. - Consolidate getting & checking the module name from spec. (This improves error message wording for `imp.create_builtin` with empty name.) - Allow non-ASCII names. Core modules (`sys` and `builtins`) still don't go through the routine. Co-authored-by: Itamar Oren <itamarost@gmail.com>
1 parent e548b47 commit 18c935f

11 files changed

Lines changed: 164 additions & 178 deletions

‎Include/internal/pycore_global_objects_fini_generated.h‎

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Include/internal/pycore_global_strings.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ struct _Py_global_strings {
262262
STRUCT_FOR_ID(_asyncio_future_blocking)
263263
STRUCT_FOR_ID(_blksize)
264264
STRUCT_FOR_ID(_bootstrap)
265+
STRUCT_FOR_ID(_bootstrap_external)
265266
STRUCT_FOR_ID(_check_retval_)
266267
STRUCT_FOR_ID(_dealloc_warn)
267268
STRUCT_FOR_ID(_feature_version)

‎Include/internal/pycore_importdl.h‎

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,9 @@ typedef enum ext_module_kind {
5151
} _Py_ext_module_kind;
5252

5353
typedef enum ext_module_origin {
54-
_Py_ext_module_origin_CORE = 1,
55-
_Py_ext_module_origin_BUILTIN = 2,
56-
_Py_ext_module_origin_DYNAMIC = 3,
54+
_Py_ext_module_origin_CORE = 1, // 'sys' & 'builtins'
55+
_Py_ext_module_origin_BUILTIN = 2, // inittab or PyImport_CreateModuleFromInitfunc
56+
_Py_ext_module_origin_DYNAMIC = 3, // loaded dynamically
5757
} _Py_ext_module_origin;
5858

5959
struct hook_prefixes {
@@ -79,21 +79,10 @@ struct _Py_ext_module_loader_info {
7979
extern void _Py_ext_module_loader_info_clear(
8080
struct _Py_ext_module_loader_info *info);
8181
extern int _Py_ext_module_loader_info_init(
82-
struct _Py_ext_module_loader_info *info,
82+
struct _Py_ext_module_loader_info *p_info,
8383
PyObject *name,
84-
PyObject *filename,
84+
PyObject *spec,
8585
_Py_ext_module_origin origin);
86-
extern int _Py_ext_module_loader_info_init_for_core(
87-
struct _Py_ext_module_loader_info *p_info,
88-
PyObject *name);
89-
extern int _Py_ext_module_loader_info_init_for_builtin(
90-
struct _Py_ext_module_loader_info *p_info,
91-
PyObject *name);
92-
#ifdef HAVE_DYNAMIC_LOADING
93-
extern int _Py_ext_module_loader_info_init_from_spec(
94-
struct _Py_ext_module_loader_info *info,
95-
PyObject *spec);
96-
#endif
9786

9887
/* The result from running an extension module's init function.
9988
* Not used for modules defined via PyModExport (slots array).

‎Include/internal/pycore_runtime_init_generated.h‎

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Include/internal/pycore_unicodeobject_generated.h‎

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Lib/test/test_embed.py‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,12 @@ def test_create_module_from_initfunc(self):
293293
"sp_pkg.sp_submod.__name__='sp_pkg.sp_submod'\n"
294294
"sys.modules[\"sp_pkg.sp_submod\"] is sp_pkg.sp_submod=True\n"
295295
"\"sp_submod\" in sys.modules=False\n"
296+
# Module with non-ASCII name. (The SystemError shows the internal
297+
# punycode-encoded name: not ideal, but also not horrible.)
298+
"created nonascii_mp_\N{SNAKE}: in sys.modules=False\n"
299+
"ascii(mp.__name__)=\"'nonascii_mp_\\\\U0001f40d'\" mp.executed='yes'\n"
300+
"SystemError: 'initialization of nonascii_sp__rf17k "
301+
"did not return PyModuleDef'\n"
296302
)
297303

298304
def test_inittab_submodule_multiphase(self):

‎Lib/test/test_import/__init__.py‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1264,6 +1264,13 @@ class Spec2:
12641264
origin = "a\x00b"
12651265
_imp.create_dynamic(Spec2())
12661266

1267+
1268+
def test_create_dynamic_no_origin(self):
1269+
with self.assertRaisesRegex(AttributeError, "no attribute 'origin'"):
1270+
class Spec:
1271+
name = "abc"
1272+
_imp.create_dynamic(Spec())
1273+
12671274
def test_create_builtin(self):
12681275
class Spec:
12691276
pass
@@ -1273,7 +1280,7 @@ class Spec:
12731280
self.assertIs(_imp.create_builtin(spec), sys)
12741281

12751282
spec.name = None
1276-
with self.assertRaisesRegex(TypeError, 'name must be string, not NoneType'):
1283+
with self.assertRaisesRegex(TypeError, 'module name must be a string, not NoneType'):
12771284
_imp.create_builtin(spec)
12781285

12791286
# gh-142029
@@ -1283,7 +1290,7 @@ class Spec:
12831290

12841291
# gh-142029
12851292
spec.name = ""
1286-
with self.assertRaisesRegex(ValueError, 'name must not be empty'):
1293+
with self.assertRaisesRegex(ValueError, 'module name must not be empty'):
12871294
_imp.create_builtin(spec)
12881295

12891296
def test_filter_syntax_warnings_by_module(self):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:c:func:`PyImport_CreateModuleFromInitfunc` now accepts non-ASCII module
2+
names for multi-phase init modules, instead of raising
3+
:exc:`UnicodeEncodeError`.

‎Programs/_testembed.c‎

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2460,6 +2460,40 @@ PyInit_cmfi_sp_pkg(void)
24602460
return PyModuleDef_Init(&cmfi_sp_pkg_def);
24612461
}
24622462

2463+
2464+
// Modules with non-ASCII names: multi-phase init is supported,
2465+
// single-phase init is not.
2466+
static PyModuleDef_Slot cmfi_nonascii_mp_slots[] = {
2467+
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
2468+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
2469+
{0, NULL},
2470+
};
2471+
2472+
static PyModuleDef cmfi_nonascii_mp_def = {
2473+
PyModuleDef_HEAD_INIT,
2474+
.m_name = "nonascii_mp_\xf0\x9f\x90\x8d",
2475+
.m_size = 0,
2476+
.m_slots = cmfi_nonascii_mp_slots,
2477+
};
2478+
2479+
static PyObject*
2480+
PyInit_cmfi_nonascii_mp(void)
2481+
{
2482+
return PyModuleDef_Init(&cmfi_nonascii_mp_def);
2483+
}
2484+
2485+
static PyModuleDef cmfi_nonascii_sp_def = {
2486+
PyModuleDef_HEAD_INIT,
2487+
.m_name = "nonascii_sp_\xf0\x9f\x90\x8d",
2488+
.m_size = -1,
2489+
};
2490+
2491+
static PyObject*
2492+
PyInit_cmfi_nonascii_sp(void)
2493+
{
2494+
return PyModule_Create(&cmfi_nonascii_sp_def);
2495+
}
2496+
24632497
static PyObject*
24642498
create_module(PyObject* self, PyObject* spec)
24652499
{
@@ -2487,6 +2521,14 @@ create_module(PyObject* self, PyObject* spec)
24872521
Py_DECREF(name);
24882522
return PyImport_CreateModuleFromInitfunc(spec, PyInit_cmfi_sp_submod);
24892523
}
2524+
if (PyUnicode_EqualToUTF8(name, "nonascii_mp_\xf0\x9f\x90\x8d")) {
2525+
Py_DECREF(name);
2526+
return PyImport_CreateModuleFromInitfunc(spec, PyInit_cmfi_nonascii_mp);
2527+
}
2528+
if (PyUnicode_EqualToUTF8(name, "nonascii_sp_\xf0\x9f\x90\x8d")) {
2529+
Py_DECREF(name);
2530+
return PyImport_CreateModuleFromInitfunc(spec, PyInit_cmfi_nonascii_sp);
2531+
}
24902532
PyErr_Format(PyExc_LookupError, "static module %R not found", name);
24912533
Py_DECREF(name);
24922534
return NULL;
@@ -2548,6 +2590,11 @@ test_create_module_from_initfunc(void)
25482590
L"print(f'{sp_pkg.sp_submod.__name__=}');"
25492591
L"print(f'{sys.modules[\"sp_pkg.sp_submod\"] is sp_pkg.sp_submod=}');"
25502592
L"print(f'{\"sp_submod\" in sys.modules=}');"
2593+
// Non-ASCII names: multi-phase init works, single-phase init doesn't
2594+
L"import importlib;"
2595+
L"mp = importlib.import_module('nonascii_mp_\\N{SNAKE}');"
2596+
L"print(f'{ascii(mp.__name__)=} {mp.executed=}');"
2597+
L"try_import('nonascii_sp_\\N{SNAKE}');"
25512598
};
25522599
PyConfig config;
25532600
if (PyImport_AppendInittab("create_static_module",
@@ -2568,7 +2615,10 @@ test_create_module_from_initfunc(void)
25682615
" @classmethod\n"
25692616
" def find_spec(cls, fullname, path, target=None):\n"
25702617
" if fullname in {'my_test_extension', 'embedded_ext',\n"
2571-
" 'sp_pkg', 'sp_pkg.sp_submod'}:\n"
2618+
" 'sp_pkg', 'sp_pkg.sp_submod',\n"
2619+
" 'nonascii_mp_\\N{SNAKE}',\n"
2620+
" 'nonascii_sp_\\N{SNAKE}',\n"
2621+
" }:\n"
25722622
" return spec_from_loader(fullname, cls, origin=cls._ORIGIN,\n"
25732623
" is_package=(fullname == 'sp_pkg'))\n"
25742624
" return None\n"
@@ -2583,6 +2633,13 @@ test_create_module_from_initfunc(void)
25832633
" create_static_module.exec_module(module)\n"
25842634
" module.executed = 'yes'\n"
25852635
"sys.meta_path.append(StaticExtensionImporter)\n"
2636+
"def try_import(name):\n"
2637+
" try:\n"
2638+
" importlib.import_module(name)\n"
2639+
" except SystemError as exc:\n"
2640+
" print(f'SystemError: {ascii(str(exc))}')\n"
2641+
" else:\n"
2642+
" print(f'no SystemError for {ascii(name)}!')\n"
25862643
);
25872644
if (result < 0) {
25882645
fprintf(stderr, "PyRun_SimpleString() failed\n");

‎Python/import.c‎

Lines changed: 17 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -2507,27 +2507,16 @@ lookup_inittab_entry(const struct _Py_ext_module_loader_info* info)
25072507

25082508
static PyObject*
25092509
create_builtin(
2510-
PyThreadState *tstate, PyObject *name,
2510+
PyThreadState *tstate,
2511+
PyObject *name, /* if NULL, get from spec */
25112512
PyObject *spec,
25122513
PyModInitFunction initfunc)
25132514
{
25142515
struct _Py_ext_module_loader_info info;
2515-
if (_Py_ext_module_loader_info_init_for_builtin(&info, name) < 0) {
2516+
_Py_ext_module_origin origin = _Py_ext_module_origin_BUILTIN;
2517+
if (_Py_ext_module_loader_info_init(&info, name, spec, origin) < 0) {
25162518
return NULL;
25172519
}
2518-
if (initfunc != NULL) {
2519-
/* An explicitly provided init function (see
2520-
* PyImport_CreateModuleFromInitfunc()) may belong to a submodule
2521-
* whose single-phase init creates the module using only the last
2522-
* component of the name (as e.g. pybind11 does). Set the package
2523-
* context so that PyModule_Create() resolves the full name, the
2524-
* same as for dynamically loaded extensions. */
2525-
info.newcontext = PyUnicode_AsUTF8(info.name);
2526-
if (info.newcontext == NULL) {
2527-
_Py_ext_module_loader_info_clear(&info);
2528-
return NULL;
2529-
}
2530-
}
25312520

25322521
struct extensions_cache_value *cached = NULL;
25332522
PyObject *mod = import_find_extension(tstate, &info, &cached);
@@ -2558,7 +2547,7 @@ create_builtin(
25582547
struct _inittab *entry = lookup_inittab_entry(&info);
25592548
if (entry == NULL) {
25602549
mod = NULL;
2561-
_PyErr_SetModuleNotFoundError(name);
2550+
_PyErr_SetModuleNotFoundError(info.name);
25622551
goto finally;
25632552
}
25642553

@@ -2610,20 +2599,7 @@ PyImport_CreateModuleFromInitfunc(
26102599

26112600
PyThreadState *tstate = _PyThreadState_GET();
26122601

2613-
PyObject *name = PyObject_GetAttr(spec, &_Py_ID(name));
2614-
if (name == NULL) {
2615-
return NULL;
2616-
}
2617-
2618-
if (!PyUnicode_Check(name)) {
2619-
PyErr_Format(PyExc_TypeError,
2620-
"spec name must be string, not %T", name);
2621-
Py_DECREF(name);
2622-
return NULL;
2623-
}
2624-
2625-
PyObject *mod = create_builtin(tstate, name, spec, initfunc);
2626-
Py_DECREF(name);
2602+
PyObject *mod = create_builtin(tstate, NULL, spec, initfunc);
26272603
return mod;
26282604
}
26292605

@@ -2835,8 +2811,8 @@ PyImport_ExecCodeModuleWithPathnames(const char *name, PyObject *co,
28352811
Py_FatalError("no current interpreter");
28362812
}
28372813

2838-
external= PyObject_GetAttrString(IMPORTLIB(interp),
2839-
"_bootstrap_external");
2814+
external= PyObject_GetAttr(IMPORTLIB(interp),
2815+
&_Py_ID(_bootstrap_external));
28402816
if (external != NULL) {
28412817
pathobj = PyObject_CallMethodOneArg(
28422818
external, &_Py_ID(_get_sourcefile), cpathobj);
@@ -2920,8 +2896,8 @@ PyImport_ExecCodeModuleObject(PyObject *name, PyObject *co, PyObject *pathname,
29202896
if (pathname == NULL) {
29212897
pathname = ((PyCodeObject *)co)->co_filename;
29222898
}
2923-
external = PyObject_GetAttrString(IMPORTLIB(tstate->interp),
2924-
"_bootstrap_external");
2899+
external = PyObject_GetAttr(IMPORTLIB(tstate->interp),
2900+
&_Py_ID(_bootstrap_external));
29252901
if (external == NULL) {
29262902
Py_DECREF(d);
29272903
return NULL;
@@ -3499,8 +3475,8 @@ PyObject *
34993475
_PyImport_GetImportlibExternalLoader(PyInterpreterState *interp,
35003476
const char *loader_name)
35013477
{
3502-
PyObject *bootstrap = PyObject_GetAttrString(IMPORTLIB(interp),
3503-
"_bootstrap_external");
3478+
PyObject *bootstrap = PyObject_GetAttr(IMPORTLIB(interp),
3479+
&_Py_ID(_bootstrap_external));
35043480
if (bootstrap == NULL) {
35053481
return NULL;
35063482
}
@@ -3513,8 +3489,8 @@ _PyImport_GetImportlibExternalLoader(PyInterpreterState *interp,
35133489
PyObject *
35143490
_PyImport_BlessMyLoader(PyInterpreterState *interp, PyObject *module_globals)
35153491
{
3516-
PyObject *external = PyObject_GetAttrString(IMPORTLIB(interp),
3517-
"_bootstrap_external");
3492+
PyObject *external = PyObject_GetAttr(IMPORTLIB(interp),
3493+
&_Py_ID(_bootstrap_external));
35183494
if (external == NULL) {
35193495
return NULL;
35203496
}
@@ -5085,27 +5061,7 @@ _imp_create_builtin(PyObject *module, PyObject *spec)
50855061
{
50865062
PyThreadState *tstate = _PyThreadState_GET();
50875063

5088-
PyObject *name = PyObject_GetAttrString(spec, "name");
5089-
if (name == NULL) {
5090-
return NULL;
5091-
}
5092-
5093-
if (!PyUnicode_Check(name)) {
5094-
PyErr_Format(PyExc_TypeError,
5095-
"name must be string, not %.200s",
5096-
Py_TYPE(name)->tp_name);
5097-
Py_DECREF(name);
5098-
return NULL;
5099-
}
5100-
5101-
if (PyUnicode_GetLength(name) == 0) {
5102-
PyErr_Format(PyExc_ValueError, "name must not be empty");
5103-
Py_DECREF(name);
5104-
return NULL;
5105-
}
5106-
5107-
PyObject *mod = create_builtin(tstate, name, spec, NULL);
5108-
Py_DECREF(name);
5064+
PyObject *mod = create_builtin(tstate, NULL, spec, NULL);
51095065
return mod;
51105066
}
51115067

@@ -5437,7 +5393,8 @@ _imp_create_dynamic_impl(PyObject *module, PyObject *spec, PyObject *file)
54375393
PyThreadState *tstate = _PyThreadState_GET();
54385394

54395395
struct _Py_ext_module_loader_info info;
5440-
if (_Py_ext_module_loader_info_init_from_spec(&info, spec) < 0) {
5396+
_Py_ext_module_origin origin = _Py_ext_module_origin_DYNAMIC;
5397+
if (_Py_ext_module_loader_info_init(&info, NULL, spec, origin) < 0) {
54415398
return NULL;
54425399
}
54435400

0 commit comments

Comments
 (0)