Coverage Report

Created: 2026-10-31 06:21

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
18.9k
#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
545
{
41
545
    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
545
    if (fromlist == Py_None || fromlist == NULL) {
46
310
        fromlist = NULL;
47
310
    }
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
545
    PyLazyImportObject *m = PyObject_GC_New(
66
545
        PyLazyImportObject, &PyLazyImport_Type);
67
545
    if (m == NULL) {
68
0
        return NULL;
69
0
    }
70
545
    m->lz_builtins = Py_XNewRef(builtins);
71
545
    m->lz_from = Py_NewRef(name);
72
545
    m->lz_attr = Py_XNewRef(fromlist);
73
74
545
    m->lz_code = NULL;
75
545
    m->lz_instr_offset = -1;
76
545
    m->lz_active = 0;
77
78
545
    if (frame != NULL) {
79
545
        m->lz_code = (PyCodeObject *)Py_NewRef(_PyFrame_GetCode(frame));
80
545
        m->lz_instr_offset = _PyInterpreterFrame_LASTI(frame);
81
545
    }
82
83
545
    _PyObject_GC_TRACK(m);
84
545
    return (PyObject *)m;
85
545
}
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
18.8k
{
155
18.8k
    PyLazyImportObject *m = PyLazyImportObject_CAST(op);
156
18.8k
    Py_VISIT(m->lz_builtins);
157
18.8k
    Py_VISIT(m->lz_from);
158
18.8k
    Py_VISIT(m->lz_attr);
159
18.8k
    Py_VISIT(m->lz_code);
160
18.8k
    return 0;
161
18.8k
}
162
163
static int
164
lazy_import_clear(PyObject *op)
165
22
{
166
22
    PyLazyImportObject *m = PyLazyImportObject_CAST(op);
167
22
    Py_CLEAR(m->lz_builtins);
168
22
    Py_CLEAR(m->lz_from);
169
22
    Py_CLEAR(m->lz_attr);
170
22
    Py_CLEAR(m->lz_code);
171
22
    return 0;
172
22
}
173
174
static void
175
lazy_import_dealloc(PyObject *op)
176
22
{
177
22
    _PyObject_GC_UNTRACK(op);
178
22
    (void)lazy_import_clear(op);
179
22
    Py_TYPE(op)->tp_free(op);
180
22
}
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
26
{
206
26
    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
19
    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
12
    Py_ssize_t dot = PyUnicode_FindChar(
225
12
        m->lz_from, '.', 0, PyUnicode_GET_LENGTH(m->lz_from), 1
226
12
    );
227
12
    if (dot == -2) {
228
0
        return NULL;
229
0
    }
230
12
    if (dot < 0) {
231
12
        return Py_NewRef(m->lz_from);
232
12
    }
233
0
    return PyUnicode_Substring(m->lz_from, 0, dot);
234
12
}
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
26
{
267
26
    if (obj == NULL || !PyLazyImport_CheckExact(obj)) {
268
26
        return obj;
269
26
    }
270
0
    PyObject *result = _PyImport_LoadLazyImportTstate(tstate, obj);
271
0
    Py_DECREF(obj);
272
0
    return result;
273
26
}
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
19
{
360
19
    _PyThreadStateImpl *ts = (_PyThreadStateImpl *)tstate;
361
19
    assert(PyLazyImport_CheckExact(op));
362
19
    int active = ts->lazy_imports == NULL ? 0 : PySet_Contains(ts->lazy_imports, op);
363
19
    assert(active >= 0);  // Exact placeholders use identity hashing and equality.
364
19
    return active;
365
19
}
366
367
static PyObject *
368
lazy_import_resolve_impl(PyThreadState *tstate, PyObject *lazy_import,
369
                         PyObject **imported_module)
370
19
{
371
19
    PyObject *obj = NULL;
372
19
    PyObject *fromlist = NULL;
373
19
    PyObject *import_func = NULL;
374
19
    PyObject *resolving = NULL;
375
19
    assert(lazy_import != NULL);
376
19
    assert(PyLazyImport_CheckExact(lazy_import));
377
378
19
    PyLazyImportObject *lz = (PyLazyImportObject *)lazy_import;
379
380
    // Walk back to the placeholder IMPORT_NAME left, and the first lookup on it.
381
19
    PyLazyImportObject *root = lz, *first = NULL;
382
19
    while (PyLazyImport_CheckExact(root->lz_from)) {
383
7
        first = root;
384
7
        root = (PyLazyImportObject *)root->lz_from;
385
7
    }
386
387
19
    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
19
    if (_Py_EnterRecursiveCallTstate(tstate, " while resolving a lazy import")) {
404
0
        return NULL;
405
0
    }
406
19
    _PyThreadStateImpl *ts = (_PyThreadStateImpl *)tstate;
407
19
    if (ts->lazy_imports == NULL) {
408
6
        ts->lazy_imports = PySet_New(NULL);
409
6
        if (ts->lazy_imports == NULL) {
410
0
            goto done;
411
0
        }
412
6
    }
413
19
    resolving = ts->lazy_imports;
414
19
    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
19
    if (first != NULL && root->lz_attr != NULL &&
421
19
        PyTuple_Check(root->lz_attr) && PyTuple_GET_SIZE(root->lz_attr) > 0) {
422
7
        fromlist = PyTuple_Pack(1, first->lz_attr);
423
7
    }
424
12
    else {
425
12
        fromlist = Py_NewRef(root->lz_attr != NULL ? root->lz_attr : Py_None);
426
12
    }
427
19
    if (fromlist == NULL) {
428
0
        goto done;
429
0
    }
430
431
19
    PyObject *globals = PyEval_GetGlobals();
432
19
    if (globals == NULL) {
433
0
        globals = Py_None;
434
0
    }
435
436
19
    if (PyMapping_GetOptionalItem(root->lz_builtins, &_Py_ID(__import__),
437
19
                                  &import_func) < 0) {
438
0
        goto done;
439
0
    }
440
19
    if (import_func == NULL) {
441
0
        PyErr_SetString(PyExc_ImportError, "__import__ not found");
442
0
        goto done;
443
0
    }
444
19
    PyObject *name = Py_NewRef(root->lz_from);
445
19
    if (first == NULL &&
446
12
        (fromlist == Py_None ||
447
0
         (PyTuple_Check(fromlist) && PyTuple_GET_SIZE(fromlist) == 0)) &&
448
12
        _PyImport_IsDefaultImportFunc(tstate->interp, import_func) &&
449
12
        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
12
        Py_ssize_t dot = PyUnicode_FindChar(
453
12
            name, '.', 0, PyUnicode_GET_LENGTH(name), 1);
454
12
        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
12
        else if (dot == -2) {
488
0
            Py_CLEAR(name);
489
0
        }
490
12
        if (name == NULL) {
491
0
            goto done;
492
0
        }
493
12
    }
494
19
    obj = _PyEval_ImportNameWithImport(
495
19
        tstate, import_func, globals, globals,
496
19
        name, fromlist, _PyLong_GetZero()
497
19
    );
498
19
    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
19
    if (obj != NULL && name == root->lz_from &&
514
19
        _PyImport_IsDefaultImportFunc(tstate->interp, import_func) &&
515
19
        _PyImport_ClearLazySubmodule(tstate, name, 0) < 0) {
516
0
        Py_CLEAR(obj);
517
0
    }
518
19
    Py_DECREF(name);
519
19
    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
19
    if (imported_module != NULL && obj != NULL && PyModule_Check(obj) &&
523
19
        _PyImport_IsDefaultImportFunc(tstate->interp, import_func)) {
524
19
        *imported_module = Py_NewRef(obj);
525
19
    }
526
19
    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
19
done:
534
19
    if (obj == NULL) {
535
0
        lazy_import_add_exception_cause(tstate, lz);
536
0
    }
537
19
    assert(obj == NULL || !PyLazyImport_CheckExact(obj));
538
19
    if (obj != NULL) {
539
19
        PyObject *name = lazy_import_path(lz);
540
19
        if (name == NULL ||
541
19
            _PyImport_DiscardLazyModule(tstate->interp, name) < 0) {
542
0
            Py_CLEAR(obj);
543
0
        }
544
19
        Py_XDECREF(name);
545
19
    }
546
19
    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
19
        if (PySet_Discard(resolving, lazy_import) < 0) {
550
0
            Py_CLEAR(obj);
551
0
        }
552
19
        if (PySet_GET_SIZE(resolving) == 0) {
553
            // Keep the set, but release the capacity used by deep resolutions.
554
19
            (void)PySet_Clear(resolving);
555
19
        }
556
19
    }
557
558
19
    Py_XDECREF(fromlist);
559
19
    Py_XDECREF(import_func);
560
19
    _Py_LeaveRecursiveCallTstate(tstate);
561
19
    return obj;
562
19
}
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
19
{
673
19
    PyObject *imported_module = NULL;
674
19
    PyObject *value = lazy_import_resolve_impl(
675
19
        tstate, placeholder, &imported_module);
676
19
    if (value == NULL) {
677
0
        Py_XDECREF(imported_module);
678
0
        return NULL;
679
0
    }
680
19
    int rc;
681
19
    if (PyDict_CheckExact(namespace)) {
682
19
        rc = _PyDict_ReplaceItemIf(namespace, name, placeholder, value);
683
19
        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
19
    }
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
19
    if (rc < 0) {
707
0
        Py_CLEAR(value);
708
0
    }
709
19
    Py_XDECREF(imported_module);
710
19
    return value;
711
19
}
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
};