summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorIrit Katriel <1055913+iritkatriel@users.noreply.github.com>2022-04-15 18:57:47 (GMT)
committerGitHub <noreply@github.com>2022-04-15 18:57:47 (GMT)
commit5d421d7342fc0d278c129c05bea7028430e94a4e (patch)
treec5bb38c41d86c6289dfcb1f7937f2df07305f4d8
parentc06a4ffe818feddef3b5083d9746a1c0b82c84ab (diff)
downloadcpython-5d421d7342fc0d278c129c05bea7028430e94a4e.zip
cpython-5d421d7342fc0d278c129c05bea7028430e94a4e.tar.gz
cpython-5d421d7342fc0d278c129c05bea7028430e94a4e.tar.bz2
gh-90501: Add PyErr_GetHandledException and PyErr_SetHandledException (GH-30531)
-rw-r--r--Doc/c-api/exceptions.rst44
-rw-r--r--Doc/data/stable_abi.dat2
-rw-r--r--Doc/library/sys.rst17
-rw-r--r--Doc/whatsnew/3.11.rst8
-rw-r--r--Include/cpython/pyerrors.h2
-rw-r--r--Include/pyerrors.h4
-rw-r--r--Lib/test/test_capi.py22
-rw-r--r--Lib/test/test_stable_abi_ctypes.py2
-rw-r--r--Misc/NEWS.d/next/C API/2022-01-11-12-52-37.bpo-46343.JQJWhZ.rst5
-rw-r--r--Misc/stable_abi.txt5
-rw-r--r--Modules/_testcapimodule.c11
-rwxr-xr-xPC/python3dll.c2
-rw-r--r--Python/errors.c41
13 files changed, 141 insertions, 24 deletions
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);
}