From cacd0713f087e93cdb4c58d5d303ff473df8803e Mon Sep 17 00:00:00 2001 From: Simon Marchi Date: Sun, 21 Jul 2019 12:49:05 -0400 Subject: [PATCH] py-common: make bt_py_common_format_exception accept an arbitrary exception A following patch will need to format each exception of the exception chain independently. It will therefore need a function like bt_py_common_format_exception, but that: - takes the exception to format as a parameter, rather than relying on the Python error indicator - has an option for formatting just that exception, not following the causes chain This patch changes bt_py_common_format_exception to meet these requirements. The behavior of the old bt_py_common_format_exception is now offered by bt_py_common_format_current_exception, so the existing callers are updated to use that one. Change-Id: I7bbb442bc1dc363e5441b3b11ed6f0c6e213ba79 Signed-off-by: Simon Marchi Reviewed-on: https://review.lttng.org/c/babeltrace/+/1737 Tested-by: jenkins Reviewed-by: Philippe Proulx --- .../bt2/bt2/native_bt_component_class.i | 4 +- src/py-common/py-common.c | 53 +++++++++++++------ src/py-common/py-common.h | 22 ++++++-- .../python-plugin-provider.c | 4 +- 4 files changed, 59 insertions(+), 24 deletions(-) diff --git a/src/bindings/python/bt2/bt2/native_bt_component_class.i b/src/bindings/python/bt2/bt2/native_bt_component_class.i index 4b718015..0b679ec8 100644 --- a/src/bindings/python/bt2/bt2/native_bt_component_class.i +++ b/src/bindings/python/bt2/bt2/native_bt_component_class.i @@ -182,9 +182,9 @@ void log_exception_and_maybe_append_error(int log_level, GString *gstr; BT_ASSERT(PyErr_Occurred()); - gstr = bt_py_common_format_exception(BT_LOG_OUTPUT_LEVEL); + gstr = bt_py_common_format_current_exception(BT_LOG_OUTPUT_LEVEL); if (!gstr) { - /* bt_py_common_format_exception() logs errors */ + /* bt_py_common_format_current_exception() logs errors */ goto end; } diff --git a/src/py-common/py-common.c b/src/py-common/py-common.c index 4fea4d27..465f3501 100644 --- a/src/py-common/py-common.c +++ b/src/py-common/py-common.c @@ -32,11 +32,10 @@ #include "py-common.h" BT_HIDDEN -GString *bt_py_common_format_exception(int log_level) +GString *bt_py_common_format_exception(PyObject *py_exc_type, + PyObject *py_exc_value, PyObject *py_exc_tb, + int log_level, bool chain) { - PyObject *type = NULL; - PyObject *value = NULL; - PyObject *traceback = NULL; PyObject *traceback_module = NULL; PyObject *format_exception_func = NULL; PyObject *exc_str_list = NULL; @@ -44,13 +43,6 @@ GString *bt_py_common_format_exception(int log_level) const char *format_exc_func_name; Py_ssize_t i; - BT_ASSERT(PyErr_Occurred()); - PyErr_Fetch(&type, &value, &traceback); - BT_ASSERT(type); - - /* Make sure `value` is what we expected: an instance of `type` */ - PyErr_NormalizeException(&type, &value, &traceback); - /* * Import the standard `traceback` module which contains the * functions to format an exception. @@ -62,12 +54,12 @@ GString *bt_py_common_format_exception(int log_level) } /* - * `traceback` can be `NULL`, when we fail to call a Python + * `py_exc_tb` can be `NULL`, when we fail to call a Python * function from native code (there is no Python stack at that * point). For example, a function which takes 5 positional * arguments, but 8 were given. */ - format_exc_func_name = traceback ? "format_exception" : + format_exc_func_name = py_exc_tb ? "format_exception" : "format_exception_only"; format_exception_func = PyObject_GetAttrString(traceback_module, format_exc_func_name); @@ -83,8 +75,13 @@ GString *bt_py_common_format_exception(int log_level) goto error; } + /* + * If we are calling format_exception_only, `py_exc_tb` is NULL, so the + * effective argument list is of length 2. + */ exc_str_list = PyObject_CallFunctionObjArgs(format_exception_func, - type, value, traceback, NULL); + py_exc_type, py_exc_value, py_exc_tb, Py_None /* limit */, + chain ? Py_True : Py_False /* chain */, NULL); if (!exc_str_list) { if (BT_LOG_ON_ERROR) { BT_LOGE("Failed to call `traceback.%s` function:", @@ -139,6 +136,30 @@ end: Py_XDECREF(format_exception_func); Py_XDECREF(traceback_module); + return msg_buf; +} + +BT_HIDDEN +GString *bt_py_common_format_current_exception(int log_level) +{ + GString *result; + PyObject *py_exc_type = NULL; + PyObject *py_exc_value = NULL; + PyObject *py_exc_tb = NULL; + + BT_ASSERT(PyErr_Occurred()); + PyErr_Fetch(&py_exc_type, &py_exc_value, &py_exc_tb); + BT_ASSERT(py_exc_type); + + /* + * Make sure `py_exc_value` is what we expected: an instance of + * `py_exc_type`. + */ + PyErr_NormalizeException(&py_exc_type, &py_exc_value, &py_exc_tb); + + result = bt_py_common_format_exception(py_exc_type, py_exc_value, + py_exc_tb, log_level, true); + /* * We can safely call PyErr_Restore() because we always call * PyErr_Fetch(), and having an error indicator is a function's @@ -146,7 +167,7 @@ end: * * PyErr_Restore() steals our references. */ - PyErr_Restore(type, value, traceback); + PyErr_Restore(py_exc_type, py_exc_value, py_exc_tb); - return msg_buf; + return result; } diff --git a/src/py-common/py-common.h b/src/py-common/py-common.h index dbfd8825..27a3a619 100644 --- a/src/py-common/py-common.h +++ b/src/py-common/py-common.h @@ -26,13 +26,27 @@ */ #include +#include #include "common/macros.h" /* - * Formats the Python error indicator (exception) and returns the - * formatted string, or `NULL` on error. The returned string does NOT - * end with a newline. + * Formats the Python exception described by `py_exc_type`, `py_exc_value` + * and `py_exc_tb` and returns the formatted string, or `NULL` on error. The + * returned string does NOT end with a newline. + * + * If `chain` is true, include all exceptions in the causality chain + * (see parameter `chain` of Python's traceback.format_exception). + */ +BT_HIDDEN +GString *bt_py_common_format_exception(PyObject *py_exc_type, + PyObject *py_exc_value, PyObject *py_exc_tb, + int log_level, bool chain); + +/* + * Wrapper for `bt_py_common_format_exception` that passes the Python error + * indicator (the exception currently being raised). Always include the + * full exception chain. * * You must ensure that the error indicator is set with PyErr_Occurred() * before you call this function. @@ -41,6 +55,6 @@ * that is fetched is always restored. */ BT_HIDDEN -GString *bt_py_common_format_exception(int log_level); +GString *bt_py_common_format_current_exception(int log_level); #endif /* BABELTRACE_PY_COMMON_INTERNAL_H */ diff --git a/src/python-plugin-provider/python-plugin-provider.c b/src/python-plugin-provider/python-plugin-provider.c index a1324cff..765035e2 100644 --- a/src/python-plugin-provider/python-plugin-provider.c +++ b/src/python-plugin-provider/python-plugin-provider.c @@ -72,7 +72,7 @@ void append_python_traceback_error_cause(void) GString *exc = NULL; if (Py_IsInitialized() && PyErr_Occurred()) { - exc = bt_py_common_format_exception(BT_LOG_OUTPUT_LEVEL); + exc = bt_py_common_format_current_exception(BT_LOG_OUTPUT_LEVEL); if (!exc) { BT_LOGE_STR("Failed to format Python exception."); goto end; @@ -94,7 +94,7 @@ void log_python_traceback(int log_level) GString *exc = NULL; if (Py_IsInitialized() && PyErr_Occurred()) { - exc = bt_py_common_format_exception(BT_LOG_OUTPUT_LEVEL); + exc = bt_py_common_format_current_exception(BT_LOG_OUTPUT_LEVEL); if (!exc) { BT_LOGE_STR("Failed to format Python exception."); goto end; -- 2.34.1