bt2: don't print previous causes in causes created from bt2._Error
[babeltrace.git] / src / py-common / py-common.c
index 4fea4d27b5475ecfa5c6c28208ef8de3a0d79795..b1ca34ae455014f87e85116c33fd8b2ab97e4a15 100644 (file)
 #define BT_LOG_TAG "PY-COMMON"
 #include "logging/log.h"
 
+#include <stdbool.h>
+
 #include <Python.h>
 
 #include "common/assert.h"
 #include "py-common.h"
 
+/*
+ * Concatenate strings in list `py_str_list`, returning the result as a
+ * GString.  Remove the trailing \n, if the last string ends with a \n.
+ */
+static
+GString *py_str_list_to_gstring(PyObject *py_str_list, int log_level)
+{
+       Py_ssize_t i;
+       GString *gstr;
+
+       gstr = g_string_new(NULL);
+       if (!gstr) {
+               BT_LOGE("Failed to allocate a GString.");
+               goto end;
+       }
+
+       for (i = 0; i < PyList_Size(py_str_list); i++) {
+               PyObject *py_str;
+               const char *str;
+
+               py_str = PyList_GetItem(py_str_list, i);
+               BT_ASSERT(py_str);
+               BT_ASSERT(PyUnicode_CheckExact(py_str));
+
+               /* `str` is owned by `py_str`, not by us */
+               str = PyUnicode_AsUTF8(py_str);
+               if (!str) {
+                       if (BT_LOG_ON_ERROR) {
+                               BT_LOGE_STR("PyUnicode_AsUTF8() failed:");
+                               PyErr_Print();
+                       }
+
+                       goto error;
+               }
+
+               /* `str` has a trailing newline */
+               g_string_append(gstr, str);
+       }
+
+       if (gstr->len > 0) {
+               /* Remove trailing newline if any */
+               if (gstr->str[gstr->len - 1] == '\n') {
+                       g_string_truncate(gstr, gstr->len - 1);
+               }
+       }
+
+       goto end;
+
+error:
+       g_string_free(gstr, TRUE);
+       gstr = NULL;
+
+end:
+       return gstr;
+}
+
 BT_HIDDEN
-GString *bt_py_common_format_exception(int log_level)
+GString *bt_py_common_format_tb(PyObject *py_exc_tb, int log_level)
 {
-       PyObject *type = NULL;
-       PyObject *value = NULL;
-       PyObject *traceback = NULL;
        PyObject *traceback_module = NULL;
-       PyObject *format_exception_func = NULL;
+       PyObject *format_tb_func = NULL;
        PyObject *exc_str_list = NULL;
        GString *msg_buf = NULL;
-       const char *format_exc_func_name;
-       Py_ssize_t i;
 
-       BT_ASSERT(PyErr_Occurred());
-       PyErr_Fetch(&type, &value, &traceback);
-       BT_ASSERT(type);
+       BT_ASSERT(py_exc_tb);
+
+       /*
+        * Import the standard `traceback` module which contains the
+        * functions to format a traceback.
+        */
+       traceback_module = PyImport_ImportModule("traceback");
+       if (!traceback_module) {
+               BT_LOGE_STR("Failed to import `traceback` module.");
+               goto error;
+       }
+
+       format_tb_func = PyObject_GetAttrString(traceback_module,
+               "format_tb");
+       if (!format_tb_func) {
+               BT_LOGE("Cannot find `format_tb` attribute in `traceback` module.");
+               goto error;
+       }
+
+       if (!PyCallable_Check(format_tb_func)) {
+               BT_LOGE("`traceback.format_tb` attribute is not callable.");
+               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_tb_func, py_exc_tb, NULL);
+       if (!exc_str_list) {
+               if (BT_LOG_ON_ERROR) {
+                       BT_LOGE("Failed to call `traceback.format_tb` function:");
+                       PyErr_Print();
+               }
+
+               goto error;
+       }
+
+       msg_buf = py_str_list_to_gstring(exc_str_list, log_level);
+       if (!msg_buf) {
+               goto error;
+       }
+
+       goto end;
+
+error:
+       if (msg_buf) {
+               g_string_free(msg_buf, TRUE);
+               msg_buf = NULL;
+       }
 
-       /* Make sure `value` is what we expected: an instance of `type` */
-       PyErr_NormalizeException(&type, &value, &traceback);
+end:
+       Py_XDECREF(traceback_module);
+       Py_XDECREF(format_tb_func);
+       Py_XDECREF(exc_str_list);
+
+       return msg_buf;
+}
+
+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)
+{
+       PyObject *traceback_module = NULL;
+       PyObject *format_exception_func = NULL;
+       PyObject *exc_str_list = NULL;
+       GString *msg_buf = NULL;
+       const char *format_exc_func_name;
 
        /*
         * Import the standard `traceback` module which contains the
@@ -62,12 +179,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 +200,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:",
@@ -95,35 +217,9 @@ GString *bt_py_common_format_exception(int log_level)
                goto error;
        }
 
-       msg_buf = g_string_new(NULL);
-
-       for (i = 0; i < PyList_Size(exc_str_list); i++) {
-               PyObject *exc_str;
-               const char *str;
-
-               exc_str = PyList_GetItem(exc_str_list, i);
-               BT_ASSERT(exc_str);
-
-               /* `str` is owned by `exc_str`, not by us */
-               str = PyUnicode_AsUTF8(exc_str);
-               if (!str) {
-                       if (BT_LOG_ON_ERROR) {
-                               BT_LOGE_STR("PyUnicode_AsUTF8() failed:");
-                               PyErr_Print();
-                       }
-
-                       goto error;
-               }
-
-               /* `str` has a trailing newline */
-               g_string_append(msg_buf, str);
-       }
-
-       if (msg_buf->len > 0) {
-               /* Remove trailing newline if any */
-               if (msg_buf->str[msg_buf->len - 1] == '\n') {
-                       g_string_truncate(msg_buf, msg_buf->len - 1);
-               }
+       msg_buf = py_str_list_to_gstring(exc_str_list, log_level);
+       if (!msg_buf) {
+               goto error;
        }
 
        goto end;
@@ -139,6 +235,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 +266,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;
 }
This page took 0.026191 seconds and 4 git commands to generate.