Skip to content

Commit

Permalink
pythongh-113358: Fix rendering tracebacks with exceptions with a brok…
Browse files Browse the repository at this point in the history
…en __getattr__ (pythonGH-113359)

cherry picked from commit 04fabe2

Adjusted for 3.11, because exception printing also happens in C
code.

Co-authored-by: Jérome Perrin <perrinjerome@gmail.com>
Co-authored-by: Irit Katriel <1055913+iritkatriel@users.noreply.github.com>
  • Loading branch information
perrinjerome and iritkatriel committed Jan 17, 2024
1 parent 0e92265 commit acf3006
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 3 deletions.
15 changes: 15 additions & 0 deletions Lib/test/test_traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -1601,6 +1601,21 @@ def __repr__(self):
err_msg = '<note str() failed>'
self.assertEqual(self.get_report(e), vanilla + err_msg + '\nFinal Note\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):
for e in [ValueError(42), SyntaxError('bad syntax')]:
with self.subTest(e=e):
Expand Down
6 changes: 5 additions & 1 deletion Lib/traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -733,7 +733,11 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None,
# Capture now to permit freeing resources: only complication is in the
# unofficial API _format_final_exc_line
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):
# Handle SyntaxError's specially
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix rendering tracebacks with exceptions with a broken __getattr__
32 changes: 30 additions & 2 deletions Python/pythonrun.c
Original file line number Diff line number Diff line change
Expand Up @@ -1199,6 +1199,35 @@ print_exception_notes(struct exception_print_context *ctx, PyObject *notes)
return -1;
}

int
get_exception_notes(struct exception_print_context *ctx, PyObject *value, PyObject **notes) {
PyObject *type;
PyObject *errvalue;
PyObject *tback;
PyObject *note = NULL;

if (_PyObject_LookupAttr(value, &_Py_ID(__notes__), notes) < 0) {
PyErr_Fetch(&type, &errvalue, &tback);
PyErr_Clear();
*notes = PyList_New(1);
if (!*notes) {
goto error;
}
note = PyUnicode_FromFormat("Ignored error getting __notes__: %R", errvalue);
if (!note) {
goto error;
}
if (PyList_SetItem(*notes, 0, note) < 0) {
goto error;
}
}

return 0;
error:
Py_XDECREF(note);
return -1;
}

static int
print_exception(struct exception_print_context *ctx, PyObject *value)
{
Expand All @@ -1218,10 +1247,9 @@ print_exception(struct exception_print_context *ctx, PyObject *value)

/* grab the type and notes now because value can change below */
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;
}

if (print_exception_file_and_line(ctx, &value) < 0) {
goto error;
}
Expand Down

0 comments on commit acf3006

Please sign in to comment.