merge GVariantTypeInfo
authorRyan Lortie <desrt@desrt.ca>
Sun, 31 Jan 2010 01:15:25 +0000 (20:15 -0500)
committerRyan Lortie <desrt@desrt.ca>
Sun, 31 Jan 2010 01:15:25 +0000 (20:15 -0500)
glib/Makefile.am
glib/gvarianttypeinfo.c [new file with mode: 0644]
glib/gvarianttypeinfo.h [new file with mode: 0644]
glib/tests/gvarianttype.c

index 79f0b56..20daa80 100644 (file)
@@ -172,6 +172,8 @@ libglib_2_0_la_SOURCES =    \
        gunicodeprivate.h       \
        gurifuncs.c             \
        gutils.c                \
+       gvarianttypeinfo.h      \
+       gvarianttypeinfo.c      \
        gvarianttype.c          \
        gdebug.h                \
        gprintf.c               \
diff --git a/glib/gvarianttypeinfo.c b/glib/gvarianttypeinfo.c
new file mode 100644 (file)
index 0000000..08439c9
--- /dev/null
@@ -0,0 +1,841 @@
+/*
+ * Copyright © 2008 Ryan Lortie
+ * Copyright © 2010 Codethink Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#include "gvarianttypeinfo.h"
+#include <glib.h>
+
+#include "galias.h"
+
+/* < private >
+ * GVariantTypeInfo:
+ *
+ * This structure contains the necessary information to facilitate the
+ * serialisation and fast deserialisation of a given type of GVariant
+ * value.  A GVariant instance holds a pointer to one of these
+ * structures to provide for efficient operation.
+ *
+ * The GVariantTypeInfo structures for all of the base types, plus the
+ * "variant" type are stored in a read-only static array.
+ *
+ * For container types, a hash table and reference counting is used to
+ * ensure that only one of these structures exists for any given type.
+ * In general, a container GVariantTypeInfo will exist for a given type
+ * only if one or more GVariant instances of that type exist or if
+ * another GVariantTypeInfo has that type as a subtype.  For example, if
+ * a process contains a single GVariant instance with type "(asv)", then
+ * container GVariantTypeInfo structures will exist for "(asv)" and
+ * for "as" (note that "s" and "v" always exist in the static array).
+ *
+ * The trickiest part of GVariantTypeInfo (and in fact, the major reason
+ * for its existance) is the storage of somewhat magical constants that
+ * allow for O(1) lookups of items in tuples.  This is described below.
+ *
+ * 'container_class' is set to 'a' or 'r' if the GVariantTypeInfo is
+ * contained inside of an ArrayInfo or TupleInfo, respectively.  This
+ * allows the storage of the necessary additional information.
+ *
+ * 'fixed_size' is set to the fixed size of the type, if applicable, or
+ * 0 otherwise (since no type has a fixed size of 0).
+ *
+ * 'alignment' is set to one less than the alignment requirement for
+ * this type.  This makes many operations much more convenient.
+ */
+struct _GVariantTypeInfo
+{
+  gsize fixed_size;
+  guchar alignment;
+  guchar container_class;
+};
+
+/* Container types are reference counted.  They also need to have their
+ * type string stored explicitly since it is not merely a single letter.
+ */
+typedef struct
+{
+  GVariantTypeInfo info;
+
+  gchar *type_string;
+  gint ref_count;
+} ContainerInfo;
+
+/* For 'array' and 'maybe' types, we store some extra information on the
+ * end of the GVariantTypeInfo struct -- the element type (ie: "s" for
+ * "as").  The container GVariantTypeInfo structure holds a reference to
+ * the element typeinfo.
+ */
+typedef struct
+{
+  ContainerInfo container;
+
+  GVariantTypeInfo *element;
+} ArrayInfo;
+
+/* For 'tuple' and 'dict entry' types, we store extra information for
+ * each member -- its type and how to find it inside the serialised data
+ * in O(1) time using 4 variables -- 'i', 'a', 'b', and 'c'.  See the
+ * comment on GVariantMemberInfo in gvarianttypeinfo.h.
+ */
+typedef struct
+{
+  ContainerInfo container;
+
+  GVariantMemberInfo *members;
+  gsize n_members;
+} TupleInfo;
+
+
+/* Hard-code the base types in a constant array */
+static const GVariantTypeInfo g_variant_type_info_basic_table[24] = {
+#define fixed_aligned(x)  x, x - 1
+#define unaligned         0, 0
+#define aligned(x)        0, x - 1
+  /* 'b' */ { fixed_aligned(1) },   /* boolean */
+  /* 'c' */ { },
+  /* 'd' */ { fixed_aligned(8) },   /* double */
+  /* 'e' */ { },
+  /* 'f' */ { },
+  /* 'g' */ { unaligned        },   /* signature string */
+  /* 'h' */ { fixed_aligned(4) },   /* file handle (int32) */
+  /* 'i' */ { fixed_aligned(4) },   /* int32 */
+  /* 'j' */ { },
+  /* 'k' */ { },
+  /* 'l' */ { },
+  /* 'm' */ { },
+  /* 'n' */ { fixed_aligned(2) },   /* int16 */
+  /* 'o' */ { unaligned        },   /* object path string */
+  /* 'p' */ { },
+  /* 'q' */ { fixed_aligned(2) },   /* uint16 */
+  /* 'r' */ { },
+  /* 's' */ { unaligned        },   /* string */
+  /* 't' */ { fixed_aligned(8) },   /* uint64 */
+  /* 'u' */ { fixed_aligned(4) },   /* uint32 */
+  /* 'v' */ { aligned(8)       },   /* variant */
+  /* 'w' */ { },
+  /* 'x' */ { fixed_aligned(8) },   /* int64 */
+  /* 'y' */ { fixed_aligned(1) },   /* byte */
+#undef fixed_aligned
+#undef unaligned
+#undef aligned
+};
+
+/* We need to have type strings to return for the base types.  We store
+ * those in another array.  Since all base type strings are single
+ * characters this is easy.  By not storing pointers to strings into the
+ * GVariantTypeInfo itself, we save a bunch of relocations.
+ */
+static const char g_variant_type_info_basic_chars[24][2] = {
+  "b", " ", "d", " ", " ", "g", "h", "i", " ", " ", " ", " ",
+  "n", "o", " ", "q", " ", "s", "t", "u", "v", " ", "x", "y"
+};
+
+/* sanity checks to make debugging easier */
+static void
+g_variant_type_info_check (const GVariantTypeInfo *info,
+                           char                    container_class)
+{
+  g_assert (!container_class || info->container_class == container_class);
+
+  /* alignment can only be one of these */
+  g_assert (info->alignment == 0 || info->alignment == 1 ||
+            info->alignment == 3 || info->alignment == 7);
+
+  if (info->container_class)
+    {
+      ContainerInfo *container = (ContainerInfo *) info;
+
+      /* extra checks for containers */
+      g_assert_cmpint (container->ref_count, >, 0);
+      g_assert (container->type_string != NULL);
+    }
+  else
+    {
+      gint index;
+
+      /* if not a container, then ensure that it is a valid member of
+       * the basic types table
+       */
+      index = info - g_variant_type_info_basic_table;
+
+      g_assert (G_N_ELEMENTS (g_variant_type_info_basic_table) == 24);
+      g_assert (G_N_ELEMENTS (g_variant_type_info_basic_chars) == 24);
+      g_assert (0 <= index && index < 24);
+      g_assert (g_variant_type_info_basic_chars[index][0] != ' ');
+    }
+}
+
+/* < private >
+ * g_variant_type_info_get_type_string:
+ * @info: a #GVariantTypeInfo
+ *
+ * Gets the type string for @info.  The string is nul-terminated.
+ */
+const gchar *
+g_variant_type_info_get_type_string (GVariantTypeInfo *info)
+{
+  g_variant_type_info_check (info, 0);
+
+  if (info->container_class)
+    {
+      ContainerInfo *container = (ContainerInfo *) info;
+
+      /* containers have their type string stored inside them */
+      return container->type_string;
+    }
+  else
+    {
+      gint index;
+
+      /* look up the type string in the base type array.  the call to
+       * g_variant_type_info_check() above already ensured validity.
+       */
+      index = info - g_variant_type_info_basic_table;
+
+      return g_variant_type_info_basic_chars[index];
+    }
+}
+
+/* < private >
+ * g_variant_type_info_query:
+ * @info: a #GVariantTypeInfo
+ * @alignment: the location to store the alignment, or %NULL
+ * @fixed_size: the location to store the fixed size, or %NULL
+ *
+ * Queries @info to determine the alignment requirements and fixed size
+ * (if any) of the type.
+ *
+ * @fixed_size, if non-%NULL is set to the fixed size of the type, or 0
+ * to indicate that the type is a variable-sized type.  No type has a
+ * fixed size of 0.
+ *
+ * @alignment, if non-%NULL, is set to one less than the required
+ * alignment of the type.  For example, for a 32bit integer, @alignment
+ * would be set to 3.  This allows you to round an integer up to the
+ * proper alignment by performing the following efficient calculation:
+ *
+ *   offset += ((-offset) & alignment);
+ */
+void
+g_variant_type_info_query (GVariantTypeInfo *info,
+                           guint            *alignment,
+                           gsize            *fixed_size)
+{
+  g_variant_type_info_check (info, 0);
+
+  if (alignment)
+    *alignment = info->alignment;
+
+  if (fixed_size)
+    *fixed_size = info->fixed_size;
+}
+
+/* == array == */
+#define ARRAY_INFO_CLASS 'a'
+static ArrayInfo *
+ARRAY_INFO (GVariantTypeInfo *info)
+{
+  g_variant_type_info_check (info, ARRAY_INFO_CLASS);
+
+  return (ArrayInfo *) info;
+}
+
+static void
+array_info_free (GVariantTypeInfo *info)
+{
+  ArrayInfo *array_info;
+
+  g_assert (info->container_class == ARRAY_INFO_CLASS);
+  array_info = (ArrayInfo *) info;
+
+  g_variant_type_info_unref (array_info->element);
+  g_slice_free (ArrayInfo, array_info);
+}
+
+static ContainerInfo *
+array_info_new (const GVariantType *type)
+{
+  ArrayInfo *info;
+
+  info = g_slice_new (ArrayInfo);
+  info->container.info.container_class = ARRAY_INFO_CLASS;
+
+  info->element = g_variant_type_info_get (g_variant_type_element (type));
+  info->container.info.alignment = info->element->alignment;
+  info->container.info.fixed_size = 0;
+
+  return (ContainerInfo *) info;
+}
+
+/* < private >
+ * g_variant_type_info_element:
+ * @info: a #GVariantTypeInfo for an array or maybe type
+ *
+ * Returns the element type for the array or maybe type.  A reference is
+ * not added, so the caller must add their own.
+ */
+GVariantTypeInfo *
+g_variant_type_info_element (GVariantTypeInfo *info)
+{
+  return ARRAY_INFO (info)->element;
+}
+
+/* < private >
+ * g_variant_type_query_element:
+ * @info: a #GVariantTypeInfo for an array or maybe type
+ * @alignment: the location to store the alignment, or %NULL
+ * @fixed_size: the location to store the fixed size, or %NULL
+ *
+ * Returns the alignment requires and fixed size (if any) for the
+ * element type of the array.  This call is a convenience wrapper around
+ * g_variant_type_info_element() and g_variant_type_info_query().
+ */
+void
+g_variant_type_info_query_element (GVariantTypeInfo *info,
+                                   guint            *alignment,
+                                   gsize            *fixed_size)
+{
+  g_variant_type_info_query (ARRAY_INFO (info)->element,
+                             alignment, fixed_size);
+}
+
+/* == tuple == */
+#define TUPLE_INFO_CLASS 'r'
+static TupleInfo *
+TUPLE_INFO (GVariantTypeInfo *info)
+{
+  g_variant_type_info_check (info, TUPLE_INFO_CLASS);
+
+  return (TupleInfo *) info;
+}
+
+static void
+tuple_info_free (GVariantTypeInfo *info)
+{
+  TupleInfo *tuple_info;
+  gint i;
+
+  g_assert (info->container_class == TUPLE_INFO_CLASS);
+  tuple_info = (TupleInfo *) info;
+
+  for (i = 0; i < tuple_info->n_members; i++)
+    g_variant_type_info_unref (tuple_info->members[i].type);
+
+  g_slice_free1 (sizeof (GVariantMemberInfo) * tuple_info->n_members,
+                 tuple_info->members);
+  g_slice_free (TupleInfo, tuple_info);
+}
+
+static void
+tuple_allocate_members (const GVariantType  *type,
+                        GVariantMemberInfo **members,
+                        gsize               *n_members)
+{
+  const GVariantType *item_type;
+  gsize i = 0;
+
+  *n_members = g_variant_type_n_items (type);
+  *members = g_slice_alloc (sizeof (GVariantMemberInfo) * *n_members);
+
+  item_type = g_variant_type_first (type);
+  while (item_type)
+    {
+      (*members)[i++].type = g_variant_type_info_get (item_type);
+      item_type = g_variant_type_next (item_type);
+    }
+
+  g_assert (i == *n_members);
+}
+
+/* this is g_variant_type_info_query for a given member of the tuple.
+ * before the access is done, it is ensured that the item is within
+ * range and %FALSE is returned if not.
+ */
+static gboolean
+tuple_get_item (TupleInfo          *info,
+                GVariantMemberInfo *item,
+                gsize              *d,
+                gsize              *e)
+{
+  if (&info->members[info->n_members] == item)
+    return FALSE;
+
+  *d = item->type->alignment;
+  *e = item->type->fixed_size;
+  return TRUE;
+}
+
+/* Read the documentation for #GVariantMemberInfo in gvarianttype.h
+ * before attempting to understand this.
+ *
+ * This function adds one set of "magic constant" values (for one item
+ * in the tuple) to the table.
+ *
+ * The algorithm in tuple_generate_table() calculates values of 'a', 'b'
+ * and 'c' for each item, such that the procedure for finding the item
+ * is to start at the end of the previous variable-sized item, add 'a',
+ * then round up to the nearest multiple of 'b', then then add 'c'.
+ * Note that 'b' is stored in the usual "one less than" form.  ie:
+ *
+ *   start = ROUND_UP(prev_end + a, (b + 1)) + c;
+ *
+ * We tweak these values a little to allow for a slightly easier
+ * computation and more compact storage.
+ */
+static void
+tuple_table_append (GVariantMemberInfo **items,
+                    gsize                i,
+                    gsize                a,
+                    gsize                b,
+                    gsize                c)
+{
+  GVariantMemberInfo *item = (*items)++;
+
+  /* We can shift multiples of the alignment size from 'c' into 'a'.
+   * As long as we're shifting whole multiples, it won't affect the
+   * result.  This means that we can take the "aligned" portion off of
+   * 'c' and add it into 'a'.
+   *
+   *  Imagine (for sake of clarity) that ROUND_10 rounds up to the
+   *  nearest 10.  It is clear that:
+   *
+   *   ROUND_10(a) + c == ROUND_10(a + 10*(c / 10)) + (c % 10)
+   *
+   * ie: remove the 10s portion of 'c' and add it onto 'a'.
+   *
+   * To put some numbers on it, imagine we start with a = 34 and c = 27:
+   *
+   *  ROUND_10(34) + 27 = 40 + 27 = 67
+   *
+   * but also, we can split 27 up into 20 and 7 and do this:
+   *
+   *  ROUND_10(34 + 20) + 7 = ROUND_10(54) + 7 = 60 + 7 = 67
+   *                ^^    ^
+   * without affecting the result.  We do that here.
+   *
+   * This reduction in the size of 'c' means that we can store it in a
+   * gchar instead of a gsize.  Due to how the structure is packed, this
+   * ends up saving us 'two pointer sizes' per item in each tuple when
+   * allocating using GSlice.
+   */
+  a += ~b & c;    /* take the "aligned" part of 'c' and add to 'a' */
+  c &= b;         /* chop 'c' to contain only the unaligned part */
+
+
+  /* Finally, we made one last adjustment.  Recall:
+   *
+   *   start = ROUND_UP(prev_end + a, (b + 1)) + c;
+   *
+   * Forgetting the '+ c' for the moment:
+   *
+   *   ROUND_UP(prev_end + a, (b + 1));
+   *
+   * we can do a "round up" operation by adding 1 less than the amount
+   * to round up to, then rounding down.  ie:
+   *
+   *   #define ROUND_UP(x, y)    ROUND_DOWN(x + (y-1), y)
+   *
+   * Of course, for rounding down to a power of two, we can just mask
+   * out the appropriate number of low order bits:
+   *
+   *   #define ROUND_DOWN(x, y)  (x & ~(y - 1))
+   *
+   * Which gives us
+   *
+   *   #define ROUND_UP(x, y)    (x + (y - 1) & ~(y - 1))
+   *
+   * but recall that our alignment value 'b' is already "one less".
+   * This means that to round 'prev_end + a' up to 'b' we can just do:
+   *
+   *   ((prev_end + a) + b) & ~b
+   *
+   * Associativity, and putting the 'c' back on:
+   *
+   *   (prev_end + (a + b)) & ~b + c
+   *
+   * Now, since (a + b) is constant, we can just add 'b' to 'a' now and
+   * store that as the number to add to prev_end.  Then we use ~b as the
+   * number to take a bitwise 'and' with.  Finally, 'c' is added on.
+   *
+   * Note, however, that all the low order bits of the 'aligned' value
+   * are masked out and that all of the high order bits of 'c' have been
+   * "moved" to 'a' (in the previous step).  This means that there are
+   * no overlapping bits in the addition -- so we can do a bitwise 'or'
+   * equivalently.
+   *
+   * This means that we can now compute the start address of a given
+   * item in the tuple using the algorithm given in the documentation
+   * for #GVariantMemberInfo:
+   *
+   *   item_start = ((prev_end + a) & b) | c;
+   */
+
+  item->i = i;
+  item->a = a + b;
+  item->b = ~b;
+  item->c = c;
+}
+
+static gsize
+tuple_align (gsize offset,
+             guint alignment)
+{
+  return offset + ((-offset) & alignment);
+}
+
+/* This function is the heart of the algorithm for calculating 'i', 'a',
+ * 'b' and 'c' for each item in the tuple.
+ *
+ * Imagine we want to find the start of the "i" in the type "(su(qx)ni)".
+ * That's a string followed by a uint32, then a tuple containing a
+ * uint16 and a int64, then an int16, then our "i".  In order to get to
+ * our "i" we:
+ *
+ * Start at the end of the string, align to 4 (for the uint32), add 4.
+ * Align to 8, add 16 (for the tuple).  Align to 2, add 2 (for the
+ * int16).  Then we're there.  It turns out that, given 3 simple rules,
+ * we can flatten this iteration into one addition, one alignment, then
+ * one more addition.
+ *
+ * The loop below plays through each item in the tuple, querying its
+ * alignment and fixed_size into 'd' and 'e', respectively.  At all
+ * times the variables 'a', 'b', and 'c' are maintained such that in
+ * order to get to the current point, you add 'a', align to 'b' then add
+ * 'c'.  'b' is kept in "one less than" form.  For each item, the proper
+ * alignment is applied to find the values of 'a', 'b' and 'c' to get to
+ * the start of that item.  Those values are recorded into the table.
+ * The fixed size of the item (if applicable) is then added on.
+ *
+ * These 3 rules are how 'a', 'b' and 'c' are modified for alignment and
+ * addition of fixed size.  They have been proven correct but are
+ * presented here, without proof:
+ *
+ *  1) in order to "align to 'd'" where 'd' is less than or equal to the
+ *     largest level of alignment seen so far ('b'), you align 'c' to
+ *     'd'.
+ *  2) in order to "align to 'd'" where 'd' is greater than the largest
+ *     level of alignment seen so far, you add 'c' aligned to 'b' to the
+ *     value of 'a', set 'b' to 'd' (ie: increase the 'largest alignment
+ *     seen') and reset 'c' to 0.
+ *  3) in order to "add 'e'", just add 'e' to 'c'.
+ */
+static void
+tuple_generate_table (TupleInfo *info)
+{
+  GVariantMemberInfo *items = info->members;
+  gsize i = -1, a = 0, b = 0, c = 0, d, e;
+
+  /* iterate over each item in the tuple.
+   *   'd' will be the alignment of the item (in one-less form)
+   *   'e' will be the fixed size (or 0 for variable-size items)
+   */
+  while (tuple_get_item (info, items, &d, &e))
+    {
+      /* align to 'd' */
+      if (d <= b)
+        c = tuple_align (c, d);                   /* rule 1 */
+      else
+        a += tuple_align (c, b), b = d, c = 0;    /* rule 2 */
+
+      /* the start of the item is at this point (ie: right after we
+       * have aligned for it).  store this information in the table.
+       */
+      tuple_table_append (&items, i, a, b, c);
+
+      /* "move past" the item by adding in its size. */
+      if (e == 0)
+        /* variable size:
+         *
+         * we'll have an offset stored to mark the end of this item, so
+         * just bump the offset index to give us a new starting point
+         * and reset all the counters.
+         */
+        i++, a = b = c = 0;
+      else
+        /* fixed size */
+        c += e;                                   /* rule 3 */
+    }
+}
+
+static void
+tuple_set_base_info (TupleInfo *info)
+{
+  GVariantTypeInfo *base = &info->container.info;
+
+  if (info->n_members > 0)
+    {
+      GVariantMemberInfo *m;
+
+      /* the alignment requirement of the tuple is the alignment
+       * requirement of its largest item.
+       */
+      base->alignment = 0;
+      for (m = info->members; m < &info->members[info->n_members]; m++)
+        /* can find the max of a list of "one less than" powers of two
+         * by 'or'ing them
+         */
+        base->alignment |= m->type->alignment;
+
+      m--; /* take 'm' back to the last item */
+
+      /* the structure only has a fixed size if no variable-size
+       * offsets are stored and the last item is fixed-sized too (since
+       * an offset is never stored for the last item).
+       */
+      if (m->i == -1 && m->type->fixed_size)
+        /* in that case, the fixed size can be found by finding the
+         * start of the last item (in the usual way) and adding its
+         * fixed size.
+         *
+         * if a tuple has a fixed size then it is always a multiple of
+         * the alignment requirement (to make packing into arrays
+         * easier) so we round up to that here.
+         */
+        base->fixed_size =
+          tuple_align (((m->a & m->b) | m->c) + m->type->fixed_size,
+                       base->alignment);
+      else
+        /* else, the tuple is not fixed size */
+        base->fixed_size = 0;
+    }
+  else
+    {
+      /* the empty tuple: '()'.
+       *
+       * has a size of 1 and an no alignment requirement.
+       *
+       * It has a size of 1 (not 0) for two practical reasons:
+       *
+       *  1) So we can determine how many of them are in an array
+       *     without dividing by zero or without other tricks.
+       *
+       *  2) Even if we had some trick to know the number of items in
+       *     the array (as GVariant did at one time) this would open a
+       *     potential denial of service attack: an attacker could send
+       *     you an extremely small array (in terms of number of bytes)
+       *     containing trillions of zero-sized items.  If you iterated
+       *     over this array you would effectively infinite-loop your
+       *     program.  By forcing a size of at least one, we bound the
+       *     amount of computation done in response to a message to a
+       *     reasonable function of the size of that message.
+       */
+      base->alignment = 0;
+      base->fixed_size = 1;
+    }
+}
+
+static ContainerInfo *
+tuple_info_new (const GVariantType *type)
+{
+  TupleInfo *info;
+
+  info = g_slice_new (TupleInfo);
+  info->container.info.container_class = TUPLE_INFO_CLASS;
+
+  tuple_allocate_members (type, &info->members, &info->n_members);
+  tuple_generate_table (info);
+  tuple_set_base_info (info);
+
+  return (ContainerInfo *) info;
+}
+
+/* < private >
+ * g_variant_type_info_n_members:
+ * @info: a #GVariantTypeInfo for a tuple or dictionary entry type
+ *
+ * Returns the number of members in a tuple or dictionary entry type.
+ * For a dictionary entry this will always be 2.
+ */
+gsize
+g_variant_type_info_n_members (GVariantTypeInfo *info)
+{
+  return TUPLE_INFO (info)->n_members;
+}
+
+/* < private >
+ * g_variant_type_info_member_info:
+ * @info: a #GVariantTypeInfo for a tuple or dictionary entry type
+ * @index: the member to fetch information for
+ *
+ * Returns the #GVariantMemberInfo for a given member.  See
+ * documentation for that structure for why you would want this
+ * information.
+ *
+ * @index must refer to a valid child (ie: strictly less than
+ * g_variant_type_info_n_members() returns).
+ */
+const GVariantMemberInfo *
+g_variant_type_info_member_info (GVariantTypeInfo *info,
+                                 gsize             index)
+{
+  TupleInfo *tuple_info = TUPLE_INFO (info);
+
+  if (index < tuple_info->n_members)
+    return &tuple_info->members[index];
+
+  return NULL;
+}
+
+/* == new/ref/unref == */
+static GStaticRecMutex g_variant_type_info_lock = G_STATIC_REC_MUTEX_INIT;
+static GHashTable *g_variant_type_info_table;
+
+/* < private >
+ * g_variant_type_info_get:
+ * @type: a #GVariantType
+ *
+ * Returns a reference to a #GVariantTypeInfo for @type.
+ *
+ * If an info structure already exists for this type, a new reference is
+ * returned.  If not, the required calculations are performed and a new
+ * info structure is returned.
+ *
+ * It is appropriate to call g_variant_type_info_unref() on the return
+ * value.
+ */
+GVariantTypeInfo *
+g_variant_type_info_get (const GVariantType *type)
+{
+  char type_char;
+
+  type_char = g_variant_type_peek_string (type)[0];
+
+  if (type_char == G_VARIANT_TYPE_INFO_CHAR_MAYBE ||
+      type_char == G_VARIANT_TYPE_INFO_CHAR_ARRAY ||
+      type_char == G_VARIANT_TYPE_INFO_CHAR_TUPLE ||
+      type_char == G_VARIANT_TYPE_INFO_CHAR_DICT_ENTRY)
+    {
+      GVariantTypeInfo *info;
+      gchar *type_string;
+
+      if G_UNLIKELY (g_variant_type_info_table == NULL)
+        g_variant_type_info_table = g_hash_table_new (g_str_hash,
+                                                      g_str_equal);
+
+      type_string = g_variant_type_dup_string (type);
+
+      g_static_rec_mutex_lock (&g_variant_type_info_lock);
+      info = g_hash_table_lookup (g_variant_type_info_table, type_string);
+
+      if (info == NULL)
+        {
+          ContainerInfo *container;
+
+          if (type_char == G_VARIANT_TYPE_INFO_CHAR_MAYBE ||
+              type_char == G_VARIANT_TYPE_INFO_CHAR_ARRAY)
+            {
+              container = array_info_new (type);
+            }
+          else /* tuple or dict entry */
+            {
+              container = tuple_info_new (type);
+            }
+
+          info = (GVariantTypeInfo *) container;
+          container->type_string = type_string;
+          container->ref_count = 1;
+
+          g_hash_table_insert (g_variant_type_info_table, type_string, info);
+          type_string = NULL;
+        }
+      else
+        g_variant_type_info_ref (info);
+
+      g_static_rec_mutex_unlock (&g_variant_type_info_lock);
+      g_variant_type_info_check (info, 0);
+      g_free (type_string);
+
+      return info;
+    }
+  else
+    {
+      const GVariantTypeInfo *info;
+      int index;
+
+      index = type_char - 'b';
+      g_assert (G_N_ELEMENTS (g_variant_type_info_basic_table) == 24);
+      g_assert_cmpint (0, <=, index);
+      g_assert_cmpint (index, <, 24);
+
+      info = g_variant_type_info_basic_table + index;
+      g_variant_type_info_check (info, 0);
+
+      return (GVariantTypeInfo *) info;
+    }
+}
+
+/* < private >
+ * g_variant_type_info_ref:
+ * @info: a #GVariantTypeInfo
+ *
+ * Adds a reference to @info.
+ */
+GVariantTypeInfo *
+g_variant_type_info_ref (GVariantTypeInfo *info)
+{
+  g_variant_type_info_check (info, 0);
+
+  if (info->container_class)
+    {
+      ContainerInfo *container = (ContainerInfo *) info;
+
+      g_assert_cmpint (container->ref_count, >, 0);
+      g_atomic_int_inc (&container->ref_count);
+    }
+
+  return info;
+}
+
+/* < private >
+ * g_variant_type_info_unref:
+ * @info: a #GVariantTypeInfo
+ *
+ * Releases a reference held on @info.  This may result in @info being
+ * freed.
+ */
+void
+g_variant_type_info_unref (GVariantTypeInfo *info)
+{
+  g_variant_type_info_check (info, 0);
+
+  if (info->container_class)
+    {
+      ContainerInfo *container = (ContainerInfo *) info;
+
+      if (g_atomic_int_dec_and_test (&container->ref_count))
+        {
+          g_static_rec_mutex_lock (&g_variant_type_info_lock);
+          g_hash_table_remove (g_variant_type_info_table,
+                               container->type_string);
+          g_static_rec_mutex_unlock (&g_variant_type_info_lock);
+
+          g_free (container->type_string);
+
+          if (info->container_class == ARRAY_INFO_CLASS)
+            array_info_free (info);
+
+          else if (info->container_class == TUPLE_INFO_CLASS)
+            tuple_info_free (info);
+
+          else
+            g_assert_not_reached ();
+        }
+    }
+}
diff --git a/glib/gvarianttypeinfo.h b/glib/gvarianttypeinfo.h
new file mode 100644 (file)
index 0000000..c77cb20
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * Copyright © 2008 Ryan Lortie
+ * Copyright © 2010 Codethink Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#ifndef __G_VARIANT_TYPE_INFO_H__
+#define __G_VARIANT_TYPE_INFO_H__
+
+#include <glib/gvarianttype.h>
+
+#define G_VARIANT_TYPE_INFO_CHAR_MAYBE      'm'
+#define G_VARIANT_TYPE_INFO_CHAR_ARRAY      'a'
+#define G_VARIANT_TYPE_INFO_CHAR_TUPLE      '('
+#define G_VARIANT_TYPE_INFO_CHAR_DICT_ENTRY '{'
+#define G_VARIANT_TYPE_INFO_CHAR_VARIANT    'v'
+#define g_variant_type_info_get_type_char(info) \
+  (g_variant_type_info_get_type_string(info)[0])
+
+typedef struct _GVariantTypeInfo GVariantTypeInfo;
+
+/* < private >
+ * GVariantMemberInfo:
+ *
+ * This structure describes how to construct a GVariant instance
+ * corresponding to a given child of a tuple or dictionary entry in a
+ * very short constant time.  It contains the typeinfo of the child,
+ * along with 4 constants that allow the bounds of the child's
+ * serialised data within the container's serialised data to be found
+ * very efficiently.
+ *
+ * Since dictionary entries are serialised as if they were tuples of 2
+ * items, the term "tuple" will be used here in the general sense to
+ * refer to tuples and dictionary entries.
+ *
+ * BACKGROUND:
+ *   The serialised data for a tuple contains an array of "offsets" at
+ *   the end.  There is one "offset" in this array for each
+ *   variable-sized item in the tuple (except for the last one).  The
+ *   offset points to the end point of that item's serialised data.  The
+ *   procedure for finding the start point is described below.  An
+ *   offset is not needed for the last item because the end point of the
+ *   last item is merely the end point of the container itself (after
+ *   the offsets array has been accounted for).  An offset is not needed
+ *   for fixed-sized items (like integers) because, due to their fixed
+ *   size, the end point is a constant addition to the start point.
+ *
+ *   It is clear that the starting point of a given item in the tuple is
+ *   determined by the items that preceed it in the tuple.  Logically,
+ *   the start point of a particular item in a given type of tuple can
+ *   be determined entirely by the end point of the nearest
+ *   variable-sized item that came before it (or from the start of the
+ *   container itself in case there is no preceeding variable-sized
+ *   item).  In the case of "(isis)" for example, in order to find out
+ *   the start point of the last string, one must start at the end point
+ *   of the first string, align to 4 (for the integer's alignment) and
+ *   then add 4 (for storing the integer).  That's the point where the
+ *   string starts (since no special alignment is required for strings).
+ *
+ *   Of course, this process requires iterating over the types in the
+ *   tuple up to the item of interest.  As it turns out, it is possible
+ *   to determine 3 constants 'a', 'b', and 'c' for each item in the
+ *   tuple, such that, given the ending offset of the nearest previous
+ *   variable-sized item (prev_end), a very simple calculation can be
+ *   performed to determine the start of the item of interest.
+ *
+ * The constants in this structure are used as follows:
+ *
+ * First, among the array of offets contained in the tuple, 'i' is the
+ * index of the offset that refers to the end of the variable-sized item
+ * preceeding the item of interest.  If no variable-sized items preceed
+ * this item, then 'i' will be -1.
+ *
+ * Let 'prev_end' be the end offset of the previous item (or 0 in the
+ * case that there was no such item).  The start address of this item
+ * can then be calculate using 'a', 'b', and 'c':
+ *
+ *    item_start = ((prev_end + a) & b) | c;
+ *
+ * For details about how 'a', 'b' and 'c' are calculated, see the
+ * comments at the point of the implementation in gvariantypeinfo.c.
+ */
+
+typedef struct
+{
+  GVariantTypeInfo *type;
+
+  gsize i, a;
+  gint8 b, c;
+} GVariantMemberInfo;
+
+/* query */
+G_GNUC_INTERNAL
+const gchar *                   g_variant_type_info_get_type_string     (GVariantTypeInfo   *typeinfo);
+
+G_GNUC_INTERNAL
+void                            g_variant_type_info_query               (GVariantTypeInfo   *typeinfo,
+                                                                         guint              *alignment,
+                                                                         gsize              *size);
+
+/* array */
+G_GNUC_INTERNAL
+GVariantTypeInfo *              g_variant_type_info_element             (GVariantTypeInfo   *typeinfo);
+G_GNUC_INTERNAL
+void                            g_variant_type_info_query_element       (GVariantTypeInfo   *typeinfo,
+                                                                         guint              *alignment,
+                                                                         gsize              *size);
+
+/* structure */
+G_GNUC_INTERNAL
+gsize                           g_variant_type_info_n_members           (GVariantTypeInfo   *typeinfo);
+G_GNUC_INTERNAL
+const GVariantMemberInfo *      g_variant_type_info_member_info         (GVariantTypeInfo   *typeinfo,
+                                                                         gsize               index);
+
+/* new/ref/unref */
+G_GNUC_INTERNAL
+GVariantTypeInfo *              g_variant_type_info_get                 (const GVariantType *type);
+G_GNUC_INTERNAL
+GVariantTypeInfo *              g_variant_type_info_ref                 (GVariantTypeInfo   *typeinfo);
+G_GNUC_INTERNAL
+void                            g_variant_type_info_unref               (GVariantTypeInfo   *typeinfo);
+
+#endif /* __G_VARIANT_TYPE_INFO_H__ */
index 9d440d7..26c237f 100644 (file)
@@ -27,20 +27,22 @@ randomly (gdouble prob)
 }
 
 /* corecursion */
-static GVariantType *append_tuple_type_string (GString *, GString *, gint);
+static GVariantType *
+append_tuple_type_string (GString *, GString *, gboolean, gint);
 
 /* append a random GVariantType to a GString
  * append a description of the type to another GString
  * return what the type is
  */
 static GVariantType *
-append_type_string (GString *string,
-                    GString *description,
-                    gint     depth)
+append_type_string (GString  *string,
+                    GString  *description,
+                    gboolean  definite,
+                    gint      depth)
 {
   if (!depth-- || randomly (0.3))
     {
-      gchar b = BASIC[g_test_rand_int_range (0, N_BASIC)];
+      gchar b = BASIC[g_test_rand_int_range (0, N_BASIC - definite)];
       g_string_append_c (string, b);
       g_string_append_c (description, b);
 
@@ -82,7 +84,7 @@ append_type_string (GString *string,
     {
       GVariantType *result;
 
-      switch (g_test_rand_int_range (0, 7))
+      switch (g_test_rand_int_range (0, definite ? 5 : 7))
         {
         case 0:
           {
@@ -90,7 +92,8 @@ append_type_string (GString *string,
 
             g_string_append_c (string, 'a');
             g_string_append (description, "a of ");
-            element = append_type_string (string, description, depth);
+            element = append_type_string (string, description,
+                                          definite, depth);
             result = g_variant_type_new_array (element);
             g_variant_type_free (element);
           }
@@ -104,7 +107,8 @@ append_type_string (GString *string,
 
             g_string_append_c (string, 'm');
             g_string_append (description, "m of ");
-            element = append_type_string (string, description, depth);
+            element = append_type_string (string, description,
+                                          definite, depth);
             result = g_variant_type_new_maybe (element);
             g_variant_type_free (element);
           }
@@ -113,7 +117,8 @@ append_type_string (GString *string,
           break;
 
         case 2:
-          result = append_tuple_type_string (string, description, depth);
+          result = append_tuple_type_string (string, description,
+                                             definite, depth);
 
           g_assert (g_variant_type_is_tuple (result));
           break;
@@ -124,9 +129,9 @@ append_type_string (GString *string,
 
             g_string_append_c (string, '{');
             g_string_append (description, "e of [");
-            key = append_type_string (string, description, 0);
+            key = append_type_string (string, description, definite, 0);
             g_string_append (description, ", ");
-            value = append_type_string (string, description, depth);
+            value = append_type_string (string, description, definite, depth);
             g_string_append_c (description, ']');
             g_string_append_c (string, '}');
             result = g_variant_type_new_dict_entry (key, value);
@@ -167,9 +172,10 @@ append_type_string (GString *string,
 }
 
 static GVariantType *
-append_tuple_type_string (GString *string,
-                          GString *description,
-                          gint     depth)
+append_tuple_type_string (GString  *string,
+                          GString  *description,
+                          gboolean  definite,
+                          gint      depth)
 {
   GVariantType *result, *other_result;
   GVariantType **types;
@@ -184,7 +190,7 @@ append_tuple_type_string (GString *string,
 
   for (i = 0; i < size; i++)
     {
-      types[i] = append_type_string (string, description, depth);
+      types[i] = append_type_string (string, description, definite, depth);
 
       if (i < size - 1)
         g_string_append (description, ", ");
@@ -493,13 +499,13 @@ generate_subtype (const gchar *type_string)
 
   /* then store the replacement in the GString */
   if (type_string[l] == 'r')
-    replacement = append_tuple_type_string (result, junk, 3);
+    replacement = append_tuple_type_string (result, junk, FALSE, 3);
 
   else if (type_string[l] == '?')
-    replacement = append_type_string (result, junk, 0);
+    replacement = append_type_string (result, junk, FALSE, 0);
 
   else if (type_string[l] == '*')
-    replacement = append_type_string (result, junk, 3);
+    replacement = append_type_string (result, junk, FALSE, 3);
 
   else
     g_assert_not_reached ();
@@ -584,7 +590,7 @@ test_gvarianttype (void)
        *
        * exercises type constructor functions and g_variant_type_copy()
        */
-      type = append_type_string (type_string, description, 6);
+      type = append_type_string (type_string, description, FALSE, 6);
 
       /* convert the type string to a type and ensure that it is equal
        * to the one produced with the type constructor routines
@@ -630,7 +636,7 @@ test_gvarianttype (void)
       /* concatenate another type to the type string and ensure that
        * the result is recognised as being invalid
        */
-      other_type = append_type_string (type_string, description, 2);
+      other_type = append_type_string (type_string, description, FALSE, 2);
 
       g_string_free (description, TRUE);
       g_string_free (type_string, TRUE);
@@ -639,12 +645,395 @@ test_gvarianttype (void)
     }
 }
 
+#undef  G_GNUC_INTERNAL
+#define G_GNUC_INTERNAL static
+
+#define DISABLE_VISIBILITY
+#include <glib/gvarianttypeinfo.c>
+
+#define ALIGNED(x, y)   (((x + (y - 1)) / y) * y)
+
+/* do our own calculation of the fixed_size and alignment of a type
+ * using a simple algorithm to make sure the "fancy" one in the
+ * implementation is correct.
+ */
+static void
+calculate_type_info (const GVariantType *type,
+                     gsize              *fixed_size,
+                     guint              *alignment)
+{
+  if (g_variant_type_is_array (type) ||
+      g_variant_type_is_maybe (type))
+    {
+      calculate_type_info (g_variant_type_element (type), NULL, alignment);
+
+      if (fixed_size)
+        *fixed_size = 0;
+    }
+  else if (g_variant_type_is_tuple (type) ||
+           g_variant_type_is_dict_entry (type))
+    {
+      if (g_variant_type_n_items (type))
+        {
+          const GVariantType *sub;
+          gboolean variable;
+          gsize size;
+          guint al;
+
+          variable = FALSE;
+          size = 0;
+          al = 0;
+
+          sub = g_variant_type_first (type);
+          do
+            {
+              gsize this_fs;
+              guint this_al;
+
+              calculate_type_info (sub, &this_fs, &this_al);
+
+              al = MAX (al, this_al);
+
+              if (!this_fs)
+                {
+                  variable = TRUE;
+                  size = 0;
+                }
+
+              if (!variable)
+                {
+                  size = ALIGNED (size, this_al);
+                  size += this_fs;
+                }
+            }
+          while ((sub = g_variant_type_next (sub)));
+
+          size = ALIGNED (size, al);
+
+          if (alignment)
+            *alignment = al;
+
+          if (fixed_size)
+            *fixed_size = size;
+        }
+      else
+        {
+          if (fixed_size)
+            *fixed_size = 1;
+
+          if (alignment)
+            *alignment = 1;
+        }
+    }
+  else
+    {
+      gint fs, al;
+
+      if (g_variant_type_equal (type, G_VARIANT_TYPE_BOOLEAN) ||
+          g_variant_type_equal (type, G_VARIANT_TYPE_BYTE))
+        {
+          al = fs = 1;
+        }
+
+      else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT16) ||
+               g_variant_type_equal (type, G_VARIANT_TYPE_UINT16))
+        {
+          al = fs = 2;
+        }
+
+      else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT32) ||
+               g_variant_type_equal (type, G_VARIANT_TYPE_UINT32) ||
+               g_variant_type_equal (type, G_VARIANT_TYPE_HANDLE))
+        {
+          al = fs = 4;
+        }
+
+      else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT64) ||
+               g_variant_type_equal (type, G_VARIANT_TYPE_UINT64) ||
+               g_variant_type_equal (type, G_VARIANT_TYPE_DOUBLE))
+        {
+          al = fs = 8;
+        }
+      else if (g_variant_type_equal (type, G_VARIANT_TYPE_STRING) ||
+               g_variant_type_equal (type, G_VARIANT_TYPE_OBJECT_PATH) ||
+               g_variant_type_equal (type, G_VARIANT_TYPE_SIGNATURE))
+        {
+          al = 1;
+          fs = 0;
+        }
+      else if (g_variant_type_equal (type, G_VARIANT_TYPE_VARIANT))
+        {
+          al = 8;
+          fs = 0;
+        }
+      else
+        g_assert_not_reached ();
+
+      if (fixed_size)
+        *fixed_size = fs;
+
+      if (alignment)
+        *alignment = al;
+    }
+}
+
+/* same as the describe_type() function above, but iterates over
+ * typeinfo instead of types.
+ */
+static gchar *
+describe_info (GVariantTypeInfo *info)
+{
+  gchar *result;
+
+  switch (g_variant_type_info_get_type_char (info))
+    {
+    case G_VARIANT_TYPE_INFO_CHAR_MAYBE:
+      {
+        gchar *element;
+
+        element = describe_info (g_variant_type_info_element (info));
+        result = g_strdup_printf ("m of %s", element);
+        g_free (element);
+      }
+      break;
+
+    case G_VARIANT_TYPE_INFO_CHAR_ARRAY:
+      {
+        gchar *element;
+
+        element = describe_info (g_variant_type_info_element (info));
+        result = g_strdup_printf ("a of %s", element);
+        g_free (element);
+      }
+      break;
+
+    case G_VARIANT_TYPE_INFO_CHAR_TUPLE:
+      {
+        const gchar *sep = "";
+        GString *string;
+        gint length;
+        gint i;
+
+        string = g_string_new ("t of [");
+        length = g_variant_type_info_n_members (info);
+
+        for (i = 0; i < length; i++)
+          {
+            const GVariantMemberInfo *minfo;
+            gchar *subtype;
+
+            g_string_append (string, sep);
+            sep = ", ";
+
+            minfo = g_variant_type_info_member_info (info, i);
+            subtype = describe_info (minfo->type);
+            g_string_append (string, subtype);
+            g_free (subtype);
+          }
+
+        g_string_append_c (string, ']');
+
+        result = g_string_free (string, FALSE);
+      }
+      break;
+
+    case G_VARIANT_TYPE_INFO_CHAR_DICT_ENTRY:
+      {
+        const GVariantMemberInfo *keyinfo, *valueinfo;
+        gchar *key, *value;
+
+        g_assert_cmpint (g_variant_type_info_n_members (info), ==, 2);
+        keyinfo = g_variant_type_info_member_info (info, 0);
+        valueinfo = g_variant_type_info_member_info (info, 1);
+        key = describe_info (keyinfo->type);
+        value = describe_info (valueinfo->type);
+        result = g_strjoin ("", "e of [", key, ", ", value, "]", NULL);
+        g_free (key);
+        g_free (value);
+      }
+      break;
+
+    case G_VARIANT_TYPE_INFO_CHAR_VARIANT:
+      result = g_strdup ("V");
+      break;
+
+    default:
+      result = g_strdup (g_variant_type_info_get_type_string (info));
+      g_assert_cmpint (strlen (result), ==, 1);
+      break;
+    }
+
+  return result;
+}
+
+/* check that the O(1) method of calculating offsets meshes with the
+ * results of simple iteration.
+ */
+static void
+check_offsets (GVariantTypeInfo   *info,
+               const GVariantType *type)
+{
+  gint flavour;
+  gint length;
+
+  length = g_variant_type_info_n_members (info);
+  g_assert_cmpint (length, ==, g_variant_type_n_items (type));
+
+  /* the 'flavour' is the low order bits of the ending point of
+   * variable-size items in the tuple.  this lets us test that the type
+   * info is correct for various starting alignments.
+   */
+  for (flavour = 0; flavour < 8; flavour++)
+    {
+      const GVariantType *subtype;
+      gsize last_offset_index;
+      gsize last_offset;
+      gsize position;
+      gint i;
+
+      subtype = g_variant_type_first (type);
+      last_offset_index = -1;
+      last_offset = 0;
+      position = 0;
+
+      /* go through the tuple, keeping track of our position */
+      for (i = 0; i < length; i++)
+        {
+          gsize fixed_size;
+          guint alignment;
+
+          calculate_type_info (subtype, &fixed_size, &alignment);
+
+          position = ALIGNED (position, alignment);
+
+          /* compare our current aligned position (ie: the start of this
+           * item) to the start offset that would be calculated if we
+           * used the type info
+           */
+          {
+            const GVariantMemberInfo *member;
+            gsize start;
+
+            member = g_variant_type_info_member_info (info, i);
+            g_assert_cmpint (member->i, ==, last_offset_index);
+
+            /* do the calculation using the typeinfo */
+            start = last_offset;
+            start += member->a;
+            start &= member->b;
+            start |= member->c;
+
+            /* did we reach the same spot? */
+            g_assert_cmpint (start, ==, position);
+          }
+
+          if (fixed_size)
+            {
+              /* fixed size.  add that size. */
+              position += fixed_size;
+            }
+          else
+            {
+              /* variable size.  do the flavouring. */
+              while ((position & 0x7) != flavour)
+                position++;
+
+              /* and store the offset, just like it would be in the
+               * serialised data.
+               */
+              last_offset = position;
+              last_offset_index++;
+            }
+
+          /* next type */
+          subtype = g_variant_type_next (subtype);
+        }
+
+      /* make sure we used up exactly all the types */
+      g_assert (subtype == NULL);
+    }
+}
+
+static void
+test_gvarianttypeinfo (void)
+{
+  gint i;
+
+  for (i = 0; i < 2000; i++)
+    {
+      GString *type_string, *description;
+      gsize fixed_size1, fixed_size2;
+      guint alignment1, alignment2;
+      GVariantTypeInfo *info;
+      GVariantType *type;
+      gchar *desc;
+
+      type_string = g_string_new (NULL);
+      description = g_string_new (NULL);
+
+      /* random type */
+      type = append_type_string (type_string, description, TRUE, 6);
+
+      /* create a typeinfo for it */
+      info = g_variant_type_info_get (type);
+
+      /* make sure the typeinfo has the right type string */
+      g_assert_cmpstr (g_variant_type_info_get_type_string (info), ==,
+                       type_string->str);
+
+      /* calculate the alignment and fixed size, compare to the
+       * typeinfo's calculations
+       */
+      calculate_type_info (type, &fixed_size1, &alignment1);
+      g_variant_type_info_query (info, &alignment2, &fixed_size2);
+      g_assert_cmpint (fixed_size1, ==, fixed_size2);
+      g_assert_cmpint (alignment1, ==, alignment2 + 1);
+
+      /* test the iteration functions over typeinfo structures by
+       * "describing" the typeinfo and verifying equality.
+       */
+      desc = describe_info (info);
+      g_assert_cmpstr (desc, ==, description->str);
+
+      /* do extra checks for containers */
+      if (g_variant_type_is_array (type) ||
+          g_variant_type_is_maybe (type))
+        {
+          const GVariantType *element;
+          gsize efs1, efs2;
+          guint ea1, ea2;
+
+          element = g_variant_type_element (type);
+          calculate_type_info (element, &efs1, &ea1);
+          g_variant_type_info_query_element (info, &ea2, &efs2);
+          g_assert_cmpint (efs1, ==, efs2);
+          g_assert_cmpint (ea1, ==, ea2 + 1);
+
+          g_assert_cmpint (ea1, ==, alignment1);
+          g_assert_cmpint (0, ==, fixed_size1);
+        }
+      else if (g_variant_type_is_tuple (type) ||
+               g_variant_type_is_dict_entry (type))
+        {
+          /* make sure the "magic constants" are working */
+          check_offsets (info, type);
+        }
+
+      g_string_free (type_string, TRUE);
+      g_string_free (description, TRUE);
+      g_variant_type_info_unref (info);
+      g_variant_type_free (type);
+      g_free (desc);
+    }
+}
+
 int
 main (int argc, char **argv)
 {
   g_test_init (&argc, &argv, NULL);
 
   g_test_add_func ("/gvariant/type", test_gvarianttype);
+  g_test_add_func ("/gvariant/typeinfo", test_gvarianttypeinfo);
 
   return g_test_run ();
 }