From 34653169e5653b95d61c461f088e6016f042d08a Mon Sep 17 00:00:00 2001 From: Ryan Lortie Date: Sat, 18 Aug 2012 14:12:55 -0400 Subject: [PATCH] GVariant: add g_variant_check_format_string() For some time now people have been asking for a way to check for type compatibility between GVariant instances and format strings. There are several APIs inside of GLib itself that would benefit from this. This patch introduces a way to do that. --- docs/reference/glib/glib-sections.txt | 1 + glib/glib.symbols | 1 + glib/gvariant.c | 104 ++++++++++++++++++++++++++ glib/gvariant.h | 4 +- glib/tests/gvariant.c | 54 +++++++++++++ 5 files changed, 163 insertions(+), 1 deletion(-) diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt index 2d7f232b9..67eed00c9 100644 --- a/docs/reference/glib/glib-sections.txt +++ b/docs/reference/glib/glib-sections.txt @@ -3061,6 +3061,7 @@ g_variant_classify GVariantClass +g_variant_check_format_string g_variant_get g_variant_get_va g_variant_new diff --git a/glib/glib.symbols b/glib/glib.symbols index 9609798db..6161142be 100644 --- a/glib/glib.symbols +++ b/glib/glib.symbols @@ -1560,6 +1560,7 @@ g_variant_builder_end g_variant_builder_new g_variant_builder_unref g_variant_builder_ref +g_variant_check_format_string g_variant_new_va g_variant_get_va g_variant_new diff --git a/glib/gvariant.c b/glib/gvariant.c index 5f2337cf7..163782d3d 100644 --- a/glib/gvariant.c +++ b/glib/gvariant.c @@ -3741,6 +3741,110 @@ g_variant_format_string_scan (const gchar *string, return TRUE; } +/** + * g_variant_check_format_string: + * @value: a #GVariant + * @format_string: a valid #GVariant format string + * @copy_only: %TRUE to ensure the format string makes deep copies + * + * Checks if calling g_variant_get() with @format_string on @value would + * be valid from a type-compatibility standpoint. @format_string is + * assumed to be a valid format string (from a syntactic standpoint). + * + * If @copy_only is %TRUE then this function additionally checks that it + * would be safe to call g_variant_unref() on @value immediately after + * the call to g_variant_get() without invalidating the result. This is + * only possible if deep copies are made (ie: there are no pointers to + * the data inside of the soon-to-be-freed #GVariant instance). If this + * check fails then a g_critical() is printed and %FALSE is returned. + * + * This function is meant to be used by functions that wish to provide + * varargs accessors to #GVariant values of uncertain values (eg: + * g_variant_lookup() or g_menu_model_get_item_attribute()). + * + * Returns: %TRUE if @format_string is safe to use + * + * Since: 2.34 + */ +gboolean +g_variant_check_format_string (GVariant *value, + const gchar *format_string, + gboolean copy_only) +{ + const gchar *original_format = format_string; + const gchar *type_string; + + /* Interesting factoid: assuming a format string is valid, it can be + * converted to a type string by removing all '@' '&' and '^' + * characters. + * + * Instead of doing that, we can just skip those characters when + * comparing it to the type string of @value. + * + * For the copy-only case we can just drop the '&' from the list of + * characters to skip over. A '&' will never appear in a type string + * so we know that it won't be possible to return %TRUE if it is in a + * format string. + */ + type_string = g_variant_get_type_string (value); + + while (*type_string || *format_string) + { + gchar format = *format_string++; + + switch (format) + { + case '&': + if G_UNLIKELY (copy_only) + { + /* for the love of all that is good, please don't mark this string for translation... */ + g_critical ("g_variant_check_format_string() is being called by a function with a GVariant varargs " + "interface to validate the passed format string for type safety. The passed format " + "(%s) contains a '&' character which would result in a pointer being returned to the " + "data inside of a GVariant instance that may no longer exist by the time the function " + "returns. Modify your code to use a format string without '&'.", original_format); + return FALSE; + } + + /* fall through */ + case '^': + case '@': + /* ignore these 2 (or 3) */ + continue; + + case '?': + /* attempt to consume one of 'bynqiuxthdsog' */ + { + char s = *type_string++; + + if (s == '\0' || strchr ("bynqiuxthdsog", s) == NULL) + return FALSE; + } + continue; + + case 'r': + /* ensure it's a tuple */ + if (*type_string != '(') + return FALSE; + + /* fall through */ + case '*': + /* consume a full type string for the '*' or 'r' */ + if (!g_variant_type_string_scan (type_string, NULL, &type_string)) + return FALSE; + + continue; + + default: + /* attempt to consume exactly one character equal to the format */ + if (format != *type_string++) + return FALSE; + } + } + + return TRUE; +} + /*< private > * g_variant_format_string_scan_type: * @string: a string that may be prefixed with a format string diff --git a/glib/gvariant.h b/glib/gvariant.h index d0cd85794..3c92d21a0 100644 --- a/glib/gvariant.h +++ b/glib/gvariant.h @@ -265,7 +265,9 @@ void g_variant_get_va (GVarian const gchar *format_string, const gchar **endptr, va_list *app); - +gboolean g_variant_check_format_string (GVariant *value, + const gchar *format_string, + gboolean copy_only); GVariant * g_variant_parse (const GVariantType *type, const gchar *text, diff --git a/glib/tests/gvariant.c b/glib/tests/gvariant.c index d91b78fa0..d7d7e2cd2 100644 --- a/glib/tests/gvariant.c +++ b/glib/tests/gvariant.c @@ -4123,6 +4123,59 @@ test_fixed_array (void) g_variant_unref (a); } +static void +test_check_format_string (void) +{ + GVariant *value; + + value = g_variant_new ("(sas)", "foo", NULL); + g_variant_ref_sink (value); + + g_assert (g_variant_check_format_string (value, "(s*)", TRUE)); + g_assert (g_variant_check_format_string (value, "(s*)", FALSE)); + g_assert (!g_variant_check_format_string (value, "(u*)", TRUE)); + g_assert (!g_variant_check_format_string (value, "(u*)", FALSE)); + + g_assert (g_variant_check_format_string (value, "(&s*)", FALSE)); + g_test_expect_message ("GLib", G_LOG_LEVEL_CRITICAL, "*contains a '&' character*"); + g_assert (!g_variant_check_format_string (value, "(&s*)", TRUE)); + g_test_assert_expected_messages (); + + g_assert (g_variant_check_format_string (value, "(s^as)", TRUE)); + g_assert (g_variant_check_format_string (value, "(s^as)", FALSE)); + + g_test_expect_message ("GLib", G_LOG_LEVEL_CRITICAL, "*contains a '&' character*"); + g_assert (!g_variant_check_format_string (value, "(s^a&s)", TRUE)); + g_test_assert_expected_messages (); + g_assert (g_variant_check_format_string (value, "(s^a&s)", FALSE)); + + g_variant_unref (value); + + /* Do it again with a type that will let us put a '&' after a '^' */ + value = g_variant_new ("(say)", "foo", NULL); + g_variant_ref_sink (value); + + g_assert (g_variant_check_format_string (value, "(s*)", TRUE)); + g_assert (g_variant_check_format_string (value, "(s*)", FALSE)); + g_assert (!g_variant_check_format_string (value, "(u*)", TRUE)); + g_assert (!g_variant_check_format_string (value, "(u*)", FALSE)); + + g_assert (g_variant_check_format_string (value, "(&s*)", FALSE)); + g_test_expect_message ("GLib", G_LOG_LEVEL_CRITICAL, "*contains a '&' character*"); + g_assert (!g_variant_check_format_string (value, "(&s*)", TRUE)); + g_test_assert_expected_messages (); + + g_assert (g_variant_check_format_string (value, "(s^ay)", TRUE)); + g_assert (g_variant_check_format_string (value, "(s^ay)", FALSE)); + + g_test_expect_message ("GLib", G_LOG_LEVEL_CRITICAL, "*contains a '&' character*"); + g_assert (!g_variant_check_format_string (value, "(s^&ay)", TRUE)); + g_test_assert_expected_messages (); + g_assert (g_variant_check_format_string (value, "(s^&ay)", FALSE)); + + g_variant_unref (value); +} + int main (int argc, char **argv) { @@ -4167,6 +4220,7 @@ main (int argc, char **argv) g_test_add_func ("/gvariant/lookup", test_lookup); g_test_add_func ("/gvariant/compare", test_compare); g_test_add_func ("/gvariant/fixed-array", test_fixed_array); + g_test_add_func ("/gvariant/check-format-string", test_check_format_string); return g_test_run (); }