[glib/gvariant-varargs: 1/5] GVariant varargs support



commit f6b39aa624d2ffc395fa6f866a3c85ff86ed7cf8
Author: Ryan Lortie <desrt desrt ca>
Date:   Tue Mar 2 03:17:54 2010 -0500

    GVariant varargs support

 glib/glib.symbols     |   14 +-
 glib/gvariant.c       | 1388 +++++++++++++++++++++++++++++++++++++++++++------
 glib/gvariant.h       |   28 +
 glib/tests/gvariant.c |  662 +++++++++++++++++++++++-
 4 files changed, 1926 insertions(+), 166 deletions(-)
---
diff --git a/glib/glib.symbols b/glib/glib.symbols
index bc5d514..e126b44 100644
--- a/glib/glib.symbols
+++ b/glib/glib.symbols
@@ -1767,8 +1767,6 @@ g_variant_iter_free
 g_variant_iter_init
 g_variant_iter_n_children
 g_variant_iter_new
-g_variant_iter_next
-g_variant_iter_loop
 g_variant_iter_next_value
 
 g_variant_builder_add_value
@@ -1780,6 +1778,18 @@ g_variant_builder_end
 g_variant_builder_new
 g_variant_builder_unref
 g_variant_builder_ref
+
+g_variant_format_string_scan
+g_variant_format_string_scan_type
+g_variant_new_va
+g_variant_get_va
+g_variant_new
+g_variant_get
+
+g_variant_builder_add
+g_variant_get_child
+g_variant_iter_next
+g_variant_iter_loop
 #endif
 #endif
 
diff --git a/glib/gvariant.c b/glib/gvariant.c
index 4f2b560..78c8d2e 100644
--- a/glib/gvariant.c
+++ b/glib/gvariant.c
@@ -2133,167 +2133,6 @@ g_variant_iter_next_value (GVariantIter *iter)
   return NULL;
 }
 
-/**
- * g_variant_iter_loop:
- * @iter: a #GVariantIter
- * @format_string: a GVariant format string
- * @...: the arguments to unpack the value into
- * @returns: %TRUE if a value was unpacked, or %FALSE if there as no
- *           value
- *
- * Gets the next item in the container and unpacks it into the variable
- * argument list according to @format_string, returning %TRUE.
- *
- * If no more items remain then %FALSE is returned.
- *
- * On the first call to this function, the pointers appearing on the
- * variable argument list are assumed to point at uninitialised memory.
- * On the second and later calls, it is assumed that the same pointers
- * will be given and that they will point to the memory as set by the
- * previous call to this function.  This allows the previous values to
- * be freed, as appropriate.
- *
- * This function is intended to be used with a while loop as
- * demonstrated in the following example.  This function can only be
- * used when iterating over an array.  It is only valid to call this
- * function with a string constant for the format string and the same
- * string constant must be used each time.  Mixing calls to this
- * function and g_variant_iter_next() or g_variant_iter_next_value() on
- * the same iterator is not recommended.
- *
- * <example>
- *  <title>Memory management with g_variant_iter_loop()</title>
- *  <programlisting>
- *   /<!-- -->* Iterates a dictionary of type 'a{sv}' *<!-- -->/
- *   void
- *   iterate_dictionary (GVariant *dictionary)
- *   {
- *     GVariantIter iter;
- *     GVariant *value;
- *     gchar *key;
- *
- *     g_variant_iter_init (&iter, dictionary);
- *     while (g_variant_iter_loop (&iter, "{sv}", &key, &value))
- *       {
- *         g_print ("Item '%s' has type '%s'\n", key,
- *                  g_variant_get_type_string (value));
- *
- *         /<!-- -->* no need to free 'key' and 'value' here *<!-- -->/
- *       }
- *   }
- *  </programlisting>
- * </example>
- *
- * If you want a slightly less magical alternative that requires more
- * typing, see g_variant_iter_next().
- *
- * Since: 2.24
- **/
-gboolean
-g_variant_iter_loop (GVariantIter *iter,
-                     const gchar  *format_string,
-                     ...)
-{
-  gboolean first_time = GVSI(iter)->loop_format == NULL;
-  GVariant *value;
-
-  g_return_val_if_fail (first_time ||
-                        format_string == GVSI(iter)->loop_format,
-                        FALSE);
-
-  if (first_time)
-    {
-      TYPE_CHECK (GVSI(iter)->value, G_VARIANT_TYPE_ARRAY, FALSE);
-      GVSI(iter)->loop_format = format_string;
-    }
-
-  value = g_variant_iter_next_value (iter);
-
-  if (value != NULL)
-    {
-      va_list ap;
-
-      va_start (ap, format_string);
-      /* varargs get stuff */
-      va_end (ap);
-
-      g_variant_unref (value);
-    }
-
-  return value != NULL;
-}
-
-/**
- * g_variant_iter_next:
- * @iter: a #GVariantIter
- * @format_string: a GVariant format string
- * @...: the arguments to unpack the value into
- * @returns: %TRUE if a value was unpacked, or %FALSE if there as no
- *           value
- *
- * Gets the next item in the container and unpacks it into the variable
- * argument list according to @format_string, returning %TRUE.
- *
- * If no more items remain then %FALSE is returned.
- *
- * All of the pointers given on the variable arguments list of this
- * function are assumed to point at uninitialised memory.  It is the
- * responsibility of the caller to free all of the values returned by
- * the unpacking process.
- *
- * <example>
- *  <title>Memory management with g_variant_iter_next()</title>
- *  <programlisting>
- *   /<!-- -->* Iterates a dictionary of type 'a{sv}' *<!-- -->/
- *   void
- *   iterate_dictionary (GVariant *dictionary)
- *   {
- *     GVariantIter iter;
- *     GVariant *value;
- *     gchar *key;
- *
- *     g_variant_iter_init (&iter, dictionary);
- *     while (g_variant_iter_next (&iter, "{sv}", &key, &value))
- *       {
- *         g_print ("Item '%s' has type '%s'\n", key,
- *                  g_variant_get_type_string (value));
- *
- *         /<!-- -->* must free data for ourselves *<!-- -->/
- *         g_variant_unref (value);
- *         g_free (key);
- *       }
- *   }
- *  </programlisting>
- * </example>
- *
- * For a solution that is likely to be more convenient to C programmers,
- * see g_variant_iter_loop().
- *
- * Since: 2.24
- **/
-gboolean
-g_variant_iter_next (GVariantIter *iter,
-                     const gchar  *format_string,
-                     ...)
-{
-  GVariant *value;
-
-  value = g_variant_iter_next_value (iter);
-
-  if (value != NULL)
-    {
-      va_list ap;
-
-      va_start (ap, format_string);
-      /* varargs get stuff */
-      va_end (ap);
-
-      g_variant_unref (value);
-    }
-
-  return value != NULL;
-}
-
 /* GVariantBuilder {{{1 */
 /**
  * GVariantBuilder:
@@ -2837,6 +2676,1233 @@ g_variant_builder_end (GVariantBuilder *builder)
   return value;
 }
 
+/* Format strings {{{1 */
+/**
+ * g_variant_format_string_scan:
+ * @string: a string that may be prefixed with a format string
+ * @limit: a pointer to the end of @string
+ * @endptr: location to store the end pointer, or %NULL
+ * @returns: %TRUE if there was a valid format string
+ *
+ * Checks the string pointed to by @string for starting with a properly
+ * formed #GVariant varargs format string.  If no valid format string is
+ * found then %FALSE is returned.
+ *
+ * If @string does start with a valid format string then %TRUE is
+ * returned.  If @endptr is non-%NULL then it is updated to point to the
+ * first character after the format string.
+ *
+ * If @limit is non-%NULL then @limit (and any charater after it) will
+ * not be accessed and the effect is otherwise equivalent to if the
+ * character at @limit were nul.
+ *
+ * See XXX for format string infoz.
+ **/
+gboolean
+g_variant_format_string_scan (const gchar  *string,
+                              const gchar  *limit,
+                              const gchar **endptr)
+{
+#define next_char() (string == limit ? '\0' : *string++)
+#define peek_char() (string == limit ? '\0' : *string)
+  /* ISO/IEC 9899:1999 (C99) §7.21.5.2:
+   *    The terminating null character is considered to be
+   *    part of the string.
+   */
+#define is_elem(c,str) ((c) != '\0' && strchr (str, c) != NULL)
+  char c;
+
+  switch (next_char())
+    {
+    case 'b': case 'y': case 'n': case 'q': case 'i': case 'u':
+    case 'x': case 't': case 'h': case 'd': case 's': case 'o':
+    case 'g': case 'v': case '*': case '?': case 'r':
+      break;
+
+    case 'm':
+      return g_variant_format_string_scan (string, limit, endptr);
+
+    case 'a':
+    case '@':
+      return g_variant_type_string_scan (string, limit, endptr);
+
+    case '(':
+      while (peek_char() != ')')
+        if (!g_variant_format_string_scan (string, limit, &string))
+          return FALSE;
+
+      next_char(); /* consume ')' */
+      break;
+
+    case '{':
+      c = next_char();
+
+      if (c == '@')
+        c = next_char ();
+
+      if (!is_elem (c, "bynqiuxthdsog?"))
+        return FALSE;
+
+      if (!g_variant_format_string_scan (string, limit, &string))
+        return FALSE;
+
+      if (next_char() != '}')
+        return FALSE;
+
+      break;
+
+    case '^': /* '^as' or '^a&s' only */
+      if (next_char() != 'a')
+        return FALSE;
+
+      if (peek_char() == '&')
+        next_char ();
+
+      c = next_char ();
+
+      if (c != 's' && c != 'o' && c != 'g')
+        return FALSE;
+
+      break;
+
+    case '&':
+      c = next_char();
+
+      if (c != 's' && c != 'o' && c != 'g')
+        return FALSE;
+
+      break;
+
+    default:
+      return FALSE;
+    }
+
+  if (endptr != NULL)
+    *endptr = string;
+
+#undef next_char
+#undef peek_char
+#undef is_elem
+
+  return TRUE;
+}
+
+/**
+ * g_variant_format_string_scan_type:
+ * @string: a string that may be prefixed with a format string
+ * @limit: a pointer to the end of @string
+ * @endptr: location to store the end pointer, or %NULL
+ * @returns: a #GVariantType if there was a valid format string
+ *
+ * If @string starts with a valid format string then this function will
+ * return the type that the format string corresponds to.  Otherwise
+ * this function returns %NULL.
+ *
+ * Use g_variant_type_free() to free the return value when you no longer
+ * need it.
+ *
+ * This function is otherwise exactly like
+ * g_variant_format_string_scan().
+ **/
+GVariantType *
+g_variant_format_string_scan_type (const gchar  *string,
+                                   const gchar  *limit,
+                                   const gchar **endptr)
+{
+  const gchar *my_end;
+  gchar *dest;
+  gchar *new;
+
+  if (endptr == NULL)
+    endptr = &my_end;
+
+  if (!g_variant_format_string_scan (string, limit, endptr))
+    return NULL;
+
+  dest = new = g_malloc (*endptr - string + 1);
+  while (string != *endptr)
+    {
+      if (*string != '@' && *string != '&' && *string != '^')
+        *dest++ = *string;
+      string++;
+    }
+  *dest = '\0';
+
+  return (GVariantType *) G_VARIANT_TYPE (new);
+}
+
+static gboolean
+g_variant_check_valid_format (const gchar *function,
+                              const gchar *location,
+                              const gchar *format_string,
+                              gboolean     single,
+                              GVariant    *value)
+{
+  const gchar *endptr;
+  GVariantType *type;
+
+  type = g_variant_format_string_scan_type (format_string, NULL, &endptr);
+
+  if G_UNLIKELY (type == NULL || (single && *endptr != '\0'))
+    {
+      if (g_str_has_prefix (function, "IA__"))
+        function += 4;
+
+      if (single)
+        g_critical ("%s: %s: `%s' is not a valid GVariant format string",
+                    location, function, format_string);
+      else
+        g_critical ("%s: %s: `%s' does not have a valid GVariant format "
+                    "string as a prefix", location, function, format_string);
+
+      if (type != NULL)
+        g_variant_type_free (type);
+
+      return FALSE;
+    }
+
+  if G_UNLIKELY (value && !g_variant_is_of_type (value, type))
+    {
+      gchar *fragment;
+      gchar *typestr;
+
+      fragment = g_strndup (format_string, endptr - format_string);
+      typestr = g_variant_type_dup_string (type);
+
+      g_critical ("%s: %s: the GVariant format string `%s' has a type of "
+                  "`%s' but the given value has a type of `%s'",
+                  location, function, fragment, typestr,
+                  g_variant_get_type_string (value));
+
+      g_variant_type_free (type);
+
+      return FALSE;
+    }
+
+  g_variant_type_free (type);
+
+  return TRUE;
+}
+
+#define CHECK_VALID_FORMAT(str,single,value,retval) \
+  G_STMT_START {                                                             \
+    if G_UNLIKELY (!g_variant_check_valid_format (G_STRFUNC, G_STRLOC,       \
+                                                  str, single, value))       \
+      return retval;                                                         \
+  } G_STMT_END
+
+/* Variable Arguments {{{1 */
+#undef DEBUG_GVARIANT_VARARGS
+
+#ifdef DEBUG_GVARIANT_VARARGS
+#define get_arg(a,t) \
+  ({ g_print (G_STRLOC " (%s): get %s\n", G_STRFUNC, #t); va_arg (a, t); })
+#else
+#define get_arg va_arg
+#endif
+
+
+/* We consider 2 main classes of format strings:
+ *
+ *   - recursive format strings
+ *      these are ones that result in recursion and the collection of
+ *      possibly more than one argument.  Maybe types, tuples,
+ *      dictionary entries.
+ *
+ *   - leaf format string
+ *      these result in the collection of a single argument.
+ *
+ * Leaf format strings are further subdivided into two categories:
+ *
+ *   - single non-null pointer ("nnp")
+ *      these either collect or return a single non-null pointer.
+ *
+ *   - other
+ *      these collect or return something else (bool, number, etc).
+ *
+ * Based on the above, the varargs handling code is split into 4 main parts:
+ *
+ *   - nnp handling code
+ *   - leaf handling code (which may invoke nnp code)
+ *   - generic handling code (may be recursive, may invoke leaf code)
+ *   - user-facing API (which invokes the generic code)
+ *
+ * Each section implements some of the following functions:
+ *
+ *   - skip:
+ *      collect the arguments for the format string as if
+ *      g_variant_new() had been called, but do nothing with them.  used
+ *      for skipping over arguments when constructing a Nothing maybe
+ *      type.
+ *
+ *   - new:
+ *      create a GVariant *
+ *
+ *   - get:
+ *      unpack a GVariant *
+ *
+ *   - free (nnp only):
+ *      free a previously allocated item
+ */
+
+static gboolean
+g_variant_format_string_is_leaf (const gchar *str)
+{
+  return str[0] != 'm' && str[0] != '(' && str[0] != '{';
+}
+
+static gboolean
+g_variant_format_string_is_nnp (const gchar *str)
+{
+  return str[0] == 'a' || str[0] == 's' || str[0] == 'o' || str[0] == 'g' ||
+         str[0] == '^' || str[0] == '@' || str[0] == '*' || str[0] == '?' ||
+         str[0] == 'r' || str[0] == 'v' || str[0] == '&';
+}
+
+/* Single non-null pointer ("nnp") {{{2 */
+static void
+g_variant_valist_free_nnp (const gchar *str,
+                           gpointer     ptr)
+{
+  switch (*str)
+    {
+    case 'a':
+      g_variant_iter_free (ptr);
+      break;
+
+    case '^':
+      if (str[2] != '&')        /* '^as' */
+        g_strfreev (ptr);
+      else                      /* '^a&s' */
+        g_free (ptr);
+      break;
+
+    case 's': case 'o': case 'g':
+      g_free (ptr);
+      break;
+
+    case '@': case '*': case '?': case 'v':
+      g_variant_unref (ptr);
+      break;
+
+    case '&':
+      break;
+
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+static GVariant *
+g_variant_valist_new_nnp (const gchar **str,
+                          gpointer      ptr)
+{
+  if (**str == '&')
+    (*str)++;
+
+  switch (*(*str)++)
+    {
+    case 'a':
+      {
+        const GVariantType *type;
+        GVariant *value;
+
+        value = g_variant_builder_end (ptr);
+        type = g_variant_get_type (value);
+
+        if G_UNLIKELY (!g_variant_type_is_array (type))
+          g_error ("g_variant_new: expected array GVariantBuilder but "
+                   "the built value has type `%s'",
+                   g_variant_get_type_string (value));
+
+        type = g_variant_type_element (type);
+
+        if G_UNLIKELY (!g_variant_type_is_subtype_of (type, (GVariantType *) *str))
+          g_error ("g_variant_new: expected GVariantBuilder array element "
+                   "type `%s' but the built value has element type `%s'",
+                   g_variant_type_dup_string ((GVariantType *) *str),
+                   g_variant_get_type_string (value) + 1);
+
+        g_variant_type_string_scan (*str, NULL, str);
+
+        return value;
+      }
+
+    case 's':
+      return g_variant_new_string (ptr);
+
+    case 'o':
+      return g_variant_new_object_path (ptr);
+
+    case 'g':
+      return g_variant_new_signature (ptr);
+
+    case '^':
+      {
+        const GVariantType *type;
+        GVariantType *array_type;
+        GVariant **children;
+        GVariant *value;
+        guint length, i;
+        gchar **strv;
+
+        if ((*str)[1] == '&')    /* '^a&s' */
+          (*str) += 2;
+        else                     /* '^as' */
+          (*str)++;
+
+        type = (GVariantType *) (*str)++;
+        array_type = g_variant_type_new_array (type);
+        length = g_strv_length (strv = ptr);
+        children = g_new (GVariant *, length);
+        for (i = 0; i < length; i++)
+          children[i] = g_variant_ref_sink (
+            g_variant_new_from_trusted (type, strv[i], strlen (strv[i]) + 1));
+
+        value = g_variant_new_from_children (array_type, children,
+                                             length, TRUE);
+        g_variant_type_free (array_type);
+
+        return value;
+      }
+
+    case '@':
+      if G_UNLIKELY (!g_variant_is_of_type (ptr, (GVariantType *) *str))
+        g_error ("g_variant_new: expected GVariant of type `%s' but "
+                 "received value has type `%s'",
+                 g_variant_type_dup_string ((GVariantType *) *str),
+                 g_variant_get_type_string (ptr));
+
+      g_variant_type_string_scan (*str, NULL, str);
+
+      return ptr;
+
+    case '*':
+      return ptr;
+
+    case '?':
+      if G_UNLIKELY (!g_variant_type_is_basic (g_variant_get_type (ptr)))
+        g_error ("g_variant_new: format string `?' expects basic-typed "
+                 "GVariant, but received value has type `%s'",
+                 g_variant_get_type_string (ptr));
+
+      return ptr;
+
+    case 'r':
+      if G_UNLIKELY (!g_variant_type_is_tuple (g_variant_get_type (ptr)))
+        g_error ("g_variant_new: format string `r` expects tuple-typed "
+                 "GVariant, but received value has type `%s'",
+                 g_variant_get_type_string (ptr));
+
+      return ptr;
+
+    case 'v':
+      return g_variant_new_variant (ptr);
+
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+static gpointer
+g_variant_valist_get_nnp (const gchar **str,
+                          GVariant     *value)
+{
+  switch (*(*str)++)
+    {
+    case 'a':
+      g_variant_type_string_scan (*str, NULL, str);
+      return g_variant_iter_new (value);
+
+    case '&':
+      (*str)++;
+      return (gchar *) g_variant_get_string (value, NULL);
+
+    case 's': case 'o': case 'g':
+      return g_variant_dup_string (value, NULL);
+
+    case '^':
+      if ((*str)[1] == '&')    /* '^a&s' */
+        {
+          (*str) += 3;
+          return g_variant_get_strv (value, NULL);
+        }
+      else                    /* '^as' */
+        {
+          (*str) += 2;
+          return g_variant_dup_strv (value, NULL);
+        }
+
+    case '@':
+      g_variant_type_string_scan (*str, NULL, str);
+      /* fall through */
+
+    case '*': case '?': case 'r':
+      return g_variant_ref (value);
+
+    case 'v':
+      return g_variant_get_variant (value);
+
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+/* Leaves {{{2 */
+static void
+g_variant_valist_skip_leaf (const gchar **str,
+                            va_list      *app)
+{
+  if (g_variant_format_string_is_nnp (*str))
+    {
+      g_variant_format_string_scan (*str, NULL, str);
+      get_arg (*app, gpointer);
+      return;
+    }
+
+  switch (*(*str)++)
+    {
+    case 'b': case 'y': case 'n': case 'q':
+    case 'i': case 'u': case 'h':
+      get_arg (*app, int);
+      return;
+
+    case 'x': case 't':
+      get_arg (*app, guint64);
+      return;
+
+    case 'd':
+      get_arg (*app, gdouble);
+      return;
+
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+static GVariant *
+g_variant_valist_new_leaf (const gchar **str,
+                           va_list      *app)
+{
+  if (g_variant_format_string_is_nnp (*str))
+    return g_variant_valist_new_nnp (str, get_arg (*app, gpointer));
+
+  switch (*(*str)++)
+    {
+    case 'b':
+      return g_variant_new_boolean (get_arg (*app, gboolean));
+
+    case 'y':
+      return g_variant_new_byte (get_arg (*app, guint));
+
+    case 'n':
+      return g_variant_new_int16 (get_arg (*app, gint));
+
+    case 'q':
+      return g_variant_new_uint16 (get_arg (*app, guint));
+
+    case 'i':
+      return g_variant_new_int32 (get_arg (*app, gint));
+
+    case 'u':
+      return g_variant_new_uint32 (get_arg (*app, guint));
+
+    case 'x':
+      return g_variant_new_int64 (get_arg (*app, gint64));
+
+    case 't':
+      return g_variant_new_uint64 (get_arg (*app, guint64));
+
+    case 'h':
+      return g_variant_new_handle (get_arg (*app, gint));
+
+    case 'd':
+      return g_variant_new_double (get_arg (*app, gdouble));
+
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+static void
+g_variant_valist_get_leaf (const gchar **str,
+                           GVariant     *value,
+                           gboolean      free,
+                           va_list      *app)
+{
+  gpointer ptr = get_arg (*app, gpointer);
+
+  if (ptr == NULL)
+    {
+      g_variant_format_string_scan (*str, NULL, str);
+      return;
+    }
+
+  if (g_variant_format_string_is_nnp (*str))
+    {
+      gpointer *nnp = (gpointer *) ptr;
+
+      if (free && *nnp != NULL)
+        g_variant_valist_free_nnp (*str, *nnp);
+
+      *nnp = NULL;
+
+      if (value != NULL)
+        *nnp = g_variant_valist_get_nnp (str, value);
+      else
+        g_variant_format_string_scan (*str, NULL, str);
+
+      return;
+    }
+
+  if (value != NULL)
+    {
+      switch (*(*str)++)
+        {
+        case 'b':
+          *(gboolean *) ptr = g_variant_get_boolean (value);
+          return;
+
+        case 'y':
+          *(guchar *) ptr = g_variant_get_byte (value);
+          return;
+
+        case 'n':
+          *(gint16 *) ptr = g_variant_get_int16 (value);
+          return;
+
+        case 'q':
+          *(guint16 *) ptr = g_variant_get_uint16 (value);
+          return;
+
+        case 'i':
+          *(gint32 *) ptr = g_variant_get_int32 (value);
+          return;
+
+        case 'u':
+          *(guint32 *) ptr = g_variant_get_uint32 (value);
+          return;
+
+        case 'x':
+          *(gint64 *) ptr = g_variant_get_int64 (value);
+          return;
+
+        case 't':
+          *(guint64 *) ptr = g_variant_get_uint64 (value);
+          return;
+
+        case 'h':
+          *(gint32 *) ptr = g_variant_get_handle (value);
+          return;
+
+        case 'd':
+          *(gdouble *) ptr = g_variant_get_double (value);
+          return;
+        }
+    }
+  else
+    {
+      switch (*(*str)++)
+        {
+        case 'y':
+          *(guchar *) ptr = 0;
+          return;
+
+        case 'n': case 'q':
+          *(guint16 *) ptr = 0;
+          return;
+
+        case 'i': case 'u': case 'h': case 'b':
+          g_assert (sizeof (gboolean) == sizeof (guint32));
+          *(guint32 *) ptr = 0;
+          return;
+
+        case 'x': case 't': case 'd':
+          g_assert (sizeof (gdouble) == sizeof (guint64));
+          *(guint64 *) ptr = 0;
+          return;
+        }
+    }
+
+  g_assert_not_reached ();
+}
+
+/* Generic (recursive) {{{2 */
+static void
+g_variant_valist_skip (const gchar **str,
+                       va_list      *app)
+{
+  if (g_variant_format_string_is_leaf (*str))
+    g_variant_valist_skip_leaf (str, app);
+
+  else if (**str == 'm') /* maybe */
+    {
+      (*str)++;
+
+      if (!g_variant_format_string_is_nnp (*str))
+        get_arg (*app, gboolean);
+
+      g_variant_valist_skip (str, app);
+    }
+  else /* tuple, dictionary entry */
+    {
+      g_assert (**str == '(' || **str == '{');
+      (*str)++;
+      while (**str != ')' && **str != '}')
+        g_variant_valist_skip (str, app);
+      (*str)++;
+    }
+}
+
+static GVariant *
+g_variant_valist_new (const gchar **str,
+                      va_list      *app)
+{
+  if (g_variant_format_string_is_leaf (*str))
+    return g_variant_valist_new_leaf (str, app);
+
+  if (**str == 'm') /* maybe */
+    {
+      GVariantType *type = NULL;
+      GVariant *value = NULL;
+
+      (*str)++;
+
+      if (g_variant_format_string_is_nnp (*str))
+        {
+          gpointer nnp = get_arg (*app, gpointer);
+
+          if (nnp != NULL)
+            value = g_variant_valist_new_nnp (str, nnp);
+          else
+            type = g_variant_format_string_scan_type (*str, NULL, str);
+        }
+      else
+        {
+          gboolean just = get_arg (*app, gboolean);
+
+          if (just)
+            value = g_variant_valist_new (str, app);
+          else
+            {
+              type = g_variant_format_string_scan_type (*str, NULL, NULL);
+              g_variant_valist_skip (str, app);
+            }
+        }
+
+      value = g_variant_new_maybe (type, value);
+
+      if (type != NULL)
+        g_variant_type_free (type);
+
+      return value;
+    }
+  else /* tuple, dictionary entry */
+    {
+      GVariantBuilder b;
+
+      if (**str == '(')
+        g_variant_builder_init (&b, G_VARIANT_TYPE_TUPLE);
+      else
+        {
+          g_assert (**str == '{');
+          g_variant_builder_init (&b, G_VARIANT_TYPE_DICT_ENTRY);
+        }
+
+      (*str)++; /* '(' */
+      while (**str != ')' && **str != '}')
+        g_variant_builder_add_value (&b, g_variant_valist_new (str, app));
+      (*str)++; /* ')' */
+
+      return g_variant_builder_end (&b);
+    }
+}
+
+static void
+g_variant_valist_get (const gchar **str,
+                      GVariant     *value,
+                      gboolean      free,
+                      va_list      *app)
+{
+  if (g_variant_format_string_is_leaf (*str))
+    g_variant_valist_get_leaf (str, value, free, app);
+
+  else if (**str == 'm')
+    {
+      (*str)++;
+
+      if (value != NULL)
+        value = g_variant_get_maybe (value);
+
+      if (!g_variant_format_string_is_nnp (*str))
+        {
+          gboolean *ptr = get_arg (*app, gboolean *);
+
+          if (ptr != NULL)
+            *ptr = value != NULL;
+        }
+
+      g_variant_valist_get (str, value, free, app);
+
+      if (value != NULL)
+        g_variant_unref (value);
+    }
+
+  else /* tuple, dictionary entry */
+    {
+      GVariantIter iter;
+
+      g_assert (**str == '(' || **str == '{');
+      g_variant_iter_init (&iter, value);
+
+      (*str)++;
+      while (**str != ')' && **str != '}')
+        {
+          value = g_variant_iter_next_value (&iter);
+          g_variant_valist_get (str, value, free, app);
+          g_variant_unref (value);
+        }
+      (*str)++;
+    }
+}
+
+/* User-facing API {{{2 */
+/**
+ * g_variant_new:
+ * @format_string: a #GVariant format string
+ * @...: arguments, as per @format_string
+ * @returns: a new floating #GVariant instance
+ *
+ * Creates a new #GVariant instance.
+ *
+ * Think of this function as an analogue to g_strdup_printf().
+ *
+ * The type of the created instance and the arguments that are
+ * expected by this function are determined by @format_string.  Format
+ * strings are described in more detail XXX.
+ *
+ * The first character of the format string must not be '*' '?' '@' or
+ * 'r'; in essence, a new #GVariant must always be constructed by this
+ * function (and not merely passed through it unmodified).
+ *
+ * Please note that the syntax of the format string is very likely to
+ * be extended in the future.
+ **/
+GVariant *
+g_variant_new (const gchar *format_string,
+               ...)
+{
+  GVariant *value;
+  va_list ap;
+
+  CHECK_VALID_FORMAT(format_string, TRUE, NULL, NULL);
+
+  g_return_val_if_fail (format_string[0] != '?' && format_string[0] != '@' &&
+                        format_string[0] != '*' && format_string[0] != 'r',
+                        NULL);
+
+  va_start (ap, format_string);
+  value = g_variant_new_va (NULL, format_string, NULL, &ap);
+  va_end (ap);
+
+  return value;
+}
+
+/**
+ * g_variant_new_va:
+ * @must_be_null: %NULL (for future expansion)
+ * @format_string: a string that is prefixed with a format string
+ * @endptr: location to store the end pointer, or %NULL
+ * @app: a pointer to a #va_list
+ * @returns: a new, usually floating, #GVariant
+ *
+ * This function is intended to be used by libraries based on
+ * #GVariant that want to provide g_variant_new()-like functionality
+ * to their users.
+ *
+ * The API is more general than g_variant_new() to allow a wider range
+ * of possible uses.
+
+ * @format_string must still point to a valid format string, but it only
+ * need to be nul-terminated if @endptr is %NULL.  If @endptr is
+ * non-%NULL then it is updated to point to the first character past the
+ * end of the format string.
+ *
+ * @app is a pointer to a #va_list.  The arguments, according to
+ * @format_string, are collected from this #va_list and the list is left
+ * pointing to the argument following the last.
+ *
+ * These two generalisations allow mixing of multiple calls to
+ * g_variant_new_va() and g_variant_get_va() within a single actual
+ * varargs call by the user.
+ *
+ * The return value will be floating if it was a newly created GVariant
+ * instance (for example, if the format string was "(ii)").  In the case
+ * that the format_string was '*', '?', 'r', or a format starting with
+ * '@' then the collected #GVariant pointer will be returned unmodified,
+ * without adding any additional references.
+ *
+ * In order to behave correctly in all cases it is necessary for the
+ * calling function to g_variant_ref_sink() the return result before
+ * returning control to the user that originally provided the pointer.
+ * At this point, the caller will have their own full reference to the
+ * result.  This can also be done by adding the result to a container,
+ * or by passing it to another g_variant_new() call.
+ **/
+GVariant *
+g_variant_new_va (gpointer      must_be_null,
+                  const gchar  *format_string,
+                  const gchar **endptr,
+                  va_list      *app)
+{
+  GVariant *value;
+
+  CHECK_VALID_FORMAT(format_string, endptr == NULL, NULL, NULL);
+
+  g_return_val_if_fail (must_be_null == NULL, NULL);
+  g_return_val_if_fail (app != NULL, NULL);
+
+  value = g_variant_valist_new (&format_string, app);
+
+  if (endptr != NULL)
+    *endptr = format_string;
+
+  return value;
+}
+
+/**
+ * g_variant_get:
+ * @value: a #GVariant instance
+ * @format_string: a #GVariant format string
+ * @...: arguments, as per @format_string
+ *
+ * Deconstructs a #GVariant instance.
+ *
+ * Think of this function as an analogue to scanf().
+ *
+ * The arguments that are expected by this function are entirely
+ * determined by @format_string.  @format_string also restricts the
+ * permissible types of @value.  It is an error to give a value with
+ * an incompatible type.
+ *
+ * Please note that the syntax of the format string is very likely to
+ * be extended in the future.
+ **/
+void
+g_variant_get (GVariant    *value,
+               const gchar *format_string,
+               ...)
+{
+  va_list ap;
+
+  CHECK_VALID_FORMAT(format_string, TRUE, value,);
+
+  /* if any direct-pointer-access formats are in use, flatten first */
+  if (strchr (format_string, '&'))
+    g_variant_get_data (value);
+
+  va_start (ap, format_string);
+  g_variant_get_va (value, NULL, format_string, NULL, &ap);
+  va_end (ap);
+}
+
+/**
+ * g_variant_get_va:
+ * @value: a #GVariant
+ * @must_be_null: %NULL (for future expansion)
+ * @format_string: a string that is prefixed with a format string
+ * @endptr: location to store the end pointer, or %NULL
+ * @app: a pointer to a #va_list
+ *
+ * This function is intended to be used by libraries based on #GVariant
+ * that want to provide g_variant_get()-like functionality to their
+ * users.
+ *
+ * The API is more general than g_variant_get() to allow a wider range
+ * of possible uses.
+ *
+ * @format_string must still point to a valid format string, but it only
+ * need to be nul-terminated if @endptr is %NULL.  If @endptr is
+ * non-%NULL then it is updated to point to the first character past the
+ * end of the format string.
+ *
+ * @app is a pointer to a #va_list.  The arguments, according to
+ * @format_string, are collected from this #va_list and the list is left
+ * pointing to the argument following the last.
+ *
+ * These two generalisations allow mixing of multiple calls to
+ * g_variant_new_va() and g_variant_get_va() within a single actual
+ * varargs call by the user.
+ **/
+void
+g_variant_get_va (GVariant     *value,
+                  gpointer      must_be_null,
+                  const gchar  *format_string,
+                  const gchar **endptr,
+                  va_list      *app)
+{
+  g_return_if_fail (must_be_null == NULL);
+  g_return_if_fail (value != NULL);
+  g_return_if_fail (app != NULL);
+
+  CHECK_VALID_FORMAT(format_string, endptr == NULL, value,);
+
+  /* if any direct-pointer-access formats are in use, flatten first */
+  if (strchr (format_string, '&'))
+    g_variant_get_data (value);
+
+  g_variant_valist_get (&format_string, value, FALSE, app);
+
+  if (endptr != NULL)
+    *endptr = format_string;
+}
+
+/* Varargs-enabled Utility Functions {{{1 */
+
+/**
+ * g_variant_builder_add:
+ * @builder: a #GVariantBuilder
+ * @format_string: a #GVariant varargs format string
+ * @...: arguments, as per @format_string
+ *
+ * Adds to a #GVariantBuilder.
+ *
+ * This call is a convenience wrapper that is exactly equivalent to
+ * calling g_variant_new() followed by g_variant_builder_add_value().
+ *
+ * This function might be used as follows:
+ *
+ * <programlisting>
+ * GVariant *
+ * make_pointless_dictionary (void)
+ * {
+ *   GVariantBuilder *builder;
+ *   int i;
+ *
+ *   builder = g_variant_builder_new (G_VARIANT_TYPE_CLASS_ARRAY,
+ *                                    NULL);
+ *   for (i = 0; i < 16; i++)
+ *     {
+ *       gchar buf[3];
+ *
+ *       sprintf (buf, "%d", i);
+ *       g_variant_builder_add (builder, "{is}", i, buf);
+ *     }
+ *
+ *   return g_variant_builder_end (builder);
+ * }
+ * </programlisting>
+ **/
+void
+g_variant_builder_add (GVariantBuilder *builder,
+                       const gchar     *format_string,
+                       ...)
+{
+  GVariant *variant;
+  va_list ap;
+
+  va_start (ap, format_string);
+  variant = g_variant_new_va (NULL, format_string, NULL, &ap);
+  va_end (ap);
+
+  g_variant_builder_add_value (builder, variant);
+}
+
+/**
+ * g_variant_get_child:
+ * @value: a container #GVariant
+ * @index: the index of the child to deconstruct
+ * @format_string: a #GVariant format string
+ * @...: arguments, as per @format_string
+ *
+ * Reads a child item out of a container #GVariant instance and
+ * deconstructs it according to @format_string.  This call is
+ * essentially a combination of g_variant_get_child_value() and
+ * g_variant_get().
+ **/
+void
+g_variant_get_child (GVariant    *value,
+                     gsize        index_,
+                     const gchar *format_string,
+                     ...)
+{
+  GVariant *child;
+  va_list ap;
+
+  child = g_variant_get_child_value (value, index_);
+
+  CHECK_VALID_FORMAT(format_string, TRUE, child,);
+
+  va_start (ap, format_string);
+  g_variant_get_va (child, NULL, format_string, NULL, &ap);
+  va_end (ap);
+
+  g_variant_unref (child);
+}
+
+/**
+ * g_variant_iter_next:
+ * @iter: a #GVariantIter
+ * @format_string: a GVariant format string
+ * @...: the arguments to unpack the value into
+ * @returns: %TRUE if a value was unpacked, or %FALSE if there as no
+ *           value
+ *
+ * Gets the next item in the container and unpacks it into the variable
+ * argument list according to @format_string, returning %TRUE.
+ *
+ * If no more items remain then %FALSE is returned.
+ *
+ * All of the pointers given on the variable arguments list of this
+ * function are assumed to point at uninitialised memory.  It is the
+ * responsibility of the caller to free all of the values returned by
+ * the unpacking process.
+ *
+ * <example>
+ *  <title>Memory management with g_variant_iter_next()</title>
+ *  <programlisting>
+ *   /<!-- -->* Iterates a dictionary of type 'a{sv}' *<!-- -->/
+ *   void
+ *   iterate_dictionary (GVariant *dictionary)
+ *   {
+ *     GVariantIter iter;
+ *     GVariant *value;
+ *     gchar *key;
+ *
+ *     g_variant_iter_init (&iter, dictionary);
+ *     while (g_variant_iter_next (&iter, "{sv}", &key, &value))
+ *       {
+ *         g_print ("Item '%s' has type '%s'\n", key,
+ *                  g_variant_get_type_string (value));
+ *
+ *         /<!-- -->* must free data for ourselves *<!-- -->/
+ *         g_variant_unref (value);
+ *         g_free (key);
+ *       }
+ *   }
+ *  </programlisting>
+ * </example>
+ *
+ * For a solution that is likely to be more convenient to C programmers
+ * when dealing with loops, see g_variant_iter_loop().
+ *
+ * Since: 2.24
+ **/
+gboolean
+g_variant_iter_next (GVariantIter *iter,
+                     const gchar  *format_string,
+                     ...)
+{
+  GVariant *value;
+
+  value = g_variant_iter_next_value (iter);
+
+  CHECK_VALID_FORMAT(format_string, TRUE, value, FALSE);
+
+  if (value != NULL)
+    {
+      va_list ap;
+
+      va_start (ap, format_string);
+      g_variant_valist_get (&format_string, value, FALSE, &ap);
+      va_end (ap);
+
+      g_variant_unref (value);
+    }
+
+  return value != NULL;
+}
+
+/**
+ * g_variant_iter_loop:
+ * @iter: a #GVariantIter
+ * @format_string: a GVariant format string
+ * @...: the arguments to unpack the value into
+ * @returns: %TRUE if a value was unpacked, or %FALSE if there as no
+ *           value
+ *
+ * Gets the next item in the container and unpacks it into the variable
+ * argument list according to @format_string, returning %TRUE.
+ *
+ * If no more items remain then %FALSE is returned.
+ *
+ * On the first call to this function, the pointers appearing on the
+ * variable argument list are assumed to point at uninitialised memory.
+ * On the second and later calls, it is assumed that the same pointers
+ * will be given and that they will point to the memory as set by the
+ * previous call to this function.  This allows the previous values to
+ * be freed, as appropriate.
+ *
+ * This function is intended to be used with a while loop as
+ * demonstrated in the following example.  This function can only be
+ * used when iterating over an array.  It is only valid to call this
+ * function with a string constant for the format string and the same
+ * string constant must be used each time.  Mixing calls to this
+ * function and g_variant_iter_next() or g_variant_iter_next_value() on
+ * the same iterator is not recommended.
+ *
+ * <example>
+ *  <title>Memory management with g_variant_iter_loop()</title>
+ *  <programlisting>
+ *   /<!-- -->* Iterates a dictionary of type 'a{sv}' *<!-- -->/
+ *   void
+ *   iterate_dictionary (GVariant *dictionary)
+ *   {
+ *     GVariantIter iter;
+ *     GVariant *value;
+ *     gchar *key;
+ *
+ *     g_variant_iter_init (&iter, dictionary);
+ *     while (g_variant_iter_loop (&iter, "{sv}", &key, &value))
+ *       {
+ *         g_print ("Item '%s' has type '%s'\n", key,
+ *                  g_variant_get_type_string (value));
+ *
+ *         /<!-- -->* no need to free 'key' and 'value' here *<!-- -->/
+ *       }
+ *   }
+ *  </programlisting>
+ * </example>
+ *
+ * If you want a slightly less magical alternative that requires more
+ * typing, see g_variant_iter_next().
+ *
+ * Since: 2.24
+ **/
+gboolean
+g_variant_iter_loop (GVariantIter *iter,
+                     const gchar  *format_string,
+                     ...)
+{
+  gboolean first_time = GVSI(iter)->loop_format == NULL;
+  GVariant *value;
+  va_list ap;
+
+  g_return_val_if_fail (first_time ||
+                        format_string == GVSI(iter)->loop_format,
+                        FALSE);
+
+  if (first_time)
+    {
+      TYPE_CHECK (GVSI(iter)->value, G_VARIANT_TYPE_ARRAY, FALSE);
+      GVSI(iter)->loop_format = format_string;
+
+      if (strchr (format_string, '&'))
+        g_variant_get_data (GVSI(iter)->value);
+    }
+
+  value = g_variant_iter_next_value (iter);
+
+  va_start (ap, format_string);
+  g_variant_valist_get (&format_string, value, !first_time, &ap);
+  va_end (ap);
+
+  if (value != NULL)
+    g_variant_unref (value);
+
+  return value != NULL;
+}
+
 /* Epilogue {{{1 */
 #define __G_VARIANT_C__
 #include "galiasdef.c"
diff --git a/glib/gvariant.h b/glib/gvariant.h
index d8ea126..be8dd12 100644
--- a/glib/gvariant.h
+++ b/glib/gvariant.h
@@ -114,6 +114,10 @@ GVariant *                      g_variant_new_dict_entry                (GVarian
 
 GVariant *                      g_variant_get_maybe                     (GVariant             *value);
 gsize                           g_variant_n_children                    (GVariant             *value);
+void                            g_variant_get_child                     (GVariant             *value,
+                                                                         gsize                 index_,
+                                                                         const gchar          *format_string,
+                                                                         ...);
 GVariant *                      g_variant_get_child_value               (GVariant             *value,
                                                                          gsize                 index_);
 gconstpointer                   g_variant_get_fixed_array               (GVariant             *value,
@@ -178,6 +182,30 @@ void                            g_variant_builder_add                   (GVarian
                                                                          const gchar          *format_string,
                                                                          ...);
 
+gboolean                        g_variant_format_string_scan            (const gchar          *string,
+                                                                         const gchar          *limit,
+                                                                         const gchar         **endptr);
+
+GVariantType *                  g_variant_format_string_scan_type       (const gchar          *string,
+                                                                         const gchar          *limit,
+                                                                         const gchar         **endptr);
+
+GVariant *                      g_variant_new                           (const gchar          *format_string,
+                                                                         ...);
+void                            g_variant_get                           (GVariant             *value,
+                                                                         const gchar          *format_string,
+                                                                         ...);
+GVariant *                      g_variant_new_va                        (gpointer              must_be_null,
+                                                                         const gchar          *format_string,
+                                                                         const gchar         **endptr,
+                                                                         va_list              *app);
+void                            g_variant_get_va                        (GVariant             *value,
+                                                                         gpointer              must_be_null,
+                                                                         const gchar          *format_string,
+                                                                         const gchar         **endptr,
+                                                                         va_list              *app);
+
+
 G_END_DECLS
 
 #endif /* __G_VARIANT_H__ */
diff --git a/glib/tests/gvariant.c b/glib/tests/gvariant.c
index ea01500..8588ebe 100644
--- a/glib/tests/gvariant.c
+++ b/glib/tests/gvariant.c
@@ -12,6 +12,7 @@
  */
 
 #include <string.h>
+#include <stdlib.h>
 #include <glib.h>
 
 #define BASIC "bynqiuxthdsog?"
@@ -2530,7 +2531,8 @@ tree_instance_check_gvariant (TreeInstance *tree,
 
 static void
 tree_instance_build_gvariant (TreeInstance    *tree,
-                              GVariantBuilder *builder)
+                              GVariantBuilder *builder,
+                              gboolean         guess_ok)
 {
   const GVariantType *type;
 
@@ -2540,10 +2542,28 @@ tree_instance_build_gvariant (TreeInstance    *tree,
     {
       gsize i;
 
+      /* force GVariantBuilder to guess the type half the time */
+      if (guess_ok && randomly (0.5))
+        {
+          if (g_variant_type_is_array (type) && tree->n_children)
+            type = G_VARIANT_TYPE_ARRAY;
+
+          if (g_variant_type_is_maybe (type) && tree->n_children)
+            type = G_VARIANT_TYPE_MAYBE;
+
+          if (g_variant_type_is_tuple (type))
+            type = G_VARIANT_TYPE_TUPLE;
+
+          if (g_variant_type_is_dict_entry (type))
+            type = G_VARIANT_TYPE_DICT_ENTRY;
+        }
+      else
+        guess_ok = FALSE;
+
       g_variant_builder_open (builder, type);
 
       for (i = 0; i < tree->n_children; i++)
-        tree_instance_build_gvariant (tree->children[i], builder);
+        tree_instance_build_gvariant (tree->children[i], builder, guess_ok);
 
       g_variant_builder_close (builder);
     }
@@ -2626,7 +2646,7 @@ test_container (void)
       gchar *s3;
 
       g_variant_builder_init (&builder, G_VARIANT_TYPE_VARIANT);
-      tree_instance_build_gvariant (tree, &builder);
+      tree_instance_build_gvariant (tree, &builder, TRUE);
       built = g_variant_builder_end (&builder);
       g_variant_ref_sink (built);
       g_variant_get_data (built);
@@ -2661,6 +2681,638 @@ test_containers (void)
     }
 }
 
+static void
+test_format_strings (void)
+{
+  GVariantType *type;
+  const gchar *end;
+
+  g_assert (g_variant_format_string_scan ("i", NULL, &end) && *end == '\0');
+  g_assert (g_variant_format_string_scan ("@i", NULL, &end) && *end == '\0');
+  g_assert (g_variant_format_string_scan ("@ii", NULL, &end) && *end == 'i');
+  g_assert (g_variant_format_string_scan ("^a&s", NULL, &end) && *end == '\0');
+  g_assert (g_variant_format_string_scan ("(^as)", NULL, &end) &&
+            *end == '\0');
+  g_assert (!g_variant_format_string_scan ("(^s)", NULL, &end));
+  g_assert (!g_variant_format_string_scan ("(^a)", NULL, &end));
+  g_assert (!g_variant_format_string_scan ("(z)", NULL, &end));
+  g_assert (!g_variant_format_string_scan ("az", NULL, &end));
+  g_assert (!g_variant_format_string_scan ("{**}", NULL, &end));
+  g_assert (!g_variant_format_string_scan ("{ **}", NULL, &end));
+  g_assert (g_variant_format_string_scan ("{ y*}", NULL, &end) &&
+            *end == '\0');
+  g_assert (g_variant_format_string_scan ("{yv}", NULL, &end) &&
+            *end == '\0');
+  g_assert (!g_variant_format_string_scan ("{vv}", NULL, &end));
+  g_assert (!g_variant_format_string_scan ("{y}", NULL, &end));
+  g_assert (!g_variant_format_string_scan ("{yyy}", NULL, &end));
+  g_assert (!g_variant_format_string_scan ("{ya}", NULL, &end));
+  g_assert (g_variant_format_string_scan ("&s", NULL, &end) && *end == '\0');
+  g_assert (!g_variant_format_string_scan ("&as", NULL, &end));
+  g_assert (!g_variant_format_string_scan ("@z", NULL, &end));
+  g_assert (!g_variant_format_string_scan ("az", NULL, &end));
+  g_assert (!g_variant_format_string_scan ("a&s", NULL, &end));
+
+  type = g_variant_format_string_scan_type ("mm(@xy^a&s*? ?)", NULL, &end);
+  g_assert (type && *end == '\0');
+  g_assert (g_variant_type_equal (type, G_VARIANT_TYPE ("mm(xyas*?\?)")));
+  g_variant_type_free (type);
+
+  type = g_variant_format_string_scan_type ("mm(@xy^a&*? ?)", NULL, NULL);
+  g_assert (type == NULL);
+}
+
+static void
+exit_on_abort (int signal)
+{
+  exit (signal);
+}
+
+static gboolean
+do_failed_test (const gchar *pattern)
+{
+  if (g_test_trap_fork (1000000, G_TEST_TRAP_SILENCE_STDERR))
+    {
+      signal (SIGABRT, exit_on_abort);
+      return TRUE;
+    }
+
+  g_test_trap_assert_failed ();
+  g_test_trap_assert_stderr (pattern);
+
+  return FALSE;
+}
+
+static void
+test_invalid_varargs (void)
+{
+  if (do_failed_test ("*not a valid GVariant format string*"))
+    {
+      g_variant_new ("z");
+      abort ();
+    }
+
+  if (do_failed_test ("*valid GVariant format string as a prefix*"))
+    {
+      const gchar *end;
+
+      g_variant_new_va (NULL, "z", &end, NULL);
+      abort ();
+    }
+
+  if (do_failed_test ("*type of `q' but * has a type of `y'*"))
+    {
+      g_variant_get (g_variant_new ("y", 'a'), "q");
+      abort ();
+    }
+
+  if (do_failed_test ("*must_be_null*"))
+    {
+      int q;
+
+      g_variant_new_va (&q, "u", NULL, NULL);
+      abort ();
+    }
+
+  if (do_failed_test ("*must_be_null*"))
+    {
+      int q;
+
+      g_variant_get_va (g_variant_new ("u", 1), &q, "u", NULL, NULL);
+      abort ();
+    }
+}
+
+static void
+check_and_free (GVariant    *value,
+                const gchar *str)
+{
+  gchar *valstr = g_variant_print (value, FALSE);
+  g_assert_cmpstr (str, ==, valstr);
+  g_variant_unref (value);
+  g_free (valstr);
+}
+
+static void
+test_varargs (void)
+{
+  {
+    GVariantBuilder array;
+
+    g_variant_builder_init (&array, G_VARIANT_TYPE_ARRAY);
+    g_variant_builder_add (&array, "{sv}", "size",
+                           g_variant_new ("(ii)", 800, 600));
+    g_variant_builder_add (&array, "{sv}", "title",
+                           g_variant_new_string ("Test case"));
+    g_variant_builder_add_value (&array,
+      g_variant_new_dict_entry (g_variant_new_string ("temperature"),
+                                g_variant_new_variant (
+                                  g_variant_new_double (37.5))));
+    check_and_free (g_variant_new ("(ma{sv}m(a{sv})ma{sv}ii)",
+                                   NULL, FALSE, NULL, &array, 7777, 8888),
+                    "(Nothing, Nothing, {'size': <(800, 600)>, "
+                                        "'title': <'Test case'>, "
+                                        "'temperature': <37.5>}, "
+                     "7777, 8888)");
+
+    check_and_free (g_variant_new ("(imimimmimmimmi)",
+                                   123,
+                                   FALSE, 321,
+                                   TRUE, 123,
+                                   FALSE, TRUE, 321,
+                                   TRUE, FALSE, 321,
+                                   TRUE, TRUE, 123),
+                    "(123, Nothing, 123, Nothing, Just Nothing, 123)");
+
+    check_and_free (g_variant_new ("(ybnixd)",
+                                   'a', 1, 22, 33, (guint64) 44, 5.5),
+                    "(0x61, true, 22, 33, 44, 5.5)");
+
+    check_and_free (g_variant_new ("(@y?*rv)",
+                                   g_variant_new ("y", 'a'),
+                                   g_variant_new ("y", 'b'),
+                                   g_variant_new ("y", 'c'),
+                                   g_variant_new ("(y)", 'd'),
+                                   g_variant_new ("y", 'e')),
+                    "(0x61, 0x62, 0x63, (0x64,), <byte 0x65>)");
+  }
+
+  {
+    GVariantBuilder array;
+    GVariantIter iter;
+    GVariant *value;
+    gchar *number;
+    gboolean just;
+    gint i, val;
+
+    g_variant_builder_init (&array, G_VARIANT_TYPE_ARRAY);
+    for (i = 0; i < 100; i++)
+      {
+        number = g_strdup_printf ("%d", i);
+        g_variant_builder_add (&array, "s", number);
+        g_free (number);
+      }
+
+    value = g_variant_builder_end (&array);
+    g_variant_iter_init (&iter, value);
+
+    i = 0;
+    while (g_variant_iter_loop (&iter, "s", &number))
+      {
+        gchar *check = g_strdup_printf ("%d", i++);
+        g_assert_cmpstr (number, ==, check);
+        g_free (check);
+      }
+    g_assert (number == NULL);
+    g_assert (i == 100);
+
+    g_variant_unref (value);
+
+    g_variant_builder_init (&array, G_VARIANT_TYPE_ARRAY);
+    for (i = 0; i < 100; i++)
+      g_variant_builder_add (&array, "mi", i % 2 == 0, i);
+    value = g_variant_builder_end (&array);
+
+    i = 0;
+    g_variant_iter_init (&iter, value);
+    while (g_variant_iter_loop (&iter, "mi", NULL, &val))
+      g_assert (val == i++ || val == 0);
+    g_assert (i == 100);
+
+    i = 0;
+    g_variant_iter_init (&iter, value);
+    while (g_variant_iter_loop (&iter, "mi", &just, &val))
+      {
+        gint this = i++;
+
+        if (this % 2 == 0)
+          {
+            g_assert (just);
+            g_assert (val == this);
+          }
+        else
+          {
+            g_assert (!just);
+            g_assert (val == 0);
+          }
+      }
+    g_assert (i == 100);
+
+    g_variant_unref (value);
+  }
+
+  {
+    const gchar *strvector[] = {"/hello", "/world", NULL};
+    const gchar *test_strs[] = {"/foo", "/bar", "/baz" };
+    GVariantBuilder builder;
+    GVariantIter *array;
+    GVariantIter tuple;
+    const gchar **strv;
+    gchar **my_strv;
+    GVariant *value;
+    gchar *str;
+    gint i;
+
+    g_variant_builder_init (&builder, G_VARIANT_TYPE ("ao"));
+    g_variant_builder_add (&builder, "o", "/foo");
+    g_variant_builder_add (&builder, "o", "/bar");
+    g_variant_builder_add (&builder, "o", "/baz");
+    value = g_variant_new("(ao^ao^a&o)", &builder, strvector, strvector);
+    g_variant_iter_init (&tuple, value);
+    g_variant_iter_next (&tuple, "ao", &array);
+
+    i = 0;
+    while (g_variant_iter_loop (array, "o", &str))
+      g_assert_cmpstr (str, ==, test_strs[i++]);
+    g_assert (i == 3);
+
+    g_variant_iter_free (array);
+
+    /* start over */
+    g_variant_iter_init (&tuple, value);
+    g_variant_iter_next (&tuple, "ao", &array);
+
+    i = 0;
+    while (g_variant_iter_loop (array, "&o", &str))
+      g_assert_cmpstr (str, ==, test_strs[i++]);
+    g_assert (i == 3);
+
+    g_variant_iter_free (array);
+
+    g_variant_iter_next (&tuple, "^a&o", &strv);
+    g_variant_iter_next (&tuple, "^ao", &my_strv);
+
+    g_assert_cmpstr (strv[0], ==, "/hello");
+    g_assert_cmpstr (strv[1], ==, "/world");
+    g_assert (strv[2] == NULL);
+    g_assert_cmpstr (my_strv[0], ==, "/hello");
+    g_assert_cmpstr (my_strv[1], ==, "/world");
+    g_assert (my_strv[2] == NULL);
+
+    g_variant_unref (value);
+    g_strfreev (my_strv);
+    g_free (strv);
+  }
+
+  {
+    const gchar *strvector[] = { "i", "ii", "iii", "iv", "v", "vi", NULL };
+    GVariantBuilder builder;
+    GVariantIter iter;
+    GVariantIter *i2;
+    GVariant *value;
+    GVariant *sub;
+    gchar **strv;
+    gint i;
+
+    g_variant_builder_init (&builder, G_VARIANT_TYPE ("aag"));
+    g_variant_builder_open (&builder, G_VARIANT_TYPE ("ag"));
+    for (i = 0; i < 6; i++)
+      if (i & 1)
+        g_variant_builder_add (&builder, "g", strvector[i]);
+      else
+        g_variant_builder_add (&builder, "&g", strvector[i]);
+    g_variant_builder_close (&builder);
+    g_variant_builder_add (&builder, "^ag", strvector);
+    g_variant_builder_add (&builder, "^ag", strvector);
+    value = g_variant_new ("aag", &builder);
+
+    g_variant_iter_init (&iter, value);
+    while (g_variant_iter_loop (&iter, "^ag", &strv))
+      for (i = 0; i < 6; i++)
+        g_assert_cmpstr (strv[i], ==, strvector[i]);
+
+    g_variant_iter_init (&iter, value);
+    while (g_variant_iter_loop (&iter, "^a&g", &strv))
+      for (i = 0; i < 6; i++)
+        g_assert_cmpstr (strv[i], ==, strvector[i]);
+
+    g_variant_iter_init (&iter, value);
+    while (g_variant_iter_loop (&iter, "ag", &i2))
+      {
+        gchar *str;
+
+        i = 0;
+        while (g_variant_iter_loop (i2, "g", &str))
+          g_assert_cmpstr (str, ==, strvector[i++]);
+        g_assert (i == 6);
+      }
+
+    g_variant_iter_init (&iter, value);
+    while (g_variant_iter_loop (&iter, "@ag", &sub))
+      {
+        gchar *str = g_variant_print (sub, TRUE);
+        g_assert_cmpstr (str, ==,
+                         "[signature 'i', 'ii', 'iii', 'iv', 'v', 'vi']");
+        g_free (str);
+      }
+
+    g_variant_iter_init (&iter, value);
+    while (g_variant_iter_loop (&iter, "*", &sub))
+      {
+        gchar *str = g_variant_print (sub, TRUE);
+        g_assert_cmpstr (str, ==,
+                         "[signature 'i', 'ii', 'iii', 'iv', 'v', 'vi']");
+        g_free (str);
+      }
+
+    for (i = 0; i < g_variant_n_children (value); i++)
+      {
+        gint j;
+
+        g_variant_get_child (value, i, "*", &sub);
+
+        for (j = 0; j < g_variant_n_children (sub); j++)
+          {
+            const gchar *str = NULL;
+            GVariant *cval;
+
+            g_variant_get_child (sub, j, "&g", &str);
+            g_assert_cmpstr (str, ==, strvector[j]);
+
+            cval = g_variant_get_child_value (sub, j);
+            g_variant_get (cval, "&g", &str);
+            g_assert_cmpstr (str, ==, strvector[j]);
+            g_variant_unref (cval);
+          }
+
+        g_variant_unref (sub);
+      }
+
+    g_variant_unref (value);
+  }
+
+  {
+    gboolean justs[10];
+    GVariant *value;
+
+    GVariant *vval;
+    guchar byteval;
+    gboolean bval;
+    gint16 i16val;
+    guint16 u16val;
+    gint32 i32val;
+    guint32 u32val;
+    gint64 i64val;
+    guint64 u64val;
+    gdouble dval;
+    gint32 hval;
+
+    /* test all 'Nothing' */
+    value = g_variant_new ("(mymbmnmqmimumxmtmhmdmv)",
+                           FALSE, 'a',
+                           FALSE, TRUE,
+                           FALSE, (gint16) 123,
+                           FALSE, (guint16) 123,
+                           FALSE, (gint32) 123,
+                           FALSE, (guint32) 123,
+                           FALSE, (gint64) 123,
+                           FALSE, (guint64) 123,
+                           FALSE, (gint32) -1,
+                           FALSE, (gdouble) 37.5,
+                           NULL);
+
+    /* both NULL */
+    g_variant_get (value, "(mymbmnmqmimumxmtmhmdmv)",
+                   NULL, NULL,
+                   NULL, NULL,
+                   NULL, NULL,
+                   NULL, NULL,
+                   NULL, NULL,
+                   NULL, NULL,
+                   NULL, NULL,
+                   NULL, NULL,
+                   NULL, NULL,
+                   NULL, NULL,
+                   NULL);
+
+    /* NULL values */
+    memset (justs, 1, sizeof justs);
+    g_variant_get (value, "(mymbmnmqmimumxmtmhmdmv)",
+                   &justs[0], NULL,
+                   &justs[1], NULL,
+                   &justs[2], NULL,
+                   &justs[3], NULL,
+                   &justs[4], NULL,
+                   &justs[5], NULL,
+                   &justs[6], NULL,
+                   &justs[7], NULL,
+                   &justs[8], NULL,
+                   &justs[9], NULL,
+                   NULL);
+    g_assert (!(justs[0] || justs[1] || justs[2] || justs[3] || justs[4] ||
+                justs[5] || justs[6] || justs[7] || justs[8] || justs[9]));
+
+    /* both non-NULL */
+    memset (justs, 1, sizeof justs);
+    byteval = i16val = u16val = i32val = u32val = i64val = u64val = hval = 88;
+    vval = (void *) 1;
+    bval = TRUE;
+    dval = 88.88;
+    g_variant_get (value, "(mymbmnmqmimumxmtmhmdmv)",
+                   &justs[0], &byteval,
+                   &justs[1], &bval,
+                   &justs[2], &i16val,
+                   &justs[3], &u16val,
+                   &justs[4], &i32val,
+                   &justs[5], &u32val,
+                   &justs[6], &i64val,
+                   &justs[7], &u64val,
+                   &justs[8], &hval,
+                   &justs[9], &dval,
+                   &vval);
+    g_assert (!(justs[0] || justs[1] || justs[2] || justs[3] || justs[4] ||
+                justs[5] || justs[6] || justs[7] || justs[8] || justs[9]));
+    g_assert (byteval == '\0' && bval == FALSE);
+    g_assert (i16val == 0 && u16val == 0 && i32val == 0 &&
+              u32val == 0 && i64val == 0 && u64val == 0 &&
+              hval == 0 && dval == 0.0);
+    g_assert (vval == NULL);
+
+    /* NULL justs */
+    byteval = i16val = u16val = i32val = u32val = i64val = u64val = hval = 88;
+    vval = (void *) 1;
+    bval = TRUE;
+    dval = 88.88;
+    g_variant_get (value, "(mymbmnmqmimumxmtmhmdmv)",
+                   NULL, &byteval,
+                   NULL, &bval,
+                   NULL, &i16val,
+                   NULL, &u16val,
+                   NULL, &i32val,
+                   NULL, &u32val,
+                   NULL, &i64val,
+                   NULL, &u64val,
+                   NULL, &hval,
+                   NULL, &dval,
+                   &vval);
+    g_assert (byteval == '\0' && bval == FALSE);
+    g_assert (i16val == 0 && u16val == 0 && i32val == 0 &&
+              u32val == 0 && i64val == 0 && u64val == 0 &&
+              hval == 0 && dval == 0.0);
+    g_assert (vval == NULL);
+
+    g_variant_unref (value);
+
+
+    /* test all 'Just' */
+    value = g_variant_new ("(mymbmnmqmimumxmtmhmdmv)",
+                           TRUE, 'a',
+                           TRUE, TRUE,
+                           TRUE, (gint16) 123,
+                           TRUE, (guint16) 123,
+                           TRUE, (gint32) 123,
+                           TRUE, (guint32) 123,
+                           TRUE, (gint64) 123,
+                           TRUE, (guint64) 123,
+                           TRUE, (gint32) -1,
+                           TRUE, (gdouble) 37.5,
+                           g_variant_new ("()"));
+
+    /* both NULL */
+    g_variant_get (value, "(mymbmnmqmimumxmtmhmdmv)",
+                   NULL, NULL,
+                   NULL, NULL,
+                   NULL, NULL,
+                   NULL, NULL,
+                   NULL, NULL,
+                   NULL, NULL,
+                   NULL, NULL,
+                   NULL, NULL,
+                   NULL, NULL,
+                   NULL, NULL,
+                   NULL);
+
+    /* NULL values */
+    memset (justs, 0, sizeof justs);
+    g_variant_get (value, "(mymbmnmqmimumxmtmhmdmv)",
+                   &justs[0], NULL,
+                   &justs[1], NULL,
+                   &justs[2], NULL,
+                   &justs[3], NULL,
+                   &justs[4], NULL,
+                   &justs[5], NULL,
+                   &justs[6], NULL,
+                   &justs[7], NULL,
+                   &justs[8], NULL,
+                   &justs[9], NULL,
+                   NULL);
+    g_assert (justs[0] && justs[1] && justs[2] && justs[3] && justs[4] &&
+              justs[5] && justs[6] && justs[7] && justs[8] && justs[9]);
+
+    /* both non-NULL */
+    memset (justs, 0, sizeof justs);
+    byteval = i16val = u16val = i32val = u32val = i64val = u64val = hval = 88;
+    vval = (void *) 1;
+    bval = FALSE;
+    dval = 88.88;
+    g_variant_get (value, "(mymbmnmqmimumxmtmhmdmv)",
+                   &justs[0], &byteval,
+                   &justs[1], &bval,
+                   &justs[2], &i16val,
+                   &justs[3], &u16val,
+                   &justs[4], &i32val,
+                   &justs[5], &u32val,
+                   &justs[6], &i64val,
+                   &justs[7], &u64val,
+                   &justs[8], &hval,
+                   &justs[9], &dval,
+                   &vval);
+    g_assert (justs[0] && justs[1] && justs[2] && justs[3] && justs[4] &&
+              justs[5] && justs[6] && justs[7] && justs[8] && justs[9]);
+    g_assert (byteval == 'a' && bval == TRUE);
+    g_assert (i16val == 123 && u16val == 123 && i32val == 123 &&
+              u32val == 123 && i64val == 123 && u64val == 123 &&
+              hval == -1 && dval == 37.5);
+    g_assert (g_variant_is_of_type (vval, G_VARIANT_TYPE_UNIT));
+    g_variant_unref (vval);
+
+    /* NULL justs */
+    byteval = i16val = u16val = i32val = u32val = i64val = u64val = hval = 88;
+    vval = (void *) 1;
+    bval = TRUE;
+    dval = 88.88;
+    g_variant_get (value, "(mymbmnmqmimumxmtmhmdmv)",
+                   NULL, &byteval,
+                   NULL, &bval,
+                   NULL, &i16val,
+                   NULL, &u16val,
+                   NULL, &i32val,
+                   NULL, &u32val,
+                   NULL, &i64val,
+                   NULL, &u64val,
+                   NULL, &hval,
+                   NULL, &dval,
+                   &vval);
+    g_assert (byteval == 'a' && bval == TRUE);
+    g_assert (i16val == 123 && u16val == 123 && i32val == 123 &&
+              u32val == 123 && i64val == 123 && u64val == 123 &&
+              hval == -1 && dval == 37.5);
+    g_assert (g_variant_is_of_type (vval, G_VARIANT_TYPE_UNIT));
+    g_variant_unref (vval);
+
+    g_variant_unref (value);
+  }
+}
+
+static void
+hash_get (GVariant    *value,
+          const gchar *format,
+          ...)
+{
+  const gchar *endptr = NULL;
+  gboolean hash;
+  va_list ap;
+
+  hash = g_str_has_suffix (format, "#");
+
+  va_start (ap, format);
+  g_variant_get_va (value, NULL, format, hash ? &endptr : NULL, &ap);
+  va_end (ap);
+
+  if (hash)
+    g_assert (*endptr == '#');
+}
+
+static GVariant *
+hash_new (const gchar *format,
+          ...)
+{
+  const gchar *endptr = NULL;
+  GVariant *value;
+  gboolean hash;
+  va_list ap;
+
+  hash = g_str_has_suffix (format, "#");
+
+  va_start (ap, format);
+  value = g_variant_new_va (NULL, format, hash ? &endptr : NULL, &ap);
+  va_end (ap);
+
+  if (hash)
+    g_assert (*endptr == '#');
+
+  return value;
+}
+
+static void
+test_valist (void)
+{
+  GVariant *value;
+  gint32 x;
+
+  x = 0;
+  value = hash_new ("i", 234);
+  hash_get (value, "i", &x);
+  g_assert (x == 234);
+  g_variant_unref (value);
+
+  x = 0;
+  value = hash_new ("i#", 234);
+  hash_get (value, "i#", &x);
+  g_assert (x == 234);
+  g_variant_unref (value);
+}
+
 int
 main (int argc, char **argv)
 {
@@ -2688,6 +3340,10 @@ main (int argc, char **argv)
     }
 
   g_test_add_func ("/gvariant/containers", test_containers);
+  g_test_add_func ("/gvariant/format-strings", test_format_strings);
+  g_test_add_func ("/gvariant/invalid-varargs", test_invalid_varargs);
+  g_test_add_func ("/gvariant/varargs", test_varargs);
+  g_test_add_func ("/gvariant/valist", test_valist);
 
   return g_test_run ();
 }



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