https://github.com/python/cpython/commit/7ed9062dd05e735505f75e9caac56ccd64913371
commit: 7ed9062dd05e735505f75e9caac56ccd64913371
branch: main
author: Brandt Bucher <[email protected]>
committer: pablogsal <[email protected]>
date: 2026-06-28T23:24:32+01:00
summary:
GH-151619: Ensure non-module global/builtin namespaces are watched for lazy
imports (#151762)
files:
A
Misc/NEWS.d/next/Core_and_Builtins/2026-06-19-16-40-01.gh-issue-151619.35yyJW.rst
M Lib/test/test_lazy_import/__init__.py
M Python/specialize.c
diff --git a/Lib/test/test_lazy_import/__init__.py
b/Lib/test/test_lazy_import/__init__.py
index 4658882243d65ff..e145ae060d7150f 100644
--- a/Lib/test/test_lazy_import/__init__.py
+++ b/Lib/test/test_lazy_import/__init__.py
@@ -949,6 +949,81 @@ def f():
self.assertEqual(result.returncode, 0, f"stdout: {result.stdout},
stderr: {result.stderr}")
self.assertIn("OK", result.stdout)
+ def test_add_lazy_to_exec_globals_after_specialization(self):
+ code = textwrap.dedent("""
+ source = '''
+ import sys
+ import types
+
+ lazy from test.test_lazy_import.data import basic2
+
+ assert 'test.test_lazy_import.data.basic2' not in sys.modules
+
+ class C: pass
+ sneaky = C()
+ sneaky.x = 1
+
+ def f():
+ t = 0
+ for _ in range(5):
+ t += sneaky.x
+ return t
+
+ f()
+ globals()["sneaky"] = globals()["basic2"]
+ assert f() == 210
+ print("OK")
+ '''
+ ns = {"__name__": "lazy_exec_globals"}
+ exec(source, ns)
+ """)
+ result = subprocess.run(
+ [sys.executable, "-c", code],
+ capture_output=True,
+ text=True
+ )
+ self.assertEqual(result.returncode, 0, f"stdout: {result.stdout},
stderr: {result.stderr}")
+ self.assertIn("OK", result.stdout)
+
+ def test_add_lazy_to_exec_builtins_after_specialization(self):
+ code = textwrap.dedent("""
+ import builtins
+ source = '''
+ import sys
+ import types
+
+ lazy from test.test_lazy_import.data import basic2
+
+ assert 'test.test_lazy_import.data.basic2' not in sys.modules
+
+ class C: pass
+ sneaky = C()
+ sneaky.x = 1
+ __builtins__["sneaky"] = sneaky
+ del sneaky
+
+ def f():
+ t = 0
+ for _ in range(5):
+ t += sneaky.x
+ return t
+
+ f()
+ __builtins__["sneaky"] = globals()["basic2"]
+ assert f() == 210
+ print("OK")
+ '''
+ ns = {"__name__": "lazy_exec_builtins", "__builtins__":
builtins.__dict__.copy()}
+ exec(source, ns)
+ """)
+ result = subprocess.run(
+ [sys.executable, "-c", code],
+ capture_output=True,
+ text=True
+ )
+ self.assertEqual(result.returncode, 0, f"stdout: {result.stdout},
stderr: {result.stderr}")
+ self.assertIn("OK", result.stdout)
+
@support.requires_subprocess()
class MultipleNameFromImportTests(LazyImportTestCase):
diff --git
a/Misc/NEWS.d/next/Core_and_Builtins/2026-06-19-16-40-01.gh-issue-151619.35yyJW.rst
b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-19-16-40-01.gh-issue-151619.35yyJW.rst
new file mode 100644
index 000000000000000..28ba2038e876fa7
--- /dev/null
+++
b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-19-16-40-01.gh-issue-151619.35yyJW.rst
@@ -0,0 +1,3 @@
+Fix an issue where using non-module global or builtin namespaces (such as
+dictionaries passed to :func:`exec`) could cause cached global loads to
+produce unresolved :ref:`lazy imports <lazy-imports>`.
diff --git a/Python/specialize.c b/Python/specialize.c
index 79dd70b7f457673..6aaae6f383d22f5 100644
--- a/Python/specialize.c
+++ b/Python/specialize.c
@@ -1388,6 +1388,7 @@ specialize_load_global_lock_held(
SPECIALIZATION_FAIL(LOAD_GLOBAL, SPEC_FAIL_OUT_OF_RANGE);
goto fail;
}
+ PyDict_Watch(MODULE_WATCHER_ID, globals);
#ifdef Py_GIL_DISABLED
maybe_enable_deferred_ref_count(value);
#endif
@@ -1405,11 +1406,15 @@ specialize_load_global_lock_held(
SPECIALIZATION_FAIL(LOAD_GLOBAL,
SPEC_FAIL_LOAD_GLOBAL_NON_STRING_OR_SPLIT);
goto fail;
}
- index = _PyDictKeys_StringLookup(builtin_keys, name);
+ index = _PyDict_LookupIndexAndValue((PyDictObject *)builtins, name,
&value);
if (index == DKIX_ERROR) {
SPECIALIZATION_FAIL(LOAD_GLOBAL, SPEC_FAIL_EXPECTED_ERROR);
goto fail;
}
+ if (value != NULL && PyLazyImport_CheckExact(value)) {
+ SPECIALIZATION_FAIL(LOAD_GLOBAL, SPEC_FAIL_ATTR_MODULE_LAZY_VALUE);
+ goto fail;
+ }
if (index != (uint16_t)index) {
SPECIALIZATION_FAIL(LOAD_GLOBAL, SPEC_FAIL_OUT_OF_RANGE);
goto fail;
@@ -1424,6 +1429,7 @@ specialize_load_global_lock_held(
SPECIALIZATION_FAIL(LOAD_GLOBAL, SPEC_FAIL_OUT_OF_RANGE);
goto fail;
}
+ PyDict_Watch(MODULE_WATCHER_ID, globals);
uint32_t builtins_version = _PyDict_GetKeysVersionForCurrentState(
interp, (PyDictObject*) builtins);
if (builtins_version == 0) {
@@ -1434,6 +1440,7 @@ specialize_load_global_lock_held(
SPECIALIZATION_FAIL(LOAD_GLOBAL, SPEC_FAIL_OUT_OF_RANGE);
goto fail;
}
+ PyDict_Watch(MODULE_WATCHER_ID, builtins);
cache->index = (uint16_t)index;
cache->module_keys_version = (uint16_t)globals_version;
cache->builtin_keys_version = (uint16_t)builtins_version;
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]