From 5d421d7342fc0d278c129c05bea7028430e94a4e Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Fri, 15 Apr 2022 19:57:47 +0100 Subject: gh-90501: Add PyErr_GetHandledException and PyErr_SetHandledException (GH-30531) --- Doc/c-api/exceptions.rst | 44 ++++++++++++++++++++-- Doc/data/stable_abi.dat | 2 + Doc/library/sys.rst | 17 +++------ Doc/whatsnew/3.11.rst | 8 ++++ Include/cpython/pyerrors.h | 2 + Include/pyerrors.h | 4 ++ Lib/test/test_capi.py | 22 +++++++++++ Lib/test/test_stable_abi_ctypes.py | 2 + .../C API/2022-01-11-12-52-37.bpo-46343.JQJWhZ.rst | 5 +++ Misc/stable_abi.txt | 5 +++ Modules/_testcapimodule.c | 11 ++++++ PC/python3dll.c | 2 + Python/errors.c | 41 ++++++++++++++++---- 13 files changed, 141 insertions(+), 24 deletions(-) create mode 100644 Misc/NEWS.d/next/C API/2022-01-11-12-52-37.bpo-46343.JQJWhZ.rst diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst index a5a93d0..7bfeca5 100644 --- a/Doc/c-api/exceptions.rst +++ b/Doc/c-api/exceptions.rst @@ -460,12 +460,46 @@ Querying the error indicator } -.. c:function:: void PyErr_GetExcInfo(PyObject **ptype, PyObject **pvalue, PyObject **ptraceback) +.. c:function:: PyObject* PyErr_GetHandledException(void) + + Retrieve the active exception instance, as would be returned by :func:`sys.exception`. + This refers to an exception that was *already caught*, not to an exception that was + freshly raised. Returns a new reference to the exception or ``NULL``. + Does not modify the interpreter's exception state. + + .. note:: + + This function is not normally used by code that wants to handle exceptions. + Rather, it can be used when code needs to save and restore the exception + state temporarily. Use :c:func:`PyErr_SetHandledException` to restore or + clear the exception state. + + .. versionadded:: 3.11 - Retrieve the exception info, as known from ``sys.exc_info()``. This refers +.. c:function:: void PyErr_SetHandledException(PyObject *exc) + + Set the active exception, as known from ``sys.exception()``. This refers to an exception that was *already caught*, not to an exception that was - freshly raised. Returns new references for the three objects, any of which - may be ``NULL``. Does not modify the exception info state. + freshly raised. + To clear the exception state, pass ``NULL``. + + .. note:: + + This function is not normally used by code that wants to handle exceptions. + Rather, it can be used when code needs to save and restore the exception + state temporarily. Use :c:func:`PyErr_GetHandledException` to get the exception + state. + + .. versionadded:: 3.11 + +.. c:function:: void PyErr_GetExcInfo(PyObject **ptype, PyObject **pvalue, PyObject **ptraceback) + + Retrieve the old-style representation of the exception info, as known from + :func:`sys.exc_info`. This refers to an exception that was *already caught*, + not to an exception that was freshly raised. Returns new references for the + three objects, any of which may be ``NULL``. Does not modify the exception + info state. This function is kept for backwards compatibility. Prefer using + :c:func:`PyErr_GetHandledException`. .. note:: @@ -483,6 +517,8 @@ Querying the error indicator to an exception that was *already caught*, not to an exception that was freshly raised. This function steals the references of the arguments. To clear the exception state, pass ``NULL`` for all three arguments. + This function is kept for backwards compatibility. Prefer using + :c:func:`PyErr_SetHandledException`. .. note:: diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index 849a2cf..5387d0b 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -137,6 +137,7 @@ function,PyErr_Fetch,3.2,, function,PyErr_Format,3.2,, function,PyErr_FormatV,3.5,, function,PyErr_GetExcInfo,3.7,, +function,PyErr_GetHandledException,3.11,, function,PyErr_GivenExceptionMatches,3.2,, function,PyErr_NewException,3.2,, function,PyErr_NewExceptionWithDoc,3.2,, @@ -159,6 +160,7 @@ function,PyErr_SetFromErrnoWithFilenameObject,3.2,, function,PyErr_SetFromErrnoWithFilenameObjects,3.7,, function,PyErr_SetFromWindowsErr,3.7,on Windows, function,PyErr_SetFromWindowsErrWithFilename,3.7,on Windows, +function,PyErr_SetHandledException,3.11,, function,PyErr_SetImportError,3.7,, function,PyErr_SetImportErrorSubclass,3.6,, function,PyErr_SetInterrupt,3.2,, diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst index 126da31..2a8b532 100644 --- a/Doc/library/sys.rst +++ b/Doc/library/sys.rst @@ -381,19 +381,12 @@ always available. .. function:: exception() - This function returns the exception instance that is currently being - handled. This exception is specific both to the current thread and - to the current stack frame. If the current stack frame is not handling - an exception, the exception is taken from the calling stack frame, or its - caller, and so on until a stack frame is found that is handling an - exception. Here, "handling an exception" is defined as "executing an - except clause." For any stack frame, only the exception being currently - handled is accessible. + This function, when called while an exception handler is executing (such as + an ``except`` or ``except*`` clause), returns the exception instance that + was caught by this handler. When exception handlers are nested within one + another, only the exception handled by the innermost handler is accessible. - .. index:: object: traceback - - If no exception is being handled anywhere on the stack, ``None`` is - returned. + If no exception handler is executing, this function returns ``None``. .. versionadded:: 3.11 diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index a5a5268..b6f47f5 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -1161,6 +1161,14 @@ New Features :c:func:`PyFrame_GetBuiltins`, :c:func:`PyFrame_GetGenerator`, :c:func:`PyFrame_GetGlobals`, :c:func:`PyFrame_GetLasti`. +* Added two new functions to get and set the active exception instance: + :c:func:`PyErr_GetHandledException` and :c:func:`PyErr_SetHandledException`. + These are alternatives to :c:func:`PyErr_SetExcInfo()` and + :c:func:`PyErr_GetExcInfo()` which work with the legacy 3-tuple + representation of exceptions. + (Contributed by Irit Katriel in :issue:`46343`.) + + Porting to Python 3.11 ---------------------- diff --git a/Include/cpython/pyerrors.h b/Include/cpython/pyerrors.h index 5281fde..08630cc 100644 --- a/Include/cpython/pyerrors.h +++ b/Include/cpython/pyerrors.h @@ -91,6 +91,8 @@ typedef PyOSErrorObject PyWindowsErrorObject; PyAPI_FUNC(void) _PyErr_SetKeyError(PyObject *); PyAPI_FUNC(_PyErr_StackItem*) _PyErr_GetTopmostException(PyThreadState *tstate); +PyAPI_FUNC(PyObject*) _PyErr_GetHandledException(PyThreadState *); +PyAPI_FUNC(void) _PyErr_SetHandledException(PyThreadState *, PyObject *); PyAPI_FUNC(void) _PyErr_GetExcInfo(PyThreadState *, PyObject **, PyObject **, PyObject **); /* Context manipulation (PEP 3134) */ diff --git a/Include/pyerrors.h b/Include/pyerrors.h index 77d7914..34e3de3 100644 --- a/Include/pyerrors.h +++ b/Include/pyerrors.h @@ -18,6 +18,10 @@ PyAPI_FUNC(PyObject *) PyErr_Occurred(void); PyAPI_FUNC(void) PyErr_Clear(void); PyAPI_FUNC(void) PyErr_Fetch(PyObject **, PyObject **, PyObject **); PyAPI_FUNC(void) PyErr_Restore(PyObject *, PyObject *, PyObject *); +#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030b0000 +PyAPI_FUNC(PyObject*) PyErr_GetHandledException(void); +PyAPI_FUNC(void) PyErr_SetHandledException(PyObject *); +#endif #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x03030000 PyAPI_FUNC(void) PyErr_GetExcInfo(PyObject **, PyObject **, PyObject **); PyAPI_FUNC(void) PyErr_SetExcInfo(PyObject *, PyObject *, PyObject *); diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py index 40e4774..eb0edbf 100644 --- a/Lib/test/test_capi.py +++ b/Lib/test/test_capi.py @@ -88,6 +88,28 @@ class CAPITest(unittest.TestCase): def test_memoryview_from_NULL_pointer(self): self.assertRaises(ValueError, _testcapi.make_memoryview_from_NULL_pointer) + def test_exception(self): + raised_exception = ValueError("5") + new_exc = TypeError("TEST") + try: + raise raised_exception + except ValueError as e: + orig_sys_exception = sys.exception() + orig_exception = _testcapi.set_exception(new_exc) + new_sys_exception = sys.exception() + new_exception = _testcapi.set_exception(orig_exception) + reset_sys_exception = sys.exception() + + self.assertEqual(orig_exception, e) + + self.assertEqual(orig_exception, raised_exception) + self.assertEqual(orig_sys_exception, orig_exception) + self.assertEqual(reset_sys_exception, orig_exception) + self.assertEqual(new_exception, new_exc) + self.assertEqual(new_sys_exception, new_exception) + else: + self.fail("Exception not raised") + def test_exc_info(self): raised_exception = ValueError("5") new_exc = TypeError("TEST") diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py index efd3b1b..0656ff5 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -152,6 +152,7 @@ SYMBOL_NAMES = ( "PyErr_Format", "PyErr_FormatV", "PyErr_GetExcInfo", + "PyErr_GetHandledException", "PyErr_GivenExceptionMatches", "PyErr_NewException", "PyErr_NewExceptionWithDoc", @@ -168,6 +169,7 @@ SYMBOL_NAMES = ( "PyErr_SetFromErrnoWithFilename", "PyErr_SetFromErrnoWithFilenameObject", "PyErr_SetFromErrnoWithFilenameObjects", + "PyErr_SetHandledException", "PyErr_SetImportError", "PyErr_SetImportErrorSubclass", "PyErr_SetInterrupt", diff --git a/Misc/NEWS.d/next/C API/2022-01-11-12-52-37.bpo-46343.JQJWhZ.rst b/Misc/NEWS.d/next/C API/2022-01-11-12-52-37.bpo-46343.JQJWhZ.rst new file mode 100644 index 0000000..1ac8da8 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2022-01-11-12-52-37.bpo-46343.JQJWhZ.rst @@ -0,0 +1,5 @@ +Added :c:func:`PyErr_GetHandledException` and +:c:func:`PyErr_SetHandledException` as simpler alternatives to +:c:func:`PyErr_GetExcInfo` and :c:func:`PyErr_SetExcInfo`. + +They are included in the stable ABI. diff --git a/Misc/stable_abi.txt b/Misc/stable_abi.txt index 4864bf3..66777a6 100644 --- a/Misc/stable_abi.txt +++ b/Misc/stable_abi.txt @@ -2253,3 +2253,8 @@ function PyMemoryView_FromBuffer data Py_Version added 3.11 +function PyErr_GetHandledException + added 3.11 +function PyErr_SetHandledException + added 3.11 + diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 13dd294..71683ab 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2563,6 +2563,16 @@ set_errno(PyObject *self, PyObject *args) } static PyObject * +test_set_exception(PyObject *self, PyObject *new_exc) +{ + PyObject *exc = PyErr_GetHandledException(); + assert(PyExceptionInstance_Check(exc) || exc == NULL); + + PyErr_SetHandledException(new_exc); + return exc; +} + +static PyObject * test_set_exc_info(PyObject *self, PyObject *args) { PyObject *orig_exc; @@ -6068,6 +6078,7 @@ static PyMethodDef TestMethods[] = { #endif {"traceback_print", traceback_print, METH_VARARGS}, {"exception_print", exception_print, METH_VARARGS}, + {"set_exception", test_set_exception, METH_O}, {"set_exc_info", test_set_exc_info, METH_VARARGS}, {"argparsing", argparsing, METH_VARARGS}, {"code_newempty", code_newempty, METH_VARARGS}, diff --git a/PC/python3dll.c b/PC/python3dll.c index 70f11dc..0aee2ae 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -196,6 +196,7 @@ EXPORT_FUNC(PyErr_Fetch) EXPORT_FUNC(PyErr_Format) EXPORT_FUNC(PyErr_FormatV) EXPORT_FUNC(PyErr_GetExcInfo) +EXPORT_FUNC(PyErr_GetHandledException) EXPORT_FUNC(PyErr_GivenExceptionMatches) EXPORT_FUNC(PyErr_NewException) EXPORT_FUNC(PyErr_NewExceptionWithDoc) @@ -218,6 +219,7 @@ EXPORT_FUNC(PyErr_SetFromErrnoWithFilenameObject) EXPORT_FUNC(PyErr_SetFromErrnoWithFilenameObjects) EXPORT_FUNC(PyErr_SetFromWindowsErr) EXPORT_FUNC(PyErr_SetFromWindowsErrWithFilename) +EXPORT_FUNC(PyErr_SetHandledException) EXPORT_FUNC(PyErr_SetImportError) EXPORT_FUNC(PyErr_SetImportErrorSubclass) EXPORT_FUNC(PyErr_SetInterrupt) diff --git a/Python/errors.c b/Python/errors.c index e170c9d..ce77858 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -499,6 +499,38 @@ _PyErr_GetExcInfo(PyThreadState *tstate, Py_XINCREF(*p_traceback); } +PyObject* +_PyErr_GetHandledException(PyThreadState *tstate) +{ + _PyErr_StackItem *exc_info = _PyErr_GetTopmostException(tstate); + PyObject *exc = exc_info->exc_value; + if (exc == NULL || exc == Py_None) { + return NULL; + } + return Py_NewRef(exc); +} + +PyObject* +PyErr_GetHandledException(void) +{ + PyThreadState *tstate = _PyThreadState_GET(); + return _PyErr_GetHandledException(tstate); +} + +void +_PyErr_SetHandledException(PyThreadState *tstate, PyObject *exc) +{ + PyObject *oldexc = tstate->exc_info->exc_value; + tstate->exc_info->exc_value = Py_XNewRef(exc); + Py_XDECREF(oldexc); +} + +void +PyErr_SetHandledException(PyObject *exc) +{ + PyThreadState *tstate = _PyThreadState_GET(); + _PyErr_SetHandledException(tstate, exc); +} void PyErr_GetExcInfo(PyObject **p_type, PyObject **p_value, PyObject **p_traceback) @@ -510,17 +542,10 @@ PyErr_GetExcInfo(PyObject **p_type, PyObject **p_value, PyObject **p_traceback) void PyErr_SetExcInfo(PyObject *type, PyObject *value, PyObject *traceback) { - PyThreadState *tstate = _PyThreadState_GET(); - - PyObject *oldvalue = tstate->exc_info->exc_value; - - tstate->exc_info->exc_value = value; - + PyErr_SetHandledException(value); /* These args are no longer used, but we still need to steal a ref */ Py_XDECREF(type); Py_XDECREF(traceback); - - Py_XDECREF(oldvalue); } -- cgit v0.12