[committed] analyzer: implement four new warnings for <stdarg.h> misuses [PR105103]

Message ID 20220516194113.3130440-1-dmalcolm@redhat.com
State Committed
Headers
Series [committed] analyzer: implement four new warnings for <stdarg.h> misuses [PR105103] |

Commit Message

David Malcolm May 16, 2022, 7:41 p.m. UTC
  This patch adds support to the analyzer for checking usage of <stdarg.h>,
with four new warnings.

It adds:
(a) a state-machine for tracking "started" and "ended" states on va_list
instances, implementing two new warnings:
  -Wanalyzer-va-list-leak
    for complaining about missing va_end after a va_start or va_copy
  -Wanalyzer-va-list-use-after-va-end:
    for complaining about va_arg or va_copy used on a va_list that's had
    va_end called on it

(b) interprocedural tracking of variadic parameters, tracking symbolic
values, implementing two new warnings:
  -Wanalyzer-va-arg-type-mismatch
     for type-checking va_arg usage against the types of the parameters
     that were actually passed to the variadic call
  -Wanalyzer-va-list-exhausted
     for complaining if va_arg is used too many times on a va_list

Here's an LTO example of a type mismatch in a variadic call that
straddles two source files:

stdarg-lto-1-a.c: In function 'called_by_test_type_mismatch_1':
stdarg-lto-1-a.c:19:7: warning: 'va_arg' expected 'const char *' but
 received 'int' for variadic argument 1 of 'ap' [-Wanalyzer-va-arg-type-mismatch]
   19 |   str = va_arg (ap, const char *);
      |       ^
  'test_type_mismatch_1': events 1-2
    |
    |stdarg-lto-1-b.c:3:6:
    |    3 | void test_type_mismatch_1 (void)
    |      |      ^
    |      |      |
    |      |      (1) entry to 'test_type_mismatch_1'
    |    4 | {
    |    5 |   called_by_test_type_mismatch_1 (42, 1066);
    |      |   ~
    |      |   |
    |      |   (2) calling 'called_by_test_type_mismatch_1' from 'test_type_mismatch_1' with 1 variadic argument
    |
    +--> 'called_by_test_type_mismatch_1': events 3-4
           |
           |stdarg-lto-1-a.c:12:1:
           |   12 | called_by_test_type_mismatch_1 (int placeholder, ...)
           |      | ^
           |      | |
           |      | (3) entry to 'called_by_test_type_mismatch_1'
           |......
           |   19 |   str = va_arg (ap, const char *);
           |      |       ~
           |      |       |
           |      |       (4) 'va_arg' expected 'const char *' but received 'int' for variadic argument 1 of 'ap'
           |

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
Lightly tested with powerpc64le-linux-gnu and pdp11-aout.
Pushed to trunk as r13-514-g2402dc6b982c4d.

gcc/ChangeLog:
	PR analyzer/105103
	* Makefile.in (ANALYZER_OBJS): Add analyzer/varargs.o.
	* doc/invoke.texi: Add -Wanalyzer-va-arg-type-mismatch,
	-Wanalyzer-va-list-exhausted, -Wanalyzer-va-list-leak, and
	-Wanalyzer-va-list-use-after-va-end.

gcc/analyzer/ChangeLog:
	PR analyzer/105103
	* analyzer.cc (make_label_text_n): New.
	* analyzer.h (class var_arg_region): New forward decl.
	(make_label_text_n): New decl.
	* analyzer.opt (Wanalyzer-va-arg-type-mismatch): New option.
	(Wanalyzer-va-list-exhausted): New option.
	(Wanalyzer-va-list-leak): New option.
	(Wanalyzer-va-list-use-after-va-end): New option.
	* checker-path.cc (call_event::get_desc): Split out decl access
	into..
	(call_event::get_caller_fndecl): ...this new function and...
	(call_event::get_callee_fndecl): ...this new function.
	* checker-path.h (call_event::get_desc): Drop "FINAL".
	(call_event::get_caller_fndecl): New decl.
	(call_event::get_callee_fndecl): New decl.
	(class call_event): Make fields protected.
	* diagnostic-manager.cc (null_assignment_sm_context::warn): New
	overload.
	(null_assignment_sm_context::get_new_program_state): New.
	(diagnostic_manager::add_events_for_superedge): Move case
	SUPEREDGE_CALL to a new pending_diagnostic::add_call_event vfunc.
	* engine.cc (impl_sm_context::warn): Implement new override.
	(impl_sm_context::get_new_program_state): New.
	* pending-diagnostic.cc: Include "analyzer/diagnostic-manager.h",
	"cpplib.h", "digraph.h", "ordered-hash-map.h", "cfg.h",
	"basic-block.h", "gimple.h", "gimple-iterator.h", "cgraph.h"
	"analyzer/supergraph.h", "analyzer/program-state.h",
	"alloc-pool.h", "fibonacci_heap.h", "shortest-paths.h",
	"sbitmap.h", "analyzer/exploded-graph.h", "diagnostic-path.h",
	and "analyzer/checker-path.h".
	(ht_ident_eq): New.
	(fixup_location_in_macro_p): New.
	(pending_diagnostic::fixup_location): New.
	(pending_diagnostic::add_call_event): New.
	* pending-diagnostic.h (pending_diagnostic::fixup_location): Drop
	no-op inline implementation in favor of the more complex
	implementation above.
	(pending_diagnostic::add_call_event): New vfunc.
	* region-model-impl-calls.cc: Include "analyzer/sm.h",
	"diagnostic-path.h", and "analyzer/pending-diagnostic.h".
	* region-model-manager.cc
	(region_model_manager::get_var_arg_region): New.
	(region_model_manager::log_stats): Log m_var_arg_regions.
	* region-model.cc (region_model::on_call_pre): Handle IFN_VA_ARG,
	BUILT_IN_VA_START, and BUILT_IN_VA_COPY.
	(region_model::on_call_post): Handle BUILT_IN_VA_END.
	(region_model::get_representative_path_var_1): Handle RK_VAR_ARG.
	(region_model::push_frame): Push variadic arguments.
	* region-model.h (region_model_manager::get_var_arg_region): New
	decl.
	(region_model_manager::m_var_arg_regions): New field.
	(region_model::impl_call_va_start): New decl.
	(region_model::impl_call_va_copy): New decl.
	(region_model::impl_call_va_arg): New decl.
	(region_model::impl_call_va_end): New decl.
	* region.cc (alloca_region::dump_to_pp): Dump the id.
	(var_arg_region::dump_to_pp): New.
	(var_arg_region::get_frame_region): New.
	* region.h (enum region_kind): Add RK_VAR_ARG.
	(region::dyn_cast_var_arg_region): New.
	(class var_arg_region): New.
	(is_a_helper <const var_arg_region *>::test): New.
	(struct default_hash_traits<var_arg_region::key_t>): New.
	* sm.cc (make_checkers): Call make_va_list_state_machine.
	* sm.h (sm_context::warn): New vfunc.
	(sm_context::get_old_svalue): Drop unused decl.
	(sm_context::get_new_program_state): New vfunc.
	(make_va_list_state_machine): New decl.
	* varargs.cc: New file.

gcc/testsuite/ChangeLog:
	PR analyzer/105103
	* gcc.dg/analyzer/stdarg-1.c: New test.
	* gcc.dg/analyzer/stdarg-2.c: New test.
	* gcc.dg/analyzer/stdarg-fmtstring-1.c: New test.
	* gcc.dg/analyzer/stdarg-lto-1-a.c: New test.
	* gcc.dg/analyzer/stdarg-lto-1-b.c: New test.
	* gcc.dg/analyzer/stdarg-lto-1.h: New test.
	* gcc.dg/analyzer/stdarg-sentinel-1.c: New test.
	* gcc.dg/analyzer/stdarg-types-1.c: New test.
	* gcc.dg/analyzer/stdarg-types-2.c: New test.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
---
 gcc/Makefile.in                               |    3 +-
 gcc/analyzer/analyzer.cc                      |   38 +
 gcc/analyzer/analyzer.h                       |    4 +
 gcc/analyzer/analyzer.opt                     |   16 +
 gcc/analyzer/checker-path.cc                  |   16 +-
 gcc/analyzer/checker-path.h                   |    6 +-
 gcc/analyzer/diagnostic-manager.cc            |   19 +-
 gcc/analyzer/engine.cc                        |   22 +
 gcc/analyzer/pending-diagnostic.cc            |   82 ++
 gcc/analyzer/pending-diagnostic.h             |   11 +-
 gcc/analyzer/region-model-impl-calls.cc       |    3 +
 gcc/analyzer/region-model-manager.cc          |   20 +
 gcc/analyzer/region-model.cc                  |   26 +
 gcc/analyzer/region-model.h                   |    9 +
 gcc/analyzer/region.cc                        |   32 +-
 gcc/analyzer/region.h                         |   87 +-
 gcc/analyzer/sm.cc                            |    1 +
 gcc/analyzer/sm.h                             |    6 +-
 gcc/analyzer/varargs.cc                       | 1025 +++++++++++++++++
 gcc/doc/invoke.texi                           |   55 +
 gcc/testsuite/gcc.dg/analyzer/stdarg-1.c      |  433 +++++++
 gcc/testsuite/gcc.dg/analyzer/stdarg-2.c      |  436 +++++++
 .../gcc.dg/analyzer/stdarg-fmtstring-1.c      |  103 ++
 .../gcc.dg/analyzer/stdarg-lto-1-a.c          |   24 +
 .../gcc.dg/analyzer/stdarg-lto-1-b.c          |    6 +
 gcc/testsuite/gcc.dg/analyzer/stdarg-lto-1.h  |    1 +
 .../gcc.dg/analyzer/stdarg-sentinel-1.c       |   25 +
 .../gcc.dg/analyzer/stdarg-types-1.c          |   25 +
 .../gcc.dg/analyzer/stdarg-types-2.c          |   55 +
 29 files changed, 2567 insertions(+), 22 deletions(-)
 create mode 100644 gcc/analyzer/varargs.cc
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/stdarg-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/stdarg-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/stdarg-fmtstring-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/stdarg-lto-1-a.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/stdarg-lto-1-b.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/stdarg-lto-1.h
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/stdarg-sentinel-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/stdarg-types-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/stdarg-types-2.c
  

Patch

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 31ff95500c9..70f7d2191f1 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1278,7 +1278,8 @@  ANALYZER_OBJS = \
 	analyzer/store.o \
 	analyzer/supergraph.o \
 	analyzer/svalue.o \
-	analyzer/trimmed-graph.o
+	analyzer/trimmed-graph.o \
+	analyzer/varargs.o
 
 # Language-independent object files.
 # We put the *-match.o and insn-*.o files first so that a parallel make
diff --git a/gcc/analyzer/analyzer.cc b/gcc/analyzer/analyzer.cc
index 2c63a539fcb..c85dbf331d5 100644
--- a/gcc/analyzer/analyzer.cc
+++ b/gcc/analyzer/analyzer.cc
@@ -446,4 +446,42 @@  make_label_text (bool can_colorize, const char *fmt, ...)
   return result;
 }
 
+/* As above, but with singular vs plural.  */
+
+label_text
+make_label_text_n (bool can_colorize, int n,
+		   const char *singular_fmt,
+		   const char *plural_fmt, ...)
+{
+  pretty_printer *pp = global_dc->printer->clone ();
+  pp_clear_output_area (pp);
+
+  if (!can_colorize)
+    pp_show_color (pp) = false;
+
+  text_info ti;
+  rich_location rich_loc (line_table, UNKNOWN_LOCATION);
+
+  va_list ap;
+
+  va_start (ap, plural_fmt);
+
+  const char *fmt = ngettext (singular_fmt, plural_fmt, n);
+
+  ti.format_spec = fmt;
+  ti.args_ptr = &ap;
+  ti.err_no = 0;
+  ti.x_data = NULL;
+  ti.m_richloc = &rich_loc;
+
+  pp_format (pp, &ti);
+  pp_output_formatted_text (pp);
+
+  va_end (ap);
+
+  label_text result = label_text::take (xstrdup (pp_formatted_text (pp)));
+  delete pp;
+  return result;
+}
+
 #endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h
index 39934a302cb..dcefc13a546 100644
--- a/gcc/analyzer/analyzer.h
+++ b/gcc/analyzer/analyzer.h
@@ -69,6 +69,7 @@  class region;
   class field_region;
   class string_region;
   class bit_range_region;
+  class var_arg_region;
 class region_model_manager;
 class conjured_purge;
 struct model_merger;
@@ -296,6 +297,9 @@  extern const char *get_user_facing_name (const gcall *call);
 extern void register_analyzer_pass ();
 
 extern label_text make_label_text (bool can_colorize, const char *fmt, ...);
+extern label_text make_label_text_n (bool can_colorize, int n,
+				     const char *singular_fmt,
+				     const char *plural_fmt, ...);
 
 extern bool fndecl_has_gimple_body_p (tree fndecl);
 
diff --git a/gcc/analyzer/analyzer.opt b/gcc/analyzer/analyzer.opt
index a0ba2c94fb3..23dfc797cea 100644
--- a/gcc/analyzer/analyzer.opt
+++ b/gcc/analyzer/analyzer.opt
@@ -142,6 +142,22 @@  Wanalyzer-use-of-pointer-in-stale-stack-frame
 Common Var(warn_analyzer_use_of_pointer_in_stale_stack_frame) Init(1) Warning
 Warn about code paths in which a pointer to a stale stack frame is used.
 
+Wanalyzer-va-arg-type-mismatch
+Common Var(warn_analyzer_va_arg_type_mismatch) Init(1) Warning
+Warn about code paths in which va_arg uses the wrong type.
+
+Wanalyzer-va-list-exhausted
+Common Var(warn_analyzer_va_list_exhausted) Init(1) Warning
+Warn about code paths in which va_arg is used too many times on a va_list.
+
+Wanalyzer-va-list-leak
+Common Var(warn_analyzer_va_list_leak) Init(1) Warning
+Warn about code paths in which va_start or va_copy is used without a corresponding va_end.
+
+Wanalyzer-va-list-use-after-va-end
+Common Var(warn_analyzer_va_list_use_after_va_end) Init(1) Warning
+Warn about code paths in which a va_list is used after va_end.
+
 Wanalyzer-write-to-const
 Common Var(warn_analyzer_write_to_const) Init(1) Warning
 Warn about code paths which attempt to write to a const object.
diff --git a/gcc/analyzer/checker-path.cc b/gcc/analyzer/checker-path.cc
index a61b3ee0675..5fdbc388390 100644
--- a/gcc/analyzer/checker-path.cc
+++ b/gcc/analyzer/checker-path.cc
@@ -686,8 +686,8 @@  call_event::get_desc (bool can_colorize) const
 
   return make_label_text (can_colorize,
 			  "calling %qE from %qE",
-			  m_dest_snode->m_fun->decl,
-			  m_src_snode->m_fun->decl);
+			  get_callee_fndecl (),
+			  get_caller_fndecl ());
 }
 
 /* Override of checker_event::is_call_p for calls.  */
@@ -698,6 +698,18 @@  call_event::is_call_p () const
   return true;
 }
 
+tree
+call_event::get_caller_fndecl () const
+{
+  return m_src_snode->m_fun->decl;
+}
+
+tree
+call_event::get_callee_fndecl () const
+{
+  return m_dest_snode->m_fun->decl;
+}
+
 /* class return_event : public superedge_event.  */
 
 /* return_event's ctor.  */
diff --git a/gcc/analyzer/checker-path.h b/gcc/analyzer/checker-path.h
index d37c99989b5..545d7db06a2 100644
--- a/gcc/analyzer/checker-path.h
+++ b/gcc/analyzer/checker-path.h
@@ -352,10 +352,14 @@  public:
   call_event (const exploded_edge &eedge,
 	      location_t loc, tree fndecl, int depth);
 
-  label_text get_desc (bool can_colorize) const FINAL OVERRIDE;
+  label_text get_desc (bool can_colorize) const OVERRIDE;
 
   bool is_call_p () const FINAL OVERRIDE;
 
+protected:
+  tree get_caller_fndecl () const;
+  tree get_callee_fndecl () const;
+
   const supernode *m_src_snode;
   const supernode *m_dest_snode;
 };
diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc
index 2d49a3bc6ad..e8a828d748d 100644
--- a/gcc/analyzer/diagnostic-manager.cc
+++ b/gcc/analyzer/diagnostic-manager.cc
@@ -1665,6 +1665,11 @@  struct null_assignment_sm_context : public sm_context
   {
     delete d;
   }
+  void warn (const supernode *, const gimple *,
+	     const svalue *, pending_diagnostic *d) FINAL OVERRIDE
+  {
+    delete d;
+  }
 
   tree get_diagnostic_tree (tree expr) FINAL OVERRIDE
   {
@@ -1707,6 +1712,10 @@  struct null_assignment_sm_context : public sm_context
   {
     return m_old_state;
   }
+  const program_state *get_new_program_state () const FINAL OVERRIDE
+  {
+    return m_new_state;
+  }
 
   const program_state *m_old_state;
   const program_state *m_new_state;
@@ -2048,15 +2057,7 @@  diagnostic_manager::add_events_for_superedge (const path_builder &pb,
       break;
 
     case SUPEREDGE_CALL:
-      {
-	emission_path->add_event
-	  (new call_event (eedge,
-			   (last_stmt
-			    ? last_stmt->location
-			    : UNKNOWN_LOCATION),
-			   src_point.get_fndecl (),
-			   src_stack_depth));
-      }
+      pd->add_call_event (eedge, emission_path);
       break;
 
     case SUPEREDGE_INTRAPROCEDURAL_CALL:
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index 03329324346..1b1c38f3116 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -435,6 +435,23 @@  public:
        var, var_old_sval, current, d);
   }
 
+  void warn (const supernode *snode, const gimple *stmt,
+	     const svalue *sval, pending_diagnostic *d) FINAL OVERRIDE
+  {
+    LOG_FUNC (get_logger ());
+    gcc_assert (d); // take ownership
+    impl_region_model_context old_ctxt
+      (m_eg, m_enode_for_diag, m_old_state, m_new_state, NULL, NULL, NULL);
+
+    state_machine::state_t current
+      = (sval
+	 ? m_old_smap->get_state (sval, m_eg.get_ext_state ())
+	 : m_old_smap->get_global_state ());
+    m_eg.get_diagnostic_manager ().add_diagnostic
+      (&m_sm, m_enode_for_diag, snode, stmt, m_stmt_finder,
+       NULL_TREE, sval, current, d);
+  }
+
   /* Hook for picking more readable trees for SSA names of temporaries,
      so that rather than e.g.
        "double-free of '<unknown>'"
@@ -512,6 +529,11 @@  public:
     return m_old_state;
   }
 
+  const program_state *get_new_program_state () const FINAL OVERRIDE
+  {
+    return m_new_state;
+  }
+
   log_user m_logger;
   exploded_graph &m_eg;
   exploded_node *m_enode_for_diag;
diff --git a/gcc/analyzer/pending-diagnostic.cc b/gcc/analyzer/pending-diagnostic.cc
index 5e0ea4c31ac..eff050f6757 100644
--- a/gcc/analyzer/pending-diagnostic.cc
+++ b/gcc/analyzer/pending-diagnostic.cc
@@ -33,12 +33,30 @@  along with GCC; see the file COPYING3.  If not see
 #include "diagnostic-event-id.h"
 #include "analyzer/sm.h"
 #include "analyzer/pending-diagnostic.h"
+#include "analyzer/diagnostic-manager.h"
 #include "selftest.h"
 #include "tristate.h"
 #include "analyzer/call-string.h"
 #include "analyzer/program-point.h"
 #include "analyzer/store.h"
 #include "analyzer/region-model.h"
+#include "cpplib.h"
+#include "digraph.h"
+#include "ordered-hash-map.h"
+#include "cfg.h"
+#include "basic-block.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "cgraph.h"
+#include "analyzer/supergraph.h"
+#include "analyzer/program-state.h"
+#include "alloc-pool.h"
+#include "fibonacci_heap.h"
+#include "shortest-paths.h"
+#include "sbitmap.h"
+#include "analyzer/exploded-graph.h"
+#include "diagnostic-path.h"
+#include "analyzer/checker-path.h"
 
 #if ENABLE_ANALYZER
 
@@ -111,6 +129,70 @@  pending_diagnostic::same_tree_p (tree t1, tree t2)
   return simple_cst_equal (t1, t2) == 1;
 }
 
+/* Return true iff IDENT is STR.  */
+
+static bool
+ht_ident_eq (ht_identifier ident, const char *str)
+{
+  return (strlen (str) == ident.len
+	  && 0 == strcmp (str, (const char *)ident.str));
+}
+
+/* Return true if we should show the expansion location rather than unwind
+   within MACRO.  */
+
+static bool
+fixup_location_in_macro_p (cpp_hashnode *macro)
+{
+  ht_identifier ident = macro->ident;
+  /* Don't unwind inside <stdarg.h> macros, so that we don't suppress warnings
+     from them (due to being in system headers).  */
+  if (ht_ident_eq (ident, "va_start")
+      || ht_ident_eq (ident, "va_copy")
+      || ht_ident_eq (ident, "va_arg")
+      || ht_ident_eq (ident, "va_end"))
+    return true;
+  return false;
+}
+
+/* Base implementation of pending_diagnostic::fixup_location.
+   Don't unwind inside macros for which fixup_location_in_macro_p is true.  */
+
+location_t
+pending_diagnostic::fixup_location (location_t loc) const
+{
+  if (linemap_location_from_macro_expansion_p (line_table, loc))
+    {
+      line_map *map
+	= const_cast <line_map *> (linemap_lookup (line_table, loc));
+      const line_map_macro *macro_map = linemap_check_macro (map);
+      if (fixup_location_in_macro_p (macro_map->macro))
+	loc = linemap_resolve_location (line_table, loc,
+					LRK_MACRO_EXPANSION_POINT, NULL);
+    }
+  return loc;
+}
+
+/* Base implementation of pending_diagnostic::add_call_event.
+   Add a call_event to EMISSION_PATH.  */
+
+void
+pending_diagnostic::add_call_event (const exploded_edge &eedge,
+				    checker_path *emission_path)
+{
+  const exploded_node *src_node = eedge.m_src;
+  const program_point &src_point = src_node->get_point ();
+  const int src_stack_depth = src_point.get_stack_depth ();
+  const gimple *last_stmt = src_point.get_supernode ()->get_last_stmt ();
+  emission_path->add_event
+    (new call_event (eedge,
+		     (last_stmt
+		      ? last_stmt->location
+		      : UNKNOWN_LOCATION),
+		     src_point.get_fndecl (),
+		     src_stack_depth));
+}
+
 } // namespace ana
 
 #endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/pending-diagnostic.h b/gcc/analyzer/pending-diagnostic.h
index 51039ea1ba5..17db9fede95 100644
--- a/gcc/analyzer/pending-diagnostic.h
+++ b/gcc/analyzer/pending-diagnostic.h
@@ -203,10 +203,7 @@  class pending_diagnostic
   /* A vfunc for fixing up locations (both the primary location for the
      diagnostic, and for events in their paths), e.g. to avoid unwinding
      inside specific macros.  */
-  virtual location_t fixup_location (location_t loc) const
-  {
-    return loc;
-  }
+  virtual location_t fixup_location (location_t loc) const;
 
   /* For greatest precision-of-wording, the various following "describe_*"
      virtual functions give the pending diagnostic a way to describe events
@@ -295,6 +292,12 @@  class pending_diagnostic
     return false;
   }
 
+  /* Vfunc for adding a call_event to a checker_path, so that e.g.
+     the varargs diagnostics can add a custom event subclass that annotates
+     the variadic arguments.  */
+  virtual void add_call_event (const exploded_edge &,
+			       checker_path *);
+
   /* Vfunc for determining that this pending_diagnostic supercedes OTHER,
      and that OTHER should therefore not be emitted.
      They have already been tested for being at the same stmt.  */
diff --git a/gcc/analyzer/region-model-impl-calls.cc b/gcc/analyzer/region-model-impl-calls.cc
index 621e7002ffb..a76caf73133 100644
--- a/gcc/analyzer/region-model-impl-calls.cc
+++ b/gcc/analyzer/region-model-impl-calls.cc
@@ -57,6 +57,9 @@  along with GCC; see the file COPYING3.  If not see
 #include "analyzer/store.h"
 #include "analyzer/region-model.h"
 #include "analyzer/call-info.h"
+#include "analyzer/sm.h"
+#include "diagnostic-path.h"
+#include "analyzer/pending-diagnostic.h"
 #include "gimple-pretty-print.h"
 
 #if ENABLE_ANALYZER
diff --git a/gcc/analyzer/region-model-manager.cc b/gcc/analyzer/region-model-manager.cc
index 6d248c98fcf..3377f15feac 100644
--- a/gcc/analyzer/region-model-manager.cc
+++ b/gcc/analyzer/region-model-manager.cc
@@ -1601,6 +1601,25 @@  region_model_manager::get_bit_range (const region *parent, tree type,
   return bit_range_reg;
 }
 
+/* Return the region that describes accessing the IDX-th variadic argument
+   within PARENT_FRAME, creating it if necessary.  */
+
+const var_arg_region *
+region_model_manager::get_var_arg_region (const frame_region *parent_frame,
+					  unsigned idx)
+{
+  gcc_assert (parent_frame);
+
+  var_arg_region::key_t key (parent_frame, idx);
+  if (var_arg_region *reg = m_var_arg_regions.get (key))
+    return reg;
+
+  var_arg_region *var_arg_reg
+    = new var_arg_region (alloc_region_id (), parent_frame, idx);
+  m_var_arg_regions.put (key, var_arg_reg);
+  return var_arg_reg;
+}
+
 /* If we see a tree code we don't know how to handle, rather than
    ICE or generate bogus results, create a dummy region, and notify
    CTXT so that it can mark the new state as being not properly
@@ -1773,6 +1792,7 @@  region_model_manager::log_stats (logger *logger, bool show_objs) const
   log_uniq_map (logger, show_objs, "symbolic_region", m_symbolic_regions);
   log_uniq_map (logger, show_objs, "string_region", m_string_map);
   log_uniq_map (logger, show_objs, "bit_range_region", m_bit_range_regions);
+  log_uniq_map (logger, show_objs, "var_arg_region", m_var_arg_regions);
   logger->log ("  # managed dynamic regions: %i",
 	       m_managed_dynamic_regions.length ());
   m_store_mgr.log_stats (logger, show_objs);
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index 816b4100f3a..de221c3014c 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -1342,6 +1342,9 @@  region_model::on_call_pre (const gcall *call, region_model_context *ctxt,
 	 return false;
        case IFN_UBSAN_BOUNDS:
 	 return false;
+       case IFN_VA_ARG:
+	 impl_call_va_arg (cd);
+	 return false;
        }
     }
 
@@ -1428,6 +1431,13 @@  region_model::on_call_pre (const gcall *call, region_model_context *ctxt,
 	       on the return value.  */
 	    check_call_args (cd);
 	    break;
+
+	  case BUILT_IN_VA_START:
+	    impl_call_va_start (cd);
+	    return false;
+	  case BUILT_IN_VA_COPY:
+	    impl_call_va_copy (cd);
+	    return false;
 	  }
       else if (is_named_call_p (callee_fndecl, "malloc", call, 1))
 	{
@@ -1570,6 +1580,10 @@  region_model::on_call_post (const gcall *call,
 	  case BUILT_IN_REALLOC:
 	    impl_call_realloc (cd);
 	    return;
+
+	  case BUILT_IN_VA_END:
+	    impl_call_va_end (cd);
+	    return;
 	  }
     }
 
@@ -3520,6 +3534,7 @@  region_model::get_representative_path_var_1 (const region *reg,
 	return path_var (string_reg->get_string_cst (), 0);
       }
 
+    case RK_VAR_ARG:
     case RK_UNKNOWN:
       return path_var (NULL_TREE, 0);
     }
@@ -3888,6 +3903,17 @@  region_model::push_frame (function *fun, const vec<const svalue *> *arg_svals,
 	  const svalue *arg_sval = (*arg_svals)[idx];
 	  set_value (parm_reg, arg_sval, ctxt);
 	}
+
+      /* Handle any variadic args.  */
+      unsigned va_arg_idx = 0;
+      for (; idx < arg_svals->length (); idx++, va_arg_idx++)
+	{
+	  const svalue *arg_sval = (*arg_svals)[idx];
+	  const region *var_arg_reg
+	    = m_mgr->get_var_arg_region (m_current_frame,
+					 va_arg_idx);
+	  set_value (var_arg_reg, arg_sval, ctxt);
+	}
     }
   else
     {
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index eff3d4930f9..4e5cb46a649 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -326,6 +326,8 @@  public:
   const string_region *get_region_for_string (tree string_cst);
   const region *get_bit_range (const region *parent, tree type,
 			       const bit_range &bits);
+  const var_arg_region *get_var_arg_region (const frame_region *parent,
+					    unsigned idx);
 
   const region *get_unknown_symbolic_region (tree region_type);
 
@@ -488,6 +490,7 @@  private:
   string_map_t m_string_map;
 
   consolidation_map<bit_range_region> m_bit_range_regions;
+  consolidation_map<var_arg_region> m_var_arg_regions;
 
   store_manager m_store_mgr;
 
@@ -627,6 +630,12 @@  class region_model
   void impl_call_operator_delete (const call_details &cd);
   void impl_deallocation_call (const call_details &cd);
 
+  /* Implemented in varargs.cc.  */
+  void impl_call_va_start (const call_details &cd);
+  void impl_call_va_copy (const call_details &cd);
+  void impl_call_va_arg (const call_details &cd);
+  void impl_call_va_end (const call_details &cd);
+
   void handle_unrecognized_call (const gcall *call,
 				 region_model_context *ctxt);
   void get_reachable_svalues (svalue_set *out,
diff --git a/gcc/analyzer/region.cc b/gcc/analyzer/region.cc
index 1a7949b3f09..a8286231d30 100644
--- a/gcc/analyzer/region.cc
+++ b/gcc/analyzer/region.cc
@@ -1541,9 +1541,9 @@  void
 alloca_region::dump_to_pp (pretty_printer *pp, bool simple) const
 {
   if (simple)
-    pp_string (pp, "ALLOCA_REGION");
+    pp_printf (pp, "ALLOCA_REGION(%i)", get_id ());
   else
-    pp_string (pp, "alloca_region()");
+    pp_printf (pp, "alloca_region(%i)", get_id ());
 }
 
 /* class string_region : public region.  */
@@ -1637,6 +1637,34 @@  bit_range_region::get_relative_concrete_offset (bit_offset_t *out) const
   return true;
 }
 
+/* class var_arg_region : public region.  */
+
+void
+var_arg_region::dump_to_pp (pretty_printer *pp, bool simple) const
+{
+  if (simple)
+    {
+      pp_string (pp, "VAR_ARG_REG(");
+      get_parent_region ()->dump_to_pp (pp, simple);
+      pp_printf (pp, ", arg_idx: %d)", m_idx);
+    }
+  else
+    {
+      pp_string (pp, "var_arg_region(");
+      get_parent_region ()->dump_to_pp (pp, simple);
+      pp_printf (pp, ", arg_idx: %d)", m_idx);
+    }
+}
+
+/* Get the frame_region for this var_arg_region.  */
+
+const frame_region *
+var_arg_region::get_frame_region () const
+{
+  gcc_assert (get_parent_region ());
+  return as_a <const frame_region *> (get_parent_region ());
+}
+
 /* class unknown_region : public region.  */
 
 /* Implementation of region::dump_to_pp vfunc for unknown_region.  */
diff --git a/gcc/analyzer/region.h b/gcc/analyzer/region.h
index 5150be76d0b..d32110bf8e3 100644
--- a/gcc/analyzer/region.h
+++ b/gcc/analyzer/region.h
@@ -61,7 +61,8 @@  enum region_kind
   RK_ALLOCA,
   RK_STRING,
   RK_BIT_RANGE,
-  RK_UNKNOWN
+  RK_VAR_ARG,
+  RK_UNKNOWN,
 };
 
 /* Region and its subclasses.
@@ -90,6 +91,7 @@  enum region_kind
      alloca_region (RK_ALLOCA)
      string_region (RK_STRING)
      bit_range_region (RK_BIT_RANGE)
+     var_arg_region (RK_VAR_ARG)
      unknown_region (RK_UNKNOWN).  */
 
 /* Abstract base class for representing ways of accessing chunks of memory.
@@ -131,6 +133,8 @@  public:
   dyn_cast_string_region () const { return NULL; }
   virtual const bit_range_region *
   dyn_cast_bit_range_region () const { return NULL; }
+  virtual const var_arg_region *
+  dyn_cast_var_arg_region () const { return NULL; }
 
   virtual void accept (visitor *v) const;
 
@@ -1251,6 +1255,87 @@  template <> struct default_hash_traits<bit_range_region::key_t>
 
 namespace ana {
 
+/* A region for the N-th vararg within a frame_region for a variadic call.  */
+
+class var_arg_region : public region
+{
+public:
+  /* A support class for uniquifying instances of var_arg_region.  */
+  struct key_t
+  {
+    key_t (const frame_region *parent, unsigned idx)
+    : m_parent (parent), m_idx (idx)
+    {
+      gcc_assert (parent);
+    }
+
+    hashval_t hash () const
+    {
+      inchash::hash hstate;
+      hstate.add_ptr (m_parent);
+      hstate.add_int (m_idx);
+      return hstate.end ();
+    }
+
+    bool operator== (const key_t &other) const
+    {
+      return (m_parent == other.m_parent
+	      && m_idx == other.m_idx);
+    }
+
+    void mark_deleted ()
+    {
+      m_parent = reinterpret_cast<const frame_region *> (1);
+    }
+    void mark_empty () { m_parent = NULL; }
+    bool is_deleted () const
+    {
+      return m_parent == reinterpret_cast<const frame_region *> (1);
+    }
+    bool is_empty () const { return m_parent == NULL; }
+
+    const frame_region *m_parent;
+    unsigned m_idx;
+  };
+
+  var_arg_region (unsigned id, const frame_region *parent,
+		  unsigned idx)
+  : region (complexity (parent), id, parent, NULL_TREE),
+    m_idx (idx)
+  {}
+
+  const var_arg_region *
+  dyn_cast_var_arg_region () const FINAL OVERRIDE { return this; }
+
+  enum region_kind get_kind () const FINAL OVERRIDE { return RK_VAR_ARG; }
+
+  void dump_to_pp (pretty_printer *pp, bool simple) const FINAL OVERRIDE;
+
+  const frame_region *get_frame_region () const;
+  unsigned get_index () const { return m_idx; }
+
+private:
+  unsigned m_idx;
+};
+
+} // namespace ana
+
+template <>
+template <>
+inline bool
+is_a_helper <const var_arg_region *>::test (const region *reg)
+{
+  return reg->get_kind () == RK_VAR_ARG;
+}
+
+template <> struct default_hash_traits<var_arg_region::key_t>
+: public member_function_hash_traits<var_arg_region::key_t>
+{
+  static const bool empty_zero_p = true;
+};
+
+namespace ana {
+
 /* An unknown region, for handling unimplemented tree codes.  */
 
 class unknown_region : public region
diff --git a/gcc/analyzer/sm.cc b/gcc/analyzer/sm.cc
index 515f86da86a..622cb0b7ab3 100644
--- a/gcc/analyzer/sm.cc
+++ b/gcc/analyzer/sm.cc
@@ -173,6 +173,7 @@  make_checkers (auto_delete_vec <state_machine> &out, logger *logger)
     out.safe_push (make_taint_state_machine (logger));
   out.safe_push (make_sensitive_state_machine (logger));
   out.safe_push (make_signal_state_machine (logger));
+  out.safe_push (make_va_list_state_machine (logger));
 
   /* We only attempt to run the pattern tests if it might have been manually
      enabled (for DejaGnu purposes).  */
diff --git a/gcc/analyzer/sm.h b/gcc/analyzer/sm.h
index 7ce1c73e217..4cc54531c56 100644
--- a/gcc/analyzer/sm.h
+++ b/gcc/analyzer/sm.h
@@ -242,6 +242,8 @@  public:
      issue a diagnostic D using NODE and STMT for location information.  */
   virtual void warn (const supernode *node, const gimple *stmt,
 		     tree var, pending_diagnostic *d) = 0;
+  virtual void warn (const supernode *node, const gimple *stmt,
+		     const svalue *var, pending_diagnostic *d) = 0;
 
   /* For use when generating trees when creating pending_diagnostics, so that
      rather than e.g.
@@ -275,8 +277,7 @@  public:
   virtual bool unknown_side_effects_p () const { return false; }
 
   virtual const program_state *get_old_program_state () const = 0;
-
-  const svalue *get_old_svalue (tree expr) const;
+  virtual const program_state *get_new_program_state () const = 0;
 
 protected:
   sm_context (int sm_idx, const state_machine &sm)
@@ -299,6 +300,7 @@  extern state_machine *make_taint_state_machine (logger *logger);
 extern state_machine *make_sensitive_state_machine (logger *logger);
 extern state_machine *make_signal_state_machine (logger *logger);
 extern state_machine *make_pattern_test_state_machine (logger *logger);
+extern state_machine *make_va_list_state_machine (logger *logger);
 
 } // namespace ana
 
diff --git a/gcc/analyzer/varargs.cc b/gcc/analyzer/varargs.cc
new file mode 100644
index 00000000000..de77fe5d3ed
--- /dev/null
+++ b/gcc/analyzer/varargs.cc
@@ -0,0 +1,1025 @@ 
+/* Implementation of <stdarg.h> within analyzer.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC 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, or (at your option)
+any later version.
+
+GCC 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 GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "function.h"
+#include "basic-block.h"
+#include "gimple.h"
+#include "diagnostic-path.h"
+#include "json.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/analyzer-logging.h"
+#include "analyzer/sm.h"
+#include "analyzer/pending-diagnostic.h"
+#include "tristate.h"
+#include "selftest.h"
+#include "analyzer/call-string.h"
+#include "analyzer/program-point.h"
+#include "analyzer/store.h"
+#include "analyzer/region-model.h"
+#include "analyzer/program-state.h"
+#include "analyzer/checker-path.h"
+#include "digraph.h"
+#include "ordered-hash-map.h"
+#include "cfg.h"
+#include "gimple-iterator.h"
+#include "analyzer/supergraph.h"
+#include "alloc-pool.h"
+#include "fibonacci_heap.h"
+#include "shortest-paths.h"
+#include "sbitmap.h"
+#include "analyzer/diagnostic-manager.h"
+#include "analyzer/exploded-graph.h"
+
+#if ENABLE_ANALYZER
+
+namespace ana {
+
+/* Implementation of <stdarg.h> within analyzer.
+
+   Objectives:
+   - detection of interprocedural type errors involving va_arg
+   - tracking of symbolic values interprocedurally from variadic call
+     through to va_arg unpacking
+   - detection of missing va_end
+   - detection of va_arg outside of a va_start/va_end pair
+   - detection of uses of a va_list after the frame in containing the
+     va_start has returned
+
+   The analyzer runs *before* the "stdarg" and "lower_vaarg" gimple
+   passes, which have target-dependent effects.
+
+   This file implements a state machine on svalues for tracking when
+   va_start has been called, so that we can detect missing va_end,
+   and misplaced va_arg, etc.
+   To do this requires an svalue that can have state, so we implement va_start
+   by creating a stack-allocated region, and use a pointer to that region
+   as the svalue that has state.
+
+   We call this stack-allocated region the "impl_reg".  Allocating it on
+   the stack ensures that it is invalidated when the frame containing
+   the va_start returns, leading to
+   -Wanalyzer-use-of-pointer-in-stale-stack-frame on attempts to use such
+   a va_list.
+
+   To track svalues from variadic calls interprocedurally, we implement
+   variadic arguments via new child regions of the callee's frame_region,
+   var_arg_region, each one representing a storage slot for one of the
+   variadic arguments, accessed by index.
+
+   We have:
+
+    stack frame:
+      va_list: &impl_reg
+      'impl_reg': pointer to next var_arg_region
+      var_arg_region for arg 0
+      ...
+      var_arg_region for arg N-1
+
+   Hence given test_1 in stdarg-1.c, at the call to:
+
+     __analyzer_called_by_test_1 (int placeholder, ...);
+
+   here:
+
+     __analyzer_called_by_test_1 (42, "foo", 1066, '@');
+
+   we push this frame for the called function:
+     clusters within frame: ‘__analyzer_called_by_test_1’@2
+       cluster for: placeholder: (int)42
+       cluster for: VAR_ARG_REG(frame: ‘__analyzer_called_by_test_1’@2, arg_idx: 0): &"foo" (TOUCHED)
+       cluster for: VAR_ARG_REG(frame: ‘__analyzer_called_by_test_1’@2, arg_idx: 1): (int)1066 (TOUCHED)
+       cluster for: VAR_ARG_REG(frame: ‘__analyzer_called_by_test_1’@2, arg_idx: 2): (int)64 (TOUCHED)
+   where the called function's frame has been populated with both the value
+   of the regular argument "placeholder", and with values for 3 variadic
+   arguments.
+
+   At the call to
+      va_start (ap, placeholder);
+   we allocate a region ALLOCA_REGION for ap to point to, populate that
+   region with the address of variadic argument 0, and set sm-state of
+   &ALLOCA_REGION to "started":
+    clusters within frame: ‘__analyzer_called_by_test_1’@2
+      cluster for: placeholder: (int)42
+      cluster for: VAR_ARG_REG(frame: ‘__analyzer_called_by_test_1’@2, arg_idx: 0): &"foo" (TOUCHED)
+      cluster for: VAR_ARG_REG(frame: ‘__analyzer_called_by_test_1’@2, arg_idx: 1): (int)1066 (TOUCHED)
+      cluster for: VAR_ARG_REG(frame: ‘__analyzer_called_by_test_1’@2, arg_idx: 2): (int)64 (TOUCHED)
+      cluster for: ap: &ALLOCA_REGION
+      cluster for: ALLOCA_REGION: &VAR_ARG_REG(frame: ‘__analyzer_called_by_test_1’@2, arg_idx: 0) (TOUCHED)
+    va_list:
+      0x4c83700: &ALLOCA_REGION: started
+
+   At each call to
+     va_arg (ap, TYPE);
+   we can look within *ap, locate the region holding the next variadic
+   argument to be extracted, extract the svalue, and advance the index
+   by effectively updating *ap.
+
+   At the va_end, we can set &ALLOCA_REGION's state to "ended".
+
+   The various __builtin_va_* accept ap by pointer, so we have e.g.:
+
+     __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
+   below).  */
+
+/* Get a tree for diagnostics.
+   Typically we have "&ap", but it will make more sense to
+   the user as just "ap", so strip off the ADDR_EXPR.  */
+
+static tree
+get_va_list_diag_arg (tree va_list_tree)
+{
+  if (TREE_CODE (va_list_tree) == ADDR_EXPR)
+    va_list_tree = TREE_OPERAND (va_list_tree, 0);
+  return va_list_tree;
+}
+
+/* Get argument ARG_IDX of type BT_VALIST_ARG (for use by 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.  */
+
+static const svalue *
+get_BT_VALIST_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)
+    {
+      /* va_list_arg_type_node is a pointer to a va_list;
+	 return *ARG_SVAL.  */
+      const region *src_reg = model->deref_rvalue (arg_sval, arg, ctxt);
+      const svalue *src_reg_sval = model->get_store_value (src_reg, ctxt);
+      if (const svalue *cast = src_reg_sval->maybe_undo_cast ())
+	src_reg_sval = cast;
+      return src_reg_sval;
+    }
+  else
+    {
+      /* va_list_arg_type_node is a va_list; return ARG_SVAL.  */
+      return arg_sval;
+    }
+}
+
+namespace {
+
+/* A state machine for tracking the state of a va_list, so that
+   we can enforce that each va_start is paired with a va_end,
+   and va_arg only happens within a va_start/va_end pair.
+   Specifically, this tracks the state of the &ALLOCA_BUFFER
+   that va_start/va_copy allocate.  */
+
+class va_list_state_machine : public state_machine
+{
+public:
+  va_list_state_machine (logger *logger);
+
+  bool inherited_state_p () const FINAL OVERRIDE { return false; }
+
+  bool on_stmt (sm_context *sm_ctxt,
+		const supernode *node,
+		const gimple *stmt) const FINAL OVERRIDE;
+
+  bool can_purge_p (state_t s) const FINAL OVERRIDE
+  {
+    return s != m_started;
+  }
+  pending_diagnostic *on_leak (tree var) const FINAL OVERRIDE;
+
+  /* State for a va_list that the result of a va_start or va_copy.  */
+  state_t m_started;
+
+  /* State for a va_list that has had va_end called on it.  */
+  state_t m_ended;
+
+private:
+  void on_va_start (sm_context *sm_ctxt, const supernode *node,
+		    const gcall *call) const;
+  void on_va_copy (sm_context *sm_ctxt, const supernode *node,
+		   const gcall *call) const;
+  void on_va_arg (sm_context *sm_ctxt, const supernode *node,
+		  const gcall *call) const;
+  void on_va_end (sm_context *sm_ctxt, const supernode *node,
+		  const gcall *call) const;
+  void check_for_ended_va_list (sm_context *sm_ctxt,
+				const supernode *node,
+				const gcall *call,
+				const svalue *arg,
+				const char *usage_fnname) const;
+};
+
+/* va_list_state_machine's ctor.  */
+
+va_list_state_machine::va_list_state_machine (logger *logger)
+: state_machine ("va_list", logger)
+{
+  m_started = add_state ("started");
+  m_ended = add_state ("ended");
+}
+
+/* Implementation of the various "va_*" functions for
+   va_list_state_machine.  */
+
+bool
+va_list_state_machine::on_stmt (sm_context *sm_ctxt,
+				const supernode *node,
+				const gimple *stmt) const
+{
+  if (const gcall *call = dyn_cast <const gcall *> (stmt))
+    {
+      if (gimple_call_internal_p (call)
+	  && gimple_call_internal_fn (call) == IFN_VA_ARG)
+	{
+	  on_va_arg (sm_ctxt, node, call);
+	  return false;
+	}
+
+      if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call))
+	if (fndecl_built_in_p (callee_fndecl, BUILT_IN_NORMAL)
+	    && gimple_builtin_call_types_compatible_p (call, callee_fndecl))
+	  switch (DECL_UNCHECKED_FUNCTION_CODE (callee_fndecl))
+	    {
+	    default:
+	      break;
+
+	    case BUILT_IN_VA_START:
+	      on_va_start (sm_ctxt, node, call);
+	      break;
+
+	    case BUILT_IN_VA_COPY:
+	      on_va_copy (sm_ctxt, node, call);
+	      break;
+
+	    case BUILT_IN_VA_END:
+	      on_va_end (sm_ctxt, node, call);
+	      break;
+	    }
+    }
+  return false;
+}
+
+/* Get the svalue for which va_list_state_machine holds state on argument ARG_
+   IDX to CALL.  */
+
+static const svalue *
+get_stateful_arg (sm_context *sm_ctxt, const gcall *call, unsigned arg_idx)
+{
+  tree ap = gimple_call_arg (call, arg_idx);
+  if (ap
+      && POINTER_TYPE_P (TREE_TYPE (ap)))
+    {
+      if (const program_state *new_state = sm_ctxt->get_new_program_state ())
+	{
+	  const region_model *new_model = new_state->m_region_model;
+	  const svalue *ptr_sval = new_model->get_rvalue (ap, NULL);
+	  const region *reg = new_model->deref_rvalue (ptr_sval, ap, NULL);
+	  const svalue *impl_sval = new_model->get_store_value (reg, NULL);
+	  if (const svalue *cast = impl_sval->maybe_undo_cast ())
+	    impl_sval = cast;
+	  return impl_sval;
+	}
+    }
+  return NULL;
+}
+
+/* Abstract class for diagnostics relating to va_list_state_machine.  */
+
+class va_list_sm_diagnostic : public pending_diagnostic
+{
+public:
+  bool subclass_equal_p (const pending_diagnostic &base_other) const OVERRIDE
+  {
+    const va_list_sm_diagnostic &other
+      = (const va_list_sm_diagnostic &)base_other;
+    return (m_ap_sval == other.m_ap_sval
+	    && same_tree_p (m_ap_tree, other.m_ap_tree));
+  }
+
+  label_text describe_state_change (const evdesc::state_change &change)
+    OVERRIDE
+  {
+    if (const char *fnname = maybe_get_fnname (change))
+      return change.formatted_print ("%qs called here", fnname);
+    return label_text ();
+  }
+
+protected:
+  va_list_sm_diagnostic (const va_list_state_machine &sm,
+			 const svalue *ap_sval, tree ap_tree)
+    : m_sm (sm), m_ap_sval (ap_sval), m_ap_tree (ap_tree)
+  {}
+
+  static const char *maybe_get_fnname (const evdesc::state_change &change)
+  {
+    if (change.m_event.m_stmt)
+      if (const gcall *call = as_a <const gcall *> (change.m_event.m_stmt))
+	if (tree callee_fndecl = gimple_call_fndecl (call))
+	  {
+	    if (fndecl_built_in_p (callee_fndecl, BUILT_IN_NORMAL))
+	      switch (DECL_UNCHECKED_FUNCTION_CODE (callee_fndecl))
+		{
+		case BUILT_IN_VA_START:
+		  return "va_start";
+		case BUILT_IN_VA_COPY:
+		  return "va_copy";
+		case BUILT_IN_VA_END:
+		  return "va_end";
+		}
+	  }
+    return NULL;
+  }
+
+  const va_list_state_machine &m_sm;
+  const svalue *m_ap_sval;
+  tree m_ap_tree;
+};
+
+/* Concrete class for -Wanalyzer-va-list-use-after-va-end:
+   complain about use of a va_list after va_end has been called on it.  */
+
+class va_list_use_after_va_end : public va_list_sm_diagnostic
+{
+public:
+  va_list_use_after_va_end (const va_list_state_machine &sm,
+			    const svalue *ap_sval, tree ap_tree,
+			    const char *usage_fnname)
+  : va_list_sm_diagnostic (sm, ap_sval, ap_tree),
+    m_usage_fnname (usage_fnname)
+  {
+  }
+
+  int get_controlling_option () const FINAL OVERRIDE
+  {
+    return OPT_Wanalyzer_va_list_use_after_va_end;
+  }
+
+  bool operator== (const va_list_use_after_va_end &other) const
+  {
+    return (va_list_sm_diagnostic::subclass_equal_p (other)
+	    && 0 == strcmp (m_usage_fnname, other.m_usage_fnname));
+  }
+
+  bool emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    auto_diagnostic_group d;
+    return warning_at (rich_loc, get_controlling_option (),
+		       "%qs after %qs", m_usage_fnname, "va_end");
+  }
+
+  const char *get_kind () const FINAL OVERRIDE
+  {
+    return "va_list_use_after_va_end";
+  }
+
+  label_text describe_state_change (const evdesc::state_change &change)
+    FINAL OVERRIDE
+  {
+    if (change.m_new_state == m_sm.m_ended)
+      m_va_end_event = change.m_event_id;
+    return va_list_sm_diagnostic::describe_state_change (change);
+  }
+
+  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  {
+    if (ev.m_expr)
+      {
+	if (m_va_end_event.known_p ())
+	  return ev.formatted_print
+	    ("%qs on %qE after %qs at %@",
+	     m_usage_fnname, ev.m_expr, "va_end", &m_va_end_event);
+	else
+	  return ev.formatted_print
+	    ("%qs on %qE after %qs",
+	     m_usage_fnname, ev.m_expr, "va_end");
+      }
+    else
+      {
+	if (m_va_end_event.known_p ())
+	  return ev.formatted_print
+	    ("%qs after %qs at %@",
+	     m_usage_fnname, "va_end", &m_va_end_event);
+	else
+	  return ev.formatted_print
+	    ("%qs after %qs",
+	     m_usage_fnname, "va_end");
+      }
+  }
+
+private:
+  diagnostic_event_id_t m_va_end_event;
+  const char *m_usage_fnname;
+};
+
+/* Concrete class for -Wanalyzer-va-list-leak:
+   complain about a va_list in the "started" state that doesn't get after
+   va_end called on it.  */
+
+class va_list_leak : public va_list_sm_diagnostic
+{
+public:
+  va_list_leak (const va_list_state_machine &sm,
+		const svalue *ap_sval, tree ap_tree)
+  : va_list_sm_diagnostic (sm, ap_sval, ap_tree),
+    m_start_event_fnname (NULL)
+  {
+  }
+
+  int get_controlling_option () const FINAL OVERRIDE
+  {
+    return OPT_Wanalyzer_va_list_leak;
+  }
+
+  bool operator== (const va_list_leak &other) const
+  {
+    return va_list_sm_diagnostic::subclass_equal_p (other);
+  }
+
+  bool emit (rich_location *rich_loc)
+  {
+    auto_diagnostic_group d;
+    return warning_at (rich_loc, get_controlling_option (),
+		       "missing call to %qs", "va_end");
+  }
+
+  const char *get_kind () const FINAL OVERRIDE { return "va_list_leak"; }
+
+  label_text describe_state_change (const evdesc::state_change &change)
+    FINAL OVERRIDE
+  {
+    if (change.m_new_state == m_sm.m_started)
+      {
+	m_start_event = change.m_event_id;
+	m_start_event_fnname = maybe_get_fnname (change);
+      }
+    return va_list_sm_diagnostic::describe_state_change (change);
+  }
+
+  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  {
+    if (ev.m_expr)
+      {
+	if (m_start_event.known_p () && m_start_event_fnname)
+	  return ev.formatted_print
+	    ("missing call to %qs on %qE to match %qs at %@",
+	     "va_end", ev.m_expr, m_start_event_fnname, &m_start_event);
+	else
+	  return ev.formatted_print
+	    ("missing call to %qs on %qE",
+	     "va_end", ev.m_expr);
+      }
+    else
+      {
+	if (m_start_event.known_p () && m_start_event_fnname)
+	  return ev.formatted_print
+	    ("missing call to %qs to match %qs at %@",
+	     "va_end", m_start_event_fnname, &m_start_event);
+	else
+	  return ev.formatted_print
+	    ("missing call to %qs",
+	     "va_end");
+      }
+  }
+
+private:
+  diagnostic_event_id_t m_start_event;
+  const char *m_start_event_fnname;
+};
+
+/* Update state machine for a "va_start" call.  */
+
+void
+va_list_state_machine::on_va_start (sm_context *sm_ctxt,
+				    const supernode *,
+				    const gcall *call) const
+{
+  const svalue *arg = get_stateful_arg (sm_ctxt, call, 0);
+  if (arg)
+    {
+      /* Transition from start state to "started".  */
+      if (sm_ctxt->get_state (call, arg) == m_start)
+	sm_ctxt->set_next_state (call, arg, m_started);
+    }
+}
+
+/* Complain if ARG is in the "ended" state.  */
+
+void
+va_list_state_machine::check_for_ended_va_list (sm_context *sm_ctxt,
+						const supernode *node,
+						const gcall *call,
+						const svalue *arg,
+						const char *usage_fnname) const
+{
+  if (sm_ctxt->get_state (call, arg) == m_ended)
+    sm_ctxt->warn (node, call, arg,
+		   new va_list_use_after_va_end (*this, arg, NULL_TREE,
+						 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,
+   or NULL otherwise.  */
+
+static const svalue *
+get_stateful_BT_VALIST_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);
+      return arg;
+    }
+  return NULL;
+}
+
+/* Update state machine for a "va_copy" call.  */
+
+void
+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);
+  if (src_arg)
+    check_for_ended_va_list (sm_ctxt, node, call, src_arg, "va_copy");
+
+  const svalue *dst_arg = get_stateful_arg (sm_ctxt, call, 0);
+  if (dst_arg)
+    {
+      /* Transition from start state to "started".  */
+      if (sm_ctxt->get_state (call, dst_arg) == m_start)
+	sm_ctxt->set_next_state (call, dst_arg, m_started);
+    }
+}
+
+/* Update state machine for a "va_arg" call.  */
+
+void
+va_list_state_machine::on_va_arg (sm_context *sm_ctxt,
+				  const supernode *node,
+				  const gcall *call) const
+{
+  const svalue *arg = get_stateful_arg (sm_ctxt, call, 0);
+  if (arg)
+    check_for_ended_va_list (sm_ctxt, node, call, arg, "va_arg");
+}
+
+/* Update state machine for a "va_end" call.  */
+
+void
+va_list_state_machine::on_va_end (sm_context *sm_ctxt,
+				  const supernode *node,
+				  const gcall *call) const
+{
+  const svalue *arg = get_stateful_arg (sm_ctxt, call, 0);
+  if (arg)
+    {
+      state_t s = sm_ctxt->get_state (call, arg);
+      /* Transition from "started" to "ended".  */
+      if (s == m_started)
+	sm_ctxt->set_next_state (call, arg, m_ended);
+      else if (s == m_ended)
+	check_for_ended_va_list (sm_ctxt, node, call, arg, "va_end");
+    }
+}
+
+/* Implementation of state_machine::on_leak vfunc for va_list_state_machine
+   (for complaining about leaks of values in state 'started').  */
+
+pending_diagnostic *
+va_list_state_machine::on_leak (tree var) const
+{
+  return new va_list_leak (*this, NULL, var);
+}
+
+} // anonymous namespace
+
+/* Internal interface to this file. */
+
+state_machine *
+make_va_list_state_machine (logger *logger)
+{
+  return new va_list_state_machine (logger);
+}
+
+/* Handle the on_call_pre part of "__builtin_va_start".  */
+
+void
+region_model::impl_call_va_start (const call_details &cd)
+{
+  const svalue *out_ptr = cd.get_arg_svalue (0);
+  const region *out_reg
+    = deref_rvalue (out_ptr, cd.get_arg_tree (0), cd.get_ctxt ());
+
+  /* "*out_ptr = &IMPL_REGION;".  */
+  const region *impl_reg = m_mgr->create_region_for_alloca (m_current_frame);
+
+  /* We abuse the types here, since va_list_type isn't
+     necessarily anything to do with a pointer.  */
+  const svalue *ptr_to_impl_reg = m_mgr->get_ptr_svalue (NULL_TREE, impl_reg);
+  set_value (out_reg, ptr_to_impl_reg, cd.get_ctxt ());
+
+  /* "*(&IMPL_REGION) = VA_LIST_VAL (0);".  */
+  const region *init_var_arg_reg
+    = m_mgr->get_var_arg_region (get_current_frame (), 0);
+  const svalue *ap_sval = m_mgr->get_ptr_svalue (NULL_TREE, init_var_arg_reg);
+  set_value (impl_reg, ap_sval, cd.get_ctxt ());
+}
+
+/* Handle the on_call_pre part of "__builtin_va_copy".  */
+
+void
+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);
+  in_va_list = check_for_poison (in_va_list,
+				 get_va_list_diag_arg (cd.get_arg_tree (1)),
+				 cd.get_ctxt ());
+
+  const region *out_dst_reg
+    = deref_rvalue (out_dst_ptr, cd.get_arg_tree (0), cd.get_ctxt ());
+
+  /* "*out_dst_ptr = &NEW_IMPL_REGION;".  */
+  const region *new_impl_reg
+    = m_mgr->create_region_for_alloca (m_current_frame);
+  const svalue *ptr_to_new_impl_reg
+    = m_mgr->get_ptr_svalue (NULL_TREE, new_impl_reg);
+  set_value (out_dst_reg, ptr_to_new_impl_reg, cd.get_ctxt ());
+
+  if (const region *old_impl_reg = in_va_list->maybe_get_region ())
+    {
+
+      /* "(NEW_IMPL_REGION) = (OLD_IMPL_REGION);".  */
+      const svalue *existing_sval
+	= get_store_value (old_impl_reg, cd.get_ctxt ());
+      set_value (new_impl_reg, existing_sval, cd.get_ctxt ());
+    }
+}
+
+/* Get the number of variadic arguments to CALLEE_FNDECL at CALL_STMT.  */
+
+static int
+get_num_variadic_arguments (tree callee_fndecl,
+			    const gcall *call_stmt)
+{
+  int num_positional = 0;
+  for (tree iter_parm = DECL_ARGUMENTS (callee_fndecl); iter_parm;
+       iter_parm = DECL_CHAIN (iter_parm))
+    num_positional++;
+  return gimple_call_num_args (call_stmt) - num_positional;
+}
+
+/* An abstract subclass of pending_diagnostic for diagnostics relating
+   to bad va_arg invocations.
+
+   This shows the number of variadic arguments at the call of interest.
+   Ideally we'd also be able to highlight individual arguments, but
+   that location information isn't generally available from the middle end.  */
+
+class va_arg_diagnostic : public pending_diagnostic
+{
+public:
+  /* Override of pending_diagnostic::add_call_event,
+     adding a custom call_event subclass.  */
+  void add_call_event (const exploded_edge &eedge,
+		       checker_path *emission_path) OVERRIDE
+  {
+    /* As per call_event, but show the number of variadic arguments
+       in the call.  */
+    class va_arg_call_event : public call_event
+    {
+    public:
+      va_arg_call_event (const exploded_edge &eedge,
+			 location_t loc, tree fndecl, int depth,
+			 int num_variadic_arguments)
+      : call_event (eedge, loc, fndecl, depth),
+	m_num_variadic_arguments (num_variadic_arguments)
+      {
+      }
+
+      label_text get_desc (bool can_colorize) const OVERRIDE
+      {
+	return make_label_text_n
+	  (can_colorize, m_num_variadic_arguments,
+	   "calling %qE from %qE with %i variadic argument",
+	   "calling %qE from %qE with %i variadic arguments",
+	   get_callee_fndecl (),
+	   get_caller_fndecl (),
+	   m_num_variadic_arguments);
+      }
+    private:
+      int m_num_variadic_arguments;
+    };
+
+    const frame_region *frame_reg = m_var_arg_reg->get_frame_region ();
+    const exploded_node *dst_node = eedge.m_dest;
+    if (dst_node->get_state ().m_region_model->get_current_frame ()
+	== frame_reg)
+      {
+	const exploded_node *src_node = eedge.m_src;
+	const program_point &src_point = src_node->get_point ();
+	const int src_stack_depth = src_point.get_stack_depth ();
+	const gimple *last_stmt = src_point.get_supernode ()->get_last_stmt ();
+	const gcall *call_stmt = as_a <const gcall *> (last_stmt);
+	int num_variadic_arguments
+	  = get_num_variadic_arguments (dst_node->get_function ()->decl,
+					call_stmt);
+	emission_path->add_event
+	  (new va_arg_call_event (eedge,
+				  (last_stmt
+				   ? last_stmt->location
+				   : UNKNOWN_LOCATION),
+				  src_point.get_fndecl (),
+				  src_stack_depth,
+				  num_variadic_arguments));
+      }
+    else
+      pending_diagnostic::add_call_event (eedge, emission_path);
+  }
+
+protected:
+  va_arg_diagnostic (tree va_list_tree, const var_arg_region *var_arg_reg)
+  : m_va_list_tree (va_list_tree), m_var_arg_reg (var_arg_reg)
+  {}
+
+  bool subclass_equal_p (const pending_diagnostic &base_other) const OVERRIDE
+  {
+    const va_arg_diagnostic &other = (const va_arg_diagnostic &)base_other;
+    return (same_tree_p (m_va_list_tree, other.m_va_list_tree)
+	    && m_var_arg_reg == other.m_var_arg_reg);
+  }
+
+  /* Get the number of arguments consumed so far from the va_list
+     (*before* this va_arg call).  */
+  unsigned get_num_consumed () const
+  {
+    return m_var_arg_reg->get_index ();
+  }
+
+  /* Get a 1-based index of which variadic argument is being consumed.  */
+  unsigned get_variadic_index_for_diagnostic () const
+  {
+    return get_num_consumed () + 1;
+  }
+
+  /* User-readable expr for the va_list argument to va_arg.  */
+  tree m_va_list_tree;
+
+  /* The region that the va_arg attempted to access.  */
+  const var_arg_region *m_var_arg_reg;
+};
+
+/* A subclass of pending_diagnostic for complaining about a type mismatch
+   between the result of:
+     va_arg (AP);
+   and the type of the argument that was passed to the variadic call.  */
+
+class va_arg_type_mismatch : public va_arg_diagnostic
+{
+public:
+  va_arg_type_mismatch (tree va_list_tree, const var_arg_region *var_arg_reg,
+			tree expected_type, tree actual_type)
+  : va_arg_diagnostic (va_list_tree, var_arg_reg),
+    m_expected_type (expected_type), m_actual_type (actual_type)
+  {}
+
+  const char *get_kind () const FINAL OVERRIDE
+  {
+    return "va_arg_type_mismatch";
+  }
+
+  bool subclass_equal_p (const pending_diagnostic &base_other)
+    const FINAL OVERRIDE
+  {
+    if (!va_arg_diagnostic::subclass_equal_p (base_other))
+      return false;
+    const va_arg_type_mismatch &other
+      = (const va_arg_type_mismatch &)base_other;
+    return (same_tree_p (m_expected_type, other.m_expected_type)
+	    && same_tree_p (m_actual_type, other.m_actual_type));
+  }
+
+  int get_controlling_option () const FINAL OVERRIDE
+  {
+    return OPT_Wanalyzer_va_arg_type_mismatch;
+  }
+
+  bool emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    auto_diagnostic_group d;
+    bool warned
+      = warning_at (rich_loc, get_controlling_option (),
+		    "%<va_arg%> expected %qT but received %qT"
+		    " for variadic argument %i of %qE",
+		    m_expected_type, m_actual_type,
+		    get_variadic_index_for_diagnostic (), m_va_list_tree);
+    return warned;
+  }
+
+  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  {
+    return ev.formatted_print ("%<va_arg%> expected %qT but received %qT"
+			       " for variadic argument %i of %qE",
+			       m_expected_type, m_actual_type,
+			       get_variadic_index_for_diagnostic (),
+			       m_va_list_tree);
+  }
+
+private:
+  tree m_expected_type;
+  tree m_actual_type;
+};
+
+/* A subclass of pending_diagnostic for complaining about a
+     va_arg (AP);
+   after all of the args in AP have been consumed.  */
+
+class va_list_exhausted : public va_arg_diagnostic
+{
+public:
+  va_list_exhausted (tree va_list_tree, const var_arg_region *var_arg_reg)
+  : va_arg_diagnostic (va_list_tree, var_arg_reg)
+  {}
+
+  const char *get_kind () const FINAL OVERRIDE
+  {
+    return "va_list_exhausted";
+  }
+
+  int get_controlling_option () const FINAL OVERRIDE
+  {
+    return OPT_Wanalyzer_va_list_exhausted;
+  }
+
+  bool emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    auto_diagnostic_group d;
+    bool warned = warning_at (rich_loc, get_controlling_option (),
+			      "%qE has no more arguments (%i consumed)",
+			      m_va_list_tree, get_num_consumed ());
+    return warned;
+  }
+
+  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  {
+    return ev.formatted_print ("%qE has no more arguments (%i consumed)",
+			       m_va_list_tree, get_num_consumed ());
+  }
+};
+
+/* Return true if it's OK to copy a value from ARG_TYPE to LHS_TYPE via
+   va_arg (where argument promotion has already happened).  */
+
+static bool
+va_arg_compatible_types_p (tree lhs_type, tree arg_type)
+{
+  return compat_types_p (arg_type, lhs_type);
+}
+
+/* If AP_SVAL is a pointer to a var_arg_region, return that var_arg_region.
+   Otherwise return NULL.  */
+
+static const var_arg_region *
+maybe_get_var_arg_region (const svalue *ap_sval)
+{
+  if (const region *reg = ap_sval->maybe_get_region ())
+    return reg->dyn_cast_var_arg_region ();
+  return NULL;
+}
+
+/* Handle the on_call_pre part of "__builtin_va_arg".  */
+
+void
+region_model::impl_call_va_arg (const call_details &cd)
+{
+  region_model_context *ctxt = cd.get_ctxt ();
+
+  const svalue *in_ptr = cd.get_arg_svalue (0);
+  const region *ap_reg = deref_rvalue (in_ptr, cd.get_arg_tree (0), ctxt);
+
+  const svalue *ap_sval = get_store_value (ap_reg, ctxt);
+  if (const svalue *cast = ap_sval->maybe_undo_cast ())
+    ap_sval = cast;
+
+  tree va_list_tree = get_va_list_diag_arg (cd.get_arg_tree (0));
+  ap_sval = check_for_poison (ap_sval, va_list_tree, ctxt);
+
+  if (const region *impl_reg = ap_sval->maybe_get_region ())
+    {
+      const svalue *old_impl_sval = get_store_value (impl_reg, ctxt);
+      if (const var_arg_region *arg_reg
+	  = maybe_get_var_arg_region (old_impl_sval))
+	{
+	  bool saw_problem = false;
+
+	  const frame_region *frame_reg = arg_reg->get_frame_region ();
+	  unsigned next_arg_idx = arg_reg->get_index ();
+
+	  if (get_stack_depth () > 1)
+	    {
+	      /* The interprocedural case: the called frame will have been
+		 populated with any variadic aruguments.
+		 Attempt to extract arg_reg to cd's return region (which already
+		 has a conjured_svalue), or warn if there's a problem
+		 (incompatible types, or if we've run out of args).  */
+	      if (const svalue *arg_sval
+		  = m_store.get_any_binding (m_mgr->get_store_manager (),
+					     arg_reg))
+		{
+		  tree lhs_type = cd.get_lhs_type ();
+		  tree arg_type = arg_sval->get_type ();
+		  if (va_arg_compatible_types_p (lhs_type, arg_type))
+		    cd.maybe_set_lhs (arg_sval);
+		  else
+		    {
+		      if (ctxt)
+			ctxt->warn (new va_arg_type_mismatch (va_list_tree,
+							      arg_reg,
+							      lhs_type,
+							      arg_type));
+		      saw_problem = true;
+		    }
+		}
+	      else
+		{
+		  if (ctxt)
+		    ctxt->warn (new va_list_exhausted (va_list_tree, arg_reg));
+		  saw_problem = true;
+		}
+	    }
+	  else
+	    {
+	      /* This frame is an entry-point to the analysis, so there won't be
+		 any specific var_arg_regions populated within it.
+		 We already have a conjured_svalue for the result, so leave
+		 it untouched.  */
+	      gcc_assert (get_stack_depth () == 1);
+	    }
+
+	  if (saw_problem)
+	    {
+	      /* Set impl_reg to UNKNOWN to suppress further warnings.  */
+	      const svalue *new_ap_sval
+		= m_mgr->get_or_create_unknown_svalue (impl_reg->get_type ());
+	      set_value (impl_reg, new_ap_sval, ctxt);
+	    }
+	  else
+	    {
+	      /* Update impl_reg to advance to the next arg.  */
+	      const region *next_var_arg_region
+		= m_mgr->get_var_arg_region (frame_reg, next_arg_idx + 1);
+	      const svalue *new_ap_sval
+		= m_mgr->get_ptr_svalue (NULL_TREE, next_var_arg_region);
+	      set_value (impl_reg, new_ap_sval, ctxt);
+	    }
+	}
+    }
+}
+
+/* Handle the on_call_post part of "__builtin_va_end".  */
+
+void
+region_model::impl_call_va_end (const call_details &)
+{
+  /* No-op.  */
+}
+
+} // namespace ana
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 7a35d9613a4..e8e6d4e039b 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -464,6 +464,10 @@  Objective-C and Objective-C++ Dialects}.
 -Wno-analyzer-use-after-free @gol
 -Wno-analyzer-use-of-pointer-in-stale-stack-frame @gol
 -Wno-analyzer-use-of-uninitialized-value @gol
+-Wno-analyzer-va-arg-type-mismatch @gol
+-Wno-analyzer-va-list-exhausted @gol
+-Wno-analyzer-va-list-leak @gol
+-Wno-analyzer-va-list-use-after-va-end @gol
 -Wno-analyzer-write-to-const @gol
 -Wno-analyzer-write-to-string-literal @gol
 }
@@ -9689,6 +9693,10 @@  Enabling this option effectively enables the following warnings:
 -Wanalyzer-use-after-free @gol
 -Wanalyzer-use-of-pointer-in-stale-stack-frame @gol
 -Wanalyzer-use-of-uninitialized-value @gol
+-Wanalyzer-va-arg-type-mismatch @gol
+-Wanalyzer-va-list-exhausted @gol
+-Wanalyzer-va-list-leak @gol
+-Wanalyzer-va-list-use-after-va-end @gol
 -Wanalyzer-write-to-const @gol
 -Wanalyzer-write-to-string-literal @gol
 }
@@ -9971,6 +9979,53 @@  to disable it.
 This diagnostic warns for paths through the code in which a pointer
 is dereferenced that points to a variable in a stale stack frame.
 
+@item -Wno-analyzer-va-arg-type-mismatch
+@opindex Wanalyzer-va-arg-type-mismatch
+@opindex Wno-analyzer-va-arg-type-mismatch
+This warning requires @option{-fanalyzer}, which enables it; use
+@option{-Wno-analyzer-va-arg-type-mismatch}
+to disable it.
+
+This diagnostic warns for interprocedural paths through the code for which
+the analyzer detects an attempt to use @code{va_arg} to extract a value
+passed to a variadic call, but uses a type that does not match that of
+the expression passed to the call.
+
+@item -Wno-analyzer-va-list-exhausted
+@opindex Wanalyzer-va-list-exhausted
+@opindex Wno-analyzer-va-list-exhausted
+This warning requires @option{-fanalyzer}, which enables it; use
+@option{-Wno-analyzer-va-list-exhausted}
+to disable it.
+
+This diagnostic warns for interprocedural paths through the code for which
+the analyzer detects an attempt to use @code{va_arg} to access the next
+value passed to a variadic call, but all of the values in the
+@code{va_list} have already been consumed.
+
+@item -Wno-analyzer-va-list-leak
+@opindex Wanalyzer-va-list-leak
+@opindex Wno-analyzer-va-list-leak
+This warning requires @option{-fanalyzer}, which enables it; use
+@option{-Wno-analyzer-va-list-leak}
+to disable it.
+
+This diagnostic warns for interprocedural paths through the code for which
+the analyzer detects that @code{va_start} or @code{va_copy} has been called
+on a @code{va_list} without a corresponding call to @code{va_end}.
+
+@item -Wno-analyzer-va-list-use-after-va-end
+@opindex Wanalyzer-va-list-use-after-va-end
+@opindex Wno-analyzer-va-list-use-after-va-end
+This warning requires @option{-fanalyzer}, which enables it; use
+@option{-Wno-analyzer-va-list-use-after-va-end}
+to disable it.
+
+This diagnostic warns for interprocedural paths through the code for which
+the analyzer detects an attempt to use a @code{va_list}  after
+@code{va_end} has been called on it.
+@code{va_list}.
+
 @item -Wno-analyzer-write-to-const
 @opindex Wanalyzer-write-to-const
 @opindex Wno-analyzer-write-to-const
diff --git a/gcc/testsuite/gcc.dg/analyzer/stdarg-1.c b/gcc/testsuite/gcc.dg/analyzer/stdarg-1.c
new file mode 100644
index 00000000000..295f0efb74d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/stdarg-1.c
@@ -0,0 +1,433 @@ 
+#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_va_list ap;
+  __builtin_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_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_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_va_list ap;
+  __builtin_va_start (ap, placeholder);
+  __analyzer_test_2_inner (ap);
+  __builtin_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_va_list ap;
+  __builtin_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\\)" } */
+
+  __builtin_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_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_va_list ap;
+  __builtin_va_start (ap, placeholder);
+  __analyzer_test_not_enough_args_2_inner (ap);
+  __builtin_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_va_list ap;
+  __builtin_va_start (ap, placeholder);
+
+  s = __builtin_va_arg (ap, char *);
+  __analyzer_eval (s[0] == 'f'); /* { dg-warning "TRUE" } */
+
+  __builtin_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_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_va_list ap;
+  __builtin_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_va_list ap;
+  __builtin_va_start (ap, placeholder); /* { dg-message "\\(1\\) 'va_start' called here" } */
+  i = __builtin_va_arg (ap, int);
+  if (i == 42)
+    {
+      __builtin_va_end (ap);
+      return -1;
+    }
+  j = __builtin_va_arg (ap, int);
+  if (j == 1066) /* { dg-message "branch" } */
+    return -1; /* { dg-message "here" } */
+  __builtin_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_va_list ap;
+  __builtin_va_start (ap, placeholder);
+  __builtin_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_va_list ap;
+  __builtin_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'" } */
+
+  __builtin_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_va_list ap;
+  __builtin_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_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_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_va_list ap;
+  __builtin_va_start (ap, placeholder);
+
+  __analyzer_test_type_mismatch_3_inner (ap);
+
+  __builtin_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_va_list ap;
+
+  /* First traversal.  */
+  {
+    int i, j;
+
+    __builtin_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_va_end (ap);
+  }
+
+  /* Second traversal.  */
+  {
+    int i, j;
+
+    __builtin_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_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_va_list args1;
+  __builtin_va_list args2;
+
+  __builtin_va_start (args1, placeholder);
+  __builtin_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_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_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_va_list args1;
+  __builtin_va_list args2;
+
+  __builtin_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_va_copy (args2, args1);
+
+  j = __builtin_va_arg (args1, int);
+  __analyzer_eval (j == 42); /* { dg-warning "TRUE" } */
+  __builtin_va_end (args1);
+
+  /* Traversal of copy.  */
+  j = __builtin_va_arg (args2, int);
+  __analyzer_eval (j == 42); /* { dg-warning "TRUE" } */
+  __builtin_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_va_list ap1, ap2;
+  __builtin_va_start (ap1, placeholder);
+  __builtin_va_end (ap1); /* { dg-message "'va_end' called here" } */
+  __builtin_va_copy (ap2, ap1); /* { dg-warning "'va_copy' after 'va_end'" } */
+  __builtin_va_end (ap2);
+}
+
+/* leak of va_copy.  */
+
+void test_leak_of_va_copy (int placeholder, ...)
+{
+  __builtin_va_list ap1, ap2;
+  __builtin_va_start (ap1, placeholder);
+  __builtin_va_copy (ap2, ap1); /* { dg-message "'va_copy' called here" } */
+  __builtin_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_va_list ap;
+  __builtin_va_start (ap, placeholder);
+  __builtin_va_end (ap); /* { dg-message "'va_end' called here" } */
+  __builtin_va_end (ap); /* { dg-warning "'va_end' after 'va_end'" } */
+}
+
+/* double va_start.  */
+
+void test_double_va_start (int placeholder, ...)
+{
+  int i;
+  __builtin_va_list ap;
+  __builtin_va_start (ap, placeholder); /* { dg-message "'va_start' called here" } */
+  __builtin_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_va_end (ap);
+}
+
+/* va_copy before va_start.  */
+
+void test_va_copy_before_va_start (int placeholder, ...)
+{
+  __builtin_va_list ap1; /* { dg-message "region created on stack here" } */
+  __builtin_va_list ap2;
+  __builtin_va_copy (ap2, ap1); /* { dg-warning "use of uninitialized value 'ap1'" } */
+  __builtin_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_va_list global_ap;
+
+static void __attribute__((noinline))
+__analyzer_called_by_test_va_arg_after_return (int placeholder, ...)
+{
+  __builtin_va_start (global_ap, placeholder);
+  __builtin_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-2.c b/gcc/testsuite/gcc.dg/analyzer/stdarg-2.c
new file mode 100644
index 00000000000..69a2acbc3fa
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/stdarg-2.c
@@ -0,0 +1,436 @@ 
+/* As per stdarg-1.c, but using <stdarg.h>, rather than hardcoded builtins.  */
+
+#include <stdarg.h>
+#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;
+  
+  va_list ap;
+  va_start (ap, placeholder);
+
+  s = va_arg (ap, char *);
+  __analyzer_eval (s[0] == 'f'); /* { dg-warning "TRUE" } */
+
+  i = va_arg (ap, int);
+  __analyzer_eval (i == 1066); /* { dg-warning "TRUE" } */
+
+  c = (char)va_arg (ap, int);
+  __analyzer_eval (c == '@'); /* { dg-warning "TRUE" } */
+
+  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 (va_list ap)
+{
+  const char *s;
+  int i;
+  char c;
+  
+  s = va_arg (ap, char *);
+  __analyzer_eval (s[0] == 'f'); /* { dg-warning "TRUE" } */
+
+  i = va_arg (ap, int);
+  __analyzer_eval (i == 1066); /* { dg-warning "TRUE" } */
+
+  c = (char)va_arg (ap, int);
+  __analyzer_eval (c == '@'); /* { dg-warning "TRUE" } */
+}
+
+static void __attribute__((noinline))
+__analyzer_test_2_middle (int placeholder, ...)
+{
+  va_list ap;
+  va_start (ap, placeholder);
+  __analyzer_test_2_inner (ap);
+  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;
+  
+  va_list ap;
+  va_start (ap, placeholder);
+
+  s = va_arg (ap, char *);
+  __analyzer_eval (s[0] == 'f'); /* { dg-warning "TRUE" } */
+
+  i = va_arg (ap, int); /* { dg-warning "'ap' has no more arguments \\(1 consumed\\)" } */
+
+  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 (va_list ap)
+{
+  const char *s;
+  int i;
+  
+  s = va_arg (ap, char *);
+  __analyzer_eval (s[0] == 'f'); /* { dg-warning "TRUE" } */
+
+  i = 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, ...)
+{
+  va_list ap;
+  va_start (ap, placeholder);
+  __analyzer_test_not_enough_args_2_inner (ap);
+  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;
+  
+  va_list ap;
+  va_start (ap, placeholder);
+
+  s = va_arg (ap, char *);
+  __analyzer_eval (s[0] == 'f'); /* { dg-warning "TRUE" } */
+
+  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, ...)
+{
+  va_list ap; /* { dg-message "region created on stack here" } */
+  int i = va_arg (ap, int); /* { dg-warning "use of uninitialized value 'ap'" } */
+}
+
+/* Missing va_end.  */
+
+void test_missing_va_end (int placeholder, ...)
+{
+  int i;
+  va_list ap;
+  va_start (ap, placeholder); /* { dg-message "\\(1\\) 'va_start' called here" } */
+  i = 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;
+  va_list ap;
+  va_start (ap, placeholder); /* { dg-message "\\(1\\) 'va_start' called here" } */
+  i = va_arg (ap, int);
+  if (i == 42)
+    {
+      va_end (ap);
+      return -1;
+    }
+  j = va_arg (ap, int);
+  if (j == 1066) /* { dg-message "branch" } */
+    return -1; /* { dg-message "here" } */
+  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;
+  va_list ap;
+  va_start (ap, placeholder);
+  va_end (ap); /* { dg-message "'va_end' called here" } */
+  i = 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;
+  
+  va_list ap;
+  va_start (ap, placeholder);
+
+  i = va_arg (ap, int); /* { dg-warning "'va_arg' expected 'int' but received '\[^\n\r\]*' for variadic argument 1 of 'ap'" } */
+
+  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;
+  
+  va_list ap;
+  va_start (ap, placeholder);
+
+  str = va_arg (ap, const char *); /* { dg-warning "'va_arg' expected 'const char \\*' but received 'int' for variadic argument 1 of 'ap'" } */
+
+  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 (va_list ap)
+{
+  const char *str;
+  
+  str = 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, ...)
+{
+  va_list ap;
+  va_start (ap, placeholder);
+
+  __analyzer_test_type_mismatch_3_inner (ap);
+
+  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, ...)
+{
+  va_list ap;
+
+  /* First traversal.  */
+  {
+    int i, j;
+
+    va_start (ap, placeholder);
+
+    i = va_arg (ap, int);
+    __analyzer_eval (i == 1066); /* { dg-warning "TRUE" } */
+
+    j = va_arg (ap, int);
+    __analyzer_eval (j == 42); /* { dg-warning "TRUE" } */
+
+    va_end (ap);
+  }
+
+  /* Second traversal.  */
+  {
+    int i, j;
+
+    va_start (ap, placeholder);
+
+    i = va_arg (ap, int);
+    __analyzer_eval (i == 1066); /* { dg-warning "TRUE" } */
+
+    j = va_arg (ap, int);
+    __analyzer_eval (j == 42); /* { dg-warning "TRUE" } */
+
+    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;
+  va_list args1;
+  va_list args2;
+
+  va_start (args1, placeholder);
+  va_copy (args2, args1);
+
+  /* First traversal.  */
+  i = va_arg (args1, int);
+  __analyzer_eval (i == 1066); /* { dg-warning "TRUE" } */
+  j = va_arg (args1, int);
+  __analyzer_eval (j == 42); /* { dg-warning "TRUE" } */
+  va_end (args1);
+
+  /* Traversal of copy.  */
+  i = va_arg (args2, int);
+  __analyzer_eval (i == 1066); /* { dg-warning "TRUE" } */
+  j = va_arg (args2, int);
+  __analyzer_eval (j == 42); /* { dg-warning "TRUE" } */
+  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;
+  va_list args1;
+  va_list args2;
+
+  va_start (args1, placeholder);
+
+  /* First traversal.  */
+  i = va_arg (args1, int);
+  __analyzer_eval (i == 1066); /* { dg-warning "TRUE" } */
+
+  /* va_copy after the first va_arg. */
+  va_copy (args2, args1);
+
+  j = va_arg (args1, int);
+  __analyzer_eval (j == 42); /* { dg-warning "TRUE" } */
+  va_end (args1);
+
+  /* Traversal of copy.  */
+  j = va_arg (args2, int);
+  __analyzer_eval (j == 42); /* { dg-warning "TRUE" } */
+  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, ...)
+{
+  va_list ap1, ap2;
+  va_start (ap1, placeholder);
+  va_end (ap1); /* { dg-message "'va_end' called here" } */
+  va_copy (ap2, ap1); /* { dg-warning "'va_copy' after 'va_end'" } */
+  va_end (ap2);
+}
+
+/* leak of va_copy.  */
+
+void test_leak_of_va_copy (int placeholder, ...)
+{
+  va_list ap1, ap2;
+  va_start (ap1, placeholder); 
+  va_copy (ap2, ap1);  /* { dg-message "'va_copy' called here" } */
+  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, ...)
+{
+  va_list ap;
+  va_start (ap, placeholder);
+  va_end (ap); /* { dg-message "'va_end' called here" } */
+  va_end (ap); /* { dg-warning "'va_end' after 'va_end'" } */
+}
+
+/* double va_start.  */
+
+void test_double_va_start (int placeholder, ...)
+{
+  int i;
+  va_list ap;
+  va_start (ap, placeholder); /* { dg-message "'va_start' called here" } */
+  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 } */
+  va_end (ap);
+}
+
+/* va_copy before va_start.  */
+
+void test_va_copy_before_va_start (int placeholder, ...)
+{
+  va_list ap1; /* { dg-message "region created on stack here" } */
+  va_list ap2;
+  va_copy (ap2, ap1); /* { dg-warning "use of uninitialized value 'ap1'" } */
+  va_end (ap2);
+}
+
+/* Verify that we complain about uses of a va_list after the function 
+   in which va_start was called has returned.  */
+
+va_list global_ap;
+
+static void __attribute__((noinline))
+__analyzer_called_by_test_va_arg_after_return (int placeholder, ...)
+{
+  va_start (global_ap, placeholder);
+  va_end (global_ap);
+}
+
+void test_va_arg_after_return (void)
+{
+  int i;
+  __analyzer_called_by_test_va_arg_after_return (42, 1066);
+  i = 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-fmtstring-1.c b/gcc/testsuite/gcc.dg/analyzer/stdarg-fmtstring-1.c
new file mode 100644
index 00000000000..3892c3cdd1c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/stdarg-fmtstring-1.c
@@ -0,0 +1,103 @@ 
+/* { dg-additional-options "-fno-analyzer-call-summaries -fno-analyzer-state-merge -Wno-analyzer-too-complex" } */
+
+void test_format_string (const char *fmt, ...)
+{
+  __builtin_va_list ap;
+  __builtin_va_start (ap, fmt);
+  while (*fmt)
+    switch (*fmt++)
+      {
+      case 's':
+	{
+	  const char *s = __builtin_va_arg (ap, char *); /* { dg-warning "'va_arg' expected 'const char \\*' but received 'int' for variadic argument 1 of 'ap'" } */
+	  __builtin_printf ("string: %s\n", s);
+	}
+	break;
+      case 'd':
+	{
+	  int i = __builtin_va_arg (ap, int); /* { dg-warning "'va_arg' expected 'int' but received '\[^\n\r\]*' for variadic argument 1 of 'ap'" "type mismatch from wrong_type_for_percent_d" } */
+	  /* { dg-warning "'ap' has no more arguments \\(1 consumed\\)" "not_enough_args" { target *-*-* } .-1 } */
+	  __builtin_printf ("int: %d\n", i);
+	}
+	break;
+      case 'c':
+	{
+	  char c = (char)__builtin_va_arg (ap, int);
+	  __builtin_printf ("char: %c\n", c);
+	}
+	break;      
+      }
+  __builtin_va_end (ap);
+}
+
+void test_missing_va_start (const char *fmt, ...)
+{
+  __builtin_va_list ap;
+
+  while (*fmt)
+    switch (*fmt++)
+      {
+      case 's':
+	{
+	  const char *s = __builtin_va_arg (ap, char *); /* { dg-warning "use of uninitialized value 'ap'" } */
+	  __builtin_printf ("string: %s\n", s);
+	}
+	break;
+      case 'd':
+	{
+	  int i = __builtin_va_arg (ap, int); /* { dg-warning "use of uninitialized value 'ap'" } */
+	  __builtin_printf ("int: %d\n", i); 
+	}
+	break;
+      case 'c':
+	{
+	  char c = (char)__builtin_va_arg (ap, int); /* { dg-warning "use of uninitialized value 'ap'" } */
+	  __builtin_printf ("char: %c\n", c);
+	}
+	break;      
+      }
+  __builtin_va_end (ap);
+}
+
+void test_missing_va_end (const char *fmt, ...)
+{
+  __builtin_va_list ap;
+  __builtin_va_start (ap, fmt);
+  while (*fmt)
+    switch (*fmt++)
+      {
+      case 's':
+	{
+	  const char *s = __builtin_va_arg (ap, char *);
+	  __builtin_printf ("string: %s\n", s);
+	}
+	break;
+      case 'd':
+	{
+	  int i = __builtin_va_arg (ap, int);
+	  __builtin_printf ("int: %d\n", i);
+	}
+	break;
+      case 'c':
+	{
+	  char c = (char)__builtin_va_arg (ap, int);
+	  __builtin_printf ("char: %c\n", c);
+	}
+	break;      
+      }
+} /* { dg-warning "missing call to 'va_end'" } */
+
+void wrong_type_for_percent_s (void)
+{
+  test_format_string ("%s", 42);
+}
+
+void wrong_type_for_percent_d (void)
+{
+  test_format_string ("%d", "foo");
+}
+
+void not_enough_args (void)
+{
+  test_format_string ("%s%d", "foo");
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/stdarg-lto-1-a.c b/gcc/testsuite/gcc.dg/analyzer/stdarg-lto-1-a.c
new file mode 100644
index 00000000000..f56ad88d990
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/stdarg-lto-1-a.c
@@ -0,0 +1,24 @@ 
+/* { dg-do link } */
+/* { dg-require-effective-target lto } */
+/* { dg-additional-options "-flto" } */
+/* { dg-additional-sources stdarg-lto-1-b.c } */
+
+#include <stdarg.h>
+#include "stdarg-lto-1.h"
+
+/* Type mismatch: expect const char *, but passed an int.  */
+
+void
+called_by_test_type_mismatch_1 (int placeholder, ...)
+{
+  const char *str;
+  
+  va_list ap;
+  va_start (ap, placeholder);
+
+  str = va_arg (ap, const char *); /* { dg-warning "'va_arg' expected '\[^\n\r\]*' but received 'int' for variadic argument 1 of 'ap'" } */
+
+  va_end (ap);
+}
+
+int main() { return 0; }
diff --git a/gcc/testsuite/gcc.dg/analyzer/stdarg-lto-1-b.c b/gcc/testsuite/gcc.dg/analyzer/stdarg-lto-1-b.c
new file mode 100644
index 00000000000..edd51f02497
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/stdarg-lto-1-b.c
@@ -0,0 +1,6 @@ 
+#include "stdarg-lto-1.h"
+
+void test_type_mismatch_1 (void)
+{
+  called_by_test_type_mismatch_1 (42, 1066);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/stdarg-lto-1.h b/gcc/testsuite/gcc.dg/analyzer/stdarg-lto-1.h
new file mode 100644
index 00000000000..69835747316
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/stdarg-lto-1.h
@@ -0,0 +1 @@ 
+extern void called_by_test_type_mismatch_1 (int placeholder, ...);
diff --git a/gcc/testsuite/gcc.dg/analyzer/stdarg-sentinel-1.c b/gcc/testsuite/gcc.dg/analyzer/stdarg-sentinel-1.c
new file mode 100644
index 00000000000..f8c1f0eb0f8
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/stdarg-sentinel-1.c
@@ -0,0 +1,25 @@ 
+/* { dg-additional-options "-Wno-analyzer-too-complex" } */
+
+#define NULL ((void *)0)
+
+void test_sentinel (int arg, ...)
+{
+  const char *s;
+  __builtin_va_list ap;
+  __builtin_va_start (ap, arg);
+  while (s = __builtin_va_arg (ap, char *)) /* { dg-warning "'ap' has no more arguments \\(2 consumed\\)" } */
+    {
+      (void)s;
+    }
+  __builtin_va_end (ap);
+}
+
+void test_caller (void)
+{
+  test_sentinel (42, "foo", "bar", NULL);
+}
+
+void missing_sentinel (void)
+{
+  test_sentinel (42, "foo", "bar");
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/stdarg-types-1.c b/gcc/testsuite/gcc.dg/analyzer/stdarg-types-1.c
new file mode 100644
index 00000000000..dcea87e6050
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/stdarg-types-1.c
@@ -0,0 +1,25 @@ 
+/* { dg-require-effective-target int32 } */
+/* { dg-require-effective-target lp64 } */
+
+/* Type mismatch: expect long, but passed an int.  */
+
+static void __attribute__((noinline))
+__analyzer_consume_long (int placeholder, ...)
+{
+  long v;
+  __builtin_va_list ap;
+  __builtin_va_start (ap, placeholder);
+  v = __builtin_va_arg (ap, long); /* { dg-warning "'va_arg' expected 'long int' but received 'int' for variadic argument 1 of 'ap'" } */
+  __builtin_va_end (ap);
+}
+
+void test_int_to_long (void)
+{
+  __analyzer_consume_long (42, 1066);
+}
+
+void test_char_to_long (void)
+{
+  /* char promoted to int.  */
+  __analyzer_consume_long (42, 'a');
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/stdarg-types-2.c b/gcc/testsuite/gcc.dg/analyzer/stdarg-types-2.c
new file mode 100644
index 00000000000..39d5c6efb9e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/stdarg-types-2.c
@@ -0,0 +1,55 @@ 
+/* Should be OK to add a "const" qualifier to a ptr.  */
+
+static void __attribute__((noinline))
+__analyzer_consume_const_char_ptr (int placeholder, ...)
+{
+  const char *v;
+  __builtin_va_list ap;
+  __builtin_va_start (ap, placeholder);
+  v = __builtin_va_arg (ap, const char *);
+  __builtin_va_end (ap);
+}
+
+void test_char_ptr_to_const_char_ptr (char *p)
+{
+  __analyzer_consume_const_char_ptr (42, p); /* { dg-bogus "" } */
+}
+
+/* What about casting away const-ness?
+   Currently we don't complain.  */
+
+static void __attribute__((noinline))
+__analyzer_consume_char_ptr (int placeholder, ...)
+{
+  char *v;
+  __builtin_va_list ap;
+  __builtin_va_start (ap, placeholder);
+  v = __builtin_va_arg (ap, char *);
+  __builtin_va_end (ap);
+}
+
+void test_const_char_ptr_to_char_ptr (const char *p)
+{
+  __analyzer_consume_const_char_ptr (42, p);
+}
+
+/* What about casting ptrs?
+   Currently we don't complain.  */
+
+struct foo;
+struct bar;
+
+static void __attribute__((noinline))
+__analyzer_consume_bar_ptr (int placeholder, ...)
+{
+  struct bar *v;
+  __builtin_va_list ap;
+  __builtin_va_start (ap, placeholder);
+  v = __builtin_va_arg (ap, struct bar *);
+  __builtin_va_end (ap);
+}
+
+void test_foo_ptr_to_bar_ptr (struct foo *p)
+{
+  __analyzer_consume_bar_ptr (42, p);
+}