]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-95754: Better error when script shadows a standard library or third party module...
authorShantanu <12621235+hauntsaninja@users.noreply.github.com>
Tue, 23 Apr 2024 01:24:21 +0000 (18:24 -0700)
committerGitHub <noreply@github.com>
Tue, 23 Apr 2024 01:24:21 +0000 (18:24 -0700)
Doc/whatsnew/3.13.rst
Include/internal/pycore_global_objects_fini_generated.h
Include/internal/pycore_global_strings.h
Include/internal/pycore_runtime_init_generated.h
Include/internal/pycore_unicodeobject_generated.h
Lib/test/test_import/__init__.py
Misc/NEWS.d/next/Core and Builtins/2024-01-07-03-38-34.gh-issue-95754.aPjEBG.rst [new file with mode: 0644]
Objects/moduleobject.c

index 67d1956a19697e63e850b0490fe72af74c0a1194..89694afdfa3fece7cd45effb4ba9c3695f57dbcc 100644 (file)
@@ -104,6 +104,40 @@ Improved Error Messages
   variables. See also :ref:`using-on-controlling-color`.
   (Contributed by Pablo Galindo Salgado in :gh:`112730`.)
 
+* A common mistake is to write a script with the same name as a
+  standard library module. When this results in errors, we now
+  display a more helpful error message:
+
+  .. code-block:: shell-session
+
+    $ python random.py
+    Traceback (most recent call last):
+      File "/home/random.py", line 1, in <module>
+        import random; print(random.randint(5))
+        ^^^^^^^^^^^^^
+      File "/home/random.py", line 1, in <module>
+        import random; print(random.randint(5))
+                            ^^^^^^^^^^^^^^
+    AttributeError: module 'random' has no attribute 'randint' (consider renaming '/home/random.py' since it has the same name as the standard library module named 'random' and the import system gives it precedence)
+
+  Similarly, if a script has the same name as a third-party
+  module it attempts to import, and this results in errors,
+  we also display a more helpful error message:
+
+  .. code-block:: shell-session
+
+    $ python numpy.py
+    Traceback (most recent call last):
+      File "/home/numpy.py", line 1, in <module>
+        import numpy as np; np.array([1,2,3])
+        ^^^^^^^^^^^^^^^^^^
+      File "/home/numpy.py", line 1, in <module>
+        import numpy as np; np.array([1,2,3])
+                            ^^^^^^^^
+    AttributeError: module 'numpy' has no attribute 'array' (consider renaming '/home/numpy.py' if it has the same name as a third-party module you intended to import)
+
+  (Contributed by Shantanu Jain in :gh:`95754`.)
+
 * When an incorrect keyword argument is passed to a function, the error message
   now potentially suggests the correct keyword argument.
   (Contributed by Pablo Galindo Salgado and Shantanu Jain in :gh:`107944`.)
index 90a338ade17c61bde361af8e7de35e2970f6c236..4a6f40c84088e84a0fc2005a8dcbc222d78f3d85 100644 (file)
@@ -981,6 +981,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(h));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(handle));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(handle_seq));
+    _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(has_location));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(hash_name));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(header));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(headers));
index 0899e7ee776617a89324c18530c4151e2d45eecc..8332cdf874c0c988f7ef16e519b0e74cccf29621 100644 (file)
@@ -470,6 +470,7 @@ struct _Py_global_strings {
         STRUCT_FOR_ID(h)
         STRUCT_FOR_ID(handle)
         STRUCT_FOR_ID(handle_seq)
+        STRUCT_FOR_ID(has_location)
         STRUCT_FOR_ID(hash_name)
         STRUCT_FOR_ID(header)
         STRUCT_FOR_ID(headers)
index d4323e5bd12a67e7c7ed90c82ada438708ae9679..103279a4cf228b1996582b23d214c59805c8e831 100644 (file)
@@ -979,6 +979,7 @@ extern "C" {
     INIT_ID(h), \
     INIT_ID(handle), \
     INIT_ID(handle_seq), \
+    INIT_ID(has_location), \
     INIT_ID(hash_name), \
     INIT_ID(header), \
     INIT_ID(headers), \
index 9daef267069d0d106d08f84690b20a64fcd243c9..a180054d407b39a6a15483b518c2613d56e7848e 100644 (file)
@@ -1251,6 +1251,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
     string = &_Py_ID(handle_seq);
     assert(_PyUnicode_CheckConsistency(string, 1));
     _PyUnicode_InternInPlace(interp, &string);
+    string = &_Py_ID(has_location);
+    assert(_PyUnicode_CheckConsistency(string, 1));
+    _PyUnicode_InternInPlace(interp, &string);
     string = &_Py_ID(hash_name);
     assert(_PyUnicode_CheckConsistency(string, 1));
     _PyUnicode_InternInPlace(interp, &string);
index 469d1fbe59aaa2d8ec9f360903a9727850b874ea..947a7b19056bdbf83211b0f96e0afca5b888d133 100644 (file)
@@ -804,6 +804,227 @@ class ImportTests(unittest.TestCase):
         self.assertIn("Frozen object named 'x' is invalid",
                       str(cm.exception))
 
+    def test_script_shadowing_stdlib(self):
+        with os_helper.temp_dir() as tmp:
+            with open(os.path.join(tmp, "fractions.py"), "w", encoding='utf-8') as f:
+                f.write("import fractions\nfractions.Fraction")
+
+            expected_error = (
+                rb"AttributeError: module 'fractions' has no attribute 'Fraction' "
+                rb"\(consider renaming '.*fractions.py' since it has the "
+                rb"same name as the standard library module named 'fractions' "
+                rb"and the import system gives it precedence\)"
+            )
+
+            popen = script_helper.spawn_python(os.path.join(tmp, "fractions.py"), cwd=tmp)
+            stdout, stderr = popen.communicate()
+            self.assertRegex(stdout, expected_error)
+
+            popen = script_helper.spawn_python('-m', 'fractions', cwd=tmp)
+            stdout, stderr = popen.communicate()
+            self.assertRegex(stdout, expected_error)
+
+            popen = script_helper.spawn_python('-c', 'import fractions', cwd=tmp)
+            stdout, stderr = popen.communicate()
+            self.assertRegex(stdout, expected_error)
+
+            # and there's no error at all when using -P
+            popen = script_helper.spawn_python('-P', 'fractions.py', cwd=tmp)
+            stdout, stderr = popen.communicate()
+            self.assertEqual(stdout, b'')
+
+            tmp_child = os.path.join(tmp, "child")
+            os.mkdir(tmp_child)
+
+            # test the logic with different cwd
+            popen = script_helper.spawn_python(os.path.join(tmp, "fractions.py"), cwd=tmp_child)
+            stdout, stderr = popen.communicate()
+            self.assertRegex(stdout, expected_error)
+
+            popen = script_helper.spawn_python('-m', 'fractions', cwd=tmp_child)
+            stdout, stderr = popen.communicate()
+            self.assertEqual(stdout, b'')  # no error
+
+            popen = script_helper.spawn_python('-c', 'import fractions', cwd=tmp_child)
+            stdout, stderr = popen.communicate()
+            self.assertEqual(stdout, b'')  # no error
+
+    def test_package_shadowing_stdlib_module(self):
+        with os_helper.temp_dir() as tmp:
+            os.mkdir(os.path.join(tmp, "fractions"))
+            with open(os.path.join(tmp, "fractions", "__init__.py"), "w", encoding='utf-8') as f:
+                f.write("shadowing_module = True")
+            with open(os.path.join(tmp, "main.py"), "w", encoding='utf-8') as f:
+                f.write("""
+import fractions
+fractions.shadowing_module
+fractions.Fraction
+""")
+
+            expected_error = (
+                rb"AttributeError: module 'fractions' has no attribute 'Fraction' "
+                rb"\(consider renaming '.*fractions.__init__.py' since it has the "
+                rb"same name as the standard library module named 'fractions' "
+                rb"and the import system gives it precedence\)"
+            )
+
+            popen = script_helper.spawn_python(os.path.join(tmp, "main.py"), cwd=tmp)
+            stdout, stderr = popen.communicate()
+            self.assertRegex(stdout, expected_error)
+
+            popen = script_helper.spawn_python('-m', 'main', cwd=tmp)
+            stdout, stderr = popen.communicate()
+            self.assertRegex(stdout, expected_error)
+
+            # and there's no shadowing at all when using -P
+            popen = script_helper.spawn_python('-P', 'main.py', cwd=tmp)
+            stdout, stderr = popen.communicate()
+            self.assertRegex(stdout, b"module 'fractions' has no attribute 'shadowing_module'")
+
+    def test_script_shadowing_third_party(self):
+        with os_helper.temp_dir() as tmp:
+            with open(os.path.join(tmp, "numpy.py"), "w", encoding='utf-8') as f:
+                f.write("import numpy\nnumpy.array")
+
+            expected_error = (
+                rb"AttributeError: module 'numpy' has no attribute 'array' "
+                rb"\(consider renaming '.*numpy.py' if it has the "
+                rb"same name as a third-party module you intended to import\)\s+\Z"
+            )
+
+            popen = script_helper.spawn_python(os.path.join(tmp, "numpy.py"))
+            stdout, stderr = popen.communicate()
+            self.assertRegex(stdout, expected_error)
+
+            popen = script_helper.spawn_python('-m', 'numpy', cwd=tmp)
+            stdout, stderr = popen.communicate()
+            self.assertRegex(stdout, expected_error)
+
+            popen = script_helper.spawn_python('-c', 'import numpy', cwd=tmp)
+            stdout, stderr = popen.communicate()
+            self.assertRegex(stdout, expected_error)
+
+    def test_script_maybe_not_shadowing_third_party(self):
+        with os_helper.temp_dir() as tmp:
+            with open(os.path.join(tmp, "numpy.py"), "w", encoding='utf-8') as f:
+                f.write("this_script_does_not_attempt_to_import_numpy = True")
+
+            expected_error = (
+                rb"AttributeError: module 'numpy' has no attribute 'attr'\s+\Z"
+            )
+
+            popen = script_helper.spawn_python('-c', 'import numpy; numpy.attr', cwd=tmp)
+            stdout, stderr = popen.communicate()
+            self.assertRegex(stdout, expected_error)
+
+    def test_script_shadowing_stdlib_edge_cases(self):
+        with os_helper.temp_dir() as tmp:
+            with open(os.path.join(tmp, "fractions.py"), "w", encoding='utf-8') as f:
+                f.write("shadowing_module = True")
+            with open(os.path.join(tmp, "main.py"), "w", encoding='utf-8') as f:
+                f.write("""
+import fractions
+fractions.shadowing_module
+class substr(str):
+    __hash__ = None
+fractions.__name__ = substr('fractions')
+try:
+    fractions.Fraction
+except TypeError as e:
+    print(str(e))
+""")
+
+            popen = script_helper.spawn_python("main.py", cwd=tmp)
+            stdout, stderr = popen.communicate()
+            self.assertEqual(stdout.rstrip(), b"unhashable type: 'substr'")
+
+            with open(os.path.join(tmp, "main.py"), "w", encoding='utf-8') as f:
+                f.write("""
+import fractions
+fractions.shadowing_module
+
+import sys
+sys.stdlib_module_names = None
+try:
+    fractions.Fraction
+except AttributeError as e:
+    print(str(e))
+
+del sys.stdlib_module_names
+try:
+    fractions.Fraction
+except AttributeError as e:
+    print(str(e))
+
+sys.path = [0]
+try:
+    fractions.Fraction
+except AttributeError as e:
+    print(str(e))
+""")
+
+            popen = script_helper.spawn_python("main.py", cwd=tmp)
+            stdout, stderr = popen.communicate()
+            self.assertEqual(
+                stdout.splitlines(),
+                [
+                    b"module 'fractions' has no attribute 'Fraction'",
+                    b"module 'fractions' has no attribute 'Fraction'",
+                    b"module 'fractions' has no attribute 'Fraction'",
+                ],
+            )
+
+            with open(os.path.join(tmp, "main.py"), "w", encoding='utf-8') as f:
+                f.write("""
+import fractions
+fractions.shadowing_module
+del fractions.__spec__.origin
+try:
+    fractions.Fraction
+except AttributeError as e:
+    print(str(e))
+
+fractions.__spec__.origin = 0
+try:
+    fractions.Fraction
+except AttributeError as e:
+    print(str(e))
+""")
+
+            popen = script_helper.spawn_python("main.py", cwd=tmp)
+            stdout, stderr = popen.communicate()
+            self.assertEqual(
+                stdout.splitlines(),
+                [
+                    b"module 'fractions' has no attribute 'Fraction'",
+                    b"module 'fractions' has no attribute 'Fraction'"
+                ],
+            )
+
+    def test_script_shadowing_stdlib_sys_path_modification(self):
+        with os_helper.temp_dir() as tmp:
+            with open(os.path.join(tmp, "fractions.py"), "w", encoding='utf-8') as f:
+                f.write("shadowing_module = True")
+
+            expected_error = (
+                rb"AttributeError: module 'fractions' has no attribute 'Fraction' "
+                rb"\(consider renaming '.*fractions.py' since it has the "
+                rb"same name as the standard library module named 'fractions' "
+                rb"and the import system gives it precedence\)"
+            )
+
+            with open(os.path.join(tmp, "main.py"), "w", encoding='utf-8') as f:
+                f.write("""
+import sys
+sys.path.insert(0, "this_folder_does_not_exist")
+import fractions
+fractions.Fraction
+""")
+
+            popen = script_helper.spawn_python("main.py", cwd=tmp)
+            stdout, stderr = popen.communicate()
+            self.assertRegex(stdout, expected_error)
+
 
 @skip_if_dont_write_bytecode
 class FilePermissionTests(unittest.TestCase):
diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-07-03-38-34.gh-issue-95754.aPjEBG.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-07-03-38-34.gh-issue-95754.aPjEBG.rst
new file mode 100644 (file)
index 0000000..588be2d
--- /dev/null
@@ -0,0 +1,4 @@
+Improve the error message when a script shadowing a module from the standard
+library causes :exc:`AttributeError` to be raised. Similarly, improve the error
+message when a script shadowing a third party module attempts to access an
+attribute from that third party module while still initialising.
index da6a276c41be1fe8bb940ed54f29ed08cc7daeb3..2f6adb9a2e12be52c60d38cf1c463a541aefc213 100644 (file)
@@ -3,6 +3,7 @@
 
 #include "Python.h"
 #include "pycore_call.h"          // _PyObject_CallNoArgs()
+#include "pycore_fileutils.h"     // _Py_wgetcwd
 #include "pycore_interp.h"        // PyInterpreterState.importlib
 #include "pycore_modsupport.h"    // _PyModule_CreateInitialized()
 #include "pycore_moduleobject.h"  // _PyModule_GetDef()
@@ -10,6 +11,7 @@
 #include "pycore_pyerrors.h"      // _PyErr_FormatFromCause()
 #include "pycore_pystate.h"       // _PyInterpreterState_GET()
 
+#include "osdefs.h"               // MAXPATHLEN
 
 
 static PyMemberDef module_members[] = {
@@ -785,11 +787,104 @@ _PyModuleSpec_IsUninitializedSubmodule(PyObject *spec, PyObject *name)
     return rc;
 }
 
+static int
+_get_file_origin_from_spec(PyObject *spec, PyObject **p_origin)
+{
+    PyObject *has_location = NULL;
+    int rc = PyObject_GetOptionalAttr(spec, &_Py_ID(has_location), &has_location);
+    if (rc <= 0) {
+        return rc;
+    }
+    // If origin is not a location, or doesn't exist, or is not a str), we could consider falling
+    // back to module.__file__. But the cases in which module.__file__ is not __spec__.origin
+    // are cases in which we probably shouldn't be guessing.
+    rc = PyObject_IsTrue(has_location);
+    Py_DECREF(has_location);
+    if (rc <= 0) {
+        return rc;
+    }
+    // has_location is true, so origin is a location
+    PyObject *origin = NULL;
+    rc = PyObject_GetOptionalAttr(spec, &_Py_ID(origin), &origin);
+    if (rc <= 0) {
+        return rc;
+    }
+    assert(origin != NULL);
+    if (!PyUnicode_Check(origin)) {
+        Py_DECREF(origin);
+        return 0;
+    }
+    *p_origin = origin;
+    return 1;
+}
+
+static int
+_is_module_possibly_shadowing(PyObject *origin)
+{
+    // origin must be a unicode subtype
+    // Returns 1 if the module at origin could be shadowing a module of the
+    // same name later in the module search path. The condition we check is basically:
+    // root = os.path.dirname(origin.removesuffix(os.sep + "__init__.py"))
+    // return not sys.flags.safe_path and root == (sys.path[0] or os.getcwd())
+    // Returns 0 otherwise (or if we aren't sure)
+    // Returns -1 if an error occurred that should be propagated
+    if (origin == NULL) {
+        return 0;
+    }
+
+    // not sys.flags.safe_path
+    const PyConfig *config = _Py_GetConfig();
+    if (config->safe_path) {
+        return 0;
+    }
+
+    // root = os.path.dirname(origin.removesuffix(os.sep + "__init__.py"))
+    wchar_t root[MAXPATHLEN + 1];
+    Py_ssize_t size = PyUnicode_AsWideChar(origin, root, MAXPATHLEN);
+    if (size < 0) {
+        return -1;
+    }
+    assert(size <= MAXPATHLEN);
+    root[size] = L'\0';
+
+    wchar_t *sep = wcsrchr(root, SEP);
+    if (sep == NULL) {
+        return 0;
+    }
+    // If it's a package then we need to look one directory further up
+    if (wcscmp(sep + 1, L"__init__.py") == 0) {
+        *sep = L'\0';
+        sep = wcsrchr(root, SEP);
+        if (sep == NULL) {
+            return 0;
+        }
+    }
+    *sep = L'\0';
+
+    // sys.path[0] or os.getcwd()
+    wchar_t *sys_path_0 = config->sys_path_0;
+    if (!sys_path_0) {
+        return 0;
+    }
+
+    wchar_t sys_path_0_buf[MAXPATHLEN];
+    if (sys_path_0[0] == L'\0') {
+        // if sys.path[0] == "", treat it as if it were the current directory
+        if (!_Py_wgetcwd(sys_path_0_buf, MAXPATHLEN)) {
+            return -1;
+        }
+        sys_path_0 = sys_path_0_buf;
+    }
+
+    int result = wcscmp(sys_path_0, root) == 0;
+    return result;
+}
+
 PyObject*
 _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress)
 {
     // When suppress=1, this function suppresses AttributeError.
-    PyObject *attr, *mod_name, *getattr, *origin;
+    PyObject *attr, *mod_name, *getattr;
     attr = _PyObject_GenericGetAttrWithDict((PyObject *)m, name, NULL, suppress);
     if (attr) {
         return attr;
@@ -820,68 +915,111 @@ _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress)
         Py_DECREF(getattr);
         return result;
     }
+
+    // The attribute was not found.  We make a best effort attempt at a useful error message,
+    // but only if we're not suppressing AttributeError.
+    if (suppress == 1) {
+        return NULL;
+    }
     if (PyDict_GetItemRef(m->md_dict, &_Py_ID(__name__), &mod_name) < 0) {
         return NULL;
     }
-    if (mod_name && PyUnicode_Check(mod_name)) {
-        PyObject *spec;
-        if (PyDict_GetItemRef(m->md_dict, &_Py_ID(__spec__), &spec) < 0) {
-            Py_DECREF(mod_name);
-            return NULL;
+    if (!mod_name || !PyUnicode_Check(mod_name)) {
+        Py_XDECREF(mod_name);
+        PyErr_Format(PyExc_AttributeError,
+                    "module has no attribute '%U'", name);
+        return NULL;
+    }
+    PyObject *spec;
+    if (PyDict_GetItemRef(m->md_dict, &_Py_ID(__spec__), &spec) < 0) {
+        Py_DECREF(mod_name);
+        return NULL;
+    }
+    if (spec == NULL) {
+        PyErr_Format(PyExc_AttributeError,
+                     "module '%U' has no attribute '%U'",
+                     mod_name, name);
+        Py_DECREF(mod_name);
+        return NULL;
+    }
+
+    PyObject *origin = NULL;
+    if (_get_file_origin_from_spec(spec, &origin) < 0) {
+        goto done;
+    }
+
+    int is_possibly_shadowing = _is_module_possibly_shadowing(origin);
+    if (is_possibly_shadowing < 0) {
+        goto done;
+    }
+    int is_possibly_shadowing_stdlib = 0;
+    if (is_possibly_shadowing) {
+        PyObject *stdlib_modules = PySys_GetObject("stdlib_module_names");
+        if (stdlib_modules && PyAnySet_Check(stdlib_modules)) {
+            is_possibly_shadowing_stdlib = PySet_Contains(stdlib_modules, mod_name);
+            if (is_possibly_shadowing_stdlib < 0) {
+                goto done;
+            }
+        }
+    }
+
+    if (is_possibly_shadowing_stdlib) {
+        assert(origin);
+        PyErr_Format(PyExc_AttributeError,
+                    "module '%U' has no attribute '%U' "
+                    "(consider renaming '%U' since it has the same "
+                    "name as the standard library module named '%U' "
+                    "and the import system gives it precedence)",
+                    mod_name, name, origin, mod_name);
+    }
+    else {
+        int rc = _PyModuleSpec_IsInitializing(spec);
+        if (rc > 0) {
+            if (is_possibly_shadowing) {
+                assert(origin);
+                // For third-party modules, only mention the possibility of
+                // shadowing if the module is being initialized.
+                PyErr_Format(PyExc_AttributeError,
+                            "module '%U' has no attribute '%U' "
+                            "(consider renaming '%U' if it has the same name "
+                            "as a third-party module you intended to import)",
+                            mod_name, name, origin);
+            }
+            else if (origin) {
+                PyErr_Format(PyExc_AttributeError,
+                            "partially initialized "
+                            "module '%U' from '%U' has no attribute '%U' "
+                            "(most likely due to a circular import)",
+                            mod_name, origin, name);
+            }
+            else {
+                PyErr_Format(PyExc_AttributeError,
+                            "partially initialized "
+                            "module '%U' has no attribute '%U' "
+                            "(most likely due to a circular import)",
+                            mod_name, name);
+            }
         }
-        if (suppress != 1) {
-            int rc = _PyModuleSpec_IsInitializing(spec);
+        else if (rc == 0) {
+            rc = _PyModuleSpec_IsUninitializedSubmodule(spec, name);
             if (rc > 0) {
-                int valid_spec = PyObject_GetOptionalAttr(spec, &_Py_ID(origin), &origin);
-                if (valid_spec == -1) {
-                    Py_XDECREF(spec);
-                    Py_DECREF(mod_name);
-                    return NULL;
-                }
-                if (valid_spec == 1 && !PyUnicode_Check(origin)) {
-                    valid_spec = 0;
-                    Py_DECREF(origin);
-                }
-                if (valid_spec == 1) {
-                    PyErr_Format(PyExc_AttributeError,
-                                "partially initialized "
-                                "module '%U' from '%U' has no attribute '%U' "
-                                "(most likely due to a circular import)",
-                                mod_name, origin, name);
-                    Py_DECREF(origin);
-                }
-                else {
-                    PyErr_Format(PyExc_AttributeError,
-                                "partially initialized "
-                                "module '%U' has no attribute '%U' "
-                                "(most likely due to a circular import)",
-                                mod_name, name);
-                }
+                PyErr_Format(PyExc_AttributeError,
+                            "cannot access submodule '%U' of module '%U' "
+                            "(most likely due to a circular import)",
+                            name, mod_name);
             }
             else if (rc == 0) {
-                rc = _PyModuleSpec_IsUninitializedSubmodule(spec, name);
-                if (rc > 0) {
-                    PyErr_Format(PyExc_AttributeError,
-                                "cannot access submodule '%U' of module '%U' "
-                                "(most likely due to a circular import)",
-                                name, mod_name);
-                }
-                else if (rc == 0) {
-                    PyErr_Format(PyExc_AttributeError,
-                                "module '%U' has no attribute '%U'",
-                                mod_name, name);
-                }
+                PyErr_Format(PyExc_AttributeError,
+                            "module '%U' has no attribute '%U'",
+                            mod_name, name);
             }
         }
-        Py_XDECREF(spec);
-        Py_DECREF(mod_name);
-        return NULL;
-    }
-    Py_XDECREF(mod_name);
-    if (suppress != 1) {
-        PyErr_Format(PyExc_AttributeError,
-                    "module has no attribute '%U'", name);
     }
+
+done:
+    Py_XDECREF(origin);
+    Py_DECREF(spec);
+    Py_DECREF(mod_name);
     return NULL;
 }