From 201a85a8ea071d37f4fda2668c0a1c488d852f4e Mon Sep 17 00:00:00 2001 From: Daniel Elstner Date: Mon, 5 Oct 2015 01:53:43 +0200 Subject: Python: Restrict code to stable ABI subset Limit usage of the Python C API to the stable ABI subset as defined by PEP 384. This removes some type definitions and functions which libsigrokdecode made use of. Convert all affected code to suitable API alternatives. Also fix a few leaks that became apparent while working on the code. The most visible change is that PyTypeObject is now an opaque type. Thus, the custom Decoder and srd_logic types are now created on the heap via an alternative API. Unfortunately, since tp_name is now inaccessible, type names had to be removed from the log output. Stack traces after Python exceptions are now formatted by calling into Python, since the trace object C API is no longer available. --- exception.c | 167 +++++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 119 insertions(+), 48 deletions(-) (limited to 'exception.c') diff --git a/exception.c b/exception.c index 8810304..a2b2683 100644 --- a/exception.c +++ b/exception.c @@ -22,69 +22,140 @@ #include "libsigrokdecode.h" #include #include -#include /* Python header not pulled in by default. */ + +static char *py_stringify(PyObject *py_obj) +{ + PyObject *py_str, *py_bytes; + char *str = NULL; + + if (!py_obj) + return NULL; + + py_str = PyObject_Str(py_obj); + if (!py_str || !PyUnicode_Check(py_str)) + goto cleanup; + + py_bytes = PyUnicode_AsUTF8String(py_str); + if (!py_bytes) + goto cleanup; + + str = g_strdup(PyBytes_AsString(py_bytes)); + Py_DECREF(py_bytes); + +cleanup: + Py_XDECREF(py_str); + if (!str) { + PyErr_Clear(); + srd_dbg("Failed to stringify object."); + } + return str; +} + +static char *py_get_string_attr(PyObject *py_obj, const char *attr) +{ + PyObject *py_str, *py_bytes; + char *str = NULL; + + if (!py_obj) + return NULL; + + py_str = PyObject_GetAttrString(py_obj, attr); + if (!py_str || !PyUnicode_Check(py_str)) + goto cleanup; + + py_bytes = PyUnicode_AsUTF8String(py_str); + if (!py_bytes) + goto cleanup; + + str = g_strdup(PyBytes_AsString(py_bytes)); + Py_DECREF(py_bytes); + +cleanup: + Py_XDECREF(py_str); + if (!str) { + PyErr_Clear(); + srd_dbg("Failed to get object attribute %s.", attr); + } + return str; +} /** @private */ SRD_PRIV void srd_exception_catch(const char *format, ...) { - PyObject *etype, *evalue, *etb, *py_str; - PyTracebackObject *py_tb; - GString *msg; va_list args; - char *ename, *str, *tracestr; + PyObject *py_etype, *py_evalue, *py_etraceback; + PyObject *py_modname, *py_mod, *py_func, *py_tracefmt; + char *msg, *etype_name, *evalue_str, *tracefmt_str; + const char *etype_name_fallback; - if (!PyErr_Occurred()) - /* Nothing is wrong. */ - return; + py_etype = py_evalue = py_etraceback = py_mod = py_func = NULL; - PyErr_Fetch(&etype, &evalue, &etb); - PyErr_NormalizeException(&etype, &evalue, &etb); + va_start(args, format); + msg = g_strdup_vprintf(format, args); + va_end(args); - if (!(py_str = PyObject_Str(evalue))) { - /* Shouldn't happen. */ - srd_dbg("Failed to convert exception value to string."); - return; + PyErr_Fetch(&py_etype, &py_evalue, &py_etraceback); + if (!py_etype) { + /* No current exception, so just print the message. */ + srd_err("%s.", msg); + goto cleanup; } + PyErr_NormalizeException(&py_etype, &py_evalue, &py_etraceback); - /* Send the exception error message(s) to srd_err(). */ - if (evalue) - ename = (char *)Py_TYPE(evalue)->tp_name; + etype_name = py_get_string_attr(py_etype, "__name__"); + evalue_str = py_stringify(py_evalue); + etype_name_fallback = (etype_name) ? etype_name : "(unknown exception)"; + + if (evalue_str) + srd_err("%s: %s: %s", etype_name_fallback, msg, evalue_str); else - /* Can be NULL. */ - ename = "(unknown exception)"; + srd_err("%s: %s.", etype_name_fallback, msg); - msg = g_string_sized_new(128); - g_string_append(msg, ename); - g_string_append(msg, ": "); - va_start(args, format); - g_string_append_vprintf(msg, format, args); - va_end(args); - py_str_as_str(py_str, &str); - g_string_append(msg, str); - Py_DecRef(py_str); - srd_err("%s", msg->str); - - /* Send a more precise error location to srd_dbg(), if we have it. */ - if (etb && etb != Py_None) { - tracestr = NULL; - py_tb = (PyTracebackObject *)etb; - py_str = PyUnicode_FromFormat("%U:%d in %U", - py_tb->tb_frame->f_code->co_filename, - py_tb->tb_frame->f_lineno, - py_tb->tb_frame->f_code->co_name); - py_str_as_str(py_str, &tracestr); - Py_DecRef(py_str); - g_string_printf(msg, "%s in %s: %s", ename, tracestr, str); - srd_dbg("%s", msg->str); - g_free(tracestr); + g_free(evalue_str); + g_free(etype_name); + + /* If there is no traceback object, we are done. */ + if (!py_etraceback) + goto cleanup; + + py_modname = PyUnicode_FromString("traceback"); + if (!py_modname) + goto cleanup; + + py_mod = PyImport_Import(py_modname); + Py_DECREF(py_modname); + + if (!py_mod) + goto cleanup; + + py_func = PyObject_GetAttrString(py_mod, "format_exception"); + if (!py_func || !PyCallable_Check(py_func)) + goto cleanup; + + /* Call into Python to format the stack trace. */ + py_tracefmt = PyObject_CallFunctionObjArgs(py_func, + py_etype, py_evalue, py_etraceback, NULL); + if (!py_tracefmt) + goto cleanup; + + tracefmt_str = py_stringify(py_tracefmt); + Py_DECREF(py_tracefmt); + + /* Log the detailed stack trace. */ + if (tracefmt_str) { + srd_dbg("%s", tracefmt_str); + g_free(tracefmt_str); } - g_free(str); - g_string_free(msg, TRUE); - Py_XDECREF(etype); - Py_XDECREF(evalue); - Py_XDECREF(etb); +cleanup: + Py_XDECREF(py_func); + Py_XDECREF(py_mod); + Py_XDECREF(py_etraceback); + Py_XDECREF(py_evalue); + Py_XDECREF(py_etype); /* Just in case. */ PyErr_Clear(); + + g_free(msg); } -- cgit v1.2.3-70-g09d2