]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-46940: Don't override existing AttributeError suggestion information (GH-31710)
authorPablo Galindo Salgado <Pablogsal@gmail.com>
Mon, 7 Mar 2022 12:23:11 +0000 (12:23 +0000)
committerGitHub <noreply@github.com>
Mon, 7 Mar 2022 12:23:11 +0000 (12:23 +0000)
When an exception is created in a nested call to PyObject_GetAttr, any
external calls will override the context information of the
AttributeError that we have already placed in the most internal call.
This will cause the suggestions we create to nor work properly as the
attribute name and object that we will be using are the incorrect ones.

To avoid this, we need to check first if these attributes are already
set and bail out if that's the case.

Lib/test/test_exceptions.py
Misc/NEWS.d/next/Core and Builtins/2022-03-06-20-16-13.bpo-46940._X47Hx.rst [new file with mode: 0644]
Objects/object.c
Python/ceval.c

index e3897a0d89c5ea765f6f0f3fa2a9de18cf994767..a75b7fae5518e067c81d27d2893323b7a82f6cd9 100644 (file)
@@ -2272,6 +2272,24 @@ class AttributeErrorTests(unittest.TestCase):
 
         self.assertNotIn("?", err.getvalue())
 
+    def test_attribute_error_inside_nested_getattr(self):
+        class A:
+            bluch = 1
+
+        class B:
+            def __getattribute__(self, attr):
+                a = A()
+                return a.blich
+
+        try:
+            B().something
+        except AttributeError as exc:
+            with support.captured_stderr() as err:
+                sys.__excepthook__(*sys.exc_info())
+
+        self.assertIn("Did you mean", err.getvalue())
+        self.assertIn("bluch", err.getvalue())
+
 
 class ImportErrorTests(unittest.TestCase):
 
diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-03-06-20-16-13.bpo-46940._X47Hx.rst b/Misc/NEWS.d/next/Core and Builtins/2022-03-06-20-16-13.bpo-46940._X47Hx.rst
new file mode 100644 (file)
index 0000000..fabc946
--- /dev/null
@@ -0,0 +1,2 @@
+Avoid overriding :exc:`AttributeError` metadata information for nested
+attribute access calls. Patch by Pablo Galindo.
index 38919ff47a94a822a4cef2f6a7caa23626c97f27..f029a72dd3145bd5819fc8f9614975cb22d0a208 100644 (file)
@@ -875,19 +875,29 @@ static inline int
 set_attribute_error_context(PyObject* v, PyObject* name)
 {
     assert(PyErr_Occurred());
-    // Intercept AttributeError exceptions and augment them to offer
-    // suggestions later.
-    if (PyErr_ExceptionMatches(PyExc_AttributeError)){
-        PyObject *type, *value, *traceback;
-        PyErr_Fetch(&type, &value, &traceback);
-        PyErr_NormalizeException(&type, &value, &traceback);
-        if (PyErr_GivenExceptionMatches(value, PyExc_AttributeError) &&
-            (PyObject_SetAttr(value, &_Py_ID(name), name) ||
-             PyObject_SetAttr(value, &_Py_ID(obj), v))) {
-            return 1;
-        }
-        PyErr_Restore(type, value, traceback);
+    if (!PyErr_ExceptionMatches(PyExc_AttributeError)){
+        return 0;
+    }
+    // Intercept AttributeError exceptions and augment them to offer suggestions later.
+    PyObject *type, *value, *traceback;
+    PyErr_Fetch(&type, &value, &traceback);
+    PyErr_NormalizeException(&type, &value, &traceback);
+    // Check if the normalized exception is indeed an AttributeError
+    if (!PyErr_GivenExceptionMatches(value, PyExc_AttributeError)) {
+        goto restore;
+    }
+    PyAttributeErrorObject* the_exc = (PyAttributeErrorObject*) value;
+    // Check if this exception was already augmented
+    if (the_exc->name || the_exc->obj) {
+        goto restore;
+    }
+    // Augment the exception with the name and object
+    if (PyObject_SetAttr(value, &_Py_ID(name), name) ||
+        PyObject_SetAttr(value, &_Py_ID(obj), v)) {
+        return 1;
     }
+restore:
+    PyErr_Restore(type, value, traceback);
     return 0;
 }
 
index 0743894c457a7b709d8c7a3332dfae09fcbd1039..7439710ae47b7b57f76f85cc80f4a3e1ba39ddee 100644 (file)
@@ -7607,9 +7607,12 @@ format_exc_check_arg(PyThreadState *tstate, PyObject *exc,
         PyErr_Fetch(&type, &value, &traceback);
         PyErr_NormalizeException(&type, &value, &traceback);
         if (PyErr_GivenExceptionMatches(value, PyExc_NameError)) {
-            // We do not care if this fails because we are going to restore the
-            // NameError anyway.
-            (void)PyObject_SetAttr(value, &_Py_ID(name), obj);
+            PyNameErrorObject* exc = (PyNameErrorObject*) value;
+            if (exc->name == NULL) {
+                // We do not care if this fails because we are going to restore the
+                // NameError anyway.
+                (void)PyObject_SetAttr(value, &_Py_ID(name), obj);
+            }
         }
         PyErr_Restore(type, value, traceback);
     }