[3.12] gh-113358: Fix rendering tracebacks with exceptions with a broken __getattr__ (GH-113359) (#114173)

This commit is contained in:
Jérome Perrin 2024-01-22 02:12:17 +09:00 committed by GitHub
parent afefa4a74c
commit 00e8c9ce9e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 53 additions and 2 deletions

View file

@ -1654,6 +1654,21 @@ class BaseExceptionReportingTests:
err_msg = "b'please do not show me as numbers'" err_msg = "b'please do not show me as numbers'"
self.assertEqual(self.get_report(e), vanilla + err_msg + '\n') self.assertEqual(self.get_report(e), vanilla + err_msg + '\n')
# an exception with a broken __getattr__ raising a non expected error
class BrokenException(Exception):
broken = False
def __getattr__(self, name):
if self.broken:
raise ValueError(f'no {name}')
raise AttributeError(name)
e = BrokenException(123)
vanilla = self.get_report(e)
e.broken = True
self.assertEqual(
self.get_report(e),
vanilla + "Ignored error getting __notes__: ValueError('no __notes__')\n")
def test_exception_with_multiple_notes(self): def test_exception_with_multiple_notes(self):
for e in [ValueError(42), SyntaxError('bad syntax')]: for e in [ValueError(42), SyntaxError('bad syntax')]:
with self.subTest(e=e): with self.subTest(e=e):

View file

@ -738,7 +738,11 @@ class TracebackException:
# Capture now to permit freeing resources: only complication is in the # Capture now to permit freeing resources: only complication is in the
# unofficial API _format_final_exc_line # unofficial API _format_final_exc_line
self._str = _safe_string(exc_value, 'exception') self._str = _safe_string(exc_value, 'exception')
self.__notes__ = getattr(exc_value, '__notes__', None) try:
self.__notes__ = getattr(exc_value, '__notes__', None)
except Exception as e:
self.__notes__ = [
f'Ignored error getting __notes__: {_safe_string(e, '__notes__', repr)}']
if exc_type and issubclass(exc_type, SyntaxError): if exc_type and issubclass(exc_type, SyntaxError):
# Handle SyntaxError's specially # Handle SyntaxError's specially

View file

@ -0,0 +1 @@
Fix rendering tracebacks for exceptions with a broken ``__getattr__``.

View file

@ -1164,6 +1164,37 @@ error:
return -1; return -1;
} }
static int
get_exception_notes(struct exception_print_context *ctx, PyObject *value, PyObject **notes) {
PyObject *note = NULL;
if (_PyObject_LookupAttr(value, &_Py_ID(__notes__), notes) < 0) {
PyObject *type, *errvalue, *tback;
PyErr_Fetch(&type, &errvalue, &tback);
PyErr_NormalizeException(&type, &errvalue, &tback);
note = PyUnicode_FromFormat("Ignored error getting __notes__: %R", errvalue);
Py_XDECREF(type);
Py_XDECREF(errvalue);
Py_XDECREF(tback);
if (!note) {
goto error;
}
*notes = PyList_New(1);
if (!*notes) {
goto error;
}
if (PyList_SetItem(*notes, 0, note) < 0) {
Py_DECREF(*notes);
goto error;
}
}
return 0;
error:
Py_XDECREF(note);
return -1;
}
static int static int
print_exception(struct exception_print_context *ctx, PyObject *value) print_exception(struct exception_print_context *ctx, PyObject *value)
{ {
@ -1183,7 +1214,7 @@ print_exception(struct exception_print_context *ctx, PyObject *value)
/* grab the type and notes now because value can change below */ /* grab the type and notes now because value can change below */
PyObject *type = (PyObject *) Py_TYPE(value); PyObject *type = (PyObject *) Py_TYPE(value);
if (_PyObject_LookupAttr(value, &_Py_ID(__notes__), &notes) < 0) { if (get_exception_notes(ctx, value, &notes) < 0) {
goto error; goto error;
} }