]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
analyzer: fix ICE on __builtin_ms_va_copy [PR105765]
authorDavid Malcolm <dmalcolm@redhat.com>
Wed, 19 Oct 2022 20:49:38 +0000 (16:49 -0400)
committerDavid Malcolm <dmalcolm@redhat.com>
Wed, 19 Oct 2022 20:49:38 +0000 (16:49 -0400)
gcc/analyzer/ChangeLog:
PR analyzer/105765
* varargs.cc (get_BT_VALIST_ARG): Rename to...
(get_va_copy_arg): ...this, and update logic for determining level
of indirection of va_copy's argument to use type of argument,
rather than looking at va_list_type_node, to correctly handle
__builtin_ms_va_copy.
(get_stateful_BT_VALIST_ARG): Rename to...
(get_stateful_va_copy_arg): ...this.
(va_list_state_machine::on_va_copy): Update for renaming.
(region_model::impl_call_va_copy): Likewise.

gcc/testsuite/ChangeLog:
PR analyzer/105765
* gcc.dg/analyzer/stdarg-1-ms_abi.c: New test, based on stdarg-1.c.
* gcc.dg/analyzer/stdarg-1-sysv_abi.c: Likewise.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
gcc/analyzer/varargs.cc
gcc/testsuite/gcc.dg/analyzer/stdarg-1-ms_abi.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/analyzer/stdarg-1-sysv_abi.c [new file with mode: 0644]

index b2e6cd53c066859e0578f81fab2ecec11a33e330..20c83dbbadc8bc78d763f9d6ba12046a82ecc3ef 100644 (file)
@@ -132,7 +132,7 @@ namespace ana {
      __builtin_va_start (&ap, [...]);
 
    except for the 2nd param of __builtin_va_copy, where the type
-   is already target-dependent (see the discussion of BT_VALIST_ARG
+   is already target-dependent (see the discussion of get_va_copy_arg
    below).  */
 
 /* Get a tree for diagnostics.
@@ -147,26 +147,33 @@ get_va_list_diag_arg (tree va_list_tree)
   return va_list_tree;
 }
 
-/* Get argument ARG_IDX of type BT_VALIST_ARG (for use by va_copy).
+/* Get argument ARG_IDX of va_copy.
 
    builtin-types.def has:
      DEF_PRIMITIVE_TYPE (BT_VALIST_ARG, va_list_arg_type_node)
 
    and c_common_nodes_and_builtins initializes va_list_arg_type_node
    based on whether TREE_CODE (va_list_type_node) is of ARRAY_TYPE or
-   not, giving either one or zero levels of indirection.  */
+   not, giving either one or zero levels of indirection.
+
+   Alternatively we could be dealing with __builtin_ms_va_copy or
+   __builtin_sysv_va_copy.
+
+   Handle this by looking at the types of the argument in question.  */
 
 static const svalue *
-get_BT_VALIST_ARG (const region_model *model,
-                  region_model_context *ctxt,
-                  const gcall *call,
-                  unsigned arg_idx)
+get_va_copy_arg (const region_model *model,
+                region_model_context *ctxt,
+                const gcall *call,
+                unsigned arg_idx)
 {
   tree arg = gimple_call_arg (call, arg_idx);
   const svalue *arg_sval = model->get_rvalue (arg, ctxt);
   if (const svalue *cast = arg_sval->maybe_undo_cast ())
     arg_sval = cast;
-  if (TREE_CODE (va_list_type_node) == ARRAY_TYPE)
+  /* Expect a POINTER_TYPE; does it point to an array type?  */
+  gcc_assert (TREE_CODE (TREE_TYPE (arg)) == POINTER_TYPE);
+  if (TREE_CODE (TREE_TYPE (TREE_TYPE (arg))) == ARRAY_TYPE)
     {
       /* va_list_arg_type_node is a pointer to a va_list;
         return *ARG_SVAL.  */
@@ -551,19 +558,19 @@ va_list_state_machine::check_for_ended_va_list (sm_context *sm_ctxt,
                                                 usage_fnname));
 }
 
-/* Get the svalue with associated va_list_state_machine state for a
-   BT_VALIST_ARG for ARG_IDX of CALL, if SM_CTXT supports this,
+/* Get the svalue with associated va_list_state_machine state for
+   ARG_IDX of CALL to va_copy, if SM_CTXT supports this,
    or NULL otherwise.  */
 
 static const svalue *
-get_stateful_BT_VALIST_ARG (sm_context *sm_ctxt,
-                           const gcall *call,
-                           unsigned arg_idx)
+get_stateful_va_copy_arg (sm_context *sm_ctxt,
+                         const gcall *call,
+                         unsigned arg_idx)
 {
   if (const program_state *new_state = sm_ctxt->get_new_program_state ())
     {
       const region_model *new_model = new_state->m_region_model;
-      const svalue *arg = get_BT_VALIST_ARG (new_model, NULL, call, arg_idx);
+      const svalue *arg = get_va_copy_arg (new_model, NULL, call, arg_idx);
       return arg;
     }
   return NULL;
@@ -576,7 +583,7 @@ va_list_state_machine::on_va_copy (sm_context *sm_ctxt,
                                   const supernode *node,
                                   const gcall *call) const
 {
-  const svalue *src_arg = get_stateful_BT_VALIST_ARG (sm_ctxt, call, 1);
+  const svalue *src_arg = get_stateful_va_copy_arg (sm_ctxt, call, 1);
   if (src_arg)
     check_for_ended_va_list (sm_ctxt, node, call, src_arg, "va_copy");
 
@@ -686,7 +693,7 @@ region_model::impl_call_va_copy (const call_details &cd)
 {
   const svalue *out_dst_ptr = cd.get_arg_svalue (0);
   const svalue *in_va_list
-    = get_BT_VALIST_ARG (this, cd.get_ctxt (), cd.get_call_stmt (), 1);
+    = get_va_copy_arg (this, cd.get_ctxt (), cd.get_call_stmt (), 1);
   in_va_list = check_for_poison (in_va_list,
                                 get_va_list_diag_arg (cd.get_arg_tree (1)),
                                 cd.get_ctxt ());
diff --git a/gcc/testsuite/gcc.dg/analyzer/stdarg-1-ms_abi.c b/gcc/testsuite/gcc.dg/analyzer/stdarg-1-ms_abi.c
new file mode 100644 (file)
index 0000000..b0143a7
--- /dev/null
@@ -0,0 +1,437 @@
+/* As per stdarg-1.c, but using the ms_abi versions of the builtins.  */
+
+/* { dg-do compile { target { x86_64-*-* && lp64 } } } */
+
+#include "analyzer-decls.h"
+
+/* Unpacking a va_list.  */
+
+static void __attribute__((noinline))
+__analyzer_called_by_test_1 (int placeholder, ...)
+{
+  const char *s;
+  int i;
+  char c;
+
+  __builtin_ms_va_list ap;
+  __builtin_ms_va_start (ap, placeholder);
+
+  s = __builtin_va_arg (ap, char *);
+  __analyzer_eval (s[0] == 'f'); /* { dg-warning "TRUE" } */
+
+  i = __builtin_va_arg (ap, int);
+  __analyzer_eval (i == 1066); /* { dg-warning "TRUE" } */
+
+  c = (char)__builtin_va_arg (ap, int);
+  __analyzer_eval (c == '@'); /* { dg-warning "TRUE" } */
+
+  __builtin_ms_va_end (ap);
+}
+
+void test_1 (void)
+{
+  __analyzer_called_by_test_1 (42, "foo", 1066, '@');
+}
+
+/* Unpacking a va_list passed from an intermediate function.  */
+
+static void __attribute__((noinline))
+__analyzer_test_2_inner (__builtin_ms_va_list ap)
+{
+  const char *s;
+  int i;
+  char c;
+  
+  s = __builtin_va_arg (ap, char *);
+  __analyzer_eval (s[0] == 'f'); /* { dg-warning "TRUE" } */
+
+  i = __builtin_va_arg (ap, int);
+  __analyzer_eval (i == 1066); /* { dg-warning "TRUE" } */
+
+  c = (char)__builtin_va_arg (ap, int);
+  __analyzer_eval (c == '@'); /* { dg-warning "TRUE" } */
+}
+
+static void __attribute__((noinline))
+__analyzer_test_2_middle (int placeholder, ...)
+{
+  __builtin_ms_va_list ap;
+  __builtin_ms_va_start (ap, placeholder);
+  __analyzer_test_2_inner (ap);
+  __builtin_ms_va_end (ap);
+}
+
+void test_2 (void)
+{
+  __analyzer_test_2_middle (42, "foo", 1066, '@');
+}
+
+/* Not enough args.  */
+
+static void __attribute__((noinline))
+__analyzer_called_by_test_not_enough_args (int placeholder, ...)
+{
+  const char *s;
+  int i;
+  
+  __builtin_ms_va_list ap;
+  __builtin_ms_va_start (ap, placeholder);
+
+  s = __builtin_va_arg (ap, char *);
+  __analyzer_eval (s[0] == 'f'); /* { dg-warning "TRUE" } */
+
+  i = __builtin_va_arg (ap, int); /* { dg-warning "'ap' has no more arguments \\(1 consumed\\) \\\[CWE-685\\\]" } */
+
+  __builtin_ms_va_end (ap);
+}
+
+void test_not_enough_args (void)
+{
+  __analyzer_called_by_test_not_enough_args (42, "foo");
+}
+
+/* Not enough args, with an intermediate function.  */
+
+static void __attribute__((noinline))
+__analyzer_test_not_enough_args_2_inner (__builtin_ms_va_list ap)
+{
+  const char *s;
+  int i;
+  
+  s = __builtin_va_arg (ap, char *);
+  __analyzer_eval (s[0] == 'f'); /* { dg-warning "TRUE" } */
+
+  i = __builtin_va_arg (ap, int); /* { dg-warning "'ap' has no more arguments \\(1 consumed\\)" } */
+}
+
+static void __attribute__((noinline))
+__analyzer_test_not_enough_args_2_middle (int placeholder, ...)
+{
+  __builtin_ms_va_list ap;
+  __builtin_ms_va_start (ap, placeholder);
+  __analyzer_test_not_enough_args_2_inner (ap);
+  __builtin_ms_va_end (ap);
+}
+
+void test_not_enough_args_2 (void)
+{
+  __analyzer_test_not_enough_args_2_middle (42, "foo");
+}
+
+/* Excess args (not a problem).  */
+
+static void __attribute__((noinline))
+__analyzer_called_by_test_excess_args (int placeholder, ...)
+{
+  const char *s;
+  
+  __builtin_ms_va_list ap;
+  __builtin_ms_va_start (ap, placeholder);
+
+  s = __builtin_va_arg (ap, char *);
+  __analyzer_eval (s[0] == 'f'); /* { dg-warning "TRUE" } */
+
+  __builtin_ms_va_end (ap);
+}
+
+void test_excess_args (void)
+{
+  __analyzer_called_by_test_excess_args (42, "foo", "bar");
+}
+
+/* Missing va_start.  */
+
+void test_missing_va_start (int placeholder, ...)
+{
+  __builtin_ms_va_list ap; /* { dg-message "region created on stack here" } */
+  int i = __builtin_va_arg (ap, int); /* { dg-warning "use of uninitialized value 'ap'" } */
+}
+
+/* Missing va_end.  */
+
+void test_missing_va_end (int placeholder, ...)
+{
+  int i;
+  __builtin_ms_va_list ap;
+  __builtin_ms_va_start (ap, placeholder); /* { dg-message "\\(1\\) 'va_start' called here" } */
+  i = __builtin_va_arg (ap, int);
+} /* { dg-warning "missing call to 'va_end'" "warning" } */
+/* { dg-message "\\(2\\) missing call to 'va_end' to match 'va_start' at \\(1\\)" "final event" { target *-*-* } .-1 } */
+
+/* Missing va_end due to error-handling.  */
+
+int test_missing_va_end_2 (int placeholder, ...)
+{
+  int i, j;
+  __builtin_ms_va_list ap;
+  __builtin_ms_va_start (ap, placeholder); /* { dg-message "\\(1\\) 'va_start' called here" } */
+  i = __builtin_va_arg (ap, int);
+  if (i == 42)
+    {
+      __builtin_ms_va_end (ap);
+      return -1;
+    }
+  j = __builtin_va_arg (ap, int);
+  if (j == 1066) /* { dg-message "branch" } */
+    return -1; /* { dg-message "here" } */
+  __builtin_ms_va_end (ap);
+  return 0;
+} /* { dg-warning "missing call to 'va_end'" "warning" } */
+
+/* va_arg after va_end.  */
+
+void test_va_arg_after_va_end (int placeholder, ...)
+{
+  int i;
+  __builtin_ms_va_list ap;
+  __builtin_ms_va_start (ap, placeholder);
+  __builtin_ms_va_end (ap); /* { dg-message "'va_end' called here" } */
+  i = __builtin_va_arg (ap, int); /* { dg-warning "'va_arg' after 'va_end'" } */
+}
+
+/* Type mismatch: expect int, but passed a char *.  */
+
+static void __attribute__((noinline))
+__analyzer_called_by_test_type_mismatch_1 (int placeholder, ...)
+{
+  int i;
+  
+  __builtin_ms_va_list ap;
+  __builtin_ms_va_start (ap, placeholder);
+
+  i = __builtin_va_arg (ap, int); /* { dg-warning "'va_arg' expected 'int' but received '\[^\n\r\]*' for variadic argument 1 of 'ap' \\\[CWE-686\\\]" } */
+
+  __builtin_ms_va_end (ap);
+}
+
+void test_type_mismatch_1 (void)
+{
+  __analyzer_called_by_test_type_mismatch_1 (42, "foo");
+}
+
+/* Type mismatch: expect char *, but passed an int.  */
+
+static void __attribute__((noinline))
+__analyzer_called_by_test_type_mismatch_2 (int placeholder, ...)
+{
+  const char *str;
+  
+  __builtin_ms_va_list ap;
+  __builtin_ms_va_start (ap, placeholder);
+
+  str = __builtin_va_arg (ap, const char *); /* { dg-warning "'va_arg' expected 'const char \\*' but received 'int' for variadic argument 1" } */
+
+  __builtin_ms_va_end (ap);
+}
+
+void test_type_mismatch_2 (void)
+{
+  __analyzer_called_by_test_type_mismatch_2 (42, 1066);
+}
+
+/* As above, but with an intermediate function.  */
+
+static void __attribute__((noinline))
+__analyzer_test_type_mismatch_3_inner (__builtin_ms_va_list ap)
+{
+  const char *str;
+  
+  str = __builtin_va_arg (ap, const char *); /* { dg-warning "'va_arg' expected 'const char \\*' but received 'int' for variadic argument 1 of 'ap'" } */
+}
+
+static void __attribute__((noinline))
+__analyzer_test_type_mismatch_3_middle (int placeholder, ...)
+{
+  __builtin_ms_va_list ap;
+  __builtin_ms_va_start (ap, placeholder);
+
+  __analyzer_test_type_mismatch_3_inner (ap);
+
+  __builtin_ms_va_end (ap);
+}
+
+void test_type_mismatch_3 (void)
+{
+  __analyzer_test_type_mismatch_3_middle (42, 1066);
+}
+
+/* Multiple traversals of the args.  */
+
+static void __attribute__((noinline))
+__analyzer_called_by_test_multiple_traversals (int placeholder, ...)
+{
+  __builtin_ms_va_list ap;
+
+  /* First traversal.  */
+  {
+    int i, j;
+
+    __builtin_ms_va_start (ap, placeholder);
+
+    i = __builtin_va_arg (ap, int);
+    __analyzer_eval (i == 1066); /* { dg-warning "TRUE" } */
+
+    j = __builtin_va_arg (ap, int);
+    __analyzer_eval (j == 42); /* { dg-warning "TRUE" } */
+
+    __builtin_ms_va_end (ap);
+  }
+
+  /* Second traversal.  */
+  {
+    int i, j;
+
+    __builtin_ms_va_start (ap, placeholder);
+
+    i = __builtin_va_arg (ap, int);
+    __analyzer_eval (i == 1066); /* { dg-warning "TRUE" } */
+
+    j = __builtin_va_arg (ap, int);
+    __analyzer_eval (j == 42); /* { dg-warning "TRUE" } */
+
+    __builtin_ms_va_end (ap);
+  }
+}
+
+void test_multiple_traversals (void)
+{
+  __analyzer_called_by_test_multiple_traversals (0, 1066, 42);
+}
+
+/* Multiple traversals, using va_copy.  */
+
+static void __attribute__((noinline))
+__analyzer_called_by_test_multiple_traversals_2 (int placeholder, ...)
+{
+  int i, j;
+  __builtin_ms_va_list args1;
+  __builtin_ms_va_list args2;
+
+  __builtin_ms_va_start (args1, placeholder);
+  __builtin_ms_va_copy (args2, args1);
+
+  /* First traversal.  */
+  i = __builtin_va_arg (args1, int);
+  __analyzer_eval (i == 1066); /* { dg-warning "TRUE" } */
+  j = __builtin_va_arg (args1, int);
+  __analyzer_eval (j == 42); /* { dg-warning "TRUE" } */
+  __builtin_ms_va_end (args1);
+
+  /* Traversal of copy.  */
+  i = __builtin_va_arg (args2, int);
+  __analyzer_eval (i == 1066); /* { dg-warning "TRUE" } */
+  j = __builtin_va_arg (args2, int);
+  __analyzer_eval (j == 42); /* { dg-warning "TRUE" } */
+  __builtin_ms_va_end (args2);
+}
+
+void test_multiple_traversals_2 (void)
+{
+  __analyzer_called_by_test_multiple_traversals_2 (0, 1066, 42);
+}
+
+/* Multiple traversals, using va_copy after a va_arg.  */
+
+static void __attribute__((noinline))
+__analyzer_called_by_test_multiple_traversals_3 (int placeholder, ...)
+{
+  int i, j;
+  __builtin_ms_va_list args1;
+  __builtin_ms_va_list args2;
+
+  __builtin_ms_va_start (args1, placeholder);
+
+  /* First traversal.  */
+  i = __builtin_va_arg (args1, int);
+  __analyzer_eval (i == 1066); /* { dg-warning "TRUE" } */
+
+  /* va_copy after the first va_arg. */
+  __builtin_ms_va_copy (args2, args1);
+
+  j = __builtin_va_arg (args1, int);
+  __analyzer_eval (j == 42); /* { dg-warning "TRUE" } */
+  __builtin_ms_va_end (args1);
+
+  /* Traversal of copy.  */
+  j = __builtin_va_arg (args2, int);
+  __analyzer_eval (j == 42); /* { dg-warning "TRUE" } */
+  __builtin_ms_va_end (args2);
+}
+
+void test_multiple_traversals_3 (void)
+{
+  __analyzer_called_by_test_multiple_traversals_3 (0, 1066, 42);
+}
+
+/* va_copy after va_end.  */
+
+void test_va_copy_after_va_end (int placeholder, ...)
+{
+  __builtin_ms_va_list ap1, ap2;
+  __builtin_ms_va_start (ap1, placeholder);
+  __builtin_ms_va_end (ap1); /* { dg-message "'va_end' called here" } */
+  __builtin_ms_va_copy (ap2, ap1); /* { dg-warning "'va_copy' after 'va_end'" } */
+  __builtin_ms_va_end (ap2);
+}
+
+/* leak of va_copy.  */
+
+void test_leak_of_va_copy (int placeholder, ...)
+{
+  __builtin_ms_va_list ap1, ap2;
+  __builtin_ms_va_start (ap1, placeholder);
+  __builtin_ms_va_copy (ap2, ap1); /* { dg-message "'va_copy' called here" } */
+  __builtin_ms_va_end (ap1);
+} /* { dg-warning "missing call to 'va_end'" "warning" } */
+  /* { dg-message "missing call to 'va_end' to match 'va_copy' at \\(1\\)" "final event" { target *-*-* } .-1 } */
+
+/* double va_end.  */
+
+void test_double_va_end (int placeholder, ...)
+{
+  __builtin_ms_va_list ap;
+  __builtin_ms_va_start (ap, placeholder);
+  __builtin_ms_va_end (ap); /* { dg-message "'va_end' called here" } */
+  __builtin_ms_va_end (ap); /* { dg-warning "'va_end' after 'va_end'" } */
+}
+
+/* double va_start.  */
+
+void test_double_va_start (int placeholder, ...)
+{
+  int i;
+  __builtin_ms_va_list ap;
+  __builtin_ms_va_start (ap, placeholder); /* { dg-message "'va_start' called here" } */
+  __builtin_ms_va_start (ap, placeholder);  /* { dg-warning "missing call to 'va_end'" "warning" } */
+  /* { dg-message "missing call to 'va_end' to match 'va_start' at \\(1\\)" "final event" { target *-*-* } .-1 } */
+  __builtin_ms_va_end (ap);
+}
+
+/* va_copy before va_start.  */
+
+void test_va_copy_before_va_start (int placeholder, ...)
+{
+  __builtin_ms_va_list ap1; /* { dg-message "region created on stack here" } */
+  __builtin_ms_va_list ap2;
+  __builtin_ms_va_copy (ap2, ap1); /* { dg-warning "use of uninitialized value 'ap1'" } */
+  __builtin_ms_va_end (ap2);
+}
+
+/* Verify that we complain about uses of a va_list after the function 
+   in which va_start was called has returned.  */
+
+__builtin_ms_va_list global_ap;
+
+static void __attribute__((noinline))
+__analyzer_called_by_test_va_arg_after_return (int placeholder, ...)
+{
+  __builtin_ms_va_start (global_ap, placeholder);
+  __builtin_ms_va_end (global_ap);
+}
+
+void test_va_arg_after_return (void)
+{
+  int i;
+  __analyzer_called_by_test_va_arg_after_return (42, 1066);
+  i = __builtin_va_arg (global_ap, int); /* { dg-warning "dereferencing pointer 'global_ap' to within stale stack frame" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/stdarg-1-sysv_abi.c b/gcc/testsuite/gcc.dg/analyzer/stdarg-1-sysv_abi.c
new file mode 100644 (file)
index 0000000..1dc97ea
--- /dev/null
@@ -0,0 +1,437 @@
+/* As per stdarg-1.c, but using the sysv_abi versions of the builtins.  */
+
+/* { dg-do compile { target { x86_64-*-* && lp64 } } } */
+
+#include "analyzer-decls.h"
+
+/* Unpacking a va_list.  */
+
+static void __attribute__((noinline))
+__analyzer_called_by_test_1 (int placeholder, ...)
+{
+  const char *s;
+  int i;
+  char c;
+
+  __builtin_sysv_va_list ap;
+  __builtin_sysv_va_start (ap, placeholder);
+
+  s = __builtin_va_arg (ap, char *);
+  __analyzer_eval (s[0] == 'f'); /* { dg-warning "TRUE" } */
+
+  i = __builtin_va_arg (ap, int);
+  __analyzer_eval (i == 1066); /* { dg-warning "TRUE" } */
+
+  c = (char)__builtin_va_arg (ap, int);
+  __analyzer_eval (c == '@'); /* { dg-warning "TRUE" } */
+
+  __builtin_sysv_va_end (ap);
+}
+
+void test_1 (void)
+{
+  __analyzer_called_by_test_1 (42, "foo", 1066, '@');
+}
+
+/* Unpacking a va_list passed from an intermediate function.  */
+
+static void __attribute__((noinline))
+__analyzer_test_2_inner (__builtin_sysv_va_list ap)
+{
+  const char *s;
+  int i;
+  char c;
+  
+  s = __builtin_va_arg (ap, char *);
+  __analyzer_eval (s[0] == 'f'); /* { dg-warning "TRUE" } */
+
+  i = __builtin_va_arg (ap, int);
+  __analyzer_eval (i == 1066); /* { dg-warning "TRUE" } */
+
+  c = (char)__builtin_va_arg (ap, int);
+  __analyzer_eval (c == '@'); /* { dg-warning "TRUE" } */
+}
+
+static void __attribute__((noinline))
+__analyzer_test_2_middle (int placeholder, ...)
+{
+  __builtin_sysv_va_list ap;
+  __builtin_sysv_va_start (ap, placeholder);
+  __analyzer_test_2_inner (ap);
+  __builtin_sysv_va_end (ap);
+}
+
+void test_2 (void)
+{
+  __analyzer_test_2_middle (42, "foo", 1066, '@');
+}
+
+/* Not enough args.  */
+
+static void __attribute__((noinline))
+__analyzer_called_by_test_not_enough_args (int placeholder, ...)
+{
+  const char *s;
+  int i;
+  
+  __builtin_sysv_va_list ap;
+  __builtin_sysv_va_start (ap, placeholder);
+
+  s = __builtin_va_arg (ap, char *);
+  __analyzer_eval (s[0] == 'f'); /* { dg-warning "TRUE" } */
+
+  i = __builtin_va_arg (ap, int); /* { dg-warning "'ap' has no more arguments \\(1 consumed\\) \\\[CWE-685\\\]" } */
+
+  __builtin_sysv_va_end (ap);
+}
+
+void test_not_enough_args (void)
+{
+  __analyzer_called_by_test_not_enough_args (42, "foo");
+}
+
+/* Not enough args, with an intermediate function.  */
+
+static void __attribute__((noinline))
+__analyzer_test_not_enough_args_2_inner (__builtin_sysv_va_list ap)
+{
+  const char *s;
+  int i;
+  
+  s = __builtin_va_arg (ap, char *);
+  __analyzer_eval (s[0] == 'f'); /* { dg-warning "TRUE" } */
+
+  i = __builtin_va_arg (ap, int); /* { dg-warning "'ap' has no more arguments \\(1 consumed\\)" } */
+}
+
+static void __attribute__((noinline))
+__analyzer_test_not_enough_args_2_middle (int placeholder, ...)
+{
+  __builtin_sysv_va_list ap;
+  __builtin_sysv_va_start (ap, placeholder);
+  __analyzer_test_not_enough_args_2_inner (ap);
+  __builtin_sysv_va_end (ap);
+}
+
+void test_not_enough_args_2 (void)
+{
+  __analyzer_test_not_enough_args_2_middle (42, "foo");
+}
+
+/* Excess args (not a problem).  */
+
+static void __attribute__((noinline))
+__analyzer_called_by_test_excess_args (int placeholder, ...)
+{
+  const char *s;
+  
+  __builtin_sysv_va_list ap;
+  __builtin_sysv_va_start (ap, placeholder);
+
+  s = __builtin_va_arg (ap, char *);
+  __analyzer_eval (s[0] == 'f'); /* { dg-warning "TRUE" } */
+
+  __builtin_sysv_va_end (ap);
+}
+
+void test_excess_args (void)
+{
+  __analyzer_called_by_test_excess_args (42, "foo", "bar");
+}
+
+/* Missing va_start.  */
+
+void test_missing_va_start (int placeholder, ...)
+{
+  __builtin_sysv_va_list ap; /* { dg-message "region created on stack here" } */
+  int i = __builtin_va_arg (ap, int); /* { dg-warning "use of uninitialized value 'ap'" } */
+}
+
+/* Missing va_end.  */
+
+void test_missing_va_end (int placeholder, ...)
+{
+  int i;
+  __builtin_sysv_va_list ap;
+  __builtin_sysv_va_start (ap, placeholder); /* { dg-message "\\(1\\) 'va_start' called here" } */
+  i = __builtin_va_arg (ap, int);
+} /* { dg-warning "missing call to 'va_end'" "warning" } */
+/* { dg-message "\\(2\\) missing call to 'va_end' to match 'va_start' at \\(1\\)" "final event" { target *-*-* } .-1 } */
+
+/* Missing va_end due to error-handling.  */
+
+int test_missing_va_end_2 (int placeholder, ...)
+{
+  int i, j;
+  __builtin_sysv_va_list ap;
+  __builtin_sysv_va_start (ap, placeholder); /* { dg-message "\\(1\\) 'va_start' called here" } */
+  i = __builtin_va_arg (ap, int);
+  if (i == 42)
+    {
+      __builtin_sysv_va_end (ap);
+      return -1;
+    }
+  j = __builtin_va_arg (ap, int);
+  if (j == 1066) /* { dg-message "branch" } */
+    return -1; /* { dg-message "here" } */
+  __builtin_sysv_va_end (ap);
+  return 0;
+} /* { dg-warning "missing call to 'va_end'" "warning" } */
+
+/* va_arg after va_end.  */
+
+void test_va_arg_after_va_end (int placeholder, ...)
+{
+  int i;
+  __builtin_sysv_va_list ap;
+  __builtin_sysv_va_start (ap, placeholder);
+  __builtin_sysv_va_end (ap); /* { dg-message "'va_end' called here" } */
+  i = __builtin_va_arg (ap, int); /* { dg-warning "'va_arg' after 'va_end'" } */
+}
+
+/* Type mismatch: expect int, but passed a char *.  */
+
+static void __attribute__((noinline))
+__analyzer_called_by_test_type_mismatch_1 (int placeholder, ...)
+{
+  int i;
+  
+  __builtin_sysv_va_list ap;
+  __builtin_sysv_va_start (ap, placeholder);
+
+  i = __builtin_va_arg (ap, int); /* { dg-warning "'va_arg' expected 'int' but received '\[^\n\r\]*' for variadic argument 1 of 'ap' \\\[CWE-686\\\]" } */
+
+  __builtin_sysv_va_end (ap);
+}
+
+void test_type_mismatch_1 (void)
+{
+  __analyzer_called_by_test_type_mismatch_1 (42, "foo");
+}
+
+/* Type mismatch: expect char *, but passed an int.  */
+
+static void __attribute__((noinline))
+__analyzer_called_by_test_type_mismatch_2 (int placeholder, ...)
+{
+  const char *str;
+  
+  __builtin_sysv_va_list ap;
+  __builtin_sysv_va_start (ap, placeholder);
+
+  str = __builtin_va_arg (ap, const char *); /* { dg-warning "'va_arg' expected 'const char \\*' but received 'int' for variadic argument 1" } */
+
+  __builtin_sysv_va_end (ap);
+}
+
+void test_type_mismatch_2 (void)
+{
+  __analyzer_called_by_test_type_mismatch_2 (42, 1066);
+}
+
+/* As above, but with an intermediate function.  */
+
+static void __attribute__((noinline))
+__analyzer_test_type_mismatch_3_inner (__builtin_sysv_va_list ap)
+{
+  const char *str;
+  
+  str = __builtin_va_arg (ap, const char *); /* { dg-warning "'va_arg' expected 'const char \\*' but received 'int' for variadic argument 1 of 'ap'" } */
+}
+
+static void __attribute__((noinline))
+__analyzer_test_type_mismatch_3_middle (int placeholder, ...)
+{
+  __builtin_sysv_va_list ap;
+  __builtin_sysv_va_start (ap, placeholder);
+
+  __analyzer_test_type_mismatch_3_inner (ap);
+
+  __builtin_sysv_va_end (ap);
+}
+
+void test_type_mismatch_3 (void)
+{
+  __analyzer_test_type_mismatch_3_middle (42, 1066);
+}
+
+/* Multiple traversals of the args.  */
+
+static void __attribute__((noinline))
+__analyzer_called_by_test_multiple_traversals (int placeholder, ...)
+{
+  __builtin_sysv_va_list ap;
+
+  /* First traversal.  */
+  {
+    int i, j;
+
+    __builtin_sysv_va_start (ap, placeholder);
+
+    i = __builtin_va_arg (ap, int);
+    __analyzer_eval (i == 1066); /* { dg-warning "TRUE" } */
+
+    j = __builtin_va_arg (ap, int);
+    __analyzer_eval (j == 42); /* { dg-warning "TRUE" } */
+
+    __builtin_sysv_va_end (ap);
+  }
+
+  /* Second traversal.  */
+  {
+    int i, j;
+
+    __builtin_sysv_va_start (ap, placeholder);
+
+    i = __builtin_va_arg (ap, int);
+    __analyzer_eval (i == 1066); /* { dg-warning "TRUE" } */
+
+    j = __builtin_va_arg (ap, int);
+    __analyzer_eval (j == 42); /* { dg-warning "TRUE" } */
+
+    __builtin_sysv_va_end (ap);
+  }
+}
+
+void test_multiple_traversals (void)
+{
+  __analyzer_called_by_test_multiple_traversals (0, 1066, 42);
+}
+
+/* Multiple traversals, using va_copy.  */
+
+static void __attribute__((noinline))
+__analyzer_called_by_test_multiple_traversals_2 (int placeholder, ...)
+{
+  int i, j;
+  __builtin_sysv_va_list args1;
+  __builtin_sysv_va_list args2;
+
+  __builtin_sysv_va_start (args1, placeholder);
+  __builtin_sysv_va_copy (args2, args1);
+
+  /* First traversal.  */
+  i = __builtin_va_arg (args1, int);
+  __analyzer_eval (i == 1066); /* { dg-warning "TRUE" } */
+  j = __builtin_va_arg (args1, int);
+  __analyzer_eval (j == 42); /* { dg-warning "TRUE" } */
+  __builtin_sysv_va_end (args1);
+
+  /* Traversal of copy.  */
+  i = __builtin_va_arg (args2, int);
+  __analyzer_eval (i == 1066); /* { dg-warning "TRUE" } */
+  j = __builtin_va_arg (args2, int);
+  __analyzer_eval (j == 42); /* { dg-warning "TRUE" } */
+  __builtin_sysv_va_end (args2);
+}
+
+void test_multiple_traversals_2 (void)
+{
+  __analyzer_called_by_test_multiple_traversals_2 (0, 1066, 42);
+}
+
+/* Multiple traversals, using va_copy after a va_arg.  */
+
+static void __attribute__((noinline))
+__analyzer_called_by_test_multiple_traversals_3 (int placeholder, ...)
+{
+  int i, j;
+  __builtin_sysv_va_list args1;
+  __builtin_sysv_va_list args2;
+
+  __builtin_sysv_va_start (args1, placeholder);
+
+  /* First traversal.  */
+  i = __builtin_va_arg (args1, int);
+  __analyzer_eval (i == 1066); /* { dg-warning "TRUE" } */
+
+  /* va_copy after the first va_arg. */
+  __builtin_sysv_va_copy (args2, args1);
+
+  j = __builtin_va_arg (args1, int);
+  __analyzer_eval (j == 42); /* { dg-warning "TRUE" } */
+  __builtin_sysv_va_end (args1);
+
+  /* Traversal of copy.  */
+  j = __builtin_va_arg (args2, int);
+  __analyzer_eval (j == 42); /* { dg-warning "TRUE" } */
+  __builtin_sysv_va_end (args2);
+}
+
+void test_multiple_traversals_3 (void)
+{
+  __analyzer_called_by_test_multiple_traversals_3 (0, 1066, 42);
+}
+
+/* va_copy after va_end.  */
+
+void test_va_copy_after_va_end (int placeholder, ...)
+{
+  __builtin_sysv_va_list ap1, ap2;
+  __builtin_sysv_va_start (ap1, placeholder);
+  __builtin_sysv_va_end (ap1); /* { dg-message "'va_end' called here" } */
+  __builtin_sysv_va_copy (ap2, ap1); /* { dg-warning "'va_copy' after 'va_end'" } */
+  __builtin_sysv_va_end (ap2);
+}
+
+/* leak of va_copy.  */
+
+void test_leak_of_va_copy (int placeholder, ...)
+{
+  __builtin_sysv_va_list ap1, ap2;
+  __builtin_sysv_va_start (ap1, placeholder);
+  __builtin_sysv_va_copy (ap2, ap1); /* { dg-message "'va_copy' called here" } */
+  __builtin_sysv_va_end (ap1);
+} /* { dg-warning "missing call to 'va_end'" "warning" } */
+  /* { dg-message "missing call to 'va_end' to match 'va_copy' at \\(1\\)" "final event" { target *-*-* } .-1 } */
+
+/* double va_end.  */
+
+void test_double_va_end (int placeholder, ...)
+{
+  __builtin_sysv_va_list ap;
+  __builtin_sysv_va_start (ap, placeholder);
+  __builtin_sysv_va_end (ap); /* { dg-message "'va_end' called here" } */
+  __builtin_sysv_va_end (ap); /* { dg-warning "'va_end' after 'va_end'" } */
+}
+
+/* double va_start.  */
+
+void test_double_va_start (int placeholder, ...)
+{
+  int i;
+  __builtin_sysv_va_list ap;
+  __builtin_sysv_va_start (ap, placeholder); /* { dg-message "'va_start' called here" } */
+  __builtin_sysv_va_start (ap, placeholder);  /* { dg-warning "missing call to 'va_end'" "warning" } */
+  /* { dg-message "missing call to 'va_end' to match 'va_start' at \\(1\\)" "final event" { target *-*-* } .-1 } */
+  __builtin_sysv_va_end (ap);
+}
+
+/* va_copy before va_start.  */
+
+void test_va_copy_before_va_start (int placeholder, ...)
+{
+  __builtin_sysv_va_list ap1; /* { dg-message "region created on stack here" } */
+  __builtin_sysv_va_list ap2;
+  __builtin_sysv_va_copy (ap2, ap1); /* { dg-warning "use of uninitialized value 'ap1'" } */
+  __builtin_sysv_va_end (ap2);
+}
+
+/* Verify that we complain about uses of a va_list after the function 
+   in which va_start was called has returned.  */
+
+__builtin_sysv_va_list global_ap;
+
+static void __attribute__((noinline))
+__analyzer_called_by_test_va_arg_after_return (int placeholder, ...)
+{
+  __builtin_sysv_va_start (global_ap, placeholder);
+  __builtin_sysv_va_end (global_ap);
+}
+
+void test_va_arg_after_return (void)
+{
+  int i;
+  __analyzer_called_by_test_va_arg_after_return (42, 1066);
+  i = __builtin_va_arg (global_ap, int); /* { dg-warning "dereferencing pointer 'global_ap' to within stale stack frame" } */
+}