From: Daniel Elstner Date: Sun, 4 Oct 2015 23:53:43 +0000 (+0200) Subject: Python: Restrict code to stable ABI subset X-Git-Tag: libsigrokdecode-0.4.0~47 X-Git-Url: https://sigrok.org/gitweb/?p=libsigrokdecode.git;a=commitdiff_plain;h=201a85a8ea071d37f4fda2668c0a1c488d852f4e 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. --- diff --git a/decoder.c b/decoder.c index 753a88a..e50345f 100644 --- 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; } 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); } diff --git a/instance.c b/instance.c index 4eca3f5..53a8d92 100644 --- a/instance.c +++ b/instance.c @@ -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; } diff --git a/libsigrokdecode-internal.h b/libsigrokdecode-internal.h index bcf6bb7..fa8e91c 100644 --- a/libsigrokdecode-internal.h +++ b/libsigrokdecode-internal.h @@ -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 /* 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, ...); diff --git a/module_sigrokdecode.c b/module_sigrokdecode.c index 468a1cb..17563c1 100644 --- a/module_sigrokdecode.c +++ b/module_sigrokdecode.c @@ -23,11 +23,7 @@ /** @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 */ diff --git a/type_decoder.c b/type_decoder.c index d4c592f..89b5ca1 100644 --- a/type_decoder.c +++ b/type_decoder.c @@ -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); +} diff --git a/type_logic.c b/type_logic.c index 0b11339..d126d7b 100644 --- a/type_logic.c +++ b/type_logic.c @@ -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 be14041..fcadf47 100644 --- a/util.c +++ b/util.c @@ -20,47 +20,37 @@ #include #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; }