Skip to content

Commit 8a45ca5

Browse files
authored
bpo-45711: Change exc_info related APIs to derive type and traceback from the exception instance (GH-29780)
1 parent af8c8ca commit 8a45ca5

File tree

7 files changed

+104
-36
lines changed

7 files changed

+104
-36
lines changed

Doc/c-api/exceptions.rst

+6-1
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,6 @@ Querying the error indicator
482482
to an exception that was *already caught*, not to an exception that was
483483
freshly raised. This function steals the references of the arguments.
484484
To clear the exception state, pass ``NULL`` for all three arguments.
485-
For general rules about the three arguments, see :c:func:`PyErr_Restore`.
486485
487486
.. note::
488487
@@ -493,6 +492,12 @@ Querying the error indicator
493492
494493
.. versionadded:: 3.3
495494
495+
.. versionchanged:: 3.11
496+
The ``type`` and ``traceback`` arguments are no longer used and
497+
can be NULL. The interpreter now derives them from the exception
498+
instance (the ``value`` argument). The function still steals
499+
references of all three arguments.
500+
496501
497502
Signal Handling
498503
===============

Doc/library/sys.rst

+8-3
Original file line numberDiff line numberDiff line change
@@ -396,9 +396,14 @@ always available.
396396
``(type, value, traceback)``. Their meaning is: *type* gets the type of the
397397
exception being handled (a subclass of :exc:`BaseException`); *value* gets
398398
the exception instance (an instance of the exception type); *traceback* gets
399-
a :ref:`traceback object <traceback-objects>` which encapsulates the call
400-
stack at the point where the exception originally occurred.
401-
399+
a :ref:`traceback object <traceback-objects>` which typically encapsulates
400+
the call stack at the point where the exception last occurred.
401+
402+
.. versionchanged:: 3.11
403+
The ``type`` and ``traceback`` fields are now derived from the ``value``
404+
(the exception instance), so when an exception is modified while it is
405+
being handled, the changes are reflected in the results of subsequent
406+
calls to :func:`exc_info`.
402407

403408
.. data:: exec_prefix
404409

Doc/reference/simple_stmts.rst

+6
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,12 @@ and information about handling exceptions is in section :ref:`try`.
655655
The ``__suppress_context__`` attribute to suppress automatic display of the
656656
exception context.
657657

658+
.. versionchanged:: 3.11
659+
If the traceback of the active exception is modified in an :keyword:`except`
660+
clause, a subsequent ``raise`` statement re-raises the exception with the
661+
modified traceback. Previously, the exception was re-raised with the
662+
traceback it had when it was caught.
663+
658664
.. _break:
659665

660666
The :keyword:`!break` statement

Doc/whatsnew/3.11.rst

+27
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,12 @@ Other CPython Implementation Changes
181181
hash-based pyc files now use ``siphash13``, too.
182182
(Contributed by Inada Naoki in :issue:`29410`.)
183183

184+
* When an active exception is re-raised by a :keyword:`raise` statement with no parameters,
185+
the traceback attached to this exception is now always ``sys.exc_info()[1].__traceback__``.
186+
This means that changes made to the traceback in the current :keyword:`except` clause are
187+
reflected in the re-raised exception.
188+
(Contributed by Irit Katriel in :issue:`45711`.)
189+
184190
New Modules
185191
===========
186192

@@ -266,6 +272,16 @@ sqlite3
266272
(Contributed by Erlend E. Aasland in :issue:`45828`.)
267273

268274

275+
sys
276+
---
277+
278+
* :func:`sys.exc_info` now derives the ``type`` and ``traceback`` fields
279+
from the ``value`` (the exception instance), so when an exception is
280+
modified while it is being handled, the changes are reflected in
281+
the results of subsequent calls to :func:`exc_info`.
282+
(Contributed by Irit Katriel in :issue:`45711`.)
283+
284+
269285
threading
270286
---------
271287

@@ -579,6 +595,17 @@ New Features
579595
suspend and resume tracing and profiling.
580596
(Contributed by Victor Stinner in :issue:`43760`.)
581597

598+
* :c:func:`PyErr_SetExcInfo()` no longer uses the ``type`` and ``traceback``
599+
arguments, the interpreter now derives those values from the exception
600+
instance (the ``value`` argument). The function still steals references
601+
of all three arguments.
602+
(Contributed by Irit Katriel in :issue:`45711`.)
603+
604+
* :c:func:`PyErr_GetExcInfo()` now derives the ``type`` and ``traceback``
605+
fields of the result from the exception instance (the ``value`` field).
606+
(Contributed by Irit Katriel in :issue:`45711`.)
607+
608+
582609
Porting to Python 3.11
583610
----------------------
584611

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
The three values of ``exc_info`` are now always consistent with each other.
2+
In particular, the ``type`` and ``traceback`` fields are now derived from
3+
the exception instance. This impacts the return values of :func:`sys.exc_info`
4+
and :c:func:`PyErr_GetExcInfo()` if the exception instance is modified while
5+
the exception is handled, as well as :c:func:`PyErr_SetExcInfo()`, which now
6+
ignores the ``type`` and ``traceback`` arguments provided to it.

Python/ceval.c

+3-6
Original file line numberDiff line numberDiff line change
@@ -5918,20 +5918,17 @@ do_raise(PyThreadState *tstate, PyObject *exc, PyObject *cause)
59185918
if (exc == NULL) {
59195919
/* Reraise */
59205920
_PyErr_StackItem *exc_info = _PyErr_GetTopmostException(tstate);
5921-
PyObject *tb;
5922-
type = exc_info->exc_type;
59235921
value = exc_info->exc_value;
5924-
tb = exc_info->exc_traceback;
5925-
assert(((Py_IsNone(value) || value == NULL)) ==
5926-
((Py_IsNone(type) || type == NULL)));
59275922
if (Py_IsNone(value) || value == NULL) {
59285923
_PyErr_SetString(tstate, PyExc_RuntimeError,
59295924
"No active exception to reraise");
59305925
return 0;
59315926
}
5927+
assert(PyExceptionInstance_Check(value));
5928+
type = PyExceptionInstance_Class(value);
59325929
Py_XINCREF(type);
59335930
Py_XINCREF(value);
5934-
Py_XINCREF(tb);
5931+
PyObject *tb = PyException_GetTraceback(value); /* new ref */
59355932
_PyErr_Restore(tstate, type, value, tb);
59365933
return 1;
59375934
}

Python/errors.c

+48-26
Original file line numberDiff line numberDiff line change
@@ -470,25 +470,43 @@ PyErr_Clear(void)
470470
_PyErr_Clear(tstate);
471471
}
472472

473+
static PyObject*
474+
get_exc_type(PyObject *exc_value) /* returns a borrowed ref */
475+
{
476+
if (exc_value == NULL || exc_value == Py_None) {
477+
return Py_None;
478+
}
479+
else {
480+
assert(PyExceptionInstance_Check(exc_value));
481+
PyObject *type = PyExceptionInstance_Class(exc_value);
482+
assert(type != NULL);
483+
return type;
484+
}
485+
}
486+
487+
static PyObject*
488+
get_exc_traceback(PyObject *exc_value) /* returns a borrowed ref */
489+
{
490+
if (exc_value == NULL || exc_value == Py_None) {
491+
return Py_None;
492+
}
493+
else {
494+
assert(PyExceptionInstance_Check(exc_value));
495+
PyObject *tb = PyException_GetTraceback(exc_value);
496+
Py_XDECREF(tb);
497+
return tb ? tb : Py_None;
498+
}
499+
}
473500

474501
void
475502
_PyErr_GetExcInfo(PyThreadState *tstate,
476503
PyObject **p_type, PyObject **p_value, PyObject **p_traceback)
477504
{
478505
_PyErr_StackItem *exc_info = _PyErr_GetTopmostException(tstate);
479506

507+
*p_type = get_exc_type(exc_info->exc_value);
480508
*p_value = exc_info->exc_value;
481-
*p_traceback = exc_info->exc_traceback;
482-
483-
if (*p_value == NULL || *p_value == Py_None) {
484-
assert(exc_info->exc_type == NULL || exc_info->exc_type == Py_None);
485-
*p_type = Py_None;
486-
}
487-
else {
488-
assert(PyExceptionInstance_Check(*p_value));
489-
assert(exc_info->exc_type == PyExceptionInstance_Class(*p_value));
490-
*p_type = PyExceptionInstance_Class(*p_value);
491-
}
509+
*p_traceback = get_exc_traceback(exc_info->exc_value);
492510

493511
Py_XINCREF(*p_type);
494512
Py_XINCREF(*p_value);
@@ -504,7 +522,7 @@ PyErr_GetExcInfo(PyObject **p_type, PyObject **p_value, PyObject **p_traceback)
504522
}
505523

506524
void
507-
PyErr_SetExcInfo(PyObject *p_type, PyObject *p_value, PyObject *p_traceback)
525+
PyErr_SetExcInfo(PyObject *type, PyObject *value, PyObject *traceback)
508526
{
509527
PyObject *oldtype, *oldvalue, *oldtraceback;
510528
PyThreadState *tstate = _PyThreadState_GET();
@@ -513,9 +531,16 @@ PyErr_SetExcInfo(PyObject *p_type, PyObject *p_value, PyObject *p_traceback)
513531
oldvalue = tstate->exc_info->exc_value;
514532
oldtraceback = tstate->exc_info->exc_traceback;
515533

516-
tstate->exc_info->exc_type = p_type;
517-
tstate->exc_info->exc_value = p_value;
518-
tstate->exc_info->exc_traceback = p_traceback;
534+
535+
tstate->exc_info->exc_type = get_exc_type(value);
536+
Py_XINCREF(tstate->exc_info->exc_type);
537+
tstate->exc_info->exc_value = value;
538+
tstate->exc_info->exc_traceback = get_exc_traceback(value);
539+
Py_XINCREF(tstate->exc_info->exc_traceback);
540+
541+
/* These args are no longer used, but we still need to steal a ref */
542+
Py_XDECREF(type);
543+
Py_XDECREF(traceback);
519544

520545
Py_XDECREF(oldtype);
521546
Py_XDECREF(oldvalue);
@@ -527,22 +552,19 @@ PyObject*
527552
_PyErr_StackItemToExcInfoTuple(_PyErr_StackItem *err_info)
528553
{
529554
PyObject *exc_value = err_info->exc_value;
530-
if (exc_value == NULL) {
531-
exc_value = Py_None;
532-
}
533555

534-
assert(exc_value == Py_None || PyExceptionInstance_Check(exc_value));
556+
assert(exc_value == NULL ||
557+
exc_value == Py_None ||
558+
PyExceptionInstance_Check(exc_value));
535559

536-
PyObject *exc_type = PyExceptionInstance_Check(exc_value) ?
537-
PyExceptionInstance_Class(exc_value) :
538-
Py_None;
560+
PyObject *exc_type = get_exc_type(exc_value);
561+
PyObject *exc_traceback = get_exc_traceback(exc_value);
539562

540563
return Py_BuildValue(
541564
"(OOO)",
542-
exc_type,
543-
exc_value,
544-
err_info->exc_traceback != NULL ?
545-
err_info->exc_traceback : Py_None);
565+
exc_type ? exc_type : Py_None,
566+
exc_value ? exc_value : Py_None,
567+
exc_traceback ? exc_traceback : Py_None);
546568
}
547569

548570

0 commit comments

Comments
 (0)