mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-01-23 12:41:50 +01:00
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.
This commit is contained in:
parent
6a6b64ef4d
commit
34653169e5
@ -3061,6 +3061,7 @@ g_variant_classify
|
|||||||
GVariantClass
|
GVariantClass
|
||||||
|
|
||||||
<SUBSECTION>
|
<SUBSECTION>
|
||||||
|
g_variant_check_format_string
|
||||||
g_variant_get
|
g_variant_get
|
||||||
g_variant_get_va
|
g_variant_get_va
|
||||||
g_variant_new
|
g_variant_new
|
||||||
|
@ -1560,6 +1560,7 @@ g_variant_builder_end
|
|||||||
g_variant_builder_new
|
g_variant_builder_new
|
||||||
g_variant_builder_unref
|
g_variant_builder_unref
|
||||||
g_variant_builder_ref
|
g_variant_builder_ref
|
||||||
|
g_variant_check_format_string
|
||||||
g_variant_new_va
|
g_variant_new_va
|
||||||
g_variant_get_va
|
g_variant_get_va
|
||||||
g_variant_new
|
g_variant_new
|
||||||
|
104
glib/gvariant.c
104
glib/gvariant.c
@ -3741,6 +3741,110 @@ g_variant_format_string_scan (const gchar *string,
|
|||||||
return TRUE;
|
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 >
|
/*< private >
|
||||||
* g_variant_format_string_scan_type:
|
* g_variant_format_string_scan_type:
|
||||||
* @string: a string that may be prefixed with a format string
|
* @string: a string that may be prefixed with a format string
|
||||||
|
@ -265,7 +265,9 @@ void g_variant_get_va (GVarian
|
|||||||
const gchar *format_string,
|
const gchar *format_string,
|
||||||
const gchar **endptr,
|
const gchar **endptr,
|
||||||
va_list *app);
|
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,
|
GVariant * g_variant_parse (const GVariantType *type,
|
||||||
const gchar *text,
|
const gchar *text,
|
||||||
|
@ -4123,6 +4123,59 @@ test_fixed_array (void)
|
|||||||
g_variant_unref (a);
|
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
|
int
|
||||||
main (int argc, char **argv)
|
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/lookup", test_lookup);
|
||||||
g_test_add_func ("/gvariant/compare", test_compare);
|
g_test_add_func ("/gvariant/compare", test_compare);
|
||||||
g_test_add_func ("/gvariant/fixed-array", test_fixed_array);
|
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 ();
|
return g_test_run ();
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user