Skip to content

Commit aa2efcb

Browse files
committed
Issue #14098: New functions PyErr_GetExcInfo and PyErr_SetExcInfo.
Patch by Stefan Behnel.
1 parent e27b360 commit aa2efcb

File tree

6 files changed

+120
-0
lines changed

6 files changed

+120
-0
lines changed

Doc/c-api/exceptions.rst

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,41 @@ in various ways. There is a separate error indicator for each thread.
129129
exception state.
130130
131131
132+
.. c:function:: void PyErr_GetExcInfo(PyObject **ptype, PyObject **pvalue, PyObject **ptraceback)
133+
134+
Retrieve the exception info, as known from ``sys.exc_info()``. This refers
135+
to an exception that was already caught, not to an exception that was
136+
freshly raised. Returns new references for the three objects, any of which
137+
may be *NULL*. Does not modify the exception info state.
138+
139+
.. note::
140+
141+
This function is not normally used by code that wants to handle exceptions.
142+
Rather, it can be used when code needs to save and restore the exception
143+
state temporarily. Use :c:func:`PyErr_SetExcInfo` to restore or clear the
144+
exception state.
145+
146+
.. versionadded:: 3.3
147+
148+
149+
.. c:function:: void PyErr_SetExcInfo(PyObject *type, PyObject *value, PyObject *traceback)
150+
151+
Set the exception info, as known from ``sys.exc_info()``. This refers
152+
to an exception that was already caught, not to an exception that was
153+
freshly raised. This function steals the references of the arguments.
154+
To clear the exception state, pass *NULL* for all three arguments.
155+
For general rules about the three arguments, see :c:func:`PyErr_Restore`.
156+
157+
.. note::
158+
159+
This function is not normally used by code that wants to handle exceptions.
160+
Rather, it can be used when code needs to save and restore the exception
161+
state temporarily. Use :c:func:`PyErr_GetExcInfo` to read the exception
162+
state.
163+
164+
.. versionadded:: 3.3
165+
166+
132167
.. c:function:: void PyErr_SetString(PyObject *type, const char *message)
133168
134169
This is the most common way to set the error indicator. The first argument

Include/pyerrors.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ PyAPI_FUNC(PyObject *) PyErr_Occurred(void);
8282
PyAPI_FUNC(void) PyErr_Clear(void);
8383
PyAPI_FUNC(void) PyErr_Fetch(PyObject **, PyObject **, PyObject **);
8484
PyAPI_FUNC(void) PyErr_Restore(PyObject *, PyObject *, PyObject *);
85+
PyAPI_FUNC(void) PyErr_GetExcInfo(PyObject **, PyObject **, PyObject **);
86+
PyAPI_FUNC(void) PyErr_SetExcInfo(PyObject *, PyObject *, PyObject *);
8587

8688
#if defined(__clang__) || \
8789
(defined(__GNUC__) && \

Lib/test/test_capi.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,29 @@ def test_no_FatalError_infinite_loop(self):
5555
def test_memoryview_from_NULL_pointer(self):
5656
self.assertRaises(ValueError, _testcapi.make_memoryview_from_NULL_pointer)
5757

58+
def test_exc_info(self):
59+
raised_exception = ValueError("5")
60+
new_exc = TypeError("TEST")
61+
try:
62+
raise raised_exception
63+
except ValueError as e:
64+
tb = e.__traceback__
65+
orig_sys_exc_info = sys.exc_info()
66+
orig_exc_info = _testcapi.set_exc_info(new_exc.__class__, new_exc, None)
67+
new_sys_exc_info = sys.exc_info()
68+
new_exc_info = _testcapi.set_exc_info(*orig_exc_info)
69+
reset_sys_exc_info = sys.exc_info()
70+
71+
self.assertEqual(orig_exc_info[1], e)
72+
73+
self.assertSequenceEqual(orig_exc_info, (raised_exception.__class__, raised_exception, tb))
74+
self.assertSequenceEqual(orig_sys_exc_info, orig_exc_info)
75+
self.assertSequenceEqual(reset_sys_exc_info, orig_exc_info)
76+
self.assertSequenceEqual(new_exc_info, (new_exc.__class__, new_exc, None))
77+
self.assertSequenceEqual(new_sys_exc_info, new_exc_info)
78+
else:
79+
self.assertTrue(False)
80+
5881
@unittest.skipUnless(threading, 'Threading required for this test.')
5982
class TestPendingCalls(unittest.TestCase):
6083

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ What's New in Python 3.3.0 Alpha 3?
1010
Core and Builtins
1111
-----------------
1212

13+
- Issue #14098: New functions PyErr_GetExcInfo and PyErr_SetExcInfo.
14+
Patch by Stefan Behnel.
15+
1316
- Issue #14385: It is now possible to use a custom type for the __builtins__
1417
namespace, instead of a dict. It can be used for sandboxing for example.
1518
Raise also a NameError instead of ImportError if __build_class__ name if not

Modules/_testcapimodule.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1639,6 +1639,29 @@ raise_exception(PyObject *self, PyObject *args)
16391639
return NULL;
16401640
}
16411641

1642+
static PyObject *
1643+
test_set_exc_info(PyObject *self, PyObject *args)
1644+
{
1645+
PyObject *orig_exc;
1646+
PyObject *new_type, *new_value, *new_tb;
1647+
PyObject *type, *value, *tb;
1648+
if (!PyArg_ParseTuple(args, "OOO:test_set_exc_info",
1649+
&new_type, &new_value, &new_tb))
1650+
return NULL;
1651+
1652+
PyErr_GetExcInfo(&type, &value, &tb);
1653+
1654+
Py_INCREF(new_type);
1655+
Py_INCREF(new_value);
1656+
Py_INCREF(new_tb);
1657+
PyErr_SetExcInfo(new_type, new_value, new_tb);
1658+
1659+
orig_exc = PyTuple_Pack(3, type ? type : Py_None, value ? value : Py_None, tb ? tb : Py_None);
1660+
Py_XDECREF(type);
1661+
Py_XDECREF(value);
1662+
Py_XDECREF(tb);
1663+
return orig_exc;
1664+
}
16421665

16431666
static int test_run_counter = 0;
16441667

@@ -2471,6 +2494,7 @@ static PyMethodDef TestMethods[] = {
24712494
#endif
24722495
{"traceback_print", traceback_print, METH_VARARGS},
24732496
{"exception_print", exception_print, METH_VARARGS},
2497+
{"set_exc_info", test_set_exc_info, METH_VARARGS},
24742498
{"argparsing", argparsing, METH_VARARGS},
24752499
{"code_newempty", code_newempty, METH_VARARGS},
24762500
{"make_exception_with_doc", (PyCFunction)make_exception_with_doc,

Python/errors.c

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,39 @@ PyErr_Clear(void)
320320
PyErr_Restore(NULL, NULL, NULL);
321321
}
322322

323+
void
324+
PyErr_GetExcInfo(PyObject **p_type, PyObject **p_value, PyObject **p_traceback)
325+
{
326+
PyThreadState *tstate = PyThreadState_GET();
327+
328+
*p_type = tstate->exc_type;
329+
*p_value = tstate->exc_value;
330+
*p_traceback = tstate->exc_traceback;
331+
332+
Py_XINCREF(*p_type);
333+
Py_XINCREF(*p_value);
334+
Py_XINCREF(*p_traceback);
335+
}
336+
337+
void
338+
PyErr_SetExcInfo(PyObject *p_type, PyObject *p_value, PyObject *p_traceback)
339+
{
340+
PyObject *oldtype, *oldvalue, *oldtraceback;
341+
PyThreadState *tstate = PyThreadState_GET();
342+
343+
oldtype = tstate->exc_type;
344+
oldvalue = tstate->exc_value;
345+
oldtraceback = tstate->exc_traceback;
346+
347+
tstate->exc_type = p_type;
348+
tstate->exc_value = p_value;
349+
tstate->exc_traceback = p_traceback;
350+
351+
Py_XDECREF(oldtype);
352+
Py_XDECREF(oldvalue);
353+
Py_XDECREF(oldtraceback);
354+
}
355+
323356
/* Convenience functions to set a type error exception and return 0 */
324357

325358
int

0 commit comments

Comments
 (0)