@@ -477,7 +477,9 @@ PyErr_SetExcInfo(PyObject *p_type, PyObject *p_value, PyObject *p_traceback)
477477
478478/* Like PyErr_Restore(), but if an exception is already set,
479479 set the context associated with it.
480- */
480+
481+ The caller is responsible for ensuring that this call won't create
482+ any cycles in the exception context chain. */
481483void
482484_PyErr_ChainExceptions (PyObject * exc , PyObject * val , PyObject * tb )
483485{
@@ -512,18 +514,60 @@ _PyErr_ChainExceptions(PyObject *exc, PyObject *val, PyObject *tb)
512514 }
513515}
514516
517+ /* Set the currently set exception's context to the given exception.
518+
519+ If the provided exc_info is NULL, then the current Python thread state's
520+ exc_info will be used for the context instead.
521+
522+ This function can only be called when _PyErr_Occurred() is true.
523+ Also, this function won't create any cycles in the exception context
524+ chain to the extent that _PyErr_SetObject ensures this. */
515525void
516- _PyErr_ChainStackItem (_PyErr_StackItem * exc_state )
526+ _PyErr_ChainStackItem (_PyErr_StackItem * exc_info )
517527{
518- if (exc_state -> exc_type == NULL || exc_state -> exc_type == Py_None ) {
528+ PyThreadState * tstate = _PyThreadState_GET ();
529+ assert (_PyErr_Occurred (tstate ));
530+
531+ int exc_info_given ;
532+ if (exc_info == NULL ) {
533+ exc_info_given = 0 ;
534+ exc_info = tstate -> exc_info ;
535+ } else {
536+ exc_info_given = 1 ;
537+ }
538+ if (exc_info -> exc_type == NULL || exc_info -> exc_type == Py_None ) {
519539 return ;
520540 }
521- Py_INCREF (exc_state -> exc_type );
522- Py_XINCREF (exc_state -> exc_value );
523- Py_XINCREF (exc_state -> exc_traceback );
524- _PyErr_ChainExceptions (exc_state -> exc_type ,
525- exc_state -> exc_value ,
526- exc_state -> exc_traceback );
541+
542+ _PyErr_StackItem * saved_exc_info ;
543+ if (exc_info_given ) {
544+ /* Temporarily set the thread state's exc_info since this is what
545+ _PyErr_SetObject uses for implicit exception chaining. */
546+ saved_exc_info = tstate -> exc_info ;
547+ tstate -> exc_info = exc_info ;
548+ }
549+
550+ PyObject * exc , * val , * tb ;
551+ _PyErr_Fetch (tstate , & exc , & val , & tb );
552+
553+ PyObject * exc2 , * val2 , * tb2 ;
554+ exc2 = exc_info -> exc_type ;
555+ val2 = exc_info -> exc_value ;
556+ tb2 = exc_info -> exc_traceback ;
557+ _PyErr_NormalizeException (tstate , & exc2 , & val2 , & tb2 );
558+ if (tb2 != NULL ) {
559+ PyException_SetTraceback (val2 , tb2 );
560+ }
561+
562+ /* _PyErr_SetObject sets the context from PyThreadState. */
563+ _PyErr_SetObject (tstate , exc , val );
564+ Py_DECREF (exc ); // since _PyErr_Occurred was true
565+ Py_XDECREF (val );
566+ Py_XDECREF (tb );
567+
568+ if (exc_info_given ) {
569+ tstate -> exc_info = saved_exc_info ;
570+ }
527571}
528572
529573static PyObject *
0 commit comments