]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-90501: Add PyErr_GetHandledException and PyErr_SetHandledException (GH-30531)
authorIrit Katriel <1055913+iritkatriel@users.noreply.github.com>
Fri, 15 Apr 2022 18:57:47 +0000 (19:57 +0100)
committerGitHub <noreply@github.com>
Fri, 15 Apr 2022 18:57:47 +0000 (19:57 +0100)
13 files changed:
Doc/c-api/exceptions.rst
Doc/data/stable_abi.dat
Doc/library/sys.rst
Doc/whatsnew/3.11.rst
Include/cpython/pyerrors.h
Include/pyerrors.h
Lib/test/test_capi.py
Lib/test/test_stable_abi_ctypes.py
Misc/NEWS.d/next/C API/2022-01-11-12-52-37.bpo-46343.JQJWhZ.rst [new file with mode: 0644]
Misc/stable_abi.txt
Modules/_testcapimodule.c
PC/python3dll.c
Python/errors.c

index a5a93d0ebbf284804ee362ad503eb9b012764930..7bfeca5958cc424579c8b88d3ec23c1c937ef4c4 100644 (file)
@@ -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::
 
index 849a2cfd51f24aa181812e7d056ba520ce1aebf0..5387d0bf983fa2f0aa695bae217011939d77b8f1 100644 (file)
@@ -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,,
index 126da31b5bd32d1ab24ed130ae146de5119caf49..2a8b532b592e871a340f9720360e93578101217a 100644 (file)
@@ -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
 
index a5a52682b503c4a459eac4f491946f5c6e12df08..b6f47f532ca4cc3dd5c7c1f0193dd2bf915b4f88 100644 (file)
@@ -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
 ----------------------
 
index 5281fde1f1a54c056ec582e84b86d10325a096fe..08630cce8ac90ac6ee12d246318520fa3cf3f2d8 100644 (file)
@@ -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) */
index 77d791427d492899f9763a97993b2390e3e203f3..34e3de3328f4108a387cf4f6b006e93cbf31577b 100644 (file)
@@ -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 *);
index 40e4774c6b8ed249b547bc9f44287b56ab097957..eb0edbf5a3a1234011cdcd4255cc4cffe6d27e7f 100644 (file)
@@ -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")
index efd3b1b7cd2d242d55e365ac5902000b88d2709d..0656ff5581be5d7cc15d153adce231da8d48e625 100644 (file)
@@ -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 (file)
index 0000000..1ac8da8
--- /dev/null
@@ -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.
index 4864bf319a76f5447a5c9b942f1bb015dc5a24a8..66777a62c430183aa8995970cdb0d22397574738 100644 (file)
@@ -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
+
index 13dd29427aa2ca8fb22faddfa7e29e8be2abd40d..71683abebb23163ca01631519735058cfcfed03f 100644 (file)
@@ -2562,6 +2562,16 @@ set_errno(PyObject *self, PyObject *args)
     Py_RETURN_NONE;
 }
 
+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)
 {
@@ -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},
index 70f11dc1905547325a1f950af9662da389cb25d7..0aee2aec847261cdb26574223212522975f2bc46 100755 (executable)
@@ -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)
index e170c9dff2dbbcc121b3867190c54b1ab7b65121..ce7785855b8e548b892b2efb165c84094c689e96 100644 (file)
@@ -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);
 }