From 73f520d3c1890b1f9434ebf52d28ece37a936686 Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Sat, 25 Jan 2025 13:00:23 +0000 Subject: [PATCH 1/4] gh-128799: Add frame of except* to traceback when wrapping a naked exception (#128971) (cherry picked from commit c39ae8922bad3e5ceeafa05891536c1584b6f3db) --- Include/internal/pycore_ceval.h | 2 +- Lib/test/test_traceback.py | 27 +++++++++++++++++++ ...-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 +- 7 files changed, 44 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 043f5957d481e5..746b21ba744596 100644 --- a/Include/internal/pycore_ceval.h +++ b/Include/internal/pycore_ceval.h @@ -247,7 +247,7 @@ PyAPI_DATA(const conversion_func) _PyEval_ConversionFuncs[]; 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/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 2f543bccf566d5..f89fe8b26dc1bf 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -2938,6 +2938,33 @@ 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' | raise\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(): 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..eb2361bb5d4525 --- /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 1e6185d3c9e489..e9aec83d30b512 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -2340,7 +2340,7 @@ dummy_func( match = NULL; rest = NULL; - int res = _PyEval_ExceptionGroupMatch(exc_value, match_type, + int res = _PyEval_ExceptionGroupMatch(frame, exc_value, match_type, &match, &rest); DECREF_INPUTS(); ERROR_IF(res < 0, error); diff --git a/Python/ceval.c b/Python/ceval.c index 2f67d40874ba41..fe5dfc9b98a5d9 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -27,6 +27,7 @@ #include "pycore_setobject.h" // _PySet_Update() #include "pycore_sliceobject.h" // _PyBuildSlice_ConsumeRefs #include "pycore_sysmodule.h" // _PySys_Audit() +#include "pycore_traceback.h" // _PyTraceBack_FromFrame #include "pycore_tuple.h" // _PyTuple_ITEMS() #include "pycore_typeobject.h" // _PySuper_Lookup() #include "pycore_uop_ids.h" // Uops @@ -1991,8 +1992,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); @@ -2018,6 +2019,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 df7c19a80e40c5..b7a36c2031cccb 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -2447,7 +2447,7 @@ } match = NULL; rest = NULL; - int res = _PyEval_ExceptionGroupMatch(exc_value, match_type, + int res = _PyEval_ExceptionGroupMatch(frame, exc_value, match_type, &match, &rest); Py_DECREF(exc_value); Py_DECREF(match_type); diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 1488e4215cf579..73e36182759425 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -2109,7 +2109,7 @@ } match = NULL; rest = NULL; - int res = _PyEval_ExceptionGroupMatch(exc_value, match_type, + int res = _PyEval_ExceptionGroupMatch(frame, exc_value, match_type, &match, &rest); Py_DECREF(exc_value); Py_DECREF(match_type); From ac173b7bab737bb521867d9fe827e83db12438af Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Wed, 29 Jan 2025 23:35:27 +0000 Subject: [PATCH 2/4] avoid changing ABI --- Include/internal/pycore_ceval.h | 3 ++- Python/bytecodes.c | 4 ++-- Python/ceval.c | 14 ++++++++++++-- Python/executor_cases.c.h | 2 +- Python/generated_cases.c.h | 2 +- 5 files changed, 18 insertions(+), 7 deletions(-) diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h index 746b21ba744596..27f158209657fa 100644 --- a/Include/internal/pycore_ceval.h +++ b/Include/internal/pycore_ceval.h @@ -247,7 +247,7 @@ PyAPI_DATA(const conversion_func) _PyEval_ConversionFuncs[]; PyAPI_FUNC(int) _PyEval_CheckExceptStarTypeValid(PyThreadState *tstate, PyObject* right); PyAPI_FUNC(int) _PyEval_CheckExceptTypeValid(PyThreadState *tstate, PyObject* right); -PyAPI_FUNC(int) _PyEval_ExceptionGroupMatch(_PyInterpreterFrame *, PyObject* exc_value, PyObject *match_type, PyObject **match, PyObject **rest); +PyAPI_FUNC(int) _PyEval_ExceptionGroupMatch(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); @@ -258,6 +258,7 @@ PyAPI_FUNC(int) _PyEval_UnpackIterable(PyThreadState *tstate, PyObject *v, int a PyAPI_FUNC(void) _PyEval_MonitorRaise(PyThreadState *tstate, _PyInterpreterFrame *frame, _Py_CODEUNIT *instr); PyAPI_FUNC(void) _PyEval_FrameClearAndPop(PyThreadState *tstate, _PyInterpreterFrame *frame); +extern int _Py_exception_group_match(_PyInterpreterFrame *frame, PyObject* exc_value, PyObject *match_type, PyObject **match, PyObject **rest); /* Bits that can be set in PyThreadState.eval_breaker */ #define _PY_GIL_DROP_REQUEST_BIT (1U << 0) diff --git a/Python/bytecodes.c b/Python/bytecodes.c index e9aec83d30b512..2584150ac086e3 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -2340,8 +2340,8 @@ dummy_func( match = NULL; rest = NULL; - int res = _PyEval_ExceptionGroupMatch(frame, exc_value, match_type, - &match, &rest); + int res = _Py_exception_group_match(frame, exc_value, match_type, + &match, &rest); DECREF_INPUTS(); ERROR_IF(res < 0, error); diff --git a/Python/ceval.c b/Python/ceval.c index fe5dfc9b98a5d9..19bfa38db3c672 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -1992,8 +1992,8 @@ do_raise(PyThreadState *tstate, PyObject *exc, PyObject *cause) */ int -_PyEval_ExceptionGroupMatch(_PyInterpreterFrame *frame, PyObject* exc_value, - PyObject *match_type, PyObject **match, PyObject **rest) +_Py_exception_group_match(_PyInterpreterFrame *frame, PyObject* exc_value, + PyObject *match_type, PyObject **match, PyObject **rest) { if (Py_IsNone(exc_value)) { *match = Py_NewRef(Py_None); @@ -2073,6 +2073,16 @@ _PyEval_ExceptionGroupMatch(_PyInterpreterFrame *frame, PyObject* exc_value, return 0; } +int +_PyEval_ExceptionGroupMatch(PyObject* exc_value, PyObject *match_type, + PyObject **match, PyObject **rest) +{ + PyThreadState *tstate = _PyThreadState_GET(); + _PyInterpreterFrame *frame = _PyThreadState_GetFrame(tstate); + return _Py_exception_group_match(frame, exc_value, match_type, match, rest); +} + + /* Iterate v argcnt times and store the results on the stack (via decreasing sp). Return 1 for success, 0 if error. diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index b7a36c2031cccb..ceed18fd0cb862 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -2447,7 +2447,7 @@ } match = NULL; rest = NULL; - int res = _PyEval_ExceptionGroupMatch(frame, exc_value, match_type, + int res = _Py_exception_group_match(frame, exc_value, match_type, &match, &rest); Py_DECREF(exc_value); Py_DECREF(match_type); diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 73e36182759425..08b8ebe585cd6d 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -2109,7 +2109,7 @@ } match = NULL; rest = NULL; - int res = _PyEval_ExceptionGroupMatch(frame, exc_value, match_type, + int res = _Py_exception_group_match(frame, exc_value, match_type, &match, &rest); Py_DECREF(exc_value); Py_DECREF(match_type); From 078036e72618db0b152a62e1840bb5e2ff21752d Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Thu, 30 Jan 2025 11:12:43 +0000 Subject: [PATCH 3/4] Revert "avoid changing ABI" This reverts commit ac173b7bab737bb521867d9fe827e83db12438af. --- Include/internal/pycore_ceval.h | 3 +-- Python/bytecodes.c | 4 ++-- Python/ceval.c | 14 ++------------ Python/executor_cases.c.h | 2 +- Python/generated_cases.c.h | 2 +- 5 files changed, 7 insertions(+), 18 deletions(-) diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h index 27f158209657fa..746b21ba744596 100644 --- a/Include/internal/pycore_ceval.h +++ b/Include/internal/pycore_ceval.h @@ -247,7 +247,7 @@ PyAPI_DATA(const conversion_func) _PyEval_ConversionFuncs[]; 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); @@ -258,7 +258,6 @@ PyAPI_FUNC(int) _PyEval_UnpackIterable(PyThreadState *tstate, PyObject *v, int a PyAPI_FUNC(void) _PyEval_MonitorRaise(PyThreadState *tstate, _PyInterpreterFrame *frame, _Py_CODEUNIT *instr); PyAPI_FUNC(void) _PyEval_FrameClearAndPop(PyThreadState *tstate, _PyInterpreterFrame *frame); -extern int _Py_exception_group_match(_PyInterpreterFrame *frame, PyObject* exc_value, PyObject *match_type, PyObject **match, PyObject **rest); /* Bits that can be set in PyThreadState.eval_breaker */ #define _PY_GIL_DROP_REQUEST_BIT (1U << 0) diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 2584150ac086e3..e9aec83d30b512 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -2340,8 +2340,8 @@ dummy_func( match = NULL; rest = NULL; - int res = _Py_exception_group_match(frame, exc_value, match_type, - &match, &rest); + int res = _PyEval_ExceptionGroupMatch(frame, exc_value, match_type, + &match, &rest); DECREF_INPUTS(); ERROR_IF(res < 0, error); diff --git a/Python/ceval.c b/Python/ceval.c index 19bfa38db3c672..fe5dfc9b98a5d9 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -1992,8 +1992,8 @@ do_raise(PyThreadState *tstate, PyObject *exc, PyObject *cause) */ int -_Py_exception_group_match(_PyInterpreterFrame *frame, 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); @@ -2073,16 +2073,6 @@ _Py_exception_group_match(_PyInterpreterFrame *frame, PyObject* exc_value, return 0; } -int -_PyEval_ExceptionGroupMatch(PyObject* exc_value, PyObject *match_type, - PyObject **match, PyObject **rest) -{ - PyThreadState *tstate = _PyThreadState_GET(); - _PyInterpreterFrame *frame = _PyThreadState_GetFrame(tstate); - return _Py_exception_group_match(frame, exc_value, match_type, match, rest); -} - - /* Iterate v argcnt times and store the results on the stack (via decreasing sp). Return 1 for success, 0 if error. diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index ceed18fd0cb862..b7a36c2031cccb 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -2447,7 +2447,7 @@ } match = NULL; rest = NULL; - int res = _Py_exception_group_match(frame, exc_value, match_type, + int res = _PyEval_ExceptionGroupMatch(frame, exc_value, match_type, &match, &rest); Py_DECREF(exc_value); Py_DECREF(match_type); diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 08b8ebe585cd6d..73e36182759425 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -2109,7 +2109,7 @@ } match = NULL; rest = NULL; - int res = _Py_exception_group_match(frame, exc_value, match_type, + int res = _PyEval_ExceptionGroupMatch(frame, exc_value, match_type, &match, &rest); Py_DECREF(exc_value); Py_DECREF(match_type); From 49eb82d3b8848d62adc7f5a0ce41206c2c571644 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Thu, 30 Jan 2025 11:25:40 +0000 Subject: [PATCH 4/4] do not change signature --- Include/internal/pycore_ceval.h | 2 +- Python/bytecodes.c | 2 +- Python/ceval.c | 6 ++++-- Python/executor_cases.c.h | 2 +- Python/generated_cases.c.h | 2 +- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h index 746b21ba744596..043f5957d481e5 100644 --- a/Include/internal/pycore_ceval.h +++ b/Include/internal/pycore_ceval.h @@ -247,7 +247,7 @@ PyAPI_DATA(const conversion_func) _PyEval_ConversionFuncs[]; PyAPI_FUNC(int) _PyEval_CheckExceptStarTypeValid(PyThreadState *tstate, PyObject* right); PyAPI_FUNC(int) _PyEval_CheckExceptTypeValid(PyThreadState *tstate, PyObject* right); -PyAPI_FUNC(int) _PyEval_ExceptionGroupMatch(_PyInterpreterFrame *, PyObject* exc_value, PyObject *match_type, PyObject **match, PyObject **rest); +PyAPI_FUNC(int) _PyEval_ExceptionGroupMatch(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/Python/bytecodes.c b/Python/bytecodes.c index e9aec83d30b512..1e6185d3c9e489 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -2340,7 +2340,7 @@ dummy_func( match = NULL; rest = NULL; - int res = _PyEval_ExceptionGroupMatch(frame, exc_value, match_type, + int res = _PyEval_ExceptionGroupMatch(exc_value, match_type, &match, &rest); DECREF_INPUTS(); ERROR_IF(res < 0, error); diff --git a/Python/ceval.c b/Python/ceval.c index fe5dfc9b98a5d9..763c8688266f9d 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -1992,8 +1992,8 @@ do_raise(PyThreadState *tstate, PyObject *exc, PyObject *cause) */ int -_PyEval_ExceptionGroupMatch(_PyInterpreterFrame *frame, PyObject* exc_value, - PyObject *match_type, PyObject **match, PyObject **rest) +_PyEval_ExceptionGroupMatch(PyObject* exc_value, PyObject *match_type, + PyObject **match, PyObject **rest) { if (Py_IsNone(exc_value)) { *match = Py_NewRef(Py_None); @@ -2019,6 +2019,8 @@ _PyEval_ExceptionGroupMatch(_PyInterpreterFrame *frame, PyObject* exc_value, if (wrapped == NULL) { return -1; } + PyThreadState *tstate = _PyThreadState_GET(); + _PyInterpreterFrame *frame = _PyThreadState_GetFrame(tstate); PyFrameObject *f = _PyFrame_GetFrameObject(frame); if (f != NULL) { PyObject *tb = _PyTraceBack_FromFrame(NULL, f); diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index b7a36c2031cccb..df7c19a80e40c5 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -2447,7 +2447,7 @@ } match = NULL; rest = NULL; - int res = _PyEval_ExceptionGroupMatch(frame, exc_value, match_type, + int res = _PyEval_ExceptionGroupMatch(exc_value, match_type, &match, &rest); Py_DECREF(exc_value); Py_DECREF(match_type); diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 73e36182759425..1488e4215cf579 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -2109,7 +2109,7 @@ } match = NULL; rest = NULL; - int res = _PyEval_ExceptionGroupMatch(frame, exc_value, match_type, + int res = _PyEval_ExceptionGroupMatch(exc_value, match_type, &match, &rest); Py_DECREF(exc_value); Py_DECREF(match_type);