Coverage Report

Created: 2025-11-24 06:11

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/cpython/Python/importdl.c
Line
Count
Source
1
2
/* Support for dynamic loading of extension modules */
3
4
#include "Python.h"
5
#include "pycore_call.h"          // _PyObject_CallMethod()
6
#include "pycore_import.h"        // _PyImport_SwapPackageContext()
7
#include "pycore_importdl.h"
8
#include "pycore_moduleobject.h"  // _PyModule_GetDefOrNull()
9
#include "pycore_pyerrors.h"      // _PyErr_FormatFromCause()
10
#include "pycore_runtime.h"       // _Py_ID()
11
12
13
/***********************************/
14
/* module info to use when loading */
15
/***********************************/
16
17
static const struct hook_prefixes ascii_only_prefixes = {
18
    "PyInit", "PyModExport"};
19
static const struct hook_prefixes nonascii_prefixes = {
20
    "PyInitU", "PyModExportU"};
21
22
/* Get the variable part of a module's export symbol name.
23
 * Returns a bytes instance. For non-ASCII-named modules, the name is
24
 * encoded as per PEP 489.
25
 * The hook_prefix pointer is set to either ascii_only_prefix or
26
 * nonascii_prefix, as appropriate.
27
 */
28
static PyObject *
29
124
get_encoded_name(PyObject *name, const struct hook_prefixes **hook_prefixes) {
30
124
    PyObject *tmp;
31
124
    PyObject *encoded = NULL;
32
124
    PyObject *modname = NULL;
33
124
    Py_ssize_t name_len, lastdot;
34
35
    /* Get the short name (substring after last dot) */
36
124
    name_len = PyUnicode_GetLength(name);
37
124
    if (name_len < 0) {
38
0
        return NULL;
39
0
    }
40
124
    lastdot = PyUnicode_FindChar(name, '.', 0, name_len, -1);
41
124
    if (lastdot < -1) {
42
0
        return NULL;
43
124
    } else if (lastdot >= 0) {
44
0
        tmp = PyUnicode_Substring(name, lastdot + 1, name_len);
45
0
        if (tmp == NULL)
46
0
            return NULL;
47
0
        name = tmp;
48
        /* "name" now holds a new reference to the substring */
49
124
    } else {
50
124
        Py_INCREF(name);
51
124
    }
52
53
    /* Encode to ASCII or Punycode, as needed */
54
124
    encoded = PyUnicode_AsEncodedString(name, "ascii", NULL);
55
124
    if (encoded != NULL) {
56
124
        *hook_prefixes = &ascii_only_prefixes;
57
124
    } else {
58
0
        if (PyErr_ExceptionMatches(PyExc_UnicodeEncodeError)) {
59
0
            PyErr_Clear();
60
0
            encoded = PyUnicode_AsEncodedString(name, "punycode", NULL);
61
0
            if (encoded == NULL) {
62
0
                goto error;
63
0
            }
64
0
            *hook_prefixes = &nonascii_prefixes;
65
0
        } else {
66
0
            goto error;
67
0
        }
68
0
    }
69
70
    /* Replace '-' by '_' */
71
124
    modname = _PyObject_CallMethod(encoded, &_Py_ID(replace), "cc", '-', '_');
72
124
    if (modname == NULL)
73
0
        goto error;
74
75
124
    Py_DECREF(name);
76
124
    Py_DECREF(encoded);
77
124
    return modname;
78
0
error:
79
0
    Py_DECREF(name);
80
0
    Py_XDECREF(encoded);
81
0
    return NULL;
82
124
}
83
84
void
85
_Py_ext_module_loader_info_clear(struct _Py_ext_module_loader_info *info)
86
683
{
87
683
    Py_CLEAR(info->filename);
88
683
#ifndef MS_WINDOWS
89
683
    Py_CLEAR(info->filename_encoded);
90
683
#endif
91
683
    Py_CLEAR(info->name);
92
683
    Py_CLEAR(info->name_encoded);
93
683
}
94
95
int
96
_Py_ext_module_loader_info_init(struct _Py_ext_module_loader_info *p_info,
97
                                PyObject *name, PyObject *filename,
98
                                _Py_ext_module_origin origin)
99
124
{
100
124
    struct _Py_ext_module_loader_info info = {
101
124
        .origin=origin,
102
124
    };
103
104
124
    assert(name != NULL);
105
124
    if (!PyUnicode_Check(name)) {
106
0
        PyErr_SetString(PyExc_TypeError,
107
0
                        "module name must be a string");
108
0
        _Py_ext_module_loader_info_clear(&info);
109
0
        return -1;
110
0
    }
111
124
    assert(PyUnicode_GetLength(name) > 0);
112
124
    info.name = Py_NewRef(name);
113
114
124
    info.name_encoded = get_encoded_name(info.name, &info.hook_prefixes);
115
124
    if (info.name_encoded == NULL) {
116
0
        _Py_ext_module_loader_info_clear(&info);
117
0
        return -1;
118
0
    }
119
120
124
    info.newcontext = PyUnicode_AsUTF8(info.name);
121
124
    if (info.newcontext == NULL) {
122
0
        _Py_ext_module_loader_info_clear(&info);
123
0
        return -1;
124
0
    }
125
126
124
    if (filename != NULL) {
127
124
        if (!PyUnicode_Check(filename)) {
128
0
            PyErr_SetString(PyExc_TypeError,
129
0
                            "module filename must be a string");
130
0
            _Py_ext_module_loader_info_clear(&info);
131
0
            return -1;
132
0
        }
133
124
        info.filename = Py_NewRef(filename);
134
135
124
#ifndef MS_WINDOWS
136
124
        info.filename_encoded = PyUnicode_EncodeFSDefault(info.filename);
137
124
        if (info.filename_encoded == NULL) {
138
0
            _Py_ext_module_loader_info_clear(&info);
139
0
            return -1;
140
0
        }
141
124
#endif
142
143
124
        info.path = info.filename;
144
124
    }
145
0
    else {
146
0
        info.path = info.name;
147
0
    }
148
149
124
    *p_info = info;
150
124
    return 0;
151
124
}
152
153
int
154
_Py_ext_module_loader_info_init_for_builtin(
155
                            struct _Py_ext_module_loader_info *info,
156
                            PyObject *name)
157
559
{
158
559
    assert(PyUnicode_Check(name));
159
559
    assert(PyUnicode_FindChar(name, '.', 0, PyUnicode_GetLength(name), -1) == -1);
160
559
    assert(PyUnicode_GetLength(name) > 0);
161
162
559
    PyObject *name_encoded = PyUnicode_AsEncodedString(name, "ascii", NULL);
163
559
    if (name_encoded == NULL) {
164
0
        return -1;
165
0
    }
166
167
559
    *info = (struct _Py_ext_module_loader_info){
168
559
        .name=Py_NewRef(name),
169
559
        .name_encoded=name_encoded,
170
        /* We won't need filename. */
171
559
        .path=name,
172
559
        .origin=_Py_ext_module_origin_BUILTIN,
173
559
        .hook_prefixes=&ascii_only_prefixes,
174
559
        .newcontext=NULL,
175
559
    };
176
559
    return 0;
177
559
}
178
179
int
180
_Py_ext_module_loader_info_init_for_core(
181
                            struct _Py_ext_module_loader_info *info,
182
                            PyObject *name)
183
0
{
184
0
    if (_Py_ext_module_loader_info_init_for_builtin(info, name) < 0) {
185
0
        return -1;
186
0
    }
187
0
    info->origin = _Py_ext_module_origin_CORE;
188
0
    return 0;
189
0
}
190
191
#ifdef HAVE_DYNAMIC_LOADING
192
int
193
_Py_ext_module_loader_info_init_from_spec(
194
                            struct _Py_ext_module_loader_info *p_info,
195
                            PyObject *spec)
196
124
{
197
124
    PyObject *name = PyObject_GetAttrString(spec, "name");
198
124
    if (name == NULL) {
199
0
        return -1;
200
0
    }
201
124
    PyObject *filename = PyObject_GetAttrString(spec, "origin");
202
124
    if (filename == NULL) {
203
0
        Py_DECREF(name);
204
0
        return -1;
205
0
    }
206
    /* We could also accommodate builtin modules here without much trouble. */
207
124
    _Py_ext_module_origin origin = _Py_ext_module_origin_DYNAMIC;
208
124
    int err = _Py_ext_module_loader_info_init(p_info, name, filename, origin);
209
124
    Py_DECREF(name);
210
124
    Py_DECREF(filename);
211
124
    return err;
212
124
}
213
#endif /* HAVE_DYNAMIC_LOADING */
214
215
216
/********************************/
217
/* module init function results */
218
/********************************/
219
220
void
221
_Py_ext_module_loader_result_clear(struct _Py_ext_module_loader_result *res)
222
683
{
223
    /* Instead, the caller should have called
224
     * _Py_ext_module_loader_result_apply_error(). */
225
683
    assert(res->err == NULL);
226
683
    *res = (struct _Py_ext_module_loader_result){0};
227
683
}
228
229
static void
230
_Py_ext_module_loader_result_set_error(
231
                            struct _Py_ext_module_loader_result *res,
232
                            enum _Py_ext_module_loader_result_error_kind kind)
233
0
{
234
#ifndef NDEBUG
235
    switch (kind) {
236
    case _Py_ext_module_loader_result_EXCEPTION: _Py_FALLTHROUGH;
237
    case _Py_ext_module_loader_result_ERR_UNREPORTED_EXC:
238
        assert(PyErr_Occurred());
239
        break;
240
    case _Py_ext_module_loader_result_ERR_MISSING: _Py_FALLTHROUGH;
241
    case _Py_ext_module_loader_result_ERR_UNINITIALIZED: _Py_FALLTHROUGH;
242
    case _Py_ext_module_loader_result_ERR_NONASCII_NOT_MULTIPHASE: _Py_FALLTHROUGH;
243
    case _Py_ext_module_loader_result_ERR_NOT_MODULE: _Py_FALLTHROUGH;
244
    case _Py_ext_module_loader_result_ERR_MISSING_DEF:
245
        assert(!PyErr_Occurred());
246
        break;
247
    default:
248
        /* We added a new error kind but forgot to add it to this switch. */
249
        assert(0);
250
    }
251
#endif
252
253
0
    assert(res->err == NULL && res->_err.exc == NULL);
254
0
    res->err = &res->_err;
255
0
    *res->err = (struct _Py_ext_module_loader_result_error){
256
0
        .kind=kind,
257
0
        .exc=PyErr_GetRaisedException(),
258
0
    };
259
260
    /* For some kinds, we also set/check res->kind. */
261
0
    switch (kind) {
262
0
    case _Py_ext_module_loader_result_ERR_UNINITIALIZED:
263
0
        assert(res->kind == _Py_ext_module_kind_UNKNOWN);
264
0
        res->kind = _Py_ext_module_kind_INVALID;
265
0
        break;
266
    /* None of the rest affect the result kind. */
267
0
    case _Py_ext_module_loader_result_EXCEPTION: _Py_FALLTHROUGH;
268
0
    case _Py_ext_module_loader_result_ERR_MISSING: _Py_FALLTHROUGH;
269
0
    case _Py_ext_module_loader_result_ERR_UNREPORTED_EXC: _Py_FALLTHROUGH;
270
0
    case _Py_ext_module_loader_result_ERR_NONASCII_NOT_MULTIPHASE: _Py_FALLTHROUGH;
271
0
    case _Py_ext_module_loader_result_ERR_NOT_MODULE: _Py_FALLTHROUGH;
272
0
    case _Py_ext_module_loader_result_ERR_MISSING_DEF:
273
0
        break;
274
0
    default:
275
        /* We added a new error kind but forgot to add it to this switch. */
276
0
        assert(0);
277
0
    }
278
0
}
279
280
void
281
_Py_ext_module_loader_result_apply_error(
282
                            struct _Py_ext_module_loader_result *res,
283
                            const char *name)
284
0
{
285
0
    assert(!PyErr_Occurred());
286
0
    assert(res->err != NULL && res->err == &res->_err);
287
0
    struct _Py_ext_module_loader_result_error err = *res->err;
288
0
    res->err = NULL;
289
290
    /* We're otherwise done with the result at this point. */
291
0
    _Py_ext_module_loader_result_clear(res);
292
293
#ifndef NDEBUG
294
    switch (err.kind) {
295
    case _Py_ext_module_loader_result_EXCEPTION: _Py_FALLTHROUGH;
296
    case _Py_ext_module_loader_result_ERR_UNREPORTED_EXC:
297
        assert(err.exc != NULL);
298
        break;
299
    case _Py_ext_module_loader_result_ERR_MISSING: _Py_FALLTHROUGH;
300
    case _Py_ext_module_loader_result_ERR_UNINITIALIZED: _Py_FALLTHROUGH;
301
    case _Py_ext_module_loader_result_ERR_NONASCII_NOT_MULTIPHASE: _Py_FALLTHROUGH;
302
    case _Py_ext_module_loader_result_ERR_NOT_MODULE: _Py_FALLTHROUGH;
303
    case _Py_ext_module_loader_result_ERR_MISSING_DEF:
304
        assert(err.exc == NULL);
305
        break;
306
    default:
307
        /* We added a new error kind but forgot to add it to this switch. */
308
        assert(0);
309
    }
310
#endif
311
312
0
    const char *msg = NULL;
313
0
    switch (err.kind) {
314
0
    case _Py_ext_module_loader_result_EXCEPTION:
315
0
        break;
316
0
    case _Py_ext_module_loader_result_ERR_MISSING:
317
0
        msg = "initialization of %s failed without raising an exception";
318
0
        break;
319
0
    case _Py_ext_module_loader_result_ERR_UNREPORTED_EXC:
320
0
        msg = "initialization of %s raised unreported exception";
321
0
        break;
322
0
    case _Py_ext_module_loader_result_ERR_UNINITIALIZED:
323
0
        msg = "init function of %s returned uninitialized object";
324
0
        break;
325
0
    case _Py_ext_module_loader_result_ERR_NONASCII_NOT_MULTIPHASE:
326
0
        msg = "initialization of %s did not return PyModuleDef";
327
0
        break;
328
0
    case _Py_ext_module_loader_result_ERR_NOT_MODULE:
329
0
        msg = "initialization of %s did not return an extension module";
330
0
        break;
331
0
    case _Py_ext_module_loader_result_ERR_MISSING_DEF:
332
0
        msg = "initialization of %s did not return a valid extension module";
333
0
        break;
334
0
    default:
335
        /* We added a new error kind but forgot to add it to this switch. */
336
0
        assert(0);
337
0
        PyErr_Format(PyExc_SystemError,
338
0
                     "loading %s failed due to init function", name);
339
0
        return;
340
0
    }
341
342
0
    if (err.exc != NULL) {
343
0
        PyErr_SetRaisedException(err.exc);
344
0
        err.exc = NULL;  /* PyErr_SetRaisedException() stole our reference. */
345
0
        if (msg != NULL) {
346
0
            _PyErr_FormatFromCause(PyExc_SystemError, msg, name);
347
0
        }
348
0
    }
349
0
    else {
350
0
        assert(msg != NULL);
351
0
        PyErr_Format(PyExc_SystemError, msg, name);
352
0
    }
353
0
}
354
355
356
/********************************************/
357
/* getting/running the module init function */
358
/********************************************/
359
360
#ifdef HAVE_DYNAMIC_LOADING
361
static dl_funcptr
362
findfuncptr(const char *prefix, const char *name_buf,
363
            struct _Py_ext_module_loader_info *info,
364
            FILE *fp)
365
248
{
366
#ifdef MS_WINDOWS
367
    return _PyImport_FindSharedFuncptrWindows(
368
            prefix, name_buf, info->filename, fp);
369
#else
370
248
    const char *path_buf = PyBytes_AS_STRING(info->filename_encoded);
371
248
    return _PyImport_FindSharedFuncptr(
372
248
            prefix, name_buf, path_buf, fp);
373
248
#endif
374
248
}
375
376
int
377
_PyImport_GetModuleExportHooks(
378
    struct _Py_ext_module_loader_info *info,
379
    FILE *fp,
380
    PyModInitFunction *modinit,
381
    PyModExportFunction *modexport)
382
124
{
383
124
    *modinit = NULL;
384
124
    *modexport = NULL;
385
386
124
    const char *name_buf = PyBytes_AS_STRING(info->name_encoded);
387
124
    dl_funcptr exportfunc;
388
389
124
    exportfunc = findfuncptr(
390
124
        info->hook_prefixes->export_prefix,
391
124
        name_buf, info, fp);
392
124
    if (exportfunc) {
393
0
        *modexport = (PyModExportFunction)exportfunc;
394
0
        return 2;
395
0
    }
396
124
    if (PyErr_Occurred()) {
397
0
        return -1;
398
0
    }
399
400
124
    exportfunc = findfuncptr(
401
124
        info->hook_prefixes->init_prefix,
402
124
        name_buf, info, fp);
403
124
    if (exportfunc) {
404
124
        *modinit = (PyModInitFunction)exportfunc;
405
124
        return 1;
406
124
    }
407
408
0
    if (!PyErr_Occurred()) {
409
0
        PyObject *msg;
410
0
        msg = PyUnicode_FromFormat(
411
0
            "dynamic module does not define "
412
0
            "module export function (%s_%s or %s_%s)",
413
0
            info->hook_prefixes->export_prefix, name_buf,
414
0
            info->hook_prefixes->init_prefix, name_buf);
415
0
        if (msg != NULL) {
416
0
            PyErr_SetImportError(msg, info->name, info->filename);
417
0
            Py_DECREF(msg);
418
0
        }
419
0
    }
420
0
    return -1;
421
124
}
422
#endif /* HAVE_DYNAMIC_LOADING */
423
424
int
425
_PyImport_RunModInitFunc(PyModInitFunction p0,
426
                         struct _Py_ext_module_loader_info *info,
427
                         struct _Py_ext_module_loader_result *p_res)
428
683
{
429
683
    struct _Py_ext_module_loader_result res = {
430
683
        .kind=_Py_ext_module_kind_UNKNOWN,
431
683
    };
432
433
    /* Call the module init function. */
434
435
    /* Package context is needed for single-phase init */
436
683
    const char *oldcontext = _PyImport_SwapPackageContext(info->newcontext);
437
683
    PyObject *m = p0();
438
683
    _PyImport_SwapPackageContext(oldcontext);
439
440
    /* Validate the result (and populate "res". */
441
442
683
    if (m == NULL) {
443
        /* The init func for multi-phase init modules is expected
444
         * to return a PyModuleDef after calling PyModuleDef_Init().
445
         * That function never raises an exception nor returns NULL,
446
         * so at this point it must be a single-phase init modules. */
447
0
        res.kind = _Py_ext_module_kind_SINGLEPHASE;
448
0
        if (PyErr_Occurred()) {
449
0
            _Py_ext_module_loader_result_set_error(
450
0
                        &res, _Py_ext_module_loader_result_EXCEPTION);
451
0
        }
452
0
        else {
453
0
            _Py_ext_module_loader_result_set_error(
454
0
                        &res, _Py_ext_module_loader_result_ERR_MISSING);
455
0
        }
456
0
        goto error;
457
683
    } else if (PyErr_Occurred()) {
458
        /* Likewise, we infer that this is a single-phase init module. */
459
0
        res.kind = _Py_ext_module_kind_SINGLEPHASE;
460
0
        _Py_ext_module_loader_result_set_error(
461
0
                &res, _Py_ext_module_loader_result_ERR_UNREPORTED_EXC);
462
        /* We would probably be correct to decref m here,
463
         * but we weren't doing so before,
464
         * so we stick with doing nothing. */
465
0
        m = NULL;
466
0
        goto error;
467
0
    }
468
469
683
    if (Py_IS_TYPE(m, NULL)) {
470
        /* This can happen when a PyModuleDef is returned without calling
471
         * PyModuleDef_Init on it
472
         */
473
0
        _Py_ext_module_loader_result_set_error(
474
0
                &res, _Py_ext_module_loader_result_ERR_UNINITIALIZED);
475
        /* Likewise, decref'ing here makes sense.  However, the original
476
         * code has a note about "prevent segfault in DECREF",
477
         * so we play it safe and leave it alone. */
478
0
        m = NULL; /* prevent segfault in DECREF */
479
0
        goto error;
480
0
    }
481
482
683
    if (PyObject_TypeCheck(m, &PyModuleDef_Type)) {
483
        /* multi-phase init */
484
683
        res.kind = _Py_ext_module_kind_MULTIPHASE;
485
683
        res.def = (PyModuleDef *)m;
486
        /* Run PyModule_FromDefAndSpec() to finish loading the module. */
487
683
    }
488
0
    else if (info->hook_prefixes == &nonascii_prefixes) {
489
        /* Non-ASCII is only supported for multi-phase init. */
490
0
        res.kind = _Py_ext_module_kind_MULTIPHASE;
491
        /* Don't allow legacy init for non-ASCII module names. */
492
0
        _Py_ext_module_loader_result_set_error(
493
0
                &res, _Py_ext_module_loader_result_ERR_NONASCII_NOT_MULTIPHASE);
494
0
        goto error;
495
0
    }
496
0
    else {
497
        /* single-phase init (legacy) */
498
0
        res.kind = _Py_ext_module_kind_SINGLEPHASE;
499
0
        res.module = m;
500
501
0
        if (!PyModule_Check(m)) {
502
0
            _Py_ext_module_loader_result_set_error(
503
0
                    &res, _Py_ext_module_loader_result_ERR_NOT_MODULE);
504
0
            goto error;
505
0
        }
506
507
0
        res.def = _PyModule_GetDefOrNull(m);
508
0
        if (res.def == NULL) {
509
0
            PyErr_Clear();
510
0
            _Py_ext_module_loader_result_set_error(
511
0
                    &res, _Py_ext_module_loader_result_ERR_MISSING_DEF);
512
0
            goto error;
513
0
        }
514
0
    }
515
516
683
    assert(!PyErr_Occurred());
517
683
    assert(res.err == NULL);
518
683
    *p_res = res;
519
683
    return 0;
520
521
0
error:
522
0
    assert(!PyErr_Occurred());
523
0
    assert(res.err != NULL);
524
0
    Py_CLEAR(res.module);
525
    res.def = NULL;
526
0
    *p_res = res;
527
0
    p_res->err = &p_res->_err;
528
0
    return -1;
529
683
}