Coverage Report

Created: 2026-10-17 06:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/cpython/Objects/lazyimportobject.c
Line
Count
Source
1
// Lazy object implementation.
2
3
#include "Python.h"
4
#include "pycore_ceval.h"
5
#include "pycore_dict.h"
6
#include "pycore_gc.h"
7
#include "pycore_import.h"
8
#include "pycore_interpframe.h"
9
#include "pycore_lazyimportobject.h"
10
#include "pycore_long.h"
11
#include "pycore_moduleobject.h"
12
#include "pycore_pyatomic_ft_wrappers.h"
13
#include "pycore_pyerrors.h"
14
#include "pycore_traceback.h"
15
#include "pycore_tstate.h"
16
17
typedef struct {
18
    PyObject_HEAD
19
    PyObject *lz_builtins;  // Roots own the mapping; projections retain the root.
20
    // A root stores its absolute name (PyUnicode) in lz_from, and original
21
    // fromlist in lz_attr.
22
    // A projection stores its source placeholder (a PyLazyImportObject)
23
    // in lz_from, and the attribute to import from (PyUnicode) it in lz_attr.
24
    PyObject *lz_from;
25
    PyObject *lz_attr;
26
    // Declaration location.
27
    PyCodeObject *lz_code;
28
    int lz_instr_offset;
29
    // The root was accessed, so its declared path requires import semantics.
30
    int lz_active;
31
} PyLazyImportObject;
32
33
16.1k
#define PyLazyImportObject_CAST(op) ((PyLazyImportObject *)(op))
34
35
static PyObject *lazy_import_name(PyLazyImportObject *m);
36
37
PyObject *
38
_PyLazyImport_New(_PyInterpreterFrame *frame, PyObject *builtins,
39
                  PyObject *name, PyObject *fromlist)
40
490
{
41
490
    if (!name || !(PyUnicode_Check(name) || PyLazyImport_CheckExact(name))) {
42
0
        PyErr_SetString(PyExc_TypeError, "expected str or lazy_import for name");
43
0
        return NULL;
44
0
    }
45
490
    if (fromlist == Py_None || fromlist == NULL) {
46
255
        fromlist = NULL;
47
255
    }
48
235
    else if (!PyUnicode_Check(fromlist) && !PyTuple_Check(fromlist)) {
49
0
        PyErr_SetString(PyExc_TypeError,
50
0
            "lazy_import: fromlist must be None, a string, or a tuple");
51
0
        return NULL;
52
0
    }
53
#ifndef NDEBUG
54
    if (PyLazyImport_CheckExact(name)) {
55
        // projection
56
        assert(builtins == NULL);
57
        assert(fromlist != NULL);
58
        assert(PyUnicode_Check(fromlist));
59
    }
60
    else {
61
        // root
62
        assert(builtins != NULL);
63
    }
64
#endif
65
490
    PyLazyImportObject *m = PyObject_GC_New(
66
490
        PyLazyImportObject, &PyLazyImport_Type);
67
490
    if (m == NULL) {
68
0
        return NULL;
69
0
    }
70
490
    m->lz_builtins = Py_XNewRef(builtins);
71
490
    m->lz_from = Py_NewRef(name);
72
490
    m->lz_attr = Py_XNewRef(fromlist);
73
74
490
    m->lz_code = NULL;
75
490
    m->lz_instr_offset = -1;
76
490
    m->lz_active = 0;
77
78
490
    if (frame != NULL) {
79
490
        m->lz_code = (PyCodeObject *)Py_NewRef(_PyFrame_GetCode(frame));
80
490
        m->lz_instr_offset = _PyInterpreterFrame_LASTI(frame);
81
490
    }
82
83
490
    _PyObject_GC_TRACK(m);
84
490
    return (PyObject *)m;
85
490
}
86
87
// Reuse concrete attributes of initialized modules without waiting for imports
88
// or resolving lazy attributes. Failed cache lookups are retried at resolution.
89
// May return NULL with or without an exception set.
90
static PyObject *
91
lazy_import_get_loaded_attr(PyThreadState *tstate, PyObject *name,
92
                            PyObject *attr_name)
93
141
{
94
141
    PyObject *mod = NULL, *spec = NULL, *current = NULL, *attr = NULL;
95
141
    PyObject *modules = Py_XNewRef(_PyImport_GetModules(tstate->interp));
96
141
    if (modules == NULL) {
97
0
        return NULL;
98
0
    }
99
141
    int rc = PyMapping_GetOptionalItem(modules, name, &mod);
100
141
    if (rc <= 0 || !PyModule_Check(mod)) {
101
139
        goto done;
102
139
    }
103
2
    PyObject *dict = _PyModule_GetDict(mod);
104
2
    if (PyObject_GetOptionalAttr(mod, &_Py_ID(__spec__), &spec) < 0 ||
105
2
        _PyModuleSpec_IsInitializing(spec) != 0) {
106
0
        goto done;
107
0
    }
108
    // An initialization check can replace the module in sys.modules.
109
2
    if (modules != _PyImport_GetModules(tstate->interp) ||
110
2
        PyMapping_GetOptionalItem(modules, name, &current) <= 0 ||
111
2
        current != mod) {
112
0
        goto done;
113
0
    }
114
2
    if (PyDict_GetItemRef(dict, attr_name, &attr) < 0) {
115
0
        goto done;
116
0
    }
117
2
    if (attr != NULL && PyLazyImport_CheckExact(attr)) {
118
0
        Py_CLEAR(attr);
119
0
    }
120
121
141
done:
122
141
    Py_XDECREF(current);
123
141
    Py_XDECREF(spec);
124
141
    Py_XDECREF(mod);
125
141
    Py_DECREF(modules);
126
141
    if (PyErr_ExceptionMatches(PyExc_Exception)) {
127
0
        PyErr_Clear();
128
0
    }
129
141
    return attr;
130
2
}
131
132
PyObject *
133
_PyEval_LazyImportFrom(PyThreadState *tstate, _PyInterpreterFrame *frame,
134
                       PyObject *v, PyObject *name)
135
141
{
136
141
    assert(PyLazyImport_CheckExact(v));
137
141
    assert(name);
138
141
    assert(PyUnicode_Check(name));
139
141
    PyLazyImportObject *lz = PyLazyImportObject_CAST(v);
140
    // Only `from a import b` can take b off an already imported a;
141
    // `import a.b as c` has to import a.b first.
142
141
    if (lz->lz_attr != NULL && PyTuple_Check(lz->lz_attr) &&
143
141
        PyTuple_GET_SIZE(lz->lz_attr) > 0) {
144
141
        PyObject *attr = lazy_import_get_loaded_attr(tstate, lz->lz_from, name);
145
141
        if (attr != NULL || PyErr_Occurred()) {
146
2
            return attr;
147
2
        }
148
141
    }
149
139
    return _PyLazyImport_New(frame, NULL, v, name);
150
141
}
151
152
static int
153
lazy_import_traverse(PyObject *op, visitproc visit, void *arg)
154
15.9k
{
155
15.9k
    PyLazyImportObject *m = PyLazyImportObject_CAST(op);
156
15.9k
    Py_VISIT(m->lz_builtins);
157
15.9k
    Py_VISIT(m->lz_from);
158
15.9k
    Py_VISIT(m->lz_attr);
159
15.9k
    Py_VISIT(m->lz_code);
160
15.9k
    return 0;
161
15.9k
}
162
163
static int
164
lazy_import_clear(PyObject *op)
165
24
{
166
24
    PyLazyImportObject *m = PyLazyImportObject_CAST(op);
167
24
    Py_CLEAR(m->lz_builtins);
168
24
    Py_CLEAR(m->lz_from);
169
24
    Py_CLEAR(m->lz_attr);
170
24
    Py_CLEAR(m->lz_code);
171
24
    return 0;
172
24
}
173
174
static void
175
lazy_import_dealloc(PyObject *op)
176
24
{
177
24
    _PyObject_GC_UNTRACK(op);
178
24
    (void)lazy_import_clear(op);
179
24
    Py_TYPE(op)->tp_free(op);
180
24
}
181
182
/* Specialize the error message for failed attribute lookups. */
183
static PyObject *
184
lazy_import_getattro(PyObject *op, PyObject *name)
185
0
{
186
0
    PyObject *value = _PyObject_GenericGetAttrWithDict(
187
0
        op, name, NULL, /* suppress */ 1);
188
0
    if (value != NULL || PyErr_Occurred()) {
189
0
        return value;
190
0
    }
191
0
    PyObject *lz_name = lazy_import_name(PyLazyImportObject_CAST(op));
192
0
    if (lz_name == NULL) {
193
0
        return NULL;
194
0
    }
195
0
    PyErr_Format(PyExc_AttributeError,
196
0
                 "cannot access attribute %R on unresolved lazy import %R",
197
0
                 name, lz_name);
198
0
    Py_DECREF(lz_name);
199
0
    return NULL;
200
0
}
201
202
// The dotted name of the object that resolving the placeholder returns.
203
static PyObject *
204
lazy_import_path(PyLazyImportObject *m)
205
28
{
206
28
    if (PyLazyImport_CheckExact(m->lz_from)) {
207
7
        if (_Py_EnterRecursiveCall(" while getting the name of a lazy import")) {
208
0
            return NULL;
209
0
        }
210
7
        PyObject *base = lazy_import_path((PyLazyImportObject *)m->lz_from);
211
7
        _Py_LeaveRecursiveCall();
212
7
        if (base == NULL) {
213
0
            return NULL;
214
0
        }
215
7
        PyObject *res = PyUnicode_FromFormat("%U.%U", base, m->lz_attr);
216
7
        Py_DECREF(base);
217
7
        return res;
218
7
    }
219
21
    if (m->lz_attr != NULL &&
220
7
        (!PyTuple_Check(m->lz_attr) || PyTuple_GET_SIZE(m->lz_attr) > 0)) {
221
7
        return Py_NewRef(m->lz_from);
222
7
    }
223
    // __import__("a.b") returns the top-level package `a`.
224
14
    Py_ssize_t dot = PyUnicode_FindChar(
225
14
        m->lz_from, '.', 0, PyUnicode_GET_LENGTH(m->lz_from), 1
226
14
    );
227
14
    if (dot == -2) {
228
0
        return NULL;
229
0
    }
230
14
    if (dot < 0) {
231
14
        return Py_NewRef(m->lz_from);
232
14
    }
233
0
    return PyUnicode_Substring(m->lz_from, 0, dot);
234
14
}
235
236
static PyObject *
237
lazy_import_name(PyLazyImportObject *m)
238
0
{
239
0
    if (PyLazyImport_CheckExact(m->lz_from)) {
240
0
        return lazy_import_path(m);
241
0
    }
242
0
    if (m->lz_attr != NULL &&
243
0
        (!PyTuple_Check(m->lz_attr) || PyTuple_GET_SIZE(m->lz_attr) > 0)) {
244
0
        return PyUnicode_FromFormat("%U...", m->lz_from);
245
0
    }
246
0
    return Py_NewRef(m->lz_from);
247
0
}
248
249
static PyObject *
250
lazy_import_repr(PyObject *op)
251
0
{
252
0
    PyLazyImportObject *m = PyLazyImportObject_CAST(op);
253
0
    PyObject *name = lazy_import_name(m);
254
0
    if (name == NULL) {
255
0
        return NULL;
256
0
    }
257
0
    PyObject *res = PyUnicode_FromFormat("<%T '%U'>", op, name);
258
0
    Py_DECREF(name);
259
0
    return res;
260
0
}
261
262
// Consume a result, resolving a placeholder returned by an import hook or
263
// an attribute lookup under the same cycle and recursion checks.
264
static PyObject *
265
lazy_import_resolve_result(PyThreadState *tstate, PyObject *obj)
266
28
{
267
28
    if (obj == NULL || !PyLazyImport_CheckExact(obj)) {
268
28
        return obj;
269
28
    }
270
0
    PyObject *result = _PyImport_LoadLazyImportTstate(tstate, obj);
271
0
    Py_DECREF(obj);
272
0
    return result;
273
28
}
274
275
// Look up, in order, the attributes recorded from the root placeholder to lz
276
// on the module the root's import returned.
277
static PyObject *
278
lazy_import_replay_from(PyThreadState *tstate, PyObject *mod,
279
                        PyLazyImportObject *lz)
280
14
{
281
14
    if (!PyLazyImport_CheckExact(lz->lz_from)) {
282
7
        return Py_NewRef(mod);
283
7
    }
284
7
    if (_Py_EnterRecursiveCallTstate(tstate, " while resolving a lazy import")) {
285
0
        return NULL;
286
0
    }
287
7
    PyObject *from = lazy_import_replay_from(
288
7
        tstate, mod, (PyLazyImportObject *)lz->lz_from);
289
7
    _Py_LeaveRecursiveCallTstate(tstate);
290
7
    if (from == NULL) {
291
0
        return NULL;
292
0
    }
293
7
    PyObject *obj = _PyEval_ImportFrom(tstate, from, lz->lz_attr);
294
7
    Py_DECREF(from);
295
7
    return lazy_import_resolve_result(tstate, obj);
296
7
}
297
298
// Preserve the resolution error and attach the import's declaration location.
299
static void
300
lazy_import_add_exception_cause(PyThreadState *tstate, PyLazyImportObject *lz)
301
0
{
302
0
    if (!PyErr_Occurred() || lz->lz_code == NULL || lz->lz_instr_offset < 0) {
303
0
        return;
304
0
    }
305
0
    PyObject *exc = _PyErr_GetRaisedException(tstate);
306
0
    PyObject *name = lazy_import_name(lz);
307
0
    if (name == NULL) {
308
0
        goto done;
309
0
    }
310
0
    int lineno = PyCode_Addr2Line(lz->lz_code, lz->lz_instr_offset * 2);
311
0
    const char *filename = PyUnicode_AsUTF8(lz->lz_code->co_filename);
312
0
    if (filename == NULL) {
313
0
        goto done;
314
0
    }
315
0
    const char *funcname = PyUnicode_AsUTF8(lz->lz_code->co_name);
316
0
    if (funcname == NULL) {
317
0
        goto done;
318
0
    }
319
0
    PyBaseExceptionObject *base_exc = (PyBaseExceptionObject *)exc;
320
0
    if (base_exc->cause != NULL || base_exc->context != NULL ||
321
0
        base_exc->suppress_context) {
322
        // Preserve the original chain, including `raise ... from None`.
323
0
        PyObject *note = PyUnicode_FromFormat(
324
0
            "lazy import of '%U' declared in %s at %s:%d",
325
0
            name, funcname, filename, lineno);
326
0
        if (note != NULL) {
327
0
            PyObject *notes;
328
0
            if (PyObject_GetOptionalAttr(exc, &_Py_ID(__notes__), &notes) >= 0) {
329
0
                if (notes == NULL || PySequence_Contains(notes, note) == 0) {
330
0
                    (void)_PyException_AddNote(exc, note);
331
0
                }
332
0
                Py_XDECREF(notes);
333
0
            }
334
0
            Py_DECREF(note);
335
0
        }
336
0
        goto done;
337
0
    }
338
0
    PyObject *msg = PyUnicode_FromFormat(
339
0
        "lazy import of '%U' raised an exception during resolution", name);
340
0
    if (msg == NULL) {
341
0
        goto done;
342
0
    }
343
0
    PyObject *cause = PyObject_CallOneArg(PyExc_ImportError, msg);
344
0
    Py_DECREF(msg);
345
0
    if (cause == NULL) {
346
0
        goto done;
347
0
    }
348
0
    _PyErr_SetRaisedException(tstate, cause);
349
0
    _PyTraceback_Add(funcname, filename, lineno);
350
0
    PyException_SetCause(exc, _PyErr_GetRaisedException(tstate));
351
352
0
done:
353
0
    Py_XDECREF(name);
354
0
    _PyErr_SetRaisedException(tstate, exc);
355
0
}
356
357
int
358
_PyLazyImport_IsResolving(PyThreadState *tstate, PyObject *op)
359
21
{
360
21
    _PyThreadStateImpl *ts = (_PyThreadStateImpl *)tstate;
361
21
    assert(PyLazyImport_CheckExact(op));
362
21
    int active = ts->lazy_imports == NULL ? 0 : PySet_Contains(ts->lazy_imports, op);
363
21
    assert(active >= 0);  // Exact placeholders use identity hashing and equality.
364
21
    return active;
365
21
}
366
367
static PyObject *
368
lazy_import_resolve_impl(PyThreadState *tstate, PyObject *lazy_import,
369
                         PyObject **imported_module)
370
21
{
371
21
    PyObject *obj = NULL;
372
21
    PyObject *fromlist = NULL;
373
21
    PyObject *import_func = NULL;
374
21
    PyObject *resolving = NULL;
375
21
    assert(lazy_import != NULL);
376
21
    assert(PyLazyImport_CheckExact(lazy_import));
377
378
21
    PyLazyImportObject *lz = (PyLazyImportObject *)lazy_import;
379
380
    // Walk back to the placeholder IMPORT_NAME left, and the first lookup on it.
381
21
    PyLazyImportObject *root = lz, *first = NULL;
382
21
    while (PyLazyImport_CheckExact(root->lz_from)) {
383
7
        first = root;
384
7
        root = (PyLazyImportObject *)root->lz_from;
385
7
    }
386
387
21
    if (_PyLazyImport_IsResolving(tstate, lazy_import)) {
388
0
        PyObject *name = lazy_import_name(lz);
389
0
        if (name == NULL) {
390
0
            return NULL;
391
0
        }
392
0
        PyObject *errmsg = PyUnicode_FromFormat(
393
0
            "cannot import name %R (most likely due to a circular import)",
394
0
            name);
395
0
        Py_DECREF(name);
396
0
        if (errmsg != NULL) {
397
0
            PyErr_SetImportErrorSubclass(PyExc_ImportCycleError, errmsg,
398
0
                                         root->lz_from, NULL);
399
0
            Py_DECREF(errmsg);
400
0
        }
401
0
        return NULL;
402
0
    }
403
21
    if (_Py_EnterRecursiveCallTstate(tstate, " while resolving a lazy import")) {
404
0
        return NULL;
405
0
    }
406
21
    _PyThreadStateImpl *ts = (_PyThreadStateImpl *)tstate;
407
21
    if (ts->lazy_imports == NULL) {
408
7
        ts->lazy_imports = PySet_New(NULL);
409
7
        if (ts->lazy_imports == NULL) {
410
0
            goto done;
411
0
        }
412
7
    }
413
21
    resolving = ts->lazy_imports;
414
21
    if (PySet_Add(resolving, lazy_import) < 0) {
415
0
        goto done;
416
0
    }
417
418
    // `from a import b, c`: import only the name being resolved.
419
    // Keep an empty tuple intact for custom __import__ hooks.
420
21
    if (first != NULL && root->lz_attr != NULL &&
421
21
        PyTuple_Check(root->lz_attr) && PyTuple_GET_SIZE(root->lz_attr) > 0) {
422
7
        fromlist = PyTuple_Pack(1, first->lz_attr);
423
7
    }
424
14
    else {
425
14
        fromlist = Py_NewRef(root->lz_attr != NULL ? root->lz_attr : Py_None);
426
14
    }
427
21
    if (fromlist == NULL) {
428
0
        goto done;
429
0
    }
430
431
21
    PyObject *globals = PyEval_GetGlobals();
432
21
    if (globals == NULL) {
433
0
        globals = Py_None;
434
0
    }
435
436
21
    if (PyMapping_GetOptionalItem(root->lz_builtins, &_Py_ID(__import__),
437
21
                                  &import_func) < 0) {
438
0
        goto done;
439
0
    }
440
21
    if (import_func == NULL) {
441
0
        PyErr_SetString(PyExc_ImportError, "__import__ not found");
442
0
        goto done;
443
0
    }
444
21
    PyObject *name = Py_NewRef(root->lz_from);
445
21
    if (first == NULL &&
446
14
        (fromlist == Py_None ||
447
0
         (PyTuple_Check(fromlist) && PyTuple_GET_SIZE(fromlist) == 0)) &&
448
14
        _PyImport_IsDefaultImportFunc(tstate->interp, import_func) &&
449
14
        root->lz_builtins == tstate->interp->builtins) {
450
        // A plain import binds the root package. Its pending children are
451
        // imported separately when their attributes are accessed.
452
14
        Py_ssize_t dot = PyUnicode_FindChar(
453
14
            name, '.', 0, PyUnicode_GET_LENGTH(name), 1);
454
14
        if (dot >= 0) {
455
            // Preserve eager traversal through aliased or non-package parents.
456
0
            int regular = 1;
457
0
            int complete = 1;
458
0
            Py_ssize_t end = PyUnicode_GET_LENGTH(name);
459
0
            for (Py_ssize_t i = dot; i >= 0 && regular;
460
0
                 i = PyUnicode_FindChar(name, '.', i + 1, end, 1)) {
461
0
                PyObject *prefix = PyUnicode_Substring(name, 0, i);
462
0
                PyObject *cached = prefix == NULL ? NULL :
463
0
                    lazy_import_get_loaded_attr(tstate, prefix, &_Py_ID(__name__));
464
0
                if (cached == NULL) {
465
0
                    complete = 0;
466
0
                    Py_XDECREF(prefix);
467
0
                    break;
468
0
                }
469
0
                PyObject *path = lazy_import_get_loaded_attr(
470
0
                    tstate, prefix, &_Py_ID(__path__));
471
0
                regular = path != NULL && PyUnicode_Check(cached) &&
472
0
                    PyUnicode_Compare(cached, prefix) == 0;
473
0
                Py_XDECREF(path);
474
0
                Py_DECREF(cached);
475
0
                Py_DECREF(prefix);
476
0
            }
477
0
            PyObject *loaded = lazy_import_get_loaded_attr(
478
0
                tstate, name, &_Py_ID(__name__));
479
0
            if (regular && (!complete || loaded == NULL) && !PyErr_Occurred()) {
480
0
                Py_SETREF(name, PyUnicode_Substring(name, 0, dot));
481
0
            }
482
0
            Py_XDECREF(loaded);
483
0
            if (PyErr_Occurred()) {
484
0
                Py_CLEAR(name);
485
0
            }
486
0
        }
487
14
        else if (dot == -2) {
488
0
            Py_CLEAR(name);
489
0
        }
490
14
        if (name == NULL) {
491
0
            goto done;
492
0
        }
493
14
    }
494
21
    obj = _PyEval_ImportNameWithImport(
495
21
        tstate, import_func, globals, globals,
496
21
        name, fromlist, _PyLong_GetZero()
497
21
    );
498
21
    if (obj != NULL && name != root->lz_from) {
499
0
        FT_ATOMIC_STORE_INT_RELAXED(root->lz_active, 1);
500
0
        FT_ATOMIC_STORE_INT_RELAXED(tstate->interp->imports.has_lazy_submodules, 1);
501
0
        int package = PyModule_CheckExact(obj) ?
502
0
            PyDict_Contains(_PyModule_GetDict(obj), &_Py_ID(__path__)) : 0;
503
0
        if (package < 0) {
504
0
            Py_CLEAR(obj);
505
0
        }
506
0
        else if (!package) {
507
0
            Py_DECREF(obj);
508
0
            obj = _PyEval_ImportNameWithImport(
509
0
                tstate, import_func, globals, globals,
510
0
                root->lz_from, fromlist, _PyLong_GetZero());
511
0
        }
512
0
    }
513
21
    if (obj != NULL && name == root->lz_from &&
514
21
        _PyImport_IsDefaultImportFunc(tstate->interp, import_func) &&
515
21
        _PyImport_ClearLazySubmodule(tstate, name, 0) < 0) {
516
0
        Py_CLEAR(obj);
517
0
    }
518
21
    Py_DECREF(name);
519
21
    obj = lazy_import_resolve_result(tstate, obj);
520
    // The normal importer may publish this module on its parent. Custom
521
    // hooks retain control of their own assignments to the parent.
522
21
    if (imported_module != NULL && obj != NULL && PyModule_Check(obj) &&
523
21
        _PyImport_IsDefaultImportFunc(tstate->interp, import_func)) {
524
21
        *imported_module = Py_NewRef(obj);
525
21
    }
526
21
    if (obj != NULL && first != NULL) {
527
        // Keep the hook and root result alive until all attribute lookups finish.
528
7
        PyObject *from = obj;
529
7
        obj = lazy_import_replay_from(tstate, from, lz);
530
7
        Py_DECREF(from);
531
7
    }
532
533
21
done:
534
21
    if (obj == NULL) {
535
0
        lazy_import_add_exception_cause(tstate, lz);
536
0
    }
537
21
    assert(obj == NULL || !PyLazyImport_CheckExact(obj));
538
21
    if (obj != NULL) {
539
21
        PyObject *name = lazy_import_path(lz);
540
21
        if (name == NULL ||
541
21
            _PyImport_DiscardLazyModule(tstate->interp, name) < 0) {
542
0
            Py_CLEAR(obj);
543
0
        }
544
21
        Py_XDECREF(name);
545
21
    }
546
21
    if (resolving != NULL) {
547
        // A failed set resize can leave the placeholder inserted. Removing by
548
        // identity also permits greenlets to finish in a different order.
549
21
        if (PySet_Discard(resolving, lazy_import) < 0) {
550
0
            Py_CLEAR(obj);
551
0
        }
552
21
        if (PySet_GET_SIZE(resolving) == 0) {
553
            // Keep the set, but release the capacity used by deep resolutions.
554
21
            (void)PySet_Clear(resolving);
555
21
        }
556
21
    }
557
558
21
    Py_XDECREF(fromlist);
559
21
    Py_XDECREF(import_func);
560
21
    _Py_LeaveRecursiveCallTstate(tstate);
561
21
    return obj;
562
21
}
563
564
PyObject *
565
_PyImport_LoadLazyImportTstate(PyThreadState *tstate, PyObject *lazy_import)
566
0
{
567
0
    return lazy_import_resolve_impl(tstate, lazy_import, NULL);
568
0
}
569
570
// Resolve a pending child as an aliased import, preserving its declaration.
571
PyObject *
572
_PyLazyImport_LoadChild(PyThreadState *tstate, PyObject *declaration,
573
                        PyObject *name)
574
0
{
575
0
    assert(PyLazyImport_CheckExact(declaration));
576
0
    PyLazyImportObject *source = PyLazyImportObject_CAST(declaration);
577
0
    PyObject *lz = _PyLazyImport_New(NULL, source->lz_builtins, name, NULL);
578
0
    Py_ssize_t end = PyUnicode_GET_LENGTH(name);
579
0
    Py_ssize_t dot = PyUnicode_FindChar(name, '.', 0, end, 1);
580
0
    if (dot == -2) {
581
0
        Py_CLEAR(lz);
582
0
    }
583
0
    while (lz != NULL && dot >= 0) {
584
0
        Py_ssize_t start = dot + 1;
585
0
        dot = PyUnicode_FindChar(name, '.', start, end, 1);
586
0
        if (dot == -2) {
587
0
            Py_CLEAR(lz);
588
0
            break;
589
0
        }
590
0
        PyObject *attr = PyUnicode_Substring(name, start, dot < 0 ? end : dot);
591
0
        PyObject *next = attr == NULL ? NULL :
592
0
            _PyLazyImport_New(NULL, NULL, lz, attr);
593
0
        Py_XDECREF(attr);
594
0
        Py_SETREF(lz, next);
595
0
    }
596
0
    if (lz == NULL) {
597
0
        return NULL;
598
0
    }
599
0
    PyLazyImportObject *child = PyLazyImportObject_CAST(lz);
600
0
    child->lz_code = (PyCodeObject *)Py_XNewRef(source->lz_code);
601
0
    child->lz_instr_offset = source->lz_instr_offset;
602
0
    PyObject *result = _PyImport_LoadLazyImportTstate(tstate, lz);
603
0
    Py_DECREF(lz);
604
0
    if (result != NULL && PyModule_Check(result) &&
605
0
        PyUnicode_Compare(source->lz_from, name) != 0 &&
606
0
        !PyDict_Contains(_PyModule_GetDict(result), &_Py_ID(__path__))) {
607
        // Reaching a non-package must still reject an invalid dotted import.
608
0
        Py_DECREF(result);
609
0
        return _PyLazyImport_LoadChild(tstate, declaration, source->lz_from);
610
0
    }
611
0
    return result;
612
0
}
613
614
int
615
_PyLazyImport_IsActive(PyObject *declaration)
616
0
{
617
0
    assert(PyLazyImport_CheckExact(declaration));
618
0
    return FT_ATOMIC_LOAD_INT_RELAXED(
619
0
        PyLazyImportObject_CAST(declaration)->lz_active);
620
0
}
621
622
// Loading pkg.child can replace a placeholder in pkg.child with the module
623
// before a from-import retrieves the value that belongs in that binding.
624
// This is an optimization that can be safely skipped.
625
static int
626
lazy_import_replace_child(PyThreadState *tstate, PyObject *placeholder,
627
                          PyObject *name, PyObject *namespace,
628
                          PyObject *child, PyObject *value)
629
0
{
630
0
    PyLazyImportObject *root = (PyLazyImportObject *)placeholder;
631
0
    if (!PyLazyImport_CheckExact(root->lz_from)) {
632
0
        return 0;
633
0
    }
634
0
    while (PyLazyImport_CheckExact(root->lz_from)) {
635
0
        root = (PyLazyImportObject *)root->lz_from;
636
0
    }
637
0
    Py_ssize_t end = PyUnicode_GET_LENGTH(root->lz_from);
638
0
    Py_ssize_t dot = PyUnicode_FindChar(root->lz_from, '.', 0, end, -1);
639
0
    if (dot < 0) {
640
0
        return dot == -1 ? 0 : -1;
641
0
    }
642
0
    if (end - dot - 1 != PyUnicode_GET_LENGTH(name)) {
643
0
        return 0;
644
0
    }
645
0
    Py_ssize_t matches = PyUnicode_Tailmatch(root->lz_from, name, dot + 1, end, 1);
646
0
    if (matches <= 0) {
647
0
        return matches ? -1 : 0;
648
0
    }
649
0
    PyObject *parent_name = PyUnicode_Substring(root->lz_from, 0, dot);
650
0
    if (parent_name == NULL) {
651
0
        return -1;
652
0
    }
653
0
    PyObject *modules = Py_XNewRef(_PyImport_GetModules(tstate->interp));
654
0
    PyObject *parent = NULL;
655
0
    int rc = 0;
656
0
    if (modules != NULL) {
657
0
        rc = PyMapping_GetOptionalItem(modules, parent_name, &parent);
658
0
        if (rc > 0 && PyModule_Check(parent) &&
659
0
            _PyModule_GetDict(parent) == namespace) {
660
0
            rc = _PyDict_ReplaceItemIf(namespace, name, child, value);
661
0
        }
662
0
    }
663
0
    Py_XDECREF(parent);
664
0
    Py_XDECREF(modules);
665
0
    Py_DECREF(parent_name);
666
0
    return rc;
667
0
}
668
669
PyObject *
670
_PyLazyImport_Reify(PyThreadState *tstate, PyObject *placeholder,
671
                    PyObject *name, PyObject *namespace)
672
21
{
673
21
    PyObject *imported_module = NULL;
674
21
    PyObject *value = lazy_import_resolve_impl(
675
21
        tstate, placeholder, &imported_module);
676
21
    if (value == NULL) {
677
0
        Py_XDECREF(imported_module);
678
0
        return NULL;
679
0
    }
680
21
    int rc;
681
21
    if (PyDict_CheckExact(namespace)) {
682
21
        rc = _PyDict_ReplaceItemIf(namespace, name, placeholder, value);
683
21
        if (rc == 0 && imported_module != NULL) {
684
0
            rc = lazy_import_replace_child(
685
0
                tstate, placeholder, name, namespace, imported_module, value);
686
0
        }
687
21
    }
688
0
    else if (Py_TYPE(namespace)->tp_as_mapping == NULL ||
689
0
             Py_TYPE(namespace)->tp_as_mapping->mp_ass_subscript == NULL) {
690
        // Read-only namespaces can resolve a value without caching it.
691
0
        Py_XDECREF(imported_module);
692
0
        return value;
693
0
    }
694
0
    else {
695
        // Custom namespaces retain their mapping protocol. Atomic replacement
696
        // is only available for exact dictionaries.
697
0
        PyObject *current;
698
0
        rc = PyMapping_GetOptionalItem(namespace, name, &current);
699
0
        if (rc > 0) {
700
0
            if (current == placeholder) {
701
0
                rc = PyObject_SetItem(namespace, name, value);
702
0
            }
703
0
            Py_DECREF(current);
704
0
        }
705
0
    }
706
21
    if (rc < 0) {
707
0
        Py_CLEAR(value);
708
0
    }
709
21
    Py_XDECREF(imported_module);
710
21
    return value;
711
21
}
712
713
static PyObject *
714
lazy_import_resolve(PyObject *self, PyObject *args)
715
0
{
716
0
    return _PyImport_LoadLazyImportTstate(PyThreadState_GET(), self);
717
0
}
718
719
static PyMethodDef lazy_import_methods[] = {
720
    {
721
        "resolve", lazy_import_resolve, METH_NOARGS,
722
        PyDoc_STR("Resolve the lazy import and return the imported object.")
723
    },
724
    {NULL, NULL}
725
};
726
727
728
PyDoc_STRVAR(lazy_import_doc,
729
"lazy_import(builtins, name, fromlist=None, /)\n"
730
"--\n"
731
"\n"
732
"Represents a lazy import that will be resolved on first use.\n"
733
"\n"
734
"Instances of this object accessed from the global scope will be\n"
735
"automatically imported based upon their name and then replaced with\n"
736
"the imported value.");
737
738
PyTypeObject PyLazyImport_Type = {
739
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
740
    .tp_name = "lazy_import",
741
    .tp_basicsize = sizeof(PyLazyImportObject),
742
    .tp_dealloc = lazy_import_dealloc,
743
    .tp_repr = lazy_import_repr,
744
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
745
    .tp_doc = lazy_import_doc,
746
    .tp_getattro = lazy_import_getattro,
747
    .tp_traverse = lazy_import_traverse,
748
    .tp_clear = lazy_import_clear,
749
    .tp_methods = lazy_import_methods,
750
    .tp_alloc = PyType_GenericAlloc,
751
    .tp_free = PyObject_GC_Del,
752
};