Skip to content

Commit 33d3ade

Browse files
committed
gh-152298: Protect pending lazy owner bindings
1 parent 61614b0 commit 33d3ade

7 files changed

Lines changed: 232 additions & 71 deletions

File tree

Include/internal/pycore_lazyimportobject.h

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,34 @@ typedef struct {
1919
PyObject *lz_builtins;
2020
PyObject *lz_from;
2121
PyObject *lz_attr;
22-
// Protected by the import lock. The owner fields remember the first
23-
// globals/key pair until resolution succeeds or the proxy is cleared.
22+
// Protected by the import lock. lz_owner_* records the first module
23+
// global that resolve() may replace. Pending owners are being stored;
24+
// finalized owners cannot be claimed again.
2425
PyObject *lz_owner_globals;
2526
PyObject *lz_owner_key;
2627
PyObject *lz_resolved;
2728
// Frame information for the original import location.
2829
PyCodeObject *lz_code; // Code object where the lazy import was created.
30+
int lz_owner_pending;
31+
int lz_owner_finalized;
2932
int lz_instr_offset; // Instruction offset where the lazy import was created.
3033
} PyLazyImportObject;
3134

3235

3336
PyAPI_FUNC(PyObject *) _PyLazyImport_GetName(PyObject *lazy_import);
3437
PyAPI_FUNC(PyObject *) _PyLazyImport_New(
3538
struct _PyInterpreterFrame *frame, PyObject *import_func, PyObject *from, PyObject *attr);
36-
PyAPI_FUNC(void) _PyLazyImport_BindGlobal(
39+
PyAPI_FUNC(int) _PyLazyImport_BindGlobal(
3740
PyThreadState *tstate,
3841
PyObject *lazy_import,
3942
PyObject *globals,
4043
PyObject *name);
44+
PyAPI_FUNC(void) _PyLazyImport_FinishGlobalBinding(
45+
PyThreadState *tstate,
46+
PyObject *lazy_import,
47+
PyObject *globals,
48+
PyObject *name,
49+
int stored);
4150
PyAPI_FUNC(int) _PyLazyImport_CommitIfCurrent(
4251
PyThreadState *tstate,
4352
PyObject *lazy_import,

Lib/test/test_lazy_import/__init__.py

Lines changed: 41 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -585,11 +585,6 @@ def test_module_attr_dynamic_equal_name_clears_owner(self):
585585
module = sys.modules[__name__]
586586
587587
lazy import target_module as target
588-
proxy = globals()["target"]
589-
stored_name = next(name for name in globals() if name == "target")
590-
dynamic_name = bytearray(b"target").decode()
591-
assert dynamic_name == stored_name
592-
assert dynamic_name is not stored_name
593588
594589
def custom_import(name, globals=None, locals=None, fromlist=None,
595590
level=0):
@@ -599,8 +594,14 @@ def custom_import(name, globals=None, locals=None, fromlist=None,
599594
return resolved
600595
return real_import(name, globals, locals, fromlist, level)
601596
602-
builtins.__import__ = custom_import
603-
try:
597+
def check():
598+
proxy = globals()["target"]
599+
stored_name = next(name for name in globals()
600+
if name == "target")
601+
dynamic_name = bytearray(b"target").decode()
602+
assert dynamic_name == stored_name
603+
assert dynamic_name is not stored_name
604+
604605
resolved = getattr(module, dynamic_name)
605606
assert resolved.VALUE == "resolved"
606607
assert module.__dict__["target"] is resolved
@@ -609,6 +610,10 @@ def custom_import(name, globals=None, locals=None, fromlist=None,
609610
again = proxy.resolve()
610611
assert again is resolved
611612
assert module.__dict__["target"] is proxy
613+
614+
builtins.__import__ = custom_import
615+
try:
616+
check()
612617
finally:
613618
builtins.__import__ = real_import
614619
""")
@@ -624,7 +629,6 @@ def test_rebound_back_to_same_proxy_after_stale_resolve_does_not_replace(self):
624629
calls = []
625630
626631
lazy import target_module as target
627-
proxy = globals()["target"]
628632
629633
def custom_import(name, globals=None, locals=None, fromlist=None,
630634
level=0):
@@ -635,8 +639,8 @@ def custom_import(name, globals=None, locals=None, fromlist=None,
635639
return resolved
636640
return real_import(name, globals, locals, fromlist, level)
637641
638-
builtins.__import__ = custom_import
639-
try:
642+
def check():
643+
proxy = globals()["target"]
640644
sentinel = object()
641645
globals()["target"] = sentinel
642646
resolved = proxy.resolve()
@@ -646,6 +650,10 @@ def custom_import(name, globals=None, locals=None, fromlist=None,
646650
again = proxy.resolve()
647651
assert again is resolved
648652
assert globals()["target"] is proxy
653+
654+
builtins.__import__ = custom_import
655+
try:
656+
check()
649657
assert len(calls) == 1, calls
650658
finally:
651659
builtins.__import__ = real_import
@@ -661,7 +669,6 @@ def test_resolve_replaces_unaliased_import_global(self):
661669
real_import = builtins.__import__
662670
663671
lazy import target_module
664-
proxy = globals()["target_module"]
665672
666673
def custom_import(name, globals=None, locals=None, fromlist=None,
667674
level=0):
@@ -671,11 +678,15 @@ def custom_import(name, globals=None, locals=None, fromlist=None,
671678
return resolved
672679
return real_import(name, globals, locals, fromlist, level)
673680
674-
builtins.__import__ = custom_import
675-
try:
681+
def check():
682+
proxy = globals()["target_module"]
676683
resolved = proxy.resolve()
677684
assert resolved.VALUE == "resolved"
678685
assert globals()["target_module"] is resolved
686+
687+
builtins.__import__ = custom_import
688+
try:
689+
check()
679690
finally:
680691
builtins.__import__ = real_import
681692
""")
@@ -687,13 +698,16 @@ def test_resolve_replaces_unaliased_dotted_import_binding(self):
687698
import types
688699
689700
lazy import test.test_lazy_import.data.pkg.bar
690-
proxy = globals()["test"]
691701
692-
resolved = proxy.resolve()
693-
assert isinstance(resolved, types.ModuleType)
694-
assert resolved.__name__ == "test"
695-
assert globals()["test"] is resolved
696-
assert "test.test_lazy_import.data.pkg" not in globals()
702+
def check():
703+
proxy = globals()["test"]
704+
resolved = proxy.resolve()
705+
assert isinstance(resolved, types.ModuleType)
706+
assert resolved.__name__ == "test"
707+
assert globals()["test"] is resolved
708+
assert "test.test_lazy_import.data.pkg" not in globals()
709+
710+
check()
697711
""")
698712
assert_python_ok("-c", code)
699713

@@ -707,8 +721,6 @@ def test_copying_lazy_proxy_to_second_global_keeps_first_owner(self):
707721
calls = []
708722
709723
lazy import target_module as primary
710-
secondary = globals()["primary"]
711-
proxy = globals()["primary"]
712724
713725
def custom_import(name, globals=None, locals=None, fromlist=None,
714726
level=0):
@@ -719,15 +731,21 @@ def custom_import(name, globals=None, locals=None, fromlist=None,
719731
return resolved
720732
return real_import(name, globals, locals, fromlist, level)
721733
722-
builtins.__import__ = custom_import
723-
try:
734+
def check():
735+
proxy = globals()["primary"]
736+
globals()["secondary"] = proxy
737+
724738
resolved = proxy.resolve()
725739
assert globals()["primary"] is resolved
726740
assert globals()["secondary"] is proxy
727741
728742
again = globals()["secondary"].resolve()
729743
assert again is resolved
730744
assert globals()["secondary"] is proxy
745+
746+
builtins.__import__ = custom_import
747+
try:
748+
check()
731749
assert len(calls) == 1, calls
732750
finally:
733751
builtins.__import__ = real_import

Modules/_testinternalcapi/test_cases.c.h

Lines changed: 20 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)