Skip to content
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

bpo-45711: Change exc_info related APIs to derive type and traceback from the exception instance #29780

Merged
merged 9 commits into from
Nov 30, 2021
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.
iritkatriel marked this conversation as resolved.
Show resolved Hide resolved
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;
iritkatriel marked this conversation as resolved.
Show resolved Hide resolved
}
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;
iritkatriel marked this conversation as resolved.
Show resolved Hide resolved

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