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

This commit is contained in:
Irit Katriel 2021-11-30 22:37:04 +00:00 committed by GitHub
parent af8c8caaf5
commit 8a45ca542a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 103 additions and 35 deletions

View file

@ -482,7 +482,6 @@ Querying the error indicator
to an exception that was *already caught*, not to an exception that was to an exception that was *already caught*, not to an exception that was
freshly raised. This function steals the references of the arguments. freshly raised. This function steals the references of the arguments.
To clear the exception state, pass ``NULL`` for all three 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:: .. note::
@ -493,6 +492,12 @@ Querying the error indicator
.. versionadded:: 3.3 .. versionadded:: 3.3
.. versionchanged:: 3.11
The ``type`` and ``traceback`` arguments are no longer used and
can be NULL. The interpreter now derives them from the exception
instance (the ``value`` argument). The function still steals
references of all three arguments.
Signal Handling Signal Handling
=============== ===============

View file

@ -396,9 +396,14 @@ always available.
``(type, value, traceback)``. Their meaning is: *type* gets the type of the ``(type, value, traceback)``. Their meaning is: *type* gets the type of the
exception being handled (a subclass of :exc:`BaseException`); *value* gets exception being handled (a subclass of :exc:`BaseException`); *value* gets
the exception instance (an instance of the exception type); *traceback* gets the exception instance (an instance of the exception type); *traceback* gets
a :ref:`traceback object <traceback-objects>` which encapsulates the call a :ref:`traceback object <traceback-objects>` which typically encapsulates
stack at the point where the exception originally occurred. 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 .. data:: exec_prefix

View file

@ -655,6 +655,12 @@ and information about handling exceptions is in section :ref:`try`.
The ``__suppress_context__`` attribute to suppress automatic display of the The ``__suppress_context__`` attribute to suppress automatic display of the
exception context. exception context.
.. versionchanged:: 3.11
If the traceback of the active exception is modified in an :keyword:`except`
clause, a subsequent ``raise`` statement re-raises the exception with the
modified traceback. Previously, the exception was re-raised with the
traceback it had when it was caught.
.. _break: .. _break:
The :keyword:`!break` statement The :keyword:`!break` statement

View file

@ -181,6 +181,12 @@ Other CPython Implementation Changes
hash-based pyc files now use ``siphash13``, too. hash-based pyc files now use ``siphash13``, too.
(Contributed by Inada Naoki in :issue:`29410`.) (Contributed by Inada Naoki in :issue:`29410`.)
* When an active exception is re-raised by a :keyword:`raise` statement with no parameters,
the traceback attached to this exception is now always ``sys.exc_info()[1].__traceback__``.
This means that changes made to the traceback in the current :keyword:`except` clause are
reflected in the re-raised exception.
(Contributed by Irit Katriel in :issue:`45711`.)
New Modules New Modules
=========== ===========
@ -266,6 +272,16 @@ sqlite3
(Contributed by Erlend E. Aasland in :issue:`45828`.) (Contributed by Erlend E. Aasland in :issue:`45828`.)
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 threading
--------- ---------
@ -579,6 +595,17 @@ New Features
suspend and resume tracing and profiling. suspend and resume tracing and profiling.
(Contributed by Victor Stinner in :issue:`43760`.) (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 Porting to Python 3.11
---------------------- ----------------------

View file

@ -0,0 +1,6 @@
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 provided to it.

View file

@ -5918,20 +5918,17 @@ do_raise(PyThreadState *tstate, PyObject *exc, PyObject *cause)
if (exc == NULL) { if (exc == NULL) {
/* Reraise */ /* Reraise */
_PyErr_StackItem *exc_info = _PyErr_GetTopmostException(tstate); _PyErr_StackItem *exc_info = _PyErr_GetTopmostException(tstate);
PyObject *tb;
type = exc_info->exc_type;
value = exc_info->exc_value; value = exc_info->exc_value;
tb = exc_info->exc_traceback;
assert(((Py_IsNone(value) || value == NULL)) ==
((Py_IsNone(type) || type == NULL)));
if (Py_IsNone(value) || value == NULL) { if (Py_IsNone(value) || value == NULL) {
_PyErr_SetString(tstate, PyExc_RuntimeError, _PyErr_SetString(tstate, PyExc_RuntimeError,
"No active exception to reraise"); "No active exception to reraise");
return 0; return 0;
} }
assert(PyExceptionInstance_Check(value));
type = PyExceptionInstance_Class(value);
Py_XINCREF(type); Py_XINCREF(type);
Py_XINCREF(value); Py_XINCREF(value);
Py_XINCREF(tb); PyObject *tb = PyException_GetTraceback(value); /* new ref */
_PyErr_Restore(tstate, type, value, tb); _PyErr_Restore(tstate, type, value, tb);
return 1; return 1;
} }

View file

@ -470,6 +470,33 @@ PyErr_Clear(void)
_PyErr_Clear(tstate); _PyErr_Clear(tstate);
} }
static PyObject*
get_exc_type(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 *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 void
_PyErr_GetExcInfo(PyThreadState *tstate, _PyErr_GetExcInfo(PyThreadState *tstate,
@ -477,18 +504,9 @@ _PyErr_GetExcInfo(PyThreadState *tstate,
{ {
_PyErr_StackItem *exc_info = _PyErr_GetTopmostException(tstate); _PyErr_StackItem *exc_info = _PyErr_GetTopmostException(tstate);
*p_type = get_exc_type(exc_info->exc_value);
*p_value = exc_info->exc_value; *p_value = exc_info->exc_value;
*p_traceback = exc_info->exc_traceback; *p_traceback = get_exc_traceback(exc_info->exc_value);
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);
}
Py_XINCREF(*p_type); Py_XINCREF(*p_type);
Py_XINCREF(*p_value); Py_XINCREF(*p_value);
@ -504,7 +522,7 @@ PyErr_GetExcInfo(PyObject **p_type, PyObject **p_value, PyObject **p_traceback)
} }
void void
PyErr_SetExcInfo(PyObject *p_type, PyObject *p_value, PyObject *p_traceback) PyErr_SetExcInfo(PyObject *type, PyObject *value, PyObject *traceback)
{ {
PyObject *oldtype, *oldvalue, *oldtraceback; PyObject *oldtype, *oldvalue, *oldtraceback;
PyThreadState *tstate = _PyThreadState_GET(); PyThreadState *tstate = _PyThreadState_GET();
@ -513,9 +531,16 @@ PyErr_SetExcInfo(PyObject *p_type, PyObject *p_value, PyObject *p_traceback)
oldvalue = tstate->exc_info->exc_value; oldvalue = tstate->exc_info->exc_value;
oldtraceback = tstate->exc_info->exc_traceback; oldtraceback = tstate->exc_info->exc_traceback;
tstate->exc_info->exc_type = p_type;
tstate->exc_info->exc_value = p_value; tstate->exc_info->exc_type = get_exc_type(value);
tstate->exc_info->exc_traceback = p_traceback; Py_XINCREF(tstate->exc_info->exc_type);
tstate->exc_info->exc_value = value;
tstate->exc_info->exc_traceback = get_exc_traceback(value);
Py_XINCREF(tstate->exc_info->exc_traceback);
/* These args are no longer used, but we still need to steal a ref */
Py_XDECREF(type);
Py_XDECREF(traceback);
Py_XDECREF(oldtype); Py_XDECREF(oldtype);
Py_XDECREF(oldvalue); Py_XDECREF(oldvalue);
@ -527,22 +552,19 @@ PyObject*
_PyErr_StackItemToExcInfoTuple(_PyErr_StackItem *err_info) _PyErr_StackItemToExcInfoTuple(_PyErr_StackItem *err_info)
{ {
PyObject *exc_value = err_info->exc_value; 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) ? PyObject *exc_type = get_exc_type(exc_value);
PyExceptionInstance_Class(exc_value) : PyObject *exc_traceback = get_exc_traceback(exc_value);
Py_None;
return Py_BuildValue( return Py_BuildValue(
"(OOO)", "(OOO)",
exc_type, exc_type ? exc_type : Py_None,
exc_value, exc_value ? exc_value : Py_None,
err_info->exc_traceback != NULL ? exc_traceback ? exc_traceback : Py_None);
err_info->exc_traceback : Py_None);
} }