mirror of https://gitee.com/openkylin/glib2.0.git
2956 lines
76 KiB
C
2956 lines
76 KiB
C
/*
|
||
* 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);
|
||
}
|