8000 gh-90501: Add PyErr_GetHandledException and PyErr_SetHandledException by iritkatriel · Pull Request #30531 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

gh-90501: Add PyErr_GetHandledException and PyErr_SetHandledException #30531

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Apr 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
2b4d518
bpo-46343: Add PyErr_GetActiveException and PyErr_SetActiveException
iritkatriel Jan 11, 2022
87e6087
GetActiveException returns exception as its return value
iritkatriel Jan 11, 2022
ee4d98e
fix test
iritkatriel Jan 11, 2022
ebd9278
use sys.exception instead of sys.exc_info
iritkatriel Jan 13, 2022
5631d32
updated whatsnew
iritkatriel Jan 13, 2022
20fa421
regen-limited-abi
iritkatriel Jan 13, 2022
3a7ab17
[1] Do not steal reference. [2] () --> (void)
iritkatriel Jan 13, 2022
dbfd9d4
Use the new Py_(X)NewRef in a couple of places. Use self.fail() in test.
iritkatriel Jan 17, 2022
a412e4b
add :func: markup in doc
iritkatriel Jan 17, 2022
bcdf599
split out _PyErr_SetActiveException(tstate, exc)
iritkatriel Feb 1, 2022
c63eabf
Merge remote-tracking branch 'upstream/main' into bpo-46343-GetSetExc…
iritkatriel Mar 15, 2022
18eb35c
read --> get
iritkatriel Mar 23, 2022
87981ab
fix limited api version
iritkatriel Mar 23, 2022
9555597
PyErr_GetActiveException always returns NULL if no exception. Fix ref…
iritkatriel Mar 23, 2022
e0ee504
doc changes following review by Victor
iritkatriel Mar 23, 2022
e6a3a7e
renamed Get/SetActiveException --> Get/SetHandledException
iritkatriel Apr 13, 2022
261464c
Merge remote-tracking branch 'upstream/main' into bpo-46343-GetSetExc…
iritkatriel Apr 13, 2022
0355cc9
add new functions to Doc/data/stable_abi.dat
iritkatriel Apr 13, 2022
9382215
alphabetical
iritkatriel Apr 13, 2022
1cc3a4b
make regen-limited-abi
iritkatriel Apr 13, 2022
ffa870b
clearer doc
iritkatriel Apr 15, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 40 additions & 4 deletions Doc/c-api/exceptions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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::

Expand All @@ -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::

Expand Down
2 changes: 2 additions & 0 deletions Doc/data/stable_abi.dat

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 5 additions & 12 deletions Doc/library/sys.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 8 additions & 0 deletions Doc/whatsnew/3.11.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1145,6 +1145,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
----------------------

Expand Down
2 changes: 2 additions & 0 deletions Include/cpython/pyerrors.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) */
Expand Down
4 changes: 4 additions & 0 deletions Include/pyerrors.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 *);
Expand Down
22 changes: 22 additions & 0 deletions Lib/test/test_capi.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,28 @@ def test_no_FatalError_infinite_loop(self):
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")
Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_stable_abi_ctypes.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -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.
5 changes: 5 additions & 0 deletions Misc/stable_abi.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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

11 changes: 11 additions & 0 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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},
Expand Down
2 changes: 2 additions & 0 deletions PC/python3dll.c

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 33 additions & 8 deletions Python/errors.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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);
}


Expand Down
0