From 8c12e96bdfdbf243da052ffe6a75b3c226edac50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9rome=20Perrin?= Date: Tue, 16 Jan 2024 18:49:24 +0900 Subject: [PATCH 1/7] gh-113358: Fix rendering tracebacks with exceptions with a broken __getattr__ (GH-113359) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cherry picked from commit 04fabe22dd98b4d87f672254b743fbadd5206352 Adjusted for 3.11, because exception printing also happens in C code. Co-authored-by: Jérome Perrin Co-authored-by: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> --- Lib/test/test_traceback.py | 15 +++++++++ Lib/traceback.py | 6 +++- ...-12-21-14-55-06.gh-issue-113358.nRkiSL.rst | 1 + Python/pythonrun.c | 31 ++++++++++++++++++- 4 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-21-14-55-06.gh-issue-113358.nRkiSL.rst diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 2881a3489084a0..a857d61e9b4a5f 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -1601,6 +1601,21 @@ def __repr__(self): err_msg = '' 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): diff --git a/Lib/traceback.py b/Lib/traceback.py index ea045e27610d4d..e7026e545cb1b4 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -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 diff --git a/Misc/NEWS.d/next/Library/2023-12-21-14-55-06.gh-issue-113358.nRkiSL.rst b/Misc/NEWS.d/next/Library/2023-12-21-14-55-06.gh-issue-113358.nRkiSL.rst new file mode 100644 index 00000000000000..76416553a231a0 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-21-14-55-06.gh-issue-113358.nRkiSL.rst @@ -0,0 +1 @@ +Fix rendering tracebacks with exceptions with a broken __getattr__ diff --git a/Python/pythonrun.c b/Python/pythonrun.c index 91c2ad3a13d432..8162e4324b94a7 100644 --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -1199,6 +1199,35 @@ print_exception_notes(struct exception_print_context *ctx, PyObject *notes) return -1; } +static 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) { @@ -1218,7 +1247,7 @@ 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__), ¬es) < 0) { + if (get_exception_notes(ctx, value, ¬es) < 0) { goto error; } From 67ac0546344687d770a584fba3874c747d9ad6c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9rome=20Perrin?= Date: Wed, 17 Jan 2024 13:40:06 +0000 Subject: [PATCH 2/7] Address review comments --- .../Library/2023-12-21-14-55-06.gh-issue-113358.nRkiSL.rst | 2 +- Python/pythonrun.c | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2023-12-21-14-55-06.gh-issue-113358.nRkiSL.rst b/Misc/NEWS.d/next/Library/2023-12-21-14-55-06.gh-issue-113358.nRkiSL.rst index 76416553a231a0..4afbbda9442676 100644 --- a/Misc/NEWS.d/next/Library/2023-12-21-14-55-06.gh-issue-113358.nRkiSL.rst +++ b/Misc/NEWS.d/next/Library/2023-12-21-14-55-06.gh-issue-113358.nRkiSL.rst @@ -1 +1 @@ -Fix rendering tracebacks with exceptions with a broken __getattr__ +Fix rendering tracebacks for exceptions with a broken ``__getattr__``. diff --git a/Python/pythonrun.c b/Python/pythonrun.c index 8162e4324b94a7..c5ef7f1f1de2b9 100644 --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -1208,16 +1208,17 @@ get_exception_notes(struct exception_print_context *ctx, PyObject *value, PyObje 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) { + Py_XDECREF(*notes); goto error; } if (PyList_SetItem(*notes, 0, note) < 0) { + Py_XDECREF(*notes); goto error; } } From 2c882e08d8ce96fd3e446a66234f6d9d4c122d05 Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Wed, 17 Jan 2024 13:57:26 +0000 Subject: [PATCH 3/7] Apply suggestions from code review --- Python/pythonrun.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Python/pythonrun.c b/Python/pythonrun.c index c5ef7f1f1de2b9..04edcbf450e023 100644 --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -1214,11 +1214,11 @@ get_exception_notes(struct exception_print_context *ctx, PyObject *value, PyObje } note = PyUnicode_FromFormat("Ignored error getting __notes__: %R", errvalue); if (!note) { - Py_XDECREF(*notes); + Py_DECREF(*notes); goto error; } if (PyList_SetItem(*notes, 0, note) < 0) { - Py_XDECREF(*notes); + Py_DECREF(*notes); goto error; } } From a4613e262491698a1bf0f1af5d0a9eb33c91b2db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9rome=20Perrin?= Date: Wed, 17 Jan 2024 23:40:25 +0000 Subject: [PATCH 4/7] fix reference leak, after PyErr_Fetch we own the references --- Python/pythonrun.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Python/pythonrun.c b/Python/pythonrun.c index 04edcbf450e023..22ef471f659160 100644 --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -1201,9 +1201,9 @@ print_exception_notes(struct exception_print_context *ctx, PyObject *notes) static int get_exception_notes(struct exception_print_context *ctx, PyObject *value, PyObject **notes) { - PyObject *type; - PyObject *errvalue; - PyObject *tback; + PyObject *type = NULL; + PyObject *errvalue = NULL; + PyObject *tback = NULL; PyObject *note = NULL; if (_PyObject_LookupAttr(value, &_Py_ID(__notes__), notes) < 0) { @@ -1223,9 +1223,15 @@ get_exception_notes(struct exception_print_context *ctx, PyObject *value, PyObje } } + Py_XDECREF(type); + Py_XDECREF(errvalue); + Py_XDECREF(tback); return 0; error: Py_XDECREF(note); + Py_XDECREF(type); + Py_XDECREF(errvalue); + Py_XDECREF(tback); return -1; } From 25d62fb8aab7e3283435c08be5cc0af36c83486f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9rome=20Perrin?= Date: Fri, 19 Jan 2024 13:36:26 +0000 Subject: [PATCH 5/7] move PyList_New as suggested --- Python/pythonrun.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Python/pythonrun.c b/Python/pythonrun.c index 22ef471f659160..c5e6e8670aeeba 100644 --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -1208,24 +1208,23 @@ get_exception_notes(struct exception_print_context *ctx, PyObject *value, PyObje if (_PyObject_LookupAttr(value, &_Py_ID(__notes__), notes) < 0) { PyErr_Fetch(&type, &errvalue, &tback); - *notes = PyList_New(1); - if (!*notes) { - goto error; - } note = PyUnicode_FromFormat("Ignored error getting __notes__: %R", errvalue); if (!note) { - Py_DECREF(*notes); + goto error; + } + *notes = PyList_New(1); + if (!*notes) { goto error; } if (PyList_SetItem(*notes, 0, note) < 0) { Py_DECREF(*notes); goto error; } + Py_XDECREF(type); + Py_XDECREF(errvalue); + Py_XDECREF(tback); } - Py_XDECREF(type); - Py_XDECREF(errvalue); - Py_XDECREF(tback); return 0; error: Py_XDECREF(note); From 618d12841b9163b9134cdec58281ed1cf33f2c55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9rome=20Perrin?= Date: Fri, 19 Jan 2024 13:37:42 +0000 Subject: [PATCH 6/7] fixup! move PyList_New as suggested --- Python/pythonrun.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/Python/pythonrun.c b/Python/pythonrun.c index c5e6e8670aeeba..4cfa3e5d221faa 100644 --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -1228,9 +1228,6 @@ get_exception_notes(struct exception_print_context *ctx, PyObject *value, PyObje return 0; error: Py_XDECREF(note); - Py_XDECREF(type); - Py_XDECREF(errvalue); - Py_XDECREF(tback); return -1; } From d72e989cdc52409842e7d32c08eaca14a84ca8ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9rome=20Perrin?= Date: Fri, 19 Jan 2024 14:33:56 +0000 Subject: [PATCH 7/7] address review comments --- Python/pythonrun.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Python/pythonrun.c b/Python/pythonrun.c index 4cfa3e5d221faa..d3c0c85680e17b 100644 --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -1201,14 +1201,15 @@ print_exception_notes(struct exception_print_context *ctx, PyObject *notes) static int get_exception_notes(struct exception_print_context *ctx, PyObject *value, PyObject **notes) { - PyObject *type = NULL; - PyObject *errvalue = NULL; - PyObject *tback = NULL; PyObject *note = NULL; if (_PyObject_LookupAttr(value, &_Py_ID(__notes__), notes) < 0) { + PyObject *type, *errvalue, *tback; PyErr_Fetch(&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; } @@ -1220,9 +1221,6 @@ get_exception_notes(struct exception_print_context *ctx, PyObject *value, PyObje Py_DECREF(*notes); goto error; } - Py_XDECREF(type); - Py_XDECREF(errvalue); - Py_XDECREF(tback); } return 0;