75539819529261d49747d63595ce13acdacf3d49
[babeltrace.git] / src / py-common / py-common.c
1 /*
2 * Copyright (c) 2019 EfficiOS Inc. and Linux Foundation
3 * Copyright (c) 2019 Philippe Proulx <pproulx@efficios.com>
4 * Copyright (c) 2019 Simon Marchi <simon.marchi@efficios.com>
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 * SOFTWARE.
23 */
24
25 #define BT_LOG_OUTPUT_LEVEL log_level
26 #define BT_LOG_TAG "PY-COMMON"
27 #include "logging/log.h"
28
29 #include <stdbool.h>
30
31 #include <Python.h>
32
33 #include "common/assert.h"
34 #include "py-common.h"
35
36 /*
37 * Concatenate strings in list `py_str_list`, returning the result as a
38 * GString. Remove the trailing \n, if the last string ends with a \n.
39 */
40 static
41 GString *py_str_list_to_gstring(PyObject *py_str_list, int log_level)
42 {
43 Py_ssize_t i;
44 GString *gstr;
45
46 gstr = g_string_new(NULL);
47 if (!gstr) {
48 BT_LOGE("Failed to allocate a GString.");
49 goto end;
50 }
51
52 for (i = 0; i < PyList_Size(py_str_list); i++) {
53 PyObject *py_str;
54 const char *str;
55
56 py_str = PyList_GetItem(py_str_list, i);
57 BT_ASSERT(py_str);
58 BT_ASSERT(PyUnicode_CheckExact(py_str));
59
60 /* `str` is owned by `py_str`, not by us */
61 str = PyUnicode_AsUTF8(py_str);
62 if (!str) {
63 if (BT_LOG_ON_ERROR) {
64 BT_LOGE_STR("PyUnicode_AsUTF8() failed:");
65 PyErr_Print();
66 }
67
68 goto error;
69 }
70
71 /* `str` has a trailing newline */
72 g_string_append(gstr, str);
73 }
74
75 if (gstr->len > 0) {
76 /* Remove trailing newline if any */
77 if (gstr->str[gstr->len - 1] == '\n') {
78 g_string_truncate(gstr, gstr->len - 1);
79 }
80 }
81
82 goto end;
83
84 error:
85 g_string_free(gstr, TRUE);
86 gstr = NULL;
87
88 end:
89 return gstr;
90 }
91
92 BT_HIDDEN
93 GString *bt_py_common_format_tb(PyObject *py_exc_tb, int log_level)
94 {
95 PyObject *traceback_module = NULL;
96 PyObject *format_tb_func = NULL;
97 PyObject *exc_str_list = NULL;
98 GString *msg_buf = NULL;
99
100 BT_ASSERT(py_exc_tb);
101
102 /*
103 * Import the standard `traceback` module which contains the
104 * functions to format a traceback.
105 */
106 traceback_module = PyImport_ImportModule("traceback");
107 if (!traceback_module) {
108 BT_LOGE_STR("Failed to import `traceback` module.");
109 goto error;
110 }
111
112 format_tb_func = PyObject_GetAttrString(traceback_module,
113 "format_tb");
114 if (!format_tb_func) {
115 BT_LOGE("Cannot find `format_tb` attribute in `traceback` module.");
116 goto error;
117 }
118
119 if (!PyCallable_Check(format_tb_func)) {
120 BT_LOGE("`traceback.format_tb` attribute is not callable.");
121 goto error;
122 }
123
124 /*
125 * If we are calling format_exception_only, `py_exc_tb` is NULL, so the
126 * effective argument list is of length 2.
127 */
128 exc_str_list = PyObject_CallFunctionObjArgs(
129 format_tb_func, py_exc_tb, NULL);
130 if (!exc_str_list) {
131 if (BT_LOG_ON_ERROR) {
132 BT_LOGE("Failed to call `traceback.format_tb` function:");
133 PyErr_Print();
134 }
135
136 goto error;
137 }
138
139 msg_buf = py_str_list_to_gstring(exc_str_list, log_level);
140 if (!msg_buf) {
141 goto error;
142 }
143
144 error:
145 Py_XDECREF(traceback_module);
146 Py_XDECREF(format_tb_func);
147 Py_XDECREF(exc_str_list);
148
149 return msg_buf;
150 }
151
152 BT_HIDDEN
153 GString *bt_py_common_format_exception(PyObject *py_exc_type,
154 PyObject *py_exc_value, PyObject *py_exc_tb,
155 int log_level, bool chain)
156 {
157 PyObject *traceback_module = NULL;
158 PyObject *format_exception_func = NULL;
159 PyObject *exc_str_list = NULL;
160 GString *msg_buf = NULL;
161 const char *format_exc_func_name;
162
163 /*
164 * Import the standard `traceback` module which contains the
165 * functions to format an exception.
166 */
167 traceback_module = PyImport_ImportModule("traceback");
168 if (!traceback_module) {
169 BT_LOGE_STR("Failed to import `traceback` module.");
170 goto error;
171 }
172
173 /*
174 * `py_exc_tb` can be `NULL`, when we fail to call a Python
175 * function from native code (there is no Python stack at that
176 * point). For example, a function which takes 5 positional
177 * arguments, but 8 were given.
178 */
179 format_exc_func_name = py_exc_tb ? "format_exception" :
180 "format_exception_only";
181 format_exception_func = PyObject_GetAttrString(traceback_module,
182 format_exc_func_name);
183 if (!format_exception_func) {
184 BT_LOGE("Cannot find `%s` attribute in `traceback` module.",
185 format_exc_func_name);
186 goto error;
187 }
188
189 if (!PyCallable_Check(format_exception_func)) {
190 BT_LOGE("`traceback.%s` attribute is not callable.",
191 format_exc_func_name);
192 goto error;
193 }
194
195 /*
196 * If we are calling format_exception_only, `py_exc_tb` is NULL, so the
197 * effective argument list is of length 2.
198 */
199 exc_str_list = PyObject_CallFunctionObjArgs(format_exception_func,
200 py_exc_type, py_exc_value, py_exc_tb, Py_None /* limit */,
201 chain ? Py_True : Py_False /* chain */, NULL);
202 if (!exc_str_list) {
203 if (BT_LOG_ON_ERROR) {
204 BT_LOGE("Failed to call `traceback.%s` function:",
205 format_exc_func_name);
206 PyErr_Print();
207 }
208
209 goto error;
210 }
211
212 msg_buf = py_str_list_to_gstring(exc_str_list, log_level);
213 if (!msg_buf) {
214 goto error;
215 }
216
217 error:
218 Py_XDECREF(exc_str_list);
219 Py_XDECREF(format_exception_func);
220 Py_XDECREF(traceback_module);
221
222 return msg_buf;
223 }
224
225 BT_HIDDEN
226 GString *bt_py_common_format_current_exception(int log_level)
227 {
228 GString *result;
229 PyObject *py_exc_type = NULL;
230 PyObject *py_exc_value = NULL;
231 PyObject *py_exc_tb = NULL;
232
233 BT_ASSERT(PyErr_Occurred());
234 PyErr_Fetch(&py_exc_type, &py_exc_value, &py_exc_tb);
235 BT_ASSERT(py_exc_type);
236
237 /*
238 * Make sure `py_exc_value` is what we expected: an instance of
239 * `py_exc_type`.
240 */
241 PyErr_NormalizeException(&py_exc_type, &py_exc_value, &py_exc_tb);
242
243 result = bt_py_common_format_exception(py_exc_type, py_exc_value,
244 py_exc_tb, log_level, true);
245
246 /*
247 * We can safely call PyErr_Restore() because we always call
248 * PyErr_Fetch(), and having an error indicator is a function's
249 * precondition.
250 *
251 * PyErr_Restore() steals our references.
252 */
253 PyErr_Restore(py_exc_type, py_exc_value, py_exc_tb);
254
255 return result;
256 }
This page took 0.041214 seconds and 4 git commands to generate.