From 9716caa4b53ef8319e439d4e5a86e4867f460362 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Sat, 18 Jan 2025 01:06:04 +0000 Subject: [PATCH 1/4] gh-128799: Add frame of except* to traceback when wrapping a naked exception --- Include/internal/pycore_ceval.h | 2 +- .../2025-01-18-01-06-58.gh-issue-128799.vSNagk.rst | 1 + Python/bytecodes.c | 2 +- Python/ceval.c | 14 ++++++++++++-- Python/executor_cases.c.h | 2 +- Python/generated_cases.c.h | 2 +- 6 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-01-18-01-06-58.gh-issue-128799.vSNagk.rst diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h index 80bd19a887871c..fea8665ae39ab5 100644 --- a/Include/internal/pycore_ceval.h +++ b/Include/internal/pycore_ceval.h @@ -264,7 +264,7 @@ PyAPI_DATA(const size_t) _Py_FunctionAttributeOffsets[]; PyAPI_FUNC(int) _PyEval_CheckExceptStarTypeValid(PyThreadState *tstate, PyObject* right); PyAPI_FUNC(int) _PyEval_CheckExceptTypeValid(PyThreadState *tstate, PyObject* right); -PyAPI_FUNC(int) _PyEval_ExceptionGroupMatch(PyObject* exc_value, PyObject *match_type, PyObject **match, PyObject **rest); +PyAPI_FUNC(int) _PyEval_ExceptionGroupMatch(_PyInterpreterFrame *, PyObject* exc_value, PyObject *match_type, PyObject **match, PyObject **rest); PyAPI_FUNC(void) _PyEval_FormatAwaitableError(PyThreadState *tstate, PyTypeObject *type, int oparg); PyAPI_FUNC(void) _PyEval_FormatExcCheckArg(PyThreadState *tstate, PyObject *exc, const char *format_str, PyObject *obj); PyAPI_FUNC(void) _PyEval_FormatExcUnbound(PyThreadState *tstate, PyCodeObject *co, int oparg); diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-01-18-01-06-58.gh-issue-128799.vSNagk.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-01-18-01-06-58.gh-issue-128799.vSNagk.rst new file mode 100644 index 00000000000000..a4cdb5eedd35fa --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-01-18-01-06-58.gh-issue-128799.vSNagk.rst @@ -0,0 +1 @@ +Add frame of `except*` to traceback when it wraps a naked exception. diff --git a/Python/bytecodes.c b/Python/bytecodes.c index c0ef767a9dd68b..0d1084de7a8119 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -2717,7 +2717,7 @@ dummy_func( PyObject *match_o = NULL; PyObject *rest_o = NULL; - int res = _PyEval_ExceptionGroupMatch(exc_value, match_type, + int res = _PyEval_ExceptionGroupMatch(frame, exc_value, match_type, &match_o, &rest_o); DECREF_INPUTS(); ERROR_IF(res < 0, error); diff --git a/Python/ceval.c b/Python/ceval.c index 28b0b4c6de39a7..7ee71bef6d9a04 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -27,6 +27,7 @@ #include "pycore_range.h" // _PyRangeIterObject #include "pycore_setobject.h" // _PySet_Update() #include "pycore_sliceobject.h" // _PyBuildSlice_ConsumeRefs +#include "pycore_traceback.h" // _PyTraceBack_FromFrame #include "pycore_tuple.h" // _PyTuple_ITEMS() #include "pycore_uop_ids.h" // Uops #include "pycore_pyerrors.h" @@ -2094,8 +2095,8 @@ do_raise(PyThreadState *tstate, PyObject *exc, PyObject *cause) */ int -_PyEval_ExceptionGroupMatch(PyObject* exc_value, PyObject *match_type, - PyObject **match, PyObject **rest) +_PyEval_ExceptionGroupMatch(_PyInterpreterFrame *frame, PyObject* exc_value, + PyObject *match_type, PyObject **match, PyObject **rest) { if (Py_IsNone(exc_value)) { *match = Py_NewRef(Py_None); @@ -2121,6 +2122,15 @@ _PyEval_ExceptionGroupMatch(PyObject* exc_value, PyObject *match_type, if (wrapped == NULL) { return -1; } + PyFrameObject *f = _PyFrame_GetFrameObject(frame); + if (f != NULL) { + PyObject *tb = _PyTraceBack_FromFrame(NULL, f); + if (tb == NULL) { + return -1; + } + PyException_SetTraceback(wrapped, tb); + Py_DECREF(tb); + } *match = wrapped; } *rest = Py_NewRef(Py_None); diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index e2eaca2c90fa76..52f16fd8ee572a 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -3452,7 +3452,7 @@ PyObject *match_o = NULL; PyObject *rest_o = NULL; _PyFrame_SetStackPointer(frame, stack_pointer); - int res = _PyEval_ExceptionGroupMatch(exc_value, match_type, + int res = _PyEval_ExceptionGroupMatch(frame, exc_value, match_type, &match_o, &rest_o); stack_pointer = _PyFrame_GetStackPointer(frame); PyStackRef_CLOSE(exc_value_st); diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index dc90f75f2645e1..ef2438c6506e11 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -3205,7 +3205,7 @@ PyObject *match_o = NULL; PyObject *rest_o = NULL; _PyFrame_SetStackPointer(frame, stack_pointer); - int res = _PyEval_ExceptionGroupMatch(exc_value, match_type, + int res = _PyEval_ExceptionGroupMatch(frame, exc_value, match_type, &match_o, &rest_o); stack_pointer = _PyFrame_GetStackPointer(frame); PyStackRef_CLOSE(exc_value_st); From 1443bf8323c5080233610b413349cdea7471e2ea Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Sat, 18 Jan 2025 01:18:17 +0000 Subject: [PATCH 2/4] Update Misc/NEWS.d/next/Core_and_Builtins/2025-01-18-01-06-58.gh-issue-128799.vSNagk.rst --- .../2025-01-18-01-06-58.gh-issue-128799.vSNagk.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-01-18-01-06-58.gh-issue-128799.vSNagk.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-01-18-01-06-58.gh-issue-128799.vSNagk.rst index a4cdb5eedd35fa..eb2361bb5d4525 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2025-01-18-01-06-58.gh-issue-128799.vSNagk.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-01-18-01-06-58.gh-issue-128799.vSNagk.rst @@ -1 +1 @@ -Add frame of `except*` to traceback when it wraps a naked exception. +Add frame of ``except*`` to traceback when it wraps a naked exception. From 65cf6e77cac5d91b09051fe40022446f69c99524 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Sat, 18 Jan 2025 23:52:47 +0000 Subject: [PATCH 3/4] add test --- Lib/test/test_traceback.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index abdfc4638f2e9c..fa9354ded6019c 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -2919,6 +2919,32 @@ def exc(): report = self.get_report(exc) self.assertEqual(report, expected) + def test_exception_group_wrapped_naked(self): + # See gh-128799 + + def exc(): + try: + raise Exception(42) + except* Exception as e: + raise + + expected = (f' + Exception Group Traceback (most recent call last):\n' + f' | File "{__file__}", line {self.callable_line}, in get_exception\n' + f' | exception_or_callable()\n' + f' | ~~~~~~~~~~~~~~~~~~~~~^^\n' + f' | File "{__file__}", line {exc.__code__.co_firstlineno + 3}, in exc\n' + f' | except* Exception as e:\n' + f' | ExceptionGroup: (1 sub-exception)\n' + f' +-+---------------- 1 ----------------\n' + f' | Traceback (most recent call last):\n' + f' | File "{__file__}", line {exc.__code__.co_firstlineno + 2}, in exc\n' + f' | raise Exception(42)\n' + f' | Exception: 42\n' + f' +------------------------------------\n') + + report = self.get_report(exc) + self.assertEqual(report, expected) + def test_KeyboardInterrupt_at_first_line_of_frame(self): # see GH-93249 def f(): From 54a6d26a17016aad9e3d92b6092007c500199dec Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Wed, 22 Jan 2025 22:55:26 +0000 Subject: [PATCH 4/4] re-break traceback --- Lib/test/test_traceback.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 9038caea32199d..c1fd469d79c6d0 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -2928,6 +2928,7 @@ def exc(): f' | ~~~~~~~~~~~~~~~~~~~~~^^\n' f' | File "{__file__}", line {exc.__code__.co_firstlineno + 3}, in exc\n' f' | except* Exception as e:\n' + f' | raise\n' f' | ExceptionGroup: (1 sub-exception)\n' f' +-+---------------- 1 ----------------\n' f' | Traceback (most recent call last):\n'