diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index e3897a0d89c..a75b7fae551 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -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 index 00000000000..fabc9460197 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2022-03-06-20-16-13.bpo-46940._X47Hx.rst @@ -0,0 +1,2 @@ +Avoid overriding :exc:`AttributeError` metadata information for nested +attribute access calls. Patch by Pablo Galindo. diff --git a/Objects/object.c b/Objects/object.c index 38919ff47a9..f029a72dd31 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -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; } diff --git a/Python/ceval.c b/Python/ceval.c index 0743894c457..7439710ae47 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -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); }