summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilip Withnall <philip.withnall@collabora.co.uk>2014-05-01 19:42:04 +0200
committerPhilip Withnall <philip.withnall@collabora.co.uk>2014-05-03 18:34:28 +0200
commit2da365c985d90096aa82efefa9bbacb3b5a3962a (patch)
treeb32b0ffb1a652a5392ce1091df4be755cc0a9c6a
parent8df49aaf1e335f70b6ed8626cd05069b293bbb75 (diff)
clang-plugin: Add support for a GVariant checker
This checker will examine the code for GVariant method calls, such as those to g_variant_new(), and will proceed to check the types of the variadic arguments in the call against the GVariant format string in the call (assuming this format string is constant). For GVariant methods such as g_variant_new_va(), which take a va_list rather than varargs, the validity of the GVariant format string is checked, but the va_list itself cannot be. There are a few limitations to the checker at the moment. These are noted in FIXME comments, but are minor and don’t affect correctness. (Implementing them would, however, make the checker more strict.) A test suite is included with over 100 tests for parsing valid and invalid g_variant_new() and g_variant_new_va() calls. The test suite uses a custom test harness which splits up the test file, adds boilerplate, and then attempts to compile it with gnome-clang enabled. It then compares the compiler output to an expected error message.
-rw-r--r--Makefile.am4
-rw-r--r--clang-plugin/gvariant-checker.cpp1007
-rw-r--r--clang-plugin/gvariant-checker.h59
-rw-r--r--clang-plugin/plugin.cpp3
-rw-r--r--configure.ac3
-rw-r--r--tests/Makefile.am11
-rw-r--r--tests/gvariant-new.c944
-rwxr-xr-xtests/wrapper-compiler-errors130
8 files changed, 2159 insertions, 2 deletions
diff --git a/Makefile.am b/Makefile.am
index 142e8fc..2123e22 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = . po
+SUBDIRS = . po tests
ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS}
@@ -24,6 +24,8 @@ clang_plugin_libclang_gnome_la_SOURCES = \
clang-plugin/gir-manager.h \
clang-plugin/gassert-attributes.cpp \
clang-plugin/gassert-attributes.h \
+ clang-plugin/gvariant-checker.cpp \
+ clang-plugin/gvariant-checker.h \
clang-plugin/nullability-checker.cpp \
clang-plugin/nullability-checker.h \
$(NULL)
diff --git a/clang-plugin/gvariant-checker.cpp b/clang-plugin/gvariant-checker.cpp
new file mode 100644
index 0000000..94356ee
--- /dev/null
+++ b/clang-plugin/gvariant-checker.cpp
@@ -0,0 +1,1007 @@
+/* -*- Mode: C++; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * gnome-clang
+ * Copyright © 2014 Philip Withnall
+ *
+ * gnome-clang is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * gnome-clang 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with gnome-clang. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Philip Withnall <philip@tecnocode.co.uk>
+ */
+
+/**
+ * GVariantVisitor:
+ *
+ * This is a checker for GVariant format strings and varargs. For GVariant
+ * methods which accept varargs, it validates the type and nullability of each
+ * vararg against the corresponding element in the GVariant format string (if
+ * a constant format string is provided — non-constant format strings cannot be
+ * validated, but the user should probably be using GVariantBuilder directly if
+ * they’re dynamically generating a format string).
+ *
+ * For GVariant methods with format strings but no varargs, the format string is
+ * validated.
+ *
+ * The format string is parsed and varargs are consumed in parallel. The static
+ * type of the varargs is used, so if a weird cast is used (e.g. casting a
+ * string literal to an integer and passing it to a ‘u’ format string), no error
+ * will be raised. One limitation on the current checker is that the types of
+ * GVariants passed in are not checked. e.g. No error is emitted for the
+ * following invalid code:
+ * g_variant_new ('@s', g_variant_new_boolean (FALSE));
+ *
+ * The checker is quite flexible, and a lot of its behaviour is controlled by
+ * the set of #VariantCheckFlags in use for the current part of the parse tree.
+ *
+ * The error messages produced by this checker should give as much context and
+ * guidance towards fixing the problem as possible. Empirically, it seems that
+ * the GVariant Format String documentation in GLib’s manual is used quite a
+ * lot, since people can’t memorise the format strings. Contextual help in the
+ * error messages should try to avoid this.
+ *
+ * FIXME: Future work could be to implement:
+ * • Reference counting validation of GVariants (might be better placed in a
+ * general reference counting checker).
+ * • GVariant print format parsing (for g_variant_new_parsed()).
+ * • Character-granularity error diagnostic locations, e.g. pointing to the
+ * erroneous character in a format string, not just to the start of the
+ * format string argument itself.
+ *
+ * FIXME: If Clang’s DiagnosticsEngine gains support for multiple
+ * SourceLocations, it would be great to highlight both the relevant character
+ * of the GVariant format string, and the erroneous variadic arguments in the
+ * function call, when an error is printed. At the moment we have to just pick
+ * the most important of the two and highlight that.
+ */
+
+#include <cstring>
+
+#include <clang/AST/Attr.h>
+
+#include <glib.h>
+
+#include "debug.h"
+#include "gvariant-checker.h"
+
+
+/* Information about the GVariant functions we’re interested in. If you want to
+ * add support for a new GVariant function, it may be enough to add a new
+ * element here. */
+typedef struct {
+ /* C name of the function */
+ const char *func_name;
+ /* Zero-based index of the GVariant format string parameter to the
+ * function; the validity of this string will be checked */
+ unsigned int format_param_index;
+ /* Zero-based index of the first varargs parameter or va_list. */
+ unsigned int first_vararg_param_index;
+ /* Whether the function takes a va_list instead of varargs. */
+ bool uses_va_list;
+} VariantFuncInfo;
+
+static const VariantFuncInfo gvariant_format_funcs[] = {
+ { "g_variant_new", 0, 1, false },
+ { "g_variant_new_va", 0, 2, true },
+/* TODO:
+ { "g_variant_get", 1, 2, false },
+ { "g_variant_get_va", 1, 3, true },
+ { "g_variant_get_child", 2, 3, false },
+ { "g_variant_lookup", 2, 3, false },
+ { "g_variant_iter_next", 1, 2, false },
+ { "g_variant_iter_loop", 1, 2, false },
+ { "g_variant_builder_add", 1, 2, false },
+ { "g_variant_builder_add_parsed", 1, 2, false },
+*/
+};
+
+/**
+ * VariantCheckFlags:
+ * @CHECK_FLAG_NONE: No flags.
+ * @CHECK_FLAG_FORCE_GVARIANT: Force the expected type of the next variadic
+ * argument to be consumed to be GVariant*.
+ * @CHECK_FLAG_FORCE_GVARIANTBUILDER: Force the expected type of the next
+ * variadic argument to be consumed to be GVariantBuilder*.
+ * @CHECK_FLAG_FORCE_VALIST: Force the expected type of the next variadic
+ * argument to be consumed to be va_list*.
+ * @CHECK_FLAG_ALLOW_MAYBE: Allow the next variadic argument to be consumed to
+ * be potentially %NULL.
+ * @CHECK_FLAG_CONSUME_ARGS: Consume variadic arguments when parsing. If this
+ * is not specified, the argument pointer will never be advanced, and all
+ * GVariant format strings for a given call will be checked against the same
+ * function argument.
+ *
+ * Flags affecting the parser and checker’s behaviour.
+ */
+typedef enum {
+ CHECK_FLAG_NONE = 0,
+ CHECK_FLAG_FORCE_GVARIANT = 1 << 0,
+ CHECK_FLAG_FORCE_GVARIANTBUILDER = 1 << 1,
+ CHECK_FLAG_FORCE_VALIST = 1 << 2,
+ /* … */
+ CHECK_FLAG_ALLOW_MAYBE = 1 << 6,
+ CHECK_FLAG_CONSUME_ARGS = 1 << 7,
+} VariantCheckFlags;
+
+
+static const VariantFuncInfo *
+_func_uses_gvariant_format (const FunctionDecl& func)
+{
+ const char *func_name = func.getNameAsString ().c_str ();
+ guint i;
+
+ /* Fast path elimination of irrelevant functions. */
+ if (*func_name != 'g')
+ return NULL;
+
+ for (i = 0; i < G_N_ELEMENTS (gvariant_format_funcs); i++) {
+ if (strcmp (func_name, gvariant_format_funcs[i].func_name) == 0)
+ return &gvariant_format_funcs[i];
+ }
+
+ return NULL;
+}
+
+/* Consume a single variadic argument from the varargs array, checking that one
+ * exists and has the given @expected_type. If %CHECK_FLAG_FORCE_GVARIANT is
+ * set, the expected type is forced to be GVariant*. (This is necessary because
+ * I can find no way to represent GVariant* as a QualType. If someone can fix
+ * that, the boolean argument can be removed.) Same for
+ * %CHECK_FLAG_FORCE_GVARIANTBUILDER, but with GVariantBuilder*; and
+ * %CHECK_FLAG_FORCE_VALIST, but with va_list*.
+ *
+ * Iff %CHECK_FLAG_ALLOW_MAYBE is set, the variadic argument may be NULL.
+ *
+ * This will emit errors where found. */
+static bool
+_consume_variadic_argument (QualType expected_type,
+ CallExpr::const_arg_iterator *args_begin,
+ CallExpr::const_arg_iterator *args_end,
+ unsigned int /* VariantCheckFlags */ flags,
+ CompilerInstance& compiler,
+ const StringLiteral *format_arg_str,
+ ASTContext& context)
+{
+ DEBUG ("Consuming variadic argument with expected type ‘" <<
+ expected_type.getAsString () << "’.");
+
+ std::string expected_type_str;
+
+ if (flags & CHECK_FLAG_FORCE_GVARIANTBUILDER) {
+ /* Note: Stricter checking is implemented below. */
+ DEBUG ("Forcing expected type to ‘GVariantBuilder*’.");
+ expected_type = context.VoidPtrTy;
+ expected_type_str = std::string ("GVariantBuilder *");
+ } else if (flags & CHECK_FLAG_FORCE_GVARIANT) {
+ /* Note: Stricter checking is implemented below. */
+ DEBUG ("Forcing expected type to ‘GVariant*’.");
+ expected_type = context.VoidPtrTy;
+ expected_type_str = std::string ("GVariant *");
+ } else if (flags & CHECK_FLAG_FORCE_VALIST) {
+ /* Note: Stricter checking is implemented below. */
+ DEBUG ("Forcing expected type to ‘va_list*’.");
+ expected_type = context.VoidPtrTy;
+ expected_type_str = std::string ("va_list *");
+ } else {
+ expected_type_str = expected_type.getAsString ();
+ }
+
+ if (*args_begin == *args_end) {
+ gchar *error;
+
+ error = g_strdup_printf (
+ "Expected a GVariant variadic argument of type ‘%s’ "
+ "but there wasn’t one.",
+ expected_type_str.c_str ());
+ Debug::emit_error (error, compiler,
+ format_arg_str->getLocStart ());
+ g_free (error);
+
+ return false;
+ }
+
+ const Expr *arg = **args_begin;
+
+ /* Check its nullability. */
+ const QualType& actual_type = arg->getType ();
+ bool is_null_constant = arg->isNullPointerConstant (context,
+ Expr::NPC_ValueDependentIsNull);
+
+ if (is_null_constant && !(flags & CHECK_FLAG_ALLOW_MAYBE)) {
+ gchar *error;
+
+ error = g_strdup_printf (
+ "Expected a GVariant variadic argument of type ‘%s’ "
+ "but saw NULL instead.",
+ expected_type_str.c_str ());
+ Debug::emit_error (error, compiler,
+ arg->getLocStart ());
+ g_free (error);
+
+ return false;
+ } else if (!is_null_constant &&
+ (flags & (CHECK_FLAG_FORCE_GVARIANT |
+ CHECK_FLAG_FORCE_GVARIANTBUILDER |
+ CHECK_FLAG_FORCE_VALIST))) {
+ /* Special case handling for GVariant[Builder]* types, because I
+ * can’t find a reasonable way of retrieving the QualType for
+ * the GVariant or GVariantBuilder typedefs; so we use this
+ * hacky approach instead. */
+ const PointerType *actual_pointer_type = dyn_cast<PointerType> (actual_type);
+ if (actual_pointer_type == NULL) {
+ gchar *error;
+
+ error = g_strdup_printf (
+ "Expected a GVariant variadic argument of type "
+ "‘%s’ but saw one of type ‘%s’.",
+ expected_type_str.c_str (),
+ actual_type.getAsString ().c_str ());
+ Debug::emit_error (error, compiler,
+ arg->getLocStart ());
+ g_free (error);
+
+ return false;
+ }
+
+ QualType actual_pointee_type = actual_pointer_type->getPointeeType ();
+ if (!(flags & CHECK_FLAG_FORCE_GVARIANTBUILDER &&
+ actual_pointee_type.getUnqualifiedType ().getAsString () == "GVariantBuilder") &&
+ !(flags & CHECK_FLAG_FORCE_GVARIANT &&
+ actual_pointee_type.getUnqualifiedType ().getAsString () == "GVariant") &&
+ !(flags & CHECK_FLAG_FORCE_VALIST &&
+ actual_pointee_type.getUnqualifiedType ().getAsString () == "va_list")) {
+ gchar *error;
+
+ error = g_strdup_printf (
+ "Expected a GVariant variadic argument of type "
+ "‘%s’ but saw one of type ‘%s’.",
+ expected_type_str.c_str (),
+ actual_type.getAsString ().c_str ());
+ Debug::emit_error (error, compiler,
+ arg->getLocStart ());
+ g_free (error);
+
+ return false;
+ }
+ } else if (!is_null_constant &&
+ !(flags & (CHECK_FLAG_FORCE_GVARIANT |
+ CHECK_FLAG_FORCE_GVARIANTBUILDER |
+ CHECK_FLAG_FORCE_VALIST))) {
+ /* Normal non-GVariant, non-GVariantBuilder case. */
+ const PointerType *actual_pointer_type = dyn_cast<PointerType> (actual_type);
+ const PointerType *actual_pointer2_type = NULL;
+ QualType actual_pointee_type, actual_pointee2_type;
+
+ if (actual_pointer_type != NULL) {
+ actual_pointee_type = actual_pointer_type->getPointeeType ();
+
+ actual_pointer2_type = dyn_cast<PointerType> (actual_pointee_type);
+ if (actual_pointer2_type != NULL) {
+ actual_pointee2_type = actual_pointer2_type->getPointeeType ();
+ }
+ }
+
+ const PointerType *expected_pointer_type = dyn_cast<PointerType> (expected_type);
+ const PointerType *expected_pointer2_type = NULL;
+ QualType expected_pointee_type, expected_pointee2_type;
+
+ if (expected_pointer_type != NULL) {
+ expected_pointee_type = expected_pointer_type->getPointeeType ();
+
+ expected_pointer2_type = dyn_cast<PointerType> (expected_pointee_type);
+ if (expected_pointer2_type != NULL) {
+ expected_pointee2_type = expected_pointer2_type->getPointeeType ();
+ }
+ }
+
+ /* Check it’s of @expected_type. We need to compare the
+ * unqualified (with const, volatile, restrict removed) types,
+ * plus the unqualified pointee types if the normal types are
+ * pointers, plus the unqualified pointee pointee types.
+ * .e.g
+ * char* ≡ const char*
+ * int ≡ int
+ * char* ≡ char*
+ * GVariant* ≡ const GVariant*
+ * char** ≡ const char * const * */
+ if (!context.hasSameUnqualifiedType (actual_type,
+ expected_type) &&
+ (actual_pointer_type == NULL ||
+ expected_pointer_type == NULL ||
+ !context.hasSameUnqualifiedType (actual_pointee_type,
+ expected_pointee_type)) &&
+ (actual_pointer2_type == NULL ||
+ expected_pointer2_type == NULL ||
+ !context.hasSameUnqualifiedType (actual_pointee2_type,
+ expected_pointee2_type))) {
+ gchar *error;
+
+ error = g_strdup_printf (
+ "Expected a GVariant variadic argument of type "
+ "‘%s’ but saw one of type ‘%s’.",
+ expected_type.getAsString ().c_str (),
+ actual_type.getAsString ().c_str ());
+ Debug::emit_error (error, compiler,
+ arg->getLocStart ());
+ g_free (error);
+
+ return false;
+ }
+ }
+
+ /* If the GVariant method doesn’t use varargs, don’t actually consume
+ * the argument. */
+ if (flags & CHECK_FLAG_CONSUME_ARGS)
+ *args_begin = *args_begin + 1; /* consume the format */
+
+ return true;
+}
+
+/* Parse a single basic type string from the beginning of the string pointed to
+ * by @type_str (i.e. *type_str). Consume any variadic parameters from
+ * @args_begin as appropriate. This will emit errors where found.
+ *
+ * @type_str and @args_begin are updated as the type string and arguments are
+ * consumed. */
+static bool
+_check_basic_type_string (const gchar **type_str,
+ CallExpr::const_arg_iterator *args_begin,
+ CallExpr::const_arg_iterator *args_end,
+ unsigned int /* VariantCheckFlags */ flags,
+ CompilerInstance& compiler,
+ const StringLiteral *format_arg_str,
+ ASTContext& context)
+{
+ DEBUG ("Checking basic type string ‘" << *type_str << "’.");
+
+ QualType expected_type;
+
+ /* Reference: GVariant Type Strings. */
+ switch (**type_str) {
+ /* Numeric Types */
+ case 'b': /* gboolean ≡ gint ≡ int */
+ expected_type = context.IntTy;
+ break;
+ case 'y': /* guchar ≡ unsigned char */
+ expected_type = context.UnsignedCharTy;
+ break;
+ case 'n': /* gint16 */
+ expected_type = context.getIntTypeForBitwidth (16, true);
+ break;
+ case 'q': /* guint16 */
+ expected_type = context.getIntTypeForBitwidth (16, false);
+ break;
+ case 'i':
+ case 'h': /* gint32 */
+ expected_type = context.getIntTypeForBitwidth (32, true);
+ break;
+ case 'u': /* guint32 */
+ expected_type = context.getIntTypeForBitwidth (32, false);
+ break;
+ case 'x': /* gint64 */
+ expected_type = context.getIntTypeForBitwidth (64, true);
+ break;
+ case 't': /* guint64 */
+ expected_type = context.getIntTypeForBitwidth (64, false);
+ break;
+ case 'd': /* gdouble ≡ double */
+ expected_type = context.DoubleTy;
+ break;
+ /* Strings */
+ case 's':
+ case 'o':
+ case 'g': /* gchar* ≡ char* */
+ /* FIXME: Could also validate o and g as D-Bus object paths and
+ * type signatures. */
+ expected_type = context.getPointerType (context.CharTy);
+ break;
+ /* Basic types */
+ case '?': /* GVariant* of any type */
+ /* Stricter validation is applied in
+ * _consume_variadic_argument(). */
+ expected_type = context.VoidPtrTy;
+ flags |= CHECK_FLAG_FORCE_GVARIANT;
+ break;
+ default:
+ gchar *error;
+
+ error = g_strdup_printf ("Expected a GVariant basic type "
+ "string but saw ‘%c’.", **type_str);
+ Debug::emit_error (error, compiler,
+ format_arg_str->getLocStart ());
+ g_free (error);
+
+ return false;
+ }
+
+ /* Handle type promotion. Integer types which are smaller than 32 bits
+ * (for all architectures we care about) are automatically promoted to
+ * 32 bits when passed as varargs.
+ *
+ * A subtlety of the standard (ISO/IEC 9899, §6.3.1.1¶2) means that all
+ * types are promoted to *signed* 32-bit integers. This is because int
+ * can represent all values representable by 16-bit (and smaller)
+ * unsigned integers.
+ *
+ * References:
+ * • GVariant Format Strings, §Numeric Types
+ * • ISO/IEC 9899, §6.5.2.2¶6
+ */
+ if (**type_str == 'y' || **type_str == 'n' || **type_str == 'q') {
+ assert (expected_type->isPromotableIntegerType ());
+ expected_type = context.IntTy;
+ }
+
+ /* Consume the type string. */
+ *type_str = *type_str + 1;
+
+ return _consume_variadic_argument (expected_type,
+ args_begin, args_end,
+ flags, compiler,
+ format_arg_str, context);
+}
+
+/* Parse a single type string from the beginning of the string pointed to
+ * by @type_str (i.e. *type_str). Consume any variadic parameters from
+ * @args_begin as appropriate. This will emit errors where found.
+ *
+ * @type_str and @args_begin are updated as the type string and arguments are
+ * consumed. */
+static bool
+_check_type_string (const gchar **type_str,
+ CallExpr::const_arg_iterator *args_begin,
+ CallExpr::const_arg_iterator *args_end,
+ unsigned int /* VariantCheckFlags */ flags,
+ CompilerInstance& compiler,
+ const StringLiteral *format_arg_str,
+ ASTContext& context)
+{
+ DEBUG ("Checking type string ‘" << *type_str << "’.");
+
+ QualType expected_type;
+
+ /* Reference: GVariant Type Strings. */
+ switch (**type_str) {
+ /* Variants */
+ case 'v': /* GVariant* */
+ /* Stricter validation is applied in
+ * _consume_variadic_argument(). */
+ expected_type = context.VoidPtrTy;
+ flags |= CHECK_FLAG_FORCE_GVARIANT;
+ break;
+ /* Arrays */
+ case 'a':
+ /* Consume the ‘a’. */
+ *type_str = *type_str + 1;
+
+ /* Check and consume the type string for the array element
+ * type. */
+ if (!_check_type_string (type_str, args_begin, args_end,
+ (flags |
+ CHECK_FLAG_FORCE_GVARIANTBUILDER |
+ CHECK_FLAG_ALLOW_MAYBE) &
+ ~CHECK_FLAG_CONSUME_ARGS,
+ compiler, format_arg_str, context)) {
+ return false;
+ }
+
+ /* Consume the single GVariantBuilder for the array.
+ * FIXME: ALLOW_MAYBE only for definite types */
+ return _consume_variadic_argument (context.VoidPtrTy,
+ args_begin, args_end,
+ flags |
+ CHECK_FLAG_FORCE_GVARIANTBUILDER |
+ CHECK_FLAG_ALLOW_MAYBE,
+ compiler,
+ format_arg_str, context);
+ /* Maybe Types */
+ case 'm':
+ *type_str = *type_str + 1; /* consume the ‘m’ */
+ return _check_type_string (type_str, args_begin, args_end,
+ flags | CHECK_FLAG_ALLOW_MAYBE,
+ compiler, format_arg_str, context);
+ /* Tuples */
+ case '(':
+ *type_str = *type_str + 1; /* consume the opening bracket */
+
+ while (**type_str != ')' && **type_str != '\0') {
+ if (!_check_type_string (type_str, args_begin,
+ args_end,
+ flags, compiler,
+ format_arg_str, context)) {
+ return false;
+ }
+ }
+
+ if (**type_str != ')') {
+ Debug::emit_error (
+ "Invalid GVariant type string: tuple "
+ "did not end with ‘)’.",
+ compiler,
+ format_arg_str->getLocStart ());
+ return false;
+ }
+
+ *type_str = *type_str + 1; /* consume the closing bracket */
+
+ return true;
+ case 'r': /* GVariant* of tuple type */
+ /* Stricter validation is applied in
+ * _consume_variadic_argument().
+ *
+ * FIXME: Validate that the GVariant* has a tuple type. */
+ expected_type = context.VoidPtrTy;
+ flags |= CHECK_FLAG_FORCE_GVARIANT;
+ break;
+ /* Dictionaries */
+ case '{':
+ *type_str = *type_str + 1; /* consume the opening brace */
+
+ if (**type_str == '}') {
+ Debug::emit_error (
+ "Invalid GVariant type string: dict did not "
+ "contain exactly two elements.",
+ compiler,
+ format_arg_str->getLocStart ());
+ return false;
+ } else if (!_check_basic_type_string (type_str, args_begin,
+ args_end,
+ flags, compiler,
+ format_arg_str, context)) {
+ return false;
+ }
+
+ if (**type_str == '}') {
+ Debug::emit_error (
+ "Invalid GVariant type string: dict did not "
+ "contain exactly two elements.",
+ compiler,
+ format_arg_str->getLocStart ());
+ return false;
+ } else if (!_check_type_string (type_str, args_begin, args_end,
+ flags, compiler,
+ format_arg_str, context)) {
+ return false;
+ }
+
+ if (**type_str == '\0') {
+ Debug::emit_error (
+ "Invalid GVariant type string: dict "
+ "did not end with ‘}’.",
+ compiler,
+ format_arg_str->getLocStart ());
+ return false;
+ } else if (**type_str != '}') {
+ Debug::emit_error (
+ "Invalid GVariant type string: dict "
+ "contains more than two elements.",
+ compiler,
+ format_arg_str->getLocStart ());
+ return false;
+ }
+
+ *type_str = *type_str + 1; /* consume the closing brace */
+
+ return true;
+ /* GVariant* */
+ case '*': /* GVariant* of any type */
+ /* Stricter validation is applied in
+ * _consume_variadic_argument(). */
+ expected_type = context.VoidPtrTy;
+ flags |= CHECK_FLAG_FORCE_GVARIANT;
+ break;
+ default:
+ /* Fall back to checking basic types. */
+ return _check_basic_type_string (type_str, args_begin, args_end,
+ flags, compiler,
+ format_arg_str, context);
+ }
+
+ /* Consume the type string. */
+ *type_str = *type_str + 1;
+
+ return _consume_variadic_argument (expected_type,
+ args_begin, args_end,
+ flags, compiler,
+ format_arg_str, context);
+}
+
+/* Parse a single basic format string from the beginning of the string pointed
+ * to by @format_str (i.e. *format_str). Consume any variadic parameters from
+ * @args_begin as appropriate. This will emit errors where found.
+ *
+ * @format_str and @args_begin are updated as the format string and arguments
+ * are consumed. */
+static bool
+_check_basic_format_string (const gchar **format_str,
+ CallExpr::const_arg_iterator *args_begin,
+ CallExpr::const_arg_iterator *args_end,
+ unsigned int /* VariantCheckFlags */ flags,
+ CompilerInstance& compiler,
+ const StringLiteral *format_arg_str,
+ ASTContext& context)
+{
+ DEBUG ("Checking format string ‘" << *format_str << "’.");
+
+ /* Reference: GVariant Format Strings documentation, §Syntax. */
+ switch (**format_str) {
+ case '@':
+ *format_str = *format_str + 1; /* consume the ‘@’ */
+ return _check_basic_type_string (format_str, args_begin,
+ args_end,
+ flags |
+ CHECK_FLAG_FORCE_GVARIANT,
+ compiler, format_arg_str,
+ context);
+ case '?':
+ /* Direct GVariant. Stricter checking is implemented later on,
+ * so we don’t just validate to VoidPtrTy. */
+ *format_str = *format_str + 1; /* consume the argument */
+ return _consume_variadic_argument (context.VoidPtrTy,
+ args_begin, args_end,
+ flags |
+ CHECK_FLAG_FORCE_GVARIANT,
+ compiler, format_arg_str,
+ context);
+ case '&':
+ /* Ignore it. */
+ *format_str = *format_str + 1;
+ return _check_basic_type_string (format_str, args_begin,
+ args_end,
+ flags, compiler,
+ format_arg_str, context);
+ case '^': {
+ /* Various different hard-coded types. */
+ *format_str = *format_str + 1;
+
+ QualType expected_type;
+ QualType char_array = context.getPointerType (context.CharTy);
+ guint skip;
+
+ /* Effectively hard-code the table from
+ * §Convenience Conversions. */
+ if (strcmp (*format_str, "as") == 0 ||
+ strcmp (*format_str, "ao") == 0) {
+ expected_type = context.getPointerType (char_array);
+ skip = 2;
+ } else if (strcmp (*format_str, "a&s") == 0 ||
+ strcmp (*format_str, "a&o") == 0 ||
+ strcmp (*format_str, "aay") == 0) {
+ expected_type = context.getPointerType (char_array);
+ skip = 3;
+ } else if (strcmp (*format_str, "ay") == 0) {
+ expected_type = char_array;
+ skip = 2;
+ } else if (strcmp (*format_str, "&ay") == 0) {
+ expected_type = char_array;
+ skip = 3;
+ } else if (strcmp (*format_str, "a&ay") == 0) {
+ expected_type = context.getPointerType (char_array);
+ skip = 4;
+ } else {
+ Debug::emit_error (
+ "Invalid GVariant basic format string: "
+ "convenience operator ‘^’ was not followed by "
+ "a recognized convenience conversion.",
+ compiler,
+ format_arg_str->getLocStart ());
+ return false;
+ }
+
+ *format_str = *format_str + skip;
+
+ return _consume_variadic_argument (expected_type, args_begin,
+ args_end,
+ flags, compiler,
+ format_arg_str, context);
+ }
+ default:
+ /* Assume it’s a type string. */
+ return _check_basic_type_string (format_str, args_begin,
+ args_end,
+ flags, compiler,
+ format_arg_str, context);
+ }
+}
+
+/* Parse a single format string from the beginning of the string pointed
+ * to by @format_str (i.e. *format_str). Consume any variadic parameters from
+ * @args_begin as appropriate. This will emit errors where found.
+ *
+ * @format_str and @args_begin are updated as the format string and arguments
+ * are consumed. */
+static bool
+_check_format_string (const gchar **format_str,
+ CallExpr::const_arg_iterator *args_begin,
+ CallExpr::const_arg_iterator *args_end,
+ unsigned int /* VariantCheckFlags */ flags,
+ CompilerInstance& compiler,
+ const StringLiteral *format_arg_str,
+ ASTContext& context)
+{
+ DEBUG ("Checking format string ‘" << *format_str << "’.");
+
+ /* Reference: GVariant Format Strings documentation, §Syntax. */
+ switch (**format_str) {
+ case '@':
+ *format_str = *format_str + 1; /* consume the ‘@’ */
+ return _check_type_string (format_str, args_begin, args_end,
+ flags | CHECK_FLAG_FORCE_GVARIANT,
+ compiler, format_arg_str, context);
+ case 'm':
+ *format_str = *format_str + 1; /* consume the ‘m’ */
+ return _check_format_string (format_str, args_begin, args_end,
+ flags | CHECK_FLAG_ALLOW_MAYBE,
+ compiler, format_arg_str, context);
+ case '*':
+ case '?':
+ case 'r':
+ /* Direct GVariants. Stricter checking is implemented later on,
+ * so we don’t just validate to VoidPtrTy. */
+ *format_str = *format_str + 1; /* consume the argument */
+ return _consume_variadic_argument (context.VoidPtrTy,
+ args_begin, args_end,
+ flags |
+ CHECK_FLAG_FORCE_GVARIANT,
+ compiler, format_arg_str,
+ context);
+ case '(':
+ *format_str = *format_str + 1; /* consume the opening bracket */
+
+ while (**format_str != ')' && **format_str != '\0') {
+ if (!_check_format_string (format_str, args_begin,
+ args_end,
+ flags, compiler,
+ format_arg_str, context)) {
+ return false;
+ }
+ }
+
+ if (**format_str != ')') {
+ Debug::emit_error (
+ "Invalid GVariant format string: tuple "
+ "did not end with ‘)’.",
+ compiler,
+ format_arg_str->getLocStart ());
+ return false;
+ }
+
+ *format_str = *format_str + 1; /* consume the closing bracket */
+
+ return true;
+ case '{':
+ *format_str = *format_str + 1; /* consume the opening brace */
+
+ if (**format_str == '}') {
+ Debug::emit_error (
+ "Invalid GVariant format string: dict did not "
+ "contain exactly two elements.",
+ compiler,
+ format_arg_str->getLocStart ());
+ return false;
+ } else if (!_check_basic_format_string (format_str, args_begin,
+ args_end,
+ flags, compiler,
+ format_arg_str,
+ context)) {
+ return false;
+ }
+
+ if (**format_str == '}') {
+ Debug::emit_error (
+ "Invalid GVariant format string: dict did not "
+ "contain exactly two elements.",
+ compiler,
+ format_arg_str->getLocStart ());
+ return false;
+ } else if (!_check_format_string (format_str, args_begin,
+ args_end,
+ flags, compiler,
+ format_arg_str, context)) {
+ return false;
+ }
+
+ if (**format_str == '\0') {
+ Debug::emit_error (
+ "Invalid GVariant format string: dict "
+ "did not end with ‘}’.",
+ compiler,
+ format_arg_str->getLocStart ());
+ return false;
+ } else if (**format_str != '}') {
+ Debug::emit_error (
+ "Invalid GVariant format string: dict "
+ "contains more than two elements.",
+ compiler,
+ format_arg_str->getLocStart ());
+ return false;
+ }
+
+ *format_str = *format_str + 1; /* consume the closing brace */
+
+ return true;
+ case '&':
+ /* Ignore it. */
+ *format_str = *format_str + 1;
+ return _check_type_string (format_str, args_begin, args_end,
+ flags, compiler,
+ format_arg_str, context);
+ case '^':
+ /* Handled by the basic format string parser. */
+ return _check_basic_format_string (format_str, args_begin,
+ args_end,
+ flags, compiler,
+ format_arg_str, context);
+ default:
+ /* Assume it’s a type string. */
+ return _check_type_string (format_str, args_begin, args_end,
+ flags, compiler,
+ format_arg_str, context);
+ }
+}
+
+/* Build a GVariant format string to represent the given type, or return NULL if
+ * no representation is known. The returned value must be freed using
+ * g_free(). */
+static gchar *
+_gvariant_format_string_for_type (QualType type)
+{
+ /* TODO: Return the format string for a basic type */
+ return NULL;
+}
+
+/* Check a GVariant function call which passes a format parameter. Validate the
+ * format parameter string, and if the function takes varargs, validate their
+ * types against that parameter.
+ *
+ * If the format string is not a string literal, we can’t check anything. */
+static bool
+_check_gvariant_format_param (const CallExpr& call,
+ const FunctionDecl &func,
+ const VariantFuncInfo *func_info,
+ CompilerInstance& compiler,
+ ASTContext& context)
+{
+ /* Grab the format parameter string. */
+ const Expr *format_arg = call.getArg (func_info->format_param_index)->IgnoreParenImpCasts ();
+
+ DEBUG ("Checking GVariant format strings in " << func.getNameAsString () << "().");
+
+ const StringLiteral *format_arg_str = dyn_cast<StringLiteral> (format_arg);
+ if (format_arg_str == NULL) {
+ Debug::emit_warning (
+ "Non-literal GVariant format string in call to " +
+ func.getNameAsString () +
+ "(). Cannot check format string correctness. Instead "
+ "of a non-literal format string, use GVariantBuilder.",
+ compiler,
+ format_arg->getLocStart ());
+ return false;
+ }
+
+ /* Check the string. Parse it hand-in-hand with iterating through the
+ * varargs list. */
+ DEBUG ("Checking GVariant format string ‘" <<
+ format_arg_str->getString () << "’ with " <<
+ call.getNumArgs () << " variadic arguments.");
+
+ const gchar *format_str = format_arg_str->getString ().data ();
+ CallExpr::const_arg_iterator args_begin = call.arg_begin ();
+ CallExpr::const_arg_iterator args_end = call.arg_end ();
+
+ /* Skip up to the varargs. If args_begin points to a va_list, the rest
+ * of the code will ignore it. */
+ for (unsigned int i = 0; i < func_info->first_vararg_param_index; i++)
+ ++args_begin;
+
+ unsigned int flags = CHECK_FLAG_NONE;
+ if (!func_info->uses_va_list)
+ flags |= CHECK_FLAG_CONSUME_ARGS;
+ else
+ flags |= CHECK_FLAG_FORCE_VALIST;
+
+ if (!_check_format_string (&format_str, &args_begin, &args_end,
+ flags, compiler, format_arg_str, context)) {
+ return false;
+ }
+
+ /* Sanity check that we’ve consumed all format strings. If not, the
+ * user has probably forgotten to add tuple brackets around their format
+ * string. Don’t emit any error messages about unpaired variadic
+ * arguments because that would just confuse things. */
+ if (*format_str != '\0') {
+ gchar *error;
+
+ error = g_strdup_printf ("Unexpected GVariant format strings "
+ "‘%s’ with unpaired arguments. If "
+ "using multiple format strings, they "
+ "should be enclosed in brackets to "
+ "create a tuple (e.g. ‘(%s)’).",
+ format_str,
+ format_arg_str->getString ().data ());
+ Debug::emit_error (error, compiler,
+ format_arg_str->getLocStart ());
+ g_free (error);
+
+ return false;
+ }
+
+ /* Sanity check that we’ve consumed all arguments. */
+ bool retval = true;
+
+ for (; !func_info->uses_va_list && args_begin != args_end;
+ ++args_begin) {
+ const Expr *arg = *args_begin;
+ gchar *error;
+ gchar *error_format_str;
+
+ error_format_str = _gvariant_format_string_for_type (arg->getType ());
+
+ if (error_format_str != NULL) {
+ error = g_strdup_printf (
+ "Unexpected GVariant variadic argument of type "
+ "‘%s’. A ‘%s’ GVariant format string should be "
+ "added to the format argument to use it.",
+ arg->getType ().getAsString ().c_str (),
+ error_format_str);
+ } else {
+ error = g_strdup_printf (
+ "Unexpected GVariant variadic argument of type "
+ "‘%s’. A GVariant format string should be "
+ "added to the format argument to use it, but "
+ "there is no known GVariant representation of "
+ "the argument’s type. The argument must be "
+ "serialized to a GVariant-representable type "
+ "first.",
+ arg->getType ().getAsString ().c_str ());
+ }
+
+ Debug::emit_error (error, compiler, arg->getLocStart ());
+ g_free (error);
+
+ retval = false;
+ }
+
+ return retval;
+}
+
+void
+GVariantConsumer::HandleTranslationUnit (ASTContext& context)
+{
+ this->_visitor.TraverseDecl (context.getTranslationUnitDecl ());
+}
+
+/* Note: Specifically overriding the Traverse* method here to re-implement
+ * recursion to child nodes. */
+bool
+GVariantVisitor::VisitCallExpr (CallExpr* expr)
+{
+ const VariantFuncInfo *func_info;
+
+ /* Can only handle direct function calls (i.e. not calling dereferenced
+ * function pointers). */
+ const FunctionDecl *func = expr->getDirectCallee ();
+ if (func == NULL)
+ return true;
+
+ /* We’re only interested in functions which handle GVariants. */
+ func_info = _func_uses_gvariant_format (*func);
+ if (func_info == NULL)
+ return true;
+
+ /* Check the format parameter. */
+ _check_gvariant_format_param (*expr, *func, func_info, this->_compiler,
+ func->getASTContext ());
+
+ return true;
+}
diff --git a/clang-plugin/gvariant-checker.h b/clang-plugin/gvariant-checker.h
new file mode 100644
index 0000000..8cdb2bd
--- /dev/null
+++ b/clang-plugin/gvariant-checker.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * gnome-clang
+ * Copyright © 2014 Philip Withnall
+ *
+ * gnome-clang is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * gnome-clang 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with gnome-clang. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Philip Withnall <philip@tecnocode.co.uk>
+ */
+
+#ifndef GNOME_CLANG_GVARIANT_CHECKER_H
+#define GNOME_CLANG_GVARIANT_CHECKER_H
+
+#include <clang/AST/AST.h>
+#include <clang/AST/ASTConsumer.h>
+#include <clang/AST/RecursiveASTVisitor.h>
+#include <clang/Frontend/CompilerInstance.h>
+
+using namespace clang;
+
+class GVariantVisitor : public RecursiveASTVisitor<GVariantVisitor> {
+public:
+ explicit GVariantVisitor (CompilerInstance& compiler) :
+ _compiler (compiler), _context (compiler.getASTContext ()) {}
+
+private:
+ QualType _gvariant_pointer_type;
+ CompilerInstance& _compiler;
+ const ASTContext& _context;
+
+public:
+ bool VisitCallExpr (CallExpr* call);
+};
+
+class GVariantConsumer : public ASTConsumer {
+public:
+ GVariantConsumer (CompilerInstance& compiler) :
+ _visitor (compiler) {}
+
+private:
+ GVariantVisitor _visitor;
+
+public:
+ virtual void HandleTranslationUnit (ASTContext& context);
+};
+
+#endif /* !GNOME_CLANG_GVARIANT_CHECKER_H */
diff --git a/clang-plugin/plugin.cpp b/clang-plugin/plugin.cpp
index a72b534..875a8c2 100644
--- a/clang-plugin/plugin.cpp
+++ b/clang-plugin/plugin.cpp
@@ -30,6 +30,7 @@
#include "debug.h"
#include "gir-attributes.h"
#include "gassert-attributes.h"
+#include "gvariant-checker.h"
#include "nullability-checker.h"
using namespace clang;
@@ -60,6 +61,8 @@ protected:
new NullabilityConsumer (compiler,
this->_gir_manager));
consumers.push_back (
+ new GVariantConsumer (compiler));
+ consumers.push_back (
new GirAttributesChecker (compiler,
this->_gir_manager));
diff --git a/configure.ac b/configure.ac
index 10bc8b8..0feb07a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -15,7 +15,7 @@ AC_CONFIG_SRCDIR([clang-plugin/plugin.cpp])
AC_CONFIG_HEADERS([config.h])
AC_USE_SYSTEM_EXTENSIONS
-AM_INIT_AUTOMAKE([1.12 dist-xz no-dist-gzip check-news subdir-objects])
+AM_INIT_AUTOMAKE([1.12 dist-xz no-dist-gzip check-news subdir-objects parallel-tests])
AM_SILENT_RULES([yes])
AC_PROG_CXX
@@ -109,5 +109,6 @@ AC_SUBST([AM_LDFLAGS])
AC_CONFIG_FILES([
Makefile
po/Makefile.in
+tests/Makefile
])
AC_OUTPUT
diff --git a/tests/Makefile.am b/tests/Makefile.am
new file mode 100644
index 0000000..eb297d9
--- /dev/null
+++ b/tests/Makefile.am
@@ -0,0 +1,11 @@
+# Tests are implemented as a series of example C files, each of which should
+# compile successfully without gnome-clang, and which should fail compilation
+# with gnome-clang (and -Werror).
+
+TEST_EXTENSIONS = .c
+C_LOG_COMPILER = ./wrapper-compiler-errors
+
+TESTS = \
+ gvariant-new.c
+
+-include $(top_srcdir)/git.mk
diff --git a/tests/gvariant-new.c b/tests/gvariant-new.c
new file mode 100644
index 0000000..2aebff9
--- /dev/null
+++ b/tests/gvariant-new.c
@@ -0,0 +1,944 @@
+/*
+ * Expected a GVariant variadic argument of type ‘char *’ but saw one of type ‘guint’.
+ * floating_variant = g_variant_new ("(sss)", "hello", my_string, a_little_int_short_and_stout);
+ * ^ ^
+ */
+{
+ const gchar *my_string = "there";
+ guint a_little_int_short_and_stout = 5;
+
+ floating_variant = g_variant_new ("(sss)", "hello", my_string, a_little_int_short_and_stout);
+}
+
+/*
+ * No error
+ */
+{
+ floating_variant = g_variant_new ("(ss)", "hi", "there");
+}
+
+/*
+ * Expected a GVariant variadic argument of type ‘int’ but there wasn’t one.
+ * floating_variant = g_variant_new ("invalid");
+ * ^
+ */
+{
+ floating_variant = g_variant_new ("invalid");
+}
+
+/*
+ * No error
+ */
+{
+ floating_variant = g_variant_new ("x", (gint64) 56);
+}
+
+/*
+ * No error
+ */
+{
+ floating_variant = g_variant_new ("b", TRUE);
+}
+
+/*
+ * Expected a GVariant variadic argument of type ‘int’ but saw one of type ‘char *’.
+ * floating_variant = g_variant_new ("b", "nope");
+ */
+{
+ floating_variant = g_variant_new ("b", "nope");
+}
+
+/*
+ * No error
+ */
+{
+ floating_variant = g_variant_new ("y", (guchar) 'h');
+}
+
+/*
+ * Expected a GVariant variadic argument of type ‘unsigned char’ but saw one of type ‘char *’.
+ * floating_variant = g_variant_new ("y", "nope");
+ * ^ ^
+ */
+{
+ floating_variant = g_variant_new ("y", "nope");
+}
+
+/*
+ * No error
+ */
+{
+ gint16 some_var = 5;
+ floating_variant = g_variant_new ("n", some_var);
+}
+
+/*
+ * No error
+ */
+{
+ floating_variant = g_variant_new ("n", (gint16) -6);
+}
+
+/*
+ * Expected a GVariant variadic argument of type ‘short’ but saw one of type ‘char *’.
+ * floating_variant = g_variant_new ("n", "nope");
+ * ^
+ */
+{
+ floating_variant = g_variant_new ("n", "nope");
+}
+
+/*
+ * No error
+ */
+{
+ guint16 some_var = 5;
+ floating_variant = g_variant_new ("q", some_var);
+}
+
+/*
+ * No error
+ */
+{
+ floating_variant = g_variant_new ("q", (guint16) 6);
+}
+
+/*
+ * No error
+ */
+{
+ floating_variant = g_variant_new ("q", 6);
+}
+
+/*
+ * No error
+ */
+{
+ floating_variant = g_variant_new ("q", (gint32) 6);
+}
+
+/*
+ * No error
+ */
+{
+ // This is a subtle one, but we deliberately don’t currently warn about
+ // it. ‘q’ will read a 32-bit signed integer, which -6 fits into.
+ floating_variant = g_variant_new ("q", -6);
+}
+
+/*
+ * Expected a GVariant variadic argument of type ‘int’ but saw one of type ‘char *’.
+ * floating_variant = g_variant_new ("i", "nope");
+ * ^
+ */
+{
+ floating_variant = g_variant_new ("i", "nope");
+}
+
+/*
+ * No error
+ */
+{
+ floating_variant = g_variant_new ("i", -16);
+}
+
+/*
+ * No error
+ */
+{
+ gint32 some_var = -5;
+ floating_variant = g_variant_new ("i", some_var);
+}
+
+/*
+ * Expected a GVariant variadic argument of type ‘unsigned int’ but saw one of type ‘char *’.
+ * floating_variant = g_variant_new ("u", "nope");
+ * ^
+ */
+{
+ floating_variant = g_variant_new ("u", "nope");
+}
+
+/*
+ * No error
+ */
+{
+ floating_variant = g_variant_new ("u", G_MAXUINT);
+}
+
+/*
+ * No error
+ */
+{
+ guint32 some_var = 5;
+ floating_variant = g_variant_new ("u", some_var);
+}
+
+/*
+ * Expected a GVariant variadic argument of type ‘long’ but saw one of type ‘char *’.
+ * floating_variant = g_variant_new ("x", "nope");
+ * ^
+ */
+{
+ floating_variant = g_variant_new ("x", "nope");
+}
+
+/*
+ * Expected a GVariant variadic argument of type ‘long’ but saw one of type ‘int’.
+ * floating_variant = g_variant_new ("x", -5);
+ * ^
+ */
+{
+ floating_variant = g_variant_new ("x", -5);
+}
+
+/*
+ * Expected a GVariant variadic argument of type ‘long’ but saw one of type ‘gint32’.
+ * floating_variant = g_variant_new ("x", some_var);
+ * ^
+ */
+{
+ gint32 some_var = -5; // deliberately not wide enough
+ floating_variant = g_variant_new ("x", some_var);
+}
+
+/*
+ * No error
+ */
+{
+ gint64 some_var = -5;
+ floating_variant = g_variant_new ("x", some_var);
+}
+
+/*
+ * No error
+ */
+{
+ floating_variant = g_variant_new ("x", (gint64) 500);
+}
+
+/*
+ * Expected a GVariant variadic argument of type ‘signed long’ but saw one of type ‘char *’.
+ * floating_variant = g_variant_new ("t", "nada");
+ * ^
+ */
+{
+ floating_variant = g_variant_new ("t", "nada");
+}
+
+/*
+ * Expected a GVariant variadic argument of type ‘unsigned long’ but saw one of type ‘int’.
+ * floating_variant = g_variant_new ("t", 5);
+ * ^
+ */
+{
+ floating_variant = g_variant_new ("t", 5);
+}
+
+/*
+ * Expected a GVariant variadic argument of type ‘unsigned long’ but saw one of type ‘guint32’.
+ * floating_variant = g_variant_new ("t", some_var);
+ * ^
+ */
+{
+ guint32 some_var = 5; // deliberately not wide enough
+ floating_variant = g_variant_new ("t", some_var);
+}
+
+/*
+ * No error
+ */
+{
+ guint64 some_var = 5;
+ floating_variant = g_variant_new ("t", some_var);
+}
+
+/*
+ * No error
+ */
+{
+ floating_variant = g_variant_new ("t", (guint64) 500);
+}
+
+/*
+ * Expected a GVariant variadic argument of type ‘int’ but saw one of type ‘char *’.
+ * floating_variant = g_variant_new ("h", "nope");
+ * ^
+ */
+{
+ floating_variant = g_variant_new ("h", "nope");
+}
+
+/*
+ * No error
+ */
+{
+ floating_variant = g_variant_new ("h", -16);
+}
+
+/*
+ * No error
+ */
+{
+ gint32 some_var = -5;
+ floating_variant = g_variant_new ("h", some_var);
+}
+
+/*
+ * Expected a GVariant variadic argument of type ‘double’ but saw one of type ‘int’.
+ * floating_variant = g_variant_new ("d", 5);
+ * ^
+ */
+{
+ floating_variant = g_variant_new ("d", 5);
+}
+
+/*
+ * No error
+ */
+{
+ gfloat some_var = 5.1;
+ floating_variant = g_variant_new ("d", some_var);
+}
+
+/*
+ * No error
+ */
+{
+ floating_variant = g_variant_new ("d", 51.07);
+}
+
+/*
+ * No error
+ */
+{
+ floating_variant = g_variant_new ("d", (gdouble) 7);
+}
+
+/*
+ * No error
+ */
+{
+ floating_variant = g_variant_new ("s", "some string");
+}
+
+/*
+ * Expected a GVariant variadic argument of type ‘char *’ but saw one of type ‘int’.
+ * floating_variant = g_variant_new ("s", 152);
+ * ^
+ */
+{
+ floating_variant = g_variant_new ("s", 152);
+}
+
+/*
+ * No error
+ */
+{
+ const gchar *some_var = "hello world";
+ floating_variant = g_variant_new ("s", some_var);
+}
+
+/*
+ * No error
+ */
+{
+ const char *some_var = "hello world"; // try plain C types
+ floating_variant = g_variant_new ("s", some_var);
+}
+
+/*
+ * No error
+ */
+{
+ gchar *some_var = g_strdup_printf ("%s %s", "hello", "world");
+ floating_variant = g_variant_new ("s", some_var);
+ g_free (some_var);
+}
+
+/*
+ * No error
+ */
+{
+ floating_variant = g_variant_new ("o", "some string");
+}
+
+/*
+ * Expected a GVariant variadic argument of type ‘char *’ but saw one of type ‘int’.
+ * floating_variant = g_variant_new ("o", 152);
+ * ^
+ */
+{
+ floating_variant = g_variant_new ("o", 152);
+}
+
+/*
+ * No error
+ */
+{
+ const gchar *some_var = "hello world";
+ floating_variant = g_variant_new ("o", some_var);
+}
+
+/*
+ * No error
+ */
+{
+ const char *some_var = "hello world"; // try plain C types
+ floating_variant = g_variant_new ("o", some_var);
+}
+
+/*
+ * No error
+ */
+{
+ gchar *some_var = g_strdup_printf ("%s %s", "hello", "world");
+ floating_variant = g_variant_new ("o", some_var);
+ g_free (some_var);
+}
+
+/*
+ * No error
+ */
+{
+ floating_variant = g_variant_new ("g", "some string");
+}
+
+/*
+ * Expected a GVariant variadic argument of type ‘char *’ but saw one of type ‘int’.
+ * floating_variant = g_variant_new ("g", 152);
+ * ^
+ */
+{
+ floating_variant = g_variant_new ("g", 152);
+}
+
+/*
+ * No error
+ */
+{
+ const gchar *some_var = "hello world";
+ floating_variant = g_variant_new ("g", some_var);
+}
+
+/*
+ * No error
+ */
+{
+ const char *some_var = "hello world"; // try plain C types
+ floating_variant = g_variant_new ("g", some_var);
+}
+
+/*
+ * No error
+ */
+{
+ gchar *some_var = g_strdup_printf ("%s %s", "hello", "world");
+ floating_variant = g_variant_new ("g", some_var);
+ g_free (some_var);
+}
+
+/*
+ * Unexpected GVariant format strings ‘u’ with unpaired arguments. If using multiple format strings, they should be enclosed in brackets to create a tuple (e.g. ‘(su)’).
+ * floating_variant = g_variant_new ("su", "some", 56);
+ * ^
+ */
+{
+ // Missing tuple brackets
+ floating_variant = g_variant_new ("si", "some", 56);
+}
+
+/*
+ * No error
+ */
+{
+ // Simple tuple
+ floating_variant = g_variant_new ("(si)", "some", 56);
+}
+
+/*
+ * No error
+ */
+{
+ // Nested tuples
+ floating_variant = g_variant_new ("((s)(si))", "some", "other", 56);
+}
+
+/*
+ * No error
+ */
+{
+ // Empty tuple
+ floating_variant = g_variant_new ("()");
+}
+
+/*
+ * Invalid GVariant format string: tuple did not end with ‘)’.
+ * floating_variant = g_variant_new ("(si", "some", 56);
+ * ^
+ */
+{
+ // Unbalanced brackets
+ floating_variant = g_variant_new ("(si", "some", 56);
+}
+
+/*
+ * No error
+ */
+{
+ GVariant *some_var = g_variant_new_boolean (FALSE);
+ floating_variant = g_variant_new ("v", some_var);
+}
+
+/*
+ * No error
+ */
+{
+ GVariant *some_var = g_variant_new_boolean (FALSE);
+ floating_variant = g_variant_new ("v", (const GVariant *) some_var);
+}
+
+/*
+ * Expected a GVariant variadic argument of type ‘GVariant *’ but saw one of type ‘char *’.
+ * floating_variant = g_variant_new ("v", "nope");
+ * ^
+ */
+{
+ floating_variant = g_variant_new ("v", "nope");
+}
+
+/*
+ * No error
+ */
+{
+ floating_variant = g_variant_new ("v", g_variant_new_boolean (FALSE));
+}
+
+/*
+ * No error
+ */
+{
+ floating_variant = g_variant_new ("ms", "some string");
+}
+
+/*
+ * No error
+ */
+{
+ floating_variant = g_variant_new ("ms", NULL);
+}
+
+/*
+ * Expected a GVariant variadic argument of type ‘char *’ but saw NULL instead.
+ * floating_variant = g_variant_new ("s", NULL);
+ * ^
+ */
+{
+ // NULL strings are invalid.
+ floating_variant = g_variant_new ("s", NULL);
+}
+
+/*
+ * No error
+ */
+{
+ floating_variant = g_variant_new ("m(ss)", NULL, NULL);
+}
+
+/*
+ * Expected a GVariant variadic argument of type ‘GVariant *’ but saw NULL instead.
+ * floating_variant = g_variant_new ("@s", NULL);
+ * ^
+ */
+{
+ floating_variant = g_variant_new ("@s", NULL);
+}
+
+/*
+ * Expected a GVariant variadic argument of type ‘GVariant *’ but saw one of type ‘char *’.
+ * floating_variant = g_variant_new ("@s", "some string");
+ * ^
+ */
+{
+ floating_variant = g_variant_new ("@s", "some string");
+}
+
+/*
+ * No error
+ */
+{
+ GVariant *some_variant = g_variant_new_string ("asd");
+ floating_variant = g_variant_new ("@s", some_variant);
+}
+
+/*
+ * No error
+ */
+{
+ GVariant *some_variant1 = g_variant_new_string ("fasd");
+ GVariant *some_variant2 = g_variant_new_string ("asdasd");
+ floating_variant = g_variant_new ("(@s@ss)", some_variant1, some_variant2, "some string");
+}
+
+/*
+ * No error
+ */
+{
+ floating_variant = g_variant_new ("{ss}", "some string", "some string");
+}
+
+/*
+ * Expected a GVariant variadic argument of type ‘char *’ but saw one of type ‘int’.
+ * floating_variant = g_variant_new ("{ss}", 5, FALSE);
+ * ^
+ */
+{
+ floating_variant = g_variant_new ("{ss}", 5, FALSE);
+}
+
+/*
+ * Expected a GVariant basic type string but saw ‘m’.
+ * floating_variant = g_variant_new ("{msv}", "key", some_variant);
+ * ^
+ */
+{
+ // Non-basic type as the key
+ GVariant *some_variant = g_variant_new_string ("value");
+ floating_variant = g_variant_new ("{msv}", "key", some_variant);
+}
+
+/*
+ * Expected a GVariant variadic argument of type ‘char *’ but saw NULL instead.
+ * floating_variant = g_variant_new ("{ss}", "key", NULL);
+ * ^
+ */
+{
+ floating_variant = g_variant_new ("{ss}", "key", NULL);
+}
+
+/*
+ * Expected a GVariant variadic argument of type ‘char *’ but saw NULL instead.
+ * floating_variant = g_variant_new ("{ss}", NULL, "key");
+ * ^
+ */
+{
+ floating_variant = g_variant_new ("{ss}", NULL, "value");
+}
+
+/*
+ * Invalid GVariant format string: dict contains more than two elements.
+ * floating_variant = g_variant_new ("{sss}", "a", "b", "c");
+ * ^
+ */
+{
+ // Too many elements
+ floating_variant = g_variant_new ("{sss}", "a", "b", "c");
+}
+
+/*
+ * Invalid GVariant format string: dict did not contain exactly two elements.
+ * floating_variant = g_variant_new ("{s}", "asd");
+ * ^
+ */
+{
+ // Too few elements
+ floating_variant = g_variant_new ("{s}", "asd");
+}
+
+/*
+ * Invalid GVariant format string: dict did not contain exactly two elements.
+ * floating_variant = g_variant_new ("{}");
+ * ^
+ */
+{
+ // Too few elements
+ floating_variant = g_variant_new ("{}");
+}
+
+/*
+ * No error
+ */
+{
+ GVariant *some_variant = g_variant_new_boolean (FALSE);
+ floating_variant = g_variant_new ("*", some_variant);
+}
+
+/*
+ * No error
+ */
+{
+ GVariant *some_variant = g_variant_new_boolean (FALSE);
+ floating_variant = g_variant_new ("@*", some_variant);
+}
+
+/*
+ * No error
+ */
+{
+ GVariant *some_variant = g_variant_new_boolean (FALSE);
+ floating_variant = g_variant_new ("?", some_variant);
+}
+
+/*
+ * No error
+ */
+{
+ GVariant *some_variant = g_variant_new_boolean (FALSE);
+ floating_variant = g_variant_new ("@?", some_variant);
+}
+
+/*
+ * No error
+ */
+{
+ // FIXME: Check for non-basic types.
+ GVariant *some_variant = g_variant_new_tuple (NULL, 0);
+ floating_variant = g_variant_new ("@?", some_variant);
+}
+
+/*
+ * No error
+ */
+{
+ GVariant *some_variant = g_variant_new_tuple (NULL, 0);
+ floating_variant = g_variant_new ("r", some_variant);
+}
+
+/*
+ * No error
+ */
+{
+ GVariant *some_variant = g_variant_new_tuple (NULL, 0);
+ floating_variant = g_variant_new ("@r", some_variant);
+}
+
+/*
+ * No error
+ */
+{
+ // FIXME: Check for non-tuple types.
+ GVariant *some_variant = g_variant_new_boolean (FALSE);
+ floating_variant = g_variant_new ("@r", some_variant);
+}
+
+/*
+ * No error
+ */
+{
+ GVariantBuilder some_builder;
+ g_variant_builder_init (&some_builder, G_VARIANT_TYPE_STRING);
+ floating_variant = g_variant_new ("as", &some_builder);
+}
+
+/*
+ * No error
+ */
+{
+ GVariantBuilder *some_builder = g_variant_builder_new (G_VARIANT_TYPE_STRING);
+ floating_variant = g_variant_new ("as", some_builder);
+}
+
+/*
+ * No error
+ */
+{
+ floating_variant = g_variant_new ("as", NULL);
+}
+
+/*
+ * No error
+ */
+{
+ // FIXME: Check formats of GVariantBuilders.
+ GVariantBuilder *some_builder = g_variant_builder_new (G_VARIANT_TYPE_BOOLEAN);
+ floating_variant = g_variant_new ("as", some_builder);
+}
+
+/*
+ * No error
+ */
+{
+ // FIXME: Check formats of GVariantBuilders.
+ GVariantBuilder some_builder;
+ g_variant_builder_init (&some_builder, G_VARIANT_TYPE_BOOLEAN);
+ floating_variant = g_variant_new ("as", &some_builder);
+}
+
+/*
+ * No error
+ */
+{
+ // FIXME: Disallow NULL for non-definite array element types.
+ floating_variant = g_variant_new ("a?", NULL);
+}
+
+/*
+ * No error
+ */
+{
+ GVariantBuilder *some_builder = g_variant_builder_new (G_VARIANT_TYPE_TUPLE);
+ floating_variant = g_variant_new ("a(sss)", some_builder);
+}
+
+/*
+ * No error
+ */
+{
+ GVariantBuilder *some_builder = g_variant_builder_new (G_VARIANT_TYPE_DICT_ENTRY);
+ floating_variant = g_variant_new ("a{?*}", some_builder);
+}
+
+/*
+ * Expected a GVariant variadic argument of type ‘GVariantBuilder *’ but saw one of type ‘char *’.
+ * floating_variant = g_variant_new ("au", "nope");
+ * ^
+ */
+{
+ floating_variant = g_variant_new ("au", "nope");
+}
+
+/*
+ * No error
+ */
+{
+ const gchar *str_array[] = { "a", "b", NULL };
+ const gchar * const *some_str_array = (const gchar * const *) str_array;
+ floating_variant = g_variant_new ("^as", some_str_array);
+}
+
+/*
+ * No error
+ */
+{
+ const gchar *str_array[] = { "a", "b", NULL };
+ const gchar **some_str_array = (const gchar **) str_array;
+ floating_variant = g_variant_new ("^as", some_str_array);
+}
+
+/*
+ * No error
+ */
+{
+ gchar **some_str_array = g_malloc (sizeof (gchar *) * 2);
+ floating_variant = g_variant_new ("^as", some_str_array);
+}
+
+/*
+ * No error
+ */
+{
+ const gchar *some_str_array[] = { "a", "b", "c", NULL };
+ floating_variant = g_variant_new ("^as", some_str_array);
+}
+
+/*
+ * Expected a GVariant variadic argument of type ‘char **’ but saw NULL instead.
+ * floating_variant = g_variant_new ("^as", NULL);
+ * ^
+ */
+{
+ // String arrays must be NULL-terminated.
+ floating_variant = g_variant_new ("^as", NULL);
+}
+
+/*
+ * No error
+ */
+{
+ const gchar *str_array[] = { "a", "b", NULL };
+ const gchar * const *some_str_array = (const gchar * const *) str_array;
+ floating_variant = g_variant_new ("^a&s", some_str_array);
+}
+
+/*
+ * No error
+ */
+{
+ // FIXME: Validate that strings are object paths
+ const gchar *some_str_array[] = { "/some/obj/path", "/some/object/path", NULL };
+ floating_variant = g_variant_new ("^ao", some_str_array);
+}
+
+/*
+ * No error
+ */
+{
+ // FIXME: Validate that strings are object paths
+ const gchar *some_str_array[] = { "/some/obj/path", "/some/object/path", NULL };
+ floating_variant = g_variant_new ("^a&o", some_str_array);
+}
+
+/*
+ * No error
+ */
+{
+ floating_variant = g_variant_new ("^ay", "some byte string");
+}
+
+/*
+ * No error
+ */
+{
+ const gchar *some_byte_string = "hello mum";
+ floating_variant = g_variant_new ("^ay", some_byte_string);
+}
+
+/*
+ * No error
+ */
+{
+ const gchar some_byte_array[] = { 'a', 'b', 'c', '\0' };
+ floating_variant = g_variant_new ("^ay", some_byte_array);
+}
+
+/*
+ * No error
+ */
+{
+ floating_variant = g_variant_new ("^&ay", "some byte string");
+}
+
+/*
+ * No error
+ */
+{
+ const gchar *some_bytes_array[] = { "byte string", "more bytes", NULL };
+ floating_variant = g_variant_new ("^aay", some_bytes_array);
+}
+
+/*
+ * No error
+ */
+{
+ const gchar *some_bytes_array[] = { "byte string", "more bytes", NULL };
+ floating_variant = g_variant_new ("^a&ay", some_bytes_array);
+}
+
+/*
+ * No error
+ */
+{
+ va_list some_va_list;
+ floating_variant = g_variant_new_va ("(sss)", NULL, &some_va_list);
+}
+
+/*
+ * Unexpected GVariant format strings ‘nvalid’ with unpaired arguments. If using multiple format strings, they should be enclosed in brackets to create a tuple (e.g. ‘(invalid)’).
+ * floating_variant = g_variant_new_va ("invalid", NULL, &some_va_list);
+ * ^
+ */
+{
+ va_list some_va_list;
+ floating_variant = g_variant_new_va ("invalid", NULL, &some_va_list);
+}
+
+/*
+ * Non-literal GVariant format string in call to g_variant_new(). Cannot check format string correctness. Instead of a non-literal format string, use GVariantBuilder.
+ * floating_variant = g_variant_new (some_format, "hi", "there");
+ * ^
+ */
+{
+ // Check that non-const format strings are ignored.
+ gchar *some_format = g_malloc (5);
+ floating_variant = g_variant_new (some_format, "hi", "there");
+}
diff --git a/tests/wrapper-compiler-errors b/tests/wrapper-compiler-errors
new file mode 100755
index 0000000..ec2441b
--- /dev/null
+++ b/tests/wrapper-compiler-errors
@@ -0,0 +1,130 @@
+#!/bin/sh
+
+# Take an input file which contains one or more sections of the form:
+# /*
+# [Error message|‘No error’]
+# */
+# {
+# [Code]
+# }
+#
+# Sections are separated by ‘/*’ on a line of its own. The code must not contain
+# C-style comments (‘/* … */’), but can contain C++-style ones (‘// …’).
+#
+# The wrapper script takes each section and wraps the code in a main() function
+# with some standard variables and reference count handling. It then compiles
+# the code using Clang with gnome-clang, and checks the compiler output against
+# the expected error message. If the expected error message is ‘No error’ it
+# asserts there’s no error.
+
+input_filename=$1
+temp_dir=`mktemp -d`
+
+echo "Reading input from ${input_filename}."
+echo "Using temporary directory ${temp_dir}."
+echo ""
+
+test_status=0
+
+# Before starting, work out the compiler’s system include paths.
+# Thanks to: http://stackoverflow.com/a/17940271/2931197
+system_includes=`echo | cpp -Wp,-v 2>&1 | grep '^[[:space:]]' | \
+ sed -e 's/^[[:space:]]*/-isystem/' | tr "\n" ' '`
+
+# Split the input file up into sections, delimiting on ‘/*’ on a line by itself.
+csplit --keep-files --elide-empty-files --silent \
+ --prefix="${temp_dir}/${input_filename}_" \
+ --suffix-format='%02d.c' \
+ "${input_filename}" '/\/\*/' '{*}'
+
+num=0
+while [[ -f `printf "${temp_dir}/${input_filename}_%02d.c" ${num}` ]]; do
+ section_filename=`printf ${temp_dir}/${input_filename}_%02d.c ${num}`
+ expected_error_filename=`printf ${temp_dir}/${input_filename}_%02d.expected ${num}`
+ actual_error_filename=`printf ${temp_dir}/${input_filename}_%02d.actual ${num}`
+
+ echo "${section_filename}:"
+ echo "-------"
+ echo ""
+ echo " - Building section file ${section_filename}."
+ echo " - Outputting to error files ${expected_error_filename} and ${actual_error_filename}."
+
+ # Wrap the section’s code with a prefix and suffix.
+ (cat << EOF
+#include <glib.h>
+
+int
+main (void)
+{
+ GVariant *floating_variant = NULL;
+EOF
+ cat $section_filename
+ cat << EOF
+ if (floating_variant != NULL)
+ g_variant_unref (floating_variant);
+}
+EOF
+) > $section_filename.tmp
+ mv -f $section_filename.tmp $section_filename
+
+ num=$((num + 1))
+
+ # Extract the expected comment.
+ sed -n '/^\/\*/n; s/^ \* \(.*\)/\1/p' < $section_filename > $expected_error_filename
+
+ if [[ $(<"${expected_error_filename}") == "No error" ]]; then
+ echo " - Expecting no error"
+ expect_error=false
+ else
+ echo " - Expecting an error"
+ expect_error=true
+ fi
+
+ # Run the compiler.
+ # TODO: Get rid of the hard-coding.
+ gnome-clang -cc1 -analyze -std=c89 -Wno-visibility \
+ `pkg-config --cflags glib-2.0` \
+ $system_includes \
+ $section_filename > $actual_error_filename 2>&1
+
+ # Compare the errors.
+ if $expect_error; then
+ # Expecting an error. Check that the expected errors are a
+ # subset of the actual errors, to allow for spurious Clang
+ # warnings because generated code is hard.
+ grep -F "$(<${expected_error_filename})" "${actual_error_filename}" >/dev/null
+ grep_status=$?
+
+ if [ $grep_status -ne 0 ]; then
+ echo " * Error: Expected compiler error was not seen." 1>&2
+
+ echo " - Expected:" 1>&2
+ cat "${expected_error_filename}" 1>&2
+ echo "" 1>&2
+ echo " - Actual:" 1>&2
+ cat "${actual_error_filename}" 1>&2
+
+ test_status=1
+ fi
+ else
+ # Expecting no error.
+ if [[ -s "${actual_error_filename}" ]]; then
+ echo " * Error: Compiler error when none was expected." 1>&2
+
+ echo " - Actual:" 1>&2
+ cat "${actual_error_filename}" 1>&2
+
+ test_status=1
+ fi
+ fi
+
+ echo ""
+ echo ""
+done
+
+# Exit status. Leave the temporary directory alone on failure.
+if [[ $test_status -eq 0 ]]; then
+ rm -rf ${temp_dir}
+fi
+
+exit $test_status