Skip to content

Commit 9ac9e3e

Browse files
committed
Check for script shadowing stdlib
1 parent d479931 commit 9ac9e3e

File tree

2 files changed

+110
-24
lines changed

2 files changed

+110
-24
lines changed

Lib/test/test_import/__init__.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -790,6 +790,28 @@ def test_issue105979(self):
790790
self.assertIn("Frozen object named 'x' is invalid",
791791
str(cm.exception))
792792

793+
def test_delete_sys_stdlib_module_names(self):
794+
args = ["-c", "import sys; del sys.stdlib_module_names; sys.stdlib_module_names"]
795+
popen = script_helper.spawn_python(*args)
796+
stdout, stderr = popen.communicate()
797+
self.assertIn(
798+
b"AttributeError: module 'sys' has no attribute 'stdlib_module_names'",
799+
stdout
800+
)
801+
802+
def test_cwd_script_shadowing_stdlib(self):
803+
with CleanImport('collections'):
804+
import collections
805+
collections.__spec__ = types.SimpleNamespace()
806+
collections.__spec__.origin = os.path.join(os.getcwd(), 'collections.py')
807+
with self.assertRaisesRegex(
808+
AttributeError,
809+
r"module 'collections' has no attribute 'does_not_exist' \(most "
810+
r"likely due to '.*collections.py' shadowing the standard "
811+
r"library module named 'collections'\)"
812+
):
813+
collections.does_not_exist
814+
793815

794816
@skip_if_dont_write_bytecode
795817
class FilePermissionTests(unittest.TestCase):

Objects/moduleobject.c

Lines changed: 88 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -848,39 +848,103 @@ _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress)
848848
Py_DECREF(origin);
849849
}
850850

851-
int rc = _PyModuleSpec_IsInitializing(spec);
852-
if (rc > 0) {
853-
if (valid_origin == 1) {
854-
PyErr_Format(PyExc_AttributeError,
855-
"partially initialized "
856-
"module '%U' from '%U' has no attribute '%U' "
857-
"(most likely due to a circular import)",
858-
mod_name, origin, name);
859-
}
860-
else {
861-
PyErr_Format(PyExc_AttributeError,
862-
"partially initialized "
863-
"module '%U' has no attribute '%U' "
864-
"(most likely due to a circular import)",
865-
mod_name, name);
851+
int is_script_shadowing_stdlib = 0;
852+
// Check mod.__name__ in sys.stdlib_module_names
853+
// and os.path.dirname(mod.__spec__.origin) == os.getcwd()
854+
PyObject *stdlib = NULL;
855+
if (valid_origin == 1) {
856+
if (
857+
// avoid bad recursion
858+
PyUnicode_CompareWithASCIIString(mod_name, "sys") != 0
859+
&& PyUnicode_CompareWithASCIIString(mod_name, "os") != 0
860+
&& PyUnicode_CompareWithASCIIString(mod_name, "builtins") != 0
861+
) {
862+
stdlib = _PyImport_GetModuleAttrString("sys", "stdlib_module_names");
863+
if (!stdlib) {
864+
goto done;
865+
}
866+
if (PySet_Contains(stdlib, mod_name)) {
867+
PyObject *os_path = _PyImport_GetModuleAttrString("os", "path");
868+
if (!os_path) {
869+
goto done;
870+
}
871+
PyObject *dirname = PyObject_GetAttrString(os_path, "dirname");
872+
Py_DECREF(os_path);
873+
if (!dirname) {
874+
goto done;
875+
}
876+
PyObject *origin_dir = _PyObject_CallOneArg(dirname, origin);
877+
Py_DECREF(dirname);
878+
if (!origin_dir) {
879+
goto done;
880+
}
881+
882+
PyObject *getcwd = _PyImport_GetModuleAttrString("os", "getcwd");
883+
if (!getcwd) {
884+
Py_DECREF(origin_dir);
885+
goto done;
886+
}
887+
PyObject *cwd = _PyObject_CallNoArgs(getcwd);
888+
Py_DECREF(getcwd);
889+
if (!cwd) {
890+
Py_DECREF(origin_dir);
891+
goto done;
892+
}
893+
894+
is_script_shadowing_stdlib = PyObject_RichCompareBool(origin_dir, cwd, Py_EQ);
895+
Py_DECREF(origin_dir);
896+
Py_DECREF(cwd);
897+
if (is_script_shadowing_stdlib < 0) {
898+
goto done;
899+
}
900+
}
866901
}
867902
}
868-
else if (rc == 0) {
869-
rc = _PyModuleSpec_IsUninitializedSubmodule(spec, name);
903+
904+
if (is_script_shadowing_stdlib == 1) {
905+
PyErr_Format(PyExc_AttributeError,
906+
"module '%U' has no attribute '%U' "
907+
"(most likely due to '%U' shadowing the standard library "
908+
"module named '%U')",
909+
mod_name, name, origin, mod_name);
910+
} else {
911+
int rc = _PyModuleSpec_IsInitializing(spec);
870912
if (rc > 0) {
871-
PyErr_Format(PyExc_AttributeError,
872-
"cannot access submodule '%U' of module '%U' "
873-
"(most likely due to a circular import)",
874-
name, mod_name);
913+
if (valid_origin == 1) {
914+
PyErr_Format(PyExc_AttributeError,
915+
"partially initialized "
916+
"module '%U' from '%U' has no attribute '%U' "
917+
"(most likely due to a circular import)",
918+
mod_name, origin, name);
919+
}
920+
else {
921+
PyErr_Format(PyExc_AttributeError,
922+
"partially initialized "
923+
"module '%U' has no attribute '%U' "
924+
"(most likely due to a circular import)",
925+
mod_name, name);
926+
}
875927
}
876928
else if (rc == 0) {
877-
PyErr_Format(PyExc_AttributeError,
878-
"module '%U' has no attribute '%U'",
879-
mod_name, name);
929+
rc = _PyModuleSpec_IsUninitializedSubmodule(spec, name);
930+
if (rc > 0) {
931+
PyErr_Format(PyExc_AttributeError,
932+
"cannot access submodule '%U' of module '%U' "
933+
"(most likely due to a circular import)",
934+
name, mod_name);
935+
}
936+
else if (rc == 0) {
937+
PyErr_Format(PyExc_AttributeError,
938+
"module '%U' has no attribute '%U'",
939+
mod_name, name);
940+
}
880941
}
881942
}
943+
944+
done:
882945
Py_XDECREF(spec);
883946
Py_XDECREF(origin);
947+
Py_XDECREF(stdlib);
884948
Py_DECREF(mod_name);
885949
return NULL;
886950
}

0 commit comments

Comments
 (0)