gh-90501: Add PyErr_GetHandledException and PyErr_SetHandledException (GH-30531)

This commit is contained in:
Irit Katriel 2022-04-15 19:57:47 +01:00 committed by GitHub
parent c06a4ffe81
commit 5d421d7342
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 141 additions and 24 deletions

View File

@ -460,12 +460,46 @@ Querying the error indicator
}
.. 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
.. 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.
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 exception info, as known from ``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.
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::

View 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,,

View 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

View 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
----------------------

View 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) */

View 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 *);

View 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")

View 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",

View File

@ -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.

View 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

View 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},

2
PC/python3dll.c generated
View File

@ -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)

View 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);
}