aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gcc/Makefile.in3
-rw-r--r--gcc/analyzer/analyzer.cc38
-rw-r--r--gcc/analyzer/analyzer.h4
-rw-r--r--gcc/analyzer/analyzer.opt16
-rw-r--r--gcc/analyzer/checker-path.cc16
-rw-r--r--gcc/analyzer/checker-path.h6
-rw-r--r--gcc/analyzer/diagnostic-manager.cc19
-rw-r--r--gcc/analyzer/engine.cc22
-rw-r--r--gcc/analyzer/pending-diagnostic.cc82
-rw-r--r--gcc/analyzer/pending-diagnostic.h11
-rw-r--r--gcc/analyzer/region-model-impl-calls.cc3
-rw-r--r--gcc/analyzer/region-model-manager.cc20
-rw-r--r--gcc/analyzer/region-model.cc26
-rw-r--r--gcc/analyzer/region-model.h9
-rw-r--r--gcc/analyzer/region.cc32
-rw-r--r--gcc/analyzer/region.h87
-rw-r--r--gcc/analyzer/sm.cc1
-rw-r--r--gcc/analyzer/sm.h6
-rw-r--r--gcc/analyzer/varargs.cc1025
-rw-r--r--gcc/doc/invoke.texi55
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/stdarg-1.c433
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/stdarg-2.c436
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/stdarg-fmtstring-1.c103
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/stdarg-lto-1-a.c24
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/stdarg-lto-1-b.c6
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/stdarg-lto-1.h1
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/stdarg-sentinel-1.c25
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/stdarg-types-1.c25
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/stdarg-types-2.c55
29 files changed, 2567 insertions, 22 deletions
diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 31ff955..70f7d21 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 2c63a53..c85dbf3 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 = ≈
+ 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 39934a3..dcefc13 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 a0ba2c9..23dfc79 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 a61b3ee..5fdbc38 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 d37c999..545d7db 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 2d49a3b..e8a828d 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 24dd598..1638662 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 5e0ea4c..eff050f 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 51039ea..17db9fe 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 621e700..a76caf7 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 6d248c9..3377f15 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 816b410..de221c3 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 eff3d49..4e5cb46 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 1a7949b3f..a828623 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 5150be7..d32110b 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 515f86d..622cb0b 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 7ce1c73..4cc5453 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 0000000..de77fe5d3
--- /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 7a35d96..e8e6d4e 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 0000000..295f0ef
--- /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 0000000..69a2acb
--- /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 0000000..3892c3c
--- /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 0000000..f56ad88
--- /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 0000000..edd51f0
--- /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 0000000..6983574
--- /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 0000000..f8c1f0e
--- /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 0000000..dcea87e
--- /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 0000000..39d5c6e
--- /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);
+}