From 04407ce2e81bceced54247c70c75e7724c5605e9 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Thu, 25 Nov 2021 17:31:07 +0000 Subject: [PATCH 1/8] bpo-45711: Change exc_info related APIs to derive type and traceback from the exception instance --- Doc/c-api/exceptions.rst | 7 +++- Doc/library/sys.rst | 11 +++++-- Doc/whatsnew/3.11.rst | 19 +++++++++++ Python/errors.c | 70 ++++++++++++++++++++++++++-------------- 4 files changed, 79 insertions(+), 28 deletions(-) diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst index 5d90248f85a5d4..f8bafedc70dbe1 100644 --- a/Doc/c-api/exceptions.rst +++ b/Doc/c-api/exceptions.rst @@ -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:: @@ -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 =============== diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst index 175fc091652068..7d1b21f05edb19 100644 --- a/Doc/library/sys.rst +++ b/Doc/library/sys.rst @@ -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 ` which encapsulates the call - stack at the point where the exception originally occurred. - + a :ref:`traceback object ` 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 diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index 9751f894f9a9a5..959978c2a3ab2f 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -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 --------- @@ -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 ---------------------- diff --git a/Python/errors.c b/Python/errors.c index 6e74d19b78ef33..7415f17f3720c4 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -470,6 +470,33 @@ 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, @@ -477,18 +504,9 @@ _PyErr_GetExcInfo(PyThreadState *tstate, { _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); @@ -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); @@ -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); } From e8645ff29a651f28a41792ed9746cd251bf3831b Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Thu, 25 Nov 2021 17:51:30 +0000 Subject: [PATCH 2/8] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Core and Builtins/2021-11-25-17-51-29.bpo-45711.D2igmz.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2021-11-25-17-51-29.bpo-45711.D2igmz.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-11-25-17-51-29.bpo-45711.D2igmz.rst b/Misc/NEWS.d/next/Core and Builtins/2021-11-25-17-51-29.bpo-45711.D2igmz.rst new file mode 100644 index 00000000000000..ffb6688a67d23c --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2021-11-25-17-51-29.bpo-45711.D2igmz.rst @@ -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. \ No newline at end of file From 971887a4e3d508531c76bc9f3fb05259acfbcfd9 Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Fri, 26 Nov 2021 10:50:01 +0000 Subject: [PATCH 3/8] Tweak news text Co-authored-by: Guido van Rossum --- .../Core and Builtins/2021-11-25-17-51-29.bpo-45711.D2igmz.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-11-25-17-51-29.bpo-45711.D2igmz.rst b/Misc/NEWS.d/next/Core and Builtins/2021-11-25-17-51-29.bpo-45711.D2igmz.rst index ffb6688a67d23c..9ede833a6f6f1b 100644 --- a/Misc/NEWS.d/next/Core and Builtins/2021-11-25-17-51-29.bpo-45711.D2igmz.rst +++ b/Misc/NEWS.d/next/Core and Builtins/2021-11-25-17-51-29.bpo-45711.D2igmz.rst @@ -1 +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. \ No newline at end of file +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. \ No newline at end of file From 995ec591eb4e00af481cd33bcec3e4a928aa4efe Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Fri, 26 Nov 2021 10:53:17 +0000 Subject: [PATCH 4/8] remove p_ prefix from arg names in PyErr_SetExcInfo --- Python/errors.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Python/errors.c b/Python/errors.c index 7415f17f3720c4..b3b11a60a94013 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -522,7 +522,7 @@ PyErr_GetExcInfo(PyObject **p_type, PyObject **p_value, PyObject **p_traceback) } void -PyErr_SetExcInfo(PyObject *p_type, PyObject *p_value, PyObject *p_traceback) +PyErr_SetExcInfo(PyObject *type, PyObject *value, PyObject *traceback) { PyObject *oldtype, *oldvalue, *oldtraceback; PyThreadState *tstate = _PyThreadState_GET(); @@ -532,15 +532,15 @@ PyErr_SetExcInfo(PyObject *p_type, PyObject *p_value, PyObject *p_traceback) oldtraceback = tstate->exc_info->exc_traceback; - tstate->exc_info->exc_type = get_exc_type(p_value); + tstate->exc_info->exc_type = get_exc_type(value); Py_XINCREF(tstate->exc_info->exc_type); - tstate->exc_info->exc_value = p_value; - tstate->exc_info->exc_traceback = get_exc_traceback(p_value); + 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(p_type); - Py_XDECREF(p_traceback); + Py_XDECREF(type); + Py_XDECREF(traceback); Py_XDECREF(oldtype); Py_XDECREF(oldvalue); From 92835f000c35c1e692d272246b142180c434d955 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Fri, 26 Nov 2021 10:56:00 +0000 Subject: [PATCH 5/8] type is None if value is None or NULL --- Python/errors.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Python/errors.c b/Python/errors.c index b3b11a60a94013..0a8b5a257fb2c7 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -474,7 +474,7 @@ static PyObject* get_exc_type(PyObject *exc_value) /* returns a borrowed ref */ { if (exc_value == NULL || exc_value == Py_None) { - return exc_value; + return Py_None; } else { assert(PyExceptionInstance_Check(exc_value)); From 4602710afd4a0e55a8e6ff5acb08063965153732 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Fri, 26 Nov 2021 11:02:38 +0000 Subject: [PATCH 6/8] wrap news text --- .../2021-11-25-17-51-29.bpo-45711.D2igmz.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-11-25-17-51-29.bpo-45711.D2igmz.rst b/Misc/NEWS.d/next/Core and Builtins/2021-11-25-17-51-29.bpo-45711.D2igmz.rst index 9ede833a6f6f1b..c499f185d2038f 100644 --- a/Misc/NEWS.d/next/Core and Builtins/2021-11-25-17-51-29.bpo-45711.D2igmz.rst +++ b/Misc/NEWS.d/next/Core and Builtins/2021-11-25-17-51-29.bpo-45711.D2igmz.rst @@ -1 +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. \ No newline at end of file +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. From 7bc139fdaf9739db53e519173a30a1aa0f6b4fb1 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Fri, 26 Nov 2021 11:03:42 +0000 Subject: [PATCH 7/8] tweak doc wording to add 'and can be NULL' --- Doc/c-api/exceptions.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst index f8bafedc70dbe1..27feab92dede6e 100644 --- a/Doc/c-api/exceptions.rst +++ b/Doc/c-api/exceptions.rst @@ -493,10 +493,10 @@ 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. + 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 From 05af230b8871e965028123daaba83e828a9fa84c Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Mon, 29 Nov 2021 00:00:15 +0000 Subject: [PATCH 8/8] Use traceback from exception instead of exc_info in reraise --- Doc/reference/simple_stmts.rst | 6 ++++++ Doc/whatsnew/3.11.rst | 6 ++++++ Python/ceval.c | 9 +++------ 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/Doc/reference/simple_stmts.rst b/Doc/reference/simple_stmts.rst index bb1209dfc33beb..3d02074960ff3c 100644 --- a/Doc/reference/simple_stmts.rst +++ b/Doc/reference/simple_stmts.rst @@ -655,6 +655,12 @@ and information about handling exceptions is in section :ref:`try`. The ``__suppress_context__`` attribute to suppress automatic display of the 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: The :keyword:`!break` statement diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index 959978c2a3ab2f..cb706ead8261f3 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -181,6 +181,12 @@ Other CPython Implementation Changes hash-based pyc files now use ``siphash13``, too. (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 =========== diff --git a/Python/ceval.c b/Python/ceval.c index 9beb1a4368226c..a893be0cf67924 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -5920,20 +5920,17 @@ do_raise(PyThreadState *tstate, PyObject *exc, PyObject *cause) if (exc == NULL) { /* Reraise */ _PyErr_StackItem *exc_info = _PyErr_GetTopmostException(tstate); - PyObject *tb; - type = exc_info->exc_type; 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) { _PyErr_SetString(tstate, PyExc_RuntimeError, "No active exception to reraise"); return 0; } + assert(PyExceptionInstance_Check(value)); + type = PyExceptionInstance_Class(value); Py_XINCREF(type); Py_XINCREF(value); - Py_XINCREF(tb); + PyObject *tb = PyException_GetTraceback(value); /* new ref */ _PyErr_Restore(tstate, type, value, tb); return 1; }