| /* |
| * Copyright © 2009, 2010 Codethink Limited |
| * |
| * SPDX-License-Identifier: LGPL-2.1-or-later |
| * |
| * 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.1 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, see <http://www.gnu.org/licenses/>. |
| * |
| * Author: Ryan Lortie <desrt@desrt.ca> |
| */ |
| |
| #include "config.h" |
| |
| #include <stdlib.h> |
| #include <string.h> |
| #include <errno.h> |
| |
| #include "gerror.h" |
| #include "gquark.h" |
| #include "gstring.h" |
| #include "gstrfuncs.h" |
| #include "gtestutils.h" |
| #include "gvariant.h" |
| #include "glib/gvariant-core.h" |
| #include "gvariant-internal.h" |
| #include "gvarianttype.h" |
| #include "gslice.h" |
| #include "gthread.h" |
| |
| /* |
| * two-pass algorithm |
| * designed by ryan lortie and william hua |
| * designed in itb-229 and at ghazi's, 2009. |
| */ |
| |
| /** |
| * G_VARIANT_PARSE_ERROR: |
| * |
| * Error domain for GVariant text format parsing. Specific error codes |
| * are not currently defined for this domain. See #GError for |
| * information on error domains. |
| **/ |
| /** |
| * GVariantParseError: |
| * @G_VARIANT_PARSE_ERROR_FAILED: generic error (unused) |
| * @G_VARIANT_PARSE_ERROR_BASIC_TYPE_EXPECTED: a non-basic #GVariantType was given where a basic type was expected |
| * @G_VARIANT_PARSE_ERROR_CANNOT_INFER_TYPE: cannot infer the #GVariantType |
| * @G_VARIANT_PARSE_ERROR_DEFINITE_TYPE_EXPECTED: an indefinite #GVariantType was given where a definite type was expected |
| * @G_VARIANT_PARSE_ERROR_INPUT_NOT_AT_END: extra data after parsing finished |
| * @G_VARIANT_PARSE_ERROR_INVALID_CHARACTER: invalid character in number or unicode escape |
| * @G_VARIANT_PARSE_ERROR_INVALID_FORMAT_STRING: not a valid #GVariant format string |
| * @G_VARIANT_PARSE_ERROR_INVALID_OBJECT_PATH: not a valid object path |
| * @G_VARIANT_PARSE_ERROR_INVALID_SIGNATURE: not a valid type signature |
| * @G_VARIANT_PARSE_ERROR_INVALID_TYPE_STRING: not a valid #GVariant type string |
| * @G_VARIANT_PARSE_ERROR_NO_COMMON_TYPE: could not find a common type for array entries |
| * @G_VARIANT_PARSE_ERROR_NUMBER_OUT_OF_RANGE: the numerical value is out of range of the given type |
| * @G_VARIANT_PARSE_ERROR_NUMBER_TOO_BIG: the numerical value is out of range for any type |
| * @G_VARIANT_PARSE_ERROR_TYPE_ERROR: cannot parse as variant of the specified type |
| * @G_VARIANT_PARSE_ERROR_UNEXPECTED_TOKEN: an unexpected token was encountered |
| * @G_VARIANT_PARSE_ERROR_UNKNOWN_KEYWORD: an unknown keyword was encountered |
| * @G_VARIANT_PARSE_ERROR_UNTERMINATED_STRING_CONSTANT: unterminated string constant |
| * @G_VARIANT_PARSE_ERROR_VALUE_EXPECTED: no value given |
| * @G_VARIANT_PARSE_ERROR_RECURSION: variant was too deeply nested; #GVariant is only guaranteed to handle nesting up to 64 levels (Since: 2.64) |
| * |
| * Error codes returned by parsing text-format GVariants. |
| **/ |
| G_DEFINE_QUARK (g-variant-parse-error-quark, g_variant_parse_error) |
| |
| /** |
| * g_variant_parser_get_error_quark: |
| * |
| * Same as g_variant_error_quark(). |
| * |
| * Deprecated: Use g_variant_parse_error_quark() instead. |
| */ |
| GQuark |
| g_variant_parser_get_error_quark (void) |
| { |
| return g_variant_parse_error_quark (); |
| } |
| |
| typedef struct |
| { |
| gint start, end; |
| } SourceRef; |
| |
| G_GNUC_PRINTF(5, 0) |
| static void |
| parser_set_error_va (GError **error, |
| SourceRef *location, |
| SourceRef *other, |
| gint code, |
| const gchar *format, |
| va_list ap) |
| { |
| GString *msg = g_string_new (NULL); |
| |
| if (location->start == location->end) |
| g_string_append_printf (msg, "%d", location->start); |
| else |
| g_string_append_printf (msg, "%d-%d", location->start, location->end); |
| |
| if (other != NULL) |
| { |
| g_assert (other->start != other->end); |
| g_string_append_printf (msg, ",%d-%d", other->start, other->end); |
| } |
| g_string_append_c (msg, ':'); |
| |
| g_string_append_vprintf (msg, format, ap); |
| g_set_error_literal (error, G_VARIANT_PARSE_ERROR, code, msg->str); |
| g_string_free (msg, TRUE); |
| } |
| |
| G_GNUC_PRINTF(5, 6) |
| static void |
| parser_set_error (GError **error, |
| SourceRef *location, |
| SourceRef *other, |
| gint code, |
| const gchar *format, |
| ...) |
| { |
| va_list ap; |
| |
| va_start (ap, format); |
| parser_set_error_va (error, location, other, code, format, ap); |
| va_end (ap); |
| } |
| |
| typedef struct |
| { |
| const gchar *start; |
| const gchar *stream; |
| const gchar *end; |
| |
| const gchar *this; |
| } TokenStream; |
| |
| |
| G_GNUC_PRINTF(5, 6) |
| static void |
| token_stream_set_error (TokenStream *stream, |
| GError **error, |
| gboolean this_token, |
| gint code, |
| const gchar *format, |
| ...) |
| { |
| SourceRef ref; |
| va_list ap; |
| |
| ref.start = stream->this - stream->start; |
| |
| if (this_token) |
| ref.end = stream->stream - stream->start; |
| else |
| ref.end = ref.start; |
| |
| va_start (ap, format); |
| parser_set_error_va (error, &ref, NULL, code, format, ap); |
| va_end (ap); |
| } |
| |
| static gboolean |
| token_stream_prepare (TokenStream *stream) |
| { |
| gint brackets = 0; |
| const gchar *end; |
| |
| if (stream->this != NULL) |
| return TRUE; |
| |
| while (stream->stream != stream->end && g_ascii_isspace (*stream->stream)) |
| stream->stream++; |
| |
| if (stream->stream == stream->end || *stream->stream == '\0') |
| { |
| stream->this = stream->stream; |
| return FALSE; |
| } |
| |
| switch (stream->stream[0]) |
| { |
| case '-': case '+': case '.': case '0': case '1': case '2': |
| case '3': case '4': case '5': case '6': case '7': case '8': |
| case '9': |
| for (end = stream->stream; end != stream->end; end++) |
| if (!g_ascii_isalnum (*end) && |
| *end != '-' && *end != '+' && *end != '.') |
| break; |
| break; |
| |
| case 'b': |
| if (stream->stream + 1 != stream->end && |
| (stream->stream[1] == '\'' || stream->stream[1] == '"')) |
| { |
| for (end = stream->stream + 2; end != stream->end; end++) |
| if (*end == stream->stream[1] || *end == '\0' || |
| (*end == '\\' && (++end == stream->end || *end == '\0'))) |
| break; |
| |
| if (end != stream->end && *end) |
| end++; |
| break; |
| } |
| |
| G_GNUC_FALLTHROUGH; |
| |
| case 'a': /* 'b' */ case 'c': case 'd': case 'e': case 'f': |
| case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': |
| case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': |
| case 's': case 't': case 'u': case 'v': case 'w': case 'x': |
| case 'y': case 'z': |
| for (end = stream->stream; end != stream->end; end++) |
| if (!g_ascii_isalnum (*end)) |
| break; |
| break; |
| |
| case '\'': case '"': |
| for (end = stream->stream + 1; end != stream->end; end++) |
| if (*end == stream->stream[0] || *end == '\0' || |
| (*end == '\\' && (++end == stream->end || *end == '\0'))) |
| break; |
| |
| if (end != stream->end && *end) |
| end++; |
| break; |
| |
| case '@': case '%': |
| /* stop at the first space, comma, colon or unmatched bracket. |
| * deals nicely with cases like (%i, %i) or {%i: %i}. |
| * Also: ] and > are never in format strings. |
| */ |
| for (end = stream->stream + 1; |
| end != stream->end && *end != '\0' && *end != ',' && |
| *end != ':' && *end != '>' && *end != ']' && !g_ascii_isspace (*end); |
| end++) |
| |
| if (*end == '(' || *end == '{') |
| brackets++; |
| |
| else if ((*end == ')' || *end == '}') && !brackets--) |
| break; |
| |
| break; |
| |
| default: |
| end = stream->stream + 1; |
| break; |
| } |
| |
| stream->this = stream->stream; |
| stream->stream = end; |
| |
| /* We must have at least one byte in a token. */ |
| g_assert (stream->stream - stream->this >= 1); |
| |
| return TRUE; |
| } |
| |
| static void |
| token_stream_next (TokenStream *stream) |
| { |
| stream->this = NULL; |
| } |
| |
| static gboolean |
| token_stream_peek (TokenStream *stream, |
| gchar first_char) |
| { |
| if (!token_stream_prepare (stream)) |
| return FALSE; |
| |
| return stream->stream - stream->this >= 1 && |
| stream->this[0] == first_char; |
| } |
| |
| static gboolean |
| token_stream_peek2 (TokenStream *stream, |
| gchar first_char, |
| gchar second_char) |
| { |
| if (!token_stream_prepare (stream)) |
| return FALSE; |
| |
| return stream->stream - stream->this >= 2 && |
| stream->this[0] == first_char && |
| stream->this[1] == second_char; |
| } |
| |
| static gboolean |
| token_stream_is_keyword (TokenStream *stream) |
| { |
| if (!token_stream_prepare (stream)) |
| return FALSE; |
| |
| return stream->stream - stream->this >= 2 && |
| g_ascii_isalpha (stream->this[0]) && |
| g_ascii_isalpha (stream->this[1]); |
| } |
| |
| static gboolean |
| token_stream_is_numeric (TokenStream *stream) |
| { |
| if (!token_stream_prepare (stream)) |
| return FALSE; |
| |
| return (stream->stream - stream->this >= 1 && |
| (g_ascii_isdigit (stream->this[0]) || |
| stream->this[0] == '-' || |
| stream->this[0] == '+' || |
| stream->this[0] == '.')); |
| } |
| |
| static gboolean |
| token_stream_peek_string (TokenStream *stream, |
| const gchar *token) |
| { |
| gint length = strlen (token); |
| |
| return token_stream_prepare (stream) && |
| stream->stream - stream->this == length && |
| memcmp (stream->this, token, length) == 0; |
| } |
| |
| static gboolean |
| token_stream_consume (TokenStream *stream, |
| const gchar *token) |
| { |
| if (!token_stream_peek_string (stream, token)) |
| return FALSE; |
| |
| token_stream_next (stream); |
| return TRUE; |
| } |
| |
| static gboolean |
| token_stream_require (TokenStream *stream, |
| const gchar *token, |
| const gchar *purpose, |
| GError **error) |
| { |
| |
| if (!token_stream_consume (stream, token)) |
| { |
| token_stream_set_error (stream, error, FALSE, |
| G_VARIANT_PARSE_ERROR_UNEXPECTED_TOKEN, |
| "expected '%s'%s", token, purpose); |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| static void |
| token_stream_assert (TokenStream *stream, |
| const gchar *token) |
| { |
| gboolean correct_token G_GNUC_UNUSED /* when compiling with G_DISABLE_ASSERT */; |
| |
| correct_token = token_stream_consume (stream, token); |
| g_assert (correct_token); |
| } |
| |
| static gchar * |
| token_stream_get (TokenStream *stream) |
| { |
| gchar *result; |
| |
| if (!token_stream_prepare (stream)) |
| return NULL; |
| |
| result = g_strndup (stream->this, stream->stream - stream->this); |
| |
| return result; |
| } |
| |
| static void |
| token_stream_start_ref (TokenStream *stream, |
| SourceRef *ref) |
| { |
| token_stream_prepare (stream); |
| ref->start = stream->this - stream->start; |
| } |
| |
| static void |
| token_stream_end_ref (TokenStream *stream, |
| SourceRef *ref) |
| { |
| ref->end = stream->stream - stream->start; |
| } |
| |
| static void |
| pattern_copy (gchar **out, |
| const gchar **in) |
| { |
| gint brackets = 0; |
| |
| while (**in == 'a' || **in == 'm' || **in == 'M') |
| *(*out)++ = *(*in)++; |
| |
| do |
| { |
| if (**in == '(' || **in == '{') |
| brackets++; |
| |
| else if (**in == ')' || **in == '}') |
| brackets--; |
| |
| *(*out)++ = *(*in)++; |
| } |
| while (brackets); |
| } |
| |
| /* Returns the most general pattern that is subpattern of left and subpattern |
| * of right, or NULL if there is no such pattern. */ |
| static gchar * |
| pattern_coalesce (const gchar *left, |
| const gchar *right) |
| { |
| gchar *result; |
| gchar *out; |
| |
| /* the length of the output is loosely bound by the sum of the input |
| * lengths, not simply the greater of the two lengths. |
| * |
| * (*(iii)) + ((iii)*) ((iii)(iii)) |
| * |
| * 8 + 8 = 12 |
| */ |
| out = result = g_malloc (strlen (left) + strlen (right)); |
| |
| while (*left && *right) |
| { |
| if (*left == *right) |
| { |
| *out++ = *left++; |
| right++; |
| } |
| |
| else |
| { |
| const gchar **one = &left, **the_other = &right; |
| |
| again: |
| if (**one == '*' && **the_other != ')') |
| { |
| pattern_copy (&out, the_other); |
| (*one)++; |
| } |
| |
| else if (**one == 'M' && **the_other == 'm') |
| { |
| *out++ = *(*the_other)++; |
| } |
| |
| else if (**one == 'M' && **the_other != 'm' && **the_other != '*') |
| { |
| (*one)++; |
| } |
| |
| else if (**one == 'N' && strchr ("ynqiuxthd", **the_other)) |
| { |
| *out++ = *(*the_other)++; |
| (*one)++; |
| } |
| |
| else if (**one == 'S' && strchr ("sog", **the_other)) |
| { |
| *out++ = *(*the_other)++; |
| (*one)++; |
| } |
| |
| else if (one == &left) |
| { |
| one = &right, the_other = &left; |
| goto again; |
| } |
| |
| else |
| break; |
| } |
| } |
| |
| if (*left || *right) |
| { |
| g_free (result); |
| result = NULL; |
| } |
| else |
| *out++ = '\0'; |
| |
| return result; |
| } |
| |
| typedef struct _AST AST; |
| typedef gchar * (*get_pattern_func) (AST *ast, |
| GError **error); |
| typedef GVariant * (*get_value_func) (AST *ast, |
| const GVariantType *type, |
| GError **error); |
| typedef GVariant * (*get_base_value_func) (AST *ast, |
| const GVariantType *type, |
| GError **error); |
| typedef void (*free_func) (AST *ast); |
| |
| typedef struct |
| { |
| gchar * (* get_pattern) (AST *ast, |
| GError **error); |
| GVariant * (* get_value) (AST *ast, |
| const GVariantType *type, |
| GError **error); |
| GVariant * (* get_base_value) (AST *ast, |
| const GVariantType *type, |
| GError **error); |
| void (* free) (AST *ast); |
| } ASTClass; |
| |
| struct _AST |
| { |
| const ASTClass *class; |
| SourceRef source_ref; |
| }; |
| |
| static gchar * |
| ast_get_pattern (AST *ast, |
| GError **error) |
| { |
| return ast->class->get_pattern (ast, error); |
| } |
| |
| static GVariant * |
| ast_get_value (AST *ast, |
| const GVariantType *type, |
| GError **error) |
| { |
| return ast->class->get_value (ast, type, error); |
| } |
| |
| static void |
| ast_free (AST *ast) |
| { |
| ast->class->free (ast); |
| } |
| |
| G_GNUC_PRINTF(5, 6) |
| static void |
| ast_set_error (AST *ast, |
| GError **error, |
| AST *other_ast, |
| gint code, |
| const gchar *format, |
| ...) |
| { |
| va_list ap; |
| |
| va_start (ap, format); |
| parser_set_error_va (error, &ast->source_ref, |
| other_ast ? & other_ast->source_ref : NULL, |
| code, |
| format, ap); |
| va_end (ap); |
| } |
| |
| static GVariant * |
| ast_type_error (AST *ast, |
| const GVariantType *type, |
| GError **error) |
| { |
| gchar *typestr; |
| |
| typestr = g_variant_type_dup_string (type); |
| ast_set_error (ast, error, NULL, |
| G_VARIANT_PARSE_ERROR_TYPE_ERROR, |
| "can not parse as value of type '%s'", |
| typestr); |
| g_free (typestr); |
| |
| return NULL; |
| } |
| |
| static GVariant * |
| ast_resolve (AST *ast, |
| GError **error) |
| { |
| GVariant *value; |
| gchar *pattern; |
| gint i, j = 0; |
| |
| pattern = ast_get_pattern (ast, error); |
| |
| if (pattern == NULL) |
| return NULL; |
| |
| /* choose reasonable defaults |
| * |
| * 1) favour non-maybe values where possible |
| * 2) default type for strings is 's' |
| * 3) default type for integers is 'i' |
| */ |
| for (i = 0; pattern[i]; i++) |
| switch (pattern[i]) |
| { |
| case '*': |
| ast_set_error (ast, error, NULL, |
| G_VARIANT_PARSE_ERROR_CANNOT_INFER_TYPE, |
| "unable to infer type"); |
| g_free (pattern); |
| return NULL; |
| |
| case 'M': |
| break; |
| |
| case 'S': |
| pattern[j++] = 's'; |
| break; |
| |
| case 'N': |
| pattern[j++] = 'i'; |
| break; |
| |
| default: |
| pattern[j++] = pattern[i]; |
| break; |
| } |
| pattern[j++] = '\0'; |
| |
| value = ast_get_value (ast, G_VARIANT_TYPE (pattern), error); |
| g_free (pattern); |
| |
| return value; |
| } |
| |
| |
| static AST *parse (TokenStream *stream, |
| guint max_depth, |
| va_list *app, |
| GError **error); |
| |
| static void |
| ast_array_append (AST ***array, |
| gint *n_items, |
| AST *ast) |
| { |
| if ((*n_items & (*n_items - 1)) == 0) |
| *array = g_renew (AST *, *array, *n_items ? 2 ** n_items : 1); |
| |
| (*array)[(*n_items)++] = ast; |
| } |
| |
| static void |
| ast_array_free (AST **array, |
| gint n_items) |
| { |
| gint i; |
| |
| for (i = 0; i < n_items; i++) |
| ast_free (array[i]); |
| g_free (array); |
| } |
| |
| static gchar * |
| ast_array_get_pattern (AST **array, |
| gint n_items, |
| GError **error) |
| { |
| gchar *pattern; |
| gint i; |
| |
| /* Find the pattern which applies to all children in the array, by l-folding a |
| * coalesce operation. |
| */ |
| pattern = ast_get_pattern (array[0], error); |
| |
| if (pattern == NULL) |
| return NULL; |
| |
| for (i = 1; i < n_items; i++) |
| { |
| gchar *tmp, *merged; |
| |
| tmp = ast_get_pattern (array[i], error); |
| |
| if (tmp == NULL) |
| { |
| g_free (pattern); |
| return NULL; |
| } |
| |
| merged = pattern_coalesce (pattern, tmp); |
| g_free (pattern); |
| pattern = merged; |
| |
| if (merged == NULL) |
| /* set coalescence implies pairwise coalescence (i think). |
| * we should therefore be able to trace the failure to a single |
| * pair of values. |
| */ |
| { |
| int j = 0; |
| |
| while (TRUE) |
| { |
| gchar *tmp2; |
| gchar *m; |
| |
| /* if 'j' reaches 'i' then we didn't find the pair that failed |
| * to coalesce. This shouldn't happen (see above), but just in |
| * case report an error: |
| */ |
| if (j >= i) |
| { |
| ast_set_error (array[i], error, NULL, |
| G_VARIANT_PARSE_ERROR_NO_COMMON_TYPE, |
| "unable to find a common type"); |
| g_free (tmp); |
| return NULL; |
| } |
| |
| tmp2 = ast_get_pattern (array[j], NULL); |
| g_assert (tmp2 != NULL); |
| |
| m = pattern_coalesce (tmp, tmp2); |
| g_free (tmp2); |
| g_free (m); |
| |
| if (m == NULL) |
| { |
| /* we found a conflict between 'i' and 'j'. |
| * |
| * report the error. note: 'j' is first. |
| */ |
| ast_set_error (array[j], error, array[i], |
| G_VARIANT_PARSE_ERROR_NO_COMMON_TYPE, |
| "unable to find a common type"); |
| g_free (tmp); |
| return NULL; |
| } |
| |
| j++; |
| } |
| |
| } |
| |
| g_free (tmp); |
| } |
| |
| return pattern; |
| } |
| |
| typedef struct |
| { |
| AST ast; |
| |
| AST *child; |
| } Maybe; |
| |
| static gchar * |
| maybe_get_pattern (AST *ast, |
| GError **error) |
| { |
| Maybe *maybe = (Maybe *) ast; |
| |
| if (maybe->child != NULL) |
| { |
| gchar *child_pattern; |
| gchar *pattern; |
| |
| child_pattern = ast_get_pattern (maybe->child, error); |
| |
| if (child_pattern == NULL) |
| return NULL; |
| |
| pattern = g_strdup_printf ("m%s", child_pattern); |
| g_free (child_pattern); |
| |
| return pattern; |
| } |
| |
| return g_strdup ("m*"); |
| } |
| |
| static GVariant * |
| maybe_get_value (AST *ast, |
| const GVariantType *type, |
| GError **error) |
| { |
| Maybe *maybe = (Maybe *) ast; |
| GVariant *value; |
| |
| if (!g_variant_type_is_maybe (type)) |
| return ast_type_error (ast, type, error); |
| |
| type = g_variant_type_element (type); |
| |
| if (maybe->child) |
| { |
| value = ast_get_value (maybe->child, type, error); |
| |
| if (value == NULL) |
| return NULL; |
| } |
| else |
| value = NULL; |
| |
| return g_variant_new_maybe (type, value); |
| } |
| |
| static void |
| maybe_free (AST *ast) |
| { |
| Maybe *maybe = (Maybe *) ast; |
| |
| if (maybe->child != NULL) |
| ast_free (maybe->child); |
| |
| g_slice_free (Maybe, maybe); |
| } |
| |
| static AST * |
| maybe_parse (TokenStream *stream, |
| guint max_depth, |
| va_list *app, |
| GError **error) |
| { |
| static const ASTClass maybe_class = { |
| maybe_get_pattern, |
| maybe_get_value, NULL, |
| maybe_free |
| }; |
| AST *child = NULL; |
| Maybe *maybe; |
| |
| if (token_stream_consume (stream, "just")) |
| { |
| child = parse (stream, max_depth - 1, app, error); |
| if (child == NULL) |
| return NULL; |
| } |
| |
| else if (!token_stream_consume (stream, "nothing")) |
| { |
| token_stream_set_error (stream, error, TRUE, |
| G_VARIANT_PARSE_ERROR_UNKNOWN_KEYWORD, |
| "unknown keyword"); |
| return NULL; |
| } |
| |
| maybe = g_slice_new (Maybe); |
| maybe->ast.class = &maybe_class; |
| maybe->child = child; |
| |
| return (AST *) maybe; |
| } |
| |
| static GVariant * |
| maybe_wrapper (AST *ast, |
| const GVariantType *type, |
| GError **error) |
| { |
| const GVariantType *base_type; |
| GVariant *base_value; |
| GVariant *value = NULL; |
| unsigned int depth; |
| gboolean trusted; |
| GVariantTypeInfo *base_type_info = NULL; |
| gsize base_serialised_fixed_size, base_serialised_size, serialised_size, n_suffix_zeros; |
| guint8 *serialised = NULL; |
| GBytes *bytes = NULL; |
| gsize i; |
| |
| for (depth = 0, base_type = type; |
| g_variant_type_is_maybe (base_type); |
| depth++, base_type = g_variant_type_element (base_type)); |
| |
| base_value = ast->class->get_base_value (ast, base_type, error); |
| |
| if (base_value == NULL || depth == 0) |
| return g_steal_pointer (&base_value); |
| |
| /* This is the equivalent of calling g_variant_new_maybe() in a loop enough |
| * times to match the number of nested maybe types in @type. It does the same |
| * in a single `GVariant` allocation, though. |
| * |
| * This avoids maybe_wrapper() becoming an attack vector where a malicious |
| * text-form variant can create a long array, and insert a typedecl for a |
| * deeply nested maybe type on one of its elements. This is achievable with a |
| * relatively short text form, but results in O(array length × typedecl depth) |
| * allocations. This is a denial of service attack. |
| * |
| * Instead of constructing a tree of `GVariant`s in tree-form to match the |
| * @ast, construct a single `GVariant` containing the serialised form of the |
| * maybe-wrappers and the base value that they contain. This is relatively |
| * straightforward: serialise the base value, and then append the correct |
| * number of zero bytes for the maybe-wrappers. |
| * |
| * This is a bit of a layering violation, unfortunately. |
| * |
| * By doing this, the typedecl depth variable is reduced to O(1). |
| */ |
| trusted = g_variant_is_trusted (base_value); |
| |
| /* See https://developer.gnome.org/documentation/specifications/gvariant-specification-1.0.html#maybes |
| * |
| * The serialised form of a `Just x` is the serialised form of `x` if `x` is |
| * fixed-size, and the serialised form of `x` plus a trailing zero byte if `x` |
| * is variable-size. A `Maybe` variant is always variable-size, even if its |
| * child element is fixed-size, because it might be `Nothing`. This means that |
| * all the maybe-wrappers which are not the innermost are always serialised |
| * with one trailing zero byte each. |
| * |
| * The serialised form of a `Nothing` is an empty byte sequence, but that’s |
| * already handled above in the `base_value == NULL` case. |
| */ |
| base_type_info = g_variant_type_info_get (base_type); |
| g_variant_type_info_query (base_type_info, NULL, &base_serialised_fixed_size); |
| g_variant_type_info_unref (base_type_info); |
| |
| base_serialised_size = g_variant_get_size (base_value); |
| n_suffix_zeros = (base_serialised_fixed_size > 0) ? depth - 1 : depth; |
| g_assert (base_serialised_size <= G_MAXSIZE - n_suffix_zeros); |
| serialised_size = base_serialised_size + n_suffix_zeros; |
| |
| g_assert (serialised_size >= base_serialised_size); |
| |
| /* Serialise the base value. */ |
| serialised = g_malloc (serialised_size); |
| g_variant_store (base_value, serialised); |
| |
| /* Zero-out the suffix zeros to complete the serialisation of the maybe wrappers. */ |
| for (i = base_serialised_size; i < serialised_size; i++) |
| serialised[i] = 0; |
| |
| bytes = g_bytes_new_take (g_steal_pointer (&serialised), serialised_size); |
| value = g_variant_new_from_bytes (type, bytes, trusted); |
| g_bytes_unref (bytes); |
| |
| g_variant_unref (base_value); |
| |
| return g_steal_pointer (&value); |
| } |
| |
| typedef struct |
| { |
| AST ast; |
| |
| AST **children; |
| gint n_children; |
| } Array; |
| |
| static gchar * |
| array_get_pattern (AST *ast, |
| GError **error) |
| { |
| Array *array = (Array *) ast; |
| gchar *pattern; |
| gchar *result; |
| |
| if (array->n_children == 0) |
| return g_strdup ("Ma*"); |
| |
| pattern = ast_array_get_pattern (array->children, array->n_children, error); |
| |
| if (pattern == NULL) |
| return NULL; |
| |
| result = g_strdup_printf ("Ma%s", pattern); |
| g_free (pattern); |
| |
| return result; |
| } |
| |
| static GVariant * |
| array_get_value (AST *ast, |
| const GVariantType *type, |
| GError **error) |
| { |
| Array *array = (Array *) ast; |
| const GVariantType *childtype; |
| GVariantBuilder builder; |
| gint i; |
| |
| if (!g_variant_type_is_array (type)) |
| return ast_type_error (ast, type, error); |
| |
| g_variant_builder_init (&builder, type); |
| childtype = g_variant_type_element (type); |
| |
| for (i = 0; i < array->n_children; i++) |
| { |
| GVariant *child; |
| |
| if (!(child = ast_get_value (array->children[i], childtype, error))) |
| { |
| g_variant_builder_clear (&builder); |
| return NULL; |
| } |
| |
| g_variant_builder_add_value (&builder, child); |
| } |
| |
| return g_variant_builder_end (&builder); |
| } |
| |
| static void |
| array_free (AST *ast) |
| { |
| Array *array = (Array *) ast; |
| |
| ast_array_free (array->children, array->n_children); |
| g_slice_free (Array, array); |
| } |
| |
| static AST * |
| array_parse (TokenStream *stream, |
| guint max_depth, |
| va_list *app, |
| GError **error) |
| { |
| static const ASTClass array_class = { |
| array_get_pattern, |
| maybe_wrapper, array_get_value, |
| array_free |
| }; |
| gboolean need_comma = FALSE; |
| Array *array; |
| |
| array = g_slice_new (Array); |
| array->ast.class = &array_class; |
| array->children = NULL; |
| array->n_children = 0; |
| |
| token_stream_assert (stream, "["); |
| while (!token_stream_consume (stream, "]")) |
| { |
| AST *child; |
| |
| if (need_comma && |
| !token_stream_require (stream, ",", |
| " or ']' to follow array element", |
| error)) |
| goto error; |
| |
| child = parse (stream, max_depth - 1, app, error); |
| |
| if (!child) |
| goto error; |
| |
| ast_array_append (&array->children, &array->n_children, child); |
| need_comma = TRUE; |
| } |
| |
| return (AST *) array; |
| |
| error: |
| ast_array_free (array->children, array->n_children); |
| g_slice_free (Array, array); |
| |
| return NULL; |
| } |
| |
| typedef struct |
| { |
| AST ast; |
| |
| AST **children; |
| gint n_children; |
| } Tuple; |
| |
| static gchar * |
| tuple_get_pattern (AST *ast, |
| GError **error) |
| { |
| Tuple *tuple = (Tuple *) ast; |
| gchar *result = NULL; |
| gchar **parts; |
| gint i; |
| |
| parts = g_new (gchar *, tuple->n_children + 4); |
| parts[tuple->n_children + 1] = (gchar *) ")"; |
| parts[tuple->n_children + 2] = NULL; |
| parts[0] = (gchar *) "M("; |
| |
| for (i = 0; i < tuple->n_children; i++) |
| if (!(parts[i + 1] = ast_get_pattern (tuple->children[i], error))) |
| break; |
| |
| if (i == tuple->n_children) |
| result = g_strjoinv ("", parts); |
| |
| /* parts[0] should not be freed */ |
| while (i) |
| g_free (parts[i--]); |
| g_free (parts); |
| |
| return result; |
| } |
| |
| static GVariant * |
| tuple_get_value (AST *ast, |
| const GVariantType *type, |
| GError **error) |
| { |
| Tuple *tuple = (Tuple *) ast; |
| const GVariantType *childtype; |
| GVariantBuilder builder; |
| gint i; |
| |
| if (!g_variant_type_is_tuple (type)) |
| return ast_type_error (ast, type, error); |
| |
| g_variant_builder_init (&builder, type); |
| childtype = g_variant_type_first (type); |
| |
| for (i = 0; i < tuple->n_children; i++) |
| { |
| GVariant *child; |
| |
| if (childtype == NULL) |
| { |
| g_variant_builder_clear (&builder); |
| return ast_type_error (ast, type, error); |
| } |
| |
| if (!(child = ast_get_value (tuple->children[i], childtype, error))) |
| { |
| g_variant_builder_clear (&builder); |
| return FALSE; |
| } |
| |
| g_variant_builder_add_value (&builder, child); |
| childtype = g_variant_type_next (childtype); |
| } |
| |
| if (childtype != NULL) |
| { |
| g_variant_builder_clear (&builder); |
| return ast_type_error (ast, type, error); |
| } |
| |
| return g_variant_builder_end (&builder); |
| } |
| |
| static void |
| tuple_free (AST *ast) |
| { |
| Tuple *tuple = (Tuple *) ast; |
| |
| ast_array_free (tuple->children, tuple->n_children); |
| g_slice_free (Tuple, tuple); |
| } |
| |
| static AST * |
| tuple_parse (TokenStream *stream, |
| guint max_depth, |
| va_list *app, |
| GError **error) |
| { |
| static const ASTClass tuple_class = { |
| tuple_get_pattern, |
| maybe_wrapper, tuple_get_value, |
| tuple_free |
| }; |
| gboolean need_comma = FALSE; |
| gboolean first = TRUE; |
| Tuple *tuple; |
| |
| tuple = g_slice_new (Tuple); |
| tuple->ast.class = &tuple_class; |
| tuple->children = NULL; |
| tuple->n_children = 0; |
| |
| token_stream_assert (stream, "("); |
| while (!token_stream_consume (stream, ")")) |
| { |
| AST *child; |
| |
| if (need_comma && |
| !token_stream_require (stream, ",", |
| " or ')' to follow tuple element", |
| error)) |
| goto error; |
| |
| child = parse (stream, max_depth - 1, app, error); |
| |
| if (!child) |
| goto error; |
| |
| ast_array_append (&tuple->children, &tuple->n_children, child); |
| |
| /* the first time, we absolutely require a comma, so grab it here |
| * and leave need_comma = FALSE so that the code above doesn't |
| * require a second comma. |
| * |
| * the second and remaining times, we set need_comma = TRUE. |
| */ |
| if (first) |
| { |
| if (!token_stream_require (stream, ",", |
| " after first tuple element", error)) |
| goto error; |
| |
| first = FALSE; |
| } |
| else |
| need_comma = TRUE; |
| } |
| |
| return (AST *) tuple; |
| |
| error: |
| ast_array_free (tuple->children, tuple->n_children); |
| g_slice_free (Tuple, tuple); |
| |
| return NULL; |
| } |
| |
| typedef struct |
| { |
| AST ast; |
| |
| AST *value; |
| } Variant; |
| |
| static gchar * |
| variant_get_pattern (AST *ast, |
| GError **error) |
| { |
| return g_strdup ("Mv"); |
| } |
| |
| static GVariant * |
| variant_get_value (AST *ast, |
| const GVariantType *type, |
| GError **error) |
| { |
| Variant *variant = (Variant *) ast; |
| GVariant *child; |
| |
| if (!g_variant_type_equal (type, G_VARIANT_TYPE_VARIANT)) |
| return ast_type_error (ast, type, error); |
| |
| child = ast_resolve (variant->value, error); |
| |
| if (child == NULL) |
| return NULL; |
| |
| return g_variant_new_variant (child); |
| } |
| |
| static void |
| variant_free (AST *ast) |
| { |
| Variant *variant = (Variant *) ast; |
| |
| ast_free (variant->value); |
| g_slice_free (Variant, variant); |
| } |
| |
| static AST * |
| variant_parse (TokenStream *stream, |
| guint max_depth, |
| va_list *app, |
| GError **error) |
| { |
| static const ASTClass variant_class = { |
| variant_get_pattern, |
| maybe_wrapper, variant_get_value, |
| variant_free |
| }; |
| Variant *variant; |
| AST *value; |
| |
| token_stream_assert (stream, "<"); |
| value = parse (stream, max_depth - 1, app, error); |
| |
| if (!value) |
| return NULL; |
| |
| if (!token_stream_require (stream, ">", " to follow variant value", error)) |
| { |
| ast_free (value); |
| return NULL; |
| } |
| |
| variant = g_slice_new (Variant); |
| variant->ast.class = &variant_class; |
| variant->value = value; |
| |
| return (AST *) variant; |
| } |
| |
| typedef struct |
| { |
| AST ast; |
| |
| AST **keys; |
| AST **values; |
| gint n_children; |
| } Dictionary; |
| |
| static gchar * |
| dictionary_get_pattern (AST *ast, |
| GError **error) |
| { |
| Dictionary *dict = (Dictionary *) ast; |
| gchar *value_pattern; |
| gchar *key_pattern; |
| gchar key_char; |
| gchar *result; |
| |
| if (dict->n_children == 0) |
| return g_strdup ("Ma{**}"); |
| |
| key_pattern = ast_array_get_pattern (dict->keys, |
| abs (dict->n_children), |
| error); |
| |
| if (key_pattern == NULL) |
| return NULL; |
| |
| /* we can not have maybe keys */ |
| if (key_pattern[0] == 'M') |
| key_char = key_pattern[1]; |
| else |
| key_char = key_pattern[0]; |
| |
| g_free (key_pattern); |
| |
| /* the basic types, |
| * plus undetermined number type and undetermined string type. |
| */ |
| if (!strchr ("bynqiuxthdsogNS", key_char)) |
| { |
| ast_set_error (ast, error, NULL, |
| G_VARIANT_PARSE_ERROR_BASIC_TYPE_EXPECTED, |
| "dictionary keys must have basic types"); |
| return NULL; |
| } |
| |
| value_pattern = ast_get_pattern (dict->values[0], error); |
| |
| if (value_pattern == NULL) |
| return NULL; |
| |
| result = g_strdup_printf ("M%s{%c%s}", |
| dict->n_children > 0 ? "a" : "", |
| key_char, value_pattern); |
| g_free (value_pattern); |
| |
| return result; |
| } |
| |
| static GVariant * |
| dictionary_get_value (AST *ast, |
| const GVariantType *type, |
| GError **error) |
| { |
| Dictionary *dict = (Dictionary *) ast; |
| |
| if (dict->n_children == -1) |
| { |
| const GVariantType *subtype; |
| GVariantBuilder builder; |
| GVariant *subvalue; |
| |
| if (!g_variant_type_is_dict_entry (type)) |
| return ast_type_error (ast, type, error); |
| |
| g_variant_builder_init (&builder, type); |
| |
| subtype = g_variant_type_key (type); |
| if (!(subvalue = ast_get_value (dict->keys[0], subtype, error))) |
| { |
| g_variant_builder_clear (&builder); |
| return NULL; |
| } |
| g_variant_builder_add_value (&builder, subvalue); |
| |
| subtype = g_variant_type_value (type); |
| if (!(subvalue = ast_get_value (dict->values[0], subtype, error))) |
| { |
| g_variant_builder_clear (&builder); |
| return NULL; |
| } |
| g_variant_builder_add_value (&builder, subvalue); |
| |
| return g_variant_builder_end (&builder); |
| } |
| else |
| { |
| const GVariantType *entry, *key, *val; |
| GVariantBuilder builder; |
| gint i; |
| |
| if (!g_variant_type_is_subtype_of (type, G_VARIANT_TYPE_DICTIONARY)) |
| return ast_type_error (ast, type, error); |
| |
| entry = g_variant_type_element (type); |
| key = g_variant_type_key (entry); |
| val = g_variant_type_value (entry); |
| |
| g_variant_builder_init (&builder, type); |
| |
| for (i = 0; i < dict->n_children; i++) |
| { |
| GVariant *subvalue; |
| |
| g_variant_builder_open (&builder, entry); |
| |
| if (!(subvalue = ast_get_value (dict->keys[i], key, error))) |
| { |
| g_variant_builder_clear (&builder); |
| return NULL; |
| } |
| g_variant_builder_add_value (&builder, subvalue); |
| |
| if (!(subvalue = ast_get_value (dict->values[i], val, error))) |
| { |
| g_variant_builder_clear (&builder); |
| return NULL; |
| } |
| g_variant_builder_add_value (&builder, subvalue); |
| g_variant_builder_close (&builder); |
| } |
| |
| return g_variant_builder_end (&builder); |
| } |
| } |
| |
| static void |
| dictionary_free (AST *ast) |
| { |
| Dictionary *dict = (Dictionary *) ast; |
| gint n_children; |
| |
| if (dict->n_children > -1) |
| n_children = dict->n_children; |
| else |
| n_children = 1; |
| |
| ast_array_free (dict->keys, n_children); |
| ast_array_free (dict->values, n_children); |
| g_slice_free (Dictionary, dict); |
| } |
| |
| static AST * |
| dictionary_parse (TokenStream *stream, |
| guint max_depth, |
| va_list *app, |
| GError **error) |
| { |
| static const ASTClass dictionary_class = { |
| dictionary_get_pattern, |
| maybe_wrapper, dictionary_get_value, |
| dictionary_free |
| }; |
| gint n_keys, n_values; |
| gboolean only_one; |
| Dictionary *dict; |
| AST *first; |
| |
| dict = g_slice_new (Dictionary); |
| dict->ast.class = &dictionary_class; |
| dict->keys = NULL; |
| dict->values = NULL; |
| n_keys = n_values = 0; |
| |
| token_stream_assert (stream, "{"); |
| |
| if (token_stream_consume (stream, "}")) |
| { |
| dict->n_children = 0; |
| return (AST *) dict; |
| } |
| |
| if ((first = parse (stream, max_depth - 1, app, error)) == NULL) |
| goto error; |
| |
| ast_array_append (&dict->keys, &n_keys, first); |
| |
| only_one = token_stream_consume (stream, ","); |
| if (!only_one && |
| !token_stream_require (stream, ":", |
| " or ',' to follow dictionary entry key", |
| error)) |
| goto error; |
| |
| if ((first = parse (stream, max_depth - 1, app, error)) == NULL) |
| goto error; |
| |
| ast_array_append (&dict->values, &n_values, first); |
| |
| if (only_one) |
| { |
| if (!token_stream_require (stream, "}", " at end of dictionary entry", |
| error)) |
| goto error; |
| |
| g_assert (n_keys == 1 && n_values == 1); |
| dict->n_children = -1; |
| |
| return (AST *) dict; |
| } |
| |
| while (!token_stream_consume (stream, "}")) |
| { |
| AST *child; |
| |
| if (!token_stream_require (stream, ",", |
| " or '}' to follow dictionary entry", error)) |
| goto error; |
| |
| child = parse (stream, max_depth - 1, app, error); |
| |
| if (!child) |
| goto error; |
| |
| ast_array_append (&dict->keys, &n_keys, child); |
| |
| if (!token_stream_require (stream, ":", |
| " to follow dictionary entry key", error)) |
| goto error; |
| |
| child = parse (stream, max_depth - 1, app, error); |
| |
| if (!child) |
| goto error; |
| |
| ast_array_append (&dict->values, &n_values, child); |
| } |
| |
| g_assert (n_keys == n_values); |
| dict->n_children = n_keys; |
| |
| return (AST *) dict; |
| |
| error: |
| ast_array_free (dict->keys, n_keys); |
| ast_array_free (dict->values, n_values); |
| g_slice_free (Dictionary, dict); |
| |
| return NULL; |
| } |
| |
| typedef struct |
| { |
| AST ast; |
| gchar *string; |
| } String; |
| |
| static gchar * |
| string_get_pattern (AST *ast, |
| GError **error) |
| { |
| return g_strdup ("MS"); |
| } |
| |
| static GVariant * |
| string_get_value (AST *ast, |
| const GVariantType *type, |
| GError **error) |
| { |
| String *string = (String *) ast; |
| |
| if (g_variant_type_equal (type, G_VARIANT_TYPE_STRING)) |
| return g_variant_new_string (string->string); |
| |
| else if (g_variant_type_equal (type, G_VARIANT_TYPE_OBJECT_PATH)) |
| { |
| if (!g_variant_is_object_path (string->string)) |
| { |
| ast_set_error (ast, error, NULL, |
| G_VARIANT_PARSE_ERROR_INVALID_OBJECT_PATH, |
| "not a valid object path"); |
| return NULL; |
| } |
| |
| return g_variant_new_object_path (string->string); |
| } |
| |
| else if (g_variant_type_equal (type, G_VARIANT_TYPE_SIGNATURE)) |
| { |
| if (!g_variant_is_signature (string->string)) |
| { |
| ast_set_error (ast, error, NULL, |
| G_VARIANT_PARSE_ERROR_INVALID_SIGNATURE, |
| "not a valid signature"); |
| return NULL; |
| } |
| |
| return g_variant_new_signature (string->string); |
| } |
| |
| else |
| return ast_type_error (ast, type, error); |
| } |
| |
| static void |
| string_free (AST *ast) |
| { |
| String *string = (String *) ast; |
| |
| g_free (string->string); |
| g_slice_free (String, string); |
| } |
| |
| /* Accepts exactly @length hexadecimal digits. No leading sign or `0x`/`0X` prefix allowed. |
| * No leading/trailing space allowed. */ |
| static gboolean |
| unicode_unescape (const gchar *src, |
| gint *src_ofs, |
| gchar *dest, |
| gint *dest_ofs, |
| gsize length, |
| SourceRef *ref, |
| GError **error) |
| { |
| gchar buffer[9]; |
| guint64 value = 0; |
| gchar *end = NULL; |
| gsize n_valid_chars; |
| |
| (*src_ofs)++; |
| |
| g_assert (length < sizeof (buffer)); |
| strncpy (buffer, src + *src_ofs, length); |
| buffer[length] = '\0'; |
| |
| for (n_valid_chars = 0; n_valid_chars < length; n_valid_chars++) |
| if (!g_ascii_isxdigit (buffer[n_valid_chars])) |
| break; |
| |
| if (n_valid_chars == length) |
| value = g_ascii_strtoull (buffer, &end, 0x10); |
| |
| if (value == 0 || end != buffer + length) |
| { |
| SourceRef escape_ref; |
| |
| escape_ref = *ref; |
| escape_ref.start += *src_ofs; |
| escape_ref.end = escape_ref.start + n_valid_chars; |
| |
| parser_set_error (error, &escape_ref, NULL, |
| G_VARIANT_PARSE_ERROR_INVALID_CHARACTER, |
| "invalid %" G_GSIZE_FORMAT "-character unicode escape", length); |
| return FALSE; |
| } |
| |
| g_assert (value <= G_MAXUINT32); |
| |
| *dest_ofs += g_unichar_to_utf8 (value, dest + *dest_ofs); |
| *src_ofs += length; |
| |
| return TRUE; |
| } |
| |
| static AST * |
| string_parse (TokenStream *stream, |
| va_list *app, |
| GError **error) |
| { |
| static const ASTClass string_class = { |
| string_get_pattern, |
| maybe_wrapper, string_get_value, |
| string_free |
| }; |
| String *string; |
| SourceRef ref; |
| gchar *token; |
| gsize length; |
| gchar quote; |
| gchar *str; |
| gint i, j; |
| |
| token_stream_start_ref (stream, &ref); |
| token = token_stream_get (stream); |
| token_stream_end_ref (stream, &ref); |
| length = strlen (token); |
| quote = token[0]; |
| |
| str = g_malloc (length); |
| g_assert (quote == '"' || quote == '\''); |
| j = 0; |
| i = 1; |
| while (token[i] != quote) |
| switch (token[i]) |
| { |
| case '\0': |
| parser_set_error (error, &ref, NULL, |
| G_VARIANT_PARSE_ERROR_UNTERMINATED_STRING_CONSTANT, |
| "unterminated string constant"); |
| g_free (token); |
| g_free (str); |
| return NULL; |
| |
| case '\\': |
| switch (token[++i]) |
| { |
| case '\0': |
| parser_set_error (error, &ref, NULL, |
| G_VARIANT_PARSE_ERROR_UNTERMINATED_STRING_CONSTANT, |
| "unterminated string constant"); |
| g_free (token); |
| g_free (str); |
| return NULL; |
| |
| case 'u': |
| if (!unicode_unescape (token, &i, str, &j, 4, &ref, error)) |
| { |
| g_free (token); |
| g_free (str); |
| return NULL; |
| } |
| continue; |
| |
| case 'U': |
| if (!unicode_unescape (token, &i, str, &j, 8, &ref, error)) |
| { |
| g_free (token); |
| g_free (str); |
| return NULL; |
| } |
| continue; |
| |
| case 'a': str[j++] = '\a'; i++; continue; |
| case 'b': str[j++] = '\b'; i++; continue; |
| case 'f': str[j++] = '\f'; i++; continue; |
| case 'n': str[j++] = '\n'; i++; continue; |
| case 'r': str[j++] = '\r'; i++; continue; |
| case 't': str[j++] = '\t'; i++; continue; |
| case 'v': str[j++] = '\v'; i++; continue; |
| case '\n': i++; continue; |
| } |
| |
| G_GNUC_FALLTHROUGH; |
| |
| default: |
| str[j++] = token[i++]; |
| } |
| str[j++] = '\0'; |
| g_free (token); |
| |
| string = g_slice_new (String); |
| string->ast.class = &string_class; |
| string->string = str; |
| |
| token_stream_next (stream); |
| |
| return (AST *) string; |
| } |
| |
| typedef struct |
| { |
| AST ast; |
| gchar *string; |
| } ByteString; |
| |
| static gchar * |
| bytestring_get_pattern (AST *ast, |
| GError **error) |
| { |
| return g_strdup ("May"); |
| } |
| |
| static GVariant * |
| bytestring_get_value (AST *ast, |
| const GVariantType *type, |
| GError **error) |
| { |
| ByteString *string = (ByteString *) ast; |
| |
| if (!g_variant_type_equal (type, G_VARIANT_TYPE_BYTESTRING)) |
| return ast_type_error (ast, type, error); |
| |
| return g_variant_new_bytestring (string->string); |
| } |
| |
| static void |
| bytestring_free (AST *ast) |
| { |
| ByteString *string = (ByteString *) ast; |
| |
| g_free (string->string); |
| g_slice_free (ByteString, string); |
| } |
| |
| static AST * |
| bytestring_parse (TokenStream *stream, |
| va_list *app, |
| GError **error) |
| { |
| static const ASTClass bytestring_class = { |
| bytestring_get_pattern, |
| maybe_wrapper, bytestring_get_value, |
| bytestring_free |
| }; |
| ByteString *string; |
| SourceRef ref; |
| gchar *token; |
| gsize length; |
| gchar quote; |
| gchar *str; |
| gint i, j; |
| |
| token_stream_start_ref (stream, &ref); |
| token = token_stream_get (stream); |
| token_stream_end_ref (stream, &ref); |
| g_assert (token[0] == 'b'); |
| length = strlen (token); |
| quote = token[1]; |
| |
| str = g_malloc (length); |
| g_assert (quote == '"' || quote == '\''); |
| j = 0; |
| i = 2; |
| while (token[i] != quote) |
| switch (token[i]) |
| { |
| case '\0': |
| parser_set_error (error, &ref, NULL, |
| G_VARIANT_PARSE_ERROR_UNTERMINATED_STRING_CONSTANT, |
| "unterminated string constant"); |
| g_free (str); |
| g_free (token); |
| return NULL; |
| |
| case '\\': |
| switch (token[++i]) |
| { |
| case '\0': |
| parser_set_error (error, &ref, NULL, |
| G_VARIANT_PARSE_ERROR_UNTERMINATED_STRING_CONSTANT, |
| "unterminated string constant"); |
| g_free (str); |
| g_free (token); |
| return NULL; |
| |
| case '0': case '1': case '2': case '3': |
| case '4': case '5': case '6': case '7': |
| { |
| /* up to 3 characters */ |
| guchar val = token[i++] - '0'; |
| |
| if ('0' <= token[i] && token[i] < '8') |
| val = (val << 3) | (token[i++] - '0'); |
| |
| if ('0' <= token[i] && token[i] < '8') |
| val = (val << 3) | (token[i++] - '0'); |
| |
| str[j++] = val; |
| } |
| continue; |
| |
| case 'a': str[j++] = '\a'; i++; continue; |
| case 'b': str[j++] = '\b'; i++; continue; |
| case 'f': str[j++] = '\f'; i++; continue; |
| case 'n': str[j++] = '\n'; i++; continue; |
| case 'r': str[j++] = '\r'; i++; continue; |
| case 't': str[j++] = '\t'; i++; continue; |
| case 'v': str[j++] = '\v'; i++; continue; |
| case '\n': i++; continue; |
| } |
| |
| G_GNUC_FALLTHROUGH; |
| |
| default: |
| str[j++] = token[i++]; |
| } |
| str[j++] = '\0'; |
| g_free (token); |
| |
| string = g_slice_new (ByteString); |
| string->ast.class = &bytestring_class; |
| string->string = str; |
| |
| token_stream_next (stream); |
| |
| return (AST *) string; |
| } |
| |
| typedef struct |
| { |
| AST ast; |
| |
| gchar *token; |
| } Number; |
| |
| static gchar * |
| number_get_pattern (AST *ast, |
| GError **error) |
| { |
| Number *number = (Number *) ast; |
| |
| if (strchr (number->token, '.') || |
| (!g_str_has_prefix (number->token, "0x") && strchr (number->token, 'e')) || |
| strstr (number->token, "inf") || |
| strstr (number->token, "nan")) |
| return g_strdup ("Md"); |
| |
| return g_strdup ("MN"); |
| } |
| |
| static GVariant * |
| number_overflow (AST *ast, |
| const GVariantType *type, |
| GError **error) |
| { |
| ast_set_error (ast, error, NULL, |
| G_VARIANT_PARSE_ERROR_NUMBER_OUT_OF_RANGE, |
| "number out of range for type '%c'", |
| g_variant_type_peek_string (type)[0]); |
| return NULL; |
| } |
| |
| static GVariant * |
| number_get_value (AST *ast, |
| const GVariantType *type, |
| GError **error) |
| { |
| Number *number = (Number *) ast; |
| const gchar *token; |
| gboolean negative; |
| gboolean floating; |
| guint64 abs_val; |
| gdouble dbl_val; |
| gchar *end; |
| |
| token = number->token; |
| |
| if (g_variant_type_equal (type, G_VARIANT_TYPE_DOUBLE)) |
| { |
| floating = TRUE; |
| |
| errno = 0; |
| dbl_val = g_ascii_strtod (token, &end); |
| if (dbl_val != 0.0 && errno == ERANGE) |
| { |
| ast_set_error (ast, error, NULL, |
| G_VARIANT_PARSE_ERROR_NUMBER_TOO_BIG, |
| "number too big for any type"); |
| return NULL; |
| } |
| |
| /* silence uninitialised warnings... */ |
| negative = FALSE; |
| abs_val = 0; |
| } |
| else |
| { |
| floating = FALSE; |
| negative = token[0] == '-'; |
| if (token[0] == '-') |
| token++; |
| |
| errno = 0; |
| abs_val = g_ascii_strtoull (token, &end, 0); |
| if (abs_val == G_MAXUINT64 && errno == ERANGE) |
| { |
| ast_set_error (ast, error, NULL, |
| G_VARIANT_PARSE_ERROR_NUMBER_TOO_BIG, |
| "integer too big for any type"); |
| return NULL; |
| } |
| |
| if (abs_val == 0) |
| negative = FALSE; |
| |
| /* silence uninitialised warning... */ |
| dbl_val = 0.0; |
| } |
| |
| if (*end != '\0') |
| { |
| SourceRef ref; |
| |
| ref = ast->source_ref; |
| ref.start += end - number->token; |
| ref.end = ref.start + 1; |
| |
| parser_set_error (error, &ref, NULL, |
| G_VARIANT_PARSE_ERROR_INVALID_CHARACTER, |
| "invalid character in number"); |
| return NULL; |
| } |
| |
| if (floating) |
| return g_variant_new_double (dbl_val); |
| |
| switch (*g_variant_type_peek_string (type)) |
| { |
| case 'y': |
| if (negative || abs_val > G_MAXUINT8) |
| return number_overflow (ast, type, error); |
| return g_variant_new_byte (abs_val); |
| |
| case 'n': |
| if (abs_val - negative > G_MAXINT16) |
| return number_overflow (ast, type, error); |
| if (negative && abs_val > G_MAXINT16) |
| return g_variant_new_int16 (G_MININT16); |
| return g_variant_new_int16 (negative ? |
| -((gint16) abs_val) : ((gint16) abs_val)); |
| |
| case 'q': |
| if (negative || abs_val > G_MAXUINT16) |
| return number_overflow (ast, type, error); |
| return g_variant_new_uint16 (abs_val); |
| |
| case 'i': |
| if (abs_val - negative > G_MAXINT32) |
| return number_overflow (ast, type, error); |
| if (negative && abs_val > G_MAXINT32) |
| return g_variant_new_int32 (G_MININT32); |
| return g_variant_new_int32 (negative ? |
| -((gint32) abs_val) : ((gint32) abs_val)); |
| |
| case 'u': |
| if (negative || abs_val > G_MAXUINT32) |
| return number_overflow (ast, type, error); |
| return g_variant_new_uint32 (abs_val); |
| |
| case 'x': |
| if (abs_val - negative > G_MAXINT64) |
| return number_overflow (ast, type, error); |
| if (negative && abs_val > G_MAXINT64) |
| return g_variant_new_int64 (G_MININT64); |
| return g_variant_new_int64 (negative ? |
| -((gint64) abs_val) : ((gint64) abs_val)); |
| |
| case 't': |
| if (negative) |
| return number_overflow (ast, type, error); |
| return g_variant_new_uint64 (abs_val); |
| |
| case 'h': |
| if (abs_val - negative > G_MAXINT32) |
| return number_overflow (ast, type, error); |
| if (negative && abs_val > G_MAXINT32) |
| return g_variant_new_handle (G_MININT32); |
| return g_variant_new_handle (negative ? |
| -((gint32) abs_val) : ((gint32) abs_val)); |
| |
| default: |
| return ast_type_error (ast, type, error); |
| } |
| } |
| |
| static void |
| number_free (AST *ast) |
| { |
| Number *number = (Number *) ast; |
| |
| g_free (number->token); |
| g_slice_free (Number, number); |
| } |
| |
| static AST * |
| number_parse (TokenStream *stream, |
| va_list *app, |
| GError **error) |
| { |
| static const ASTClass number_class = { |
| number_get_pattern, |
| maybe_wrapper, number_get_value, |
| number_free |
| }; |
| Number *number; |
| |
| number = g_slice_new (Number); |
| number->ast.class = &number_class; |
| number->token = token_stream_get (stream); |
| token_stream_next (stream); |
| |
| return (AST *) number; |
| } |
| |
| typedef struct |
| { |
| AST ast; |
| gboolean value; |
| } Boolean; |
| |
| static gchar * |
| boolean_get_pattern (AST *ast, |
| GError **error) |
| { |
| return g_strdup ("Mb"); |
| } |
| |
| static GVariant * |
| boolean_get_value (AST *ast, |
| const GVariantType *type, |
| GError **error) |
| { |
| Boolean *boolean = (Boolean *) ast; |
| |
| if (!g_variant_type_equal (type, G_VARIANT_TYPE_BOOLEAN)) |
| return ast_type_error (ast, type, error); |
| |
| return g_variant_new_boolean (boolean->value); |
| } |
| |
| static void |
| boolean_free (AST *ast) |
| { |
| Boolean *boolean = (Boolean *) ast; |
| |
| g_slice_free (Boolean, boolean); |
| } |
| |
| static AST * |
| boolean_new (gboolean value) |
| { |
| static const ASTClass boolean_class = { |
| boolean_get_pattern, |
| maybe_wrapper, boolean_get_value, |
| boolean_free |
| }; |
| Boolean *boolean; |
| |
| boolean = g_slice_new (Boolean); |
| boolean->ast.class = &boolean_class; |
| boolean->value = value; |
| |
| return (AST *) boolean; |
| } |
| |
| typedef struct |
| { |
| AST ast; |
| |
| GVariant *value; |
| } Positional; |
| |
| static gchar * |
| positional_get_pattern (AST *ast, |
| GError **error) |
| { |
| Positional *positional = (Positional *) ast; |
| |
| return g_strdup (g_variant_get_type_string (positional->value)); |
| } |
| |
| static GVariant * |
| positional_get_value (AST *ast, |
| const GVariantType *type, |
| GError **error) |
| { |
| Positional *positional = (Positional *) ast; |
| GVariant *value; |
| |
| g_assert (positional->value != NULL); |
| |
| if G_UNLIKELY (!g_variant_is_of_type (positional->value, type)) |
| return ast_type_error (ast, type, error); |
| |
| /* NOTE: if _get is called more than once then |
| * things get messed up with respect to floating refs. |
| * |
| * fortunately, this function should only ever get called once. |
| */ |
| g_assert (positional->value != NULL); |
| value = positional->value; |
| positional->value = NULL; |
| |
| return value; |
| } |
| |
| static void |
| positional_free (AST *ast) |
| { |
| Positional *positional = (Positional *) ast; |
| |
| /* if positional->value is set, just leave it. |
| * memory management doesn't matter in case of programmer error. |
| */ |
| g_slice_free (Positional, positional); |
| } |
| |
| static AST * |
| positional_parse (TokenStream *stream, |
| va_list *app, |
| GError **error) |
| { |
| static const ASTClass positional_class = { |
| positional_get_pattern, |
| positional_get_value, NULL, |
| positional_free |
| }; |
| Positional *positional; |
| const gchar *endptr; |
| gchar *token; |
| |
| token = token_stream_get (stream); |
| g_assert (token[0] == '%'); |
| |
| positional = g_slice_new (Positional); |
| positional->ast.class = &positional_class; |
| positional->value = g_variant_new_va (token + 1, &endptr, app); |
| |
| if (*endptr || positional->value == NULL) |
| { |
| token_stream_set_error (stream, error, TRUE, |
| G_VARIANT_PARSE_ERROR_INVALID_FORMAT_STRING, |
| "invalid GVariant format string"); |
| /* memory management doesn't matter in case of programmer error. */ |
| return NULL; |
| } |
| |
| token_stream_next (stream); |
| g_free (token); |
| |
| return (AST *) positional; |
| } |
| |
| typedef struct |
| { |
| AST ast; |
| |
| GVariantType *type; |
| AST *child; |
| } TypeDecl; |
| |
| static gchar * |
| typedecl_get_pattern (AST *ast, |
| GError **error) |
| { |
| TypeDecl *decl = (TypeDecl *) ast; |
| |
| return g_variant_type_dup_string (decl->type); |
| } |
| |
| static GVariant * |
| typedecl_get_value (AST *ast, |
| const GVariantType *type, |
| GError **error) |
| { |
| TypeDecl *decl = (TypeDecl *) ast; |
| |
| return ast_get_value (decl->child, type, error); |
| } |
| |
| static void |
| typedecl_free (AST *ast) |
| { |
| TypeDecl *decl = (TypeDecl *) ast; |
| |
| ast_free (decl->child); |
| g_variant_type_free (decl->type); |
| g_slice_free (TypeDecl, decl); |
| } |
| |
| static AST * |
| typedecl_parse (TokenStream *stream, |
| guint max_depth, |
| va_list *app, |
| GError **error) |
| { |
| static const ASTClass typedecl_class = { |
| typedecl_get_pattern, |
| typedecl_get_value, NULL, |
| typedecl_free |
| }; |
| GVariantType *type; |
| TypeDecl *decl; |
| AST *child; |
| |
| if (token_stream_peek (stream, '@')) |
| { |
| gchar *token; |
| |
| token = token_stream_get (stream); |
| |
| if (!g_variant_type_string_is_valid (token + 1)) |
| { |
| token_stream_set_error (stream, error, TRUE, |
| G_VARIANT_PARSE_ERROR_INVALID_TYPE_STRING, |
| "invalid type declaration"); |
| g_free (token); |
| |
| return NULL; |
| } |
| |
| if (g_variant_type_string_get_depth_ (token + 1) > max_depth) |
| { |
| token_stream_set_error (stream, error, TRUE, |
| G_VARIANT_PARSE_ERROR_RECURSION, |
| "type declaration recurses too deeply"); |
| g_free (token); |
| |
| return NULL; |
| } |
| |
| type = g_variant_type_new (token + 1); |
| |
| if (!g_variant_type_is_definite (type)) |
| { |
| token_stream_set_error (stream, error, TRUE, |
| G_VARIANT_PARSE_ERROR_DEFINITE_TYPE_EXPECTED, |
| "type declarations must be definite"); |
| g_variant_type_free (type); |
| g_free (token); |
| |
| return NULL; |
| } |
| |
| token_stream_next (stream); |
| g_free (token); |
| } |
| else |
| { |
| if (token_stream_consume (stream, "boolean")) |
| type = g_variant_type_copy (G_VARIANT_TYPE_BOOLEAN); |
| |
| else if (token_stream_consume (stream, "byte")) |
| type = g_variant_type_copy (G_VARIANT_TYPE_BYTE); |
| |
| else if (token_stream_consume (stream, "int16")) |
| type = g_variant_type_copy (G_VARIANT_TYPE_INT16); |
| |
| else if (token_stream_consume (stream, "uint16")) |
| type = g_variant_type_copy (G_VARIANT_TYPE_UINT16); |
| |
| else if (token_stream_consume (stream, "int32")) |
| type = g_variant_type_copy (G_VARIANT_TYPE_INT32); |
| |
| else if (token_stream_consume (stream, "handle")) |
| type = g_variant_type_copy (G_VARIANT_TYPE_HANDLE); |
| |
| else if (token_stream_consume (stream, "uint32")) |
| type = g_variant_type_copy (G_VARIANT_TYPE_UINT32); |
| |
| else if (token_stream_consume (stream, "int64")) |
| type = g_variant_type_copy (G_VARIANT_TYPE_INT64); |
| |
| else if (token_stream_consume (stream, "uint64")) |
| type = g_variant_type_copy (G_VARIANT_TYPE_UINT64); |
| |
| else if (token_stream_consume (stream, "double")) |
| type = g_variant_type_copy (G_VARIANT_TYPE_DOUBLE); |
| |
| else if (token_stream_consume (stream, "string")) |
| type = g_variant_type_copy (G_VARIANT_TYPE_STRING); |
| |
| else if (token_stream_consume (stream, "objectpath")) |
| type = g_variant_type_copy (G_VARIANT_TYPE_OBJECT_PATH); |
| |
| else if (token_stream_consume (stream, "signature")) |
| type = g_variant_type_copy (G_VARIANT_TYPE_SIGNATURE); |
| |
| else |
| { |
| token_stream_set_error (stream, error, TRUE, |
| G_VARIANT_PARSE_ERROR_UNKNOWN_KEYWORD, |
| "unknown keyword"); |
| return NULL; |
| } |
| } |
| |
| if ((child = parse (stream, max_depth - 1, app, error)) == NULL) |
| { |
| g_variant_type_free (type); |
| return NULL; |
| } |
| |
| decl = g_slice_new (TypeDecl); |
| decl->ast.class = &typedecl_class; |
| decl->type = type; |
| decl->child = child; |
| |
| return (AST *) decl; |
| } |
| |
| static AST * |
| parse (TokenStream *stream, |
| guint max_depth, |
| va_list *app, |
| GError **error) |
| { |
| SourceRef source_ref; |
| AST *result; |
| |
| if (max_depth == 0) |
| { |
| token_stream_set_error (stream, error, FALSE, |
| G_VARIANT_PARSE_ERROR_RECURSION, |
| "variant nested too deeply"); |
| return NULL; |
| } |
| |
| token_stream_prepare (stream); |
| token_stream_start_ref (stream, &source_ref); |
| |
| if (token_stream_peek (stream, '[')) |
| result = array_parse (stream, max_depth, app, error); |
| |
| else if (token_stream_peek (stream, '(')) |
| result = tuple_parse (stream, max_depth, app, error); |
| |
| else if (token_stream_peek (stream, '<')) |
| result = variant_parse (stream, max_depth, app, error); |
| |
| else if (token_stream_peek (stream, '{')) |
| result = dictionary_parse (stream, max_depth, app, error); |
| |
| else if (app && token_stream_peek (stream, '%')) |
| result = positional_parse (stream, app, error); |
| |
| else if (token_stream_consume (stream, "true")) |
| result = boolean_new (TRUE); |
| |
| else if (token_stream_consume (stream, "false")) |
| result = boolean_new (FALSE); |
| |
| else if (token_stream_is_numeric (stream) || |
| token_stream_peek_string (stream, "inf") || |
| token_stream_peek_string (stream, "nan")) |
| result = number_parse (stream, app, error); |
| |
| else if (token_stream_peek (stream, 'n') || |
| token_stream_peek (stream, 'j')) |
| result = maybe_parse (stream, max_depth, app, error); |
| |
| else if (token_stream_peek (stream, '@') || |
| token_stream_is_keyword (stream)) |
| result = typedecl_parse (stream, max_depth, app, error); |
| |
| else if (token_stream_peek (stream, '\'') || |
| token_stream_peek (stream, '"')) |
| result = string_parse (stream, app, error); |
| |
| else if (token_stream_peek2 (stream, 'b', '\'') || |
| token_stream_peek2 (stream, 'b', '"')) |
| result = bytestring_parse (stream, app, error); |
| |
| else |
| { |
| token_stream_set_error (stream, error, FALSE, |
| G_VARIANT_PARSE_ERROR_VALUE_EXPECTED, |
| "expected value"); |
| return NULL; |
| } |
| |
| if (result != NULL) |
| { |
| token_stream_end_ref (stream, &source_ref); |
| result->source_ref = source_ref; |
| } |
| |
| return result; |
| } |
| |
| /** |
| * g_variant_parse: |
| * @type: (nullable): a #GVariantType, or %NULL |
| * @text: a string containing a GVariant in text form |
| * @limit: (nullable): a pointer to the end of @text, or %NULL |
| * @endptr: (nullable): a location to store the end pointer, or %NULL |
| * @error: (nullable): a pointer to a %NULL #GError pointer, or %NULL |
| * |
| * Parses a #GVariant from a text representation. |
| * |
| * A single #GVariant is parsed from the content of @text. |
| * |
| * The format is described [here](gvariant-text-format.html). |
| * |
| * The memory at @limit will never be accessed and the parser behaves as |
| * if the character at @limit is the nul terminator. This has the |
| * effect of bounding @text. |
| * |
| * If @endptr is non-%NULL then @text is permitted to contain data |
| * following the value that this function parses and @endptr will be |
| * updated to point to the first character past the end of the text |
| * parsed by this function. If @endptr is %NULL and there is extra data |
| * then an error is returned. |
| * |
| * If @type is non-%NULL then the value will be parsed to have that |
| * type. This may result in additional parse errors (in the case that |
| * the parsed value doesn't fit the type) but may also result in fewer |
| * errors (in the case that the type would have been ambiguous, such as |
| * with empty arrays). |
| * |
| * In the event that the parsing is successful, the resulting #GVariant |
| * is returned. It is never floating, and must be freed with |
| * [method@GLib.Variant.unref]. |
| * |
| * In case of any error, %NULL will be returned. If @error is non-%NULL |
| * then it will be set to reflect the error that occurred. |
| * |
| * Officially, the language understood by the parser is “any string |
| * produced by [method@GLib.Variant.print]”. This explicitly includes |
| * `g_variant_print()`’s annotated types like `int64 -1000`. |
| * |
| * There may be implementation specific restrictions on deeply nested values, |
| * which would result in a %G_VARIANT_PARSE_ERROR_RECURSION error. #GVariant is |
| * guaranteed to handle nesting up to at least 64 levels. |
| * |
| * Returns: a non-floating reference to a #GVariant, or %NULL |
| **/ |
| GVariant * |
| g_variant_parse (const GVariantType *type, |
| const gchar *text, |
| const gchar *limit, |
| const gchar **endptr, |
| GError **error) |
| { |
| TokenStream stream = { 0, }; |
| GVariant *result = NULL; |
| AST *ast; |
| |
| g_return_val_if_fail (text != NULL, NULL); |
| g_return_val_if_fail (text == limit || text != NULL, NULL); |
| |
| stream.start = text; |
| stream.stream = text; |
| stream.end = limit; |
| |
| if ((ast = parse (&stream, G_VARIANT_MAX_RECURSION_DEPTH, NULL, error))) |
| { |
| if (type == NULL) |
| result = ast_resolve (ast, error); |
| else |
| result = ast_get_value (ast, type, error); |
| |
| if (result != NULL) |
| { |
| g_variant_ref_sink (result); |
| |
| if (endptr == NULL) |
| { |
| while (stream.stream != limit && |
| g_ascii_isspace (*stream.stream)) |
| stream.stream++; |
| |
| if (stream.stream != limit && *stream.stream != '\0') |
| { |
| SourceRef ref = { stream.stream - text, |
| stream.stream - text }; |
| |
| parser_set_error (error, &ref, NULL, |
| G_VARIANT_PARSE_ERROR_INPUT_NOT_AT_END, |
| "expected end of input"); |
| g_variant_unref (result); |
| |
| result = NULL; |
| } |
| } |
| else |
| *endptr = stream.stream; |
| } |
| |
| ast_free (ast); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * g_variant_new_parsed_va: |
| * @format: a text format #GVariant |
| * @app: a pointer to a #va_list |
| * |
| * Parses @format and returns the result. |
| * |
| * This is the version of g_variant_new_parsed() intended to be used |
| * from libraries. |
| * |
| * The return value will be floating if it was a newly created GVariant |
| * instance. In the case that @format simply specified the collection |
| * of a #GVariant pointer (eg: @format was "%*") then the collected |
| * #GVariant pointer will be returned unmodified, without adding any |
| * additional references. |
| * |
| * Note that the arguments in @app must be of the correct width for their types |
| * specified in @format when collected into the #va_list. See |
| * the [GVariant varargs documentation][gvariant-varargs]. |
| * |
| * In order to behave correctly in all cases it is necessary for the |
| * calling function to g_variant_ref_sink() the return result before |
| * returning control to the user that originally provided the pointer. |
| * At this point, the caller will have their own full reference to the |
| * result. This can also be done by adding the result to a container, |
| * or by passing it to another g_variant_new() call. |
| * |
| * Returns: a new, usually floating, #GVariant |
| **/ |
| GVariant * |
| g_variant_new_parsed_va (const gchar *format, |
| va_list *app) |
| { |
| TokenStream stream = { 0, }; |
| GVariant *result = NULL; |
| GError *error = NULL; |
| AST *ast; |
| |
| g_return_val_if_fail (format != NULL, NULL); |
| g_return_val_if_fail (app != NULL, NULL); |
| |
| stream.start = format; |
| stream.stream = format; |
| stream.end = NULL; |
| |
| if ((ast = parse (&stream, G_VARIANT_MAX_RECURSION_DEPTH, app, &error))) |
| { |
| result = ast_resolve (ast, &error); |
| ast_free (ast); |
| } |
| |
| if (error != NULL) |
| g_error ("g_variant_new_parsed: %s", error->message); |
| |
| if (*stream.stream) |
| g_error ("g_variant_new_parsed: trailing text after value"); |
| |
| g_clear_error (&error); |
| |
| return result; |
| } |
| |
| /** |
| * g_variant_new_parsed: |
| * @format: a text format #GVariant |
| * @...: arguments as per @format |
| * |
| * Parses @format and returns the result. |
| * |
| * @format must be a text format #GVariant with one extension: at any |
| * point that a value may appear in the text, a '%' character followed |
| * by a GVariant format string (as per g_variant_new()) may appear. In |
| * that case, the same arguments are collected from the argument list as |
| * g_variant_new() would have collected. |
| * |
| * Note that the arguments must be of the correct width for their types |
| * specified in @format. This can be achieved by casting them. See |
| * the [GVariant varargs documentation][gvariant-varargs]. |
| * |
| * Consider this simple example: |
| * |[<!-- language="C" --> |
| * g_variant_new_parsed ("[('one', 1), ('two', %i), (%s, 3)]", 2, "three"); |
| * ]| |
| * |
| * In the example, the variable argument parameters are collected and |
| * filled in as if they were part of the original string to produce the |
| * result of |
| * |[<!-- language="C" --> |
| * [('one', 1), ('two', 2), ('three', 3)] |
| * ]| |
| * |
| * This function is intended only to be used with @format as a string |
| * literal. Any parse error is fatal to the calling process. If you |
| * want to parse data from untrusted sources, use g_variant_parse(). |
| * |
| * You may not use this function to return, unmodified, a single |
| * #GVariant pointer from the argument list. ie: @format may not solely |
| * be anything along the lines of "%*", "%?", "\%r", or anything starting |
| * with "%@". |
| * |
| * Returns: a new floating #GVariant instance |
| **/ |
| GVariant * |
| g_variant_new_parsed (const gchar *format, |
| ...) |
| { |
| GVariant *result; |
| va_list ap; |
| |
| va_start (ap, format); |
| result = g_variant_new_parsed_va (format, &ap); |
| va_end (ap); |
| |
| return result; |
| } |
| |
| /** |
| * g_variant_builder_add_parsed: |
| * @builder: a #GVariantBuilder |
| * @format: a text format #GVariant |
| * @...: arguments as per @format |
| * |
| * Adds to a #GVariantBuilder. |
| * |
| * This call is a convenience wrapper that is exactly equivalent to |
| * calling g_variant_new_parsed() followed by |
| * g_variant_builder_add_value(). |
| * |
| * Note that the arguments must be of the correct width for their types |
| * specified in @format_string. This can be achieved by casting them. See |
| * the [GVariant varargs documentation][gvariant-varargs]. |
| * |
| * This function might be used as follows: |
| * |
| * |[<!-- language="C" --> |
| * GVariant * |
| * make_pointless_dictionary (void) |
| * { |
| * GVariantBuilder builder; |
| * int i; |
| * |
| * g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY); |
| * g_variant_builder_add_parsed (&builder, "{'width', <%i>}", 600); |
| * g_variant_builder_add_parsed (&builder, "{'title', <%s>}", "foo"); |
| * g_variant_builder_add_parsed (&builder, "{'transparency', <0.5>}"); |
| * return g_variant_builder_end (&builder); |
| * } |
| * ]| |
| * |
| * Since: 2.26 |
| */ |
| void |
| g_variant_builder_add_parsed (GVariantBuilder *builder, |
| const gchar *format, |
| ...) |
| { |
| va_list ap; |
| |
| va_start (ap, format); |
| g_variant_builder_add_value (builder, g_variant_new_parsed_va (format, &ap)); |
| va_end (ap); |
| } |
| |
| static gboolean |
| parse_num (const gchar *num, |
| const gchar *limit, |
| guint *result) |
| { |
| gchar *endptr; |
| gint64 bignum; |
| |
| bignum = g_ascii_strtoll (num, &endptr, 10); |
| |
| if (endptr != limit) |
| return FALSE; |
| |
| if (bignum < 0 || bignum > G_MAXINT) |
| return FALSE; |
| |
| *result = (guint) bignum; |
| |
| return TRUE; |
| } |
| |
| static void |
| add_last_line (GString *err, |
| const gchar *str) |
| { |
| const gchar *last_nl; |
| gchar *chomped; |
| gint i; |
| |
| /* This is an error at the end of input. If we have a file |
| * with newlines, that's probably the empty string after the |
| * last newline, which is not the most useful thing to show. |
| * |
| * Instead, show the last line of non-whitespace that we have |
| * and put the pointer at the end of it. |
| */ |
| chomped = g_strchomp (g_strdup (str)); |
| last_nl = strrchr (chomped, '\n'); |
| if (last_nl == NULL) |
| last_nl = chomped; |
| else |
| last_nl++; |
| |
| /* Print the last line like so: |
| * |
| * [1, 2, 3, |
| * ^ |
| */ |
| g_string_append (err, " "); |
| if (last_nl[0]) |
| g_string_append (err, last_nl); |
| else |
| g_string_append (err, "(empty input)"); |
| g_string_append (err, "\n "); |
| for (i = 0; last_nl[i]; i++) |
| g_string_append_c (err, ' '); |
| g_string_append (err, "^\n"); |
| g_free (chomped); |
| } |
| |
| static void |
| add_lines_from_range (GString *err, |
| const gchar *str, |
| const gchar *start1, |
| const gchar *end1, |
| const gchar *start2, |
| const gchar *end2) |
| { |
| while (str < end1 || str < end2) |
| { |
| const gchar *nl; |
| |
| nl = str + strcspn (str, "\n"); |
| |
| if ((start1 < nl && str < end1) || (start2 < nl && str < end2)) |
| { |
| const gchar *s; |
| |
| /* We're going to print this line */ |
| g_string_append (err, " "); |
| g_string_append_len (err, str, nl - str); |
| g_string_append (err, "\n "); |
| |
| /* And add underlines... */ |
| for (s = str; s < nl; s++) |
| { |
| if ((start1 <= s && s < end1) || (start2 <= s && s < end2)) |
| g_string_append_c (err, '^'); |
| else |
| g_string_append_c (err, ' '); |
| } |
| g_string_append_c (err, '\n'); |
| } |
| |
| if (!*nl) |
| break; |
| |
| str = nl + 1; |
| } |
| } |
| |
| /** |
| * g_variant_parse_error_print_context: |
| * @error: a #GError from the #GVariantParseError domain |
| * @source_str: the string that was given to the parser |
| * |
| * Pretty-prints a message showing the context of a #GVariant parse |
| * error within the string for which parsing was attempted. |
| * |
| * The resulting string is suitable for output to the console or other |
| * monospace media where newlines are treated in the usual way. |
| * |
| * The message will typically look something like one of the following: |
| * |
| * |[ |
| * unterminated string constant: |
| * (1, 2, 3, 'abc |
| * ^^^^ |
| * ]| |
| * |
| * or |
| * |
| * |[ |
| * unable to find a common type: |
| * [1, 2, 3, 'str'] |
| * ^ ^^^^^ |
| * ]| |
| * |
| * The format of the message may change in a future version. |
| * |
| * @error must have come from a failed attempt to g_variant_parse() and |
| * @source_str must be exactly the same string that caused the error. |
| * If @source_str was not nul-terminated when you passed it to |
| * g_variant_parse() then you must add nul termination before using this |
| * function. |
| * |
| * Returns: (transfer full): the printed message |
| * |
| * Since: 2.40 |
| **/ |
| gchar * |
| g_variant_parse_error_print_context (GError *error, |
| const gchar *source_str) |
| { |
| const gchar *colon, *dash, *comma; |
| gboolean success = FALSE; |
| GString *err; |
| |
| g_return_val_if_fail (error->domain == G_VARIANT_PARSE_ERROR, FALSE); |
| |
| /* We can only have a limited number of possible types of ranges |
| * emitted from the parser: |
| * |
| * - a: -- usually errors from the tokeniser (eof, invalid char, etc.) |
| * - a-b: -- usually errors from handling one single token |
| * - a-b,c-d: -- errors involving two tokens (ie: type inferencing) |
| * |
| * We never see, for example "a,c". |
| */ |
| |
| colon = strchr (error->message, ':'); |
| dash = strchr (error->message, '-'); |
| comma = strchr (error->message, ','); |
| |
| if (!colon) |
| return NULL; |
| |
| err = g_string_new (colon + 1); |
| g_string_append (err, ":\n"); |
| |
| if (dash == NULL || colon < dash) |
| { |
| guint point; |
| |
| /* we have a single point */ |
| if (!parse_num (error->message, colon, &point)) |
| goto out; |
| |
| if (point >= strlen (source_str)) |
| /* the error is at the end of the input */ |
| add_last_line (err, source_str); |
| else |
| /* otherwise just treat it as an error at a thin range */ |
| add_lines_from_range (err, source_str, source_str + point, source_str + point + 1, NULL, NULL); |
| } |
| else |
| { |
| /* We have one or two ranges... */ |
| if (comma && comma < colon) |
| { |
| guint start1, end1, start2, end2; |
| const gchar *dash2; |
| |
| /* Two ranges */ |
| dash2 = strchr (comma, '-'); |
| |
| if (!parse_num (error->message, dash, &start1) || !parse_num (dash + 1, comma, &end1) || |
| !parse_num (comma + 1, dash2, &start2) || !parse_num (dash2 + 1, colon, &end2)) |
| goto out; |
| |
| add_lines_from_range (err, source_str, |
| source_str + start1, source_str + end1, |
| source_str + start2, source_str + end2); |
| } |
| else |
| { |
| guint start, end; |
| |
| /* One range */ |
| if (!parse_num (error->message, dash, &start) || !parse_num (dash + 1, colon, &end)) |
| goto out; |
| |
| add_lines_from_range (err, source_str, source_str + start, source_str + end, NULL, NULL); |
| } |
| } |
| |
| success = TRUE; |
| |
| out: |
| return g_string_free (err, !success); |
| } |