Skip to content
7 changes: 6 additions & 1 deletion Doc/c-api/exceptions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,6 @@ 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.
For general rules about the three arguments, see :c:func:`PyErr_Restore`.

.. note::

Expand All @@ -493,6 +492,12 @@ Querying the error indicator

.. versionadded:: 3.3

.. versionchanged:: 3.11
The ``type`` and ``traceback`` arguments are no longer used, the
interpreter now derives them the exception instance (the ``value``
argument). The function still steals references of all three
arguments.


Signal Handling
===============
Expand Down
11 changes: 8 additions & 3 deletions Doc/library/sys.rst
Original file line number Diff line number Diff line change
Expand Up @@ -396,9 +396,14 @@ always available.
``(type, value, traceback)``. Their meaning is: *type* gets the type of the
exception being handled (a subclass of :exc:`BaseException`); *value* gets
the exception instance (an instance of the exception type); *traceback* gets
a :ref:`traceback object <traceback-objects>` which encapsulates the call
stack at the point where the exception originally occurred.

a :ref:`traceback object <traceback-objects>` which typically encapsulates
the call stack at the point where the exception last occurred.

.. versionchanged:: 3.11
The ``type`` and ``traceback`` fields are now derived from the ``value``
(the exception instance), so when an exception is modified while it is
being handled, the changes are reflected in the results of subsequent
calls to :func:`exc_info`.

.. data:: exec_prefix

Expand Down
19 changes: 19 additions & 0 deletions Doc/whatsnew/3.11.rst
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,14 @@ sqlite3
threading mode the underlying SQLite library has been compiled with.
(Contributed by Erlend E. Aasland in :issue:`45613`.)

sys
---

* :func:`sys.exc_info` now derives the ``type`` and ``traceback`` fields
from the ``value`` (the exception instance), so when an exception is
modified while it is being handled, the changes are reflected in
the results of subsequent calls to :func:`exc_info`.
(Contributed by Irit Katriel in :issue:`45711`.)

threading
---------
Expand Down Expand Up @@ -572,6 +580,17 @@ New Features
suspend and resume tracing and profiling.
(Contributed by Victor Stinner in :issue:`43760`.)

* :c:func:`PyErr_SetExcInfo()` no longer uses the ``type`` and ``traceback``
arguments, the interpreter now derives those values from the exception
instance (the ``value`` argument). The function still steals references
of all three arguments.
(Contributed by Irit Katriel in :issue:`45711`.)

* :c:func:`PyErr_GetExcInfo()` now derives the ``type`` and ``traceback``
fields of the result from the exception instance (the ``value`` field).
(Contributed by Irit Katriel in :issue:`45711`.)


Porting to Python 3.11
----------------------

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The three values of ``exc_info`` are now always consistent with each other. In particular, the ``type`` and ``traceback`` fields are now derived from the exception instance. This impacts the return values of :func:`sys.exc_info` and :c:func:`PyErr_GetExcInfo()` if the exception instance is modified while the exception is handled, as well as :c:func:`PyErr_SetExcInfo()`, which now ignores the ``type`` and ``traceback`` arguments that are provided to it.
70 changes: 46 additions & 24 deletions Python/errors.c
Original file line number Diff line number Diff line change
Expand Up @@ -470,25 +470,43 @@ PyErr_Clear(void)
_PyErr_Clear(tstate);
}

static PyObject*
get_exc_type(PyObject *exc_value) /* returns a borrowed ref */
{
if (exc_value == NULL || exc_value == Py_None) {
return exc_value;
}
else {
assert(PyExceptionInstance_Check(exc_value));
PyObject *type = PyExceptionInstance_Class(exc_value);
assert(type != NULL);
return type;
}
}

static PyObject*
get_exc_traceback(PyObject *exc_value) /* returns a borrowed ref */
{
if (exc_value == NULL || exc_value == Py_None) {
return Py_None;
}
else {
assert(PyExceptionInstance_Check(exc_value));
PyObject *tb = PyException_GetTraceback(exc_value);
Py_XDECREF(tb);
return tb ? tb : Py_None;
}
}

void
_PyErr_GetExcInfo(PyThreadState *tstate,
PyObject **p_type, PyObject **p_value, PyObject **p_traceback)
{
_PyErr_StackItem *exc_info = _PyErr_GetTopmostException(tstate);

*p_type = get_exc_type(exc_info->exc_value);
*p_value = exc_info->exc_value;
*p_traceback = exc_info->exc_traceback;

if (*p_value == NULL || *p_value == Py_None) {
assert(exc_info->exc_type == NULL || exc_info->exc_type == Py_None);
*p_type = Py_None;
}
else {
assert(PyExceptionInstance_Check(*p_value));
assert(exc_info->exc_type == PyExceptionInstance_Class(*p_value));
*p_type = PyExceptionInstance_Class(*p_value);
}
*p_traceback = get_exc_traceback(exc_info->exc_value);

Py_XINCREF(*p_type);
Py_XINCREF(*p_value);
Expand All @@ -513,9 +531,16 @@ PyErr_SetExcInfo(PyObject *p_type, PyObject *p_value, PyObject *p_traceback)
oldvalue = tstate->exc_info->exc_value;
oldtraceback = tstate->exc_info->exc_traceback;

tstate->exc_info->exc_type = p_type;

tstate->exc_info->exc_type = get_exc_type(p_value);
Py_XINCREF(tstate->exc_info->exc_type);
tstate->exc_info->exc_value = p_value;
tstate->exc_info->exc_traceback = p_traceback;
tstate->exc_info->exc_traceback = get_exc_traceback(p_value);
Py_XINCREF(tstate->exc_info->exc_traceback);

/* These args are no longer used, but we still need to steal a ref */
Py_XDECREF(p_type);
Py_XDECREF(p_traceback);

Py_XDECREF(oldtype);
Py_XDECREF(oldvalue);
Expand All @@ -527,22 +552,19 @@ PyObject*
_PyErr_StackItemToExcInfoTuple(_PyErr_StackItem *err_info)
{
PyObject *exc_value = err_info->exc_value;
if (exc_value == NULL) {
exc_value = Py_None;
}

assert(exc_value == Py_None || PyExceptionInstance_Check(exc_value));
assert(exc_value == NULL ||
exc_value == Py_None ||
PyExceptionInstance_Check(exc_value));

PyObject *exc_type = PyExceptionInstance_Check(exc_value) ?
PyExceptionInstance_Class(exc_value) :
Py_None;
PyObject *exc_type = get_exc_type(exc_value);
PyObject *exc_traceback = get_exc_traceback(exc_value);

return Py_BuildValue(
"(OOO)",
exc_type,
exc_value,
err_info->exc_traceback != NULL ?
err_info->exc_traceback : Py_None);
exc_type ? exc_type : Py_None,
exc_value ? exc_value : Py_None,
exc_traceback ? exc_traceback : Py_None);
}


Expand Down