[glib] GVariant: add g_variant_check_format_string()



commit 34653169e5653b95d61c461f088e6016f042d08a
Author: Ryan Lortie <desrt desrt ca>
Date:   Sat Aug 18 14:12:55 2012 -0400

    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 deletions(-)
---
diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt
index 2d7f232..67eed00 100644
--- a/docs/reference/glib/glib-sections.txt
+++ b/docs/reference/glib/glib-sections.txt
@@ -3061,6 +3061,7 @@ g_variant_classify
 GVariantClass
 
 <SUBSECTION>
+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 9609798..6161142 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 5f2337c..163782d 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 d0cd857..3c92d21 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 d91b78f..d7d7e2c 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 ();
 }



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