From 8f816e9b35933fc1f59e91300a9bb29a84dd3db9 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Sat, 5 Mar 2022 13:23:26 +0100 Subject: [PATCH] bpo-46940: Don't override existing AttributeError suggestion information 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 | 18 ++++++++++ .../2022-03-06-20-16-13.bpo-46940._X47Hx.rst | 2 ++ Objects/object.c | 34 ++++++++++++------- Python/ceval.c | 9 +++-- 4 files changed, 48 insertions(+), 15 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2022-03-06-20-16-13.bpo-46940._X47Hx.rst diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index e3897a0d89c5ea7..a75b7fae5518e06 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -2272,6 +2272,24 @@ def test_attribute_error_with_bad_name(self): 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 000000000000000..fabc94601975827 --- /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 38919ff47a94a82..f029a72dd3145bd 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 c86b7443768a155..1b2a6c8c282c86c 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -7676,9 +7676,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); }