diff options
author | Victor Stinner <vstinner@redhat.com> | 2019-05-22 09:28:22 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-05-22 09:28:22 (GMT) |
commit | ef9d9b63129a2f243591db70e9a2dd53fab95d86 (patch) | |
tree | 3ecd9bb04fba6c9d360b8db5d8b1e78cda50d49b /Python/errors.c | |
parent | 2725cb01d7cbf5caecb51cc20d97ba324b09ce96 (diff) | |
download | cpython-ef9d9b63129a2f243591db70e9a2dd53fab95d86.zip cpython-ef9d9b63129a2f243591db70e9a2dd53fab95d86.tar.gz cpython-ef9d9b63129a2f243591db70e9a2dd53fab95d86.tar.bz2 |
bpo-36829: Add sys.unraisablehook() (GH-13187)
Add new sys.unraisablehook() function which can be overridden to
control how "unraisable exceptions" are handled. It is called when an
exception has occurred but there is no way for Python to handle it.
For example, when a destructor raises an exception or during garbage
collection (gc.collect()).
Changes:
* Add an internal UnraisableHookArgs type used to pass arguments to
sys.unraisablehook.
* Add _PyErr_WriteUnraisableDefaultHook().
* The default hook now ignores exception on writing the traceback.
* test_sys now uses unittest.main() to automatically discover tests:
remove test_main().
* Add _PyErr_Init().
* Fix PyErr_WriteUnraisable(): hold a strong reference to sys.stderr
while using it
Diffstat (limited to 'Python/errors.c')
-rw-r--r-- | Python/errors.c | 278 |
1 files changed, 230 insertions, 48 deletions
diff --git a/Python/errors.c b/Python/errors.c index b8af1df..9622b5a 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -2,6 +2,7 @@ /* Error handling */ #include "Python.h" +#include "pycore_coreconfig.h" #include "pycore_pystate.h" #ifndef __STDC__ @@ -944,90 +945,271 @@ PyErr_NewExceptionWithDoc(const char *name, const char *doc, } -/* Call when an exception has occurred but there is no way for Python - to handle it. Examples: exception in __del__ or during GC. */ -void -PyErr_WriteUnraisable(PyObject *obj) +PyDoc_STRVAR(UnraisableHookArgs__doc__, +"UnraisableHookArgs\n\ +\n\ +Type used to pass arguments to sys.unraisablehook."); + +static PyTypeObject UnraisableHookArgsType; + +static PyStructSequence_Field UnraisableHookArgs_fields[] = { + {"exc_type", "Exception type"}, + {"exc_value", "Exception value"}, + {"exc_traceback", "Exception traceback"}, + {"object", "Object causing the exception"}, + {0} +}; + +static PyStructSequence_Desc UnraisableHookArgs_desc = { + .name = "UnraisableHookArgs", + .doc = UnraisableHookArgs__doc__, + .fields = UnraisableHookArgs_fields, + .n_in_sequence = 4 +}; + + +_PyInitError +_PyErr_Init(void) { - _Py_IDENTIFIER(__module__); - PyObject *f, *t, *v, *tb; - PyObject *moduleName = NULL; - const char *className; + if (UnraisableHookArgsType.tp_name == NULL) { + if (PyStructSequence_InitType2(&UnraisableHookArgsType, + &UnraisableHookArgs_desc) < 0) { + return _Py_INIT_ERR("failed to initialize UnraisableHookArgs type"); + } + } + return _Py_INIT_OK(); +} - PyErr_Fetch(&t, &v, &tb); - f = _PySys_GetObjectId(&PyId_stderr); - if (f == NULL || f == Py_None) - goto done; +static PyObject * +make_unraisable_hook_args(PyObject *exc_type, PyObject *exc_value, + PyObject *exc_tb, PyObject *obj) +{ + PyObject *args = PyStructSequence_New(&UnraisableHookArgsType); + if (args == NULL) { + return NULL; + } + + Py_ssize_t pos = 0; +#define ADD_ITEM(exc_type) \ + do { \ + if (exc_type == NULL) { \ + exc_type = Py_None; \ + } \ + Py_INCREF(exc_type); \ + PyStructSequence_SET_ITEM(args, pos++, exc_type); \ + } while (0) + + + ADD_ITEM(exc_type); + ADD_ITEM(exc_value); + ADD_ITEM(exc_tb); + ADD_ITEM(obj); +#undef ADD_ITEM + + if (PyErr_Occurred()) { + Py_DECREF(args); + return NULL; + } + return args; +} + + + +/* Default implementation of sys.unraisablehook. + + It can be called to log the exception of a custom sys.unraisablehook. - if (obj) { - if (PyFile_WriteString("Exception ignored in: ", f) < 0) - goto done; - if (PyFile_WriteObject(obj, f, 0) < 0) { + Do nothing if sys.stderr attribute doesn't exist or is set to None. */ +static int +write_unraisable_exc_file(PyObject *exc_type, PyObject *exc_value, + PyObject *exc_tb, PyObject *obj, PyObject *file) +{ + if (obj != NULL && obj != Py_None) { + if (PyFile_WriteString("Exception ignored in: ", file) < 0) { + return -1; + } + + if (PyFile_WriteObject(obj, file, 0) < 0) { PyErr_Clear(); - if (PyFile_WriteString("<object repr() failed>", f) < 0) { - goto done; + if (PyFile_WriteString("<object repr() failed>", file) < 0) { + return -1; } } - if (PyFile_WriteString("\n", f) < 0) - goto done; + if (PyFile_WriteString("\n", file) < 0) { + return -1; + } } - if (PyTraceBack_Print(tb, f) < 0) - goto done; + if (exc_tb != NULL && exc_tb != Py_None) { + if (PyTraceBack_Print(exc_tb, file) < 0) { + /* continue even if writing the traceback failed */ + PyErr_Clear(); + } + } - if (!t) - goto done; + if (!exc_type) { + return -1; + } - assert(PyExceptionClass_Check(t)); - className = PyExceptionClass_Name(t); + assert(PyExceptionClass_Check(exc_type)); + const char *className = PyExceptionClass_Name(exc_type); if (className != NULL) { const char *dot = strrchr(className, '.'); if (dot != NULL) className = dot+1; } - moduleName = _PyObject_GetAttrId(t, &PyId___module__); + _Py_IDENTIFIER(__module__); + PyObject *moduleName = _PyObject_GetAttrId(exc_type, &PyId___module__); if (moduleName == NULL || !PyUnicode_Check(moduleName)) { + Py_XDECREF(moduleName); PyErr_Clear(); - if (PyFile_WriteString("<unknown>", f) < 0) - goto done; + if (PyFile_WriteString("<unknown>", file) < 0) { + return -1; + } } else { if (!_PyUnicode_EqualToASCIIId(moduleName, &PyId_builtins)) { - if (PyFile_WriteObject(moduleName, f, Py_PRINT_RAW) < 0) - goto done; - if (PyFile_WriteString(".", f) < 0) - goto done; + if (PyFile_WriteObject(moduleName, file, Py_PRINT_RAW) < 0) { + Py_DECREF(moduleName); + return -1; + } + Py_DECREF(moduleName); + if (PyFile_WriteString(".", file) < 0) { + return -1; + } + } + else { + Py_DECREF(moduleName); } } if (className == NULL) { - if (PyFile_WriteString("<unknown>", f) < 0) - goto done; + if (PyFile_WriteString("<unknown>", file) < 0) { + return -1; + } } else { - if (PyFile_WriteString(className, f) < 0) - goto done; + if (PyFile_WriteString(className, file) < 0) { + return -1; + } } - if (v && v != Py_None) { - if (PyFile_WriteString(": ", f) < 0) - goto done; - if (PyFile_WriteObject(v, f, Py_PRINT_RAW) < 0) { + if (exc_value && exc_value != Py_None) { + if (PyFile_WriteString(": ", file) < 0) { + return -1; + } + if (PyFile_WriteObject(exc_value, file, Py_PRINT_RAW) < 0) { PyErr_Clear(); - if (PyFile_WriteString("<exception str() failed>", f) < 0) { + if (PyFile_WriteString("<exception str() failed>", file) < 0) { + return -1; + } + } + } + if (PyFile_WriteString("\n", file) < 0) { + return -1; + } + return 0; +} + + +static int +write_unraisable_exc(PyObject *exc_type, PyObject *exc_value, + PyObject *exc_tb, PyObject *obj) +{ + PyObject *file = _PySys_GetObjectId(&PyId_stderr); + if (file == NULL || file == Py_None) { + return 0; + } + + /* Hold a strong reference to ensure that sys.stderr doesn't go away + while we use it */ + Py_INCREF(file); + int res = write_unraisable_exc_file(exc_type, exc_value, exc_tb, + obj, file); + Py_DECREF(file); + + return res; +} + + +PyObject* +_PyErr_WriteUnraisableDefaultHook(PyObject *args) +{ + if (Py_TYPE(args) != &UnraisableHookArgsType) { + PyErr_SetString(PyExc_TypeError, + "sys.unraisablehook argument type " + "must be UnraisableHookArgs"); + return NULL; + } + + /* Borrowed references */ + PyObject *exc_type = PyStructSequence_GET_ITEM(args, 0); + PyObject *exc_value = PyStructSequence_GET_ITEM(args, 1); + PyObject *exc_tb = PyStructSequence_GET_ITEM(args, 2); + PyObject *obj = PyStructSequence_GET_ITEM(args, 3); + + if (write_unraisable_exc(exc_type, exc_value, exc_tb, obj) < 0) { + return NULL; + } + Py_RETURN_NONE; +} + + +/* Call sys.unraisablehook(). + + This function can be used when an exception has occurred but there is no way + for Python to handle it. For example, when a destructor raises an exception + or during garbage collection (gc.collect()). + + An exception must be set when calling this function. */ +void +PyErr_WriteUnraisable(PyObject *obj) +{ + PyObject *exc_type, *exc_value, *exc_tb; + + PyErr_Fetch(&exc_type, &exc_value, &exc_tb); + + assert(exc_type != NULL); + + if (exc_type == NULL) { + /* sys.unraisablehook requires that at least exc_type is set */ + goto default_hook; + } + + _Py_IDENTIFIER(unraisablehook); + PyObject *hook = _PySys_GetObjectId(&PyId_unraisablehook); + if (hook != NULL && hook != Py_None) { + PyObject *hook_args; + + hook_args = make_unraisable_hook_args(exc_type, exc_value, exc_tb, obj); + if (hook_args != NULL) { + PyObject *args[1] = {hook_args}; + PyObject *res = _PyObject_FastCall(hook, args, 1); + Py_DECREF(hook_args); + if (res != NULL) { + Py_DECREF(res); goto done; } } + + /* sys.unraisablehook failed: log its error using default hook */ + Py_XDECREF(exc_type); + Py_XDECREF(exc_value); + Py_XDECREF(exc_tb); + PyErr_Fetch(&exc_type, &exc_value, &exc_tb); + + obj = hook; } - if (PyFile_WriteString("\n", f) < 0) - goto done; + +default_hook: + /* Call the default unraisable hook (ignore failure) */ + (void)write_unraisable_exc(exc_type, exc_value, exc_tb, obj); done: - Py_XDECREF(moduleName); - Py_XDECREF(t); - Py_XDECREF(v); - Py_XDECREF(tb); + Py_XDECREF(exc_type); + Py_XDECREF(exc_value); + Py_XDECREF(exc_tb); PyErr_Clear(); /* Just in case */ } |