From: Ryan Lortie Date: Sun, 31 Jan 2010 01:15:25 +0000 (-0500) Subject: merge GVariantTypeInfo X-Git-Tag: 2.23.3~61 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=0f246e28ca6651b7b40a5a5668b45729226ca177;p=platform%2Fupstream%2Fglib.git merge GVariantTypeInfo --- diff --git a/glib/Makefile.am b/glib/Makefile.am index 79f0b56..20daa80 100644 --- a/glib/Makefile.am +++ b/glib/Makefile.am @@ -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 index 0000000..08439c9 --- /dev/null +++ b/glib/gvarianttypeinfo.c @@ -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 + */ + +#include "gvarianttypeinfo.h" +#include + +#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 index 0000000..c77cb20 --- /dev/null +++ b/glib/gvarianttypeinfo.h @@ -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 + */ + +#ifndef __G_VARIANT_TYPE_INFO_H__ +#define __G_VARIANT_TYPE_INFO_H__ + +#include + +#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__ */ diff --git a/glib/tests/gvarianttype.c b/glib/tests/gvarianttype.c index 9d440d7..26c237f 100644 --- a/glib/tests/gvarianttype.c +++ b/glib/tests/gvarianttype.c @@ -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 + +#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 (); }