[pygobject] Fix array handling for interfaces, properties, and signals



commit a31fabdc12f1da301c8df0af319ca3f4181671ee
Author: Mikkel Kamstrup Erlandsen <mikkel kamstrup canonical com>
Date:   Thu Jul 12 09:19:42 2012 +0200

    Fix array handling for interfaces, properties, and signals
    
    Fix lots of corner cases where arrays are not handled properly.
    _pygi_argument_to_object() now has the documented expectation of getting arrays
    packed in GArrays. This was implicit before and not correctly done on most call
    sites.
    
    The helper _pygi_argument_to_array() has been improved to work on any kind of
    array. Fix all call sites of _pygi_argument_to_object() to do the
    array conversion appropriately before calling _pygi_argument_to_object().
    
    Adds a test case that implements a GInterface with a method that takes an array
    of variants as input.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=667244

 gi/pygi-argument.c       |  156 ++++++++++++++++++++++++++++++++--------------
 gi/pygi-argument.h       |    4 +-
 gi/pygi-closure.c        |   11 +++
 gi/pygi-info.c           |   20 ++++--
 gi/pygi-property.c       |    1 +
 gi/pygi-signal-closure.c |   14 ++++-
 tests/test_gi.py         |   18 +++++
 7 files changed, 167 insertions(+), 57 deletions(-)
---
diff --git a/gi/pygi-argument.c b/gi/pygi-argument.c
index fb798e1..b73973e 100644
--- a/gi/pygi-argument.c
+++ b/gi/pygi-argument.c
@@ -653,50 +653,105 @@ check_number_release:
     return retval;
 }
 
+/**
+ * _pygi_argument_to_array
+ * @arg: The argument to convert
+ * @args: Arguments to method invocation, possibly contaning the array length.
+ *        Set to NULL if this is not for a method call
+ * @type_info: The type info for @arg
+ * @out_free_array: A return location for a gboolean that indicates whether
+ *                  or not the wrapped GArray should be freed
+ *
+ * Make sure an array type argument is wrapped in a GArray.
+ *
+ * Note: This method can *not* be folded into _pygi_argument_to_object() because
+ * arrays are special in the sense that they might require access to @args in
+ * order to get the length.
+ *
+ * Returns: A GArray wrapping @arg. If @out_free_array has been set to TRUE then
+ *          free the array with g_array_free() without freeing the data members.
+ *          Otherwise don't free the array.
+ */
 GArray *
 _pygi_argument_to_array (GIArgument  *arg,
                          GIArgument  *args[],
-                         GITypeInfo *type_info,
-                         gboolean is_method)
+                         GITypeInfo  *type_info,
+                         gboolean    *out_free_array)
 {
     GITypeInfo *item_type_info;
     gboolean is_zero_terminated;
     gsize item_size;
     gssize length;
     GArray *g_array;
+    
+    g_return_val_if_fail (g_type_info_get_tag (type_info) == GI_TYPE_TAG_ARRAY, NULL);
 
     if (arg->v_pointer == NULL) {
         return NULL;
     }
+    
+    switch (g_type_info_get_array_type (type_info)) {
+        case GI_ARRAY_TYPE_C:
+            is_zero_terminated = g_type_info_is_zero_terminated (type_info);
+            item_type_info = g_type_info_get_param_type (type_info, 0);
 
-    is_zero_terminated = g_type_info_is_zero_terminated (type_info);
-    item_type_info = g_type_info_get_param_type (type_info, 0);
-
-    item_size = _pygi_g_type_info_size (item_type_info);
+            item_size = _pygi_g_type_info_size (item_type_info);
 
-    g_base_info_unref ( (GIBaseInfo *) item_type_info);
+            g_base_info_unref ( (GIBaseInfo *) item_type_info);
 
-    if (is_zero_terminated) {
-        length = g_strv_length (arg->v_pointer);
-    } else {
-        length = g_type_info_get_array_fixed_size (type_info);
-        if (length < 0) {
-            gint length_arg_pos;
+            if (is_zero_terminated) {
+                length = g_strv_length (arg->v_pointer);
+            } else {
+                length = g_type_info_get_array_fixed_size (type_info);
+                if (length < 0) {
+                    if (G_UNLIKELY (args == NULL)) {
+                        g_critical ("Unable to determine array length for %p",
+                                    arg->v_pointer);
+                        g_array = g_array_new (is_zero_terminated, FALSE, item_size);
+                        *out_free_array = TRUE;
+                        return g_array;
+                    }
+                    gint length_arg_pos;
 
-            length_arg_pos = g_type_info_get_array_length (type_info);
-            g_assert (length_arg_pos >= 0);
+                    length_arg_pos = g_type_info_get_array_length (type_info);
+                    g_assert (length_arg_pos >= 0);
 
-            /* FIXME: Take into account the type of the argument. */
-            length = args[length_arg_pos]->v_int;
-        }
-    }
+                    /* FIXME: Take into account the type of the length argument */
+                    length = args[length_arg_pos]->v_int;
+                }
+            }
 
-    g_assert (length >= 0);
+            g_assert (length >= 0);
 
-    g_array = g_array_new (is_zero_terminated, FALSE, item_size);
+            g_array = g_array_new (is_zero_terminated, FALSE, item_size);
 
-    g_array->data = arg->v_pointer;
-    g_array->len = length;
+            g_array->data = arg->v_pointer;
+            g_array->len = length;
+            *out_free_array = TRUE;
+            break;
+        case GI_ARRAY_TYPE_ARRAY:
+        case GI_ARRAY_TYPE_BYTE_ARRAY:
+            /* Note: GByteArray is really just a GArray */
+            g_array = arg->v_pointer;
+            *out_free_array = FALSE;
+            break;
+        case GI_ARRAY_TYPE_PTR_ARRAY:
+        {
+            GPtrArray *ptr_array = (GPtrArray*) arg->v_pointer;
+            g_array = g_array_sized_new (FALSE, FALSE,
+                                         sizeof(gpointer),
+                                         ptr_array->len);
+             g_array->data = (char*) ptr_array->pdata;
+             g_array->len = ptr_array->len;
+             *out_free_array = TRUE;
+             break;
+        }
+        default:
+            g_critical ("Unexpected array type %u",
+                        g_type_info_get_array_type (type_info));
+            g_array = NULL;
+            break;
+    }
 
     return g_array;
 }
@@ -1345,6 +1400,18 @@ hash_table_release:
     return arg;
 }
 
+/**
+ * _pygi_argument_to_object:
+ * @arg: The argument to convert to an object. 
+ * @type_info: Type info for @arg
+ * @transfer:
+ *
+ * If the argument is of type array, it must be encoded in a GArray, by calling
+ * _pygi_argument_to_array(). This logic can not be folded into this method
+ * as determining array lengths may require access to method call arguments.
+ * 
+ * Returns: A PyObject representing @arg
+ */
 PyObject *
 _pygi_argument_to_object (GIArgument  *arg,
                           GITypeInfo *type_info,
@@ -1481,19 +1548,30 @@ _pygi_argument_to_object (GIArgument  *arg,
         }
         case GI_TYPE_TAG_ARRAY:
         {
+            /* Arrays are assumed to be packed in a GArray */
             GArray *array;
             GITypeInfo *item_type_info;
             GITypeTag item_type_tag;
             GITransfer item_transfer;
             gsize i, item_size;
-
-            array = arg->v_pointer;
-
+            
+            if (arg->v_pointer == NULL)
+                return PyList_New (0);
+            
             item_type_info = g_type_info_get_param_type (type_info, 0);
             g_assert (item_type_info != NULL);
 
             item_type_tag = g_type_info_get_tag (item_type_info);
             item_transfer = transfer == GI_TRANSFER_CONTAINER ? GI_TRANSFER_NOTHING : transfer;
+            
+            array = arg->v_pointer;
+            item_size = g_array_get_element_size (array);
+            
+            if (G_UNLIKELY (item_size > sizeof(GIArgument))) {
+                g_critical ("Stack overflow protection. "
+                            "Can't copy array element into GIArgument.");
+                return PyList_New (0);
+            }
 
             if (item_type_tag == GI_TYPE_TAG_UINT8) {
                 /* Return as a byte array */
@@ -1516,31 +1594,13 @@ _pygi_argument_to_object (GIArgument  *arg,
                     break;
                 }
 
-            }
-            item_size = g_array_get_element_size (array);
+            }            
 
             for (i = 0; i < array->len; i++) {
-                GIArgument item;
+                GIArgument item = { 0 };
                 PyObject *py_item;
-                gboolean is_struct = FALSE;
-
-                if (item_type_tag == GI_TYPE_TAG_INTERFACE) {
-                    GIBaseInfo *iface_info = g_type_info_get_interface (item_type_info);
-                    switch (g_base_info_get_type (iface_info)) {
-                        case GI_INFO_TYPE_STRUCT:
-                        case GI_INFO_TYPE_BOXED:
-                            is_struct = TRUE;
-                        default:
-                            break;
-                    }
-                    g_base_info_unref ( (GIBaseInfo *) iface_info);
-                }
-
-                if (is_struct) {
-                    item.v_pointer = &_g_array_index (array, GIArgument, i);
-                } else {
-                    memcpy (&item, &_g_array_index (array, GIArgument, i), item_size);
-                }
+                
+                memcpy (&item, array->data + i * item_size, item_size);
 
                 py_item = _pygi_argument_to_object (&item, item_type_info, item_transfer);
                 if (py_item == NULL) {
diff --git a/gi/pygi-argument.h b/gi/pygi-argument.h
index 7224c75..668fabb 100644
--- a/gi/pygi-argument.h
+++ b/gi/pygi-argument.h
@@ -44,8 +44,8 @@ gint _pygi_g_registered_type_info_check_object (GIRegisteredTypeInfo *info,
 
 GArray* _pygi_argument_to_array (GIArgument  *arg,
                                  GIArgument  *args[],
-                                 GITypeInfo *type_info,
-                                 gboolean    is_method);
+                                 GITypeInfo  *type_info,
+                                 gboolean    *out_free_array);
 
 GIArgument _pygi_argument_from_object (PyObject   *object,
                                       GITypeInfo *type_info,
diff --git a/gi/pygi-closure.c b/gi/pygi-closure.c
index 241d91a..8c8e13d 100644
--- a/gi/pygi-closure.c
+++ b/gi/pygi-closure.c
@@ -185,10 +185,12 @@ _pygi_closure_convert_ffi_arguments (GICallableInfo *callable_info, void **args)
                 case GI_TYPE_TAG_GHASH:
                 case GI_TYPE_TAG_GLIST:
                 case GI_TYPE_TAG_GSLIST:
+                case GI_TYPE_TAG_ARRAY:
                 case GI_TYPE_TAG_VOID:
                     g_args[i].v_pointer = * (gpointer *) args[i];
                     break;
                 default:
+                    g_warning ("Unhandled type tag %s", g_type_tag_to_string (tag));
                     g_args[i].v_pointer = 0;
             }
         }
@@ -235,6 +237,7 @@ _pygi_closure_convert_arguments (GICallableInfo *callable_info, void **args,
             GITransfer transfer = g_arg_info_get_ownership_transfer (arg_info);
             PyObject *value;
             GIArgument *arg;
+            gboolean free_array = FALSE;
 
             if (direction == GI_DIRECTION_IN && arg_tag == GI_TYPE_TAG_VOID &&
                     g_type_info_is_pointer (arg_type)) {
@@ -290,8 +293,16 @@ _pygi_closure_convert_arguments (GICallableInfo *callable_info, void **args,
                     arg = (GIArgument*) &g_args[i];
                 else
                     arg = (GIArgument*) g_args[i].v_pointer;
+                
+                if (g_type_info_get_tag (arg_type) == GI_TYPE_TAG_ARRAY)
+                    arg->v_pointer = _pygi_argument_to_array (arg, args, 
+                                                              arg_type, &free_array);
 
                 value = _pygi_argument_to_object (arg, arg_type, transfer);
+                
+                if (free_array)
+                    g_array_free (arg->v_pointer, FALSE);
+                
                 if (value == NULL) {
                     g_base_info_unref (arg_type);
                     g_base_info_unref (arg_info);
diff --git a/gi/pygi-info.c b/gi/pygi-info.c
index f3fedab..8aafe8b 100644
--- a/gi/pygi-info.c
+++ b/gi/pygi-info.c
@@ -1147,6 +1147,7 @@ _wrap_g_constant_info_get_value (PyGIBaseInfo *self)
     GITypeInfo *type_info;
     GIArgument value;
     PyObject *py_value;
+    gboolean free_array = FALSE;
 
     if (g_constant_info_get_value ( (GIConstantInfo *) self->info, &value) < 0) {
         PyErr_SetString (PyExc_RuntimeError, "unable to get value");
@@ -1155,7 +1156,16 @@ _wrap_g_constant_info_get_value (PyGIBaseInfo *self)
 
     type_info = g_constant_info_get_type ( (GIConstantInfo *) self->info);
 
+    if (g_type_info_get_tag (type_info) == GI_TYPE_TAG_ARRAY) {
+        value.v_pointer = _pygi_argument_to_array (&value, NULL,
+                                                   type_info, &free_array);
+    }
+
     py_value = _pygi_argument_to_object (&value, type_info, GI_TRANSFER_NOTHING);
+    
+    if (free_array) {
+        g_array_free (value.v_pointer, FALSE);
+    }
 
     g_constant_info_free_value (self->info, &value);
     g_base_info_unref ( (GIBaseInfo *) type_info);
@@ -1202,6 +1212,7 @@ _wrap_g_field_info_get_value (PyGIBaseInfo *self,
     GITypeInfo *field_type_info;
     GIArgument value;
     PyObject *py_value = NULL;
+    gboolean free_array = FALSE;
 
     memset(&value, 0, sizeof(GIArgument));
 
@@ -1278,18 +1289,15 @@ _wrap_g_field_info_get_value (PyGIBaseInfo *self,
         goto out;
     }
 
-    if ( (g_type_info_get_tag (field_type_info) == GI_TYPE_TAG_ARRAY) &&
-            (g_type_info_get_array_type (field_type_info) == GI_ARRAY_TYPE_C)) {
+    if (g_type_info_get_tag (field_type_info) == GI_TYPE_TAG_ARRAY) {
         value.v_pointer = _pygi_argument_to_array (&value, NULL,
-                                                   field_type_info, FALSE);
+                                                   field_type_info, &free_array);
     }
 
 argument_to_object:
     py_value = _pygi_argument_to_object (&value, field_type_info, GI_TRANSFER_NOTHING);
 
-    if ( (value.v_pointer != NULL) &&
-            (g_type_info_get_tag (field_type_info) == GI_TYPE_TAG_ARRAY) &&
-               (g_type_info_get_array_type (field_type_info) == GI_ARRAY_TYPE_C)) {
+    if (free_array) {
         g_array_free (value.v_pointer, FALSE);
     }
 
diff --git a/gi/pygi-property.c b/gi/pygi-property.c
index 56a9745..7c83eef 100644
--- a/gi/pygi-property.c
+++ b/gi/pygi-property.c
@@ -211,6 +211,7 @@ pygi_get_property_value_real (PyGObject *instance,
             arg.v_pointer = g_value_get_boxed (&value);
             break;
         case GI_TYPE_TAG_GLIST:
+        case GI_TYPE_TAG_GSLIST:
             arg.v_pointer = g_value_get_pointer (&value);
             break;
         case GI_TYPE_TAG_ARRAY:
diff --git a/gi/pygi-signal-closure.c b/gi/pygi-signal-closure.c
index 1482529..4e9dcb5 100644
--- a/gi/pygi-signal-closure.c
+++ b/gi/pygi-signal-closure.c
@@ -145,13 +145,25 @@ pygi_signal_closure_marshal(GClosure *closure,
             GITransfer transfer;
             GIArgument arg = { 0, };
             PyObject *item = NULL;
+            gboolean free_array = FALSE;
 
             g_callable_info_load_arg(signal_info, i - 1, &arg_info);
             g_arg_info_load_type(&arg_info, &type_info);
             transfer = g_arg_info_get_ownership_transfer(&arg_info);
 
             arg = _pygi_argument_from_g_value(&param_values[i], &type_info);
-            item = _pygi_argument_to_object(&arg, &type_info, transfer);
+            
+            if (g_type_info_get_tag (&type_info) == GI_TYPE_TAG_ARRAY) {
+                arg.v_pointer = _pygi_argument_to_array (&arg, NULL,
+                                                         &type_info, &free_array);
+            }
+            
+            item = _pygi_argument_to_object (&arg, &type_info, transfer);
+            
+            if (free_array) {
+                g_array_free (arg.v_pointer, FALSE);
+            }
+            
 
             if (item == NULL) {
                 goto out;
diff --git a/tests/test_gi.py b/tests/test_gi.py
index ce988c0..428e7fd 100644
--- a/tests/test_gi.py
+++ b/tests/test_gi.py
@@ -1643,6 +1643,16 @@ class TestPythonGObject(unittest.TestCase):
         def do_method_with_default_implementation(self, int8):
             self.val = int8
 
+    class Interface3Impl(GObject.Object, GIMarshallingTests.Interface3):
+        def __init__(self):
+            GObject.Object.__init__(self)
+            self.variants = None
+            self.n_variants = None
+
+        def do_test_variant_array_in(self, variants, n_variants):
+            self.variants = variants
+            self.n_variants = n_variants
+
     def test_object(self):
         self.assertTrue(issubclass(self.Object, GIMarshallingTests.Object))
 
@@ -1732,6 +1742,14 @@ class TestPythonGObject(unittest.TestCase):
         GIMarshallingTests.SubSubObject.do_method_deep_hierarchy(sub_sub_sub_object, 5)
         self.assertEqual(sub_sub_sub_object.props.int, 5)
 
+    def test_interface3impl(self):
+        iface3 = self.Interface3Impl()
+        variants = [GLib.Variant('i', 27), GLib.Variant('s', 'Hello')]
+        iface3.test_variant_array_in(variants)
+        self.assertEqual(iface3.n_variants, 2)
+        self.assertEqual(iface3.variants[0].unpack(), 27)
+        self.assertEqual(iface3.variants[1].unpack(), 'Hello')
+
     def test_python_subsubobject_vfunc(self):
         class PySubObject(GIMarshallingTests.Object):
             def __init__(self):



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]