Python: Restrict code to stable ABI subset
authorDaniel Elstner <daniel.kitta@gmail.com>
Sun, 4 Oct 2015 23:53:43 +0000 (01:53 +0200)
committerDaniel Elstner <daniel.kitta@gmail.com>
Tue, 6 Oct 2015 21:25:36 +0000 (23:25 +0200)
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.

decoder.c
exception.c
instance.c
libsigrokdecode-internal.h
module_sigrokdecode.c
type_decoder.c
type_logic.c
util.c

index 753a88ae12433e79fdd7792817f3d3023fd76df9..e50345f21566d2bd41bad103f6d1eab8fb45aeb6 100644 (file)
--- a/decoder.c
+++ b/decoder.c
@@ -225,8 +225,7 @@ static int get_options(struct srd_decoder *d)
                                o->def = g_variant_new_double(dval);
                        } else {
                                srd_err("Protocol decoder %s option 'default' has "
-                                               "value of unsupported type '%s'.", d->name,
-                                               Py_TYPE(py_default)->tp_name);
+                                               "value of unsupported type.", d->name);
                                return SRD_ERR_PYTHON;
                        }
                        g_variant_ref_sink(o->def);
@@ -333,7 +332,7 @@ SRD_API int srd_decoder_load(const char *module_name)
 
        /* Import the Python module. */
        if (!(d->py_mod = PyImport_ImportModule(module_name))) {
-               srd_exception_catch("Import of '%s' failed.", module_name);
+               srd_exception_catch("Import of '%s' failed", module_name);
                goto err_out;
        }
 
@@ -376,7 +375,7 @@ SRD_API int srd_decoder_load(const char *module_name)
                goto err_out;
        }
        py_method = PyObject_GetAttrString(d->py_dec, "start");
-       if (!PyFunction_Check(py_method)) {
+       if (!PyCallable_Check(py_method)) {
                srd_err("Protocol decoder %s Decoder class attribute 'start' "
                        "is not a method.", module_name);
                goto err_out;
@@ -390,7 +389,7 @@ SRD_API int srd_decoder_load(const char *module_name)
                goto err_out;
        }
        py_method = PyObject_GetAttrString(d->py_dec, "decode");
-       if (!PyFunction_Check(py_method)) {
+       if (!PyCallable_Check(py_method)) {
                srd_err("Protocol decoder %s Decoder class attribute 'decode' "
                        "is not a method.", module_name);
                goto err_out;
@@ -588,7 +587,7 @@ SRD_API char *srd_decoder_doc_get(const struct srd_decoder *dec)
                return NULL;
 
        if (!(py_str = PyObject_GetAttrString(dec->py_mod, "__doc__"))) {
-               srd_exception_catch("");
+               srd_exception_catch("Failed to get docstring");
                return NULL;
        }
 
index 8810304aac6090d580abc41057320f89fc1566ba..a2b2683cc347087379e49881821389208acfb027 100644 (file)
 #include "libsigrokdecode.h"
 #include <stdarg.h>
 #include <glib.h>
-#include <frameobject.h> /* 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);
 }
index 4eca3f54b3ad990d9613d75d6bdc4e03bf6d9aeb..53a8d9258b6560c728b1d0be50aa115af7ea670a 100644 (file)
@@ -30,8 +30,8 @@
 
 extern SRD_PRIV GSList *sessions;
 
-/* type_logic.c */
-extern SRD_PRIV PyTypeObject srd_logic_type;
+/* module_sigrokdecode.c */
+extern SRD_PRIV PyObject *srd_logic_type;
 
 /** @endcond */
 
@@ -163,7 +163,7 @@ SRD_API int srd_inst_option_set(struct srd_decoder_inst *di,
 err_out:
        Py_XDECREF(py_optval);
        if (PyErr_Occurred()) {
-               srd_exception_catch("Stray exception in srd_inst_option_set().");
+               srd_exception_catch("Stray exception in srd_inst_option_set()");
                ret = SRD_ERR_PYTHON;
        }
 
@@ -341,7 +341,7 @@ SRD_API struct srd_decoder_inst *srd_inst_new(struct srd_session *sess,
        /* Create a new instance of this decoder class. */
        if (!(di->py_inst = PyObject_CallObject(dec->py_dec, NULL))) {
                if (PyErr_Occurred())
-                       srd_exception_catch("failed to create %s instance: ",
+                       srd_exception_catch("Failed to create %s instance",
                                        decoder_id);
                g_free(di->dec_channelmap);
                g_free(di);
@@ -505,7 +505,7 @@ SRD_PRIV int srd_inst_start(struct srd_decoder_inst *di)
                        di->inst_id);
 
        if (!(py_res = PyObject_CallMethod(di->py_inst, "start", NULL))) {
-               srd_exception_catch("Protocol decoder instance %s",
+               srd_exception_catch("Protocol decoder instance %s",
                                di->inst_id);
                return SRD_ERR_PYTHON;
        }
@@ -572,7 +572,7 @@ SRD_PRIV int srd_inst_decode(const struct srd_decoder_inst *di,
         * Create new srd_logic object. Each iteration around the PD's loop
         * will fill one sample into this object.
         */
-       logic = PyObject_New(srd_logic, &srd_logic_type);
+       logic = PyObject_New(srd_logic, (PyTypeObject *)srd_logic_type);
        Py_INCREF(logic);
        logic->di = (struct srd_decoder_inst *)di;
        logic->start_samplenum = start_samplenum;
@@ -585,7 +585,7 @@ SRD_PRIV int srd_inst_decode(const struct srd_decoder_inst *di,
        Py_IncRef(di->py_inst);
        if (!(py_res = PyObject_CallMethod(di->py_inst, "decode",
                        "KKO", start_samplenum, end_samplenum, logic))) {
-               srd_exception_catch("Protocol decoder instance %s",
+               srd_exception_catch("Protocol decoder instance %s",
                                di->inst_id);
                return SRD_ERR_PYTHON;
        }
index bcf6bb75b243a027a727db752a3af97f8240f392..fa8e91c8d2348723a56945a553720f4d0ae1c9b3 100644 (file)
@@ -22,6 +22,9 @@
 #ifndef LIBSIGROKDECODE_LIBSIGROKDECODE_INTERNAL_H
 #define LIBSIGROKDECODE_LIBSIGROKDECODE_INTERNAL_H
 
+/* Use the stable ABI subset as per PEP 384. */
+#define Py_LIMITED_API 0x03020000
+
 #include <Python.h> /* First, so we avoid a _POSIX_C_SOURCE warning. */
 #include "libsigrokdecode.h"
 
@@ -83,16 +86,20 @@ SRD_PRIV int srd_log(int loglevel, const char *format, ...) G_GNUC_PRINTF(2, 3);
 #define srd_warn(...)  srd_log(SRD_LOG_WARN, __VA_ARGS__)
 #define srd_err(...)   srd_log(SRD_LOG_ERR,  __VA_ARGS__)
 
+/* type_decoder.c */
+SRD_PRIV PyObject *srd_Decoder_type_new(void);
+
+/* type_logic.c */
+SRD_PRIV PyObject *srd_logic_type_new(void);
+
 /* module_sigrokdecode.c */
 PyMODINIT_FUNC PyInit_sigrokdecode(void);
 
 /* util.c */
-SRD_PRIV int py_attr_as_str(const PyObject *py_obj, const char *attr,
-        char **outstr);
-SRD_PRIV int py_dictitem_as_str(const PyObject *py_obj, const char *key,
-        char **outstr);
-SRD_PRIV int py_str_as_str(const PyObject *py_str, char **outstr);
-SRD_PRIV int py_strseq_to_char(const PyObject *py_strseq, char ***outstr);
+SRD_PRIV int py_attr_as_str(PyObject *py_obj, const char *attr, char **outstr);
+SRD_PRIV int py_dictitem_as_str(PyObject *py_obj, const char *key, char **outstr);
+SRD_PRIV int py_str_as_str(PyObject *py_str, char **outstr);
+SRD_PRIV int py_strseq_to_char(PyObject *py_strseq, char ***out_strv);
 
 /* exception.c */
 SRD_PRIV void srd_exception_catch(const char *format, ...);
index 468a1cb0a7276c798a238edc1b262fc0255222b5..17563c1d85d4571e362b5b71a3c5868a36d8c488 100644 (file)
 
 /** @cond PRIVATE */
 
-/* type_decoder.c */
-extern SRD_PRIV PyTypeObject srd_Decoder_type;
-
-/* type_logic.c */
-extern SRD_PRIV PyTypeObject srd_logic_type;
+SRD_PRIV PyObject *srd_logic_type = NULL;
 
 /*
  * When initialized, a reference to this module inside the Python interpreter
@@ -47,42 +43,45 @@ static struct PyModuleDef sigrokdecode_module = {
 /** @cond PRIVATE */
 PyMODINIT_FUNC PyInit_sigrokdecode(void)
 {
-       PyObject *mod;
+       PyObject *mod, *Decoder_type, *logic_type;
 
-       /* tp_new needs to be assigned here for compiler portability. */
-       srd_Decoder_type.tp_new = PyType_GenericNew;
-       if (PyType_Ready(&srd_Decoder_type) < 0)
-               return NULL;
+       mod = PyModule_Create(&sigrokdecode_module);
+       if (!mod)
+               goto err_out;
 
-       srd_logic_type.tp_new = PyType_GenericNew;
-       if (PyType_Ready(&srd_logic_type) < 0)
-               return NULL;
+       Decoder_type = srd_Decoder_type_new();
+       if (!Decoder_type)
+               goto err_out;
+       if (PyModule_AddObject(mod, "Decoder", Decoder_type) < 0)
+               goto err_out;
 
-       mod = PyModule_Create(&sigrokdecode_module);
-       Py_INCREF(&srd_Decoder_type);
-       if (PyModule_AddObject(mod, "Decoder",
-           (PyObject *)&srd_Decoder_type) == -1)
-               return NULL;
-       Py_INCREF(&srd_logic_type);
-       if (PyModule_AddObject(mod, "srd_logic",
-           (PyObject *)&srd_logic_type) == -1)
-               return NULL;
+       logic_type = srd_logic_type_new();
+       if (!logic_type)
+               goto err_out;
+       if (PyModule_AddObject(mod, "srd_logic", logic_type) < 0)
+               goto err_out;
 
        /* Expose output types as symbols in the sigrokdecode module */
-       if (PyModule_AddIntConstant(mod, "OUTPUT_ANN", SRD_OUTPUT_ANN) == -1)
-               return NULL;
-       if (PyModule_AddIntConstant(mod, "OUTPUT_PYTHON", SRD_OUTPUT_PYTHON) == -1)
-               return NULL;
-       if (PyModule_AddIntConstant(mod, "OUTPUT_BINARY", SRD_OUTPUT_BINARY) == -1)
-               return NULL;
-       if (PyModule_AddIntConstant(mod, "OUTPUT_META", SRD_OUTPUT_META) == -1)
-               return NULL;
+       if (PyModule_AddIntConstant(mod, "OUTPUT_ANN", SRD_OUTPUT_ANN) < 0)
+               goto err_out;
+       if (PyModule_AddIntConstant(mod, "OUTPUT_PYTHON", SRD_OUTPUT_PYTHON) < 0)
+               goto err_out;
+       if (PyModule_AddIntConstant(mod, "OUTPUT_BINARY", SRD_OUTPUT_BINARY) < 0)
+               goto err_out;
+       if (PyModule_AddIntConstant(mod, "OUTPUT_META", SRD_OUTPUT_META) < 0)
+               goto err_out;
        /* Expose meta input symbols. */
-       if (PyModule_AddIntConstant(mod, "SRD_CONF_SAMPLERATE", SRD_CONF_SAMPLERATE) == -1)
-               return NULL;
+       if (PyModule_AddIntConstant(mod, "SRD_CONF_SAMPLERATE", SRD_CONF_SAMPLERATE) < 0)
+               goto err_out;
 
+       srd_logic_type = logic_type;
        mod_sigrokdecode = mod;
 
        return mod;
+err_out:
+       Py_XDECREF(mod);
+       srd_exception_catch("Failed to initialize module");
+
+       return NULL;
 }
 /** @endcond */
index d4c592f224e8c3f1b933cf0fb1ac5d6f981e47a8..89b5ca1119c439d5caecd1279c2272dae907f413 100644 (file)
@@ -26,13 +26,19 @@ typedef struct {
         PyObject_HEAD
 } srd_Decoder;
 
-/* This is only used for nicer srd_dbg() output. */
-static const char *OUTPUT_TYPES[] = {
-       "OUTPUT_ANN",
-       "OUTPUT_PYTHON",
-       "OUTPUT_BINARY",
-       "OUTPUT_META",
-};
+/* This is only used for nicer srd_dbg() output.
+ */
+static const char *output_type_name(unsigned int idx)
+{
+       static const char names[][16] = {
+               "OUTPUT_ANN",
+               "OUTPUT_PYTHON",
+               "OUTPUT_BINARY",
+               "OUTPUT_META",
+               "(invalid)"
+       };
+       return names[MIN(idx, G_N_ELEMENTS(names) - 1)];
+}
 
 static int convert_annotation(struct srd_decoder_inst *di, PyObject *obj,
                struct srd_proto_data *pdata)
@@ -45,8 +51,8 @@ static int convert_annotation(struct srd_decoder_inst *di, PyObject *obj,
 
        /* Should be a list of [annotation class, [string, ...]]. */
        if (!PyList_Check(obj) && !PyTuple_Check(obj)) {
-               srd_err("Protocol decoder %s submitted %s instead of list.",
-                       di->decoder->name, obj->ob_type->tp_name);
+               srd_err("Protocol decoder %s submitted an annotation that"
+                       " is not a list or tuple", di->decoder->name);
                return SRD_ERR_PYTHON;
        }
 
@@ -107,9 +113,8 @@ static int convert_binary(struct srd_decoder_inst *di, PyObject *obj,
 
        /* Should be a tuple of (binary class, bytes). */
        if (!PyTuple_Check(obj)) {
-               srd_err("Protocol decoder %s submitted SRD_OUTPUT_BINARY with "
-                               "%s instead of tuple.", di->decoder->name,
-                               obj->ob_type->tp_name);
+               srd_err("Protocol decoder %s submitted non-tuple for SRD_OUTPUT_BINARY.",
+                       di->decoder->name);
                return SRD_ERR_PYTHON;
        }
 
@@ -171,7 +176,7 @@ static int convert_meta(struct srd_proto_data *pdata, PyObject *obj)
        if (pdata->pdo->meta_type == G_VARIANT_TYPE_INT64) {
                if (!PyLong_Check(obj)) {
                        PyErr_Format(PyExc_TypeError, "This output was registered "
-                                       "as 'int', but '%s' was passed.", obj->ob_type->tp_name);
+                                       "as 'int', but something else was passed.");
                        return SRD_ERR_PYTHON;
                }
                intvalue = PyLong_AsLongLong(obj);
@@ -181,7 +186,7 @@ static int convert_meta(struct srd_proto_data *pdata, PyObject *obj)
        } else if (pdata->pdo->meta_type == G_VARIANT_TYPE_DOUBLE) {
                if (!PyFloat_Check(obj)) {
                        PyErr_Format(PyExc_TypeError, "This output was registered "
-                                       "as 'float', but '%s' was passed.", obj->ob_type->tp_name);
+                                       "as 'float', but something else was passed.");
                        return SRD_ERR_PYTHON;
                }
                dvalue = PyFloat_AsDouble(obj);
@@ -229,7 +234,7 @@ static PyObject *Decoder_put(PyObject *self, PyObject *args)
 
        srd_spew("Instance %s put %" PRIu64 "-%" PRIu64 " %s on oid %d.",
                 di->inst_id, start_sample, end_sample,
-                OUTPUT_TYPES[pdo->output_type], output_id);
+                output_type_name(pdo->output_type), output_id);
 
        pdata = g_malloc0(sizeof(struct srd_proto_data));
        pdata->start_sample = start_sample;
@@ -256,7 +261,7 @@ static PyObject *Decoder_put(PyObject *self, PyObject *args)
                        if (!(py_res = PyObject_CallMethod(
                                next_di->py_inst, "decode", "KKO", start_sample,
                                end_sample, py_data))) {
-                               srd_exception_catch("Calling %s decode()",
+                               srd_exception_catch("Calling %s decode() failed",
                                                        next_di->inst_id);
                        }
                        Py_XDECREF(py_res);
@@ -336,8 +341,7 @@ static PyObject *Decoder_register(PyObject *self, PyObject *args,
                else if (meta_type_py == &PyFloat_Type)
                        meta_type_gv = G_VARIANT_TYPE_DOUBLE;
                else {
-                       PyErr_Format(PyExc_TypeError, "Unsupported type '%s'.",
-                                       meta_type_py->tp_name);
+                       PyErr_Format(PyExc_TypeError, "Unsupported type.");
                        return NULL;
                }
        }
@@ -373,13 +377,24 @@ static PyMethodDef Decoder_methods[] = {
        {NULL, NULL, 0, NULL}
 };
 
-/** @cond PRIVATE */
-SRD_PRIV PyTypeObject srd_Decoder_type = {
-       PyVarObject_HEAD_INIT(NULL, 0)
-       .tp_name = "sigrokdecode.Decoder",
-       .tp_basicsize = sizeof(srd_Decoder),
-       .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
-       .tp_doc = "sigrok Decoder base class",
-       .tp_methods = Decoder_methods,
-};
-/** @endcond */
+/** Create the sigrokdecode.Decoder type.
+ * @return The new type object.
+ * @private
+ */
+SRD_PRIV PyObject *srd_Decoder_type_new(void)
+{
+       PyType_Spec spec;
+       PyType_Slot slots[] = {
+               { Py_tp_doc, "sigrok Decoder base class" },
+               { Py_tp_methods, Decoder_methods },
+               { Py_tp_new, (void *)&PyType_GenericNew },
+               { 0, NULL }
+       };
+       spec.name = "sigrokdecode.Decoder";
+       spec.basicsize = sizeof(srd_Decoder);
+       spec.itemsize = 0;
+       spec.flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;
+       spec.slots = slots;
+
+       return PyType_FromSpec(&spec);
+}
index 0b113397cd5e058f9525100e70941308eafd89f0..d126d7b420cd058aa89f5ec248c4aef1e8339313 100644 (file)
@@ -73,14 +73,25 @@ static PyObject *srd_logic_iternext(PyObject *self)
        return logic->sample;
 }
 
-/** @cond PRIVATE */
-SRD_PRIV PyTypeObject srd_logic_type = {
-       PyVarObject_HEAD_INIT(NULL, 0)
-       .tp_name = "srd_logic",
-       .tp_basicsize = sizeof(srd_logic),
-       .tp_flags = Py_TPFLAGS_DEFAULT,
-       .tp_doc = "Sigrokdecode logic sample object",
-       .tp_iter = srd_logic_iter,
-       .tp_iternext = srd_logic_iternext,
-};
-/** @endcond */
+/** Create the srd_logic type.
+ * @return The new type object.
+ * @private
+ */
+SRD_PRIV PyObject *srd_logic_type_new(void)
+{
+       PyType_Spec spec;
+       PyType_Slot slots[] = {
+               { Py_tp_doc, "sigrokdecode logic sample object" },
+               { Py_tp_iter, (void *)&srd_logic_iter },
+               { Py_tp_iternext, (void *)&srd_logic_iternext },
+               { Py_tp_new, (void *)&PyType_GenericNew },
+               { 0, NULL }
+       };
+       spec.name = "srd_logic";
+       spec.basicsize = sizeof(srd_logic);
+       spec.itemsize = 0;
+       spec.flags = Py_TPFLAGS_DEFAULT;
+       spec.slots = slots;
+
+       return PyType_FromSpec(&spec);
+}
diff --git a/util.c b/util.c
index be14041ce5147a1dcf616000bdcfb8729cd915b4..fcadf47edcdfd5658b6ccdd5257e016524c74722 100644 (file)
--- a/util.c
+++ b/util.c
 
 #include <config.h>
 #include "libsigrokdecode-internal.h" /* First, so we avoid a _POSIX_C_SOURCE warning. */
-#include "libsigrokdecode.h"
 
 /**
  * Get the value of a Python object's attribute, returned as a newly
  * allocated char *.
  *
- * @param py_obj The object to probe.
- * @param attr Name of the attribute to retrieve.
- * @param outstr ptr to char * storage to be filled in.
+ * @param[in] py_obj The object to probe.
+ * @param[in] attr Name of the attribute to retrieve.
+ * @param[out] outstr ptr to char * storage to be filled in.
  *
  * @return SRD_OK upon success, a (negative) error code otherwise.
- *         The 'outstr' argument points to a malloc()ed string upon success.
+ *         The 'outstr' argument points to a g_malloc()ed string upon success.
  *
  * @private
  */
-SRD_PRIV int py_attr_as_str(const PyObject *py_obj, const char *attr,
-                           char **outstr)
+SRD_PRIV int py_attr_as_str(PyObject *py_obj, const char *attr, char **outstr)
 {
        PyObject *py_str;
        int ret;
 
-       if (!PyObject_HasAttrString((PyObject *)py_obj, attr)) {
-               srd_dbg("%s object has no attribute '%s'.",
-                       Py_TYPE(py_obj)->tp_name, attr);
+       if (!PyObject_HasAttrString(py_obj, attr)) {
+               srd_dbg("Object has no attribute '%s'.", attr);
                return SRD_ERR_PYTHON;
        }
 
-       if (!(py_str = PyObject_GetAttrString((PyObject *)py_obj, attr))) {
-               srd_exception_catch("");
-               return SRD_ERR_PYTHON;
-       }
-
-       if (!PyUnicode_Check(py_str)) {
-               srd_dbg("%s attribute should be a string, but is a %s.",
-                       attr, Py_TYPE(py_str)->tp_name);
-               Py_DecRef(py_str);
+       if (!(py_str = PyObject_GetAttrString(py_obj, attr))) {
+               srd_exception_catch("Failed to get attribute '%s'", attr);
                return SRD_ERR_PYTHON;
        }
 
        ret = py_str_as_str(py_str, outstr);
-       Py_DecRef(py_str);
+       Py_DECREF(py_str);
 
        return ret;
 }
@@ -69,128 +59,133 @@ SRD_PRIV int py_attr_as_str(const PyObject *py_obj, const char *attr,
  * Get the value of a Python dictionary item, returned as a newly
  * allocated char *.
  *
- * @param py_obj The dictionary to probe.
- * @param key Key of the item to retrieve.
- * @param outstr Pointer to char * storage to be filled in.
+ * @param[in] py_obj The dictionary to probe.
+ * @param[in] key Key of the item to retrieve.
+ * @param[out] outstr Pointer to char * storage to be filled in.
  *
  * @return SRD_OK upon success, a (negative) error code otherwise.
- *         The 'outstr' argument points to a malloc()ed string upon success.
+ *         The 'outstr' argument points to a g_malloc()ed string upon success.
  *
  * @private
  */
-SRD_PRIV int py_dictitem_as_str(const PyObject *py_obj, const char *key,
+SRD_PRIV int py_dictitem_as_str(PyObject *py_obj, const char *key,
                                char **outstr)
 {
        PyObject *py_value;
-       int ret;
 
-       if (!PyDict_Check((PyObject *)py_obj)) {
-               srd_dbg("Object is a %s, not a dictionary.",
-                       Py_TYPE((PyObject *)py_obj)->tp_name);
+       if (!PyDict_Check(py_obj)) {
+               srd_dbg("Object is not a dictionary.");
                return SRD_ERR_PYTHON;
        }
 
-       if (!(py_value = PyDict_GetItemString((PyObject *)py_obj, key))) {
+       if (!(py_value = PyDict_GetItemString(py_obj, key))) {
                srd_dbg("Dictionary has no attribute '%s'.", key);
                return SRD_ERR_PYTHON;
        }
 
-       if (!PyUnicode_Check(py_value)) {
-               srd_dbg("Dictionary value for %s should be a string, but is "
-                       "a %s.", key, Py_TYPE(py_value)->tp_name);
-               return SRD_ERR_PYTHON;
-       }
-
-       ret = py_str_as_str(py_value, outstr);
-
-       return ret;
+       return py_str_as_str(py_value, outstr);
 }
 
 /**
  * Get the value of a Python unicode string object, returned as a newly
  * allocated char *.
  *
- * @param py_str The unicode string object.
- * @param outstr ptr to char * storage to be filled in.
+ * @param[in] py_str The unicode string object.
+ * @param[out] outstr ptr to char * storage to be filled in.
  *
  * @return SRD_OK upon success, a (negative) error code otherwise.
- *         The 'outstr' argument points to a malloc()ed string upon success.
+ *         The 'outstr' argument points to a g_malloc()ed string upon success.
  *
  * @private
  */
-SRD_PRIV int py_str_as_str(const PyObject *py_str, char **outstr)
+SRD_PRIV int py_str_as_str(PyObject *py_str, char **outstr)
 {
-       PyObject *py_encstr;
-       int ret;
+       PyObject *py_bytes;
        char *str;
 
-       py_encstr = NULL;
-       str = NULL;
-       ret = SRD_OK;
-
-       if (!PyUnicode_Check((PyObject *)py_str)) {
-               srd_dbg("Object is a %s, not a string object.",
-                       Py_TYPE((PyObject *)py_str)->tp_name);
-               ret = SRD_ERR_PYTHON;
-               goto err_out;
-       }
-
-       if (!(py_encstr = PyUnicode_AsEncodedString((PyObject *)py_str,
-           "utf-8", NULL))) {
-               ret = SRD_ERR_PYTHON;
-               goto err_out;
-       }
-       if (!(str = PyBytes_AS_STRING(py_encstr))) {
-               ret = SRD_ERR_PYTHON;
-               goto err_out;
+       if (!PyUnicode_Check(py_str)) {
+               srd_dbg("Object is not a string object.");
+               return SRD_ERR_PYTHON;
        }
 
-       *outstr = g_strdup(str);
-
-err_out:
-       if (py_encstr)
-               Py_XDECREF(py_encstr);
-
-       if (PyErr_Occurred()) {
-               srd_exception_catch("string conversion failed");
+       py_bytes = PyUnicode_AsUTF8String(py_str);
+       if (py_bytes) {
+               str = g_strdup(PyBytes_AsString(py_bytes));
+               Py_DECREF(py_bytes);
+               if (str) {
+                       *outstr = str;
+                       return SRD_OK;
+               }
        }
+       srd_exception_catch("Failed to extract string");
 
-       return ret;
+       return SRD_ERR_PYTHON;
 }
 
 /**
- * Convert a Python list of unicode strings to a NULL-terminated UTF8-encoded
- * char * array. The caller must g_free() each string when finished.
+ * Convert a Python list of unicode strings to a C string vector.
+ * On success, a pointer to a newly allocated NULL-terminated array of
+ * allocated C strings is written to @a out_strv. The caller must g_free()
+ * each string and the array itself.
  *
- * @param py_strlist The list object.
- * @param outstr ptr to char ** storage to be filled in.
+ * @param[in] py_strseq The sequence object.
+ * @param[out] out_strv Address of string vector to be filled in.
  *
  * @return SRD_OK upon success, a (negative) error code otherwise.
- *         The 'outstr' argument points to a g_malloc()ed char** upon success.
  *
  * @private
  */
-SRD_PRIV int py_strseq_to_char(const PyObject *py_strseq, char ***outstr)
+SRD_PRIV int py_strseq_to_char(PyObject *py_strseq, char ***out_strv)
 {
-       PyObject *py_str;
-       int list_len, i;
-       char **out, *str;
+       PyObject *py_item, *py_bytes;
+       char **strv, *str;
+       ssize_t seq_len, i;
+
+       if (!PySequence_Check(py_strseq)) {
+               srd_err("Object does not provide sequence protocol.");
+               return SRD_ERR_PYTHON;
+       }
+
+       seq_len = PySequence_Size(py_strseq);
+       if (seq_len < 0) {
+               srd_exception_catch("Failed to obtain sequence size");
+               return SRD_ERR_PYTHON;
+       }
 
-       list_len = PySequence_Size((PyObject *)py_strseq);
-       if (!(out = g_try_malloc(sizeof(char *) * (list_len + 1)))) {
-               srd_err("Failed to g_malloc() 'out'.");
+       strv = g_try_new0(char *, seq_len + 1);
+       if (!strv) {
+               srd_err("Failed to allocate result string vector.");
                return SRD_ERR_MALLOC;
        }
-       for (i = 0; i < list_len; i++) {
-               if (!(py_str = PyUnicode_AsEncodedString(
-                   PySequence_GetItem((PyObject *)py_strseq, i), "utf-8", NULL)))
-                       return SRD_ERR_PYTHON;
-               if (!(str = PyBytes_AS_STRING(py_str)))
-                       return SRD_ERR_PYTHON;
-               out[i] = g_strdup(str);
+
+       for (i = 0; i < seq_len; i++) {
+               py_item = PySequence_GetItem(py_strseq, i);
+               if (!py_item)
+                       goto err_out;
+
+               if (!PyUnicode_Check(py_item)) {
+                       Py_DECREF(py_item);
+                       goto err_out;
+               }
+               py_bytes = PyUnicode_AsUTF8String(py_item);
+               Py_DECREF(py_item);
+               if (!py_bytes)
+                       goto err_out;
+
+               str = g_strdup(PyBytes_AsString(py_bytes));
+               Py_DECREF(py_bytes);
+               if (!str)
+                       goto err_out;
+
+               strv[i] = str;
        }
-       out[i] = NULL;
-       *outstr = out;
+       *out_strv = strv;
 
        return SRD_OK;
+
+err_out:
+       g_strfreev(strv);
+       srd_exception_catch("Failed to obtain string item");
+
+       return SRD_ERR_PYTHON;
 }