diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst index 525a773b7c768f2..0c9cda7095454bc 100644 --- a/Doc/c-api/exceptions.rst +++ b/Doc/c-api/exceptions.rst @@ -125,6 +125,44 @@ For convenience, some of these functions will always return a .. versionadded:: 3.5 +.. c:function:: void PyErr_ChainExceptions(PyObject *exc, PyObject *val, PyObject *tb) + + Set the provided exception as the context for the current exception. If there + is no exception currently set (:c:func:`PyErr_Occurred` returns ``0``) this + function behaves like :c:func:`PyErr_Restore`, but if an exception is + already set, set the provided exception as the context (accessible from + Python through the :attr:`__context__` attribute of the exception) of the + one that is currently set. + + The exception type should be a class. Do not pass an invalid exception type + or value. This function steals a reference to each of its arguments in the + same fashion as :c:func:`PyErr_Restore` does. + + .. warning:: + + The caller is responsible for ensuring that this call won't create + any cycles in the exception context chain. + + See :ref:`tut-exception-chaining` for more information regarding the + semantics of exception chaining. + + Example usage: + + .. code-block:: c + + PyObject *type, *value, *traceback; + + /* Fetch the original exception */ + PyErr_Fetch(&type, &value, &traceback); + + /* ... code that might produce other errors ... */ + + /* Set the original exception as the context for the new exception, and set the new exception as the current exception */ + PyErr_ChainExceptions(type, value, traceback); + + + .. versionadded:: 3.11 + .. c:function:: void PyErr_SetNone(PyObject *type) diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index ea57cc83f7a5109..1ee4051e39ca0a3 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -121,6 +121,7 @@ var,PyEllipsis_Type,3.2, var,PyEnum_Type,3.2, function,PyErr_BadArgument,3.2, function,PyErr_BadInternalCall,3.2, +function,PyErr_ChainExceptions,3.11, function,PyErr_CheckSignals,3.2, function,PyErr_Clear,3.2, function,PyErr_Display,3.2, diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index c546ec0fc6d9b4c..a0eabf053e0e82f 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -295,6 +295,11 @@ Changes in the Python API C API Changes ============= + +* Add a new :c:func:`PyErr_ChainExceptions` function that allows to chain exceptions + using the C-API. See :ref:`tut-exception-chaining` for more information + regarding the semantics of exception chaining. (Contributed by Pablo Galindo in :issue:`44938`.) + * Add a new :c:func:`PyType_GetName` function to get type's short name. (Contributed by Hai Shi in :issue:`42035`.) diff --git a/Include/cpython/pyerrors.h b/Include/cpython/pyerrors.h index a3ec5afdb7c7885..6e9264df2cdf64a 100644 --- a/Include/cpython/pyerrors.h +++ b/Include/cpython/pyerrors.h @@ -89,8 +89,6 @@ PyAPI_FUNC(void) _PyErr_GetExcInfo(PyThreadState *, PyObject **, PyObject **, Py /* Context manipulation (PEP 3134) */ -PyAPI_FUNC(void) _PyErr_ChainExceptions(PyObject *, PyObject *, PyObject *); - /* Like PyErr_Format(), but saves current exception as __context__ and __cause__. */ diff --git a/Include/pyerrors.h b/Include/pyerrors.h index f5d1c7115771869..e51538832f61d20 100644 --- a/Include/pyerrors.h +++ b/Include/pyerrors.h @@ -22,6 +22,9 @@ PyAPI_FUNC(void) PyErr_Restore(PyObject *, PyObject *, PyObject *); PyAPI_FUNC(void) PyErr_GetExcInfo(PyObject **, PyObject **, PyObject **); PyAPI_FUNC(void) PyErr_SetExcInfo(PyObject *, PyObject *, PyObject *); #endif +#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030B0000 +PyAPI_FUNC(void) PyErr_ChainExceptions(PyObject *, PyObject *, PyObject *); +#endif /* Defined in Python/pylifecycle.c diff --git a/Misc/NEWS.d/next/C API/2021-08-17-18-45-25.bpo-44938.CmJPZ3.rst b/Misc/NEWS.d/next/C API/2021-08-17-18-45-25.bpo-44938.CmJPZ3.rst new file mode 100644 index 000000000000000..1d130bc2a409a82 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2021-08-17-18-45-25.bpo-44938.CmJPZ3.rst @@ -0,0 +1,2 @@ +Expose :c:func:`PyErr_ChainExceptions` in the stable API. Patch by Pablo +Galindo. diff --git a/Misc/stable_abi.txt b/Misc/stable_abi.txt index a3ee0f01beccb93..084dd07c4918cf8 100644 --- a/Misc/stable_abi.txt +++ b/Misc/stable_abi.txt @@ -2149,6 +2149,8 @@ function PyType_GetName added 3.11 function PyType_GetQualName added 3.11 +function PyErr_ChainExceptions + added 3.11 # (Detailed comments aren't really needed for further entries: from here on # we can use version control logs.) diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index ecc73d1ca8bf0f5..e3c66281e1f4523 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -2955,7 +2955,7 @@ task_step(TaskObj *task, PyObject *exc) PyObject *et, *ev, *tb; PyErr_Fetch(&et, &ev, &tb); leave_task(task->task_loop, (PyObject*)task); - _PyErr_ChainExceptions(et, ev, tb); + PyErr_ChainExceptions(et, ev, tb); return NULL; } else { diff --git a/Modules/_io/_iomodule.c b/Modules/_io/_iomodule.c index 170dea41e8abdd2..4d1d44180d805c3 100644 --- a/Modules/_io/_iomodule.c +++ b/Modules/_io/_iomodule.c @@ -497,7 +497,7 @@ _io_open_impl(PyObject *module, PyObject *file, const char *mode, PyObject *exc, *val, *tb, *close_result; PyErr_Fetch(&exc, &val, &tb); close_result = _PyObject_CallMethodIdNoArgs(result, &PyId_close); - _PyErr_ChainExceptions(exc, val, tb); + PyErr_ChainExceptions(exc, val, tb); Py_XDECREF(close_result); Py_DECREF(result); } diff --git a/Modules/_io/bufferedio.c b/Modules/_io/bufferedio.c index 5984d34cc08290c..2a7233ac8914f9e 100644 --- a/Modules/_io/bufferedio.c +++ b/Modules/_io/bufferedio.c @@ -526,7 +526,7 @@ buffered_close(buffered *self, PyObject *args) } if (exc != NULL) { - _PyErr_ChainExceptions(exc, val, tb); + PyErr_ChainExceptions(exc, val, tb); Py_CLEAR(res); } @@ -2239,7 +2239,7 @@ bufferedrwpair_close(rwpair *self, PyObject *Py_UNUSED(ignored)) Py_DECREF(ret); ret = _forward_call(self->reader, &PyId_close, NULL); if (exc != NULL) { - _PyErr_ChainExceptions(exc, val, tb); + PyErr_ChainExceptions(exc, val, tb); Py_CLEAR(ret); } return ret; diff --git a/Modules/_io/fileio.c b/Modules/_io/fileio.c index b9856b3b6316570..4dcabcbe470971f 100644 --- a/Modules/_io/fileio.c +++ b/Modules/_io/fileio.c @@ -163,7 +163,7 @@ _io_FileIO_close_impl(fileio *self) } rc = internal_close(self); if (res == NULL) - _PyErr_ChainExceptions(exc, val, tb); + PyErr_ChainExceptions(exc, val, tb); if (rc < 0) Py_CLEAR(res); return res; diff --git a/Modules/_io/iobase.c b/Modules/_io/iobase.c index 5b687b78176e8c5..85df6ae89fb53c8 100644 --- a/Modules/_io/iobase.c +++ b/Modules/_io/iobase.c @@ -240,7 +240,7 @@ _io__IOBase_close_impl(PyObject *self) PyErr_Fetch(&exc, &val, &tb); rc = _PyObject_SetAttrId(self, &PyId___IOBase_closed, Py_True); - _PyErr_ChainExceptions(exc, val, tb); + PyErr_ChainExceptions(exc, val, tb); if (rc < 0) { Py_CLEAR(res); } diff --git a/Modules/_io/textio.c b/Modules/_io/textio.c index eb05ae1a16eb03a..2350696ca4399e3 100644 --- a/Modules/_io/textio.c +++ b/Modules/_io/textio.c @@ -2863,7 +2863,7 @@ _io_TextIOWrapper_tell_impl(textio *self) PyObject *type, *value, *traceback; PyErr_Fetch(&type, &value, &traceback); res = _PyObject_CallMethodIdOneArg(self->decoder, &PyId_setstate, saved_state); - _PyErr_ChainExceptions(type, value, traceback); + PyErr_ChainExceptions(type, value, traceback); Py_DECREF(saved_state); Py_XDECREF(res); } @@ -3078,7 +3078,7 @@ _io_TextIOWrapper_close_impl(textio *self) res = _PyObject_CallMethodIdNoArgs(self->buffer, &PyId_close); if (exc != NULL) { - _PyErr_ChainExceptions(exc, val, tb); + PyErr_ChainExceptions(exc, val, tb); Py_CLEAR(res); } return res; diff --git a/Modules/_io/winconsoleio.c b/Modules/_io/winconsoleio.c index 460f2d3fa071a89..c29696b27dbbb7e 100644 --- a/Modules/_io/winconsoleio.c +++ b/Modules/_io/winconsoleio.c @@ -206,7 +206,7 @@ _io__WindowsConsoleIO_close_impl(winconsoleio *self) PyErr_Fetch(&exc, &val, &tb); rc = internal_close(self); if (res == NULL) - _PyErr_ChainExceptions(exc, val, tb); + PyErr_ChainExceptions(exc, val, tb); if (rc < 0) Py_CLEAR(res); return res; diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 84cc3697b07063b..4cb48d0326de9ad 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -574,7 +574,7 @@ PySSL_ChainExceptions(PySSLSocket *sslsock) { if (sslsock->exc_type == NULL) return 0; - _PyErr_ChainExceptions(sslsock->exc_type, sslsock->exc_value, sslsock->exc_tb); + PyErr_ChainExceptions(sslsock->exc_type, sslsock->exc_value, sslsock->exc_tb); sslsock->exc_type = NULL; sslsock->exc_value = NULL; sslsock->exc_tb = NULL; diff --git a/Modules/_zoneinfo.c b/Modules/_zoneinfo.c index c722330ee491a9d..dc36d9a1901fcd8 100644 --- a/Modules/_zoneinfo.c +++ b/Modules/_zoneinfo.c @@ -228,7 +228,7 @@ zoneinfo_new_instance(PyTypeObject *type, PyObject *key) PyObject *exc, *val, *tb; PyErr_Fetch(&exc, &val, &tb); PyObject *tmp = PyObject_CallMethod(file_obj, "close", NULL); - _PyErr_ChainExceptions(exc, val, tb); + PyErr_ChainExceptions(exc, val, tb); if (tmp == NULL) { Py_CLEAR(self); } diff --git a/Objects/odictobject.c b/Objects/odictobject.c index e5361da6dcdedbd..09a81758183899b 100644 --- a/Objects/odictobject.c +++ b/Objects/odictobject.c @@ -1617,7 +1617,7 @@ _PyODict_SetItem_KnownHash(PyObject *od, PyObject *key, PyObject *value, PyObject *exc, *val, *tb; PyErr_Fetch(&exc, &val, &tb); (void) _PyDict_DelItem_KnownHash(od, key, hash); - _PyErr_ChainExceptions(exc, val, tb); + PyErr_ChainExceptions(exc, val, tb); } } return res; diff --git a/Objects/weakrefobject.c b/Objects/weakrefobject.c index bb56c7dbdb83229..1d5a03e2599ff7e 100644 --- a/Objects/weakrefobject.c +++ b/Objects/weakrefobject.c @@ -1006,7 +1006,7 @@ PyObject_ClearWeakRefs(PyObject *object) tuple = PyTuple_New(count * 2); if (tuple == NULL) { - _PyErr_ChainExceptions(err_type, err_value, err_tb); + PyErr_ChainExceptions(err_type, err_value, err_tb); return; } diff --git a/PC/python3dll.c b/PC/python3dll.c index 2420e53e7052d5e..0f93140411904e0 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -181,6 +181,7 @@ EXPORT_FUNC(PyDict_Values) EXPORT_FUNC(PyDictProxy_New) EXPORT_FUNC(PyErr_BadArgument) EXPORT_FUNC(PyErr_BadInternalCall) +EXPORT_FUNC(PyErr_ChainExceptions) EXPORT_FUNC(PyErr_CheckSignals) EXPORT_FUNC(PyErr_Clear) EXPORT_FUNC(PyErr_Display) diff --git a/Python/ceval.c b/Python/ceval.c index 333c54f50e2e3a1..880e594cf5886e9 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -697,7 +697,7 @@ _Py_FinishPendingCalls(PyThreadState *tstate) PyObject *exc, *val, *tb; _PyErr_Fetch(tstate, &exc, &val, &tb); PyErr_BadInternalCall(); - _PyErr_ChainExceptions(exc, val, tb); + PyErr_ChainExceptions(exc, val, tb); _PyErr_Print(tstate); } } diff --git a/Python/errors.c b/Python/errors.c index ae1cde690eafa50..8c82eef96f8e8fa 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -510,7 +510,7 @@ PyErr_SetExcInfo(PyObject *p_type, PyObject *p_value, PyObject *p_traceback) The caller is responsible for ensuring that this call won't create any cycles in the exception context chain. */ void -_PyErr_ChainExceptions(PyObject *exc, PyObject *val, PyObject *tb) +PyErr_ChainExceptions(PyObject *exc, PyObject *val, PyObject *tb) { if (exc == NULL) return; @@ -519,7 +519,7 @@ _PyErr_ChainExceptions(PyObject *exc, PyObject *val, PyObject *tb) if (!PyExceptionClass_Check(exc)) { _PyErr_Format(tstate, PyExc_SystemError, - "_PyErr_ChainExceptions: " + "PyErr_ChainExceptions: " "exception %R is not a BaseException subclass", exc); return; diff --git a/Python/import.c b/Python/import.c index 7301fccb9fac0ca..7b1f7d590fba9bb 100644 --- a/Python/import.c +++ b/Python/import.c @@ -655,7 +655,7 @@ remove_module(PyThreadState *tstate, PyObject *name) } } - _PyErr_ChainExceptions(type, value, traceback); + PyErr_ChainExceptions(type, value, traceback); } diff --git a/Python/traceback.c b/Python/traceback.c index 204121ba66d96e2..a3da3e208e25ac4 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -252,7 +252,7 @@ PyTraceBack_Here(PyFrameObject *frame) PyErr_Fetch(&exc, &val, &tb); newtb = _PyTraceBack_FromFrame(tb, frame); if (newtb == NULL) { - _PyErr_ChainExceptions(exc, val, tb); + PyErr_ChainExceptions(exc, val, tb); return -1; } PyErr_Restore(exc, val, newtb); @@ -294,7 +294,7 @@ void _PyTraceback_Add(const char *funcname, const char *filename, int lineno) return; error: - _PyErr_ChainExceptions(exc, val, tb); + PyErr_ChainExceptions(exc, val, tb); } static PyObject *