cpython/Python/importdl.c
Eric Snow 44f57a952e
gh-117953: Split Up _PyImport_LoadDynamicModuleWithSpec() (gh-118203)
Basically, I've turned most of _PyImport_LoadDynamicModuleWithSpec() into two new functions (_PyImport_GetModInitFunc() and _PyImport_RunModInitFunc()) and moved the rest of it out into _imp_create_dynamic_impl().  There shouldn't be any changes in behavior.

This change makes some future changes simpler.  This is particularly relevant to potentially calling each module init function in the main interpreter first.  Thus the critical part of the PR is the addition of _PyImport_RunModInitFunc(), which is strictly focused on running the init func and validating the result.  A later PR will take it a step farther by capturing error information rather than raising exceptions.

FWIW, this change also helps readers by clarifying a bit more about what happens when an extension/builtin module is imported.
2024-04-29 09:29:07 -06:00

310 lines
9.1 KiB
C

/* Support for dynamic loading of extension modules */
#include "Python.h"
#include "pycore_call.h"
#include "pycore_import.h"
#include "pycore_pyerrors.h" // _PyErr_FormatFromCause()
#include "pycore_pystate.h"
#include "pycore_runtime.h"
/* ./configure sets HAVE_DYNAMIC_LOADING if dynamic loading of modules is
supported on this platform. configure will then compile and link in one
of the dynload_*.c files, as appropriate. We will call a function in
those modules to get a function pointer to the module's init function.
*/
#ifdef HAVE_DYNAMIC_LOADING
#include "pycore_importdl.h"
#ifdef MS_WINDOWS
extern dl_funcptr _PyImport_FindSharedFuncptrWindows(const char *prefix,
const char *shortname,
PyObject *pathname,
FILE *fp);
#else
extern dl_funcptr _PyImport_FindSharedFuncptr(const char *prefix,
const char *shortname,
const char *pathname, FILE *fp);
#endif
static const char * const ascii_only_prefix = "PyInit";
static const char * const nonascii_prefix = "PyInitU";
/* Get the variable part of a module's export symbol name.
* Returns a bytes instance. For non-ASCII-named modules, the name is
* encoded as per PEP 489.
* The hook_prefix pointer is set to either ascii_only_prefix or
* nonascii_prefix, as appropriate.
*/
static PyObject *
get_encoded_name(PyObject *name, const char **hook_prefix) {
PyObject *tmp;
PyObject *encoded = NULL;
PyObject *modname = NULL;
Py_ssize_t name_len, lastdot;
/* Get the short name (substring after last dot) */
name_len = PyUnicode_GetLength(name);
if (name_len < 0) {
return NULL;
}
lastdot = PyUnicode_FindChar(name, '.', 0, name_len, -1);
if (lastdot < -1) {
return NULL;
} else if (lastdot >= 0) {
tmp = PyUnicode_Substring(name, lastdot + 1, name_len);
if (tmp == NULL)
return NULL;
name = tmp;
/* "name" now holds a new reference to the substring */
} else {
Py_INCREF(name);
}
/* Encode to ASCII or Punycode, as needed */
encoded = PyUnicode_AsEncodedString(name, "ascii", NULL);
if (encoded != NULL) {
*hook_prefix = ascii_only_prefix;
} else {
if (PyErr_ExceptionMatches(PyExc_UnicodeEncodeError)) {
PyErr_Clear();
encoded = PyUnicode_AsEncodedString(name, "punycode", NULL);
if (encoded == NULL) {
goto error;
}
*hook_prefix = nonascii_prefix;
} else {
goto error;
}
}
/* Replace '-' by '_' */
modname = _PyObject_CallMethod(encoded, &_Py_ID(replace), "cc", '-', '_');
if (modname == NULL)
goto error;
Py_DECREF(name);
Py_DECREF(encoded);
return modname;
error:
Py_DECREF(name);
Py_XDECREF(encoded);
return NULL;
}
void
_Py_ext_module_loader_info_clear(struct _Py_ext_module_loader_info *info)
{
Py_CLEAR(info->filename);
#ifndef MS_WINDOWS
Py_CLEAR(info->filename_encoded);
#endif
Py_CLEAR(info->name);
Py_CLEAR(info->name_encoded);
}
int
_Py_ext_module_loader_info_init(struct _Py_ext_module_loader_info *p_info,
PyObject *name, PyObject *filename)
{
struct _Py_ext_module_loader_info info = {0};
assert(name != NULL);
if (!PyUnicode_Check(name)) {
PyErr_SetString(PyExc_TypeError,
"module name must be a string");
_Py_ext_module_loader_info_clear(&info);
return -1;
}
info.name = Py_NewRef(name);
info.name_encoded = get_encoded_name(info.name, &info.hook_prefix);
if (info.name_encoded == NULL) {
_Py_ext_module_loader_info_clear(&info);
return -1;
}
info.newcontext = PyUnicode_AsUTF8(info.name);
if (info.newcontext == NULL) {
_Py_ext_module_loader_info_clear(&info);
return -1;
}
if (filename != NULL) {
if (!PyUnicode_Check(filename)) {
PyErr_SetString(PyExc_TypeError,
"module filename must be a string");
_Py_ext_module_loader_info_clear(&info);
return -1;
}
info.filename = Py_NewRef(filename);
#ifndef MS_WINDOWS
info.filename_encoded = PyUnicode_EncodeFSDefault(info.filename);
if (info.filename_encoded == NULL) {
_Py_ext_module_loader_info_clear(&info);
return -1;
}
#endif
info.path = info.filename;
}
else {
info.path = info.name;
}
*p_info = info;
return 0;
}
int
_Py_ext_module_loader_info_init_from_spec(
struct _Py_ext_module_loader_info *p_info,
PyObject *spec)
{
PyObject *name = PyObject_GetAttrString(spec, "name");
if (name == NULL) {
return -1;
}
PyObject *filename = PyObject_GetAttrString(spec, "origin");
if (filename == NULL) {
Py_DECREF(name);
return -1;
}
int err = _Py_ext_module_loader_info_init(p_info, name, filename);
Py_DECREF(name);
Py_DECREF(filename);
return err;
}
PyModInitFunction
_PyImport_GetModInitFunc(struct _Py_ext_module_loader_info *info,
FILE *fp)
{
const char *name_buf = PyBytes_AS_STRING(info->name_encoded);
dl_funcptr exportfunc;
#ifdef MS_WINDOWS
exportfunc = _PyImport_FindSharedFuncptrWindows(
info->hook_prefix, name_buf, info->filename, fp);
#else
{
const char *path_buf = PyBytes_AS_STRING(info->filename_encoded);
exportfunc = _PyImport_FindSharedFuncptr(
info->hook_prefix, name_buf, path_buf, fp);
}
#endif
if (exportfunc == NULL) {
if (!PyErr_Occurred()) {
PyObject *msg;
msg = PyUnicode_FromFormat(
"dynamic module does not define "
"module export function (%s_%s)",
info->hook_prefix, name_buf);
if (msg != NULL) {
PyErr_SetImportError(msg, info->name, info->filename);
Py_DECREF(msg);
}
}
return NULL;
}
return (PyModInitFunction)exportfunc;
}
int
_PyImport_RunModInitFunc(PyModInitFunction p0,
struct _Py_ext_module_loader_info *info,
struct _Py_ext_module_loader_result *p_res)
{
struct _Py_ext_module_loader_result res = {0};
const char *name_buf = PyBytes_AS_STRING(info->name_encoded);
/* Call the module init function. */
/* Package context is needed for single-phase init */
const char *oldcontext = _PyImport_SwapPackageContext(info->newcontext);
PyObject *m = p0();
_PyImport_SwapPackageContext(oldcontext);
/* Validate the result (and populate "res". */
if (m == NULL) {
if (!PyErr_Occurred()) {
PyErr_Format(
PyExc_SystemError,
"initialization of %s failed without raising an exception",
name_buf);
}
goto error;
} else if (PyErr_Occurred()) {
_PyErr_FormatFromCause(
PyExc_SystemError,
"initialization of %s raised unreported exception",
name_buf);
/* We would probably be correct to decref m here,
* but we weren't doing so before,
* so we stick with doing nothing. */
m = NULL;
goto error;
}
if (Py_IS_TYPE(m, NULL)) {
/* This can happen when a PyModuleDef is returned without calling
* PyModuleDef_Init on it
*/
PyErr_Format(PyExc_SystemError,
"init function of %s returned uninitialized object",
name_buf);
/* Likewise, decref'ing here makes sense. However, the original
* code has a note about "prevent segfault in DECREF",
* so we play it safe and leave it alone. */
m = NULL; /* prevent segfault in DECREF */
goto error;
}
if (PyObject_TypeCheck(m, &PyModuleDef_Type)) {
/* multi-phase init */
res.def = (PyModuleDef *)m;
/* Run PyModule_FromDefAndSpec() to finish loading the module. */
}
else if (info->hook_prefix == nonascii_prefix) {
/* It should have been multi-phase init? */
/* Don't allow legacy init for non-ASCII module names. */
PyErr_Format(
PyExc_SystemError,
"initialization of %s did not return PyModuleDef",
name_buf);
Py_DECREF(m);
return -1;
}
else {
/* single-phase init (legacy) */
res.module = m;
res.def = PyModule_GetDef(m);
if (res.def == NULL) {
PyErr_Clear();
PyErr_Format(PyExc_SystemError,
"initialization of %s did not return an extension "
"module", name_buf);
goto error;
}
}
assert(!PyErr_Occurred());
*p_res = res;
return 0;
error:
assert(PyErr_Occurred());
Py_CLEAR(res.module);
res.def = NULL;
*p_res = res;
return -1;
}
#endif /* HAVE_DYNAMIC_LOADING */