Skip to content

Commit 3b3be05

Browse files
authored
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.
1 parent 5c06dba commit 3b3be05

File tree

4 files changed

+48
-15
lines changed

4 files changed

+48
-15
lines changed

Lib/test/test_exceptions.py

+18
Original file line numberDiff line numberDiff line change
@@ -2272,6 +2272,24 @@ def test_attribute_error_with_bad_name(self):
22722272

22732273
self.assertNotIn("?", err.getvalue())
22742274

2275+
def test_attribute_error_inside_nested_getattr(self):
2276+
class A:
2277+
bluch = 1
2278+
2279+
class B:
2280+
def __getattribute__(self, attr):
2281+
a = A()
2282+
return a.blich
2283+
2284+
try:
2285+
B().something
2286+
except AttributeError as exc:
2287+
with support.captured_stderr() as err:
2288+
sys.__excepthook__(*sys.exc_info())
2289+
2290+
self.assertIn("Did you mean", err.getvalue())
2291+
self.assertIn("bluch", err.getvalue())
2292+
22752293

22762294
class ImportErrorTests(unittest.TestCase):
22772295

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

Objects/object.c

+22-12
Original file line numberDiff line numberDiff line change
@@ -875,19 +875,29 @@ static inline int
875875
set_attribute_error_context(PyObject* v, PyObject* name)
876876
{
877877
assert(PyErr_Occurred());
878-
// Intercept AttributeError exceptions and augment them to offer
879-
// suggestions later.
880-
if (PyErr_ExceptionMatches(PyExc_AttributeError)){
881-
PyObject *type, *value, *traceback;
882-
PyErr_Fetch(&type, &value, &traceback);
883-
PyErr_NormalizeException(&type, &value, &traceback);
884-
if (PyErr_GivenExceptionMatches(value, PyExc_AttributeError) &&
885-
(PyObject_SetAttr(value, &_Py_ID(name), name) ||
886-
PyObject_SetAttr(value, &_Py_ID(obj), v))) {
887-
return 1;
888-
}
889-
PyErr_Restore(type, value, traceback);
878+
if (!PyErr_ExceptionMatches(PyExc_AttributeError)){
879+
return 0;
880+
}
881+
// Intercept AttributeError exceptions and augment them to offer suggestions later.
882+
PyObject *type, *value, *traceback;
883+
PyErr_Fetch(&type, &value, &traceback);
884+
PyErr_NormalizeException(&type, &value, &traceback);
885+
// Check if the normalized exception is indeed an AttributeError
886+
if (!PyErr_GivenExceptionMatches(value, PyExc_AttributeError)) {
887+
goto restore;
888+
}
889+
PyAttributeErrorObject* the_exc = (PyAttributeErrorObject*) value;
890+
// Check if this exception was already augmented
891+
if (the_exc->name || the_exc->obj) {
892+
goto restore;
893+
}
894+
// Augment the exception with the name and object
895+
if (PyObject_SetAttr(value, &_Py_ID(name), name) ||
896+
PyObject_SetAttr(value, &_Py_ID(obj), v)) {
897+
return 1;
890898
}
899+
restore:
900+
PyErr_Restore(type, value, traceback);
891901
return 0;
892902
}
893903

Python/ceval.c

+6-3
Original file line numberDiff line numberDiff line change
@@ -7607,9 +7607,12 @@ format_exc_check_arg(PyThreadState *tstate, PyObject *exc,
76077607
PyErr_Fetch(&type, &value, &traceback);
76087608
PyErr_NormalizeException(&type, &value, &traceback);
76097609
if (PyErr_GivenExceptionMatches(value, PyExc_NameError)) {
7610-
// We do not care if this fails because we are going to restore the
7611-
// NameError anyway.
7612-
(void)PyObject_SetAttr(value, &_Py_ID(name), obj);
7610+
PyNameErrorObject* exc = (PyNameErrorObject*) value;
7611+
if (exc->name == NULL) {
7612+
// We do not care if this fails because we are going to restore the
7613+
// NameError anyway.
7614+
(void)PyObject_SetAttr(value, &_Py_ID(name), obj);
7615+
}
76137616
}
76147617
PyErr_Restore(type, value, traceback);
76157618
}

0 commit comments

Comments
 (0)