Skip to content

Commit a133975

Browse files
committed
Watch lazy import dicts during global specialization
1 parent 9e863fa commit a133975

2 files changed

Lines changed: 83 additions & 1 deletion

File tree

Lib/test/test_lazy_import/__init__.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -949,6 +949,81 @@ def f():
949949
self.assertEqual(result.returncode, 0, f"stdout: {result.stdout}, stderr: {result.stderr}")
950950
self.assertIn("OK", result.stdout)
951951

952+
def test_add_lazy_to_exec_globals_after_specialization(self):
953+
code = textwrap.dedent("""
954+
source = '''
955+
import sys
956+
import types
957+
958+
lazy from test.test_lazy_import.data import basic2
959+
960+
assert 'test.test_lazy_import.data.basic2' not in sys.modules
961+
962+
class C: pass
963+
sneaky = C()
964+
sneaky.x = 1
965+
966+
def f():
967+
t = 0
968+
for _ in range(5):
969+
t += sneaky.x
970+
return t
971+
972+
f()
973+
globals()["sneaky"] = globals()["basic2"]
974+
assert f() == 210
975+
print("OK")
976+
'''
977+
ns = {"__name__": "lazy_exec_globals"}
978+
exec(source, ns)
979+
""")
980+
result = subprocess.run(
981+
[sys.executable, "-c", code],
982+
capture_output=True,
983+
text=True
984+
)
985+
self.assertEqual(result.returncode, 0, f"stdout: {result.stdout}, stderr: {result.stderr}")
986+
self.assertIn("OK", result.stdout)
987+
988+
def test_add_lazy_to_exec_builtins_after_specialization(self):
989+
code = textwrap.dedent("""
990+
import builtins
991+
source = '''
992+
import sys
993+
import types
994+
995+
lazy from test.test_lazy_import.data import basic2
996+
997+
assert 'test.test_lazy_import.data.basic2' not in sys.modules
998+
999+
class C: pass
1000+
sneaky = C()
1001+
sneaky.x = 1
1002+
__builtins__["sneaky"] = sneaky
1003+
del sneaky
1004+
1005+
def f():
1006+
t = 0
1007+
for _ in range(5):
1008+
t += sneaky.x
1009+
return t
1010+
1011+
f()
1012+
__builtins__["sneaky"] = globals()["basic2"]
1013+
assert f() == 210
1014+
print("OK")
1015+
'''
1016+
ns = {"__name__": "lazy_exec_builtins", "__builtins__": builtins.__dict__.copy()}
1017+
exec(source, ns)
1018+
""")
1019+
result = subprocess.run(
1020+
[sys.executable, "-c", code],
1021+
capture_output=True,
1022+
text=True
1023+
)
1024+
self.assertEqual(result.returncode, 0, f"stdout: {result.stdout}, stderr: {result.stderr}")
1025+
self.assertIn("OK", result.stdout)
1026+
9521027

9531028
@support.requires_subprocess()
9541029
class MultipleNameFromImportTests(LazyImportTestCase):

Python/specialize.c

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1386,6 +1386,7 @@ specialize_load_global_lock_held(
13861386
SPECIALIZATION_FAIL(LOAD_GLOBAL, SPEC_FAIL_OUT_OF_RANGE);
13871387
goto fail;
13881388
}
1389+
PyDict_Watch(MODULE_WATCHER_ID, globals);
13891390
#ifdef Py_GIL_DISABLED
13901391
maybe_enable_deferred_ref_count(value);
13911392
#endif
@@ -1403,11 +1404,15 @@ specialize_load_global_lock_held(
14031404
SPECIALIZATION_FAIL(LOAD_GLOBAL, SPEC_FAIL_LOAD_GLOBAL_NON_STRING_OR_SPLIT);
14041405
goto fail;
14051406
}
1406-
index = _PyDictKeys_StringLookup(builtin_keys, name);
1407+
index = _PyDict_LookupIndexAndValue((PyDictObject *)builtins, name, &value);
14071408
if (index == DKIX_ERROR) {
14081409
SPECIALIZATION_FAIL(LOAD_GLOBAL, SPEC_FAIL_EXPECTED_ERROR);
14091410
goto fail;
14101411
}
1412+
if (value != NULL && PyLazyImport_CheckExact(value)) {
1413+
SPECIALIZATION_FAIL(LOAD_GLOBAL, SPEC_FAIL_ATTR_MODULE_LAZY_VALUE);
1414+
goto fail;
1415+
}
14111416
if (index != (uint16_t)index) {
14121417
SPECIALIZATION_FAIL(LOAD_GLOBAL, SPEC_FAIL_OUT_OF_RANGE);
14131418
goto fail;
@@ -1422,6 +1427,7 @@ specialize_load_global_lock_held(
14221427
SPECIALIZATION_FAIL(LOAD_GLOBAL, SPEC_FAIL_OUT_OF_RANGE);
14231428
goto fail;
14241429
}
1430+
PyDict_Watch(MODULE_WATCHER_ID, globals);
14251431
uint32_t builtins_version = _PyDict_GetKeysVersionForCurrentState(
14261432
interp, (PyDictObject*) builtins);
14271433
if (builtins_version == 0) {
@@ -1432,6 +1438,7 @@ specialize_load_global_lock_held(
14321438
SPECIALIZATION_FAIL(LOAD_GLOBAL, SPEC_FAIL_OUT_OF_RANGE);
14331439
goto fail;
14341440
}
1441+
PyDict_Watch(MODULE_WATCHER_ID, builtins);
14351442
cache->index = (uint16_t)index;
14361443
cache->module_keys_version = (uint16_t)globals_version;
14371444
cache->builtin_keys_version = (uint16_t)builtins_version;

0 commit comments

Comments
 (0)