Skip to content

Commit 35a69fe

Browse files
committed
Check for script shadowing stdlib
1 parent afe03da commit 35a69fe

File tree

2 files changed

+137
-31
lines changed

2 files changed

+137
-31
lines changed

Lib/test/test_import/__init__.py

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

793+
def test_cwd_script_shadowing_stdlib(self):
794+
with CleanImport('collections'):
795+
import collections
796+
collections.__spec__ = types.SimpleNamespace()
797+
collections.__spec__.origin = os.path.join(os.getcwd(), 'collections.py')
798+
with self.assertRaisesRegex(
799+
AttributeError,
800+
r"module 'collections' has no attribute 'does_not_exist' \(most "
801+
r"likely due to '.*collections.py' shadowing the standard "
802+
r"library module named 'collections'\)"
803+
):
804+
collections.does_not_exist
805+
806+
def test_delete_sys_stdlib_module_names(self):
807+
with CleanImport('collections'):
808+
import collections
809+
collections.__spec__ = types.SimpleNamespace()
810+
collections.__spec__.origin = os.path.join(os.getcwd(), 'collections.py')
811+
with CleanImport('sys'):
812+
import sys
813+
sys.stdlib_module_names = None
814+
with self.assertRaisesRegex(
815+
AttributeError,
816+
r"module 'collections' has no attribute 'does_not_exist'"
817+
):
818+
collections.does_not_exist
819+
820+
del sys.stdlib_module_names
821+
with self.assertRaisesRegex(
822+
AttributeError,
823+
r"module 'collections' has no attribute 'does_not_exist'"
824+
):
825+
collections.does_not_exist
826+
793827

794828
@skip_if_dont_write_bytecode
795829
class FilePermissionTests(unittest.TestCase):

Objects/moduleobject.c

Lines changed: 103 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -788,7 +788,7 @@ PyObject*
788788
_Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress)
789789
{
790790
// When suppress=1, this function suppresses AttributeError.
791-
PyObject *attr, *mod_name, *getattr, *origin;
791+
PyObject *attr, *mod_name, *getattr;
792792
attr = _PyObject_GenericGetAttrWithDict((PyObject *)m, name, NULL, suppress);
793793
if (attr) {
794794
return attr;
@@ -837,48 +837,120 @@ _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress)
837837
Py_DECREF(mod_name);
838838
return NULL;
839839
}
840-
int rc = _PyModuleSpec_IsInitializing(spec);
841-
if (rc > 0) {
842-
int valid_spec = PyObject_GetOptionalAttr(spec, &_Py_ID(origin), &origin);
843-
if (valid_spec == -1) {
844-
Py_XDECREF(spec);
840+
PyObject *origin = NULL;
841+
if (spec) {
842+
int rc = PyObject_GetOptionalAttr(spec, &_Py_ID(origin), &origin);
843+
if (rc == -1) {
844+
Py_DECREF(spec);
845845
Py_DECREF(mod_name);
846846
return NULL;
847847
}
848-
if (valid_spec == 1 && !PyUnicode_Check(origin)) {
849-
valid_spec = 0;
850-
Py_DECREF(origin);
851-
}
852-
if (valid_spec == 1) {
853-
PyErr_Format(PyExc_AttributeError,
854-
"partially initialized "
855-
"module '%U' from '%U' has no attribute '%U' "
856-
"(most likely due to a circular import)",
857-
mod_name, origin, name);
848+
if (rc == 1 && !PyUnicode_Check(origin)) {
858849
Py_DECREF(origin);
850+
origin = NULL;
859851
}
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);
852+
}
853+
854+
int is_script_shadowing_stdlib = 0;
855+
// Check mod.__name__ in sys.stdlib_module_names
856+
// and os.path.dirname(mod.__spec__.origin) == os.getcwd()
857+
PyObject *stdlib = NULL;
858+
if (origin) {
859+
if (
860+
// avoid bad recursion
861+
PyUnicode_CompareWithASCIIString(mod_name, "sys") != 0
862+
&& PyUnicode_CompareWithASCIIString(mod_name, "os") != 0
863+
&& PyUnicode_CompareWithASCIIString(mod_name, "builtins") != 0
864+
) {
865+
stdlib = _PyImport_GetModuleAttrString("sys", "stdlib_module_names");
866+
if (!stdlib) {
867+
if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
868+
PyErr_Clear();
869+
} else {
870+
goto done;
871+
}
872+
}
873+
if (stdlib && PyFrozenSet_Check(stdlib) && PySet_Contains(stdlib, mod_name)) {
874+
PyObject *os_path = _PyImport_GetModuleAttrString("os", "path");
875+
if (!os_path) {
876+
goto done;
877+
}
878+
PyObject *dirname = PyObject_GetAttrString(os_path, "dirname");
879+
Py_DECREF(os_path);
880+
if (!dirname) {
881+
goto done;
882+
}
883+
PyObject *origin_dir = _PyObject_CallOneArg(dirname, origin);
884+
Py_DECREF(dirname);
885+
if (!origin_dir) {
886+
goto done;
887+
}
888+
889+
PyObject *getcwd = _PyImport_GetModuleAttrString("os", "getcwd");
890+
if (!getcwd) {
891+
Py_DECREF(origin_dir);
892+
goto done;
893+
}
894+
PyObject *cwd = _PyObject_CallNoArgs(getcwd);
895+
Py_DECREF(getcwd);
896+
if (!cwd) {
897+
Py_DECREF(origin_dir);
898+
goto done;
899+
}
900+
901+
is_script_shadowing_stdlib = PyObject_RichCompareBool(origin_dir, cwd, Py_EQ);
902+
Py_DECREF(origin_dir);
903+
Py_DECREF(cwd);
904+
if (is_script_shadowing_stdlib < 0) {
905+
goto done;
906+
}
907+
}
866908
}
867909
}
868-
else if (rc == 0) {
869-
rc = _PyModuleSpec_IsUninitializedSubmodule(spec, name);
910+
911+
if (is_script_shadowing_stdlib == 1) {
912+
PyErr_Format(PyExc_AttributeError,
913+
"module '%U' has no attribute '%U' "
914+
"(most likely due to '%U' shadowing the standard library "
915+
"module named '%U')",
916+
mod_name, name, origin, mod_name);
917+
} else {
918+
int rc = _PyModuleSpec_IsInitializing(spec);
870919
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);
920+
if (origin) {
921+
PyErr_Format(PyExc_AttributeError,
922+
"partially initialized "
923+
"module '%U' from '%U' has no attribute '%U' "
924+
"(most likely due to a circular import)",
925+
mod_name, origin, name);
926+
}
927+
else {
928+
PyErr_Format(PyExc_AttributeError,
929+
"partially initialized "
930+
"module '%U' has no attribute '%U' "
931+
"(most likely due to a circular import)",
932+
mod_name, name);
933+
}
875934
}
876935
else if (rc == 0) {
877-
PyErr_Format(PyExc_AttributeError,
878-
"module '%U' has no attribute '%U'",
879-
mod_name, name);
936+
rc = _PyModuleSpec_IsUninitializedSubmodule(spec, name);
937+
if (rc > 0) {
938+
PyErr_Format(PyExc_AttributeError,
939+
"cannot access submodule '%U' of module '%U' "
940+
"(most likely due to a circular import)",
941+
name, mod_name);
942+
}
943+
else if (rc == 0) {
944+
PyErr_Format(PyExc_AttributeError,
945+
"module '%U' has no attribute '%U'",
946+
mod_name, name);
947+
}
880948
}
881949
}
950+
951+
done:
952+
Py_XDECREF(stdlib);
953+
Py_XDECREF(origin);
882954
Py_XDECREF(spec);
883955
Py_DECREF(mod_name);
884956
return NULL;

0 commit comments

Comments
 (0)