Skip to content

Commit 00e8c9c

Browse files
authored
[3.12] gh-113358: Fix rendering tracebacks with exceptions with a broken __getattr__ (GH-113359) (#114173)
1 parent afefa4a commit 00e8c9c

File tree

4 files changed

+53
-2
lines changed

4 files changed

+53
-2
lines changed

Lib/test/test_traceback.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1654,6 +1654,21 @@ def __repr__(self):
16541654
err_msg = "b'please do not show me as numbers'"
16551655
self.assertEqual(self.get_report(e), vanilla + err_msg + '\n')
16561656

1657+
# an exception with a broken __getattr__ raising a non expected error
1658+
class BrokenException(Exception):
1659+
broken = False
1660+
def __getattr__(self, name):
1661+
if self.broken:
1662+
raise ValueError(f'no {name}')
1663+
raise AttributeError(name)
1664+
1665+
e = BrokenException(123)
1666+
vanilla = self.get_report(e)
1667+
e.broken = True
1668+
self.assertEqual(
1669+
self.get_report(e),
1670+
vanilla + "Ignored error getting __notes__: ValueError('no __notes__')\n")
1671+
16571672
def test_exception_with_multiple_notes(self):
16581673
for e in [ValueError(42), SyntaxError('bad syntax')]:
16591674
with self.subTest(e=e):

Lib/traceback.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -738,7 +738,11 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None,
738738
# Capture now to permit freeing resources: only complication is in the
739739
# unofficial API _format_final_exc_line
740740
self._str = _safe_string(exc_value, 'exception')
741-
self.__notes__ = getattr(exc_value, '__notes__', None)
741+
try:
742+
self.__notes__ = getattr(exc_value, '__notes__', None)
743+
except Exception as e:
744+
self.__notes__ = [
745+
f'Ignored error getting __notes__: {_safe_string(e, '__notes__', repr)}']
742746

743747
if exc_type and issubclass(exc_type, SyntaxError):
744748
# Handle SyntaxError's specially
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix rendering tracebacks for exceptions with a broken ``__getattr__``.

Python/pythonrun.c

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1164,6 +1164,37 @@ print_exception_notes(struct exception_print_context *ctx, PyObject *notes)
11641164
return -1;
11651165
}
11661166

1167+
static int
1168+
get_exception_notes(struct exception_print_context *ctx, PyObject *value, PyObject **notes) {
1169+
PyObject *note = NULL;
1170+
1171+
if (_PyObject_LookupAttr(value, &_Py_ID(__notes__), notes) < 0) {
1172+
PyObject *type, *errvalue, *tback;
1173+
PyErr_Fetch(&type, &errvalue, &tback);
1174+
PyErr_NormalizeException(&type, &errvalue, &tback);
1175+
note = PyUnicode_FromFormat("Ignored error getting __notes__: %R", errvalue);
1176+
Py_XDECREF(type);
1177+
Py_XDECREF(errvalue);
1178+
Py_XDECREF(tback);
1179+
if (!note) {
1180+
goto error;
1181+
}
1182+
*notes = PyList_New(1);
1183+
if (!*notes) {
1184+
goto error;
1185+
}
1186+
if (PyList_SetItem(*notes, 0, note) < 0) {
1187+
Py_DECREF(*notes);
1188+
goto error;
1189+
}
1190+
}
1191+
1192+
return 0;
1193+
error:
1194+
Py_XDECREF(note);
1195+
return -1;
1196+
}
1197+
11671198
static int
11681199
print_exception(struct exception_print_context *ctx, PyObject *value)
11691200
{
@@ -1183,7 +1214,7 @@ print_exception(struct exception_print_context *ctx, PyObject *value)
11831214

11841215
/* grab the type and notes now because value can change below */
11851216
PyObject *type = (PyObject *) Py_TYPE(value);
1186-
if (_PyObject_LookupAttr(value, &_Py_ID(__notes__), &notes) < 0) {
1217+
if (get_exception_notes(ctx, value, &notes) < 0) {
11871218
goto error;
11881219
}
11891220

0 commit comments

Comments
 (0)