bpo-46940: Don't override existing AttributeError suggestion information (GH-31710)

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.
This commit is contained in:
Pablo Galindo Salgado 2022-03-07 12:23:11 +00:00 committed by GitHub
parent 5c06dba21b
commit 3b3be05a16
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 48 additions and 15 deletions

View 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):

View File

@ -0,0 +1,2 @@
Avoid overriding :exc:`AttributeError` metadata information for nested
attribute access calls. Patch by Pablo Galindo.

View 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;
}

View 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);
}