public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
* [PATCH 09/49] gimple const-correctness fixes
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (3 preceding siblings ...)
  2019-11-16  1:17 ` [PATCH 14/49] hash-map-tests.c: add a selftest involving int_hash David Malcolm
@ 2019-11-16  1:17 ` David Malcolm
  2019-12-04 17:06   ` Martin Sebor
                     ` (2 more replies)
  2019-11-16  1:17 ` [PATCH 01/49] analyzer: user-facing documentation David Malcolm
                   ` (45 subsequent siblings)
  50 siblings, 3 replies; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:17 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This patch converts various "gimple *" to "const gimple *" and similar
fixes for gimple subclasses, adding is_a_helper for gimple subclasses
to support the const form of as_a, and adding a few "const" overloads
of accessors.

This is enough to make pp_gimple_stmt_1's stmt const.

gcc/ChangeLog:
	* gimple-predict.h (gimple_predict_predictor): Make "gs" param
	const.
	(gimple_predict_outcome): Likewise.
	* gimple-pretty-print.c (do_niy): Likewise.
	(dump_unary_rhs): Likewise.
	(dump_binary_rhs): Likewise.
	(dump_ternary_rhs): Likewise.
	(dump_gimple_assign): Likewise.
	(dump_gimple_return): Likewise.
	(dump_gimple_call_args): Likewise.
	(pp_points_to_solution): Make "pt" param const.
	(dump_gimple_call): Make "gs" param const.
	(dump_gimple_switch): Likewise.
	(dump_gimple_cond): Likewise.
	(dump_gimple_label): Likewise.
	(dump_gimple_goto): Likewise.
	(dump_gimple_bind): Likewise.
	(dump_gimple_try): Likewise.
	(dump_gimple_catch): Likewise.
	(dump_gimple_eh_filter): Likewise.
	(dump_gimple_eh_must_not_throw): Likewise.
	(dump_gimple_eh_else): Likewise.
	(dump_gimple_resx): Likewise.
	(dump_gimple_eh_dispatch): Likewise.
	(dump_gimple_debug): Likewise.
	(dump_gimple_omp_for): Likewise.
	(dump_gimple_omp_continue): Likewise.
	(dump_gimple_omp_single): Likewise.
	(dump_gimple_omp_taskgroup): Likewise.
	(dump_gimple_omp_target): Likewise.
	(dump_gimple_omp_teams): Likewise.
	(dump_gimple_omp_sections): Likewise.
	(dump_gimple_omp_block): Likewise.
	(dump_gimple_omp_critical): Likewise.
	(dump_gimple_omp_ordered): Likewise.
	(dump_gimple_omp_scan): Likewise.
	(dump_gimple_omp_return): Likewise.
	(dump_gimple_transaction): Likewise.
	(dump_gimple_asm): Likewise.
	(dump_gimple_phi): Make "phi" param const.
	(dump_gimple_omp_parallel): Make "gs" param const.
	(dump_gimple_omp_task): Likewise.
	(dump_gimple_omp_atomic_load): Likewise.
	(dump_gimple_omp_atomic_store): Likewise.
	(dump_gimple_mem_ops): Likewise.
	(pp_gimple_stmt_1): Likewise.  Add "const" to the various as_a <>
	casts throughout.
	* gimple-pretty-print.h (gimple_stmt_1): Make gimple * param const.
	* gimple.h (is_a_helper <const gdebug *>::test): New.
	(is_a_helper <const ggoto *>::test): New.
	(is_a_helper <const glabel *>::test): New.
	(is_a_helper <const geh_else *>::test): New.
	(is_a_helper <const geh_mnt *>::test): New.
	(is_a_helper <const gswitch *>::test): New.
	(is_a_helper <const gtry *>::test): New.
	(is_a_helper <const greturn *>::test): New.
	(gimple_call_tail_p): Make param const.
	(gimple_call_return_slot_opt_p): Likewise.
	(gimple_call_va_arg_pack_p): Likewise.
	(gimple_call_use_set): Add const overload.
	(gimple_call_clobber_set): Likewise.
	(gimple_has_lhs): Make param const.
	(gimple_bind_body): Likewise.
	(gimple_catch_handler): Likewise.
	(gimple_eh_filter_failure): Likewise.
	(gimple_eh_must_not_throw_fndecl): Likewise.
	(gimple_eh_else_n_body): Likewise.
	(gimple_eh_else_e_body): Likewise.
	(gimple_try_eval): Likewise.
	(gimple_try_cleanup): Likewise.
	(gimple_phi_arg): Add const overload.
	(gimple_phi_arg_def): Make param const.
	(gimple_phi_arg_edge): Likewise.
	(gimple_phi_arg_location): Likewise.
	(gimple_phi_arg_has_location): Likewise.
	(gimple_debug_bind_get_var): Likewise.
	(gimple_debug_bind_get_value): Likewise.
	(gimple_debug_source_bind_get_var): Likewise.
	(gimple_debug_source_bind_get_value): Likewise.
	(gimple_omp_body): Likewise.
	(gimple_omp_for_collapse): Likewise.
	(gimple_omp_for_pre_body): Likewise.
	(gimple_transaction_body): Likewise.
	* tree-eh.c (lookup_stmt_eh_lp_fn): Make param "t" const.
	(lookup_stmt_eh_lp): Likewise.
	* tree-eh.h (lookup_stmt_eh_lp_fn): Make param const.
	(lookup_stmt_eh_lp): Likewise.
	* tree-ssa-alias.h (pt_solution_empty_p): Make param const.
	* tree-ssa-structalias.c (pt_solution_empty_p): Likewise.
---
 gcc/gimple-predict.h       |   4 +-
 gcc/gimple-pretty-print.c  | 159 +++++++++++++++++++++++----------------------
 gcc/gimple-pretty-print.h  |   3 +-
 gcc/gimple.h               | 156 ++++++++++++++++++++++++++++++++++----------
 gcc/tree-eh.c              |   6 +-
 gcc/tree-eh.h              |   4 +-
 gcc/tree-ssa-alias.h       |   2 +-
 gcc/tree-ssa-structalias.c |   2 +-
 8 files changed, 213 insertions(+), 123 deletions(-)

diff --git a/gcc/gimple-predict.h b/gcc/gimple-predict.h
index 761098b..d976317 100644
--- a/gcc/gimple-predict.h
+++ b/gcc/gimple-predict.h
@@ -26,7 +26,7 @@ along with GCC; see the file COPYING3.  If not see
 /* Return the predictor of GIMPLE_PREDICT statement GS.  */
 
 static inline enum br_predictor
-gimple_predict_predictor (gimple *gs)
+gimple_predict_predictor (const gimple *gs)
 {
   GIMPLE_CHECK (gs, GIMPLE_PREDICT);
   return (enum br_predictor) (gs->subcode & ~GF_PREDICT_TAKEN);
@@ -47,7 +47,7 @@ gimple_predict_set_predictor (gimple *gs, enum br_predictor predictor)
 /* Return the outcome of GIMPLE_PREDICT statement GS.  */
 
 static inline enum prediction
-gimple_predict_outcome (gimple *gs)
+gimple_predict_outcome (const gimple *gs)
 {
   GIMPLE_CHECK (gs, GIMPLE_PREDICT);
   return (gs->subcode & GF_PREDICT_TAKEN) ? TAKEN : NOT_TAKEN;
diff --git a/gcc/gimple-pretty-print.c b/gcc/gimple-pretty-print.c
index 2d5ece0..f044628 100644
--- a/gcc/gimple-pretty-print.c
+++ b/gcc/gimple-pretty-print.c
@@ -59,7 +59,7 @@ along with GCC; see the file COPYING3.  If not see
    gimple statement GS.  */
 
 static void
-do_niy (pretty_printer *buffer, gimple *gs)
+do_niy (pretty_printer *buffer, const gimple *gs)
 {
   pp_printf (buffer, "<<< Unknown GIMPLE statement: %s >>>\n",
 	     gimple_code_name[(int) gimple_code (gs)]);
@@ -327,7 +327,7 @@ dump_gimple_fmt (pretty_printer *buffer, int spc, dump_flags_t flags,
    assignment GS.  BUFFER, SPC and FLAGS are as in pp_gimple_stmt_1.  */
 
 static void
-dump_unary_rhs (pretty_printer *buffer, gassign *gs, int spc,
+dump_unary_rhs (pretty_printer *buffer, const gassign *gs, int spc,
 		dump_flags_t flags)
 {
   enum tree_code rhs_code = gimple_assign_rhs_code (gs);
@@ -423,7 +423,7 @@ dump_unary_rhs (pretty_printer *buffer, gassign *gs, int spc,
    assignment GS.  BUFFER, SPC and FLAGS are as in pp_gimple_stmt_1.  */
 
 static void
-dump_binary_rhs (pretty_printer *buffer, gassign *gs, int spc,
+dump_binary_rhs (pretty_printer *buffer, const gassign *gs, int spc,
 		 dump_flags_t flags)
 {
   const char *p;
@@ -495,7 +495,7 @@ dump_binary_rhs (pretty_printer *buffer, gassign *gs, int spc,
    assignment GS.  BUFFER, SPC and FLAGS are as in pp_gimple_stmt_1.  */
 
 static void
-dump_ternary_rhs (pretty_printer *buffer, gassign *gs, int spc,
+dump_ternary_rhs (pretty_printer *buffer, const gassign *gs, int spc,
 		  dump_flags_t flags)
 {
   const char *p;
@@ -625,7 +625,7 @@ dump_ternary_rhs (pretty_printer *buffer, gassign *gs, int spc,
    pp_gimple_stmt_1.  */
 
 static void
-dump_gimple_assign (pretty_printer *buffer, gassign *gs, int spc,
+dump_gimple_assign (pretty_printer *buffer, const gassign *gs, int spc,
 		    dump_flags_t flags)
 {
   if (flags & TDF_RAW)
@@ -687,7 +687,7 @@ dump_gimple_assign (pretty_printer *buffer, gassign *gs, int spc,
    pp_gimple_stmt_1.  */
 
 static void
-dump_gimple_return (pretty_printer *buffer, greturn *gs, int spc,
+dump_gimple_return (pretty_printer *buffer, const greturn *gs, int spc,
 		    dump_flags_t flags)
 {
   tree t;
@@ -712,7 +712,8 @@ dump_gimple_return (pretty_printer *buffer, greturn *gs, int spc,
    dump_gimple_call.  */
 
 static void
-dump_gimple_call_args (pretty_printer *buffer, gcall *gs, dump_flags_t flags)
+dump_gimple_call_args (pretty_printer *buffer, const gcall *gs,
+		       dump_flags_t flags)
 {
   size_t i = 0;
 
@@ -795,7 +796,7 @@ dump_gimple_call_args (pretty_printer *buffer, gcall *gs, dump_flags_t flags)
 /* Dump the points-to solution *PT to BUFFER.  */
 
 static void
-pp_points_to_solution (pretty_printer *buffer, struct pt_solution *pt)
+pp_points_to_solution (pretty_printer *buffer, const pt_solution *pt)
 {
   if (pt->anything)
     {
@@ -868,7 +869,7 @@ pp_points_to_solution (pretty_printer *buffer, struct pt_solution *pt)
    pp_gimple_stmt_1.  */
 
 static void
-dump_gimple_call (pretty_printer *buffer, gcall *gs, int spc,
+dump_gimple_call (pretty_printer *buffer, const gcall *gs, int spc,
 		  dump_flags_t flags)
 {
   tree lhs = gimple_call_lhs (gs);
@@ -876,7 +877,7 @@ dump_gimple_call (pretty_printer *buffer, gcall *gs, int spc,
 
   if (flags & TDF_ALIAS)
     {
-      struct pt_solution *pt;
+      const pt_solution *pt;
       pt = gimple_call_use_set (gs);
       if (!pt_solution_empty_p (pt))
 	{
@@ -1006,7 +1007,7 @@ dump_gimple_call (pretty_printer *buffer, gcall *gs, int spc,
    pp_gimple_stmt_1.  */
 
 static void
-dump_gimple_switch (pretty_printer *buffer, gswitch *gs, int spc,
+dump_gimple_switch (pretty_printer *buffer, const gswitch *gs, int spc,
 		    dump_flags_t flags)
 {
   unsigned int i;
@@ -1064,7 +1065,7 @@ dump_gimple_switch (pretty_printer *buffer, gswitch *gs, int spc,
    pp_gimple_stmt_1.  */
 
 static void
-dump_gimple_cond (pretty_printer *buffer, gcond *gs, int spc,
+dump_gimple_cond (pretty_printer *buffer, const gcond *gs, int spc,
 		  dump_flags_t flags)
 {
   if (flags & TDF_RAW)
@@ -1131,7 +1132,7 @@ dump_gimple_cond (pretty_printer *buffer, gcond *gs, int spc,
    TDF_* in dumpfils.h).  */
 
 static void
-dump_gimple_label (pretty_printer *buffer, glabel *gs, int spc,
+dump_gimple_label (pretty_printer *buffer, const glabel *gs, int spc,
 		   dump_flags_t flags)
 {
   tree label = gimple_label_label (gs);
@@ -1155,7 +1156,7 @@ dump_gimple_label (pretty_printer *buffer, glabel *gs, int spc,
    TDF_* in dumpfile.h).  */
 
 static void
-dump_gimple_goto (pretty_printer *buffer, ggoto *gs, int spc,
+dump_gimple_goto (pretty_printer *buffer, const ggoto *gs, int spc,
 		  dump_flags_t flags)
 {
   tree label = gimple_goto_dest (gs);
@@ -1171,7 +1172,7 @@ dump_gimple_goto (pretty_printer *buffer, ggoto *gs, int spc,
    TDF_* in dumpfile.h).  */
 
 static void
-dump_gimple_bind (pretty_printer *buffer, gbind *gs, int spc,
+dump_gimple_bind (pretty_printer *buffer, const gbind *gs, int spc,
 		  dump_flags_t flags)
 {
   if (flags & TDF_RAW)
@@ -1205,7 +1206,7 @@ dump_gimple_bind (pretty_printer *buffer, gbind *gs, int spc,
    dumpfile.h).  */
 
 static void
-dump_gimple_try (pretty_printer *buffer, gtry *gs, int spc,
+dump_gimple_try (pretty_printer *buffer, const gtry *gs, int spc,
 		 dump_flags_t flags)
 {
   if (flags & TDF_RAW)
@@ -1280,7 +1281,7 @@ dump_gimple_try (pretty_printer *buffer, gtry *gs, int spc,
    dumpfile.h).  */
 
 static void
-dump_gimple_catch (pretty_printer *buffer, gcatch *gs, int spc,
+dump_gimple_catch (pretty_printer *buffer, const gcatch *gs, int spc,
 		   dump_flags_t flags)
 {
   if (flags & TDF_RAW)
@@ -1297,7 +1298,7 @@ dump_gimple_catch (pretty_printer *buffer, gcatch *gs, int spc,
    dumpfile.h).  */
 
 static void
-dump_gimple_eh_filter (pretty_printer *buffer, geh_filter *gs, int spc,
+dump_gimple_eh_filter (pretty_printer *buffer, const geh_filter *gs, int spc,
 		       dump_flags_t flags)
 {
   if (flags & TDF_RAW)
@@ -1315,7 +1316,7 @@ dump_gimple_eh_filter (pretty_printer *buffer, geh_filter *gs, int spc,
 
 static void
 dump_gimple_eh_must_not_throw (pretty_printer *buffer,
-			       geh_mnt *gs, int spc, dump_flags_t flags)
+			       const geh_mnt *gs, int spc, dump_flags_t flags)
 {
   if (flags & TDF_RAW)
     dump_gimple_fmt (buffer, spc, flags, "%G <%T>", gs,
@@ -1331,7 +1332,7 @@ dump_gimple_eh_must_not_throw (pretty_printer *buffer,
    dumpfile.h).  */
 
 static void
-dump_gimple_eh_else (pretty_printer *buffer, geh_else *gs, int spc,
+dump_gimple_eh_else (pretty_printer *buffer, const geh_else *gs, int spc,
 		     dump_flags_t flags)
 {
   if (flags & TDF_RAW)
@@ -1350,7 +1351,7 @@ dump_gimple_eh_else (pretty_printer *buffer, geh_else *gs, int spc,
    dumpfile.h).  */
 
 static void
-dump_gimple_resx (pretty_printer *buffer, gresx *gs, int spc,
+dump_gimple_resx (pretty_printer *buffer, const gresx *gs, int spc,
 		  dump_flags_t flags)
 {
   if (flags & TDF_RAW)
@@ -1363,8 +1364,8 @@ dump_gimple_resx (pretty_printer *buffer, gresx *gs, int spc,
 /* Dump a GIMPLE_EH_DISPATCH tuple on the pretty_printer BUFFER.  */
 
 static void
-dump_gimple_eh_dispatch (pretty_printer *buffer, geh_dispatch *gs, int spc,
-			 dump_flags_t flags)
+dump_gimple_eh_dispatch (pretty_printer *buffer, const geh_dispatch *gs,
+			 int spc, dump_flags_t flags)
 {
   if (flags & TDF_RAW)
     dump_gimple_fmt (buffer, spc, flags, "%G <%d>", gs,
@@ -1379,7 +1380,7 @@ dump_gimple_eh_dispatch (pretty_printer *buffer, geh_dispatch *gs, int spc,
    in dumpfile.h).  */
 
 static void
-dump_gimple_debug (pretty_printer *buffer, gdebug *gs, int spc,
+dump_gimple_debug (pretty_printer *buffer, const gdebug *gs, int spc,
 		   dump_flags_t flags)
 {
   switch (gs->subcode)
@@ -1433,7 +1434,7 @@ dump_gimple_debug (pretty_printer *buffer, gdebug *gs, int spc,
 
 /* Dump a GIMPLE_OMP_FOR tuple on the pretty_printer BUFFER.  */
 static void
-dump_gimple_omp_for (pretty_printer *buffer, gomp_for *gs, int spc,
+dump_gimple_omp_for (pretty_printer *buffer, const gomp_for *gs, int spc,
 		     dump_flags_t flags)
 {
   size_t i;
@@ -1566,7 +1567,7 @@ dump_gimple_omp_for (pretty_printer *buffer, gomp_for *gs, int spc,
 /* Dump a GIMPLE_OMP_CONTINUE tuple on the pretty_printer BUFFER.  */
 
 static void
-dump_gimple_omp_continue (pretty_printer *buffer, gomp_continue *gs,
+dump_gimple_omp_continue (pretty_printer *buffer, const gomp_continue *gs,
 			  int spc, dump_flags_t flags)
 {
   if (flags & TDF_RAW)
@@ -1591,7 +1592,7 @@ dump_gimple_omp_continue (pretty_printer *buffer, gomp_continue *gs,
 /* Dump a GIMPLE_OMP_SINGLE tuple on the pretty_printer BUFFER.  */
 
 static void
-dump_gimple_omp_single (pretty_printer *buffer, gomp_single *gs,
+dump_gimple_omp_single (pretty_printer *buffer, const gomp_single *gs,
 			int spc, dump_flags_t flags)
 {
   if (flags & TDF_RAW)
@@ -1620,7 +1621,7 @@ dump_gimple_omp_single (pretty_printer *buffer, gomp_single *gs,
 /* Dump a GIMPLE_OMP_TASKGROUP tuple on the pretty_printer BUFFER.  */
 
 static void
-dump_gimple_omp_taskgroup (pretty_printer *buffer, gimple *gs,
+dump_gimple_omp_taskgroup (pretty_printer *buffer, const gimple *gs,
 			   int spc, dump_flags_t flags)
 {
   if (flags & TDF_RAW)
@@ -1649,7 +1650,7 @@ dump_gimple_omp_taskgroup (pretty_printer *buffer, gimple *gs,
 /* Dump a GIMPLE_OMP_TARGET tuple on the pretty_printer BUFFER.  */
 
 static void
-dump_gimple_omp_target (pretty_printer *buffer, gomp_target *gs,
+dump_gimple_omp_target (pretty_printer *buffer, const gomp_target *gs,
 			int spc, dump_flags_t flags)
 {
   const char *kind;
@@ -1742,7 +1743,7 @@ dump_gimple_omp_target (pretty_printer *buffer, gomp_target *gs,
 /* Dump a GIMPLE_OMP_TEAMS tuple on the pretty_printer BUFFER.  */
 
 static void
-dump_gimple_omp_teams (pretty_printer *buffer, gomp_teams *gs, int spc,
+dump_gimple_omp_teams (pretty_printer *buffer, const gomp_teams *gs, int spc,
 		       dump_flags_t flags)
 {
   if (flags & TDF_RAW)
@@ -1771,7 +1772,7 @@ dump_gimple_omp_teams (pretty_printer *buffer, gomp_teams *gs, int spc,
 /* Dump a GIMPLE_OMP_SECTIONS tuple on the pretty_printer BUFFER.  */
 
 static void
-dump_gimple_omp_sections (pretty_printer *buffer, gomp_sections *gs,
+dump_gimple_omp_sections (pretty_printer *buffer, const gomp_sections *gs,
 			  int spc, dump_flags_t flags)
 {
   if (flags & TDF_RAW)
@@ -1808,7 +1809,7 @@ dump_gimple_omp_sections (pretty_printer *buffer, gomp_sections *gs,
    pretty_printer BUFFER.  */
 
 static void
-dump_gimple_omp_block (pretty_printer *buffer, gimple *gs, int spc,
+dump_gimple_omp_block (pretty_printer *buffer, const gimple *gs, int spc,
 		       dump_flags_t flags)
 {
   if (flags & TDF_RAW)
@@ -1845,7 +1846,7 @@ dump_gimple_omp_block (pretty_printer *buffer, gimple *gs, int spc,
 /* Dump a GIMPLE_OMP_CRITICAL tuple on the pretty_printer BUFFER.  */
 
 static void
-dump_gimple_omp_critical (pretty_printer *buffer, gomp_critical *gs,
+dump_gimple_omp_critical (pretty_printer *buffer, const gomp_critical *gs,
 			  int spc, dump_flags_t flags)
 {
   if (flags & TDF_RAW)
@@ -1877,7 +1878,7 @@ dump_gimple_omp_critical (pretty_printer *buffer, gomp_critical *gs,
 /* Dump a GIMPLE_OMP_ORDERED tuple on the pretty_printer BUFFER.  */
 
 static void
-dump_gimple_omp_ordered (pretty_printer *buffer, gomp_ordered *gs,
+dump_gimple_omp_ordered (pretty_printer *buffer, const gomp_ordered *gs,
 			 int spc, dump_flags_t flags)
 {
   if (flags & TDF_RAW)
@@ -1902,7 +1903,7 @@ dump_gimple_omp_ordered (pretty_printer *buffer, gomp_ordered *gs,
 /* Dump a GIMPLE_OMP_SCAN tuple on the pretty_printer BUFFER.  */
 
 static void
-dump_gimple_omp_scan (pretty_printer *buffer, gomp_scan *gs,
+dump_gimple_omp_scan (pretty_printer *buffer, const gomp_scan *gs,
 		      int spc, dump_flags_t flags)
 {
   if (flags & TDF_RAW)
@@ -1930,7 +1931,7 @@ dump_gimple_omp_scan (pretty_printer *buffer, gomp_scan *gs,
 /* Dump a GIMPLE_OMP_RETURN tuple on the pretty_printer BUFFER.  */
 
 static void
-dump_gimple_omp_return (pretty_printer *buffer, gimple *gs, int spc,
+dump_gimple_omp_return (pretty_printer *buffer, const gimple *gs, int spc,
 			dump_flags_t flags)
 {
   if (flags & TDF_RAW)
@@ -1961,7 +1962,7 @@ dump_gimple_omp_return (pretty_printer *buffer, gimple *gs, int spc,
 /* Dump a GIMPLE_TRANSACTION tuple on the pretty_printer BUFFER.  */
 
 static void
-dump_gimple_transaction (pretty_printer *buffer, gtransaction *gs,
+dump_gimple_transaction (pretty_printer *buffer, const gtransaction *gs,
 			 int spc, dump_flags_t flags)
 {
   unsigned subcode = gimple_transaction_subcode (gs);
@@ -2063,7 +2064,8 @@ dump_gimple_transaction (pretty_printer *buffer, gtransaction *gs,
    dumpfile.h).  */
 
 static void
-dump_gimple_asm (pretty_printer *buffer, gasm *gs, int spc, dump_flags_t flags)
+dump_gimple_asm (pretty_printer *buffer, const gasm *gs, int spc,
+		 dump_flags_t flags)
 {
   unsigned int i, n, f, fields;
 
@@ -2282,7 +2284,7 @@ dump_ssaname_info_to_file (FILE *file, tree node, int spc)
    pretty printer.  If COMMENT is true, print this after #.  */
 
 static void
-dump_gimple_phi (pretty_printer *buffer, gphi *phi, int spc, bool comment,
+dump_gimple_phi (pretty_printer *buffer, const gphi *phi, int spc, bool comment,
 		 dump_flags_t flags)
 {
   size_t i;
@@ -2339,7 +2341,7 @@ dump_gimple_phi (pretty_printer *buffer, gphi *phi, int spc, bool comment,
    dumpfile.h).  */
 
 static void
-dump_gimple_omp_parallel (pretty_printer *buffer, gomp_parallel *gs,
+dump_gimple_omp_parallel (pretty_printer *buffer, const gomp_parallel *gs,
 			  int spc, dump_flags_t flags)
 {
   if (flags & TDF_RAW)
@@ -2393,7 +2395,7 @@ dump_gimple_omp_parallel (pretty_printer *buffer, gomp_parallel *gs,
    dumpfile.h).  */
 
 static void
-dump_gimple_omp_task (pretty_printer *buffer, gomp_task *gs, int spc,
+dump_gimple_omp_task (pretty_printer *buffer, const gomp_task *gs, int spc,
 		      dump_flags_t flags)
 {
   if (flags & TDF_RAW)
@@ -2455,7 +2457,7 @@ dump_gimple_omp_task (pretty_printer *buffer, gomp_task *gs, int spc,
    in dumpfile.h).  */
 
 static void
-dump_gimple_omp_atomic_load (pretty_printer *buffer, gomp_atomic_load *gs,
+dump_gimple_omp_atomic_load (pretty_printer *buffer, const gomp_atomic_load *gs,
 			     int spc, dump_flags_t flags)
 {
   if (flags & TDF_RAW)
@@ -2489,7 +2491,7 @@ dump_gimple_omp_atomic_load (pretty_printer *buffer, gomp_atomic_load *gs,
 
 static void
 dump_gimple_omp_atomic_store (pretty_printer *buffer,
-			      gomp_atomic_store *gs, int spc,
+			      const gomp_atomic_store *gs, int spc,
 			      dump_flags_t flags)
 {
   if (flags & TDF_RAW)
@@ -2517,7 +2519,7 @@ dump_gimple_omp_atomic_store (pretty_printer *buffer,
    FLAGS are as in pp_gimple_stmt_1.  */
 
 static void
-dump_gimple_mem_ops (pretty_printer *buffer, gimple *gs, int spc,
+dump_gimple_mem_ops (pretty_printer *buffer, const gimple *gs, int spc,
 		     dump_flags_t flags)
 {
   tree vdef = gimple_vdef (gs);
@@ -2548,14 +2550,14 @@ dump_gimple_mem_ops (pretty_printer *buffer, gimple *gs, int spc,
    pp_flush on BUFFER to finalize the pretty printer.  */
 
 void
-pp_gimple_stmt_1 (pretty_printer *buffer, gimple *gs, int spc,
+pp_gimple_stmt_1 (pretty_printer *buffer, const gimple *gs, int spc,
 		  dump_flags_t flags)
 {
   if (!gs)
     return;
 
   if (flags & TDF_STMTADDR)
-    pp_printf (buffer, "<&%p> ", (void *) gs);
+    pp_printf (buffer, "<&%p> ", (const void *) gs);
 
   if ((flags & TDF_LINENO) && gimple_has_location (gs))
     dump_location (buffer, gimple_location (gs));
@@ -2580,31 +2582,31 @@ pp_gimple_stmt_1 (pretty_printer *buffer, gimple *gs, int spc,
   switch (gimple_code (gs))
     {
     case GIMPLE_ASM:
-      dump_gimple_asm (buffer, as_a <gasm *> (gs), spc, flags);
+      dump_gimple_asm (buffer, as_a <const gasm *> (gs), spc, flags);
       break;
 
     case GIMPLE_ASSIGN:
-      dump_gimple_assign (buffer, as_a <gassign *> (gs), spc, flags);
+      dump_gimple_assign (buffer, as_a <const gassign *> (gs), spc, flags);
       break;
 
     case GIMPLE_BIND:
-      dump_gimple_bind (buffer, as_a <gbind *> (gs), spc, flags);
+      dump_gimple_bind (buffer, as_a <const gbind *> (gs), spc, flags);
       break;
 
     case GIMPLE_CALL:
-      dump_gimple_call (buffer, as_a <gcall *> (gs), spc, flags);
+      dump_gimple_call (buffer, as_a <const gcall *> (gs), spc, flags);
       break;
 
     case GIMPLE_COND:
-      dump_gimple_cond (buffer, as_a <gcond *> (gs), spc, flags);
+      dump_gimple_cond (buffer, as_a <const gcond *> (gs), spc, flags);
       break;
 
     case GIMPLE_LABEL:
-      dump_gimple_label (buffer, as_a <glabel *> (gs), spc, flags);
+      dump_gimple_label (buffer, as_a <const glabel *> (gs), spc, flags);
       break;
 
     case GIMPLE_GOTO:
-      dump_gimple_goto (buffer, as_a <ggoto *> (gs), spc, flags);
+      dump_gimple_goto (buffer, as_a <const ggoto *> (gs), spc, flags);
       break;
 
     case GIMPLE_NOP:
@@ -2612,62 +2614,62 @@ pp_gimple_stmt_1 (pretty_printer *buffer, gimple *gs, int spc,
       break;
 
     case GIMPLE_RETURN:
-      dump_gimple_return (buffer, as_a <greturn *> (gs), spc, flags);
+      dump_gimple_return (buffer, as_a <const greturn *> (gs), spc, flags);
       break;
 
     case GIMPLE_SWITCH:
-      dump_gimple_switch (buffer, as_a <gswitch *> (gs), spc, flags);
+      dump_gimple_switch (buffer, as_a <const gswitch *> (gs), spc, flags);
       break;
 
     case GIMPLE_TRY:
-      dump_gimple_try (buffer, as_a <gtry *> (gs), spc, flags);
+      dump_gimple_try (buffer, as_a <const gtry *> (gs), spc, flags);
       break;
 
     case GIMPLE_PHI:
-      dump_gimple_phi (buffer, as_a <gphi *> (gs), spc, false, flags);
+      dump_gimple_phi (buffer, as_a <const gphi *> (gs), spc, false, flags);
       break;
 
     case GIMPLE_OMP_PARALLEL:
-      dump_gimple_omp_parallel (buffer, as_a <gomp_parallel *> (gs), spc,
+      dump_gimple_omp_parallel (buffer, as_a <const gomp_parallel *> (gs), spc,
 				flags);
       break;
 
     case GIMPLE_OMP_TASK:
-      dump_gimple_omp_task (buffer, as_a <gomp_task *> (gs), spc, flags);
+      dump_gimple_omp_task (buffer, as_a <const gomp_task *> (gs), spc, flags);
       break;
 
     case GIMPLE_OMP_ATOMIC_LOAD:
-      dump_gimple_omp_atomic_load (buffer, as_a <gomp_atomic_load *> (gs),
+      dump_gimple_omp_atomic_load (buffer, as_a <const gomp_atomic_load *> (gs),
 				   spc, flags);
       break;
 
     case GIMPLE_OMP_ATOMIC_STORE:
       dump_gimple_omp_atomic_store (buffer,
-				    as_a <gomp_atomic_store *> (gs),
+				    as_a <const gomp_atomic_store *> (gs),
 				    spc, flags);
       break;
 
     case GIMPLE_OMP_FOR:
-      dump_gimple_omp_for (buffer, as_a <gomp_for *> (gs), spc, flags);
+      dump_gimple_omp_for (buffer, as_a <const gomp_for *> (gs), spc, flags);
       break;
 
     case GIMPLE_OMP_CONTINUE:
-      dump_gimple_omp_continue (buffer, as_a <gomp_continue *> (gs), spc,
+      dump_gimple_omp_continue (buffer, as_a <const gomp_continue *> (gs), spc,
 				flags);
       break;
 
     case GIMPLE_OMP_SINGLE:
-      dump_gimple_omp_single (buffer, as_a <gomp_single *> (gs), spc,
+      dump_gimple_omp_single (buffer, as_a <const gomp_single *> (gs), spc,
 			      flags);
       break;
 
     case GIMPLE_OMP_TARGET:
-      dump_gimple_omp_target (buffer, as_a <gomp_target *> (gs), spc,
+      dump_gimple_omp_target (buffer, as_a <const gomp_target *> (gs), spc,
 			      flags);
       break;
 
     case GIMPLE_OMP_TEAMS:
-      dump_gimple_omp_teams (buffer, as_a <gomp_teams *> (gs), spc,
+      dump_gimple_omp_teams (buffer, as_a <const gomp_teams *> (gs), spc,
 			     flags);
       break;
 
@@ -2676,7 +2678,7 @@ pp_gimple_stmt_1 (pretty_printer *buffer, gimple *gs, int spc,
       break;
 
     case GIMPLE_OMP_SECTIONS:
-      dump_gimple_omp_sections (buffer, as_a <gomp_sections *> (gs),
+      dump_gimple_omp_sections (buffer, as_a <const gomp_sections *> (gs),
 				spc, flags);
       break;
 
@@ -2695,49 +2697,50 @@ pp_gimple_stmt_1 (pretty_printer *buffer, gimple *gs, int spc,
       break;
 
     case GIMPLE_OMP_ORDERED:
-      dump_gimple_omp_ordered (buffer, as_a <gomp_ordered *> (gs), spc,
+      dump_gimple_omp_ordered (buffer, as_a <const gomp_ordered *> (gs), spc,
 			       flags);
       break;
 
     case GIMPLE_OMP_SCAN:
-      dump_gimple_omp_scan (buffer, as_a <gomp_scan *> (gs), spc,
+      dump_gimple_omp_scan (buffer, as_a <const gomp_scan *> (gs), spc,
 			    flags);
       break;
 
     case GIMPLE_OMP_CRITICAL:
-      dump_gimple_omp_critical (buffer, as_a <gomp_critical *> (gs), spc,
+      dump_gimple_omp_critical (buffer, as_a <const gomp_critical *> (gs), spc,
 				flags);
       break;
 
     case GIMPLE_CATCH:
-      dump_gimple_catch (buffer, as_a <gcatch *> (gs), spc, flags);
+      dump_gimple_catch (buffer, as_a <const gcatch *> (gs), spc, flags);
       break;
 
     case GIMPLE_EH_FILTER:
-      dump_gimple_eh_filter (buffer, as_a <geh_filter *> (gs), spc, flags);
+      dump_gimple_eh_filter (buffer, as_a <const geh_filter *> (gs), spc,
+			     flags);
       break;
 
     case GIMPLE_EH_MUST_NOT_THROW:
       dump_gimple_eh_must_not_throw (buffer,
-				     as_a <geh_mnt *> (gs),
+				     as_a <const geh_mnt *> (gs),
 				     spc, flags);
       break;
 
     case GIMPLE_EH_ELSE:
-      dump_gimple_eh_else (buffer, as_a <geh_else *> (gs), spc, flags);
+      dump_gimple_eh_else (buffer, as_a <const geh_else *> (gs), spc, flags);
       break;
 
     case GIMPLE_RESX:
-      dump_gimple_resx (buffer, as_a <gresx *> (gs), spc, flags);
+      dump_gimple_resx (buffer, as_a <const gresx *> (gs), spc, flags);
       break;
 
     case GIMPLE_EH_DISPATCH:
-      dump_gimple_eh_dispatch (buffer, as_a <geh_dispatch *> (gs), spc,
+      dump_gimple_eh_dispatch (buffer, as_a <const geh_dispatch *> (gs), spc,
 			       flags);
       break;
 
     case GIMPLE_DEBUG:
-      dump_gimple_debug (buffer, as_a <gdebug *> (gs), spc, flags);
+      dump_gimple_debug (buffer, as_a <const gdebug *> (gs), spc, flags);
       break;
 
     case GIMPLE_PREDICT:
@@ -2751,7 +2754,7 @@ pp_gimple_stmt_1 (pretty_printer *buffer, gimple *gs, int spc,
       break;
 
     case GIMPLE_TRANSACTION:
-      dump_gimple_transaction (buffer, as_a <gtransaction *> (gs), spc,
+      dump_gimple_transaction (buffer, as_a <const gtransaction *> (gs), spc,
 			       flags);
       break;
 
diff --git a/gcc/gimple-pretty-print.h b/gcc/gimple-pretty-print.h
index 8d8e105..186f2cf 100644
--- a/gcc/gimple-pretty-print.h
+++ b/gcc/gimple-pretty-print.h
@@ -31,7 +31,8 @@ extern void print_gimple_stmt (FILE *, gimple *, int, dump_flags_t = TDF_NONE);
 extern void debug (gimple &ref);
 extern void debug (gimple *ptr);
 extern void print_gimple_expr (FILE *, gimple *, int, dump_flags_t = TDF_NONE);
-extern void pp_gimple_stmt_1 (pretty_printer *, gimple *, int, dump_flags_t);
+extern void pp_gimple_stmt_1 (pretty_printer *, const gimple *, int,
+			      dump_flags_t);
 extern void gimple_dump_bb (FILE *, basic_block, int, dump_flags_t);
 extern void gimple_dump_bb_for_graph (pretty_printer *, basic_block);
 extern void dump_ssaname_info_to_file (FILE *, tree, int);
diff --git a/gcc/gimple.h b/gcc/gimple.h
index cf1f8da..10a4b8b 100644
--- a/gcc/gimple.h
+++ b/gcc/gimple.h
@@ -1017,6 +1017,14 @@ is_a_helper <gdebug *>::test (gimple *gs)
 template <>
 template <>
 inline bool
+is_a_helper <const gdebug *>::test (const gimple *gs)
+{
+  return gs->code == GIMPLE_DEBUG;
+}
+
+template <>
+template <>
+inline bool
 is_a_helper <ggoto *>::test (gimple *gs)
 {
   return gs->code == GIMPLE_GOTO;
@@ -1025,6 +1033,14 @@ is_a_helper <ggoto *>::test (gimple *gs)
 template <>
 template <>
 inline bool
+is_a_helper <const ggoto *>::test (const gimple *gs)
+{
+  return gs->code == GIMPLE_GOTO;
+}
+
+template <>
+template <>
+inline bool
 is_a_helper <glabel *>::test (gimple *gs)
 {
   return gs->code == GIMPLE_LABEL;
@@ -1033,6 +1049,14 @@ is_a_helper <glabel *>::test (gimple *gs)
 template <>
 template <>
 inline bool
+is_a_helper <const glabel *>::test (const gimple *gs)
+{
+  return gs->code == GIMPLE_LABEL;
+}
+
+template <>
+template <>
+inline bool
 is_a_helper <gresx *>::test (gimple *gs)
 {
   return gs->code == GIMPLE_RESX;
@@ -1057,6 +1081,14 @@ is_a_helper <geh_else *>::test (gimple *gs)
 template <>
 template <>
 inline bool
+is_a_helper <const geh_else *>::test (const gimple *gs)
+{
+  return gs->code == GIMPLE_EH_ELSE;
+}
+
+template <>
+template <>
+inline bool
 is_a_helper <geh_filter *>::test (gimple *gs)
 {
   return gs->code == GIMPLE_EH_FILTER;
@@ -1073,6 +1105,14 @@ is_a_helper <geh_mnt *>::test (gimple *gs)
 template <>
 template <>
 inline bool
+is_a_helper <const geh_mnt *>::test (const gimple *gs)
+{
+  return gs->code == GIMPLE_EH_MUST_NOT_THROW;
+}
+
+template <>
+template <>
+inline bool
 is_a_helper <gomp_atomic_load *>::test (gimple *gs)
 {
   return gs->code == GIMPLE_OMP_ATOMIC_LOAD;
@@ -1219,6 +1259,14 @@ is_a_helper <gswitch *>::test (gimple *gs)
 template <>
 template <>
 inline bool
+is_a_helper <const gswitch *>::test (const gimple *gs)
+{
+  return gs->code == GIMPLE_SWITCH;
+}
+
+template <>
+template <>
+inline bool
 is_a_helper <gtransaction *>::test (gimple *gs)
 {
   return gs->code == GIMPLE_TRANSACTION;
@@ -1235,6 +1283,14 @@ is_a_helper <gtry *>::test (gimple *gs)
 template <>
 template <>
 inline bool
+is_a_helper <const gtry *>::test (const gimple *gs)
+{
+  return gs->code == GIMPLE_TRY;
+}
+
+template <>
+template <>
+inline bool
 is_a_helper <gimple_statement_wce *>::test (gimple *gs)
 {
   return gs->code == GIMPLE_WITH_CLEANUP_EXPR;
@@ -1429,6 +1485,14 @@ is_a_helper <const gphi *>::test (const gimple *gs)
 template <>
 template <>
 inline bool
+is_a_helper <const greturn *>::test (const gimple *gs)
+{
+  return gs->code == GIMPLE_RETURN;
+}
+
+template <>
+template <>
+inline bool
 is_a_helper <const gtransaction *>::test (const gimple *gs)
 {
   return gs->code == GIMPLE_TRANSACTION;
@@ -3254,7 +3318,7 @@ gimple_call_set_tail (gcall *s, bool tail_p)
 /* Return true if GIMPLE_CALL S is marked as a tail call.  */
 
 static inline bool
-gimple_call_tail_p (gcall *s)
+gimple_call_tail_p (const gcall *s)
 {
   return (s->subcode & GF_CALL_TAILCALL) != 0;
 }
@@ -3296,7 +3360,7 @@ gimple_call_set_return_slot_opt (gcall *s, bool return_slot_opt_p)
 /* Return true if S is marked for return slot optimization.  */
 
 static inline bool
-gimple_call_return_slot_opt_p (gcall *s)
+gimple_call_return_slot_opt_p (const gcall *s)
 {
   return (s->subcode & GF_CALL_RETURN_SLOT_OPT) != 0;
 }
@@ -3341,7 +3405,7 @@ gimple_call_set_va_arg_pack (gcall *s, bool pass_arg_pack_p)
    argument pack in its argument list.  */
 
 static inline bool
-gimple_call_va_arg_pack_p (gcall *s)
+gimple_call_va_arg_pack_p (const gcall *s)
 {
   return (s->subcode & GF_CALL_VA_ARG_PACK) != 0;
 }
@@ -3443,6 +3507,13 @@ gimple_call_use_set (gcall *call_stmt)
   return &call_stmt->call_used;
 }
 
+/* As above, but const.  */
+
+static inline const pt_solution *
+gimple_call_use_set (const gcall *call_stmt)
+{
+  return &call_stmt->call_used;
+}
 
 /* Return a pointer to the points-to solution for the set of call-used
    variables of the call CALL_STMT.  */
@@ -3453,16 +3524,24 @@ gimple_call_clobber_set (gcall *call_stmt)
   return &call_stmt->call_clobbered;
 }
 
+/* As above, but const.  */
+
+static inline const pt_solution *
+gimple_call_clobber_set (const gcall *call_stmt)
+{
+  return &call_stmt->call_clobbered;
+}
+
 
 /* Returns true if this is a GIMPLE_ASSIGN or a GIMPLE_CALL with a
    non-NULL lhs.  */
 
 static inline bool
-gimple_has_lhs (gimple *stmt)
+gimple_has_lhs (const gimple *stmt)
 {
   if (is_gimple_assign (stmt))
     return true;
-  if (gcall *call = dyn_cast <gcall *> (stmt))
+  if (const gcall *call = dyn_cast <const gcall *> (stmt))
     return gimple_call_lhs (call) != NULL_TREE;
   return false;
 }
@@ -3760,9 +3839,9 @@ gimple_bind_body_ptr (gbind *bind_stmt)
 /* Return the GIMPLE sequence contained in the GIMPLE_BIND statement GS.  */
 
 static inline gimple_seq
-gimple_bind_body (gbind *gs)
+gimple_bind_body (const gbind *gs)
 {
-  return *gimple_bind_body_ptr (gs);
+  return *gimple_bind_body_ptr (const_cast <gbind *> (gs));
 }
 
 
@@ -4034,9 +4113,9 @@ gimple_catch_handler_ptr (gcatch *catch_stmt)
    GIMPLE_CATCH statement CATCH_STMT.  */
 
 static inline gimple_seq
-gimple_catch_handler (gcatch *catch_stmt)
+gimple_catch_handler (const gcatch *catch_stmt)
 {
-  return *gimple_catch_handler_ptr (catch_stmt);
+  return *gimple_catch_handler_ptr (const_cast <gcatch *> (catch_stmt));
 }
 
 
@@ -4094,9 +4173,9 @@ gimple_eh_filter_failure_ptr (gimple *gs)
    statement fails.  */
 
 static inline gimple_seq
-gimple_eh_filter_failure (gimple *gs)
+gimple_eh_filter_failure (const gimple *gs)
 {
-  return *gimple_eh_filter_failure_ptr (gs);
+  return *gimple_eh_filter_failure_ptr (const_cast <gimple *> (gs));
 }
 
 
@@ -4123,7 +4202,7 @@ gimple_eh_filter_set_failure (geh_filter *eh_filter_stmt,
 /* Get the function decl to be called by the MUST_NOT_THROW region.  */
 
 static inline tree
-gimple_eh_must_not_throw_fndecl (geh_mnt *eh_mnt_stmt)
+gimple_eh_must_not_throw_fndecl (const geh_mnt *eh_mnt_stmt)
 {
   return eh_mnt_stmt->fndecl;
 }
@@ -4146,9 +4225,9 @@ gimple_eh_else_n_body_ptr (geh_else *eh_else_stmt)
 }
 
 static inline gimple_seq
-gimple_eh_else_n_body (geh_else *eh_else_stmt)
+gimple_eh_else_n_body (const geh_else *eh_else_stmt)
 {
-  return *gimple_eh_else_n_body_ptr (eh_else_stmt);
+  return *gimple_eh_else_n_body_ptr (const_cast <geh_else *> (eh_else_stmt));
 }
 
 static inline gimple_seq *
@@ -4158,9 +4237,9 @@ gimple_eh_else_e_body_ptr (geh_else *eh_else_stmt)
 }
 
 static inline gimple_seq
-gimple_eh_else_e_body (geh_else *eh_else_stmt)
+gimple_eh_else_e_body (const geh_else *eh_else_stmt)
 {
-  return *gimple_eh_else_e_body_ptr (eh_else_stmt);
+  return *gimple_eh_else_e_body_ptr (const_cast <geh_else *> (eh_else_stmt));
 }
 
 static inline void
@@ -4224,9 +4303,9 @@ gimple_try_eval_ptr (gimple *gs)
 /* Return the sequence of statements used as the body for GIMPLE_TRY GS.  */
 
 static inline gimple_seq
-gimple_try_eval (gimple *gs)
+gimple_try_eval (const gimple *gs)
 {
-  return *gimple_try_eval_ptr (gs);
+  return *gimple_try_eval_ptr (const_cast <gimple *> (gs));
 }
 
 
@@ -4245,9 +4324,9 @@ gimple_try_cleanup_ptr (gimple *gs)
    GIMPLE_TRY GS.  */
 
 static inline gimple_seq
-gimple_try_cleanup (gimple *gs)
+gimple_try_cleanup (const gimple *gs)
 {
-  return *gimple_try_cleanup_ptr (gs);
+  return *gimple_try_cleanup_ptr (const_cast <gimple *> (gs));
 }
 
 
@@ -4406,6 +4485,13 @@ gimple_phi_arg (gphi *gs, unsigned index)
   return &(gs->args[index]);
 }
 
+static inline const phi_arg_d *
+gimple_phi_arg (const gphi *gs, unsigned index)
+{
+  gcc_gimple_checking_assert (index < gs->nargs);
+  return &(gs->args[index]);
+}
+
 static inline struct phi_arg_d *
 gimple_phi_arg (gimple *gs, unsigned index)
 {
@@ -4445,7 +4531,7 @@ phi_nodes_ptr (basic_block bb)
 /* Return the tree operand for argument I of PHI node GS.  */
 
 static inline tree
-gimple_phi_arg_def (gphi *gs, size_t index)
+gimple_phi_arg_def (const gphi *gs, size_t index)
 {
   return gimple_phi_arg (gs, index)->def;
 }
@@ -4468,7 +4554,7 @@ gimple_phi_arg_def_ptr (gphi *phi, size_t index)
 /* Return the edge associated with argument I of phi node PHI.  */
 
 static inline edge
-gimple_phi_arg_edge (gphi *phi, size_t i)
+gimple_phi_arg_edge (const gphi *phi, size_t i)
 {
   return EDGE_PRED (gimple_bb (phi), i);
 }
@@ -4476,7 +4562,7 @@ gimple_phi_arg_edge (gphi *phi, size_t i)
 /* Return the source location of gimple argument I of phi node PHI.  */
 
 static inline location_t
-gimple_phi_arg_location (gphi *phi, size_t i)
+gimple_phi_arg_location (const gphi *phi, size_t i)
 {
   return gimple_phi_arg (phi, i)->locus;
 }
@@ -4500,7 +4586,7 @@ gimple_phi_arg_set_location (gphi *phi, size_t i, location_t loc)
 /* Return TRUE if argument I of phi node PHI has a location record.  */
 
 static inline bool
-gimple_phi_arg_has_location (gphi *phi, size_t i)
+gimple_phi_arg_has_location (const gphi *phi, size_t i)
 {
   return gimple_phi_arg_location (phi, i) != UNKNOWN_LOCATION;
 }
@@ -4668,7 +4754,7 @@ gimple_debug_bind_p (const gimple *s)
 /* Return the variable bound in a GIMPLE_DEBUG bind statement.  */
 
 static inline tree
-gimple_debug_bind_get_var (gimple *dbg)
+gimple_debug_bind_get_var (const gimple *dbg)
 {
   GIMPLE_CHECK (dbg, GIMPLE_DEBUG);
   gcc_gimple_checking_assert (gimple_debug_bind_p (dbg));
@@ -4679,7 +4765,7 @@ gimple_debug_bind_get_var (gimple *dbg)
    statement.  */
 
 static inline tree
-gimple_debug_bind_get_value (gimple *dbg)
+gimple_debug_bind_get_value (const gimple *dbg)
 {
   GIMPLE_CHECK (dbg, GIMPLE_DEBUG);
   gcc_gimple_checking_assert (gimple_debug_bind_p (dbg));
@@ -4760,7 +4846,7 @@ gimple_debug_source_bind_p (const gimple *s)
 /* Return the variable bound in a GIMPLE_DEBUG source bind statement.  */
 
 static inline tree
-gimple_debug_source_bind_get_var (gimple *dbg)
+gimple_debug_source_bind_get_var (const gimple *dbg)
 {
   GIMPLE_CHECK (dbg, GIMPLE_DEBUG);
   gcc_gimple_checking_assert (gimple_debug_source_bind_p (dbg));
@@ -4771,7 +4857,7 @@ gimple_debug_source_bind_get_var (gimple *dbg)
    statement.  */
 
 static inline tree
-gimple_debug_source_bind_get_value (gimple *dbg)
+gimple_debug_source_bind_get_value (const gimple *dbg)
 {
   GIMPLE_CHECK (dbg, GIMPLE_DEBUG);
   gcc_gimple_checking_assert (gimple_debug_source_bind_p (dbg));
@@ -4872,9 +4958,9 @@ gimple_omp_body_ptr (gimple *gs)
 /* Return the body for the OMP statement GS.  */
 
 static inline gimple_seq
-gimple_omp_body (gimple *gs)
+gimple_omp_body (const gimple *gs)
 {
-  return *gimple_omp_body_ptr (gs);
+  return *gimple_omp_body_ptr (const_cast <gimple *> (gs));
 }
 
 /* Set BODY to be the body for the OMP statement GS.  */
@@ -5139,9 +5225,9 @@ gimple_omp_for_set_clauses (gimple *gs, tree clauses)
 /* Get the collapse count of the OMP_FOR statement GS.  */
 
 static inline size_t
-gimple_omp_for_collapse (gimple *gs)
+gimple_omp_for_collapse (const gimple *gs)
 {
-  gomp_for *omp_for_stmt = as_a <gomp_for *> (gs);
+  const gomp_for *omp_for_stmt = as_a <const gomp_for *> (gs);
   return omp_for_stmt->collapse;
 }
 
@@ -5316,9 +5402,9 @@ gimple_omp_for_pre_body_ptr (gimple *gs)
    statement GS starts.  */
 
 static inline gimple_seq
-gimple_omp_for_pre_body (gimple *gs)
+gimple_omp_for_pre_body (const gimple *gs)
 {
-  return *gimple_omp_for_pre_body_ptr (gs);
+  return *gimple_omp_for_pre_body_ptr (const_cast <gimple *> (gs));
 }
 
 
@@ -6303,7 +6389,7 @@ gimple_transaction_body_ptr (gtransaction *transaction_stmt)
 /* Return the body for the GIMPLE_TRANSACTION statement TRANSACTION_STMT.  */
 
 static inline gimple_seq
-gimple_transaction_body (gtransaction *transaction_stmt)
+gimple_transaction_body (const gtransaction *transaction_stmt)
 {
   return transaction_stmt->body;
 }
diff --git a/gcc/tree-eh.c b/gcc/tree-eh.c
index 54502e6..b996f49 100644
--- a/gcc/tree-eh.c
+++ b/gcc/tree-eh.c
@@ -139,19 +139,19 @@ remove_stmt_from_eh_lp (gimple *t)
    statement is not recorded in the region table.  */
 
 int
-lookup_stmt_eh_lp_fn (struct function *ifun, gimple *t)
+lookup_stmt_eh_lp_fn (struct function *ifun, const gimple *t)
 {
   if (ifun->eh->throw_stmt_table == NULL)
     return 0;
 
-  int *lp_nr = ifun->eh->throw_stmt_table->get (t);
+  int *lp_nr = ifun->eh->throw_stmt_table->get (const_cast <gimple *> (t));
   return lp_nr ? *lp_nr : 0;
 }
 
 /* Likewise, but always use the current function.  */
 
 int
-lookup_stmt_eh_lp (gimple *t)
+lookup_stmt_eh_lp (const gimple *t)
 {
   /* We can get called from initialized data when -fnon-call-exceptions
      is on; prevent crash.  */
diff --git a/gcc/tree-eh.h b/gcc/tree-eh.h
index 511bb84..ca3b521 100644
--- a/gcc/tree-eh.h
+++ b/gcc/tree-eh.h
@@ -27,8 +27,8 @@ extern void using_eh_for_cleanups (void);
 extern void add_stmt_to_eh_lp (gimple *, int);
 extern bool remove_stmt_from_eh_lp_fn (struct function *, gimple *);
 extern bool remove_stmt_from_eh_lp (gimple *);
-extern int lookup_stmt_eh_lp_fn (struct function *, gimple *);
-extern int lookup_stmt_eh_lp (gimple *);
+extern int lookup_stmt_eh_lp_fn (struct function *, const gimple *);
+extern int lookup_stmt_eh_lp (const gimple *);
 extern bool make_eh_dispatch_edges (geh_dispatch *);
 extern void make_eh_edges (gimple *);
 extern edge redirect_eh_edge (edge, basic_block);
diff --git a/gcc/tree-ssa-alias.h b/gcc/tree-ssa-alias.h
index 6d72c2f..de85312 100644
--- a/gcc/tree-ssa-alias.h
+++ b/gcc/tree-ssa-alias.h
@@ -162,7 +162,7 @@ extern void dump_alias_stats (FILE *);
 
 /* In tree-ssa-structalias.c  */
 extern unsigned int compute_may_aliases (void);
-extern bool pt_solution_empty_p (struct pt_solution *);
+extern bool pt_solution_empty_p (const pt_solution *);
 extern bool pt_solution_singleton_or_null_p (struct pt_solution *, unsigned *);
 extern bool pt_solution_includes_global (struct pt_solution *);
 extern bool pt_solution_includes (struct pt_solution *, const_tree);
diff --git a/gcc/tree-ssa-structalias.c b/gcc/tree-ssa-structalias.c
index 75c6fae..b248dfa 100644
--- a/gcc/tree-ssa-structalias.c
+++ b/gcc/tree-ssa-structalias.c
@@ -6801,7 +6801,7 @@ pt_solution_ior_into (struct pt_solution *dest, struct pt_solution *src)
 /* Return true if the points-to solution *PT is empty.  */
 
 bool
-pt_solution_empty_p (struct pt_solution *pt)
+pt_solution_empty_p (const pt_solution *pt)
 {
   if (pt->anything
       || pt->nonlocal)
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 02/49] analyzer: internal documentation
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
  2019-11-16  1:17 ` [PATCH 08/49] Introduce pretty_printer::clone vfunc David Malcolm
  2019-11-16  1:17 ` [PATCH 11/49] Add diagnostic_metadata and CWE support David Malcolm
@ 2019-11-16  1:17 ` David Malcolm
  2019-11-16  1:17 ` [PATCH 14/49] hash-map-tests.c: add a selftest involving int_hash David Malcolm
                   ` (47 subsequent siblings)
  50 siblings, 0 replies; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:17 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

gcc/ChangeLog:
	* Makefile.in (TEXI_GCCINT_FILES): Add analyzer.texi.
	* doc/analyzer.texi: New file.
	* doc/gccint.texi ("Static Analyzer") New menu item.
	(analyzer.texi): Include it.
---
 gcc/Makefile.in       |   2 +-
 gcc/doc/analyzer.texi | 470 ++++++++++++++++++++++++++++++++++++++++++++++++++
 gcc/doc/gccint.texi   |   2 +
 3 files changed, 473 insertions(+), 1 deletion(-)
 create mode 100644 gcc/doc/analyzer.texi

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index c82858f..53d74e2 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -3182,7 +3182,7 @@ TEXI_GCCINT_FILES = gccint.texi gcc-common.texi gcc-vers.texi		\
 	 gnu.texi gpl_v3.texi fdl.texi contrib.texi languages.texi	\
 	 sourcebuild.texi gty.texi libgcc.texi cfg.texi tree-ssa.texi	\
 	 loop.texi generic.texi gimple.texi plugins.texi optinfo.texi   \
-	 match-and-simplify.texi ux.texi poly-int.texi
+	 match-and-simplify.texi analyzer.texi ux.texi poly-int.texi
 
 TEXI_GCCINSTALL_FILES = install.texi install-old.texi fdl.texi		\
 	 gcc-common.texi gcc-vers.texi
diff --git a/gcc/doc/analyzer.texi b/gcc/doc/analyzer.texi
new file mode 100644
index 0000000..8e7b26f
--- /dev/null
+++ b/gcc/doc/analyzer.texi
@@ -0,0 +1,470 @@
+@c Copyright (C) 2019 Free Software Foundation, Inc.
+@c This is part of the GCC manual.
+@c For copying conditions, see the file gcc.texi.
+@c Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+@node Static Analyzer
+@chapter Static Analyzer
+@cindex analyzer
+@cindex static analysis
+@cindex static analyzer
+
+@menu
+* Analyzer Internals::       Analyzer Internals
+* Debugging the Analyzer::   Useful debugging tips
+@end menu
+
+@node Analyzer Internals
+@section Analyzer Internals
+@cindex analyzer, internals
+@cindex static analyzer, internals
+
+@subsection Overview
+
+The analyzer implementation works on the gimple-SSA representation.
+(I chose this in the hopes of making it easy to work with LTO to
+do whole-program analysis).
+
+The implementation is read-only: it doesn't attempt to change anything,
+just emit warnings.
+
+First, we build a @code{supergraph} which combines the callgraph and all
+of the CFGs into a single directed graph, with both interprocedural and
+intraprocedural edges.  The nodes and edges in the supergraph are called
+``supernodes'' and ``superedges'', and often referred to in code as
+@code{snodes} and @code{sedges}.  Basic blocks in the CFGs are split at
+interprocedural calls, so there can be more than one supernode per
+basic block.  Most statements will be in just one supernode, but a call
+statement can appear in two supernodes: at the end of one for the call,
+and again at the start of another for the return.
+
+The supergraph can be seen using @option{-fdump-analyzer-supergraph}.
+
+We then build an @code{analysis_plan} which walks the callgraph to
+determine which calls might be suitable for being summarized (rather
+than fully explored) and thus in what order to explore the functions.
+
+Next is the heart of the analyzer: we use a worklist to explore state
+within the supergraph, building an "exploded graph".
+Nodes in the exploded graph correspond to <point,@w{ }state> pairs, as in
+     "Precise Interprocedural Dataflow Analysis via Graph Reachability"
+     (Thomas Reps, Susan Horwitz and Mooly Sagiv).
+
+We reuse nodes for <point, state> pairs we've already seen, and avoid
+tracking state too closely, so that (hopefully) we rapidly converge
+on a final exploded graph, and terminate the analysis.  We also bail
+out if the number of exploded <end-of-basic-block, state> nodes gets
+larger than a particular multiple of the total number of basic blocks
+(to ensure termination in the face of pathological state-explosion
+cases, or bugs).  We also stop exploring a point once we hit a limit
+of states for that point.
+
+We can identify problems directly when processing a <point,@w{ }state>
+instance.  For example, if we're finding the successors of
+
+@smallexample
+   <point: before-stmt: "free (ptr);",
+    state: @{"ptr": freed@}>
+@end smallexample
+
+then we can detect a double-free of "ptr".  We can then emit a path
+to reach the problem by finding the simplest route through the graph.
+
+Program points in the analysis are much more fine-grained than in the
+CFG and supergraph, with points (and thus potentially exploded nodes)
+for various events, including before individual statements.
+By default the exploded graph merges multiple consecutive statements
+in a supernode into one exploded edge to minimize the size of the
+exploded graph.  This can be suppressed via
+@option{-fanalyzer-fine-grained}.
+The fine-grained approach seems to make things simpler and more debuggable
+that other approaches I tried, in that each point is responsible for one
+thing.
+
+Program points in the analysis also have a "call string" identifying the
+stack of callsites below them, so that paths in the exploded graph
+correspond to interprocedurally valid paths: we always return to the
+correct call site, propagating state information accordingly.
+We avoid infinite recursion by stopping the analysis if a callsite
+appears more than @code{analyzer-max-recursion-depth} in a callstring
+(defaulting to 2).
+
+@subsection Graphs
+
+Nodes and edges in the exploded graph are called ``exploded nodes'' and
+``exploded edges'' and often referred to in the code as
+@code{enodes} and @code{eedges} (especially when distinguishing them
+from the @code{snodes} and @code{sedges} in the supergraph).
+
+Each graph numbers its nodes, giving unique identifiers - supernodes
+are referred to throughout dumps in the form @samp{SN': @var{index}} and
+exploded nodes in the form @samp{EN: @var{index}} (e.g. @samp{SN: 2} and
+@samp{EN:29}).
+
+The supergraph can be seen using @option{-fdump-analyzer-supergraph-graph}.
+
+The exploded graph can be seen using @option{-fdump-analyzer-exploded-graph}
+and other dump options.  Exploded nodes are color-coded in the .dot output
+based on state-machine states to make it easier to see state changes at
+a glance.
+
+@subsection State Tracking
+
+There's a tension between:
+@itemize @bullet
+@item
+precision of analysis in the straight-line case, vs
+@item
+exponential blow-up in the face of control flow.
+@end itemize
+
+For example, in general, given this CFG:
+
+@smallexample
+      A
+     / \
+    B   C
+     \ /
+      D
+     / \
+    E   F
+     \ /
+      G
+@end smallexample
+
+we want to avoid differences in state-tracking in B and C from
+leading to blow-up.  If we don't prevent state blowup, we end up
+with exponential growth of the exploded graph like this:
+
+@smallexample
+
+           1:A
+          /   \
+         /     \
+        /       \
+      2:B       3:C
+       |         |
+      4:D       5:D        (2 exploded nodes for D)
+     /   \     /   \
+   6:E   7:F 8:E   9:F
+    |     |   |     |
+   10:G 11:G 12:G  13:G    (4 exploded nodes for G)
+
+@end smallexample
+
+Similar issues arise with loops.
+
+To prevent this, we follow various approaches:
+
+@enumerate a
+@item
+state pruning: which tries to discard state that won't be relevant
+later on withing the function.
+This can be disabled via @option{-fno-analyzer-state-purge}.
+
+@item
+state merging.  We can try to find the commonality between two
+program_state instances to make a third, simpler program_state.
+We have two strategies here:
+
+  @enumerate
+  @item
+     the worklist keeps new nodes for the same program_point together,
+     and tries to merge them before processing, and thus before they have
+     successors.  Hence, in the above, the two nodes for D (4 and 5) reach
+     the front of the worklist together, and we create a node for D with
+     the merger of the incoming states.
+
+  @item
+     try merging with the state of existing enodes for the program_point
+     (which may have already been explored).  There will be duplication,
+     but only one set of duplication; subsequent duplicates are more likely
+     to hit the cache.  In particular, (hopefully) all merger chains are
+     finite, and so we guarantee termination.
+     This is intended to help with loops: we ought to explore the first
+     iteration, and then have a "subsequent iterations" exploration,
+     which uses a state merged from that of the first, to be more abstract.
+  @end enumerate
+
+We avoid merging pairs of states that have state-machine differences,
+as these are the kinds of differences that are likely to be most
+interesting.  So, for example, given:
+
+@smallexample
+      if (condition)
+        ptr = malloc (size);
+      else
+        ptr = local_buf;
+
+      .... do things with 'ptr'
+
+      if (condition)
+        free (ptr);
+
+      ...etc
+@end smallexample
+
+then we end up with an exploded graph that looks like this:
+
+@smallexample
+
+                   if (condition)
+                     / T      \ F
+            ---------          ----------
+           /                             \
+      ptr = malloc (size)             ptr = local_buf
+          |                               |
+      copy of                         copy of
+        "do things with 'ptr'"          "do things with 'ptr'"
+      with ptr: heap-allocated        with ptr: stack-allocated
+          |                               |
+      if (condition)                  if (condition)
+          | known to be T                 | known to be F
+      free (ptr);                         |
+           \                             /
+            -----------------------------
+                         | ('ptr' is pruned, so states can be merged)
+                        etc
+
+@end smallexample
+
+where some duplication has occurred, but only for the places where the
+the different paths are worth exploringly separately.
+
+Merging can be disabled via @option{-fno-analyzer-state-merge}.
+@end enumerate
+
+@subsection Region Model
+
+Part of the state stored at a @code{exploded_node} is a @code{region_model}.
+This is an implementation of the region-based ternary model described in
+@url{http://lcs.ios.ac.cn/~xuzb/canalyze/memmodel.pdf,
+"A Memory Model for Static Analysis of C Programs"}
+(Zhongxing Xu, Ted Kremenek, and Jian Zhang).
+
+A @code{region_model} encapsulates a representation of the state of
+memory, with a tree of @code{region} instances, along with their associated
+values.  The representation is graph-like because values can be pointers
+to regions.  It also stores a constraint_manager, capturing relationships
+between the values.
+
+Because each node in the @code{exploded_graph} has a @code{region_model},
+and each of the latter is graph-like, the @code{exploded_graph} is in some
+ways a graph of graphs.
+
+Here's an example of printing a @code{region_model}, showing the ASCII-art
+used to visualize the region hierarchy (colorized when printing to stderr):
+
+@smallexample
+(gdb) call debug (*this)
+r0: @{kind: 'root', parent: null, sval: null@}
+|-stack: r1: @{kind: 'stack', parent: r0, sval: sv1@}
+|  |: sval: sv1: @{poisoned: uninit@}
+|  |-frame for 'test': r2: @{kind: 'frame', parent: r1, sval: null, map: @{'ptr_3': r3@}, function: 'test', depth: 0@}
+|  |  `-'ptr_3': r3: @{kind: 'map', parent: r2, sval: sv3, type: 'void *', map: @{@}@}
+|  |    |: sval: sv3: @{type: 'void *', unknown@}
+|  |    |: type: 'void *'
+|  `-frame for 'calls_malloc': r4: @{kind: 'frame', parent: r1, sval: null, map: @{'result_3': r7, '_4': r8, '<anonymous>': r5@}, function: 'calls_malloc', depth: 1@}
+|    |-'<anonymous>': r5: @{kind: 'map', parent: r4, sval: sv4, type: 'void *', map: @{@}@}
+|    |  |: sval: sv4: @{type: 'void *', &r6@}
+|    |  |: type: 'void *'
+|    |-'result_3': r7: @{kind: 'map', parent: r4, sval: sv4, type: 'void *', map: @{@}@}
+|    |  |: sval: sv4: @{type: 'void *', &r6@}
+|    |  |: type: 'void *'
+|    `-'_4': r8: @{kind: 'map', parent: r4, sval: sv4, type: 'void *', map: @{@}@}
+|      |: sval: sv4: @{type: 'void *', &r6@}
+|      |: type: 'void *'
+`-heap: r9: @{kind: 'heap', parent: r0, sval: sv2@}
+  |: sval: sv2: @{poisoned: uninit@}
+  `-r6: @{kind: 'symbolic', parent: r9, sval: null, map: @{@}@}
+svalues:
+  sv0: @{type: 'size_t', '1024'@}
+  sv1: @{poisoned: uninit@}
+  sv2: @{poisoned: uninit@}
+  sv3: @{type: 'void *', unknown@}
+  sv4: @{type: 'void *', &r6@}
+constraint manager:
+  equiv classes:
+    ec0: @{sv0 == '1024'@}
+    ec1: @{sv4@}
+  constraints:
+@end smallexample
+
+This is the state at the point of returning from @code{calls_malloc} back
+to @code{test} in the following:
+
+@smallexample
+void *
+calls_malloc (void)
+@{
+  void *result = malloc (1024);
+  return result;
+@}
+
+void test (void)
+@{
+  void *ptr = calls_malloc ();
+  /* etc.  */
+@}
+@end smallexample
+
+The ``root'' region (``r0'') has a ``stack'' child (``r1''), with two
+children: a frame for @code{test} (``r2''), and a frame for
+@code{calls_malloc} (``r4'').  These frame regions have child regions for
+storing their local variables.  For example, the return region
+and that of various other regions within the ``calls_malloc'' frame all have
+value ``sv4'', a pointer to a heap-allocated region ``r6''.  Within the parent
+frame, @code{ptr_3} has value ``sv3'', an unknown @code{void *}.
+
+@subsection Analyzer Paths
+
+We need to explain to the user what the problem is, and to persuade them
+that there really is a problem.  Hence having a @code{diagnostic_path}
+isn't just an incidental detail of the analyzer; it's required.
+
+Paths ought to be:
+@itemize @bullet
+@item
+interprocedurally-valid
+@item
+feasible
+@end itemize
+
+Without state-merging, all paths in the exploded graph are feasible
+(in terms of constraints being satisified).
+With state-merging, paths in the exploded graph can be infeasible.
+
+We collate warnings and only emit them for the simplest path
+e.g. for a bug in a utility function, with lots of routes to calling it,
+we only emit the simplest path (which could be intraprocedural, if
+it can be reproduced without a caller).  We apply a check that
+each duplicate warning's shortest path is feasible, rejecting any
+warnings for which the shortest path is infeasible (which could lead to
+false negatives).
+
+We use the shortest feasible path through the exploded_graph (a list of
+edges) to build a @code{diagnostic_path} (a list of events for the
+diagnostic subsystem).
+
+@subsection Limitations
+
+@itemize @bullet
+@item
+Only for C so far
+@item
+The implementation of call summaries is currently very simplistic.
+@item
+Lack of function pointer analysis
+@item
+The region model code creates lots of little mutable objects at each
+@code{region_model} (and thus per @code{exploded_node}) rather than
+sharing immutable objects and having the mutable state in the
+@code{program_state} or @code{region_model}.  The latter approach might be
+more efficient, and might avoid dealing with IDs rather than pointers
+(which requires us to impose an ordering to get meaningful equality).
+@item
+The region model code doesn't yet support @code{memcpy}.  At the
+gimple-ssa level these have been optimized to statements like this:
+@smallexample
+_10 = MEM <long unsigned int> [(char * @{ref-all@})&c]
+MEM <long unsigned int> [(char * @{ref-all@})&d] = _10;
+@end smallexample
+Perhaps they could be supported via a new @code{compound_svalue} type.
+@item
+There are various other limitations in the region model (grep for TODO/xfail
+in the testsuite).
+@item
+The constraint_manager's implementation of transitivity is currently too
+expensive to enable by default and so must be manually enabled via
+@option{-fanalyzer-transitivity}).
+@item
+The checkers are currently hardcoded and don't allow for user extensibility
+(e.g. adding allocate/release pairs).
+@item
+Although the analyzer's test suite has a proof-of-concept test case for
+LTO, it will crash on anything non-trivial.  There are various
+lang-specific things in the analyzer that assume C rather than LTO.
+For example, casts are currently implemented via @code{convert}, and this
+leads to an ICE on LTO.  Similarly, SSA names are printed to the user in
+``raw'' form, rather than printing the underlying variable name.
+@end itemize
+
+Some ideas for other checkers
+@itemize @bullet
+@item
+File-descriptor-based APIs
+@item
+Linux kernel internal APIs
+@item
+Signal handling
+@end itemize
+
+@node Debugging the Analyzer
+@section Debugging the Analyzer
+@cindex analyzer, debugging
+@cindex static analyzer, debugging
+
+@subsection Builtins for Debugging the Analyzer
+
+Add:
+@smallexample
+  __analyzer_break ();
+@end smallexample
+to the source being analyzed to trigger a breakpoint in the analyzer when
+that source is reached.  By putting a series of these in the source, it's
+much easier to effectively step through the program state as it's analyzed.
+
+@smallexample
+__analyzer_dump ();
+@end smallexample
+
+will dump the copious information about the analyzer's state each time it
+reaches the call in its traversal of the source.
+
+@smallexample
+__analyzer_dump_path ();
+@end smallexample
+
+will emit a placeholder ``note'' diagnostic with a path to that call site,
+if the analyzer finds a feasible path to it.
+
+The builtin @code{__analyzer_dump_exploded_nodes} will dump information
+after analysis on all of the exploded nodes at that program point:
+
+@smallexample
+  __analyzer_dump_exploded_nodes (0);
+@end smallexample
+
+will dump just the number of nodes, and their IDs.
+
+@smallexample
+  __analyzer_dump_exploded_nodes (1);
+@end smallexample
+
+will also dump all of the states within those nodes.
+
+@code{region_model::get_value_by_name} can be used when inserting custom
+debugging code (e.g. adding it @code{region_model::validate} to detect the
+point at which a named variable acquires an unexpected state).
+
+@subsection Other Debugging Techniques
+
+One approach when tracking down where a particular bogus state is
+introduced into the @code{exploded_graph} is to add custom code to
+@code{region_model::validate}.
+
+For example, this custom code breaks with an assertion failure when a
+variable called @code{ptr} acquires a value that's unknown:
+
+@smallexample
+
+    /* Find a variable matching "ptr".  */
+    svalue_id sid = get_value_by_name ("ptr");
+    if (!sid.null_p ())
+      @{
+	svalue *sval = get_svalue (sid);
+	gcc_assert (sval->get_kind () != SK_UNKNOWN);
+      @}
+@end smallexample
+
+making it easier to investigate further in a debugger when this occurs.
diff --git a/gcc/doc/gccint.texi b/gcc/doc/gccint.texi
index 99ec005..9dd935d 100644
--- a/gcc/doc/gccint.texi
+++ b/gcc/doc/gccint.texi
@@ -125,6 +125,7 @@ Additional tutorial information is linked to from
 * LTO::             Using Link-Time Optimization.
 
 * Match and Simplify:: How to write expression simplification patterns for GIMPLE and GENERIC
+* Static Analyzer:: Working with the static analyzer.
 * User Experience Guidelines:: Guidelines for implementing diagnostics and options.
 * Funding::         How to help assure funding for free software.
 * GNU Project::     The GNU Project and GNU/Linux.
@@ -163,6 +164,7 @@ Additional tutorial information is linked to from
 @include plugins.texi
 @include lto.texi
 @include match-and-simplify.texi
+@include analyzer.texi
 @include ux.texi
 
 @include funding.texi
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 01/49] analyzer: user-facing documentation
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (4 preceding siblings ...)
  2019-11-16  1:17 ` [PATCH 09/49] gimple const-correctness fixes David Malcolm
@ 2019-11-16  1:17 ` David Malcolm
  2019-12-06 19:57   ` Eric Gallager
  2019-12-09  5:36   ` Sandra Loosemore
  2019-11-16  1:18 ` [PATCH 32/49] analyzer: new files: pending-diagnostic.{cc|h} David Malcolm
                   ` (44 subsequent siblings)
  50 siblings, 2 replies; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:17 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

gcc/ChangeLog:
	* doc/invoke.texi ("Static Analyzer Options"): New list and new section.
	("Warning Options"): Add static analysis warnings to the list.
	(-Wno-analyzer-double-fclose): New option.
	(-Wno-analyzer-double-free): New option.
	(-Wno-analyzer-exposure-through-output-file): New option.
	(-Wno-analyzer-file-leak): New option.
	(-Wno-analyzer-free-of-non-heap): New option.
	(-Wno-analyzer-malloc-leak): New option.
	(-Wno-analyzer-possible-null-argument): New option.
	(-Wno-analyzer-possible-null-dereference): New option.
	(-Wno-analyzer-null-argument): New option.
	(-Wno-analyzer-null-dereference): New option.
	(-Wno-analyzer-stale-setjmp-buffer): New option.
	(-Wno-analyzer-tainted-array-index): New option.
	(-Wno-analyzer-use-after-free): New option.
	(-Wno-analyzer-use-of-pointer-in-stale-stack-frame): New option.
	(-Wno-analyzer-use-of-uninitialized-value): New option.
	(-Wanalyzer-too-complex): New option.
	(-fanalyzer-call-summaries): New warning.
	(-fanalyzer-checker=): New warning.
	(-fanalyzer-fine-grained): New warning.
	(-fno-analyzer-state-merge): New warning.
	(-fno-analyzer-state-purge): New warning.
	(-fanalyzer-transitivity): New warning.
	(-fanalyzer-verbose-edges): New warning.
	(-fanalyzer-verbose-state-changes): New warning.
	(-fanalyzer-verbosity=): New warning.
	(-fdump-analyzer): New warning.
	(-fdump-analyzer-callgraph): New warning.
	(-fdump-analyzer-exploded-graph): New warning.
	(-fdump-analyzer-exploded-nodes): New warning.
	(-fdump-analyzer-exploded-nodes-2): New warning.
	(-fdump-analyzer-exploded-nodes-3): New warning.
	(-fdump-analyzer-supergraph): New warning.
---
 gcc/doc/invoke.texi | 420 +++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 418 insertions(+), 2 deletions(-)

diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 085e8b0..384848a 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -153,6 +153,7 @@ listing and explanation of the binary and decimal byte size prefixes.
 * Diagnostic Message Formatting Options:: Controlling how diagnostics should
                         be formatted.
 * Warning Options::     How picky should the compiler be?
+* Static Analyzer Options:: More expensive warnings.
 * Debugging Options::   Producing debuggable code.
 * Optimize Options::    How much optimization?
 * Instrumentation Options:: Enabling profiling and extra run-time error checking.
@@ -284,13 +285,30 @@ Objective-C and Objective-C++ Dialects}.
 
 @item Warning Options
 @xref{Warning Options,,Options to Request or Suppress Warnings}.
-@gccoptlist{-fsyntax-only  -fmax-errors=@var{n}  -Wpedantic @gol
+@gccoptlist{--analyzer -fsyntax-only  -fmax-errors=@var{n}  -Wpedantic @gol
 -pedantic-errors @gol
 -w  -Wextra  -Wall  -Waddress  -Waddress-of-packed-member @gol
 -Waggregate-return  -Waligned-new @gol
 -Walloc-zero  -Walloc-size-larger-than=@var{byte-size} @gol
 -Walloca  -Walloca-larger-than=@var{byte-size} @gol
--Wno-aggressive-loop-optimizations  -Warray-bounds  -Warray-bounds=@var{n} @gol
+-Wno-aggressive-loop-optimizations @gol
+-Wno-analyzer-double-fclose @gol
+-Wno-analyzer-double-free @gol
+-Wno-analyzer-exposure-through-output-file @gol
+-Wno-analyzer-file-leak @gol
+-Wno-analyzer-free-of-non-heap @gol
+-Wno-analyzer-malloc-leak @gol
+-Wno-analyzer-possible-null-argument @gol
+-Wno-analyzer-possible-null-dereference @gol
+-Wno-analyzer-null-argument @gol
+-Wno-analyzer-null-dereference @gol
+-Wno-analyzer-stale-setjmp-buffer @gol
+-Wno-analyzer-tainted-array-index @gol
+-Wno-analyzer-use-after-free @gol
+-Wno-analyzer-use-of-pointer-in-stale-stack-frame @gol
+-Wno-analyzer-use-of-uninitialized-value @gol
+-Wanalyzer-too-complex @gol
+-Warray-bounds  -Warray-bounds=@var{n} @gol
 -Wno-attributes  -Wattribute-alias=@var{n}  @gol
 -Wbool-compare  -Wbool-operation @gol
 -Wno-builtin-declaration-mismatch @gol
@@ -370,6 +388,43 @@ Objective-C and Objective-C++ Dialects}.
 -Wwrite-strings @gol
 -Wzero-as-null-pointer-constant}
 
+@item Static Analyzer Options
+@gccoptlist{-Wanalyzer-double-fclose @gol
+-Wanalyzer-double-free @gol
+-Wanalyzer-exposure-through-output-file @gol
+-Wanalyzer-file-leak @gol
+-Wanalyzer-free-of-non-heap @gol
+-Wanalyzer-malloc-leak @gol
+-Wanalyzer-null-argument @gol
+-Wanalyzer-null-dereference @gol
+-Wanalyzer-possible-null-argument @gol
+-Wanalyzer-possible-null-dereference @gol
+-Wanalyzer-stale-setjmp-buffer @gol
+-Wanalyzer-tainted-array-index @gol
+-Wanalyzer-use-after-free @gol
+-Wanalyzer-use-of-pointer-in-stale-stack-frame @gol
+-Wanalyzer-use-of-uninitialized-value @gol
+-Wanalyzer-too-complex  @gol
+-fanalyzer-call-summaries @gol
+-fanalyzer-checker=@var{name} @gol
+-fanalyzer-fine-grained @gol
+-fanalyzer-state-merge @gol
+-fanalyzer-state-purge @gol
+-fanalyzer-transitivity @gol
+-fanalyzer-verbose-edges @gol
+-fanalyzer-verbose-state-changes @gol
+-fanalyzer-verbosity=@var{level} @gol
+-fdump-analyzer @gol
+-fdump-analyzer-stderr @gol
+-fdump-analyzer-callgraph @gol
+-fdump-analyzer-exploded-graph @gol
+-fdump-analyzer-exploded-nodes @gol
+-fdump-analyzer-exploded-nodes-2 @gol
+-fdump-analyzer-exploded-nodes-3 @gol
+-fdump-analyzer-state-purge @gol
+-fdump-analyzer-supergraph @gol
+}
+
 @item C and Objective-C-only Warning Options
 @gccoptlist{-Wbad-function-cast  -Wmissing-declarations @gol
 -Wmissing-parameter-type  -Wmissing-prototypes  -Wnested-externs @gol
@@ -6184,6 +6239,159 @@ See also @option{-Wvla-larger-than=}@samp{byte-size}.
 Disable @option{-Walloca-larger-than=} warnings.  The option is
 equivalent to @option{-Walloca-larger-than=}@samp{SIZE_MAX} or larger.
 
+@item -Wno-analyzer-double-fclose
+@opindex Wanalyzer-double-fclose
+@opindex Wno-analyzer-double-fclose
+This warning requires @option{--analyzer}, which enables it; use
+@option{-Wno-analyzer-double-fclose} to disable it.
+
+This diagnostic warns for paths through the code in which a @code{FILE *}
+can have @code{fclose} called on it more than once.
+
+@item -Wno-analyzer-double-free
+@opindex Wanalyzer-double-free
+@opindex Wno-analyzer-double-free
+This warning requires @option{--analyzer}, which enables it; use
+@option{-Wno-analyzer-double-free} to disable it.
+
+This diagnostic warns for paths through the code in which a pointer
+can have @code{free} called on it more than once.
+
+@item -Wno-analyzer-exposure-through-output-file
+@opindex Wanalyzer-exposure-through-output-file
+@opindex Wno-analyzer-exposure-through-output-file
+This warning requires @option{--analyzer}, which enables it; use
+@option{-Wno-analyzer-exposure-through-output-file}
+to disable it.
+
+This diagnostic warns for paths through the code in which a
+security-sensitive value is written to an output file
+(such as writing a password to a log file).
+
+@item -Wno-analyzer-file-leak
+@opindex Wanalyzer-file-leak
+@opindex Wno-analyzer-file-leak
+This warning requires @option{--analyzer}, which enables it; use
+@option{-Wno-analyzer-file-leak}
+to disable it.
+
+This diagnostic warns for paths through the code in which a
+@code{<stdio.h>} @code{FILE *} stream object is leaked.
+
+@item -Wno-analyzer-free-of-non-heap
+@opindex Wanalyzer-free-of-non-heap
+@opindex Wno-analyzer-free-of-non-heap
+This warning requires @option{--analyzer}, which enables it; use
+@option{-Wno-analyzer-free-of-non-heap}
+to disable it.
+
+This diagnostic warns for paths through the code in which @code{free}
+is called on a non-heap pointer (e.g. an on-stack buffer, or a global).
+
+@item -Wno-analyzer-malloc-leak
+@opindex Wanalyzer-malloc-leak
+@opindex Wno-analyzer-malloc-leak
+This warning requires @option{--analyzer}, which enables it; use
+@option{-Wno-analyzer-malloc-leak}
+to disable it.
+
+This diagnostic warns for paths through the code in which a
+pointer allocated via @code{malloc} is leaked.
+
+@item -Wno-analyzer-possible-null-argument
+@opindex Wanalyzer-possible-null-argument
+@opindex Wno-analyzer-possible-null-argument
+This warning requires @option{--analyzer}, which enables it; use
+@option{-Wno-analyzer-possible-null-argument} to disable it.
+
+This diagnostic warns for paths through the code in which a
+possibly-NULL value is passed to a function argument marked
+with @code{__attribute__((nonnull))} as requiring a non-NULL
+value.
+
+@item -Wno-analyzer-possible-null-dereference
+@opindex Wanalyzer-possible-null-dereference
+@opindex Wno-analyzer-possible-null-dereference
+This warning requires @option{--analyzer}, which enables it; use
+@option{-Wno-analyzer-possible-null-dereference} to disable it.
+
+This diagnostic warns for paths through the code in which a
+possibly-NULL value is dereferenced.
+
+@item -Wno-analyzer-null-argument
+@opindex Wanalyzer-null-argument
+@opindex Wno-analyzer-null-argument
+This warning requires @option{--analyzer}, which enables it; use
+@option{-Wno-analyzer-null-argument} to disable it.
+
+This diagnostic warns for paths through the code in which a
+value known to be NULL is passed to a function argument marked
+with @code{__attribute__((nonnull))} as requiring a non-NULL
+value.
+
+@item -Wno-analyzer-null-dereference
+@opindex Wanalyzer-null-dereference
+@opindex Wno-analyzer-null-dereference
+This warning requires @option{--analyzer}, which enables it; use
+@option{-Wno-analyzer-null-dereference} to disable it.
+
+This diagnostic warns for paths through the code in which a
+value known to be NULL is dereferenced.
+
+@item -Wno-analyzer-stale-setjmp-buffer
+@opindex Wanalyzer-stale-setjmp-buffer
+@opindex Wno-analyzer-stale-setjmp-buffer
+This warning requires @option{--analyzer}, which enables it; use
+@option{-Wno-analyzer-stale-setjmp-buffer} to disable it.
+
+This diagnostic warns for paths through the code in which
+@code{longjmp} is called to rewind to a @code{jmp_buf} relating
+to a @code{setjmp} call in a function that has returned.
+
+When @code{setjmp} is called on a @code{jmp_buf} to record a rewind
+location, it records the stack frame.  The stack frame becomes invalid
+when the function containing the @code{setjmp} call returns.  Attempting
+to rewind to it via @code{longjmp} would reference a stack frame that
+no longer exists, and likely lead to a crash (or worse).
+
+@item -Wno-analyzer-tainted-array-index
+@opindex Wanalyzer-tainted-array-index
+@opindex Wno-analyzer-tainted-array-index
+This warning requires @option{--analyzer}, which enables it; use
+@option{-Wno-analyzer-tainted-array-index} to disable it.
+
+This diagnostic warns for paths through the code in which a value
+that could be under an attacker's control is used as the index
+of an array access without being sanitized.
+
+@item -Wno-analyzer-use-after-free
+@opindex Wanalyzer-use-after-free
+@opindex Wno-analyzer-use-after-free
+This warning requires @option{--analyzer}, which enables it; use
+@option{-Wno-analyzer-use-after-free} to disable it.
+
+This diagnostic warns for paths through the code in which a
+pointer is used after @code{free} is called on it.
+
+@item -Wno-analyzer-use-of-pointer-in-stale-stack-frame
+@opindex Wanalyzer-use-of-pointer-in-stale-stack-frame
+@opindex Wno-analyzer-use-of-pointer-in-stale-stack-frame
+This warning requires @option{--analyzer}, which enables it; use
+@option{-Wno-analyzer-use-of-pointer-in-stale-stack-frame}
+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-use-of-uninitialized-value
+@opindex Wanalyzer-use-of-uninitialized-value
+@opindex Wno-analyzer-use-of-uninitialized-value
+This warning requires @option{--analyzer}, which enables it; use
+@option{-Wno-analyzer-use-of-uninitialized-value} to disable it.
+
+This diagnostic warns for paths through the code in which an uninitialized
+value is used.
+
 @item -Warray-bounds
 @itemx -Warray-bounds=@var{n}
 @opindex Wno-array-bounds
@@ -7808,6 +8016,214 @@ OpenMP construct.
 
 @end table
 
+@node Static Analyzer Options
+@section Options That Control Static Analysis
+
+@table @gcctabopt
+@item --analyzer
+@opindex analyzer
+This option enables an static analysis of program flow which looks
+for ``interesting'' interprocedural paths through the
+code, and issues warnings for problems found on them.
+
+This analysis is much more expensive than other GCC warnings.
+
+Enabling this option effectively enables the following warnings:
+
+@gccoptlist{ @gol
+-Wanalyzer-double-fclose @gol
+-Wanalyzer-double-free @gol
+-Wanalyzer-exposure-through-output-file @gol
+-Wanalyzer-file-leak @gol
+-Wanalyzer-free-of-non-heap @gol
+-Wanalyzer-malloc-leak @gol
+-Wanalyzer-possible-null-argument @gol
+-Wanalyzer-possible-null-dereference @gol
+-Wanalyzer-null-argument @gol
+-Wanalyzer-null-dereference @gol
+-Wanalyzer-tainted-array-index @gol
+-Wanalyzer-use-after-free @gol
+-Wanalyzer-use-of-uninitialized-value @gol
+-Wanalyzer-use-of-pointer-in-stale-stack-frame @gol
+}
+
+This option is only available if GCC was configured with analyzer
+support enabled.
+
+@item -Wanalyzer-too-complex
+@opindex Wanalyzer-too-complex
+@opindex Wno-analyzer-too-complex
+If @option{--analyzer} is enabled, the analyzer uses various heuristics
+to attempt to explore the control flow and data flow in the program,
+but these can be defeated by sufficiently complicated code.
+
+By default, the analysis will silently stop if the code is too
+complicated for the analyzer to fully explore and it reaches an internal
+limit.
+
+The @option{-Wanalyzer-too-complex} option will warn if this occurs.
+
+@end table
+
+Pertinent parameters for controlling the exploration are:
+@option{--param analyzer-bb-explosion-factor=@var{value}},
+@option{--param analyzer-max-enodes-per-program-point=@var{value}},
+@option{--param analyzer-max-recursion-depth=@var{value}}, and
+@option{--param analyzer-min-snodes-for-call-summary=@var{value}}.
+
+The following options control the analyzer.
+
+@table @gcctabopt
+
+@item -fanalyzer-call-summaries
+@opindex fanalyzer-call-summaries
+@opindex fno-analyzer-call-summaries
+Simplify interprocedural analysis by computing the effect of certain calls,
+rather than exploring all paths through the function from callsite to each
+possible return.
+
+If enabled, call summaries are only used for functions with more than one
+call site, and that are sufficiently complicated (as per
+@option{--param analyzer-min-snodes-for-call-summary=@var{value}}).
+
+@item -fanalyzer-checker=@var{name}
+@opindex fanalyzer-checker
+Restrict the analyzer to run just the named checker.
+
+@item -fanalyzer-fine-grained
+@opindex fanalyzer-fine-grained
+@opindex fno-analyzer-fine-grained
+This option is intended for analyzer developers.
+
+Internally the analyzer builds an ``exploded graph'' that combines
+control flow graphs with data flow information.
+
+By default, an edge in this graph can contain the effects of a run
+of multiple statements within a basic block.  With
+@option{-fanalyzer-fine-grained}, each statement gets its own edge.
+
+@item -fno-analyzer-state-merge
+@opindex fanalyzer-state-merge
+@opindex fno-analyzer-state-merge
+This option is intended for analyzer developers.
+
+By default the analyzer will attempt to simplify analysis by merging
+sufficiently similar states at each program point as it builds its
+``exploded graph''.  With @option{-fno-analyzer-state-merge} this
+merging can be suppressed, for debugging state-handling issues.
+
+@item -fno-analyzer-state-purge
+@opindex fanalyzer-state-purge
+@opindex fno-analyzer-state-purge
+This option is intended for analyzer developers.
+
+By default the analyzer will attempt to simplify analysis by purging
+aspects of state at a program point that appear to no longer be relevant
+e.g. the values of locals that aren't accessed later in the function
+and which aren't relevant to leak analysis.
+
+With @option{-fno-analyzer-state-purge} this purging of state can
+be suppressed, for debugging state-handling issues.
+
+@item -fanalyzer-transitivity
+@opindex fanalyzer-transitivity
+@opindex fno-analyzer-transitivity
+This option enables transitivity of constraints within the analyzer.
+
+@item -fanalyzer-verbose-edges
+This option is intended for analyzer developers.  It enables more
+verbose, lower-level detail in the descriptions of control flow
+within diagnostic paths.
+
+@item -fanalyzer-verbose-state-changes
+This option is intended for analyzer developers.  It enables more
+verbose, lower-level detail in the descriptions of events relating
+to state machines within diagnostic paths.
+
+@item -fanalyzer-verbosity=@var{level}
+This option controls the complexity of the control flow paths that are
+emitted for analyzer diagnostics.
+
+The @var{level} can be one of:
+
+@table @samp
+@item 0
+At this level, interprocedural call and return events are displayed,
+along with the most pertinent state-change events relating to
+a diagnostic.  For example, for a double-@code{free} diagnostic,
+both calls to @code{free} will be shown.
+
+@item 1
+As per the previous level, but also show events for the entry
+to each function.
+
+@item 2
+As per the previous level, but also show  events relating to
+control flow (e.g. ``true path taken'' at a conditional).
+
+This level is the default.
+
+@item 3
+This level is intended for analyzer developers; it adds various
+other events intended for debugging the analyzer.
+
+@end table
+
+@item -fdump-analyzer
+@opindex fdump-analyzer
+Dump internal details about what the analyzer is doing to
+@file{@var{file}.analyzer.txt}.
+This option is overridden by @option{-fdump-analyzer-stderr}.
+
+@item -fdump-analyzer-stderr
+@opindex fdump-analyzer-stderr
+Dump internal details about what the analyzer is doing to stderr.
+This option overrides @option{-fdump-analyzer}.
+
+@item -fdump-analyzer-callgraph
+@opindex fdump-analyzer-callgraph
+Dump a representation of the call graph suitable for viewing with
+GraphViz to @file{@var{file}.callgraph.dot}.
+
+@item -fdump-analyzer-exploded-graph
+@opindex fdump-analyzer-exploded-graph
+Dump a representation of the ``exploded graph'' suitable for viewing with
+GraphViz to @file{@var{file}.eg.dot}.
+Nodes are color-coded based on state-machine states to emphasize
+state changes.
+
+@item -fdump-analyzer-exploded-nodes
+@opindex dump-analyzer-exploded-nodes
+Emit diagnostics showing where nodes in the ``exploded graph'' are
+in relation to the program source.
+
+@item -fdump-analyzer-exploded-nodes-2
+@opindex dump-analyzer-exploded-nodes-2
+Dump a textual representation of the ``exploded graph'' to
+@file{@var{file}.eg.txt}.
+
+@item -fdump-analyzer-exploded-nodes-3
+@opindex dump-analyzer-exploded-nodes-3
+Dump a textual representation of the ``exploded graph'' to
+one dump file per node, to @file{@var{file}.eg-@var{id}.txt}.
+This is typically a large number of dump files.
+
+@item -fdump-analyzer-state-purge
+@opindex fdump-analyzer-state-purge
+As per @option{-fdump-analyzer-supergraph}, dump a representation of the
+``supergraph'' suitable for viewing with GraphViz, but annotate the
+graph with information on what state will be purged at each node.
+The graph is written to @file{@var{file}.state-purge.dot}.
+
+@item -fdump-analyzer-supergraph
+@opindex fdump-analyzer-supergraph
+Dump a representation of the ``supergraph'' suitable for viewing with
+GraphViz to @file{@var{file}.supergraph.dot}.  This shows all of the
+control flow graphs in the program, with interprocedural edges for
+calls and returns.
+
+@end table
+
 @node Debugging Options
 @section Options for Debugging Your Program
 @cindex options, debugging
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 08/49] Introduce pretty_printer::clone vfunc
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
@ 2019-11-16  1:17 ` David Malcolm
  2019-12-07 14:36   ` Jeff Law
  2019-11-16  1:17 ` [PATCH 11/49] Add diagnostic_metadata and CWE support David Malcolm
                   ` (49 subsequent siblings)
  50 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:17 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This patch provides a way to clone a pretty_printer.

This is needed so that we can capture text in a label_text and make
layout decisions based on it, using the policy of global_dc's printer,
whilst within a call to diagnostic_show_locus.  We can't print with
the pretty_printer itself within a call to diagnostic_show_locus since
it has partly-buffered content.

gcc/c-family/ChangeLog:
	* c-pretty-print.c (c_pretty_printer::clone): New vfunc
	implementation.
	* c-pretty-print.h (c_pretty_printer::clone): New vfunc decl.

gcc/cp/ChangeLog:
	* cxx-pretty-print.c (cxx_pretty_printer::clone): New vfunc
	implementation.
	* cxx-pretty-print.h (cxx_pretty_printer::clone): New vfunc decl.
	* error.c (cxx_format_postprocessor::clone): New vfunc.

gcc/ChangeLog:
	* pretty-print.c (pretty_printer::pretty_printer): New copy-ctor.
	(pretty_printer::clone): New vfunc implementation.
	* pretty-print.h (format_postprocessor::clone): New pure vfunc
	decl.
	(pretty_printer::pretty_printer): New copy-ctor decl.
	(pretty_printer::clone): New vfunc decl.
---
 gcc/c-family/c-pretty-print.c |  7 +++++++
 gcc/c-family/c-pretty-print.h |  1 +
 gcc/cp/cxx-pretty-print.c     |  8 ++++++++
 gcc/cp/cxx-pretty-print.h     |  2 ++
 gcc/cp/error.c                |  5 +++++
 gcc/pretty-print.c            | 34 ++++++++++++++++++++++++++++++++++
 gcc/pretty-print.h            |  4 ++++
 7 files changed, 61 insertions(+)

diff --git a/gcc/c-family/c-pretty-print.c b/gcc/c-family/c-pretty-print.c
index 1b06cc2..9f73d23 100644
--- a/gcc/c-family/c-pretty-print.c
+++ b/gcc/c-family/c-pretty-print.c
@@ -2368,6 +2368,13 @@ c_pretty_printer::c_pretty_printer ()
   parameter_list            = pp_c_parameter_type_list;
 }
 
+/* c_pretty_printer's implementation of pretty_printer::clone vfunc.  */
+
+pretty_printer *
+c_pretty_printer::clone () const
+{
+  return new c_pretty_printer (*this);
+}
 
 /* Print the tree T in full, on file FILE.  */
 
diff --git a/gcc/c-family/c-pretty-print.h b/gcc/c-family/c-pretty-print.h
index 8d69620..df21e21 100644
--- a/gcc/c-family/c-pretty-print.h
+++ b/gcc/c-family/c-pretty-print.h
@@ -51,6 +51,7 @@ class c_pretty_printer : public pretty_printer
 {
 public:
   c_pretty_printer ();
+  pretty_printer *clone () const OVERRIDE;
 
   // Format string, possibly translated.
   void translate_string (const char *);
diff --git a/gcc/cp/cxx-pretty-print.c b/gcc/cp/cxx-pretty-print.c
index 2a129a3..d9d57be 100644
--- a/gcc/cp/cxx-pretty-print.c
+++ b/gcc/cp/cxx-pretty-print.c
@@ -3012,3 +3012,11 @@ cxx_pretty_printer::cxx_pretty_printer ()
   type_specifier_seq = (pp_fun) pp_cxx_type_specifier_seq;
   parameter_list = (pp_fun) pp_cxx_parameter_declaration_clause;
 }
+
+/* cxx_pretty_printer's implementation of pretty_printer::clone vfunc.  */
+
+pretty_printer *
+cxx_pretty_printer::clone () const
+{
+  return new cxx_pretty_printer (*this);
+}
diff --git a/gcc/cp/cxx-pretty-print.h b/gcc/cp/cxx-pretty-print.h
index 347811f..dd96226 100644
--- a/gcc/cp/cxx-pretty-print.h
+++ b/gcc/cp/cxx-pretty-print.h
@@ -34,6 +34,8 @@ class cxx_pretty_printer : public c_pretty_printer
 public:
   cxx_pretty_printer ();
 
+  pretty_printer *clone () const OVERRIDE;
+
   void constant (tree);
   void id_expression (tree);
   void primary_expression (tree);
diff --git a/gcc/cp/error.c b/gcc/cp/error.c
index f40b90d..bc8c9dc 100644
--- a/gcc/cp/error.c
+++ b/gcc/cp/error.c
@@ -143,6 +143,11 @@ class cxx_format_postprocessor : public format_postprocessor
   : m_type_a (), m_type_b ()
   {}
 
+  format_postprocessor *clone() const FINAL OVERRIDE
+  {
+    return new cxx_format_postprocessor ();
+  }
+
   void handle (pretty_printer *pp) FINAL OVERRIDE;
 
   deferred_printed_type m_type_a;
diff --git a/gcc/pretty-print.c b/gcc/pretty-print.c
index c57a3db..89242ed 100644
--- a/gcc/pretty-print.c
+++ b/gcc/pretty-print.c
@@ -1588,6 +1588,32 @@ pretty_printer::pretty_printer (int maximum_length)
   pp_set_prefix (this, NULL);
 }
 
+/* Copy constructor for pretty_printer.  */
+
+pretty_printer::pretty_printer (const pretty_printer &other)
+: buffer (new (XCNEW (output_buffer)) output_buffer ()),
+  prefix (),
+  padding (other.padding),
+  maximum_length (other.maximum_length),
+  indent_skip (other.indent_skip),
+  wrapping (other.wrapping),
+  format_decoder (other.format_decoder),
+  m_format_postprocessor (NULL),
+  emitted_prefix (other.emitted_prefix),
+  need_newline (other.need_newline),
+  translate_identifiers (other.translate_identifiers),
+  show_color (other.show_color),
+  show_urls (other.show_urls)
+{
+  pp_line_cutoff (this) = maximum_length;
+  /* By default, we emit prefixes once per message.  */
+  pp_prefixing_rule (this) = pp_prefixing_rule (&other);
+  pp_set_prefix (this, NULL);
+
+  if (other.m_format_postprocessor)
+    m_format_postprocessor = other.m_format_postprocessor->clone ();
+}
+
 pretty_printer::~pretty_printer ()
 {
   if (m_format_postprocessor)
@@ -1597,6 +1623,14 @@ pretty_printer::~pretty_printer ()
   free (prefix);
 }
 
+/* Base class implementation of pretty_printer::clone vfunc.  */
+
+pretty_printer *
+pretty_printer::clone () const
+{
+  return new pretty_printer (*this);
+}
+
 /* Append a string delimited by START and END to the output area of
    PRETTY-PRINTER.  No line wrapping is done.  However, if beginning a
    new line then emit PRETTY-PRINTER's prefix and skip any leading
diff --git a/gcc/pretty-print.h b/gcc/pretty-print.h
index c73fc30..493507d 100644
--- a/gcc/pretty-print.h
+++ b/gcc/pretty-print.h
@@ -192,6 +192,7 @@ class format_postprocessor
 {
  public:
   virtual ~format_postprocessor () {}
+  virtual format_postprocessor *clone() const = 0;
   virtual void handle (pretty_printer *) = 0;
 };
 
@@ -221,9 +222,12 @@ public:
   /* Default construct a pretty printer with specified
      maximum line length cut off limit.  */
   explicit pretty_printer (int = 0);
+  explicit pretty_printer (const pretty_printer &other);
 
   virtual ~pretty_printer ();
 
+  virtual pretty_printer *clone () const;
+
   /* Where we print external representation of ENTITY.  */
   output_buffer *buffer;
 
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 14/49] hash-map-tests.c: add a selftest involving int_hash
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (2 preceding siblings ...)
  2019-11-16  1:17 ` [PATCH 02/49] analyzer: internal documentation David Malcolm
@ 2019-11-16  1:17 ` David Malcolm
  2019-12-07 14:38   ` Jeff Law
  2019-11-16  1:17 ` [PATCH 09/49] gimple const-correctness fixes David Malcolm
                   ` (46 subsequent siblings)
  50 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:17 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

gcc/ChangeLog:
	* hash-map-tests.c (selftest::test_map_of_int_to_strings): New
	selftest.
	(selftest::hash_map_tests_c_tests): Call it.
---
 gcc/hash-map-tests.c | 41 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 41 insertions(+)

diff --git a/gcc/hash-map-tests.c b/gcc/hash-map-tests.c
index f3b216b..213a8da 100644
--- a/gcc/hash-map-tests.c
+++ b/gcc/hash-map-tests.c
@@ -103,6 +103,46 @@ test_map_of_strings_to_int ()
   ASSERT_EQ (1, string_map.elements ());
 }
 
+/* Construct a hash_map using int_hash and verify that
+   various operations work correctly.  */
+
+static void
+test_map_of_int_to_strings ()
+{
+  const int EMPTY = -1;
+  const int DELETED = -2;
+  typedef int_hash <int, EMPTY, DELETED> int_hash_t;
+  hash_map <int_hash_t, const char *> m;
+
+  const char *ostrich = "ostrich";
+  const char *elephant = "elephant";
+  const char *ant = "ant";
+  const char *spider = "spider";
+  const char *millipede = "Illacme plenipes";
+  const char *eric = "half a bee";
+
+  /* A fresh hash_map should be empty.  */
+  ASSERT_EQ (0, m.elements ());
+  ASSERT_EQ (NULL, m.get (2));
+
+  /* Populate the hash_map.  */
+  ASSERT_EQ (false, m.put (2, ostrich));
+  ASSERT_EQ (false, m.put (4, elephant));
+  ASSERT_EQ (false, m.put (6, ant));
+  ASSERT_EQ (false, m.put (8, spider));
+  ASSERT_EQ (false, m.put (750, millipede));
+  ASSERT_EQ (false, m.put (3, eric));
+
+  /* Verify that we can recover the stored values.  */
+  ASSERT_EQ (6, m.elements ());
+  ASSERT_EQ (*m.get (2), ostrich);
+  ASSERT_EQ (*m.get (4), elephant);
+  ASSERT_EQ (*m.get (6), ant);
+  ASSERT_EQ (*m.get (8), spider);
+  ASSERT_EQ (*m.get (750), millipede);
+  ASSERT_EQ (*m.get (3), eric);
+}
+
 typedef class hash_map_test_val_t
 {
 public:
@@ -243,6 +283,7 @@ void
 hash_map_tests_c_tests ()
 {
   test_map_of_strings_to_int ();
+  test_map_of_int_to_strings ();
   test_map_of_type_with_ctor_and_dtor ();
 }
 
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 11/49] Add diagnostic_metadata and CWE support
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
  2019-11-16  1:17 ` [PATCH 08/49] Introduce pretty_printer::clone vfunc David Malcolm
@ 2019-11-16  1:17 ` David Malcolm
  2019-12-04 17:36   ` Martin Sebor
  2019-11-16  1:17 ` [PATCH 02/49] analyzer: internal documentation David Malcolm
                   ` (48 subsequent siblings)
  50 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:17 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This patch adds support for associating a diagnostic with an optional
diagnostic_metadata object, so that plugins can add extra data to their
diagnostics (e.g. mapping a diagnostic to a taxonomy or coding standard
such as from CERT or MISRA).

Currently this only supports associating a CWE identifier with a
diagnostic (which is what I'm using for the analyzer warnings later in
the patch kit), but adding a diagnostic_metadata class allows for future
growth in this area without an explosion of further "warning_at" overloads
for all of the different kinds of custom data that a plugin might want to
add.

gcc/ChangeLog:
	* common.opt (fdiagnostics-show-metadata): New option.
	* diagnostic-core.h (class diagnostic_metadata): New forward decl.
	(warning_at): Add overload taking a const diagnostic_metadata &.
	(emit_diagnostic_valist): Add overload taking a
	const diagnostic_metadata *.
	* diagnostic-format-json.cc: Include "diagnostic-metadata.h".
	(json_from_metadata): New function.
	(json_end_diagnostic): Call it to add "metadata" child for
	diagnostics with metadata.
	(diagnostic_output_format_init): Clear context->show_metadata.
	* diagnostic-metadata.h: New file.
	* diagnostic.c: Include "diagnostic-metadata.h".
	(diagnostic_impl): Add const diagnostic_metadata * param.
	(diagnostic_n_impl): Likewise.
	(diagnostic_initialize): Initialize context->show_metadata.
	(diagnostic_set_info_translated): Initialize diagnostic->metadata.
	(get_cwe_url): New function.
	(print_any_metadata): New function.
	(diagnostic_report_diagnostic): Call print_any_metadata if the
	diagnostic has non-NULL metadata.
	(emit_diagnostic): Pass NULL as the metadata in the call to
	diagnostic_impl.
	(emit_diagnostic_valist): Likewise.
	(emit_diagnostic_valist): New overload taking a
	const diagnostic_metadata *.
	(inform): Pass NULL as the metadata in the call to
	diagnostic_impl.
	(inform_n): Likewise for diagnostic_n_impl.
	(warning): Likewise.
	(warning_at): Likewise.  Add overload that takes a
	const diagnostic_metadata &.
	(warning_n): Pass NULL as the metadata in the call to
	diagnostic_n_impl.
	(pedwarn): Likewise for diagnostic_impl.
	(permerror): Likewise.
	(error): Likewise.
	(error_n): Likewise.
	(error_at): Likewise.
	(sorry): Likewise.
	(sorry_at): Likewise.
	(fatal_error): Likewise.
	(internal_error): Likewise.
	(internal_error_no_backtrace): Likewise.
	* diagnostic.h (diagnostic_info::metadata): New field.
	(diagnostic_context::show_metadata): New field.
	* doc/invoke.texi (-fno-diagnostics-show-metadata): New option.
	* opts.c (common_handle_option): Handle
	OPT_fdiagnostics_show_metadata.
	* toplev.c (general_init): Initialize global_dc->show_metadata.
---
 gcc/common.opt                |   4 ++
 gcc/diagnostic-core.h         |  10 +++
 gcc/diagnostic-format-json.cc |  23 +++++++
 gcc/diagnostic-metadata.h     |  42 +++++++++++++
 gcc/diagnostic.c              | 142 +++++++++++++++++++++++++++++++++---------
 gcc/diagnostic.h              |   7 +++
 gcc/doc/invoke.texi           |   8 +++
 gcc/opts.c                    |   4 ++
 gcc/toplev.c                  |   2 +
 9 files changed, 211 insertions(+), 31 deletions(-)
 create mode 100644 gcc/diagnostic-metadata.h

diff --git a/gcc/common.opt b/gcc/common.opt
index 3c024b3..228df32 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1334,6 +1334,10 @@ fdiagnostics-show-option
 Common Var(flag_diagnostics_show_option) Init(1)
 Amend appropriate diagnostic messages with the command line option that controls them.
 
+fdiagnostics-show-metadata
+Common Var(flag_diagnostics_show_metadata) Init(1)
+Print additional metadata for diagnostic messages, where available.
+
 fdiagnostics-minimum-margin-width=
 Common Joined UInteger Var(diagnostics_minimum_margin_width) Init(6)
 Set minimum width of left margin of source code when showing source.
diff --git a/gcc/diagnostic-core.h b/gcc/diagnostic-core.h
index efafde4..2e7f120 100644
--- a/gcc/diagnostic-core.h
+++ b/gcc/diagnostic-core.h
@@ -45,6 +45,9 @@ class auto_diagnostic_group
   ~auto_diagnostic_group ();
 };
 
+/* Forward decl.  */
+class diagnostic_metadata; /* See diagnostic-metadata.h.  */
+
 extern const char *progname;
 
 extern const char *trim_filename (const char *);
@@ -78,6 +81,9 @@ extern bool warning_at (location_t, int, const char *, ...)
     ATTRIBUTE_GCC_DIAG(3,4);
 extern bool warning_at (rich_location *, int, const char *, ...)
     ATTRIBUTE_GCC_DIAG(3,4);
+extern bool warning_at (rich_location *, const diagnostic_metadata &, int,
+			const char *, ...)
+    ATTRIBUTE_GCC_DIAG(4,5);
 extern void error (const char *, ...) ATTRIBUTE_GCC_DIAG(1,2);
 extern void error_n (location_t, unsigned HOST_WIDE_INT, const char *,
 		     const char *, ...)
@@ -109,6 +115,10 @@ extern bool emit_diagnostic (diagnostic_t, rich_location *, int,
 			     const char *, ...) ATTRIBUTE_GCC_DIAG(4,5);
 extern bool emit_diagnostic_valist (diagnostic_t, location_t, int, const char *,
 				    va_list *) ATTRIBUTE_GCC_DIAG (4,0);
+extern bool emit_diagnostic_valist (diagnostic_t, rich_location *,
+				    const diagnostic_metadata *metadata,
+				    int, const char *, va_list *)
+  ATTRIBUTE_GCC_DIAG (5,0);
 extern bool seen_error (void);
 
 #ifdef BUFSIZ
diff --git a/gcc/diagnostic-format-json.cc b/gcc/diagnostic-format-json.cc
index eb99952..200e844 100644
--- a/gcc/diagnostic-format-json.cc
+++ b/gcc/diagnostic-format-json.cc
@@ -23,6 +23,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "system.h"
 #include "coretypes.h"
 #include "diagnostic.h"
+#include "diagnostic-metadata.h"
 #include "json.h"
 #include "selftest.h"
 
@@ -103,6 +104,19 @@ json_from_fixit_hint (const fixit_hint *hint)
   return fixit_obj;
 }
 
+/* Generate a JSON object for METADATA.  */
+
+static json::object *
+json_from_metadata (const diagnostic_metadata *metadata)
+{
+  json::object *metadata_obj = new json::object ();
+
+  if (metadata->get_cwe ())
+    metadata_obj->set ("cwe", new json::number (metadata->get_cwe ()));
+
+  return metadata_obj;
+}
+
 /* No-op implementation of "begin_diagnostic" for JSON output.  */
 
 static void
@@ -211,6 +225,12 @@ json_end_diagnostic (diagnostic_context *context, diagnostic_info *diagnostic,
      TODO: functions
      TODO: inlining information
      TODO: macro expansion information.  */
+
+  if (diagnostic->metadata)
+    {
+      json::object *metadata_obj = json_from_metadata (diagnostic->metadata);
+      diag_obj->set ("metadata", metadata_obj);
+    }
 }
 
 /* No-op implementation of "begin_group_cb" for JSON output.  */
@@ -268,6 +288,9 @@ diagnostic_output_format_init (diagnostic_context *context,
 	context->end_group_cb =  json_end_group;
 	context->final_cb =  json_final_cb;
 
+	/* The metadata is handled in JSON format, rather than as text.  */
+	context->show_metadata = false;
+
 	/* The option is handled in JSON format, rather than as text.  */
 	context->show_option_requested = false;
 
diff --git a/gcc/diagnostic-metadata.h b/gcc/diagnostic-metadata.h
new file mode 100644
index 0000000..a759d44
--- /dev/null
+++ b/gcc/diagnostic-metadata.h
@@ -0,0 +1,42 @@
+/* Additional metadata for a diagnostic.
+   Copyright (C) 2019 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/>.  */
+
+#ifndef GCC_DIAGNOSTIC_METADATA_H
+#define GCC_DIAGNOSTIC_METADATA_H
+
+/* A bundle of additional metadata that can be associated with a
+   diagnostic.
+
+   Currently this only supports associating a CWE identifier with a
+   diagnostic.  */
+
+class diagnostic_metadata
+{
+ public:
+  diagnostic_metadata () : m_cwe (0) {}
+
+  void add_cwe (int cwe) { m_cwe = cwe; }
+  int get_cwe () const { return m_cwe; }
+
+ private:
+  int m_cwe;
+};
+
+#endif /* ! GCC_DIAGNOSTIC_METADATA_H */
diff --git a/gcc/diagnostic.c b/gcc/diagnostic.c
index 81e0a10..e8d0613 100644
--- a/gcc/diagnostic.c
+++ b/gcc/diagnostic.c
@@ -32,6 +32,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "diagnostic.h"
 #include "diagnostic-color.h"
 #include "diagnostic-url.h"
+#include "diagnostic-metadata.h"
 #include "edit-context.h"
 #include "selftest.h"
 #include "selftest-diagnostic.h"
@@ -58,11 +59,13 @@ along with GCC; see the file COPYING3.  If not see
 #define permissive_error_option(DC) ((DC)->opt_permissive)
 
 /* Prototypes.  */
-static bool diagnostic_impl (rich_location *, int, const char *,
-			     va_list *, diagnostic_t) ATTRIBUTE_GCC_DIAG(3,0);
-static bool diagnostic_n_impl (rich_location *, int, unsigned HOST_WIDE_INT,
+static bool diagnostic_impl (rich_location *, const diagnostic_metadata *,
+			     int, const char *,
+			     va_list *, diagnostic_t) ATTRIBUTE_GCC_DIAG(4,0);
+static bool diagnostic_n_impl (rich_location *, const diagnostic_metadata *,
+			       int, unsigned HOST_WIDE_INT,
 			       const char *, const char *, va_list *,
-			       diagnostic_t) ATTRIBUTE_GCC_DIAG(5,0);
+			       diagnostic_t) ATTRIBUTE_GCC_DIAG(6,0);
 
 static void error_recursion (diagnostic_context *) ATTRIBUTE_NORETURN;
 static void real_abort (void) ATTRIBUTE_NORETURN;
@@ -183,6 +186,7 @@ diagnostic_initialize (diagnostic_context *context, int n_opts)
   diagnostic_set_caret_max_width (context, pp_line_cutoff (context->printer));
   for (i = 0; i < rich_location::STATICALLY_ALLOCATED_RANGES; i++)
     context->caret_chars[i] = '^';
+  context->show_metadata = false;
   context->show_option_requested = false;
   context->abort_on_error = false;
   context->show_column = false;
@@ -300,6 +304,7 @@ diagnostic_set_info_translated (diagnostic_info *diagnostic, const char *msg,
   diagnostic->message.format_spec = msg;
   diagnostic->message.m_richloc = richloc;
   diagnostic->richloc = richloc;
+  diagnostic->metadata = NULL;
   diagnostic->kind = kind;
   diagnostic->option_index = 0;
 }
@@ -899,6 +904,47 @@ update_effective_level_from_pragmas (diagnostic_context *context,
   return diag_class;
 }
 
+/* Generate a URL string describing CWE.  The caller is responsible for
+   freeing the string.  */
+
+static char *
+get_cwe_url (int cwe)
+{
+  return xasprintf ("https://cwe.mitre.org/data/definitions/%i.html", cwe);
+}
+
+/* If DIAGNOSTIC has metadata, print it.
+
+   For example, if the diagnostic metadata associates it with CWE-119,
+   " [CWE-119]" will be printed, suitably colorized, and with a URL of a
+   description of the security issue.  */
+
+static void
+print_any_metadata (diagnostic_context *context,
+		    const diagnostic_info *diagnostic)
+{
+  if (diagnostic->metadata == NULL)
+    return;
+
+  int cwe = diagnostic->metadata->get_cwe ();
+  if (cwe)
+    {
+      pretty_printer *pp = context->printer;
+      char *saved_prefix = pp_take_prefix (context->printer);
+      pp_string (pp, " [");
+      pp_string (pp, colorize_start (pp_show_color (pp),
+				     diagnostic_kind_color[diagnostic->kind]));
+      char *cwe_url = get_cwe_url (cwe);
+      pp_begin_url (pp, cwe_url);
+      free (cwe_url);
+      pp_printf (pp, "CWE-%i", cwe);
+      pp_set_prefix (context->printer, saved_prefix);
+      pp_end_url (pp);
+      pp_string (pp, colorize_stop (pp_show_color (pp)));
+      pp_character (pp, ']');
+    }
+}
+
 /* Print any metadata about the option used to control DIAGNOSTIC to CONTEXT's
    printer, e.g. " [-Werror=uninitialized]".
    Subroutine of diagnostic_report_diagnostic.  */
@@ -1059,6 +1105,8 @@ diagnostic_report_diagnostic (diagnostic_context *context,
   pp_format (context->printer, &diagnostic->message);
   (*diagnostic_starter (context)) (context, diagnostic);
   pp_output_formatted_text (context->printer);
+  if (context->show_metadata)
+    print_any_metadata (context, diagnostic);
   if (context->show_option_requested)
     print_option_information (context, diagnostic, orig_diag_kind);
   (*diagnostic_finalizer (context)) (context, diagnostic, orig_diag_kind);
@@ -1190,8 +1238,8 @@ diagnostic_append_note (diagnostic_context *context,
    permerror, error, error_at, error_at, sorry, fatal_error, internal_error,
    and internal_error_no_backtrace, as documented and defined below.  */
 static bool
-diagnostic_impl (rich_location *richloc, int opt,
-		 const char *gmsgid,
+diagnostic_impl (rich_location *richloc, const diagnostic_metadata *metadata,
+		 int opt, const char *gmsgid,
 		 va_list *ap, diagnostic_t kind)
 {
   diagnostic_info diagnostic;
@@ -1207,13 +1255,15 @@ diagnostic_impl (rich_location *richloc, int opt,
       if (kind == DK_WARNING || kind == DK_PEDWARN)
 	diagnostic.option_index = opt;
     }
+  diagnostic.metadata = metadata;
   return diagnostic_report_diagnostic (global_dc, &diagnostic);
 }
 
 /* Implement inform_n, warning_n, and error_n, as documented and
    defined below.  */
 static bool
-diagnostic_n_impl (rich_location *richloc, int opt, unsigned HOST_WIDE_INT n,
+diagnostic_n_impl (rich_location *richloc, const diagnostic_metadata *metadata,
+		   int opt, unsigned HOST_WIDE_INT n,
 		   const char *singular_gmsgid,
 		   const char *plural_gmsgid,
 		   va_list *ap, diagnostic_t kind)
@@ -1233,6 +1283,7 @@ diagnostic_n_impl (rich_location *richloc, int opt, unsigned HOST_WIDE_INT n,
   diagnostic_set_info_translated (&diagnostic, text, ap, richloc, kind);
   if (kind == DK_WARNING)
     diagnostic.option_index = opt;
+  diagnostic.metadata = metadata;
   return diagnostic_report_diagnostic (global_dc, &diagnostic);
 }
 
@@ -1246,7 +1297,7 @@ emit_diagnostic (diagnostic_t kind, location_t location, int opt,
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, location);
-  bool ret = diagnostic_impl (&richloc, opt, gmsgid, &ap, kind);
+  bool ret = diagnostic_impl (&richloc, NULL, opt, gmsgid, &ap, kind);
   va_end (ap);
   return ret;
 }
@@ -1260,7 +1311,7 @@ emit_diagnostic (diagnostic_t kind, rich_location *richloc, int opt,
   auto_diagnostic_group d;
   va_list ap;
   va_start (ap, gmsgid);
-  bool ret = diagnostic_impl (richloc, opt, gmsgid, &ap, kind);
+  bool ret = diagnostic_impl (richloc, NULL, opt, gmsgid, &ap, kind);
   va_end (ap);
   return ret;
 }
@@ -1272,7 +1323,18 @@ emit_diagnostic_valist (diagnostic_t kind, location_t location, int opt,
 			const char *gmsgid, va_list *ap)
 {
   rich_location richloc (line_table, location);
-  return diagnostic_impl (&richloc, opt, gmsgid, ap, kind);
+  return diagnostic_impl (&richloc, NULL, opt, gmsgid, ap, kind);
+}
+
+/* Wrapper around diagnostic_impl taking a va_list parameter.  */
+
+bool
+emit_diagnostic_valist (diagnostic_t kind, rich_location *richloc,
+			const diagnostic_metadata *metadata,
+			int opt,
+			const char *gmsgid, va_list *ap)
+{
+  return diagnostic_impl (richloc, metadata, opt, gmsgid, ap, kind);
 }
 
 /* An informative note at LOCATION.  Use this for additional details on an error
@@ -1284,7 +1346,7 @@ inform (location_t location, const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, location);
-  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_NOTE);
+  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_NOTE);
   va_end (ap);
 }
 
@@ -1297,7 +1359,7 @@ inform (rich_location *richloc, const char *gmsgid, ...)
   auto_diagnostic_group d;
   va_list ap;
   va_start (ap, gmsgid);
-  diagnostic_impl (richloc, -1, gmsgid, &ap, DK_NOTE);
+  diagnostic_impl (richloc, NULL, -1, gmsgid, &ap, DK_NOTE);
   va_end (ap);
 }
 
@@ -1311,7 +1373,7 @@ inform_n (location_t location, unsigned HOST_WIDE_INT n,
   va_start (ap, plural_gmsgid);
   auto_diagnostic_group d;
   rich_location richloc (line_table, location);
-  diagnostic_n_impl (&richloc, -1, n, singular_gmsgid, plural_gmsgid,
+  diagnostic_n_impl (&richloc, NULL, -1, n, singular_gmsgid, plural_gmsgid,
 		     &ap, DK_NOTE);
   va_end (ap);
 }
@@ -1326,7 +1388,7 @@ warning (int opt, const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, input_location);
-  bool ret = diagnostic_impl (&richloc, opt, gmsgid, &ap, DK_WARNING);
+  bool ret = diagnostic_impl (&richloc, NULL, opt, gmsgid, &ap, DK_WARNING);
   va_end (ap);
   return ret;
 }
@@ -1342,7 +1404,7 @@ warning_at (location_t location, int opt, const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, location);
-  bool ret = diagnostic_impl (&richloc, opt, gmsgid, &ap, DK_WARNING);
+  bool ret = diagnostic_impl (&richloc, NULL, opt, gmsgid, &ap, DK_WARNING);
   va_end (ap);
   return ret;
 }
@@ -1357,7 +1419,25 @@ warning_at (rich_location *richloc, int opt, const char *gmsgid, ...)
   auto_diagnostic_group d;
   va_list ap;
   va_start (ap, gmsgid);
-  bool ret = diagnostic_impl (richloc, opt, gmsgid, &ap, DK_WARNING);
+  bool ret = diagnostic_impl (richloc, NULL, opt, gmsgid, &ap, DK_WARNING);
+  va_end (ap);
+  return ret;
+}
+
+/* Same as "warning at" above, but using METADATA.  */
+
+bool
+warning_at (rich_location *richloc, const diagnostic_metadata &metadata,
+	    int opt, const char *gmsgid, ...)
+{
+  gcc_assert (richloc);
+
+  auto_diagnostic_group d;
+  va_list ap;
+  va_start (ap, gmsgid);
+  bool ret
+    = diagnostic_impl (richloc, &metadata, opt, gmsgid, &ap,
+		       DK_WARNING);
   va_end (ap);
   return ret;
 }
@@ -1373,7 +1453,7 @@ warning_n (rich_location *richloc, int opt, unsigned HOST_WIDE_INT n,
   auto_diagnostic_group d;
   va_list ap;
   va_start (ap, plural_gmsgid);
-  bool ret = diagnostic_n_impl (richloc, opt, n,
+  bool ret = diagnostic_n_impl (richloc, NULL, opt, n,
 				singular_gmsgid, plural_gmsgid,
 				&ap, DK_WARNING);
   va_end (ap);
@@ -1392,7 +1472,7 @@ warning_n (location_t location, int opt, unsigned HOST_WIDE_INT n,
   va_list ap;
   va_start (ap, plural_gmsgid);
   rich_location richloc (line_table, location);
-  bool ret = diagnostic_n_impl (&richloc, opt, n,
+  bool ret = diagnostic_n_impl (&richloc, NULL, opt, n,
 				singular_gmsgid, plural_gmsgid,
 				&ap, DK_WARNING);
   va_end (ap);
@@ -1419,7 +1499,7 @@ pedwarn (location_t location, int opt, const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, location);
-  bool ret = diagnostic_impl (&richloc, opt, gmsgid, &ap, DK_PEDWARN);
+  bool ret = diagnostic_impl (&richloc, NULL, opt, gmsgid, &ap, DK_PEDWARN);
   va_end (ap);
   return ret;
 }
@@ -1434,7 +1514,7 @@ pedwarn (rich_location *richloc, int opt, const char *gmsgid, ...)
   auto_diagnostic_group d;
   va_list ap;
   va_start (ap, gmsgid);
-  bool ret = diagnostic_impl (richloc, opt, gmsgid, &ap, DK_PEDWARN);
+  bool ret = diagnostic_impl (richloc, NULL, opt, gmsgid, &ap, DK_PEDWARN);
   va_end (ap);
   return ret;
 }
@@ -1453,7 +1533,7 @@ permerror (location_t location, const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, location);
-  bool ret = diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_PERMERROR);
+  bool ret = diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_PERMERROR);
   va_end (ap);
   return ret;
 }
@@ -1468,7 +1548,7 @@ permerror (rich_location *richloc, const char *gmsgid, ...)
   auto_diagnostic_group d;
   va_list ap;
   va_start (ap, gmsgid);
-  bool ret = diagnostic_impl (richloc, -1, gmsgid, &ap, DK_PERMERROR);
+  bool ret = diagnostic_impl (richloc, NULL, -1, gmsgid, &ap, DK_PERMERROR);
   va_end (ap);
   return ret;
 }
@@ -1482,7 +1562,7 @@ error (const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, input_location);
-  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_ERROR);
+  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_ERROR);
   va_end (ap);
 }
 
@@ -1496,7 +1576,7 @@ error_n (location_t location, unsigned HOST_WIDE_INT n,
   va_list ap;
   va_start (ap, plural_gmsgid);
   rich_location richloc (line_table, location);
-  diagnostic_n_impl (&richloc, -1, n, singular_gmsgid, plural_gmsgid,
+  diagnostic_n_impl (&richloc, NULL, -1, n, singular_gmsgid, plural_gmsgid,
 		     &ap, DK_ERROR);
   va_end (ap);
 }
@@ -1509,7 +1589,7 @@ error_at (location_t loc, const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, loc);
-  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_ERROR);
+  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_ERROR);
   va_end (ap);
 }
 
@@ -1523,7 +1603,7 @@ error_at (rich_location *richloc, const char *gmsgid, ...)
   auto_diagnostic_group d;
   va_list ap;
   va_start (ap, gmsgid);
-  diagnostic_impl (richloc, -1, gmsgid, &ap, DK_ERROR);
+  diagnostic_impl (richloc, NULL, -1, gmsgid, &ap, DK_ERROR);
   va_end (ap);
 }
 
@@ -1537,7 +1617,7 @@ sorry (const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, input_location);
-  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_SORRY);
+  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_SORRY);
   va_end (ap);
 }
 
@@ -1549,7 +1629,7 @@ sorry_at (location_t loc, const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, loc);
-  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_SORRY);
+  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_SORRY);
   va_end (ap);
 }
 
@@ -1571,7 +1651,7 @@ fatal_error (location_t loc, const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, loc);
-  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_FATAL);
+  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_FATAL);
   va_end (ap);
 
   gcc_unreachable ();
@@ -1588,7 +1668,7 @@ internal_error (const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, input_location);
-  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_ICE);
+  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_ICE);
   va_end (ap);
 
   gcc_unreachable ();
@@ -1604,7 +1684,7 @@ internal_error_no_backtrace (const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, input_location);
-  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_ICE_NOBT);
+  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_ICE_NOBT);
   va_end (ap);
 
   gcc_unreachable ();
diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
index 2fb75e6..0e4b6ad 100644
--- a/gcc/diagnostic.h
+++ b/gcc/diagnostic.h
@@ -46,6 +46,10 @@ struct diagnostic_info
   /* The location at which the diagnostic is to be reported.  */
   rich_location *richloc;
 
+  /* An optional bundle of metadata associated with the diagnostic
+     (or NULL).  */
+  const diagnostic_metadata *metadata;
+
   /* Auxiliary data for client.  */
   void *x_data;
   /* The kind of diagnostic it is about.  */
@@ -126,6 +130,9 @@ struct diagnostic_context
   /* Character used for caret diagnostics.  */
   char caret_chars[rich_location::STATICALLY_ALLOCATED_RANGES];
 
+  /* True if we should print any metadata associated with diagnostics.  */
+  bool show_metadata;
+
   /* True if we should print the command line option which controls
      each diagnostic, if known.  */
   bool show_option_requested;
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index fc92913..209e2c6 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -279,6 +279,7 @@ Objective-C and Objective-C++ Dialects}.
 -fno-diagnostics-show-option  -fno-diagnostics-show-caret @gol
 -fno-diagnostics-show-labels  -fno-diagnostics-show-line-numbers @gol
 -fdiagnostics-nn-line-numbers @gol
+-fno-diagnostics-show-metadata  @gol
 -fdiagnostics-minimum-margin-width=@var{width} @gol
 -fdiagnostics-parseable-fixits  -fdiagnostics-generate-patch @gol
 -fdiagnostics-show-template-tree  -fno-elide-type @gol
@@ -4012,6 +4013,13 @@ as the types of expressions:
 This option suppresses the printing of these labels (in the example above,
 the vertical bars and the ``char *'' and ``long int'' text).
 
+@item -fno-diagnostics-show-metadata
+@opindex fno-diagnostics-show-metadata
+@opindex fdiagnostics-show-metadata
+Diagnostics can optionally contain @url{https://cwe.mitre.org/index.html, CWE}
+metadata.  By default, if this information is present, it will be printed with
+the diagnostic.  This option suppresses the printing of this metadata.
+
 @item -fno-diagnostics-show-line-numbers
 @opindex fno-diagnostics-show-line-numbers
 @opindex fdiagnostics-show-line-numbers
diff --git a/gcc/opts.c b/gcc/opts.c
index 9928d01..0caaf04 100644
--- a/gcc/opts.c
+++ b/gcc/opts.c
@@ -2491,6 +2491,10 @@ common_handle_option (struct gcc_options *opts,
       dc->parseable_fixits_p = value;
       break;
 
+    case OPT_fdiagnostics_show_metadata:
+      dc->show_metadata = value;
+      break;
+
     case OPT_fdiagnostics_show_option:
       dc->show_option_requested = value;
       break;
diff --git a/gcc/toplev.c b/gcc/toplev.c
index 2d0a390..7136164 100644
--- a/gcc/toplev.c
+++ b/gcc/toplev.c
@@ -1102,6 +1102,8 @@ general_init (const char *argv0, bool init_signals)
     = global_options_init.x_flag_diagnostics_show_line_numbers;
   global_dc->use_nn_for_line_numbers_p
     = global_options_init.x_flag_diagnostics_nn_line_numbers;
+  global_dc->show_metadata
+    = global_options_init.x_flag_diagnostics_show_metadata;
   global_dc->show_option_requested
     = global_options_init.x_flag_diagnostics_show_option;
   global_dc->min_margin_width
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 21/49] analyzer: command-line options
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (15 preceding siblings ...)
  2019-11-16  1:18 ` [PATCH 22/49] analyzer: params.def: new parameters David Malcolm
@ 2019-11-16  1:18 ` David Malcolm
  2019-12-04 18:35   ` Martin Sebor
  2019-11-16  1:18 ` [PATCH 24/49] analyzer: new file: analyzer-pass.cc David Malcolm
                   ` (33 subsequent siblings)
  50 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:18 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This patch contains the command-line options for the analyzer.

gcc/ChangeLog:
	* analyzer/plugin.opt: New file.
	* common.opt (--analyzer): New driver option.
---
 gcc/analyzer/plugin.opt | 161 ++++++++++++++++++++++++++++++++++++++++++++++++
 gcc/common.opt          |   3 +
 2 files changed, 164 insertions(+)
 create mode 100644 gcc/analyzer/plugin.opt

diff --git a/gcc/analyzer/plugin.opt b/gcc/analyzer/plugin.opt
new file mode 100644
index 0000000..55f54bb
--- /dev/null
+++ b/gcc/analyzer/plugin.opt
@@ -0,0 +1,161 @@
+; plugin.opt -- Options for the analyzer.
+
+; Copyright (C) 2019 Free Software Foundation, Inc.
+;
+; 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/>.
+
+; See the GCC internals manual for a description of this file's format.
+
+; Please try to keep this file in ASCII collating order.
+
+Wanalyzer-double-fclose
+Common Var(warn_analyzer_double_fclose) Init(1) Warning
+Warn about code paths in which a stdio FILE can be closed more than once.
+
+Wanalyzer-double-free
+Common Var(warn_analyzer_double_free) Init(1) Warning
+Warn about code paths in which a pointer can be freed more than once.
+
+Wanalyzer-exposure-through-output-file
+Common Var(warn_analyzer_exposure_through_output_file) Init(1) Warning
+Warn about code paths in which sensitive data is written to a file.
+
+Wanalyzer-file-leak
+Common Var(warn_analyzer_file_leak) Init(1) Warning
+Warn about code paths in which a stdio FILE is not closed.
+
+Wanalyzer-free-of-non-heap
+Common Var(warn_analyzer_free_of_non_heap) Init(1) Warning
+Warn about code paths in which a non-heap pointer is freed.
+
+Wanalyzer-malloc-leak
+Common Var(warn_analyzer_malloc_leak) Init(1) Warning
+Warn about code paths in which a heap-allocated pointer leaks.
+
+Wanalyzer-possible-null-argument
+Common Var(warn_analyzer_possible_null_argument) Init(1) Warning
+Warn about code paths in which a possibly-NULL value is passed to a must-not-be-NULL function argument.
+
+Wanalyzer-possible-null-dereference
+Common Var(warn_analyzer_possible_null_dereference) Init(1) Warning
+Warn about code paths in which a possibly-NULL pointer is dereferenced.
+
+Wanalyzer-null-argument
+Common Var(warn_analyzer_null_argument) Init(1) Warning
+Warn about code paths in which NULL is passed to a must-not-be-NULL function argument.
+
+Wanalyzer-null-dereference
+Common Var(warn_analyzer_null_dereference) Init(1) Warning
+Warn about code paths in which a NULL pointer is dereferenced.
+
+Wanalyzer-stale-setjmp-buffer
+Common Var(warn_analyzer_stale_setjmp_buffer) Init(1) Warning
+Warn about code paths in which a longjmp rewinds to a jmp_buf saved in a stack frame that has returned.
+
+Wanalyzer-tainted-array-index
+Common Var(warn_analyzer_tainted_array_index) Init(1) Warning
+Warn about code paths in which an unsanitized value is used as an array index.
+
+Wanalyzer-use-after-free
+Common Var(warn_analyzer_use_after_free) Init(1) Warning
+Warn about code paths in which a freed value is used.
+
+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-use-of-uninitialized-value
+Common Var(warn_analyzer_use_of_uninitialized_value) Init(1) Warning
+Warn about code paths in which an initialized value is used.
+
+Wanalyzer-too-complex
+Common Var(warn_analyzer_too_complex) Init(0) Warning
+Warn if the code is too complicated for the analyzer to fully explore.
+
+fanalyzer-checker=
+Common Joined RejectNegative Var(flag_analyzer_checker)
+Restrict the analyzer to run just the named checker.
+
+fanalyzer-fine-grained
+Common Var(flag_analyzer_fine_grained) Init(0)
+Avoid combining multiple statements into one exploded edge.
+
+fanalyzer-state-purge
+Common Var(flag_analyzer_state_purge) Init(1)
+Purge unneeded state during analysis.
+
+fanalyzer-state-merge
+Common Var(flag_analyzer_state_merge) Init(1)
+Merge similar-enough states during analysis.
+
+fanalyzer-transitivity
+Common Var(flag_analyzer_transitivity) Init(0)
+Enable transitivity of constraints during analysis.
+
+fanalyzer-call-summaries
+Common Var(flag_analyzer_call_summaries) Init(0)
+Approximate the effect of function calls to simplify analysis.
+
+fanalyzer-verbose-edges
+Common Var(flag_analyzer_verbose_edges) Init(0)
+Emit more verbose descriptions of control flow in diagnostics.
+
+fanalyzer-verbose-state-changes
+Common Var(flag_analyzer_verbose_state_changes) Init(0)
+Emit more verbose descriptions of state changes in diagnostics.
+
+fanalyzer-verbosity=
+Common Joined UInteger Var(analyzer_verbosity) Init(2)
+Control which events are displayed in diagnostic paths.
+
+fdump-analyzer
+Common RejectNegative Var(flag_dump_analyzer)
+Dump internal details about what the analyzer is doing to SRCFILE.analyzer.txt.
+
+fdump-analyzer-stderr
+Common RejectNegative Var(flag_dump_analyzer_stderr)
+Dump internal details about what the analyzer is doing to stderr.
+
+fdump-analyzer-callgraph
+Common RejectNegative Var(flag_dump_analyzer_callgraph)
+Dump the analyzer supergraph to a SRCFILE.callgraph.dot file.
+
+fdump-analyzer-exploded-graph
+Common RejectNegative Var(flag_dump_analyzer_exploded_graph)
+Dump the analyzer exploded graph to a SRCFILE.eg.dot file.
+
+fdump-analyzer-exploded-nodes
+Common RejectNegative Var(flag_dump_analyzer_exploded_nodes)
+Emit diagnostics showing the location of nodes in the exploded graph.
+
+fdump-analyzer-exploded-nodes-2
+Common RejectNegative Var(flag_dump_analyzer_exploded_nodes_2)
+Dump a textual representation of the exploded graph to SRCFILE.eg.txt.
+
+fdump-analyzer-exploded-nodes-3
+Common RejectNegative Var(flag_dump_analyzer_exploded_nodes_3)
+Dump a textual representation of the exploded graph to SRCFILE.eg-ID.txt.
+
+fdump-analyzer-state-purge
+Common RejectNegative Var(flag_dump_analyzer_state_purge)
+Dump state-purging information to a SRCFILE.state-purge.dot file.
+
+fdump-analyzer-supergraph
+Common RejectNegative Var(flag_dump_analyzer_supergraph)
+Dump the analyzer supergraph to a SRCFILE.supergraph.dot file.
+
+; This comment is to ensure we retain the blank line above.
diff --git a/gcc/common.opt b/gcc/common.opt
index adc9931..0c352a3 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -269,6 +269,9 @@ Driver Joined Alias(e)
 -extra-warnings
 Common Warning Alias(Wextra)
 
+-analyzer
+Driver Var(analyzer_flag)
+
 -for-assembler
 Driver Separate Alias(Xassembler)
 
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 07/49] Replace label_text ctor with "borrow" and "take"
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (22 preceding siblings ...)
  2019-11-16  1:18 ` [PATCH 35/49] analyzer: new file: sm-file.cc David Malcolm
@ 2019-11-16  1:18 ` David Malcolm
  2019-12-07 14:34   ` Jeff Law
  2019-11-16  1:18 ` [PATCH 33/49] analyzer: new files: sm.{cc|h} David Malcolm
                   ` (26 subsequent siblings)
  50 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:18 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

libcpp's label_text class wraps a text buffer, along with a flag to
determine if it "owns" the buffer.

The existing ctor exposed this directly, but I found it difficult
to remember the sense of flag, so this patch hides the ctor, in
favor of static member functions "borrow" and "take", to make
the effect on ownership explicit in the name.

gcc/c-family/ChangeLog:
	* c-format.c (range_label_for_format_type_mismatch::get_text):
	Replace label_text ctor called with true with label_text::take.

gcc/c/ChangeLog:
	* c-objc-common.c (range_label_for_type_mismatch::get_text):
	Replace label_text ctor calls.

gcc/cp/ChangeLog:
	* error.c (range_label_for_type_mismatch::get_text): Replace
	label_text ctor calls with label_text::borrow.

gcc/ChangeLog:
	* gcc-rich-location.c
	(maybe_range_label_for_tree_type_mismatch::get_text): Replace
	label_text ctor call with label_text::borrow.
	* gcc-rich-location.h (text_range_label::get_text): Replace
	label_text ctor called with false with label_text::borrow.

libcpp/ChangeLog:
	* include/line-map.h (label_text::label_text): Make private.
	(label_text::borrow): New.
	(label_text::take): New.
	(label_text::take_or_copy): New.
---
 gcc/c-family/c-format.c   |  2 +-
 gcc/c/c-objc-common.c     |  4 ++--
 gcc/cp/error.c            |  4 ++--
 gcc/gcc-rich-location.c   |  2 +-
 gcc/gcc-rich-location.h   |  2 +-
 libcpp/include/line-map.h | 31 +++++++++++++++++++++++++++----
 6 files changed, 34 insertions(+), 11 deletions(-)

diff --git a/gcc/c-family/c-format.c b/gcc/c-family/c-format.c
index 7ddf064..84475db 100644
--- a/gcc/c-family/c-format.c
+++ b/gcc/c-family/c-format.c
@@ -4629,7 +4629,7 @@ class range_label_for_format_type_mismatch
 
     char *result = concat (text.m_buffer, p, NULL);
     text.maybe_free ();
-    return label_text (result, true);
+    return label_text::take (result);
   }
 
  private:
diff --git a/gcc/c/c-objc-common.c b/gcc/c/c-objc-common.c
index 10d72c5..665c7a6 100644
--- a/gcc/c/c-objc-common.c
+++ b/gcc/c/c-objc-common.c
@@ -340,12 +340,12 @@ label_text
 range_label_for_type_mismatch::get_text (unsigned /*range_idx*/) const
 {
   if (m_labelled_type == NULL_TREE)
-    return label_text (NULL, false);
+    return label_text::borrow (NULL);
 
   c_pretty_printer cpp;
   bool quoted = false;
   print_type (&cpp, m_labelled_type, &quoted);
-  return label_text (xstrdup (pp_formatted_text (&cpp)), true);
+  return label_text::take (xstrdup (pp_formatted_text (&cpp)));
 }
 
 
diff --git a/gcc/cp/error.c b/gcc/cp/error.c
index 1fd87d2..f40b90d 100644
--- a/gcc/cp/error.c
+++ b/gcc/cp/error.c
@@ -4521,7 +4521,7 @@ label_text
 range_label_for_type_mismatch::get_text (unsigned /*range_idx*/) const
 {
   if (m_labelled_type == NULL_TREE)
-    return label_text (NULL, false);
+    return label_text::borrow (NULL);
 
   const bool verbose = false;
   const bool show_color = false;
@@ -4536,5 +4536,5 @@ range_label_for_type_mismatch::get_text (unsigned /*range_idx*/) const
 
   /* Both of the above return GC-allocated buffers, so the caller mustn't
      free them.  */
-  return label_text (const_cast <char *> (result), false);
+  return label_text::borrow (result);
 }
diff --git a/gcc/gcc-rich-location.c b/gcc/gcc-rich-location.c
index 82d4f52..071463e 100644
--- a/gcc/gcc-rich-location.c
+++ b/gcc/gcc-rich-location.c
@@ -196,7 +196,7 @@ maybe_range_label_for_tree_type_mismatch::get_text (unsigned range_idx) const
 {
   if (m_expr == NULL_TREE
       || !EXPR_P (m_expr))
-    return label_text (NULL, false);
+    return label_text::borrow (NULL);
   tree expr_type = TREE_TYPE (m_expr);
 
   tree other_type = NULL_TREE;
diff --git a/gcc/gcc-rich-location.h b/gcc/gcc-rich-location.h
index 3bee2e8..71f4f3d 100644
--- a/gcc/gcc-rich-location.h
+++ b/gcc/gcc-rich-location.h
@@ -111,7 +111,7 @@ class text_range_label : public range_label
 
   label_text get_text (unsigned /*range_idx*/) const FINAL OVERRIDE
   {
-    return label_text (const_cast <char *> (m_text), false);
+    return label_text::borrow (m_text);
   }
 
  private:
diff --git a/libcpp/include/line-map.h b/libcpp/include/line-map.h
index bde5e53..e4c7952 100644
--- a/libcpp/include/line-map.h
+++ b/libcpp/include/line-map.h
@@ -1791,18 +1791,41 @@ public:
   : m_buffer (NULL), m_caller_owned (false)
   {}
 
-  label_text (char *buffer, bool caller_owned)
-  : m_buffer (buffer), m_caller_owned (caller_owned)
-  {}
-
   void maybe_free ()
   {
     if (m_caller_owned)
       free (m_buffer);
   }
 
+  /* Create a label_text instance that borrows BUFFER from a
+     longer-lived owner.  */
+  static label_text borrow (const char *buffer)
+  {
+    return label_text (const_cast <char *> (buffer), false);
+  }
+
+  /* Create a label_text instance that takes ownership of BUFFER.  */
+  static label_text take (char *buffer)
+  {
+    return label_text (buffer, true);
+  }
+
+  /* Take ownership of the buffer, copying if necessary.  */
+  char *take_or_copy ()
+  {
+    if (m_caller_owned)
+      return m_buffer;
+    else
+      return xstrdup (m_buffer);
+  }
+
   char *m_buffer;
   bool m_caller_owned;
+
+private:
+  label_text (char *buffer, bool owned)
+  : m_buffer (buffer), m_caller_owned (owned)
+  {}
 };
 
 /* Abstract base class for labelling a range within a rich_location
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 28/49] analyzer: new files: analyzer.{cc|h}
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (17 preceding siblings ...)
  2019-11-16  1:18 ` [PATCH 24/49] analyzer: new file: analyzer-pass.cc David Malcolm
@ 2019-11-16  1:18 ` David Malcolm
  2019-12-07  3:38   ` Eric Gallager
                     ` (2 more replies)
  2019-11-16  1:18 ` [PATCH 17/49] Support for adding selftests via a plugin David Malcolm
                   ` (31 subsequent siblings)
  50 siblings, 3 replies; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:18 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

gcc/ChangeLog:
	* analyzer/analyzer.cc: New file.
	* analyzer/analyzer.h: New file.
---
 gcc/analyzer/analyzer.cc | 125 ++++++++++++++++++++++++++++++++++++++++++++++
 gcc/analyzer/analyzer.h  | 126 +++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 251 insertions(+)
 create mode 100644 gcc/analyzer/analyzer.cc
 create mode 100644 gcc/analyzer/analyzer.h

diff --git a/gcc/analyzer/analyzer.cc b/gcc/analyzer/analyzer.cc
new file mode 100644
index 0000000..399925c
--- /dev/null
+++ b/gcc/analyzer/analyzer.cc
@@ -0,0 +1,125 @@
+/* Utility functions for the analyzer.
+   Copyright (C) 2019 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 "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "gimple.h"
+#include "diagnostic.h"
+#include "intl.h"
+#include "analyzer/analyzer.h"
+
+/* Helper function for checkers.  Is the CALL to the given function name?  */
+
+bool
+is_named_call_p (const gcall *call, const char *funcname)
+{
+  gcc_assert (funcname);
+
+  tree fndecl = gimple_call_fndecl (call);
+  if (!fndecl)
+    return false;
+
+  return 0 == strcmp (IDENTIFIER_POINTER (DECL_NAME (fndecl)), funcname);
+}
+
+/* Helper function for checkers.  Is the CALL to the given function name,
+   and with the given number of arguments?  */
+
+bool
+is_named_call_p (const gcall *call, const char *funcname,
+		 unsigned int num_args)
+{
+  gcc_assert (funcname);
+
+  if (!is_named_call_p (call, funcname))
+    return false;
+
+  if (gimple_call_num_args (call) != num_args)
+    return false;
+
+  return true;
+}
+
+/* Return true if stmt is a setjmp call.  */
+
+bool
+is_setjmp_call_p (const gimple *stmt)
+{
+  /* TODO: is there a less hacky way to check for "setjmp"?  */
+  if (const gcall *call = dyn_cast <const gcall *> (stmt))
+    if (is_named_call_p (call, "_setjmp", 1))
+      return true;
+
+  return false;
+}
+
+/* Return true if stmt is a longjmp call.  */
+
+bool
+is_longjmp_call_p (const gcall *call)
+{
+  /* TODO: is there a less hacky way to check for "longjmp"?  */
+  if (is_named_call_p (call, "longjmp", 2))
+    return true;
+
+  return false;
+}
+
+/* Generate a label_text instance by formatting FMT, using a
+   temporary clone of the global_dc's printer (thus using its
+   formatting callbacks).
+
+   Colorize if the global_dc supports colorization and CAN_COLORIZE is
+   true.  */
+
+label_text
+make_label_text (bool can_colorize, const char *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, fmt);
+
+  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;
+}
diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h
new file mode 100644
index 0000000..ace8924
--- /dev/null
+++ b/gcc/analyzer/analyzer.h
@@ -0,0 +1,126 @@
+/* Utility functions for the analyzer.
+   Copyright (C) 2019 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/>.  */
+
+#ifndef GCC_ANALYZER_ANALYZER_H
+#define GCC_ANALYZER_ANALYZER_H
+
+/* Forward decls of common types, with indentation to show inheritance.  */
+
+class graphviz_out;
+class supergraph;
+class supernode;
+class superedge;
+  class cfg_superedge;
+    class switch_cfg_superedge;
+  class callgraph_superedge;
+    class call_superedge;
+    class return_superedge;
+class svalue;
+  class region_svalue;
+  class constant_svalue;
+  class poisoned_svalue;
+  class unknown_svalue;
+  class setjmp_svalue;
+class region;
+  class map_region;
+  class symbolic_region;
+class region_model;
+class region_model_context;
+  class impl_region_model_context;
+class constraint_manager;
+class equiv_class;
+struct model_merger;
+struct svalue_id_merger_mapping;
+struct canonicalization;
+class pending_diagnostic;
+class checker_path;
+class extrinsic_state;
+class sm_state_map;
+class stmt_finder;
+class program_point;
+class program_state;
+class exploded_graph;
+class exploded_node;
+class exploded_edge;
+class exploded_cluster;
+class exploded_path;
+class analysis_plan;
+class state_purge_map;
+class state_purge_per_ssa_name;
+class state_change;
+
+////////////////////////////////////////////////////////////////////////////
+
+extern bool is_named_call_p (const gcall *call, const char *funcname);
+extern bool is_named_call_p (const gcall *call, const char *funcname,
+			     unsigned int num_args);
+extern bool is_setjmp_call_p (const gimple *stmt);
+extern bool is_longjmp_call_p (const gcall *call);
+
+extern void register_analyzer_pass ();
+
+extern label_text make_label_text (bool can_colorize, const char *fmt, ...);
+
+////////////////////////////////////////////////////////////////////////////
+
+/* An RAII-style class for pushing/popping cfun within a scope.
+   Doing so ensures we get "In function " announcements
+   from the diagnostics subsystem.  */
+
+class auto_cfun
+{
+public:
+  auto_cfun (function *fun) { push_cfun (fun); }
+  ~auto_cfun () { pop_cfun (); }
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Begin suppressing -Wformat and -Wformat-extra-args.  */
+
+#define PUSH_IGNORE_WFORMAT \
+  _Pragma("GCC diagnostic push") \
+  _Pragma("GCC diagnostic ignored \"-Wformat\"") \
+  _Pragma("GCC diagnostic ignored \"-Wformat-extra-args\"")
+
+/* Finish suppressing -Wformat and -Wformat-extra-args.  */
+
+#define POP_IGNORE_WFORMAT \
+  _Pragma("GCC diagnostic pop")
+
+////////////////////////////////////////////////////////////////////////////
+
+/* A template for creating hash traits for a POD type.  */
+
+template <typename Type>
+struct pod_hash_traits : typed_noop_remove<Type>
+{
+  typedef Type value_type;
+  typedef Type compare_type;
+  static inline hashval_t hash (value_type);
+  static inline bool equal (const value_type &existing,
+			    const value_type &candidate);
+  static inline void mark_deleted (Type &);
+  static inline void mark_empty (Type &);
+  static inline bool is_deleted (Type);
+  static inline bool is_empty (Type);
+};
+
+#endif /* GCC_ANALYZER_ANALYZER_H */
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 24/49] analyzer: new file: analyzer-pass.cc
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (16 preceding siblings ...)
  2019-11-16  1:18 ` [PATCH 21/49] analyzer: command-line options David Malcolm
@ 2019-11-16  1:18 ` David Malcolm
  2019-12-07 14:51   ` Jeff Law
  2019-11-16  1:18 ` [PATCH 28/49] analyzer: new files: analyzer.{cc|h} David Malcolm
                   ` (32 subsequent siblings)
  50 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:18 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This patch adds the IPA pass boilerplate for the analyzer.

gcc/ChangeLog:
	* analyzer/analyzer-pass.cc: New file.
---
 gcc/analyzer/analyzer-pass.cc | 103 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 103 insertions(+)
 create mode 100644 gcc/analyzer/analyzer-pass.cc

diff --git a/gcc/analyzer/analyzer-pass.cc b/gcc/analyzer/analyzer-pass.cc
new file mode 100644
index 0000000..54b8a5c
--- /dev/null
+++ b/gcc/analyzer/analyzer-pass.cc
@@ -0,0 +1,103 @@
+/* Integration of the analyzer with GCC's pass manager.
+   Copyright (C) 2019 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 "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "context.h"
+#include "tree-pass.h"
+#include "analyzer/engine.h"
+
+namespace {
+
+/* Data for the analyzer pass.  */
+
+const pass_data pass_data_analyzer =
+{
+  IPA_PASS, /* type */
+  "analyzer", /* name */
+  OPTGROUP_NONE, /* optinfo_flags */
+  TV_NONE, /* tv_id */
+  PROP_ssa, /* properties_required */
+  0, /* properties_provided */
+  0, /* properties_destroyed */
+  0, /* todo_flags_start */
+  0, /* todo_flags_finish */
+};
+
+/* The analyzer pass.  */
+
+class pass_analyzer : public ipa_opt_pass_d
+{
+public:
+   pass_analyzer(gcc::context *ctxt)
+   : ipa_opt_pass_d (pass_data_analyzer, ctxt,
+		     NULL, /* generate_summary */
+		     NULL, /* write_summary */
+		     NULL, /* read_summary */
+		     NULL, /* write_optimization_summary */
+		     NULL, /* read_optimization_summary */
+		     NULL, /* stmt_fixup */
+		     0, /* function_transform_todo_flags_start */
+		     NULL, /* function_transform */
+		     NULL) /* variable_transform */
+  {}
+
+  /* opt_pass methods: */
+  unsigned int execute (function *) FINAL OVERRIDE;
+}; // class pass_analyzer
+
+/* Entrypoint for the analyzer pass.  */
+
+unsigned int
+pass_analyzer::execute (function *)
+{
+  run_checkers ();
+  return 0;
+}
+
+} // anon namespace
+
+/* Make an instance of the analyzer pass.  */
+
+static ipa_opt_pass_d *
+make_pass_analyzer (gcc::context *ctxt)
+{
+  return new pass_analyzer (ctxt);
+}
+
+/* Register the analyzer pass with GCC's pass manager.
+   Called by plugin_init.  */
+
+void
+register_analyzer_pass ()
+{
+  static struct register_pass_info pass_info;
+
+  /* IPA-LTO pass.  */
+  pass_info.pass = make_pass_analyzer (g);
+  pass_info.reference_pass_name = "whole-program";
+  pass_info.ref_pass_instance_number = 1;
+  pass_info.pos_op = PASS_POS_INSERT_BEFORE;
+
+  register_callback ("analyzer", PLUGIN_PASS_MANAGER_SETUP, NULL,
+		     &pass_info);
+}
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 32/49] analyzer: new files: pending-diagnostic.{cc|h}
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (5 preceding siblings ...)
  2019-11-16  1:17 ` [PATCH 01/49] analyzer: user-facing documentation David Malcolm
@ 2019-11-16  1:18 ` David Malcolm
  2019-12-07 15:05   ` Jeff Law
  2019-11-16  1:18 ` [PATCH 06/49] timevar.h: add auto_client_timevar class David Malcolm
                   ` (43 subsequent siblings)
  50 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:18 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This patch adds classes used by the analyzer for handling its diagnostics
(queueing them, deduplicating them, precision-of-wording hooks).

gcc/ChangeLog:
	* analyzer/pending-diagnostic.cc: New file.
	* analyzer/pending-diagnostic.h: New file.
---
 gcc/analyzer/pending-diagnostic.cc |  61 +++++++++
 gcc/analyzer/pending-diagnostic.h  | 265 +++++++++++++++++++++++++++++++++++++
 2 files changed, 326 insertions(+)
 create mode 100644 gcc/analyzer/pending-diagnostic.cc
 create mode 100644 gcc/analyzer/pending-diagnostic.h

diff --git a/gcc/analyzer/pending-diagnostic.cc b/gcc/analyzer/pending-diagnostic.cc
new file mode 100644
index 0000000..96ab824
--- /dev/null
+++ b/gcc/analyzer/pending-diagnostic.cc
@@ -0,0 +1,61 @@
+/* Classes for analyzer diagnostics.
+   Copyright (C) 2019 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 "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "intl.h"
+#include "diagnostic.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/pending-diagnostic.h"
+
+/* Generate a label_text by printing FMT.
+
+   Use a clone of the global_dc for formatting callbacks.
+
+   Use this evdesc::event_desc's m_colorize flag to control colorization
+   (so that e.g. we can disable it for JSON output).  */
+
+label_text
+evdesc::event_desc::formatted_print (const char *fmt, ...) const
+{
+  pretty_printer *pp = global_dc->printer->clone ();
+
+  pp_show_color (pp) = m_colorize;
+
+  text_info ti;
+  rich_location rich_loc (line_table, UNKNOWN_LOCATION);
+  va_list ap;
+  va_start (ap, fmt);
+  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;
+}
diff --git a/gcc/analyzer/pending-diagnostic.h b/gcc/analyzer/pending-diagnostic.h
new file mode 100644
index 0000000..f1ab484
--- /dev/null
+++ b/gcc/analyzer/pending-diagnostic.h
@@ -0,0 +1,265 @@
+/* Classes for analyzer diagnostics.
+   Copyright (C) 2019 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/>.  */
+
+#ifndef GCC_ANALYZER_PENDING_DIAGNOSTIC_H
+#define GCC_ANALYZER_PENDING_DIAGNOSTIC_H
+
+#include "diagnostic-event-id.h"
+#include "analyzer/sm.h"
+
+/* Various bundles of information used for generating more precise
+   messages for events within a diagnostic_path, for passing to the
+   various "describe_*" vfuncs of pending_diagnostic.  See those
+   for more information.  */
+
+namespace evdesc {
+
+struct event_desc
+{
+  event_desc (bool colorize) : m_colorize (colorize) {}
+
+  label_text formatted_print (const char *fmt, ...) const
+    ATTRIBUTE_GCC_DIAG(2,3);
+
+  bool m_colorize;
+};
+
+/* For use by pending_diagnostic::describe_state_change.  */
+
+struct state_change : public event_desc
+{
+  state_change (bool colorize,
+		tree expr,
+		tree origin,
+		state_machine::state_t old_state,
+		state_machine::state_t new_state,
+		diagnostic_event_id_t event_id)
+  : event_desc (colorize),
+    m_expr (expr), m_origin (origin),
+    m_old_state (old_state), m_new_state (new_state),
+    m_event_id (event_id)
+  {}
+
+  tree m_expr;
+  tree m_origin;
+  state_machine::state_t m_old_state;
+  state_machine::state_t m_new_state;
+  diagnostic_event_id_t m_event_id;
+};
+
+/* For use by pending_diagnostic::describe_call_with_state.  */
+
+struct call_with_state : public event_desc
+{
+  call_with_state (bool colorize,
+		   tree caller_fndecl, tree callee_fndecl,
+		   tree expr, state_machine::state_t state)
+  : event_desc (colorize),
+    m_caller_fndecl (caller_fndecl),
+    m_callee_fndecl (callee_fndecl),
+    m_expr (expr),
+    m_state (state)
+  {
+  }
+
+  tree m_caller_fndecl;
+  tree m_callee_fndecl;
+  tree m_expr;
+  state_machine::state_t m_state;
+};
+
+/* For use by pending_diagnostic::describe_return_of_state.  */
+
+struct return_of_state : public event_desc
+{
+  return_of_state (bool colorize,
+		   tree caller_fndecl, tree callee_fndecl,
+		   state_machine::state_t state)
+  : event_desc (colorize),
+    m_caller_fndecl (caller_fndecl),
+    m_callee_fndecl (callee_fndecl),
+    m_state (state)
+  {
+  }
+
+  tree m_caller_fndecl;
+  tree m_callee_fndecl;
+  state_machine::state_t m_state;
+};
+
+/* For use by pending_diagnostic::describe_final_event.  */
+
+struct final_event : public event_desc
+{
+  final_event (bool colorize,
+	       tree expr, state_machine::state_t state)
+  : event_desc (colorize),
+    m_expr (expr), m_state (state)
+  {}
+
+  tree m_expr;
+  state_machine::state_t m_state;
+};
+
+} /* end of namespace evdesc */
+
+/* An abstract base class for capturing information about a diagnostic in
+   a form that is ready to emit at a later point (or be rejected).
+   Each kind of diagnostic will have a concrete subclass of
+   pending_diagnostic.
+
+   Normally, gcc diagnostics are emitted using va_list, which can't be
+   portably stored for later use, so we have to use an "emit" virtual
+   function.
+
+   This class also supports comparison, so that multiple pending_diagnostic
+   instances can be de-duplicated.
+
+   As well as emitting a diagnostic, the class has various "precision of
+   wording" virtual functions, for generating descriptions for events
+   within a diagnostic_path.  These are optional, but implementing these
+   allows for more precise wordings than the more generic
+   implementation.  */
+
+class pending_diagnostic
+{
+ public:
+  virtual ~pending_diagnostic () {}
+
+  /* Vfunc for emitting the diagnostic.  The rich_location will have been
+     populated with a diagnostic_path.
+     Return true if a diagnostic is actually emitted.  */
+  virtual bool emit (rich_location *) = 0;
+
+  /* Hand-coded RTTI: get an ID for the subclass.  */
+  virtual const char *get_kind () const = 0;
+
+  /* Compare for equality with OTHER, which might be of a different
+     subclass.  */
+
+  bool equal_p (const pending_diagnostic &other)
+  {
+    /* Check for pointer equality on the IDs from get_kind.  */
+    if (get_kind () != other.get_kind ())
+      return false;
+    /* Call vfunc now we know they have the same ID: */
+    return subclass_equal_p (other);
+  }
+
+  /* A vfunc for testing for equality, where we've already
+     checked they have the same ID.  See pending_diagnostic_subclass
+     below for a convenience subclass for implementing this.  */
+  virtual bool subclass_equal_p (const pending_diagnostic &other) const = 0;
+
+  /* For greatest precision-of-wording, the various following "describe_*"
+     virtual functions give the pending diagnostic a way to describe events
+     in a diagnostic_path in terms that make sense for that diagnostic.
+
+     In each case, return a non-NULL label_text to give the event a custom
+     description; NULL otherwise (falling back on a more generic
+     description).  */
+
+  /* Precision-of-wording vfunc for describing a critical state change
+     within the diagnostic_path.
+
+     For example, a double-free diagnostic might use the descriptions:
+     - "first 'free' happens here"
+     - "second 'free' happens here"
+     for the pertinent events, whereas a use-after-free might use the
+     descriptions:
+     - "freed here"
+     - "use after free here"
+     Note how in both cases the first event is a "free": the best
+     description to use depends on the diagnostic.  */
+
+  virtual label_text describe_state_change (const evdesc::state_change &)
+  {
+    /* Default no-op implementation.  */
+    return label_text ();
+  }
+
+  /* Precision-of-wording vfunc for describing an interprocedural call
+     carrying critial state for the diagnostic, from caller to callee.
+
+     For example a double-free diagnostic might use:
+     - "passing freed pointer 'ptr' in call to 'deallocator' from 'test'"
+     to make it clearer how the freed value moves from caller to
+     callee.  */
+
+  virtual label_text describe_call_with_state (const evdesc::call_with_state &)
+  {
+    /* Default no-op implementation.  */
+    return label_text ();
+  }
+
+  /* Precision-of-wording vfunc for describing an interprocedural return
+     within the diagnostic_path that carries critial state for the
+     diagnostic, from callee back to caller.
+
+     For example, a deref-of-unchecked-malloc diagnostic might use:
+     - "returning possibly-NULL pointer to 'make_obj' from 'allocator'"
+     to make it clearer how the unchecked value moves from callee
+     back to caller.  */
+
+  virtual label_text describe_return_of_state (const evdesc::return_of_state &)
+  {
+    /* Default no-op implementation.  */
+    return label_text ();
+  }
+
+  /* Precision-of-wording vfunc for describing the final event within a
+     diagnostic_path.
+
+     For example a double-free diagnostic might use:
+      - "second 'free' here; first 'free' was at (3)"
+     and a use-after-free might use
+      - "use after 'free' here; memory was freed at (2)".  */
+
+  virtual label_text describe_final_event (const evdesc::final_event &)
+  {
+    /* Default no-op implementation.  */
+    return label_text ();
+  }
+
+  /* End of precision-of-wording vfuncs.  */
+};
+
+/* A template to make it easier to make subclasses of pending_diagnostic.
+
+   This uses the curiously-recurring template pattern, to implement
+   pending_diagnostic::subclass_equal_p by casting and calling
+   the operator==
+
+   This assumes that BASE_OTHER has already been checked to have
+   been of the same subclass (which pending_diagnostic::equal_p does).  */
+
+template <class Subclass>
+class pending_diagnostic_subclass : public pending_diagnostic
+{
+ public:
+  bool subclass_equal_p (const pending_diagnostic &base_other) const
+    FINAL OVERRIDE
+  {
+    const Subclass &other = (const Subclass &)base_other;
+    return *(const Subclass*)this == other;
+  }
+};
+
+#endif /* GCC_ANALYZER_PENDING_DIAGNOSTIC_H */
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 19/49] analyzer: new files: analyzer-selftests.{cc|h}
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (13 preceding siblings ...)
  2019-11-16  1:18 ` [PATCH 30/49] analyzer: new files: constraint-manager.{cc|h} David Malcolm
@ 2019-11-16  1:18 ` David Malcolm
  2019-12-07 14:48   ` Jeff Law
  2019-11-16  1:18 ` [PATCH 22/49] analyzer: params.def: new parameters David Malcolm
                   ` (35 subsequent siblings)
  50 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:18 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

gcc/ChangeLog:
	* analyzer/analyzer-selftests.cc: New file.
	* analyzer/analyzer-selftests.h: New file.
---
 gcc/analyzer/analyzer-selftests.cc | 61 ++++++++++++++++++++++++++++++++++++++
 gcc/analyzer/analyzer-selftests.h  | 46 ++++++++++++++++++++++++++++
 2 files changed, 107 insertions(+)
 create mode 100644 gcc/analyzer/analyzer-selftests.cc
 create mode 100644 gcc/analyzer/analyzer-selftests.h

diff --git a/gcc/analyzer/analyzer-selftests.cc b/gcc/analyzer/analyzer-selftests.cc
new file mode 100644
index 0000000..5696af5
--- /dev/null
+++ b/gcc/analyzer/analyzer-selftests.cc
@@ -0,0 +1,61 @@
+/* Selftest support for the analyzer.
+   Copyright (C) 2019 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 "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "stringpool.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/analyzer-selftests.h"
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Build a VAR_DECL named NAME of type TYPE, simulating a file-level
+   static variable.  */
+
+tree
+build_global_decl (const char *name, tree type)
+{
+  tree decl = build_decl (UNKNOWN_LOCATION, VAR_DECL,
+			  get_identifier (name), type);
+  TREE_STATIC (decl) = 1;
+  return decl;
+}
+
+/* Run all analyzer-specific selftests.  */
+
+void
+run_analyzer_selftests ()
+{
+  analyzer_constraint_manager_cc_tests ();
+  analyzer_digraph_cc_tests ();
+  analyzer_program_point_cc_tests ();
+  analyzer_program_state_cc_tests ();
+  analyzer_region_model_cc_tests ();
+  analyzer_tristate_cc_tests ();
+}
+
+} /* end of namespace selftest.  */
+
+#endif /* #if CHECKING_P */
diff --git a/gcc/analyzer/analyzer-selftests.h b/gcc/analyzer/analyzer-selftests.h
new file mode 100644
index 0000000..1c26f25
--- /dev/null
+++ b/gcc/analyzer/analyzer-selftests.h
@@ -0,0 +1,46 @@
+/* Selftests for the analyzer.
+   Copyright (C) 2019 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/>.  */
+
+#ifndef GCC_ANALYZER_SELFTESTS_H
+#define GCC_ANALYZER_SELFTESTS_H
+
+#if CHECKING_P
+
+namespace selftest {
+
+extern tree build_global_decl (const char *name, tree type);
+
+extern void run_analyzer_selftests ();
+
+/* Declarations for specific families of tests (by source file), in
+   alphabetical order, for the checker plugin.  */
+extern void analyzer_checker_script_cc_tests ();
+extern void analyzer_constraint_manager_cc_tests ();
+extern void analyzer_digraph_cc_tests ();
+extern void analyzer_program_point_cc_tests ();
+extern void analyzer_program_state_cc_tests ();
+extern void analyzer_region_model_cc_tests ();
+extern void analyzer_tristate_cc_tests ();
+
+} /* end of namespace selftest.  */
+
+#endif /* #if CHECKING_P */
+
+#endif /* GCC_ANALYZER_SELFTESTS_H */
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 12/49] Add diagnostic paths
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (11 preceding siblings ...)
  2019-11-16  1:18 ` [PATCH 03/49] diagnostic_show_locus: move initial newline to callers David Malcolm
@ 2019-11-16  1:18 ` David Malcolm
  2019-12-07 14:45   ` Jeff Law
  2019-11-16  1:18 ` [PATCH 30/49] analyzer: new files: constraint-manager.{cc|h} David Malcolm
                   ` (37 subsequent siblings)
  50 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:18 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This patch adds support for associating a "diagnostic_path" with a
diagnostic: a sequence of events predicted by the compiler that leads to
the problem occurring, with their locations in the user's source,
text descriptions, and stack information (for handling interprocedural
paths).

For example, the following (hypothetical) error has a 3-event
intraprocedural path:

test.c: In function 'demo':
test.c:29:5: error: passing NULL as argument 1 to 'PyList_Append' which
  requires a non-NULL parameter
   29 |     PyList_Append(list, item);
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~
  'demo': events 1-3
     |
     |   25 |   list = PyList_New(0);
     |      |          ^~~~~~~~~~~~~
     |      |          |
     |      |          (1) when 'PyList_New' fails, returning NULL
     |   26 |
     |   27 |   for (i = 0; i < count; i++) {
     |      |   ~~~
     |      |   |
     |      |   (2) when 'i < count'
     |   28 |     item = PyLong_FromLong(random());
     |   29 |     PyList_Append(list, item);
     |      |     ~~~~~~~~~~~~~~~~~~~~~~~~~
     |      |     |
     |      |     (3) when calling 'PyList_Append', passing NULL from (1) as argument 1
     |

The patch adds a new "%@" format code for printing event IDs, so that
in the above, the description of event (3) mentions event (1), showing
the user where the bogus NULL value comes from (the event IDs are
colorized to draw the user's attention to them).

There is a separation between data vs presentation: the above shows how
the diagnostic-printing code has consolidated the path into a single run
of events, since all the events are near each other and within the same
function; more complicated examples (such as interprocedural paths)
might be printed as multiple runs of events.

Examples of how interprocedural paths are printed can be seen in the
test suite (which uses a plugin to exercise the code without relying
on specific warnings using this functionality).

Other output formats include
- JSON,
- printing each event as a separate "note", and
- to not emit paths.

(I have a separate script that can generate HTML from the JSON, but HTML
is not my speciality; help from a web front-end expert to make it look
good would be appreciated).

gcc/ChangeLog:
	* Makefile.in (OBJS): Add tree-diagnostic-path.o.
	* common.opt (fdiagnostics-path-format=): New option.
	(diagnostic_path_format): New enum.
	(fdiagnostics-show-path-depths): New option.
	* coretypes.h (diagnostic_event_id_t): New forward decl.
	* diagnostic-color.c (color_dict): Add "path".
	* diagnostic-event-id.h: New file.
	* diagnostic-format-json.cc (json_from_expanded_location): Make
	non-static.
	(json_end_diagnostic): Call context->make_json_for_path if it
	exists and the diagnostic has a path.
	(diagnostic_output_format_init): Clear context->print_path.
	* diagnostic-path.h: New file.
	* diagnostic-show-locus.c (colorizer::set_range): Special-case
	when printing a run of events in a diagnostic_path so that they
	all get the same color.
	(layout::m_diagnostic_path_p): New field.
	(layout::layout): Initialize it.
	(layout::print_any_labels): Don't colorize the label text for an
	event in a diagnostic_path.
	(gcc_rich_location::add_location_if_nearby): Add
	"restrict_to_current_line_spans" and "label" params.  Pass the
	former to layout.maybe_add_location_range; pass the latter
	when calling add_range.
	* diagnostic.c: Include "diagnostic-path.h".
	(diagnostic_initialize): Initialize context->path_format and
	context->show_path_depths.
	(diagnostic_show_any_path): New function.
	(diagnostic_path::interprocedural_p): New function.
	(diagnostic_report_diagnostic): Call diagnostic_show_any_path.
	(simple_diagnostic_path::num_events): New function.
	(simple_diagnostic_path::get_event): New function.
	(simple_diagnostic_path::add_event): New function.
	(simple_diagnostic_event::simple_diagnostic_event): New ctor.
	(simple_diagnostic_event::~simple_diagnostic_event): New dtor.
	(debug): New overload taking a diagnostic_path *.
	* diagnostic.def (DK_DIAGNOSTIC_PATH): New.
	* diagnostic.h (enum diagnostic_path_format): New enum.
	(json::value): New forward decl.
	(diagnostic_context::path_format): New field.
	(diagnostic_context::show_path_depths): New field.
	(diagnostic_context::print_path): New callback field.
	(diagnostic_context::make_json_for_path): New callback field.
	(diagnostic_show_any_path): New decl.
	(json_from_expanded_location): New decl.
	* doc/invoke.texi (-fdiagnostics-path-format=): New option.
	(-fdiagnostics-show-path-depths): New option.
	(-fdiagnostics-color): Add "path" to description of default
	GCC_COLORS; describe it.
	(-fdiagnostics-format=json): Document how diagnostic paths are
	represented in the JSON output format.
	* gcc-rich-location.h (gcc_rich_location::add_location_if_nearby):
	Add optional params "restrict_to_current_line_spans" and "label".
	* opts.c (common_handle_option): Handle
	OPT_fdiagnostics_path_format_ and
	OPT_fdiagnostics_show_path_depths.
	* pretty-print.c: Include "diagnostic-event-id.h".
	(pp_format): Implement "%@" format code for printing
	diagnostic_event_id_t *.
	(selftest::test_pp_format): Add tests for "%@".
	* selftest-run-tests.c (selftest::run_tests): Call
	selftest::tree_diagnostic_path_cc_tests.
	* selftest.h (selftest::tree_diagnostic_path_cc_tests): New decl.
	* toplev.c (general_init): Initialize global_dc->path_format and
	global_dc->show_path_depths.
	* tree-diagnostic-path.cc: New file.
	* tree-diagnostic.c (maybe_unwind_expanded_macro_loc): Make
	non-static.  Drop "diagnostic" param in favor of storing the
	original value of "where" and re-using it.
	(virt_loc_aware_diagnostic_finalizer): Update for dropped param of
	maybe_unwind_expanded_macro_loc.
	(tree_diagnostics_defaults): Initialize context->print_path and
	context->make_json_for_path.
	* tree-diagnostic.h (default_tree_diagnostic_path_printer): New
	decl.
	(default_tree_make_json_for_path): New decl.
	(maybe_unwind_expanded_macro_loc): New decl.

gcc/c-family/ChangeLog:
	* c-format.c (local_event_ptr_node): New.
	(PP_FORMAT_CHAR_TABLE): Add entry for "%@".
	(init_dynamic_diag_info): Initialize local_event_ptr_node.
	* c-format.h (T_EVENT_PTR): New define.

gcc/testsuite/ChangeLog:
	* gcc.dg/format/gcc_diag-10.c (diagnostic_event_id_t): New
	typedef.
	(test_diag): Add coverage of "%@".
	* gcc.dg/plugin/diagnostic-path-format-default.c: New test.
	* gcc.dg/plugin/diagnostic-path-format-inline-events-1.c: New test.
	* gcc.dg/plugin/diagnostic-path-format-inline-events-2.c: New test.
	* gcc.dg/plugin/diagnostic-path-format-inline-events-3.c: New test.
	* gcc.dg/plugin/diagnostic-path-format-none.c: New test.
	* gcc.dg/plugin/diagnostic-test-paths-1.c: New test.
	* gcc.dg/plugin/diagnostic-test-paths-2.c: New test.
	* gcc.dg/plugin/diagnostic-test-paths-3.c: New test.
	* gcc.dg/plugin/diagnostic_plugin_test_paths.c: New.
	* gcc.dg/plugin/plugin.exp: Add the new plugin and test cases.

libcpp/ChangeLog:
	* include/line-map.h (class diagnostic_path): New forward decl.
	(rich_location::get_path): New accessor.
	(rich_location::set_path): New function.
	(rich_location::m_path): New field.
	* line-map.c (rich_location::rich_location): Initialize m_path.
---
 gcc/Makefile.in                                    |   1 +
 gcc/c-family/c-format.c                            |   7 +
 gcc/c-family/c-format.h                            |   1 +
 gcc/common.opt                                     |  20 +
 gcc/coretypes.h                                    |   1 +
 gcc/diagnostic-color.c                             |   3 +-
 gcc/diagnostic-event-id.h                          |  61 ++
 gcc/diagnostic-format-json.cc                      |  10 +-
 gcc/diagnostic-path.h                              | 149 ++++
 gcc/diagnostic-show-locus.c                        |  28 +-
 gcc/diagnostic.c                                   | 126 ++++
 gcc/diagnostic.def                                 |   5 +
 gcc/diagnostic.h                                   |  30 +
 gcc/doc/invoke.texi                                | 165 ++++-
 gcc/gcc-rich-location.h                            |   4 +-
 gcc/opts.c                                         |   8 +
 gcc/pretty-print.c                                 |  32 +
 gcc/selftest-run-tests.c                           |   1 +
 gcc/selftest.h                                     |   1 +
 gcc/testsuite/gcc.dg/format/gcc_diag-10.c          |   6 +-
 .../gcc.dg/plugin/diagnostic-path-format-default.c | 142 ++++
 .../diagnostic-path-format-inline-events-1.c       | 142 ++++
 .../diagnostic-path-format-inline-events-2.c       | 154 ++++
 .../diagnostic-path-format-inline-events-3.c       | 153 ++++
 .../gcc.dg/plugin/diagnostic-path-format-none.c    |  43 ++
 .../diagnostic-path-format-separate-events.c       |  44 ++
 .../gcc.dg/plugin/diagnostic-test-paths-1.c        |  38 +
 .../gcc.dg/plugin/diagnostic-test-paths-2.c        |  56 ++
 .../gcc.dg/plugin/diagnostic-test-paths-3.c        |  38 +
 .../gcc.dg/plugin/diagnostic_plugin_test_paths.c   | 379 ++++++++++
 gcc/testsuite/gcc.dg/plugin/plugin.exp             |  10 +
 gcc/toplev.c                                       |   4 +
 gcc/tree-diagnostic-path.cc                        | 809 +++++++++++++++++++++
 gcc/tree-diagnostic.c                              |  12 +-
 gcc/tree-diagnostic.h                              |   8 +
 libcpp/include/line-map.h                          |   7 +
 libcpp/line-map.c                                  |   3 +-
 37 files changed, 2685 insertions(+), 16 deletions(-)
 create mode 100644 gcc/diagnostic-event-id.h
 create mode 100644 gcc/diagnostic-path.h
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-default.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-1.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-2.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-3.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-none.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-separate-events.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-1.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-3.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c
 create mode 100644 gcc/tree-diagnostic-path.cc

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 53d74e2..c87b00f 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1521,6 +1521,7 @@ OBJS = \
 	tree-data-ref.o \
 	tree-dfa.o \
 	tree-diagnostic.o \
+	tree-diagnostic-path.o \
 	tree-dump.o \
 	tree-eh.o \
 	tree-emutls.o \
diff --git a/gcc/c-family/c-format.c b/gcc/c-family/c-format.c
index 84475db..0e62d89 100644
--- a/gcc/c-family/c-format.c
+++ b/gcc/c-family/c-format.c
@@ -65,6 +65,7 @@ struct function_format_info
 
 /* Initialized in init_dynamic_diag_info.  */
 static GTY(()) tree local_tree_type_node;
+static GTY(()) tree local_event_ptr_node;
 static GTY(()) tree local_gimple_ptr_node;
 static GTY(()) tree local_cgraph_node_ptr_node;
 static GTY(()) tree locus;
@@ -752,6 +753,7 @@ static const format_char_info asm_fprintf_char_table[] =
   { "s",   1, STD_C89, { T89_C,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "pq", "cR", NULL }, \
   { "p",   1, STD_C89, { T89_V,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "q",  "c",  NULL }, \
   { "r",   1, STD_C89, { T89_C,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "",    "//cR",   NULL }, \
+  { "@",   1, STD_C89, { T_EVENT_PTR,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "", "\"",   NULL }, \
   { "<",   0, STD_C89, NOARGUMENTS, "",      "<",   NULL }, \
   { ">",   0, STD_C89, NOARGUMENTS, "",      ">",   NULL }, \
   { "'" ,  0, STD_C89, NOARGUMENTS, "",      "",    NULL }, \
@@ -4986,6 +4988,11 @@ init_dynamic_diag_info (void)
       || local_cgraph_node_ptr_node == void_type_node)
     local_cgraph_node_ptr_node = get_pointer_to_named_type ("cgraph_node");
 
+  /* Similar to the above but for diagnostic_event_id_t*.  */
+  if (!local_event_ptr_node
+      || local_event_ptr_node == void_type_node)
+    local_event_ptr_node = get_pointer_to_named_type ("diagnostic_event_id_t");
+
   static tree hwi;
 
   if (!hwi)
diff --git a/gcc/c-family/c-format.h b/gcc/c-family/c-format.h
index 15a3153..91b1ee0 100644
--- a/gcc/c-family/c-format.h
+++ b/gcc/c-family/c-format.h
@@ -303,6 +303,7 @@ struct format_kind_info
 #define T_V	&void_type_node
 #define T89_G   { STD_C89, NULL, &local_gimple_ptr_node }
 #define T_CGRAPH_NODE   { STD_C89, NULL, &local_cgraph_node_ptr_node }
+#define T_EVENT_PTR    { STD_C89, NULL, &local_event_ptr_node }
 #define T89_T   { STD_C89, NULL, &local_tree_type_node }
 #define T89_V	{ STD_C89, NULL, T_V }
 #define T_W	&wchar_type_node
diff --git a/gcc/common.opt b/gcc/common.opt
index 228df32..adc9931 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1338,6 +1338,26 @@ fdiagnostics-show-metadata
 Common Var(flag_diagnostics_show_metadata) Init(1)
 Print additional metadata for diagnostic messages, where available.
 
+fdiagnostics-path-format=
+Common Joined RejectNegative Var(flag_diagnostics_path_format) Enum(diagnostic_path_format) Init(DPF_INLINE_EVENTS)
+Specify how to print any control-flow path associated with a diagnostic.
+
+Enum
+Name(diagnostic_path_format) Type(int)
+
+EnumValue
+Enum(diagnostic_path_format) String(none) Value(DPF_NONE)
+
+EnumValue
+Enum(diagnostic_path_format) String(separate-events) Value(DPF_SEPARATE_EVENTS)
+
+EnumValue
+Enum(diagnostic_path_format) String(inline-events) Value(DPF_INLINE_EVENTS)
+
+fdiagnostics-show-path-depths
+Common Var(flag_diagnostics_show_path_depths) Init(0)
+Show stack depths of events in paths.
+
 fdiagnostics-minimum-margin-width=
 Common Joined UInteger Var(diagnostics_minimum_margin_width) Init(6)
 Set minimum width of left margin of source code when showing source.
diff --git a/gcc/coretypes.h b/gcc/coretypes.h
index b683f12..1c03cc0 100644
--- a/gcc/coretypes.h
+++ b/gcc/coretypes.h
@@ -153,6 +153,7 @@ struct cl_decoded_option;
 struct cl_option_handlers;
 struct diagnostic_context;
 class pretty_printer;
+class diagnostic_event_id_t;
 
 template<typename T> struct array_traits;
 
diff --git a/gcc/diagnostic-color.c b/gcc/diagnostic-color.c
index abc919f..d32cff8 100644
--- a/gcc/diagnostic-color.c
+++ b/gcc/diagnostic-color.c
@@ -90,6 +90,7 @@ static struct color_cap color_dict[] =
   { "range2", SGR_SEQ (COLOR_FG_BLUE), 6, false },
   { "locus", SGR_SEQ (COLOR_BOLD), 5, false },
   { "quote", SGR_SEQ (COLOR_BOLD), 5, false },
+  { "path", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_CYAN), 4, false },
   { "fixit-insert", SGR_SEQ (COLOR_FG_GREEN), 12, false },
   { "fixit-delete", SGR_SEQ (COLOR_FG_RED), 12, false },
   { "diff-filename", SGR_SEQ (COLOR_BOLD), 13, false },
@@ -126,7 +127,7 @@ colorize_stop (bool show_color)
 
 /* Parse GCC_COLORS.  The default would look like:
    GCC_COLORS='error=01;31:warning=01;35:note=01;36:\
-   range1=32:range2=34:locus=01:quote=01:\
+   range1=32:range2=34:locus=01:quote=01:path=01;36:\
    fixit-insert=32:fixit-delete=31:'\
    diff-filename=01:diff-hunk=32:diff-delete=31:diff-insert=32:\
    type-diff=01;32'
diff --git a/gcc/diagnostic-event-id.h b/gcc/diagnostic-event-id.h
new file mode 100644
index 0000000..7f0f4e4
--- /dev/null
+++ b/gcc/diagnostic-event-id.h
@@ -0,0 +1,61 @@
+/* A class for referring to events within a diagnostic_path.
+   Copyright (C) 2019 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/>.  */
+
+#ifndef GCC_DIAGNOSTIC_EVENT_ID_H
+#define GCC_DIAGNOSTIC_EVENT_ID_H
+
+/* A class for referring to events within a diagnostic_path.
+
+   They are stored as 0-based offsets into the events, but
+   printed (e.g. via %@) as 1-based numbers.
+
+   For example, a 3-event path has event offsets 0, 1, and 2,
+   which would be shown to the user as "(1)", "(2)" and "(3)".
+
+   This has its own header so that pretty-print.c can use this
+   to implement "%@" without bringing in all of diagnostic_path
+   (which e.g. refers to "tree").  */
+
+class diagnostic_event_id_t
+{
+ public:
+  diagnostic_event_id_t () : m_index (UNKNOWN_EVENT_IDX) {}
+  diagnostic_event_id_t (int zero_based_idx) : m_index (zero_based_idx) {}
+
+  bool known_p () const { return m_index != UNKNOWN_EVENT_IDX; }
+
+  int one_based () const
+  {
+    gcc_assert (known_p ());
+    return m_index + 1;
+  }
+
+ private:
+  static const int UNKNOWN_EVENT_IDX = -1;
+  int m_index; // zero-based
+};
+
+/* A pointer to a diagnostic_event_id_t, for use with the "%@" format
+   code, which will print a 1-based representation for it, with suitable
+   colorization, e.g. "(1)".
+   The %@ format code requires that known_p be true for the event ID. */
+typedef diagnostic_event_id_t *diagnostic_event_id_ptr;
+
+#endif /* ! GCC_DIAGNOSTIC_EVENT_ID_H */
diff --git a/gcc/diagnostic-format-json.cc b/gcc/diagnostic-format-json.cc
index 200e844..c9beef9 100644
--- a/gcc/diagnostic-format-json.cc
+++ b/gcc/diagnostic-format-json.cc
@@ -42,7 +42,7 @@ static json::array *cur_children_array;
 
 /* Generate a JSON object for LOC.  */
 
-static json::object *
+json::value *
 json_from_expanded_location (location_t loc)
 {
   expanded_location exploc = expand_location (loc);
@@ -231,6 +231,13 @@ json_end_diagnostic (diagnostic_context *context, diagnostic_info *diagnostic,
       json::object *metadata_obj = json_from_metadata (diagnostic->metadata);
       diag_obj->set ("metadata", metadata_obj);
     }
+
+  const diagnostic_path *path = richloc->get_path ();
+  if (path && context->make_json_for_path)
+    {
+      json::value *path_value = context->make_json_for_path (context, path);
+      diag_obj->set ("path", path_value);
+    }
 }
 
 /* No-op implementation of "begin_group_cb" for JSON output.  */
@@ -287,6 +294,7 @@ diagnostic_output_format_init (diagnostic_context *context,
 	context->begin_group_cb = json_begin_group;
 	context->end_group_cb =  json_end_group;
 	context->final_cb =  json_final_cb;
+	context->print_path = NULL; /* handled in json_end_diagnostic.  */
 
 	/* The metadata is handled in JSON format, rather than as text.  */
 	context->show_metadata = false;
diff --git a/gcc/diagnostic-path.h b/gcc/diagnostic-path.h
new file mode 100644
index 0000000..de7d133
--- /dev/null
+++ b/gcc/diagnostic-path.h
@@ -0,0 +1,149 @@
+/* Paths through the code associated with a diagnostic.
+   Copyright (C) 2019 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/>.  */
+
+#ifndef GCC_DIAGNOSTIC_PATH_H
+#define GCC_DIAGNOSTIC_PATH_H
+
+#include "diagnostic.h" /* for ATTRIBUTE_GCC_DIAG.  */
+#include "diagnostic-event-id.h"
+
+/* A diagnostic_path is an optional additional piece of metadata associated
+   with a diagnostic (via its rich_location).
+
+   It describes a sequence of events predicted by the compiler that
+   lead to the problem occurring, with their locations in the user's source,
+   and text descriptions.
+
+   For example, the following error has a 3-event path:
+
+     test.c: In function 'demo':
+     test.c:29:5: error: passing NULL as argument 1 to 'PyList_Append' which
+       requires a non-NULL parameter
+        29 |     PyList_Append(list, item);
+           |     ^~~~~~~~~~~~~~~~~~~~~~~~~
+       'demo': events 1-3
+          |
+          |   25 |   list = PyList_New(0);
+          |      |          ^~~~~~~~~~~~~
+          |      |          |
+          |      |          (1) when 'PyList_New' fails, returning NULL
+          |   26 |
+          |   27 |   for (i = 0; i < count; i++) {
+          |      |   ~~~
+          |      |   |
+          |      |   (2) when 'i < count'
+          |   28 |     item = PyLong_FromLong(random());
+          |   29 |     PyList_Append(list, item);
+          |      |     ~~~~~~~~~~~~~~~~~~~~~~~~~
+          |      |     |
+          |      |     (3) when calling 'PyList_Append', passing NULL from (1) as argument 1
+          |
+
+    The diagnostic-printing code has consolidated the path into a single
+    run of events, since all the events are near each other and within the same
+    function; more complicated examples (such as interprocedural paths)
+    might be printed as multiple runs of events.  */
+
+/* Abstract base classes, describing events within a path, and the paths
+   themselves.  */
+
+/* One event within a diagnostic_path.  */
+
+class diagnostic_event
+{
+ public:
+  virtual ~diagnostic_event () {}
+
+  virtual location_t get_location () const = 0;
+
+  virtual tree get_fndecl () const = 0;
+
+  /* Stack depth, so that consumers can visualizes the interprocedural
+     calls, returns, and frame nesting.  */
+  virtual int get_stack_depth () const = 0;
+
+  /* Get a localized (and possibly colorized) description of this event.  */
+  virtual label_text get_desc (bool can_colorize) const = 0;
+};
+
+/* Abstract base class for getting at a sequence of events.  */
+
+class diagnostic_path
+{
+ public:
+  virtual ~diagnostic_path () {}
+  virtual unsigned num_events () const = 0;
+  virtual const diagnostic_event & get_event (int idx) const = 0;
+
+  bool interprocedural_p () const;
+};
+
+/* Concrete subclasses.  */
+
+/* A simple implementation of diagnostic_event.  */
+
+class simple_diagnostic_event : public diagnostic_event
+{
+ public:
+  simple_diagnostic_event (location_t loc, tree fndecl, int depth,
+			   const char *desc);
+  ~simple_diagnostic_event ();
+
+  location_t get_location () const FINAL OVERRIDE { return m_loc; }
+  tree get_fndecl () const FINAL OVERRIDE { return m_fndecl; }
+  int get_stack_depth () const FINAL OVERRIDE { return m_depth; }
+  label_text get_desc (bool) const FINAL OVERRIDE
+  {
+    return label_text::borrow (m_desc);
+  }
+
+ private:
+  location_t m_loc;
+  tree m_fndecl;
+  int m_depth;
+  char *m_desc; // has been i18n-ed and formatted
+};
+
+/* A simple implementation of diagnostic_path, as a vector of
+   simple_diagnostic_event instances.  */
+
+class simple_diagnostic_path : public diagnostic_path
+{
+ public:
+  simple_diagnostic_path (pretty_printer *event_pp)
+  : m_event_pp (event_pp) {}
+
+  unsigned num_events () const FINAL OVERRIDE;
+  const diagnostic_event & get_event (int idx) const FINAL OVERRIDE;
+
+  diagnostic_event_id_t add_event (location_t loc, tree fndecl, int depth,
+				   const char *fmt, ...)
+    ATTRIBUTE_GCC_DIAG(5,6);
+
+ private:
+  auto_delete_vec<simple_diagnostic_event> m_events;
+
+  /* (for use by add_event).  */
+  pretty_printer *m_event_pp;
+};
+
+extern void debug (diagnostic_path *path);
+
+#endif /* ! GCC_DIAGNOSTIC_PATH_H */
diff --git a/gcc/diagnostic-show-locus.c b/gcc/diagnostic-show-locus.c
index ec5fdc0..3cd385c 100644
--- a/gcc/diagnostic-show-locus.c
+++ b/gcc/diagnostic-show-locus.c
@@ -86,7 +86,17 @@ class colorizer
 	     diagnostic_t diagnostic_kind);
   ~colorizer ();
 
-  void set_range (int range_idx) { set_state (range_idx); }
+  void set_range (int range_idx)
+  {
+    /* Normally we emphasize the primary location, then alternate between
+       two colors for the secondary locations.
+       But if we're printing a run of events in a diagnostic path, that
+       makes no sense, so print all of them with the same colorization.  */
+    if (m_diagnostic_kind == DK_DIAGNOSTIC_PATH)
+      set_state (0);
+    else
+      set_state (range_idx);
+  }
   void set_normal_text () { set_state (STATE_NORMAL_TEXT); }
   void set_fixit_insert () { set_state (STATE_FIXIT_INSERT); }
   void set_fixit_delete () { set_state (STATE_FIXIT_DELETE); }
@@ -304,6 +314,7 @@ class layout
   bool m_show_labels_p;
   bool m_show_line_numbers_p;
   bool m_use_nn_for_line_numbers_p;
+  bool m_diagnostic_path_p;
   auto_vec <layout_range> m_layout_ranges;
   auto_vec <const fixit_hint *> m_fixit_hints;
   auto_vec <line_span> m_line_spans;
@@ -851,6 +862,7 @@ layout::layout (diagnostic_context * context,
   m_show_labels_p (context->show_labels_p),
   m_show_line_numbers_p (context->show_line_numbers_p),
   m_use_nn_for_line_numbers_p (context->use_nn_for_line_numbers_p),
+  m_diagnostic_path_p (diagnostic_kind == DK_DIAGNOSTIC_PATH),
   m_layout_ranges (richloc->get_num_locations ()),
   m_fixit_hints (richloc->get_num_fixit_hints ()),
   m_line_spans (1 + richloc->get_num_locations ()),
@@ -1571,7 +1583,10 @@ layout::print_any_labels (linenum_type row)
 	      {
 		gcc_assert (column <= label->m_column);
 		move_to_column (&column, label->m_column, true);
-		m_colorizer.set_range (label->m_state_idx);
+		/* Colorize the text, unless it's for events in a
+		   diagnostic_path.  */
+		if (!m_diagnostic_path_p)
+		  m_colorizer.set_range (label->m_state_idx);
 		pp_string (m_pp, label->m_text.m_buffer);
 		m_colorizer.set_normal_text ();
 		column += label->m_length;
@@ -2264,7 +2279,9 @@ layout::print_line (linenum_type row)
    Otherwise return false.  */
 
 bool
-gcc_rich_location::add_location_if_nearby (location_t loc)
+gcc_rich_location::add_location_if_nearby (location_t loc,
+					   bool restrict_to_current_line_spans,
+					   const range_label *label)
 {
   /* Use the layout location-handling logic to sanitize LOC,
      filtering it to the current line spans within a temporary
@@ -2273,10 +2290,11 @@ gcc_rich_location::add_location_if_nearby (location_t loc)
   location_range loc_range;
   loc_range.m_loc = loc;
   loc_range.m_range_display_kind = SHOW_RANGE_WITHOUT_CARET;
-  if (!layout.maybe_add_location_range (&loc_range, 0, true))
+  if (!layout.maybe_add_location_range (&loc_range, 0,
+					restrict_to_current_line_spans))
     return false;
 
-  add_range (loc);
+  add_range (loc, SHOW_RANGE_WITHOUT_CARET, label);
   return true;
 }
 
diff --git a/gcc/diagnostic.c b/gcc/diagnostic.c
index e8d0613..a645946 100644
--- a/gcc/diagnostic.c
+++ b/gcc/diagnostic.c
@@ -33,6 +33,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "diagnostic-color.h"
 #include "diagnostic-url.h"
 #include "diagnostic-metadata.h"
+#include "diagnostic-path.h"
 #include "edit-context.h"
 #include "selftest.h"
 #include "selftest-diagnostic.h"
@@ -187,6 +188,8 @@ diagnostic_initialize (diagnostic_context *context, int n_opts)
   for (i = 0; i < rich_location::STATICALLY_ALLOCATED_RANGES; i++)
     context->caret_chars[i] = '^';
   context->show_metadata = false;
+  context->path_format = DPF_NONE;
+  context->show_path_depths = false;
   context->show_option_requested = false;
   context->abort_on_error = false;
   context->show_column = false;
@@ -659,6 +662,38 @@ diagnostic_report_current_module (diagnostic_context *context, location_t where)
     }
 }
 
+/* If DIAGNOSTIC has a diagnostic_path and CONTEXT supports printing paths,
+   print the path.  */
+
+void
+diagnostic_show_any_path (diagnostic_context *context,
+			  diagnostic_info *diagnostic)
+{
+  const diagnostic_path *path = diagnostic->richloc->get_path ();
+  if (!path)
+    return;
+
+  if (context->print_path)
+    context->print_path (context, path);
+}
+
+/* Return true if the events in this path involve more than one
+   function, or false if it is purely intraprocedural.  */
+
+bool
+diagnostic_path::interprocedural_p () const
+{
+  const unsigned num = num_events ();
+  for (unsigned i = 0; i < num; i++)
+    {
+      if (get_event (i).get_fndecl () != get_event (0).get_fndecl ())
+	return true;
+      if (get_event (i).get_stack_depth () != get_event (0).get_stack_depth ())
+	return true;
+    }
+  return false;
+}
+
 void
 default_diagnostic_starter (diagnostic_context *context,
 			    diagnostic_info *diagnostic)
@@ -1124,6 +1159,8 @@ diagnostic_report_diagnostic (diagnostic_context *context,
 
   context->lock--;
 
+  diagnostic_show_any_path (context, diagnostic);
+
   return true;
 }
 
@@ -1765,6 +1802,95 @@ auto_diagnostic_group::~auto_diagnostic_group ()
     }
 }
 
+/* Implementation of diagnostic_path::num_events vfunc for
+   simple_diagnostic_path: simply get the number of events in the vec.  */
+
+unsigned
+simple_diagnostic_path::num_events () const
+{
+  return m_events.length ();
+}
+
+/* Implementation of diagnostic_path::get_event vfunc for
+   simple_diagnostic_path: simply return the event in the vec.  */
+
+const diagnostic_event &
+simple_diagnostic_path::get_event (int idx) const
+{
+  return *m_events[idx];
+}
+
+/* Add an event to this path at LOC within function FNDECL at
+   stack depth DEPTH.
+
+   Use m_context's printer to format FMT, as the text of the new
+   event.
+
+   Return the id of the new event.  */
+
+diagnostic_event_id_t
+simple_diagnostic_path::add_event (location_t loc, tree fndecl, int depth,
+				   const char *fmt, ...)
+{
+  pretty_printer *pp = m_event_pp;
+  pp_clear_output_area (pp);
+
+  text_info ti;
+  rich_location rich_loc (line_table, UNKNOWN_LOCATION);
+
+  va_list ap;
+
+  va_start (ap, fmt);
+
+  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);
+
+  simple_diagnostic_event *new_event
+    = new simple_diagnostic_event (loc, fndecl, depth, pp_formatted_text (pp));
+  m_events.safe_push (new_event);
+
+  pp_clear_output_area (pp);
+
+  return diagnostic_event_id_t (m_events.length () - 1);
+}
+
+/* struct simple_diagnostic_event.  */
+
+/* simple_diagnostic_event's ctor.  */
+
+simple_diagnostic_event::simple_diagnostic_event (location_t loc,
+						  tree fndecl,
+						  int depth,
+						  const char *desc)
+: m_loc (loc), m_fndecl (fndecl), m_depth (depth), m_desc (xstrdup (desc))
+{
+}
+
+/* simple_diagnostic_event's dtor.  */
+
+simple_diagnostic_event::~simple_diagnostic_event ()
+{
+  free (m_desc);
+}
+
+/* Print PATH by emitting a dummy "note" associated with it.  */
+
+DEBUG_FUNCTION
+void debug (diagnostic_path *path)
+{
+  rich_location richloc (line_table, UNKNOWN_LOCATION);
+  richloc.set_path (path);
+  inform (&richloc, "debug path");
+}
+
 /* Really call the system 'abort'.  This has to go right at the end of
    this file, so that there are no functions after it that call abort
    and get the system abort instead of our macro.  */
diff --git a/gcc/diagnostic.def b/gcc/diagnostic.def
index f3c0b2c..b271ea4 100644
--- a/gcc/diagnostic.def
+++ b/gcc/diagnostic.def
@@ -38,6 +38,11 @@ DEFINE_DIAGNOSTIC_KIND (DK_WARNING, "warning: ", "warning")
 DEFINE_DIAGNOSTIC_KIND (DK_ANACHRONISM, "anachronism: ", "warning")
 DEFINE_DIAGNOSTIC_KIND (DK_NOTE, "note: ", "note")
 DEFINE_DIAGNOSTIC_KIND (DK_DEBUG, "debug: ", "note")
+
+/* For use when using the diagnostic_show_locus machinery to show
+   a range of events within a path.  */
+DEFINE_DIAGNOSTIC_KIND (DK_DIAGNOSTIC_PATH, "path: ", "path")
+
 /* These two would be re-classified as DK_WARNING or DK_ERROR, so the
 prefix does not matter.  */
 DEFINE_DIAGNOSTIC_KIND (DK_PEDWARN, "pedwarn: ", NULL)
diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
index 0e4b6ad..fcf981e 100644
--- a/gcc/diagnostic.h
+++ b/gcc/diagnostic.h
@@ -35,6 +35,23 @@ enum diagnostics_output_format
   DIAGNOSTICS_OUTPUT_FORMAT_JSON
 };
 
+/* An enum for controlling how diagnostic_paths should be printed.  */
+enum diagnostic_path_format
+{
+  /* Don't print diagnostic_paths.  */
+  DPF_NONE,
+
+  /* Print diagnostic_paths by emitting a separate "note" for every event
+     in the path.  */
+  DPF_SEPARATE_EVENTS,
+
+  /* Print diagnostic_paths by consolidating events together where they
+     are close enough, and printing such runs of events with multiple
+     calls to diagnostic_show_locus, showing the individual events in
+     each run via labels in the source.  */
+  DPF_INLINE_EVENTS
+};
+
 /* A diagnostic is described by the MESSAGE to send, the FILE and LINE of
    its context and its KIND (ice, error, warning, note, ...)  See complete
    list in diagnostic.def.  */
@@ -80,6 +97,7 @@ typedef void (*diagnostic_finalizer_fn) (diagnostic_context *,
 					 diagnostic_t);
 
 class edit_context;
+namespace json { class value; }
 
 /* This data structure bundles altogether any information relevant to
    the context of a diagnostic message.  */
@@ -133,6 +151,12 @@ struct diagnostic_context
   /* True if we should print any metadata associated with diagnostics.  */
   bool show_metadata;
 
+  /* How should diagnostic_path objects be printed.  */
+  enum diagnostic_path_format path_format;
+
+  /* True if we should print stack depths when printing diagnostic paths.  */
+  bool show_path_depths;
+
   /* True if we should print the command line option which controls
      each diagnostic, if known.  */
   bool show_option_requested;
@@ -207,6 +231,9 @@ struct diagnostic_context
      particular option.  */
   char *(*get_option_url) (diagnostic_context *, int);
 
+  void (*print_path) (diagnostic_context *, const diagnostic_path *);
+  json::value *(*make_json_for_path) (diagnostic_context *, const diagnostic_path *);
+
   /* Auxiliary data for client.  */
   void *x_data;
 
@@ -354,6 +381,7 @@ extern void diagnostic_report_current_module (diagnostic_context *, location_t);
 extern void diagnostic_show_locus (diagnostic_context *,
 				   rich_location *richloc,
 				   diagnostic_t diagnostic_kind);
+extern void diagnostic_show_any_path (diagnostic_context *, diagnostic_info *);
 
 /* Force diagnostics controlled by OPTIDX to be kind KIND.  */
 extern diagnostic_t diagnostic_classify_diagnostic (diagnostic_context *,
@@ -445,4 +473,6 @@ extern void diagnostic_output_format_init (diagnostic_context *,
 /* Compute the number of digits in the decimal representation of an integer.  */
 extern int num_digits (int, bool use_nn_p = false);
 
+extern json::value *json_from_expanded_location (location_t loc);
+
 #endif /* ! GCC_DIAGNOSTIC_H */
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 209e2c6..08d9870 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -283,6 +283,8 @@ Objective-C and Objective-C++ Dialects}.
 -fdiagnostics-minimum-margin-width=@var{width} @gol
 -fdiagnostics-parseable-fixits  -fdiagnostics-generate-patch @gol
 -fdiagnostics-show-template-tree  -fno-elide-type @gol
+-fdiagnostics-path-format=@r{[}none@r{|}separate-events@r{|}inline-events@r{]} @gol
+-fdiagnostics-show-path-depths @gol
 -fno-show-column}
 
 @item Warning Options
@@ -3893,7 +3895,7 @@ for 88-color and 256-color modes background colors.
 The default @env{GCC_COLORS} is
 @smallexample
 error=01;31:warning=01;35:note=01;36:range1=32:range2=34:locus=01:\
-quote=01:fixit-insert=32:fixit-delete=31:\
+quote=01:path=01;36:fixit-insert=32:fixit-delete=31:\
 diff-filename=01:diff-hunk=32:diff-delete=31:diff-insert=32:\
 type-diff=01;32
 @end smallexample
@@ -3917,6 +3919,12 @@ SGR substring for warning: markers.
 @vindex note GCC_COLORS @r{capability}
 SGR substring for note: markers.
 
+@item path=
+@vindex path GCC_COLORS @r{capability}
+SGR substring for colorizing paths of control-flow events as printed
+via @option{-fdiagnostics-path-format=}, such as the identifiers of
+individual events and lines indicating interprocedural calls and returns.
+
 @item range1=
 @vindex range1 GCC_COLORS @r{capability}
 SGR substring for first additional range.
@@ -4131,6 +4139,114 @@ Specifying the @option{-fno-elide-type} flag suppresses that behavior.
 This flag also affects the output of the
 @option{-fdiagnostics-show-template-tree} flag.
 
+@item -fdiagnostics-path-format=@var{KIND}
+@opindex fdiagnostics-path-format
+Specify how to print paths of control-flow events for diagnostics that
+have such a path associated with them.
+
+@var{KIND} is @samp{none}, @samp{separate-events}, or @samp{inline-events},
+the default.
+
+@samp{none} means to not print diagnostic paths.
+
+@samp{separate-events} means to print a separate ``note'' diagnostic for
+each event within the diagnostic.  For example:
+
+@smallexample
+test.c:29:5: error: passing NULL as argument 1 to 'PyList_Append' which requires a non-NULL parameter
+test.c:25:10: note: (1) when 'PyList_New' fails, returning NULL
+test.c:27:3: note: (2) when 'i < count'
+test.c:29:5: note: (3) when calling 'PyList_Append', passing NULL from (1) as argument 1
+@end smallexample
+
+@samp{inline-events} means to print the events ``inline'' within the source
+code.  This view attempts to consolidate the events into runs of
+sufficiently-close events, printing them as labelled ranges within the source.
+
+For example, the same events as above might be printed as:
+
+@smallexample
+  'test': events 1-3
+    |
+    |   25 |   list = PyList_New(0);
+    |      |          ^~~~~~~~~~~~~
+    |      |          |
+    |      |          (1) when 'PyList_New' fails, returning NULL
+    |   26 |
+    |   27 |   for (i = 0; i < count; i++) @{
+    |      |   ~~~
+    |      |   |
+    |      |   (2) when 'i < count'
+    |   28 |     item = PyLong_FromLong(random());
+    |   29 |     PyList_Append(list, item);
+    |      |     ~~~~~~~~~~~~~~~~~~~~~~~~~
+    |      |     |
+    |      |     (3) when calling 'PyList_Append', passing NULL from (1) as argument 1
+    |
+@end smallexample
+
+Interprocedural control flow is shown by grouping the events by stack frame,
+and using indentation to show how stack frames are nested, pushed, and popped.
+
+For example:
+
+@smallexample
+  'test': events 1-2
+    |
+    |  133 | @{
+    |      | ^
+    |      | |
+    |      | (1) entering 'test'
+    |  134 |   boxed_int *obj = make_boxed_int (i);
+    |      |                    ~~~~~~~~~~~~~~~~~~
+    |      |                    |
+    |      |                    (2) calling 'make_boxed_int'
+    |
+    +--> 'make_boxed_int': events 3-4
+           |
+           |  120 | @{
+           |      | ^
+           |      | |
+           |      | (3) entering 'make_boxed_int'
+           |  121 |   boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+           |      |                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+           |      |                                    |
+           |      |                                    (4) calling 'wrapped_malloc'
+           |
+           +--> 'wrapped_malloc': events 5-6
+                  |
+                  |    7 | @{
+                  |      | ^
+                  |      | |
+                  |      | (5) entering 'wrapped_malloc'
+                  |    8 |   return malloc (size);
+                  |      |          ~~~~~~~~~~~~~
+                  |      |          |
+                  |      |          (6) calling 'malloc'
+                  |
+    <-------------+
+    |
+ 'test': event 7
+    |
+    |  138 |   free_boxed_int (obj);
+    |      |   ^~~~~~~~~~~~~~~~~~~~
+    |      |   |
+    |      |   (7) calling 'free_boxed_int'
+    |
+(etc)
+@end smallexample
+
+@item -fdiagnostics-show-path-depths
+@opindex fdiagnostics-show-path-depths
+This option provides additional information when printing control-flow paths
+associated with a diagnostic.
+
+If this is option is provided then the stack depth will be printed for
+each run of events within @option{-fdiagnostics-path-format=separate-events}.
+
+This is intended for use by GCC developers and plugin developers when
+debugging diagnostics that report interprocedural control flow.
+
 @item -fno-show-column
 @opindex fno-show-column
 @opindex fshow-column
@@ -4326,6 +4442,53 @@ to but not including @code{next} with @code{string}'s value.  Deletions
 are expressed via an empty value for @code{string}, insertions by
 having @code{start} equal @code{next}.
 
+If the diagnostic has a path of control-flow events associated with it,
+it has a @code{path} array of objects representing the events.  Each
+event object has a @code{description} string, a @code{location} object,
+along with a @code{function} string and a @code{depth} number for
+representing interprocedural paths.  The @code{function} represents the
+current function at that event, and the @code{depth} represents the
+stack depth relative to some baseline: the higher, the more frames are
+within the stack.
+
+For example, the intraprocedural example shown for
+@option{-fdiagnostics-path-format=} might have this JSON for its path:
+
+@smallexample
+    "path": [
+        @{
+            "depth": 0,
+            "description": "when 'PyList_New' fails, returning NULL",
+            "function": "test",
+            "location": @{
+                "column": 10,
+                "file": "test.c",
+                "line": 25
+            @}
+        @},
+        @{
+            "depth": 0,
+            "description": "when 'i < count'",
+            "function": "test",
+            "location": @{
+                "column": 3,
+                "file": "test.c",
+                "line": 27
+            @}
+        @},
+        @{
+            "depth": 0,
+            "description": "when calling 'PyList_Append', passing NULL from (1) as argument 1",
+            "function": "test",
+            "location": @{
+                "column": 5,
+                "file": "test.c",
+                "line": 29
+            @}
+        @}
+    ]
+@end smallexample
+
 @end table
 
 @node Warning Options
diff --git a/gcc/gcc-rich-location.h b/gcc/gcc-rich-location.h
index 71f4f3d..4de697a 100644
--- a/gcc/gcc-rich-location.h
+++ b/gcc/gcc-rich-location.h
@@ -62,7 +62,9 @@ class gcc_rich_location : public rich_location
 
      Implemented in diagnostic-show-locus.c.  */
 
-  bool add_location_if_nearby (location_t loc);
+  bool add_location_if_nearby (location_t loc,
+			       bool restrict_to_current_line_spans = true,
+			       const range_label *label = NULL);
 
   /* Add a fix-it hint suggesting the insertion of CONTENT before
      INSERTION_POINT.
diff --git a/gcc/opts.c b/gcc/opts.c
index 0caaf04..b3436b2 100644
--- a/gcc/opts.c
+++ b/gcc/opts.c
@@ -2495,6 +2495,14 @@ common_handle_option (struct gcc_options *opts,
       dc->show_metadata = value;
       break;
 
+    case OPT_fdiagnostics_path_format_:
+      dc->path_format = (enum diagnostic_path_format)value;
+      break;
+
+    case OPT_fdiagnostics_show_path_depths:
+      dc->show_path_depths = value;
+      break;
+
     case OPT_fdiagnostics_show_option:
       dc->show_option_requested = value;
       break;
diff --git a/gcc/pretty-print.c b/gcc/pretty-print.c
index 89242ed..d5aa451 100644
--- a/gcc/pretty-print.c
+++ b/gcc/pretty-print.c
@@ -24,6 +24,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "intl.h"
 #include "pretty-print.h"
 #include "diagnostic-color.h"
+#include "diagnostic-event-id.h"
 #include "selftest.h"
 
 #if HAVE_ICONV
@@ -989,6 +990,7 @@ pp_indent (pretty_printer *pp)
    %>: closing quote.
    %': apostrophe (should only be used in untranslated messages;
        translations should use appropriate punctuation directly).
+   %@: diagnostic_event_id_ptr, for which event_id->known_p () must be true.
    %.*s: a substring the length of which is specified by an argument
 	 integer.
    %Ns: likewise, but length specified as constant in the format string.
@@ -1378,6 +1380,21 @@ pp_format (pretty_printer *pp, text_info *text)
 	  }
 	  break;
 
+	case '@':
+	  {
+	    /* diagnostic_event_id_t *.  */
+	    diagnostic_event_id_ptr event_id
+	      = va_arg (*text->args_ptr, diagnostic_event_id_ptr);
+	    gcc_assert (event_id->known_p ());
+
+	    pp_string (pp, colorize_start (pp_show_color (pp), "path"));
+	    pp_character (pp, '(');
+	    pp_decimal_int (pp, event_id->one_based ());
+	    pp_character (pp, ')');
+	    pp_string (pp, colorize_stop (pp_show_color (pp)));
+	  }
+	  break;
+
 	default:
 	  {
 	    bool ok;
@@ -2269,6 +2286,21 @@ test_pp_format ()
   assert_pp_format_colored (SELFTEST_LOCATION,
 			    "`\33[01m\33[Kfoo\33[m\33[K' 12345678", "%qs %x",
 			    "foo", 0x12345678);
+  /* Verify "%@".  */
+  {
+    diagnostic_event_id_t first (2);
+    diagnostic_event_id_t second (7);
+
+    ASSERT_PP_FORMAT_2 ("first `free' at (3); second `free' at (8)",
+			"first %<free%> at %@; second %<free%> at %@",
+			&first, &second);
+    assert_pp_format_colored
+      (SELFTEST_LOCATION,
+       "first `^[[01m^[[Kfree^[[m^[[K' at ^[[01;36m^[[K(3)^[[m^[[K;"
+       " second `^[[01m^[[Kfree^[[m^[[K' at ^[[01;36m^[[K(8)^[[m^[[K",
+       "first %<free%> at %@; second %<free%> at %@",
+       &first, &second);
+  }
 
   /* Verify %Z.  */
   int v[] = { 1, 2, 3 }; 
diff --git a/gcc/selftest-run-tests.c b/gcc/selftest-run-tests.c
index 6ed7d82..92c8de7 100644
--- a/gcc/selftest-run-tests.c
+++ b/gcc/selftest-run-tests.c
@@ -96,6 +96,7 @@ selftest::run_tests ()
   spellcheck_c_tests ();
   spellcheck_tree_c_tests ();
   tree_cfg_c_tests ();
+  tree_diagnostic_path_cc_tests ();
   attribute_c_tests ();
 
   /* This one relies on most of the above.  */
diff --git a/gcc/selftest.h b/gcc/selftest.h
index 6f2c2af..165dfff 100644
--- a/gcc/selftest.h
+++ b/gcc/selftest.h
@@ -248,6 +248,7 @@ extern void sreal_c_tests ();
 extern void store_merging_c_tests ();
 extern void tree_c_tests ();
 extern void tree_cfg_c_tests ();
+extern void tree_diagnostic_path_cc_tests ();
 extern void typed_splay_tree_c_tests ();
 extern void unique_ptr_tests_cc_tests ();
 extern void vec_c_tests ();
diff --git a/gcc/testsuite/gcc.dg/format/gcc_diag-10.c b/gcc/testsuite/gcc.dg/format/gcc_diag-10.c
index ba2629b..a2f99fe 100644
--- a/gcc/testsuite/gcc.dg/format/gcc_diag-10.c
+++ b/gcc/testsuite/gcc.dg/format/gcc_diag-10.c
@@ -22,6 +22,9 @@ typedef struct gimple gimple;
 /* Likewise for gimple.  */
 typedef struct cgraph_node cgraph_node;
 
+/* Likewise for diagnostic_event_id_t.  */
+typedef struct diagnostic_event_id_t diagnostic_event_id_t;
+
 #define FORMAT(kind) __attribute__ ((format (__gcc_## kind ##__, 1, 2)))
 
 void diag (const char*, ...) FORMAT (diag);
@@ -30,7 +33,7 @@ void tdiag (const char*, ...) FORMAT (tdiag);
 void cxxdiag (const char*, ...) FORMAT (cxxdiag);
 void dump (const char*, ...) FORMAT (dump_printf);
 
-void test_diag (tree t, gimple *gc)
+void test_diag (tree t, gimple *gc, diagnostic_event_id_t *event_id_ptr)
 {
   diag ("%<");   /* { dg-warning "unterminated quoting directive" } */
   diag ("%>");   /* { dg-warning "unmatched quoting directive " } */
@@ -38,6 +41,7 @@ void test_diag (tree t, gimple *gc)
 
   diag ("%G", gc); /* { dg-warning "format" } */
   diag ("%K", t); /* { dg-warning "format" } */
+  diag ("%@", event_id_ptr);
 
   diag ("%R");       /* { dg-warning "unmatched color reset directive" } */
   diag ("%r", "");   /* { dg-warning "unterminated color directive" } */
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-default.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-default.c
new file mode 100644
index 0000000..5712dbd
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-default.c
@@ -0,0 +1,142 @@
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-show-caret" } */
+
+#include <stdlib.h>
+
+void *wrapped_malloc (size_t size)
+{
+  return malloc (size);
+}
+
+void wrapped_free (void *ptr)
+{
+  free (ptr); /* { dg-warning "double-free of 'ptr' \\\[CWE-415\\]" } */
+  /* { dg-begin-multiline-output "" }
+   free (ptr);
+   ^~~~~~~~~~
+  'test': events 1-2
+    |
+    | {
+    | ^
+    | |
+    | (1) entering 'test'
+    |   boxed_int *obj = make_boxed_int (i);
+    |                    ~~~~~~~~~~~~~~~~~~
+    |                    |
+    |                    (2) calling 'make_boxed_int'
+    |
+    +--> 'make_boxed_int': events 3-4
+           |
+           | {
+           | ^
+           | |
+           | (3) entering 'make_boxed_int'
+           |   boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+           |                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+           |                                    |
+           |                                    (4) calling 'wrapped_malloc'
+           |
+           +--> 'wrapped_malloc': events 5-6
+                  |
+                  | {
+                  | ^
+                  | |
+                  | (5) entering 'wrapped_malloc'
+                  |   return malloc (size);
+                  |          ~~~~~~~~~~~~~
+                  |          |
+                  |          (6) calling 'malloc'
+                  |
+    <-------------+
+    |
+  'test': event 7
+    |
+    |   free_boxed_int (obj);
+    |   ^~~~~~~~~~~~~~~~~~~~
+    |   |
+    |   (7) calling 'free_boxed_int'
+    |
+    +--> 'free_boxed_int': events 8-9
+           |
+           | {
+           | ^
+           | |
+           | (8) entering 'free_boxed_int'
+           |   wrapped_free (bi);
+           |   ~~~~~~~~~~~~~~~~~
+           |   |
+           |   (9) calling 'wrapped_free'
+           |
+           +--> 'wrapped_free': events 10-11
+                  |
+                  | {
+                  | ^
+                  | |
+                  | (10) entering 'wrapped_free'
+                  |   free (ptr);
+                  |   ~~~~~~~~~~
+                  |   |
+                  |   (11) calling 'free'
+                  |
+    <-------------+
+    |
+  'test': event 12
+    |
+    |   free_boxed_int (obj);
+    |   ^~~~~~~~~~~~~~~~~~~~
+    |   |
+    |   (12) calling 'free_boxed_int'
+    |
+    +--> 'free_boxed_int': events 13-14
+           |
+           | {
+           | ^
+           | |
+           | (13) entering 'free_boxed_int'
+           |   wrapped_free (bi);
+           |   ~~~~~~~~~~~~~~~~~
+           |   |
+           |   (14) calling 'wrapped_free'
+           |
+           +--> 'wrapped_free': events 15-16
+                  |
+                  | {
+                  | ^
+                  | |
+                  | (15) entering 'wrapped_free'
+                  |   free (ptr);
+                  |   ~~~~~~~~~~
+                  |   |
+                  |   (16) calling 'free'
+                  |
+     { dg-end-multiline-output "" } */
+}
+
+typedef struct boxed_int
+{
+  int i;
+} boxed_int;
+
+boxed_int *
+make_boxed_int (int i)
+{
+  boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+  result->i = i;
+  return result;
+}
+
+void
+free_boxed_int (boxed_int *bi)
+{
+  wrapped_free (bi);
+}
+
+void test (int i)
+{
+  boxed_int *obj = make_boxed_int (i);
+
+  free_boxed_int (obj);
+
+  free_boxed_int (obj);
+}
+
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-1.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-1.c
new file mode 100644
index 0000000..430d817
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-1.c
@@ -0,0 +1,142 @@
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-path-format=inline-events -fdiagnostics-show-caret" } */
+
+#include <stdlib.h>
+
+void *wrapped_malloc (size_t size)
+{
+  return malloc (size);
+}
+
+void wrapped_free (void *ptr)
+{
+  free (ptr); /* { dg-warning "double-free of 'ptr' \\\[CWE-415\\]" } */
+  /* { dg-begin-multiline-output "" }
+   free (ptr);
+   ^~~~~~~~~~
+  'test': events 1-2
+    |
+    | {
+    | ^
+    | |
+    | (1) entering 'test'
+    |   boxed_int *obj = make_boxed_int (i);
+    |                    ~~~~~~~~~~~~~~~~~~
+    |                    |
+    |                    (2) calling 'make_boxed_int'
+    |
+    +--> 'make_boxed_int': events 3-4
+           |
+           | {
+           | ^
+           | |
+           | (3) entering 'make_boxed_int'
+           |   boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+           |                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+           |                                    |
+           |                                    (4) calling 'wrapped_malloc'
+           |
+           +--> 'wrapped_malloc': events 5-6
+                  |
+                  | {
+                  | ^
+                  | |
+                  | (5) entering 'wrapped_malloc'
+                  |   return malloc (size);
+                  |          ~~~~~~~~~~~~~
+                  |          |
+                  |          (6) calling 'malloc'
+                  |
+    <-------------+
+    |
+  'test': event 7
+    |
+    |   free_boxed_int (obj);
+    |   ^~~~~~~~~~~~~~~~~~~~
+    |   |
+    |   (7) calling 'free_boxed_int'
+    |
+    +--> 'free_boxed_int': events 8-9
+           |
+           | {
+           | ^
+           | |
+           | (8) entering 'free_boxed_int'
+           |   wrapped_free (bi);
+           |   ~~~~~~~~~~~~~~~~~
+           |   |
+           |   (9) calling 'wrapped_free'
+           |
+           +--> 'wrapped_free': events 10-11
+                  |
+                  | {
+                  | ^
+                  | |
+                  | (10) entering 'wrapped_free'
+                  |   free (ptr);
+                  |   ~~~~~~~~~~
+                  |   |
+                  |   (11) calling 'free'
+                  |
+    <-------------+
+    |
+  'test': event 12
+    |
+    |   free_boxed_int (obj);
+    |   ^~~~~~~~~~~~~~~~~~~~
+    |   |
+    |   (12) calling 'free_boxed_int'
+    |
+    +--> 'free_boxed_int': events 13-14
+           |
+           | {
+           | ^
+           | |
+           | (13) entering 'free_boxed_int'
+           |   wrapped_free (bi);
+           |   ~~~~~~~~~~~~~~~~~
+           |   |
+           |   (14) calling 'wrapped_free'
+           |
+           +--> 'wrapped_free': events 15-16
+                  |
+                  | {
+                  | ^
+                  | |
+                  | (15) entering 'wrapped_free'
+                  |   free (ptr);
+                  |   ~~~~~~~~~~
+                  |   |
+                  |   (16) calling 'free'
+                  |
+     { dg-end-multiline-output "" } */
+}
+
+typedef struct boxed_int
+{
+  int i;
+} boxed_int;
+
+boxed_int *
+make_boxed_int (int i)
+{
+  boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+  result->i = i;
+  return result;
+}
+
+void
+free_boxed_int (boxed_int *bi)
+{
+  wrapped_free (bi);
+}
+
+void test (int i)
+{
+  boxed_int *obj = make_boxed_int (i);
+
+  free_boxed_int (obj);
+
+  free_boxed_int (obj);
+}
+
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-2.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-2.c
new file mode 100644
index 0000000..c2bfabe
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-2.c
@@ -0,0 +1,154 @@
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-path-format=inline-events -fdiagnostics-show-caret" } */
+
+/* Verify that 'inline-events' copes gracefully with events with an
+   unknown location.  */
+
+#include <stdlib.h>
+
+extern void missing_location ();
+
+void *wrapped_malloc (size_t size)
+{
+  return malloc (size);
+}
+
+void wrapped_free (void *ptr)
+{
+  free (ptr); /* { dg-warning "double-free of 'ptr' \\\[CWE-415\\]" } */
+  /* { dg-begin-multiline-output "" }
+   free (ptr);
+   ^~~~~~~~~~
+  'test': events 1-2
+    |
+    | {
+    | ^
+    | |
+    | (1) entering 'test'
+    |   boxed_int *obj = make_boxed_int (i);
+    |                    ~~~~~~~~~~~~~~~~~~
+    |                    |
+    |                    (2) calling 'make_boxed_int'
+    |
+    +--> 'make_boxed_int': events 3-4
+           |
+           | {
+           | ^
+           | |
+           | (3) entering 'make_boxed_int'
+           |   boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+           |                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+           |                                    |
+           |                                    (4) calling 'wrapped_malloc'
+           |
+           +--> 'wrapped_malloc': events 5-6
+                  |
+                  | {
+                  | ^
+                  | |
+                  | (5) entering 'wrapped_malloc'
+                  |   return malloc (size);
+                  |          ~~~~~~~~~~~~~
+                  |          |
+                  |          (6) calling 'malloc'
+                  |
+    <-------------+
+    |
+  'test': event 7
+    |
+    |   free_boxed_int (obj);
+    |   ^~~~~~~~~~~~~~~~~~~~
+    |   |
+    |   (7) calling 'free_boxed_int'
+    |
+    +--> 'free_boxed_int': events 8-9
+           |
+           | {
+           | ^
+           | |
+           | (8) entering 'free_boxed_int'
+           |   wrapped_free (bi);
+           |   ~~~~~~~~~~~~~~~~~
+           |   |
+           |   (9) calling 'wrapped_free'
+           |
+           +--> 'wrapped_free': events 10-11
+                  |
+                  | {
+                  | ^
+                  | |
+                  | (10) entering 'wrapped_free'
+                  |   free (ptr);
+                  |   ~~~~~~~~~~
+                  |   |
+                  |   (11) calling 'free'
+                  |
+    <-------------+
+    |
+  'test': event 12
+    |
+    |cc1:
+    | (12): calling 'missing_location'
+    |
+  'test': event 13
+    |
+    |   free_boxed_int (obj);
+    |   ^~~~~~~~~~~~~~~~~~~~
+    |   |
+    |   (13) calling 'free_boxed_int'
+    |
+    +--> 'free_boxed_int': events 14-15
+           |
+           | {
+           | ^
+           | |
+           | (14) entering 'free_boxed_int'
+           |   wrapped_free (bi);
+           |   ~~~~~~~~~~~~~~~~~
+           |   |
+           |   (15) calling 'wrapped_free'
+           |
+           +--> 'wrapped_free': events 16-17
+                  |
+                  | {
+                  | ^
+                  | |
+                  | (16) entering 'wrapped_free'
+                  |   free (ptr);
+                  |   ~~~~~~~~~~
+                  |   |
+                  |   (17) calling 'free'
+                  |
+     { dg-end-multiline-output "" } */
+}
+
+typedef struct boxed_int
+{
+  int i;
+} boxed_int;
+
+boxed_int *
+make_boxed_int (int i)
+{
+  boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+  result->i = i;
+  return result;
+}
+
+void
+free_boxed_int (boxed_int *bi)
+{
+  wrapped_free (bi);
+}
+
+void test (int i)
+{
+  boxed_int *obj = make_boxed_int (i);
+
+  free_boxed_int (obj);
+
+  missing_location ();
+
+  free_boxed_int (obj);
+}
+
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-3.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-3.c
new file mode 100644
index 0000000..5e7b099
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-3.c
@@ -0,0 +1,153 @@
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-path-format=inline-events -fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fdiagnostics-show-caret" } */
+
+/* Verify the interaction of inline-events with line numbers.  */
+
+#include <stdlib.h>
+
+extern void missing_location ();
+
+void *wrapped_malloc (size_t size)
+{
+  return malloc (size);
+}
+
+void wrapped_free (void *ptr)
+{
+  free (ptr); /* { dg-warning "double-free of 'ptr' \\\[CWE-415\\]" } */
+  /* { dg-begin-multiline-output "" }
+   NN |   free (ptr);
+      |   ^~~~~~~~~~
+  'test': events 1-2
+    |
+    |   NN | {
+    |      | ^
+    |      | |
+    |      | (1) entering 'test'
+    |   NN |   boxed_int *obj = make_boxed_int (i);
+    |      |                    ~~~~~~~~~~~~~~~~~~
+    |      |                    |
+    |      |                    (2) calling 'make_boxed_int'
+    |
+    +--> 'make_boxed_int': events 3-4
+           |
+           |   NN | {
+           |      | ^
+           |      | |
+           |      | (3) entering 'make_boxed_int'
+           |   NN |   boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+           |      |                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+           |      |                                    |
+           |      |                                    (4) calling 'wrapped_malloc'
+           |
+           +--> 'wrapped_malloc': events 5-6
+                  |
+                  |   NN | {
+                  |      | ^
+                  |      | |
+                  |      | (5) entering 'wrapped_malloc'
+                  |   NN |   return malloc (size);
+                  |      |          ~~~~~~~~~~~~~
+                  |      |          |
+                  |      |          (6) calling 'malloc'
+                  |
+    <-------------+
+    |
+  'test': event 7
+    |
+    |   NN |   free_boxed_int (obj);
+    |      |   ^~~~~~~~~~~~~~~~~~~~
+    |      |   |
+    |      |   (7) calling 'free_boxed_int'
+    |
+    +--> 'free_boxed_int': events 8-9
+           |
+           |   NN | {
+           |      | ^
+           |      | |
+           |      | (8) entering 'free_boxed_int'
+           |   NN |   wrapped_free (bi);
+           |      |   ~~~~~~~~~~~~~~~~~
+           |      |   |
+           |      |   (9) calling 'wrapped_free'
+           |
+           +--> 'wrapped_free': events 10-11
+                  |
+                  |   NN | {
+                  |      | ^
+                  |      | |
+                  |      | (10) entering 'wrapped_free'
+                  |   NN |   free (ptr);
+                  |      |   ~~~~~~~~~~
+                  |      |   |
+                  |      |   (11) calling 'free'
+                  |
+    <-------------+
+    |
+  'test': event 12
+    |
+    |cc1:
+    | (12): calling 'missing_location'
+    |
+  'test': event 13
+    |
+    |   NN |   free_boxed_int (obj);
+    |      |   ^~~~~~~~~~~~~~~~~~~~
+    |      |   |
+    |      |   (13) calling 'free_boxed_int'
+    |
+    +--> 'free_boxed_int': events 14-15
+           |
+           |   NN | {
+           |      | ^
+           |      | |
+           |      | (14) entering 'free_boxed_int'
+           |   NN |   wrapped_free (bi);
+           |      |   ~~~~~~~~~~~~~~~~~
+           |      |   |
+           |      |   (15) calling 'wrapped_free'
+           |
+           +--> 'wrapped_free': events 16-17
+                  |
+                  |   NN | {
+                  |      | ^
+                  |      | |
+                  |      | (16) entering 'wrapped_free'
+                  |   NN |   free (ptr);
+                  |      |   ~~~~~~~~~~
+                  |      |   |
+                  |      |   (17) calling 'free'
+                  |
+     { dg-end-multiline-output "" } */
+}
+
+typedef struct boxed_int
+{
+  int i;
+} boxed_int;
+
+boxed_int *
+make_boxed_int (int i)
+{
+  boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+  result->i = i;
+  return result;
+}
+
+void
+free_boxed_int (boxed_int *bi)
+{
+  wrapped_free (bi);
+}
+
+void test (int i)
+{
+  boxed_int *obj = make_boxed_int (i);
+
+  free_boxed_int (obj);
+
+  missing_location ();
+
+  free_boxed_int (obj);
+}
+
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-none.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-none.c
new file mode 100644
index 0000000..0a29f67
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-none.c
@@ -0,0 +1,43 @@
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-path-format=none" } */
+
+#include <stdlib.h>
+
+void *wrapped_malloc (size_t size)
+{
+  return malloc (size);
+}
+
+void wrapped_free (void *ptr)
+{
+  free (ptr); /* { dg-warning "double-free of 'ptr' \\\[CWE-415\\]" } */
+}
+
+typedef struct boxed_int
+{
+  int i;
+} boxed_int;
+
+boxed_int *
+make_boxed_int (int i)
+{
+  boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+  result->i = i;
+  return result;
+}
+
+void
+free_boxed_int (boxed_int *bi)
+{
+  wrapped_free (bi);
+}
+
+void test (int i)
+{
+  boxed_int *obj = make_boxed_int (i);
+
+  free_boxed_int (obj);
+
+  free_boxed_int (obj);
+}
+
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-separate-events.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-separate-events.c
new file mode 100644
index 0000000..dcb72c0
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-separate-events.c
@@ -0,0 +1,44 @@
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-path-format=separate-events" } */
+
+#include <stdlib.h>
+
+void *wrapped_malloc (size_t size)
+{
+  return malloc (size);
+}
+
+void wrapped_free (void *ptr)
+{
+  free (ptr); /* { dg-warning "double-free of 'ptr' \\\[CWE-415\\]" } */
+}
+
+typedef struct boxed_int
+{
+  int i;
+} boxed_int;
+
+boxed_int *
+make_boxed_int (int i)
+{
+  boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+  result->i = i;
+  return result;
+}
+
+void
+free_boxed_int (boxed_int *bi)
+{
+  wrapped_free (bi);
+}
+
+void test (int i)
+{ /* { dg-message "\\(1\\) entering 'test'" } */
+  boxed_int *obj = make_boxed_int (i); /* { dg-message "\\(2\\) calling 'make_boxed_int'" } */
+  /* etc */
+
+  free_boxed_int (obj);
+
+  free_boxed_int (obj);
+}
+
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-1.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-1.c
new file mode 100644
index 0000000..7b11c90
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-1.c
@@ -0,0 +1,38 @@
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-path-format=separate-events" } */
+
+#include <stddef.h>
+#include <stdlib.h>
+
+/* Minimal reimplementation of cpython API.  */
+typedef struct PyObject {} PyObject;
+extern int PyArg_ParseTuple (PyObject *args, const char *fmt, ...);
+extern PyObject *PyList_New (int);
+extern PyObject *PyLong_FromLong(long);
+extern void PyList_Append(PyObject *list, PyObject *item);
+
+PyObject *
+make_a_list_of_random_ints_badly(PyObject *self,
+				 PyObject *args)
+{
+  PyObject *list, *item;
+  long count, i;
+
+  if (!PyArg_ParseTuple(args, "i", &count)) {
+    return NULL;
+  }
+
+  list = PyList_New(0); /* { dg-line PyList_New } */
+	
+  for (i = 0; i < count; i++) { /* { dg-line for } */
+    item = PyLong_FromLong(random());
+    PyList_Append(list, item); /* { dg-line PyList_Append } */
+  }
+  
+  return list;
+
+  /* { dg-error "passing NULL as argument 1 to 'PyList_Append' which requires a non-NULL parameter" "" { target *-*-* } PyList_Append } */
+  /* { dg-message "\\(1\\) when 'PyList_New' fails, returning NULL" "" { target *-*-* } PyList_New } */
+  /* { dg-message "\\(2\\) when 'i < count'" "" { target *-*-* } for } */
+  /* { dg-message "\\(3\\) when calling 'PyList_Append', passing NULL from \\(1\\) as argument 1" "" { target *-*-* } PyList_Append } */
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.c
new file mode 100644
index 0000000..391aeb9
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.c
@@ -0,0 +1,56 @@
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-show-caret -fdiagnostics-show-line-numbers" } */
+
+#include <stddef.h>
+#include <stdlib.h>
+
+/* Minimal reimplementation of cpython API.  */
+typedef struct PyObject {} PyObject;
+extern int PyArg_ParseTuple (PyObject *args, const char *fmt, ...);
+extern PyObject *PyList_New (int);
+extern PyObject *PyLong_FromLong(long);
+extern void PyList_Append(PyObject *list, PyObject *item);
+
+PyObject *
+make_a_list_of_random_ints_badly(PyObject *self,
+				 PyObject *args)
+{
+  PyObject *list, *item;
+  long count, i;
+
+  if (!PyArg_ParseTuple(args, "i", &count)) {
+    return NULL;
+  }
+
+  list = PyList_New(0); /* { dg-line PyList_New } */
+	
+  for (i = 0; i < count; i++) {
+    item = PyLong_FromLong(random());
+    PyList_Append(list, item); /* { dg-line PyList_Append } */
+  }
+  
+  return list;
+
+  /* { dg-error "passing NULL as argument 1 to 'PyList_Append' which requires a non-NULL parameter" "" { target *-*-* } PyList_Append } */
+  /* { dg-begin-multiline-output "" }
+   29 |     PyList_Append(list, item);
+      |     ^~~~~~~~~~~~~~~~~~~~~~~~~
+  'make_a_list_of_random_ints_badly': events 1-3
+    |
+    |   25 |   list = PyList_New(0);
+    |      |          ^~~~~~~~~~~~~
+    |      |          |
+    |      |          (1) when 'PyList_New' fails, returning NULL
+    |   26 | 
+    |   27 |   for (i = 0; i < count; i++) {
+    |      |   ~~~     
+    |      |   |
+    |      |   (2) when 'i < count'
+    |   28 |     item = PyLong_FromLong(random());
+    |   29 |     PyList_Append(list, item);
+    |      |     ~~~~~~~~~~~~~~~~~~~~~~~~~
+    |      |     |
+    |      |     (3) when calling 'PyList_Append', passing NULL from (1) as argument 1
+    |
+     { dg-end-multiline-output "" } */
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-3.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-3.c
new file mode 100644
index 0000000..6971d7c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-3.c
@@ -0,0 +1,38 @@
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-format=json" } */
+
+#include <stddef.h>
+#include <stdlib.h>
+
+/* Minimal reimplementation of cpython API.  */
+typedef struct PyObject {} PyObject;
+extern int PyArg_ParseTuple (PyObject *args, const char *fmt, ...);
+extern PyObject *PyList_New (int);
+extern PyObject *PyLong_FromLong(long);
+extern void PyList_Append(PyObject *list, PyObject *item);
+
+PyObject *
+make_a_list_of_random_ints_badly(PyObject *self,
+				 PyObject *args)
+{
+  PyObject *list, *item;
+  long count, i;
+
+  if (!PyArg_ParseTuple(args, "i", &count)) {
+    return NULL;
+  }
+
+  list = PyList_New(0);
+	
+  for (i = 0; i < count; i++) {
+    item = PyLong_FromLong(random());
+    PyList_Append(list, item);
+  }
+  
+  return list;
+}
+
+/* FIXME: test the events within a path.  */
+/* { dg-regexp "\"kind\": \"error\"" } */
+/* { dg-regexp "\"path\": " } */
+/* { dg-regexp ".*" } */
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c
new file mode 100644
index 0000000..8239815
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c
@@ -0,0 +1,379 @@
+/* { dg-options "-O" } */
+
+/* This plugin exercises the path-printing code.
+
+   The goal is to unit-test the path-printing code without needing any
+   specific tests within the compiler's IR.  We can't use any real
+   diagnostics for this, so we have to fake it, hence this plugin.  */
+
+#include "gcc-plugin.h"
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tm.h"
+#include "tree.h"
+#include "stringpool.h"
+#include "toplev.h"
+#include "basic-block.h"
+#include "hash-table.h"
+#include "vec.h"
+#include "ggc.h"
+#include "basic-block.h"
+#include "tree-ssa-alias.h"
+#include "internal-fn.h"
+#include "gimple-fold.h"
+#include "tree-eh.h"
+#include "gimple-expr.h"
+#include "is-a.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "tree.h"
+#include "tree-pass.h"
+#include "intl.h"
+#include "plugin-version.h"
+#include "diagnostic.h"
+#include "diagnostic-path.h"
+#include "diagnostic-metadata.h"
+#include "context.h"
+#include "print-tree.h"
+#include "gcc-rich-location.h"
+#include "cgraph.h"
+
+int plugin_is_GPL_compatible;
+
+const pass_data pass_data_test_show_path =
+{
+  IPA_PASS, /* type */
+  "test_show_path", /* name */
+  OPTGROUP_NONE, /* optinfo_flags */
+  TV_NONE, /* tv_id */
+  PROP_ssa, /* properties_required */
+  0, /* properties_provided */
+  0, /* properties_destroyed */
+  0, /* todo_flags_start */
+  0, /* todo_flags_finish */
+};
+
+class pass_test_show_path : public ipa_opt_pass_d
+{
+public:
+  pass_test_show_path(gcc::context *ctxt)
+    : ipa_opt_pass_d (pass_data_test_show_path, ctxt,
+		      NULL, /* generate_summary */
+		      NULL, /* write_summary */
+		      NULL, /* read_summary */
+		      NULL, /* write_optimization_summary */
+		      NULL, /* read_optimization_summary */
+		      NULL, /* stmt_fixup */
+		      0, /* function_transform_todo_flags_start */
+		      NULL, /* function_transform */
+		      NULL) /* variable_transform */
+  {}
+
+  /* opt_pass methods: */
+  bool gate (function *) { return true; }
+  virtual unsigned int execute (function *);
+
+}; // class pass_test_show_path
+
+/* Determine if STMT is a call with NUM_ARGS arguments to a function
+   named FUNCNAME.
+   If so, return STMT as a gcall *.  Otherwise return NULL.  */
+
+static gcall *
+check_for_named_call (gimple *stmt,
+		      const char *funcname, unsigned int num_args)
+{
+  gcc_assert (funcname);
+
+  gcall *call = dyn_cast <gcall *> (stmt);
+  if (!call)
+    return NULL;
+
+  tree fndecl = gimple_call_fndecl (call);
+  if (!fndecl)
+    return NULL;
+
+  if (strcmp (IDENTIFIER_POINTER (DECL_NAME (fndecl)), funcname))
+    return NULL;
+
+  if (gimple_call_num_args (call) != num_args)
+    {
+      error_at (stmt->location, "expected number of args: %i (got %i)",
+		num_args, gimple_call_num_args (call));
+      return NULL;
+    }
+
+  return call;
+}
+
+/* Example 1: a purely intraprocedural path.  */
+
+static void
+example_1 ()
+{
+  gimple_stmt_iterator gsi;
+  basic_block bb;
+
+  gcall *call_to_PyList_Append = NULL;
+  gcall *call_to_PyList_New = NULL;
+  gcond *for_cond = NULL;
+  function *example_a_fun = NULL;
+
+  cgraph_node *node;
+  FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
+    {
+      function *fun = node->get_fun ();
+      FOR_EACH_BB_FN (bb, fun)
+	{
+	  for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
+	    {
+	      gimple *stmt = gsi_stmt (gsi);
+	      if (gcall *call = check_for_named_call (stmt, "PyList_New", 1))
+		{
+		  call_to_PyList_New = call;
+		  example_a_fun = fun;
+		}
+	      if (gcall *call = check_for_named_call (stmt, "PyList_Append", 2))
+		call_to_PyList_Append = call;
+	      if (gcond *cond = dyn_cast <gcond *> (stmt))
+		for_cond = cond;
+	    }
+	}
+    }
+
+  if (call_to_PyList_New && for_cond && call_to_PyList_Append)
+    {
+      auto_diagnostic_group d;
+      gcc_rich_location richloc (gimple_location (call_to_PyList_Append));
+      simple_diagnostic_path path (global_dc->printer);
+      diagnostic_event_id_t alloc_event_id
+	= path.add_event (gimple_location (call_to_PyList_New),
+			  example_a_fun->decl, 0,
+			  "when %qs fails, returning NULL",
+			  "PyList_New");
+      path.add_event (gimple_location (for_cond),
+		      example_a_fun->decl, 0,
+		      "when %qs", "i < count");
+      path.add_event (gimple_location (call_to_PyList_Append),
+		      example_a_fun->decl, 0,
+		      "when calling %qs, passing NULL from %@ as argument %i",
+		      "PyList_Append", &alloc_event_id, 1);
+      richloc.set_path (&path);
+      error_at (&richloc,
+		"passing NULL as argument %i to %qs"
+		" which requires a non-NULL parameter",
+		1, "PyList_Append");
+    }
+}
+
+/* A (function, location_t) pair.  */
+
+struct event_location_t
+{
+  event_location_t ()
+  : m_fun (NULL), m_loc (UNKNOWN_LOCATION)
+  {}
+
+  event_location_t (function *fun, location_t loc)
+  : m_fun (fun), m_loc (loc)
+  {}
+
+  void set (const gimple *stmt, function *fun)
+  {
+    m_fun = fun;
+    m_loc = gimple_location (stmt);
+  }
+
+  function *m_fun;
+  location_t m_loc;
+};
+
+/* If FUN's name matches FUNCNAME, write the function and its start location
+   into *OUT_ENTRY.  */
+
+static void
+check_for_named_function (function *fun, const char *funcname,
+			  event_location_t *out_entry)
+{
+  gcc_assert (fun);
+  gcc_assert (funcname);
+
+  if (strcmp (IDENTIFIER_POINTER (DECL_NAME (fun->decl)), funcname))
+    return;
+
+  *out_entry = event_location_t (fun, fun->function_start_locus);
+}
+
+
+/* Example 2: an interprocedural path.  */
+
+class test_diagnostic_path : public simple_diagnostic_path
+{
+ public:
+  test_diagnostic_path (pretty_printer *event_pp)
+  : simple_diagnostic_path (event_pp)
+  {
+  }
+  void add_entry (event_location_t evloc, int stack_depth,
+		  const char *funcname)
+  {
+    gcc_assert (evloc.m_fun);
+    add_event (evloc.m_loc, evloc.m_fun->decl, stack_depth,
+	       "entering %qs", funcname);
+  }
+
+  void add_call (event_location_t call_evloc, int caller_stack_depth,
+		 event_location_t callee_entry_evloc, const char *callee)
+  {
+    gcc_assert (call_evloc.m_fun);
+    add_event (call_evloc.m_loc, call_evloc.m_fun->decl, caller_stack_depth,
+	       "calling %qs", callee);
+    add_entry (callee_entry_evloc, caller_stack_depth + 1, callee);
+  }
+
+  void add_leaf_call (event_location_t call_evloc, int caller_stack_depth,
+		      const char *callee)
+  {
+    gcc_assert (call_evloc.m_fun);
+    add_event (call_evloc.m_loc, call_evloc.m_fun->decl, caller_stack_depth,
+	       "calling %qs", callee);
+  }
+};
+
+static void
+example_2 ()
+{
+  gimple_stmt_iterator gsi;
+  basic_block bb;
+
+  event_location_t entry_to_wrapped_malloc;
+  event_location_t call_to_malloc;
+
+  event_location_t entry_to_wrapped_free;
+  event_location_t call_to_free;
+
+  event_location_t entry_to_make_boxed_int;
+  event_location_t call_to_wrapped_malloc;
+
+  event_location_t entry_to_free_boxed_int;
+  event_location_t call_to_wrapped_free;
+
+  event_location_t entry_to_test;
+  event_location_t call_to_make_boxed_int;
+  event_location_t call_to_free_boxed_int;
+
+  event_location_t call_to_missing_location;
+
+  cgraph_node *node;
+  FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
+    {
+      function *fun = node->get_fun ();
+      FOR_EACH_BB_FN (bb, fun)
+	{
+	  check_for_named_function (fun, "wrapped_malloc",
+				    &entry_to_wrapped_malloc);
+	  check_for_named_function (fun, "wrapped_free",
+				    &entry_to_wrapped_free);
+	  check_for_named_function (fun, "make_boxed_int",
+				    &entry_to_make_boxed_int);
+	  check_for_named_function (fun, "free_boxed_int",
+				    &entry_to_free_boxed_int);
+	  check_for_named_function (fun, "test",
+				    &entry_to_test);
+
+	  for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
+	    {
+	      gimple *stmt = gsi_stmt (gsi);
+	      if (gcall *call = check_for_named_call (stmt, "malloc", 1))
+		call_to_malloc.set (call, fun);
+	      if (gcall *call = check_for_named_call (stmt, "free", 1))
+		call_to_free.set (call, fun);
+	      if (gcall *call = check_for_named_call (stmt, "wrapped_malloc", 1))
+		call_to_wrapped_malloc.set (call, fun);
+	      if (gcall *call = check_for_named_call (stmt, "wrapped_free", 1))
+		call_to_wrapped_free.set (call, fun);
+	      if (gcall *call = check_for_named_call (stmt, "make_boxed_int", 1))
+		call_to_make_boxed_int.set (call, fun);
+	      if (gcall *call = check_for_named_call (stmt, "free_boxed_int", 1))
+		call_to_free_boxed_int.set (call, fun);
+	      if (gcall *call = check_for_named_call (stmt, "missing_location", 0))
+		{
+		  call_to_missing_location.set (call, fun);
+		  /* Simulate an event that's missing a useful location_t.  */
+		  call_to_missing_location.m_loc = UNKNOWN_LOCATION;
+		}
+	    }
+	}
+    }
+
+  if (call_to_malloc.m_fun)
+    {
+      auto_diagnostic_group d;
+
+      gcc_rich_location richloc (call_to_free.m_loc);
+      test_diagnostic_path path (global_dc->printer);
+      path.add_entry (entry_to_test, 0, "test");
+      path.add_call (call_to_make_boxed_int, 0,
+		     entry_to_make_boxed_int, "make_boxed_int");
+      path.add_call (call_to_wrapped_malloc, 1,
+		     entry_to_wrapped_malloc, "wrapped_malloc");
+      path.add_leaf_call (call_to_malloc, 2, "malloc");
+
+      for (int i = 0; i < 2; i++)
+	{
+	  path.add_call (call_to_free_boxed_int, 0,
+			 entry_to_free_boxed_int, "free_boxed_int");
+	  path.add_call (call_to_wrapped_free, 1,
+			 entry_to_wrapped_free, "wrapped_free");
+	  path.add_leaf_call (call_to_free, 2, "free");
+	  if (i == 0 && call_to_missing_location.m_fun)
+	    path.add_leaf_call (call_to_missing_location, 0, "missing_location");
+	}
+
+      richloc.set_path (&path);
+
+      diagnostic_metadata m;
+      m.add_cwe (415); /* CWE-415: Double Free.  */
+
+      warning_at (&richloc, m, 0,
+		  "double-free of %qs", "ptr");
+    }
+}
+
+unsigned int
+pass_test_show_path::execute (function *)
+{
+  example_1 ();
+  example_2 ();
+
+  return 0;
+}
+
+static opt_pass *
+make_pass_test_show_path (gcc::context *ctxt)
+{
+  return new pass_test_show_path (ctxt);
+}
+
+int
+plugin_init (struct plugin_name_args *plugin_info,
+	     struct plugin_gcc_version *version)
+{
+  struct register_pass_info pass_info;
+  const char *plugin_name = plugin_info->base_name;
+  int argc = plugin_info->argc;
+  struct plugin_argument *argv = plugin_info->argv;
+
+  if (!plugin_default_version_check (version, &gcc_version))
+    return 1;
+
+  pass_info.pass = make_pass_test_show_path (g);
+  pass_info.reference_pass_name = "whole-program";
+  pass_info.ref_pass_instance_number = 1;
+  pass_info.pos_op = PASS_POS_INSERT_BEFORE;
+  register_callback (plugin_name, PLUGIN_PASS_MANAGER_SETUP, NULL,
+		     &pass_info);
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/plugin.exp b/gcc/testsuite/gcc.dg/plugin/plugin.exp
index 2f75463..4ae3048 100644
--- a/gcc/testsuite/gcc.dg/plugin/plugin.exp
+++ b/gcc/testsuite/gcc.dg/plugin/plugin.exp
@@ -94,6 +94,16 @@ set plugin_test_list [list \
 	  diagnostic-test-inlining-2.c \
 	  diagnostic-test-inlining-3.c \
 	  diagnostic-test-inlining-4.c } \
+    { diagnostic_plugin_test_paths.c \
+	  diagnostic-test-paths-1.c \
+	  diagnostic-test-paths-2.c \
+	  diagnostic-test-paths-3.c \
+	  diagnostic-path-format-default.c \
+	  diagnostic-path-format-none.c \
+	  diagnostic-path-format-separate-events.c \
+	  diagnostic-path-format-inline-events-1.c \
+	  diagnostic-path-format-inline-events-2.c \
+	  diagnostic-path-format-inline-events-3.c } \
     { location_overflow_plugin.c \
 	  location-overflow-test-1.c \
 	  location-overflow-test-2.c \
diff --git a/gcc/toplev.c b/gcc/toplev.c
index 7136164..d5dad4b 100644
--- a/gcc/toplev.c
+++ b/gcc/toplev.c
@@ -1104,6 +1104,10 @@ general_init (const char *argv0, bool init_signals)
     = global_options_init.x_flag_diagnostics_nn_line_numbers;
   global_dc->show_metadata
     = global_options_init.x_flag_diagnostics_show_metadata;
+  global_dc->path_format
+    = (enum diagnostic_path_format)global_options_init.x_flag_diagnostics_path_format;
+  global_dc->show_path_depths
+    = global_options_init.x_flag_diagnostics_show_path_depths;
   global_dc->show_option_requested
     = global_options_init.x_flag_diagnostics_show_option;
   global_dc->min_margin_width
diff --git a/gcc/tree-diagnostic-path.cc b/gcc/tree-diagnostic-path.cc
new file mode 100644
index 0000000..d044152
--- /dev/null
+++ b/gcc/tree-diagnostic-path.cc
@@ -0,0 +1,809 @@
+/* Paths through the code associated with a diagnostic.
+   Copyright (C) 2019 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 "diagnostic.h"
+#include "tree-pretty-print.h"
+#include "gimple-pretty-print.h"
+#include "tree-diagnostic.h"
+#include "langhooks.h"
+#include "intl.h"
+#include "diagnostic-path.h"
+#include "json.h"
+#include "gcc-rich-location.h"
+#include "diagnostic-color.h"
+#include "diagnostic-event-id.h"
+#include "selftest.h"
+#include "selftest-diagnostic.h"
+
+/* Anonymous namespace for path-printing code.  */
+
+namespace {
+
+/* Subclass of range_label for showing a consecutive run of events
+   within a diagnostic_path as labelled ranges within one
+   gcc_rich_location.  */
+
+class path_label : public range_label
+{
+ public:
+  path_label (const diagnostic_path *path, unsigned start_idx)
+  : m_path (path), m_start_idx (start_idx)
+  {}
+
+  label_text get_text (unsigned range_idx) const FINAL OVERRIDE
+  {
+    unsigned event_idx = m_start_idx + range_idx;
+    const diagnostic_event &event = m_path->get_event (event_idx);
+
+    /* Get the description of the event, perhaps with colorization:
+       normally, we don't colorize within a range_label, but this
+       is special-cased for diagnostic paths.  */
+    bool colorize = pp_show_color (global_dc->printer);
+    label_text event_text (event.get_desc (colorize));
+    gcc_assert (event_text.m_buffer);
+    pretty_printer pp;
+    pp_show_color (&pp) = pp_show_color (global_dc->printer);
+    diagnostic_event_id_t event_id (event_idx);
+    pp_printf (&pp, "%@ %s", &event_id, event_text.m_buffer);
+    event_text.maybe_free ();
+    label_text result = label_text::take (xstrdup (pp_formatted_text (&pp)));
+    return result;
+  }
+
+ private:
+  const diagnostic_path *m_path;
+  unsigned m_start_idx;
+};
+
+/* Return true if E1 and E2 can be consolidated into the same run of events
+   when printing a diagnostic_path.  */
+
+static bool
+can_consolidate_events (const diagnostic_event &e1,
+			const diagnostic_event &e2,
+			bool check_locations)
+{
+  if (e1.get_fndecl () != e2.get_fndecl ())
+    return false;
+
+  if (e1.get_stack_depth () != e2.get_stack_depth ())
+    return false;
+
+  if (check_locations)
+    {
+      location_t loc1 = e1.get_location ();
+      location_t loc2 = e2.get_location ();
+
+      if (loc1 < RESERVED_LOCATION_COUNT
+	  || loc2 < RESERVED_LOCATION_COUNT)
+	return false;
+
+      /* Neither can be macro-based.  */
+      if (linemap_location_from_macro_expansion_p (line_table, loc1))
+	return false;
+      if (linemap_location_from_macro_expansion_p (line_table, loc2))
+	return false;
+    }
+
+  /* Passed all the tests.  */
+  return true;
+}
+
+/* A class for grouing together the events in a diagnostic_path into
+   ranges of events, partitioned by stack frame (i.e. by fndecl and
+   stack depth).  */
+
+class path_summary
+{
+  /* A range of consecutive events within a diagnostic_path,
+     all with the same fndecl and stack_depth, and which are suitable
+     to print with a single call to diagnostic_show_locus.  */
+  struct event_range
+  {
+    event_range (const diagnostic_path *path, unsigned start_idx,
+		 const diagnostic_event &initial_event)
+    : m_path (path),
+      m_initial_event (initial_event),
+      m_fndecl (initial_event.get_fndecl ()),
+      m_stack_depth (initial_event.get_stack_depth ()),
+      m_start_idx (start_idx), m_end_idx (start_idx),
+      m_path_label (path, start_idx),
+      m_richloc (initial_event.get_location (), &m_path_label)
+    {}
+
+    bool maybe_add_event (const diagnostic_event &new_ev, unsigned idx,
+			  bool check_rich_locations)
+    {
+      if (!can_consolidate_events (m_initial_event, new_ev,
+				   check_rich_locations))
+	return false;
+      if (check_rich_locations)
+	if (!m_richloc.add_location_if_nearby (new_ev.get_location (),
+					       false, &m_path_label))
+	  return false;
+      m_end_idx = idx;
+      return true;
+    }
+
+    /* Print the events in this range to DC, typically as a single
+       call to the printer's diagnostic_show_locus.  */
+
+    void print (diagnostic_context *dc)
+    {
+      location_t initial_loc = m_initial_event.get_location ();
+
+      /* Emit a span indicating the filename (and line/column) if the
+	 line has changed relative to the last call to
+	 diagnostic_show_locus.  */
+      if (dc->show_caret)
+	{
+	  expanded_location exploc
+	    = linemap_client_expand_location_to_spelling_point
+	    (initial_loc, LOCATION_ASPECT_CARET);
+	  if (exploc.file != LOCATION_FILE (dc->last_location))
+	    dc->start_span (dc, exploc);
+	}
+
+      /* If we have an UNKNOWN_LOCATION (or BUILTINS_LOCATION) as the
+	 primary location for an event, diagnostic_show_locus won't print
+	 anything.
+
+	 In particular the label for the event won't get printed.
+	 Fail more gracefully in this case by showing the event
+	 index and text, at no particular location.  */
+      if (initial_loc <= BUILTINS_LOCATION)
+	{
+	  for (unsigned i = m_start_idx; i <= m_end_idx; i++)
+	    {
+	      const diagnostic_event &iter_event = m_path->get_event (i);
+	      diagnostic_event_id_t event_id (i);
+	      label_text event_text (iter_event.get_desc (true));
+	      pretty_printer *pp = dc->printer;
+	      pp_printf (pp, " %@: %s", &event_id, event_text.m_buffer);
+	      pp_newline (pp);
+	      event_text.maybe_free ();
+	    }
+	  return;
+	}
+
+      /* Call diagnostic_show_locus to show the events using labels.  */
+      diagnostic_show_locus (dc, &m_richloc, DK_DIAGNOSTIC_PATH);
+
+      /* If we have a macro expansion, show the expansion to the user.  */
+      if (linemap_location_from_macro_expansion_p (line_table, initial_loc))
+	{
+	  gcc_assert (m_start_idx == m_end_idx);
+	  maybe_unwind_expanded_macro_loc (dc, initial_loc);
+	}
+    }
+
+    const diagnostic_path *m_path;
+    const diagnostic_event &m_initial_event;
+    tree m_fndecl;
+    int m_stack_depth;
+    unsigned m_start_idx;
+    unsigned m_end_idx;
+    path_label m_path_label;
+    gcc_rich_location m_richloc;
+  };
+
+ public:
+  path_summary (const diagnostic_path &path, bool check_rich_locations);
+
+  void print (diagnostic_context *dc, bool show_depths) const;
+
+  unsigned get_num_ranges () const { return m_ranges.length (); }
+
+ private:
+  auto_delete_vec <event_range> m_ranges;
+};
+
+/* path_summary's ctor.  */
+
+path_summary::path_summary (const diagnostic_path &path,
+			    bool check_rich_locations)
+{
+  const unsigned num_events = path.num_events ();
+
+  event_range *cur_event_range = NULL;
+  for (unsigned idx = 0; idx < num_events; idx++)
+    {
+      const diagnostic_event &event = path.get_event (idx);
+      if (cur_event_range)
+	if (cur_event_range->maybe_add_event (event, idx, check_rich_locations))
+	  continue;
+
+      cur_event_range = new event_range (&path, idx, event);
+      m_ranges.safe_push (cur_event_range);
+    }
+}
+
+/* Write SPACES to PP.  */
+
+static void
+write_indent (pretty_printer *pp, int spaces)
+{
+  for (int i = 0; i < spaces; i++)
+    pp_space (pp);
+}
+
+/* Print FNDDECL to PP, quoting it if QUOTED is true.
+
+   We can't use "%qE" here since we can't guarantee the capabilities
+   of PP.  */
+
+static void
+print_fndecl (pretty_printer *pp, tree fndecl, bool quoted)
+{
+  const char *n = DECL_NAME (fndecl)
+    ? identifier_to_locale (lang_hooks.decl_printable_name (fndecl, 2))
+    : _("<anonymous>");
+  if (quoted)
+    pp_printf (pp, "%qs", n);
+  else
+    pp_string (pp, n);
+}
+
+/* Print this path_summary to DC, giving an overview of the interprocedural
+   calls and returns.
+
+   Print the event descriptions in a nested form, printing the event
+   descriptions within calls to diagnostic_show_locus, using labels to
+   show the events:
+
+   'foo' (events 1-2)
+     | NN |
+     |    |
+     +--> 'bar' (events 3-4)
+            | NN |
+            |    |
+            +--> 'baz' (events 5-6)
+                   | NN |
+                   |    |
+     <------------ +
+     |
+   'foo' (events 7-8)
+     | NN |
+     |    |
+     +--> 'bar' (events 9-10)
+            | NN |
+            |    |
+            +--> 'baz' (events 11-12)
+                   | NN |
+                   |    |
+
+   If SHOW_DEPTHS is true, append " (depth N)" to the header of each run
+   of events.
+
+   For events with UNKNOWN_LOCATION, print a summary of each the event.  */
+
+void
+path_summary::print (diagnostic_context *dc, bool show_depths) const
+{
+  pretty_printer *pp = dc->printer;
+
+  const int per_frame_indent = 2;
+
+  const char *const line_color = "path";
+  const char *start_line_color
+    = colorize_start (pp_show_color (pp), line_color);
+  const char *end_line_color = colorize_stop (pp_show_color (pp));
+
+  /* Keep track of column numbers of existing '|' characters for
+     stack depths we've already printed.  */
+  const int EMPTY = -1;
+  const int DELETED = -2;
+  typedef int_hash <int, EMPTY, DELETED> vbar_hash;
+  hash_map <vbar_hash, int> vbar_column_for_depth;
+
+  /* Print the ranges.  */
+  const int base_indent = 2;
+  int cur_indent = base_indent;
+  unsigned i;
+  event_range *range;
+  FOR_EACH_VEC_ELT (m_ranges, i, range)
+    {
+      write_indent (pp, cur_indent);
+      if (i > 0)
+	{
+	  const path_summary::event_range *prev_range
+	    = m_ranges[i - 1];
+	  if (range->m_stack_depth > prev_range->m_stack_depth)
+	    {
+	      /* Show pushed stack frame(s).  */
+	      const char *push_prefix = "+--> ";
+	      pp_string (pp, start_line_color);
+	      pp_string (pp, push_prefix);
+	      pp_string (pp, end_line_color);
+	      cur_indent += strlen (push_prefix);
+	    }
+	}
+      print_fndecl (pp, range->m_fndecl, true);
+      if (range->m_start_idx == range->m_end_idx)
+	pp_printf (pp, ": event %i",
+		   range->m_start_idx + 1);
+      else
+	pp_printf (pp, ": events %i-%i",
+		   range->m_start_idx + 1, range->m_end_idx + 1);
+      if (show_depths)
+	pp_printf (pp, " (depth %i)", range->m_stack_depth);
+      pp_newline (pp);
+
+      /* Print a run of events.  */
+      {
+	write_indent (pp, cur_indent + per_frame_indent);
+	pp_string (pp, start_line_color);
+	pp_string (pp, "|");
+	pp_string (pp, end_line_color);
+	pp_newline (pp);
+
+	char *saved_prefix = pp_take_prefix (pp);
+	char *prefix;
+	{
+	  pretty_printer tmp_pp;
+	  write_indent (&tmp_pp, cur_indent + per_frame_indent);
+	  pp_string (&tmp_pp, start_line_color);
+	  pp_string (&tmp_pp, "|");
+	  pp_string (&tmp_pp, end_line_color);
+	  prefix = xstrdup (pp_formatted_text (&tmp_pp));
+	}
+	pp_set_prefix (pp, prefix);
+	pp_prefixing_rule (pp) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
+	range->print (dc);
+	pp_set_prefix (pp, saved_prefix);
+
+	write_indent (pp, cur_indent + per_frame_indent);
+	pp_string (pp, start_line_color);
+	pp_string (pp, "|");
+	pp_string (pp, end_line_color);
+	pp_newline (pp);
+      }
+
+      if (i < m_ranges.length () - 1)
+	{
+	  const path_summary::event_range *next_range
+	    = m_ranges[i + 1];
+
+	  if (range->m_stack_depth > next_range->m_stack_depth)
+	    {
+	      /* Show returning from stack frame(s), by printing
+		 something like:
+		 "                   |\n"
+		 "     <------------ +\n"
+		 "     |\n".  */
+
+	      gcc_assert (vbar_column_for_depth.get
+			  (next_range->m_stack_depth));
+
+	      int vbar_for_next_frame
+		= *vbar_column_for_depth.get (next_range->m_stack_depth);
+
+	      int indent_for_next_frame
+		= vbar_for_next_frame - per_frame_indent;
+	      write_indent (pp, vbar_for_next_frame);
+	      pp_string (pp, start_line_color);
+	      pp_character (pp, '<');
+	      for (int i = indent_for_next_frame + per_frame_indent;
+		   i < cur_indent + per_frame_indent - 1; i++)
+		pp_character (pp, '-');
+	      pp_character (pp, '+');
+	      pp_string (pp, end_line_color);
+	      pp_newline (pp);
+	      cur_indent = indent_for_next_frame;
+
+	      write_indent (pp, vbar_for_next_frame);
+	      pp_string (pp, start_line_color);
+	      pp_printf (pp, "|");
+	      pp_string (pp, end_line_color);
+	      pp_newline (pp);
+	    }
+	  else if (range->m_stack_depth < next_range->m_stack_depth)
+	    {
+	      /* Prepare to show pushed stack frame.  */
+	      gcc_assert (range->m_stack_depth != EMPTY);
+	      gcc_assert (range->m_stack_depth != DELETED);
+	      vbar_column_for_depth.put (range->m_stack_depth,
+					 cur_indent + per_frame_indent);
+	      cur_indent += per_frame_indent;
+	    }
+
+	}
+    }
+}
+
+} /* end of anonymous namespace for path-printing code.  */
+
+/* Print PATH to CONTEXT, according to CONTEXT's path_format.  */
+
+void
+default_tree_diagnostic_path_printer (diagnostic_context *context,
+				      const diagnostic_path *path)
+{
+  gcc_assert (path);
+
+  const unsigned num_events = path->num_events ();
+
+  switch (context->path_format)
+    {
+    case DPF_NONE:
+      /* Do nothing.  */
+      return;
+
+    case DPF_SEPARATE_EVENTS:
+      {
+	/* A note per event.  */
+	for (unsigned i = 0; i < num_events; i++)
+	  {
+	    const diagnostic_event &event = path->get_event (i);
+	    label_text event_text (event.get_desc (false));
+	    gcc_assert (event_text.m_buffer);
+	    diagnostic_event_id_t event_id (i);
+	    inform (event.get_location (),
+		    "%@ %s", &event_id, event_text.m_buffer);
+	    event_text.maybe_free ();
+	  }
+      }
+      break;
+
+    case DPF_INLINE_EVENTS:
+      {
+	/* Consolidate related events.  */
+	path_summary summary (*path, true);
+	char *saved_prefix = pp_take_prefix (context->printer);
+	pp_set_prefix (context->printer, NULL);
+	summary.print (context, context->show_path_depths);
+	pp_flush (context->printer);
+	pp_set_prefix (context->printer, saved_prefix);
+      }
+    }
+}
+
+/* This has to be here, rather than diagnostic-format-json.cc,
+   since diagnostic-format-json.o is within OBJS-libcommon and thus
+   doesn't have access to trees (for m_fndecl).  */
+
+json::value *
+default_tree_make_json_for_path (diagnostic_context *, const diagnostic_path *path)
+{
+  json::array *path_array = new json::array ();
+  for (unsigned i = 0; i < path->num_events (); i++)
+    {
+      const diagnostic_event &event = path->get_event (i);
+
+      json::object *event_obj = new json::object ();
+      if (event.get_location ())
+	event_obj->set ("location",
+			json_from_expanded_location (event.get_location ()));
+      label_text event_text (event.get_desc (false));
+      event_obj->set ("description", new json::string (event_text.m_buffer));
+      event_text.maybe_free ();
+      if (tree fndecl = event.get_fndecl ())
+	{
+	  const char *function
+	    = identifier_to_locale (lang_hooks.decl_printable_name (fndecl, 2));
+	  event_obj->set ("function", new json::string (function));
+	}
+      event_obj->set ("depth", new json::number (event.get_stack_depth ()));
+      path_array->append (event_obj);
+    }
+  return path_array;
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* A subclass of simple_diagnostic_path that adds member functions
+   for adding test events.  */
+
+class test_diagnostic_path : public simple_diagnostic_path
+{
+ public:
+  test_diagnostic_path (pretty_printer *event_pp)
+  : simple_diagnostic_path (event_pp)
+  {
+  }
+
+  void add_entry (tree fndecl, int stack_depth)
+  {
+    add_event (UNKNOWN_LOCATION, fndecl, stack_depth,
+	       "entering %qE", fndecl);
+  }
+
+  void add_return (tree fndecl, int stack_depth)
+  {
+    add_event (UNKNOWN_LOCATION, fndecl, stack_depth,
+	       "returning to %qE", fndecl);
+  }
+
+  void add_call (tree caller, int caller_stack_depth, tree callee)
+  {
+    add_event (UNKNOWN_LOCATION, caller, caller_stack_depth,
+	       "calling %qE", callee);
+    add_entry (callee, caller_stack_depth + 1);
+  }
+};
+
+/* Verify that empty paths are handled gracefully.  */
+
+static void
+test_empty_path (pretty_printer *event_pp)
+{
+  test_diagnostic_path path (event_pp);
+  ASSERT_FALSE (path.interprocedural_p ());
+
+  path_summary summary (path, false);
+  ASSERT_EQ (summary.get_num_ranges (), 0);
+
+  test_diagnostic_context dc;
+  summary.print (&dc, true);
+  ASSERT_STREQ ("",
+		pp_formatted_text (dc.printer));
+}
+
+/* Verify that print_path_summary works on a purely intraprocedural path.  */
+
+static void
+test_intraprocedural_path (pretty_printer *event_pp)
+{
+  tree fntype_void_void
+    = build_function_type_array (void_type_node, 0, NULL);
+  tree fndecl_foo = build_fn_decl ("foo", fntype_void_void);
+
+  test_diagnostic_path path (event_pp);
+  path.add_event (UNKNOWN_LOCATION, fndecl_foo, 0, "first %qs", "free");
+  path.add_event (UNKNOWN_LOCATION, fndecl_foo, 0, "double %qs", "free");
+
+  ASSERT_FALSE (path.interprocedural_p ());
+
+  path_summary summary (path, false);
+  ASSERT_EQ (summary.get_num_ranges (), 1);
+
+  test_diagnostic_context dc;
+  summary.print (&dc, true);
+  ASSERT_STREQ ("  `foo': events 1-2 (depth 0)\n"
+		"    |\n"
+		"    | (1): first `free'\n"
+		"    | (2): double `free'\n"
+		"    |\n",
+		pp_formatted_text (dc.printer));
+}
+
+/* Verify that print_path_summary works on an interprocedural path.  */
+
+static void
+test_interprocedural_path_1 (pretty_printer *event_pp)
+{
+  /* Build fndecls.  The types aren't quite right, but that
+     doesn't matter for the purposes of this test.  */
+  tree fntype_void_void
+    = build_function_type_array (void_type_node, 0, NULL);
+  tree fndecl_test = build_fn_decl ("test", fntype_void_void);
+  tree fndecl_make_boxed_int
+    = build_fn_decl ("make_boxed_int", fntype_void_void);
+  tree fndecl_wrapped_malloc
+    = build_fn_decl ("wrapped_malloc", fntype_void_void);
+  tree fndecl_free_boxed_int
+    = build_fn_decl ("free_boxed_int", fntype_void_void);
+  tree fndecl_wrapped_free
+    = build_fn_decl ("wrapped_free", fntype_void_void);
+
+  test_diagnostic_path path (event_pp);
+  path.add_entry (fndecl_test, 0);
+  path.add_call (fndecl_test, 0, fndecl_make_boxed_int);
+  path.add_call (fndecl_make_boxed_int, 1, fndecl_wrapped_malloc);
+  path.add_event (UNKNOWN_LOCATION, fndecl_wrapped_malloc, 2, "calling malloc");
+  path.add_return (fndecl_test, 0);
+  path.add_call (fndecl_test, 0, fndecl_free_boxed_int);
+  path.add_call (fndecl_free_boxed_int, 1, fndecl_wrapped_free);
+  path.add_event (UNKNOWN_LOCATION, fndecl_wrapped_free, 2, "calling free");
+  path.add_return (fndecl_test, 0);
+  path.add_call (fndecl_test, 0, fndecl_free_boxed_int);
+  path.add_call (fndecl_free_boxed_int, 1, fndecl_wrapped_free);
+  path.add_event (UNKNOWN_LOCATION, fndecl_wrapped_free, 2, "calling free");
+  ASSERT_EQ (path.num_events (), 18);
+
+  ASSERT_TRUE (path.interprocedural_p ());
+
+  path_summary summary (path, false);
+  ASSERT_EQ (summary.get_num_ranges (), 9);
+
+  test_diagnostic_context dc;
+  summary.print (&dc, true);
+  ASSERT_STREQ
+    ("  `test': events 1-2 (depth 0)\n"
+     "    |\n"
+     "    | (1): entering `test'\n"
+     "    | (2): calling `make_boxed_int'\n"
+     "    |\n"
+     "    +--> `make_boxed_int': events 3-4 (depth 1)\n"
+     "           |\n"
+     "           | (3): entering `make_boxed_int'\n"
+     "           | (4): calling `wrapped_malloc'\n"
+     "           |\n"
+     "           +--> `wrapped_malloc': events 5-6 (depth 2)\n"
+     "                  |\n"
+     "                  | (5): entering `wrapped_malloc'\n"
+     "                  | (6): calling malloc\n"
+     "                  |\n"
+     "    <-------------+\n"
+     "    |\n"
+     "  `test': events 7-8 (depth 0)\n"
+     "    |\n"
+     "    | (7): returning to `test'\n"
+     "    | (8): calling `free_boxed_int'\n"
+     "    |\n"
+     "    +--> `free_boxed_int': events 9-10 (depth 1)\n"
+     "           |\n"
+     "           | (9): entering `free_boxed_int'\n"
+     "           | (10): calling `wrapped_free'\n"
+     "           |\n"
+     "           +--> `wrapped_free': events 11-12 (depth 2)\n"
+     "                  |\n"
+     "                  | (11): entering `wrapped_free'\n"
+     "                  | (12): calling free\n"
+     "                  |\n"
+     "    <-------------+\n"
+     "    |\n"
+     "  `test': events 13-14 (depth 0)\n"
+     "    |\n"
+     "    | (13): returning to `test'\n"
+     "    | (14): calling `free_boxed_int'\n"
+     "    |\n"
+     "    +--> `free_boxed_int': events 15-16 (depth 1)\n"
+     "           |\n"
+     "           | (15): entering `free_boxed_int'\n"
+     "           | (16): calling `wrapped_free'\n"
+     "           |\n"
+     "           +--> `wrapped_free': events 17-18 (depth 2)\n"
+     "                  |\n"
+     "                  | (17): entering `wrapped_free'\n"
+     "                  | (18): calling free\n"
+     "                  |\n",
+     pp_formatted_text (dc.printer));
+}
+
+/* Example where we pop the stack to an intermediate frame, rather than the
+   initial one.  */
+
+static void
+test_interprocedural_path_2 (pretty_printer *event_pp)
+{
+  /* Build fndecls.  The types aren't quite right, but that
+     doesn't matter for the purposes of this test.  */
+  tree fntype_void_void
+    = build_function_type_array (void_type_node, 0, NULL);
+  tree fndecl_foo = build_fn_decl ("foo", fntype_void_void);
+  tree fndecl_bar = build_fn_decl ("bar", fntype_void_void);
+  tree fndecl_baz = build_fn_decl ("baz", fntype_void_void);
+
+  test_diagnostic_path path (event_pp);
+  path.add_entry (fndecl_foo, 0);
+  path.add_call (fndecl_foo, 0, fndecl_bar);
+  path.add_call (fndecl_bar, 1, fndecl_baz);
+  path.add_return (fndecl_bar, 1);
+  path.add_call (fndecl_bar, 1, fndecl_baz);
+  ASSERT_EQ (path.num_events (), 8);
+
+  ASSERT_TRUE (path.interprocedural_p ());
+
+  path_summary summary (path, false);
+  ASSERT_EQ (summary.get_num_ranges (), 5);
+
+  test_diagnostic_context dc;
+  summary.print (&dc, true);
+  ASSERT_STREQ
+    ("  `foo': events 1-2 (depth 0)\n"
+     "    |\n"
+     "    | (1): entering `foo'\n"
+     "    | (2): calling `bar'\n"
+     "    |\n"
+     "    +--> `bar': events 3-4 (depth 1)\n"
+     "           |\n"
+     "           | (3): entering `bar'\n"
+     "           | (4): calling `baz'\n"
+     "           |\n"
+     "           +--> `baz': event 5 (depth 2)\n"
+     "                  |\n"
+     "                  | (5): entering `baz'\n"
+     "                  |\n"
+     "           <------+\n"
+     "           |\n"
+     "         `bar': events 6-7 (depth 1)\n"
+     "           |\n"
+     "           | (6): returning to `bar'\n"
+     "           | (7): calling `baz'\n"
+     "           |\n"
+     "           +--> `baz': event 8 (depth 2)\n"
+     "                  |\n"
+     "                  | (8): entering `baz'\n"
+     "                  |\n",
+     pp_formatted_text (dc.printer));
+}
+
+/* Verify that print_path_summary is sane in the face of a recursive
+   diagnostic_path.  */
+
+static void
+test_recursion (pretty_printer *event_pp)
+{
+  tree fntype_void_void
+    = build_function_type_array (void_type_node, 0, NULL);
+  tree fndecl_factorial = build_fn_decl ("factorial", fntype_void_void);
+
+ test_diagnostic_path path (event_pp);
+  path.add_entry (fndecl_factorial, 0);
+  for (int depth = 0; depth < 3; depth++)
+    path.add_call (fndecl_factorial, depth, fndecl_factorial);
+  ASSERT_EQ (path.num_events (), 7);
+
+  ASSERT_TRUE (path.interprocedural_p ());
+
+  path_summary summary (path, false);
+  ASSERT_EQ (summary.get_num_ranges (), 4);
+
+  test_diagnostic_context dc;
+  summary.print (&dc, true);
+  ASSERT_STREQ
+    ("  `factorial': events 1-2 (depth 0)\n"
+     "    |\n"
+     "    | (1): entering `factorial'\n"
+     "    | (2): calling `factorial'\n"
+     "    |\n"
+     "    +--> `factorial': events 3-4 (depth 1)\n"
+     "           |\n"
+     "           | (3): entering `factorial'\n"
+     "           | (4): calling `factorial'\n"
+     "           |\n"
+     "           +--> `factorial': events 5-6 (depth 2)\n"
+     "                  |\n"
+     "                  | (5): entering `factorial'\n"
+     "                  | (6): calling `factorial'\n"
+     "                  |\n"
+     "                  +--> `factorial': event 7 (depth 3)\n"
+     "                         |\n"
+     "                         | (7): entering `factorial'\n"
+     "                         |\n",
+     pp_formatted_text (dc.printer));
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+tree_diagnostic_path_cc_tests ()
+{
+  auto_fix_quotes fix_quotes;
+  pretty_printer *event_pp = global_dc->printer->clone ();
+  pp_show_color (event_pp) = 0;
+  test_empty_path (event_pp);
+  test_intraprocedural_path (event_pp);
+  test_interprocedural_path_1 (event_pp);
+  test_interprocedural_path_2 (event_pp);
+  test_recursion (event_pp);
+  delete event_pp;
+}
+
+} // namespace selftest
+
+#endif /* #if CHECKING_P */
diff --git a/gcc/tree-diagnostic.c b/gcc/tree-diagnostic.c
index 7bd7430..d5f2f9e 100644
--- a/gcc/tree-diagnostic.c
+++ b/gcc/tree-diagnostic.c
@@ -96,9 +96,8 @@ struct loc_map_pair
    unwound macro expansion trace.  That's the part generated by this
    function.  */
 
-static void
+void
 maybe_unwind_expanded_macro_loc (diagnostic_context *context,
-                                 const diagnostic_info *diagnostic,
                                  location_t where)
 {
   const struct line_map *map;
@@ -106,6 +105,8 @@ maybe_unwind_expanded_macro_loc (diagnostic_context *context,
   unsigned ix;
   loc_map_pair loc, *iter;
 
+  const location_t original_loc = where;
+
   map = linemap_lookup (line_table, where);
   if (!linemap_macro_expansion_map_p (map))
     return;
@@ -142,7 +143,7 @@ maybe_unwind_expanded_macro_loc (diagnostic_context *context,
      first macro which expansion triggered this trace was expanded
      inside a system header.  */
   int saved_location_line =
-    expand_location_to_spelling_point (diagnostic_location (diagnostic)).line;
+    expand_location_to_spelling_point (original_loc).line;
 
   if (!LINEMAP_SYSP (ord_map))
     FOR_EACH_VEC_ELT (loc_vec, ix, iter)
@@ -238,8 +239,7 @@ void
 virt_loc_aware_diagnostic_finalizer (diagnostic_context *context,
 				     diagnostic_info *diagnostic)
 {
-  maybe_unwind_expanded_macro_loc (context, diagnostic,
-				   diagnostic_location (diagnostic));
+  maybe_unwind_expanded_macro_loc (context, diagnostic_location (diagnostic));
 }
 
 /* Default tree printer.   Handles declarations only.  */
@@ -312,4 +312,6 @@ tree_diagnostics_defaults (diagnostic_context *context)
   diagnostic_starter (context) = default_tree_diagnostic_starter;
   diagnostic_finalizer (context) = default_diagnostic_finalizer;
   diagnostic_format_decoder (context) = default_tree_printer;
+  context->print_path = default_tree_diagnostic_path_printer;
+  context->make_json_for_path = default_tree_make_json_for_path;
 }
diff --git a/gcc/tree-diagnostic.h b/gcc/tree-diagnostic.h
index 3ce61c0..d97a973 100644
--- a/gcc/tree-diagnostic.h
+++ b/gcc/tree-diagnostic.h
@@ -57,4 +57,12 @@ void tree_diagnostics_defaults (diagnostic_context *context);
 bool default_tree_printer (pretty_printer *, text_info *, const char *,
 			   int, bool, bool, bool, bool *, const char **);
 
+extern void default_tree_diagnostic_path_printer (diagnostic_context *,
+						  const diagnostic_path *);
+extern json::value *default_tree_make_json_for_path (diagnostic_context *,
+						     const diagnostic_path *);
+
+extern void maybe_unwind_expanded_macro_loc (diagnostic_context *context,
+					     location_t where);
+
 #endif /* ! GCC_TREE_DIAGNOSTIC_H */
diff --git a/libcpp/include/line-map.h b/libcpp/include/line-map.h
index e4c7952..bd26fb7c 100644
--- a/libcpp/include/line-map.h
+++ b/libcpp/include/line-map.h
@@ -1459,6 +1459,7 @@ semi_embedded_vec<T, NUM_EMBEDDED>::truncate (int len)
 }
 
 class fixit_hint;
+class diagnostic_path;
 
 /* A "rich" source code location, for use when printing diagnostics.
    A rich_location has one or more carets&ranges, where the carets
@@ -1754,6 +1755,10 @@ class rich_location
     return !m_fixits_cannot_be_auto_applied;
   }
 
+  /* An optional path through the code.  */
+  const diagnostic_path *get_path () const { return m_path; }
+  void set_path (const diagnostic_path *path) { m_path = path; }
+
 private:
   bool reject_impossible_fixit (location_t where);
   void stop_supporting_fixits ();
@@ -1778,6 +1783,8 @@ protected:
 
   bool m_seen_impossible_fixit;
   bool m_fixits_cannot_be_auto_applied;
+
+  const diagnostic_path *m_path;
 };
 
 /* A struct for the result of range_label::get_text: a NUL-terminated buffer
diff --git a/libcpp/line-map.c b/libcpp/line-map.c
index feeb748..6f936e8 100644
--- a/libcpp/line-map.c
+++ b/libcpp/line-map.c
@@ -2006,7 +2006,8 @@ rich_location::rich_location (line_maps *set, location_t loc,
   m_have_expanded_location (false),
   m_fixit_hints (),
   m_seen_impossible_fixit (false),
-  m_fixits_cannot_be_auto_applied (false)
+  m_fixits_cannot_be_auto_applied (false),
+  m_path (NULL)
 {
   add_range (loc, SHOW_RANGE_WITH_CARET, label);
 }
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 30/49] analyzer: new files: constraint-manager.{cc|h}
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (12 preceding siblings ...)
  2019-11-16  1:18 ` [PATCH 12/49] Add diagnostic paths David Malcolm
@ 2019-11-16  1:18 ` David Malcolm
  2019-11-16  1:18 ` [PATCH 19/49] analyzer: new files: analyzer-selftests.{cc|h} David Malcolm
                   ` (36 subsequent siblings)
  50 siblings, 0 replies; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:18 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This patch adds classes for tracking the equivalence classes and
constraints that hold at a point on an execution path.

gcc/ChangeLog:
	* analyzer/constraint-manager.cc: New file.
	* analyzer/constraint-manager.h: New file.
---
 gcc/analyzer/constraint-manager.cc | 2263 ++++++++++++++++++++++++++++++++++++
 gcc/analyzer/constraint-manager.h  |  248 ++++
 2 files changed, 2511 insertions(+)
 create mode 100644 gcc/analyzer/constraint-manager.cc
 create mode 100644 gcc/analyzer/constraint-manager.h

diff --git a/gcc/analyzer/constraint-manager.cc b/gcc/analyzer/constraint-manager.cc
new file mode 100644
index 0000000..12846f2
--- /dev/null
+++ b/gcc/analyzer/constraint-manager.cc
@@ -0,0 +1,2263 @@
+/* Tracking equivalence classes and constraints at a point on an execution path.
+   Copyright (C) 2019 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 "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "graphviz.h"
+#include "selftest.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/supergraph.h"
+#include "analyzer/constraint-manager.h"
+#include "analyzer/analyzer-selftests.h"
+
+/* One of the end-points of a range.  */
+
+struct bound
+{
+  bound () : m_constant (NULL_TREE), m_closed (false) {}
+  bound (tree constant, bool closed)
+  : m_constant (constant), m_closed (closed) {}
+
+  void ensure_closed (bool is_upper);
+
+  const char * get_relation_as_str () const;
+
+  tree m_constant;
+  bool m_closed;
+};
+
+/* A range of values, used for determining if a value has been
+   constrained to just one possible constant value.  */
+
+struct range
+{
+  range () : m_lower_bound (), m_upper_bound () {}
+  range (const bound &lower, const bound &upper)
+  : m_lower_bound (lower), m_upper_bound (upper) {}
+
+  void dump (pretty_printer *pp) const;
+
+  bool constrained_to_single_element (tree *out);
+
+  bound m_lower_bound;
+  bound m_upper_bound;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* struct bound.  */
+
+/* Ensure that this bound is closed by converting an open bound to a
+   closed one.  */
+
+void
+bound::ensure_closed (bool is_upper)
+{
+  if (!m_closed)
+    {
+      /* Offset by 1 in the appropriate direction.
+	 For example, convert 3 < x into 4 <= x,
+	 and convert x < 5 into x <= 4.  */
+      gcc_assert (CONSTANT_CLASS_P (m_constant));
+      m_constant = fold_build2 (is_upper ? MINUS_EXPR : PLUS_EXPR,
+				TREE_TYPE (m_constant),
+				m_constant, integer_one_node);
+      gcc_assert (CONSTANT_CLASS_P (m_constant));
+      m_closed = true;
+    }
+}
+
+/* Get "<=" vs "<" for this bound.  */
+
+const char *
+bound::get_relation_as_str () const
+{
+  if (m_closed)
+    return "<=";
+  else
+    return "<";
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* struct range.  */
+
+/* Dump this range to PP, which must support %E for tree.  */
+
+void
+range::dump (pretty_printer *pp) const
+{
+PUSH_IGNORE_WFORMAT
+  pp_printf (pp, "%qE %s x %s %qE",
+	     m_lower_bound.m_constant,
+	     m_lower_bound.get_relation_as_str (),
+	     m_upper_bound.get_relation_as_str (),
+	     m_upper_bound.m_constant);
+POP_IGNORE_WFORMAT
+}
+
+/* Determine if there is only one possible value for this range.
+   If so, return true and write the constant to *OUT.
+   Otherwise, return false.  */
+
+bool
+range::constrained_to_single_element (tree *out)
+{
+  if (!INTEGRAL_TYPE_P (TREE_TYPE (m_lower_bound.m_constant)))
+    return false;
+  if (!INTEGRAL_TYPE_P (TREE_TYPE (m_upper_bound.m_constant)))
+    return false;
+
+  /* Convert any open bounds to closed bounds.  */
+  m_lower_bound.ensure_closed (false);
+  m_upper_bound.ensure_closed (true);
+
+  // Are they equal?
+  tree comparison
+    = fold_build2 (EQ_EXPR, boolean_type_node,
+		   m_lower_bound.m_constant,
+		   m_upper_bound.m_constant);
+  if (comparison == boolean_true_node)
+    {
+      *out = m_lower_bound.m_constant;
+      return true;
+    }
+  else
+    return false;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class equiv_class.  */
+
+/* equiv_class's default ctor.  */
+
+equiv_class::equiv_class ()
+: m_constant (NULL_TREE), m_cst_sid (svalue_id::null ()),
+  m_vars ()
+{
+}
+
+/* equiv_class's copy ctor.  */
+
+equiv_class::equiv_class (const equiv_class &other)
+: m_constant (other.m_constant), m_cst_sid (other.m_cst_sid),
+  m_vars (other.m_vars.length ())
+{
+  int i;
+  svalue_id *sid;
+  FOR_EACH_VEC_ELT (other.m_vars, i, sid)
+    m_vars.quick_push (*sid);
+}
+
+/* Print an all-on-one-line representation of this equiv_class to PP,
+   which must support %E for trees.  */
+
+void
+equiv_class::print (pretty_printer *pp) const
+{
+  pp_character (pp, '{');
+  int i;
+  svalue_id *sid;
+  FOR_EACH_VEC_ELT (m_vars, i, sid)
+    {
+      if (i > 0)
+	pp_string (pp, " == ");
+      sid->print (pp);
+    }
+  if (m_constant)
+    {
+      if (i > 0)
+	pp_string (pp, " == ");
+PUSH_IGNORE_WFORMAT
+      pp_printf (pp, "%qE", m_constant);
+POP_IGNORE_WFORMAT
+    }
+  pp_character (pp, '}');
+}
+
+/* Generate a hash value for this equiv_class.  */
+
+hashval_t
+equiv_class::hash () const
+{
+  inchash::hash hstate;
+  int i;
+  svalue_id *sid;
+
+  inchash::add_expr (m_constant, hstate);
+  FOR_EACH_VEC_ELT (m_vars, i, sid)
+    inchash::add (*sid, hstate);
+  return hstate.end ();
+}
+
+/* Equality operator for equiv_class.  */
+
+bool
+equiv_class::operator== (const equiv_class &other)
+{
+  if (m_constant != other.m_constant)
+    return false; // TODO: use tree equality here?
+
+  /* FIXME: should we compare m_cst_sid?  */
+
+  if (m_vars.length () != other.m_vars.length ())
+    return false;
+
+  int i;
+  svalue_id *sid;
+  FOR_EACH_VEC_ELT (m_vars, i, sid)
+    if (! (*sid == other.m_vars[i]))
+      return false;
+
+  return true;
+}
+
+/* Add SID to this equiv_class, using CM to check if it's a constant.  */
+
+void
+equiv_class::add (svalue_id sid, const constraint_manager &cm)
+{
+  gcc_assert (!sid.null_p ());
+  if (tree cst = cm.maybe_get_constant (sid))
+    {
+      gcc_assert (CONSTANT_CLASS_P (cst));
+      /* FIXME: should we canonicalize which svalue is the constant
+	 when there are multiple equal constants?  */
+      m_constant = cst;
+      m_cst_sid = sid;
+    }
+  m_vars.safe_push (sid);
+}
+
+/* Remove SID from this equivalence class.
+   Return true if SID was the last var in the equivalence class (suggesting
+   a possible leak).  */
+
+bool
+equiv_class::del (svalue_id sid)
+{
+  gcc_assert (!sid.null_p ());
+  gcc_assert (sid != m_cst_sid);
+
+  int i;
+  svalue_id *iv;
+  FOR_EACH_VEC_ELT (m_vars, i, iv)
+    {
+      if (*iv == sid)
+	{
+	  m_vars[i] = m_vars[m_vars.length () - 1];
+	  m_vars.pop ();
+	  return m_vars.length () == 0;
+	}
+    }
+
+  /* SID must be in the class.  */
+  gcc_unreachable ();
+  return false;
+}
+
+/* Get a representative member of this class, for handling cases
+   where the IDs can change mid-traversal.  */
+
+svalue_id
+equiv_class::get_representative () const
+{
+  if (!m_cst_sid.null_p ())
+    return m_cst_sid;
+  else
+    {
+      gcc_assert (m_vars.length () > 0);
+      return m_vars[0];
+    }
+}
+
+/* Remap all svalue_ids within this equiv_class using MAP.  */
+
+void
+equiv_class::remap_svalue_ids (const svalue_id_map &map)
+{
+  int i;
+  svalue_id *iv;
+  FOR_EACH_VEC_ELT (m_vars, i, iv)
+    map.update (iv);
+  map.update (&m_cst_sid);
+}
+
+/* Comparator for use by equiv_class::canonicalize.  */
+
+static int
+svalue_id_cmp_by_id (const void *p1, const void *p2)
+{
+  const svalue_id *sid1 = (const svalue_id *)p1;
+  const svalue_id *sid2 = (const svalue_id *)p2;
+  return sid1->as_int () - sid2->as_int ();
+}
+
+/* Sort the svalues_ids within this equiv_class.  */
+
+void
+equiv_class::canonicalize ()
+{
+  m_vars.qsort (svalue_id_cmp_by_id);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Get a debug string for C_OP.  */
+
+const char *
+constraint_op_code (enum constraint_op c_op)
+{
+  switch (c_op)
+    {
+    default:
+      gcc_unreachable ();
+    case CONSTRAINT_NE: return "!=";
+    case CONSTRAINT_LT: return "<";
+    case CONSTRAINT_LE: return "<=";
+    }
+}
+
+/* Convert C_OP to an enum tree_code.  */
+
+enum tree_code
+constraint_tree_code (enum constraint_op c_op)
+{
+  switch (c_op)
+    {
+    default:
+      gcc_unreachable ();
+    case CONSTRAINT_NE: return NE_EXPR;
+    case CONSTRAINT_LT: return LT_EXPR;
+    case CONSTRAINT_LE: return LE_EXPR;
+    }
+}
+
+/* Given "lhs C_OP rhs", determine "lhs T_OP rhs".
+
+   For example, given "x < y", then "x > y" is false.  */
+
+static tristate
+eval_constraint_op_for_op (enum constraint_op c_op, enum tree_code t_op)
+{
+  switch (c_op)
+    {
+    default:
+      gcc_unreachable ();
+    case CONSTRAINT_NE:
+      if (t_op == EQ_EXPR)
+	return tristate (tristate::TS_FALSE);
+      if (t_op == NE_EXPR)
+	return tristate (tristate::TS_TRUE);
+      break;
+    case CONSTRAINT_LT:
+      if (t_op == LT_EXPR || t_op == LE_EXPR || t_op == NE_EXPR)
+	return tristate (tristate::TS_TRUE);
+      if (t_op == EQ_EXPR || t_op == GT_EXPR || t_op == GE_EXPR)
+	return tristate (tristate::TS_FALSE);
+      break;
+    case CONSTRAINT_LE:
+      if (t_op == LE_EXPR)
+	return tristate (tristate::TS_TRUE);
+      if (t_op == GT_EXPR)
+	return tristate (tristate::TS_FALSE);
+      break;
+    }
+  return tristate (tristate::TS_UNKNOWN);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class constraint.  */
+
+/* Print this constraint to PP (which must support %E for trees),
+   using CM to look up equiv_class instances from ids.  */
+
+void
+constraint::print (pretty_printer *pp, const constraint_manager &cm) const
+{
+  m_lhs.print (pp);
+  pp_string (pp, ": ");
+  m_lhs.get_obj (cm).print (pp);
+  pp_string (pp, " ");
+  pp_string (pp, constraint_op_code (m_op));
+  pp_string (pp, " ");
+  m_rhs.print (pp);
+  pp_string (pp, ": ");
+  m_rhs.get_obj (cm).print (pp);
+}
+
+/* Generate a hash value for this constraint.  */
+
+hashval_t
+constraint::hash () const
+{
+  inchash::hash hstate;
+  hstate.add_int (m_lhs.m_idx);
+  hstate.add_int (m_op);
+  hstate.add_int (m_rhs.m_idx);
+  return hstate.end ();
+}
+
+/* Equality operator for constraints.  */
+
+bool
+constraint::operator== (const constraint &other) const
+{
+  if (m_lhs != other.m_lhs)
+    return false;
+  if (m_op != other.m_op)
+    return false;
+  if (m_rhs != other.m_rhs)
+    return false;
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class equiv_class_id.  */
+
+/* Get the underlying equiv_class for this ID from CM.  */
+
+const equiv_class &
+equiv_class_id::get_obj (const constraint_manager &cm) const
+{
+  return cm.get_equiv_class_by_index (m_idx);
+}
+
+/* Access the underlying equiv_class for this ID from CM.  */
+
+equiv_class &
+equiv_class_id::get_obj (constraint_manager &cm) const
+{
+  return cm.get_equiv_class_by_index (m_idx);
+}
+
+/* Print this equiv_class_id to PP.  */
+
+void
+equiv_class_id::print (pretty_printer *pp) const
+{
+  if (null_p ())
+    pp_printf (pp, "null");
+  else
+    pp_printf (pp, "ec%i", m_idx);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class constraint_manager.  */
+
+/* constraint_manager's copy ctor.  */
+
+constraint_manager::constraint_manager (const constraint_manager &other)
+: m_equiv_classes (other.m_equiv_classes.length ()),
+  m_constraints (other.m_constraints.length ())
+{
+  int i;
+  equiv_class *ec;
+  FOR_EACH_VEC_ELT (other.m_equiv_classes, i, ec)
+    m_equiv_classes.quick_push (new equiv_class (*ec));
+  constraint *c;
+  FOR_EACH_VEC_ELT (other.m_constraints, i, c)
+    m_constraints.quick_push (*c);
+}
+
+/* constraint_manager's assignment operator.  */
+
+constraint_manager&
+constraint_manager::operator= (const constraint_manager &other)
+{
+  gcc_assert (m_equiv_classes.length () == 0);
+  gcc_assert (m_constraints.length () == 0);
+
+  int i;
+  equiv_class *ec;
+  m_equiv_classes.reserve (other.m_equiv_classes.length ());
+  FOR_EACH_VEC_ELT (other.m_equiv_classes, i, ec)
+    m_equiv_classes.quick_push (new equiv_class (*ec));
+  constraint *c;
+  m_constraints.reserve (other.m_constraints.length ());
+  FOR_EACH_VEC_ELT (other.m_constraints, i, c)
+    m_constraints.quick_push (*c);
+
+  return *this;
+}
+
+/* Generate a hash value for this constraint_manager.  */
+
+hashval_t
+constraint_manager::hash () const
+{
+  inchash::hash hstate;
+  int i;
+  equiv_class *ec;
+  constraint *c;
+
+  FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+    hstate.merge_hash (ec->hash ());
+  FOR_EACH_VEC_ELT (m_constraints, i, c)
+    hstate.merge_hash (c->hash ());
+  return hstate.end ();
+}
+
+/* Equality operator for constraint_manager.  */
+
+bool
+constraint_manager::operator== (const constraint_manager &other) const
+{
+  if (m_equiv_classes.length () != other.m_equiv_classes.length ())
+    return false;
+  if (m_constraints.length () != other.m_constraints.length ())
+    return false;
+
+  int i;
+  equiv_class *ec;
+
+  FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+    if (!(*ec == *other.m_equiv_classes[i]))
+      return false;
+
+  constraint *c;
+
+  FOR_EACH_VEC_ELT (m_constraints, i, c)
+    if (!(*c == other.m_constraints[i]))
+      return false;
+
+  return true;
+}
+
+/* Print this constraint_manager to PP (which must support %E for trees).  */
+
+void
+constraint_manager::print (pretty_printer *pp) const
+{
+  pp_string (pp, "{");
+  int i;
+  equiv_class *ec;
+  FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+    {
+      if (i > 0)
+	pp_string (pp, ", ");
+      equiv_class_id (i).print (pp);
+      pp_string (pp, ": ");
+      ec->print (pp);
+    }
+  pp_string (pp, "  |  ");
+  constraint *c;
+  FOR_EACH_VEC_ELT (m_constraints, i, c)
+    {
+      if (i > 0)
+	pp_string (pp, " && ");
+      c->print (pp, *this);
+    }
+  pp_printf (pp, "}");
+}
+
+/* Dump a multiline representation of this constraint_manager to PP
+   (which must support %E for trees).  */
+
+void
+constraint_manager::dump_to_pp (pretty_printer *pp) const
+{
+  // TODO
+  pp_string (pp, "  equiv classes:");
+  pp_newline (pp);
+  int i;
+  equiv_class *ec;
+  FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+    {
+      pp_string (pp, "    ");
+      equiv_class_id (i).print (pp);
+      pp_string (pp, ": ");
+      ec->print (pp);
+      pp_newline (pp);
+    }
+  pp_string (pp, "  constraints:");
+  pp_newline (pp);
+  constraint *c;
+  FOR_EACH_VEC_ELT (m_constraints, i, c)
+    {
+      pp_printf (pp, "    %i: ", i);
+      c->print (pp, *this);
+      pp_newline (pp);
+    }
+}
+
+/* Dump a multiline representation of this constraint_manager to FP.  */
+
+void
+constraint_manager::dump (FILE *fp) const
+{
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  pp_show_color (&pp) = pp_show_color (global_dc->printer);
+  pp.buffer->stream = fp;
+  dump_to_pp (&pp);
+  pp_flush (&pp);
+}
+
+/* Dump a multiline representation of this constraint_manager to stderr.  */
+
+DEBUG_FUNCTION void
+constraint_manager::dump () const
+{
+  dump (stderr);
+}
+
+/* Dump a multiline representation of CM to stderr.  */
+
+DEBUG_FUNCTION void
+debug (const constraint_manager &cm)
+{
+  cm.dump ();
+}
+
+/* Attempt to add the constraint LHS OP RHS to this constraint_manager.
+   Return true if the constraint could be added (or is already true).
+   Return false if the constraint contradicts existing knowledge.  */
+
+bool
+constraint_manager::add_constraint (svalue_id lhs,
+				    enum tree_code op,
+				    svalue_id rhs)
+{
+  equiv_class_id lhs_ec_id = get_or_add_equiv_class (lhs);
+  equiv_class_id rhs_ec_id = get_or_add_equiv_class (rhs);
+  return add_constraint (lhs_ec_id, op,rhs_ec_id);
+}
+
+/* Attempt to add the constraint LHS_EC_ID OP RHS_EC_ID to this
+   constraint_manager.
+   Return true if the constraint could be added (or is already true).
+   Return false if the constraint contradicts existing knowledge.  */
+
+bool
+constraint_manager::add_constraint (equiv_class_id lhs_ec_id,
+				    enum tree_code op,
+				    equiv_class_id rhs_ec_id)
+{
+  tristate t = eval_condition (lhs_ec_id, op, rhs_ec_id);
+
+  /* Discard constraints that are already known.  */
+  if (t.is_true ())
+    return true;
+
+  /* Reject unsatisfiable constraints.  */
+  if (t.is_false ())
+    return false;
+
+  gcc_assert (lhs_ec_id != rhs_ec_id);
+
+  /* For now, simply accumulate constraints, without attempting any further
+     optimization.  */
+  switch (op)
+    {
+    case EQ_EXPR:
+      {
+	/* Merge rhs_ec into lhs_ec.  */
+	equiv_class &lhs_ec_obj = lhs_ec_id.get_obj (*this);
+	const equiv_class &rhs_ec_obj = rhs_ec_id.get_obj (*this);
+
+	int i;
+	svalue_id *sid;
+	FOR_EACH_VEC_ELT (rhs_ec_obj.m_vars, i, sid)
+	  lhs_ec_obj.add (*sid, *this);
+
+	if (rhs_ec_obj.m_constant)
+	  {
+	    //gcc_assert (lhs_ec_obj.m_constant == NULL);
+	    lhs_ec_obj.m_constant = rhs_ec_obj.m_constant;
+	  }
+
+	/* Drop rhs equivalence class, overwriting it with the
+	   final ec (which might be the same one).  */
+	equiv_class_id final_ec_id = m_equiv_classes.length () - 1;
+	equiv_class *old_ec = m_equiv_classes[rhs_ec_id.m_idx];
+	equiv_class *final_ec = m_equiv_classes.pop ();
+	if (final_ec != old_ec)
+	  m_equiv_classes[rhs_ec_id.m_idx] = final_ec;
+	delete old_ec;
+
+	/* Update the constraints.  */
+	constraint *c;
+	FOR_EACH_VEC_ELT (m_constraints, i, c)
+	  {
+	    /* Update references to the rhs_ec so that
+	       they refer to the lhs_ec.  */
+	    if (c->m_lhs == rhs_ec_id)
+	      c->m_lhs = lhs_ec_id;
+	    if (c->m_rhs == rhs_ec_id)
+	      c->m_rhs = lhs_ec_id;
+
+	    /* Renumber all constraints that refer to the final rhs_ec
+	       to the old rhs_ec, where the old final_ec now lives.  */
+	    if (c->m_lhs == final_ec_id)
+	      c->m_lhs = rhs_ec_id;
+	    if (c->m_rhs == final_ec_id)
+	      c->m_rhs = rhs_ec_id;
+	  }
+      }
+      break;
+    case GE_EXPR:
+      add_constraint_internal (rhs_ec_id, CONSTRAINT_LE, lhs_ec_id);
+      break;
+    case LE_EXPR:
+      add_constraint_internal (lhs_ec_id, CONSTRAINT_LE, rhs_ec_id);
+      break;
+    case NE_EXPR:
+      add_constraint_internal (lhs_ec_id, CONSTRAINT_NE, rhs_ec_id);
+      break;
+    case GT_EXPR:
+      add_constraint_internal (rhs_ec_id, CONSTRAINT_LT, lhs_ec_id);
+      break;
+    case LT_EXPR:
+      add_constraint_internal (lhs_ec_id, CONSTRAINT_LT, rhs_ec_id);
+      break;
+    default:
+      /* do nothing.  */
+      break;
+    }
+  validate ();
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Subroutine of constraint_manager::add_constraint, for handling all
+   operations other than equality (for which equiv classes are merged).  */
+
+void
+constraint_manager::add_constraint_internal (equiv_class_id lhs_id,
+					     enum constraint_op c_op,
+					     equiv_class_id rhs_id)
+{
+  /* Add the constraint.  */
+  m_constraints.safe_push (constraint (lhs_id, c_op, rhs_id));
+
+  if (!flag_analyzer_transitivity)
+    return;
+
+  if (c_op != CONSTRAINT_NE)
+    {
+      /* The following can potentially add EQ_EXPR facts, which could lead
+	 to ECs being merged, which would change the meaning of the EC IDs.
+	 Hence we need to do this via representatives.  */
+      svalue_id lhs = lhs_id.get_obj (*this).get_representative ();
+      svalue_id rhs = rhs_id.get_obj (*this).get_representative ();
+
+      /* We have LHS </<= RHS */
+
+      /* Handle transitivity of ordering by adding additional constraints
+	 based on what we already knew.
+
+	 So if we have already have:
+	   (a < b)
+	   (c < d)
+	 Then adding:
+	   (b < c)
+	 will also add:
+	   (a < c)
+	   (b < d)
+	 We need to recurse to ensure we also add:
+	   (a < d).
+	 We call the checked add_constraint to avoid adding constraints
+	 that are already present.  Doing so also ensures termination
+	 in the case of cycles.
+
+	 We also check for single-element ranges, adding EQ_EXPR facts
+	 where we discover them.  For example 3 < x < 5 implies
+	 that x == 4 (if x is an integer).  */
+      for (unsigned i = 0; i < m_constraints.length (); i++)
+	{
+	  const constraint *other = &m_constraints[i];
+	  if (other->is_ordering_p ())
+	    {
+	      /* Refresh the EC IDs, in case any mergers have happened.  */
+	      lhs_id = get_or_add_equiv_class (lhs);
+	      rhs_id = get_or_add_equiv_class (rhs);
+
+	      tree lhs_const = lhs_id.get_obj (*this).m_constant;
+	      tree rhs_const = rhs_id.get_obj (*this).m_constant;
+	      tree other_lhs_const
+		= other->m_lhs.get_obj (*this).m_constant;
+	      tree other_rhs_const
+		= other->m_rhs.get_obj (*this).m_constant;
+
+	      /* We have "LHS </<= RHS" and "other.lhs </<= other.rhs".  */
+
+	      /* If we have LHS </<= RHS and RHS </<= LHS, then we have a
+		 cycle.  */
+	      if (rhs_id == other->m_lhs
+		  && other->m_rhs == lhs_id)
+		{
+		  /* We must have equality for this to be possible.  */
+		  gcc_assert (c_op == CONSTRAINT_LE
+			      && other->m_op == CONSTRAINT_LE);
+		  add_constraint (lhs_id, EQ_EXPR, rhs_id);
+		  /* Adding an equality will merge the two ECs and potentially
+		     reorganize the constraints.  Stop iterating.  */
+		  return;
+		}
+	      /* Otherwise, check for transitivity.  */
+	      if (rhs_id == other->m_lhs)
+		{
+		  /* With RHS == other.lhs, we have:
+		     "LHS </<= (RHS, other.lhs) </<= other.rhs"
+		     and thus this implies "LHS </<= other.rhs".  */
+
+		  /* Do we have a tightly-constrained range?  */
+		  if (lhs_const
+		      && !rhs_const
+		      && other_rhs_const)
+		    {
+		      range r (bound (lhs_const, c_op == CONSTRAINT_LE),
+			       bound (other_rhs_const,
+				      other->m_op == CONSTRAINT_LE));
+		      tree constant;
+		      if (r.constrained_to_single_element (&constant))
+			{
+			  svalue_id cst_sid = get_sid_for_constant (constant);
+			  add_constraint
+			    (rhs_id, EQ_EXPR,
+			     get_or_add_equiv_class (cst_sid));
+			  return;
+			}
+		    }
+
+		  /* Otherwise, add the constraint implied by transitivity.  */
+		  enum tree_code new_op
+		    = ((c_op == CONSTRAINT_LE && other->m_op == CONSTRAINT_LE)
+		       ? LE_EXPR : LT_EXPR);
+		  add_constraint (lhs_id, new_op, other->m_rhs);
+		}
+	      else if (other->m_rhs == lhs_id)
+		{
+		  /* With other.rhs == LHS, we have:
+		     "other.lhs </<= (other.rhs, LHS) </<= RHS"
+		     and thus this implies "other.lhs </<= RHS".  */
+
+		  /* Do we have a tightly-constrained range?  */
+		  if (other_lhs_const
+		      && !lhs_const
+		      && rhs_const)
+		    {
+		      range r (bound (other_lhs_const,
+				      other->m_op == CONSTRAINT_LE),
+			       bound (rhs_const,
+				      c_op == CONSTRAINT_LE));
+		      tree constant;
+		      if (r.constrained_to_single_element (&constant))
+			{
+			  svalue_id cst_sid = get_sid_for_constant (constant);
+			  add_constraint
+			    (lhs_id, EQ_EXPR,
+			     get_or_add_equiv_class (cst_sid));
+			  return;
+			}
+		    }
+
+		  /* Otherwise, add the constraint implied by transitivity.  */
+		  enum tree_code new_op
+		    = ((c_op == CONSTRAINT_LE && other->m_op == CONSTRAINT_LE)
+		       ? LE_EXPR : LT_EXPR);
+		  add_constraint (other->m_lhs, new_op, rhs_id);
+		}
+	    }
+	}
+    }
+}
+
+/* Look for SID within the equivalence classes of this constraint_manager;
+   if found, write the id to *OUT and return true, otherwise return false.  */
+
+bool
+constraint_manager::get_equiv_class_by_sid (svalue_id sid, equiv_class_id *out) const
+{
+  /* TODO: should we have a map, rather than these searches?  */
+  int i;
+  equiv_class *ec;
+  FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+    {
+      int j;
+      svalue_id *iv;
+      FOR_EACH_VEC_ELT (ec->m_vars, j, iv)
+	if (*iv == sid)
+	  {
+	    *out = equiv_class_id (i);
+	    return true;
+	  }
+    }
+  return false;
+}
+
+/* Ensure that SID has an equivalence class within this constraint_manager;
+   return the ID of the class.  */
+
+equiv_class_id
+constraint_manager::get_or_add_equiv_class (svalue_id sid)
+{
+  equiv_class_id result (-1);
+
+  /* Try svalue_id match.  */
+  if (get_equiv_class_by_sid (sid, &result))
+    return result;
+
+  /* Try equality of constants.  */
+  if (tree cst = maybe_get_constant (sid))
+    {
+      int i;
+      equiv_class *ec;
+      FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+	if (ec->m_constant)
+	  {
+	    tree eq = fold_build2 (EQ_EXPR, boolean_type_node,
+				   cst, ec->m_constant);
+	    if (eq == boolean_true_node)
+	      {
+		ec->add (sid, *this);
+		return equiv_class_id (i);
+	      }
+	  }
+    }
+
+
+  /* Not found.  */
+  equiv_class *new_ec = new equiv_class ();
+  new_ec->add (sid, *this);
+  m_equiv_classes.safe_push (new_ec);
+
+  equiv_class_id new_id (m_equiv_classes.length () - 1);
+
+  if (maybe_get_constant (sid))
+    {
+      /* If we have a new EC for a constant, add constraints comparing this
+	 to other constants we may have (so that we accumulate the transitive
+	 closure of all constraints on constants as the constants are
+	 added).  */
+      for (equiv_class_id other_id (0); other_id.m_idx < new_id.m_idx;
+	   other_id.m_idx++)
+	{
+	  const equiv_class &other_ec = other_id.get_obj (*this);
+	  if (other_ec.m_constant)
+	    {
+	      /* If we have two ECs, both with constants, the constants must be
+		 non-equal (or they would be in the same EC).
+		 Determine the direction of the inequality, and record that
+		 fact.  */
+	      tree lt
+		= fold_build2 (LT_EXPR, boolean_type_node,
+			       new_ec->m_constant, other_ec.m_constant);
+	      //gcc_assert (lt == boolean_true_node || lt == boolean_false_node);
+	      // not true for int vs float comparisons
+	      if (lt == boolean_true_node)
+		add_constraint_internal (new_id, CONSTRAINT_LT, other_id);
+	      else if (lt == boolean_false_node)
+		add_constraint_internal (other_id, CONSTRAINT_LT, new_id);
+	      /* Refresh new_id, in case ECs were merged.  SID should always
+		 be present by now, so this should never lead to a
+		 recursion.  */
+	      new_id = get_or_add_equiv_class (sid);
+	    }
+	}
+    }
+
+  return new_id;
+}
+
+/* Evaluate the condition LHS_EC OP RHS_EC.  */
+
+tristate
+constraint_manager::eval_condition (equiv_class_id lhs_ec,
+				    enum tree_code op,
+				    equiv_class_id rhs_ec)
+{
+  if (lhs_ec == rhs_ec)
+    {
+      switch (op)
+	{
+	case EQ_EXPR:
+	case GE_EXPR:
+	case LE_EXPR:
+	  return tristate (tristate::TS_TRUE);
+
+	case NE_EXPR:
+	case GT_EXPR:
+	case LT_EXPR:
+	  return tristate (tristate::TS_FALSE);
+	default:
+	  break;
+	}
+    }
+
+  tree lhs_const = lhs_ec.get_obj (*this).get_any_constant ();
+  tree rhs_const = rhs_ec.get_obj (*this).get_any_constant ();
+  if (lhs_const && rhs_const)
+    {
+      tree comparison
+	= fold_build2 (op, boolean_type_node, lhs_const, rhs_const);
+      if (comparison == boolean_true_node)
+	return tristate (tristate::TS_TRUE);
+      if (comparison == boolean_false_node)
+	return tristate (tristate::TS_FALSE);
+    }
+
+  enum tree_code swapped_op = swap_tree_comparison (op);
+
+  int i;
+  constraint *c;
+  FOR_EACH_VEC_ELT (m_constraints, i, c)
+    {
+      if (c->m_lhs == lhs_ec
+	  && c->m_rhs == rhs_ec)
+	{
+	  tristate result_for_constraint
+	    = eval_constraint_op_for_op (c->m_op, op);
+	  if (result_for_constraint.is_known ())
+	    return result_for_constraint;
+	}
+      /* Swapped operands.  */
+      if (c->m_lhs == rhs_ec
+	  && c->m_rhs == lhs_ec)
+	{
+	  tristate result_for_constraint
+	    = eval_constraint_op_for_op (c->m_op, swapped_op);
+	  if (result_for_constraint.is_known ())
+	    return result_for_constraint;
+	}
+    }
+
+  return tristate (tristate::TS_UNKNOWN);
+}
+
+/* Evaluate the condition LHS OP RHS, creating equiv_class instances for
+   LHS and RHS if they aren't already in equiv_classes.  */
+
+tristate
+constraint_manager::eval_condition (svalue_id lhs,
+				    enum tree_code op,
+				    svalue_id rhs)
+{
+  return eval_condition (get_or_add_equiv_class (lhs),
+			 op,
+			 get_or_add_equiv_class (rhs));
+}
+
+/* Delete any information about svalue_id instances identified by P.
+   Such instances are removed from equivalence classes, and any
+   redundant ECs and constraints are also removed.
+   Accumulate stats into STATS.  */
+
+void
+constraint_manager::purge (const purge_criteria &p, purge_stats *stats)
+{
+  /* Delete any svalue_ids identified by P within the various equivalence
+     classes.  */
+  for (unsigned ec_idx = 0; ec_idx < m_equiv_classes.length (); )
+    {
+      equiv_class *ec = m_equiv_classes[ec_idx];
+
+      int i;
+      svalue_id *pv;
+      bool delete_ec = false;
+      FOR_EACH_VEC_ELT (ec->m_vars, i, pv)
+	{
+	  if (*pv == ec->m_cst_sid)
+	    continue;
+	  if (p.should_purge_p (*pv))
+	    {
+	      if (ec->del (*pv))
+		if (!ec->m_constant)
+		  delete_ec = true;
+	    }
+	}
+
+      if (delete_ec)
+	{
+	  delete ec;
+	  m_equiv_classes.ordered_remove (ec_idx);
+	  if (stats)
+	    stats->m_num_equiv_classes++;
+
+	  /* Update the constraints, potentially removing some.  */
+	  for (unsigned con_idx = 0; con_idx < m_constraints.length (); )
+	    {
+	      constraint *c = &m_constraints[con_idx];
+
+	      /* Remove constraints that refer to the deleted EC.  */
+	      if (c->m_lhs == ec_idx
+		  || c->m_rhs == ec_idx)
+		{
+		  m_constraints.ordered_remove (con_idx);
+		  if (stats)
+		    stats->m_num_constraints++;
+		}
+	      else
+		{
+		  /* Renumber constraints that refer to ECs that have
+		     had their idx changed.  */
+		  c->m_lhs.update_for_removal (ec_idx);
+		  c->m_rhs.update_for_removal (ec_idx);
+
+		  con_idx++;
+		}
+	    }
+	}
+      else
+	ec_idx++;
+    }
+
+  /* Now delete any constraints that are purely between constants.  */
+  for (unsigned con_idx = 0; con_idx < m_constraints.length (); )
+    {
+      constraint *c = &m_constraints[con_idx];
+      if (m_equiv_classes[c->m_lhs.m_idx]->m_vars.length () == 0
+	  && m_equiv_classes[c->m_rhs.m_idx]->m_vars.length () == 0)
+	{
+	  m_constraints.ordered_remove (con_idx);
+	  if (stats)
+	    stats->m_num_constraints++;
+	}
+      else
+	{
+	  con_idx++;
+	}
+    }
+
+  /* Finally, delete any ECs that purely contain constants and aren't
+     referenced by any constraints.  */
+  for (unsigned ec_idx = 0; ec_idx < m_equiv_classes.length (); )
+    {
+      equiv_class *ec = m_equiv_classes[ec_idx];
+      if (ec->m_vars.length () == 0)
+	{
+	  equiv_class_id ec_id (ec_idx);
+	  bool has_constraint = false;
+	  for (unsigned con_idx = 0; con_idx < m_constraints.length ();
+	       con_idx++)
+	    {
+	      constraint *c = &m_constraints[con_idx];
+	      if (c->m_lhs == ec_id
+		  || c->m_rhs == ec_id)
+		{
+		  has_constraint = true;
+		  break;
+		}
+	    }
+	  if (!has_constraint)
+	    {
+	      delete ec;
+	      m_equiv_classes.ordered_remove (ec_idx);
+	      if (stats)
+		stats->m_num_equiv_classes++;
+
+	      /* Renumber constraints that refer to ECs that have
+		 had their idx changed.  */
+	      for (unsigned con_idx = 0; con_idx < m_constraints.length ();
+		   con_idx++)
+		{
+		  constraint *c = &m_constraints[con_idx];
+		  c->m_lhs.update_for_removal (ec_idx);
+		  c->m_rhs.update_for_removal (ec_idx);
+		}
+	      continue;
+	    }
+	}
+      ec_idx++;
+    }
+
+  validate ();
+}
+
+/* Remap all svalue_ids within this constraint_manager using MAP.  */
+
+void
+constraint_manager::remap_svalue_ids (const svalue_id_map &map)
+{
+  int i;
+  equiv_class *ec;
+  FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+    ec->remap_svalue_ids (map);
+}
+
+/* Comparator for use by constraint_manager::canonicalize.
+   Sort a pair of equiv_class instances, using the representative
+   svalue_id as a sort key.  */
+
+static int
+equiv_class_cmp (const void *p1, const void *p2)
+{
+  const equiv_class *ec1 = *(const equiv_class * const *)p1;
+  const equiv_class *ec2 = *(const equiv_class * const *)p2;
+
+  svalue_id rep1 = ec1->get_representative ();
+  svalue_id rep2 = ec2->get_representative ();
+
+  return rep1.as_int () - rep2.as_int ();
+}
+
+/* Comparator for use by constraint_manager::canonicalize.
+   Sort a pair of constraint instances.  */
+
+static int
+constraint_cmp (const void *p1, const void *p2)
+{
+  const constraint *c1 = (const constraint *)p1;
+  const constraint *c2 = (const constraint *)p2;
+  int lhs_cmp = c1->m_lhs.as_int () - c2->m_lhs.as_int ();
+  if (lhs_cmp)
+    return lhs_cmp;
+  int rhs_cmp = c1->m_rhs.as_int () - c2->m_rhs.as_int ();
+  if (rhs_cmp)
+    return rhs_cmp;
+  return c1->m_op - c2->m_op;
+}
+
+/* Reorder the equivalence classes and constraints within this
+   constraint_manager into a canonical order, to increase the
+   chances of finding equality with another instance.  */
+
+void
+constraint_manager::canonicalize (unsigned num_svalue_ids)
+{
+  /* First, sort svalue_ids within the ECs.  */
+  unsigned i;
+  equiv_class *ec;
+  FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+    ec->canonicalize ();
+
+  /* Next, sort the ECs into a canonical order.  */
+
+  /* We will need to remap the equiv_class_ids in the constraints,
+     so we need to store the original index of each EC.
+     Build a lookup table, mapping from representative svalue_id
+     to the original equiv_class_id of that svalue_id.  */
+  auto_vec<equiv_class_id> original_ec_id (num_svalue_ids);
+  for (i = 0; i < num_svalue_ids; i++)
+    original_ec_id.quick_push (equiv_class_id::null ());
+  FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+    {
+      svalue_id rep = ec->get_representative ();
+      gcc_assert (!rep.null_p ());
+      original_ec_id[rep.as_int ()] = i;
+    }
+
+  /* Sort the equivalence classes.  */
+  m_equiv_classes.qsort (equiv_class_cmp);
+
+  /* Populate ec_id_map based on the old vs new EC ids.  */
+  one_way_id_map<equiv_class_id> ec_id_map (m_equiv_classes.length ());
+  FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+    {
+      svalue_id rep = ec->get_representative ();
+      ec_id_map.put (original_ec_id[rep.as_int ()], i);
+    }
+
+  /* Update the EC ids within the constraints.  */
+  constraint *c;
+  FOR_EACH_VEC_ELT (m_constraints, i, c)
+    {
+      ec_id_map.update (&c->m_lhs);
+      ec_id_map.update (&c->m_rhs);
+    }
+
+  /* Finally, sort the constraints. */
+  m_constraints.qsort (constraint_cmp);
+}
+
+/* A concrete subclass of constraint_manager for use when
+   merging two constraint_manager into a third constraint_manager,
+   each of which has its own region_model.
+   Calls are delegated to the constraint_manager for the merged model,
+   and thus affect its region_model.  */
+
+class cleaned_constraint_manager : public constraint_manager
+{
+public:
+  cleaned_constraint_manager (constraint_manager *merged) : m_merged (merged) {}
+
+  constraint_manager *clone (region_model *) const FINAL OVERRIDE
+  {
+    gcc_unreachable ();
+  }
+  tree maybe_get_constant (svalue_id sid) const FINAL OVERRIDE
+  {
+    return m_merged->maybe_get_constant (sid);
+  }
+  svalue_id get_sid_for_constant (tree cst) const FINAL OVERRIDE
+  {
+    return m_merged->get_sid_for_constant (cst);
+  }
+  virtual int get_num_svalues () const FINAL OVERRIDE
+  {
+    return m_merged->get_num_svalues ();
+  }
+private:
+  constraint_manager *m_merged;
+};
+
+/* Concrete subclass of fact_visitor for use by constraint_manager::merge.
+   For every fact in CM_A, see if it is also true in *CM_B.  Add such
+   facts to *OUT.  */
+
+class merger_fact_visitor : public fact_visitor
+{
+public:
+  merger_fact_visitor (constraint_manager *cm_b,
+		       constraint_manager *out)
+  : m_cm_b (cm_b), m_out (out)
+  {}
+
+  void on_fact (svalue_id lhs, enum tree_code code, svalue_id rhs)
+    FINAL OVERRIDE
+  {
+    if (m_cm_b->eval_condition (lhs, code, rhs).is_true ())
+      {
+	bool sat = m_out->add_constraint (lhs, code, rhs);
+	gcc_assert (sat);
+      }
+  }
+
+private:
+  constraint_manager *m_cm_b;
+  constraint_manager *m_out;
+};
+
+/* Use MERGER to merge CM_A and CM_B into *OUT.
+   If one thinks of a constraint_manager as a subset of N-dimensional
+   space, this takes the union of the points of CM_A and CM_B, and
+   expresses that into *OUT.  Alternatively, it can be thought of
+   as the intersection of the constraints.  */
+
+void
+constraint_manager::merge (const constraint_manager &cm_a,
+			   const constraint_manager &cm_b,
+			   constraint_manager *out,
+			   const model_merger &merger)
+{
+  gcc_assert (merger.m_sid_mapping);
+
+  /* Map svalue_ids in each equiv class from both sources
+     to the merged region_model, dropping ids that don't survive merger,
+     and potentially creating svalues in *OUT for constants.  */
+  cleaned_constraint_manager cleaned_cm_a (out);
+  const one_way_svalue_id_map &map_a_to_m
+    = merger.m_sid_mapping->m_map_from_a_to_m;
+  clean_merger_input (cm_a, map_a_to_m, &cleaned_cm_a);
+
+  cleaned_constraint_manager cleaned_cm_b (out);
+  const one_way_svalue_id_map &map_b_to_m
+    = merger.m_sid_mapping->m_map_from_b_to_m;
+  clean_merger_input (cm_b, map_b_to_m, &cleaned_cm_b);
+
+  /* At this point, the two cleaned CMs have ECs and constraints referring
+     to svalues in the merged region model, but both of them have separate
+     ECs.  */
+
+  /* Merge the equivalence classes and constraints.
+     The easiest way to do this seems to be to enumerate all of the facts
+     in cleaned_cm_a, see which are also true in cleaned_cm_b,
+     and add those to *OUT.  */
+  merger_fact_visitor v (&cleaned_cm_b, out);
+  cleaned_cm_a.for_each_fact (&v);
+}
+
+/* A subroutine of constraint_manager::merge.
+   Use MAP_SID_TO_M to map equivalence classes and constraints from
+   SM_IN to *OUT.  Purge any non-constant svalue_id that don't appear
+   in the result of MAP_SID_TO_M, purging any ECs and their constraints
+   that become empty as a result.  Potentially create svalues in
+   the merged region_model for constants that weren't already in use there.  */
+
+void
+constraint_manager::
+clean_merger_input (const constraint_manager &cm_in,
+		    const one_way_svalue_id_map &map_sid_to_m,
+		    constraint_manager *out)
+{
+  one_way_id_map<equiv_class_id> map_ec_to_m
+    (cm_in.m_equiv_classes.length ());
+  unsigned ec_idx;
+  equiv_class *ec;
+  FOR_EACH_VEC_ELT (cm_in.m_equiv_classes, ec_idx, ec)
+    {
+      equiv_class cleaned_ec;
+      if (tree cst = ec->get_any_constant ())
+	{
+	  cleaned_ec.m_constant = cst;
+	  /* Lazily create the constant in the out region_model.  */
+	  cleaned_ec.m_cst_sid = out->get_sid_for_constant (cst);
+	}
+      unsigned var_idx;
+      svalue_id *var_in_sid;
+      FOR_EACH_VEC_ELT (ec->m_vars, var_idx, var_in_sid)
+	{
+	  svalue_id var_m_sid = map_sid_to_m.get_dst_for_src (*var_in_sid);
+	  if (!var_m_sid.null_p ())
+	    cleaned_ec.m_vars.safe_push (var_m_sid);
+	}
+      if (cleaned_ec.get_any_constant () || !cleaned_ec.m_vars.is_empty ())
+	{
+	  map_ec_to_m.put (ec_idx, out->m_equiv_classes.length ());
+	  out->m_equiv_classes.safe_push (new equiv_class (cleaned_ec));
+	}
+    }
+
+  /* Write out to *OUT any constraints for which both sides survived
+     cleaning, using the new EC IDs.  */
+  unsigned con_idx;
+  constraint *c;
+  FOR_EACH_VEC_ELT (cm_in.m_constraints, con_idx, c)
+    {
+      equiv_class_id new_lhs = map_ec_to_m.get_dst_for_src (c->m_lhs);
+      if (new_lhs.null_p ())
+	continue;
+      equiv_class_id new_rhs = map_ec_to_m.get_dst_for_src (c->m_rhs);
+      if (new_rhs.null_p ())
+	continue;
+      out->m_constraints.safe_push (constraint (new_lhs,
+						c->m_op,
+						new_rhs));
+    }
+}
+
+/* Call VISITOR's on_fact vfunc repeatedly to express the various
+   equivalence classes and constraints.
+   This is used by constraint_manager::merge to find the common
+   facts between two input constraint_managers.  */
+
+void
+constraint_manager::for_each_fact (fact_visitor *visitor) const
+{
+  /* First, call EQ_EXPR within the various equivalence classes.  */
+  unsigned ec_idx;
+  equiv_class *ec;
+  FOR_EACH_VEC_ELT (m_equiv_classes, ec_idx, ec)
+    {
+      if (!ec->m_cst_sid.null_p ())
+	{
+	  unsigned i;
+	  svalue_id *sid;
+	  FOR_EACH_VEC_ELT (ec->m_vars, i, sid)
+	    visitor->on_fact (ec->m_cst_sid, EQ_EXPR, *sid);
+	}
+      for (unsigned i = 0; i < ec->m_vars.length (); i++)
+	for (unsigned j = i + 1; j < ec->m_vars.length (); j++)
+	  visitor->on_fact (ec->m_vars[i], EQ_EXPR, ec->m_vars[j]);
+    }
+
+  /* Now, express the various constraints.  */
+  unsigned con_idx;
+  constraint *c;
+  FOR_EACH_VEC_ELT (m_constraints, con_idx, c)
+    {
+      const equiv_class &ec_lhs = c->m_lhs.get_obj (*this);
+      const equiv_class &ec_rhs = c->m_rhs.get_obj (*this);
+      enum tree_code code = constraint_tree_code (c->m_op);
+
+      if (!ec_lhs.m_cst_sid.null_p ())
+	{
+	  for (unsigned j = 0; j < ec_rhs.m_vars.length (); j++)
+	    {
+	      visitor->on_fact (ec_lhs.m_cst_sid, code, ec_rhs.m_vars[j]);
+	    }
+	}
+      for (unsigned i = 0; i < ec_lhs.m_vars.length (); i++)
+	{
+	  if (!ec_rhs.m_cst_sid.null_p ())
+	    visitor->on_fact (ec_lhs.m_vars[i], code, ec_rhs.m_cst_sid);
+	  for (unsigned j = 0; j < ec_rhs.m_vars.length (); j++)
+	    visitor->on_fact (ec_lhs.m_vars[i], code, ec_rhs.m_vars[j]);
+	}
+    }
+}
+
+/* Assert that this object is valid.  */
+
+void
+constraint_manager::validate () const
+{
+  /* Skip this in a release build.  */
+#if !CHECKING_P
+  return;
+#endif
+
+  int i;
+  equiv_class *ec;
+  FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+    {
+      gcc_assert (ec);
+
+      int j;
+      svalue_id *sid;
+      FOR_EACH_VEC_ELT (ec->m_vars, j, sid)
+	{
+	  gcc_assert (!sid->null_p ());
+	  gcc_assert (sid->as_int () < get_num_svalues ());
+	}
+      if (ec->m_constant)
+	gcc_assert (CONSTANT_CLASS_P (ec->m_constant));
+#if 0
+      else
+	gcc_assert (ec->m_vars.length () > 0);
+#endif
+    }
+
+  constraint *c;
+  FOR_EACH_VEC_ELT (m_constraints, i, c)
+    {
+      gcc_assert (!c->m_lhs.null_p ());
+      gcc_assert (c->m_lhs.as_int () <= (int)m_equiv_classes.length ());
+      gcc_assert (!c->m_rhs.null_p ());
+      gcc_assert (c->m_rhs.as_int () <= (int)m_equiv_classes.length ());
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Various constraint_manager selftests.
+   These have to be written in terms of a region_model, since
+   the latter is responsible for managing svalue and svalue_id
+   instances.  */
+
+/* Verify that setting and getting simple conditions within a region_model
+   work (thus exercising the underlying constraint_manager).  */
+
+static void
+test_constraint_conditions ()
+{
+  tree int_42 = build_int_cst (integer_type_node, 42);
+  tree int_0 = build_int_cst (integer_type_node, 0);
+
+  tree x = build_global_decl ("x", integer_type_node);
+  tree y = build_global_decl ("y", integer_type_node);
+  tree z = build_global_decl ("z", integer_type_node);
+
+  /* Self-comparisons.  */
+  {
+    region_model model;
+    ASSERT_CONDITION_TRUE (model, x, EQ_EXPR, x);
+    ASSERT_CONDITION_TRUE (model, x, LE_EXPR, x);
+    ASSERT_CONDITION_TRUE (model, x, GE_EXPR, x);
+    ASSERT_CONDITION_FALSE (model, x, NE_EXPR, x);
+    ASSERT_CONDITION_FALSE (model, x, LT_EXPR, x);
+    ASSERT_CONDITION_FALSE (model, x, GT_EXPR, x);
+  }
+
+  /* x == y.  */
+  {
+    region_model model;
+    ASSERT_CONDITION_UNKNOWN (model, x, EQ_EXPR, y);
+
+    ADD_SAT_CONSTRAINT (model, x, EQ_EXPR, y);
+
+    ASSERT_CONDITION_TRUE (model, x, EQ_EXPR, y);
+    ASSERT_CONDITION_TRUE (model, x, LE_EXPR, y);
+    ASSERT_CONDITION_TRUE (model, x, GE_EXPR, y);
+    ASSERT_CONDITION_FALSE (model, x, NE_EXPR, y);
+    ASSERT_CONDITION_FALSE (model, x, LT_EXPR, y);
+    ASSERT_CONDITION_FALSE (model, x, GT_EXPR, y);
+
+    /* Swapped operands.  */
+    ASSERT_CONDITION_TRUE (model, y, EQ_EXPR, x);
+    ASSERT_CONDITION_TRUE (model, y, LE_EXPR, x);
+    ASSERT_CONDITION_TRUE (model, y, GE_EXPR, x);
+    ASSERT_CONDITION_FALSE (model, y, NE_EXPR, x);
+    ASSERT_CONDITION_FALSE (model, y, LT_EXPR, x);
+    ASSERT_CONDITION_FALSE (model, y, GT_EXPR, x);
+
+    /* Comparison with other var.  */
+    ASSERT_CONDITION_UNKNOWN (model, x, EQ_EXPR, z);
+    ASSERT_CONDITION_UNKNOWN (model, x, LE_EXPR, z);
+    ASSERT_CONDITION_UNKNOWN (model, x, GE_EXPR, z);
+    ASSERT_CONDITION_UNKNOWN (model, x, NE_EXPR, z);
+    ASSERT_CONDITION_UNKNOWN (model, x, LT_EXPR, z);
+    ASSERT_CONDITION_UNKNOWN (model, x, GT_EXPR, z);
+  }
+
+  /* x == y, then y == z  */
+  {
+    region_model model;
+    ASSERT_CONDITION_UNKNOWN (model, x, EQ_EXPR, y);
+
+    ADD_SAT_CONSTRAINT (model, x, EQ_EXPR, y);
+    ADD_SAT_CONSTRAINT (model, y, EQ_EXPR, z);
+
+    ASSERT_CONDITION_TRUE (model, x, EQ_EXPR, z);
+    ASSERT_CONDITION_TRUE (model, x, LE_EXPR, z);
+    ASSERT_CONDITION_TRUE (model, x, GE_EXPR, z);
+    ASSERT_CONDITION_FALSE (model, x, NE_EXPR, z);
+    ASSERT_CONDITION_FALSE (model, x, LT_EXPR, z);
+    ASSERT_CONDITION_FALSE (model, x, GT_EXPR, z);
+  }
+
+  /* x != y.  */
+  {
+    region_model model;
+
+    ADD_SAT_CONSTRAINT (model, x, NE_EXPR, y);
+
+    ASSERT_CONDITION_TRUE (model, x, NE_EXPR, y);
+    ASSERT_CONDITION_FALSE (model, x, EQ_EXPR, y);
+    ASSERT_CONDITION_UNKNOWN (model, x, LE_EXPR, y);
+    ASSERT_CONDITION_UNKNOWN (model, x, GE_EXPR, y);
+    ASSERT_CONDITION_UNKNOWN (model, x, LT_EXPR, y);
+    ASSERT_CONDITION_UNKNOWN (model, x, GT_EXPR, y);
+
+    /* Swapped operands.  */
+    ASSERT_CONDITION_TRUE (model, y, NE_EXPR, x);
+    ASSERT_CONDITION_FALSE (model, y, EQ_EXPR, x);
+    ASSERT_CONDITION_UNKNOWN (model, y, LE_EXPR, x);
+    ASSERT_CONDITION_UNKNOWN (model, y, GE_EXPR, x);
+    ASSERT_CONDITION_UNKNOWN (model, y, LT_EXPR, x);
+    ASSERT_CONDITION_UNKNOWN (model, y, GT_EXPR, x);
+
+    /* Comparison with other var.  */
+    ASSERT_CONDITION_UNKNOWN (model, x, EQ_EXPR, z);
+    ASSERT_CONDITION_UNKNOWN (model, x, LE_EXPR, z);
+    ASSERT_CONDITION_UNKNOWN (model, x, GE_EXPR, z);
+    ASSERT_CONDITION_UNKNOWN (model, x, NE_EXPR, z);
+    ASSERT_CONDITION_UNKNOWN (model, x, LT_EXPR, z);
+    ASSERT_CONDITION_UNKNOWN (model, x, GT_EXPR, z);
+  }
+
+  /* x < y.  */
+  {
+    region_model model;
+
+    ADD_SAT_CONSTRAINT (model, x, LT_EXPR, y);
+
+    ASSERT_CONDITION_TRUE (model, x, LT_EXPR, y);
+    ASSERT_CONDITION_TRUE (model, x, LE_EXPR, y);
+    ASSERT_CONDITION_TRUE (model, x, NE_EXPR, y);
+    ASSERT_CONDITION_FALSE (model, x, EQ_EXPR, y);
+    ASSERT_CONDITION_FALSE (model, x, GT_EXPR, y);
+    ASSERT_CONDITION_FALSE (model, x, GE_EXPR, y);
+
+    /* Swapped operands.  */
+    ASSERT_CONDITION_FALSE (model, y, LT_EXPR, x);
+    ASSERT_CONDITION_FALSE (model, y, LE_EXPR, x);
+    ASSERT_CONDITION_TRUE (model, y, NE_EXPR, x);
+    ASSERT_CONDITION_FALSE (model, y, EQ_EXPR, x);
+    ASSERT_CONDITION_TRUE (model, y, GT_EXPR, x);
+    ASSERT_CONDITION_TRUE (model, y, GE_EXPR, x);
+  }
+
+  /* x <= y.  */
+  {
+    region_model model;
+
+    ADD_SAT_CONSTRAINT (model, x, LE_EXPR, y);
+
+    ASSERT_CONDITION_UNKNOWN (model, x, LT_EXPR, y);
+    ASSERT_CONDITION_TRUE (model, x, LE_EXPR, y);
+    ASSERT_CONDITION_UNKNOWN (model, x, NE_EXPR, y);
+    ASSERT_CONDITION_UNKNOWN (model, x, EQ_EXPR, y);
+    ASSERT_CONDITION_FALSE (model, x, GT_EXPR, y);
+    ASSERT_CONDITION_UNKNOWN (model, x, GE_EXPR, y);
+
+    /* Swapped operands.  */
+    ASSERT_CONDITION_FALSE (model, y, LT_EXPR, x);
+    ASSERT_CONDITION_UNKNOWN (model, y, LE_EXPR, x);
+    ASSERT_CONDITION_UNKNOWN (model, y, NE_EXPR, x);
+    ASSERT_CONDITION_UNKNOWN (model, y, EQ_EXPR, x);
+    ASSERT_CONDITION_UNKNOWN (model, y, GT_EXPR, x);
+    ASSERT_CONDITION_TRUE (model, y, GE_EXPR, x);
+  }
+
+  /* x > y.  */
+  {
+    region_model model;
+
+    ADD_SAT_CONSTRAINT (model, x, GT_EXPR, y);
+
+    ASSERT_CONDITION_TRUE (model, x, GT_EXPR, y);
+    ASSERT_CONDITION_TRUE (model, x, GE_EXPR, y);
+    ASSERT_CONDITION_TRUE (model, x, NE_EXPR, y);
+    ASSERT_CONDITION_FALSE (model, x, EQ_EXPR, y);
+    ASSERT_CONDITION_FALSE (model, x, LT_EXPR, y);
+    ASSERT_CONDITION_FALSE (model, x, LE_EXPR, y);
+
+    /* Swapped operands.  */
+    ASSERT_CONDITION_FALSE (model, y, GT_EXPR, x);
+    ASSERT_CONDITION_FALSE (model, y, GE_EXPR, x);
+    ASSERT_CONDITION_TRUE (model, y, NE_EXPR, x);
+    ASSERT_CONDITION_FALSE (model, y, EQ_EXPR, x);
+    ASSERT_CONDITION_TRUE (model, y, LT_EXPR, x);
+    ASSERT_CONDITION_TRUE (model, y, LE_EXPR, x);
+  }
+
+  /* x >= y.  */
+  {
+    region_model model;
+
+    ADD_SAT_CONSTRAINT (model, x, GE_EXPR, y);
+
+    ASSERT_CONDITION_UNKNOWN (model, x, GT_EXPR, y);
+    ASSERT_CONDITION_TRUE (model, x, GE_EXPR, y);
+    ASSERT_CONDITION_UNKNOWN (model, x, NE_EXPR, y);
+    ASSERT_CONDITION_UNKNOWN (model, x, EQ_EXPR, y);
+    ASSERT_CONDITION_FALSE (model, x, LT_EXPR, y);
+    ASSERT_CONDITION_UNKNOWN (model, x, LE_EXPR, y);
+
+    /* Swapped operands.  */
+    ASSERT_CONDITION_FALSE (model, y, GT_EXPR, x);
+    ASSERT_CONDITION_UNKNOWN (model, y, GE_EXPR, x);
+    ASSERT_CONDITION_UNKNOWN (model, y, NE_EXPR, x);
+    ASSERT_CONDITION_UNKNOWN (model, y, EQ_EXPR, x);
+    ASSERT_CONDITION_UNKNOWN (model, y, LT_EXPR, x);
+    ASSERT_CONDITION_TRUE (model, y, LE_EXPR, x);
+  }
+
+  // TODO: implied orderings
+
+  /* Constants.  */
+  {
+    region_model model;
+    ASSERT_CONDITION_FALSE (model, int_0, EQ_EXPR, int_42);
+    ASSERT_CONDITION_TRUE (model, int_0, NE_EXPR, int_42);
+    ASSERT_CONDITION_TRUE (model, int_0, LT_EXPR, int_42);
+    ASSERT_CONDITION_TRUE (model, int_0, LE_EXPR, int_42);
+    ASSERT_CONDITION_FALSE (model, int_0, GT_EXPR, int_42);
+    ASSERT_CONDITION_FALSE (model, int_0, GE_EXPR, int_42);
+  }
+
+  /* x == 0, y == 42.  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, x, EQ_EXPR, int_0);
+    ADD_SAT_CONSTRAINT (model, y, EQ_EXPR, int_42);
+
+    ASSERT_CONDITION_TRUE (model, x, NE_EXPR, y);
+    ASSERT_CONDITION_FALSE (model, x, EQ_EXPR, y);
+    ASSERT_CONDITION_TRUE (model, x, LE_EXPR, y);
+    ASSERT_CONDITION_FALSE (model, x, GE_EXPR, y);
+    ASSERT_CONDITION_TRUE (model, x, LT_EXPR, y);
+    ASSERT_CONDITION_FALSE (model, x, GT_EXPR, y);
+  }
+
+  /* Unsatisfiable combinations.  */
+
+  /* x == y && x != y.  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, x, EQ_EXPR, y);
+    ADD_UNSAT_CONSTRAINT (model, x, NE_EXPR, y);
+  }
+
+  /* x == 0 then x == 42.  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, x, EQ_EXPR, int_0);
+    ADD_UNSAT_CONSTRAINT (model, x, EQ_EXPR, int_42);
+  }
+
+  /* x == 0 then x != 0.  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, x, EQ_EXPR, int_0);
+    ADD_UNSAT_CONSTRAINT (model, x, NE_EXPR, int_0);
+  }
+
+  /* x == 0 then x > 0.  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, x, EQ_EXPR, int_0);
+    ADD_UNSAT_CONSTRAINT (model, x, GT_EXPR, int_0);
+  }
+
+  /* x != y && x == y.  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, x, NE_EXPR, y);
+    ADD_UNSAT_CONSTRAINT (model, x, EQ_EXPR, y);
+  }
+
+  /* x <= y && x > y.  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, x, LE_EXPR, y);
+    ADD_UNSAT_CONSTRAINT (model, x, GT_EXPR, y);
+  }
+
+  // etc
+}
+
+/* Test transitivity of conditions.  */
+
+static void
+test_transitivity ()
+{
+  tree a = build_global_decl ("a", integer_type_node);
+  tree b = build_global_decl ("b", integer_type_node);
+  tree c = build_global_decl ("c", integer_type_node);
+  tree d = build_global_decl ("d", integer_type_node);
+
+  /* a == b, then c == d, then c == b.  */
+  {
+    region_model model;
+    ASSERT_CONDITION_UNKNOWN (model, a, EQ_EXPR, b);
+    ASSERT_CONDITION_UNKNOWN (model, b, EQ_EXPR, c);
+    ASSERT_CONDITION_UNKNOWN (model, c, EQ_EXPR, d);
+    ASSERT_CONDITION_UNKNOWN (model, a, EQ_EXPR, d);
+
+    ADD_SAT_CONSTRAINT (model, a, EQ_EXPR, b);
+    ASSERT_CONDITION_TRUE (model, a, EQ_EXPR, b);
+
+    ADD_SAT_CONSTRAINT (model, c, EQ_EXPR, d);
+    ASSERT_CONDITION_TRUE (model, c, EQ_EXPR, d);
+    ASSERT_CONDITION_UNKNOWN (model, a, EQ_EXPR, d);
+
+    ADD_SAT_CONSTRAINT (model, c, EQ_EXPR, b);
+    ASSERT_CONDITION_TRUE (model, c, EQ_EXPR, b);
+    ASSERT_CONDITION_TRUE (model, a, EQ_EXPR, d);
+  }
+
+  /* Transitivity: "a < b", "b < c" should imply "a < c".  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, a, LT_EXPR, b);
+    ADD_SAT_CONSTRAINT (model, b, LT_EXPR, c);
+
+    ASSERT_CONDITION_TRUE (model, a, LT_EXPR, c);
+    ASSERT_CONDITION_FALSE (model, a, EQ_EXPR, c);
+  }
+
+  /* Transitivity: "a <= b", "b < c" should imply "a < c".  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, a, LE_EXPR, b);
+    ADD_SAT_CONSTRAINT (model, b, LT_EXPR, c);
+
+    ASSERT_CONDITION_TRUE (model, a, LT_EXPR, c);
+    ASSERT_CONDITION_FALSE (model, a, EQ_EXPR, c);
+  }
+
+  /* Transitivity: "a <= b", "b <= c" should imply "a <= c".  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, a, LE_EXPR, b);
+    ADD_SAT_CONSTRAINT (model, b, LE_EXPR, c);
+
+    ASSERT_CONDITION_TRUE (model, a, LE_EXPR, c);
+    ASSERT_CONDITION_UNKNOWN (model, a, EQ_EXPR, c);
+  }
+
+  /* Transitivity: "a > b", "b > c" should imply "a > c".  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, a, GT_EXPR, b);
+    ADD_SAT_CONSTRAINT (model, b, GT_EXPR, c);
+
+    ASSERT_CONDITION_TRUE (model, a, GT_EXPR, c);
+    ASSERT_CONDITION_FALSE (model, a, EQ_EXPR, c);
+  }
+
+  /* Transitivity: "a >= b", "b > c" should imply " a > c".  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, a, GE_EXPR, b);
+    ADD_SAT_CONSTRAINT (model, b, GT_EXPR, c);
+
+    ASSERT_CONDITION_TRUE (model, a, GT_EXPR, c);
+    ASSERT_CONDITION_FALSE (model, a, EQ_EXPR, c);
+  }
+
+  /* Transitivity: "a >= b", "b >= c" should imply "a >= c".  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, a, GE_EXPR, b);
+    ADD_SAT_CONSTRAINT (model, b, GE_EXPR, c);
+
+    ASSERT_CONDITION_TRUE (model, a, GE_EXPR, c);
+    ASSERT_CONDITION_UNKNOWN (model, a, EQ_EXPR, c);
+  }
+
+  /* Transitivity: "(a < b)", "(c < d)", "(b < c)" should
+     imply the easy cases:
+       (a < c)
+       (b < d)
+     but also that:
+       (a < d).  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, a, LT_EXPR, b);
+    ADD_SAT_CONSTRAINT (model, c, LT_EXPR, d);
+    ADD_SAT_CONSTRAINT (model, b, LT_EXPR, c);
+
+    ASSERT_CONDITION_TRUE (model, a, LT_EXPR, c);
+    ASSERT_CONDITION_TRUE (model, b, LT_EXPR, d);
+    ASSERT_CONDITION_TRUE (model, a, LT_EXPR, d);
+  }
+
+  /* Transitivity: "a >= b", "b >= a" should imply that a == b.  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, a, GE_EXPR, b);
+    ADD_SAT_CONSTRAINT (model, b, GE_EXPR, a);
+
+    // TODO:
+    ASSERT_CONDITION_TRUE (model, a, EQ_EXPR, b);
+  }
+
+  /* Transitivity: "a >= b", "b > a" should be impossible.  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, a, GE_EXPR, b);
+    ADD_UNSAT_CONSTRAINT (model, b, GT_EXPR, a);
+  }
+
+  /* Transitivity: "a >= b", "b >= c", "c >= a" should imply
+     that a == b == c.  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, a, GE_EXPR, b);
+    ADD_SAT_CONSTRAINT (model, b, GE_EXPR, c);
+    ADD_SAT_CONSTRAINT (model, c, GE_EXPR, a);
+
+    ASSERT_CONDITION_TRUE (model, a, EQ_EXPR, c);
+  }
+
+  /* Transitivity: "a > b", "b > c", "c > a"
+     should be impossible.  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, a, GT_EXPR, b);
+    ADD_SAT_CONSTRAINT (model, b, GT_EXPR, c);
+    ADD_UNSAT_CONSTRAINT (model, c, GT_EXPR, a);
+  }
+
+}
+
+/* Test various conditionals involving constants where the results
+   ought to be implied based on the values of the constants.  */
+
+static void
+test_constant_comparisons ()
+{
+  tree int_3 = build_int_cst (integer_type_node, 3);
+  tree int_4 = build_int_cst (integer_type_node, 4);
+  tree int_5 = build_int_cst (integer_type_node, 5);
+
+  tree int_1023 = build_int_cst (integer_type_node, 1023);
+  tree int_1024 = build_int_cst (integer_type_node, 1024);
+
+  tree a = build_global_decl ("a", integer_type_node);
+  tree b = build_global_decl ("b", integer_type_node);
+
+  /* Given a >= 1024, then a <= 1023 should be impossible.  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, a, GE_EXPR, int_1024);
+    ADD_UNSAT_CONSTRAINT (model, a, LE_EXPR, int_1023);
+  }
+
+  /* a > 4.  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, a, GT_EXPR, int_4);
+    ASSERT_CONDITION_TRUE (model, a, GT_EXPR, int_4);
+    ASSERT_CONDITION_TRUE (model, a, NE_EXPR, int_3);
+    ASSERT_CONDITION_UNKNOWN (model, a, NE_EXPR, int_5);
+  }
+
+  /* a <= 4.  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, a, LE_EXPR, int_4);
+    ASSERT_CONDITION_FALSE (model, a, GT_EXPR, int_4);
+    ASSERT_CONDITION_FALSE (model, a, GT_EXPR, int_5);
+    ASSERT_CONDITION_UNKNOWN (model, a, NE_EXPR, int_3);
+  }
+
+  /* If "a > b" and "a == 3", then "b == 4" ought to be unsatisfiable.  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, a, GT_EXPR, b);
+    ADD_SAT_CONSTRAINT (model, a, EQ_EXPR, int_3);
+    ADD_UNSAT_CONSTRAINT (model, b, EQ_EXPR, int_4);
+  }
+
+  /* Various tests of int ranges where there is only one possible candidate.  */
+  {
+    /* If "a <= 4" && "a > 3", then "a == 4",
+       assuming a is of integral type.  */
+    {
+      region_model model;
+      ADD_SAT_CONSTRAINT (model, a, LE_EXPR, int_4);
+      ADD_SAT_CONSTRAINT (model, a, GT_EXPR, int_3);
+      ASSERT_CONDITION_TRUE (model, a, EQ_EXPR, int_4);
+    }
+
+    /* If "a > 3" && "a <= 4", then "a == 4",
+       assuming a is of integral type.  */
+    {
+      region_model model;
+      ADD_SAT_CONSTRAINT (model, a, GT_EXPR, int_3);
+      ADD_SAT_CONSTRAINT (model, a, LE_EXPR, int_4);
+      ASSERT_CONDITION_TRUE (model, a, EQ_EXPR, int_4);
+    }
+    /* If "a > 3" && "a < 5", then "a == 4",
+       assuming a is of integral type.  */
+    {
+      region_model model;
+      ADD_SAT_CONSTRAINT (model, a, GT_EXPR, int_3);
+      ADD_SAT_CONSTRAINT (model, a, LT_EXPR, int_5);
+      ASSERT_CONDITION_TRUE (model, a, EQ_EXPR, int_4);
+    }
+    /* If "a >= 4" && "a < 5", then "a == 4",
+       assuming a is of integral type.  */
+    {
+      region_model model;
+      ADD_SAT_CONSTRAINT (model, a, GE_EXPR, int_4);
+      ADD_SAT_CONSTRAINT (model, a, LT_EXPR, int_5);
+      ASSERT_CONDITION_TRUE (model, a, EQ_EXPR, int_4);
+    }
+    /* If "a >= 4" && "a <= 4", then "a == 4".  */
+    {
+      region_model model;
+      ADD_SAT_CONSTRAINT (model, a, GE_EXPR, int_4);
+      ADD_SAT_CONSTRAINT (model, a, LE_EXPR, int_4);
+      ASSERT_CONDITION_TRUE (model, a, EQ_EXPR, int_4);
+    }
+  }
+
+  /* As above, but for floating-point:
+     if "f > 3" && "f <= 4" we don't know that f == 4.  */
+  {
+    tree f = build_global_decl ("f", double_type_node);
+    tree float_3 = build_real_from_int_cst (double_type_node, int_3);
+    tree float_4 = build_real_from_int_cst (double_type_node, int_4);
+
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, f, GT_EXPR, float_3);
+    ADD_SAT_CONSTRAINT (model, f, LE_EXPR, float_4);
+    ASSERT_CONDITION_UNKNOWN (model, f, EQ_EXPR, float_4);
+    ASSERT_CONDITION_UNKNOWN (model, f, EQ_EXPR, int_4);
+  }
+}
+
+/* Verify various lower-level implementation details about
+   constraint_manager.  */
+
+static void
+test_constraint_impl ()
+{
+  tree int_42 = build_int_cst (integer_type_node, 42);
+  tree int_0 = build_int_cst (integer_type_node, 0);
+
+  tree x = build_global_decl ("x", integer_type_node);
+  tree y = build_global_decl ("y", integer_type_node);
+  tree z = build_global_decl ("z", integer_type_node);
+
+  /* x == y.  */
+  {
+    region_model model;
+
+    ADD_SAT_CONSTRAINT (model, x, EQ_EXPR, y);
+
+    /* Assert various things about the insides of model.  */
+    constraint_manager *cm = model.get_constraints ();
+    ASSERT_EQ (cm->m_constraints.length (), 0);
+    ASSERT_EQ (cm->m_equiv_classes.length (), 1);
+  }
+
+  /* y <= z; x == y.  */
+  {
+    region_model model;
+    ASSERT_CONDITION_UNKNOWN (model, x, EQ_EXPR, y);
+    ASSERT_CONDITION_UNKNOWN (model, x, GE_EXPR, z);
+
+    ADD_SAT_CONSTRAINT (model, y, GE_EXPR, z);
+    ASSERT_CONDITION_TRUE (model, y, GE_EXPR, z);
+    ASSERT_CONDITION_UNKNOWN (model, x, GE_EXPR, z);
+
+    ADD_SAT_CONSTRAINT (model, x, EQ_EXPR, y);
+
+    /* Assert various things about the insides of model.  */
+    constraint_manager *cm = model.get_constraints ();
+    ASSERT_EQ (cm->m_constraints.length (), 1);
+    ASSERT_EQ (cm->m_equiv_classes.length (), 2);
+
+    /* Ensure that we merged the constraints.  */
+    ASSERT_CONDITION_TRUE (model, x, GE_EXPR, z);
+  }
+
+  /* y <= z; y == x.  */
+  {
+    region_model model;
+    ASSERT_CONDITION_UNKNOWN (model, x, EQ_EXPR, y);
+    ASSERT_CONDITION_UNKNOWN (model, x, GE_EXPR, z);
+
+    ADD_SAT_CONSTRAINT (model, y, GE_EXPR, z);
+    ASSERT_CONDITION_TRUE (model, y, GE_EXPR, z);
+    ASSERT_CONDITION_UNKNOWN (model, x, GE_EXPR, z);
+
+    ADD_SAT_CONSTRAINT (model, y, EQ_EXPR, x);
+
+    /* Assert various things about the insides of model.  */
+    constraint_manager *cm = model.get_constraints ();
+    ASSERT_EQ (cm->m_constraints.length (), 1);
+    ASSERT_EQ (cm->m_equiv_classes.length (), 2);
+
+    /* Ensure that we merged the constraints.  */
+    ASSERT_CONDITION_TRUE (model, x, GE_EXPR, z);
+  }
+
+  /* x == 0, then x != 42.  */
+  {
+    region_model model;
+
+    ADD_SAT_CONSTRAINT (model, x, EQ_EXPR, int_0);
+    ADD_SAT_CONSTRAINT (model, x, NE_EXPR, int_42);
+
+    /* Assert various things about the insides of model.  */
+    constraint_manager *cm = model.get_constraints ();
+    ASSERT_EQ (cm->m_constraints.length (), 1);
+    ASSERT_EQ (cm->m_equiv_classes.length (), 2);
+    ASSERT_EQ (cm->m_constraints[0].m_lhs,
+	       cm->get_or_add_equiv_class (model.get_rvalue (int_0, NULL)));
+    ASSERT_EQ (cm->m_constraints[0].m_rhs,
+	       cm->get_or_add_equiv_class (model.get_rvalue (int_42, NULL)));
+    ASSERT_EQ (cm->m_constraints[0].m_op, CONSTRAINT_LT);
+  }
+
+  // TODO: selftest for merging ecs "in the middle"
+  // where a non-final one gets overwritten
+
+  // TODO: selftest where there are pre-existing constraints
+}
+
+/* Check that operator== and hashing works as expected for the
+   various types.  */
+
+static void
+test_equality ()
+{
+  tree x = build_global_decl ("x", integer_type_node);
+  tree y = build_global_decl ("y", integer_type_node);
+
+  {
+    region_model model0;
+    region_model model1;
+
+    constraint_manager *cm0 = model0.get_constraints ();
+    constraint_manager *cm1 = model1.get_constraints ();
+
+    ASSERT_EQ (cm0->hash (), cm1->hash ());
+    ASSERT_EQ (*cm0, *cm1);
+
+    ASSERT_EQ (model0.hash (), model1.hash ());
+    ASSERT_EQ (model0, model1);
+
+    ADD_SAT_CONSTRAINT (model1, x, EQ_EXPR, y);
+    ASSERT_NE (cm0->hash (), cm1->hash ());
+    ASSERT_NE (*cm0, *cm1);
+
+    ASSERT_NE (model0.hash (), model1.hash ());
+    ASSERT_NE (model0, model1);
+
+    region_model model2;
+    constraint_manager *cm2 = model2.get_constraints ();
+    /* Make the same change to cm2.  */
+    ADD_SAT_CONSTRAINT (model2, x, EQ_EXPR, y);
+    ASSERT_EQ (cm1->hash (), cm2->hash ());
+    ASSERT_EQ (*cm1, *cm2);
+
+    ASSERT_EQ (model1.hash (), model2.hash ());
+    ASSERT_EQ (model1, model2);
+  }
+}
+
+/* Verify tracking inequality of a variable against many constants.  */
+
+static void
+test_many_constants ()
+{
+  tree a = build_global_decl ("a", integer_type_node);
+
+  region_model model;
+  auto_vec<tree> constants;
+  for (int i = 0; i < 20; i++)
+    {
+      tree constant = build_int_cst (integer_type_node, i);
+      constants.safe_push (constant);
+      ADD_SAT_CONSTRAINT (model, a, NE_EXPR, constant);
+
+      /* Merge, and check the result.  */
+      region_model other (model);
+
+      region_model merged;
+      ASSERT_TRUE (model.can_merge_with_p (other, &merged));
+      model.canonicalize (NULL);
+      merged.canonicalize (NULL);
+      ASSERT_EQ (model, merged);
+
+      for (int j = 0; j <= i; j++)
+	ASSERT_CONDITION_TRUE (model, a, NE_EXPR, constants[j]);
+    }
+}
+
+/* Run the selftests in this file, temporarily overriding
+   flag_analyzer_transitivity with TRANSITIVITY.  */
+
+static void
+run_constraint_manager_tests (bool transitivity)
+{
+  int saved_flag_analyzer_transitivity = flag_analyzer_transitivity;
+  flag_analyzer_transitivity = transitivity;
+
+  test_constraint_conditions ();
+  if (flag_analyzer_transitivity)
+    {
+      /* These selftests assume transitivity.  */
+      test_transitivity ();
+      test_constant_comparisons ();
+    }
+  test_constraint_impl ();
+  test_equality ();
+  test_many_constants ();
+
+  flag_analyzer_transitivity = saved_flag_analyzer_transitivity;
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+analyzer_constraint_manager_cc_tests ()
+{
+  /* Run the tests twice: with and without transitivity.  */
+  run_constraint_manager_tests (true);
+  run_constraint_manager_tests (false);
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
diff --git a/gcc/analyzer/constraint-manager.h b/gcc/analyzer/constraint-manager.h
new file mode 100644
index 0000000..7710dc8
--- /dev/null
+++ b/gcc/analyzer/constraint-manager.h
@@ -0,0 +1,248 @@
+/* Tracking equivalence classes and constraints at a point on an execution path.
+   Copyright (C) 2019 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/>.  */
+
+#ifndef GCC_ANALYZER_CONSTRAINT_MANAGER_H
+#define GCC_ANALYZER_CONSTRAINT_MANAGER_H
+
+#include "analyzer/region-model.h" // for svalue_id
+
+class constraint_manager;
+
+/* Abstract base class for specifying how state should be purged.  */
+
+class purge_criteria
+{
+public:
+  virtual ~purge_criteria () {}
+  virtual bool should_purge_p (svalue_id sid) const = 0;
+};
+
+/* An equivalence class within a constraint manager: a set of
+   svalue_ids that are known to all be equal to each other,
+   together with an optional tree constant that they are equal to.  */
+
+class equiv_class
+{
+public:
+  equiv_class ();
+  equiv_class (const equiv_class &other);
+
+  hashval_t hash () const;
+  bool operator== (const equiv_class &other);
+
+  void add (svalue_id sid, const constraint_manager &cm);
+  bool del (svalue_id sid);
+
+  tree get_any_constant () const { return m_constant; }
+
+  svalue_id get_representative () const;
+
+  void remap_svalue_ids (const svalue_id_map &map);
+
+  void canonicalize ();
+
+  void print (pretty_printer *pp) const;
+
+  /* An equivalence class can contain multiple constants (e.g. multiple
+     different zeroes, for different types); these are just for the last
+     constant added.  */
+  tree m_constant;
+  svalue_id m_cst_sid;
+
+  // TODO: should this be a set rather than a vec?
+  auto_vec<svalue_id> m_vars;
+};
+
+/* The various kinds of constraint.  */
+
+enum constraint_op
+{
+  CONSTRAINT_NE,
+  CONSTRAINT_LT,
+  CONSTRAINT_LE
+};
+
+const char *constraint_op_code (enum constraint_op c_op);
+
+/* An ID for an equiv_class within a constraint_manager.  Internally, this
+   is an index into a vector of equiv_class * within the constraint_manager.  */
+
+class equiv_class_id
+{
+public:
+  static equiv_class_id null () { return equiv_class_id (-1); }
+
+  equiv_class_id (unsigned idx) : m_idx (idx) {}
+  const equiv_class &get_obj (const constraint_manager &cm) const;
+  equiv_class &get_obj (constraint_manager &cm) const;
+
+  bool operator== (const equiv_class_id &other) const
+  {
+    return m_idx == other.m_idx;
+  }
+  bool operator!= (const equiv_class_id &other) const
+  {
+    return m_idx != other.m_idx;
+  }
+
+  bool null_p () const { return m_idx == -1; }
+
+  static equiv_class_id from_int (int idx) { return equiv_class_id (idx); }
+  int as_int () const { return m_idx; }
+
+  void print (pretty_printer *pp) const;
+
+  void update_for_removal (equiv_class_id other)
+  {
+    if (m_idx > other.m_idx)
+      m_idx--;
+  }
+
+  int m_idx;
+};
+
+/* A relationship between two equivalence classes in a constraint_manager.  */
+
+class constraint
+{
+ public:
+  constraint (equiv_class_id lhs, enum constraint_op c_op, equiv_class_id rhs)
+  : m_lhs (lhs), m_op (c_op), m_rhs (rhs)
+  {
+    gcc_assert (!lhs.null_p ());
+    gcc_assert (!rhs.null_p ());
+  }
+
+  void print (pretty_printer *pp, const constraint_manager &cm) const;
+
+  hashval_t hash () const;
+  bool operator== (const constraint &other) const;
+
+  /* Is this an ordering, rather than a "!=".  */
+  bool is_ordering_p () const
+  {
+    return m_op != CONSTRAINT_NE;
+  }
+
+  equiv_class_id m_lhs;
+  enum constraint_op m_op;
+  equiv_class_id m_rhs;
+};
+
+/* An abstract base class for use with constraint_manager::for_each_fact.  */
+
+class fact_visitor
+{
+ public:
+  virtual ~fact_visitor () {}
+  virtual void on_fact (svalue_id lhs, enum tree_code, svalue_id rhs) = 0;
+};
+
+/* A collection of equivalence classes and constraints on them.
+
+   Given N svalues, this can be thought of as representing a subset of
+   N-dimensional space.  When we call add_constraint,
+   we are effectively taking an intersection with that constraint.  */
+
+class constraint_manager
+{
+public:
+  constraint_manager () {}
+  constraint_manager (const constraint_manager &other);
+  virtual ~constraint_manager () {}
+
+  virtual constraint_manager *clone (region_model *) const = 0;
+  virtual tree maybe_get_constant (svalue_id sid) const = 0;
+  virtual svalue_id get_sid_for_constant (tree cst) const = 0;
+  virtual int get_num_svalues () const = 0;
+
+  constraint_manager& operator= (const constraint_manager &other);
+
+  hashval_t hash () const;
+  bool operator== (const constraint_manager &other) const;
+  bool operator!= (const constraint_manager &other) const
+  {
+    return !(*this == other);
+  }
+
+  void print (pretty_printer *pp) const;
+  void dump_to_pp (pretty_printer *pp) const;
+  void dump (FILE *fp) const;
+  void dump () const;
+
+  const equiv_class &get_equiv_class_by_index (unsigned idx) const
+  {
+    return *m_equiv_classes[idx];
+  }
+  equiv_class &get_equiv_class_by_index (unsigned idx)
+  {
+    return *m_equiv_classes[idx];
+  }
+
+  equiv_class &get_equiv_class (svalue_id sid)
+  {
+    equiv_class_id ec_id = get_or_add_equiv_class (sid);
+    return ec_id.get_obj (*this);
+  }
+
+  bool add_constraint (svalue_id lhs, enum tree_code op, svalue_id rhs);
+
+  bool add_constraint (equiv_class_id lhs_ec_id,
+		       enum tree_code op,
+		       equiv_class_id rhs_ec_id);
+
+  bool get_equiv_class_by_sid (svalue_id sid, equiv_class_id *out) const;
+  equiv_class_id get_or_add_equiv_class (svalue_id sid);
+  tristate eval_condition (equiv_class_id lhs,
+			   enum tree_code op,
+			   equiv_class_id rhs);
+  tristate eval_condition (svalue_id lhs,
+			   enum tree_code op,
+			   svalue_id rhs);
+
+  void purge (const purge_criteria &p, purge_stats *stats);
+
+  void remap_svalue_ids (const svalue_id_map &map);
+
+  void canonicalize (unsigned num_svalue_ids);
+
+  static void merge (const constraint_manager &cm_a,
+		     const constraint_manager &cm_b,
+		     constraint_manager *out,
+		     const model_merger &merger);
+
+  void for_each_fact (fact_visitor *) const;
+
+  void validate () const;
+
+  auto_delete_vec<equiv_class> m_equiv_classes;
+  auto_vec<constraint> m_constraints;
+
+ private:
+  static void clean_merger_input (const constraint_manager &cm_in,
+				  const one_way_svalue_id_map &map_sid_to_m,
+				  constraint_manager *out);
+
+  void add_constraint_internal (equiv_class_id lhs_id,
+				enum constraint_op c_op,
+				equiv_class_id rhs_id);
+};
+
+#endif /* GCC_ANALYZER_CONSTRAINT_MANAGER_H */
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 17/49] Support for adding selftests via a plugin
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (18 preceding siblings ...)
  2019-11-16  1:18 ` [PATCH 28/49] analyzer: new files: analyzer.{cc|h} David Malcolm
@ 2019-11-16  1:18 ` David Malcolm
  2019-12-07 14:41   ` Jeff Law
  2019-11-16  1:18 ` [PATCH 29/49] analyzer: new files: tristate.{cc|h} David Malcolm
                   ` (30 subsequent siblings)
  50 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:18 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This patch provides a plugin callback for invoking selftests, so that a
plugin can add tests to those run by -fself-test=DIR.  The callback
invocation uses invoke_plugin_callbacks, which is a no-op if plugin
support is disabled.

gcc/ChangeLog:
	* plugin.c (register_callback): Add case for PLUGIN_RUN_SELFTESTS.
	(invoke_plugin_callbacks_full): Likewise.
	* plugin.def (PLUGIN_RUN_SELFTESTS): New event.
	* selftest-run-tests.c: Include "plugin.h".
	(selftest::run_tests): Run any plugin-provided selftests.
---
 gcc/plugin.c             | 2 ++
 gcc/plugin.def           | 3 +++
 gcc/selftest-run-tests.c | 4 ++++
 3 files changed, 9 insertions(+)

diff --git a/gcc/plugin.c b/gcc/plugin.c
index a9d3171e..6a151af 100644
--- a/gcc/plugin.c
+++ b/gcc/plugin.c
@@ -497,6 +497,7 @@ register_callback (const char *plugin_name,
       case PLUGIN_EARLY_GIMPLE_PASSES_END:
       case PLUGIN_NEW_PASS:
       case PLUGIN_INCLUDE_FILE:
+      case PLUGIN_RUN_SELFTESTS:
         {
           struct callback_info *new_callback;
           if (!callback)
@@ -577,6 +578,7 @@ invoke_plugin_callbacks_full (int event, void *gcc_data)
       case PLUGIN_EARLY_GIMPLE_PASSES_END:
       case PLUGIN_NEW_PASS:
       case PLUGIN_INCLUDE_FILE:
+      case PLUGIN_RUN_SELFTESTS:
         {
           /* Iterate over every callback registered with this event and
              call it.  */
diff --git a/gcc/plugin.def b/gcc/plugin.def
index eb31f1a..e200728 100644
--- a/gcc/plugin.def
+++ b/gcc/plugin.def
@@ -99,6 +99,9 @@ DEFEVENT (PLUGIN_NEW_PASS)
    as a const char* pointer.  */
 DEFEVENT (PLUGIN_INCLUDE_FILE)
 
+/* Called when running selftests.  */
+DEFEVENT (PLUGIN_RUN_SELFTESTS)
+
 /* When adding a new hard-coded plugin event, don't forget to edit in
    file plugin.c the functions register_callback and
    invoke_plugin_callbacks_full accordingly!  */
diff --git a/gcc/selftest-run-tests.c b/gcc/selftest-run-tests.c
index f41244b..d7fa69f 100644
--- a/gcc/selftest-run-tests.c
+++ b/gcc/selftest-run-tests.c
@@ -27,6 +27,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "options.h"
 #include "stringpool.h"
 #include "attribs.h"
+#include "plugin.h"
 
 /* This function needed to be split out from selftest.c as it references
    tests from the whole source tree, and so is within
@@ -114,6 +115,9 @@ selftest::run_tests ()
   /* Run any lang-specific selftests.  */
   lang_hooks.run_lang_selftests ();
 
+  /* Run any plugin-provided selftests.  */
+  invoke_plugin_callbacks (PLUGIN_RUN_SELFTESTS, NULL);
+
   /* Force a GC at the end of the selftests, to shake out GC-related
      issues.  For example, if any GC-managed items have buggy (or missing)
      finalizers, this last collection will ensure that things that were
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 36/49] analyzer: new file: sm-pattern-test.cc
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (25 preceding siblings ...)
  2019-11-16  1:18 ` [PATCH 05/49] vec.h: add auto_delete_vec David Malcolm
@ 2019-11-16  1:18 ` David Malcolm
  2019-12-07 15:16   ` Jeff Law
  2019-11-16  1:18 ` [PATCH 16/49] Add support for in-tree plugins David Malcolm
                   ` (23 subsequent siblings)
  50 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:18 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This patch adds a custom state machine checker intended purely for DejaGnu
testing of the sm "machinery".

gcc/ChangeLog:
	* analyzer/sm-pattern-test.cc: New file.
---
 gcc/analyzer/sm-pattern-test.cc | 165 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 165 insertions(+)
 create mode 100644 gcc/analyzer/sm-pattern-test.cc

diff --git a/gcc/analyzer/sm-pattern-test.cc b/gcc/analyzer/sm-pattern-test.cc
new file mode 100644
index 0000000..cf42cfd
--- /dev/null
+++ b/gcc/analyzer/sm-pattern-test.cc
@@ -0,0 +1,165 @@
+/* A state machine for use in DejaGnu tests, to check that
+   pattern-matching works as expected.
+
+   Copyright (C) 2019 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 "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "gimple.h"
+#include "tree-pretty-print.h"
+#include "diagnostic-path.h"
+#include "diagnostic-metadata.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/pending-diagnostic.h"
+#include "analyzer/sm.h"
+
+namespace {
+
+/* A state machine for use in DejaGnu tests, to check that
+   pattern-matching works as expected.  */
+
+class pattern_test_state_machine : public state_machine
+{
+public:
+  pattern_test_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;
+
+  void on_condition (sm_context *sm_ctxt,
+		     const supernode *node,
+		     const gimple *stmt,
+		     tree lhs,
+		     enum tree_code op,
+		     tree rhs) const FINAL OVERRIDE;
+
+  void on_leak (sm_context *sm_ctxt,
+		const supernode *node,
+		const gimple *stmt,
+		tree var,
+		state_machine::state_t state) const FINAL OVERRIDE;
+  bool can_purge_p (state_t s) const FINAL OVERRIDE;
+
+private:
+  state_t m_start;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+class pattern_match : public pending_diagnostic_subclass<pattern_match>
+{
+public:
+  pattern_match (tree lhs, enum tree_code op, tree rhs)
+  : m_lhs (lhs), m_op (op), m_rhs (rhs) {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "pattern_match"; }
+
+  bool operator== (const pattern_match &other) const
+  {
+    return (m_lhs == other.m_lhs
+	    && m_op == other.m_op
+	    && m_rhs == other.m_rhs);
+  }
+
+  bool emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    return warning_at (rich_loc, 0, "pattern match on %<%E %s %E%>",
+		       m_lhs, op_symbol_code (m_op), m_rhs);
+  }
+
+private:
+  tree m_lhs;
+  enum tree_code m_op;
+  tree m_rhs;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+pattern_test_state_machine::pattern_test_state_machine (logger *logger)
+: state_machine ("pattern-test", logger)
+{
+  m_start = add_state ("start");
+}
+
+bool
+pattern_test_state_machine::on_stmt (sm_context *sm_ctxt ATTRIBUTE_UNUSED,
+				     const supernode *node ATTRIBUTE_UNUSED,
+				     const gimple *stmt ATTRIBUTE_UNUSED) const
+{
+  return false;
+}
+
+/* Implementation of state_machine::on_condition vfunc for
+   pattern_test_state_machine.
+
+   Queue a pattern_match diagnostic for any comparison against a
+   constant.  */
+
+void
+pattern_test_state_machine::on_condition (sm_context *sm_ctxt,
+					  const supernode *node,
+					  const gimple *stmt,
+					  tree lhs,
+					  enum tree_code op,
+					  tree rhs) const
+{
+  if (stmt == NULL)
+    return;
+
+  if (!CONSTANT_CLASS_P (rhs))
+    return;
+
+  pending_diagnostic *diag = new pattern_match (lhs, op, rhs);
+  sm_ctxt->warn_for_state (node, stmt, lhs, m_start, diag);
+}
+
+void
+pattern_test_state_machine::on_leak (sm_context *sm_ctxt ATTRIBUTE_UNUSED,
+				     const supernode *node ATTRIBUTE_UNUSED,
+				     const gimple *stmt ATTRIBUTE_UNUSED,
+				     tree var ATTRIBUTE_UNUSED,
+				     state_machine::state_t state
+				       ATTRIBUTE_UNUSED)
+  const
+{
+  /* Empty.  */
+}
+
+bool
+pattern_test_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const
+{
+  return true;
+}
+
+} // anonymous namespace
+
+/* Internal interface to this file. */
+
+state_machine *
+make_pattern_test_state_machine (logger *logger)
+{
+  return new pattern_test_state_machine (logger);
+}
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 22/49] analyzer: params.def: new parameters
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (14 preceding siblings ...)
  2019-11-16  1:18 ` [PATCH 19/49] analyzer: new files: analyzer-selftests.{cc|h} David Malcolm
@ 2019-11-16  1:18 ` David Malcolm
  2019-12-07 14:49   ` Jeff Law
  2019-11-16  1:18 ` [PATCH 21/49] analyzer: command-line options David Malcolm
                   ` (34 subsequent siblings)
  50 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:18 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

gcc/ChangeLog:
	* params.def (PARAM_ANALYZER_BB_EXPLOSION_FACTOR): New param.
	(PARAM_ANALYZER_MAX_ENODES_PER_PROGRAM_POINT): New param.
	(PARAM_ANALYZER_MAX_RECURSION_DEPTH): New param.
	(PARAM_ANALYZER_MIN_SNODES_FOR_CALL_SUMMARY): New param.
---
 gcc/params.def | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/gcc/params.def b/gcc/params.def
index 4cb48d9..6715cf6 100644
--- a/gcc/params.def
+++ b/gcc/params.def
@@ -1487,6 +1487,31 @@ DEFPARAM(PARAM_SSA_NAME_DEF_CHAIN_LIMIT,
 	 "a value.",
 	 512, 0, 0)
 
+DEFPARAM(PARAM_ANALYZER_BB_EXPLOSION_FACTOR,
+	 "analyzer-bb-explosion-factor",
+	 "The maximum number of 'after supernode' exploded nodes within the "
+	 "analyzer per supernode, before terminating analysis.",
+	 5, 1, 0)
+
+DEFPARAM(PARAM_ANALYZER_MAX_ENODES_PER_PROGRAM_POINT,
+	 "analyzer-max-enodes-per-program-point",
+	 "The maximum number of exploded nodes per program point within the "
+	 "analyzer, before terminating analysis of that point.",
+	 8, 1, 0)
+
+DEFPARAM(PARAM_ANALYZER_MAX_RECURSION_DEPTH,
+	 "analyzer-max-recursion-depth",
+	 "The maximum number of times a callsite can appear in a call stack "
+	 " within the analyzer, before terminating analysis of a call that "
+	 " would recurse deeper.",
+	 2, 1, 0)
+
+DEFPARAM(PARAM_ANALYZER_MIN_SNODES_FOR_CALL_SUMMARY,
+	 "analyzer-min-snodes-for-call-summary",
+	 "The minimum number of supernodes within a function for the "
+	 "analyzer to consider summarizing its effects at call sites.",
+	 10, 1, 0)
+
 /*
 
 Local variables:
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 35/49] analyzer: new file: sm-file.cc
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (21 preceding siblings ...)
  2019-11-16  1:18 ` [PATCH 27/49] analyzer: new files: supergraph.{cc|h} David Malcolm
@ 2019-11-16  1:18 ` David Malcolm
  2019-12-07 15:15   ` Jeff Law
  2019-11-16  1:18 ` [PATCH 07/49] Replace label_text ctor with "borrow" and "take" David Malcolm
                   ` (27 subsequent siblings)
  50 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:18 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This patch adds a state machine checker for stdio's FILE stream API.

gcc/ChangeLog:
	* analyzer/sm-file.cc: New file.
---
 gcc/analyzer/sm-file.cc | 338 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 338 insertions(+)
 create mode 100644 gcc/analyzer/sm-file.cc

diff --git a/gcc/analyzer/sm-file.cc b/gcc/analyzer/sm-file.cc
new file mode 100644
index 0000000..4fc308c
--- /dev/null
+++ b/gcc/analyzer/sm-file.cc
@@ -0,0 +1,338 @@
+/* A state machine for detecting misuses of <stdio.h>'s FILE * API.
+   Copyright (C) 2019 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 "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "gimple.h"
+#include "diagnostic-path.h"
+#include "diagnostic-metadata.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/pending-diagnostic.h"
+#include "analyzer/sm.h"
+
+namespace {
+
+/* A state machine for detecting misuses of <stdio.h>'s FILE * API.  */
+
+class fileptr_state_machine : public state_machine
+{
+public:
+  fileptr_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;
+
+  void on_condition (sm_context *sm_ctxt,
+		     const supernode *node,
+		     const gimple *stmt,
+		     tree lhs,
+		     enum tree_code op,
+		     tree rhs) const FINAL OVERRIDE;
+
+  void on_leak (sm_context *sm_ctxt,
+		const supernode *node,
+		const gimple *stmt,
+		tree var,
+		state_machine::state_t state) const FINAL OVERRIDE;
+  bool can_purge_p (state_t s) const FINAL OVERRIDE;
+
+  /* Start state.  */
+  state_t m_start;
+
+  /* State for a FILE * returned from fopen that hasn't been checked for
+     NULL.
+     It could be an open stream, or could be NULL.  */
+  state_t m_unchecked;
+
+  /* State for a FILE * that's known to be NULL.  */
+  state_t m_null;
+
+  /* State for a FILE * that's known to be a non-NULL open stream.  */
+  state_t m_nonnull;
+
+  /* State for a FILE * that's had fclose called on it.  */
+  state_t m_closed;
+
+  /* Stop state, for a FILE * we don't want to track any more.  */
+  state_t m_stop;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+class file_diagnostic : public pending_diagnostic
+{
+public:
+  file_diagnostic (const fileptr_state_machine &sm, tree arg)
+  : m_sm (sm), m_arg (arg)
+  {}
+
+  bool subclass_equal_p (const pending_diagnostic &base_other) const OVERRIDE
+  {
+    return m_arg == ((const file_diagnostic &)base_other).m_arg;
+  }
+
+  label_text describe_state_change (const evdesc::state_change &change)
+    OVERRIDE
+  {
+    if (change.m_old_state == m_sm.m_start
+	&& change.m_new_state == m_sm.m_unchecked)
+      // TODO: verify that it's the fopen stmt, not a copy
+      return label_text::borrow ("opened here");
+    if (change.m_old_state == m_sm.m_unchecked
+	&& change.m_new_state == m_sm.m_nonnull)
+      return change.formatted_print ("assuming %qE is non-NULL",
+				     change.m_expr);
+    if (change.m_new_state == m_sm.m_null)
+      return change.formatted_print ("assuming %qE is NULL",
+				     change.m_expr);
+    return label_text ();
+  }
+
+protected:
+  const fileptr_state_machine &m_sm;
+  tree m_arg;
+};
+
+class double_fclose : public file_diagnostic
+{
+public:
+  double_fclose (const fileptr_state_machine &sm, tree arg)
+    : file_diagnostic (sm, arg)
+  {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "double_fclose"; }
+
+  bool emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    return warning_at (rich_loc, OPT_Wanalyzer_double_fclose,
+		       "double %<fclose%> of FILE %qE",
+		       m_arg);
+  }
+
+  label_text describe_state_change (const evdesc::state_change &change)
+    OVERRIDE
+  {
+    if (change.m_new_state == m_sm.m_closed)
+      {
+	m_first_fclose_event = change.m_event_id;
+	return change.formatted_print ("first %qs here", "fclose");
+      }
+    return file_diagnostic::describe_state_change (change);
+  }
+
+  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  {
+    if (m_first_fclose_event.known_p ())
+      return ev.formatted_print ("second %qs here; first %qs was at %@",
+				 "fclose", "fclose",
+				 &m_first_fclose_event);
+    return ev.formatted_print ("second %qs here", "fclose");
+  }
+
+private:
+  diagnostic_event_id_t m_first_fclose_event;
+};
+
+class file_leak : public file_diagnostic
+{
+public:
+  file_leak (const fileptr_state_machine &sm, tree arg)
+    : file_diagnostic (sm, arg)
+  {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "file_leak"; }
+
+  bool emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    diagnostic_metadata m;
+    /* CWE-775: "Missing Release of File Descriptor or Handle after
+       Effective Lifetime". */
+    m.add_cwe (775);
+    return warning_at (rich_loc, m, OPT_Wanalyzer_file_leak,
+		       "leak of FILE %qE",
+		       m_arg);
+  }
+
+  label_text describe_state_change (const evdesc::state_change &change)
+    FINAL OVERRIDE
+  {
+    if (change.m_new_state == m_sm.m_unchecked)
+      {
+	m_fopen_event = change.m_event_id;
+	return label_text::borrow ("opened here");
+      }
+    return file_diagnostic::describe_state_change (change);
+  }
+
+  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  {
+    if (m_fopen_event.known_p ())
+      return ev.formatted_print ("%qE leaks here; was opened at %@",
+				 ev.m_expr, &m_fopen_event);
+    else
+      return ev.formatted_print ("%qE leaks here", ev.m_expr);
+  }
+
+private:
+  diagnostic_event_id_t m_fopen_event;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+fileptr_state_machine::fileptr_state_machine (logger *logger)
+: state_machine ("file", logger)
+{
+  m_start = add_state ("start");
+  m_unchecked = add_state ("unchecked");
+  m_null = add_state ("null");
+  m_nonnull = add_state ("nonnull");
+  m_closed = add_state ("closed");
+  m_stop = add_state ("stop");
+}
+
+/* Implementation of state_machine::on_stmt vfunc for fileptr_state_machine.  */
+
+bool
+fileptr_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 (is_named_call_p (call, "fopen", 2))
+	{
+	  tree lhs = gimple_call_lhs (call);
+	  if (lhs)
+	    {
+	      lhs = sm_ctxt->get_readable_tree (lhs);
+	      sm_ctxt->on_transition (node, stmt, lhs, m_start, m_unchecked);
+	    }
+	  else
+	    {
+	      /* TODO: report leak.  */
+	    }
+	  return true;
+	}
+
+      if (is_named_call_p (call, "fclose", 1))
+	{
+	  tree arg = gimple_call_arg (call, 0);
+	  arg = sm_ctxt->get_readable_tree (arg);
+
+	  sm_ctxt->on_transition (node, stmt, arg, m_start, m_closed);
+
+	  // TODO: is it safe to call fclose (NULL) ?
+	  sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_closed);
+	  sm_ctxt->on_transition (node, stmt, arg, m_null, m_closed);
+
+	  sm_ctxt->on_transition (node, stmt , arg, m_nonnull, m_closed);
+
+	  sm_ctxt->warn_for_state (node, stmt, arg, m_closed,
+				   new double_fclose (*this, arg));
+	  sm_ctxt->on_transition (node, stmt, arg, m_closed, m_stop);
+	  return true;
+	}
+
+      // TODO: operations on closed file
+      // etc
+    }
+  return false;
+}
+
+/* Implementation of state_machine::on_condition vfunc for
+   fileptr_state_machine.
+   Potentially transition state 'unchecked' to 'nonnull' or to 'null'.  */
+
+void
+fileptr_state_machine::on_condition (sm_context *sm_ctxt,
+				     const supernode *node,
+				     const gimple *stmt,
+				     tree lhs,
+				     enum tree_code op,
+				     tree rhs) const
+{
+  if (!zerop (rhs))
+    return;
+
+  // TODO: has to be a FILE *, specifically
+  if (TREE_CODE (TREE_TYPE (lhs)) != POINTER_TYPE)
+    return;
+
+  // TODO: has to be a FILE *, specifically
+  if (TREE_CODE (TREE_TYPE (rhs)) != POINTER_TYPE)
+    return;
+
+  if (op == NE_EXPR)
+    {
+      log ("got 'ARG != 0' match");
+      sm_ctxt->on_transition (node, stmt,
+			      lhs, m_unchecked, m_nonnull);
+    }
+  else if (op == EQ_EXPR)
+    {
+      log ("got 'ARG == 0' match");
+      sm_ctxt->on_transition (node, stmt,
+			      lhs, m_unchecked, m_null);
+    }
+}
+
+/* Implementation of state_machine::on_leak vfunc for
+   fileptr_state_machine.
+   Complain about leaks of FILE * in state 'unchecked' and 'nonnull'.  */
+
+void
+fileptr_state_machine::on_leak (sm_context *sm_ctxt,
+				const supernode *node,
+				const gimple *stmt,
+				tree var,
+				state_machine::state_t state ATTRIBUTE_UNUSED)
+  const
+{
+  sm_ctxt->warn_for_state (node, stmt, var, m_unchecked,
+			   new file_leak (*this, var));
+  sm_ctxt->warn_for_state (node, stmt, var, m_nonnull,
+			   new file_leak (*this, var));
+}
+
+/* Implementation of state_machine::can_purge_p vfunc for fileptr_state_machine.
+   Don't allow purging of pointers in state 'unchecked' or 'nonnull'
+   (to avoid false leak reports).  */
+
+bool
+fileptr_state_machine::can_purge_p (state_t s) const
+{
+  return s != m_unchecked && s != m_nonnull;
+}
+
+} // anonymous namespace
+
+/* Internal interface to this file. */
+
+state_machine *
+make_fileptr_state_machine (logger *logger)
+{
+  return new fileptr_state_machine (logger);
+}
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 29/49] analyzer: new files: tristate.{cc|h}
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (19 preceding siblings ...)
  2019-11-16  1:18 ` [PATCH 17/49] Support for adding selftests via a plugin David Malcolm
@ 2019-11-16  1:18 ` David Malcolm
  2019-12-07 15:03   ` Jeff Law
  2019-12-10  0:59   ` Martin Sebor
  2019-11-16  1:18 ` [PATCH 27/49] analyzer: new files: supergraph.{cc|h} David Malcolm
                   ` (29 subsequent siblings)
  50 siblings, 2 replies; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:18 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

gcc/ChangeLog:
	* analyzer/tristate.cc: New file.
	* analyzer/tristate.h: New file.
---
 gcc/analyzer/tristate.cc | 222 +++++++++++++++++++++++++++++++++++++++++++++++
 gcc/analyzer/tristate.h  |  82 +++++++++++++++++
 2 files changed, 304 insertions(+)
 create mode 100644 gcc/analyzer/tristate.cc
 create mode 100644 gcc/analyzer/tristate.h

diff --git a/gcc/analyzer/tristate.cc b/gcc/analyzer/tristate.cc
new file mode 100644
index 0000000..ac16129
--- /dev/null
+++ b/gcc/analyzer/tristate.cc
@@ -0,0 +1,222 @@
+/* "True" vs "False" vs "Unknown".
+   Copyright (C) 2019 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 "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "analyzer/tristate.h"
+#include "selftest.h"
+
+const char *
+tristate::as_string () const
+{
+  switch (m_value)
+    {
+    default:
+      gcc_unreachable ();
+    case TS_UNKNOWN:
+      return "UNKNOWN";
+    case TS_TRUE:
+      return "TRUE";
+    case TS_FALSE:
+      return "FALSE";
+    }
+}
+
+tristate
+tristate::not_ () const
+{
+  switch (m_value)
+    {
+    default:
+      gcc_unreachable ();
+    case TS_UNKNOWN:
+      return tristate (TS_UNKNOWN);
+    case TS_TRUE:
+      return tristate (TS_FALSE);
+    case TS_FALSE:
+      return tristate (TS_TRUE);
+    }
+}
+
+tristate
+tristate::or_ (tristate other) const
+{
+  switch (m_value)
+    {
+    default:
+      gcc_unreachable ();
+    case TS_UNKNOWN:
+      if (other.is_true ())
+	return tristate (TS_TRUE);
+      else
+	return tristate (TS_UNKNOWN);
+    case TS_FALSE:
+      return other;
+    case TS_TRUE:
+      return tristate (TS_TRUE);
+    }
+}
+
+tristate
+tristate::and_ (tristate other) const
+{
+  switch (m_value)
+    {
+    default:
+      gcc_unreachable ();
+    case TS_UNKNOWN:
+      if (other.is_false ())
+	return tristate (TS_FALSE);
+      else
+	return tristate (TS_UNKNOWN);
+    case TS_TRUE:
+      return other;
+    case TS_FALSE:
+      return tristate (TS_FALSE);
+    }
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+#define ASSERT_TRISTATE_TRUE(TRISTATE) \
+  SELFTEST_BEGIN_STMT					\
+  ASSERT_EQ (TRISTATE, tristate (tristate::TS_TRUE));	\
+  SELFTEST_END_STMT
+
+#define ASSERT_TRISTATE_FALSE(TRISTATE) \
+  SELFTEST_BEGIN_STMT					\
+  ASSERT_EQ (TRISTATE, tristate (tristate::TS_FALSE));	\
+  SELFTEST_END_STMT
+
+#define ASSERT_TRISTATE_UNKNOWN(TRISTATE) \
+  SELFTEST_BEGIN_STMT						\
+  ASSERT_EQ (TRISTATE, tristate (tristate::TS_UNKNOWN));	\
+  SELFTEST_END_STMT
+
+/* Test tristate's ctors, along with is_*, as_string, operator==, and
+   operator!=.  */
+
+static void
+test_ctors ()
+{
+  tristate u (tristate::TS_UNKNOWN);
+  ASSERT_FALSE (u.is_known ());
+  ASSERT_FALSE (u.is_true ());
+  ASSERT_FALSE (u.is_false ());
+  ASSERT_STREQ (u.as_string (), "UNKNOWN");
+
+  tristate t (tristate::TS_TRUE);
+  ASSERT_TRUE (t.is_known ());
+  ASSERT_TRUE (t.is_true ());
+  ASSERT_FALSE (t.is_false ());
+  ASSERT_STREQ (t.as_string (), "TRUE");
+
+  tristate f (tristate::TS_FALSE);
+  ASSERT_TRUE (f.is_known ());
+  ASSERT_FALSE (f.is_true ());
+  ASSERT_TRUE (f.is_false ());
+  ASSERT_STREQ (f.as_string (), "FALSE");
+
+  ASSERT_EQ (u, u);
+  ASSERT_EQ (t, t);
+  ASSERT_EQ (f, f);
+  ASSERT_NE (u, t);
+  ASSERT_NE (u, f);
+  ASSERT_NE (t, f);
+
+  tristate t2 (true);
+  ASSERT_TRUE (t2.is_true ());
+  ASSERT_EQ (t, t2);
+
+  tristate f2 (false);
+  ASSERT_TRUE (f2.is_false ());
+  ASSERT_EQ (f, f2);
+
+  tristate u2 (tristate::unknown ());
+  ASSERT_TRUE (!u2.is_known ());
+  ASSERT_EQ (u, u2);
+}
+
+/* Test && on tristate instances.  */
+
+static void
+test_and ()
+{
+  ASSERT_TRISTATE_UNKNOWN (tristate::unknown () && tristate::unknown ());
+
+  ASSERT_TRISTATE_FALSE (tristate (false) && tristate (false));
+  ASSERT_TRISTATE_FALSE (tristate (false) && tristate (true));
+  ASSERT_TRISTATE_FALSE (tristate (true) && tristate (false));
+  ASSERT_TRISTATE_TRUE (tristate (true) && tristate (true));
+
+  ASSERT_TRISTATE_UNKNOWN (tristate::unknown () && tristate (true));
+  ASSERT_TRISTATE_UNKNOWN (tristate (true) && tristate::unknown ());
+
+  ASSERT_TRISTATE_FALSE (tristate::unknown () && tristate (false));
+  ASSERT_TRISTATE_FALSE (tristate (false) && tristate::unknown ());
+}
+
+/* Test || on tristate instances.  */
+
+static void
+test_or ()
+{
+  ASSERT_TRISTATE_UNKNOWN (tristate::unknown () || tristate::unknown ());
+
+  ASSERT_TRISTATE_FALSE (tristate (false) || tristate (false));
+  ASSERT_TRISTATE_TRUE (tristate (false) || tristate (true));
+  ASSERT_TRISTATE_TRUE (tristate (true) || tristate (false));
+  ASSERT_TRISTATE_TRUE (tristate (true) || tristate (true));
+
+  ASSERT_TRISTATE_TRUE (tristate::unknown () || tristate (true));
+  ASSERT_TRISTATE_TRUE (tristate (true) || tristate::unknown ());
+
+  ASSERT_TRISTATE_UNKNOWN (tristate::unknown () || tristate (false));
+  ASSERT_TRISTATE_UNKNOWN (tristate (false) || tristate::unknown ());
+}
+
+/* Test ! on tristate instances.  */
+
+static void
+test_not ()
+{
+  ASSERT_TRISTATE_UNKNOWN (!tristate::unknown ());
+  ASSERT_TRISTATE_FALSE (!tristate (true));
+  ASSERT_TRISTATE_TRUE (!tristate (false));
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+analyzer_tristate_cc_tests ()
+{
+  test_ctors ();
+  test_and ();
+  test_or ();
+  test_not ();
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
diff --git a/gcc/analyzer/tristate.h b/gcc/analyzer/tristate.h
new file mode 100644
index 0000000..88b9657
--- /dev/null
+++ b/gcc/analyzer/tristate.h
@@ -0,0 +1,82 @@
+/* "True" vs "False" vs "Unknown".
+   Copyright (C) 2019 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/>.  */
+
+#ifndef GCC_ANALYZER_TRISTATE_H
+#define GCC_ANALYZER_TRISTATE_H
+
+/* "True" vs "False" vs "Unknown".  */
+
+class tristate {
+ public:
+  enum value {
+    TS_UNKNOWN,
+    TS_TRUE,
+    TS_FALSE
+  };
+
+  tristate (enum value val) : m_value (val) {}
+  tristate (bool val) : m_value (val ? TS_TRUE : TS_FALSE) {}
+  static tristate unknown () { return tristate (TS_UNKNOWN); }
+
+  const char *as_string () const;
+
+  bool is_known () const { return m_value != TS_UNKNOWN; }
+  bool is_true () const { return m_value == TS_TRUE; }
+  bool is_false () const { return m_value == TS_FALSE; }
+
+  tristate not_ () const;
+  tristate or_ (tristate other) const;
+  tristate and_ (tristate other) const;
+
+  bool operator== (const tristate &other) const
+  {
+    return m_value == other.m_value;
+  }
+
+  bool operator!= (const tristate &other) const
+  {
+    return m_value != other.m_value;
+  }
+
+ private:
+  enum value m_value;
+};
+
+/* Overloaded boolean operators on tristates.  */
+
+inline tristate
+operator ! (tristate t)
+{
+  return t.not_ ();
+}
+
+inline tristate
+operator || (tristate a, tristate b)
+{
+  return a.or_ (b);
+}
+
+inline tristate
+operator && (tristate a, tristate b)
+{
+  return a.and_ (b);
+}
+
+#endif /* GCC_ANALYZER_TRISTATE_H */
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 27/49] analyzer: new files: supergraph.{cc|h}
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (20 preceding siblings ...)
  2019-11-16  1:18 ` [PATCH 29/49] analyzer: new files: tristate.{cc|h} David Malcolm
@ 2019-11-16  1:18 ` David Malcolm
  2019-11-16  1:18 ` [PATCH 35/49] analyzer: new file: sm-file.cc David Malcolm
                   ` (28 subsequent siblings)
  50 siblings, 0 replies; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:18 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This patch adds a "supergraph" class that combines CFGs and callgraph into
one directed graph, along with "supernode" and "superedge" classes.

gcc/ChangeLog:
	* analyzer/supergraph.cc: New file.
	* analyzer/supergraph.h: New file.
---
 gcc/analyzer/supergraph.cc | 936 +++++++++++++++++++++++++++++++++++++++++++++
 gcc/analyzer/supergraph.h  | 560 +++++++++++++++++++++++++++
 2 files changed, 1496 insertions(+)
 create mode 100644 gcc/analyzer/supergraph.cc
 create mode 100644 gcc/analyzer/supergraph.h

diff --git a/gcc/analyzer/supergraph.cc b/gcc/analyzer/supergraph.cc
new file mode 100644
index 0000000..da5b13a
--- /dev/null
+++ b/gcc/analyzer/supergraph.cc
@@ -0,0 +1,936 @@
+/* "Supergraph" classes that combine CFGs and callgraph into one digraph.
+   Copyright (C) 2019 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 "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "tm.h"
+#include "toplev.h"
+#include "hash-table.h"
+#include "vec.h"
+#include "ggc.h"
+#include "basic-block.h"
+#include "gimple-fold.h"
+#include "tree-eh.h"
+#include "gimple-expr.h"
+#include "is-a.h"
+#include "timevar.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "gimple-pretty-print.h"
+#include "tree-pretty-print.h"
+#include "graphviz.h"
+#include "cgraph.h"
+#include "tree-dfa.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/supergraph.h"
+#include "analyzer/analyzer-logging.h"
+
+/* Get the cgraph_edge, but only if there's an underlying function body.  */
+
+cgraph_edge *
+supergraph_call_edge (function *fun, gimple *stmt)
+{
+  gcall *call = dyn_cast<gcall *> (stmt);
+  if (!call)
+    return NULL;
+  cgraph_edge *edge = cgraph_node::get (fun->decl)->get_edge (stmt);
+  if (!edge)
+    return NULL;
+  if (!edge->callee)
+    return NULL; /* e.g. for a function pointer.  */
+  if (!edge->callee->get_fun ())
+    return NULL;
+  return edge;
+}
+
+/* supergraph's ctor.  Walk the callgraph, building supernodes for each
+   CFG basic block, splitting the basic blocks at callsites.  Join
+   together the supernodes with interprocedural and intraprocedural
+   superedges as appropriate.  */
+
+supergraph::supergraph (logger *logger)
+{
+  auto_client_timevar tv ("building supergraph");
+
+  LOG_FUNC (logger);
+
+  /* First pass: make supernodes.  */
+  {
+    /* Sort the cgraph_nodes?  */
+    cgraph_node *node;
+    FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
+    {
+      function *fun = node->get_fun ();
+
+      /* Ensure that EDGE_DFS_BACK is correct for every CFG edge in
+	 the supergraph (by doing it per-function).  */
+      auto_cfun sentinel (fun);
+      mark_dfs_back_edges ();
+
+      const int start_idx = m_nodes.length ();
+
+      basic_block bb;
+      FOR_ALL_BB_FN (bb, fun)
+	{
+	  /* The initial supernode for the BB gets the phi nodes (if any).  */
+	  supernode *node_for_stmts = add_node (fun, bb, NULL, phi_nodes (bb));
+	  m_bb_to_initial_node.put (bb, node_for_stmts);
+	  for (gphi_iterator gpi = gsi_start_phis (bb); !gsi_end_p (gpi);
+	       gsi_next (&gpi))
+	    {
+	      gimple *stmt = gsi_stmt (gpi);
+	      m_stmt_to_node_t.put (stmt, node_for_stmts);
+	    }
+
+	  /* Append statements from BB to the current supernode, splitting
+	     them into a new supernode at each call site; such call statements
+	     appear in both supernodes (representing call and return).  */
+	  gimple_stmt_iterator gsi;
+	  for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
+	    {
+	      gimple *stmt = gsi_stmt (gsi);
+	      node_for_stmts->m_stmts.safe_push (stmt);
+	      m_stmt_to_node_t.put (stmt, node_for_stmts);
+	      if (cgraph_edge *edge = supergraph_call_edge (fun, stmt))
+		{
+		  m_cgraph_edge_to_caller_prev_node.put(edge, node_for_stmts);
+		  node_for_stmts = add_node (fun, bb, as_a <gcall *> (stmt), NULL);
+		  m_cgraph_edge_to_caller_next_node.put (edge, node_for_stmts);
+		}
+	    }
+
+	  m_bb_to_final_node.put (bb, node_for_stmts);
+	}
+
+      const unsigned num_snodes = m_nodes.length () - start_idx;
+      m_function_to_num_snodes.put (fun, num_snodes);
+
+      if (logger)
+	{
+	  const int end_idx = m_nodes.length () - 1;
+	  logger->log ("SN: %i...%i: function %qD",
+		       start_idx, end_idx, fun->decl);
+	}
+    }
+  }
+
+  /* Second pass: make superedges.  */
+  {
+    /* Make superedges for CFG edges.  */
+    for (bb_to_node_t::iterator iter = m_bb_to_final_node.begin ();
+	 iter != m_bb_to_final_node.end ();
+	 ++iter)
+      {
+	basic_block bb = (*iter).first;
+	supernode *src_supernode = (*iter).second;
+
+	::edge cfg_edge;
+	int idx;
+	if (bb->succs)
+	  FOR_EACH_VEC_ELT (*bb->succs, idx, cfg_edge)
+	    {
+	      basic_block dest_cfg_block = cfg_edge->dest;
+	      supernode *dest_supernode
+		= *m_bb_to_initial_node.get (dest_cfg_block);
+	      cfg_superedge *cfg_sedge
+		= add_cfg_edge (src_supernode, dest_supernode, cfg_edge, idx);
+	      m_cfg_edge_to_cfg_superedge.put (cfg_edge, cfg_sedge);
+	    }
+      }
+
+    /* Make interprocedural superedges for calls.  */
+    {
+      for (cgraph_edge_to_node_t::iterator iter
+	     = m_cgraph_edge_to_caller_prev_node.begin ();
+	   iter != m_cgraph_edge_to_caller_prev_node.end ();
+	   ++iter)
+	{
+	  cgraph_edge *edge = (*iter).first;
+	  supernode *caller_prev_supernode = (*iter).second;
+	  basic_block callee_cfg_block
+	    = ENTRY_BLOCK_PTR_FOR_FN (edge->callee->get_fun ());
+	  supernode *callee_supernode
+	    = *m_bb_to_initial_node.get (callee_cfg_block);
+	  call_superedge *sedge
+	    = add_call_superedge (caller_prev_supernode,
+				  callee_supernode,
+				  edge);
+	  m_cgraph_edge_to_call_superedge.put (edge, sedge);
+	}
+    }
+
+    /* Make interprocedural superedges for returns.  */
+    {
+      for (cgraph_edge_to_node_t::iterator iter
+	     = m_cgraph_edge_to_caller_next_node.begin ();
+	   iter != m_cgraph_edge_to_caller_next_node.end ();
+	   ++iter)
+	{
+	  cgraph_edge *edge = (*iter).first;
+	  supernode *caller_next_supernode = (*iter).second;
+	  basic_block callee_cfg_block
+	    = EXIT_BLOCK_PTR_FOR_FN (edge->callee->get_fun ());
+	  supernode *callee_supernode
+	    = *m_bb_to_initial_node.get (callee_cfg_block);
+	  return_superedge *sedge
+	    = add_return_superedge (callee_supernode,
+				    caller_next_supernode,
+				    edge);
+	  m_cgraph_edge_to_return_superedge.put (edge, sedge);
+	}
+    }
+
+    /* Make intraprocedural superedges linking the two halves of a call.  */
+    {
+      for (cgraph_edge_to_node_t::iterator iter
+	     = m_cgraph_edge_to_caller_prev_node.begin ();
+	   iter != m_cgraph_edge_to_caller_prev_node.end ();
+	   ++iter)
+	{
+	  cgraph_edge *edge = (*iter).first;
+	  supernode *caller_prev_supernode = (*iter).second;
+	  supernode *caller_next_supernode
+	    = *m_cgraph_edge_to_caller_next_node.get (edge);
+	  superedge *sedge
+	    = new callgraph_superedge (caller_prev_supernode,
+				       caller_next_supernode,
+				       SUPEREDGE_INTRAPROCEDURAL_CALL,
+				       edge);
+	  add_edge (sedge);
+	  m_cgraph_edge_to_intraproc_superedge.put (edge, sedge);
+	}
+
+    }
+  }
+}
+
+/* Dump this graph in .dot format to PP, using DUMP_ARGS.
+   Cluster the supernodes by function, then by BB from original CFG.  */
+
+void
+supergraph::dump_dot_to_pp (pretty_printer *pp,
+			    const dump_args_t &dump_args) const
+{
+  graphviz_out gv (pp);
+
+  pp_string (pp, "digraph \"");
+  pp_write_text_to_stream (pp);
+  pp_string (pp, "supergraph");
+  pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/false);
+  pp_string (pp, "\" {\n");
+  gv.indent ();
+
+  gv.println ("overlap=false;");
+  gv.println ("compound=true;");
+
+  /* TODO: maybe (optionally) sub-subdivide by TU, for LTO; see also:
+     https://gcc-python-plugin.readthedocs.io/en/latest/_images/sample-supergraph.png
+  */
+
+  /* Break out the supernodes into clusters by function.  */
+  {
+    cgraph_node *node;
+    FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
+    {
+      function *fun = node->get_fun ();
+      const char *funcname = function_name (fun);
+      gv.println ("subgraph \"cluster_%s\" {",
+		  funcname);
+      gv.indent ();
+      pp_printf (pp,
+		 ("style=\"dashed\";"
+		  " color=\"black\";"
+		  " label=\"%s\";\n"),
+		 funcname);
+
+      /* Break out the nodes into clusters by BB from original CFG.  */
+      {
+	basic_block bb;
+	FOR_ALL_BB_FN (bb, fun)
+	  {
+	    if (dump_args.m_flags & SUPERGRAPH_DOT_SHOW_BBS)
+	      {
+		gv.println ("subgraph \"cluster_%s_bb_%i\" {",
+			    funcname, bb->index);
+		gv.indent ();
+		pp_printf (pp,
+			   ("style=\"dashed\";"
+			    " color=\"black\";"
+			    " label=\"bb: %i\";\n"),
+			   bb->index);
+	      }
+
+	    // TODO: maybe keep an index per-function/per-bb to speed this up???
+	    int i;
+	    supernode *n;
+	    FOR_EACH_VEC_ELT (m_nodes, i, n)
+	      if (n->m_fun == fun && n->m_bb == bb)
+		n->dump_dot (&gv, dump_args);
+
+	    if (dump_args.m_flags & SUPERGRAPH_DOT_SHOW_BBS)
+	      {
+		/* Terminate per-bb "subgraph" */
+		gv.outdent ();
+		gv.println ("}");
+	      }
+	  }
+      }
+
+      /* Add an invisible edge from ENTRY to EXIT, to improve the graph layout.  */
+      pp_string (pp, "\t");
+      get_node_for_function_entry (fun)->dump_dot_id (pp);
+      pp_string (pp, ":s -> ");
+      get_node_for_function_exit (fun)->dump_dot_id (pp);
+      pp_string (pp, ":n [style=\"invis\",constraint=true];\n");
+
+      /* Terminate per-function "subgraph" */
+      gv.outdent ();
+      gv.println ("}");
+    }
+  }
+
+  /* Superedges.  */
+  int i;
+  superedge *e;
+  FOR_EACH_VEC_ELT (m_edges, i, e)
+    e->dump_dot (&gv, dump_args);
+
+  /* Terminate "digraph" */
+  gv.outdent ();
+  gv.println ("}");
+}
+
+/* Dump this graph in .dot format to FP, using DUMP_ARGS.  */
+
+void
+supergraph::dump_dot_to_file (FILE *fp, const dump_args_t &dump_args) const
+{
+  pretty_printer *pp = global_dc->printer->clone ();
+  pp_show_color (pp) = 0;
+  /* %qE in logs for SSA_NAMEs should show the ssa names, rather than
+     trying to prettify things by showing the underlying var.  */
+  pp_format_decoder (pp) = default_tree_printer;
+
+  pp->buffer->stream = fp;
+  dump_dot_to_pp (pp, dump_args);
+  pp_flush (pp);
+  delete pp;
+}
+
+/* Dump this graph in .dot format to PATH, using DUMP_ARGS.  */
+
+void
+supergraph::dump_dot (const char *path, const dump_args_t &dump_args) const
+{
+  FILE *fp = fopen (path, "w");
+  dump_dot_to_file (fp, dump_args);
+  fclose (fp);
+}
+
+/* Create a supernode for BB within FUN and add it to this supergraph.
+
+   If RETURNING_CALL is non-NULL, the supernode represents the resumption
+   of the basic block after returning from that call.
+
+   If PHI_NODES is non-NULL, this is the initial supernode for the basic
+   block, and is responsible for any handling of the phi nodes.  */
+
+supernode *
+supergraph::add_node (function *fun, basic_block bb, gcall *returning_call,
+		      gimple_seq phi_nodes)
+{
+  supernode *n = new supernode (fun, bb, returning_call, phi_nodes,
+				m_nodes.length ());
+  m_nodes.safe_push (n);
+  return n;
+}
+
+/* Create a new cfg_superedge from SRC to DEST for the underlying CFG edge E,
+   adding it to this supergraph.
+
+   If the edge is for a switch statement, create a switch_cfg_superedge
+   subclass using IDX (the index of E within the out-edges from SRC's
+   underlying basic block).  */
+
+cfg_superedge *
+supergraph::add_cfg_edge (supernode *src, supernode *dest, ::edge e, int idx)
+{
+  /* Special-case switch edges.  */
+  gimple *stmt = src->get_last_stmt ();
+  cfg_superedge *new_edge;
+  if (stmt && stmt->code == GIMPLE_SWITCH)
+    new_edge = new switch_cfg_superedge (src, dest, e, idx);
+  else
+    new_edge = new cfg_superedge (src, dest, e);
+  add_edge (new_edge);
+  return new_edge;
+}
+
+/* Create and add a call_superedge representing an interprocedural call
+   from SRC to DEST, using CEDGE.  */
+
+call_superedge *
+supergraph::add_call_superedge (supernode *src, supernode *dest,
+				cgraph_edge *cedge)
+{
+  call_superedge *new_edge = new call_superedge (src, dest, cedge);
+  add_edge (new_edge);
+  return new_edge;
+}
+
+/* Create and add a return_superedge representing returning from an
+   interprocedural call, returning from SRC to DEST, using CEDGE.  */
+
+return_superedge *
+supergraph::add_return_superedge (supernode *src, supernode *dest,
+				  cgraph_edge *cedge)
+{
+  return_superedge *new_edge = new return_superedge (src, dest, cedge);
+  add_edge (new_edge);
+  return new_edge;
+}
+
+/* Implementation of dnode::dump_dot vfunc for supernodes.
+
+   Write a cluster for the node, and within it a record showing
+   the phi nodes and stmts.  Call into any node annotator from ARGS to
+   potentially add other records to the cluster.  */
+
+void
+supernode::dump_dot (graphviz_out *gv, const dump_args_t &args) const
+{
+  gv->println ("subgraph cluster_node_%i {",
+	       m_index);
+  gv->indent ();
+
+  gv->println("style=\"solid\";");
+  gv->println("color=\"black\";");
+  gv->println("fillcolor=\"lightgrey\";");
+  gv->println("label=\"sn: %i\";", m_index);
+
+  pretty_printer *pp = gv->get_pp ();
+
+  if (args.m_node_annotator)
+    args.m_node_annotator->add_node_annotations (pp, *this);
+
+  gv->write_indent ();
+  dump_dot_id (pp);
+  pp_printf (pp, " [shape=%s,style=filled,fillcolor=%s,label=\"",
+	     "record", "lightgrey");
+  pp_left_brace (pp);
+  pp_write_text_to_stream (pp);
+
+  if (m_returning_call)
+    {
+      pp_string (pp, "returning call: ");
+      pp_gimple_stmt_1 (pp, m_returning_call, 0, (dump_flags_t)0);
+      if (args.m_node_annotator)
+	args.m_node_annotator->add_stmt_annotations (pp, m_returning_call);
+      pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
+      pp_newline (pp);
+    }
+
+  if (entry_p ())
+    {
+      pp_string (pp, "ENTRY");
+      pp_newline (pp);
+    }
+
+  if (return_p ())
+    {
+      pp_string (pp, "EXIT");
+      pp_newline (pp);
+    }
+
+  /* Phi nodes.  */
+  for (gphi_iterator gpi = const_cast<supernode *> (this)->start_phis ();
+       !gsi_end_p (gpi); gsi_next (&gpi))
+    {
+      const gimple *stmt = gsi_stmt (gpi);
+      pp_gimple_stmt_1 (pp, stmt, 0, (dump_flags_t)0);
+      if (args.m_node_annotator)
+	args.m_node_annotator->add_stmt_annotations (pp, stmt);
+      pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
+      pp_newline (pp);
+    }
+
+  /* Statements.  */
+  int i;
+  gimple *stmt;
+  FOR_EACH_VEC_ELT (m_stmts, i, stmt)
+    {
+      pp_gimple_stmt_1 (pp, stmt, 0, (dump_flags_t)0);
+      if (args.m_node_annotator)
+	args.m_node_annotator->add_stmt_annotations (pp, stmt);
+      pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
+      pp_newline (pp);
+    }
+
+  pp_right_brace (pp);
+  pp_string (pp, "\"];\n\n");
+  pp_flush (pp);
+
+  /* Terminate "subgraph" */
+  gv->outdent ();
+  gv->println ("}");
+}
+
+/* Write an ID for this node to PP, for use in .dot output.  */
+
+void
+supernode::dump_dot_id (pretty_printer *pp) const
+{
+  pp_printf (pp, "node_%i", m_index);
+}
+
+/* Get a location_t for the start of this supernode.  */
+
+location_t
+supernode::get_start_location () const
+{
+  if (m_returning_call && m_returning_call->location != UNKNOWN_LOCATION)
+    return m_returning_call->location;
+
+  int i;
+  gimple *stmt;
+  FOR_EACH_VEC_ELT (m_stmts, i, stmt)
+    if (stmt->location != UNKNOWN_LOCATION)
+      return stmt->location;
+
+  if (entry_p ())
+    {
+      // TWEAK: show the decl instead; this leads to more readable output:
+      return DECL_SOURCE_LOCATION (m_fun->decl);
+
+      return m_fun->function_start_locus;
+    }
+  if (return_p ())
+    return m_fun->function_end_locus;
+
+  return UNKNOWN_LOCATION;
+}
+
+/* Get a location_t for the end of this supernode.  */
+
+location_t
+supernode::get_end_location () const
+{
+  int i;
+  gimple *stmt;
+  FOR_EACH_VEC_ELT_REVERSE (m_stmts, i, stmt)
+    if (stmt->location != UNKNOWN_LOCATION)
+      return stmt->location;
+
+  if (m_returning_call && m_returning_call->location != UNKNOWN_LOCATION)
+    return m_returning_call->location;
+
+  if (entry_p ())
+    return m_fun->function_start_locus;
+  if (return_p ())
+    return m_fun->function_end_locus;
+
+  return UNKNOWN_LOCATION;
+}
+
+/* Given STMT within this supernode, return its index within m_stmts.  */
+
+unsigned int
+supernode::get_stmt_index (const gimple *stmt) const
+{
+  unsigned i;
+  gimple *iter_stmt;
+  FOR_EACH_VEC_ELT (m_stmts, i, iter_stmt)
+    if (iter_stmt == stmt)
+      return i;
+  gcc_unreachable ();
+}
+
+/* Implementation of dedge::dump_dot for superedges.
+   Write a .dot edge to GV representing this superedge.  */
+
+void
+superedge::dump_dot (graphviz_out *gv, const dump_args_t &) const
+{
+  const char *style = "\"solid,bold\"";
+  const char *color = "black";
+  int weight = 10;
+  const char *constraint = "true";
+
+  switch (m_kind)
+    {
+    default:
+      gcc_unreachable ();
+    case SUPEREDGE_CFG_EDGE:
+      break;
+    case SUPEREDGE_CALL:
+      color = "red";
+      break;
+    case SUPEREDGE_RETURN:
+      color = "green";
+      break;
+    case SUPEREDGE_INTRAPROCEDURAL_CALL:
+      style = "\"dotted\"";
+      break;
+    }
+
+  /* Adapted from graph.c:draw_cfg_node_succ_edges.  */
+  if (::edge cfg_edge = get_any_cfg_edge ())
+    {
+      if (cfg_edge->flags & EDGE_FAKE)
+	{
+	  style = "dotted";
+	  color = "green";
+	  weight = 0;
+	}
+      else if (cfg_edge->flags & EDGE_DFS_BACK)
+	{
+	  style = "\"dotted,bold\"";
+	  color = "blue";
+	  weight = 10;
+	}
+      else if (cfg_edge->flags & EDGE_FALLTHRU)
+	{
+	  color = "blue";
+	  weight = 100;
+	}
+
+      if (cfg_edge->flags & EDGE_ABNORMAL)
+	color = "red";
+    }
+
+  gv->write_indent ();
+
+  pretty_printer *pp = gv->get_pp ();
+
+  m_src->dump_dot_id (pp);
+  pp_string (pp, " -> ");
+  m_dest->dump_dot_id (pp);
+  pp_printf (pp,
+	     (" [style=%s, color=%s, weight=%d, constraint=%s,"
+	      " ltail=\"cluster_node_%i\", lhead=\"cluster_node_%i\""
+	      " headlabel=\""),
+	     style, color, weight, constraint,
+	     m_src->m_index, m_dest->m_index);
+
+  dump_label_to_pp (pp, false);
+
+  pp_printf (pp, "\"];\n");
+}
+
+/* If this is an intraprocedural superedge, return the associated
+   CFG edge.  Otherwise, return NULL.  */
+
+::edge
+superedge::get_any_cfg_edge () const
+{
+  if (const cfg_superedge *sub = dyn_cast_cfg_superedge ())
+    return sub->get_cfg_edge ();
+  return NULL;
+}
+
+/* If this is an interprocedural superedge, return the associated
+   cgraph_edge *.  Otherwise, return NULL.  */
+
+cgraph_edge *
+superedge::get_any_callgraph_edge () const
+{
+  if (const callgraph_superedge *sub = dyn_cast_callgraph_superedge ())
+    return sub->m_cedge;
+  return NULL;
+}
+
+/* Build a description of this superedge (e.g. "true" for the true
+   edge of a conditional, or "case 42:" for a switch case).
+
+   The caller is responsible for freeing the result.
+
+   If USER_FACING is false, the result also contains any underlying
+   CFG edge flags. e.g. " (flags FALLTHRU | DFS_BACK)".  */
+
+char *
+superedge::get_description (bool user_facing) const
+{
+  pretty_printer pp;
+  dump_label_to_pp (&pp, user_facing);
+  return xstrdup (pp_formatted_text (&pp));
+}
+
+/* Implementation of superedge::dump_label_to_pp for non-switch CFG
+   superedges.
+
+   For true/false edges, print "true" or "false" to PP.
+
+   If USER_FACING is false, also print flags on the underlying CFG edge to
+   PP.  */
+
+void
+cfg_superedge::dump_label_to_pp (pretty_printer *pp,
+				 bool user_facing) const
+{
+  if (true_value_p ())
+    pp_printf (pp, "true");
+  else if (false_value_p ())
+    pp_printf (pp, "false");
+
+  if (user_facing)
+    return;
+
+  /* Express edge flags as a string with " | " separator.
+     e.g. " (flags FALLTHRU | DFS_BACK)".  */
+  if (get_flags ())
+    {
+      pp_string (pp, " (flags ");
+      bool seen_flag = false;
+#define DEF_EDGE_FLAG(NAME,IDX)			\
+  do {						\
+    if (get_flags () & EDGE_##NAME)			\
+      {						\
+	if (seen_flag)				\
+	  pp_string (pp, " | ");			\
+	pp_printf (pp, "%s", (#NAME));		\
+	seen_flag = true;			\
+      }						\
+  } while (0);
+#include "cfg-flags.def"
+#undef DEF_EDGE_FLAG
+      pp_string (pp, ")");
+    }
+
+  /* Otherwise, no label.  */
+}
+
+/* Get the phi argument for PHI for this CFG edge.  */
+
+tree
+cfg_superedge::get_phi_arg (const gphi *phi) const
+{
+  size_t index = m_cfg_edge->dest_idx;
+  return gimple_phi_arg_def (phi, index);
+}
+
+/* Implementation of superedge::dump_label_to_pp for CFG superedges for
+   "switch" statements.
+
+   Print "case VAL:", "case LOWER ... UPPER:", or "default:" to PP.  */
+
+void
+switch_cfg_superedge::dump_label_to_pp (pretty_printer *pp,
+					bool user_facing ATTRIBUTE_UNUSED) const
+{
+  tree case_label = get_case_label ();
+  gcc_assert (TREE_CODE (case_label) == CASE_LABEL_EXPR);
+  tree lower_bound = CASE_LOW (case_label);
+  tree upper_bound = CASE_HIGH (case_label);
+  if (lower_bound)
+    {
+      pp_printf (pp, "case ");
+      dump_generic_node (pp, lower_bound, 0, (dump_flags_t)0, false);
+      if (upper_bound)
+	{
+	  pp_printf (pp, " ... ");
+	  dump_generic_node (pp, upper_bound, 0, (dump_flags_t)0, false);
+	}
+      pp_printf (pp, ":");
+    }
+  else
+    pp_printf (pp, "default:");
+}
+
+/* Get the case label for this "switch" superedge.  */
+
+tree
+switch_cfg_superedge::get_case_label () const
+{
+  return gimple_switch_label (get_switch_stmt (), m_idx);
+}
+
+/* Implementation of superedge::dump_label_to_pp for interprocedural
+   superedges.  */
+
+void
+callgraph_superedge::dump_label_to_pp (pretty_printer *pp,
+				       bool user_facing ATTRIBUTE_UNUSED) const
+{
+  switch (m_kind)
+    {
+    default:
+    case SUPEREDGE_CFG_EDGE:
+      gcc_unreachable ();
+
+    case SUPEREDGE_CALL:
+      pp_printf (pp, "call");
+      break;
+
+    case SUPEREDGE_RETURN:
+      pp_printf (pp, "return");
+      break;
+
+    case SUPEREDGE_INTRAPROCEDURAL_CALL:
+      pp_printf (pp, "intraproc link");
+      break;
+    }
+}
+
+/* Get the function that was called at this interprocedural call/return
+   edge.  */
+
+function *
+callgraph_superedge::get_callee_function () const
+{
+  return m_cedge->callee->get_fun ();
+}
+
+/* Get the calling function at this interprocedural call/return edge.  */
+
+function *
+callgraph_superedge::get_caller_function () const
+{
+  return m_cedge->caller->get_fun ();
+}
+
+/* Get the fndecl that was called at this interprocedural call/return
+   edge.  */
+
+tree
+callgraph_superedge::get_callee_decl () const
+{
+  return get_callee_function ()->decl;
+}
+
+/* Get the calling fndecl at this interprocedural call/return edge.  */
+
+tree
+callgraph_superedge::get_caller_decl () const
+{
+  return get_caller_function ()->decl;
+}
+
+/* Given PARM_TO_FIND, a PARM_DECL, identify its index (writing it
+   to *OUT if OUT is non-NULL), and return the corresponding argument
+   at the callsite.  */
+
+tree
+callgraph_superedge::get_arg_for_parm (tree parm_to_find,
+				       callsite_expr *out) const
+{
+  gcc_assert  (TREE_CODE (parm_to_find) == PARM_DECL);
+
+  tree callee = get_callee_decl ();
+
+  int i = 0;
+  for (tree iter_parm = DECL_ARGUMENTS (callee); iter_parm;
+       iter_parm = DECL_CHAIN (iter_parm), ++i)
+    {
+      if (iter_parm == parm_to_find)
+	{
+	  if (out)
+	    *out = callsite_expr::from_zero_based_param (i);
+	  return gimple_call_arg (get_call_stmt (), i);
+	}
+    }
+
+  /* Not found.  */
+  return NULL_TREE;
+}
+
+/* Look for a use of ARG_TO_FIND as an argument at this callsite.
+   If found, return the default SSA def of the corresponding parm within
+   the callee, and if OUT is non-NULL, write the index to *OUT.
+   Only the first match is handled.  */
+
+tree
+callgraph_superedge::get_parm_for_arg (tree arg_to_find,
+				       callsite_expr *out) const
+{
+  tree callee = get_callee_decl ();
+
+  int i = 0;
+  for (tree iter_parm = DECL_ARGUMENTS (callee); iter_parm;
+       iter_parm = DECL_CHAIN (iter_parm), ++i)
+    {
+      tree param = gimple_call_arg (get_call_stmt (), i);
+      if (arg_to_find == param)
+	{
+	  if (out)
+	    *out = callsite_expr::from_zero_based_param (i);
+	  return ssa_default_def (get_callee_function (), iter_parm);
+	}
+    }
+
+  /* Not found.  */
+  return NULL_TREE;
+}
+
+/* Map caller_expr back to an expr within the callee, or return NULL_TREE.
+   If non-NULL is returned, populate OUT.  */
+
+tree
+callgraph_superedge::map_expr_from_caller_to_callee (tree caller_expr,
+						     callsite_expr *out) const
+{
+  /* Is it an argument (actual param)?  If so, convert to
+     parameter (formal param).  */
+  tree parm = get_parm_for_arg (caller_expr, out);
+  if (parm)
+    return parm;
+  /* Otherwise try return value.  */
+  if (caller_expr == gimple_call_lhs (get_call_stmt ()))
+    {
+      if (out)
+	*out = callsite_expr::from_return_value ();
+      return DECL_RESULT (get_callee_decl ());
+    }
+
+  return NULL_TREE;
+}
+
+/* Map callee_expr back to an expr within the caller, or return NULL_TREE.
+   If non-NULL is returned, populate OUT.  */
+
+tree
+callgraph_superedge::map_expr_from_callee_to_caller (tree callee_expr,
+						     callsite_expr *out) const
+{
+  if (callee_expr == NULL_TREE)
+    return NULL_TREE;
+
+  /* If it's a parameter (formal param), get the argument (actual param).  */
+  if (TREE_CODE (callee_expr) == PARM_DECL)
+    return get_arg_for_parm (callee_expr, out);
+
+  /* Similar for the default SSA name of the PARM_DECL.  */
+  if (TREE_CODE (callee_expr) == SSA_NAME
+      && SSA_NAME_IS_DEFAULT_DEF (callee_expr)
+      && TREE_CODE (SSA_NAME_VAR (callee_expr)) == PARM_DECL)
+    return get_arg_for_parm (SSA_NAME_VAR (callee_expr), out);
+
+  /* Otherwise try return value.  */
+  if (callee_expr == DECL_RESULT (get_callee_decl ()))
+    {
+      if (out)
+	*out = callsite_expr::from_return_value ();
+      return gimple_call_lhs (get_call_stmt ());
+    }
+
+  return NULL_TREE;
+}
diff --git a/gcc/analyzer/supergraph.h b/gcc/analyzer/supergraph.h
new file mode 100644
index 0000000..78ae93a
--- /dev/null
+++ b/gcc/analyzer/supergraph.h
@@ -0,0 +1,560 @@
+/* "Supergraph" classes that combine CFGs and callgraph into one digraph.
+   Copyright (C) 2019 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/>.  */
+
+#ifndef GCC_ANALYZER_SUPERGRAPH_H
+#define GCC_ANALYZER_SUPERGRAPH_H
+
+#include "ordered-hash-map.h"
+#include "cgraph.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "analyzer/digraph.h"
+
+/* Forward decls, using indentation to show inheritance.  */
+
+class supergraph;
+class supernode;
+class superedge;
+  class callgraph_superedge;
+    class call_superedge;
+    class return_superedge;
+  class cfg_superedge;
+    class switch_cfg_superedge;
+class supercluster;
+class dot_annotator;
+
+class logger;
+
+/* An enum for discriminating between superedge subclasses.  */
+
+enum edge_kind
+{
+  SUPEREDGE_CFG_EDGE,
+  SUPEREDGE_CALL,
+  SUPEREDGE_RETURN,
+  SUPEREDGE_INTRAPROCEDURAL_CALL
+};
+
+/* Flags for controlling the appearance of .dot dumps.  */
+
+enum supergraph_dot_flags
+{
+  SUPERGRAPH_DOT_SHOW_BBS = (1 << 0)
+};
+
+/* A traits struct describing the family of node, edge and digraph
+   classes for supergraphs.  */
+
+struct supergraph_traits
+{
+  typedef supernode node_t;
+  typedef superedge edge_t;
+  typedef supergraph graph_t;
+  struct dump_args_t
+  {
+    dump_args_t (enum supergraph_dot_flags flags,
+		 const dot_annotator *node_annotator)
+    : m_flags (flags),
+      m_node_annotator (node_annotator)
+    {}
+
+    enum supergraph_dot_flags m_flags;
+    const dot_annotator *m_node_annotator;
+  };
+  typedef supercluster cluster_t;
+};
+
+/* A "supergraph" is a directed graph formed by joining together all CFGs,
+   linking them via interprocedural call and return edges.
+
+   Basic blocks are split at callsites, so that a call statement occurs
+   twice: once at the end of a supernode, and a second instance at the
+   start of the next supernode (to handle the return).  */
+
+class supergraph : public digraph<supergraph_traits>
+{
+public:
+  supergraph (logger *logger);
+
+  supernode *get_node_for_function_entry (function *fun) const
+  {
+    return get_node_for_block (ENTRY_BLOCK_PTR_FOR_FN (fun));
+  }
+
+  supernode *get_node_for_function_exit (function *fun) const
+  {
+    return get_node_for_block (EXIT_BLOCK_PTR_FOR_FN (fun));
+  }
+
+  supernode *get_node_for_block (basic_block bb) const
+  {
+    return *const_cast <bb_to_node_t &> (m_bb_to_initial_node).get (bb);
+  }
+
+  /* Get the supernode containing the second half of the gcall *
+     at an interprocedural call, within the caller.  */
+  supernode *get_caller_next_node (cgraph_edge *edge) const
+  {
+    return (*const_cast <cgraph_edge_to_node_t &>
+	      (m_cgraph_edge_to_caller_next_node).get (edge));
+  }
+
+  call_superedge *get_edge_for_call (cgraph_edge *edge) const
+  {
+    return (*const_cast <cgraph_edge_to_call_superedge_t &>
+	      (m_cgraph_edge_to_call_superedge).get (edge));
+  }
+
+  return_superedge *get_edge_for_return (cgraph_edge *edge) const
+  {
+    return (*const_cast <cgraph_edge_to_return_superedge_t &>
+	      (m_cgraph_edge_to_return_superedge).get (edge));
+  }
+
+  superedge *get_intraprocedural_edge_for_call (cgraph_edge *edge) const
+  {
+    return (*const_cast <cgraph_edge_to_intraproc_superedge_t &>
+	      (m_cgraph_edge_to_intraproc_superedge).get (edge));
+  }
+
+  cfg_superedge *get_edge_for_cfg_edge (edge e) const
+  {
+    return (*const_cast <cfg_edge_to_cfg_superedge_t &>
+	      (m_cfg_edge_to_cfg_superedge).get (e));
+  }
+
+  supernode *get_supernode_for_stmt (const gimple *stmt) const
+  {
+    return (*const_cast <stmt_to_node_t &>(m_stmt_to_node_t).get
+	    (const_cast <gimple *> (stmt)));
+  }
+
+  void dump_dot_to_pp (pretty_printer *pp, const dump_args_t &) const;
+  void dump_dot_to_file (FILE *fp, const dump_args_t &) const;
+  void dump_dot (const char *path, const dump_args_t &) const;
+
+  int num_nodes () const { return m_nodes.length (); }
+  int num_edges () const { return m_edges.length (); }
+
+  supernode *get_node_by_index (int idx) const
+  {
+    return m_nodes[idx];
+  }
+
+  unsigned get_num_snodes (function *fun) const
+  {
+    function_to_num_snodes_t &map
+      = const_cast <function_to_num_snodes_t &>(m_function_to_num_snodes);
+    return *map.get (fun);
+  }
+
+private:
+  supernode *add_node (function *fun, basic_block bb, gcall *returning_call,
+		       gimple_seq phi_nodes);
+  cfg_superedge *add_cfg_edge (supernode *src, supernode *dest, ::edge e, int idx);
+  call_superedge *add_call_superedge (supernode *src, supernode *dest,
+				      cgraph_edge *cedge);
+  return_superedge *add_return_superedge (supernode *src, supernode *dest,
+					  cgraph_edge *cedge);
+
+  /* Data.  */
+
+  typedef ordered_hash_map<basic_block, supernode *> bb_to_node_t;
+  bb_to_node_t m_bb_to_initial_node;
+  bb_to_node_t m_bb_to_final_node;
+
+  typedef ordered_hash_map<cgraph_edge *, supernode *> cgraph_edge_to_node_t;
+  cgraph_edge_to_node_t m_cgraph_edge_to_caller_prev_node;
+  cgraph_edge_to_node_t m_cgraph_edge_to_caller_next_node;
+
+  typedef ordered_hash_map< ::edge, cfg_superedge *>
+    cfg_edge_to_cfg_superedge_t;
+  cfg_edge_to_cfg_superedge_t m_cfg_edge_to_cfg_superedge;
+
+  typedef ordered_hash_map<cgraph_edge *, call_superedge *>
+    cgraph_edge_to_call_superedge_t;
+  cgraph_edge_to_call_superedge_t m_cgraph_edge_to_call_superedge;
+
+  typedef ordered_hash_map<cgraph_edge *, return_superedge *>
+    cgraph_edge_to_return_superedge_t;
+  cgraph_edge_to_return_superedge_t m_cgraph_edge_to_return_superedge;
+
+  typedef ordered_hash_map<cgraph_edge *, superedge *>
+    cgraph_edge_to_intraproc_superedge_t;
+  cgraph_edge_to_intraproc_superedge_t m_cgraph_edge_to_intraproc_superedge;
+
+  typedef ordered_hash_map<gimple *, supernode *> stmt_to_node_t;
+  stmt_to_node_t m_stmt_to_node_t;
+
+  typedef hash_map<function *, unsigned> function_to_num_snodes_t;
+  function_to_num_snodes_t m_function_to_num_snodes;
+};
+
+/* A node within a supergraph.  */
+
+class supernode : public dnode<supergraph_traits>
+{
+ public:
+  supernode (function *fun, basic_block bb, gcall *returning_call,
+	     gimple_seq phi_nodes, int index)
+  : m_fun (fun), m_bb (bb), m_returning_call (returning_call),
+    m_phi_nodes (phi_nodes), m_index (index)
+  {}
+
+  bool entry_p () const
+  {
+    return m_bb == ENTRY_BLOCK_PTR_FOR_FN (m_fun);
+  }
+
+  bool return_p () const
+  {
+    return m_bb == EXIT_BLOCK_PTR_FOR_FN (m_fun);
+  }
+
+  void dump_dot (graphviz_out *gv, const dump_args_t &args) const OVERRIDE;
+  void dump_dot_id (pretty_printer *pp) const;
+
+  location_t get_start_location () const;
+  location_t get_end_location () const;
+
+  /* Returns iterator at the start of the list of phi nodes, if any.  */
+  gphi_iterator start_phis ()
+  {
+    gimple_seq *pseq = &m_phi_nodes;
+
+    /* Adapted from gsi_start_1. */
+    gphi_iterator i;
+
+    i.ptr = gimple_seq_first (*pseq);
+    i.seq = pseq;
+    i.bb = i.ptr ? gimple_bb (i.ptr) : NULL;
+
+    return i;
+  }
+
+  gimple *get_last_stmt () const
+  {
+    if (m_stmts.length () == 0)
+      return NULL;
+    return m_stmts[m_stmts.length () - 1];
+  }
+
+  gcall *get_final_call () const
+  {
+    gimple *stmt = get_last_stmt ();
+    if (stmt == NULL)
+      return NULL;
+    return dyn_cast<gcall *> (stmt);
+  }
+
+  unsigned int get_stmt_index (const gimple *stmt) const;
+
+  function * const m_fun; // alternatively could be stored as runs of indices within the supergraph
+  const basic_block m_bb;
+  gcall * const m_returning_call; // for handling the result of a returned call
+  gimple_seq m_phi_nodes; // ptr to that of the underlying BB, for the first supernode for the BB
+  auto_vec<gimple *> m_stmts;
+  const int m_index; /* unique within the supergraph as a whole.  */
+};
+
+/* An abstract base class encapsulating an edge within a supergraph.
+   Edges can be CFG edges, or calls/returns for callgraph edges.  */
+
+class superedge : public dedge<supergraph_traits>
+{
+ public:
+  virtual ~superedge () {}
+
+  void dump_dot (graphviz_out *gv, const dump_args_t &args) const;
+
+  virtual void dump_label_to_pp (pretty_printer *pp,
+				 bool user_facing) const = 0;
+
+  enum edge_kind get_kind () const { return m_kind; }
+
+  virtual cfg_superedge *dyn_cast_cfg_superedge () { return NULL; }
+  virtual const cfg_superedge *dyn_cast_cfg_superedge () const { return NULL; }
+  virtual const switch_cfg_superedge *dyn_cast_switch_cfg_superedge () const { return NULL; }
+  virtual callgraph_superedge *dyn_cast_callgraph_superedge () { return NULL; }
+  virtual const callgraph_superedge *dyn_cast_callgraph_superedge () const { return NULL; }
+  virtual call_superedge *dyn_cast_call_superedge () { return NULL; }
+  virtual const call_superedge *dyn_cast_call_superedge () const { return NULL; }
+  virtual return_superedge *dyn_cast_return_superedge () { return NULL; }
+  virtual const return_superedge *dyn_cast_return_superedge () const { return NULL; }
+
+  ::edge get_any_cfg_edge () const;
+  cgraph_edge *get_any_callgraph_edge () const;
+
+  char *get_description (bool user_facing) const;
+
+ protected:
+  superedge (supernode *src, supernode *dest, enum edge_kind kind)
+  : dedge (src, dest),
+    m_kind (kind)
+  {}
+
+ public:
+  const enum edge_kind m_kind;
+};
+
+/* An ID representing an expression at a callsite:
+   either a parameter index, or the return value (or unknown).  */
+
+class callsite_expr
+{
+ public:
+  callsite_expr () : m_val (-1) {}
+
+  static callsite_expr from_zero_based_param (int idx)
+  {
+    return callsite_expr (idx + 1);
+  }
+
+  static callsite_expr from_return_value ()
+  {
+    return callsite_expr (0);
+  }
+
+  bool param_p () const
+  {
+    return m_val > 0;
+  }
+
+  bool return_value_p () const
+  {
+    return m_val == 0;
+  }
+
+ private:
+  callsite_expr (int val) : m_val (val) {}
+
+  int m_val; /* 1-based parm, 0 for return value, or -1 for "unknown".  */
+};
+
+/* A subclass of superedge with an associated callgraph edge (either a
+   call or a return).  */
+
+class callgraph_superedge : public superedge
+{
+ public:
+  callgraph_superedge (supernode *src, supernode *dst, enum edge_kind kind,
+		       cgraph_edge *cedge)
+  : superedge (src, dst, kind),
+    m_cedge (cedge)
+  {}
+
+  void dump_label_to_pp (pretty_printer *pp, bool user_facing) const
+    FINAL OVERRIDE;
+
+  function *get_callee_function () const;
+  function *get_caller_function () const;
+  tree get_callee_decl () const;
+  tree get_caller_decl () const;
+  gcall *get_call_stmt () const { return m_cedge->call_stmt; }
+  tree get_arg_for_parm (tree parm, callsite_expr *out) const;
+  tree get_parm_for_arg (tree arg, callsite_expr *out) const;
+  tree map_expr_from_caller_to_callee (tree caller_expr,
+				       callsite_expr *out) const;
+  tree map_expr_from_callee_to_caller (tree callee_expr,
+				       callsite_expr *out) const;
+
+  cgraph_edge *const m_cedge;
+};
+
+template <>
+template <>
+inline bool
+is_a_helper <const callgraph_superedge *>::test (const superedge *sedge)
+{
+  return (sedge->get_kind () == SUPEREDGE_INTRAPROCEDURAL_CALL
+	  || sedge->get_kind () == SUPEREDGE_CALL
+	  || sedge->get_kind () == SUPEREDGE_RETURN);
+}
+
+/* A subclass of superedge representing an interprocedural call.  */
+
+class call_superedge : public callgraph_superedge
+{
+ public:
+  call_superedge (supernode *src, supernode *dst, cgraph_edge *cedge)
+  : callgraph_superedge (src, dst, SUPEREDGE_CALL, cedge)
+  {}
+
+  callgraph_superedge *dyn_cast_callgraph_superedge () FINAL OVERRIDE
+  {
+    return this;
+  }
+  const callgraph_superedge *dyn_cast_callgraph_superedge () const
+    FINAL OVERRIDE
+  {
+    return this;
+  }
+
+  call_superedge *dyn_cast_call_superedge () FINAL OVERRIDE
+  {
+    return this;
+  }
+  const call_superedge *dyn_cast_call_superedge () const FINAL OVERRIDE
+  {
+    return this;
+  }
+
+  return_superedge *get_edge_for_return (const supergraph &sg) const
+  {
+    return sg.get_edge_for_return (m_cedge);
+  }
+};
+
+template <>
+template <>
+inline bool
+is_a_helper <const call_superedge *>::test (const superedge *sedge)
+{
+  return sedge->get_kind () == SUPEREDGE_CALL;
+}
+
+/* A subclass of superedge represesnting an interprocedural return.  */
+
+class return_superedge : public callgraph_superedge
+{
+ public:
+  return_superedge (supernode *src, supernode *dst, cgraph_edge *cedge)
+  : callgraph_superedge (src, dst, SUPEREDGE_RETURN, cedge)
+  {}
+
+  callgraph_superedge *dyn_cast_callgraph_superedge () FINAL OVERRIDE
+  {
+    return this;
+  }
+  const callgraph_superedge *dyn_cast_callgraph_superedge () const
+    FINAL OVERRIDE
+  { return this; }
+
+  return_superedge *dyn_cast_return_superedge () FINAL OVERRIDE { return this; }
+  const return_superedge *dyn_cast_return_superedge () const FINAL OVERRIDE
+  {
+    return this;
+  }
+
+  call_superedge *get_edge_for_call (const supergraph &sg) const
+  {
+    return sg.get_edge_for_call (m_cedge);
+  }
+};
+
+template <>
+template <>
+inline bool
+is_a_helper <const return_superedge *>::test (const superedge *sedge)
+{
+  return sedge->get_kind () == SUPEREDGE_RETURN;
+}
+
+/* A subclass of superedge that corresponds to a CFG edge.  */
+
+class cfg_superedge : public superedge
+{
+ public:
+  cfg_superedge (supernode *src, supernode *dst, ::edge e)
+  : superedge (src, dst, SUPEREDGE_CFG_EDGE),
+    m_cfg_edge (e)
+  {}
+
+  void dump_label_to_pp (pretty_printer *pp, bool user_facing) const OVERRIDE;
+  cfg_superedge *dyn_cast_cfg_superedge () FINAL OVERRIDE { return this; }
+  const cfg_superedge *dyn_cast_cfg_superedge () const FINAL OVERRIDE { return this; }
+
+  ::edge get_cfg_edge () const { return m_cfg_edge; }
+  int get_flags () const { return m_cfg_edge->flags; }
+  int true_value_p () const { return get_flags () & EDGE_TRUE_VALUE; }
+  int false_value_p () const { return get_flags () & EDGE_FALSE_VALUE; }
+  int back_edge_p () const { return get_flags () & EDGE_DFS_BACK; }
+
+  tree get_phi_arg (const gphi *phi) const;
+
+ private:
+  const ::edge m_cfg_edge;
+};
+
+template <>
+template <>
+inline bool
+is_a_helper <const cfg_superedge *>::test (const superedge *sedge)
+{
+  return sedge->get_kind () == SUPEREDGE_CFG_EDGE;
+}
+
+/* A subclass for edges from switch statements, retaining enough
+   information to identify the pertinent case, and for adding labels
+   when rendering via graphviz.  */
+
+class switch_cfg_superedge : public cfg_superedge {
+ public:
+  switch_cfg_superedge (supernode *src, supernode *dst, ::edge e, int idx)
+  : cfg_superedge (src, dst, e),
+    m_idx (idx)
+  {}
+
+  const switch_cfg_superedge *dyn_cast_switch_cfg_superedge () const
+    FINAL OVERRIDE
+  {
+    return this;
+  }
+
+  void dump_label_to_pp (pretty_printer *pp, bool user_facing) const
+    FINAL OVERRIDE;
+
+  gswitch *get_switch_stmt () const
+  {
+    return as_a <gswitch *> (m_src->get_last_stmt ());
+  }
+
+  tree get_case_label () const;
+
+ private:
+  const int m_idx;
+};
+
+template <>
+template <>
+inline bool
+is_a_helper <const switch_cfg_superedge *>::test (const superedge *sedge)
+{
+  return sedge->dyn_cast_switch_cfg_superedge () != NULL;
+}
+
+/* Base class for adding additional content to the .dot output
+   for a supergraph.  */
+
+class dot_annotator
+{
+ public:
+  virtual ~dot_annotator () {}
+  virtual void add_node_annotations (pretty_printer *pp ATTRIBUTE_UNUSED,
+				     const supernode &n ATTRIBUTE_UNUSED)
+    const {}
+  virtual void add_stmt_annotations (pretty_printer *pp ATTRIBUTE_UNUSED,
+				     const gimple *stmt ATTRIBUTE_UNUSED)
+    const {}
+};
+
+extern cgraph_edge *supergraph_call_edge (function *fun, gimple *stmt);
+
+#endif /* GCC_ANALYZER_SUPERGRAPH_H */
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 26/49] analyzer: new files: digraph.{cc|h} and shortest-paths.h
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (9 preceding siblings ...)
  2019-11-16  1:18 ` [PATCH 23/49] analyzer: logging support David Malcolm
@ 2019-11-16  1:18 ` David Malcolm
  2019-12-07 14:58   ` Jeff Law
  2019-12-10  0:45   ` Martin Sebor
  2019-11-16  1:18 ` [PATCH 03/49] diagnostic_show_locus: move initial newline to callers David Malcolm
                   ` (39 subsequent siblings)
  50 siblings, 2 replies; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:18 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This patch adds template classes for directed graphs, their nodes
and edges, and for finding the shortest path through such a graph.

gcc/ChangeLog:
	* analyzer/digraph.cc: New file.
	* analyzer/digraph.h: New file.
	* analyzer/shortest-paths.h: New file.
---
 gcc/analyzer/digraph.cc       | 189 ++++++++++++++++++++++++++++++++
 gcc/analyzer/digraph.h        | 248 ++++++++++++++++++++++++++++++++++++++++++
 gcc/analyzer/shortest-paths.h | 147 +++++++++++++++++++++++++
 3 files changed, 584 insertions(+)
 create mode 100644 gcc/analyzer/digraph.cc
 create mode 100644 gcc/analyzer/digraph.h
 create mode 100644 gcc/analyzer/shortest-paths.h

diff --git a/gcc/analyzer/digraph.cc b/gcc/analyzer/digraph.cc
new file mode 100644
index 0000000..c1fa46e
--- /dev/null
+++ b/gcc/analyzer/digraph.cc
@@ -0,0 +1,189 @@
+/* Template classes for directed graphs.
+   Copyright (C) 2019 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 "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "diagnostic.h"
+#include "analyzer/graphviz.h"
+#include "analyzer/digraph.h"
+#include "analyzer/shortest-paths.h"
+#include "selftest.h"
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* A family of digraph classes for writing selftests.  */
+
+struct test_node;
+struct test_edge;
+struct test_graph;
+struct test_dump_args_t {};
+struct test_cluster;
+
+struct test_graph_traits
+{
+  typedef test_node node_t;
+  typedef test_edge edge_t;
+  typedef test_graph graph_t;
+  typedef test_dump_args_t dump_args_t;
+  typedef test_cluster cluster_t;
+};
+
+struct test_node : public dnode<test_graph_traits>
+{
+  test_node (const char *name, int index) : m_name (name), m_index (index) {}
+  void dump_dot (graphviz_out *, const dump_args_t &) const OVERRIDE
+  {
+  }
+
+  const char *m_name;
+  int m_index;
+};
+
+struct test_edge : public dedge<test_graph_traits>
+{
+  test_edge (node_t *src, node_t *dest)
+  : dedge (src, dest)
+  {}
+
+  void dump_dot (graphviz_out *gv, const dump_args_t &) const OVERRIDE
+  {
+    gv->println ("%s -> %s;", m_src->m_name, m_dest->m_name);
+  }
+};
+
+struct test_graph : public digraph<test_graph_traits>
+{
+  test_node *add_test_node (const char *name)
+  {
+    test_node *result = new test_node (name, m_nodes.length ());
+    add_node (result);
+    return result;
+  }
+
+  test_edge *add_test_edge (test_node *src, test_node *dst)
+  {
+    test_edge *result = new test_edge (src, dst);
+    add_edge (result);
+    return result;
+  }
+};
+
+struct test_cluster : public cluster<test_graph_traits>
+{
+};
+
+struct test_path
+{
+  auto_vec<const test_edge *> m_edges;
+};
+
+/* Smoketest of digraph dumping.  */
+
+static void
+test_dump_to_dot ()
+{
+  test_graph g;
+  test_node *a = g.add_test_node ("a");
+  test_node *b = g.add_test_node ("b");
+  g.add_test_edge (a, b);
+
+  pretty_printer pp;
+  pp.buffer->stream = NULL;
+  test_dump_args_t dump_args;
+  g.dump_dot_to_pp (&pp, NULL, dump_args);
+
+  ASSERT_STR_CONTAINS (pp_formatted_text (&pp),
+		       "a -> b;\n");
+}
+
+/* Test shortest paths from A in this digraph,
+   where edges run top-to-bottom if not otherwise labeled:
+
+      A
+     / \
+    B   C-->D
+    |   |
+    E   |
+     \ /
+      F.  */
+
+static void
+test_shortest_paths ()
+{
+  test_graph g;
+  test_node *a = g.add_test_node ("a");
+  test_node *b = g.add_test_node ("b");
+  test_node *c = g.add_test_node ("d");
+  test_node *d = g.add_test_node ("d");
+  test_node *e = g.add_test_node ("e");
+  test_node *f = g.add_test_node ("f");
+
+  test_edge *ab = g.add_test_edge (a, b);
+  test_edge *ac = g.add_test_edge (a, c);
+  test_edge *cd = g.add_test_edge (c, d);
+  test_edge *be = g.add_test_edge (b, e);
+  g.add_test_edge (e, f);
+  test_edge *cf = g.add_test_edge (c, f);
+
+  shortest_paths<test_graph_traits, test_path> sp (g, a);
+
+  test_path path_to_a = sp.get_shortest_path (a);
+  ASSERT_EQ (path_to_a.m_edges.length (), 0);
+
+  test_path path_to_b = sp.get_shortest_path (b);
+  ASSERT_EQ (path_to_b.m_edges.length (), 1);
+  ASSERT_EQ (path_to_b.m_edges[0], ab);
+
+  test_path path_to_c = sp.get_shortest_path (c);
+  ASSERT_EQ (path_to_c.m_edges.length (), 1);
+  ASSERT_EQ (path_to_c.m_edges[0], ac);
+
+  test_path path_to_d = sp.get_shortest_path (d);
+  ASSERT_EQ (path_to_d.m_edges.length (), 2);
+  ASSERT_EQ (path_to_d.m_edges[0], ac);
+  ASSERT_EQ (path_to_d.m_edges[1], cd);
+
+  test_path path_to_e = sp.get_shortest_path (e);
+  ASSERT_EQ (path_to_e.m_edges.length (), 2);
+  ASSERT_EQ (path_to_e.m_edges[0], ab);
+  ASSERT_EQ (path_to_e.m_edges[1], be);
+
+  test_path path_to_f = sp.get_shortest_path (f);
+  ASSERT_EQ (path_to_f.m_edges.length (), 2);
+  ASSERT_EQ (path_to_f.m_edges[0], ac);
+  ASSERT_EQ (path_to_f.m_edges[1], cf);
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+analyzer_digraph_cc_tests ()
+{
+  test_dump_to_dot ();
+  test_shortest_paths ();
+}
+
+} // namespace selftest
+
+#endif /* #if CHECKING_P */
diff --git a/gcc/analyzer/digraph.h b/gcc/analyzer/digraph.h
new file mode 100644
index 0000000..5c6a496
--- /dev/null
+++ b/gcc/analyzer/digraph.h
@@ -0,0 +1,248 @@
+/* Template classes for directed graphs.
+   Copyright (C) 2019 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/>.  */
+
+#ifndef GCC_ANALYZER_DIGRAPH_H
+#define GCC_ANALYZER_DIGRAPH_H
+
+#include "diagnostic.h"
+#include "tree-diagnostic.h" /* for default_tree_printer.  */
+#include "analyzer/graphviz.h"
+
+/* Templates for a family of classes: digraph, node, edge, and cluster.
+   This assumes a traits type with the following typedefs:
+   node_t: the node class
+   edge_t: the edge class
+   dump_args_t: additional args for dot-dumps
+   cluster_t: the cluster class (for use when generating .dot files).
+
+   Using a template allows for typesafe nodes and edges: a node's
+   predecessor and successor edges can be of a node-specific edge
+   subclass, without needing casting.  */
+
+/* Abstract base class for a node in a directed graph.  */
+
+template <typename GraphTraits>
+class dnode
+{
+ public:
+  typedef typename GraphTraits::edge_t edge_t;
+  typedef typename GraphTraits::dump_args_t dump_args_t;
+
+  virtual ~dnode () {}
+  virtual void dump_dot (graphviz_out *gv, const dump_args_t &args) const = 0;
+
+  auto_vec<edge_t *> m_preds;
+  auto_vec<edge_t *> m_succs;
+};
+
+/* Abstract base class for an edge in a directed graph.  */
+
+template <typename GraphTraits>
+class dedge
+{
+ public:
+  typedef typename GraphTraits::node_t node_t;
+  typedef typename GraphTraits::dump_args_t dump_args_t;
+
+  dedge (node_t *src, node_t *dest)
+  : m_src (src), m_dest (dest) {}
+
+  virtual ~dedge () {}
+
+  virtual void dump_dot (graphviz_out *gv, const dump_args_t &args) const = 0;
+
+  node_t *const m_src;
+  node_t *const m_dest;
+};
+
+/* Abstract base class for a directed graph.
+   This class maintains the vectors of nodes and edges,
+   and owns the nodes and edges.  */
+
+template <typename GraphTraits>
+class digraph
+{
+ public:
+  typedef typename GraphTraits::node_t node_t;
+  typedef typename GraphTraits::edge_t edge_t;
+  typedef typename GraphTraits::dump_args_t dump_args_t;
+  typedef typename GraphTraits::cluster_t cluster_t;
+
+  digraph () {}
+  virtual ~digraph () {}
+
+  void dump_dot_to_pp (pretty_printer *pp,
+		       cluster_t *root_cluster,
+		       const dump_args_t &args) const;
+  void dump_dot_to_file (FILE *fp,
+			 cluster_t *root_cluster,
+			 const dump_args_t &args) const;
+  void dump_dot (const char *path,
+		 cluster_t *root_cluster,
+		 const dump_args_t &args) const;
+
+  void add_node (node_t *node);
+  void add_edge (edge_t *edge);
+
+  auto_delete_vec<node_t> m_nodes;
+  auto_delete_vec<edge_t> m_edges;
+};
+
+/* Abstract base class for splitting dnodes into hierarchical clusters
+   in the generated .dot file.
+
+   See "Subgraphs and Clusters" within
+     https://www.graphviz.org/doc/info/lang.html
+   and e.g.
+     https://graphviz.gitlab.io/_pages/Gallery/directed/cluster.html
+
+   If a root_cluster is passed to dump_dot*, then all nodes will be
+   added to it at the start of dumping, via calls to add_node.
+
+   The root cluster can organize the nodes into a hierarchy of
+   child clusters.
+
+   After all nodes are added to the root cluster, dump_dot will then
+   be called on it (and not on the nodes themselves).  */
+
+template <typename GraphTraits>
+class cluster
+{
+ public:
+  typedef typename GraphTraits::node_t node_t;
+  typedef typename GraphTraits::dump_args_t dump_args_t;
+
+  virtual ~cluster () {}
+
+  virtual void add_node (node_t *node) = 0;
+
+  /* Recursively dump the cluster, all nodes, and child clusters.  */
+  virtual void dump_dot (graphviz_out *gv, const dump_args_t &) const = 0;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Write .dot information for this graph to PP, passing ARGS to the nodes
+   and edges.
+   If ROOT_CLUSTER is non-NULL, use it to organize the nodes into clusters.  */
+
+template <typename GraphTraits>
+inline void
+digraph<GraphTraits>::dump_dot_to_pp (pretty_printer *pp,
+				      cluster_t *root_cluster,
+				      const dump_args_t &args) const
+{
+  graphviz_out gv (pp);
+
+  pp_string (pp, "digraph \"");
+  pp_string (pp, "base");
+  pp_string (pp, "\" {\n");
+
+  gv.indent ();
+
+  pp_string (pp, "overlap=false;\n");
+  pp_string (pp, "compound=true;\n");
+
+  /* If using clustering, emit all nodes via clusters.  */
+  if (root_cluster)
+    {
+      int i;
+      node_t *n;
+      FOR_EACH_VEC_ELT (m_nodes, i, n)
+	root_cluster->add_node (n);
+      root_cluster->dump_dot (&gv, args);
+    }
+  else
+    {
+      /* Otherwise, display all nodes at top level.  */
+      int i;
+      node_t *n;
+      FOR_EACH_VEC_ELT (m_nodes, i, n)
+	n->dump_dot (&gv, args);
+    }
+
+  /* Edges.  */
+  int i;
+  edge_t *e;
+  FOR_EACH_VEC_ELT (m_edges, i, e)
+    e->dump_dot (&gv, args);
+
+  /* Terminate "digraph" */
+  gv.outdent ();
+  pp_string (pp, "}");
+  pp_newline (pp);
+}
+
+/* Write .dot information for this graph to FP, passing ARGS to the nodes
+   and edges.
+   If ROOT_CLUSTER is non-NULL, use it to organize the nodes into clusters.  */
+
+template <typename GraphTraits>
+inline void
+digraph<GraphTraits>::dump_dot_to_file (FILE *fp,
+					cluster_t *root_cluster,
+					const dump_args_t &args) const
+{
+  pretty_printer pp;
+  // TODO:
+  pp_format_decoder (&pp) = default_tree_printer;
+  pp.buffer->stream = fp;
+  dump_dot_to_pp (&pp, root_cluster, args);
+  pp_flush (&pp);
+}
+
+/* Write .dot information for this graph to a file at PATH, passing ARGS
+   to the nodes and edges.
+   If ROOT_CLUSTER is non-NULL, use it to organize the nodes into clusters.  */
+
+template <typename GraphTraits>
+inline void
+digraph<GraphTraits>::dump_dot (const char *path,
+				cluster_t *root_cluster,
+				const dump_args_t &args) const
+{
+  FILE *fp = fopen (path, "w");
+  dump_dot_to_file (fp, root_cluster, args);
+  fclose (fp);
+}
+
+/* Add NODE to this DIGRAPH, taking ownership.  */
+
+template <typename GraphTraits>
+inline void
+digraph<GraphTraits>::add_node (node_t *node)
+{
+  m_nodes.safe_push (node);
+}
+
+/* Add EDGE to this digraph, and to the preds/succs of its endpoints.
+   Take ownership of EDGE.  */
+
+template <typename GraphTraits>
+inline void
+digraph<GraphTraits>::add_edge (edge_t *edge)
+{
+  m_edges.safe_push (edge);
+  edge->m_dest->m_preds.safe_push (edge);
+  edge->m_src->m_succs.safe_push (edge);
+
+}
+
+#endif /* GCC_ANALYZER_DIGRAPH_H */
diff --git a/gcc/analyzer/shortest-paths.h b/gcc/analyzer/shortest-paths.h
new file mode 100644
index 0000000..4738f18
--- /dev/null
+++ b/gcc/analyzer/shortest-paths.h
@@ -0,0 +1,147 @@
+/* Template class for Dijkstra's algorithm on directed graphs.
+   Copyright (C) 2019 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/>.  */
+
+#ifndef GCC_ANALYZER_SHORTEST_PATHS_H
+#define GCC_ANALYZER_SHORTEST_PATHS_H
+
+#include "timevar.h"
+
+/* A record of the shortest path to each node in an graph
+   from the origin node.
+   The constructor runs Dijkstra's algorithm, and the results are
+   stored in this class.  */
+
+template <typename GraphTraits, typename Path_t>
+class shortest_paths
+{
+public:
+  typedef typename GraphTraits::graph_t graph_t;
+  typedef typename GraphTraits::node_t node_t;
+  typedef typename GraphTraits::edge_t edge_t;
+  typedef Path_t path_t;
+
+  shortest_paths (const graph_t &graph, const node_t *origin);
+
+  path_t get_shortest_path (const node_t *to) const;
+
+private:
+  const graph_t &m_graph;
+
+  /* For each node (by index), the minimal distance to that node from the
+     origin.  */
+  auto_vec<int> m_dist;
+
+  /* For each exploded_node (by index), the previous edge in the shortest
+     path from the origin.  */
+  auto_vec<const edge_t *> m_prev;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* shortest_paths's constructor.
+
+   Use Dijkstra's algorithm relative to ORIGIN to populate m_dist and
+   m_prev with enough information to be able to generate Path_t instances
+   to give the shortest path to any node in GRAPH from ORIGIN.  */
+
+template <typename GraphTraits, typename Path_t>
+inline
+shortest_paths<GraphTraits, Path_t>::shortest_paths (const graph_t &graph,
+						     const node_t *origin)
+: m_graph (graph),
+  m_dist (graph.m_nodes.length ()),
+  m_prev (graph.m_nodes.length ())
+{
+  auto_client_timevar tv ("shortest_paths");
+
+  auto_vec<int> queue (graph.m_nodes.length ());
+
+  for (unsigned i = 0; i < graph.m_nodes.length (); i++)
+    {
+      m_dist.quick_push (INT_MAX);
+      m_prev.quick_push (NULL);
+      queue.quick_push (i);
+    }
+  m_dist[origin->m_index] = 0;
+
+  while (queue.length () > 0)
+    {
+      /* Get minimal distance in queue.
+	 FIXME: this is O(N^2); replace with a priority queue.  */
+      int idx_with_min_dist = -1;
+      int idx_in_queue_with_min_dist = -1;
+      int min_dist = INT_MAX;
+      for (unsigned i = 0; i < queue.length (); i++)
+	{
+	  int idx = queue[i];
+	  if (m_dist[queue[i]] < min_dist)
+	    {
+	      min_dist = m_dist[idx];
+	      idx_with_min_dist = idx;
+	      idx_in_queue_with_min_dist = i;
+	    }
+	}
+      gcc_assert (idx_with_min_dist != -1);
+      gcc_assert (idx_in_queue_with_min_dist != -1);
+
+      // FIXME: this is confusing: there are two indices here
+
+      queue.unordered_remove (idx_in_queue_with_min_dist);
+
+      node_t *n
+	= static_cast <node_t *> (m_graph.m_nodes[idx_with_min_dist]);
+
+      int i;
+      edge_t *succ;
+      FOR_EACH_VEC_ELT (n->m_succs, i, succ)
+	{
+	  // TODO: only for dest still in queue
+	  node_t *dest = succ->m_dest;
+	  int alt = m_dist[n->m_index] + 1;
+	  if (alt < m_dist[dest->m_index])
+	    {
+	      m_dist[dest->m_index] = alt;
+	      m_prev[dest->m_index] = succ;
+	    }
+	}
+   }
+}
+
+/* Generate an Path_t instance giving the shortest path to the node
+   TO from the origin node.  */
+
+template <typename GraphTraits, typename Path_t>
+inline Path_t
+shortest_paths<GraphTraits, Path_t>::get_shortest_path (const node_t *to) const
+{
+  Path_t result;
+
+  while (m_prev[to->m_index])
+    {
+      result.m_edges.safe_push (m_prev[to->m_index]);
+      to = m_prev[to->m_index]->m_src;
+    }
+
+  result.m_edges.reverse ();
+
+  return result;
+}
+
+#endif /* GCC_ANALYZER_SHORTEST_PATHS_H */
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 05/49] vec.h: add auto_delete_vec
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (24 preceding siblings ...)
  2019-11-16  1:18 ` [PATCH 33/49] analyzer: new files: sm.{cc|h} David Malcolm
@ 2019-11-16  1:18 ` David Malcolm
  2019-12-04 16:29   ` Martin Sebor
  2019-11-16  1:18 ` [PATCH 36/49] analyzer: new file: sm-pattern-test.cc David Malcolm
                   ` (24 subsequent siblings)
  50 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:18 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This patch adds a class auto_delete_vec<T>, a subclass of auto_vec <T *>
that deletes all of its elements on destruction; it's used in many
places later in the kit.

This is a crude way for a vec to "own" the objects it points to
and clean up automatically (essentially a workaround for not being able
to use unique_ptr, due to C++98).

gcc/ChangeLog:
	* vec.c (class selftest::count_dtor): New class.
	(selftest::test_auto_delete_vec): New test.
	(selftest::vec_c_tests): Call it.
	* vec.h (class auto_delete_vec): New class template.
	(auto_delete_vec<T>::~auto_delete_vec): New dtor.
---
 gcc/vec.c | 27 +++++++++++++++++++++++++++
 gcc/vec.h | 35 +++++++++++++++++++++++++++++++++++
 2 files changed, 62 insertions(+)

diff --git a/gcc/vec.c b/gcc/vec.c
index bac5eb7..a9c840d 100644
--- a/gcc/vec.c
+++ b/gcc/vec.c
@@ -516,6 +516,32 @@ test_reverse ()
   }
 }
 
+/* A test class that increments a counter every time its dtor is called.  */
+
+class count_dtor
+{
+ public:
+  count_dtor (int *counter) : m_counter (counter) {}
+  ~count_dtor () { (*m_counter)++; }
+
+ private:
+  int *m_counter;
+};
+
+/* Verify that auto_delete_vec deletes the elements within it.  */
+
+static void
+test_auto_delete_vec ()
+{
+  int dtor_count = 0;
+  {
+    auto_delete_vec <count_dtor> v;
+    v.safe_push (new count_dtor (&dtor_count));
+    v.safe_push (new count_dtor (&dtor_count));
+  }
+  ASSERT_EQ (dtor_count, 2);
+}
+
 /* Run all of the selftests within this file.  */
 
 void
@@ -533,6 +559,7 @@ vec_c_tests ()
   test_block_remove ();
   test_qsort ();
   test_reverse ();
+  test_auto_delete_vec ();
 }
 
 } // namespace selftest
diff --git a/gcc/vec.h b/gcc/vec.h
index 091056b..1957739 100644
--- a/gcc/vec.h
+++ b/gcc/vec.h
@@ -1539,6 +1539,28 @@ class auto_string_vec : public auto_vec <char *>
   ~auto_string_vec ();
 };
 
+/* A subclass of auto_vec <T *> that deletes all of its elements on
+   destruction.
+
+   This is a crude way for a vec to "own" the objects it points to
+   and clean up automatically.
+
+   For example, no attempt is made to delete elements when an item
+   within the vec is overwritten.
+
+   We can't rely on gnu::unique_ptr within a container,
+   since we can't rely on move semantics in C++98.  */
+
+template <typename T>
+class auto_delete_vec : public auto_vec <T *>
+{
+ public:
+  auto_delete_vec () {}
+  auto_delete_vec (size_t s) : auto_vec <T *> (s) {}
+
+  ~auto_delete_vec ();
+};
+
 /* Conditionally allocate heap memory for VEC and its internal vector.  */
 
 template<typename T>
@@ -1643,6 +1665,19 @@ auto_string_vec::~auto_string_vec ()
     free (str);
 }
 
+/* auto_delete_vec's dtor, deleting all contained items, automatically
+   chaining up to ~auto_vec <T*>, which frees the internal buffer.  */
+
+template <typename T>
+inline
+auto_delete_vec<T>::~auto_delete_vec ()
+{
+  int i;
+  T *item;
+  FOR_EACH_VEC_ELT (*this, i, item)
+    delete item;
+}
+
 
 /* Return a copy of this vector.  */
 
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 03/49] diagnostic_show_locus: move initial newline to callers
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (10 preceding siblings ...)
  2019-11-16  1:18 ` [PATCH 26/49] analyzer: new files: digraph.{cc|h} and shortest-paths.h David Malcolm
@ 2019-11-16  1:18 ` David Malcolm
  2019-12-07 14:30   ` Jeff Law
  2019-11-16  1:18 ` [PATCH 12/49] Add diagnostic paths David Malcolm
                   ` (38 subsequent siblings)
  50 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:18 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

diagnostic_show_locus adds a newline before doing anything (including
the do-nothing-else case).

This patch removes this initial newline, adding it to all callers
of diagnostic_show_locus instead.

Doing so makes diagnostic_show_locus more flexible, allowing it to be
used later in this patch kit when printing diagnostic paths.

gcc/c-family/ChangeLog:
	* c-format.c (test_type_mismatch_range_labels): Remove initial
	newline from expected outputs.
	* c-opts.c (c_diagnostic_finalizer): Add pp_newline call before
	call to diagnostic_show_locus.

gcc/ChangeLog:
	* diagnostic-show-locus.c (diagnostic_show_locus): Remove initial
	newline.
	(selftest::test_diagnostic_show_locus_unknown_location): Remove
	initial newline from expected output.
	(selftest::test_one_liner_simple_caret): Likewise.
	(selftest::test_one_liner_caret_and_range): Likewise.
	(selftest::test_one_liner_multiple_carets_and_ranges): Likewise.
	(selftest::test_one_liner_fixit_insert_before): Likewise.
	(selftest::test_one_liner_fixit_insert_after): Likewise.
	(selftest::test_one_liner_fixit_remove): Likewise.
	(selftest::test_one_liner_fixit_replace): Likewise.
	(selftest::test_one_liner_fixit_replace_non_equal_range):
	Likewise.
	(selftest::test_one_liner_fixit_replace_equal_secondary_range):
	Likewise.
	(selftest::test_one_liner_fixit_validation_adhoc_locations):
	Likewise.
	(selftest::test_one_liner_many_fixits_1): Likewise.
	(selftest::test_one_liner_many_fixits_2): Likewise.
	(selftest::test_one_liner_labels): Likewise.
	(selftest::test_add_location_if_nearby): Likewise.
	(selftest::test_diagnostic_show_locus_fixit_lines): Likewise.
	(selftest::test_overlapped_fixit_printing): Likewise.
	(selftest::test_overlapped_fixit_printing_2): Likewise.
	(selftest::test_fixit_insert_containing_newline): Likewise.
	(selftest::test_fixit_insert_containing_newline_2): Likewise.
	(selftest::test_fixit_replace_containing_newline): Likewise.
	(selftest::test_fixit_deletion_affecting_newline): Likewise.
	(selftest::test_line_numbers_multiline_range): Likewise.
	* diagnostic.c (default_diagnostic_finalizer): Add pp_newline call
	before call to diagnostic_show_locus.
	(diagnostic_append_note): Likewise.

gcc/fortran/ChangeLog:
	* error.c (gfc_diagnostic_starter): Add pp_newline call
	before call to diagnostic_show_locus.

gcc/testsuite/ChangeLog:
	* gcc.dg/plugin/diagnostic_plugin_test_show_locus.c
	(custom_diagnostic_finalizer): Add pp_newline call before call to
	diagnostic_show_locus.
---
 gcc/c-family/c-format.c                            |   6 +-
 gcc/c-family/c-opts.c                              |   1 +
 gcc/diagnostic-show-locus.c                        | 142 +++++++--------------
 gcc/diagnostic.c                                   |   2 +
 gcc/fortran/error.c                                |   1 +
 .../plugin/diagnostic_plugin_test_show_locus.c     |   1 +
 6 files changed, 54 insertions(+), 99 deletions(-)

diff --git a/gcc/c-family/c-format.c b/gcc/c-family/c-format.c
index 3c291ca..7ddf064 100644
--- a/gcc/c-family/c-format.c
+++ b/gcc/c-family/c-format.c
@@ -5390,16 +5390,14 @@ test_type_mismatch_range_labels ()
   diagnostic_show_locus (&dc, &richloc, DK_ERROR);
   if (c_dialect_cxx ())
     /* "char*", without a space.  */
-    ASSERT_STREQ ("\n"
-		  "   printf (\"msg: %i\\n\", msg);\n"
+    ASSERT_STREQ ("   printf (\"msg: %i\\n\", msg);\n"
 		  "                 ~^     ~~~\n"
 		  "                  |     |\n"
 		  "                  char* int\n",
 		  pp_formatted_text (dc.printer));
   else
     /* "char *", with a space.  */
-    ASSERT_STREQ ("\n"
-		  "   printf (\"msg: %i\\n\", msg);\n"
+    ASSERT_STREQ ("   printf (\"msg: %i\\n\", msg);\n"
 		  "                 ~^     ~~~\n"
 		  "                  |     |\n"
 		  "                  |     int\n"
diff --git a/gcc/c-family/c-opts.c b/gcc/c-family/c-opts.c
index 0fffe60..d752328 100644
--- a/gcc/c-family/c-opts.c
+++ b/gcc/c-family/c-opts.c
@@ -170,6 +170,7 @@ c_diagnostic_finalizer (diagnostic_context *context,
 {
   char *saved_prefix = pp_take_prefix (context->printer);
   pp_set_prefix (context->printer, NULL);
+  pp_newline (context->printer);
   diagnostic_show_locus (context, diagnostic->richloc, diagnostic->kind);
   /* By default print macro expansion contexts in the diagnostic
      finalizer -- for tokens resulting from macro expansion.  */
diff --git a/gcc/diagnostic-show-locus.c b/gcc/diagnostic-show-locus.c
index cb920f6..412b0d4 100644
--- a/gcc/diagnostic-show-locus.c
+++ b/gcc/diagnostic-show-locus.c
@@ -2283,8 +2283,6 @@ diagnostic_show_locus (diagnostic_context * context,
 		       rich_location *richloc,
 		       diagnostic_t diagnostic_kind)
 {
-  pp_newline (context->printer);
-
   location_t loc = richloc->get_loc ();
   /* Do nothing if source-printing has been disabled.  */
   if (!context->show_caret)
@@ -2347,7 +2345,7 @@ test_diagnostic_show_locus_unknown_location ()
   test_diagnostic_context dc;
   rich_location richloc (line_table, UNKNOWN_LOCATION);
   diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-  ASSERT_STREQ ("\n", pp_formatted_text (dc.printer));
+  ASSERT_STREQ ("", pp_formatted_text (dc.printer));
 }
 
 /* Verify that diagnostic_show_locus works sanely for various
@@ -2369,8 +2367,7 @@ test_one_liner_simple_caret ()
   location_t caret = linemap_position_for_column (line_table, 10);
   rich_location richloc (line_table, caret);
   diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-  ASSERT_STREQ ("\n"
-		" foo = bar.field;\n"
+  ASSERT_STREQ (" foo = bar.field;\n"
 		"          ^\n",
 		pp_formatted_text (dc.printer));
 }
@@ -2387,8 +2384,7 @@ test_one_liner_caret_and_range ()
   location_t loc = make_location (caret, start, finish);
   rich_location richloc (line_table, loc);
   diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-  ASSERT_STREQ ("\n"
-		" foo = bar.field;\n"
+  ASSERT_STREQ (" foo = bar.field;\n"
 		"       ~~~^~~~~~\n",
 		pp_formatted_text (dc.printer));
 }
@@ -2421,8 +2417,7 @@ test_one_liner_multiple_carets_and_ranges ()
   richloc.add_range (bar, SHOW_RANGE_WITH_CARET);
   richloc.add_range (field, SHOW_RANGE_WITH_CARET);
   diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-  ASSERT_STREQ ("\n"
-		" foo = bar.field;\n"
+  ASSERT_STREQ (" foo = bar.field;\n"
 		" ~A~   ~B~ ~~C~~\n",
 		pp_formatted_text (dc.printer));
 }
@@ -2437,8 +2432,7 @@ test_one_liner_fixit_insert_before ()
   rich_location richloc (line_table, caret);
   richloc.add_fixit_insert_before ("&");
   diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-  ASSERT_STREQ ("\n"
-		" foo = bar.field;\n"
+  ASSERT_STREQ (" foo = bar.field;\n"
 		"       ^\n"
 		"       &\n",
 		pp_formatted_text (dc.printer));
@@ -2456,8 +2450,7 @@ test_one_liner_fixit_insert_after ()
   rich_location richloc (line_table, foo);
   richloc.add_fixit_insert_after ("[0]");
   diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-  ASSERT_STREQ ("\n"
-		" foo = bar.field;\n"
+  ASSERT_STREQ (" foo = bar.field;\n"
 		" ^~~\n"
 		"    [0]\n",
 		pp_formatted_text (dc.printer));
@@ -2480,8 +2473,7 @@ test_one_liner_fixit_remove ()
   {
     test_diagnostic_context dc;
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-		  " foo = bar.field;\n"
+    ASSERT_STREQ (" foo = bar.field;\n"
 		  "          ^~~~~~\n"
 		  "          ------\n",
 		  pp_formatted_text (dc.printer));
@@ -2493,8 +2485,7 @@ test_one_liner_fixit_remove ()
     pp_prefixing_rule (dc.printer) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
     pp_set_prefix (dc.printer, xstrdup ("TEST PREFIX:"));
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-		  "TEST PREFIX: foo = bar.field;\n"
+    ASSERT_STREQ ("TEST PREFIX: foo = bar.field;\n"
 		  "TEST PREFIX:          ^~~~~~\n"
 		  "TEST PREFIX:          ------\n",
 		  pp_formatted_text (dc.printer));
@@ -2506,8 +2497,7 @@ test_one_liner_fixit_remove ()
     dc.show_ruler_p = true;
     dc.caret_max_width = 104;
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-		  "          0         0         0         0         0         0         0         0         0         1    \n"
+    ASSERT_STREQ ("          0         0         0         0         0         0         0         0         0         1    \n"
 		  "          1         2         3         4         5         6         7         8         9         0    \n"
 		  " 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234\n"
 		  " foo = bar.field;\n"
@@ -2524,8 +2514,7 @@ test_one_liner_fixit_remove ()
     pp_prefixing_rule (dc.printer) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
     pp_set_prefix (dc.printer, xstrdup ("TEST PREFIX:"));
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-		  "TEST PREFIX:          1         2         3         4         5\n"
+    ASSERT_STREQ ("TEST PREFIX:          1         2         3         4         5\n"
 		  "TEST PREFIX: 12345678901234567890123456789012345678901234567890\n"
 		  "TEST PREFIX: foo = bar.field;\n"
 		  "TEST PREFIX:          ^~~~~~\n"
@@ -2542,8 +2531,7 @@ test_one_liner_fixit_remove ()
     pp_prefixing_rule (dc.printer) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
     pp_set_prefix (dc.printer, xstrdup ("TEST PREFIX:"));
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-		  "TEST PREFIX:      |          1         2         3         4         5\n"
+    ASSERT_STREQ ("TEST PREFIX:      |          1         2         3         4         5\n"
 		  "TEST PREFIX:      | 12345678901234567890123456789012345678901234567890\n"
 		  "TEST PREFIX:    1 | foo = bar.field;\n"
 		  "TEST PREFIX:      |          ^~~~~~\n"
@@ -2564,8 +2552,7 @@ test_one_liner_fixit_replace ()
   rich_location richloc (line_table, field);
   richloc.add_fixit_replace ("m_field");
   diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-  ASSERT_STREQ ("\n"
-		" foo = bar.field;\n"
+  ASSERT_STREQ (" foo = bar.field;\n"
 		"           ^~~~~\n"
 		"           m_field\n",
 		pp_formatted_text (dc.printer));
@@ -2589,8 +2576,7 @@ test_one_liner_fixit_replace_non_equal_range ()
   diagnostic_show_locus (&dc, &richloc, DK_ERROR);
   /* The replacement range is not indicated in the annotation line, so
      it should be indicated via an additional underline.  */
-  ASSERT_STREQ ("\n"
-		" foo = bar.field;\n"
+  ASSERT_STREQ (" foo = bar.field;\n"
 		"     ^\n"
 		"           -----\n"
 		"           m_field\n",
@@ -2615,8 +2601,7 @@ test_one_liner_fixit_replace_equal_secondary_range ()
   diagnostic_show_locus (&dc, &richloc, DK_ERROR);
   /* The replacement range is indicated in the annotation line,
      so it shouldn't be indicated via an additional underline.  */
-  ASSERT_STREQ ("\n"
-		" foo = bar.field;\n"
+  ASSERT_STREQ (" foo = bar.field;\n"
 		"     ^     ~~~~~\n"
 		"           m_field\n",
 		pp_formatted_text (dc.printer));
@@ -2649,8 +2634,7 @@ test_one_liner_fixit_validation_adhoc_locations ()
 
     test_diagnostic_context dc;
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-		  " foo = bar.field;\n"
+    ASSERT_STREQ (" foo = bar.field;\n"
 		  "       ^~~~~~~~~~                               \n"
 		  "       test\n",
 		  pp_formatted_text (dc.printer));
@@ -2666,8 +2650,7 @@ test_one_liner_fixit_validation_adhoc_locations ()
 
     test_diagnostic_context dc;
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-		  " foo = bar.field;\n"
+    ASSERT_STREQ (" foo = bar.field;\n"
 		  "       ^~~~~~~~~~                               \n"
 		  "       -----------------------------------------\n",
 		  pp_formatted_text (dc.printer));
@@ -2683,8 +2666,7 @@ test_one_liner_fixit_validation_adhoc_locations ()
 
     test_diagnostic_context dc;
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-		  " foo = bar.field;\n"
+    ASSERT_STREQ (" foo = bar.field;\n"
 		  "       ^~~~~~~~~~                               \n"
 		  "       test\n",
 		  pp_formatted_text (dc.printer));
@@ -2703,8 +2685,7 @@ test_one_liner_many_fixits_1 ()
     richloc.add_fixit_insert_before ("a");
   ASSERT_EQ (1, richloc.get_num_fixit_hints ());
   diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-  ASSERT_STREQ ("\n"
-		" foo = bar.field;\n"
+  ASSERT_STREQ (" foo = bar.field;\n"
 		"     ^\n"
 		"     aaaaaaaaaaaaaaaaaaa\n",
 		pp_formatted_text (dc.printer));
@@ -2726,8 +2707,7 @@ test_one_liner_many_fixits_2 ()
     }
   ASSERT_EQ (19, richloc.get_num_fixit_hints ());
   diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-  ASSERT_STREQ ("\n"
-		" foo = bar.field;\n"
+  ASSERT_STREQ (" foo = bar.field;\n"
 		"     ^\n"
 		"a a a a a a a a a a a a a a a a a a a\n",
 		pp_formatted_text (dc.printer));
@@ -2763,8 +2743,7 @@ test_one_liner_labels ()
     {
       test_diagnostic_context dc;
       diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-      ASSERT_STREQ ("\n"
-		    " foo = bar.field;\n"
+      ASSERT_STREQ (" foo = bar.field;\n"
 		    " ^~~   ~~~ ~~~~~\n"
 		    " |     |   |\n"
 		    " 0     1   2\n",
@@ -2776,8 +2755,7 @@ test_one_liner_labels ()
       test_diagnostic_context dc;
       dc.show_labels_p = false;
       diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-      ASSERT_STREQ ("\n"
-		    " foo = bar.field;\n"
+      ASSERT_STREQ (" foo = bar.field;\n"
 		    " ^~~   ~~~ ~~~~~\n",
 		    pp_formatted_text (dc.printer));
     }
@@ -2794,8 +2772,7 @@ test_one_liner_labels ()
 
     test_diagnostic_context dc;
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-		  " foo = bar.field;\n"
+    ASSERT_STREQ (" foo = bar.field;\n"
 		  " ^~~   ~~~ ~~~~~\n"
 		  " |     |   |\n"
 		  " |     |   label 2\n"
@@ -2816,8 +2793,7 @@ test_one_liner_labels ()
 
     test_diagnostic_context dc;
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-		  " foo = bar.field;\n"
+    ASSERT_STREQ (" foo = bar.field;\n"
 		  " ^~~   ~~~ ~~~~~\n"
 		  " |     |   |\n"
 		  " |     |   c\n"
@@ -2836,8 +2812,7 @@ test_one_liner_labels ()
 
     test_diagnostic_context dc;
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-		  " foo = bar.field;\n"
+    ASSERT_STREQ (" foo = bar.field;\n"
 		  " ~~~   ~~~ ^~~~~\n"
 		  " |     |   |\n"
 		  " 2     1   0\n",
@@ -2856,8 +2831,7 @@ test_one_liner_labels ()
 
     test_diagnostic_context dc;
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-		  " foo = bar.field;\n"
+    ASSERT_STREQ (" foo = bar.field;\n"
 		  "       ^~~\n"
 		  "       |\n"
 		  "       label 0\n"
@@ -2892,8 +2866,7 @@ test_one_liner_labels ()
 
     test_diagnostic_context dc;
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-		  " foo = bar.field;\n"
+    ASSERT_STREQ (" foo = bar.field;\n"
 		  " ~~~   ~~~ ^~~~~\n"
 		  " |     |   |\n"
 		  " |     |   label 0a\n"
@@ -2916,8 +2889,7 @@ test_one_liner_labels ()
 
     test_diagnostic_context dc;
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-		  " foo = bar.field;\n"
+    ASSERT_STREQ (" foo = bar.field;\n"
 		  "       ^~~\n",
 		  pp_formatted_text (dc.printer));
    }
@@ -3009,8 +2981,7 @@ test_add_location_if_nearby (const line_table_case &case_)
     ASSERT_EQ (2, richloc.get_num_locations ());
     test_diagnostic_context dc;
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-		  " struct same_line { double x; double y; ;\n"
+    ASSERT_STREQ (" struct same_line { double x; double y; ;\n"
 		  "                  ~                    ^\n",
 		  pp_formatted_text (dc.printer));
   }
@@ -3074,8 +3045,7 @@ test_diagnostic_show_locus_fixit_lines (const line_table_case &case_)
     richloc.add_fixit_insert_before (x, ".");
     richloc.add_fixit_replace (colon, "=");
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-		  " struct point origin = {x: 0.0,\n"
+    ASSERT_STREQ (" struct point origin = {x: 0.0,\n"
 		  "                         ^\n"
 		  "                        .=\n",
 		  pp_formatted_text (dc.printer));
@@ -3095,8 +3065,7 @@ test_diagnostic_show_locus_fixit_lines (const line_table_case &case_)
     richloc.add_fixit_insert_before (y, ".");
     richloc.add_fixit_replace (colon, "=");
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-		  "FILENAME:3:24:\n"
+    ASSERT_STREQ ("FILENAME:3:24:\n"
 		  "                        y\n"
 		  "                        .\n"
 		  "FILENAME:6:25:\n"
@@ -3119,8 +3088,7 @@ test_diagnostic_show_locus_fixit_lines (const line_table_case &case_)
     test_diagnostic_context dc;
     dc.show_line_numbers_p = true;
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-		  "    3 |                        y\n"
+    ASSERT_STREQ ("    3 |                        y\n"
 		  "      |                        .\n"
 		  "......\n"
 		  "    6 |                         : 0.0};\n"
@@ -3330,8 +3298,7 @@ test_overlapped_fixit_printing (const line_table_case &case_)
     richloc.add_fixit_insert_after (")");
 
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-		  "   foo *f = (foo *)ptr->field;\n"
+    ASSERT_STREQ ("   foo *f = (foo *)ptr->field;\n"
 		  "                   ^~~~~~~~~~\n"
 		  "            -----------------\n"
 		  "            const_cast<foo *> (ptr->field)\n",
@@ -3388,8 +3355,7 @@ test_overlapped_fixit_printing (const line_table_case &case_)
     richloc.add_fixit_insert_after (")");
 
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-		  "   foo *f = (foo *)ptr->field;\n"
+    ASSERT_STREQ ("   foo *f = (foo *)ptr->field;\n"
 		  "                   ^~~~~~~~~~\n"
 		  "            -\n"
 		  "            CAST (-\n"
@@ -3406,8 +3372,7 @@ test_overlapped_fixit_printing (const line_table_case &case_)
     richloc.add_fixit_insert_after (")");
 
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-		  "   foo *f = (foo *)ptr->field;\n"
+    ASSERT_STREQ ("   foo *f = (foo *)ptr->field;\n"
 		  "                   ^~~~~~~~~~\n"
 		  "            -\n"
 		  "            CST ( -\n"
@@ -3428,8 +3393,7 @@ test_overlapped_fixit_printing (const line_table_case &case_)
     ASSERT_EQ (1, richloc.get_num_fixit_hints ());
 
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-		  "   foo *f = (foo *)ptr->field;\n"
+    ASSERT_STREQ ("   foo *f = (foo *)ptr->field;\n"
 		  "                   ^~~~~~~~~~\n"
 		  "            -------\n"
 		  "            (bar *)\n",
@@ -3449,8 +3413,7 @@ test_overlapped_fixit_printing (const line_table_case &case_)
 
     /* But the corrections are.  */
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-		  "   foo *f = (foo *)ptr->field;\n"
+    ASSERT_STREQ ("   foo *f = (foo *)ptr->field;\n"
 		  "                   ^~~~~~~~~~\n"
 		  "            -----------------\n"
 		  "            (longer *)(foo *)\n",
@@ -3468,8 +3431,7 @@ test_overlapped_fixit_printing (const line_table_case &case_)
        it would overlap with the second.
        Verify that they are printed as a single replacement.  */
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-		  "   foo *f = (foo *)ptr->field;\n"
+    ASSERT_STREQ ("   foo *f = (foo *)ptr->field;\n"
 		  "                   ^~~~~~~~~~\n"
 		  "            -------\n"
 		  "            LONGER THAN THE CAST(foo *)TEST\n",
@@ -3535,8 +3497,7 @@ test_overlapped_fixit_printing_2 (const line_table_case &case_)
     /* Verify that they're printed correctly.  */
     test_diagnostic_context dc;
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-		  " int a5[][0][0] = { 1, 2 };\n"
+    ASSERT_STREQ (" int a5[][0][0] = { 1, 2 };\n"
 		  "                    ^\n"
 		  "                     } {\n",
 		  pp_formatted_text (dc.printer));
@@ -3558,8 +3519,7 @@ test_overlapped_fixit_printing_2 (const line_table_case &case_)
     richloc.add_fixit_insert_before (col_1, "{");
     richloc.add_fixit_insert_before (col_25, "}");
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-		  " int a5[][0][0] = { 1, 2 };\n"
+    ASSERT_STREQ (" int a5[][0][0] = { 1, 2 };\n"
 		  "                    ^\n"
 		  " {                  -----\n"
 		  "                    {{1}}}}, {{{2 }}\n",
@@ -3602,8 +3562,7 @@ test_fixit_insert_containing_newline (const line_table_case &case_)
     {
       test_diagnostic_context dc;
       diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-      ASSERT_STREQ ("\n"
-		    "       x = a;\n"
+      ASSERT_STREQ ("       x = a;\n"
 		    "+      break;\n"
 		    "     case 'b':\n"
 		    "     ^~~~~~~~~\n",
@@ -3615,8 +3574,7 @@ test_fixit_insert_containing_newline (const line_table_case &case_)
       test_diagnostic_context dc;
       dc.show_line_numbers_p = true;
       diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-      ASSERT_STREQ ("\n"
-		    "    2 |       x = a;\n"
+      ASSERT_STREQ ("    2 |       x = a;\n"
 		    "  +++ |+      break;\n"
 		    "    3 |     case 'b':\n"
 		    "      |     ^~~~~~~~~\n",
@@ -3632,8 +3590,7 @@ test_fixit_insert_containing_newline (const line_table_case &case_)
     ASSERT_TRUE (richloc.seen_impossible_fixit_p ());
     test_diagnostic_context dc;
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-		  "     case 'b':\n"
+    ASSERT_STREQ ("     case 'b':\n"
 		  "     ^~~~~~~~~\n",
 		  pp_formatted_text (dc.printer));
   }
@@ -3681,8 +3638,7 @@ test_fixit_insert_containing_newline_2 (const line_table_case &case_)
   {
     test_diagnostic_context dc;
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-		  "FILENAME:1:1:\n"
+    ASSERT_STREQ ("FILENAME:1:1:\n"
 		  "+#include <stdio.h>\n"
 		  " test (int ch)\n"
 		  "FILENAME:3:2:\n"
@@ -3697,8 +3653,7 @@ test_fixit_insert_containing_newline_2 (const line_table_case &case_)
     test_diagnostic_context dc;
     dc.show_line_numbers_p = true;
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-		  "  +++ |+#include <stdio.h>\n"
+    ASSERT_STREQ ("  +++ |+#include <stdio.h>\n"
 		  "    1 | test (int ch)\n"
 		  "    2 | {\n"
 		  "    3 |  putchar (ch);\n"
@@ -3741,8 +3696,7 @@ test_fixit_replace_containing_newline (const line_table_case &case_)
 
   test_diagnostic_context dc;
   diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-  ASSERT_STREQ ("\n"
-		" foo = bar ();\n"
+  ASSERT_STREQ (" foo = bar ();\n"
 		"             ^\n",
 		pp_formatted_text (dc.printer));
 }
@@ -3786,8 +3740,7 @@ test_fixit_deletion_affecting_newline (const line_table_case &case_)
 
   test_diagnostic_context dc;
   diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-  ASSERT_STREQ ("\n"
-		" foo = bar (\n"
+  ASSERT_STREQ (" foo = bar (\n"
 		"          ~^\n"
 		"       );\n"
 		"       ~    \n",
@@ -3830,8 +3783,7 @@ test_line_numbers_multiline_range ()
   dc.min_margin_width = 0;
   gcc_rich_location richloc (loc);
   diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-  ASSERT_STREQ ("\n"
-		" 9 | this is line 9\n"
+  ASSERT_STREQ (" 9 | this is line 9\n"
 		"   |         ~~~~~~\n"
 		"10 | this is line 10\n"
 		"   | ~~~~~^~~~~~~~~~\n"
diff --git a/gcc/diagnostic.c b/gcc/diagnostic.c
index a29bcf1..d6604e6 100644
--- a/gcc/diagnostic.c
+++ b/gcc/diagnostic.c
@@ -679,6 +679,7 @@ default_diagnostic_finalizer (diagnostic_context *context,
 {
   char *saved_prefix = pp_take_prefix (context->printer);
   pp_set_prefix (context->printer, NULL);
+  pp_newline (context->printer);
   diagnostic_show_locus (context, diagnostic->richloc, diagnostic->kind);
   pp_set_prefix (context->printer, saved_prefix);
   pp_flush (context->printer);
@@ -1173,6 +1174,7 @@ diagnostic_append_note (diagnostic_context *context,
   pp_output_formatted_text (context->printer);
   pp_destroy_prefix (context->printer);
   pp_set_prefix (context->printer, saved_prefix);
+  pp_newline (context->printer);
   diagnostic_show_locus (context, &richloc, DK_NOTE);
   va_end (ap);
 }
diff --git a/gcc/fortran/error.c b/gcc/fortran/error.c
index b20aa86..03de73e 100644
--- a/gcc/fortran/error.c
+++ b/gcc/fortran/error.c
@@ -1147,6 +1147,7 @@ gfc_diagnostic_starter (diagnostic_context *context,
       /* Fortran uses an empty line between locus and caret line.  */
       pp_newline (context->printer);
       pp_set_prefix (context->printer, NULL);
+      pp_newline (context->printer);
       diagnostic_show_locus (context, diagnostic->richloc, diagnostic->kind);
       /* If the caret line was shown, the prefix does not contain the
 	 locus.  */
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_show_locus.c b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_show_locus.c
index 3f62edd..fc52de4 100644
--- a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_show_locus.c
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_show_locus.c
@@ -137,6 +137,7 @@ custom_diagnostic_finalizer (diagnostic_context *context,
     pp_show_color (context->printer) = true;
   char *saved_prefix = pp_take_prefix (context->printer);
   pp_set_prefix (context->printer, NULL);
+  pp_newline (context->printer);
   diagnostic_show_locus (context, diagnostic->richloc, diagnostic->kind);
   pp_show_color (context->printer) = old_show_color;
   pp_set_prefix (context->printer, saved_prefix);
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 16/49] Add support for in-tree plugins
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (26 preceding siblings ...)
  2019-11-16  1:18 ` [PATCH 36/49] analyzer: new file: sm-pattern-test.cc David Malcolm
@ 2019-11-16  1:18 ` David Malcolm
  2019-12-06 22:41   ` Eric Gallager
  2019-12-07 14:39   ` Jeff Law
  2019-11-16  1:20 ` [PATCH 04/49] sbitmap.h: add operator const sbitmap & to auto_sbitmap David Malcolm
                   ` (22 subsequent siblings)
  50 siblings, 2 replies; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:18 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This patch adds support for "in-tree" plugins i.e. GCC plugins that live
in the GCC source tree and are shipped as part of the GCC tarball.

The patch adds Makefile/configure machinery for handling in-tree GCC
plugins, adapted from how we support frontends.

Each in-tree plugin should provide a Make-plugin.in and config-plugin.in,
analogous to the Make-lang.in and config-lang.in provided by a frontend;
they can also supply options via a plugin.opt, analogous to a frontend's
lang.opt.

The patch adds a --enable-plugins=[LIST OF PLUGIN NAMES] configure option,
analogous to --enable-languages.  The default is for no such plugins to be
enabled.

ChangeLog:
	* configure.ac: Handle --enable-plugins.

gcc/ChangeLog:
	* Makefile.in (CONFIG_PLUGINS): New.
	(SUBDIRS, subdirs): Generalize to cover plugins as well as
	languages.
	(plugin_opt_files): New.
	(ALL_OPT_FILES): Add plugin_opt_files.
	(ALL_HOST_PLUGIN_OBJS): New.
	(ALL_HOST_OBJS): Add ALL_HOST_PLUGIN_OBJS.
	(plugin_hooks): New.
	(PLUGIN_MAKEFRAGS): New; include them.
	(Makefile): Add dependency on PLUGIN_MAKEFRAGS.
	(all.cross): Add dependency on plugin.all.cross.
	(start.encap): Add plugin.start.encap.
	(rest.encap): Add plugin.rest.encap.
	(SELFTEST_TARGETS): Add selftest_plugins.
	(info): Add dependency on lang.info.
	(dvi): Add dependency on plugin.dvi.
	(pdf): Add dependency on plugin.pdf.
	(HTMLS_BUILD): Add plugin.html.
	(man): Add plugin.man.
	(mostlyclean): Add plugin.mostlyclean.
	(clean): Add plugin.clean.
	(distclean): Update for renaming of Make-hooks to Make-lang-hooks;
	add Make-plugin-hooks.  Add plugin.distclean dependency.
	(maintainer-clean): Add plugin.maintainer-clean.
	(install-plugin): Add plugin.install-plugin.
	(install-common): Add plugin.install-common.
	(install-info): Add plugin.install-info.
	(install-pdf): Add plugin.install-pdf.
	(install-html): Add plugin.install-html.
	(install-man): Add plugin.install-man.
	(uninstall): Add plugin.uninstall.
	(TAGS): Add plugin.tags.
	* configure.ac (Make-hooks): Rename to Make-lang-hooks.
	(plugin_opt_files): New.
	(plugin_specs_files): New.
	(plugin_tree_files): New.
	(all_plugins): New.
	(all_plugin_makefrags): New.
	(all_selected_plugins): New.
	(plugin_hooks): New.
	("Plugin hooks"): New section.  Iterate through config-plugin.in,
	analogously to config-lang.in.
	(check_plugins): New.
	(selftest_plugins): New.
	(Make-plugin-hooks): Emit.
	* doc/install.texi (--enable-plugins): New option.
---
 configure.ac         |   6 ++
 gcc/Makefile.in      |  98 ++++++++++++++++++++---------
 gcc/configure.ac     | 172 +++++++++++++++++++++++++++++++++++++++++++++++++--
 gcc/doc/install.texi |   9 +++
 4 files changed, 251 insertions(+), 34 deletions(-)

diff --git a/configure.ac b/configure.ac
index b8ce2ad..9b8b8ab 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2188,6 +2188,12 @@ Supported languages are: ${potential_languages}])
   ac_configure_args=`echo " $ac_configure_args" | sed -e "s/ '--enable-languages=[[^ ]]*'//g" -e "s/$/ '--enable-languages="$enable_languages"'/" `
 fi
 
+# Look if the user specified --enable-plugins="..."
+if test -d ${srcdir}/gcc; then
+  enable_plugins=`echo "${enable_plugins}" | sed -e 's/[[ 	,]][[ 	,]]*/,/g' -e 's/,$//'`
+  ac_configure_args=`echo " $ac_configure_args" | sed -e "s/ '--enable-plugins=[[^ ]]*'//g" -e "s/$/ '--enable-plugins="$enable_plugins"'/" `
+fi
+
 # Handle --disable-<component> generically.
 for dir in $configdirs $build_configdirs $target_configdirs ; do
   dirname=`echo $dir | sed -e s/target-//g -e s/build-//g -e s/-/_/g`
diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 5feef6a..c0dfbde 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -132,7 +132,7 @@ CROSS=@CROSS@
 # Variables that exist for you to override.
 # See below for how to change them for certain systems.
 
-# List of language subdirectories.
+# List of language and plugin subdirectories.
 SUBDIRS =@subdirs@ build
 
 # Selection of languages to be made.
@@ -142,6 +142,8 @@ ifeq (@enable_gcov@,yes)
 LANGUAGES += gcov$(exeext) gcov-dump$(exeext) gcov-tool$(exeext)
 endif
 
+CONFIG_PLUGINS = @all_selected_plugins@
+
 # Default values for variables overridden in Makefile fragments.
 # CFLAGS is for the user to override to, e.g., do a cross build with -O2.
 # TCFLAGS is used for compilations with the GCC just built.
@@ -570,6 +572,7 @@ lang_checks_parallelized=
 lang_opt_files=@lang_opt_files@ $(srcdir)/c-family/c.opt $(srcdir)/common.opt
 lang_specs_files=@lang_specs_files@
 lang_tree_files=@lang_tree_files@
+plugin_opt_files=@plugin_opt_files@
 target_cpu_default=@target_cpu_default@
 OBJC_BOEHM_GC=@objc_boehm_gc@
 extra_modes_file=@extra_modes_file@
@@ -1186,7 +1189,7 @@ FLAGS_TO_PASS = \
 # Lists of files for various purposes.
 
 # All option source files
-ALL_OPT_FILES=$(lang_opt_files) $(extra_opt_files)
+ALL_OPT_FILES=$(lang_opt_files) $(plugin_opt_files) $(extra_opt_files)
 
 # Target specific, C specific object file
 C_TARGET_OBJS=@c_target_objs@
@@ -1639,6 +1642,8 @@ OBJS-libcommon-target = $(common_out_object_file) prefix.o params.o \
 # This lists all host objects for the front ends.
 ALL_HOST_FRONTEND_OBJS = $(foreach v,$(CONFIG_LANGUAGES),$($(v)_OBJS))
 
+ALL_HOST_PLUGIN_OBJS = $(foreach v,$(CONFIG_PLUGINS),$($(v)_OBJS))
+
 ALL_HOST_BACKEND_OBJS = $(GCC_OBJS) $(OBJS) $(OBJS-libcommon) \
   $(OBJS-libcommon-target) main.o c-family/cppspec.o \
   $(COLLECT2_OBJS) $(EXTRA_GCC_OBJS) $(GCOV_OBJS) $(GCOV_DUMP_OBJS) \
@@ -1655,7 +1660,10 @@ endif
 
 # This lists all host object files, whether they are included in this
 # compilation or not.
-ALL_HOST_OBJS = $(ALL_HOST_FRONTEND_OBJS) $(ALL_HOST_BACKEND_OBJS)
+ALL_HOST_OBJS = \
+  $(ALL_HOST_FRONTEND_OBJS) \
+  $(ALL_HOST_PLUGIN_OBJS) \
+  $(ALL_HOST_BACKEND_OBJS)
 
 BACKEND = libbackend.a main.o libcommon-target.a libcommon.a \
 	$(CPPLIB) $(LIBDECNUMBER)
@@ -1752,6 +1760,32 @@ s-alltree: Makefile
 $(foreach file,$(ALL_HOST_FRONTEND_OBJS),$(eval CFLAGS-$(file) += -DIN_GCC_FRONTEND))
 
 #\f
+# Plugin makefile fragments.
+
+# The following targets define the interface between us and the
+# in-tree plugins.
+#
+# all.cross, start.encap, rest.encap,
+# install-common, install-info, install-man,
+# uninstall,
+# mostlyclean, clean, distclean, maintainer-clean,
+#
+# Each plugin is linked in with a series of hooks.  The name of each
+# hooked is "plugin.${target_name}" (eg: plugin.info).  Configure computes
+# and adds these here.  We use double-colon rules for some of the hooks;
+# double-colon rules should be preferred for any new hooks.
+
+# plugin hooks, generated by configure
+@plugin_hooks@
+
+# per-plugin makefile fragments
+PLUGIN_MAKEFRAGS = @all_plugin_makefrags@
+
+ifneq ($(PLUGIN_MAKEFRAGS),)
+include $(PLUGIN_MAKEFRAGS)
+endif
+
+#\f
 
 # -----------------------------
 # Rebuilding this configuration
@@ -1793,8 +1827,9 @@ $(foreach file,$(ALL_HOST_FRONTEND_OBJS),$(eval CFLAGS-$(file) += -DIN_GCC_FRONT
 # This is what is done in this makefile. Note that mkconfig.sh has a
 # move-if-change built-in
 
-Makefile: config.status $(srcdir)/Makefile.in $(LANG_MAKEFRAGS)
+Makefile: config.status $(srcdir)/Makefile.in $(LANG_MAKEFRAGS) $(PLUGIN_MAKEFRAGS)
 	LANGUAGES="$(CONFIG_LANGUAGES)" \
+	PLUGINS="$(CONFIG_PLUGINS)" \
 	CONFIG_HEADERS= \
 	CONFIG_SHELL="$(SHELL)" \
 	CONFIG_FILES=$@ $(SHELL) config.status
@@ -1924,12 +1959,13 @@ quickstrap: all
 all.internal: start.encap rest.encap doc selftest
 # This is what to compile if making a cross-compiler.
 all.cross: native gcc-cross$(exeext) cpp$(exeext) specs \
-	libgcc-support lang.all.cross doc selftest @GENINSRC@ srcextra
+	libgcc-support lang.all.cross plugin.all.cross doc \
+	selftest @GENINSRC@ srcextra
 # This is what must be made before installing GCC and converting libraries.
 start.encap: native xgcc$(exeext) cpp$(exeext) specs \
-	libgcc-support lang.start.encap @GENINSRC@ srcextra
+	libgcc-support lang.start.encap plugin.start.encap @GENINSRC@ srcextra
 # These can't be made until after GCC can run.
-rest.encap: lang.rest.encap
+rest.encap: lang.rest.encap plugin.rest.encap
 # This is what is made with the host's compiler
 # whether making a cross compiler or not.
 native: config.status auto-host.h build-@POSUB@ $(LANGUAGES) \
@@ -1967,11 +2003,12 @@ SELFTEST_DEPS = $(GCC_PASSES) stmp-int-hdrs $(srcdir)/testsuite/selftests
 # driver, frontend, or selftest data change.
 .PHONY: selftest
 
-# Potentially run all selftest-<LANG>.  The various <LANG>/Make-lang.in can
-# require the selftests to be run by defining their selftest-<LANG> as
-# s-selftest-<LANG>.  Otherwise, they should define it as empty.
+# Potentially run all selftest-<LANG> and selftest-<PLUGIN>.
+# The various <LANG>/Make-lang.in and <PLUGIN>/Make-plugin.in can
+# require the selftests to be run by defining their selftest-<NAME> as
+# s-selftest-<NAME>.  Otherwise, they should define it as empty.
 
-SELFTEST_TARGETS = @selftest_languages@
+SELFTEST_TARGETS = @selftest_languages@ @selftest_plugins@
 selftest: $(SELFTEST_TARGETS)
 
 # Recompile all the language-independent object files.
@@ -3157,7 +3194,8 @@ doc: $(BUILD_INFO) $(GENERATED_MANPAGES)
 INFOFILES = doc/cpp.info doc/gcc.info doc/gccint.info \
             doc/gccinstall.info doc/cppinternals.info
 
-info: $(INFOFILES) lang.info @GENINSRC@ srcinfo lang.srcinfo
+info: $(INFOFILES) lang.info plugin.info @GENINSRC@ srcinfo lang.srcinfo \
+	plugin.srcinfo
 
 srcinfo: $(INFOFILES)
 	-cp -p $^ $(srcdir)/doc
@@ -3244,7 +3282,7 @@ $(build_htmldir)/gccint/index.html: $(TEXI_GCCINT_FILES)
 $(build_htmldir)/cppinternals/index.html: $(TEXI_CPPINT_FILES)
 
 dvi:: doc/gcc.dvi doc/gccint.dvi doc/gccinstall.dvi doc/cpp.dvi \
-      doc/cppinternals.dvi lang.dvi
+      doc/cppinternals.dvi lang.dvi plugin.dvi
 
 doc/%.dvi: %.texi
 	$(TEXI2DVI) -I . -I $(abs_docdir) -I $(abs_docdir)/include -o $@ $<
@@ -3256,7 +3294,7 @@ doc/gccinstall.dvi: $(TEXI_GCCINSTALL_FILES)
 PDFFILES = doc/gcc.pdf doc/gccint.pdf doc/gccinstall.pdf doc/cpp.pdf \
            doc/cppinternals.pdf
 
-pdf:: $(PDFFILES) lang.pdf
+pdf:: $(PDFFILES) lang.pdf plugin.pdf
 
 doc/%.pdf: %.texi
 	$(TEXI2PDF) -I . -I $(abs_docdir) -I $(abs_docdir)/include -o $@ $<
@@ -3275,7 +3313,7 @@ HTMLS_INSTALL=$(build_htmldir)/cpp $(build_htmldir)/gcc \
 # List the html file targets.
 HTMLS_BUILD=$(build_htmldir)/cpp/index.html $(build_htmldir)/gcc/index.html \
        $(build_htmldir)/gccinstall/index.html $(build_htmldir)/gccint/index.html \
-       $(build_htmldir)/cppinternals/index.html lang.html
+       $(build_htmldir)/cppinternals/index.html lang.html plugin.html
 
 html:: $(HTMLS_BUILD)
 
@@ -3297,7 +3335,8 @@ MANFILES = doc/gcov.1 doc/cpp.1 doc/gcc.1 doc/gfdl.7 doc/gpl.7 \
 
 generated-manpages: man
 
-man: $(MANFILES) lang.man @GENINSRC@ srcman lang.srcman
+man: $(MANFILES) lang.man plugin.man @GENINSRC@ srcman lang.srcman \
+	plugin.srcman
 
 srcman: $(MANFILES)
 	-cp -p $^ $(srcdir)/doc
@@ -3351,7 +3390,7 @@ gpl.pod: gpl_v3.texi
 # We remove as much from the language subdirectories as we can
 # (less duplicated code).
 
-mostlyclean: lang.mostlyclean
+mostlyclean: lang.mostlyclean plugin.mostlyclean
 	-rm -f $(MOSTLYCLEANFILES)
 	-rm -f *$(objext) c-family/*$(objext)
 	-rm -f *$(coverageexts)
@@ -3388,7 +3427,7 @@ mostlyclean: lang.mostlyclean
 
 # Delete all files made by compilation
 # that don't exist in the distribution.
-clean: mostlyclean lang.clean
+clean: mostlyclean lang.clean plugin.clean
 	-rm -f libgcc.a libgcc_eh.a libgcov.a
 	-rm -f libgcc_s*
 	-rm -f libunwind*
@@ -3409,11 +3448,11 @@ clean: mostlyclean lang.clean
 
 # Delete all files that users would normally create
 # while building and installing GCC.
-distclean: clean lang.distclean
+distclean: clean lang.distclean plugin.distclean
 	-rm -f auto-host.h auto-build.h
 	-rm -f cstamp-h
 	-rm -f config.status config.run config.cache config.bak
-	-rm -f Make-lang Make-hooks Make-host Make-target
+	-rm -f Make-lang Make-lang-hooks Make-plugin-hooks Make-host Make-target
 	-rm -f Makefile *.oaux
 	-rm -f gthr-default.h
 	-rm -f TAGS */TAGS
@@ -3435,7 +3474,7 @@ distclean: clean lang.distclean
 maintainer-clean:
 	@echo 'This command is intended for maintainers to use; it'
 	@echo 'deletes files that may need special tools to rebuild.'
-	$(MAKE) lang.maintainer-clean distclean
+	$(MAKE) lang.maintainer-clean plugin.maintainer-clean distclean
 	-rm -f cpp.??s cpp.*aux
 	-rm -f gcc.??s gcc.*aux
 	-rm -f $(gcc_docdir)/*.info $(gcc_docdir)/*.1 $(gcc_docdir)/*.7 $(gcc_docdir)/*.dvi $(gcc_docdir)/*.pdf
@@ -3546,7 +3585,7 @@ install-gengtype: installdirs gengtype$(exeext) gtype.state
 	$(INSTALL_PROGRAM) gengtype$(exeext) $(DESTDIR)$(plugin_bindir)/gengtype$(exeext)
 
 # Install the headers needed to build a plugin.
-install-plugin: installdirs lang.install-plugin s-header-vars install-gengtype
+install-plugin: installdirs lang.install-plugin plugin.install-plugin s-header-vars install-gengtype
 # We keep the directory structure for files in config or c-family and .def
 # files. All other files are flattened to a single directory.
 	$(mkinstalldirs) $(DESTDIR)$(plugin_includedir)
@@ -3573,7 +3612,7 @@ install-plugin: installdirs lang.install-plugin s-header-vars install-gengtype
 	$(INSTALL_DATA) b-header-vars $(DESTDIR)$(plugin_includedir)/b-header-vars
 
 # Install the compiler executables built during cross compilation.
-install-common: native lang.install-common installdirs
+install-common: native lang.install-common plugin.install-common installdirs
 	for file in $(COMPILERS); do \
 	  if [ -f $$file ] ; then \
 	    rm -f $(DESTDIR)$(libexecsubdir)/$$file; \
@@ -3648,7 +3687,8 @@ install-info:: doc installdirs \
 	$(DESTDIR)$(infodir)/cppinternals.info \
 	$(DESTDIR)$(infodir)/gccinstall.info \
 	$(DESTDIR)$(infodir)/gccint.info \
-	lang.install-info
+	lang.install-info \
+	plugin.install-info
 
 $(DESTDIR)$(infodir)/%.info: doc/%.info installdirs
 	rm -f $@
@@ -3667,7 +3707,7 @@ $(DESTDIR)$(infodir)/%.info: doc/%.info installdirs
 
 pdf__strip_dir = `echo $$p | sed -e 's|^.*/||'`;
 
-install-pdf: $(PDFFILES) lang.install-pdf
+install-pdf: $(PDFFILES) lang.install-pdf plugin.install-pdf
 	@$(NORMAL_INSTALL)
 	test -z "$(pdfdir)/gcc" || $(mkinstalldirs) "$(DESTDIR)$(pdfdir)/gcc"
 	@list='$(PDFFILES)'; for p in $$list; do \
@@ -3679,7 +3719,7 @@ install-pdf: $(PDFFILES) lang.install-pdf
 
 html__strip_dir = `echo $$p | sed -e 's|^.*/||'`;
 
-install-html: $(HTMLS_BUILD) lang.install-html
+install-html: $(HTMLS_BUILD) lang.install-html plugin.install-html
 	@$(NORMAL_INSTALL)
 	test -z "$(htmldir)" || $(mkinstalldirs) "$(DESTDIR)$(htmldir)"
 	@list='$(HTMLS_INSTALL)'; for p in $$list; do \
@@ -3697,7 +3737,7 @@ install-html: $(HTMLS_BUILD) lang.install-html
 	done
 
 # Install the man pages.
-install-man: lang.install-man \
+install-man: lang.install-man plugin.install-man \
 	$(DESTDIR)$(man1dir)/$(GCC_INSTALL_NAME)$(man1ext) \
 	$(DESTDIR)$(man1dir)/$(CPP_INSTALL_NAME)$(man1ext) \
 	$(DESTDIR)$(man1dir)/$(GCOV_INSTALL_NAME)$(man1ext) \
@@ -3857,7 +3897,7 @@ install-gcc-ar: installdirs gcc-ar$(exeext) gcc-nm$(exeext) gcc-ranlib$(exeext)
 	fi
 
 # Cancel installation by deleting the installed files.
-uninstall: lang.uninstall
+uninstall: lang.uninstall plugin.uninstall
 	-rm -rf $(DESTDIR)$(libsubdir)
 	-rm -rf $(DESTDIR)$(libexecsubdir)
 	-rm -rf $(DESTDIR)$(bindir)/$(GCC_INSTALL_NAME)$(exeext)
@@ -4164,7 +4204,7 @@ paranoia: paranoia.o real.o $(LIBIBERTY)
 # These exist for maintenance purposes.
 
 # Update the tags table.
-TAGS: lang.tags
+TAGS: lang.tags plugin.tags
 	(cd $(srcdir);					\
 	incs= ;						\
 	list='$(SUBDIRS)'; for dir in $$list; do	\
diff --git a/gcc/configure.ac b/gcc/configure.ac
index 62f4b26..537d3d8 100644
--- a/gcc/configure.ac
+++ b/gcc/configure.ac
@@ -6300,7 +6300,7 @@ all_selected_languages=
 # The other mechanism is a set of hooks for each of the main targets
 # like `clean', `install', etc.
 
-language_hooks="Make-hooks"
+language_hooks="Make-lang-hooks"
 
 for lang in ${srcdir}/*/config-lang.in
 do
@@ -6409,8 +6409,8 @@ done
 # We link each language in with a set of hooks, reached indirectly via
 # lang.${target}.  Only do so for selected languages.
 
-rm -f Make-hooks
-touch Make-hooks
+rm -f Make-lang-hooks
+touch Make-lang-hooks
 target_list="all.cross start.encap rest.encap tags \
 	install-common install-man install-info install-pdf install-html dvi \
 	pdf html uninstall info man srcextra srcman srcinfo \
@@ -6423,7 +6423,160 @@ do
 	do
 		x="$x $lang.$t"
 	done
-	echo "lang.$t: $x" >> Make-hooks
+	echo "lang.$t: $x" >> Make-lang-hooks
+done
+
+# --------------
+# Plugin hooks
+# --------------
+
+# Make empty files to contain the specs and options for each plugin.
+# Then add #include lines to for a compiler that has specs and/or options.
+
+plugin_opt_files=
+plugin_specs_files=
+plugin_tree_files=
+# These (without "all_") are set in each config-plugin.in.
+# `plugin' must be a single word so is spelled singularly.
+all_plugins=
+# List of plugin makefile fragments.
+all_plugin_makefrags=
+
+# These are the plugins that are set in --enable-plugins,
+# and are available in the GCC tree.
+all_selected_plugins=
+
+# Add the plugin fragments.
+# Plugins are added via two mechanisms.  Some information must be
+# recorded in makefile variables, these are defined in config-plugin.in.
+# We accumulate them and plug them into the main Makefile.
+# The other mechanism is a set of hooks for each of the main targets
+# like `clean', `install', etc.
+
+plugin_hooks="Make-plugin-hooks"
+
+for plugin in ${srcdir}/*/config-plugin.in
+do
+changequote(,)dnl
+	test "$plugin" = "${srcdir}/*/config-plugin.in" && continue
+
+        plugin_alias=`sed -n -e 's,^plugin=['"'"'"'"]\(.*\)["'"'"'"'].*$,\1,p' -e 's,^plugin=\([^   ]*\).*$,\1,p' $plugin`
+        if test "x$plugin_alias" = x
+        then
+              echo "$plugin doesn't set \$plugin." 1>&2
+              exit 1
+        fi
+        subdir="`echo $plugin | sed -e 's,^.*/\([^/]*\)/config-plugin.in$,\1,'`"
+        subdirs="$subdirs $subdir"
+
+	# $gcc_subdir is where the gcc integration files are to be found
+	# for a plugin, both for internal compiler purposes (compiler
+	# sources implementing front-end to GCC tree converters), and for
+	# build infrastructure purposes (Make-plugin.in, etc.)
+	#
+	# This will be <subdir> (relative to $srcdir) if a line like 
+	# gcc_subdir="<subdir>" or gcc_subdir=<subdir>
+	# is found in <plugindir>/config-plugin.in, and will remain <plugindir>
+	# otherwise.
+	#
+	# Except for the plugin alias (fetched above), the regular
+	# "config-plugin.in" contents are always retrieved from $gcc_subdir,
+	# so a <plugindir>/config-plugin.in setting gcc_subdir typically sets
+	# only this and the plugin alias.
+
+        gcc_subdir=`sed -n -e 's,^gcc_subdir=['"'"'"'"]\(.*\)["'"'"'"'].*$,\1,p' -e 's,^gcc_subdir=\([^   ]*\).*$,\1,p' $plugin`
+        if [ "$gcc_subdir" = "" ]; then
+           gcc_subdir="$subdir"
+        fi
+
+        case ",$enable_plugins," in
+        *,$plugin_alias,*)
+            all_selected_plugins="$all_selected_plugins $plugin_alias"
+            if test -f $srcdir/$gcc_subdir/plugin-specs.h; then
+                plugin_specs_files="$plugin_specs_files $srcdir/$gcc_subdir/plugin-specs.h"
+	    fi
+	    ;;
+        esac
+changequote([,])dnl
+
+	plugin=
+	boot_plugin=
+	compilers=
+	outputs=
+	gtfiles=
+	subdir_requires=
+	. ${srcdir}/$gcc_subdir/config-plugin.in
+	if test "x$plugin" = x
+	then
+		echo "${srcdir}/$gcc_subdir/config-plugin.in doesn't set \$plugin." 1>&2
+		exit 1
+	fi
+
+	ok=:
+        case ",$enable_plugins," in
+        	*,$plugin_alias,*) ;;
+		*)
+			for i in $subdir_requires; do
+				test -f "${srcdir}/$i/config-plugin.in" && continue
+				ok=false
+				break
+			done
+		;;
+	esac
+	$ok || continue
+
+	all_plugin_makefrags="$all_plugin_makefrags \$(srcdir)/$gcc_subdir/Make-plugin.in"
+	if test -f $srcdir/$gcc_subdir/plugin.opt; then
+	    plugin_opt_files="$plugin_opt_files $srcdir/$gcc_subdir/plugin.opt"
+	    all_opt_files="$all_opt_files $srcdir/$gcc_subdir/plugin.opt"
+	fi
+	if test -f $srcdir/$gcc_subdir/$subdir-tree.def; then
+	    plugin_tree_files="$plugin_tree_files $srcdir/$gcc_subdir/$subdir-tree.def"
+	fi
+	all_plugins="$all_plugins $plugin"
+	all_compilers="$all_compilers $compilers"
+	all_outputs="$all_outputs $outputs"
+	all_gtfiles="$all_gtfiles [[$subdir]] $gtfiles"
+	case ",$enable_plugins," in
+		*,analyzer,*)
+		    AC_DEFINE(ENABLE_ANALYZER, 1, [Define to enable static analyzer.])
+		    enable_analyzer=yes
+		    AC_SUBST(enable_analyzer)
+		    ;;
+		*) ;;
+	esac
+done
+
+check_plugins=
+for plugin in $all_selected_plugins
+do
+	check_plugins="$check_plugins check-$plugin"
+done
+
+selftest_plugins=
+for plugin in $all_selected_plugins
+do
+	selftest_plugins="$selftest_plugins selftest-$plugin"
+done
+
+# We link each plugin in with a set of hooks, reached indirectly via
+# plugin.${target}.  Only do so for selected plugins.
+
+rm -f Make-plugin-hooks
+touch Make-plugin-hooks
+target_list="all.cross start.encap rest.encap tags \
+	install-common install-man install-info install-pdf install-html dvi \
+	pdf html uninstall info man srcextra srcman srcinfo \
+	mostlyclean clean distclean maintainer-clean install-plugin"
+
+for t in $target_list
+do
+	x=
+	for plugin in $all_selected_plugins
+	do
+		x="$x $plugin.$t"
+	done
+	echo "plugin.$t: $x" >> Make-plugin-hooks
 done
 
 # --------
@@ -6481,7 +6634,10 @@ AC_SUBST(all_compilers)
 AC_SUBST(all_gtfiles)
 AC_SUBST(all_lang_makefrags)
 AC_SUBST(all_languages)
+AC_SUBST(all_plugin_makefrags)
 AC_SUBST(all_selected_languages)
+AC_SUBST(all_plugins)
+AC_SUBST(all_selected_plugins)
 AC_SUBST(build_exeext)
 AC_SUBST(build_install_headers_dir)
 AC_SUBST(build_xm_file_list)
@@ -6490,6 +6646,8 @@ AC_SUBST(build_xm_defines)
 AC_SUBST(build_file_translate)
 AC_SUBST(check_languages)
 AC_SUBST(selftest_languages)
+AC_SUBST(check_plugins)
+AC_SUBST(selftest_plugins)
 AC_SUBST(cpp_install_dir)
 AC_SUBST(xmake_file)
 AC_SUBST(tmake_file)
@@ -6515,6 +6673,9 @@ AC_SUBST(install)
 AC_SUBST(lang_opt_files)
 AC_SUBST(lang_specs_files)
 AC_SUBST(lang_tree_files)
+AC_SUBST(plugin_opt_files)
+AC_SUBST(plugin_specs_files)
+AC_SUBST(plugin_tree_files)
 AC_SUBST(local_prefix)
 AC_SUBST(md_file)
 AC_SUBST(objc_boehm_gc)
@@ -6540,6 +6701,7 @@ AC_SUBST(d_target_objs)
 AC_SUBST(target_cpu_default)
 
 AC_SUBST_FILE(language_hooks)
+AC_SUBST_FILE(plugin_hooks)
 
 # Echo link setup.
 if test x${build} = x${host} ; then
@@ -6742,7 +6904,7 @@ AC_MSG_RESULT($ld_pushpopstate_support)
 # AC_CONFIG_SUBDIRS($subdirs)
 
 # Create the Makefile
-# and configure language subdirectories
+# and configure language and plugin subdirectories
 AC_CONFIG_FILES($all_outputs)
 
 AC_CONFIG_COMMANDS([default],
diff --git a/gcc/doc/install.texi b/gcc/doc/install.texi
index dd36a1e..c27807f 100644
--- a/gcc/doc/install.texi
+++ b/gcc/doc/install.texi
@@ -1707,6 +1707,15 @@ specified languages with the stage-1 compiler by using @command{make
 stage1-bubble all-target}, or run the testsuite on the stage-1 compiler
 for the specified languages using @command{make stage1-start check-gcc}.
 
+@item --enable-plugins=@var{plugin1},@var{plugin2},@dots{}
+Specify that a particular subset of in-tree plugins should be built.
+For a list of valid values for @var{pluginN} you can issue the following
+command in the @file{gcc} directory of your GCC source tree:@*
+@smallexample
+grep ^plugin= */config-plugin.in
+@end smallexample
+If you do not pass this flag, then no in-tree plugins will be built.
+
 @item --disable-libada
 Specify that the run-time libraries and tools used by GNAT should not
 be built.  This can be useful for debugging, or for compatibility with
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 23/49] analyzer: logging support
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (8 preceding siblings ...)
  2019-11-16  1:18 ` [PATCH 18/49] Add in-tree plugin: "analyzer" David Malcolm
@ 2019-11-16  1:18 ` David Malcolm
  2019-12-04 18:56   ` Martin Sebor
  2019-11-16  1:18 ` [PATCH 26/49] analyzer: new files: digraph.{cc|h} and shortest-paths.h David Malcolm
                   ` (40 subsequent siblings)
  50 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:18 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This patch adds a logging framework to the analyzer which handles
hierarchical messages (showing the nested structure of the calls).

This code is largely based on that in the "jit" subdirectory (with
a few changes).  An alternative would be to generalize that code
and move it to the gcc parent directory.

gcc/ChangeLog:
	* analyzer/analyzer-logging.cc: New file.
	* analyzer/analyzer-logging.h: New file.
---
 gcc/analyzer/analyzer-logging.cc | 220 +++++++++++++++++++++++++++++++++
 gcc/analyzer/analyzer-logging.h  | 256 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 476 insertions(+)
 create mode 100644 gcc/analyzer/analyzer-logging.cc
 create mode 100644 gcc/analyzer/analyzer-logging.h

diff --git a/gcc/analyzer/analyzer-logging.cc b/gcc/analyzer/analyzer-logging.cc
new file mode 100644
index 0000000..575435c
--- /dev/null
+++ b/gcc/analyzer/analyzer-logging.cc
@@ -0,0 +1,220 @@
+/* Hierarchical log messages for the analyzer.
+   Copyright (C) 2014-2019 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 "toplev.h" /* for print_version */
+#include "pretty-print.h" /* for print_version */
+#include "diagnostic.h"
+#include "tree-diagnostic.h"
+
+#include "analyzer/analyzer-logging.h"
+
+/* Implementation of class logger.  */
+
+/* ctor for logger.  */
+
+logger::logger (FILE *f_out,
+		int, /* flags */
+		int /* verbosity */,
+		const pretty_printer &reference_pp) :
+  m_refcount (0),
+  m_f_out (f_out),
+  m_indent_level (0),
+  m_log_refcount_changes (false),
+  m_pp (reference_pp.clone ())
+{
+  pp_show_color (m_pp) = 0;
+  pp_buffer (m_pp)->stream = f_out;
+
+  /* %qE in logs for SSA_NAMEs should show the ssa names, rather than
+     trying to prettify things by showing the underlying var.  */
+  pp_format_decoder (m_pp) = default_tree_printer;
+
+  /* Begin the log by writing the GCC version.  */
+  print_version (f_out, "", false);
+}
+
+/* The destructor for logger, invoked via
+   the decref method when the refcount hits zero.
+   Note that we do not close the underlying FILE * (m_f_out).  */
+
+logger::~logger ()
+{
+  /* This should be the last message emitted.  */
+  log ("%s", __PRETTY_FUNCTION__);
+  gcc_assert (m_refcount == 0);
+  delete m_pp;
+}
+
+/* Increment the reference count of the logger.  */
+
+void
+logger::incref (const char *reason)
+{
+  m_refcount++;
+  if (m_log_refcount_changes)
+    log ("%s: reason: %s refcount now %i ",
+	 __PRETTY_FUNCTION__, reason, m_refcount);
+}
+
+/* Decrement the reference count of the logger,
+   deleting it if nothing is referring to it.  */
+
+void
+logger::decref (const char *reason)
+{
+  gcc_assert (m_refcount > 0);
+  --m_refcount;
+  if (m_log_refcount_changes)
+    log ("%s: reason: %s refcount now %i",
+	 __PRETTY_FUNCTION__, reason, m_refcount);
+  if (m_refcount == 0)
+    delete this;
+}
+
+/* Write a formatted message to the log, by calling the log_va method.  */
+
+void
+logger::log (const char *fmt, ...)
+{
+  va_list ap;
+  va_start (ap, fmt);
+  log_va (fmt, &ap);
+  va_end (ap);
+}
+
+/* Write an indented line to the log file.
+
+   We explicitly flush after each line: if something crashes the process,
+   we want the logfile/stream to contain the most up-to-date hint about the
+   last thing that was happening, without it being hidden in an in-process
+   buffer.  */
+
+void
+logger::log_va (const char *fmt, va_list *ap)
+{
+  start_log_line ();
+  log_va_partial (fmt, ap);
+  end_log_line ();
+}
+
+void
+logger::start_log_line ()
+{
+  for (int i = 0; i < m_indent_level; i++)
+    fputc (' ', m_f_out);
+}
+
+void
+logger::log_partial (const char *fmt, ...)
+{
+  va_list ap;
+  va_start (ap, fmt);
+  log_va_partial (fmt, &ap);
+  va_end (ap);
+}
+
+void
+logger::log_va_partial (const char *fmt, va_list *ap)
+{
+  text_info text;
+  text.format_spec = fmt;
+  text.args_ptr = ap;
+  text.err_no = 0;
+  pp_format (m_pp, &text);
+  pp_output_formatted_text (m_pp);
+}
+
+void
+logger::end_log_line ()
+{
+  pp_flush (m_pp);
+  pp_clear_output_area (m_pp);
+  fprintf (m_f_out, "\n");
+  fflush (m_f_out);
+}
+
+/* Record the entry within a particular scope, indenting subsequent
+   log lines accordingly.  */
+
+void
+logger::enter_scope (const char *scope_name)
+{
+  log ("entering: %s", scope_name);
+  m_indent_level += 1;
+}
+
+void
+logger::enter_scope (const char *scope_name, const char *fmt, va_list *ap)
+{
+  start_log_line ();
+  log_partial ("entering: %s: ", scope_name);
+  log_va_partial (fmt, ap);
+  end_log_line ();
+
+  m_indent_level += 1;
+}
+
+
+/* Record the exit from a particular scope, restoring the indent level to
+   before the scope was entered.  */
+
+void
+logger::exit_scope (const char *scope_name)
+{
+  if (m_indent_level)
+    m_indent_level -= 1;
+  else
+    log ("(mismatching indentation)");
+  log ("exiting: %s", scope_name);
+}
+
+/* Implementation of class log_user.  */
+
+/* The constructor for log_user.  */
+
+log_user::log_user (logger *logger) : m_logger (logger)
+{
+  if (m_logger)
+    m_logger->incref("log_user ctor");
+}
+
+/* The destructor for log_user.  */
+
+log_user::~log_user ()
+{
+  if (m_logger)
+    m_logger->decref("log_user dtor");
+}
+
+/* Set the logger for a log_user, managing the reference counts
+   of the old and new logger (either of which might be NULL).  */
+
+void
+log_user::set_logger (logger *logger)
+{
+  if (logger)
+    logger->incref ("log_user::set_logger");
+  if (m_logger)
+    m_logger->decref ("log_user::set_logger");
+  m_logger = logger;
+}
diff --git a/gcc/analyzer/analyzer-logging.h b/gcc/analyzer/analyzer-logging.h
new file mode 100644
index 0000000..3e000b6
--- /dev/null
+++ b/gcc/analyzer/analyzer-logging.h
@@ -0,0 +1,256 @@
+/* Hierarchical log messages for the analyzer.
+   Copyright (C) 2014-2019 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/>.  */
+
+/* Adapted from jit-logging.h.  */
+
+#ifndef ANALYZER_LOGGING_H
+#define ANALYZER_LOGGING_H
+
+#include "diagnostic-core.h"
+
+/* A logger encapsulates a logging stream: a way to send
+   lines of pertinent information to a FILE *.  */
+
+class logger
+{
+ public:
+  logger (FILE *f_out, int flags, int verbosity, const pretty_printer &reference_pp);
+  ~logger ();
+
+  void incref (const char *reason);
+  void decref (const char *reason);
+
+  void log (const char *fmt, ...)
+    ATTRIBUTE_GCC_DIAG(2, 3);
+  void log_va (const char *fmt, va_list *ap)
+    ATTRIBUTE_GCC_DIAG(2, 0);
+  void start_log_line ();
+  void log_partial (const char *fmt, ...)
+    ATTRIBUTE_GCC_DIAG(2, 3);
+  void log_va_partial (const char *fmt, va_list *ap)
+    ATTRIBUTE_GCC_DIAG(2, 0);
+  void end_log_line ();
+
+  void enter_scope (const char *scope_name);
+  void enter_scope (const char *scope_name, const char *fmt, va_list *ap)
+    ATTRIBUTE_GCC_DIAG(3, 0);
+  void exit_scope (const char *scope_name);
+
+  pretty_printer *get_printer () const { return m_pp; }
+  FILE *get_file () const { return m_f_out; }
+
+private:
+  int m_refcount;
+  FILE *m_f_out;
+  int m_indent_level;
+  bool m_log_refcount_changes;
+  pretty_printer *m_pp;
+};
+
+/* The class log_scope is an RAII-style class intended to make
+   it easy to notify a logger about entering and exiting the body of a
+   given function.  */
+
+class log_scope
+{
+public:
+  log_scope (logger *logger, const char *name);
+  log_scope (logger *logger, const char *name, const char *fmt, ...)
+    ATTRIBUTE_GCC_DIAG(4, 5);
+  ~log_scope ();
+
+ private:
+  logger *m_logger;
+  const char *m_name;
+};
+
+/* The constructor for log_scope.
+
+   The normal case is that the logger is NULL, in which case this should
+   be largely a no-op.
+
+   If we do have a logger, notify it that we're entering the given scope.
+   We also need to hold a reference on it, to avoid a use-after-free
+   when logging the cleanup of the owner of the logger.  */
+
+inline
+log_scope::log_scope (logger *logger, const char *name) :
+ m_logger (logger),
+ m_name (name)
+{
+  if (m_logger)
+    {
+      m_logger->incref ("log_scope ctor");
+      m_logger->enter_scope (m_name);
+    }
+}
+
+inline
+log_scope::log_scope (logger *logger, const char *name, const char *fmt, ...):
+ m_logger (logger),
+ m_name (name)
+{
+  if (m_logger)
+    {
+      m_logger->incref ("log_scope ctor");
+      va_list ap;
+      va_start (ap, fmt);
+      m_logger->enter_scope (m_name, fmt, &ap);
+      va_end (ap);
+    }
+}
+
+
+/* The destructor for log_scope; essentially the opposite of
+   the constructor.  */
+
+inline
+log_scope::~log_scope ()
+{
+  if (m_logger)
+    {
+      m_logger->exit_scope (m_name);
+      m_logger->decref ("log_scope dtor");
+    }
+}
+
+/* A log_user is something that potentially uses a logger (which could be NULL).
+
+   The log_user class keeps the reference-count of a logger up-to-date.  */
+
+class log_user
+{
+ public:
+  log_user (logger *logger);
+  ~log_user ();
+
+  logger * get_logger () const { return m_logger; }
+  void set_logger (logger * logger);
+
+  void log (const char *fmt, ...) const
+    ATTRIBUTE_GCC_DIAG(2, 3);
+
+  void start_log_line () const;
+  void end_log_line () const;
+
+  void enter_scope (const char *scope_name);
+  void exit_scope (const char *scope_name);
+
+  pretty_printer *get_logger_pp () const
+  {
+    gcc_assert (m_logger);
+    return m_logger->get_printer ();
+  }
+
+  FILE *get_logger_file () const
+  {
+    if (m_logger == NULL)
+      return NULL;
+    return m_logger->get_file ();
+  }
+
+ private:
+  logger *m_logger;
+};
+
+/* A shortcut for calling log from a log_user, handling the common
+   case where the underlying logger is NULL via a no-op.  */
+
+inline void
+log_user::log (const char *fmt, ...) const
+{
+  if (m_logger)
+    {
+      va_list ap;
+      va_start (ap, fmt);
+      m_logger->log_va (fmt, &ap);
+      va_end (ap);
+    }
+}
+
+/* A shortcut for starting a log line from a log_user,
+   handling the common case where the underlying logger is NULL via
+   a no-op.  */
+
+inline void
+log_user::start_log_line () const
+{
+  if (m_logger)
+    m_logger->start_log_line ();
+}
+
+/* A shortcut for ending a log line from a log_user,
+   handling the common case where the underlying logger is NULL via
+   a no-op.  */
+
+inline void
+log_user::end_log_line () const
+{
+  if (m_logger)
+    m_logger->end_log_line ();
+}
+
+/* A shortcut for recording entry into a scope from a log_user,
+   handling the common case where the underlying logger is NULL via
+   a no-op.  */
+
+inline void
+log_user::enter_scope (const char *scope_name)
+{
+  if (m_logger)
+    m_logger->enter_scope (scope_name);
+}
+
+/* A shortcut for recording exit from a scope from a log_user,
+   handling the common case where the underlying logger is NULL via
+   a no-op.  */
+
+inline void
+log_user::exit_scope (const char *scope_name)
+{
+  if (m_logger)
+    m_logger->exit_scope (scope_name);
+}
+
+/* If the given logger is non-NULL, log entry/exit of this scope to
+   it, identifying it using __PRETTY_FUNCTION__.  */
+
+#define LOG_SCOPE(LOGGER)		\
+  log_scope s (LOGGER, __PRETTY_FUNCTION__)
+
+/* If the given logger is non-NULL, log entry/exit of this scope to
+   it, identifying it using __func__.  */
+
+#define LOG_FUNC(LOGGER) \
+  log_scope s (LOGGER, __func__)
+
+#define LOG_FUNC_1(LOGGER, FMT, A0)	\
+  log_scope s (LOGGER, __func__, FMT, A0)
+
+#define LOG_FUNC_2(LOGGER, FMT, A0, A1)		\
+  log_scope s (LOGGER, __func__, FMT, A0, A1)
+
+#define LOG_FUNC_3(LOGGER, FMT, A0, A1, A2)	\
+  log_scope s (LOGGER, __func__, FMT, A0, A1, A2)
+
+#define LOG_FUNC_4(LOGGER, FMT, A0, A1, A2, A3) \
+  log_scope s (LOGGER, __func__, FMT, A0, A1, A2, A3)
+
+#endif /* ANALYZER_LOGGING_H */
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 33/49] analyzer: new files: sm.{cc|h}
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (23 preceding siblings ...)
  2019-11-16  1:18 ` [PATCH 07/49] Replace label_text ctor with "borrow" and "take" David Malcolm
@ 2019-11-16  1:18 ` David Malcolm
  2019-12-11 19:24   ` Jeff Law
  2019-11-16  1:18 ` [PATCH 05/49] vec.h: add auto_delete_vec David Malcolm
                   ` (25 subsequent siblings)
  50 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:18 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This patch adds a "state_machine" base class for describing
API checkers in terms of state machine transitions.  Followup
patches use this to add specific API checkers.

gcc/ChangeLog:
	* analyzer/sm.cc: New file.
	* analyzer/sm.h: New file.
---
 gcc/analyzer/sm.cc | 135 ++++++++++++++++++++++++++++++++++++++++++++
 gcc/analyzer/sm.h  | 160 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 295 insertions(+)
 create mode 100644 gcc/analyzer/sm.cc
 create mode 100644 gcc/analyzer/sm.h

diff --git a/gcc/analyzer/sm.cc b/gcc/analyzer/sm.cc
new file mode 100644
index 0000000..eda9350
--- /dev/null
+++ b/gcc/analyzer/sm.cc
@@ -0,0 +1,135 @@
+/* Modeling API uses and misuses via state machines.
+   Copyright (C) 2019 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 "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "gimple.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/sm.h"
+
+////////////////////////////////////////////////////////////////////////////
+
+/* If STMT is an assignment to zero, return the LHS.  */
+
+tree
+is_zero_assignment (const gimple *stmt)
+{
+  const gassign *assign_stmt = dyn_cast <const gassign *> (stmt);
+  if (!assign_stmt)
+    return NULL_TREE;
+
+  enum tree_code op = gimple_assign_rhs_code (assign_stmt);
+  if (op != INTEGER_CST)
+    return NULL_TREE;
+
+  if (!zerop (gimple_assign_rhs1 (assign_stmt)))
+    return NULL_TREE;
+
+  return gimple_assign_lhs (assign_stmt);
+}
+
+/* If COND_STMT is a comparison against zero of the form (LHS OP 0),
+   return true and write what's being compared to *OUT_LHS and the kind of
+   the comparison to *OUT_OP.  */
+
+bool
+is_comparison_against_zero (const gcond *cond_stmt,
+			    tree *out_lhs, enum tree_code *out_op)
+{
+  enum tree_code op = gimple_cond_code (cond_stmt);
+  tree lhs = gimple_cond_lhs (cond_stmt);
+  tree rhs = gimple_cond_rhs (cond_stmt);
+  if (!zerop (rhs))
+    return false;
+  // TODO: make it symmetric?
+
+  switch (op)
+    {
+    case NE_EXPR:
+    case EQ_EXPR:
+      *out_lhs = lhs;
+      *out_op = op;
+      return true;
+
+    default:
+      return false;
+    }
+}
+
+bool
+any_pointer_p (tree var)
+{
+  if (TREE_CODE (TREE_TYPE (var)) != POINTER_TYPE)
+    return false;
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+state_machine::state_t
+state_machine::add_state (const char *name)
+{
+  m_state_names.safe_push (name);
+  return m_state_names.length () - 1;
+}
+
+const char *
+state_machine::get_state_name (state_t s) const
+{
+  return m_state_names[s];
+}
+
+void
+state_machine::validate (state_t s) const
+{
+  gcc_assert (s < m_state_names.length ());
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+void
+make_checkers (auto_delete_vec <state_machine> &out, logger *logger)
+{
+  out.safe_push (make_malloc_state_machine (logger));
+  out.safe_push (make_fileptr_state_machine (logger));
+  out.safe_push (make_taint_state_machine (logger));
+  out.safe_push (make_sensitive_state_machine (logger));
+
+  /* We only attempt to run the pattern tests if it might have been manually
+     enabled (for DejaGnu purposes).  */
+  if (flag_analyzer_checker)
+    out.safe_push (make_pattern_test_state_machine (logger));
+
+  if (flag_analyzer_checker)
+    {
+      unsigned read_index, write_index;
+      state_machine **sm;
+
+      /* TODO: this leaks the machines
+	 Would be nice to log the things that were removed.  */
+      VEC_ORDERED_REMOVE_IF (out, read_index, write_index, sm,
+			     0 != strcmp (flag_analyzer_checker,
+					  (*sm)->get_name ()));
+    }
+}
diff --git a/gcc/analyzer/sm.h b/gcc/analyzer/sm.h
new file mode 100644
index 0000000..0a89f8a
--- /dev/null
+++ b/gcc/analyzer/sm.h
@@ -0,0 +1,160 @@
+/* Modeling API uses and misuses via state machines.
+   Copyright (C) 2019 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/>.  */
+
+#ifndef GCC_ANALYZER_SM_H
+#define GCC_ANALYZER_SM_H
+
+#include "analyzer/analyzer-logging.h"
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Utility functions for use by state machines.  */
+
+extern tree is_zero_assignment (const gimple *stmt);
+extern bool is_comparison_against_zero (const gcond *cond_stmt,
+					tree *out_lhs, enum tree_code *out_op);
+extern bool any_pointer_p (tree var);
+
+////////////////////////////////////////////////////////////////////////////
+
+class state_machine;
+class sm_context;
+class pending_diagnostic;
+
+/* An abstract base class for a state machine describing an API.
+   A mapping from state IDs to names, and various virtual functions
+   for pattern-matching on statements.  */
+
+class state_machine : public log_user
+{
+public:
+  typedef unsigned state_t;
+
+  state_machine (const char *name, logger *logger)
+  : log_user (logger), m_name (name) {}
+
+  virtual ~state_machine () {}
+
+  /* Should states be inherited from a parent region to a child region,
+     when first accessing a child region?
+     For example we should inherit the taintedness of a subregion,
+     but we should not inherit the "malloc:non-null" state of a field
+     within a heap-allocated struct.  */
+  virtual bool inherited_state_p () const = 0;
+
+  const char *get_name () const { return m_name; }
+
+  const char *get_state_name (state_t s) const;
+
+  /* Return true if STMT is a function call recognized by this sm.  */
+  virtual bool on_stmt (sm_context *sm_ctxt,
+			const supernode *node,
+			const gimple *stmt) const = 0;
+
+  virtual void on_condition (sm_context *sm_ctxt,
+			     const supernode *node,
+			     const gimple *stmt,
+			     tree lhs, enum tree_code op, tree rhs) const = 0;
+
+  virtual void on_leak (sm_context *sm_ctxt,
+			const supernode *node,
+			const gimple *stmt,
+			tree var,
+			state_machine::state_t state) const = 0;
+
+  /* Return true if it safe to discard the given state (to help
+     when simplifying state objects).
+     States that need leak detection should return false.  */
+  virtual bool can_purge_p (state_t s) const = 0;
+
+  void validate (state_t s) const;
+
+protected:
+  state_t add_state (const char *name);
+
+private:
+  const char *m_name;
+  auto_vec<const char *> m_state_names;
+};
+
+/* Is STATE the start state?  (zero is hardcoded as the start state).  */
+
+static inline bool
+start_start_p (state_machine::state_t state)
+{
+  return state == 0;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Abstract base class giving an interface for the state machine to call
+   the checker engine, at a particular stmt.  */
+
+class sm_context
+{
+public:
+  virtual ~sm_context () {}
+
+  /* Called by state_machine in response to pattern matches:
+     if VAR is in state FROM, transition it to state TO, potentially
+     recording the "origin" of the state as ORIGIN.
+     Use NODE and STMT for location information.  */
+   virtual void on_transition (const supernode *node, const gimple *stmt,
+			      tree var,
+			      state_machine::state_t from,
+			      state_machine::state_t to,
+			      tree origin = NULL_TREE) = 0;
+
+  /* Called by state_machine in response to pattern matches:
+     issue a diagnostic D if VAR is in state STATE, using NODE and STMT
+     for location information.  */
+  virtual void warn_for_state (const supernode *node, const gimple *stmt,
+			       tree var, state_machine::state_t state,
+			       pending_diagnostic *d) = 0;
+
+  virtual tree get_readable_tree (tree expr)
+  {
+    return expr;
+  }
+
+protected:
+  sm_context (int sm_idx, const state_machine &sm)
+  : m_sm_idx (sm_idx), m_sm (sm) {}
+
+  int m_sm_idx;
+  const state_machine &m_sm;
+};
+
+
+////////////////////////////////////////////////////////////////////////////
+
+/* The various state_machine subclasses are hidden in their respective
+   implementation files.  */
+
+extern void make_checkers (auto_delete_vec <state_machine> &out,
+			   logger *logger);
+
+extern state_machine *make_malloc_state_machine (logger *logger);
+extern state_machine *make_fileptr_state_machine (logger *logger);
+extern state_machine *make_taint_state_machine (logger *logger);
+extern state_machine *make_sensitive_state_machine (logger *logger);
+extern state_machine *make_pattern_test_state_machine (logger *logger);
+
+#endif /* GCC_ANALYZER_SM_H */
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 06/49] timevar.h: add auto_client_timevar class
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (6 preceding siblings ...)
  2019-11-16  1:18 ` [PATCH 32/49] analyzer: new files: pending-diagnostic.{cc|h} David Malcolm
@ 2019-11-16  1:18 ` David Malcolm
  2019-12-04 16:39   ` Martin Sebor
  2019-12-07 14:29   ` Jeff Law
  2019-11-16  1:18 ` [PATCH 18/49] Add in-tree plugin: "analyzer" David Malcolm
                   ` (42 subsequent siblings)
  50 siblings, 2 replies; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:18 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This patch adds a class "auto_client_timevar", similar to auto_timevar,
but for plugins to use on their own timing items that aren't in
timevar.def

gcc/ChangeLog:
	* timevar.h (class auto_client_timevar): New class.
---
 gcc/timevar.h | 33 +++++++++++++++++++++++++++++++++
 1 file changed, 33 insertions(+)

diff --git a/gcc/timevar.h b/gcc/timevar.h
index ef404d0..d053ab7 100644
--- a/gcc/timevar.h
+++ b/gcc/timevar.h
@@ -256,6 +256,39 @@ class auto_timevar
   timevar_id_t m_tv;
 };
 
+/* An RAII class analogous to auto_timevar, but for a client item,
+   for use by plugins.  */
+
+class auto_client_timevar
+{
+ public:
+  auto_client_timevar (timer *t, const char *item_name)
+    : m_timer (t)
+  {
+    if (m_timer)
+      m_timer->push_client_item (item_name);
+  }
+
+  explicit auto_client_timevar (const char *item_name)
+    : m_timer (g_timer)
+  {
+    if (m_timer)
+      m_timer->push_client_item (item_name);
+  }
+
+  ~auto_client_timevar ()
+  {
+    if (m_timer)
+      m_timer->pop_client_item ();
+  }
+
+ private:
+  // Private to disallow copies.
+  auto_client_timevar (const auto_client_timevar &);
+
+  timer *m_timer;
+};
+
 extern void print_time (const char *, long);
 
 #endif /* ! GCC_TIMEVAR_H */
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 18/49] Add in-tree plugin: "analyzer"
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (7 preceding siblings ...)
  2019-11-16  1:18 ` [PATCH 06/49] timevar.h: add auto_client_timevar class David Malcolm
@ 2019-11-16  1:18 ` David Malcolm
  2019-11-16  1:18 ` [PATCH 23/49] analyzer: logging support David Malcolm
                   ` (41 subsequent siblings)
  50 siblings, 0 replies; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:18 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This patch supplies the {Make|config}-plugin.in files for the analyzer,
along with the entrypoint to the plugin, and the driver logic for
converting "--analyzer" into the option to inject the plugin.

gcc/ChangeLog:
	* analyzer/Make-plugin.in: New file.
	* analyzer/analyzer-plugin.cc: New file.
	* analyzer/config-plugin.in: New file.
	* gcc.c (driver_handle_option): Handle "--analyzer" by injecting
	the plugin, if available.
---
 gcc/analyzer/Make-plugin.in     | 181 ++++++++++++++++++++++++++++++++++++++++
 gcc/analyzer/analyzer-plugin.cc |  63 ++++++++++++++
 gcc/analyzer/config-plugin.in   |  34 ++++++++
 gcc/gcc.c                       |  13 +++
 4 files changed, 291 insertions(+)
 create mode 100644 gcc/analyzer/Make-plugin.in
 create mode 100644 gcc/analyzer/analyzer-plugin.cc
 create mode 100644 gcc/analyzer/config-plugin.in

diff --git a/gcc/analyzer/Make-plugin.in b/gcc/analyzer/Make-plugin.in
new file mode 100644
index 0000000..08c96f1
--- /dev/null
+++ b/gcc/analyzer/Make-plugin.in
@@ -0,0 +1,181 @@
+# Top level -*- makefile -*- fragment for analyzer
+#   Copyright (C) 2013-2019 Free Software Foundation, Inc.
+
+#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/>.
+
+# This file provides the plugin dependent support in the main Makefile.
+# Each plugin makefile fragment must provide the following targets:
+#
+# foo.all.cross, foo.start.encap, foo.rest.encap,
+# foo.install-common, foo.install-man, foo.install-info, foo.install-pdf,
+# foo.install-html, foo.info, foo.dvi, foo.pdf, foo.html, foo.uninstall,
+# foo.mostlyclean, foo.clean, foo.distclean,
+# foo.maintainer-clean, foo.stage1, foo.stage2, foo.stage3, foo.stage4
+#
+# where `foo' is the name of the plugin.
+#
+# It should also provide rules for:
+#
+# - making any compiler driver (eg: g++)
+# - the plugin proper (eg: analyzer_plugin.so)
+# - define the names for selecting the plugin in PLUGINS.
+
+#\f
+# Define the names for selecting analyzer in PLUGINS.
+# Note that it would be nice to move the dependency on g++
+# into the analyzer rule, but that needs a little bit of work
+# to do the right thing within all.cross.
+
+plugin_builddir = plugin
+
+ANALYZER_PLUGIN_SO = $(plugin_builddir)/analyzer_plugin.so
+
+analyzer: \
+	$(FULL_DRIVER_NAME) \
+	$(ANALYZER_PLUGIN_SO)
+
+# Tell GNU make to ignore these if they exist.
+.PHONY: analyzer
+
+# Files that are linked into analyzer plugin
+
+analyzer_OBJS = \
+	analyzer/analysis-plan.o \
+	analyzer/analyzer.o \
+	analyzer/analyzer-logging.o \
+	analyzer/analyzer-pass.o \
+	analyzer/analyzer-plugin.o \
+	analyzer/analyzer-selftests.o \
+	analyzer/call-string.o \
+	analyzer/checker-path.o \
+	analyzer/constraint-manager.o \
+	analyzer/diagnostic-manager.o \
+	analyzer/digraph.o \
+	analyzer/graphviz.o \
+	analyzer/engine.o \
+	analyzer/pending-diagnostic.o \
+	analyzer/program-point.o \
+	analyzer/program-state.o \
+	analyzer/region-model.o \
+	analyzer/sm.o \
+	analyzer/sm-file.o \
+	analyzer/sm-malloc.o \
+	analyzer/sm-pattern-test.o \
+	analyzer/sm-sensitive.o \
+	analyzer/sm-taint.o \
+	analyzer/state-purge.o \
+	analyzer/supergraph.o \
+	analyzer/tristate.o \
+
+# Use strict warnings for this plugin.
+analyzer-warn = $(STRICT_WARN)
+
+#\f
+# Build hooks:
+
+analyzer.all.cross:
+analyzer.start.encap:
+analyzer.rest.encap:
+
+$(plugin_builddir):
+	mkdir $@
+
+$(ANALYZER_PLUGIN_SO): $(analyzer_OBJS) $(plugin_builddir)
+	$(LINKER) $(ALL_LINKERFLAGS) $(LDFLAGS) \
+	  -o $@ \
+	  -shared \
+	  $(analyzer_OBJS) \
+	-lpthread
+
+# If the analyzer is enabled, require the selftests to be run for it
+# at each stage of the build:
+selftest-analyzer: s-selftest-analyzer
+
+ANALYZER_SELFTEST_FLAGS = -xc $(SELFTEST_FLAGS) --analyzer
+ANALYZER_SELFTEST_DEPS = cc1$(exeext) $(SELFTEST_DEPS) $(ANALYZER_PLUGIN_SO)
+
+# Run the analyzer selftests:
+s-selftest-analyzer: $(ANALYZER_SELFTEST_DEPS)
+	$(GCC_FOR_TARGET) $(ANALYZER_SELFTEST_FLAGS)
+	$(STAMP) $@
+
+# Convenience methods for running analyzer selftests under gdb:
+.PHONY: selftest-analyzer-gdb
+selftest-analyzer-gdb: $(ANALYZER_SELFTEST_DEPS)
+	$(GCC_FOR_TARGET) $(ANALYZER_SELFTEST_FLAGS) \
+	  -wrapper gdb,--args
+
+# Convenience methods for running analyzer selftests under valgrind:
+.PHONY: selftest-analyzer-valgrind
+selftest-analyzer-valgrind: $(ANALYZER_SELFTEST_DEPS)
+	$(GCC_FOR_TARGET) $(ANALYZER_SELFTEST_FLAGS) \
+	  -wrapper valgrind,--leak-check=full
+
+# Documentation build hooks.
+analyzer.info:
+analyzer.dvi:
+analyzer.pdf:
+analyzer.html:
+analyzer.srcinfo:
+analyzer.man:
+analyzer.srcman:
+
+# Testing hooks:
+lang_checks += check-analyzer
+
+#\f
+# Install hooks:
+analyzer.install-common:
+analyzer.install-man:
+analyzer.install-info:
+analyzer.install-pdf:
+analyzer.install-html:
+
+analyzer.install-plugin: installdirs
+	$(INSTALL_DATA) $(ANALYZER_PLUGIN_SO) $(DESTDIR)/$(plugin_resourcesdir)/analyzer_plugin.so
+
+analyzer.uninstall:
+
+#\f
+# Clean hooks:
+# A lot of the ancillary files are deleted by the main makefile.
+# We just have to delete files specific to us.
+
+analyzer.mostlyclean:
+
+analyzer.clean:
+
+analyzer.distclean:
+
+analyzer.maintainer-clean:
+
+#\f
+# Stage hooks:
+# The main makefile has already created stage?/analyzer.
+
+analyzer.stage1: stage1-start
+	-mv analyzer/*$(objext) stage1/analyzer
+analyzer.stage2: stage2-start
+	-mv analyzer/*$(objext) stage2/analyzer
+analyzer.stage3: stage3-start
+	-mv analyzer/*$(objext) stage3/analyzer
+analyzer.stage4: stage4-start
+	-mv analyzer/*$(objext) stage4/analyzer
+analyzer.stageprofile: stageprofile-start
+	-mv analyzer/*$(objext) stageprofile/analyzer
+analyzer.stagefeedback: stagefeedback-start
+	-mv analyzer/*$(objext) stagefeedback/analyzer
diff --git a/gcc/analyzer/analyzer-plugin.cc b/gcc/analyzer/analyzer-plugin.cc
new file mode 100644
index 0000000..1e7c56e
--- /dev/null
+++ b/gcc/analyzer/analyzer-plugin.cc
@@ -0,0 +1,63 @@
+/* Top-level interface to the analyzer, as a GCC plugin.
+   Copyright (C) 2019 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 "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/analyzer-selftests.h"
+
+int plugin_is_GPL_compatible;
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Callback for running the analyzer's selftests.  */
+
+static void
+analyzer_selftests_cb (void */*gcc_data*/, void */*user_data*/)
+{
+  run_analyzer_selftests ();
+}
+
+} /* end of namespace selftest.  */
+
+#endif /* #if CHECKING_P */
+
+/* Entrypoint to the pass.  */
+
+int
+plugin_init (struct plugin_name_args *plugin_info ATTRIBUTE_UNUSED,
+	     struct plugin_gcc_version */*version*/)
+{
+  /* Register our selftests.  */
+#if CHECKING_P
+  register_callback (plugin_info->base_name,
+		     PLUGIN_RUN_SELFTESTS,
+		     selftest::analyzer_selftests_cb, NULL);
+#endif /* #if CHECKING_P */
+
+  /* Register our pass.  */
+  register_analyzer_pass ();
+
+  return 0;
+}
diff --git a/gcc/analyzer/config-plugin.in b/gcc/analyzer/config-plugin.in
new file mode 100644
index 0000000..283e8e9
--- /dev/null
+++ b/gcc/analyzer/config-plugin.in
@@ -0,0 +1,34 @@
+# Top level configure fragment for static analyzer
+#   Copyright (C) 2013-2015 Free Software Foundation, Inc.
+
+#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/>.
+
+# Configure looks for the existence of this file to auto-config each plugin.
+# We define several parameters used by configure:
+#
+# plugin	- name of plugin as it would appear in $(PLUGINS)
+# compilers	- value to add to $(COMPILERS)
+
+plugin="analyzer"
+
+compilers=""
+
+target_libs=""
+
+gtfiles=""
+
+build_by_default="no"
diff --git a/gcc/gcc.c b/gcc/gcc.c
index c45a1df..733f080 100644
--- a/gcc/gcc.c
+++ b/gcc/gcc.c
@@ -4290,6 +4290,19 @@ driver_handle_option (struct gcc_options *opts,
       handle_foffload_option (arg);
       break;
 
+    case OPT__analyzer:
+      {
+	validated = true;
+#ifdef ENABLE_ANALYZER
+	save_switch ("-fplugin=analyzer_plugin", 0, NULL, validated, true);
+#else
+	sorry ("%qs was not enabled in this build of GCC"
+	       " (missing configure-time option %qs)",
+	       "--analyzer", "--enable-plugins=analyzer");
+#endif
+      }
+      break;
+
     default:
       /* Various driver options need no special processing at this
 	 point, having been handled in a prescan above or being
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 34/49] analyzer: new file: sm-malloc.cc
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (28 preceding siblings ...)
  2019-11-16  1:20 ` [PATCH 04/49] sbitmap.h: add operator const sbitmap & to auto_sbitmap David Malcolm
@ 2019-11-16  1:20 ` David Malcolm
  2019-12-07 15:11   ` Jeff Law
  2019-11-16  1:20 ` [PATCH 39/49] analyzer: new files: analysis-plan.{cc|h} David Malcolm
                   ` (20 subsequent siblings)
  50 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:20 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This patch adds a state machine checker for malloc/free.

gcc/ChangeLog:
	* analyzer/sm-malloc.cc: New file.
---
 gcc/analyzer/sm-malloc.cc | 799 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 799 insertions(+)
 create mode 100644 gcc/analyzer/sm-malloc.cc

diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc
new file mode 100644
index 0000000..4e02f37
--- /dev/null
+++ b/gcc/analyzer/sm-malloc.cc
@@ -0,0 +1,799 @@
+/* A state machine for detecting misuses of the malloc/free API.
+   Copyright (C) 2019 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 "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "gimple.h"
+#include "diagnostic-path.h"
+#include "diagnostic-metadata.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/pending-diagnostic.h"
+#include "analyzer/sm.h"
+
+namespace {
+
+////////////////////////////////////////////////////////////////////////////
+
+/* A state machine for detecting misuses of the malloc/free API.  */
+
+class malloc_state_machine : public state_machine
+{
+public:
+  malloc_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;
+
+  void on_condition (sm_context *sm_ctxt,
+		     const supernode *node,
+		     const gimple *stmt,
+		     tree lhs,
+		     enum tree_code op,
+		     tree rhs) const FINAL OVERRIDE;
+
+  void on_leak (sm_context *sm_ctxt,
+		const supernode *node,
+		const gimple *stmt,
+		tree var,
+		state_machine::state_t state) const FINAL OVERRIDE;
+  bool can_purge_p (state_t s) const FINAL OVERRIDE;
+
+  /* Start state.  */
+  state_t m_start;
+
+  /* State for a pointer returned from malloc that hasn't been checked for
+     NULL.
+     It could be a pointer to heap-allocated memory, or could be NULL.  */
+  state_t m_unchecked;
+
+  /* State for a pointer that's known to be NULL.  */
+  state_t m_null;
+
+  /* State for a pointer to heap-allocated memory, known to be non-NULL.  */
+  state_t m_nonnull;
+
+  /* State for a pointer to freed memory.  */
+  state_t m_freed;
+
+  /* State for a pointer that's known to not be on the heap (e.g. to a local
+     or global).  */
+  state_t m_non_heap; // TODO: or should this be a different state machine?
+  // or do we need child values etc?
+
+  /* Stop state, for pointers we don't want to track any more.  */
+  state_t m_stop;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Class for diagnostics relating to malloc_state_machine.  */
+
+class malloc_diagnostic : public pending_diagnostic
+{
+public:
+  malloc_diagnostic (const malloc_state_machine &sm, tree arg)
+  : m_sm (sm), m_arg (arg)
+  {}
+
+  bool subclass_equal_p (const pending_diagnostic &base_other) const OVERRIDE
+  {
+    return m_arg == ((const malloc_diagnostic &)base_other).m_arg;
+  }
+
+  label_text describe_state_change (const evdesc::state_change &change)
+    OVERRIDE
+  {
+    if (change.m_old_state == m_sm.m_start
+	&& change.m_new_state == m_sm.m_unchecked)
+      // TODO: verify that it's the allocation stmt, not a copy
+      return label_text::borrow ("allocated here");
+    if (change.m_old_state == m_sm.m_unchecked
+	&& change.m_new_state == m_sm.m_nonnull)
+      return change.formatted_print ("assuming %qE is non-NULL",
+				     change.m_expr);
+    if (change.m_new_state == m_sm.m_null)
+      return change.formatted_print ("assuming %qE is NULL",
+				     change.m_expr);
+    return label_text ();
+  }
+
+protected:
+  const malloc_state_machine &m_sm;
+  tree m_arg;
+};
+
+/* Concrete subclass for reporting double-free diagnostics.  */
+
+class double_free : public malloc_diagnostic
+{
+public:
+  double_free (const malloc_state_machine &sm, tree arg)
+  : malloc_diagnostic (sm, arg)
+  {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "double_free"; }
+
+  bool emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    auto_diagnostic_group d;
+    diagnostic_metadata m;
+    m.add_cwe (415); /* CWE-415: Double Free.  */
+    return warning_at (rich_loc, m, OPT_Wanalyzer_double_free,
+		       "double-%<free%> of %qE", m_arg);
+  }
+
+  label_text describe_state_change (const evdesc::state_change &change)
+    FINAL OVERRIDE
+  {
+    if (change.m_new_state == m_sm.m_freed)
+      {
+	m_first_free_event = change.m_event_id;
+	return change.formatted_print ("first %qs here", "free");
+      }
+    return malloc_diagnostic::describe_state_change (change);
+  }
+
+  label_text describe_call_with_state (const evdesc::call_with_state &info)
+    FINAL OVERRIDE
+  {
+    if (info.m_state == m_sm.m_freed)
+      return info.formatted_print
+	("passing freed pointer %qE in call to %qE from %qE",
+	 info.m_expr, info.m_callee_fndecl, info.m_caller_fndecl);
+    return label_text ();
+  }
+
+  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  {
+    if (m_first_free_event.known_p ())
+      return ev.formatted_print ("second %qs here; first %qs was at %@",
+				 "free", "free",
+				 &m_first_free_event);
+    return ev.formatted_print ("second %qs here", "free");
+  }
+
+private:
+  diagnostic_event_id_t m_first_free_event;
+};
+
+/* Abstract subclass for describing possible bad uses of NULL.
+   Responsible for describing the call that could return NULL.  */
+
+class possible_null : public malloc_diagnostic
+{
+public:
+  possible_null (const malloc_state_machine &sm, tree arg)
+  : malloc_diagnostic (sm, arg)
+  {}
+
+  label_text describe_state_change (const evdesc::state_change &change)
+    FINAL OVERRIDE
+  {
+    if (change.m_old_state == m_sm.m_start
+	&& change.m_new_state == m_sm.m_unchecked)
+      {
+	m_origin_of_unchecked_event = change.m_event_id;
+	return label_text::borrow ("this call could return NULL");
+      }
+    return malloc_diagnostic::describe_state_change (change);
+  }
+
+  label_text describe_return_of_state (const evdesc::return_of_state &info)
+    FINAL OVERRIDE
+  {
+    if (info.m_state == m_sm.m_unchecked)
+      return info.formatted_print ("possible return of NULL to %qE from %qE",
+				   info.m_caller_fndecl, info.m_callee_fndecl);
+    return label_text ();
+  }
+
+protected:
+  diagnostic_event_id_t m_origin_of_unchecked_event;
+};
+
+/* Concrete subclass for describing dereference of a possible NULL
+   value.  */
+
+class possible_null_deref : public possible_null
+{
+public:
+  possible_null_deref (const malloc_state_machine &sm, tree arg)
+  : possible_null (sm, arg)
+  {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "possible_null_deref"; }
+
+  bool emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    /* CWE-690: Unchecked Return Value to NULL Pointer Dereference.  */
+    diagnostic_metadata m;
+    m.add_cwe (690);
+    return warning_at (rich_loc, m, OPT_Wanalyzer_possible_null_dereference,
+		       "dereference of possibly-NULL %qE", m_arg);
+  }
+
+  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  {
+    if (m_origin_of_unchecked_event.known_p ())
+      return ev.formatted_print ("%qE could be NULL: unchecked value from %@",
+				 ev.m_expr,
+				 &m_origin_of_unchecked_event);
+    else
+      return ev.formatted_print ("%qE could be NULL", ev.m_expr);
+  }
+
+};
+
+/* Subroutine for use by possible_null_arg::emit and null_arg::emit.
+   Issue a note informing that the pertinent argument must be non-NULL.  */
+
+static void
+inform_nonnull_attribute (tree fndecl, int arg_idx)
+{
+  inform (DECL_SOURCE_LOCATION (fndecl),
+	  "argument %u of %qD must be non-null",
+	  arg_idx + 1, fndecl);
+  /* Ideally we would use the location of the parm and underline the
+     attribute also - but we don't have the location_t values at this point
+     in the middle-end.
+     For reference, the C and C++ FEs have get_fndecl_argument_location.  */
+}
+
+/* Concrete subclass for describing passing a possibly-NULL value to a
+   function marked with __attribute__((nonnull)).  */
+
+class possible_null_arg : public possible_null
+{
+public:
+  possible_null_arg (const malloc_state_machine &sm, tree arg,
+		     tree fndecl, int arg_idx)
+  : possible_null (sm, arg),
+    m_fndecl (fndecl), m_arg_idx (arg_idx)
+  {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "possible_null_arg"; }
+
+  bool subclass_equal_p (const pending_diagnostic &base_other) const
+  {
+    const possible_null_arg &sub_other
+      = (const possible_null_arg &)base_other;
+    return (m_arg == sub_other.m_arg
+	    && m_fndecl == sub_other.m_fndecl
+	    && m_arg_idx == sub_other.m_arg_idx);
+  }
+
+
+  bool emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    /* CWE-690: Unchecked Return Value to NULL Pointer Dereference.  */
+    auto_diagnostic_group d;
+    diagnostic_metadata m;
+    m.add_cwe (690);
+    bool warned
+      = warning_at (rich_loc, m, OPT_Wanalyzer_possible_null_argument,
+		    "use of possibly-NULL %qE where non-null expected",
+		    m_arg);
+    if (warned)
+      inform_nonnull_attribute (m_fndecl, m_arg_idx);
+    return warned;
+  }
+
+  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  {
+    if (m_origin_of_unchecked_event.known_p ())
+      return ev.formatted_print ("argument %u (%qE) from %@ could be NULL"
+				 " where non-null expected",
+				 m_arg_idx + 1, ev.m_expr,
+				 &m_origin_of_unchecked_event);
+    else
+      return ev.formatted_print ("argument %u (%qE) could be NULL"
+				 " where non-null expected",
+				 m_arg_idx + 1, ev.m_expr);
+  }
+
+private:
+  tree m_fndecl;
+  int m_arg_idx;
+};
+
+/* Concrete subclass for describing a dereference of a NULL value.  */
+
+class null_deref : public malloc_diagnostic
+{
+public:
+  null_deref (const malloc_state_machine &sm, tree arg)
+  : malloc_diagnostic (sm, arg) {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "null_deref"; }
+
+  bool emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    /* CWE-690: Unchecked Return Value to NULL Pointer Dereference.  */
+    diagnostic_metadata m;
+    m.add_cwe (690);
+    return warning_at (rich_loc, m, OPT_Wanalyzer_null_dereference,
+		       "dereference of NULL %qE", m_arg);
+  }
+
+  label_text describe_return_of_state (const evdesc::return_of_state &info)
+    FINAL OVERRIDE
+  {
+    if (info.m_state == m_sm.m_null)
+      return info.formatted_print ("return of NULL to %qE from %qE",
+				   info.m_caller_fndecl, info.m_callee_fndecl);
+    return label_text ();
+  }
+
+  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  {
+    return ev.formatted_print ("dereference of NULL %qE", ev.m_expr);
+  }
+};
+
+/* Concrete subclass for describing passing a NULL value to a
+   function marked with __attribute__((nonnull)).  */
+
+class null_arg : public malloc_diagnostic
+{
+public:
+  null_arg (const malloc_state_machine &sm, tree arg,
+	    tree fndecl, int arg_idx)
+  : malloc_diagnostic (sm, arg),
+    m_fndecl (fndecl), m_arg_idx (arg_idx)
+  {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "null_arg"; }
+
+  bool subclass_equal_p (const pending_diagnostic &base_other) const
+  {
+    const null_arg &sub_other
+      = (const null_arg &)base_other;
+    return (m_arg == sub_other.m_arg
+	    && m_fndecl == sub_other.m_fndecl
+	    && m_arg_idx == sub_other.m_arg_idx);
+  }
+
+  bool emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    /* CWE-690: Unchecked Return Value to NULL Pointer Dereference.  */
+    auto_diagnostic_group d;
+    diagnostic_metadata m;
+    m.add_cwe (690);
+    bool warned = warning_at (rich_loc, m, OPT_Wanalyzer_null_argument,
+			      "use of NULL %qE where non-null expected", m_arg);
+    if (warned)
+      inform_nonnull_attribute (m_fndecl, m_arg_idx);
+    return warned;
+  }
+
+  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  {
+    return ev.formatted_print ("argument %u (%qE) NULL"
+			       " where non-null expected",
+			       m_arg_idx + 1, ev.m_expr);
+  }
+
+private:
+  tree m_fndecl;
+  int m_arg_idx;
+};
+
+class use_after_free : public malloc_diagnostic
+{
+public:
+  use_after_free (const malloc_state_machine &sm, tree arg)
+  : malloc_diagnostic (sm, arg)
+  {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "use_after_free"; }
+
+  bool emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    /* CWE-416: Use After Free.  */
+    diagnostic_metadata m;
+    m.add_cwe (416);
+    return warning_at (rich_loc, m, OPT_Wanalyzer_use_after_free,
+		       "use after %<free%> of %qE", m_arg);
+  }
+
+  label_text describe_state_change (const evdesc::state_change &change)
+    FINAL OVERRIDE
+  {
+    if (change.m_new_state == m_sm.m_freed)
+      {
+	m_free_event = change.m_event_id;
+	return label_text::borrow ("freed here");
+      }
+    return malloc_diagnostic::describe_state_change (change);
+  }
+
+  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  {
+    if (m_free_event.known_p ())
+      return ev.formatted_print ("use after %<free%> of %qE; freed at %@",
+				 ev.m_expr, &m_free_event);
+    else
+      return ev.formatted_print ("use after %<free%> of %qE", ev.m_expr);
+  }
+
+private:
+  diagnostic_event_id_t m_free_event;
+};
+
+class malloc_leak : public malloc_diagnostic
+{
+public:
+  malloc_leak (const malloc_state_machine &sm, tree arg)
+  : malloc_diagnostic (sm, arg) {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "malloc_leak"; }
+
+  bool emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    diagnostic_metadata m;
+    m.add_cwe (401);
+    return warning_at (rich_loc, m, OPT_Wanalyzer_malloc_leak,
+		       "leak of %qE", m_arg);
+  }
+
+  label_text describe_state_change (const evdesc::state_change &change)
+    FINAL OVERRIDE
+  {
+    if (change.m_new_state == m_sm.m_unchecked)
+      {
+	m_malloc_event = change.m_event_id;
+	return label_text::borrow ("allocated here");
+      }
+    return malloc_diagnostic::describe_state_change (change);
+  }
+
+  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  {
+    if (m_malloc_event.known_p ())
+      return ev.formatted_print ("%qE leaks here; was allocated at %@",
+				 ev.m_expr, &m_malloc_event);
+    else
+      return ev.formatted_print ("%qE leaks here", ev.m_expr);
+  }
+
+private:
+  diagnostic_event_id_t m_malloc_event;
+};
+
+class free_of_non_heap : public malloc_diagnostic
+{
+public:
+  free_of_non_heap (const malloc_state_machine &sm, tree arg)
+  : malloc_diagnostic (sm, arg), m_kind (KIND_UNKNOWN)
+  {
+  }
+
+  const char *get_kind () const FINAL OVERRIDE { return "free_of_non_heap"; }
+
+  bool subclass_equal_p (const pending_diagnostic &base_other) const
+    FINAL OVERRIDE
+  {
+    const free_of_non_heap &other = (const free_of_non_heap &)base_other;
+    return (m_arg == other.m_arg && m_kind == other.m_kind);
+  }
+
+  bool emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    auto_diagnostic_group d;
+    diagnostic_metadata m;
+    m.add_cwe (590); /* CWE-590: Free of Memory not on the Heap.  */
+    switch (m_kind)
+      {
+      default:
+	gcc_unreachable ();
+      case KIND_UNKNOWN:
+	return warning_at (rich_loc, m, OPT_Wanalyzer_free_of_non_heap,
+			   "%<free%> of %qE which points to memory"
+			   " not on the heap",
+			   m_arg);
+	break;
+      case KIND_ALLOCA:
+	return warning_at (rich_loc, m, OPT_Wanalyzer_free_of_non_heap,
+			   "%<free%> of memory allocated on the stack by"
+			   " %qs (%qE) will corrupt the heap",
+			   "alloca", m_arg);
+	break;
+      }
+  }
+
+  label_text describe_state_change (const evdesc::state_change &change)
+    FINAL OVERRIDE
+  {
+    /* Attempt to reconstruct what kind of pointer it is.
+       (It seems neater for this to be a part of the state, though).  */
+    if (TREE_CODE (change.m_expr) == SSA_NAME)
+      {
+	gimple *def_stmt = SSA_NAME_DEF_STMT (change.m_expr);
+	if (gcall *call = dyn_cast <gcall *> (def_stmt))
+	  {
+	    if (is_named_call_p (call, "alloca", 1)
+		|| is_named_call_p (call, "__builtin_alloca", 1))
+	      {
+		m_kind = KIND_ALLOCA;
+		return label_text::borrow
+		  ("memory is allocated on the stack here");
+	      }
+	  }
+      }
+    return label_text::borrow ("pointer is from here");
+  }
+
+  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  {
+    return ev.formatted_print ("call to %qs here", "free");
+  }
+
+private:
+  enum kind
+  {
+    KIND_UNKNOWN,
+    KIND_ALLOCA
+  };
+  enum kind m_kind;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* malloc_state_machine's ctor.  */
+
+malloc_state_machine::malloc_state_machine (logger *logger)
+: state_machine ("malloc", logger)
+{
+  m_start = add_state ("start");
+  m_unchecked = add_state ("unchecked");
+  m_null = add_state ("null");
+  m_nonnull = add_state ("nonnull");
+  m_freed = add_state ("freed");
+  m_non_heap = add_state ("non-heap");
+  m_stop = add_state ("stop");
+}
+
+/* Implementation of state_machine::on_stmt vfunc for malloc_state_machine.  */
+
+bool
+malloc_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 (is_named_call_p (call, "malloc", 1)
+	  || is_named_call_p (call, "calloc", 2))
+	{
+	  tree lhs = gimple_call_lhs (call);
+	  if (lhs)
+	    {
+	      lhs = sm_ctxt->get_readable_tree (lhs);
+	      sm_ctxt->on_transition (node, stmt, lhs, m_start, m_unchecked);
+	    }
+	  else
+	    {
+	      /* TODO: report leak.  */
+	    }
+	  return true;
+	}
+
+      if (is_named_call_p (call, "alloca", 1)
+	  || is_named_call_p (call, "__builtin_alloca", 1))
+	{
+	  tree lhs = gimple_call_lhs (call);
+	  if (lhs)
+	    {
+	      lhs = sm_ctxt->get_readable_tree (lhs);
+	      sm_ctxt->on_transition (node, stmt, lhs, m_start, m_non_heap);
+	    }
+	  return true;
+	}
+
+      if (is_named_call_p (call, "free", 1))
+	{
+	  tree arg = gimple_call_arg (call, 0);
+
+	  arg = sm_ctxt->get_readable_tree (arg);
+
+	  /* start/unchecked/nonnull -> free.  */
+	  sm_ctxt->on_transition (node, stmt, arg, m_start, m_freed);
+	  sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_freed);
+	  sm_ctxt->on_transition (node, stmt, arg, m_nonnull, m_freed);
+
+	  /* Keep state "null" as-is, rather than transitioning to "free";
+	     we don't want want to complain about double-free of NULL.  */
+
+	  /* free -> stop, with warning.  */
+	  sm_ctxt->warn_for_state (node, stmt, arg, m_freed,
+				   new double_free (*this, arg));
+	  sm_ctxt->on_transition (node, stmt, arg, m_freed, m_stop);
+
+	  /* non-heap -> stop, with warning.  */
+	  sm_ctxt->warn_for_state (node, stmt, arg, m_non_heap,
+				   new free_of_non_heap (*this, arg));
+	  sm_ctxt->on_transition (node, stmt, arg, m_non_heap, m_stop);
+	  return true;
+	}
+
+      /* Handle "__attribute__((nonnull))".   */
+      if (tree callee_fndecl = gimple_call_fndecl (stmt))
+	{
+	  tree fntype = TREE_TYPE (callee_fndecl);
+	  bitmap nonnull_args = get_nonnull_args (fntype);
+	  if (nonnull_args)
+	    {
+	      for (unsigned i = 0; i < gimple_call_num_args (stmt); i++)
+		{
+		  tree arg = gimple_call_arg (stmt, i);
+		  if (TREE_CODE (TREE_TYPE (arg)) != POINTER_TYPE)
+		    continue;
+		  /* If we have a nonnull-args, and either all pointers, or just
+		     the specified pointers.  */
+		  if (bitmap_empty_p (nonnull_args)
+		      || bitmap_bit_p (nonnull_args, i))
+		    {
+		      sm_ctxt->warn_for_state
+			(node, stmt, arg, m_unchecked,
+			 new possible_null_arg (*this, arg, callee_fndecl, i));
+		      sm_ctxt->on_transition (node, stmt, arg, m_unchecked,
+					      m_nonnull);
+
+		      sm_ctxt->warn_for_state
+			(node, stmt, arg, m_null,
+			 new null_arg (*this, arg, callee_fndecl, i));
+		      sm_ctxt->on_transition (node, stmt, arg, m_null, m_stop);
+		    }
+		}
+	      BITMAP_FREE (nonnull_args);
+	    }
+	}
+    }
+
+  if (tree lhs = is_zero_assignment (stmt))
+    {
+      if (any_pointer_p (lhs))
+	{
+	  sm_ctxt->on_transition (node, stmt, lhs, m_start, m_null);
+	  sm_ctxt->on_transition (node, stmt, lhs, m_unchecked, m_null);
+	  sm_ctxt->on_transition (node, stmt, lhs, m_nonnull, m_null);
+	  sm_ctxt->on_transition (node, stmt, lhs, m_freed, m_null);
+	}
+    }
+
+  if (const gassign *assign_stmt = dyn_cast <const gassign *> (stmt))
+    {
+      enum tree_code op = gimple_assign_rhs_code (assign_stmt);
+      if (op == ADDR_EXPR)
+	{
+	  tree lhs = gimple_assign_lhs (assign_stmt);
+	  if (lhs)
+	    {
+	      lhs = sm_ctxt->get_readable_tree (lhs);
+	      sm_ctxt->on_transition (node, stmt, lhs, m_start, m_non_heap);
+	    }
+	}
+    }
+
+  /* Handle dereferences.  */
+  for (unsigned i = 0; i < gimple_num_ops (stmt); i++)
+    {
+      tree op = gimple_op (stmt, i);
+      if (!op)
+	continue;
+      if (TREE_CODE (op) == COMPONENT_REF)
+	op = TREE_OPERAND (op, 0);
+
+      if (TREE_CODE (op) == MEM_REF)
+	{
+	  tree arg = TREE_OPERAND (op, 0);
+	  arg = sm_ctxt->get_readable_tree (arg);
+
+	  sm_ctxt->warn_for_state (node, stmt, arg, m_unchecked,
+				   new possible_null_deref (*this, arg));
+	  sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_nonnull);
+
+	  sm_ctxt->warn_for_state (node, stmt, arg, m_null,
+				   new null_deref (*this, arg));
+	  sm_ctxt->on_transition (node, stmt, arg, m_null, m_stop);
+
+	  sm_ctxt->warn_for_state (node, stmt, arg, m_freed,
+				   new use_after_free (*this, arg));
+	  sm_ctxt->on_transition (node, stmt, arg, m_freed, m_stop);
+	}
+    }
+  return false;
+}
+
+/* Implementation of state_machine::on_condition vfunc for malloc_state_machine.
+   Potentially transition state 'unchecked' to 'nonnull' or to 'null'.  */
+
+void
+malloc_state_machine::on_condition (sm_context *sm_ctxt,
+				    const supernode *node,
+				    const gimple *stmt,
+				    tree lhs,
+				    enum tree_code op,
+				    tree rhs) const
+{
+  if (!zerop (rhs))
+    return;
+
+  if (!any_pointer_p (lhs))
+    return;
+  if (!any_pointer_p (rhs))
+    return;
+
+  if (op == NE_EXPR)
+    {
+      log ("got 'ARG != 0' match");
+      sm_ctxt->on_transition (node, stmt,
+			      lhs, m_unchecked, m_nonnull);
+    }
+  else if (op == EQ_EXPR)
+    {
+      log ("got 'ARG == 0' match");
+      sm_ctxt->on_transition (node, stmt,
+			      lhs, m_unchecked, m_null);
+    }
+}
+
+/* Implementation of state_machine::on_leak vfunc for malloc_state_machine.
+   Complain about leaks of pointers in state 'unchecked' and 'nonnull'.  */
+
+void
+malloc_state_machine::on_leak (sm_context *sm_ctxt,
+			       const supernode *node,
+			       const gimple *stmt,
+			       tree var,
+			       state_machine::state_t state ATTRIBUTE_UNUSED)
+  const
+{
+  sm_ctxt->warn_for_state (node, stmt, var, m_unchecked,
+			   new malloc_leak (*this, var));
+  sm_ctxt->warn_for_state (node, stmt, var, m_nonnull,
+			   new malloc_leak (*this, var));
+}
+
+/* Implementation of state_machine::can_purge_p vfunc for malloc_state_machine.
+   Don't allow purging of pointers in state 'unchecked' or 'nonnull'
+   (to avoid false leak reports).  */
+
+bool
+malloc_state_machine::can_purge_p (state_t s) const
+{
+  return s != m_unchecked && s != m_nonnull;
+}
+
+} // anonymous namespace
+
+/* Internal interface to this file. */
+
+state_machine *
+make_malloc_state_machine (logger *logger)
+{
+  return new malloc_state_machine (logger);
+}
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 04/49] sbitmap.h: add operator const sbitmap & to auto_sbitmap
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (27 preceding siblings ...)
  2019-11-16  1:18 ` [PATCH 16/49] Add support for in-tree plugins David Malcolm
@ 2019-11-16  1:20 ` David Malcolm
  2019-12-04 16:54   ` Martin Sebor
  2019-11-16  1:20 ` [PATCH 34/49] analyzer: new file: sm-malloc.cc David Malcolm
                   ` (21 subsequent siblings)
  50 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:20 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

gcc/ChangeLog:
	* sbitmap.h (auto_sbitmap): Add operator const sbitmap &.
---
 gcc/sbitmap.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/gcc/sbitmap.h b/gcc/sbitmap.h
index a33175d..3c5b0a4 100644
--- a/gcc/sbitmap.h
+++ b/gcc/sbitmap.h
@@ -295,6 +295,7 @@ public:
 
   /* Allow calling sbitmap functions on our bitmap.  */
   operator sbitmap () { return m_bitmap; }
+  operator const sbitmap &() const { return m_bitmap; }
 
 private:
   /* Prevent making a copy that refers to our sbitmap.  */
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 39/49] analyzer: new files: analysis-plan.{cc|h}
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (29 preceding siblings ...)
  2019-11-16  1:20 ` [PATCH 34/49] analyzer: new file: sm-malloc.cc David Malcolm
@ 2019-11-16  1:20 ` David Malcolm
  2019-12-07 15:24   ` Jeff Law
  2019-11-16  1:21 ` [PATCH 43/49] analyzer: new file: exploded-graph.h David Malcolm
                   ` (19 subsequent siblings)
  50 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:20 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This patch adds an analysis_plan class, which encapsulate decisions about
how the analysis should happen (e.g. the order in which functions should
be traversed).

gcc/ChangeLog:
	* analyzer/analysis-plan.cc: New file.
	* analyzer/analysis-plan.h: New file.
---
 gcc/analyzer/analysis-plan.cc | 115 ++++++++++++++++++++++++++++++++++++++++++
 gcc/analyzer/analysis-plan.h  |  56 ++++++++++++++++++++
 2 files changed, 171 insertions(+)
 create mode 100644 gcc/analyzer/analysis-plan.cc
 create mode 100644 gcc/analyzer/analysis-plan.h

diff --git a/gcc/analyzer/analysis-plan.cc b/gcc/analyzer/analysis-plan.cc
new file mode 100644
index 0000000..920783b
--- /dev/null
+++ b/gcc/analyzer/analysis-plan.cc
@@ -0,0 +1,115 @@
+/* A class to encapsulate decisions about how the analysis should happen.
+   Copyright (C) 2019 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 "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "cgraph.h"
+#include "timevar.h"
+#include "ipa-utils.h"
+#include "params.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/analysis-plan.h"
+#include "analyzer/supergraph.h"
+
+/* class analysis_plan.  */
+
+/* analysis_plan's ctor.  */
+
+analysis_plan::analysis_plan (const supergraph &sg, logger *logger)
+: log_user (logger), m_sg (sg),
+  m_cgraph_node_postorder (XCNEWVEC (struct cgraph_node *,
+				     symtab->cgraph_count)),
+  m_index_by_uid (symtab->cgraph_max_uid)
+{
+  LOG_SCOPE (logger);
+  auto_client_timevar tv ("creating analysis plan");
+
+  m_num_cgraph_nodes = ipa_reverse_postorder (m_cgraph_node_postorder);
+  gcc_assert (m_num_cgraph_nodes == symtab->cgraph_count);
+  if (get_logger_file ())
+    ipa_print_order (get_logger_file (),
+		     "analysis_plan", m_cgraph_node_postorder,
+		     m_num_cgraph_nodes);
+
+  /* Populate m_index_by_uid.  */
+  for (int i = 0; i < symtab->cgraph_max_uid; i++)
+    m_index_by_uid.quick_push (-1);
+  for (int i = 0; i < m_num_cgraph_nodes; i++)
+    {
+      gcc_assert (m_cgraph_node_postorder[i]->get_uid ()
+		  < symtab->cgraph_max_uid);
+      m_index_by_uid[m_cgraph_node_postorder[i]->get_uid ()] = i;
+    }
+}
+
+/* analysis_plan's dtor.  */
+
+analysis_plan::~analysis_plan ()
+{
+  free (m_cgraph_node_postorder);
+}
+
+/* Comparator for use by the exploded_graph's worklist, to order FUN_A
+   and FUN_B so that functions that are to be summarized are visited
+   before the summary is needed (based on a sort of the callgraph).  */
+
+int
+analysis_plan::cmp_function (function *fun_a, function *fun_b) const
+{
+  cgraph_node *node_a = cgraph_node::get (fun_a->decl);
+  cgraph_node *node_b = cgraph_node::get (fun_b->decl);
+
+  int idx_a = m_index_by_uid[node_a->get_uid ()];
+  int idx_b = m_index_by_uid[node_b->get_uid ()];
+
+  return idx_b - idx_a;
+}
+
+/* Return true if the call EDGE should be analyzed using a call summary.
+   Return false if it should be analyzed using a full call and return.  */
+
+bool
+analysis_plan::use_summary_p (const cgraph_edge *edge) const
+{
+  /* Don't use call summaries if -fno-analyzer-call-summaries.  */
+  if (!flag_analyzer_call_summaries)
+    return false;
+
+  /* TODO: don't count callsites each time.  */
+  int num_call_sites = 0;
+  const cgraph_node *callee = edge->callee;
+  for (cgraph_edge *edge = callee->callers; edge; edge = edge->next_caller)
+    ++num_call_sites;
+
+  /* Don't use a call summary if there's only one call site.  */
+  if (num_call_sites <= 1)
+    return false;
+
+  /* Require the callee to be sufficiently complex to be worth
+     summarizing.  */
+  if ((int)m_sg.get_num_snodes (callee->get_fun ())
+      < PARAM_VALUE (PARAM_ANALYZER_MIN_SNODES_FOR_CALL_SUMMARY))
+    return false;
+
+  return true;
+}
diff --git a/gcc/analyzer/analysis-plan.h b/gcc/analyzer/analysis-plan.h
new file mode 100644
index 0000000..c9396ec
--- /dev/null
+++ b/gcc/analyzer/analysis-plan.h
@@ -0,0 +1,56 @@
+/* A class to encapsulate decisions about how the analysis should happen.
+   Copyright (C) 2019 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/>.  */
+
+#ifndef GCC_ANALYZER_ANALYSIS_PLAN_H
+#define GCC_ANALYZER_ANALYSIS_PLAN_H
+
+#include "analyzer/analyzer-logging.h"
+
+/* A class to encapsulate decisions about how the analysis should happen.
+   Examples:
+   - the order in which functions should be analyzed, so that function
+     summaries are created before analysis of call sites that might use
+     them
+   - which callgraph edges should use call summaries
+   TODO: the above is a work-in-progress.  */
+
+class analysis_plan : public log_user
+{
+public:
+  analysis_plan (const supergraph &sg, logger *logger);
+  ~analysis_plan ();
+
+  int cmp_function (function *fun_a, function *fun_b) const;
+
+  bool use_summary_p (const cgraph_edge *edge) const;
+
+private:
+  const supergraph &m_sg;
+
+  /* Result of ipa_reverse_postorder.  */
+  cgraph_node **m_cgraph_node_postorder;
+  int m_num_cgraph_nodes;
+
+  /* Index of each node within the postorder ordering,
+     accessed via the "m_uid" field.  */
+  auto_vec<int> m_index_by_uid;
+};
+
+#endif /* GCC_ANALYZER_ANALYSIS_PLAN_H */
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 37/49] analyzer: new file: sm-sensitive.cc
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (42 preceding siblings ...)
  2019-11-16  1:21 ` [PATCH 13/49] function-tests.c: expose selftest::make_fndecl for use elsewhere David Malcolm
@ 2019-11-16  1:21 ` David Malcolm
  2019-12-07 15:18   ` Jeff Law
  2019-11-16  1:21 ` [PATCH 38/49] analyzer: new file: sm-taint.cc David Malcolm
                   ` (6 subsequent siblings)
  50 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:21 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This patch adds a state machine checker for tracking exposure of
sensitive data (e.g. writing passwords to log files).

This checker isn't ready for production, and is presented as a
proof-of-concept of the sm-based approach.

gcc/ChangeLog:
	* analyzer/sm-sensitive.cc: New file.
---
 gcc/analyzer/sm-sensitive.cc | 209 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 209 insertions(+)
 create mode 100644 gcc/analyzer/sm-sensitive.cc

diff --git a/gcc/analyzer/sm-sensitive.cc b/gcc/analyzer/sm-sensitive.cc
new file mode 100644
index 0000000..f634b8f
--- /dev/null
+++ b/gcc/analyzer/sm-sensitive.cc
@@ -0,0 +1,209 @@
+/* An experimental state machine, for tracking exposure of sensitive
+   data (e.g. through logging).
+   Copyright (C) 2019 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 "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "gimple.h"
+#include "diagnostic-path.h"
+#include "diagnostic-metadata.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/pending-diagnostic.h"
+#include "analyzer/sm.h"
+
+namespace {
+
+/* An experimental state machine, for tracking exposure of sensitive
+   data (e.g. through logging).  */
+
+class sensitive_state_machine : public state_machine
+{
+public:
+  sensitive_state_machine (logger *logger);
+
+  bool inherited_state_p () const FINAL OVERRIDE { return true; }
+
+  bool on_stmt (sm_context *sm_ctxt,
+		const supernode *node,
+		const gimple *stmt) const FINAL OVERRIDE;
+
+  void on_condition (sm_context *sm_ctxt,
+		     const supernode *node,
+		     const gimple *stmt,
+		     tree lhs,
+		     enum tree_code op,
+		     tree rhs) const FINAL OVERRIDE;
+
+  void on_leak (sm_context *sm_ctxt,
+		const supernode *node,
+		const gimple *stmt,
+		tree var,
+		state_machine::state_t state) const FINAL OVERRIDE;
+  bool can_purge_p (state_t s) const FINAL OVERRIDE;
+
+private:
+  void warn_for_any_exposure (sm_context *sm_ctxt,
+			      const supernode *node,
+			      const gimple *stmt,
+			      tree arg) const;
+
+  /* Start state.  */
+  state_t m_start;
+
+  /* State for "sensitive" data, such as a password.  */
+  state_t m_sensitive;
+
+  /* Stop state, for a value we don't want to track any more.  */
+  state_t m_stop;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+class exposure_through_output_file
+  : public pending_diagnostic_subclass<exposure_through_output_file>
+{
+public:
+  exposure_through_output_file (tree arg) : m_arg (arg) {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "exposure_through_output_file"; }
+
+  bool operator== (const exposure_through_output_file &other) const
+  {
+    return m_arg == other.m_arg;
+  }
+
+  bool emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    diagnostic_metadata m;
+    /* CWE-532: Information Exposure Through Log Files */
+    m.add_cwe (532);
+    return warning_at (rich_loc, m, OPT_Wanalyzer_exposure_through_output_file,
+		       "sensitive value %qE written to output file",
+		       m_arg);
+  }
+
+private:
+  tree m_arg;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* sensitive_state_machine's ctor.  */
+
+sensitive_state_machine::sensitive_state_machine (logger *logger)
+: state_machine ("sensitive", logger)
+{
+  m_start = add_state ("start");
+  m_sensitive = add_state ("sensitive");
+  m_stop = add_state ("stop");
+}
+
+/* Warn about an exposure at NODE and STMT if ARG is in the "sensitive"
+   state.  */
+
+void
+sensitive_state_machine::warn_for_any_exposure (sm_context *sm_ctxt,
+						const supernode *node,
+						const gimple *stmt,
+						tree arg) const
+{
+  sm_ctxt->warn_for_state (node, stmt, arg, m_sensitive,
+			   new exposure_through_output_file (arg));
+}
+
+/* Implementation of state_machine::on_stmt vfunc for
+   sensitive_state_machine.  */
+
+bool
+sensitive_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 (is_named_call_p (call, "getpass", 1))
+	{
+	  tree lhs = gimple_call_lhs (call);
+	  if (lhs)
+	    sm_ctxt->on_transition (node, stmt, lhs, m_start, m_sensitive);
+	  return true;
+	}
+      else if (is_named_call_p (call, "fprintf")
+	       || is_named_call_p (call, "printf"))
+	{
+	  /* Handle a match at any position in varargs.  */
+	  for (unsigned idx = 1; idx < gimple_call_num_args (call); idx++)
+	    {
+	      tree arg = gimple_call_arg (call, idx);
+	      warn_for_any_exposure (sm_ctxt, node, stmt, arg);
+	    }
+	  return true;
+	}
+      else if (is_named_call_p (call, "fwrite", 4))
+	{
+	  tree arg = gimple_call_arg (call, 0);
+	  warn_for_any_exposure (sm_ctxt, node, stmt, arg);
+	  return true;
+	}
+      // TODO: ...etc.  This is just a proof-of-concept at this point.
+    }
+  return false;
+}
+
+void
+sensitive_state_machine::on_condition (sm_context *sm_ctxt ATTRIBUTE_UNUSED,
+				       const supernode *node ATTRIBUTE_UNUSED,
+				       const gimple *stmt ATTRIBUTE_UNUSED,
+				       tree lhs ATTRIBUTE_UNUSED,
+				       enum tree_code op ATTRIBUTE_UNUSED,
+				       tree rhs ATTRIBUTE_UNUSED) const
+{
+  /* Empty.  */
+}
+
+void
+sensitive_state_machine::on_leak (sm_context *sm_ctxt ATTRIBUTE_UNUSED,
+				  const supernode *node ATTRIBUTE_UNUSED,
+				  const gimple *stmt ATTRIBUTE_UNUSED,
+				  tree var ATTRIBUTE_UNUSED,
+				  state_machine::state_t state ATTRIBUTE_UNUSED)
+  const
+{
+  /* Empty.  */
+}
+
+bool
+sensitive_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const
+{
+  return true;
+}
+
+} // anonymous namespace
+
+/* Internal interface to this file. */
+
+state_machine *
+make_sensitive_state_machine (logger *logger)
+{
+  return new sensitive_state_machine (logger);
+}
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 49/49] analyzer: test suite
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (38 preceding siblings ...)
  2019-11-16  1:21 ` [PATCH 10/49] Add -fdiagnostics-nn-line-numbers David Malcolm
@ 2019-11-16  1:21 ` David Malcolm
  2019-11-16  1:21 ` [PATCH 15/49] Add ordered_hash_map David Malcolm
                   ` (10 subsequent siblings)
  50 siblings, 0 replies; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:21 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This patch adds the testsuite for the analyzer.

gcc/testsuite/ChangeLog:
	* gcc.dg/analyzer/CVE-2005-1689-minimal.c: New test.
	* gcc.dg/analyzer/abort.c: New test.
	* gcc.dg/analyzer/alloca-leak.c: New test.
	* gcc.dg/analyzer/analyzer-verbosity-0.c: New test.
	* gcc.dg/analyzer/analyzer-verbosity-1.c: New test.
	* gcc.dg/analyzer/analyzer-verbosity-2.c: New test.
	* gcc.dg/analyzer/analyzer.exp: New suite.
	* gcc.dg/analyzer/attribute-nonnull.c: New test.
	* gcc.dg/analyzer/call-summaries-1.c: New test.
	* gcc.dg/analyzer/conditionals-2.c: New test.
	* gcc.dg/analyzer/conditionals-3.c: New test.
	* gcc.dg/analyzer/conditionals-notrans.c: New test.
	* gcc.dg/analyzer/conditionals-trans.c: New test.
	* gcc.dg/analyzer/data-model-1.c: New test.
	* gcc.dg/analyzer/data-model-2.c: New test.
	* gcc.dg/analyzer/data-model-3.c: New test.
	* gcc.dg/analyzer/data-model-4.c: New test.
	* gcc.dg/analyzer/data-model-5.c: New test.
	* gcc.dg/analyzer/data-model-5b.c: New test.
	* gcc.dg/analyzer/data-model-5c.c: New test.
	* gcc.dg/analyzer/data-model-5d.c: New test.
	* gcc.dg/analyzer/data-model-6.c: New test.
	* gcc.dg/analyzer/data-model-7.c: New test.
	* gcc.dg/analyzer/data-model-8.c: New test.
	* gcc.dg/analyzer/data-model-9.c: New test.
	* gcc.dg/analyzer/data-model-11.c: New test.
	* gcc.dg/analyzer/data-model-12.c: New test.
	* gcc.dg/analyzer/data-model-13.c: New test.
	* gcc.dg/analyzer/data-model-14.c: New test.
	* gcc.dg/analyzer/data-model-15.c: New test.
	* gcc.dg/analyzer/data-model-16.c: New test.
	* gcc.dg/analyzer/data-model-17.c: New test.
	* gcc.dg/analyzer/data-model-18.c: New test.
	* gcc.dg/analyzer/data-model-19.c: New test.
	* gcc.dg/analyzer/data-model-path-1.c: New test.
	* gcc.dg/analyzer/double-free-lto-1-a.c: New test.
	* gcc.dg/analyzer/double-free-lto-1-b.c: New test.
	* gcc.dg/analyzer/double-free-lto-1.h: New header.
	* gcc.dg/analyzer/equivalence.c: New test.
	* gcc.dg/analyzer/explode-1.c: New test.
	* gcc.dg/analyzer/explode-2.c: New test.
	* gcc.dg/analyzer/factorial.c: New test.
	* gcc.dg/analyzer/fibonacci.c: New test.
	* gcc.dg/analyzer/fields.c: New test.
	* gcc.dg/analyzer/file-1.c: New test.
	* gcc.dg/analyzer/file-2.c: New test.
	* gcc.dg/analyzer/function-ptr-1.c: New test.
	* gcc.dg/analyzer/function-ptr-2.c: New test.
	* gcc.dg/analyzer/function-ptr-3.c: New test.
	* gcc.dg/analyzer/gzio-2.c: New test.
	* gcc.dg/analyzer/gzio-3.c: New test.
	* gcc.dg/analyzer/gzio-3a.c: New test.
	* gcc.dg/analyzer/gzio.c: New test.
	* gcc.dg/analyzer/infinite-recursion.c: New test.
	* gcc.dg/analyzer/loop-2.c: New test.
	* gcc.dg/analyzer/loop-2a.c: New test.
	* gcc.dg/analyzer/loop-3.c: New test.
	* gcc.dg/analyzer/loop-4.c: New test.
	* gcc.dg/analyzer/loop.c: New test.
	* gcc.dg/analyzer/malloc-1.c: New test.
	* gcc.dg/analyzer/malloc-2.c: New test.
	* gcc.dg/analyzer/malloc-3.c: New test.
	* gcc.dg/analyzer/malloc-dce.c: New test.
	* gcc.dg/analyzer/malloc-dedupe-1.c: New test.
	* gcc.dg/analyzer/malloc-ipa-1.c: New test.
	* gcc.dg/analyzer/malloc-ipa-10.c: New test.
	* gcc.dg/analyzer/malloc-ipa-11.c: New test.
	* gcc.dg/analyzer/malloc-ipa-12.c: New test.
	* gcc.dg/analyzer/malloc-ipa-13.c: New test.
	* gcc.dg/analyzer/malloc-ipa-2.c: New test.
	* gcc.dg/analyzer/malloc-ipa-3.c: New test.
	* gcc.dg/analyzer/malloc-ipa-4.c: New test.
	* gcc.dg/analyzer/malloc-ipa-5.c: New test.
	* gcc.dg/analyzer/malloc-ipa-6.c: New test.
	* gcc.dg/analyzer/malloc-ipa-7.c: New test.
	* gcc.dg/analyzer/malloc-ipa-8-double-free.c: New test.
	* gcc.dg/analyzer/malloc-ipa-8-unchecked.c: New test.
	* gcc.dg/analyzer/malloc-ipa-9.c: New test.
	* gcc.dg/analyzer/malloc-macro-inline-events.c: New test.
	* gcc.dg/analyzer/malloc-macro-separate-events.c: New test.
	* gcc.dg/analyzer/malloc-macro.h: New header.
	* gcc.dg/analyzer/malloc-many-paths-1.c: New test.
	* gcc.dg/analyzer/malloc-many-paths-2.c: New test.
	* gcc.dg/analyzer/malloc-many-paths-3.c: New test.
	* gcc.dg/analyzer/malloc-paths-1.c: New test.
	* gcc.dg/analyzer/malloc-paths-10.c: New test.
	* gcc.dg/analyzer/malloc-paths-2.c: New test.
	* gcc.dg/analyzer/malloc-paths-3.c: New test.
	* gcc.dg/analyzer/malloc-paths-4.c: New test.
	* gcc.dg/analyzer/malloc-paths-5.c: New test.
	* gcc.dg/analyzer/malloc-paths-6.c: New test.
	* gcc.dg/analyzer/malloc-paths-7.c: New test.
	* gcc.dg/analyzer/malloc-paths-8.c: New test.
	* gcc.dg/analyzer/malloc-paths-9.c: New test.
	* gcc.dg/analyzer/malloc-vs-local-1a.c: New test.
	* gcc.dg/analyzer/malloc-vs-local-1b.c: New test.
	* gcc.dg/analyzer/malloc-vs-local-2.c: New test.
	* gcc.dg/analyzer/malloc-vs-local-3.c: New test.
	* gcc.dg/analyzer/malloc-vs-local-4.c: New test.
	* gcc.dg/analyzer/operations.c: New test.
	* gcc.dg/analyzer/params-2.c: New test.
	* gcc.dg/analyzer/params.c: New test.
	* gcc.dg/analyzer/paths-1.c: New test.
	* gcc.dg/analyzer/paths-1a.c: New test.
	* gcc.dg/analyzer/paths-2.c: New test.
	* gcc.dg/analyzer/paths-3.c: New test.
	* gcc.dg/analyzer/paths-4.c: New test.
	* gcc.dg/analyzer/paths-5.c: New test.
	* gcc.dg/analyzer/paths-6.c: New test.
	* gcc.dg/analyzer/paths-7.c: New test.
	* gcc.dg/analyzer/pattern-test-1.c: New test.
	* gcc.dg/analyzer/pattern-test-2.c: New test.
	* gcc.dg/analyzer/pointer-merging.c: New test.
	* gcc.dg/analyzer/pr61861.c: New test.
	* gcc.dg/analyzer/pragma-1.c: New test.
	* gcc.dg/analyzer/scope-1.c: New test.
	* gcc.dg/analyzer/sensitive-1.c: New test.
	* gcc.dg/analyzer/setjmp-1.c: New test.
	* gcc.dg/analyzer/setjmp-2.c: New test.
	* gcc.dg/analyzer/setjmp-3.c: New test.
	* gcc.dg/analyzer/setjmp-4.c: New test.
	* gcc.dg/analyzer/setjmp-5.c: New test.
	* gcc.dg/analyzer/setjmp-6.c: New test.
	* gcc.dg/analyzer/setjmp-7.c: New test.
	* gcc.dg/analyzer/setjmp-8.c: New test.
	* gcc.dg/analyzer/setjmp-9.c: New test.
	* gcc.dg/analyzer/switch.c: New test.
	* gcc.dg/analyzer/taint-1.c: New test.
	* gcc.dg/analyzer/zlib-1.c: New test.
	* gcc.dg/analyzer/zlib-2.c: New test.
	* gcc.dg/analyzer/zlib-3.c: New test.
	* gcc.dg/analyzer/zlib-4.c: New test.
	* gcc.dg/analyzer/zlib-5.c: New test.
	* gcc.dg/analyzer/zlib-6.c: New test.
	* lib/target-supports.exp (check_effective_target_analyzer): New.
---
 .../gcc.dg/analyzer/CVE-2005-1689-minimal.c        |   30 +
 gcc/testsuite/gcc.dg/analyzer/abort.c              |   71 ++
 gcc/testsuite/gcc.dg/analyzer/alloca-leak.c        |    8 +
 .../gcc.dg/analyzer/analyzer-verbosity-0.c         |  133 +++
 .../gcc.dg/analyzer/analyzer-verbosity-1.c         |  160 +++
 .../gcc.dg/analyzer/analyzer-verbosity-2.c         |  191 ++++
 gcc/testsuite/gcc.dg/analyzer/analyzer.exp         |   49 +
 gcc/testsuite/gcc.dg/analyzer/attribute-nonnull.c  |   57 ++
 gcc/testsuite/gcc.dg/analyzer/call-summaries-1.c   |   14 +
 gcc/testsuite/gcc.dg/analyzer/conditionals-2.c     |   44 +
 gcc/testsuite/gcc.dg/analyzer/conditionals-3.c     |   45 +
 .../gcc.dg/analyzer/conditionals-notrans.c         |  158 +++
 gcc/testsuite/gcc.dg/analyzer/conditionals-trans.c |  143 +++
 gcc/testsuite/gcc.dg/analyzer/data-model-1.c       | 1078 ++++++++++++++++++++
 gcc/testsuite/gcc.dg/analyzer/data-model-10.c      |   17 +
 gcc/testsuite/gcc.dg/analyzer/data-model-11.c      |    6 +
 gcc/testsuite/gcc.dg/analyzer/data-model-12.c      |   13 +
 gcc/testsuite/gcc.dg/analyzer/data-model-13.c      |   21 +
 gcc/testsuite/gcc.dg/analyzer/data-model-14.c      |   24 +
 gcc/testsuite/gcc.dg/analyzer/data-model-15.c      |   34 +
 gcc/testsuite/gcc.dg/analyzer/data-model-16.c      |   50 +
 gcc/testsuite/gcc.dg/analyzer/data-model-17.c      |   20 +
 gcc/testsuite/gcc.dg/analyzer/data-model-18.c      |   20 +
 gcc/testsuite/gcc.dg/analyzer/data-model-19.c      |   31 +
 gcc/testsuite/gcc.dg/analyzer/data-model-2.c       |   13 +
 gcc/testsuite/gcc.dg/analyzer/data-model-3.c       |   15 +
 gcc/testsuite/gcc.dg/analyzer/data-model-4.c       |   16 +
 gcc/testsuite/gcc.dg/analyzer/data-model-5.c       |  100 ++
 gcc/testsuite/gcc.dg/analyzer/data-model-5b.c      |   91 ++
 gcc/testsuite/gcc.dg/analyzer/data-model-5c.c      |   84 ++
 gcc/testsuite/gcc.dg/analyzer/data-model-5d.c      |   63 ++
 gcc/testsuite/gcc.dg/analyzer/data-model-6.c       |   13 +
 gcc/testsuite/gcc.dg/analyzer/data-model-7.c       |   19 +
 gcc/testsuite/gcc.dg/analyzer/data-model-8.c       |   24 +
 gcc/testsuite/gcc.dg/analyzer/data-model-9.c       |   32 +
 gcc/testsuite/gcc.dg/analyzer/data-model-path-1.c  |   13 +
 .../gcc.dg/analyzer/double-free-lto-1-a.c          |   16 +
 .../gcc.dg/analyzer/double-free-lto-1-b.c          |    8 +
 gcc/testsuite/gcc.dg/analyzer/double-free-lto-1.h  |    1 +
 gcc/testsuite/gcc.dg/analyzer/equivalence.c        |   29 +
 gcc/testsuite/gcc.dg/analyzer/explode-1.c          |   60 ++
 gcc/testsuite/gcc.dg/analyzer/explode-2.c          |   50 +
 gcc/testsuite/gcc.dg/analyzer/factorial.c          |    7 +
 gcc/testsuite/gcc.dg/analyzer/fibonacci.c          |    9 +
 gcc/testsuite/gcc.dg/analyzer/fields.c             |   41 +
 gcc/testsuite/gcc.dg/analyzer/file-1.c             |   37 +
 gcc/testsuite/gcc.dg/analyzer/file-2.c             |   18 +
 gcc/testsuite/gcc.dg/analyzer/function-ptr-1.c     |    8 +
 gcc/testsuite/gcc.dg/analyzer/function-ptr-2.c     |   43 +
 gcc/testsuite/gcc.dg/analyzer/function-ptr-3.c     |   17 +
 gcc/testsuite/gcc.dg/analyzer/gzio-2.c             |   11 +
 gcc/testsuite/gcc.dg/analyzer/gzio-3.c             |   31 +
 gcc/testsuite/gcc.dg/analyzer/gzio-3a.c            |   27 +
 gcc/testsuite/gcc.dg/analyzer/gzio.c               |   17 +
 gcc/testsuite/gcc.dg/analyzer/infinite-recursion.c |   55 +
 gcc/testsuite/gcc.dg/analyzer/loop-2.c             |   36 +
 gcc/testsuite/gcc.dg/analyzer/loop-2a.c            |   39 +
 gcc/testsuite/gcc.dg/analyzer/loop-3.c             |   17 +
 gcc/testsuite/gcc.dg/analyzer/loop-4.c             |   41 +
 gcc/testsuite/gcc.dg/analyzer/loop.c               |   33 +
 gcc/testsuite/gcc.dg/analyzer/malloc-1.c           |  565 ++++++++++
 gcc/testsuite/gcc.dg/analyzer/malloc-2.c           |   23 +
 gcc/testsuite/gcc.dg/analyzer/malloc-3.c           |    8 +
 gcc/testsuite/gcc.dg/analyzer/malloc-dce.c         |   12 +
 gcc/testsuite/gcc.dg/analyzer/malloc-dedupe-1.c    |   46 +
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-1.c       |   24 +
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-10.c      |   32 +
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-11.c      |   95 ++
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-12.c      |    7 +
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-13.c      |   30 +
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-2.c       |   34 +
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-3.c       |   23 +
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-4.c       |   13 +
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-5.c       |   13 +
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-6.c       |   22 +
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-7.c       |   29 +
 .../gcc.dg/analyzer/malloc-ipa-8-double-free.c     |  172 ++++
 .../gcc.dg/analyzer/malloc-ipa-8-unchecked.c       |   66 ++
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-9.c       |   18 +
 .../gcc.dg/analyzer/malloc-macro-inline-events.c   |   45 +
 .../gcc.dg/analyzer/malloc-macro-separate-events.c |   15 +
 gcc/testsuite/gcc.dg/analyzer/malloc-macro.h       |    2 +
 .../gcc.dg/analyzer/malloc-many-paths-1.c          |   14 +
 .../gcc.dg/analyzer/malloc-many-paths-2.c          |   30 +
 .../gcc.dg/analyzer/malloc-many-paths-3.c          |   36 +
 gcc/testsuite/gcc.dg/analyzer/malloc-paths-1.c     |   15 +
 gcc/testsuite/gcc.dg/analyzer/malloc-paths-10.c    |   19 +
 gcc/testsuite/gcc.dg/analyzer/malloc-paths-2.c     |   13 +
 gcc/testsuite/gcc.dg/analyzer/malloc-paths-3.c     |   14 +
 gcc/testsuite/gcc.dg/analyzer/malloc-paths-4.c     |   20 +
 gcc/testsuite/gcc.dg/analyzer/malloc-paths-5.c     |   43 +
 gcc/testsuite/gcc.dg/analyzer/malloc-paths-6.c     |   11 +
 gcc/testsuite/gcc.dg/analyzer/malloc-paths-7.c     |   21 +
 gcc/testsuite/gcc.dg/analyzer/malloc-paths-8.c     |   54 +
 gcc/testsuite/gcc.dg/analyzer/malloc-paths-9.c     |  298 ++++++
 gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-1a.c |  180 ++++
 gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-1b.c |  175 ++++
 gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-2.c  |  178 ++++
 gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-3.c  |   65 ++
 gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-4.c  |   40 +
 gcc/testsuite/gcc.dg/analyzer/operations.c         |   42 +
 gcc/testsuite/gcc.dg/analyzer/params-2.c           |   16 +
 gcc/testsuite/gcc.dg/analyzer/params.c             |   32 +
 gcc/testsuite/gcc.dg/analyzer/paths-1.c            |   16 +
 gcc/testsuite/gcc.dg/analyzer/paths-1a.c           |   16 +
 gcc/testsuite/gcc.dg/analyzer/paths-2.c            |   25 +
 gcc/testsuite/gcc.dg/analyzer/paths-3.c            |   48 +
 gcc/testsuite/gcc.dg/analyzer/paths-4.c            |   49 +
 gcc/testsuite/gcc.dg/analyzer/paths-5.c            |   10 +
 gcc/testsuite/gcc.dg/analyzer/paths-6.c            |  118 +++
 gcc/testsuite/gcc.dg/analyzer/paths-7.c            |   58 ++
 gcc/testsuite/gcc.dg/analyzer/pattern-test-1.c     |   28 +
 gcc/testsuite/gcc.dg/analyzer/pattern-test-2.c     |   29 +
 gcc/testsuite/gcc.dg/analyzer/pointer-merging.c    |   16 +
 gcc/testsuite/gcc.dg/analyzer/pr61861.c            |    2 +
 gcc/testsuite/gcc.dg/analyzer/pragma-1.c           |   26 +
 gcc/testsuite/gcc.dg/analyzer/scope-1.c            |   23 +
 gcc/testsuite/gcc.dg/analyzer/sensitive-1.c        |   33 +
 gcc/testsuite/gcc.dg/analyzer/setjmp-1.c           |    1 +
 gcc/testsuite/gcc.dg/analyzer/setjmp-2.c           |   97 ++
 gcc/testsuite/gcc.dg/analyzer/setjmp-3.c           |  106 ++
 gcc/testsuite/gcc.dg/analyzer/setjmp-4.c           |  107 ++
 gcc/testsuite/gcc.dg/analyzer/setjmp-5.c           |   65 ++
 gcc/testsuite/gcc.dg/analyzer/setjmp-6.c           |   31 +
 gcc/testsuite/gcc.dg/analyzer/setjmp-7.c           |   36 +
 gcc/testsuite/gcc.dg/analyzer/setjmp-8.c           |  107 ++
 gcc/testsuite/gcc.dg/analyzer/setjmp-9.c           |  109 ++
 gcc/testsuite/gcc.dg/analyzer/switch.c             |   28 +
 gcc/testsuite/gcc.dg/analyzer/taint-1.c            |  128 +++
 gcc/testsuite/gcc.dg/analyzer/zlib-1.c             |   67 ++
 gcc/testsuite/gcc.dg/analyzer/zlib-2.c             |   51 +
 gcc/testsuite/gcc.dg/analyzer/zlib-3.c             |  214 ++++
 gcc/testsuite/gcc.dg/analyzer/zlib-4.c             |   20 +
 gcc/testsuite/gcc.dg/analyzer/zlib-5.c             |   49 +
 gcc/testsuite/gcc.dg/analyzer/zlib-6.c             |   47 +
 gcc/testsuite/lib/target-supports.exp              |    8 +
 136 files changed, 7929 insertions(+)
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/CVE-2005-1689-minimal.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/abort.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/alloca-leak.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-0.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/analyzer.exp
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/attribute-nonnull.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/call-summaries-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/conditionals-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/conditionals-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/conditionals-notrans.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/conditionals-trans.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-10.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-11.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-12.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-13.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-14.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-15.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-16.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-17.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-18.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-19.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-4.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-5.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-5b.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-5c.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-5d.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-6.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-7.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-8.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-9.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-path-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/double-free-lto-1-a.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/double-free-lto-1-b.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/double-free-lto-1.h
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/equivalence.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/explode-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/explode-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/factorial.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fibonacci.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fields.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/file-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/file-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/function-ptr-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/function-ptr-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/function-ptr-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/gzio-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/gzio-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/gzio-3a.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/gzio.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/infinite-recursion.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/loop-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/loop-2a.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/loop-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/loop-4.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/loop.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-dce.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-dedupe-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-10.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-11.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-12.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-13.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-4.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-5.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-6.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-7.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-double-free.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-unchecked.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-9.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-macro-inline-events.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-macro-separate-events.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-macro.h
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-paths-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-paths-10.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-paths-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-paths-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-paths-4.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-paths-5.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-paths-6.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-paths-7.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-paths-8.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-paths-9.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-1a.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-1b.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-4.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/operations.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/params-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/params.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/paths-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/paths-1a.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/paths-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/paths-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/paths-4.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/paths-5.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/paths-6.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/paths-7.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/pattern-test-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/pattern-test-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/pointer-merging.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/pr61861.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/pragma-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/scope-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/sensitive-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/setjmp-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/setjmp-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/setjmp-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/setjmp-4.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/setjmp-5.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/setjmp-6.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/setjmp-7.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/setjmp-8.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/setjmp-9.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/switch.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/taint-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/zlib-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/zlib-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/zlib-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/zlib-4.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/zlib-5.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/zlib-6.c

diff --git a/gcc/testsuite/gcc.dg/analyzer/CVE-2005-1689-minimal.c b/gcc/testsuite/gcc.dg/analyzer/CVE-2005-1689-minimal.c
new file mode 100644
index 0000000..aa9deb3
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/CVE-2005-1689-minimal.c
@@ -0,0 +1,30 @@
+#include <stdlib.h>
+
+typedef struct _krb5_data {
+  char *data;
+} krb5_data;
+
+void
+test_1 (krb5_data inbuf, int flag)
+{
+  free(inbuf.data); /* { dg-message "first 'free' here" } */
+  free(inbuf.data); /* { dg-warning "double-'free' of 'inbuf.data'" } */
+}
+
+void
+test_2 (krb5_data inbuf, int flag)
+{
+  if (flag) {
+    free(inbuf.data); /* { dg-message "first 'free' here" } */
+  }
+  free(inbuf.data); /* { dg-warning "double-'free' of 'inbuf.data'" } */
+}
+
+void
+test_3 (krb5_data inbuf, int flag)
+{
+  if (flag) {
+    free((char *)inbuf.data); /* { dg-message "first 'free' here" } */
+  }
+  free((char *)inbuf.data); /* { dg-warning "double-'free' of 'inbuf.data'" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/abort.c b/gcc/testsuite/gcc.dg/analyzer/abort.c
new file mode 100644
index 0000000..cfbf74c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/abort.c
@@ -0,0 +1,71 @@
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+extern void foo ();
+extern void bar ();
+
+void test_1 (int i)
+{
+  if (i == 42)
+    abort ();
+
+  __analyzer_eval (i != 42); /* { dg-warning "TRUE" } */
+}
+
+void test_2 (int i)
+{
+  if (i)
+    foo ();
+  else
+    bar ();
+
+  foo ();
+
+  if (i)
+    foo ();
+  else
+    abort ();
+
+  __analyzer_eval (i != 0); /* { dg-warning "TRUE" } */
+}
+
+/**************************************************************************/
+
+void calls_abort (const char *msg)
+{
+  fprintf (stderr, "%s", msg);
+  abort ();
+}
+
+void test_3 (void *ptr)
+{
+  if (!ptr)
+    calls_abort ("ptr was NULL");
+
+  __analyzer_eval (ptr != 0); /* { dg-warning "TRUE" } */
+}
+
+/**************************************************************************/
+
+extern void marked_noreturn (const char *msg)
+  __attribute__ ((__noreturn__));
+
+void test_4 (void *ptr)
+{
+  if (!ptr)
+    marked_noreturn ("ptr was NULL");
+
+  __analyzer_eval (ptr != 0); /* { dg-warning "TRUE" } */
+}
+
+/**************************************************************************/
+
+void test_5 (int i)
+{
+  assert (i < 10);
+
+  /* We have not defined NDEBUG, so this will call __assert_fail if
+     i >= 10, which is labelled with __attribute__ ((__noreturn__)).  */
+  __analyzer_eval (i < 10); /* { dg-warning "TRUE" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/alloca-leak.c b/gcc/testsuite/gcc.dg/analyzer/alloca-leak.c
new file mode 100644
index 0000000..6d9fe34
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/alloca-leak.c
@@ -0,0 +1,8 @@
+#include <alloca.h>
+
+void *test (void)
+{
+  void *ptr = alloca (64);
+  return ptr;
+}
+/* TODO: warn about escaping alloca.  */
diff --git a/gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-0.c b/gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-0.c
new file mode 100644
index 0000000..98200b3
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-0.c
@@ -0,0 +1,133 @@
+/* { dg-additional-options "-fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fdiagnostics-path-format=inline-events -fdiagnostics-show-caret -fanalyzer-verbosity=0" } */
+
+#include <stdlib.h>
+
+void calls_free_1 (void *ptr)
+{
+  free (ptr); /* { dg-warning "double-'free' of 'ptr'" } */
+}
+
+void test_1 (void *ptr, int a, int b)
+{
+  if (a)
+    calls_free_1 (ptr);
+
+  if (b)
+    {
+    }
+  else
+    calls_free_1 (ptr);
+}
+
+/* { dg-begin-multiline-output "" }
+   NN |   free (ptr);
+      |   ^~~~~~~~~~
+  'test_1': event 1
+    |
+    |   NN |     calls_free_1 (ptr);
+    |      |     ^~~~~~~~~~~~~~~~~~
+    |      |     |
+    |      |     (1) calling 'calls_free_1' from 'test_1'
+    |
+    +--> 'calls_free_1': event 2
+           |
+           |   NN |   free (ptr);
+           |      |   ^~~~~~~~~~
+           |      |   |
+           |      |   (2) first 'free' here
+           |
+    <------+
+    |
+  'test_1': events 3-4
+    |
+    |   NN |     calls_free_1 (ptr);
+    |      |     ^~~~~~~~~~~~~~~~~~
+    |      |     |
+    |      |     (3) returning to 'test_1' from 'calls_free_1'
+    |......
+    |   NN |     calls_free_1 (ptr);
+    |      |     ~~~~~~~~~~~~~~~~~~
+    |      |     |
+    |      |     (4) passing freed pointer 'ptr' in call to 'calls_free_1' from 'test_1'
+    |
+    +--> 'calls_free_1': event 5
+           |
+           |   NN |   free (ptr);
+           |      |   ^~~~~~~~~~
+           |      |   |
+           |      |   (5) second 'free' here; first 'free' was at (2)
+           |
+  { dg-end-multiline-output "" } */
+
+void calls_free_2 (void *ptr)
+{
+  free (ptr); /* { dg-warning "double-'free' of 'ptr'" } */
+}
+
+void test_2 (void *ptr, int a, int b)
+{
+  switch (a)
+    {
+    default:
+      break;
+    case 1:
+      break;
+    case 3:
+      calls_free_2 (ptr);
+      break;
+    }
+
+  switch (b)
+    {
+    default:
+      calls_free_2 (ptr);
+      break;
+    case 1:
+      break;
+    case 42:
+      break;
+    }
+}
+
+/* { dg-begin-multiline-output "" }
+   NN |   free (ptr);
+      |   ^~~~~~~~~~
+  'test_2': event 1
+    |
+    |   NN |       calls_free_2 (ptr);
+    |      |       ^~~~~~~~~~~~~~~~~~
+    |      |       |
+    |      |       (1) calling 'calls_free_2' from 'test_2'
+    |
+    +--> 'calls_free_2': event 2
+           |
+           |   NN |   free (ptr);
+           |      |   ^~~~~~~~~~
+           |      |   |
+           |      |   (2) first 'free' here
+           |
+    <------+
+    |
+  'test_2': events 3-4
+    |
+    |   NN |       calls_free_2 (ptr);
+    |      |       ^~~~~~~~~~~~~~~~~~
+    |      |       |
+    |      |       (3) returning to 'test_2' from 'calls_free_2'
+    |......
+    |   NN |       calls_free_2 (ptr);
+    |      |       ~~~~~~~~~~~~~~~~~~
+    |      |       |
+    |      |       (4) passing freed pointer 'ptr' in call to 'calls_free_2' from 'test_2'
+    |
+    +--> 'calls_free_2': event 5
+           |
+           |   NN |   free (ptr);
+           |      |   ^~~~~~~~~~
+           |      |   |
+           |      |   (5) second 'free' here; first 'free' was at (2)
+           |
+  { dg-end-multiline-output "" } */
+
+// TODO: range cases
+
diff --git a/gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-1.c b/gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-1.c
new file mode 100644
index 0000000..7e437bb
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-1.c
@@ -0,0 +1,160 @@
+/* { dg-additional-options "-fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fdiagnostics-path-format=inline-events -fdiagnostics-show-caret -fanalyzer-verbosity=1" } */
+
+#include <stdlib.h>
+
+void calls_free_1 (void *ptr)
+{
+  free (ptr); /* { dg-warning "double-'free' of 'ptr'" } */
+}
+
+void test_1 (void *ptr, int a, int b)
+{
+  if (a)
+    calls_free_1 (ptr);
+
+  if (b)
+    {
+    }
+  else
+    calls_free_1 (ptr);
+}
+
+/* { dg-begin-multiline-output "" }
+   NN |   free (ptr);
+      |   ^~~~~~~~~~
+  'test_1': events 1-2
+    |
+    |   NN | void test_1 (void *ptr, int a, int b)
+    |      |      ^~~~~~
+    |      |      |
+    |      |      (1) entry to 'test_1'
+    |......
+    |   NN |     calls_free_1 (ptr);
+    |      |     ~~~~~~~~~~~~~~~~~~
+    |      |     |
+    |      |     (2) calling 'calls_free_1' from 'test_1'
+    |
+    +--> 'calls_free_1': events 3-4
+           |
+           |   NN | void calls_free_1 (void *ptr)
+           |      |      ^~~~~~~~~~~~
+           |      |      |
+           |      |      (3) entry to 'calls_free_1'
+           |   NN | {
+           |   NN |   free (ptr);
+           |      |   ~~~~~~~~~~
+           |      |   |
+           |      |   (4) first 'free' here
+           |
+    <------+
+    |
+  'test_1': events 5-6
+    |
+    |   NN |     calls_free_1 (ptr);
+    |      |     ^~~~~~~~~~~~~~~~~~
+    |      |     |
+    |      |     (5) returning to 'test_1' from 'calls_free_1'
+    |......
+    |   NN |     calls_free_1 (ptr);
+    |      |     ~~~~~~~~~~~~~~~~~~
+    |      |     |
+    |      |     (6) passing freed pointer 'ptr' in call to 'calls_free_1' from 'test_1'
+    |
+    +--> 'calls_free_1': events 7-8
+           |
+           |   NN | void calls_free_1 (void *ptr)
+           |      |      ^~~~~~~~~~~~
+           |      |      |
+           |      |      (7) entry to 'calls_free_1'
+           |   NN | {
+           |   NN |   free (ptr);
+           |      |   ~~~~~~~~~~
+           |      |   |
+           |      |   (8) second 'free' here; first 'free' was at (4)
+           |
+  { dg-end-multiline-output "" } */
+
+void calls_free_2 (void *ptr)
+{
+  free (ptr); /* { dg-warning "double-'free' of 'ptr'" } */
+}
+
+void test_2 (void *ptr, int a, int b)
+{
+  switch (a)
+    {
+    default:
+      break;
+    case 1:
+      break;
+    case 3:
+      calls_free_2 (ptr);
+      break;
+    }
+
+  switch (b)
+    {
+    default:
+      calls_free_2 (ptr);
+      break;
+    case 1:
+      break;
+    case 42:
+      break;
+    }
+}
+
+/* { dg-begin-multiline-output "" }
+   NN |   free (ptr);
+      |   ^~~~~~~~~~
+  'test_2': events 1-2
+    |
+    |   NN | void test_2 (void *ptr, int a, int b)
+    |      |      ^~~~~~
+    |      |      |
+    |      |      (1) entry to 'test_2'
+    |......
+    |   NN |       calls_free_2 (ptr);
+    |      |       ~~~~~~~~~~~~~~~~~~
+    |      |       |
+    |      |       (2) calling 'calls_free_2' from 'test_2'
+    |
+    +--> 'calls_free_2': events 3-4
+           |
+           |   NN | void calls_free_2 (void *ptr)
+           |      |      ^~~~~~~~~~~~
+           |      |      |
+           |      |      (3) entry to 'calls_free_2'
+           |   NN | {
+           |   NN |   free (ptr);
+           |      |   ~~~~~~~~~~
+           |      |   |
+           |      |   (4) first 'free' here
+           |
+    <------+
+    |
+  'test_2': events 5-6
+    |
+    |   NN |       calls_free_2 (ptr);
+    |      |       ^~~~~~~~~~~~~~~~~~
+    |      |       |
+    |      |       (5) returning to 'test_2' from 'calls_free_2'
+    |......
+    |   NN |       calls_free_2 (ptr);
+    |      |       ~~~~~~~~~~~~~~~~~~
+    |      |       |
+    |      |       (6) passing freed pointer 'ptr' in call to 'calls_free_2' from 'test_2'
+    |
+    +--> 'calls_free_2': events 7-8
+           |
+           |   NN | void calls_free_2 (void *ptr)
+           |      |      ^~~~~~~~~~~~
+           |      |      |
+           |      |      (7) entry to 'calls_free_2'
+           |   NN | {
+           |   NN |   free (ptr);
+           |      |   ~~~~~~~~~~
+           |      |   |
+           |      |   (8) second 'free' here; first 'free' was at (4)
+           |
+  { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-2.c b/gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-2.c
new file mode 100644
index 0000000..2c9e5da
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-2.c
@@ -0,0 +1,191 @@
+/* { dg-additional-options "-fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fdiagnostics-path-format=inline-events -fdiagnostics-show-caret -fanalyzer-verbosity=2" } */
+
+#include <stdlib.h>
+
+void calls_free_1 (void *ptr)
+{
+  free (ptr); /* { dg-warning "double-'free' of 'ptr'" } */
+}
+
+void test_1 (void *ptr, int a, int b)
+{
+  if (a)
+    calls_free_1 (ptr);
+
+  if (b)
+    {
+    }
+  else
+    calls_free_1 (ptr);
+}
+
+/* { dg-begin-multiline-output "" }
+   NN |   free (ptr);
+      |   ^~~~~~~~~~
+  'test_1': events 1-4
+    |
+    |   NN | void test_1 (void *ptr, int a, int b)
+    |      |      ^~~~~~
+    |      |      |
+    |      |      (1) entry to 'test_1'
+    |   NN | {
+    |   NN |   if (a)
+    |      |      ~
+    |      |      |
+    |      |      (2) following 'true' branch (when 'a != 0')...
+    |   NN |     calls_free_1 (ptr);
+    |      |     ~~~~~~~~~~~~~~~~~~
+    |      |     |
+    |      |     (3) ...to here
+    |      |     (4) calling 'calls_free_1' from 'test_1'
+    |
+    +--> 'calls_free_1': events 5-6
+           |
+           |   NN | void calls_free_1 (void *ptr)
+           |      |      ^~~~~~~~~~~~
+           |      |      |
+           |      |      (5) entry to 'calls_free_1'
+           |   NN | {
+           |   NN |   free (ptr);
+           |      |   ~~~~~~~~~~
+           |      |   |
+           |      |   (6) first 'free' here
+           |
+    <------+
+    |
+  'test_1': events 7-10
+    |
+    |   NN |     calls_free_1 (ptr);
+    |      |     ^~~~~~~~~~~~~~~~~~
+    |      |     |
+    |      |     (7) returning to 'test_1' from 'calls_free_1'
+    |   NN | 
+    |   NN |   if (b)
+    |      |      ~
+    |      |      |
+    |      |      (8) following 'false' branch (when 'b == 0')...
+    |......
+    |   NN |     calls_free_1 (ptr);
+    |      |     ~~~~~~~~~~~~~~~~~~
+    |      |     |
+    |      |     (9) ...to here
+    |      |     (10) passing freed pointer 'ptr' in call to 'calls_free_1' from 'test_1'
+    |
+    +--> 'calls_free_1': events 11-12
+           |
+           |   NN | void calls_free_1 (void *ptr)
+           |      |      ^~~~~~~~~~~~
+           |      |      |
+           |      |      (11) entry to 'calls_free_1'
+           |   NN | {
+           |   NN |   free (ptr);
+           |      |   ~~~~~~~~~~
+           |      |   |
+           |      |   (12) second 'free' here; first 'free' was at (6)
+           |
+  { dg-end-multiline-output "" } */
+
+void calls_free_2 (void *ptr)
+{
+  free (ptr); /* { dg-warning "double-'free' of 'ptr'" } */
+}
+
+void test_2 (void *ptr, int a, int b)
+{
+  switch (a)
+    {
+    default:
+      break;
+    case 1:
+      break;
+    case 3:
+      calls_free_2 (ptr);
+      break;
+    }
+
+  switch (b)
+    {
+    default:
+      calls_free_2 (ptr);
+      break;
+    case 1:
+      break;
+    case 42:
+      break;
+    }
+}
+
+/* { dg-begin-multiline-output "" }
+   NN |   free (ptr);
+      |   ^~~~~~~~~~
+  'test_2': events 1-4
+    |
+    |   NN | void test_2 (void *ptr, int a, int b)
+    |      |      ^~~~~~
+    |      |      |
+    |      |      (1) entry to 'test_2'
+    |   NN | {
+    |   NN |   switch (a)
+    |      |   ~~~~~~
+    |      |   |
+    |      |   (2) following 'case 3:' branch...
+    |......
+    |   NN |     case 3:
+    |      |     ~~~~
+    |      |     |
+    |      |     (3) ...to here
+    |   NN |       calls_free_2 (ptr);
+    |      |       ~~~~~~~~~~~~~~~~~~
+    |      |       |
+    |      |       (4) calling 'calls_free_2' from 'test_2'
+    |
+    +--> 'calls_free_2': events 5-6
+           |
+           |   NN | void calls_free_2 (void *ptr)
+           |      |      ^~~~~~~~~~~~
+           |      |      |
+           |      |      (5) entry to 'calls_free_2'
+           |   NN | {
+           |   NN |   free (ptr);
+           |      |   ~~~~~~~~~~
+           |      |   |
+           |      |   (6) first 'free' here
+           |
+    <------+
+    |
+  'test_2': events 7-10
+    |
+    |   NN |       calls_free_2 (ptr);
+    |      |       ^~~~~~~~~~~~~~~~~~
+    |      |       |
+    |      |       (7) returning to 'test_2' from 'calls_free_2'
+    |......
+    |   NN |   switch (b)
+    |      |   ~~~~~~
+    |      |   |
+    |      |   (8) following 'default:' branch...
+    |   NN |     {
+    |   NN |     default:
+    |      |     ~~~~~~~
+    |      |     |
+    |      |     (9) ...to here
+    |   NN |       calls_free_2 (ptr);
+    |      |       ~~~~~~~~~~~~~~~~~~
+    |      |       |
+    |      |       (10) passing freed pointer 'ptr' in call to 'calls_free_2' from 'test_2'
+    |
+    +--> 'calls_free_2': events 11-12
+           |
+           |   NN | void calls_free_2 (void *ptr)
+           |      |      ^~~~~~~~~~~~
+           |      |      |
+           |      |      (11) entry to 'calls_free_2'
+           |   NN | {
+           |   NN |   free (ptr);
+           |      |   ~~~~~~~~~~
+           |      |   |
+           |      |   (12) second 'free' here; first 'free' was at (6)
+           |
+  { dg-end-multiline-output "" } */
+
+// TODO: range cases
diff --git a/gcc/testsuite/gcc.dg/analyzer/analyzer.exp b/gcc/testsuite/gcc.dg/analyzer/analyzer.exp
new file mode 100644
index 0000000..a830cf7
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/analyzer.exp
@@ -0,0 +1,49 @@
+#   Copyright (C) 2019 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+# 
+# This program 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/>.
+
+# GCC testsuite that uses the `dg.exp' driver.
+
+# Load support procs.
+load_lib gcc-dg.exp
+
+# If the analyzer has not been enabled, bail.
+if { ![check_effective_target_analyzer] } {
+    return
+}
+
+global DEFAULT_CFLAGS
+if [info exists DEFAULT_CFLAGS] then {
+  set save_default_cflags $DEFAULT_CFLAGS
+}
+
+# If a testcase doesn't have special options, use these.
+set DEFAULT_CFLAGS "--analyzer -fdiagnostics-path-format=separate-events -Wanalyzer-too-complex -fanalyzer-call-summaries"
+
+# Initialize `dg'.
+dg-init
+
+# Main loop.
+dg-runtest [lsort [glob -nocomplain $srcdir/$subdir/*.\[cS\]]] \
+	"" $DEFAULT_CFLAGS
+
+# All done.
+dg-finish
+
+if [info exists save_default_cflags] {
+  set DEFAULT_CFLAGS $save_default_cflags
+} else {
+  unset DEFAULT_CFLAGS
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/attribute-nonnull.c b/gcc/testsuite/gcc.dg/analyzer/attribute-nonnull.c
new file mode 100644
index 0000000..b3673fb
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/attribute-nonnull.c
@@ -0,0 +1,57 @@
+#include <stdlib.h>
+
+extern void foo(void *ptrA, void *ptrB, void *ptrC) /* { dg-message "argument 1 of 'foo' must be non-null" } */
+  __attribute__((nonnull (1, 3)));
+
+extern void bar(void *ptrA, void *ptrB, void *ptrC) /* { dg-message "argument 1 of 'bar' must be non-null" } */
+  __attribute__((nonnull));
+
+// TODO: complain about NULL and possible NULL args
+// FIXME: ought to complain about NULL args
+
+void test_1 (void *p, void *q, void *r)
+{
+  foo(p, q, r);
+  foo(NULL, q, r);
+  foo(p, NULL, r);
+  foo(p, q, NULL);
+}
+
+void test_1a (void *q, void *r)
+{
+  void *p = NULL;
+  foo(p, q, r); /* { dg-warning "use of NULL 'p' where non-null expected" } */
+  /* { dg-message "argument 1 \\('p'\\) NULL where non-null expected" "" { target *-*-* } .-1 } */
+}
+
+void test_2 (void *p, void *q, void *r)
+{
+  bar(p, q, r);
+  bar(NULL, q, r);
+  bar(p, NULL, r);
+  bar(p, q, NULL);
+}
+
+void test_3 (void *q, void *r)
+{
+  void *p = malloc(1024); /* { dg-message "\\(1\\) this call could return NULL" } */
+
+  foo(p, q, r); /* { dg-warning "use of possibly-NULL 'p' where non-null expected" } */
+  /* { dg-message "argument 1 \\('p'\\) from \\(1\\) could be NULL where non-null expected" "" { target *-*-* } .-1 } */
+
+  foo(p, q, r);
+
+  free(p);
+}
+
+void test_4 (void *q, void *r)
+{
+  void *p = malloc(1024); /* { dg-message "\\(1\\) this call could return NULL" } */
+
+  bar(p, q, r); /* { dg-warning "use of possibly-NULL 'p' where non-null expected" } */
+  /* { dg-message "argument 1 \\('p'\\) from \\(1\\) could be NULL where non-null expected" "" { target *-*-* } .-1 } */
+
+  bar(p, q, r);
+
+  free(p);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/call-summaries-1.c b/gcc/testsuite/gcc.dg/analyzer/call-summaries-1.c
new file mode 100644
index 0000000..a64b230
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/call-summaries-1.c
@@ -0,0 +1,14 @@
+/* { dg-additional-options "-fanalyzer-call-summaries" } */
+
+#include <stdlib.h>
+
+void calls_free (void *p)
+{
+  free (p); /* { dg-warning "double-'free' of 'p'" } */
+}
+
+void test (void *q)
+{
+  calls_free (q);
+  calls_free (q);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/conditionals-2.c b/gcc/testsuite/gcc.dg/analyzer/conditionals-2.c
new file mode 100644
index 0000000..f3f1ac7
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/conditionals-2.c
@@ -0,0 +1,44 @@
+// TODO: run this test case at every optimization level
+/* { dg-additional-options "-O2" } */
+
+#include <stddef.h>
+
+#define Z_NULL 0
+
+static void __attribute__((noinline))
+test_1_callee (void *p, void *q)
+{
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+  __analyzer_eval (p == Z_NULL); /* { dg-warning "FALSE" } */
+  __analyzer_eval (p != Z_NULL); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (q == Z_NULL); /* { dg-warning "FALSE" } */ 
+  __analyzer_eval (q != Z_NULL); /* { dg-warning "TRUE" } */ 
+}
+
+void test_1 (void *p, void *q)
+{
+  if (p == Z_NULL || q == Z_NULL)
+    return;
+
+  test_1_callee (p, q);
+}
+
+static void __attribute__((noinline))
+test_2_callee (void *p, void *q)
+{
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+  __analyzer_eval (p == Z_NULL); /* { dg-warning "FALSE" } */
+  __analyzer_eval (p != Z_NULL); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (q == Z_NULL); /* { dg-warning "FALSE" } */ 
+  __analyzer_eval (q != Z_NULL); /* { dg-warning "TRUE" } */ 
+}
+
+void test_2 (void *p, void *q)
+{
+  if (p != Z_NULL && q != Z_NULL)
+    test_2_callee (p, q);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/conditionals-3.c b/gcc/testsuite/gcc.dg/analyzer/conditionals-3.c
new file mode 100644
index 0000000..d19a8b7
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/conditionals-3.c
@@ -0,0 +1,45 @@
+/* { dg-additional-options "-fno-analyzer-state-merge" } */
+
+static void only_called_when_flag_a_true (int i)
+{
+  __analyzer_eval (i == 42); /* { dg-warning "TRUE" } */
+}
+
+static void only_called_when_flag_b_true (int i)
+{
+  __analyzer_eval (i == 17); /* { dg-warning "TRUE" } */
+}
+
+int test_1 (int flag_a, int flag_b)
+{
+  int i = 17;
+
+  __analyzer_eval (flag_a); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (flag_b); /* { dg-warning "UNKNOWN" } */
+
+  if (flag_a)
+    {
+      __analyzer_eval (flag_a); /* { dg-warning "TRUE" } */
+      __analyzer_eval (flag_b); /* { dg-warning "UNKNOWN" } */
+      i = 42;
+    }
+
+  __analyzer_eval (flag_b); /* { dg-warning "UNKNOWN" } */
+
+  if (flag_a)
+    {
+      __analyzer_eval (flag_a); /* { dg-warning "TRUE" } */
+      __analyzer_eval (flag_b); /* { dg-warning "UNKNOWN" } */
+      __analyzer_eval (i == 42); /* { dg-warning "TRUE" } */
+      __analyzer_eval (i == 17); /* { dg-warning "FALSE" } */
+      only_called_when_flag_a_true (i);
+    }  
+  else
+    {
+      __analyzer_eval (flag_a); /* { dg-warning "FALSE" } */
+      __analyzer_eval (flag_b); /* { dg-warning "UNKNOWN" } */
+      __analyzer_eval (i == 42); /* { dg-warning "FALSE" } */
+      __analyzer_eval (i == 17); /* { dg-warning "TRUE" } */
+      only_called_when_flag_b_true (i);
+    }
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/conditionals-notrans.c b/gcc/testsuite/gcc.dg/analyzer/conditionals-notrans.c
new file mode 100644
index 0000000..79ffe74
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/conditionals-notrans.c
@@ -0,0 +1,158 @@
+/* { dg-additional-options "-fno-analyzer-transitivity" } */
+
+void test (int i, int j)
+{
+  if (i > 4)
+    {
+      __analyzer_eval (i > 4); /* { dg-warning "TRUE" } */
+      __analyzer_eval (i <= 4); /* { dg-warning "FALSE" } */
+      __analyzer_eval (i > 3); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+      /* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */
+
+      __analyzer_eval (i > 5); /* { dg-warning "UNKNOWN" } */
+      __analyzer_eval (i != 3); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+      /* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */
+
+      __analyzer_eval (i == 3); /* { dg-warning "FALSE" "" { xfail *-*-* } } */
+      /* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */
+
+      __analyzer_eval (i != 4); /* { dg-warning "TRUE" } */
+      __analyzer_eval (i == 4); /* { dg-warning "FALSE" } */
+      __analyzer_eval (i == 5); /* { dg-warning "UNKNOWN" } */
+      __analyzer_eval (i != 5); /* { dg-warning "UNKNOWN" } */
+      __analyzer_eval (i < 5); /* { dg-warning "FALSE" } */
+      __analyzer_eval (i <= 5); /* { dg-warning "UNKNOWN" } */
+
+      /* Tests of transitivity.  */
+      if (j < i)
+	{
+	  __analyzer_eval (j < i); /* { dg-warning "TRUE" } */
+	  __analyzer_eval (j <= 4); /* { dg-warning "UNKNOWN" } */
+	}
+      else
+	{
+	  __analyzer_eval (j >= i); /* { dg-warning "TRUE" } */
+	  __analyzer_eval (j > 4); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+	  /* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */
+	}
+    }
+  else
+    {
+      __analyzer_eval (i > 4); /* { dg-warning "FALSE" } */
+      __analyzer_eval (i <= 4); /* { dg-warning "TRUE" } */
+      __analyzer_eval (i > 3); /* { dg-warning "UNKNOWN" } */
+
+      __analyzer_eval (i > 5); /* { dg-warning "FALSE" "" { xfail *-*-* } } */
+	  /* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */
+      __analyzer_eval (i != 3); /* { dg-warning "UNKNOWN" } */
+
+      __analyzer_eval (i == 3); /* { dg-warning "UNKNOWN" } */
+
+      __analyzer_eval (i != 4); /* { dg-warning "UNKNOWN" } */
+      __analyzer_eval (i == 4); /* { dg-warning "UNKNOWN" } */
+      __analyzer_eval (i == 5); /* { dg-warning "FALSE" "" { xfail *-*-* } } */
+      /* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */
+      __analyzer_eval (i != 5); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+      /* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */
+      __analyzer_eval (i < 5); /* { dg-warning "TRUE" } */
+      __analyzer_eval (i <= 5); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+      /* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */
+    }
+}
+
+void test_2 (int i, int j, int k)
+{
+  if (i >= j)
+    {
+      __analyzer_eval (i == k); /* { dg-warning "UNKNOWN" } */
+      if (j >= k)
+	{
+	  __analyzer_eval (i >= k); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+	  /* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */
+	  __analyzer_eval (i == k); /* { dg-warning "UNKNOWN" } */
+	  if (k >= i)
+	    __analyzer_eval (i == k); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+	  /* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */
+	}
+    }
+}
+
+void test_3 (int flag, unsigned int i)
+{
+  if (!flag) {
+    return;
+  }
+
+  __analyzer_eval (flag); /* { dg-warning "TRUE" } */
+      
+  if (i>0) {
+    __analyzer_eval (i > 0); /* { dg-warning "TRUE" } */
+    __analyzer_eval (flag); /* { dg-warning "TRUE" } */
+  } else {
+    __analyzer_eval (i <= 0); /* { dg-warning "TRUE" } */
+    __analyzer_eval (flag); /* { dg-warning "TRUE" } */
+  }
+
+  __analyzer_eval (flag); /* { dg-warning "TRUE" } */
+}
+
+void test_range_int_gt_lt (int i)
+{
+  if (i > 3)
+    if (i < 5)
+      __analyzer_eval (i == 4); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+      /* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */
+}
+
+void test_range_float_gt_lt (float f)
+{
+  if (f > 3)
+    if (f < 5)
+      __analyzer_eval (f == 4); /* { dg-warning "UNKNOWN" } */
+}
+
+void test_range_int_ge_lt (int i)
+{
+  if (i >= 4)
+    if (i < 5)
+      __analyzer_eval (i == 4); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+      /* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */
+}
+
+void test_range_float_ge_lt (float f)
+{
+  if (f >= 4)
+    if (f < 5)
+      __analyzer_eval (f == 4); /* { dg-warning "UNKNOWN" } */
+}
+
+void test_range_int_gt_le (int i)
+{
+  if (i > 3)
+    if (i <= 4)
+      __analyzer_eval (i == 4); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+      /* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */
+}
+
+void test_range_float_gt_le (float f)
+{
+  if (f > 3)
+    if (f <= 4)
+      __analyzer_eval (f == 4); /* { dg-warning "UNKNOWN" } */
+}
+
+void test_range_int_ge_le (int i)
+{
+  if (i >= 4)
+    if (i <= 4)
+      __analyzer_eval (i == 4); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+      /* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */
+}
+
+void test_range_float_ge_le (float f)
+{
+  if (f >= 4)
+    if (f <= 4)
+      __analyzer_eval (f == 4); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+      /* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/conditionals-trans.c b/gcc/testsuite/gcc.dg/analyzer/conditionals-trans.c
new file mode 100644
index 0000000..3f7eb5b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/conditionals-trans.c
@@ -0,0 +1,143 @@
+/* { dg-additional-options "-fanalyzer-transitivity" } */
+
+void test (int i, int j)
+{
+  if (i > 4)
+    {
+      __analyzer_eval (i > 4); /* { dg-warning "TRUE" } */
+      __analyzer_eval (i <= 4); /* { dg-warning "FALSE" } */
+      __analyzer_eval (i > 3); /* { dg-warning "TRUE" } */
+
+      __analyzer_eval (i > 5); /* { dg-warning "UNKNOWN" } */
+      __analyzer_eval (i != 3); /* { dg-warning "TRUE" } */
+
+      __analyzer_eval (i == 3); /* { dg-warning "FALSE" } */
+
+      __analyzer_eval (i != 4); /* { dg-warning "TRUE" } */
+      __analyzer_eval (i == 4); /* { dg-warning "FALSE" } */
+      __analyzer_eval (i == 5); /* { dg-warning "UNKNOWN" } */
+      __analyzer_eval (i != 5); /* { dg-warning "UNKNOWN" } */
+      __analyzer_eval (i < 5); /* { dg-warning "FALSE" } */
+      __analyzer_eval (i <= 5); /* { dg-warning "UNKNOWN" } */
+
+      /* Tests of transitivity.  */
+      if (j < i)
+	{
+	  __analyzer_eval (j < i); /* { dg-warning "TRUE" } */
+	  __analyzer_eval (j <= 4); /* { dg-warning "UNKNOWN" } */
+	}
+      else
+	{
+	  __analyzer_eval (j >= i); /* { dg-warning "TRUE" } */
+	  __analyzer_eval (j > 4); /* { dg-warning "TRUE" } */
+	}
+    }
+  else
+    {
+      __analyzer_eval (i > 4); /* { dg-warning "FALSE" } */
+      __analyzer_eval (i <= 4); /* { dg-warning "TRUE" } */
+      __analyzer_eval (i > 3); /* { dg-warning "UNKNOWN" } */
+
+      __analyzer_eval (i > 5); /* { dg-warning "FALSE" } */
+      __analyzer_eval (i != 3); /* { dg-warning "UNKNOWN" } */
+
+      __analyzer_eval (i == 3); /* { dg-warning "UNKNOWN" } */
+
+      __analyzer_eval (i != 4); /* { dg-warning "UNKNOWN" } */
+      __analyzer_eval (i == 4); /* { dg-warning "UNKNOWN" } */
+      __analyzer_eval (i == 5); /* { dg-warning "FALSE" } */
+      __analyzer_eval (i != 5); /* { dg-warning "TRUE" } */
+      __analyzer_eval (i < 5); /* { dg-warning "TRUE" } */
+      __analyzer_eval (i <= 5); /* { dg-warning "TRUE" } */
+    }
+}
+
+void test_2 (int i, int j, int k)
+{
+  if (i >= j)
+    {
+      __analyzer_eval (i == k); /* { dg-warning "UNKNOWN" } */
+      if (j >= k)
+	{
+	  __analyzer_eval (i >= k); /* { dg-warning "TRUE" } */
+	  __analyzer_eval (i == k); /* { dg-warning "UNKNOWN" } */
+	  if (k >= i)
+	    __analyzer_eval (i == k); /* { dg-warning "TRUE" } */
+	}
+    }
+}
+
+void test_3 (int flag, unsigned int i)
+{
+  if (!flag) {
+    return;
+  }
+
+  __analyzer_eval (flag); /* { dg-warning "TRUE" } */
+      
+  if (i>0) {
+    __analyzer_eval (i > 0); /* { dg-warning "TRUE" } */
+    __analyzer_eval (flag); /* { dg-warning "TRUE" } */
+  } else {
+    __analyzer_eval (i <= 0); /* { dg-warning "TRUE" } */
+    __analyzer_eval (flag); /* { dg-warning "TRUE" } */
+  }
+
+  __analyzer_eval (flag); /* { dg-warning "TRUE" } */
+}
+
+void test_range_int_gt_lt (int i)
+{
+  if (i > 3)
+    if (i < 5)
+      __analyzer_eval (i == 4); /* { dg-warning "TRUE" } */
+}
+
+void test_range_float_gt_lt (float f)
+{
+  if (f > 3)
+    if (f < 5)
+      __analyzer_eval (f == 4); /* { dg-warning "UNKNOWN" } */
+}
+
+void test_range_int_ge_lt (int i)
+{
+  if (i >= 4)
+    if (i < 5)
+      __analyzer_eval (i == 4); /* { dg-warning "TRUE" } */
+}
+
+void test_range_float_ge_lt (float f)
+{
+  if (f >= 4)
+    if (f < 5)
+      __analyzer_eval (f == 4); /* { dg-warning "UNKNOWN" } */
+}
+
+void test_range_int_gt_le (int i)
+{
+  if (i > 3)
+    if (i <= 4)
+      __analyzer_eval (i == 4); /* { dg-warning "TRUE" } */
+}
+
+void test_range_float_gt_le (float f)
+{
+  if (f > 3)
+    if (f <= 4)
+      __analyzer_eval (f == 4); /* { dg-warning "UNKNOWN" } */
+}
+
+void test_range_int_ge_le (int i)
+{
+  if (i >= 4)
+    if (i <= 4)
+      __analyzer_eval (i == 4); /* { dg-warning "TRUE" } */
+}
+
+void test_range_float_ge_le (float f)
+{
+  if (f >= 4)
+    if (f <= 4)
+      __analyzer_eval (f == 4); /* { dg-warning "TRUE" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-1.c b/gcc/testsuite/gcc.dg/analyzer/data-model-1.c
new file mode 100644
index 0000000..c195b74
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-1.c
@@ -0,0 +1,1078 @@
+#include <stdlib.h>
+#include <string.h>
+
+struct foo
+{
+  int i;
+};
+
+/* Fields of a local.  */
+
+void test_1 (void)
+{
+  struct foo f;
+  f.i = 1;
+  __analyzer_eval (f.i == 1); /* { dg-warning "TRUE" } */
+}
+
+/* Fields of a param.  */
+
+void test_2 (struct foo f)
+{
+  __analyzer_eval (f.i == 2); /* { dg-warning "UNKNOWN" } */
+  f.i = 2;
+  __analyzer_eval (f.i == 2); /* { dg-warning "TRUE" } */
+}
+
+/* Fields of a param ptr.  */
+
+void test_3 (struct foo *f)
+{
+  __analyzer_eval (f->i == 3); /* { dg-warning "UNKNOWN" } */
+  f->i = 3;
+  __analyzer_eval (f->i == 3); /* { dg-warning "TRUE" } */
+}
+
+/* Fields of a global ptr.  */
+struct foo *global_foo_ptr;
+
+void test_3a (void)
+{
+  struct foo *tmp = global_foo_ptr;
+  __analyzer_eval (global_foo_ptr->i == 3); /* { dg-warning "UNKNOWN" } */
+  global_foo_ptr->i = 3;
+  __analyzer_eval (global_foo_ptr->i == 3); /* { dg-warning "TRUE" } */
+}
+
+/* Pointer to a local.  */
+
+void test_4 (void)
+{
+  int i;
+  int *p = &i;
+  i = 1;
+  *p = 2;
+  __analyzer_eval (i == 2); /* { dg-warning "TRUE" } */
+}
+
+/* Local array.  */
+
+void test_5 (void)
+{
+  int a[10];
+  a[3] = 5; /* ARRAY_REF.  */
+  __analyzer_eval (a[3] == 5); /* { dg-warning "TRUE" } */
+}
+
+/* Local array, but using an unknown index.  */
+
+void test_5a (int idx)
+{
+  int a[10];
+  a[idx] = 5; /* ARRAY_REF.  */
+  __analyzer_eval (a[idx] == 5); /* { dg-warning "TRUE" } */
+}
+
+/* Array passed in as a param.  */
+
+void test_6 (int a[10])
+{
+  /* POINTER_PLUS_EXPR then a MEM_REF.  */
+  __analyzer_eval (a[3] == 42); /* { dg-warning "UNKNOWN" } */
+  a[3] = 42;
+  __analyzer_eval (a[3] == 42); /* { dg-warning "TRUE" } */
+}
+
+/* Array passed in as a param ptr.  */
+
+void test_7 (int *a)
+{
+  __analyzer_eval (a[3] == 42); /* { dg-warning "UNKNOWN" } */
+  a[3] = 42;
+  __analyzer_eval (a[3] == 42); /* { dg-warning "TRUE" } */
+}
+
+/* Globals.  */
+
+int glob_a;
+
+void test_10 (void)
+{
+  __analyzer_eval (glob_a == 42); /* { dg-warning "UNKNOWN" } */
+  glob_a = 42;
+  __analyzer_eval (glob_a == 42); /* { dg-warning "TRUE" } */
+}
+
+/* malloc.  */
+
+void test_11 (void)
+{
+  void *p = malloc (256);
+  void *q = malloc (256);
+
+  /* malloc results should be unique.  */
+  __analyzer_eval (p == q); /* { dg-warning "FALSE" } */
+  __analyzer_eval (p != q); /* { dg-warning "TRUE" } */
+  __analyzer_eval (p <= q); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (p >= q); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (p < q); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (p > q); /* { dg-warning "UNKNOWN" } */
+
+  __analyzer_eval (p == p); /* { dg-warning "TRUE" } */
+  __analyzer_eval (p != p); /* { dg-warning "FALSE" } */
+  __analyzer_eval (p <= p); /* { dg-warning "TRUE" } */
+  __analyzer_eval (p >= p); /* { dg-warning "TRUE" } */
+  __analyzer_eval (p < p); /* { dg-warning "FALSE" } */
+  __analyzer_eval (p > p); /* { dg-warning "FALSE" } */
+
+  free (p);
+  free (q);
+  // TODO: mark freed memory as freed
+  //__analyzer_break ();
+}
+
+/* alloca.  */
+
+void test_12 (void)
+{
+  void *p = alloca (256);
+  void *q = alloca (256);
+
+  /* alloca results should be unique.  */
+  __analyzer_eval (p == q); /* { dg-warning "FALSE" } */
+
+  // FIXME: complain about uses of poisoned values
+}
+
+/* Use of uninit value.  */
+int test_12a (void)
+{
+  int i;
+  return i; // FIXME: do we see the return stmt?
+}
+
+void test_12b (void *p, void *q)
+{
+  __analyzer_eval (p == q); /* { dg-warning "UNKNOWN" } */
+}
+
+int test_12c (void)
+{
+  int i;
+  int j;
+
+  j = i; // FIXME: should complain about this
+
+  return j;
+}
+
+struct coord
+{
+  int x;
+  int y;
+};
+
+int test_12d (struct coord c)
+{
+  struct coord d;
+  d = c;
+  __analyzer_eval (d.x == c.x); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  /* TODO(xfail): c and d share the same unknown value of type "coord", but
+     attempts to access the fields lead to different unknown values.  */
+
+  __analyzer_eval (d.y == c.y); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  // TODO(xfail): likewise
+
+  __analyzer_eval (d.x == d.y); /* { dg-warning "UNKNOWN" } */
+  /* d and c share an unknown value of type "struct coord".
+     But d.x and d.y should be different unknown values (although they inherit
+     from d's region).  */
+}
+
+/* Nested structs.  */
+
+struct outer
+{
+  struct middle {
+    struct inner {
+      float f;
+    } in;
+  } mid;
+};
+
+void test_13 (struct outer *o)
+{
+  __analyzer_eval (o->mid.in.f == 0.f); /* { dg-warning "UNKNOWN" } */
+  o->mid.in.f = 0.f;
+  __analyzer_eval (o->mid.in.f == 0.f); /* { dg-warning "TRUE" } */
+}
+
+void test_14 (struct outer o)
+{
+  __analyzer_eval (o.mid.in.f == 0.f); /* { dg-warning "UNKNOWN" } */
+  o.mid.in.f = 0.f;
+  __analyzer_eval (o.mid.in.f == 0.f); /* { dg-warning "TRUE" } */
+}
+
+void test_15 (const char *str)
+{
+  char ch = str[0];
+  __analyzer_eval (ch == 'a'); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (ch == str[0]); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  // TODO(xfail)
+
+  ch = 'a';
+  __analyzer_eval (ch == 'a'); /* { dg-warning "TRUE" } */
+  __analyzer_eval (str[0] == 'a'); /* { dg-warning "UNKNOWN" } */
+}
+
+void test_16 (void)
+{
+  const char *msg = "hello world";
+
+  __analyzer_eval (msg != NULL); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (msg[0] == 'h'); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  // TODO(xfail)
+
+  __analyzer_eval (msg[1] == 'e'); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  // TODO(xfail)
+
+  __analyzer_eval (strlen (msg) == 11); /* { dg-warning "TRUE" } */
+}
+
+static const char *__attribute__((noinline))
+get_hello_world (void)
+{
+  return "hello world";
+}
+
+void test_16_alt (void)
+{
+  const char *msg = get_hello_world ();
+
+  __analyzer_eval (msg != NULL); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (msg[0] == 'h'); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  // TODO(xfail)
+
+  __analyzer_eval (msg[1] == 'e'); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  // TODO(xfail)
+
+  __analyzer_eval (strlen (msg) == 11); /* { dg-warning "TRUE" } */
+}
+
+void test_16a (const char *msg)
+{
+  __analyzer_eval (strlen (msg) == 11); /* { dg-warning "UNKNOWN" } */
+}
+
+void test_16b (const char *msg)
+{
+  __analyzer_eval (strlen (msg) == strlen (msg)); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  // TODO(xfail)
+}
+
+extern int unknown_result (void);
+
+void test_16c (int i)
+{
+  int j;
+
+  j = i;
+  __analyzer_eval (j == i); /* { dg-warning "TRUE" } */
+
+  j = unknown_result ();
+  __analyzer_eval (j == i); /* { dg-warning "UNKNOWN" } */
+}
+
+void test_16c_a (void)
+{
+  int i, j;
+
+  i = unknown_result ();
+  j = unknown_result ();
+  __analyzer_eval (i == j); /* { dg-warning "UNKNOWN" } */
+}
+
+int global_int_16d;
+
+void test_16d (int i)
+{
+  global_int_16d = i;
+  __analyzer_eval (global_int_16d == i); /* { dg-warning "TRUE" } */
+
+  global_int_16d = unknown_result ();
+  __analyzer_eval (global_int_16d == i); /* { dg-warning "UNKNOWN" } */
+}
+
+extern void might_write_to (int *);
+
+void test_16e (int i)
+{
+  int j;
+
+  j = i;
+  __analyzer_eval (j == i); /* { dg-warning "TRUE" } */
+
+  might_write_to (&j);
+  __analyzer_eval (j == i); /* { dg-warning "UNKNOWN" "" { xfail *-*-* } } */
+  /* { dg-warning "TRUE" "" { target *-*-* } .-1 } */
+  // TODO(xfail)
+}
+
+/* TODO: and more complicated graph-like examples, where anything that's
+   reachable from the pointer might be modified.  */
+
+void test_17 (int i)
+{
+  int j = 42;
+  __analyzer_eval (j == 42); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (i == j); /* { dg-warning "UNKNOWN" } */
+  i = j;
+  __analyzer_eval (i == j); /* { dg-warning "TRUE" } */
+}
+
+void test_18 (int i)
+{
+  int j;
+  __analyzer_eval (i == 42); /* { dg-warning "UNKNOWN" } */
+
+  j = i;
+
+  __analyzer_eval (i == j); /* { dg-warning "TRUE" } */
+  __analyzer_eval (i >= j); /* { dg-warning "TRUE" } */
+  __analyzer_eval (i <= j); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (i != j); /* { dg-warning "FALSE" } */
+  __analyzer_eval (i > j); /* { dg-warning "FALSE" } */
+  __analyzer_eval (i < j); /* { dg-warning "FALSE" } */
+}
+
+void test_19 (void)
+{
+  int i, j;
+  /* Compare two uninitialized locals.  */
+    __analyzer_eval (i == j); /* { dg-warning "UNKNOWN" } */
+}
+
+void test_20 (int i, int j)
+{
+  __analyzer_eval (i + 1); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (i + j); /* { dg-warning "UNKNOWN" } */
+
+  __analyzer_eval (i - 1); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (i - j); /* { dg-warning "UNKNOWN" } */
+
+  __analyzer_eval (i * 2); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (i * j); /* { dg-warning "UNKNOWN" } */
+
+  __analyzer_eval (i / 2); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (i / j); /* { dg-warning "UNKNOWN" } */
+
+  __analyzer_eval (i % 2); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (i % j); /* { dg-warning "UNKNOWN" } */
+
+  __analyzer_eval (i & 1); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (i & j); /* { dg-warning "UNKNOWN" } */
+
+  __analyzer_eval (i | 1); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (i | j); /* { dg-warning "UNKNOWN" } */
+
+  __analyzer_eval (i ^ 1); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (i ^ j); /* { dg-warning "UNKNOWN" } */
+
+  __analyzer_eval (i >> 1); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (i >> j); /* { dg-warning "UNKNOWN" } */
+
+  __analyzer_eval (i << 1); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (i << j); /* { dg-warning "UNKNOWN" } */
+
+  __analyzer_eval (i && 0); /* { dg-warning "FALSE" } */
+  __analyzer_eval (i && 1); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (i && j); /* { dg-warning "UNKNOWN" } */
+
+  __analyzer_eval (i || 0); /* { dg-warning "UNKNOWN" } */
+
+  __analyzer_eval (i || 1); /* { dg-warning "TRUE" } */
+  __analyzer_eval (i || j); /* { dg-warning "UNKNOWN" } */
+
+  __analyzer_eval (~i); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (-i); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (+i); /* { dg-warning "UNKNOWN" } */
+
+  /* Anything added above should be added to the next function also.  */
+}
+
+/* As above, but where the values are known due to the region model,
+   but not known to GCC's regular optimizations (folding and SSA).  */
+
+void test_21 (void)
+{
+  int i, j, zero;
+  int *pi = &i;
+  int *pj = &j;
+  int *pzero = &zero;
+  *pi = 5;
+  *pj = 3;
+  *pzero = 0;
+
+  __analyzer_eval (i + j == 8); /* { dg-warning "TRUE" } */
+  __analyzer_eval (i - j == 2); /* { dg-warning "TRUE" } */
+  __analyzer_eval (i * j == 15); /* { dg-warning "TRUE" } */
+  __analyzer_eval (i / j == 1); /* { dg-warning "TRUE" } */
+  __analyzer_eval (i % j == 2); /* { dg-warning "TRUE" } */
+
+  /* Division by zero.  */
+  // TODO: should we warn for this?
+  __analyzer_eval (i / zero); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (i % zero); /* { dg-warning "UNKNOWN" } */
+
+  __analyzer_eval ((i & 1) == (5 & 1)); /* { dg-warning "TRUE" } */
+  __analyzer_eval ((i & j) == (5 & 3)); /* { dg-warning "TRUE" } */
+  __analyzer_eval ((i | 1) == (5 | 1)); /* { dg-warning "TRUE" } */
+  __analyzer_eval ((i | j) == (5 | 3)); /* { dg-warning "TRUE" } */
+  __analyzer_eval ((i ^ 1) == (5 ^ 1)); /* { dg-warning "TRUE" } */
+  __analyzer_eval ((i ^ j) == (5 ^ 3)); /* { dg-warning "TRUE" } */
+  __analyzer_eval ((i >> 1) == (5 >> 1)); /* { dg-warning "TRUE" } */
+  __analyzer_eval ((i >> j) == (5 >> 3)); /* { dg-warning "TRUE" } */
+  __analyzer_eval ((i << 1) == (5 << 1)); /* { dg-warning "TRUE" } */
+  __analyzer_eval ((i << j) == (5 << 3)); /* { dg-warning "TRUE" } */
+  __analyzer_eval (i && 0); /* { dg-warning "FALSE" } */
+  __analyzer_eval (i && 1); /* { dg-warning "TRUE" } */
+  __analyzer_eval (i && j); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (i || 0); /* { dg-warning "TRUE" } */
+  __analyzer_eval (i || 1); /* { dg-warning "TRUE" } */
+  __analyzer_eval (i || j); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (~i == ~5); /* { dg-warning "TRUE" } */
+  __analyzer_eval (-i == -5); /* { dg-warning "TRUE" } */
+  __analyzer_eval (+i == +5); /* { dg-warning "TRUE" } */
+}
+
+void test_22 (int i, int j)
+{
+  __analyzer_eval (i + j == i + j); /* { dg-warning "TRUE" } */
+  // FIXME: this is getting folded; can we build a non-folded equivalent?
+}
+
+void test_23 (struct foo *f, struct foo *g)
+{
+  int i, j, k;
+  i = f->i + g->i;
+  j = f->i + g->i;
+  k = f->i * g->i;
+  __analyzer_eval (i == j); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  /* TODO(xfail): we'd need to record that the two unknown values are both
+     the sum of the two unknown input values (and thus are the same); not
+     yet sure if we want arbitrary expression trees in the representation
+     (analysis termination concerns).  */
+
+  __analyzer_eval (i == k); /* { dg-warning "UNKNOWN" } */
+}
+
+void test_24 (struct foo *f)
+{
+  struct foo g;
+  g.i = 42;
+  __analyzer_eval (g.i == 42); /* { dg-warning "TRUE" } */
+
+  /* Overwriting a whole struct should invalidate our knowledge
+     about fields within it.  */
+  g = *f;
+  __analyzer_eval (g.i == 42); /* { dg-warning "UNKNOWN" "" { xfail *-*-* } } */
+  /* { dg-warning "TRUE" "" { target *-*-* } .-1 } */
+  // TODO(xfail)
+}
+
+void test_25 (struct foo *f)
+{
+  struct foo g;
+  g.i = 42;
+  f->i = 43;
+  __analyzer_eval (f->i == 43); /* { dg-warning "TRUE" } */
+  __analyzer_eval (g.i == 42); /* { dg-warning "TRUE" } */
+
+  /* Overwriting a whole struct where we know things about the
+     source value should update our knowledge about fields within
+     the dest value.  */
+  g = *f;
+  __analyzer_eval (g.i == 43); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "FALSE" "" { target *-*-* } .-1 } */
+  // TODO(xfail)
+}
+
+void test_26 (struct coord *p, struct coord *q)
+{
+  p->x = 42;
+  q->y = 17;
+  __analyzer_eval (p->x == 42); /* { dg-warning "TRUE" } */
+  __analyzer_eval (p->y); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (q->x); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (q->y == 17); /* { dg-warning "TRUE" } */
+
+  /* Overwriting a whole struct where we know some things about the
+     source value should update our knowledge about fields within
+     the dest value.  */
+  *p = *q;
+  __analyzer_eval (p->x); /* { dg-warning "UNKNOWN" "" { xfail *-*-* } } */
+  /* { dg-warning "TRUE" "" { target *-*-* } .-1 } */
+  // TODO(xfail): should have been overwritten
+  __analyzer_eval (p->y == 17); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  // TODO(xfail): should have been overwritten with q->y
+
+  __analyzer_eval (q->x); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (q->y == 17); /* { dg-warning "TRUE" } */
+}
+
+void test_27 (struct coord *p)
+{
+  memset (p, 0, sizeof (struct coord));
+  __analyzer_eval (p->x == 0); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  // TODO(xfail):
+  __analyzer_eval (p->y == 0); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  // TODO(xfail):
+}
+
+void test_28 (struct coord *p)
+{
+  memset (p, 0, sizeof (struct coord) * 10);
+  __analyzer_eval (p[0].x == 0); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  // TODO(xfail):
+  __analyzer_eval (p[0].y == 0); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  // TODO(xfail):
+
+  __analyzer_eval (p[9].x == 0); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  // TODO(xfail):
+  __analyzer_eval (p[9].y == 0); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  // TODO(xfail):
+
+  __analyzer_eval (p[10].x == 0); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (p[10].y == 0); /* { dg-warning "UNKNOWN" } */
+}
+
+void test_29 (struct coord *p)
+{
+  struct coord *q;
+
+  p[0].x = 100024;
+  p[0].y = 100025;
+
+  p[7].x = 107024;
+  p[7].y = 107025;
+
+  p[9].x = 109024;
+  p[9].y = 109025;
+
+  __analyzer_eval (p[0].x == 100024); /* { dg-warning "TRUE" } */
+  __analyzer_eval (p[0].y == 100025); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (p[7].x == 107024); /* { dg-warning "TRUE" } */
+  __analyzer_eval (p[7].y == 107025); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (p[9].x == 109024); /* { dg-warning "TRUE" } */
+  __analyzer_eval (p[9].y == 109025); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (p[10].x == 0); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (p[10].y == 0); /* { dg-warning "UNKNOWN" } */
+
+  q = &p[7];
+
+  __analyzer_eval (q->x == 107024); /* { dg-warning "TRUE" } */
+  __analyzer_eval (q->y == 107025); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (q[2].x == 109024); /* { dg-warning "TRUE" } */
+  __analyzer_eval (q[2].y == 109025); /* { dg-warning "TRUE" } */
+
+  q += 2;
+
+  __analyzer_eval (q->x == 109024); /* { dg-warning "TRUE" } */
+  __analyzer_eval (q->y == 109025); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (q[-2].x == 107024); /* { dg-warning "TRUE" } */
+  __analyzer_eval (q[-2].y == 107025); /* { dg-warning "TRUE" } */
+
+  q -= 2;
+
+  __analyzer_eval (q->x == 107024); /* { dg-warning "TRUE" } */
+  __analyzer_eval (q->y == 107025); /* { dg-warning "TRUE" } */
+}
+
+void test_29a (struct coord p[])
+{
+  struct coord *q;
+
+  p[0].x = 100024;
+  p[0].y = 100025;
+
+  p[7].x = 107024;
+  p[7].y = 107025;
+
+  p[9].x = 109024;
+  p[9].y = 109025;
+
+  __analyzer_eval (p[0].x == 100024); /* { dg-warning "TRUE" } */
+  __analyzer_eval (p[0].y == 100025); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (p[7].x == 107024); /* { dg-warning "TRUE" } */
+  __analyzer_eval (p[7].y == 107025); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (p[9].x == 109024); /* { dg-warning "TRUE" } */
+  __analyzer_eval (p[9].y == 109025); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (p[10].x == 0); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (p[10].y == 0); /* { dg-warning "UNKNOWN" } */
+
+  q = &p[7];
+
+  __analyzer_eval (q->x == 107024); /* { dg-warning "TRUE" } */
+  __analyzer_eval (q->y == 107025); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (q[2].x == 109024); /* { dg-warning "TRUE" } */
+  __analyzer_eval (q[2].y == 109025); /* { dg-warning "TRUE" } */
+
+  q += 2;
+
+  __analyzer_eval (q->x == 109024); /* { dg-warning "TRUE" } */
+  __analyzer_eval (q->y == 109025); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (q[-2].x == 107024); /* { dg-warning "TRUE" } */
+  __analyzer_eval (q[-2].y == 107025); /* { dg-warning "TRUE" } */
+
+  q -= 2;
+
+  __analyzer_eval (q->x == 107024); /* { dg-warning "TRUE" } */
+  __analyzer_eval (q->y == 107025); /* { dg-warning "TRUE" } */
+}
+
+void test_29b (void)
+{
+  struct coord p[11];
+  struct coord *q;
+
+  p[0].x = 100024;
+  p[0].y = 100025;
+
+  p[7].x = 107024;
+  p[7].y = 107025;
+
+  p[9].x = 109024;
+  p[9].y = 109025;
+
+  __analyzer_eval (p[0].x == 100024); /* { dg-warning "TRUE" } */
+  __analyzer_eval (p[0].y == 100025); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (p[7].x == 107024); /* { dg-warning "TRUE" } */
+  __analyzer_eval (p[7].y == 107025); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (p[9].x == 109024); /* { dg-warning "TRUE" } */
+  __analyzer_eval (p[9].y == 109025); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (p[10].x == 0); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (p[10].y == 0); /* { dg-warning "UNKNOWN" } */
+
+  q = &p[7];
+
+  __analyzer_eval (q->x == 107024); /* { dg-warning "TRUE" } */
+  __analyzer_eval (q->y == 107025); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (q[2].x == 109024); /* { dg-warning "TRUE" } */
+  __analyzer_eval (q[2].y == 109025); /* { dg-warning "TRUE" } */
+
+  q += 2;
+
+  __analyzer_eval (q->x == 109024); /* { dg-warning "TRUE" } */
+  __analyzer_eval (q->y == 109025); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (q[-2].x == 107024); /* { dg-warning "TRUE" } */
+  __analyzer_eval (q[-2].y == 107025); /* { dg-warning "TRUE" } */
+
+  q -= 2;
+
+  __analyzer_eval (q->x == 107024); /* { dg-warning "TRUE" } */
+  __analyzer_eval (q->y == 107025); /* { dg-warning "TRUE" } */
+}
+
+void test_29c (int len)
+{
+  struct coord p[len];
+  struct coord *q;
+
+  p[0].x = 100024;
+  p[0].y = 100025;
+
+  p[7].x = 107024;
+  p[7].y = 107025;
+
+  p[9].x = 109024;
+  p[9].y = 109025;
+
+  __analyzer_eval (p[0].x == 100024); /* { dg-warning "TRUE" } */
+  __analyzer_eval (p[0].y == 100025); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (p[7].x == 107024); /* { dg-warning "TRUE" } */
+  __analyzer_eval (p[7].y == 107025); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (p[9].x == 109024); /* { dg-warning "TRUE" } */
+  __analyzer_eval (p[9].y == 109025); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (p[10].x == 0); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (p[10].y == 0); /* { dg-warning "UNKNOWN" } */
+
+  q = &p[7];
+
+  __analyzer_eval (q->x == 107024); /* { dg-warning "TRUE" } */
+  __analyzer_eval (q->y == 107025); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (q[2].x == 109024); /* { dg-warning "TRUE" } */
+  __analyzer_eval (q[2].y == 109025); /* { dg-warning "TRUE" } */
+
+  q += 2;
+
+  __analyzer_eval (q->x == 109024); /* { dg-warning "TRUE" } */
+  __analyzer_eval (q->y == 109025); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (q[-2].x == 107024); /* { dg-warning "TRUE" } */
+  __analyzer_eval (q[-2].y == 107025); /* { dg-warning "TRUE" } */
+
+  q -= 2;
+
+  __analyzer_eval (q->x == 107024); /* { dg-warning "TRUE" } */
+  __analyzer_eval (q->y == 107025); /* { dg-warning "TRUE" } */
+}
+
+void test_30 (void *ptr)
+{
+  struct coord *p = (struct coord *)ptr;
+  struct coord *q = (struct coord *)ptr;
+
+  p->x = 42;
+
+  __analyzer_eval (p->x == 42); /* { dg-warning "TRUE" } */
+  __analyzer_eval (q->x == 42); /* { dg-warning "TRUE" } */
+}
+
+void test_31 (unsigned i)
+{
+  int j, k;
+
+  j = i < 100 ? i : 100; /* MIN_EXPR.  */
+  k = i < 100 ? 100 : i; /* MAX_EXPR.  */
+}
+
+enum color
+{
+  RED,
+  GREEN,
+  BLUE
+};
+
+void test_32 (enum color c)
+{
+  __analyzer_eval (c == GREEN); /* { dg-warning "UNKNOWN" } */
+
+  c = RED;
+
+  __analyzer_eval (c == RED); /* { dg-warning "TRUE" } */
+  __analyzer_eval (c == GREEN); /* { dg-warning "FALSE" } */
+}
+
+void test_33 (void)
+{
+  static int s;
+
+  __analyzer_eval (s == 42); /* { dg-warning "UNKNOWN" } */
+
+  s = 42;
+
+  __analyzer_eval (s == 42); /* { dg-warning "TRUE" } */
+}
+
+static int __attribute__((noinline))
+only_called_by_test_34 (int parm)
+{
+  __analyzer_eval (parm == 42); /* { dg-warning "TRUE" } */
+
+  return parm * 2;
+}
+
+void test_34 (void)
+{
+  int result = only_called_by_test_34 (42);
+  __analyzer_eval (result == 84); /* { dg-warning "TRUE" } */
+}
+
+void test_35 (int i, int j)
+{
+  __analyzer_eval (&i == &i); /* { dg-warning "TRUE" } */
+  __analyzer_eval (&i != &j); /* { dg-warning "TRUE" } */
+}
+
+static void __attribute__((noinline))
+write_through_ptr (int *dst, int val)
+{
+  *dst = val;
+}
+
+void test_36 (int i)
+{
+  __analyzer_eval (i == 42); /* { dg-warning "UNKNOWN" } */
+
+  write_through_ptr (&i, 42);
+
+  __analyzer_eval (i == 42); /* { dg-warning "TRUE" } */
+}
+
+/* Read through uninitialized pointer.  */
+
+int test_37 (void)
+{
+  int *ptr;
+  return *ptr; /* { dg-warning "use of uninitialized value 'ptr'" } */
+}
+
+/* Write through uninitialized pointer.  */
+
+void test_37a (int i)
+{
+  int *ptr;
+  *ptr = i; /* { dg-warning "use of uninitialized value 'ptr'" } */
+}
+
+// TODO: the various other ptr deref poisonings
+
+/* Read through NULL pointer.  */
+
+int test_38 (void)
+{
+  int *ptr = NULL;
+  return *ptr; /* { dg-warning "dereference of NULL 'ptr'" } */
+}
+
+/* Write through NULL pointer.  */
+
+int test_38a (int i)
+{
+  int *ptr = NULL;
+  *ptr = i; /* { dg-warning "dereference of NULL 'ptr'" } */
+}
+
+/* Read through non-NULL constant pointer.  */
+
+int test_39 (void)
+{
+  int *ptr = (int *)0x1000;
+  return *ptr;
+}
+
+int test_40 (int flag)
+{
+  int i;
+  if (flag)
+    i = 43;
+  else
+    i = 17;
+
+  /* With state-merging, we lose the relationship between 'flag' and 'i'.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+  if (flag)
+    __analyzer_eval (i == 43); /* { dg-warning "UNKNOWN" } */
+  else
+    __analyzer_eval (i == 17); /* { dg-warning "UNKNOWN" } */
+}
+
+struct link
+{
+  struct link *next;
+  int f;
+};
+
+/* Traversing a singly-linked list.  */
+
+void foo (struct link *in)
+{
+  struct link a;
+  struct link b;
+  struct link c;
+  a.next = &b;
+  b.next = &c;
+  in->next = &a;
+  c.f = 42;
+  __analyzer_eval (in->next->next->next->f == 42);  /* { dg-warning "TRUE" } */
+}
+
+union u
+{
+  int i;
+  int *ptr;
+};
+
+void test_41 (void)
+{
+  union u u;
+  u.i = 42;
+  __analyzer_eval (u.i == 42); /* { dg-warning "TRUE" } */
+  __analyzer_eval (u.ptr == NULL); /* { dg-warning "UNKNOWN" } */
+
+  /* Writes to a union member should invalidate knowledge about other members.  */
+  u.ptr = NULL;
+  __analyzer_eval (u.ptr == NULL); /* { dg-warning "TRUE" } */
+  __analyzer_eval (u.i == 42); /* { dg-warning "UNKNOWN" } */
+}
+
+void test_42 (void)
+{
+  int i;
+  float f;
+  i = 42;
+  f = i;
+  __analyzer_eval (f == 42.0); /* { dg-warning "TRUE" } */
+}
+
+void test_43 (void)
+{
+  int i;
+  float f;
+  f = 42.0f;
+  i = f;
+  __analyzer_eval (i == 42);  /* { dg-warning "TRUE" } */
+}
+
+struct sbits
+{
+  int b0 : 1;
+  int b123 : 3;
+  int b456 : 3;
+  int b7 : 1;
+};
+
+void test_44 (void)
+{
+  struct sbits bits;
+  bits.b0 = 1;
+  __analyzer_eval (bits.b0 == 1); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "FALSE" "" { target *-*-* } .-1 } */
+  // TODO(xfail): ^^^^
+
+  bits.b456 = 5;
+  __analyzer_eval (bits.b456 == 5); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "FALSE" "" { target *-*-* } .-1 } */
+  // TODO(xfail): ^^^^
+};
+
+struct ubits
+{
+  unsigned int b0 : 1;
+  unsigned int b123 : 3;
+  unsigned int b456 : 3;
+  unsigned int b7 : 1;
+};
+
+/* FIXME: this requires BIT_FIELD_REF to work.  */
+
+void test_45 (void)
+{
+  struct ubits bits;
+  bits.b0 = 1;
+  __analyzer_eval (bits.b0 == 1); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  // TODO(xfail): ^^^^
+
+  bits.b456 = 5;
+  __analyzer_eval (bits.b456 == 5); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  // TODO(xfail): ^^^^
+};
+
+extern const char *char_ptr;
+
+int test_46 (void)
+{
+  if (strcmp("literal", char_ptr))
+    return 1;
+  return 0;
+}
+
+char test_47 (void)
+{
+  static const char* my_version = "1.1.3";
+  return my_version[0];
+}
+
+unsigned test_48 (unsigned char *p, unsigned char *q)
+{
+  return (unsigned int)(p - q);
+}
+
+typedef struct {
+  const char *filename;
+  short lineno;
+} loc;
+
+static loc loc_last;
+
+void test_49 (void)
+{
+  loc_last = __extension__(loc) { "", 328 };
+  loc_last = __extension__(loc) { "", 333 };
+}
+
+void test_50 (void *p, void *q)
+{
+  __analyzer_eval (p == q); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (p == p); /* { dg-warning "TRUE" } */
+  __analyzer_eval (q == q); /* { dg-warning "TRUE" } */
+  __analyzer_eval (p == (struct coord *)p); /* { dg-warning "TRUE" } */
+  __analyzer_eval (p == (const struct coord *)p); /* { dg-warning "TRUE" } */
+
+  struct coord *cp = (struct coord *)p;
+  __analyzer_eval (p == cp); /* { dg-warning "TRUE" } */
+
+  struct coord *cq = (struct coord *)q;
+  __analyzer_eval (q == cq); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (cp == cq); /* { dg-warning "UNKNOWN" } */
+}
+
+void test_51 (struct coord c)
+{
+  struct coord d;
+  memcpy (&d, &c, sizeof (struct coord));
+  __analyzer_eval (c.x == d.x); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  __analyzer_eval (c.y == d.y); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+}
+
+struct big
+{
+  int ia[1024];  
+};
+
+void test_52 (struct big b)
+{
+  struct big d;
+  memcpy (&d, &b, sizeof (struct big));
+  __analyzer_eval (b.ia[0] == d.ia[0]); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-10.c b/gcc/testsuite/gcc.dg/analyzer/data-model-10.c
new file mode 100644
index 0000000..c261edc
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-10.c
@@ -0,0 +1,17 @@
+#include <stdlib.h>
+
+struct foo
+{
+  char **m_f;
+};
+
+struct foo *
+test (void)
+{
+  struct foo *new_table = (struct foo *) malloc(sizeof(struct foo));
+  if (!new_table)
+    return NULL;
+  new_table->m_f = (char **)malloc(sizeof(char **));
+  *new_table->m_f = NULL; /* { dg-warning "dereference of possibly-NULL '<unknown>'" } */ // FIXME: something better than "unknown" here
+  return new_table;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-11.c b/gcc/testsuite/gcc.dg/analyzer/data-model-11.c
new file mode 100644
index 0000000..2766324
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-11.c
@@ -0,0 +1,6 @@
+int test (void)
+{
+  unsigned char *s = "abc";
+  char *t = "xyz";
+  return s[1] + t[1];
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-12.c b/gcc/testsuite/gcc.dg/analyzer/data-model-12.c
new file mode 100644
index 0000000..653b7ad
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-12.c
@@ -0,0 +1,13 @@
+/* Mismatching decl of foo.  */
+
+int foo ();
+
+int bar (void)
+{
+  return foo() + 1;
+}
+
+int foo (int x, int y)
+{
+  return x * y;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-13.c b/gcc/testsuite/gcc.dg/analyzer/data-model-13.c
new file mode 100644
index 0000000..e7cb845
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-13.c
@@ -0,0 +1,21 @@
+#include <stdlib.h>
+
+union
+{
+  void *ptr_val;
+  int int_val;
+} global_union;
+
+void test_1 (void)
+{
+  global_union.ptr_val = malloc (1024);
+}
+
+void test_2 (void)
+{
+  global_union.ptr_val = malloc (1024); /* { dg-message "allocated here" } */
+  global_union.int_val = 0;
+} /* { dg-warning "leak of '<unknown>' " } */
+/* TODO: something better than "<unknown>".  */
+/* TODO: better location for the leak.  */
+
diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-14.c b/gcc/testsuite/gcc.dg/analyzer/data-model-14.c
new file mode 100644
index 0000000..f9bb540
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-14.c
@@ -0,0 +1,24 @@
+/* FIXME: we shouldn't need this.  */
+/* { dg-additional-options "-fanalyzer-fine-grained" } */
+
+#include <stdlib.h>
+
+void *global_ptr;
+
+void test_1 (int i)
+{
+  global_ptr = malloc (1024); /* { dg-message "allocated here" } */
+  *(int *)&global_ptr = i; /* { dg-warning "leak of '<unknown>'" } */
+  // TODO: something better than "<unknown>" here ^^^
+}
+
+void test_2 (int i)
+{
+  void *p = malloc (1024); /* { dg-message "allocated here" "" { xfail *-*-* } } */
+  // TODO(xfail)
+  global_ptr = p;
+  *(int *)&p = i;
+  p = global_ptr;
+  free (p);
+  free (global_ptr); /* { dg-warning "double-'free' of 'p'" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-15.c b/gcc/testsuite/gcc.dg/analyzer/data-model-15.c
new file mode 100644
index 0000000..12e84a1
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-15.c
@@ -0,0 +1,34 @@
+#include <string.h>
+
+struct coord
+{
+  double x;
+  double y;
+  double z;
+};
+
+struct tri {
+  struct coord verts[3];
+};
+
+double test_1 (void)
+{
+  struct tri t;
+  memset (&t, 0, sizeof (struct tri));
+  return t.verts[1].y;
+}
+
+int test_2 (const struct coord *c1, const struct coord *c2, double r_squared)
+{
+  double dx = c1->x - c2->x;
+  double dy = c1->y - c2->y;
+  double dz = c1->z - c2->z;
+  return (dx * dx) + (dy * dy) + (dz * dz) <= r_squared;
+}
+
+int test_3 (const struct coord *c1, const struct coord *c2, struct coord *out)
+{
+  out->x = c1->x + c2->x;
+  out->y = c1->y + c2->y;
+  out->z = c1->z + c2->z;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-16.c b/gcc/testsuite/gcc.dg/analyzer/data-model-16.c
new file mode 100644
index 0000000..3bfdbdd
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-16.c
@@ -0,0 +1,50 @@
+/* Labels as values.  */
+
+extern void foo (void);
+
+void *x, *y, *z;
+
+void test (void)
+{
+ label0:
+  foo ();
+ label1:
+  foo ();
+ label2:
+  foo ();
+
+  x = &&label0;
+  y = &&label1;
+  z = &&label2;
+
+  __analyzer_eval (x == x); /* { dg-warning "TRUE" } */
+  __analyzer_eval (x == y); /* { dg-warning "FALSE" } */
+}
+
+void test_2 (int i)
+{
+  static void *array[] = { &&label0, &&label1, &&label2 };
+  goto *array[i];
+
+ label0:
+  foo ();
+ label1:
+  foo ();
+ label2:
+  foo ();
+}
+
+void test_3 (int i)
+{
+  static const int array[] = { &&label0 - &&label0,
+			       &&label1 - &&label0,
+			       &&label2 - &&label0 };
+  goto *(&&label0 + array[i]);
+
+ label0:
+  foo ();
+ label1:
+  foo ();
+ label2:
+  foo ();
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-17.c b/gcc/testsuite/gcc.dg/analyzer/data-model-17.c
new file mode 100644
index 0000000..d50b84a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-17.c
@@ -0,0 +1,20 @@
+typedef struct foo {} foo_t;
+
+typedef void (*func_t)(foo_t *s);
+
+void cb_1 (foo_t *s);
+void cb_2 (foo_t *s);
+
+typedef struct config_s {
+  func_t func;
+} config;
+
+static const config table[2] = {
+  { cb_1 },
+  { cb_2 }
+};
+
+int deflate (foo_t *s, int which)
+{
+  (*(table[which].func))(s);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-18.c b/gcc/testsuite/gcc.dg/analyzer/data-model-18.c
new file mode 100644
index 0000000..88ea41d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-18.c
@@ -0,0 +1,20 @@
+void test (int *p, int i, int j)
+{
+  p[3] = 42;
+  __analyzer_eval (p[3] == 42); /* { dg-warning "TRUE" } */
+  __analyzer_eval (*(p + 3) == 42); /* { dg-warning "TRUE" } */
+  __analyzer_eval (p[i] == 42); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (p[j] == 42); /* { dg-warning "UNKNOWN" } */
+
+  //__analyzer_dump ();
+
+  p[i] = 17;
+
+  //__analyzer_dump ();
+
+  __analyzer_eval (p[3] == 42); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (p[i] == 17); /* { dg-warning "TRUE" } */
+  __analyzer_eval (p[j] == 17); /* { dg-warning "UNKNOWN" "" { xfail *-*-* } } */
+  /* { dg-bogus "TRUE" "" { xfail *-*-* } .-1 } */
+  // FIXME(xfails) ^^^
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-19.c b/gcc/testsuite/gcc.dg/analyzer/data-model-19.c
new file mode 100644
index 0000000..6997838
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-19.c
@@ -0,0 +1,31 @@
+/* { dg-additional-options "-fgimple" } */
+
+typedef long long int i64;
+
+int __GIMPLE (ssa)
+test (i64 * pA, i64 iB)
+{
+  __complex__ long long int D_37702;
+  int D_37701;
+  long long int _1;
+  long long int _2;
+  long long int _3;
+  _Bool _4;
+  __complex__ long long int _8;
+  int _10;
+
+  __BB(2):
+  _1 = __MEM <i64> (pA_6(D));
+  _8 = .ADD_OVERFLOW (_1, iB_7(D));
+  _2 = __real _8;
+  __MEM <i64> (pA_6(D)) = _2;
+  _3 = __imag _8;
+  _4 = (_Bool) _3;
+  _10 = (int) _4;
+  goto __BB3;
+
+  __BB(3):
+L0:
+  return _10;
+
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-2.c b/gcc/testsuite/gcc.dg/analyzer/data-model-2.c
new file mode 100644
index 0000000..d5be03b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-2.c
@@ -0,0 +1,13 @@
+/* { dg-additional-options "-O2" } */
+/* TODO:is there a way to automatically run the tests on various
+   optimizations levels, and with/without debuginfo, rather than
+   hardcoding options?  Adapt from torture .exp, presumably.  */
+
+
+#include <stdlib.h>
+#include <string.h>
+
+int test_1 (void)
+{
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-3.c b/gcc/testsuite/gcc.dg/analyzer/data-model-3.c
new file mode 100644
index 0000000..3d572eb
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-3.c
@@ -0,0 +1,15 @@
+/* { dg-additional-options "-O2" } */
+/* TODO:is there a way to automatically run the tests on various
+   optimizations levels, and with/without debuginfo, rather than
+   hardcoding options?  Adapt from torture .exp, presumably.  */
+
+#include <stdio.h>
+int
+main ()
+{
+  FILE *f = fopen ("conftest.out", "w");
+  return ferror (f) || fclose (f) != 0;
+
+  ;
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-4.c b/gcc/testsuite/gcc.dg/analyzer/data-model-4.c
new file mode 100644
index 0000000..33f9087
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-4.c
@@ -0,0 +1,16 @@
+/* { dg-additional-options "-fexceptions" } */
+/* TODO:is there a way to automatically run the tests on various
+   optimizations levels, and with/without debuginfo, rather than
+   hardcoding options?  Adapt from torture .exp, presumably.  */
+
+#include <stdio.h>
+int
+main ()
+{
+  FILE *f = fopen ("conftest.out", "w");
+  return ferror (f) || fclose (f) != 0;
+
+  ;
+  return 0;
+}
+
diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-5.c b/gcc/testsuite/gcc.dg/analyzer/data-model-5.c
new file mode 100644
index 0000000..5cf3347
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-5.c
@@ -0,0 +1,100 @@
+/* A toy re-implementation of CPython's object model.  */
+
+#include <stddef.h>
+#include <string.h>
+#include <stdlib.h>
+
+typedef struct base_obj
+{
+  struct type_obj *ob_type;
+  int ob_refcnt;
+} base_obj;
+
+typedef struct type_obj
+{
+  base_obj tp_base;
+  void (*tp_dealloc) (base_obj *);
+} type_obj;
+
+typedef struct tuple_obj
+{
+  base_obj tup_base;
+  int num_elements;
+  base_obj elements[];
+} tuple_obj;
+
+typedef struct list_obj
+{
+  base_obj list_base;
+  int num_elements;
+  base_obj *elements;
+} list_obj;
+
+typedef struct string_obj
+{
+  base_obj str_base;
+  size_t str_len;
+  char str_buf[];
+} string_obj;
+
+extern void type_del (base_obj *);
+extern void tuple_del (base_obj *);
+extern void str_del (base_obj *);
+
+type_obj type_type = {
+  { &type_type, 1},
+  type_del
+};
+
+type_obj tuple_type = {
+  { &type_type, 1},
+  tuple_del
+};
+
+type_obj str_type = {
+  { &str_type, 1},
+  str_del
+};
+
+base_obj *alloc_obj (type_obj *ob_type, size_t sz)
+{
+  base_obj *obj = (base_obj *)malloc (sz);
+  if (!obj)
+    return NULL;
+  obj->ob_type = ob_type;
+  obj->ob_refcnt = 1;
+  return obj;
+}
+
+base_obj *new_string_obj (const char *str)
+{
+  //__analyzer_dump ();
+  size_t len = strlen (str);
+#if 1
+  string_obj *str_obj
+    = (string_obj *)alloc_obj (&str_type, sizeof (string_obj) + len + 1);
+#else
+  string_obj *str_obj = (string_obj *)malloc (sizeof (string_obj) + len + 1);
+  if (!str_obj)
+    return NULL;
+  str_obj->str_base.ob_type = &str_type;
+  str_obj->str_base.ob_refcnt = 1;
+#endif
+  str_obj->str_len = len; /* { dg-warning "dereference of NULL 'str_obj'" } */
+  memcpy (str_obj->str_buf, str, len);
+  str_obj->str_buf[len] = '\0';
+  return (base_obj *)str_obj;
+}
+
+void unref (base_obj *obj)
+{
+  if (--obj->ob_refcnt == 0) /* { dg-bogus "dereference of uninitialized pointer 'obj'" } */
+    obj->ob_type->tp_dealloc (obj);
+}
+
+void test_1 (const char *str)
+{
+  base_obj *obj = new_string_obj (str);
+  //__analyzer_dump();
+  unref (obj);
+} /* { dg-bogus "leak" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-5b.c b/gcc/testsuite/gcc.dg/analyzer/data-model-5b.c
new file mode 100644
index 0000000..b0203af
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-5b.c
@@ -0,0 +1,91 @@
+/* A toy re-implementation of CPython's object model.  */
+
+#include <stddef.h>
+#include <string.h>
+#include <stdlib.h>
+
+typedef struct base_obj base_obj;
+typedef struct type_obj type_obj;
+typedef struct string_obj string_obj;
+
+struct base_obj
+{
+  struct type_obj *ob_type;
+  int ob_refcnt;
+};
+
+struct type_obj
+{
+  base_obj tp_base;
+  void (*tp_dealloc) (base_obj *);
+};
+
+struct string_obj
+{
+  base_obj str_base;
+  size_t str_len;
+  char str_buf[];
+};
+
+extern void type_del (base_obj *);
+extern void str_del (base_obj *);
+
+type_obj type_type = {
+  { &type_type, 1},
+  type_del
+};
+
+type_obj str_type = {
+  { &str_type, 1},
+  str_del
+};
+
+base_obj *alloc_obj (type_obj *ob_type, size_t sz)
+{
+  base_obj *obj = (base_obj *)malloc (sz);
+  if (!obj)
+    return NULL;
+  obj->ob_type = ob_type;
+  obj->ob_refcnt = 1;
+  return obj;
+}
+
+string_obj *new_string_obj (const char *str)
+{
+  //__analyzer_dump ();
+  size_t len = strlen (str);
+#if 1
+  string_obj *str_obj
+    = (string_obj *)alloc_obj (&str_type, sizeof (string_obj) + len + 1);
+#else
+  string_obj *str_obj = (string_obj *)malloc (sizeof (string_obj) + len + 1);
+  if (!str_obj)
+    return NULL;
+  str_obj->str_base.ob_type = &str_type;
+  str_obj->str_base.ob_refcnt = 1;
+#endif
+  str_obj->str_len = len; /* { dg-warning "dereference of NULL 'str_obj'" } */
+  memcpy (str_obj->str_buf, str, len);
+  str_obj->str_buf[len] = '\0';
+  return str_obj;
+}
+
+void unref (string_obj *obj)
+{
+  //__analyzer_dump();
+  if (--obj->str_base.ob_refcnt == 0)
+    {
+      //__analyzer_dump();
+      obj->str_base.ob_type->tp_dealloc ((base_obj *)obj); /* { dg-bogus "use of uninitialized value '<unknown>'" "" { xfail *-*-* } } */
+      // TODO (xfail): not sure what's going on here
+    }
+}
+
+void test_1 (const char *str)
+{
+  string_obj *obj = new_string_obj (str);
+  //__analyzer_dump();
+  if (obj)
+    unref (obj);
+} /* { dg-bogus "leak of 'obj'" "" { xfail *-*-* } } */
+// TODO (xfail): not sure why this is treated as leaking
diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-5c.c b/gcc/testsuite/gcc.dg/analyzer/data-model-5c.c
new file mode 100644
index 0000000..1e52350
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-5c.c
@@ -0,0 +1,84 @@
+/* A toy re-implementation of CPython's object model.  */
+
+#include <stddef.h>
+#include <string.h>
+#include <stdlib.h>
+
+typedef struct base_obj base_obj;
+typedef struct type_obj type_obj;
+typedef struct string_obj string_obj;
+
+struct base_obj
+{
+  struct type_obj *ob_type;
+  int ob_refcnt;
+};
+
+struct type_obj
+{
+  base_obj tp_base;
+};
+
+struct string_obj
+{
+  base_obj str_base;
+  size_t str_len;
+  char str_buf[];
+};
+
+type_obj type_type = {
+  { &type_type, 1},
+};
+
+type_obj str_type = {
+  { &str_type, 1},
+};
+
+base_obj *alloc_obj (type_obj *ob_type, size_t sz)
+{
+  base_obj *obj = (base_obj *)malloc (sz);
+  if (!obj)
+    return NULL;
+  obj->ob_type = ob_type;
+  obj->ob_refcnt = 1;
+  return obj;
+}
+
+string_obj *new_string_obj (const char *str)
+{
+  //__analyzer_dump ();
+  size_t len = strlen (str);
+#if 1
+  string_obj *str_obj
+    = (string_obj *)alloc_obj (&str_type, sizeof (string_obj) + len + 1);
+#else
+  string_obj *str_obj = (string_obj *)malloc (sizeof (string_obj) + len + 1);
+  if (!str_obj)
+    return NULL;
+  str_obj->str_base.ob_type = &str_type;
+  str_obj->str_base.ob_refcnt = 1;
+#endif
+  str_obj->str_len = len; /* { dg-warning "dereference of NULL 'str_obj'" } */
+  memcpy (str_obj->str_buf, str, len);
+  str_obj->str_buf[len] = '\0';
+  return str_obj;
+}
+
+void unref (string_obj *obj)
+{
+  //__analyzer_dump();
+  if (--obj->str_base.ob_refcnt == 0)
+    {
+      //__analyzer_dump();
+      free (obj);
+    }
+}
+
+void test_1 (const char *str)
+{
+  string_obj *obj = new_string_obj (str);
+  //__analyzer_dump();
+  if (obj)
+    unref (obj);
+} /* { dg-bogus "leak of 'obj'" "" { xfail *-*-* } } */
+// TODO (xfail): not sure why this is treated as leaking
diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-5d.c b/gcc/testsuite/gcc.dg/analyzer/data-model-5d.c
new file mode 100644
index 0000000..50ab0ac
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-5d.c
@@ -0,0 +1,63 @@
+/* A toy re-implementation of CPython's object model.  */
+
+#include <stddef.h>
+#include <string.h>
+#include <stdlib.h>
+
+typedef struct base_obj base_obj;
+typedef struct type_obj type_obj;
+typedef struct string_obj string_obj;
+
+struct base_obj
+{
+  struct type_obj *ob_type;
+  int ob_refcnt;
+};
+
+struct type_obj
+{
+  base_obj tp_base;
+};
+
+struct string_obj
+{
+  base_obj str_base;
+  size_t str_len;
+  char str_buf[];
+};
+
+type_obj type_type = {
+  { &type_type, 1},
+};
+
+type_obj str_type = {
+  { &str_type, 1},
+};
+
+base_obj *alloc_obj (type_obj *ob_type, size_t sz)
+{
+  base_obj *obj = (base_obj *)malloc (sz);
+  if (!obj)
+    return NULL;
+  obj->ob_type = ob_type;
+  obj->ob_refcnt = 1;
+  return obj;
+}
+
+void unref (base_obj *obj)
+{
+  //__analyzer_dump();
+  if (--obj->ob_refcnt == 0)
+    free (obj);
+}
+
+void test_1 ()
+{
+  base_obj *obj = alloc_obj (&str_type, sizeof (string_obj));
+  if (obj)
+    {
+      __analyzer_dump_num_heap_regions (); /* { dg-warning "num heap regions: '1'" } */
+      unref (obj);
+      __analyzer_dump_num_heap_regions (); /* { dg-warning "num heap regions: '0'" } */
+    }
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-6.c b/gcc/testsuite/gcc.dg/analyzer/data-model-6.c
new file mode 100644
index 0000000..8b63ab2
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-6.c
@@ -0,0 +1,13 @@
+#include <stdlib.h>
+
+/* Verify that we don't accumulate state after a malloc/free pair.  */
+
+void test (void)
+{
+  void *ptr;
+  __analyzer_dump_num_heap_regions (); /* { dg-warning "num heap regions: '0'" } */
+  ptr = malloc (1024);
+  __analyzer_dump_num_heap_regions (); /* { dg-warning "num heap regions: '1'" } */
+  free (ptr);
+  __analyzer_dump_num_heap_regions (); /* { dg-warning "num heap regions: '0'" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-7.c b/gcc/testsuite/gcc.dg/analyzer/data-model-7.c
new file mode 100644
index 0000000..6c8ab0a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-7.c
@@ -0,0 +1,19 @@
+/* { dg-additional-options "-fno-analyzer-state-merge" } */
+
+int test_40 (int flag)
+{
+  int i;
+  if (flag)
+    i = 43;
+  else
+    i = 17;
+
+  /* Without state-merging, we retain the relationship between 'flag' and 'i'.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  if (flag)
+    __analyzer_eval (i == 43); /* { dg-warning "TRUE" } */
+  else
+    __analyzer_eval (i == 17); /* { dg-warning "TRUE" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-8.c b/gcc/testsuite/gcc.dg/analyzer/data-model-8.c
new file mode 100644
index 0000000..2c83c75
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-8.c
@@ -0,0 +1,24 @@
+struct base
+{
+  int i;
+};
+
+struct sub
+{
+  struct base b;
+  int j;
+};
+
+void test (void)
+{
+  struct sub s;
+  s.b.i = 3;
+  s.j = 4;
+  __analyzer_eval (s.b.i == 3); /* { dg-warning "TRUE" } */
+  __analyzer_eval (s.j == 4); /* { dg-warning "TRUE" } */
+
+  struct base *bp = (struct base *)&s;
+
+  __analyzer_eval (bp->i == 3); /* { dg-warning "TRUE"  "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-9.c b/gcc/testsuite/gcc.dg/analyzer/data-model-9.c
new file mode 100644
index 0000000..a1a46c6
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-9.c
@@ -0,0 +1,32 @@
+#include <stdlib.h>
+#include <string.h>
+
+struct foo
+{
+  int i;
+};
+
+/* TODO: verify that we know that calloc zeros its memory.  */
+
+void test_1 (void)
+{
+  struct foo *f = calloc (1, sizeof (struct foo));
+  if (f == NULL)
+    return;
+  __analyzer_eval (f->i == 0); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */
+  free (f);
+}
+
+/* TODO: verify that we know the behavior of memset.  */
+
+void test_2 (void)
+{
+  struct foo *f = malloc (sizeof (struct foo));
+  if (f == NULL)
+    return;
+  memset (f, 0, sizeof (struct foo));
+  __analyzer_eval (f->i == 0); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */
+  free (f);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-path-1.c b/gcc/testsuite/gcc.dg/analyzer/data-model-path-1.c
new file mode 100644
index 0000000..d7058ea
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-path-1.c
@@ -0,0 +1,13 @@
+#include <stddef.h>
+
+static int *__attribute__((noinline))
+callee (void)
+{
+  return NULL;
+}
+
+void test_1 (void)
+{
+  int *p = callee (); /* { dg-message "return of NULL to 'test_1' from 'callee'" } */
+  *p = 42; /* { dg-warning "dereference of NULL 'p'" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/double-free-lto-1-a.c b/gcc/testsuite/gcc.dg/analyzer/double-free-lto-1-a.c
new file mode 100644
index 0000000..61e7846
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/double-free-lto-1-a.c
@@ -0,0 +1,16 @@
+/* { dg-do link } */
+/* { dg-require-effective-target lto } */
+/* { dg-additional-options "-flto" } */
+/* { dg-additional-sources double-free-lto-1-b.c } */
+
+#include <stdlib.h>
+#include "double-free-lto-1.h"
+
+void test (void *ptr)
+{
+  calls_free (ptr); /* { dg-message "calling 'calls_free' from 'test'" } */
+  free (ptr); /* { dg-warning "double-'free' of 'ptr_.+'" } */
+  // TODO: report "ptr", rather than an SSA name
+}
+
+int main() { return 0; }
diff --git a/gcc/testsuite/gcc.dg/analyzer/double-free-lto-1-b.c b/gcc/testsuite/gcc.dg/analyzer/double-free-lto-1-b.c
new file mode 100644
index 0000000..6041e69
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/double-free-lto-1-b.c
@@ -0,0 +1,8 @@
+#include <stdlib.h>
+
+#include "double-free-lto-1.h"
+
+extern void calls_free (void *ptr)
+{
+  free (ptr); 
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/double-free-lto-1.h b/gcc/testsuite/gcc.dg/analyzer/double-free-lto-1.h
new file mode 100644
index 0000000..c5e2157
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/double-free-lto-1.h
@@ -0,0 +1 @@
+extern void calls_free (void *ptr);
diff --git a/gcc/testsuite/gcc.dg/analyzer/equivalence.c b/gcc/testsuite/gcc.dg/analyzer/equivalence.c
new file mode 100644
index 0000000..fb28660
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/equivalence.c
@@ -0,0 +1,29 @@
+void test (int p, int q, int r)
+{
+  if (p == 42)
+    {
+      __analyzer_eval (p == 42);  /* { dg-warning "TRUE" } */
+      __analyzer_eval (p != 42);  /* { dg-warning "FALSE" } */
+      if (q == 42)
+	{
+	  __analyzer_eval (p == q);  /* { dg-warning "TRUE" } */
+	}
+      else
+	{
+	  __analyzer_eval (p != q);  /* { dg-warning "TRUE" } */
+	}
+    }
+  else
+    {
+      __analyzer_eval (p == 42);  /* { dg-warning "FALSE" } */
+      __analyzer_eval (p != 42);  /* { dg-warning "TRUE" } */
+      if (q == 42)
+	{
+	  __analyzer_eval (p == q);  /* { dg-warning "FALSE" } */
+	}
+      else
+	{
+	  __analyzer_eval (p == q);  /* { dg-warning "UNKNOWN" } */
+	}
+    }
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/explode-1.c b/gcc/testsuite/gcc.dg/analyzer/explode-1.c
new file mode 100644
index 0000000..6b62e8e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/explode-1.c
@@ -0,0 +1,60 @@
+/* { dg-additional-options "-Wno-analyzer-too-complex" } */
+
+#include <stdlib.h>
+
+extern int get (void);
+
+/* In theory each of p0...p8 can be in various malloc states,
+   independently, so the total combined number of states
+   at any program point within the loop is NUM_VARS * NUM_STATES.  */
+
+void test (void)
+{
+  void *p0, *p1, *p2, *p3, *p4, *p5, *p6, *p7, *p8;
+  void **pp;
+  while (get ())
+    {
+      switch (get ())
+	{
+	default:
+	case 0:
+	  pp = &p0;
+	  break;
+	case 1:
+	  pp = &p1;
+	  break;
+	case 2:
+	  pp = &p2;
+	  break;
+	case 3:
+	  pp = &p3;
+	  break;
+	case 4:
+	  pp = &p4;
+	  break;
+	case 5:
+	  pp = &p5;
+	  break;
+	case 6:
+	  pp = &p6;
+	  break;
+	case 7:
+	  pp = &p7;
+	  break;
+	}
+
+      switch (get ())
+	{
+	default:
+	case 0:
+	  *pp = malloc (16);
+	  break;
+	case 1:
+	  free (*pp);
+	  break;
+	case 2:
+	  /* no-op.  */
+	  break;
+	}
+    }
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/explode-2.c b/gcc/testsuite/gcc.dg/analyzer/explode-2.c
new file mode 100644
index 0000000..d786aa9
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/explode-2.c
@@ -0,0 +1,50 @@
+/* In theory each of p0...p3 can be in various malloc states,
+   independently, so the total combined number of states
+   at any program point within the loop is NUM_VARS * NUM_STATES.
+
+   Set the limits high enough that we can fully explore this.  */ 
+
+/* { dg-additional-options "--param analyzer-max-enodes-per-program-point=200 --param analyzer-bb-explosion-factor=50" } */
+
+#include <stdlib.h>
+
+extern int get (void);
+
+void test (void)
+{
+  void *p0, *p1, *p2, *p3;
+  while (get ())
+    {
+      switch (get ())
+	{
+	default:
+	case 0:
+	  p0 = malloc (16);
+	  break;
+	case 1:
+	  free (p0); /* { dg-warning "double-'free' of 'p0'" } */
+	  break;
+
+	case 2:
+	  p1 = malloc (16);
+	  break;
+	case 3:
+	  free (p1); /* { dg-warning "double-'free' of 'p1'" } */
+	  break;
+
+	case 4:
+	  p2 = malloc (16);
+	  break;
+	case 5:
+	  free (p2); /* { dg-warning "double-'free' of 'p2'" } */
+	  break;
+
+	case 6:
+	  p3 = malloc (16);
+	  break;
+	case 7:
+	  free (p3); /* { dg-warning "double-'free' of 'p3'" } */
+	  break;
+	}
+    }
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/factorial.c b/gcc/testsuite/gcc.dg/analyzer/factorial.c
new file mode 100644
index 0000000..384713a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/factorial.c
@@ -0,0 +1,7 @@
+int factorial (int n)
+{
+  if (n > 1)
+    return n * factorial (n - 1);
+  else
+    return 1;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/fibonacci.c b/gcc/testsuite/gcc.dg/analyzer/fibonacci.c
new file mode 100644
index 0000000..5d4a4e0
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/fibonacci.c
@@ -0,0 +1,9 @@
+int fib (int n)
+{
+  if (n > 1)
+    return fib (n - 1) + fib (n - 2);
+  else
+    return n;
+} 
+
+/* { dg-regexp "\[^\n\r\]+: warning: analysis bailed out early \\(\[0-9\]+ 'after-snode' enodes; \[0-9\]+ enodes\\) \[^\n\r\]*" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/fields.c b/gcc/testsuite/gcc.dg/analyzer/fields.c
new file mode 100644
index 0000000..de55208
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/fields.c
@@ -0,0 +1,41 @@
+typedef long unsigned int size_t;
+
+extern size_t strlen (const char *__s)
+  __attribute__ ((__nothrow__ , __leaf__))
+  __attribute__ ((__pure__))
+  __attribute__ ((__nonnull__ (1)));
+
+extern void *malloc (size_t __size)
+  __attribute__ ((__nothrow__ , __leaf__))
+  __attribute__ ((__malloc__)) ;
+
+extern void free (void *__ptr)
+  __attribute__ ((__nothrow__ , __leaf__));
+
+typedef struct _krb5_data {
+  unsigned int length;
+  char *data;
+} krb5_data;
+
+typedef struct _krb5_error {
+  krb5_data text;
+} krb5_error;
+
+extern const char *error_message (int);
+
+int
+recvauth_common (int problem)
+{
+  if (problem) {
+    krb5_error error;
+    const char *message = error_message(problem);
+    error.text.length = strlen(message) + 1;
+    if (!(error.text.data = malloc(error.text.length))) {
+      goto cleanup;
+    }
+    free(error.text.data);
+  }
+
+ cleanup:
+  return problem; /* { dg-bogus "leak" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/file-1.c b/gcc/testsuite/gcc.dg/analyzer/file-1.c
new file mode 100644
index 0000000..91d9685
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/file-1.c
@@ -0,0 +1,37 @@
+#include <stdio.h>
+
+void
+test_1 (const char *path)
+{
+  FILE *f = fopen (path, "r"); /* { dg-message "opened here" } */
+  if (!f)
+    return;
+
+  fclose (f); /* { dg-message "\\(4\\) \\.\\.\\.to here" } */
+  /* { dg-message "\\(5\\) first 'fclose' here" "" { target *-*-* } .-1 } */
+  fclose (f); /* { dg-warning "double 'fclose' of FILE 'f'" } */ 
+  /* { dg-message "second 'fclose' here; first 'fclose' was at \\(5\\)" "" { target *-*-* } .-1 } */
+}
+
+void
+test_2 (const char *src, const char *dst)
+{
+  FILE *f_in = fopen (src, "r"); /* { dg-message "\\(1\\) opened here" } */
+  if (!f_in)
+    return;
+
+  FILE *f_out = fopen (src, "w");
+  if (!f_out)
+    return; /* { dg-warning "leak of FILE 'f_in'" } */
+  /* { dg-message "\\(7\\) 'f_in' leaks here; was opened at \\(1\\)" "" { target *-*-* } .-1 } */
+
+  fclose (f_out);
+  fclose (f_in);
+}
+
+void
+test_3 (const char *path)
+{
+  FILE *f = fopen (path, "r"); /* { dg-message "opened here" } */
+  return; /* { dg-warning "leak of FILE 'f'" } */ 
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/file-2.c b/gcc/testsuite/gcc.dg/analyzer/file-2.c
new file mode 100644
index 0000000..aa04570
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/file-2.c
@@ -0,0 +1,18 @@
+#include <stdio.h>
+
+struct foo
+{
+  FILE *m_f;
+};
+
+void test (const char *path)
+{
+  struct foo f;
+  f.m_f = fopen (path, "r");
+
+  if (!f.m_f)
+    return; /* { dg-bogus "leak of FILE" } */
+
+  fclose (f.m_f);
+  fclose (f.m_f); /* { dg-warning "double 'fclose' of FILE 'f.m_f'" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/function-ptr-1.c b/gcc/testsuite/gcc.dg/analyzer/function-ptr-1.c
new file mode 100644
index 0000000..38c3e59
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/function-ptr-1.c
@@ -0,0 +1,8 @@
+#include <stdlib.h>
+
+typedef void *(*fn_ptr_t) (void *);
+
+void *test_1 (fn_ptr_t fn_ptr, void *data)
+{
+  return fn_ptr (data);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/function-ptr-2.c b/gcc/testsuite/gcc.dg/analyzer/function-ptr-2.c
new file mode 100644
index 0000000..81044a7
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/function-ptr-2.c
@@ -0,0 +1,43 @@
+#include <stdlib.h>
+
+typedef void (*fn_ptr_t) (void *);
+
+void
+calls_free (void *victim)
+{
+  free (victim);
+}
+
+void
+no_op (void *ptr)
+{
+}
+
+void test_1 (void *ptr)
+{
+  fn_ptr_t fn_ptr = calls_free;
+  __analyzer_eval (fn_ptr == calls_free); /* { dg-warning "TRUE" } */
+  __analyzer_eval (fn_ptr != NULL); /* { dg-warning "TRUE" } */
+  __analyzer_eval (fn_ptr == NULL); /* { dg-warning "FALSE" } */
+  __analyzer_eval (fn_ptr == no_op); /* { dg-warning "FALSE" } */
+
+  fn_ptr (ptr);
+  fn_ptr (ptr);
+}
+// TODO: issue a double-'free' warning at 2nd call to fn_ptr.
+
+/* As above, but with an extra indirection to try to thwart
+   the optimizer.  */
+
+void test_2 (void *ptr, fn_ptr_t *fn_ptr)
+{
+  *fn_ptr = calls_free;
+  __analyzer_eval (*fn_ptr == calls_free); /* { dg-warning "TRUE" } */
+  __analyzer_eval (*fn_ptr != NULL); /* { dg-warning "TRUE" } */
+  __analyzer_eval (*fn_ptr == NULL); /* { dg-warning "FALSE" } */
+  __analyzer_eval (*fn_ptr == no_op); /* { dg-warning "FALSE" } */
+
+  (*fn_ptr) (ptr);
+  (*fn_ptr) (ptr);
+}
+// TODO: issue a double-'free' warning at 2nd call to fn_ptr.
diff --git a/gcc/testsuite/gcc.dg/analyzer/function-ptr-3.c b/gcc/testsuite/gcc.dg/analyzer/function-ptr-3.c
new file mode 100644
index 0000000..348ee4a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/function-ptr-3.c
@@ -0,0 +1,17 @@
+#include <stdlib.h>
+
+typedef void *(*alloc_func_t) (size_t);
+typedef void (*free_func_t) (void *);
+
+typedef struct callbacks
+{
+  alloc_func_t alloc_cb;
+  free_func_t dealloc_cb;
+} callbacks_t;
+
+void test (void)
+{
+  callbacks_t cb;
+  cb.alloc_cb = (alloc_func_t)0;
+  cb.dealloc_cb = (free_func_t)0;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/gzio-2.c b/gcc/testsuite/gcc.dg/analyzer/gzio-2.c
new file mode 100644
index 0000000..855ecc8
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/gzio-2.c
@@ -0,0 +1,11 @@
+void gzseek (long offset, int whence)
+{
+  if (whence == 2)
+    return;
+  if (whence == 0)
+    offset -= 1;
+  if (offset < 0)
+    return;
+  while (offset > 0) {
+  }
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/gzio-3.c b/gcc/testsuite/gcc.dg/analyzer/gzio-3.c
new file mode 100644
index 0000000..0a11f65
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/gzio-3.c
@@ -0,0 +1,31 @@
+typedef long unsigned int size_t;
+typedef struct _IO_FILE FILE;
+extern size_t fread(void *__restrict __ptr, size_t __size, size_t __n,
+                    FILE *__restrict __stream);
+typedef unsigned char Byte;
+typedef unsigned int uInt;
+typedef unsigned long uLong;
+
+typedef struct z_stream_s {
+  uInt avail_in;
+  uInt avail_out;
+} z_stream;
+
+typedef struct gz_stream {
+  z_stream stream;
+  FILE *file;
+} gz_stream;
+
+void test_1_callee(gz_stream *s, Byte *buf) {
+  Byte *next_out = buf;
+  uInt n = s->stream.avail_in;
+  if (n > 0) {
+    next_out += n;
+  }
+  s->stream.avail_out -= fread(next_out, 1, s->stream.avail_out, s->file);
+}
+
+void test_1_caller(gz_stream *s) {
+  unsigned char c;
+  test_1_callee(s, &c);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/gzio-3a.c b/gcc/testsuite/gcc.dg/analyzer/gzio-3a.c
new file mode 100644
index 0000000..15ed010
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/gzio-3a.c
@@ -0,0 +1,27 @@
+typedef long unsigned int size_t;
+typedef struct _IO_FILE FILE;
+extern size_t fread(void *__restrict __ptr, size_t __size, size_t __n,
+                    FILE *__restrict __stream);
+typedef unsigned char Byte;
+typedef unsigned int uInt;
+typedef unsigned long uLong;
+
+typedef struct gz_stream {
+  FILE *file;
+  uInt avail_in;
+  uInt avail_out;
+} gz_stream;
+
+void test_1_callee(gz_stream *s, Byte *buf) {
+  Byte *next_out = buf;
+  uInt n = s->avail_in;
+  if (n > 0) {
+    next_out += n;
+  }
+  s->avail_out -= fread(next_out, 1, s->avail_out, s->file);
+}
+
+void test_1_caller(gz_stream *s) {
+  unsigned char c;
+  test_1_callee(s, &c);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/gzio.c b/gcc/testsuite/gcc.dg/analyzer/gzio.c
new file mode 100644
index 0000000..54efa77
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/gzio.c
@@ -0,0 +1,17 @@
+#include <stdlib.h>
+typedef struct z_stream_s {
+  unsigned char *next_out;
+} z_stream;
+typedef struct gz_stream {
+  z_stream stream;
+  unsigned char *outbuf;
+} gz_stream;
+gz_stream *s;
+static void gz_open(const char *path)
+{
+  s->stream.next_out = s->outbuf = (unsigned char *)malloc(16384); /* { dg-bogus "leak" } */
+}
+void gzopen(const char *path)
+{
+  gz_open(path);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/infinite-recursion.c b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion.c
new file mode 100644
index 0000000..b770e12
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion.c
@@ -0,0 +1,55 @@
+extern void marker_A(void);
+extern void marker_B(void);
+extern void marker_C(void);
+extern void marker_D(void);
+
+void test(int flag)
+{
+  marker_A();
+
+  if (flag) {
+    marker_B();
+
+    /* Recurse, infinitely, as it happens: */
+    test(flag);
+
+    marker_C();
+  }
+
+  marker_D();
+}
+
+/* A cycle of 4 mutually-recursive functions (but only for certain inputs).  */
+
+extern void mutual_test_1 (int flag);
+extern void mutual_test_2 (int flag);
+extern void mutual_test_3 (int flag);
+extern void mutual_test_4 (int flag);
+
+void mutual_test_1 (int flag)
+{
+  marker_A ();
+  if (flag)
+    mutual_test_2 (flag);
+}
+
+void mutual_test_2 (int flag)
+{
+  marker_B ();
+  if (flag)
+    mutual_test_3 (flag);
+}
+
+void mutual_test_3 (int flag)
+{
+  marker_C ();
+  if (flag)
+    mutual_test_4 (flag);
+}
+
+void mutual_test_4 (int flag)
+{
+  marker_D ();
+  if (flag)
+    mutual_test_1 (flag);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/loop-2.c b/gcc/testsuite/gcc.dg/analyzer/loop-2.c
new file mode 100644
index 0000000..2a6edc4
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/loop-2.c
@@ -0,0 +1,36 @@
+/* { dg-additional-options "-fno-analyzer-state-purge" } */
+
+struct s
+{
+  int i;
+};
+
+void test(void)
+{
+  struct s s;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+
+  for (s.i=0; s.i<256; s.i++) {
+    __analyzer_eval (s.i < 256); /* { dg-warning "TRUE" } */
+      /* (should report TRUE twice). */
+
+    __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+    //__analyzer_eval (s.i == 0); /* { d-todo-g-warning "UNKNOWN" "" { xfail *-*-* } } */
+      /* { d-todo-g-warning "TRUE" "" { target *-*-* } .-1 } */
+      /* TODO(xfail^^^): we're only capturing the first iteration, so
+	 we erroneously get i == 0.  */
+
+      //__analyzer_eval (s.i >= 0); /* { d-todo-g-warning "TRUE" } */
+  }
+
+  __analyzer_eval (s.i >= 256); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (s.i == 256); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  /* TODO(xfail^^^): ideally it should figure out i == 256 at exit.  */
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/loop-2a.c b/gcc/testsuite/gcc.dg/analyzer/loop-2a.c
new file mode 100644
index 0000000..94eff82
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/loop-2a.c
@@ -0,0 +1,39 @@
+/* { dg-additional-options "-fno-analyzer-state-purge" } */
+
+union u
+{
+  int i;
+};
+
+void test(void)
+{
+  union u u;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+
+  for (u.i=0; u.i<256; u.i++) {
+    __analyzer_eval (u.i < 256); /* { dg-warning "TRUE" } */
+    /* { dg-warning "TRUE" "" { xfail *-*-* } .-1 } */
+    /* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-2 } */
+    /* (should report TRUE twice). */
+
+    __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+    //__analyzer_eval (u.i == 0); /* { d-todo-g-warning "UNKNOWN" "" { xfail *-*-* } } */
+      /* { d-todo-g-warning "TRUE" "" { target *-*-* } .-1 } */
+      /* TODO(xfail^^^): we're only capturing the first iteration, so
+	 we erroneously get i == 0.  */
+
+      //__analyzer_eval (u.i >= 0); /* { d-todo-g-warning "TRUE" } */
+  }
+
+  __analyzer_eval (u.i >= 256); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+
+  __analyzer_eval (u.i == 256); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  /* TODO(xfail^^^): ideally it should figure out i == 256 at exit.  */
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/loop-3.c b/gcc/testsuite/gcc.dg/analyzer/loop-3.c
new file mode 100644
index 0000000..1d01771
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/loop-3.c
@@ -0,0 +1,17 @@
+#include <stdlib.h>
+
+void test(int c)
+{
+  int i;
+  char *buffer = (char*)malloc(256);
+
+  for (i=0; i<255; i++) {
+    buffer[i] = c; /* { dg-warning "use after 'free' of 'buffer'" } */
+		   /* BUG: the malloc could have failed
+		      TODO: the checker doesn't yet pick up on this, perhaps
+		      due to the pointer arithmetic not picking up on the
+		      state */
+    free(buffer); /* { dg-warning "double-'free' of 'buffer'" } */
+  }
+
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/loop-4.c b/gcc/testsuite/gcc.dg/analyzer/loop-4.c
new file mode 100644
index 0000000..7e3e7ab
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/loop-4.c
@@ -0,0 +1,41 @@
+// FIXME:
+/* { dg-additional-options "-fno-analyzer-state-purge" } */
+
+/* Example of nested loops.  */
+
+void test(void)
+{
+  int i, j, k;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+  for (i=0; i<256; i++) {
+
+    __analyzer_eval (i >= 0); /* { dg-warning "TRUE" } */
+      /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+
+    __analyzer_eval (i < 256); /* { dg-warning "TRUE" } */
+
+    for (j=0; j<256; j++) {
+
+      __analyzer_eval (j >= 0); /* { dg-warning "TRUE" } */
+      /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+
+      __analyzer_eval (j < 256); /* { dg-warning "TRUE" } */
+
+      __analyzer_dump_exploded_nodes (0); /* { dg-warning "3 exploded nodes" } */
+
+      for (k=0; k<256; k++) {
+
+	__analyzer_eval (k >= 0); /* { dg-warning "TRUE" } */
+	/* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+
+	__analyzer_eval (k < 256); /* { dg-warning "TRUE" } */
+
+	__analyzer_dump_exploded_nodes (0); /* { dg-warning "4 exploded nodes" } */
+      }
+    }
+  }
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/loop.c b/gcc/testsuite/gcc.dg/analyzer/loop.c
new file mode 100644
index 0000000..32fb051
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/loop.c
@@ -0,0 +1,33 @@
+/* { dg-additional-options "-fno-analyzer-state-purge" } */
+
+void test(void)
+{
+  int i;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+  for (i=0; i<256; i++) {
+      __analyzer_eval (i < 256); /* { dg-warning "TRUE" } */
+      /* (should report TRUE twice). */
+
+      __analyzer_eval (i == 0); /* { dg-warning "TRUE" } */
+      /* { dg-warning "FALSE" "" { xfail *-*-* } .-1 } */
+      /* { dg-warning "UNKNOWN" "" { target *-*-* } .-2 } */
+      /* TODO(xfail^^^): ideally we ought to figure out i > 0 after 1st iteration.  */
+
+      __analyzer_eval (i >= 0); /* { dg-warning "TRUE" } */
+      /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+      /* { dg-warning "UNKNOWN" "" { target *-*-* } .-2 } */
+      /* TODO(xfail^^^): ideally we ought to figure out i >= 0 for all iterations.  */
+
+      __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  }
+
+  __analyzer_eval (i >= 256); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (i == 256); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  /* TODO(xfail^^^): it only figures out i >= 256, rather than i == 256.  */
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-1.c b/gcc/testsuite/gcc.dg/analyzer/malloc-1.c
new file mode 100644
index 0000000..13ed4fc
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-1.c
@@ -0,0 +1,565 @@
+#include <stdlib.h>
+#include <string.h>
+
+extern int foo (void);
+extern int bar (void);
+extern void could_free (void *);
+extern void cant_free (const void *); /* since it's a const void *.  */
+
+void test_1 (void)
+{
+  void *ptr = malloc (1024);
+  free (ptr);
+  free (ptr); /* { dg-warning "double-'free' of 'ptr'" } */
+}
+
+void test_2 (void *ptr)
+{
+  free (ptr);
+  free (ptr); /* { dg-warning "double-'free' of 'ptr'" } */
+}
+
+int *test_3 (void)
+{
+  int *ptr = (int *)malloc (sizeof (int));
+  *ptr = 42; /* { dg-warning "dereference of possibly-NULL 'ptr'" } */
+  return ptr;
+}
+
+int *test_4 (void)
+{
+  int *ptr = (int *)malloc (sizeof (int));
+  if (ptr)
+    *ptr = 42;
+  else
+    *ptr = 43; /* { dg-warning "dereference of NULL 'ptr'" } */
+  return ptr;
+}
+
+int test_5 (int *ptr)
+{
+  free (ptr);
+  return *ptr; /* { dg-warning "use after 'free' of 'ptr'" } */
+}
+
+void test_6 (void *ptr)
+{
+  void *q;
+  q = ptr;
+  free (ptr);
+  free (q); /* { dg-warning "double-'free' of 'q'" } */
+  /* The above case requires us to handle equivalence classes in
+     state transitions.  */
+}
+
+void test_7 (void)
+{
+  void *ptr = malloc(4096);
+  if (!ptr)
+    return;
+  memset(ptr, 0, 4096);
+  free(ptr);
+}
+
+void *test_8 (void)
+{
+  void *ptr = malloc(4096);
+  if (!ptr)
+    return NULL;
+  memset(ptr, 0, 4096);
+  return ptr;
+  /* This needs phi nodes to affect equivalence classes, or we get a false report
+     of a leak.  */
+}
+
+void test_9 (void)
+{
+  void *ptr = malloc (1024);
+
+  int i;
+  for (i = 0; i < 1024; i++)
+    free (ptr); /* { dg-warning "double-'free' of 'ptr'" } */
+}
+
+void test_10 (void)
+{
+  void *ptr = malloc (1024);
+
+  int i;
+  for (i = 0; i < 1024; i++)
+    foo ();
+
+  free (ptr);
+  free (ptr); /* { dg-warning "double-'free' of 'ptr'" } */ 
+}
+
+void test_11 (void)
+{
+  void *ptr = malloc (1024);
+
+  while (foo ())
+    bar ();
+
+  free (ptr);
+  free (ptr); /* { dg-warning "double-'free' of 'ptr'" } */
+}
+
+void test_12 (void)
+{
+  void *ptr = malloc (1024);
+
+  while (1)
+    {
+      free (ptr);
+      free (ptr); /* { dg-warning "double-'free' of 'ptr'" } */
+    }
+}
+
+void test_13 (void)
+{
+  void *p = malloc (1024); /* { dg-message "allocated here" } */
+  void *q = malloc (1024);
+
+  foo ();
+  if (!q)
+    {
+      free (q);
+      return; /* { dg-warning "leak of 'p'" } */ 
+    }
+  bar ();
+  free (q);
+  free (p);
+}
+
+void test_14 (void)
+{
+  void *p, *q;
+  p = malloc (1024);
+  if (!p)
+    return;
+
+  q = malloc (1024);
+  if (!q)
+    {
+      free (p);
+      free (q);
+      /* oops: missing "return".  */
+    }
+  bar ();
+  free (q); /* Although this looks like a double-'free' of q,
+	       it's known to be NULL for the case where free is
+	       called twice on it.  */
+  free (p); /* { dg-warning "double-'free' of 'p'" } */
+}
+
+void test_15 (void)
+{
+  void *p = NULL, *q = NULL;
+
+  p = malloc (1024);
+  if (!p)
+    goto fail;
+
+  foo ();
+
+  q = malloc (1024);
+  if (!q)
+    goto fail;
+
+  bar ();
+
+ fail:
+  free (q);
+  free (p);
+}
+
+void test_16 (void)
+{
+  void *p, *q;
+
+  p = malloc (1024);
+  if (!p)
+    goto fail;
+
+  foo ();
+
+  q = malloc (1024);
+  if (!q)
+    goto fail;
+
+  bar ();
+
+ fail:
+  free (q); /* { dg-warning "free of uninitialized 'q'" "" { xfail *-*-* } } */ 
+  /* TODO(xfail): implement uninitialized detection.  */
+  free (p);
+}
+
+void test_17 (void)
+{
+  void *ptr = malloc (1024); /* { dg-message "allocated here" } */
+} /* { dg-warning "leak of 'ptr'" } */ 
+
+void test_18 (void)
+{
+  void *ptr = malloc (64); /* { dg-message "allocated here" } */
+  ptr = NULL; /* { dg-warning "leak of 'ptr'" } */ 
+}
+
+void test_19 (void)
+{
+  void *ptr = malloc (64);
+  free (ptr);
+  ptr = NULL;
+  free (ptr);
+}
+
+void *global_ptr_20;
+
+void test_20 (void)
+{
+  global_ptr_20 = malloc (1024);
+}
+
+int *test_21 (int i)
+{
+  int *ptr = malloc (sizeof (int));
+  if (!ptr)
+    abort ();
+  *ptr = i;
+  return ptr;
+}
+
+void test_22 (void)
+{
+  void *ptr = malloc (1024);
+
+  int i;
+  for (i = 5; i < 10; i++)
+    foo ();
+
+  free (ptr);
+  free (ptr); /* { dg-warning "double-'free' of 'ptr'" } */ 
+}
+
+int *test_23 (int n)
+{
+  int *ptr = (int *)calloc (n, sizeof (int));
+  ptr[0] = 42; /* { dg-warning "dereference of possibly-NULL 'ptr'" } */
+  return ptr;
+}
+
+int test_24 (void)
+{
+  void *ptr = alloca (sizeof (int)); /* { dg-message "memory is allocated on the stack here" } */
+  free (ptr); /* { dg-warning "'free' of memory allocated on the stack by 'alloca' \\('ptr'\\) will corrupt the heap \\\[CWE-590\\\]" } */
+}
+
+int test_25 (void)
+{
+  char tmp[100];
+  void *p = tmp; /* { dg-message "pointer is from here" } */
+  free (p); /* { dg-warning "'free' of 'p' which points to memory not on the heap \\\[CWE-590\\\]" } */
+  /* TODO: more precise messages here.  */
+}
+
+char global_buffer[100];
+
+int test_26 (void)
+{
+  void *p = global_buffer; /* { dg-message "pointer is from here" } */
+  free (p); /* { dg-warning "'free' of 'p' which points to memory not on the heap \\\[CWE-590\\\]" } */
+  /* TODO: more precise messages here.  */
+}
+
+struct coord {
+  float x;
+  float y;
+};
+
+struct coord *test_27 (void)
+{
+  struct coord *p = (struct coord *) malloc (sizeof (struct coord)); /* { dg-message "this call could return NULL" } */
+  p->x = 0.f;  /* { dg-warning "dereference of possibly-NULL 'p'" } */
+
+  /* Only the first such usage should be reported: */
+  p->y = 0.f;
+
+  return p;
+}
+
+struct coord *test_28 (void)
+{
+  struct coord *p = NULL;
+  p->x = 0.f; /* { dg-warning "dereference of NULL 'p'" } */
+
+  /* Only the first such usage should be reported: */
+  p->y = 0.f;
+
+  return p;
+}
+
+struct link
+{
+  struct link *m_ptr;
+};
+
+struct link *test_29 (void)
+{
+  struct link *res = (struct link *)malloc (sizeof (struct link));
+  if (!res)
+    return NULL;
+  res->m_ptr = (struct link *)malloc (sizeof (struct link));
+  return res;
+}
+
+struct link *test_29a (void)
+{
+  struct link *res = (struct link *)malloc (sizeof (struct link));
+  if (!res)
+    return NULL;
+  res->m_ptr = (struct link *)malloc (sizeof (struct link));
+  if (!res->m_ptr)
+    {
+      free (res);
+      return NULL;
+    }
+  res->m_ptr->m_ptr = (struct link *)malloc (sizeof (struct link));
+  return res;
+}
+
+/* Without consolidation by EC, this one shows two leaks:
+     warning: leak of '<unknown>'
+     warning: leak of 'tmp.m_ptr' 
+   We should only show the latter (favoring the most user-readable
+   expression in the equivalence class).  */
+void test_30 (void)
+{
+  struct link tmp;
+  tmp.m_ptr = (struct link *)malloc (sizeof (struct link)); /* { dg-message "allocated here" } */
+} /* { dg-warning "leak of 'tmp.m_ptr'" } */ 
+/* { dg-bogus "leak of '<unknown>'" "" { xfail *-*-* } .-1 } */ 
+
+void test_31 (void)
+{
+  struct link tmp;
+  void *ptr = malloc (sizeof (struct link)); /* { dg-message "allocated here" } */
+  tmp.m_ptr = (struct link *)ptr;
+} /* { dg-warning "leak of 'ptr'" } */ 
+/* { dg-bogus "leak of 'tmp.m_ptr'" "" { xfail *-*-* } .-1 } */ 
+
+void test_32 (void)
+{
+  void *ptr = malloc (1024);
+  could_free (ptr);
+} /* { dg-bogus "leak" } */
+
+void test_33 (void)
+{
+  void *ptr = malloc (1024); /* { dg-message "allocated here" } */
+  cant_free (ptr);
+} /* { dg-warning "leak of 'ptr'" } */ 
+
+void test_34 (void)
+{
+  float *q;
+  struct coord *p = malloc (sizeof (struct coord));
+  if (!p)
+    return;
+  p->x = 0.0f;
+  q = &p->x;
+  free (p);
+  *q = 1.0f; /* { dg-warning "use after 'free' of 'q'" } */
+};
+
+int test_35 (void)
+{
+  void *ptr = malloc(4096);
+  if (!ptr)
+    return -1;
+  memset(ptr, 0, 4096);
+  free(ptr);
+  return 0;
+}
+
+void test_36 (void)
+{
+  void *ptr = malloc(4096);
+  if (!ptr)
+    return;
+  memset(ptr, 0, 4096);
+  free(ptr);
+}
+
+void *test_37a (void)
+{
+  void *ptr = malloc(4096); /* { dg-message "this call could return NULL" } */
+  memset(ptr, 0, 4096); /* { dg-warning "use of possibly-NULL 'ptr' where non-null expected" } */
+  return ptr;
+}
+
+int test_37b (void)
+{
+  void *p = malloc(4096);
+  void *q = malloc(4096); /* { dg-message "this call could return NULL" } */
+  if (p) {
+    memset(p, 0, 4096); /* Not a bug: checked */
+  } else {
+    memset(q, 0, 4096); /* { dg-warning "use of possibly-NULL 'q' where non-null expected" } */
+  }
+  free(p);
+  free(q);
+  return 0;
+}
+
+extern void might_use_ptr (void *ptr);
+
+void test_38(int i)
+{
+  void *p;
+
+  p = malloc(1024);
+  if (p) {
+    free(p);
+    might_use_ptr(p); /* { dg-warning "use after 'free' of 'p'" "" { xfail *-*-* } } */
+    // TODO: xfail
+  }
+}
+
+int *
+test_39 (int i)
+{
+  int *p = (int*)malloc(sizeof(int*)); /* { dg-message "this call could return NULL" } */
+  *p = i; /* { dg-warning "dereference of possibly-NULL 'p'" } */
+  return p;
+}
+
+int *
+test_40 (int i)
+{
+  int *p = (int*)malloc(sizeof(int*));
+  i = *p; /* { dg-warning "dereference of possibly-NULL 'p'" } */
+  /* TODO: (it's also uninitialized) */
+  return p;
+}
+
+char *
+test_41 (int flag)
+{
+  char *buffer;
+
+  if (flag) {
+    buffer = (char*)malloc(4096);
+  } else {
+    buffer = NULL;
+  }
+
+  buffer[0] = 'a'; /* { dg-warning "dereference of possibly-NULL 'buffer'" } */
+  /* { dg-warning "dereference of NULL 'buffer'" "" { target *-*-* } .-1 } */
+
+  return buffer;
+}
+
+void test_42a (void)
+{
+  void *p = malloc (1024); /* { dg-message "allocated here" } */
+  free (p + 64); /* this could well corrupt the heap.  */
+  /* TODO: ^^^ we should warn about this.  */
+} /* { dg-warning "leak of 'p'" } */
+/* TODO: presumably we should complain about the bogus free, but then
+   maybe not complain about the leak.  */
+// CWE-761: Free of Pointer not at Start of Buffer
+
+void test_42b (void)
+{
+  void *p = malloc (1024); /* { dg-message "allocated here" } */
+  free (p - 64); /* this could well corrupt the heap.  */
+  /* TODO: ^^^ we should warn about this.  */
+} /* { dg-warning "leak of 'p'" } */
+/* TODO: presumably we should complain about the bogus free, but then
+   maybe not complain about the leak.  */
+// CWE-761: Free of Pointer not at Start of Buffer
+
+void test_42c (void)
+{
+  void *p = malloc (1024);
+  void *q = p + 64;
+  free (q - 64); /* this is probably OK.  */
+} /* { dg-bogus "leak of 'p'" "" { xfail *-*-* } } */
+// TODO(xfail)
+
+#if 0
+void test_31 (void *p)
+{
+  void *q = realloc (p, 1024);
+  free (p); /* FIXME: this is a double-'free'.  */
+  free (q);
+}
+
+void test_32 (void)
+{
+  void *p = malloc (64);
+  p = realloc (p, 1024); /* FIXME: this leaks if it fails.  */
+  free (p);
+}
+#endif
+
+struct link global_link;
+
+void test_43 (void)
+{
+  global_link.m_ptr = malloc (sizeof (struct link)); /* { dg-message "allocated here" } */
+  global_link.m_ptr = NULL;
+} /* { dg-warning "leak of '<unknown>'" } */
+/* TODO: should be more precise than just '<unknown>', and
+   ideally would be at the assigment to NULL.  */
+
+struct link *global_ptr;
+
+void test_44 (void)
+{
+  global_ptr = malloc (sizeof (struct link));
+  if (!global_ptr)
+    return;
+  global_ptr->m_ptr = malloc (sizeof (struct link)); /* { dg-message "allocated here" } */
+  free (global_ptr); /* { dg-warning "leak of '<unknown>'" } */
+  /* TODO: should be more precise than just '<unknown>'.  */
+}
+
+extern void might_take_ownership (void *ptr);
+
+void test_45 (void)
+{
+  void *p = malloc (1024);
+  might_take_ownership (p);
+}
+
+void test_46 (void)
+{
+  struct link *p = (struct link *)malloc (sizeof (struct link));
+  if (!p)
+    return;
+  struct link *q = (struct link *)malloc (sizeof (struct link));
+  p->m_ptr = q;
+  might_take_ownership (p);
+}
+
+extern int maybe_alloc (char **);
+
+int test_47 (void)
+{
+  char *p = ((void *)0);
+  int p_size = 0;
+
+  p = malloc (16);
+  if (p) {
+    free (p);
+  } else {
+    int retval = maybe_alloc (&p); /* this might write to "p".  */
+    if (retval)
+      return (retval);
+    p_size = strlen(p); /* { dg-bogus "non-null expected" } */
+    free (p);
+  }
+  return p_size;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-2.c b/gcc/testsuite/gcc.dg/analyzer/malloc-2.c
new file mode 100644
index 0000000..6d073f5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-2.c
@@ -0,0 +1,23 @@
+/* Tests for precision-of-wording within malloc warnings.  */
+
+typedef __SIZE_TYPE__ size_t;
+extern void *malloc(size_t);
+extern void free(void *);
+extern char *strcpy(char *__restrict __dest, const char *__restrict __src)
+    __attribute__((__nothrow__, __leaf__)) __attribute__((__nonnull__(1, 2)));
+
+void test_1 (void)
+{
+  void *p = malloc (1024); /* { dg-message "\\(1\\) this call could return NULL" } */
+  strcpy ((char *)p, "hello world"); /* { dg-warning "use of possibly-NULL 'p' where non-null expected" } */
+  /* { dg-message "\\(2\\) argument 1 \\('p'\\) from \\(1\\) could be NULL where non-null expected" "" { target *-*-* } .-1 } */
+  free (p);
+}
+
+int *test_2 (void)
+{
+  int *i = malloc (sizeof (int)); /* { dg-message "\\(1\\) this call could return NULL" } */
+  *i = 42; /* { dg-warning "dereference of possibly-NULL 'i'" } */
+  /* { dg-message "\\(2\\) 'i' could be NULL: unchecked value from \\(1\\)" "" { target *-*-* } .-1 } */
+  return i; 
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-3.c b/gcc/testsuite/gcc.dg/analyzer/malloc-3.c
new file mode 100644
index 0000000..5afb6b3
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-3.c
@@ -0,0 +1,8 @@
+#include <stdlib.h>
+
+/* Don't complain about leaks due to exiting from "main".  */
+
+void main (void)
+{
+  void *p = malloc (1024);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-dce.c b/gcc/testsuite/gcc.dg/analyzer/malloc-dce.c
new file mode 100644
index 0000000..1f61b4b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-dce.c
@@ -0,0 +1,12 @@
+/* { dg-options "--analyzer -O2" } */
+
+#include <stdlib.h>
+
+void test(void)
+{
+    void *ptr = malloc(512);
+    free(ptr);
+    free(ptr); /* { dg-warning "double-'free'" "" { xfail *-*-* } } */
+}
+/* With optimization, the whole of test() goes away in the "cddce" pass
+   before the analysis pass sees it, and hence we get no error message.  */
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-dedupe-1.c b/gcc/testsuite/gcc.dg/analyzer/malloc-dedupe-1.c
new file mode 100644
index 0000000..233ab48
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-dedupe-1.c
@@ -0,0 +1,46 @@
+#include <stdlib.h>
+
+extern void foo (void);
+extern void bar (void);
+
+void test_1 (int flag, int n);
+
+void caller_1_of_test_1 (int n)
+{
+  test_1 (1, n); /* { dg-bogus "test_1" } */
+  test_1 (0, n); /* { dg-bogus "test_1" } */
+}
+
+void __attribute__((noinline))
+test_1 (int flag, int n)
+{
+  int *ptr = (int *)malloc (sizeof (int));
+
+  if (flag)
+    {
+      int i;
+      for (i = 0; i < n; i++)
+	foo ();
+    }
+  else
+    bar ();
+
+  free (ptr);
+  free (ptr); /* { dg-warning "double-'free'" } */
+  /* FIXME: we get duplicates intraprocedurally, as there are two paths
+     through the function.
+     The calls in test_2 also generate additional duplicates.
+     How to verify lack of duplicates?
+     Putting a bogus on the interprocedual one detects that, at least.  */
+
+  if (flag)
+    foo ();
+  else
+    bar ();
+}
+
+void caller_2_of_test_1 (int n)
+{
+  test_1 (1, n); /* { dg-bogus "test_1" } */
+  test_1 (0, n); /* { dg-bogus "test_1" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-1.c b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-1.c
new file mode 100644
index 0000000..ad536ce
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-1.c
@@ -0,0 +1,24 @@
+#include <stdlib.h>
+
+void *
+calls_malloc (void)
+{
+  void *result = malloc (1024);
+  return result;
+}
+
+int *test_1 (int i)
+{
+  int *ptr = (int *)calls_malloc ();
+  *ptr = i; /* { dg-warning "dereference of possibly-NULL 'ptr'" } */
+  return ptr;
+}
+
+/* Same as test_1, to exercise the caches.  */
+
+int *test_2 (int i)
+{
+  int *ptr = (int *)calls_malloc ();
+  *ptr = i; /* { dg-warning "dereference of possibly-NULL 'ptr'" } */
+  return ptr;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-10.c b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-10.c
new file mode 100644
index 0000000..7e8f274
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-10.c
@@ -0,0 +1,32 @@
+#include <stdlib.h>
+
+void
+calls_free (void *victim) /* { dg-message "\\(3\\) entry to 'calls_free'" } */
+/* { dg-message "\\(7\\) entry to 'calls_free'" "" { target *-*-* } .-1 } */
+{
+  free (victim); /* { dg-warning "double-'free' of 'victim'" } */
+  /* { dg-message "\\(4\\) first 'free' here" "" { target *-*-* } .-1 } */
+  /* { dg-message "\\(8\\) second 'free' here; first 'free' was at \\(4\\)" "" { target *-*-* } .-2 } */
+
+  /* TODO: would this be better emitted at the callsite,
+     for such a simple wrapper?  */
+}
+
+void do_stuff (void)
+{
+  /* Empty.  Irrelevant, and thus should not be expanded into in paths.  */
+}
+
+void test (void *ptr) /* { dg-message "\\(1\\) entry to 'test'" } */
+{
+  do_stuff ();
+
+  calls_free (ptr); /* { dg-message "\\(2\\) calling 'calls_free' from 'test'" } */
+  /* { dg-message "\\(5\\) returning to 'test' from 'calls_free'" "" { target *-*-* } .-1 } */
+
+  do_stuff ();
+
+  calls_free (ptr); /* { dg-message "\\(6\\) passing freed pointer 'ptr' in call to 'calls_free' from 'test'" } */
+
+  do_stuff ();
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-11.c b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-11.c
new file mode 100644
index 0000000..d02250f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-11.c
@@ -0,0 +1,95 @@
+/* { dg-additional-options "-fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fdiagnostics-path-format=inline-events -fdiagnostics-show-caret" } */
+
+#include <stdlib.h>
+
+extern int some_condition ();
+extern void do_stuff (int);
+
+void
+may_call_free (void *victim)
+{
+  if (some_condition ())
+    return;
+
+  free (victim); /* { dg-warning "double-'free' of 'victim'" } */ 
+}
+
+void test (void *ptr)
+{
+  do_stuff (1);
+
+  may_call_free (ptr);
+
+  do_stuff (2);
+
+  may_call_free (ptr);
+
+  do_stuff (3);
+}
+
+/* { dg-begin-multiline-output "" }
+   NN |   free (victim);
+      |   ^~~~~~~~~~~~~
+  'test': events 1-2
+    |
+    |   NN | void test (void *ptr)
+    |      |      ^~~~
+    |      |      |
+    |      |      (1) entry to 'test'
+    |......
+    |   NN |   may_call_free (ptr);
+    |      |   ~~~~~~~~~~~~~~~~~~~
+    |      |   |
+    |      |   (2) calling 'may_call_free' from 'test'
+    |
+    +--> 'may_call_free': events 3-6
+           |
+           |   NN | may_call_free (void *victim)
+           |      | ^~~~~~~~~~~~~
+           |      | |
+           |      | (3) entry to 'may_call_free'
+           |   NN | {
+           |   NN |   if (some_condition ())
+           |      |      ~
+           |      |      |
+           |      |      (4) following 'false' branch...
+           |......
+           |   NN |   free (victim);
+           |      |   ~~~~~~~~~~~~~
+           |      |   |
+           |      |   (5) ...to here
+           |      |   (6) first 'free' here
+           |
+    <------+
+    |
+  'test': events 7-8
+    |
+    |   NN |   may_call_free (ptr);
+    |      |   ^~~~~~~~~~~~~~~~~~~
+    |      |   |
+    |      |   (7) returning to 'test' from 'may_call_free'
+    |......
+    |   NN |   may_call_free (ptr);
+    |      |   ~~~~~~~~~~~~~~~~~~~
+    |      |   |
+    |      |   (8) passing freed pointer 'ptr' in call to 'may_call_free' from 'test'
+    |
+    +--> 'may_call_free': events 9-12
+           |
+           |   NN | may_call_free (void *victim)
+           |      | ^~~~~~~~~~~~~
+           |      | |
+           |      | (9) entry to 'may_call_free'
+           |   NN | {
+           |   NN |   if (some_condition ())
+           |      |      ~
+           |      |      |
+           |      |      (10) following 'false' branch...
+           |......
+           |   NN |   free (victim);
+           |      |   ~~~~~~~~~~~~~
+           |      |   |
+           |      |   (11) ...to here
+           |      |   (12) second 'free' here; first 'free' was at (6)
+           |
+  { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-12.c b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-12.c
new file mode 100644
index 0000000..fce5437
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-12.c
@@ -0,0 +1,7 @@
+#include <stdlib.h>
+
+void recursive_free (void *ptr)
+{
+  free (ptr); /* { dg-warning "double-'free' of 'ptr'" } */
+  recursive_free (ptr);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-13.c b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-13.c
new file mode 100644
index 0000000..2e3d80e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-13.c
@@ -0,0 +1,30 @@
+/* { dg-additional-options "-fanalyzer-verbosity=1" } */
+
+#include <stdlib.h>
+
+void
+calls_free (void *victim)
+{
+  free (victim); /* { dg-warning "double-'free' of 'victim'" } */
+}
+
+extern void do_stuff (void);
+
+struct foo
+{
+  void *m_p;
+};
+
+void test (struct foo f)
+{
+  do_stuff ();
+
+  calls_free (f.m_p);
+
+  do_stuff ();
+
+  calls_free (f.m_p); /* { dg-message "passing freed pointer '<unknown>' in call to 'calls_free' from 'test'" } */
+  // TODO: something better than '<unknown>'
+
+  do_stuff ();
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-2.c b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-2.c
new file mode 100644
index 0000000..efeb94b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-2.c
@@ -0,0 +1,34 @@
+/* { dg-additional-options "-fanalyzer-verbosity=1" } */
+
+#include <stdlib.h>
+
+void
+calls_free (void *victim) /* { dg-message "\\(3\\) entry to 'calls_free'" } */
+/* { dg-message "\\(7\\) entry to 'calls_free'" "" { target *-*-* } .-1 } */
+{
+  free (victim); /* { dg-warning "double-'free' of 'victim'" } */
+  /* { dg-message "\\(4\\) first 'free' here" "" { target *-*-* } .-1 } */
+  /* { dg-message "\\(8\\) second 'free' here; first 'free' was at \\(4\\)" "" { target *-*-* } .-2 } */
+
+  /* TODO: would this be better emitted at the callsite,
+     for such a simple wrapper?  */
+}
+
+extern void do_stuff (void);
+
+void test (void *ptr) /* { dg-message "\\(1\\) entry to 'test'" } */
+{
+  do_stuff ();
+
+  calls_free (ptr); /* { dg-message "\\(2\\) calling 'calls_free' from 'test'" } */
+  /* { dg-message "\\(5\\) returning to 'test' from 'calls_free'" "" { target *-*-* } .-1 } */
+
+  do_stuff ();
+
+  calls_free (ptr); /* { dg-message "\\(6\\) passing freed pointer 'ptr' in call to 'calls_free' from 'test'" } */
+
+  do_stuff ();
+}
+
+
+
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-3.c b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-3.c
new file mode 100644
index 0000000..3dcfae8
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-3.c
@@ -0,0 +1,23 @@
+#include <stdlib.h>
+
+void *
+calls_malloc (void)
+{
+  void *result = malloc (1024);
+  return result;
+}
+
+void
+calls_free (void *victim)
+{
+  free (victim); /* { dg-warning "double-'free' of 'victim'" } */
+  /* TODO: this would be better emitted at the callsite,
+     for such a simple wrapper.  */
+}
+
+void test (void)
+{
+  void *ptr = calls_malloc ();
+  calls_free (ptr);
+  calls_free (ptr); /* BUG: double-'free'.  */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-4.c b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-4.c
new file mode 100644
index 0000000..82d50bd
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-4.c
@@ -0,0 +1,13 @@
+#include <stdlib.h>
+
+static void calls_free(int *q)
+{
+  free(q);
+}
+
+void test(void *p)
+{
+  calls_free(p);
+
+  free(p); /* { dg-warning "double-'free' of 'p'" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-5.c b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-5.c
new file mode 100644
index 0000000..c66ecb5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-5.c
@@ -0,0 +1,13 @@
+#include <stdlib.h>
+
+static int *calls_malloc(void)
+{
+  return malloc(sizeof(int));
+}
+
+int *test(void)
+{
+  int *p = calls_malloc(); /* { dg-message "possible return of NULL to 'test' from 'calls_malloc'" } */
+  *p = 42; /* { dg-warning "dereference of possibly-NULL 'p'" } */
+  return p;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-6.c b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-6.c
new file mode 100644
index 0000000..62f8b55
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-6.c
@@ -0,0 +1,22 @@
+#include <stdlib.h>
+
+void *
+calls_malloc (void)
+{
+  void *result = malloc (1024); /* { dg-message "allocated here" } */
+  return result; /* { dg-warning "leak of 'result'" } */
+}
+
+void test_1 ()
+{
+  calls_malloc (); /* { dg-message "calling 'calls_malloc' from 'test_1'" } */
+}
+
+static void callee (int i)
+{
+}
+
+void test_2 (int i)
+{
+  callee (i);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-7.c b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-7.c
new file mode 100644
index 0000000..0742370
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-7.c
@@ -0,0 +1,29 @@
+#include <stdlib.h>
+
+/**************************************************************************/
+
+static void maybe_calls_free_1(int *q, int flag)
+{
+  if (flag)
+    free(q); /* { dg-warning "double-'free' of 'q'" } */
+}
+
+void test_1(void *p)
+{
+  maybe_calls_free_1(p, 1);
+  maybe_calls_free_1(p, 1); 
+}
+
+/**************************************************************************/
+
+static void maybe_calls_free_2(int *q, int flag)
+{
+  if (flag)
+    free(q); /* { dg-bogus "double-'free'" } */
+}
+
+void test_2(void *p)
+{
+  maybe_calls_free_2(p, 0);
+  maybe_calls_free_2(p, 0);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-double-free.c b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-double-free.c
new file mode 100644
index 0000000..a63e7e8
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-double-free.c
@@ -0,0 +1,172 @@
+/* Example of a multilevel wrapper around malloc/free, with a double-'free'.  */
+
+/* { dg-options "--analyzer -fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fanalyzer-checker=malloc -fanalyzer-verbose-state-changes -fdiagnostics-show-caret" } */
+
+#include <stdlib.h>
+
+void *wrapped_malloc (size_t size)
+{
+  return malloc (size);
+}
+
+void wrapped_free (void *ptr)
+{
+  free (ptr); /* { dg-warning "double-'free' of 'ptr' \\\[CWE-415\\\]" } */
+}
+
+typedef struct boxed_int
+{
+  int i;
+} boxed_int;
+
+boxed_int *
+make_boxed_int (int i)
+{
+  boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+  if (!result)
+    abort ();
+  result->i = i;
+  return result;
+}
+
+void
+free_boxed_int (boxed_int *bi)
+{
+  wrapped_free (bi);
+}
+
+void test (int i)
+{
+  boxed_int *obj = make_boxed_int (i);
+
+  free_boxed_int (obj);
+
+  free_boxed_int (obj);
+}
+
+/* double-'free'.  */
+/* { dg-begin-multiline-output "" }
+   NN |   free (ptr);
+      |   ^~~~~~~~~~
+  'test': events 1-2
+    |
+    |   NN | void test (int i)
+    |      |      ^~~~
+    |      |      |
+    |      |      (1) entry to 'test'
+    |   NN | {
+    |   NN |   boxed_int *obj = make_boxed_int (i);
+    |      |                    ~~~~~~~~~~~~~~~~~~
+    |      |                    |
+    |      |                    (2) calling 'make_boxed_int' from 'test'
+    |
+    +--> 'make_boxed_int': events 3-6
+           |
+           |   NN | make_boxed_int (int i)
+           |      | ^~~~~~~~~~~~~~
+           |      | |
+           |      | (3) entry to 'make_boxed_int'
+           |......
+           |   NN |   if (!result)
+           |      |      ~
+           |      |      |
+           |      |      (4) following 'false' branch (when 'result' is non-NULL)...
+           |   NN |     abort ();
+           |   NN |   result->i = i;
+           |      |   ~~~~~~~~~~~~~
+           |      |             |
+           |      |             (5) ...to here
+           |   NN |   return result;
+           |      |          ~~~~~~
+           |      |          |
+           |      |          (6) state of '<return-value>': 'start' -> 'nonnull' (origin: NULL)
+           |
+    <------+
+    |
+  'test': events 7-8
+    |
+    |   NN |   boxed_int *obj = make_boxed_int (i);
+    |      |                    ^~~~~~~~~~~~~~~~~~
+    |      |                    |
+    |      |                    (7) returning to 'test' from 'make_boxed_int'
+    |   NN | 
+    |   NN |   free_boxed_int (obj);
+    |      |   ~~~~~~~~~~~~~~~~~~~~
+    |      |   |
+    |      |   (8) calling 'free_boxed_int' from 'test'
+    |
+    +--> 'free_boxed_int': events 9-10
+           |
+           |   NN | free_boxed_int (boxed_int *bi)
+           |      | ^~~~~~~~~~~~~~
+           |      | |
+           |      | (9) entry to 'free_boxed_int'
+           |   NN | {
+           |   NN |   wrapped_free (bi);
+           |      |   ~~~~~~~~~~~~~~~~~
+           |      |   |
+           |      |   (10) calling 'wrapped_free' from 'free_boxed_int'
+           |
+           +--> 'wrapped_free': events 11-12
+                  |
+                  |   NN | void wrapped_free (void *ptr)
+                  |      |      ^~~~~~~~~~~~
+                  |      |      |
+                  |      |      (11) entry to 'wrapped_free'
+                  |   NN | {
+                  |   NN |   free (ptr);
+                  |      |   ~~~~~~~~~~
+                  |      |   |
+                  |      |   (12) first 'free' here (state of 'ptr': 'nonnull' -> 'freed', origin: NULL)
+                  |
+           <------+
+           |
+         'free_boxed_int': event 13
+           |
+           |   NN |   wrapped_free (bi);
+           |      |   ^~~~~~~~~~~~~~~~~
+           |      |   |
+           |      |   (13) returning to 'free_boxed_int' from 'wrapped_free'
+           |
+    <------+
+    |
+  'test': events 14-15
+    |
+    |   NN |   free_boxed_int (obj);
+    |      |   ^~~~~~~~~~~~~~~~~~~~
+    |      |   |
+    |      |   (14) returning to 'test' from 'free_boxed_int'
+    |   NN | 
+    |   NN |   free_boxed_int (obj);
+    |      |   ~~~~~~~~~~~~~~~~~~~~
+    |      |   |
+    |      |   (15) passing freed pointer 'obj' in call to 'free_boxed_int' from 'test'
+    |
+    +--> 'free_boxed_int': events 16-17
+           |
+           |   NN | free_boxed_int (boxed_int *bi)
+           |      | ^~~~~~~~~~~~~~
+           |      | |
+           |      | (16) entry to 'free_boxed_int'
+           |   NN | {
+           |   NN |   wrapped_free (bi);
+           |      |   ~~~~~~~~~~~~~~~~~
+           |      |   |
+           |      |   (17) passing freed pointer 'bi' in call to 'wrapped_free' from 'free_boxed_int'
+           |
+           +--> 'wrapped_free': events 18-19
+                  |
+                  |   NN | void wrapped_free (void *ptr)
+                  |      |      ^~~~~~~~~~~~
+                  |      |      |
+                  |      |      (18) entry to 'wrapped_free'
+                  |   NN | {
+                  |   NN |   free (ptr);
+                  |      |   ~~~~~~~~~~
+                  |      |   |
+                  |      |   (19) second 'free' here; first 'free' was at (12) ('ptr' is in state 'freed')
+                  |
+   { dg-end-multiline-output "" } */
+
+/* TODO: the event describing the allocation is uninteresting and probably
+   should be purged.  */
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-unchecked.c b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-unchecked.c
new file mode 100644
index 0000000..a3a3909
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-unchecked.c
@@ -0,0 +1,66 @@
+/* Example of a multilevel wrapper around malloc, with an unchecked write.  */
+
+/* { dg-options "--analyzer -fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fdiagnostics-path-format=inline-events -fanalyzer-checker=malloc -fdiagnostics-show-caret -fanalyzer-verbose-state-changes" } */
+
+#include <stdlib.h>
+
+void *wrapped_malloc (size_t size)
+{
+  return malloc (size);
+}
+
+typedef struct boxed_int
+{
+  int i;
+} boxed_int;
+
+boxed_int *
+make_boxed_int (int i)
+{
+  boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+  result->i = i; /* { dg-warning "dereference of possibly-NULL 'result'" } */
+  return result;
+}
+
+/* "dereference of possibly-NULL 'result' [CWE-690]".  */
+/* { dg-begin-multiline-output "" }
+   NN |   result->i = i;
+      |   ~~~~~~~~~~^~~
+  'make_boxed_int': events 1-2
+    |
+    |   NN | make_boxed_int (int i)
+    |      | ^~~~~~~~~~~~~~
+    |      | |
+    |      | (1) entry to 'make_boxed_int'
+    |   NN | {
+    |   NN |   boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+    |      |                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+    |      |                                    |
+    |      |                                    (2) calling 'wrapped_malloc' from 'make_boxed_int'
+    |
+    +--> 'wrapped_malloc': events 3-4
+           |
+           |   NN | void *wrapped_malloc (size_t size)
+           |      |       ^~~~~~~~~~~~~~
+           |      |       |
+           |      |       (3) entry to 'wrapped_malloc'
+           |   NN | {
+           |   NN |   return malloc (size);
+           |      |          ~~~~~~~~~~~~~
+           |      |          |
+           |      |          (4) this call could return NULL (state of '<return-value>': 'start' -> 'unchecked', origin: NULL)
+           |
+    <------+
+    |
+  'make_boxed_int': events 5-6
+    |
+    |   NN |   boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+    |      |                                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+    |      |                                    |
+    |      |                                    (5) possible return of NULL to 'make_boxed_int' from 'wrapped_malloc'
+    |   NN |   result->i = i;
+    |      |   ~~~~~~~~~~~~~                     
+    |      |             |
+    |      |             (6) 'result' could be NULL: unchecked value from (4) ('result' is in state 'unchecked')
+    |
+  { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-9.c b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-9.c
new file mode 100644
index 0000000..03bcbdb
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-9.c
@@ -0,0 +1,18 @@
+/* { dg-options "--analyzer -fdiagnostics-path-format=none -fanalyzer-verbosity=1" } */
+
+#include <stdlib.h>
+
+void
+two_frees (void *p, void *q)
+{
+  free (p);
+  free (q); /* { dg-warning "double-'free' of 'q'" } */
+  /* TODO: could be useful to identify that p == q when called from 'test'.  */
+}
+
+extern void do_stuff (void);
+
+void test (void *ptr)
+{
+  two_frees (ptr, ptr);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-macro-inline-events.c b/gcc/testsuite/gcc.dg/analyzer/malloc-macro-inline-events.c
new file mode 100644
index 0000000..a34b97b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-macro-inline-events.c
@@ -0,0 +1,45 @@
+/* Test path-printing in the face of macros.  */
+
+/* { dg-options "--analyzer -fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fdiagnostics-path-format=inline-events -fdiagnostics-show-caret" } */
+
+#include "malloc-macro.h"
+
+/* { dg-warning "double-'free' of 'ptr'" "" { target *-*-* } 2 } */
+
+int test (void *ptr)
+{
+  WRAPPED_FREE (ptr); /* { dg-message "in expansion of macro 'WRAPPED_FREE'" } */
+  WRAPPED_FREE (ptr); /* { dg-message "in expansion of macro 'WRAPPED_FREE'" } */
+
+  /* Erase the spans indicating the header file
+     (to avoid embedding path assumptions).  */
+  /* { dg-regexp "\[^|\]+/malloc-macro.h:\[0-9\]+:\[0-9\]+:" } */
+  /* { dg-regexp "\[^|\]+/malloc-macro.h:\[0-9\]+:\[0-9\]+:" } */
+
+  /* { dg-begin-multiline-output "" }
+   NN | #define WRAPPED_FREE(PTR) free(PTR)
+      |                           ^~~~~~~~~
+   NN |   WRAPPED_FREE (ptr);
+      |   ^~~~~~~~~~~~
+  'test': event 1
+    |
+    |
+    |   NN | #define WRAPPED_FREE(PTR) free(PTR)
+    |      |                           ^~~~~~~~~
+    |      |                           |
+    |      |                           (1) first 'free' here
+    |   NN |   WRAPPED_FREE (ptr);
+    |      |   ^~~~~~~~~~~~
+    |
+  'test': event 2
+    |
+    |
+    |   NN | #define WRAPPED_FREE(PTR) free(PTR)
+    |      |                           ^~~~~~~~~
+    |      |                           |
+    |      |                           (2) second 'free' here; first 'free' was at (1)
+    |   NN |   WRAPPED_FREE (ptr);
+    |      |   ^~~~~~~~~~~~
+    |
+     { dg-end-multiline-output "" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-macro-separate-events.c b/gcc/testsuite/gcc.dg/analyzer/malloc-macro-separate-events.c
new file mode 100644
index 0000000..9c7e933
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-macro-separate-events.c
@@ -0,0 +1,15 @@
+/* Test path-printing in the face of macros.  */
+
+/* { dg-options "--analyzer -fdiagnostics-path-format=separate-events" } */
+
+#include "malloc-macro.h"
+
+/* { dg-warning "double-'free' of 'ptr'" "" { target *-*-* } 2 } */
+/* { dg-message "first 'free' here" "" { target *-*-* } 2 } */
+/* { dg-message "second 'free' here" "" { target *-*-* } 2 } */
+
+int test (void *ptr)
+{
+  WRAPPED_FREE (ptr); /* { dg-message "in expansion of macro 'WRAPPED_FREE'" } */
+  WRAPPED_FREE (ptr); /* { dg-message "in expansion of macro 'WRAPPED_FREE'" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-macro.h b/gcc/testsuite/gcc.dg/analyzer/malloc-macro.h
new file mode 100644
index 0000000..8c05b40
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-macro.h
@@ -0,0 +1,2 @@
+#include <stdlib.h>
+#define WRAPPED_FREE(PTR) free(PTR)
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-1.c b/gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-1.c
new file mode 100644
index 0000000..82ad547
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-1.c
@@ -0,0 +1,14 @@
+#include <stdlib.h>
+
+/* Ensure that we don't need to laboriously walk every path to get
+   to the end of the function.  */
+
+int test_1 (int n)
+{
+  int i, j, k;
+  k = 0;
+  for (int i = 0; i < n; i++)
+    for (int j = 0; j < 1000; j++)
+      k++;
+  return k;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-2.c b/gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-2.c
new file mode 100644
index 0000000..9f6440b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-2.c
@@ -0,0 +1,30 @@
+#include <stdlib.h>
+
+/* Ensure that we don't get an exponential growth in paths due to
+   repeated diamonds in the CFG.  */
+
+typedef struct obj {
+  int ob_refcnt;
+} PyObject;
+
+extern void Py_Dealloc (PyObject *op);
+
+#define Py_DECREF(op)                                   \
+    do {                                                \
+      if (--((PyObject*)(op))->ob_refcnt == 0)		\
+	Py_Dealloc((PyObject *)(op));			\
+    } while (0)
+
+int test (PyObject *obj_01, PyObject *obj_02, PyObject *obj_03,
+	  PyObject *obj_04, PyObject *obj_05, PyObject *obj_06,
+	  PyObject *obj_07, PyObject *obj_08, PyObject *obj_09,
+	  PyObject *obj_10, PyObject *obj_11, PyObject *obj_12,
+	  PyObject *obj_13, PyObject *obj_14, PyObject *obj_15
+)
+{
+  Py_DECREF (obj_01); Py_DECREF (obj_02); Py_DECREF (obj_03);
+  Py_DECREF (obj_04); Py_DECREF (obj_05); Py_DECREF (obj_06);
+  Py_DECREF (obj_07); Py_DECREF (obj_08); Py_DECREF (obj_09);
+  Py_DECREF (obj_10); Py_DECREF (obj_11); Py_DECREF (obj_12);
+  Py_DECREF (obj_13); Py_DECREF (obj_14); Py_DECREF (obj_15);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-3.c b/gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-3.c
new file mode 100644
index 0000000..e5d27a4
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-3.c
@@ -0,0 +1,36 @@
+#include <stdlib.h>
+
+extern int foo (void);
+
+int successes;
+int failures;
+
+#define ONE_DIAMOND \
+    do {                                                \
+      void *ptr = malloc (128);				\
+      if (foo ())					\
+	successes++;					\
+      else						\
+	failures++;					\
+      free (ptr);					\
+    } while (0)
+
+#define TEN_DIAMONDS \
+  do {								   \
+    ONE_DIAMOND; ONE_DIAMOND; ONE_DIAMOND; ONE_DIAMOND; ONE_DIAMOND;	\
+    ONE_DIAMOND; ONE_DIAMOND; ONE_DIAMOND; ONE_DIAMOND; ONE_DIAMOND; \
+ } while (0)
+
+void test_3 (void *ptr)
+{
+  free (ptr);
+#if 1
+  ONE_DIAMOND;
+#else
+  /* TODO: enabling this leads to numerous duplicated reports,
+     all of them detailing all the extraneous info about the malloc/free
+     within the diamonds.  */
+  TEN_DIAMONDS;
+#endif
+  free (ptr); /* { dg-warning "double-'free' of 'ptr'" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-paths-1.c b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-1.c
new file mode 100644
index 0000000..5d989ea
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-1.c
@@ -0,0 +1,15 @@
+/* Verify that we emit sane paths for state machine errors.  */
+
+#include <stdlib.h>
+
+void test_1 (void)
+{
+  void *ptr = malloc (1024); /* { dg-line malloc } */
+  free (ptr); /* { dg-line first_free } */
+  free (ptr); /* { dg-line second_free } */
+
+  /* { dg-warning "double-'free' of 'ptr'"  "" { target *-*-* } second_free } */
+  /* { dg-message "\\(1\\) allocated here" "" { target *-*-* } malloc } */
+  /* { dg-message "\\(2\\) first 'free' here" "" { target *-*-* } first_free } */
+  /* { dg-message "\\(3\\) second 'free' here; first 'free' was at \\(2\\)" "" { target *-*-* } second_free } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-paths-10.c b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-10.c
new file mode 100644
index 0000000..3eb5ffc
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-10.c
@@ -0,0 +1,19 @@
+#include <stdlib.h>
+
+int test (int flag)
+{
+  int other_flag;
+  if (flag)
+    other_flag = 1;
+  else
+    other_flag = 0;
+
+  /* With state-merging, we lose the relationship between 'flag' and 'other_flag'.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+  if (other_flag)
+    __analyzer_eval (flag); /* { dg-warning "UNKNOWN" } */
+  else
+    __analyzer_eval (flag); /* { dg-warning "UNKNOWN" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-paths-2.c b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-2.c
new file mode 100644
index 0000000..a9bf7a9
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-2.c
@@ -0,0 +1,13 @@
+/* Verify that we emit sane paths for state machine errors.  */
+
+#include <stdlib.h>
+
+void test_2 (void *ptr)
+{
+  free (ptr); /* { dg-line first_free } */
+  free (ptr); /* { dg-line second_free } */
+
+  /* { dg-warning "double-'free' of 'ptr'"  "" { target *-*-* } second_free } */
+  /* { dg-message "\\(1\\) first 'free' here" "" { target *-*-* } first_free } */
+  /* { dg-message "\\(2\\) second 'free' here; first 'free' was at \\(1\\)" "" { target *-*-* } second_free } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-paths-3.c b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-3.c
new file mode 100644
index 0000000..ed60260
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-3.c
@@ -0,0 +1,14 @@
+/* Verify that we emit sane paths for state machine errors.  */
+
+#include <stdlib.h>
+
+int *test_3 (void)
+{
+  int *ptr = (int *)malloc (sizeof (int)); /* { dg-line malloc } */
+  *ptr = 42; /* { dg-line unchecked_deref } */
+  return ptr;
+
+  /* { dg-warning "dereference of possibly-NULL 'ptr'" "" { target *-*-* } unchecked_deref } */
+  /* { dg-message "\\(1\\) this call could return NULL" "" { target *-*-* } malloc } */
+  /* { dg-message "\\(2\\) 'ptr' could be NULL" "" { target *-*-* } unchecked_deref } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-paths-4.c b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-4.c
new file mode 100644
index 0000000..3385245
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-4.c
@@ -0,0 +1,20 @@
+/* Verify that we emit sane paths for state machine errors.  */
+
+#include <stdlib.h>
+
+int *test_4 (void)
+{
+  int *ptr = (int *)malloc (sizeof (int)); /* { dg-line malloc } */
+  if (ptr) /* { dg-line cond } */
+    *ptr = 42;
+  else
+    *ptr = 43; /* { dg-line on_null_ptr } */
+  return ptr;
+
+  /* { dg-warning "dereference of NULL 'ptr'" "" { target *-*-* } on_null_ptr } */
+  /* { dg-message "\\(1\\) allocated here" "" { target *-*-* } malloc } */
+  /* { dg-message "\\(2\\) assuming 'ptr' is NULL" "" { target *-*-* } cond } */
+  /* { dg-message "\\(3\\) following 'false' branch \\(when 'ptr' is NULL\\)\\.\\.\\." "" { target *-*-* } cond } */
+  /* { dg-message "\\(4\\) \\.\\.\\.to here" "" { target *-*-* } on_null_ptr } */
+  /* { dg-message "\\(5\\) dereference of NULL 'ptr'" "" { target *-*-* } on_null_ptr } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-paths-5.c b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-5.c
new file mode 100644
index 0000000..b54a81f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-5.c
@@ -0,0 +1,43 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+extern void do_stuff (void);
+
+int test (const char *filename, int flag)
+{
+  FILE *f;
+  int *p, *q;
+  int i;
+
+  p = (int *)malloc (sizeof (int)); /* { dg-line malloc_of_p } */
+  if (!p) /* { dg-line test_of_p } */
+    {
+      free (p);
+      return -1;
+    }
+
+  q = (int *)malloc (sizeof (int)); /* { dg-line malloc_of_q } */
+  if (!q) /* { dg-line test_of_q } */
+    {
+      free (p); /* { dg-line first_free_of_p } */
+      /* oops: forgot the "return" here, so it falls through.  */
+    }
+
+  do_stuff ();
+
+  free (p); /* { dg-line second_free_of_p } */
+  free (q);
+  return 0;
+
+  /* { dg-warning "double-'free' of 'p'" "" { target *-*-* } second_free_of_p } */
+  /* { dg-message "\\(1\\) allocated here" "" { target *-*-* } malloc_of_p } */
+  /* { dg-message "\\(2\\) assuming 'p' is non-NULL" "" { target *-*-* } test_of_p } */
+  /* { dg-message "\\(3\\) following 'false' branch \\(when 'p' is non-NULL\\)\\.\\.\\." "" { target *-*-* } test_of_p } */
+  /* { dg-message "\\(4\\) \\.\\.\\.to here" "" { target *-*-* } malloc_of_q } */
+  /* { dg-message "\\(5\\) following 'true' branch \\(when 'q' is NULL\\)\\.\\.\\." "" { target *-*-* } test_of_q } */
+  /* { dg-message "\\(6\\) \\.\\.\\.to here" "" { target *-*-* } first_free_of_p } */
+  /* { dg-message "\\(7\\) first 'free' here" "" { target *-*-* } first_free_of_p } */
+  /* { dg-message "\\(8\\) second 'free' here; first 'free' was at \\(7\\)" "" { target *-*-* } second_free_of_p } */
+
+  /* We don't care about the state changes to q.  */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-paths-6.c b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-6.c
new file mode 100644
index 0000000..1df6964
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-6.c
@@ -0,0 +1,11 @@
+#include <stdlib.h>
+
+void test (void *ptr)
+{
+  void *q;
+  q = ptr;
+  free (ptr);
+  free (q); /* { dg-warning "double-'free' of 'q'" } */
+  /* The above case requires us to handle equivalence classes in
+     state transitions.  */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-paths-7.c b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-7.c
new file mode 100644
index 0000000..eb55604
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-7.c
@@ -0,0 +1,21 @@
+#include <stdlib.h>
+
+extern int foo (void);
+extern int bar (void);
+
+void test (void)
+{
+  void *p = malloc (1024); /* { dg-message "\\(1\\) allocated here" } */ 
+  void *q = malloc (1024);
+
+  foo ();
+  if (!q) /* { dg-message "\\(2\\) following 'true' branch \\(when 'q' is NULL\\)\\.\\.\\." } */ 
+    {
+      free (q); /* { dg-message "\\(3\\) \\.\\.\\.to here" } */ 
+      return; /* { dg-warning "leak of 'p'" } */ 
+      /* { dg-message "\\(4\\) 'p' leaks here; was allocated at \\(1\\)" "" { target *-*-* } .-1 } */
+    }
+  bar ();
+  free (q);
+  free (p);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-paths-8.c b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-8.c
new file mode 100644
index 0000000..bf858e0
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-8.c
@@ -0,0 +1,54 @@
+/* { dg-additional-options "-fanalyzer-transitivity" } */
+
+#include <stddef.h>
+#include <stdlib.h>
+
+extern void do_stuff (const void *);
+
+#define LIMIT 1024
+
+void test_1 (size_t sz)
+{
+  void *ptr;
+  if (sz >= LIMIT)
+    ptr = malloc (sz);
+  else
+    ptr = alloca (sz);
+
+  do_stuff (ptr);
+
+  if (sz >= LIMIT)
+    free (ptr);
+}
+
+void test_2 (size_t sz)
+{
+  void *ptr;
+  if (sz < LIMIT)
+    ptr = alloca (sz);
+  else
+    ptr = malloc (sz);
+
+  do_stuff (ptr);
+
+  if (sz >= LIMIT)
+    free (ptr);
+}
+
+void test_3 (size_t sz)
+{
+  void *ptr;
+  if (sz <= LIMIT)
+    ptr = alloca (sz); /* { dg-message "memory is allocated on the stack here" } */
+  else
+    ptr = malloc (sz);
+
+  do_stuff (ptr);
+
+  /* Bug: the "sz <= LIMIT" above should have been "sz < LIMIT",
+     so there's a free-of-alloca when sz == LIMIT.  */
+  if (sz >= LIMIT)
+    free (ptr); /* { dg-warning "'free' of memory allocated on the stack by 'alloca'" } */
+}
+/* { dg-bogus "leak of 'ptr'" } */
+/* This can't happen, as "sz > 1024" && "sz <= 1023" is impossible.  */
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-paths-9.c b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-9.c
new file mode 100644
index 0000000..6bee885
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-9.c
@@ -0,0 +1,298 @@
+/* { dg-additional-options "-fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fdiagnostics-path-format=inline-events -fdiagnostics-show-caret" } */
+
+#include <stdlib.h>
+
+void test_1 (void)
+{
+  void *ptr = malloc (1024);
+  free (ptr);
+  free (ptr); /* { dg-warning "double-'free' of 'ptr'"  } */
+}
+/* { dg-begin-multiline-output "" }
+   NN |   free (ptr);
+      |   ^~~~~~~~~~
+  'test_1': events 1-3
+    |
+    |   NN |   void *ptr = malloc (1024);
+    |      |               ^~~~~~~~~~~~~
+    |      |               |
+    |      |               (1) allocated here
+    |   NN |   free (ptr);
+    |      |   ~~~~~~~~~~   
+    |      |   |
+    |      |   (2) first 'free' here
+    |   NN |   free (ptr);
+    |      |   ~~~~~~~~~~   
+    |      |   |
+    |      |   (3) second 'free' here; first 'free' was at (2)
+    |
+   { dg-end-multiline-output "" } */
+
+void test_2 (int x, int y)
+{
+  void *ptr = malloc (1024);
+  if (x)
+    free (ptr);
+  if (y)
+    free (ptr); /* { dg-warning "double-'free' of 'ptr'"  } */
+} /* { dg-warning "leak of 'ptr'"  } */
+
+/* "double-'free' of 'ptr'".  */
+/* { dg-begin-multiline-output "" }
+   NN |     free (ptr);
+      |     ^~~~~~~~~~
+  'test_2': events 1-7
+    |
+    |   NN |   void *ptr = malloc (1024);
+    |      |               ^~~~~~~~~~~~~
+    |      |               |
+    |      |               (1) allocated here
+    |   NN |   if (x)
+    |      |      ~         
+    |      |      |
+    |      |      (2) following 'true' branch (when 'x != 0')...
+    |   NN |     free (ptr);
+    |      |     ~~~~~~~~~~ 
+    |      |     |
+    |      |     (3) ...to here
+    |      |     (4) first 'free' here
+    |   NN |   if (y)
+    |      |      ~         
+    |      |      |
+    |      |      (5) following 'true' branch (when 'y != 0')...
+    |   NN |     free (ptr);
+    |      |     ~~~~~~~~~~ 
+    |      |     |
+    |      |     (6) ...to here
+    |      |     (7) second 'free' here; first 'free' was at (4)
+    |
+   { dg-end-multiline-output "" } */
+
+/* "leak of 'ptr'.  */
+/* { dg-begin-multiline-output "" }
+   NN | }
+      | ^
+  'test_2': events 1-6
+    |
+    |   NN |   void *ptr = malloc (1024);
+    |      |               ^~~~~~~~~~~~~
+    |      |               |
+    |      |               (1) allocated here
+    |   NN |   if (x)
+    |      |      ~         
+    |      |      |
+    |      |      (2) following 'false' branch (when 'x == 0')...
+    |   NN |     free (ptr);
+    |   NN |   if (y)
+    |      |      ~         
+    |      |      |
+    |      |      (3) ...to here
+    |      |      (4) following 'false' branch (when 'y == 0')...
+    |   NN |     free (ptr);
+    |   NN | }
+    |      | ~              
+    |      | |
+    |      | (5) ...to here
+    |      | (6) 'ptr' leaks here; was allocated at (1)
+    |
+   { dg-end-multiline-output "" } */
+
+int test_3 (int x, int y)
+{
+  int *ptr = (int *)malloc (sizeof (int));
+  *ptr = 42; /* { dg-warning "dereference of possibly-NULL 'ptr'" } */
+  if (x)
+    free (ptr);
+
+  *ptr = 19; /* { dg-warning "use after 'free' of 'ptr'" } */
+  // TODO: two warnings here:  one is from sm-malloc, the other from region model
+
+  if (y)
+    free (ptr); /* No double-'free' warning: we've already attempted
+		   to dereference it above.  */
+  return *ptr; /* { dg-warning "use after 'free' of 'ptr'" } */
+  // TODO: two warnings here:  one is from sm-malloc, the other from region model
+  /* { dg-warning "leak of 'ptr'" "" { target *-*-* } .-2 } */
+}
+
+/* "dereference of possibly-NULL 'ptr'".  */
+/* { dg-begin-multiline-output "" }
+   NN |   *ptr = 42;
+      |   ~~~~~^~~~
+  'test_3': events 1-2
+    |
+    |   NN |   int *ptr = (int *)malloc (sizeof (int));
+    |      |                     ^~~~~~~~~~~~~~~~~~~~~
+    |      |                     |
+    |      |                     (1) this call could return NULL
+    |   NN |   *ptr = 42;
+    |      |   ~~~~~~~~~          
+    |      |        |
+    |      |        (2) 'ptr' could be NULL: unchecked value from (1)
+    |
+   { dg-end-multiline-output "" } */
+
+/* "use after 'free' of 'ptr'".  */
+/* { dg-begin-multiline-output "" }
+   NN |   *ptr = 19;
+      |   ~~~~~^~~~
+  'test_3': events 1-6
+    |
+    |   NN |   int *ptr = (int *)malloc (sizeof (int));
+    |      |                     ^~~~~~~~~~~~~~~~~~~~~
+    |      |                     |
+    |      |                     (1) allocated here
+    |   NN |   *ptr = 42;
+    |      |   ~~~~~~~~~          
+    |      |        |
+    |      |        (2) assuming 'ptr' is non-NULL
+    |   NN |   if (x)
+    |      |      ~               
+    |      |      |
+    |      |      (3) following 'true' branch (when 'x != 0')...
+    |   NN |     free (ptr);
+    |      |     ~~~~~~~~~~       
+    |      |     |
+    |      |     (4) ...to here
+    |      |     (5) freed here
+    |   NN | 
+    |   NN |   *ptr = 19;
+    |      |   ~~~~~~~~~          
+    |      |        |
+    |      |        (6) use after 'free' of 'ptr'; freed at (5)
+    |
+   { dg-end-multiline-output "" } */
+
+/* "use after 'free' of 'ptr'".  */
+/* { dg-begin-multiline-output "" }
+   NN |   return *ptr;
+      |          ^~~~
+  'test_3': events 1-8
+    |
+    |   NN |   int *ptr = (int *)malloc (sizeof (int));
+    |      |                     ^~~~~~~~~~~~~~~~~~~~~
+    |      |                     |
+    |      |                     (1) allocated here
+    |   NN |   *ptr = 42;
+    |      |   ~~~~~~~~~          
+    |      |        |
+    |      |        (2) assuming 'ptr' is non-NULL
+    |   NN |   if (x)
+    |      |      ~               
+    |      |      |
+    |      |      (3) following 'false' branch (when 'x == 0')...
+    |......
+    |   NN |   *ptr = 19;
+    |      |   ~~~~~~~~~          
+    |      |        |
+    |      |        (4) ...to here
+    |......
+    |   NN |   if (y)
+    |      |      ~               
+    |      |      |
+    |      |      (5) following 'true' branch (when 'y != 0')...
+    |   NN |     free (ptr);
+    |      |     ~~~~~~~~~~       
+    |      |     |
+    |      |     (6) ...to here
+    |      |     (7) freed here
+    |   NN |      
+    |   NN |   return *ptr;
+    |      |          ~~~~        
+    |      |          |
+    |      |          (8) use after 'free' of 'ptr'; freed at (7)
+    |
+   { dg-end-multiline-output "" } */
+
+/* "leak of 'ptr'".  */
+/* { dg-begin-multiline-output "" }
+   NN |   return *ptr;
+      |          ^~~~
+  'test_3': events 1-7
+    |
+    |   NN |   int *ptr = (int *)malloc (sizeof (int));
+    |      |                     ^~~~~~~~~~~~~~~~~~~~~
+    |      |                     |
+    |      |                     (1) allocated here
+    |   NN |   *ptr = 42;
+    |      |   ~~~~~~~~~          
+    |      |        |
+    |      |        (2) assuming 'ptr' is non-NULL
+    |   NN |   if (x)
+    |      |      ~               
+    |      |      |
+    |      |      (3) following 'false' branch (when 'x == 0')...
+    |......
+    |   NN |   *ptr = 19;
+    |      |   ~~~~~~~~~          
+    |      |        |
+    |      |        (4) ...to here
+    |......
+    |   NN |   if (y)
+    |      |      ~               
+    |      |      |
+    |      |      (5) following 'false' branch (when 'y == 0')...
+    |......
+    |   NN |   return *ptr;
+    |      |          ~~~~        
+    |      |          |
+    |      |          (6) ...to here
+    |      |          (7) 'ptr' leaks here; was allocated at (1)
+    |
+   { dg-end-multiline-output "" } */
+
+/* "use after 'free' of 'ptr'".  */
+/* { dg-begin-multiline-output "" }
+   NN |   *ptr = 19;
+      |   ~~~~~^~~~
+  'test_3': events 1-3
+    |
+    |   NN |   if (x)
+    |      |      ^
+    |      |      |
+    |      |      (1) following 'true' branch (when 'x != 0')...
+    |   NN |     free (ptr);
+    |      |     ~~~~~~~~~~
+    |      |     |
+    |      |     (2) ...to here
+    |   NN | 
+    |   NN |   *ptr = 19;
+    |      |   ~~~~~~~~~
+    |      |        |
+    |      |        (3) use after 'free' of 'ptr' here
+    |
+   { dg-end-multiline-output "" } */
+
+/* "use after 'free' of 'ptr'".  */
+/* { dg-begin-multiline-output "" }
+   NN |   return *ptr;
+      |          ^~~~
+  'test_3': events 1-5
+    |
+    |   NN |   if (x)
+    |      |      ^
+    |      |      |
+    |      |      (1) following 'false' branch (when 'x == 0')...
+    |......
+    |   NN |   *ptr = 19;
+    |      |   ~~~~~~~~~
+    |      |        |
+    |      |        (2) ...to here
+    |......
+    |   NN |   if (y)
+    |      |      ~
+    |      |      |
+    |      |      (3) following 'true' branch (when 'y != 0')...
+    |   NN |     free (ptr);
+    |      |     ~~~~~~~~~~
+    |      |     |
+    |      |     (4) ...to here
+    |   NN |      to dereference it above
+    |   NN |   return *ptr;
+    |      |          ~~~~
+    |      |          |
+    |      |          (5) use after 'free' of 'ptr' here
+    |
+   { dg-end-multiline-output "" } */
+/* TODO: this is really a duplicate; can we either eliminate it, or
+   improve the path?  */
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-1a.c b/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-1a.c
new file mode 100644
index 0000000..d7dfb06
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-1a.c
@@ -0,0 +1,180 @@
+/* { dg-additional-options "-fno-analyzer-call-summaries -fanalyzer-transitivity" } */
+
+#include <stdlib.h>
+
+extern int foo (int);
+
+static int __attribute__((noinline))
+do_stuff (int *p, int n)
+{
+  int sum = 0;
+  int i;
+  for (i = 0; i < n; i++)
+    p[i] = i;
+  for (i = 0; i < n; i++)
+    sum += foo (p[i]); /* { dg-bogus "uninitialized" } */
+  return sum;
+}
+
+static int __attribute__((noinline))
+do_stuff_2 (int *p, int n)
+{
+  return 0;
+}
+
+/* Various examples of functions that use either a malloc buffer
+   or a local buffer, do something, then conditionally free the
+   buffer, tracking whether "free" is necessary in various
+   ways.
+
+   In each case, there ought to be only two paths through the function,
+   not four.  */
+
+/* Repeated (n > 10) predicate.  */
+
+int test_repeated_predicate_1 (int n)
+{
+  int buf[10];
+  int *ptr;
+  int result;
+
+  if (n > 10)
+    ptr = (int *)malloc (sizeof (int) * n);
+  else
+    ptr = buf;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  result = do_stuff (ptr, n);
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "3 exploded nodes" } */
+  // FIXME: why 3 here?
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "3 exploded nodes" } */
+  // FIXME: why 3 here?
+
+  if (n > 10)
+    free (ptr); /* { dg-bogus "not on the heap" } */
+
+  return result; /* { dg-bogus "leak" } */
+}
+
+/* A simpler version of the above.  */
+
+int test_repeated_predicate_2 (int n)
+{
+  int buf[10];
+  int *ptr;
+  int result;
+
+  if (n > 10)
+    ptr = (int *)malloc (sizeof (int) * n);
+  else
+    ptr = buf;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  result = do_stuff_2 (ptr, n);
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  if (n > 10)
+    free (ptr); /* { dg-bogus "not on the heap" } */
+
+  return result; /* { dg-bogus "leak" } */
+}
+
+/* A predicate that sets a flag for the 2nd test.  */
+
+int test_explicit_flag (int n)
+{
+  int buf[10];
+  int *ptr;
+  int result;
+  int need_to_free = 0;
+
+  if (n > 10)
+    {
+      ptr = (int *)malloc (sizeof (int) * n);
+      need_to_free = 1;
+    }
+  else
+    ptr = buf;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  result = do_stuff (ptr, n);
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "3 exploded nodes" } */
+  // FIXME: why 3 here?
+
+  if (need_to_free)
+    free (ptr); /* { dg-bogus "not on the heap" } */
+
+  return result; /* { dg-bogus "leak" } */
+}
+
+/* Pointer comparison.  */
+
+int test_pointer_comparison (int n)
+{
+  int buf[10];
+  int *ptr;
+  int result;
+
+  if (n > 10)
+    ptr = (int *)malloc (sizeof (int) * n);
+  else
+    ptr = buf;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  result = do_stuff (ptr, n);
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "3 exploded nodes" } */
+  // FIXME: why 3 here?
+
+  if (ptr != buf)
+    free (ptr); /* { dg-bogus "not on the heap" } */
+
+  return result; /* { dg-bogus "leak" } */
+}
+
+/* Set a flag based on a conditional, then use it, then reuse the
+   conditional.  */
+
+int test_initial_flag (int n)
+{
+  int buf[10];
+  int *ptr;
+  int result;
+  int on_heap = 0;
+
+  if (n > 10)
+    on_heap = 1;
+  else
+    on_heap = 0;
+
+  /* Due to state-merging, we lose the relationship between 'n > 10'
+     and 'on_heap' here; we have to rely on feasibility-checking
+     in the diagnostic_manager to reject the false warnings.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+  if (on_heap)
+    ptr = (int *)malloc (sizeof (int) * n);
+  else
+    ptr = buf;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  result = do_stuff (ptr, n);
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "5 exploded nodes" } */
+  // FIXME: why 5 here?
+
+  if (n > 10)
+    free (ptr); /* { dg-bogus "not on the heap" } */
+
+  return result; /* { dg-bogus "leak" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-1b.c b/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-1b.c
new file mode 100644
index 0000000..7f1dd91
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-1b.c
@@ -0,0 +1,175 @@
+/* { dg-additional-options "-fanalyzer-call-summaries" } */
+
+#include <stdlib.h>
+
+extern int foo (int);
+
+static int __attribute__((noinline))
+do_stuff (int *p, int n)
+{
+  int sum = 0;
+  int i;
+  for (i = 0; i < n; i++)
+    p[i] = i;
+  for (i = 0; i < n; i++)
+    sum += foo (p[i]); /* { dg-bogus "uninitialized" } */
+  return sum;
+}
+
+static int __attribute__((noinline))
+do_stuff_2 (int *p, int n)
+{
+  return 0;
+}
+
+/* Various examples of functions that use either a malloc buffer
+   or a local buffer, do something, then conditionally free the
+   buffer, tracking whether "free" is necessary in various
+   ways.
+
+   In each case, there ought to be only two paths through the function,
+   not four.  */
+
+/* Repeated (n > 10) predicate.  */
+
+int test_repeated_predicate_1 (int n)
+{
+  int buf[10];
+  int *ptr;
+  int result;
+
+  if (n > 10)
+    ptr = (int *)malloc (sizeof (int) * n);
+  else
+    ptr = buf;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  result = do_stuff (ptr, n);
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  if (n > 10)
+    free (ptr); /* { dg-bogus "not on the heap" } */
+
+  return result; /* { dg-bogus "leak" } */
+}
+
+/* A simpler version of the above.  */
+
+int test_repeated_predicate_2 (int n)
+{
+  int buf[10];
+  int *ptr;
+  int result;
+
+  if (n > 10)
+    ptr = (int *)malloc (sizeof (int) * n);
+  else
+    ptr = buf;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  result = do_stuff_2 (ptr, n);
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  if (n > 10)
+    free (ptr); /* { dg-bogus "not on the heap" } */
+
+  return result; /* { dg-bogus "leak" } */
+}
+
+/* A predicate that sets a flag for the 2nd test.  */
+
+int test_explicit_flag (int n)
+{
+  int buf[10];
+  int *ptr;
+  int result;
+  int need_to_free = 0;
+
+  if (n > 10)
+    {
+      ptr = (int *)malloc (sizeof (int) * n);
+      need_to_free = 1;
+    }
+  else
+    ptr = buf;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  result = do_stuff (ptr, n);
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  if (need_to_free)
+    free (ptr); /* { dg-bogus "not on the heap" } */
+
+  return result; /* { dg-bogus "leak" } */
+}
+
+/* Pointer comparison.  */
+
+int test_pointer_comparison (int n)
+{
+  int buf[10];
+  int *ptr;
+  int result;
+
+  if (n > 10)
+    ptr = (int *)malloc (sizeof (int) * n);
+  else
+    ptr = buf;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  result = do_stuff (ptr, n);
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  if (ptr != buf)
+    free (ptr); /* { dg-bogus "not on the heap" } */
+
+  return result; /* { dg-bogus "leak" } */
+}
+
+/* Set a flag based on a conditional, then use it, then reuse the
+   conditional.  */
+
+int test_initial_flag (int n)
+{
+  int buf[10];
+  int *ptr;
+  int result;
+  int on_heap = 0;
+
+  if (n > 10)
+    on_heap = 1;
+  else
+    on_heap = 0;
+
+  /* Due to state-merging, we lose the relationship between 'n > 10'
+     and 'on_heap' here; we have to rely on feasibility-checking
+     in the diagnostic_manager to reject the false warnings.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+  if (on_heap)
+    ptr = (int *)malloc (sizeof (int) * n);
+  else
+    ptr = buf;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  result = do_stuff (ptr, n);
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  if (n > 10)
+    free (ptr); /* { dg-bogus "not on the heap" } */
+
+  return result; /* { dg-bogus "leak" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-2.c b/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-2.c
new file mode 100644
index 0000000..0307872
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-2.c
@@ -0,0 +1,178 @@
+#include <stdlib.h>
+
+extern int foo (int);
+
+static int __attribute__((noinline))
+do_stuff_2 (int *p, int n)
+{
+  return 0;
+}
+
+/* As malloc-vs-local.c, but hand-inlining the logic.  */
+
+/* Repeated (n > 10) predicate.  */
+
+int test_repeated_predicate_1 (int n)
+{
+  int buf[10];
+  int *ptr;
+  int result;
+
+  if (n > 10)
+    ptr = (int *)malloc (sizeof (int) * n);
+  else
+    ptr = buf;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  {
+    int *p = ptr;
+    int sum = 0;
+    int i;
+    for (i = 0; i < n; i++)
+      p[i] = i;
+    for (i = 0; i < n; i++)
+      sum += foo (p[i]); /* { dg-bogus "uninitialized" } */
+    result = sum;
+  }
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "3 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "3 exploded nodes" } */
+
+  if (n > 10)
+    free (ptr); /* { dg-bogus "not on the heap" } */
+
+  return result; /* { dg-bogus "leak" } */
+}
+
+/* As above, but with just one loop.  */
+
+int test_repeated_predicate_1a (int n)
+{
+  int buf[10];
+  int *ptr;
+  int result;
+
+  if (n > 10)
+    ptr = (int *)malloc (sizeof (int) * n);
+  else
+    ptr = buf;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  {
+    int *p = ptr;
+    int sum = 0;
+    int i;
+    for (i = 0; i < n; i++)
+      p[i] = i;
+    result = sum;
+  }
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "3 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "3 exploded nodes" } */
+
+  if (n > 10)
+    free (ptr); /* { dg-bogus "not on the heap" } */
+
+  return result; /* { dg-bogus "leak" } */
+}
+
+/* A simpler version of the above.  */
+
+int test_repeated_predicate_2 (int n)
+{
+  int buf[10];
+  int *ptr;
+  int result;
+
+  if (n > 10)
+    ptr = (int *)malloc (sizeof (int) * n);
+  else
+    ptr = buf;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  result = do_stuff_2 (ptr, n);
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  if (n > 10)
+    free (ptr); /* { dg-bogus "not on the heap" } */
+
+  return result; /* { dg-bogus "leak" } */
+}
+
+/* A predicate that sets a flag for the 2nd test.  */
+
+int test_explicit_flag (int n)
+{
+  int buf[10];
+  int *ptr;
+  int result;
+  int need_to_free = 0;
+
+  if (n > 10)
+    {
+      ptr = (int *)malloc (sizeof (int) * n);
+      need_to_free = 1;
+    }
+  else
+    ptr = buf;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  {
+    int *p = ptr;
+    int sum = 0;
+    int i;
+    for (i = 0; i < n; i++)
+      p[i] = i;
+    for (i = 0; i < n; i++)
+      sum += foo (p[i]); /* { dg-bogus "uninitialized" } */
+    result = sum;
+  }
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "3 exploded nodes" } */
+
+  if (need_to_free)
+    free (ptr); /* { dg-bogus "not on the heap" } */
+
+  return result; /* { dg-bogus "leak" } */
+}
+
+/* Pointer comparison.  */
+
+int test_pointer_comparison (int n)
+{
+  int buf[10];
+  int *ptr;
+  int result;
+
+  if (n > 10)
+    ptr = (int *)malloc (sizeof (int) * n);
+  else
+    ptr = buf;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  {
+    int *p = ptr;
+    int sum = 0;
+    int i;
+    for (i = 0; i < n; i++)
+      p[i] = i;
+    for (i = 0; i < n; i++)
+      sum += foo (p[i]); /* { dg-bogus "uninitialized" } */
+    result = sum;
+  }
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "3 exploded nodes" } */
+
+  if (ptr != buf)
+    free (ptr); /* { dg-bogus "not on the heap" } */
+
+  return result; /* { dg-bogus "leak" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-3.c b/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-3.c
new file mode 100644
index 0000000..2a0c8a4
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-3.c
@@ -0,0 +1,65 @@
+#include <stdlib.h>
+
+extern int foo (int);
+
+static int __attribute__((noinline))
+do_stuff_2 (int *p, int n)
+{
+  return 0;
+}
+
+/* As malloc-vs-local-2.c, but with a memory leak for the "on the heap case"
+   by not attempting to free at the end.  */
+
+int test_1 (int n)
+{
+  int buf[10];
+  int *ptr;
+  int result;
+
+  if (n > 10)
+    ptr = (int *)malloc (sizeof (int) * n);
+  else
+    ptr = buf;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  {
+    int *p = ptr;
+    int sum = 0;
+    int i;
+    for (i = 0; i < n; i++)
+      p[i] = i;
+    for (i = 0; i < n; i++)
+      sum += foo (p[i]); /* { dg-bogus "uninitialized" } */
+    result = sum;
+  }
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "3 exploded nodes" } */
+
+  return result; /* { dg-message "leak of 'p'" } */
+  /* FIXME: should this be 'ptr'?  */
+}
+
+/* A simpler version of the above.  */
+
+int test_2 (int n)
+{
+  int buf[10];
+  int *ptr;
+  int result;
+
+  if (n > 10)
+    ptr = (int *)malloc (sizeof (int) * n);
+  else
+    ptr = buf;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  result = do_stuff_2 (ptr, n);
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  return result; /* { dg-message "leak of 'ptr'" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-4.c b/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-4.c
new file mode 100644
index 0000000..7410a71
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-4.c
@@ -0,0 +1,40 @@
+#include <stdlib.h>
+
+void __attribute__((noinline)) callee_1 (int *ptr)
+{
+  *ptr = 42; /* { dg-warning "dereference of possibly-NULL 'ptr'" } */
+}
+
+int test_1 (int i, int flag)
+{
+  /* Double diamond CFG; either use &i, or a malloc-ed buffer.  */
+  int *ptr = &i;
+  if (flag)
+    ptr = (int *)malloc (sizeof (int));
+  callee_1 (ptr);
+  if (flag)
+    free (ptr);
+  return i;
+}
+
+void __attribute__((noinline)) callee_2 (int *ptr)
+{
+  *ptr = 42;
+}
+
+int test_2 (int flag)
+{
+  int i;
+
+  if (flag)
+    callee_2 (&i);
+
+  callee_2 (&i);
+
+  if (!flag)
+    {
+      void *ptr = malloc (16);
+      free (ptr);
+      free (ptr); /* { dg-warning "double-'free' of 'ptr'" } */
+    }
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/operations.c b/gcc/testsuite/gcc.dg/analyzer/operations.c
new file mode 100644
index 0000000..9ea2327
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/operations.c
@@ -0,0 +1,42 @@
+void test (int i, int j)
+{
+  int k, m;
+
+  if (i > 42) {
+    __analyzer_eval (i > 42); /* { dg-warning "TRUE" } */
+
+    i += 3;
+
+    __analyzer_eval (i > 45); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+    /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+    /* TODO(xfail): do we really know this?  what about overflow?  */
+
+    i -= 1;
+
+    __analyzer_eval (i > 44); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+    /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+    /* TODO(xfail): do we really know this?  what about overflow?  */
+
+    i = 3 * i;
+
+    __analyzer_eval (i > 132); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+    /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+    /* TODO(xfail): do we really know this?  what about overflow?  */
+
+    i /= 2;
+
+    __analyzer_eval (i > 66); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+    /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+    /* TODO(xfail): do we really know this?  what about overflow?  */
+
+    /* We don't know anything about j, so we don't know anything about k: */
+    k = i + j;
+    __analyzer_eval (k == 0); /* { dg-warning "UNKNOWN" } */
+
+    /* However, we should now know that m > 67: */
+    m = i + 1;
+    __analyzer_eval (m > 67); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+    /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+    /* TODO(xfail): do we really know this?  what about overflow?  */
+  }
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/params-2.c b/gcc/testsuite/gcc.dg/analyzer/params-2.c
new file mode 100644
index 0000000..1584ebd
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/params-2.c
@@ -0,0 +1,16 @@
+#include <stdlib.h>
+
+static void ensure_equal (int a, int b)
+{
+  if (a != b)
+    abort ();
+}
+
+void test(int i, int j)
+{
+  __analyzer_eval (i == j); /* { dg-warning "UNKNOWN" } */
+
+  ensure_equal (i, j);
+
+  __analyzer_eval (i == j); /* { dg-warning "TRUE" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/params.c b/gcc/testsuite/gcc.dg/analyzer/params.c
new file mode 100644
index 0000000..ff405f2
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/params.c
@@ -0,0 +1,32 @@
+static int called_function(int j)
+{
+  int k;
+
+  __analyzer_eval (j > 4); /* { dg-warning "TRUE" } */
+
+  k = j - 1;
+
+  __analyzer_eval (k > 3); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  /* TODO(xfail): we're not then updating based on the assignment.  */
+
+  return k;
+}
+
+void test(int i)
+{
+  __analyzer_eval (i > 4); /* { dg-warning "UNKNOWN" } */
+
+  if (i > 4) {
+
+    __analyzer_eval (i > 4); /* { dg-warning "TRUE" } */
+
+    i = called_function(i);
+
+    __analyzer_eval (i > 3); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+    /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+    /* TODO(xfail): we're not updating from the returned value.  */
+  }
+
+  __analyzer_eval (i > 3); /* { dg-warning "UNKNOWN" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/paths-1.c b/gcc/testsuite/gcc.dg/analyzer/paths-1.c
new file mode 100644
index 0000000..246f6cd
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/paths-1.c
@@ -0,0 +1,16 @@
+struct foo
+{
+  int m_flag;
+};
+
+extern void bar (int);
+
+void test (struct foo *pf)
+{
+  if (pf->m_flag)
+    bar (0);
+  else
+    bar (1);
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "3 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/paths-1a.c b/gcc/testsuite/gcc.dg/analyzer/paths-1a.c
new file mode 100644
index 0000000..a6e2679
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/paths-1a.c
@@ -0,0 +1,16 @@
+union foo
+{
+  int m_flag;
+};
+
+extern void bar (int);
+
+void test (union foo *pf)
+{
+  if (pf->m_flag)
+    bar (0);
+  else
+    bar (1);
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "3 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/paths-2.c b/gcc/testsuite/gcc.dg/analyzer/paths-2.c
new file mode 100644
index 0000000..8637c8c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/paths-2.c
@@ -0,0 +1,25 @@
+int test (int a)
+{
+  if (a != 42 && a != 113) {
+    return (-2);
+  }
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "3 exploded nodes" } */
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+  return 0;
+}
+
+int test_2 (int a)
+{
+  if (a != 42 && a != 113 && a != 666) {
+    return (-2);
+  }
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "4 exploded nodes" } */
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/paths-3.c b/gcc/testsuite/gcc.dg/analyzer/paths-3.c
new file mode 100644
index 0000000..d03ab02
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/paths-3.c
@@ -0,0 +1,48 @@
+/* { dg-additional-options "-fanalyzer-transitivity" } */
+
+#include <stdlib.h>
+
+int test_1 (int a, int b)
+{
+  void *p;
+
+  if (a > 5)
+    if (b)
+      p = malloc (16);
+    else
+      p = malloc (32);
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "4 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  if (a > 5)
+    {
+      free (p);
+      __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+    }
+
+  return 0; /* { dg-bogus "leak" } */
+}
+
+int test_2 (int a, int b)
+{
+  void *p;
+
+  if (a > 5)
+    if (b)
+      p = malloc (16);
+    else
+      p = malloc (32);
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "4 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  if (a > 6) /* different condition */
+    {
+      free (p);
+      __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+    }
+
+  return 0; /* { dg-warning "leak of 'p'" } */
+  /* leaks when a == 5.  */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/paths-4.c b/gcc/testsuite/gcc.dg/analyzer/paths-4.c
new file mode 100644
index 0000000..307bc97
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/paths-4.c
@@ -0,0 +1,49 @@
+struct state
+{
+  int mode;
+  int data;
+};
+
+extern void do_stuff (struct state *, int);
+
+int test_1 (struct state *s)
+{
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+  while (1)
+    {
+      __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+      __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+      do_stuff (s, s->mode);
+    }
+}
+
+int test_2 (struct state *s)
+{
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+  while (1)
+    {
+      __analyzer_dump_exploded_nodes (0); /* { dg-warning "3 exploded nodes" } */
+      __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+      switch (s->mode)
+	{
+	case 0:
+	  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+	  do_stuff (s, 0);
+	  break;
+	case 1:
+	  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+	  do_stuff (s, 17);
+	  break;
+	case 2:
+	  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+	  do_stuff (s, 5);
+	  break;
+	case 3:
+	  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+	  return 42;
+	case 4:
+	  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+	  return -3;
+	}
+    }
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/paths-5.c b/gcc/testsuite/gcc.dg/analyzer/paths-5.c
new file mode 100644
index 0000000..d3589b5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/paths-5.c
@@ -0,0 +1,10 @@
+void test (int *p, int n)
+{
+  int i;
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+  for (i = 0; i < n; i++)
+    {
+      p[i] = i; /* { dg-bogus "uninitialized" } */
+      __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+    }
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/paths-6.c b/gcc/testsuite/gcc.dg/analyzer/paths-6.c
new file mode 100644
index 0000000..bf4b6f3
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/paths-6.c
@@ -0,0 +1,118 @@
+#include <stdlib.h>
+
+/* Verify that ordering of writes doesn't matter when merging states.  */
+
+/* Test with locals.  */
+
+void test_1 (int flag)
+{
+  int a, b;
+  if (flag)
+    {
+      a = 3;
+      b = 4;
+    }
+  else
+    {
+      b = 4;
+      a = 3;
+    }
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  // FIXME: the above can vary between 2 and 3 exploded nodes
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+  __analyzer_eval (a == 3); /* { dg-warning "TRUE" } */
+  __analyzer_eval (b == 4); /* { dg-warning "TRUE" } */
+}
+
+/* Test with globals.  */
+
+int f, g, h;
+void test_2 (int flag)
+{
+  if (flag)
+    {
+      f = 3;
+      g = 4;
+    }
+  else
+    {
+      g = 4;
+      f = 3;
+    }
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+  __analyzer_eval (f == 3); /* { dg-warning "TRUE" } */
+  __analyzer_eval (g == 4); /* { dg-warning "TRUE" } */
+}
+
+/* All 6 orderings of writes to 3 globals.  */
+
+void test_3 (int i)
+{
+  switch (i)
+    {
+    default:
+    case 0:
+      f = 3;
+      g = 4;
+      h = 5;
+      break;
+
+    case 1:
+      f = 3;
+      h = 5;
+      g = 4;
+      break;
+
+    case 2:
+      g = 4;
+      f = 3;
+      h = 5;
+      break;
+
+    case 3:
+      g = 4;
+      h = 5;
+      f = 3;
+      break;
+
+    case 4:
+      h = 5;
+      f = 3;
+      g = 4;
+      break;
+
+    case 5:
+      h = 5;
+      g = 4;
+      f = 3;
+      break;
+    }
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "6 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded node" } */
+  __analyzer_eval (f == 3); /* { dg-warning "TRUE" } */
+  __analyzer_eval (g == 4); /* { dg-warning "TRUE" } */
+  __analyzer_eval (h == 5); /* { dg-warning "TRUE" } */
+}
+
+void test_4 (int flag)
+{
+  void *p, *q;
+  if (flag)
+    {
+      p = malloc (256);
+      q = malloc (256);
+    }
+  else
+    {
+      q = malloc (256);
+      p = malloc (256);
+    }
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+  free (p);
+  free (q);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/paths-7.c b/gcc/testsuite/gcc.dg/analyzer/paths-7.c
new file mode 100644
index 0000000..7a0826d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/paths-7.c
@@ -0,0 +1,58 @@
+#include <stdlib.h>
+
+extern int foo (int);
+
+int test (int flag, void *ptr, int *p, int n)
+{
+  int result;
+  int sum = 0;
+  int i;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+  if (flag)
+    free (ptr);
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  for (i = 0; i < n; i++)
+    p[i] = i;
+  for (i = 0; i < n; i++)
+    sum += foo (p[i]); /* { dg-bogus "uninitialized" } */
+  result = sum;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  if (flag)
+    free (ptr); /* { dg-warning "double-'free' of 'ptr'" } */
+  return result;
+}
+
+int test_2 (int flag, int *p, int n)
+{
+  int result;
+  int sum = 0;
+  int i;
+
+  void *ptr = malloc (16);
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+  if (flag)
+    free (ptr);
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  for (i = 0; i < n; i++)
+    p[i] = i;
+  for (i = 0; i < n; i++)
+    sum += foo (p[i]); /* { dg-bogus "uninitialized" } */
+  result = sum;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "5 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "5 exploded nodes" } */
+  // FIXME: why 5 here?
+
+  free (ptr); /* { dg-warning "double-'free' of 'ptr'" } */
+  return result;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/pattern-test-1.c b/gcc/testsuite/gcc.dg/analyzer/pattern-test-1.c
new file mode 100644
index 0000000..8a1ca58
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/pattern-test-1.c
@@ -0,0 +1,28 @@
+/* { dg-additional-options "-fanalyzer-checker=pattern-test" } */
+
+#include <stdlib.h>
+
+extern void foo(void *);
+extern void bar(void *);
+
+void test1(void *ptr)
+{
+  if (ptr) { /* { dg-warning "pattern match on 'ptr != 0'" } */
+  /* { dg-warning "pattern match on 'ptr == 0'" "" { target *-*-* } .-1 } */
+    foo(ptr);
+  } else {
+    bar(ptr);
+  }
+}
+
+void test_2 (void *p, void *q)
+{
+  if (p == NULL || q == NULL) /* { dg-line cond_2 }  */
+    return;
+  foo(p);
+
+  /* { dg-warning "pattern match on 'p == 0'" "" { target *-*-* } cond_2 } */
+  /* { dg-warning "pattern match on 'q == 0'" "" { target *-*-* } cond_2 } */
+  /* { dg-warning "pattern match on 'p != 0'" "" { target *-*-* } cond_2 } */
+  /* { dg-warning "pattern match on 'q != 0'" "" { target *-*-* } cond_2 } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/pattern-test-2.c b/gcc/testsuite/gcc.dg/analyzer/pattern-test-2.c
new file mode 100644
index 0000000..3924900
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/pattern-test-2.c
@@ -0,0 +1,29 @@
+/* { dg-additional-options "-fanalyzer-checker=pattern-test -O2" } */
+// TODO: run this at every optimization level
+
+#include <stdlib.h>
+
+extern void foo(void *);
+extern void bar(void *);
+
+void test1(void *ptr)
+{
+  if (ptr) { /* { dg-warning "pattern match on 'ptr != 0'" } */
+  /* { dg-warning "pattern match on 'ptr == 0'" "" { target *-*-* } .-1 } */
+    foo(ptr);
+  } else {
+    bar(ptr);
+  }
+}
+
+void test_2 (void *p, void *q)
+{
+  if (p == NULL || q == NULL) /* { dg-line cond_2 }  */
+    return;
+  foo(p);
+
+  /* { dg-warning "pattern match on '<unknown> == 0'" "" { target *-*-* } cond_2 } */
+  /* { dg-warning "pattern match on '<unknown> != 0'" "" { target *-*-* } cond_2 } */
+  /* { dg-warning "pattern match on 'p != 0'" "" { target *-*-* } cond_2 } */
+  /* { dg-warning "pattern match on 'q != 0'" "" { target *-*-* } cond_2 } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/pointer-merging.c b/gcc/testsuite/gcc.dg/analyzer/pointer-merging.c
new file mode 100644
index 0000000..dcf0ff0
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/pointer-merging.c
@@ -0,0 +1,16 @@
+static char * __attribute__((noinline))
+test_1_callee (int flag, char *a, char *b)
+{
+  char *p;
+  if (flag)
+    p = a;
+  else
+    p = b;
+  return p;
+}
+
+char test_1_caller(int flag) {
+  char a = 42;
+  char b = 43;
+  return *test_1_callee(flag, &a, &b);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/pr61861.c b/gcc/testsuite/gcc.dg/analyzer/pr61861.c
new file mode 100644
index 0000000..a85e743
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/pr61861.c
@@ -0,0 +1,2 @@
+/* { dg-additional-options "-Wno-int-conversion" } */
+#include "../pr61861.c"
diff --git a/gcc/testsuite/gcc.dg/analyzer/pragma-1.c b/gcc/testsuite/gcc.dg/analyzer/pragma-1.c
new file mode 100644
index 0000000..2e53348
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/pragma-1.c
@@ -0,0 +1,26 @@
+/* Verify that we can disable analyzer warnings via pragmas.  */
+
+#include <stdlib.h>
+
+void test_1 (void *ptr)
+{
+  free (ptr);
+  free (ptr); /* { dg-warning "double-'free'" } */
+}
+
+void test_2 (void *ptr)
+{
+  _Pragma("GCC diagnostic push")
+  _Pragma("GCC diagnostic ignored \"-Wanalyzer-double-free\"")
+
+  free (ptr);
+  free (ptr);  /* { dg-bogus "double-'free'" } */
+
+  _Pragma("GCC diagnostic pop")
+}
+
+void test_3 (void *ptr)
+{
+  free (ptr);
+  free (ptr);  /* { dg-warning "double-'free'" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/scope-1.c b/gcc/testsuite/gcc.dg/analyzer/scope-1.c
new file mode 100644
index 0000000..f8acffa
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/scope-1.c
@@ -0,0 +1,23 @@
+#include <stdlib.h>
+
+int test_1 (void)
+{
+  {
+    int *q = malloc (1024);
+  }
+
+  return 42; /* { dg-warning "leak of 'q'" } */
+  // FIXME: would be better to report it at the close-of-scope
+}
+
+int test_2 (void)
+{
+  {
+    void *q = malloc (1024);
+  }
+
+  int q = 42;
+
+  return q; /* { dg-warning "leak of 'q'" } */
+  // FIXME: would be better to report it at the close-of-scope
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/sensitive-1.c b/gcc/testsuite/gcc.dg/analyzer/sensitive-1.c
new file mode 100644
index 0000000..b884179
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/sensitive-1.c
@@ -0,0 +1,33 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+
+char test_1 (FILE *logfile)
+{
+  char *password = getpass (">"); /* { dg-message "\\(1\\) state of 'password': 'start' -> 'sensitive'" } */
+  fprintf (logfile, "got password %s\n", password); /* { dg-warning "sensitive value 'password' written to output file \\\[CWE-532\\\]" } */
+  /* { dg-message "\\(2\\) here \\('password' is in state 'sensitive'\\)" "" { target *-*-* } .-1 } */
+}
+
+char test_2 (FILE *logfile, int i)
+{
+  char *password = getpass (">"); /* { dg-message "\\(1\\) state of 'password': 'start' -> 'sensitive'" } */
+  fprintf (logfile, "got password[%i]: %s\n", i, password); /* { dg-warning "sensitive value 'password' written to output file \\\[CWE-532\\\]" } */
+  /* { dg-message "\\(2\\) here \\('password' is in state 'sensitive'\\)" "" { target *-*-* } .-1 } */
+}
+
+char test_3 (FILE *logfile)
+{
+  char *password = getpass (">"); /* { dg-message "\\(1\\) state of 'password': 'start' -> 'sensitive'" } */
+  printf ("got password %s\n", password); /* { dg-warning "sensitive value 'password' written to output file \\\[CWE-532\\\]" } */
+  /* { dg-message "\\(2\\) here \\('password' is in state 'sensitive'\\)" "" { target *-*-* } .-1 } */
+}
+
+char test_4 (FILE *logfile)
+{
+  char *password = getpass (">"); /* { dg-message "\\(1\\) state of 'password': 'start' -> 'sensitive'" } */
+  fwrite (password, strlen (password), 1, logfile); /* { dg-warning "sensitive value 'password' written to output file \\\[CWE-532\\\]" } */
+  /* { dg-message "\\(2\\) here \\('password' is in state 'sensitive'\\)" "" { target *-*-* } .-1 } */
+}
+
+/* TODO: strdup etc, strcpy, memcpy, etc.  */
diff --git a/gcc/testsuite/gcc.dg/analyzer/setjmp-1.c b/gcc/testsuite/gcc.dg/analyzer/setjmp-1.c
new file mode 100644
index 0000000..c9827fb
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/setjmp-1.c
@@ -0,0 +1 @@
+#include "../pr26983.c"
diff --git a/gcc/testsuite/gcc.dg/analyzer/setjmp-2.c b/gcc/testsuite/gcc.dg/analyzer/setjmp-2.c
new file mode 100644
index 0000000..7afe66e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/setjmp-2.c
@@ -0,0 +1,97 @@
+/* { dg-additional-options "-fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fdiagnostics-path-format=inline-events -fdiagnostics-show-caret" } */
+
+#include <setjmp.h>
+#include <stddef.h>
+
+extern void foo (int);
+
+void test_1 (void)
+{
+  setjmp (NULL);
+}
+
+void test_2 (void)
+{
+  jmp_buf env;
+  int i;
+
+  foo (0);
+
+  i = setjmp(env);
+
+  foo (1);
+
+  if (i != 0)
+    {
+      foo (2);
+      __analyzer_dump_path (); /* { dg-message "path" } */
+    }
+  else
+    longjmp (env, 1);
+
+  foo (3);
+}
+
+/* { dg-begin-multiline-output "" }
+   NN |       __analyzer_dump_path ();
+      |       ^~~~~~~~~~~~~~~~~~~~~~~
+  'test_2': event 1
+    |
+    |   NN |   i = setjmp(env);
+    |      |       ^~~~~~
+    |      |       |
+    |      |       (1) 'setjmp' called here
+    |
+  'test_2': events 2-4
+    |
+    |   NN |   if (i != 0)
+    |      |      ^
+    |      |      |
+    |      |      (2) following 'false' branch (when 'i == 0')...
+    |......
+    |   NN |     longjmp (env, 1);
+    |      |     ~~~~~~~~~~~~~~~~
+    |      |     |
+    |      |     (3) ...to here
+    |      |     (4) rewinding within 'test_2' from 'longjmp'...
+    |
+  'test_2': event 5
+    |
+    |   NN |   i = setjmp(env);
+    |      |       ^~~~~~
+    |      |       |
+    |      |       (5) ...to 'setjmp' (saved at (1))
+    |
+  'test_2': events 6-8
+    |
+    |   NN |   if (i != 0)
+    |      |      ^
+    |      |      |
+    |      |      (6) following 'true' branch (when 'i != 0')...
+    |   NN |     {
+    |   NN |       foo (2);
+    |      |       ~~~~~~~
+    |      |       |
+    |      |       (7) ...to here
+    |   NN |       __analyzer_dump_path ();
+    |      |       ~~~~~~~~~~~~~~~~~~~~~~~
+    |      |       |
+    |      |       (8) here
+    |
+    { dg-end-multiline-output "" } */
+
+void test_3 (void)
+{
+  longjmp (NULL, 0);
+}
+
+void test_4 (void)
+{
+  longjmp (NULL, 1);
+}
+
+void test_5 (void)
+{
+  jmp_buf env;
+  longjmp (env, 1);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/setjmp-3.c b/gcc/testsuite/gcc.dg/analyzer/setjmp-3.c
new file mode 100644
index 0000000..6f67ff5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/setjmp-3.c
@@ -0,0 +1,106 @@
+/* { dg-additional-options "-fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fdiagnostics-path-format=inline-events -fdiagnostics-show-caret" } */
+
+#include <setjmp.h>
+#include <stddef.h>
+
+extern void foo (int);
+
+static jmp_buf env;
+
+static void inner (void)
+{
+  longjmp (env, 1);
+}
+
+void outer (void)
+{
+  int i;
+
+  foo (0);
+
+  i = setjmp(env);
+
+  if (i != 0)
+    {
+      foo (2);
+      __analyzer_dump_path (); /* { dg-message "path" } */
+    }
+  else
+    {
+      foo (1);
+      inner ();
+    }
+  foo (3);
+}
+
+/* { dg-begin-multiline-output "" }
+   NN |       __analyzer_dump_path ();
+      |       ^~~~~~~~~~~~~~~~~~~~~~~
+  'outer': event 1
+    |
+    |   NN | void outer (void)
+    |      |      ^~~~~
+    |      |      |
+    |      |      (1) entry to 'outer'
+    |
+  'outer': event 2
+    |
+    |   NN |   i = setjmp(env);
+    |      |       ^~~~~~
+    |      |       |
+    |      |       (2) 'setjmp' called here
+    |
+  'outer': events 3-5
+    |
+    |   NN |   if (i != 0)
+    |      |      ^
+    |      |      |
+    |      |      (3) following 'false' branch (when 'i == 0')...
+    |......
+    |   NN |       foo (1);
+    |      |       ~~~~~~~
+    |      |       |
+    |      |       (4) ...to here
+    |   NN |       inner ();
+    |      |       ~~~~~~~~
+    |      |       |
+    |      |       (5) calling 'inner' from 'outer'
+    |
+    +--> 'inner': events 6-7
+           |
+           |   NN | static void inner (void)
+           |      |             ^~~~~
+           |      |             |
+           |      |             (6) entry to 'inner'
+           |   NN | {
+           |   NN |   longjmp (env, 1);
+           |      |   ~~~~~~~~~~~~~~~~
+           |      |   |
+           |      |   (7) rewinding from 'longjmp' in 'inner'...
+           |
+    <------+
+    |
+  'outer': event 8
+    |
+    |   NN |   i = setjmp(env);
+    |      |       ^~~~~~
+    |      |       |
+    |      |       (8) ...to 'setjmp' in 'outer' (saved at (2))
+    |
+  'outer': events 9-11
+    |
+    |   NN |   if (i != 0)
+    |      |      ^
+    |      |      |
+    |      |      (9) following 'true' branch (when 'i != 0')...
+    |   NN |     {
+    |   NN |       foo (2);
+    |      |       ~~~~~~~
+    |      |       |
+    |      |       (10) ...to here
+    |   NN |       __analyzer_dump_path ();
+    |      |       ~~~~~~~~~~~~~~~~~~~~~~~
+    |      |       |
+    |      |       (11) here
+    |
+    { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/setjmp-4.c b/gcc/testsuite/gcc.dg/analyzer/setjmp-4.c
new file mode 100644
index 0000000..21bb87e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/setjmp-4.c
@@ -0,0 +1,107 @@
+/* { dg-additional-options "-fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fdiagnostics-path-format=inline-events -fdiagnostics-show-caret" } */
+
+#include <setjmp.h>
+
+extern int foo (int);
+static jmp_buf buf;
+
+void inner (int x)
+{
+  foo (x);
+  longjmp (buf, 1);
+  foo (x);
+}
+
+void outer (int y)
+{
+  foo (y);
+  inner (y);
+  foo (y);
+}
+
+int main (void)
+{   
+  if (!setjmp(buf))
+    outer (42);
+  else
+    __analyzer_dump_path (); /* { dg-message "path" } */
+  return 0;
+}
+
+/* { dg-begin-multiline-output "" }
+   NN |     __analyzer_dump_path ();
+      |     ^~~~~~~~~~~~~~~~~~~~~~~
+  'main': event 1
+    |
+    |   NN | int main (void)
+    |      |     ^~~~
+    |      |     |
+    |      |     (1) entry to 'main'
+    |
+  'main': event 2
+    |
+    |   NN |   if (!setjmp(buf))
+    |      |        ^~~~~~
+    |      |        |
+    |      |        (2) 'setjmp' called here
+    |
+  'main': events 3-5
+    |
+    |   NN |   if (!setjmp(buf))
+    |      |      ^
+    |      |      |
+    |      |      (3) following 'true' branch...
+    |   NN |     outer (42);
+    |      |     ~~~~~~~~~~
+    |      |     |
+    |      |     (4) ...to here
+    |      |     (5) calling 'outer' from 'main'
+    |
+    +--> 'outer': events 6-7
+           |
+           |   NN | void outer (int y)
+           |      |      ^~~~~
+           |      |      |
+           |      |      (6) entry to 'outer'
+           |......
+           |   NN |   inner (y);
+           |      |   ~~~~~~~~~
+           |      |   |
+           |      |   (7) calling 'inner' from 'outer'
+           |
+           +--> 'inner': events 8-9
+                  |
+                  |   NN | void inner (int x)
+                  |      |      ^~~~~
+                  |      |      |
+                  |      |      (8) entry to 'inner'
+                  |......
+                  |   NN |   longjmp (buf, 1);
+                  |      |   ~~~~~~~~~~~~~~~~
+                  |      |   |
+                  |      |   (9) rewinding from 'longjmp' in 'inner'...
+                  |
+    <-------------+
+    |
+  'main': event 10
+    |
+    |   NN |   if (!setjmp(buf))
+    |      |        ^~~~~~
+    |      |        |
+    |      |        (10) ...to 'setjmp' in 'main' (saved at (2))
+    |
+  'main': events 11-13
+    |
+    |   NN |   if (!setjmp(buf))
+    |      |      ^
+    |      |      |
+    |      |      (11) following 'false' branch...
+    |......
+    |   NN |     __analyzer_dump_path ();
+    |      |     ~~~~~~~~~~~~~~~~~~~~~~~
+    |      |     |
+    |      |     (12) ...to here
+    |      |     (13) here
+    |
+    { dg-end-multiline-output "" } */
+
diff --git a/gcc/testsuite/gcc.dg/analyzer/setjmp-5.c b/gcc/testsuite/gcc.dg/analyzer/setjmp-5.c
new file mode 100644
index 0000000..faac51c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/setjmp-5.c
@@ -0,0 +1,65 @@
+/* { dg-additional-options "-fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fdiagnostics-path-format=inline-events -fdiagnostics-show-caret" } */
+
+#include <setjmp.h>
+#include <stddef.h>
+
+static jmp_buf env;
+
+static void inner (void)
+{
+  setjmp (env);
+}
+
+void outer (void)
+{
+  int i;
+
+  inner ();
+
+  longjmp (env, 42); /* { dg-warning "'longjmp' called after enclosing function of 'setjmp' has returned" } */
+}
+
+/* { dg-begin-multiline-output "" }
+   NN |   longjmp (env, 42);
+      |   ^~~~~~~~~~~~~~~~~
+  'outer': events 1-2
+    |
+    |   NN | void outer (void)
+    |      |      ^~~~~
+    |      |      |
+    |      |      (1) entry to 'outer'
+    |......
+    |   NN |   inner ();
+    |      |   ~~~~~~~~
+    |      |   |
+    |      |   (2) calling 'inner' from 'outer'
+    |
+    +--> 'inner': event 3
+           |
+           |   NN | static void inner (void)
+           |      |             ^~~~~
+           |      |             |
+           |      |             (3) entry to 'inner'
+           |
+         'inner': event 4
+           |
+           |   NN |   setjmp (env);
+           |      |   ^~~~~~
+           |      |   |
+           |      |   (4) 'setjmp' called here
+           |
+    <------+
+    |
+  'outer': events 5-6
+    |
+    |   NN |   inner ();
+    |      |   ^~~~~~~~
+    |      |   |
+    |      |   (5) returning to 'outer' from 'inner'
+    |   NN | 
+    |   NN |   longjmp (env, 42);
+    |      |   ~~~~~~~~~~~~~~~~~
+    |      |   |
+    |      |   (6) here
+    |
+    { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/setjmp-6.c b/gcc/testsuite/gcc.dg/analyzer/setjmp-6.c
new file mode 100644
index 0000000..84a6318
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/setjmp-6.c
@@ -0,0 +1,31 @@
+#include <setjmp.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+extern void foo (int);
+
+static jmp_buf env;
+
+static void inner (void)
+{
+  void *ptr = malloc (1024); /* { dg-message "allocated here" }  */
+  longjmp (env, 1); /* { dg-warning "leak of 'ptr'" } */
+  free (ptr);
+}
+
+void outer (void)
+{
+  int i;
+
+  foo (0);
+
+  i = setjmp(env);
+
+  if (i == 0)
+    {
+      foo (1);
+      inner ();
+    }
+
+  foo (3);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/setjmp-7.c b/gcc/testsuite/gcc.dg/analyzer/setjmp-7.c
new file mode 100644
index 0000000..2d44926
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/setjmp-7.c
@@ -0,0 +1,36 @@
+#include <setjmp.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+extern void foo (int);
+
+static jmp_buf env;
+
+static void inner (void)
+{
+  longjmp (env, 1); /* { dg-warning "leak of 'ptr'" "" { xfail *-*-* } } */
+}
+
+static void middle (void)
+{
+  void *ptr = malloc (1024); /* { dg-message "allocated here"  "" { xfail *-*-* } }  */
+  inner ();
+  free (ptr);
+}
+
+void outer (void)
+{
+  int i;
+
+  foo (0);
+
+  i = setjmp(env);
+
+  if (i == 0)
+    {
+      foo (1);
+      middle ();
+    }
+
+  foo (3);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/setjmp-8.c b/gcc/testsuite/gcc.dg/analyzer/setjmp-8.c
new file mode 100644
index 0000000..ad3ef8f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/setjmp-8.c
@@ -0,0 +1,107 @@
+/* { dg-additional-options "-fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fdiagnostics-path-format=inline-events -fdiagnostics-show-caret" } */
+
+#include <setjmp.h>
+#include <stddef.h>
+
+extern void foo (int);
+
+static jmp_buf env;
+
+static void inner (void)
+{
+  /* Pass known 0 to longjmp. */
+  longjmp (env, 0);
+}
+
+void outer (void)
+{
+  int i;
+
+  foo (0);
+
+  i = setjmp(env);
+
+  if (i != 0)
+    {
+      foo (2);
+      __analyzer_dump_path (); /* { dg-message "path" } */
+    }
+  else
+    {
+      foo (1);
+      inner ();
+    }
+  foo (3);
+}
+
+/* { dg-begin-multiline-output "" }
+   NN |       __analyzer_dump_path ();
+      |       ^~~~~~~~~~~~~~~~~~~~~~~
+  'outer': event 1
+    |
+    |   NN | void outer (void)
+    |      |      ^~~~~
+    |      |      |
+    |      |      (1) entry to 'outer'
+    |
+  'outer': event 2
+    |
+    |   NN |   i = setjmp(env);
+    |      |       ^~~~~~
+    |      |       |
+    |      |       (2) 'setjmp' called here
+    |
+  'outer': events 3-5
+    |
+    |   NN |   if (i != 0)
+    |      |      ^
+    |      |      |
+    |      |      (3) following 'false' branch (when 'i == 0')...
+    |......
+    |   NN |       foo (1);
+    |      |       ~~~~~~~
+    |      |       |
+    |      |       (4) ...to here
+    |   NN |       inner ();
+    |      |       ~~~~~~~~
+    |      |       |
+    |      |       (5) calling 'inner' from 'outer'
+    |
+    +--> 'inner': events 6-7
+           |
+           |   NN | static void inner (void)
+           |      |             ^~~~~
+           |      |             |
+           |      |             (6) entry to 'inner'
+           |......
+           |   NN |   longjmp (env, 0);
+           |      |   ~~~~~~~~~~~~~~~~
+           |      |   |
+           |      |   (7) rewinding from 'longjmp' in 'inner'...
+           |
+    <------+
+    |
+  'outer': event 8
+    |
+    |   NN |   i = setjmp(env);
+    |      |       ^~~~~~
+    |      |       |
+    |      |       (8) ...to 'setjmp' in 'outer' (saved at (2))
+    |
+  'outer': events 9-11
+    |
+    |   NN |   if (i != 0)
+    |      |      ^
+    |      |      |
+    |      |      (9) following 'true' branch (when 'i != 0')...
+    |   NN |     {
+    |   NN |       foo (2);
+    |      |       ~~~~~~~
+    |      |       |
+    |      |       (10) ...to here
+    |   NN |       __analyzer_dump_path ();
+    |      |       ~~~~~~~~~~~~~~~~~~~~~~~
+    |      |       |
+    |      |       (11) here
+    |
+    { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/setjmp-9.c b/gcc/testsuite/gcc.dg/analyzer/setjmp-9.c
new file mode 100644
index 0000000..081ae047
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/setjmp-9.c
@@ -0,0 +1,109 @@
+/* { dg-additional-options "-fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fdiagnostics-path-format=inline-events -fdiagnostics-show-caret" } */
+
+#include <setjmp.h>
+#include <stddef.h>
+
+extern void foo (int);
+
+static jmp_buf env;
+
+extern int unknown_val;
+
+static void inner (void)
+{
+  /* Pass value that might be 0 to longjmp. */
+  longjmp (env, unknown_val);
+}
+
+void outer (void)
+{
+  int i;
+
+  foo (0);
+
+  i = setjmp(env);
+
+  if (i != 0)
+    {
+      foo (2);
+      __analyzer_dump_path (); /* { dg-message "path" } */
+    }
+  else
+    {
+      foo (1);
+      inner ();
+    }
+  foo (3);
+}
+
+/* { dg-begin-multiline-output "" }
+   NN |       __analyzer_dump_path ();
+      |       ^~~~~~~~~~~~~~~~~~~~~~~
+  'outer': event 1
+    |
+    |   NN | void outer (void)
+    |      |      ^~~~~
+    |      |      |
+    |      |      (1) entry to 'outer'
+    |
+  'outer': event 2
+    |
+    |   NN |   i = setjmp(env);
+    |      |       ^~~~~~
+    |      |       |
+    |      |       (2) 'setjmp' called here
+    |
+  'outer': events 3-5
+    |
+    |   NN |   if (i != 0)
+    |      |      ^
+    |      |      |
+    |      |      (3) following 'false' branch (when 'i == 0')...
+    |......
+    |   NN |       foo (1);
+    |      |       ~~~~~~~
+    |      |       |
+    |      |       (4) ...to here
+    |   NN |       inner ();
+    |      |       ~~~~~~~~
+    |      |       |
+    |      |       (5) calling 'inner' from 'outer'
+    |
+    +--> 'inner': events 6-7
+           |
+           |   NN | static void inner (void)
+           |      |             ^~~~~
+           |      |             |
+           |      |             (6) entry to 'inner'
+           |......
+           |   NN |   longjmp (env, unknown_val);
+           |      |   ~~~~~~~~~~~~~~~~~~~~~~~~~~
+           |      |   |
+           |      |   (7) rewinding from 'longjmp' in 'inner'...
+           |
+    <------+
+    |
+  'outer': event 8
+    |
+    |   NN |   i = setjmp(env);
+    |      |       ^~~~~~
+    |      |       |
+    |      |       (8) ...to 'setjmp' in 'outer' (saved at (2))
+    |
+  'outer': events 9-11
+    |
+    |   NN |   if (i != 0)
+    |      |      ^
+    |      |      |
+    |      |      (9) following 'true' branch (when 'i != 0')...
+    |   NN |     {
+    |   NN |       foo (2);
+    |      |       ~~~~~~~
+    |      |       |
+    |      |       (10) ...to here
+    |   NN |       __analyzer_dump_path ();
+    |      |       ~~~~~~~~~~~~~~~~~~~~~~~
+    |      |       |
+    |      |       (11) here
+    |
+    { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/switch.c b/gcc/testsuite/gcc.dg/analyzer/switch.c
new file mode 100644
index 0000000..e261770
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/switch.c
@@ -0,0 +1,28 @@
+/* { dg-additional-options "-fanalyzer-transitivity" } */
+
+void test (int i)
+{
+  switch (i)
+    {
+    case 0:
+      __analyzer_eval (i == 0); /* { dg-warning "TRUE" } */
+      break;
+
+    case 3 ... 5:
+      __analyzer_eval (i >= 3); /* { dg-warning "TRUE" } */
+      __analyzer_eval (i <= 5); /* { dg-warning "TRUE" } */
+      break;
+
+    default:
+      __analyzer_eval (i == 0); /* { dg-warning "FALSE" } */
+      __analyzer_eval (i == 2); /* { dg-warning "UNKNOWN" } */
+      __analyzer_eval (i == 3); /* { dg-warning "FALSE" } */
+      __analyzer_eval (i == 4); /* { dg-warning "FALSE" "" { xfail *-*-* } } */
+      /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+      /* TODO(xfail^^^): we're only checking against endpoints of case
+	 ranges, not the insides.  */
+      __analyzer_eval (i == 5); /* { dg-warning "FALSE" } */
+      __analyzer_eval (i == 6); /* { dg-warning "UNKNOWN" } */
+      break;
+    }
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/taint-1.c b/gcc/testsuite/gcc.dg/analyzer/taint-1.c
new file mode 100644
index 0000000..cea5440
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/taint-1.c
@@ -0,0 +1,128 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct foo
+{
+  signed int i;
+  char buf[256];
+};
+
+char test_1(FILE *f)
+{
+  struct foo tmp;
+
+  if (1 == fread(&tmp, sizeof(tmp), 1, f)) { /* { dg-message "\\(1\\) 'tmp' gets an unchecked value here" } */
+                                             /* { dg-message "\\(2\\) following 'true' branch\\.\\.\\." "" { target *-*-* } .-1 } */
+    /* BUG: the following array lookup trusts that the input data's index is
+       in the range 0 <= i < 256; otherwise it's accessing the stack */
+    return tmp.buf[tmp.i]; // { dg-warning "use of tainted value 'tmp.i' in array lookup without bounds checking" } */
+    /* { dg-message "23: \\(3\\) \\.\\.\\.to here" "" { target *-*-* } .-1 } */
+    /* { dg-message "23: \\(4\\) 'tmp.i' has an unchecked value here \\(from 'tmp'\\)" "" { target *-*-* } .-2 } */
+    /* { dg-message "\\(5\\) use of tainted value 'tmp.i' in array lookup without bounds checking" "" { target *-*-* } .-3 } */
+    
+    // TOOD: better messages for state changes
+  }
+  return 0;
+}
+
+char test_2(struct foo *f, int i)
+{
+  /* not a bug: the data is not known to be tainted: */
+  return f->buf[f->i];
+}
+
+char test_3(FILE *f)
+{
+  struct foo tmp;
+
+  if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
+    if (tmp.i >= 0 && tmp.i < 256) {
+      /* not a bug: the access is guarded by upper and lower bounds: */
+      return tmp.buf[tmp.i];
+    }
+  }
+  return 0;
+}
+
+char test_4(FILE *f)
+{
+  struct foo tmp;
+
+  if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
+    if (tmp.i >= 0) { /* { dg-message "'tmp.i' has an unchecked value here \\(from 'tmp'\\)" } */
+      /* { dg-message "'tmp.i' has its lower bound checked here" "" { target *-*-* } .-1 } */
+      return tmp.buf[tmp.i]; /* { dg-warning "use of tainted value 'tmp.i' in array lookup without upper-bounds checking" } */
+    }
+  }
+  return 0;
+}
+
+char test_5(FILE *f)
+{
+  struct foo tmp;
+
+  if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
+    if (tmp.i < 256) { /* { dg-message "'tmp.i' has an unchecked value here \\(from 'tmp'\\)" } */
+      /* { dg-message "'tmp.i' has its upper bound checked here" "" { target *-*-* } .-1 } */
+      return tmp.buf[tmp.i]; /* { dg-warning "use of tainted value 'tmp.i' in array lookup without lower-bounds checking" } */
+    }
+  }
+  return 0;
+}
+
+/* unsigned types have a natural lower bound of 0 */
+struct bar
+{
+  unsigned int i;
+  char buf[256];
+};
+
+char test_6(FILE *f)
+{
+  struct bar tmp;
+
+  if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
+    return tmp.buf[tmp.i]; /* { dg-warning "use of tainted value 'tmp.i' in array lookup without upper-bounds checking" } */
+  }
+  return 0;
+}
+
+char test_7(FILE *f)
+{
+  struct bar tmp;
+
+  if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
+    if (tmp.i >= 0) {
+      return tmp.buf[tmp.i]; /* { dg-warning "use of tainted value 'tmp.i' in array lookup without upper-bounds checking" } */
+    }
+  }
+  return 0;
+}
+
+char test_8(FILE *f)
+{
+  struct bar tmp;
+
+  if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
+    if (tmp.i < 256) {
+      /* not a bug: has an upper bound, and an implicit lower bound: */
+      return tmp.buf[tmp.i];
+    }
+  }
+  return 0;
+}
+
+char test_9(FILE *f)
+{
+  struct foo tmp;
+
+  if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
+    if (tmp.i == 42) {
+      /* not a bug: tmp.i compared against a specific value: */
+      return tmp.buf[tmp.i]; /* { dg-bogus "tainted" "" { xfail *-*-* } } */
+      // TODO: xfail
+    }
+  }
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/zlib-1.c b/gcc/testsuite/gcc.dg/analyzer/zlib-1.c
new file mode 100644
index 0000000..52d3c62
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/zlib-1.c
@@ -0,0 +1,67 @@
+typedef void (*free_func)(void *opaque, void *address);
+
+typedef struct z_stream_s {
+  struct internal_state *state;
+  free_func zfree;
+  void *opaque;
+} z_stream;
+
+struct internal_state {
+  z_stream *strm;
+  int status;
+  unsigned char *pending_buf;
+  unsigned char *window;
+  unsigned short *prev;
+  unsigned short *head;
+};
+
+int deflateEnd(z_stream *strm)
+{
+  int status;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+  if (strm == 0 || strm->state == 0)
+    return (-2);
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+  status = strm->state->status;
+  if (status != 42 && status != 113 && status != 666) {
+    return (-2);
+  }
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "4 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+  if (strm->state->pending_buf)
+    (*(strm->zfree))(strm->opaque, (void *)(strm->state->pending_buf));
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+  if (strm->state->head)
+      (*(strm->zfree))(strm->opaque, (void *)(strm->state->head));
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+  if (strm->state->prev)
+    (*(strm->zfree))(strm->opaque, (void *)(strm->state->prev));
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+  if (strm->state->window)
+    (*(strm->zfree))(strm->opaque, (void *)(strm->state->window));
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+  (*(strm->zfree))(strm->opaque, (void *)(strm->state));
+  strm->state = 0;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+  return status == 113 ? (-3) : 0;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/zlib-2.c b/gcc/testsuite/gcc.dg/analyzer/zlib-2.c
new file mode 100644
index 0000000..d0b587c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/zlib-2.c
@@ -0,0 +1,51 @@
+/* { dg-additional-options "-Wno-analyzer-too-complex" } */
+
+typedef void * (*alloc_func)(void * opaque, unsigned items, unsigned size);
+typedef void (*free_func)(void * opaque, void * address);
+
+typedef struct z_stream_s {
+  char *msg;
+  alloc_func zalloc;
+  free_func zfree;
+  void * opaque;
+} z_stream;
+
+void * zcalloc(void * opaque, unsigned items, unsigned size);
+void zcfree(void * opaque, void * ptr);
+
+int deflateInit2_(z_stream *strm, int level, int method, int windowBits,
+                  int memLevel, int strategy, const char *version,
+                  int stream_size) {
+  int noheader = 0;
+  static const char *my_version = "1.1.3";
+
+  if (version == 0 || version[0] != my_version[0] ||
+      stream_size != sizeof(z_stream)) {
+    return (-6);
+  }
+  if (strm == 0)
+    return (-2);
+
+  strm->msg = 0;
+  if (strm->zalloc == 0) {
+    strm->zalloc = zcalloc;
+    strm->opaque = (void *)0;
+  }
+  if (strm->zfree == 0)
+    strm->zfree = zcfree;
+
+  if (level == (-1))
+    level = 6;
+
+  if (windowBits < 0) {
+    noheader = 1;
+    windowBits = -windowBits;
+  }
+  if (memLevel < 1 || memLevel > 9 || method != 8 || windowBits < 8 ||
+      windowBits > 15 || level < 0 || level > 9 || strategy < 0 ||
+      strategy > 2) {
+    return (-2);
+  }
+  (*((strm)->zalloc))((strm)->opaque, (1), 112);
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/zlib-3.c b/gcc/testsuite/gcc.dg/analyzer/zlib-3.c
new file mode 100644
index 0000000..5faada1
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/zlib-3.c
@@ -0,0 +1,214 @@
+/* { dg-additional-options "-O3 -Wno-analyzer-too-complex" } */
+/* TODO: reduce this; was triggering this assert:
+            gcc_assert (pruned_state != existing_state);
+*/
+
+typedef unsigned char Byte;
+typedef unsigned int uInt;
+
+typedef void *voidp;
+
+typedef voidp (*alloc_func)(voidp opaque, uInt items, uInt size);
+
+typedef struct z_stream_s {
+  alloc_func zalloc;
+  voidp opaque;
+} z_stream;
+
+typedef z_stream *z_streamp;
+
+typedef struct inflate_huft_s inflate_huft;
+
+struct inflate_huft_s {
+  struct {
+    Byte Exop;
+    Byte Bits;
+  } what;
+  uInt base;
+};
+
+static int huft_build(uInt *, uInt, uInt, const uInt *, const uInt *,
+                      inflate_huft **, uInt *, inflate_huft *, uInt *, uInt *);
+
+static int huft_build(uInt *b, uInt n, uInt s, const uInt *d, const uInt *e,
+                      inflate_huft **t, uInt *m, inflate_huft *hp, uInt *hn,
+                      uInt *v) {
+
+  uInt a;
+  uInt c[15 + 1];
+  uInt f;
+  int g;
+  int h;
+  register uInt i;
+  register uInt j;
+  register int k;
+  int l;
+  uInt mask;
+  register uInt *p;
+  inflate_huft *q;
+  struct inflate_huft_s r;
+  inflate_huft *u[15];
+  register int w;
+  uInt x[15 + 1];
+  uInt *xp;
+  int y;
+  uInt z;
+
+  p = c;
+
+  *p++ = 0;
+  *p++ = 0;
+  *p++ = 0;
+  *p++ = 0;
+  *p++ = 0;
+  *p++ = 0;
+  *p++ = 0;
+  *p++ = 0;
+  *p++ = 0;
+  *p++ = 0;
+  *p++ = 0;
+  *p++ = 0;
+  *p++ = 0;
+  *p++ = 0;
+  *p++ = 0;
+  *p++ = 0;
+  p = b;
+  i = n;
+  do {
+    c[*p++]++;
+  } while (--i);
+  if (c[0] == n) {
+    *t = (inflate_huft *)0;
+    *m = 0;
+    return 0;
+  }
+
+  l = *m;
+  for (j = 1; j <= 15; j++)
+    if (c[j])
+      break;
+  k = j;
+  if ((uInt)l < j)
+    l = j;
+  for (i = 15; i; i--)
+    if (c[i])
+      break;
+  g = i;
+  if ((uInt)l > i)
+    l = i;
+  *m = l;
+
+  for (y = 1 << j; j < i; j++, y <<= 1)
+    if ((y -= c[j]) < 0)
+      return (-3);
+  if ((y -= c[i]) < 0)
+    return (-3);
+  c[i] += y;
+
+  x[1] = j = 0;
+  p = c + 1;
+  xp = x + 2;
+  while (--i) {
+    *xp++ = (j += *p++);
+  }
+
+  p = b;
+  i = 0;
+  do {
+    if ((j = *p++) != 0)
+      v[x[j]++] = i;
+  } while (++i < n);
+  n = x[g];
+
+  x[0] = i = 0;
+  p = v;
+  h = -1;
+  w = -l;
+  u[0] = (inflate_huft *)0;
+  q = (inflate_huft *)0;
+  z = 0;
+
+  for (; k <= g; k++) {
+    a = c[k];
+    while (a--) {
+
+      while (k > w + l) {
+        h++;
+        w += l;
+
+        z = g - w;
+        z = z > (uInt)l ? l : z;
+        if ((f = 1 << (j = k - w)) > a + 1) {
+          f -= a + 1;
+          xp = c + k;
+          if (j < z)
+            while (++j < z) {
+              if ((f <<= 1) <= *++xp)
+                break;
+              f -= *xp;
+            }
+        }
+        z = 1 << j;
+
+        if (*hn + z > 1440)
+          return (-4);
+        u[h] = q = hp + *hn;
+        *hn += z;
+
+        if (h) {
+          x[h] = i;
+          r.what.Bits = (Byte)l;
+          r.what.Exop = (Byte)j;
+          j = i >> (w - l);
+          r.base = (uInt)(q - u[h - 1] - j);
+          u[h - 1][j] = r;
+        } else
+          *t = q;
+      }
+
+      r.what.Bits = (Byte)(k - w);
+      if (p >= v + n)
+        r.what.Exop = 128 + 64;
+      else if (*p < s) {
+        r.what.Exop = (Byte)(*p < 256 ? 0 : 32 + 64);
+        r.base = *p++;
+      } else {
+        r.what.Exop = (Byte)(e[*p - s] + 16 + 64);
+        r.base = d[*p++ - s];
+      }
+
+      f = 1 << (k - w);
+      for (j = i >> w; j < z; j += f)
+        q[j] = r;
+
+      mask = (1 << w) - 1;
+      while ((i & mask) != x[h]) {
+        h--;
+        w -= l;
+        mask = (1 << w) - 1;
+      }
+    }
+  }
+
+  return y != 0 && g != 1 ? (-5) : 0;
+}
+
+extern const uInt cplens[31];
+extern const uInt cplext[31];
+extern const uInt cpdist[30];
+extern const uInt cpdext[30];
+
+int inflate_trees_dynamic(uInt nl, uInt nd, uInt *c, uInt *bl, uInt *bd,
+                          inflate_huft **tl, inflate_huft **td,
+                          inflate_huft *hp, z_streamp z) {
+  int r;
+  uInt hn = 0;
+  uInt *v;
+
+  if ((v = (uInt *)(*((z)->zalloc))((z)->opaque, (288), (sizeof(uInt)))) == 0)
+    return (-4);
+
+  r = huft_build(c, nl, 257, cplens, cplext, tl, bl, hp, &hn, v);
+  r = huft_build(c + nl, nd, 0, cpdist, cpdext, td, bd, hp, &hn, v);
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/zlib-4.c b/gcc/testsuite/gcc.dg/analyzer/zlib-4.c
new file mode 100644
index 0000000..0826144
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/zlib-4.c
@@ -0,0 +1,20 @@
+#include <stdlib.h>
+#include <string.h>
+
+typedef unsigned char Byte;
+typedef unsigned int uInt;
+typedef unsigned long uLong;
+
+#define Z_NULL  0
+
+void test ()
+{
+    uLong comprLen = 10000*sizeof(int);
+    uLong uncomprLen = comprLen;
+    Byte *compr    = (Byte*)calloc((uInt)comprLen, 1);
+    Byte *uncompr  = (Byte*)calloc((uInt)uncomprLen, 1);
+    if (compr == Z_NULL || uncompr == Z_NULL)
+      exit (1);
+    strcpy((char*)uncompr, "garbage");
+    exit (0);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/zlib-5.c b/gcc/testsuite/gcc.dg/analyzer/zlib-5.c
new file mode 100644
index 0000000..bcf8a0a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/zlib-5.c
@@ -0,0 +1,49 @@
+/* { dg-additional-options "-O3" } */
+
+typedef long unsigned int size_t;
+typedef unsigned char Byte;
+typedef unsigned int uInt;
+typedef unsigned long uLong;
+
+extern size_t strlen(const char *__s) __attribute__((__nothrow__, __leaf__))
+    __attribute__((__pure__)) __attribute__((__nonnull__(1)));
+extern void exit(int __status) __attribute__((__nothrow__, __leaf__))
+    __attribute__((__noreturn__));
+extern char *strcpy(char *__restrict __dest, const char *__restrict __src)
+    __attribute__((__nothrow__, __leaf__)) __attribute__((__nonnull__(1, 2)));
+extern void *calloc(size_t __nmemb, size_t __size)
+    __attribute__((__nothrow__, __leaf__)) __attribute__((__malloc__));
+
+extern int compress(Byte *dest, uLong *destLen, const Byte *source,
+                    uLong sourceLen);
+
+const char hello[] = "hello, hello!";
+
+void test_compress(Byte *compr, uLong comprLen, Byte *uncompr,
+                   uLong uncomprLen) {
+  int err;
+  uLong len = strlen(hello) + 1;
+
+  err = compress(compr, &comprLen, (const Byte *)hello, len);
+  if (err != 0)
+    exit(1);
+  strcpy((char *)uncompr, "garbage"); /* { dg-bogus "NULL" } */
+}
+
+int main(int argc, char *argv[]) {
+  Byte *compr, *uncompr;
+  uLong comprLen = 10000 * sizeof(int);
+  uLong uncomprLen = comprLen;
+
+  compr = (Byte *)calloc((uInt)comprLen, 1);
+  uncompr = (Byte *)calloc((uInt)uncomprLen, 1);
+  if (compr == 0 || uncompr == 0)
+    exit(1);
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+  test_compress(compr, comprLen, uncompr, uncomprLen);
+
+  exit(0);
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/zlib-6.c b/gcc/testsuite/gcc.dg/analyzer/zlib-6.c
new file mode 100644
index 0000000..d68c387
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/zlib-6.c
@@ -0,0 +1,47 @@
+typedef unsigned char Byte;
+typedef unsigned int uInt;
+typedef unsigned long uLong;
+
+typedef struct z_stream_s {
+  Byte *next_in;
+  uInt avail_in;
+  uLong total_in;
+} z_stream;
+
+typedef struct inflate_blocks_state {
+  uInt bitk;
+  uLong bitb;
+  Byte *write;
+} inflate_blocks_statef;
+
+extern int inflate_flush(inflate_blocks_statef *, z_stream *, int);
+
+int inflate_blocks(inflate_blocks_statef *s, z_stream *z, int r) {
+  uInt t;
+  uLong b;
+  uInt k;
+  Byte *p;
+  uInt n;
+  Byte *q;
+  uInt m;
+
+  while (k < (3)) {
+    {
+      if (n)
+        r = 0;
+      else {
+        {
+	  s->bitb = b;
+	  s->bitk = k;
+	  z->avail_in = n;
+	  z->total_in += p - z->next_in;
+	  z->next_in = p;
+          s->write = q;
+        }
+        return inflate_flush(s, z, r);
+      }
+    };
+    b |= ((uLong)(n--, *p++)) << k; /* { dg-warning "use of uninitialized value" } */
+    k += 8;
+  }
+}
diff --git a/gcc/testsuite/lib/target-supports.exp b/gcc/testsuite/lib/target-supports.exp
index 6f224fa..a936d78 100644
--- a/gcc/testsuite/lib/target-supports.exp
+++ b/gcc/testsuite/lib/target-supports.exp
@@ -8407,6 +8407,14 @@ proc check_effective_target_lto_incremental { } {
     } "-flto -r -nostdlib"]
 }
 
+# Return 1 if the compiler has been configured with analyzer support.
+
+proc check_effective_target_analyzer { } {
+    return [check_no_compiler_messages analyzer object {
+	void foo (void) { }
+    } "--analyzer"]
+}
+
 # Return 1 if -mx32 -maddress-mode=short can compile, 0 otherwise.
 
 proc check_effective_target_maybe_x32 { } {
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 38/49] analyzer: new file: sm-taint.cc
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (43 preceding siblings ...)
  2019-11-16  1:21 ` [PATCH 37/49] analyzer: new file: sm-sensitive.cc David Malcolm
@ 2019-11-16  1:21 ` David Malcolm
  2019-12-07 15:22   ` Jeff Law
  2019-11-16  1:21 ` [PATCH 42/49] analyzer: new files: program-state.{cc|h} David Malcolm
                   ` (5 subsequent siblings)
  50 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:21 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This patch adds a state machine checker for tracking "taint",
where data potentially under an attacker's control is used for
things like array indices without sanitization (CWE-129).

This checker isn't ready for production, and is presented as a
proof-of-concept of the sm-based approach.

gcc/ChangeLog:
	* analyzer/sm-taint.cc: New file.
---
 gcc/analyzer/sm-taint.cc | 338 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 338 insertions(+)
 create mode 100644 gcc/analyzer/sm-taint.cc

diff --git a/gcc/analyzer/sm-taint.cc b/gcc/analyzer/sm-taint.cc
new file mode 100644
index 0000000..c664a54
--- /dev/null
+++ b/gcc/analyzer/sm-taint.cc
@@ -0,0 +1,338 @@
+/* An experimental state machine, for tracking "taint": unsanitized uses
+   of data potentially under an attacker's control.
+
+   Copyright (C) 2019 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 "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "gimple.h"
+#include "diagnostic-path.h"
+#include "diagnostic-metadata.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/pending-diagnostic.h"
+#include "analyzer/sm.h"
+
+namespace {
+
+/* An experimental state machine, for tracking "taint": unsanitized uses
+   of data potentially under an attacker's control.  */
+
+class taint_state_machine : public state_machine
+{
+public:
+  taint_state_machine (logger *logger);
+
+  bool inherited_state_p () const FINAL OVERRIDE { return true; }
+
+  bool on_stmt (sm_context *sm_ctxt,
+		const supernode *node,
+		const gimple *stmt) const FINAL OVERRIDE;
+
+  void on_condition (sm_context *sm_ctxt,
+		     const supernode *node,
+		     const gimple *stmt,
+		     tree lhs,
+		     enum tree_code op,
+		     tree rhs) const FINAL OVERRIDE;
+
+  void on_leak (sm_context *sm_ctxt,
+		const supernode *node,
+		const gimple *stmt,
+		tree var,
+		state_machine::state_t state) const FINAL OVERRIDE;
+  bool can_purge_p (state_t s) const FINAL OVERRIDE;
+
+  /* Start state.  */
+  state_t m_start;
+
+  /* State for a "tainted" value: unsanitized data potentially under an
+     attacker's control.  */
+  state_t m_tainted;
+
+  /* State for a "tainted" value that has a lower bound.  */
+  state_t m_has_lb;
+
+  /* State for a "tainted" value that has an upper bound.  */
+  state_t m_has_ub;
+
+  /* Stop state, for a value we don't want to track any more.  */
+  state_t m_stop;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+enum bounds
+{
+  BOUNDS_NONE,
+  BOUNDS_UPPER,
+  BOUNDS_LOWER
+};
+
+class tainted_array_index
+  : public pending_diagnostic_subclass<tainted_array_index>
+{
+public:
+  tainted_array_index (const taint_state_machine &sm, tree arg,
+		       enum bounds has_bounds)
+  : m_sm (sm), m_arg (arg), m_has_bounds (has_bounds) {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "tainted_array_index"; }
+
+  bool operator== (const tainted_array_index &other) const
+  {
+    return m_arg == other.m_arg;
+  }
+
+  bool emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    diagnostic_metadata m;
+    m.add_cwe (129);
+    switch (m_has_bounds)
+      {
+      default:
+	gcc_unreachable ();
+      case BOUNDS_NONE:
+	return warning_at (rich_loc, m, OPT_Wanalyzer_tainted_array_index,
+			   "use of tainted value %qE in array lookup"
+			   " without bounds checking",
+			   m_arg);
+	break;
+      case BOUNDS_UPPER:
+	return warning_at (rich_loc, m, OPT_Wanalyzer_tainted_array_index,
+			   "use of tainted value %qE in array lookup"
+			   " without lower-bounds checking",
+			   m_arg);
+	break;
+      case BOUNDS_LOWER:
+	return warning_at (rich_loc, m, OPT_Wanalyzer_tainted_array_index,
+			   "use of tainted value %qE in array lookup"
+			   " without upper-bounds checking",
+			   m_arg);
+	break;
+      }
+  }
+
+  label_text describe_state_change (const evdesc::state_change &change)
+    FINAL OVERRIDE
+  {
+    if (change.m_new_state == m_sm.m_tainted)
+      {
+	if (change.m_origin)
+	  return change.formatted_print ("%qE has an unchecked value here"
+					 " (from %qE)",
+					 change.m_expr, change.m_origin);
+	else
+	  return change.formatted_print ("%qE gets an unchecked value here",
+					 change.m_expr);
+      }
+    else if (change.m_new_state == m_sm.m_has_lb)
+      return change.formatted_print ("%qE has its lower bound checked here",
+				     change.m_expr);
+    else if (change.m_new_state == m_sm.m_has_ub)
+      return change.formatted_print ("%qE has its upper bound checked here",
+				     change.m_expr);
+    return label_text ();
+  }
+
+  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  {
+    switch (m_has_bounds)
+      {
+      default:
+	gcc_unreachable ();
+      case BOUNDS_NONE:
+	return ev.formatted_print ("use of tainted value %qE in array lookup"
+				   " without bounds checking",
+				   m_arg);
+      case BOUNDS_UPPER:
+	return ev.formatted_print ("use of tainted value %qE in array lookup"
+				   " without lower-bounds checking",
+				   m_arg);
+      case BOUNDS_LOWER:
+	return ev.formatted_print ("use of tainted value %qE in array lookup"
+				   " without upper-bounds checking",
+				   m_arg);
+      }
+  }
+
+private:
+  const taint_state_machine &m_sm;
+  tree m_arg;
+  enum bounds m_has_bounds;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* taint_state_machine's ctor.  */
+
+taint_state_machine::taint_state_machine (logger *logger)
+: state_machine ("taint", logger)
+{
+  m_start = add_state ("start");
+  m_tainted = add_state ("tainted");
+  m_has_lb = add_state ("has_lb");
+  m_has_ub = add_state ("has_ub");
+  m_stop = add_state ("stop");
+}
+
+/* Implementation of state_machine::on_stmt vfunc for taint_state_machine.  */
+
+bool
+taint_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 (is_named_call_p (call, "fread", 4))
+	{
+	  tree arg = gimple_call_arg (call, 0);
+	  arg = sm_ctxt->get_readable_tree (arg);
+
+	  sm_ctxt->on_transition (node, stmt, arg, m_start, m_tainted);
+
+	  /* Dereference an ADDR_EXPR.  */
+	  // TODO: should the engine do this?
+	  if (TREE_CODE (arg) == ADDR_EXPR)
+	    sm_ctxt->on_transition (node, stmt, TREE_OPERAND (arg, 0),
+				    m_start, m_tainted);
+	  return true;
+	}
+    }
+  // TODO: ...etc; many other sources of untrusted data
+
+  if (const gassign *assign = dyn_cast <const gassign *> (stmt))
+    {
+      tree rhs1 = gimple_assign_rhs1 (assign);
+      enum tree_code op = gimple_assign_rhs_code (assign);
+
+      /* Check array accesses.  */
+      if (op == ARRAY_REF)
+	{
+	  tree arg = TREE_OPERAND (rhs1, 1);
+	  arg = sm_ctxt->get_readable_tree (arg);
+
+	  /* Unsigned types have an implicit lower bound.  */
+	  bool is_unsigned = false;
+	  if (INTEGRAL_TYPE_P (TREE_TYPE (arg)))
+	    is_unsigned = TYPE_UNSIGNED (TREE_TYPE (arg));
+
+	  /* Complain about missing bounds.  */
+	  sm_ctxt->warn_for_state
+	    (node, stmt, arg, m_tainted,
+	     new tainted_array_index (*this, arg,
+				      is_unsigned
+				      ? BOUNDS_LOWER : BOUNDS_NONE));
+	  sm_ctxt->on_transition (node, stmt, arg, m_tainted, m_stop);
+
+	  /* Complain about missing upper bound.  */
+	  sm_ctxt->warn_for_state  (node, stmt, arg, m_has_lb,
+				    new tainted_array_index (*this, arg,
+							     BOUNDS_LOWER));
+	  sm_ctxt->on_transition (node, stmt, arg, m_has_lb, m_stop);
+
+	  /* Complain about missing lower bound.  */
+	  if (!is_unsigned)
+	    {
+	      sm_ctxt->warn_for_state  (node, stmt, arg, m_has_ub,
+					new tainted_array_index (*this, arg,
+								 BOUNDS_UPPER));
+	      sm_ctxt->on_transition (node, stmt, arg, m_has_ub, m_stop);
+	    }
+	}
+    }
+
+  return false;
+}
+
+/* Implementation of state_machine::on_condition vfunc for taint_state_machine.
+   Potentially transition state 'tainted' to 'has_ub' or 'has_lb',
+   and states 'has_ub' and 'has_lb' to 'stop'.  */
+
+void
+taint_state_machine::on_condition (sm_context *sm_ctxt,
+				   const supernode *node,
+				   const gimple *stmt,
+				   tree lhs,
+				   enum tree_code op,
+				   tree rhs ATTRIBUTE_UNUSED) const
+{
+  if (stmt == NULL)
+    return;
+
+  // TODO: this doesn't use the RHS; should we make it symmetric?
+
+  // TODO
+  switch (op)
+    {
+      //case NE_EXPR:
+      //case EQ_EXPR:
+    case GE_EXPR:
+    case GT_EXPR:
+      {
+	sm_ctxt->on_transition (node, stmt, lhs, m_tainted,
+				m_has_lb);
+	sm_ctxt->on_transition (node, stmt, lhs, m_has_ub,
+				m_stop);
+      }
+      break;
+    case LE_EXPR:
+    case LT_EXPR:
+      {
+	sm_ctxt->on_transition (node, stmt, lhs, m_tainted,
+				m_has_ub);
+	sm_ctxt->on_transition (node, stmt, lhs, m_has_lb,
+				m_stop);
+      }
+      break;
+    default:
+      break;
+    }
+}
+
+void
+taint_state_machine::on_leak (sm_context *sm_ctxt ATTRIBUTE_UNUSED,
+			      const supernode *node ATTRIBUTE_UNUSED,
+			      const gimple *stmt ATTRIBUTE_UNUSED,
+			      tree var ATTRIBUTE_UNUSED,
+			      state_machine::state_t state ATTRIBUTE_UNUSED)
+  const
+{
+  /* Empty.  */
+}
+
+bool
+taint_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const
+{
+  return true;
+}
+
+} // anonymous namespace
+
+/* Internal interface to this file. */
+
+state_machine *
+make_taint_state_machine (logger *logger)
+{
+  return new taint_state_machine (logger);
+}
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 42/49] analyzer: new files: program-state.{cc|h}
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (44 preceding siblings ...)
  2019-11-16  1:21 ` [PATCH 38/49] analyzer: new file: sm-taint.cc David Malcolm
@ 2019-11-16  1:21 ` David Malcolm
  2019-11-16  1:21 ` [PATCH 25/49] analyzer: new files: graphviz.{cc|h} David Malcolm
                   ` (4 subsequent siblings)
  50 siblings, 0 replies; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:21 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This patch introduces classes for tracking the state at a particular
path of analysis.

gcc/ChangeLog:
	* analyzer/program-state.cc: New file.
	* analyzer/program-state.h: New file.
---
 gcc/analyzer/program-state.cc | 1284 +++++++++++++++++++++++++++++++++++++++++
 gcc/analyzer/program-state.h  |  360 ++++++++++++
 2 files changed, 1644 insertions(+)
 create mode 100644 gcc/analyzer/program-state.cc
 create mode 100644 gcc/analyzer/program-state.h

diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc
new file mode 100644
index 0000000..ae15795
--- /dev/null
+++ b/gcc/analyzer/program-state.cc
@@ -0,0 +1,1284 @@
+/* Classes for representing the state of interest at a given path of analysis.
+   Copyright (C) 2019 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 "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "diagnostic.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/program-state.h"
+#include "analyzer/constraint-manager.h"
+#include "analyzer/exploded-graph.h"
+#include "analyzer/state-purge.h"
+#include "analyzer/analyzer-selftests.h"
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class sm_state_map.  */
+
+/* Clone the sm_state_map.  */
+
+sm_state_map *
+sm_state_map::clone () const
+{
+  return new sm_state_map (*this);
+}
+
+/* Clone this sm_state_map, remapping all svalue_ids within it with ID_MAP.
+
+   Return NULL if there are any svalue_ids that have sm-state for which
+   ID_MAP maps them to svalue_id::null (and thus the clone would have lost
+   the sm-state information). */
+
+sm_state_map *
+sm_state_map::clone_with_remapping (const one_way_svalue_id_map &id_map) const
+{
+  sm_state_map *result = new sm_state_map ();
+  for (typename map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      svalue_id sid = (*iter).first;
+      gcc_assert (!sid.null_p ());
+      entry_t e = (*iter).second;
+      /* TODO: what should we do if the origin maps from non-null to null?
+	 Is that loss of information acceptable?  */
+      id_map.update (&e.m_origin);
+
+      svalue_id new_sid = id_map.get_dst_for_src (sid);
+      if (new_sid.null_p ())
+	{
+	  delete result;
+	  return NULL;
+	}
+      result->m_map.put (new_sid, e);
+    }
+  return result;
+}
+
+/* Print this sm_state_map (for SM) to PP.  */
+
+void
+sm_state_map::print (const state_machine &sm, pretty_printer *pp) const
+{
+  pp_string (pp, "{");
+  for (typename map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      if (iter != m_map.begin ())
+	pp_string (pp, ", ");
+      svalue_id sid = (*iter).first;
+      sid.print (pp);
+
+      entry_t e = (*iter).second;
+      pp_printf (pp, ": %s (origin: ",
+		 sm.get_state_name (e.m_state));
+      e.m_origin.print (pp);
+      pp_string (pp, ")");
+    }
+  pp_string (pp, "}");
+}
+
+/* Dump this object (for SM) to stderr.  */
+
+DEBUG_FUNCTION void
+sm_state_map::dump (const state_machine &sm) const
+{
+  pretty_printer pp;
+  pp_show_color (&pp) = pp_show_color (global_dc->printer);
+  pp.buffer->stream = stderr;
+  print (sm, &pp);
+  pp_newline (&pp);
+  pp_flush (&pp);
+}
+
+/* Return true if no states have been set within this map
+   (all expressions are for the start state).  */
+
+bool
+sm_state_map::is_empty_p () const
+{
+  return m_map.elements () == 0;
+}
+
+/* Generate a hash value for this sm_state_map.  */
+
+hashval_t
+sm_state_map::hash () const
+{
+  hashval_t result = 0;
+
+  /* Accumulate the result by xoring a hash for each slot, so that the
+     result doesn't depend on the ordering of the slots in the map.  */
+
+  for (typename map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      inchash::hash hstate;
+      inchash::add ((*iter).first, hstate);
+      entry_t e = (*iter).second;
+      hstate.add_int (e.m_state);
+      inchash::add (e.m_origin, hstate);
+      result ^= hstate.end ();
+    }
+
+  return result;
+}
+
+/* Equality operator for sm_state_map.  */
+
+bool
+sm_state_map::operator== (const sm_state_map &other) const
+{
+  if (m_map.elements () != other.m_map.elements ())
+    return false;
+
+  for (typename map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      svalue_id sid = (*iter).first;
+      entry_t e = (*iter).second;
+      entry_t *other_slot = const_cast <map_t &> (other.m_map).get (sid);
+      if (other_slot == NULL)
+	return false;
+      if (e != *other_slot)
+	return false;
+    }
+
+  gcc_checking_assert (hash () == other.hash ());
+
+  return true;
+}
+
+/* Get the state of SID within this object.
+   States default to the start state.  */
+
+state_machine::state_t
+sm_state_map::get_state (svalue_id sid) const
+{
+  gcc_assert (!sid.null_p ());
+
+  if (entry_t *slot
+      = const_cast <map_t &> (m_map).get (sid))
+    return slot->m_state;
+  else
+    return 0;
+}
+
+/* Get the "origin" svalue_id for any state of SID.  */
+
+svalue_id
+sm_state_map::get_origin (svalue_id sid) const
+{
+  gcc_assert (!sid.null_p ());
+
+  entry_t *slot
+    = const_cast <map_t &> (m_map).get (sid);
+  if (slot)
+    return slot->m_origin;
+  else
+    return svalue_id::null ();
+}
+
+/* Set the state of SID within MODEL to STATE, recording that
+   the state came from ORIGIN.  */
+
+void
+sm_state_map::set_state (region_model *model,
+			 svalue_id sid,
+			 state_machine::state_t state,
+			 svalue_id origin)
+{
+  if (model == NULL)
+    return;
+  equiv_class &ec = model->get_constraints ()->get_equiv_class (sid);
+  set_state (ec, state, origin);
+
+  /* Also do it for all svalues that are equal via non-cm, so that
+     e.g. (void *)&r and (foo *)&r transition together.  */
+  for (unsigned i = 0; i < model->get_num_svalues (); i++)
+    {
+      svalue_id other_sid = svalue_id::from_int (i);
+      if (other_sid == sid)
+	continue;
+
+      tristate eq = model->eval_condition_without_cm (sid, EQ_EXPR, other_sid);
+      if (eq.is_true ())
+	impl_set_state (other_sid, state, origin);
+    }
+}
+
+/* Set the state of EC to STATE, recording that the state came from
+   ORIGIN.  */
+
+void
+sm_state_map::set_state (const equiv_class &ec,
+			 state_machine::state_t state,
+			 svalue_id origin)
+{
+  int i;
+  svalue_id *sid;
+  FOR_EACH_VEC_ELT (ec.m_vars, i, sid)
+    impl_set_state (*sid, state, origin);
+}
+
+/* Set state of PV to STATE, bypassing equivalence classes.  */
+
+void
+sm_state_map::impl_set_state (svalue_id sid, state_machine::state_t state,
+			      svalue_id origin)
+{
+  /* Special-case state 0 as the default value.  */
+  if (state == 0)
+    {
+      if (m_map.get (sid))
+	m_map.remove (sid);
+      return;
+    }
+  gcc_assert (!sid.null_p ());
+  m_map.put (sid, entry_t (state, origin));
+}
+
+/* Handle CALL to unknown FNDECL with an unknown function body, which
+   could do anything to the states passed to it.
+   Clear any state for SM for the params and any LHS.
+   Note that the function might be known to other state machines, but
+   not to this one.  */
+
+void
+sm_state_map::purge_for_unknown_fncall (const exploded_graph &eg,
+					const state_machine &sm,
+					const gcall *call,
+					tree fndecl,
+					region_model *new_model)
+{
+  if (fndecl)
+    eg.log ("function %qE is unknown to checker %qs",
+	    fndecl, sm.get_name ());
+  else
+    eg.log ("unknown function pointer for checker %qs",
+	    sm.get_name ());
+
+  /* Purge any state for parms.  */
+  tree iter_param_types = NULL_TREE;
+  if (fndecl)
+    iter_param_types = TYPE_ARG_TYPES (TREE_TYPE (fndecl));
+  for (unsigned arg_idx = 0; arg_idx < gimple_call_num_args (call); arg_idx++)
+    {
+      /* Track expected param type, where available.  */
+      if (iter_param_types)
+	{
+	  tree param_type = TREE_VALUE (iter_param_types);
+	  gcc_assert (param_type);
+	  iter_param_types = TREE_CHAIN (iter_param_types);
+
+	  /* Don't purge state if it was passed as a const pointer
+	     e.g. for things like strlen (PTR).  */
+	  if (TREE_CODE (param_type) == POINTER_TYPE)
+	    if (TYPE_READONLY (TREE_TYPE (param_type)))
+	      continue;
+	}
+      tree parm = gimple_call_arg (call, arg_idx);
+      svalue_id parm_sid = new_model->get_rvalue (parm, NULL);
+      set_state (new_model, parm_sid, 0, svalue_id::null ());
+
+      /* Also clear sm-state from svalue_ids that are passed via a
+	 pointer.  */
+      if (TREE_CODE (parm) == ADDR_EXPR)
+	{
+	  tree pointee = TREE_OPERAND (parm, 0);
+	  svalue_id parm_sid = new_model->get_rvalue (pointee, NULL);
+	  set_state (new_model, parm_sid, 0, svalue_id::null ());
+	}
+    }
+
+  /* Purge any state for any LHS.  */
+  if (tree lhs = gimple_call_lhs (call))
+    {
+      svalue_id lhs_sid = new_model->get_rvalue (lhs, NULL);
+      set_state (new_model, lhs_sid, 0, svalue_id::null ());
+    }
+}
+
+/* Update this map based on MAP.  */
+
+void
+sm_state_map::remap_svalue_ids (const svalue_id_map &map)
+{
+  map_t tmp_map;
+
+  /* Build an intermediate map, using the new sids.  */
+  for (typename map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      svalue_id sid = (*iter).first;
+      entry_t e = (*iter).second;
+
+      map.update (&sid);
+      map.update (&e.m_origin);
+      tmp_map.put (sid, e);
+    }
+
+  /* Clear the existing values.  */
+  m_map.empty ();
+
+  /* Copy over from intermediate map.  */
+  for (typename map_t::iterator iter = tmp_map.begin ();
+       iter != tmp_map.end ();
+       ++iter)
+    {
+      svalue_id sid = (*iter).first;
+      entry_t e = (*iter).second;
+
+      impl_set_state (sid, e.m_state, e.m_origin);
+    }
+}
+
+/* Purge any state for svalue_ids >= FIRST_UNUSED_SID.
+   If !SM::can_purge_p, then report the state as leaking,
+   using SM_IDX, CTXT, and MAP.
+   Return the number of states that were purged.  */
+
+int
+sm_state_map::on_svalue_purge (const state_machine &sm,
+			       int sm_idx,
+			       svalue_id first_unused_sid,
+			       const svalue_id_map &map,
+			       impl_region_model_context *ctxt)
+{
+  /* TODO: ideally remove the slot directly; for now
+     do it in two stages.  */
+  auto_vec<svalue_id> to_remove;
+  for (typename map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      svalue_id dst_sid ((*iter).first);
+      if (dst_sid.as_int () >= first_unused_sid.as_int ())
+	{
+	  /* Complain about leaks here.  */
+	  entry_t e = (*iter).second;
+
+	  if (!sm.can_purge_p (e.m_state))
+	    ctxt->on_state_leak (sm, sm_idx, dst_sid, first_unused_sid,
+				 map, e.m_state);
+
+	  to_remove.safe_push (dst_sid);
+	}
+    }
+
+  int i;
+  svalue_id *dst_sid;
+  FOR_EACH_VEC_ELT (to_remove, i, dst_sid)
+    m_map.remove (*dst_sid);
+
+  return to_remove.length ();
+}
+
+/* Set the state of CHILD_SID to that of PARENT_SID.  */
+
+void
+sm_state_map::on_inherited_svalue (svalue_id parent_sid,
+				   svalue_id child_sid)
+{
+  state_machine::state_t state = get_state (parent_sid);
+  impl_set_state (child_sid, state, parent_sid);
+}
+
+/* Set the state of DST_SID to that of SRC_SID.  */
+
+void
+sm_state_map::on_cast (svalue_id src_sid,
+		       svalue_id dst_sid)
+{
+  state_machine::state_t state = get_state (src_sid);
+  impl_set_state (dst_sid, state, get_origin (src_sid));
+}
+
+/* Assert that this object is sane.  */
+
+void
+sm_state_map::validate (const state_machine &sm,
+			int num_svalues) const
+{
+  /* Skip this in a release build.  */
+#if !CHECKING_P
+  return;
+#endif
+
+  for (typename map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      svalue_id sid = (*iter).first;
+      entry_t e = (*iter).second;
+
+      gcc_assert (sid.as_int () < num_svalues);
+      sm.validate (e.m_state);
+      gcc_assert (e.m_origin.as_int () < num_svalues);
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class program_state.  */
+
+/* program_state's ctor.  */
+
+program_state::program_state (const extrinsic_state &ext_state)
+: m_region_model (new region_model ()),
+  m_checker_states (ext_state.m_checkers.length ())
+{
+  int num_states = ext_state.m_checkers.length ();
+  for (int i = 0; i < num_states; i++)
+    m_checker_states.quick_push (new sm_state_map ());
+}
+
+/* program_state's copy ctor.  */
+
+program_state::program_state (const program_state &other)
+: m_region_model (new region_model (*other.m_region_model)),
+  m_checker_states (other.m_checker_states.length ())
+{
+  int i;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (other.m_checker_states, i, smap)
+    m_checker_states.quick_push (smap->clone ());
+}
+
+/* program_state's assignment operator.  */
+
+program_state&
+program_state::operator= (const program_state &other)
+{
+  delete m_region_model;
+  m_region_model = new region_model (*other.m_region_model);
+
+  int i;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (m_checker_states, i, smap)
+    delete smap;
+  m_checker_states.truncate (0);
+  gcc_assert (m_checker_states.space (other.m_checker_states.length ()));
+
+  FOR_EACH_VEC_ELT (other.m_checker_states, i, smap)
+    m_checker_states.quick_push (smap->clone ());
+
+  return *this;
+}
+
+#if __cplusplus >= 201103
+/* Move constructor for program_state (when building with C++11).  */
+program_state::program_state (program_state &&other)
+: m_region_model (other.m_region_model),
+  m_checker_states (other.m_checker_states.length ())
+{
+  other.m_region_model = NULL;
+
+  int i;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (other.m_checker_states, i, smap)
+    m_checker_states.quick_push (smap);
+  other.m_checker_states.truncate (0);
+}
+#endif
+
+/* program_state's dtor.  */
+
+program_state::~program_state ()
+{
+  delete m_region_model;
+}
+
+/* Generate a hash value for this program_state.  */
+
+hashval_t
+program_state::hash () const
+{
+  hashval_t result = m_region_model->hash ();
+
+  int i;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (m_checker_states, i, smap)
+    result ^= smap->hash ();
+  return result;
+}
+
+/* Equality operator for program_state.
+   All parts of the program_state (region model, checker states) must
+   equal their counterparts in OTHER for the two program_states to be
+   considered equal.  */
+
+bool
+program_state::operator== (const program_state &other) const
+{
+  if (!(*m_region_model == *other.m_region_model))
+    return false;
+
+  int i;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (m_checker_states, i, smap)
+    if (!(*smap == *other.m_checker_states[i]))
+      return false;
+
+  gcc_checking_assert (hash () == other.hash ());
+
+  return true;
+}
+
+/* Print a compact representation of this state to PP.  */
+
+void
+program_state::print (const extrinsic_state &ext_state,
+		      pretty_printer *pp) const
+{
+  pp_printf (pp, "rmodel: ");
+  m_region_model->print (pp);
+  pp_newline (pp);
+
+  int i;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (m_checker_states, i, smap)
+    {
+      if (!smap->is_empty_p ())
+	{
+	  pp_printf (pp, "%s: ", ext_state.get_name (i));
+	  smap->print (ext_state.get_sm (i), pp);
+	  pp_newline (pp);
+	}
+    }
+}
+
+/* Dump a multiline representation of this state to PP.  */
+
+void
+program_state::dump_to_pp (const extrinsic_state &ext_state,
+			   bool summarize,
+			   pretty_printer *pp) const
+{
+  pp_printf (pp, "rmodel: ");
+  m_region_model->dump_to_pp (pp, summarize);
+
+  int i;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (m_checker_states, i, smap)
+    {
+      if (!smap->is_empty_p ())
+	{
+	  pp_printf (pp, "%s: ", ext_state.get_name (i));
+	  smap->print (ext_state.get_sm (i), pp);
+	  pp_newline (pp);
+	}
+    }
+}
+
+/* Dump a multiline representation of this state to OUTF.  */
+
+void
+program_state::dump_to_file (const extrinsic_state &ext_state,
+			     bool summarize,
+			     FILE *outf) const
+{
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  if (outf == stderr)
+    pp_show_color (&pp) = pp_show_color (global_dc->printer);
+  pp.buffer->stream = outf;
+  dump_to_pp (ext_state, summarize, &pp);
+  pp_flush (&pp);
+}
+
+/* Dump a multiline representation of this state to stderr.  */
+
+DEBUG_FUNCTION void
+program_state::dump (const extrinsic_state &ext_state,
+		     bool summarize) const
+{
+  dump_to_file (ext_state, summarize, stderr);
+}
+
+/* Determine if following edge SUCC from ENODE is valid within the graph EG
+   and update this state accordingly in-place.
+
+   Return true if the edge can be followed, or false otherwise.
+
+   Check for relevant conditionals and switch-values for conditionals
+   and switch statements, adding the relevant conditions to this state.
+   Push/pop frames for interprocedural edges and update params/returned
+   values.
+
+   This is the "state" half of exploded_node::on_edge.  */
+
+bool
+program_state::on_edge (exploded_graph &eg,
+			const exploded_node &enode,
+			const superedge *succ,
+			state_change *change)
+{
+  /* Update state.  */
+  const program_point &point = enode.get_point ();
+  const gimple *last_stmt = point.get_supernode ()->get_last_stmt ();
+
+  /* For conditionals and switch statements, add the
+     relevant conditions (for the specific edge) to new_state;
+     skip edges for which the resulting constraints
+     are impossible.
+     This also updates frame information for call/return superedges.
+     Adding the relevant conditions for the edge could also trigger
+     sm-state transitions (e.g. transitions due to ptrs becoming known
+     to be NULL or non-NULL) */
+
+  impl_region_model_context ctxt (eg, &enode,
+				  &enode.get_state (),
+				  this, change,
+				  last_stmt);
+  if (!m_region_model->maybe_update_for_edge (*succ,
+					      last_stmt,
+					      &ctxt))
+    {
+      eg.log ("edge to SN: %i is impossible due to region_model constraints",
+	      succ->m_dest->m_index);
+      return false;
+    }
+
+  return true;
+}
+
+/* Generate a simpler version of THIS, discarding state that's no longer
+   relevant at POINT.
+   The idea is that we're more likely to be able to consolidate
+   multiple (point, state) into single exploded_nodes if we discard
+   irrelevant state (e.g. at the end of functions).
+
+   Retain state affected by CHANGE, to make it easier to generate
+   state_change_events.  */
+
+program_state
+program_state::prune_for_point (exploded_graph &eg,
+				const program_point &point,
+				state_change *change) const
+{
+  LOG_SCOPE (eg.get_logger ());
+
+  function *fun = point.get_function ();
+  if (!fun)
+    return *this;
+
+  program_state new_state (*this);
+
+  purge_stats stats;
+
+  const state_purge_map *pm = eg.get_purge_map ();
+  if (pm)
+    {
+      region_id_set purgeable_ssa_regions (new_state.m_region_model);
+      region_id frame_rid
+	= new_state.m_region_model->get_current_frame_id ();
+      frame_region *frame
+	= new_state.m_region_model->get_region <frame_region>(frame_rid);
+
+      /* TODO: maybe move to a member of region_model?  */
+
+      auto_vec<tree> ssa_names_to_purge;
+      for (frame_region::map_t::iterator iter = frame->begin ();
+	   iter != frame->end ();
+	   ++iter)
+	{
+	  tree var = (*iter).first;
+	  region_id rid = (*iter).second;
+	  if (TREE_CODE (var) == SSA_NAME)
+	    {
+	      const state_purge_per_ssa_name &per_ssa
+		= pm->get_data_for_ssa_name (var);
+	      if (!per_ssa.needed_at_point_p (point.get_function_point ()))
+		{
+		  region *region
+		    = new_state.m_region_model->get_region (rid);
+		  svalue_id sid = region->get_value_direct ();
+		  if (!sid.null_p ())
+		    {
+		      if (!new_state.can_purge_p (eg.get_ext_state (), sid))
+			{
+			  /* (currently only state maps can keep things
+			     alive).  */
+			  eg.log ("not purging RID: %i for %qE"
+				  " (used by state map)",
+				  rid.as_int (), var);
+			  continue;
+			}
+
+		      /* Don't purge regions containing svalues that
+			 have a change of sm-state, to make it easier to
+			 generate state_change_event messages.  */
+		      if (change)
+			if (change->affects_p (sid))
+			  {
+			    eg.log ("not purging RID: %i for %qE"
+				    " (affected by change)",
+				    rid.as_int (), var);
+			    continue;
+			  }
+		    }
+		  purgeable_ssa_regions.add_region (rid);
+		  ssa_names_to_purge.safe_push (var);
+		  eg.log ("purging RID: %i for %qE", rid.as_int (), var);
+		  /* We also need to remove the region from the map.
+		     We're in mid-traversal, so the removal is done in
+		     unbind below.  */
+		}
+	    }
+	}
+
+      /* Unbind the regions from the frame's map of vars-to-regions.  */
+      unsigned i;
+      tree var;
+      FOR_EACH_VEC_ELT (ssa_names_to_purge, i, var)
+	frame->unbind (var);
+
+      /* Purge the regions.  Nothing should point to them, and they
+	 should have no children, as they are for SSA names.  */
+      new_state.m_region_model->purge_regions (purgeable_ssa_regions,
+					       &stats,
+					       eg.get_logger ());
+    }
+
+  /* Purge unused svalues.  */
+  // TODO: which enode to use, if any?
+  impl_region_model_context ctxt (eg, NULL,
+				  this,
+				  &new_state,
+				  change,
+				  NULL);
+  new_state.m_region_model->purge_unused_svalues (&stats, &ctxt);
+  eg.log ("num svalues purged: %i", stats.m_num_svalues);
+  eg.log ("num regions purged: %i", stats.m_num_regions);
+  eg.log ("num equiv_classes purged: %i", stats.m_num_equiv_classes);
+  eg.log ("num constraints purged: %i", stats.m_num_constraints);
+  eg.log ("num sm map items purged: %i", stats.m_num_client_items);
+
+  new_state.m_region_model->canonicalize (&ctxt);
+
+  return new_state;
+}
+
+/* Remap all svalue_ids in this state's m_checker_states according to MAP.
+   The svalues_ids in the region_model are assumed to already have been
+   remapped.  */
+
+void
+program_state::remap_svalue_ids (const svalue_id_map &map)
+{
+  int i;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (m_checker_states, i, smap)
+    smap->remap_svalue_ids (map);
+}
+
+/* Attempt to return a tree that represents SID, or return NULL_TREE.
+   Find the first region that stores the value (e.g. a local) and
+   generate a representative tree for it.  */
+
+tree
+program_state::get_representative_tree (svalue_id sid) const
+{
+  return m_region_model->get_representative_tree (sid);
+}
+
+/* Attempt to merge this state with OTHER, both using EXT_STATE.
+   Write the result to *OUT.
+   If the states were merged successfully, return true.  */
+
+bool
+program_state::can_merge_with_p (const program_state &other,
+				 const extrinsic_state &ext_state,
+				 program_state *out) const
+{
+  gcc_assert (out);
+
+  /* TODO:  initially I had an early reject here if there
+     are sm-differences between the states.  However, this was
+     falsely rejecting merger opportunities for states where the
+     only difference was in svalue_id ordering.  */
+
+  /* Attempt to merge the region_models.  */
+
+  svalue_id_merger_mapping sid_mapping (*m_region_model,
+					*other.m_region_model);
+  if (!m_region_model->can_merge_with_p (*other.m_region_model,
+					 out->m_region_model,
+					 &sid_mapping))
+    return false;
+
+  /* Copy m_checker_states to result, remapping svalue_ids using
+     sid_mapping.  */
+  int i;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (out->m_checker_states, i, smap)
+    delete smap;
+  out->m_checker_states.truncate (0);
+
+  /* Remap this and other's m_checker_states using sid_mapping.
+     Only merge states that have equality between the two end-results:
+     sm-state differences are likely to be interesting to end-users, and
+     hence are worth exploring as separate paths in the exploded graph.  */
+  FOR_EACH_VEC_ELT (m_checker_states, i, smap)
+    {
+      sm_state_map *other_smap = other.m_checker_states[i];
+
+      /* If clone_with_remapping returns NULL for one of the input smaps,
+	 then it has sm-state for an svalue_id where the svalue_id is
+	 being mapped to svalue_id::null in its sid_mapping, meaning that
+	 the svalue is to be dropped during the merger.  We don't want
+	 to lose sm-state during a state merger, so return false for these
+	 cases.  */
+      sm_state_map *remapped_a_smap
+	= smap->clone_with_remapping (sid_mapping.m_map_from_a_to_m);
+      if (!remapped_a_smap)
+	return false;
+      sm_state_map *remapped_b_smap
+	= other_smap->clone_with_remapping (sid_mapping.m_map_from_b_to_m);
+      if (!remapped_b_smap)
+	{
+	  delete remapped_a_smap;
+	  return false;
+	}
+
+      /* Both states have sm-state for the same values; now ensure that the
+	 states are equal.  */
+      if (*remapped_a_smap == *remapped_b_smap)
+	{
+	  out->m_checker_states.safe_push (remapped_a_smap);
+	  delete remapped_b_smap;
+	}
+      else
+	{
+	  /* Don't merge if there are sm-state differences.  */
+	  delete remapped_a_smap;
+	  delete remapped_b_smap;
+	  return false;
+	}
+    }
+
+  impl_region_model_context ctxt (out, NULL, ext_state);
+  out->m_region_model->canonicalize (&ctxt);
+
+  return true;
+}
+
+/* Assert that this object is valid.  */
+
+void
+program_state::validate (const extrinsic_state &ext_state) const
+{
+  /* Skip this in a release build.  */
+#if !CHECKING_P
+  return;
+#endif
+
+  m_region_model->validate ();
+  gcc_assert (m_checker_states.length () == ext_state.get_num_checkers ());
+  int sm_idx;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (m_checker_states, sm_idx, smap)
+    {
+      const state_machine &sm = ext_state.get_sm (sm_idx);
+      smap->validate (sm, m_region_model->get_num_svalues ());
+    }
+}
+
+/* Dump this sm_change to PP.  */
+
+void
+state_change::sm_change::dump (pretty_printer *pp,
+			       const extrinsic_state &ext_state) const
+{
+  const state_machine &sm = get_sm (ext_state);
+  pp_string (pp, "(");
+  m_new_sid.print (pp);
+  pp_printf (pp, ": %s: %qs -> %qs)",
+	     sm.get_name (),
+	     sm.get_state_name (m_old_state),
+	     sm.get_state_name (m_new_state));
+}
+
+/* Remap all svalue_ids in this change according to MAP.  */
+
+void
+state_change::sm_change::remap_svalue_ids (const svalue_id_map &map)
+{
+  map.update (&m_new_sid);
+}
+
+/* Purge any svalue_ids >= FIRST_UNUSED_SID.
+   Return the number of states that were purged.  */
+
+int
+state_change::sm_change::on_svalue_purge (svalue_id first_unused_sid)
+{
+  if (m_new_sid.as_int () >= first_unused_sid.as_int ())
+    {
+      m_new_sid = svalue_id::null ();
+      return 1;
+    }
+
+  return 0;
+}
+
+/* Assert that this object is sane.  */
+
+void
+state_change::sm_change::validate (const program_state &new_state) const
+{
+  m_new_sid.validate (*new_state.m_region_model);
+}
+
+/* state_change's ctor.  */
+
+state_change::state_change ()
+{
+}
+
+/* state_change's copy ctor.  */
+
+state_change::state_change (const state_change &other)
+: m_sm_changes (other.m_sm_changes.length ())
+{
+  unsigned i;
+  sm_change *change;
+  FOR_EACH_VEC_ELT (other.m_sm_changes, i, change)
+    m_sm_changes.quick_push (*change);
+}
+
+/* Record a state-machine state change.  */
+
+void
+state_change::add_sm_change (int sm_idx,
+			     svalue_id new_sid,
+			     state_machine::state_t old_state,
+			     state_machine::state_t new_state)
+{
+  m_sm_changes.safe_push (sm_change (sm_idx,
+				     new_sid,
+				     old_state, new_state));
+}
+
+/* Return true if SID (in the new state) was affected by any
+   sm-state changes.  */
+
+bool
+state_change::affects_p (svalue_id sid) const
+{
+  unsigned i;
+  sm_change *change;
+  FOR_EACH_VEC_ELT (m_sm_changes, i, change)
+    {
+      if (sid == change->m_new_sid)
+	return true;
+    }
+  return false;
+}
+
+/* Dump this state_change to PP.  */
+
+void
+state_change::dump (pretty_printer *pp,
+		    const extrinsic_state &ext_state) const
+{
+  unsigned i;
+  sm_change *change;
+  FOR_EACH_VEC_ELT (m_sm_changes, i, change)
+    {
+      if (i > 0)
+	pp_string (pp, ", ");
+      change->dump (pp, ext_state);
+    }
+}
+
+/* Dump this state_change to stderr.  */
+
+void
+state_change::dump (const extrinsic_state &ext_state) const
+{
+  pretty_printer pp;
+  pp_show_color (&pp) = pp_show_color (global_dc->printer);
+  pp.buffer->stream = stderr;
+  dump (&pp, ext_state);
+  pp_newline (&pp);
+  pp_flush (&pp);
+}
+
+/* Remap all svalue_ids in this state_change according to MAP.  */
+
+void
+state_change::remap_svalue_ids (const svalue_id_map &map)
+{
+  unsigned i;
+  sm_change *change;
+  FOR_EACH_VEC_ELT (m_sm_changes, i, change)
+    change->remap_svalue_ids (map);
+}
+
+/* Purge any svalue_ids >= FIRST_UNUSED_SID.
+   Return the number of states that were purged.  */
+
+int
+state_change::on_svalue_purge (svalue_id first_unused_sid)
+{
+  int result = 0;
+  unsigned i;
+  sm_change *change;
+  FOR_EACH_VEC_ELT (m_sm_changes, i, change)
+    result += change->on_svalue_purge (first_unused_sid);
+  return result;
+}
+
+/* Assert that this object is sane.  */
+
+void
+state_change::validate (const program_state &new_state) const
+{
+  /* Skip this in a release build.  */
+#if !CHECKING_P
+  return;
+#endif
+  unsigned i;
+  sm_change *change;
+  FOR_EACH_VEC_ELT (m_sm_changes, i, change)
+    change->validate (new_state);
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Tests for sm_state_map.  */
+
+static void
+test_sm_state_map ()
+{
+  tree x = build_global_decl ("x", integer_type_node);
+  tree y = build_global_decl ("y", integer_type_node);
+  tree z = build_global_decl ("z", integer_type_node);
+
+  /* Test setting states on svalue_id instances directly.  */
+  {
+    region_model model;
+    svalue_id sid_x = model.get_rvalue (x, NULL);
+    svalue_id sid_y = model.get_rvalue (y, NULL);
+    svalue_id sid_z = model.get_rvalue (z, NULL);
+
+    sm_state_map map;
+    ASSERT_TRUE (map.is_empty_p ());
+    ASSERT_EQ (map.get_state (sid_x), 0);
+
+    map.impl_set_state (sid_x, 42, sid_z);
+    ASSERT_EQ (map.get_state (sid_x), 42);
+    ASSERT_EQ (map.get_origin (sid_x), sid_z);
+    ASSERT_EQ (map.get_state (sid_y), 0);
+    ASSERT_FALSE (map.is_empty_p ());
+
+    map.impl_set_state (sid_y, 0, sid_z);
+    ASSERT_EQ (map.get_state (sid_y), 0);
+
+    map.impl_set_state (sid_x, 0, sid_z);
+    ASSERT_EQ (map.get_state (sid_x), 0);
+    ASSERT_TRUE (map.is_empty_p ());
+  }
+
+  /* Test setting states via equivalence classes.  */
+  {
+    region_model model;
+    svalue_id sid_x = model.get_rvalue (x, NULL);
+    svalue_id sid_y = model.get_rvalue (y, NULL);
+    svalue_id sid_z = model.get_rvalue (z, NULL);
+
+    sm_state_map map;
+    ASSERT_TRUE (map.is_empty_p ());
+    ASSERT_EQ (map.get_state (sid_x), 0);
+    ASSERT_EQ (map.get_state (sid_y), 0);
+
+    model.add_constraint (x, EQ_EXPR, y, NULL);
+
+    /* Setting x to a state should also update y, as they
+       are in the same equivalence class.  */
+    map.set_state (&model, sid_x, 5, sid_z);
+    ASSERT_EQ (map.get_state (sid_x), 5);
+    ASSERT_EQ (map.get_state (sid_y), 5);
+    ASSERT_EQ (map.get_origin (sid_x), sid_z);
+    ASSERT_EQ (map.get_origin (sid_y), sid_z);
+  }
+
+  /* Test equality and hashing.  */
+  {
+    region_model model;
+    svalue_id sid_y = model.get_rvalue (y, NULL);
+    svalue_id sid_z = model.get_rvalue (z, NULL);
+
+    sm_state_map map0;
+    sm_state_map map1;
+    sm_state_map map2;
+
+    ASSERT_EQ (map0.hash (), map1.hash ());
+    ASSERT_EQ (map0, map1);
+
+    map1.impl_set_state (sid_y, 5, sid_z);
+    ASSERT_NE (map0.hash (), map1.hash ());
+    ASSERT_NE (map0, map1);
+
+    /* Make the same change to map2.  */
+    map2.impl_set_state (sid_y, 5, sid_z);
+    ASSERT_EQ (map1.hash (), map2.hash ());
+    ASSERT_EQ (map1, map2);
+  }
+
+  /* Equality and hashing shouldn't depend on ordering.  */
+  {
+    sm_state_map map0;
+    sm_state_map map1;
+    sm_state_map map2;
+
+    ASSERT_EQ (map0.hash (), map1.hash ());
+    ASSERT_EQ (map0, map1);
+
+    map1.impl_set_state (svalue_id::from_int (14), 2, svalue_id::null ());
+    map1.impl_set_state (svalue_id::from_int (16), 3, svalue_id::null ());
+    map1.impl_set_state (svalue_id::from_int (1), 2, svalue_id::null ());
+    map1.impl_set_state (svalue_id::from_int (9), 2, svalue_id::null ());
+
+    map2.impl_set_state (svalue_id::from_int (1), 2, svalue_id::null ());
+    map2.impl_set_state (svalue_id::from_int (16), 3, svalue_id::null ());
+    map2.impl_set_state (svalue_id::from_int (14), 2, svalue_id::null ());
+    map2.impl_set_state (svalue_id::from_int (9), 2, svalue_id::null ());
+
+    ASSERT_EQ (map1.hash (), map2.hash ());
+    ASSERT_EQ (map1, map2);
+  }
+
+  /* Test sm_state_map::remap_svalue_ids.  */
+  {
+    sm_state_map map;
+    svalue_id sid_0 = svalue_id::from_int (0);
+    svalue_id sid_1 = svalue_id::from_int (1);
+    svalue_id sid_2 = svalue_id::from_int (2);
+
+    map.impl_set_state (sid_0, 42, sid_2);
+    ASSERT_EQ (map.get_state (sid_0), 42);
+    ASSERT_EQ (map.get_origin (sid_0), sid_2);
+    ASSERT_EQ (map.get_state (sid_1), 0);
+    ASSERT_EQ (map.get_state (sid_2), 0);
+
+    /* Apply a remapping to the IDs.  */
+    svalue_id_map remapping (3);
+    remapping.put (sid_0, sid_1);
+    remapping.put (sid_1, sid_2);
+    remapping.put (sid_2, sid_0);
+    map.remap_svalue_ids (remapping);
+
+    /* Verify that the IDs have been remapped.  */
+    ASSERT_EQ (map.get_state (sid_1), 42);
+    ASSERT_EQ (map.get_origin (sid_1), sid_0);
+    ASSERT_EQ (map.get_state (sid_2), 0);
+    ASSERT_EQ (map.get_state (sid_0), 0);
+  }
+
+  // TODO: coverage for purging
+}
+
+/* Verify that program_states with identical sm-state can be merged,
+   and that the merged program_state preserves the sm-state.  */
+
+static void
+test_program_state_merging ()
+{
+  /* Create a program_state for a global ptr "p" that has
+     malloc sm-state, pointing to a region on the heap.  */
+  tree p = build_global_decl ("p", ptr_type_node);
+
+  auto_delete_vec <state_machine> checkers;
+  checkers.safe_push (make_malloc_state_machine (NULL));
+  extrinsic_state ext_state (checkers);
+
+  program_state s0 (ext_state);
+  impl_region_model_context ctxt (&s0, NULL, ext_state);
+
+  region_model *model0 = s0.m_region_model;
+  region_id new_rid = model0->add_new_malloc_region ();
+  svalue_id ptr_sid
+      = model0->get_or_create_ptr_svalue (ptr_type_node, new_rid);
+  model0->set_value (model0->get_lvalue (p, &ctxt),
+		     ptr_sid, &ctxt);
+  sm_state_map *smap = s0.m_checker_states[0];
+  const state_machine::state_t TEST_STATE = 3;
+  smap->impl_set_state (ptr_sid, TEST_STATE, svalue_id::null ());
+  ASSERT_EQ (smap->get_state (ptr_sid), TEST_STATE);
+
+  model0->canonicalize (&ctxt);
+
+  /* Verify that canonicalization preserves sm-state.  */
+  ASSERT_EQ (smap->get_state (model0->get_rvalue (p, NULL)), TEST_STATE);
+
+  /* Make a copy of the program_state.  */
+  program_state s1 (s0);
+  ASSERT_EQ (s0, s1);
+
+  /* We have two identical states with "p" pointing to a heap region
+     with the given sm-state.
+     They ought to be mergeable, preserving the sm-state.  */
+  program_state merged (ext_state);
+  ASSERT_TRUE (s0.can_merge_with_p (s1, ext_state, &merged));
+  merged.validate (ext_state);
+
+  /* Verify that the merged state has the sm-state for "p".  */
+  region_model *merged_model = merged.m_region_model;
+  sm_state_map *merged_smap = merged.m_checker_states[0];
+  ASSERT_EQ (merged_smap->get_state (merged_model->get_rvalue (p, NULL)),
+	     TEST_STATE);
+
+  /* Try canonicalizing.  */
+  impl_region_model_context merged_ctxt (&merged, NULL, ext_state);
+  merged.m_region_model->canonicalize (&merged_ctxt);
+  merged.validate (ext_state);
+
+  /* Verify that the merged state still has the sm-state for "p".  */
+  ASSERT_EQ (merged_smap->get_state (merged_model->get_rvalue (p, NULL)),
+	     TEST_STATE);
+
+  /* After canonicalization, we ought to have equality with the inputs.  */
+  ASSERT_EQ (s0, merged);
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+analyzer_program_state_cc_tests ()
+{
+  test_sm_state_map ();
+  test_program_state_merging ();
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
diff --git a/gcc/analyzer/program-state.h b/gcc/analyzer/program-state.h
new file mode 100644
index 0000000..9c87c15
--- /dev/null
+++ b/gcc/analyzer/program-state.h
@@ -0,0 +1,360 @@
+/* Classes for representing the state of interest at a given path of analysis.
+   Copyright (C) 2019 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/>.  */
+
+#ifndef GCC_ANALYZER_PROGRAM_STATE_H
+#define GCC_ANALYZER_PROGRAM_STATE_H
+
+#include "analyzer/sm.h"
+#include "analyzer/region-model.h"
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Data shared by all program_state instances.  */
+
+class extrinsic_state
+{
+public:
+  extrinsic_state (auto_delete_vec <state_machine> &checkers)
+  : m_checkers (checkers)
+  {
+  }
+
+  const state_machine &get_sm (int idx) const
+  {
+    return *m_checkers[idx];
+  }
+
+  const char *get_name (int idx) const
+  {
+    return m_checkers[idx]->get_name ();
+  }
+
+  unsigned get_num_checkers () const { return m_checkers.length (); }
+
+  /* The state machines.  */
+  auto_delete_vec <state_machine> &m_checkers;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+template <> struct default_hash_traits<svalue_id>
+: public pod_hash_traits<svalue_id>
+{
+};
+
+template <>
+inline hashval_t
+pod_hash_traits<svalue_id>::hash (value_type v)
+{
+  return v.as_int ();
+}
+
+template <>
+inline bool
+pod_hash_traits<svalue_id>::equal (const value_type &existing,
+				   const value_type &candidate)
+{
+  return existing == candidate;
+}
+template <>
+inline void
+pod_hash_traits<svalue_id>::mark_deleted (value_type &v)
+{
+  v = svalue_id::from_int (-2);
+}
+template <>
+inline void
+pod_hash_traits<svalue_id>::mark_empty (value_type &v)
+{
+  v = svalue_id::null ();
+}
+template <>
+inline bool
+pod_hash_traits<svalue_id>::is_deleted (value_type v)
+{
+  return v.as_int () == -2;
+}
+template <>
+inline bool
+pod_hash_traits<svalue_id>::is_empty (value_type v)
+{
+  return v.null_p ();
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Map from svalue_id to state machine state, also capturing the origin of
+   each state.  */
+
+class sm_state_map
+{
+public:
+  /* An entry in the hash_map.  */
+  struct entry_t
+  {
+    /* Default ctor needed by hash_map::empty.  */
+    entry_t ()
+    : m_state (0), m_origin (svalue_id::null ())
+    {
+    }
+
+    entry_t (state_machine::state_t state,
+	     svalue_id origin)
+    : m_state (state), m_origin (origin)
+    {}
+
+    bool operator== (const entry_t &other) const
+    {
+      return (m_state == other.m_state
+	      && m_origin == other.m_origin);
+    }
+    bool operator!= (const entry_t &other) const
+    {
+      return !(*this == other);
+    }
+
+    state_machine::state_t m_state;
+    svalue_id m_origin;
+  };
+  typedef hash_map <svalue_id, entry_t> map_t;
+  typedef typename map_t::iterator iterator_t;
+
+  sm_state_map *clone () const;
+
+  sm_state_map *
+  clone_with_remapping (const one_way_svalue_id_map &id_map) const;
+
+  void print (const state_machine &sm, pretty_printer *pp) const;
+  void dump (const state_machine &sm) const;
+
+  bool is_empty_p () const;
+
+  hashval_t hash () const;
+
+  bool operator== (const sm_state_map &other) const;
+  bool operator!= (const sm_state_map &other) const
+  {
+    return !(*this == other);
+  }
+
+  state_machine::state_t get_state (svalue_id sid) const;
+  svalue_id get_origin (svalue_id sid) const;
+
+  void set_state (region_model *model,
+		  svalue_id sid,
+		  state_machine::state_t state,
+		  svalue_id origin);
+  void set_state (const equiv_class &ec,
+		  state_machine::state_t state,
+		  svalue_id origin);
+  void impl_set_state (svalue_id sid,
+		       state_machine::state_t state,
+		       svalue_id origin);
+
+  void purge_for_unknown_fncall (const exploded_graph &eg,
+				 const state_machine &sm,
+				 const gcall *call, tree fndecl,
+				 region_model *new_model);
+
+  void remap_svalue_ids (const svalue_id_map &map);
+
+  int on_svalue_purge (const state_machine &sm,
+		       int sm_idx,
+		       svalue_id first_unused_sid,
+		       const svalue_id_map &map,
+		       impl_region_model_context *ctxt);
+
+  void on_inherited_svalue (svalue_id parent_sid,
+			    svalue_id child_sid);
+
+  void on_cast (svalue_id src_sid,
+		svalue_id dst_sid);
+
+  void validate (const state_machine &sm, int num_svalues) const;
+
+  iterator_t begin () const { return m_map.begin (); }
+  iterator_t end () const { return m_map.end (); }
+
+private:
+  map_t m_map;
+};
+
+/* A class for representing the state of interest at a given path of
+   analysis.
+
+   Currently this is a combination of:
+   (a) a region_model, giving:
+      (a.1) a hierarchy of memory regions
+      (a.2) values for the regions
+      (a.3) inequalities between values
+   (b) sm_state_maps per state machine, giving a sparse mapping of
+       values to states.  */
+
+class program_state
+{
+public:
+  program_state (const extrinsic_state &ext_state);
+  program_state (const program_state &other);
+  program_state& operator= (const program_state &other);
+
+#if __cplusplus >= 201103
+  program_state (program_state &&other);
+  program_state& operator= (program_state &&other); // doesn't seem to be used
+#endif
+
+  ~program_state ();
+
+  hashval_t hash () const;
+  bool operator== (const program_state &other) const;
+  bool operator!= (const program_state &other) const
+  {
+    return !(*this == other);
+  }
+
+  void print (const extrinsic_state &ext_state,
+	      pretty_printer *pp) const;
+
+  void dump_to_pp (const extrinsic_state &ext_state, bool summarize,
+		   pretty_printer *pp) const;
+  void dump_to_file (const extrinsic_state &ext_state, bool summarize,
+		     FILE *outf) const;
+  void dump (const extrinsic_state &ext_state, bool summarize) const;
+
+  bool on_edge (exploded_graph &eg,
+		const exploded_node &enode,
+		const superedge *succ,
+		state_change *change);
+
+  program_state prune_for_point (exploded_graph &eg,
+				 const program_point &point,
+				 state_change *change) const;
+
+  void remap_svalue_ids (const svalue_id_map &map);
+
+  tree get_representative_tree (svalue_id sid) const;
+
+  bool can_purge_p (const extrinsic_state &ext_state,
+		    svalue_id sid)
+  {
+    /* Don't purge vars that have non-purgeable sm state, to avoid
+       generating false "leak" complaints.  */
+    int i;
+    sm_state_map *smap;
+    FOR_EACH_VEC_ELT (m_checker_states, i, smap)
+      {
+	const state_machine &sm = ext_state.get_sm (i);
+	if (!sm.can_purge_p (smap->get_state (sid)))
+	  return false;
+      }
+    return true;
+  }
+
+  bool can_merge_with_p (const program_state &other,
+			 const extrinsic_state &ext_state,
+			 program_state *out) const;
+
+  void validate (const extrinsic_state &ext_state) const;
+
+  /* TODO: lose the pointer here (const-correctness issues?).  */
+  region_model *m_region_model;
+  auto_delete_vec<sm_state_map> m_checker_states;
+};
+
+/* An abstract base class for use with for_each_state_change.  */
+
+class state_change_visitor
+{
+public:
+  virtual ~state_change_visitor () {}
+
+  /* Return true for early exit, false to keep iterating.  */
+  virtual bool on_state_change (const state_machine &sm,
+				state_machine::state_t src_sm_val,
+				state_machine::state_t dst_sm_val,
+				tree dst_rep,
+				svalue_id dst_origin_sid) = 0;
+};
+
+extern bool for_each_state_change (const program_state &src_state,
+				   const program_state &dst_state,
+				   const extrinsic_state &ext_state,
+				   state_change_visitor *visitor);
+
+/* A class for recording "interesting" state changes.
+   This is used for annotating edges in the GraphViz output of the
+   exploded_graph, and for recording sm-state-changes, so that
+   values that change aren't purged (to make it easier to generate
+   state_change_event instances in the diagnostic_path).  */
+
+class state_change
+{
+ public:
+  struct sm_change
+  {
+    sm_change (int sm_idx,
+	       svalue_id new_sid,
+	       state_machine::state_t old_state,
+	       state_machine::state_t new_state)
+    : m_sm_idx (sm_idx),
+      m_new_sid (new_sid),
+      m_old_state (old_state), m_new_state (new_state)
+    {}
+
+    const state_machine &get_sm (const extrinsic_state &ext_state) const
+    {
+      return ext_state.get_sm (m_sm_idx);
+    }
+
+    void dump (pretty_printer *pp, const extrinsic_state &ext_state) const;
+
+    void remap_svalue_ids (const svalue_id_map &map);
+    int on_svalue_purge (svalue_id first_unused_sid);
+
+    void validate (const program_state &new_state) const;
+
+    int m_sm_idx;
+    svalue_id m_new_sid;
+    state_machine::state_t m_old_state;
+    state_machine::state_t m_new_state;
+  };
+
+  state_change ();
+  state_change (const state_change &other);
+
+  void add_sm_change (int sm_idx,
+		      svalue_id new_sid,
+		      state_machine::state_t old_state,
+		      state_machine::state_t new_state);
+
+  bool affects_p (svalue_id sid) const;
+
+  void dump (pretty_printer *pp, const extrinsic_state &ext_state) const;
+  void dump (const extrinsic_state &ext_state) const;
+
+  void remap_svalue_ids (const svalue_id_map &map);
+  int on_svalue_purge (svalue_id first_unused_sid);
+
+  void validate (const program_state &new_state) const;
+
+ private:
+  auto_vec<sm_change> m_sm_changes;
+};
+
+#endif /* GCC_ANALYZER_PROGRAM_STATE_H */
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 40/49] analyzer: new files: call-string.{cc|h}
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (31 preceding siblings ...)
  2019-11-16  1:21 ` [PATCH 43/49] analyzer: new file: exploded-graph.h David Malcolm
@ 2019-11-16  1:21 ` David Malcolm
  2019-12-11 19:28   ` Jeff Law
  2019-11-16  1:21 ` [PATCH 44/49] analyzer: new files: state-purge.{cc|h} David Malcolm
                   ` (17 subsequent siblings)
  50 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:21 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This patch adds call_string, a class for representing the
call stacks at a program_point, so that we can ensure that
paths through the code are interprocedurally valid.

gcc/ChangeLog:
	* analyzer/call-string.cc: New file.
	* analyzer/call-string.h: New file.
---
 gcc/analyzer/call-string.cc | 201 ++++++++++++++++++++++++++++++++++++++++++++
 gcc/analyzer/call-string.h  |  74 ++++++++++++++++
 2 files changed, 275 insertions(+)
 create mode 100644 gcc/analyzer/call-string.cc
 create mode 100644 gcc/analyzer/call-string.h

diff --git a/gcc/analyzer/call-string.cc b/gcc/analyzer/call-string.cc
new file mode 100644
index 0000000..796b5e7
--- /dev/null
+++ b/gcc/analyzer/call-string.cc
@@ -0,0 +1,201 @@
+/* Call stacks at program points.
+   Copyright (C) 2019 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 "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "pretty-print.h"
+#include "tree.h"
+#include "analyzer/call-string.h"
+#include "analyzer/supergraph.h"
+
+/* class call_string.  */
+
+/* call_string's copy ctor.  */
+
+call_string::call_string (const call_string &other)
+: m_return_edges (other.m_return_edges.length ())
+{
+  const return_superedge *e;
+  int i;
+  FOR_EACH_VEC_ELT (other.m_return_edges, i, e)
+    m_return_edges.quick_push (e);
+}
+
+/* call_string's assignment operator.  */
+
+call_string&
+call_string::operator= (const call_string &other)
+{
+  // would be much simpler if we could rely on vec<> assignment op
+  m_return_edges.truncate (0);
+  m_return_edges.reserve (other.m_return_edges.length (), true);
+  const return_superedge *e;
+  int i;
+  FOR_EACH_VEC_ELT (other.m_return_edges, i, e)
+    m_return_edges.quick_push (e);
+  return *this;
+}
+
+/* call_string's equality operator.  */
+
+bool
+call_string::operator== (const call_string &other) const
+{
+  if (m_return_edges.length () != other.m_return_edges.length ())
+    return false;
+  const return_superedge *e;
+  int i;
+  FOR_EACH_VEC_ELT (m_return_edges, i, e)
+    if (e != other.m_return_edges[i])
+      return false;
+  return true;
+}
+
+/* Print this to PP.  */
+
+void
+call_string::print (pretty_printer *pp) const
+{
+  pp_string (pp, "[");
+
+  const return_superedge *e;
+  int i;
+  FOR_EACH_VEC_ELT (m_return_edges, i, e)
+    {
+      if (i > 0)
+	pp_string (pp, ", ");
+      pp_printf (pp, "(SN: %i -> SN: %i in %s)",
+		 e->m_src->m_index, e->m_dest->m_index,
+		 function_name (e->m_dest->m_fun));
+    }
+
+  pp_string (pp, "]");
+}
+
+/* Generate a hash value for this call_string.  */
+
+hashval_t
+call_string::hash () const
+{
+  inchash::hash hstate;
+  int i;
+  const return_superedge *e;
+  FOR_EACH_VEC_ELT (m_return_edges, i, e)
+    hstate.add_ptr (e);
+  return hstate.end ();
+}
+
+/* Push the return superedge for CALL_SEDGE onto the end of this
+   call_string.  */
+
+void
+call_string::push_call (const supergraph &sg,
+			const call_superedge *call_sedge)
+{
+  gcc_assert (call_sedge);
+  const return_superedge *return_sedge = call_sedge->get_edge_for_return (sg);
+  gcc_assert (return_sedge);
+  m_return_edges.safe_push (return_sedge);
+}
+
+/* Count the number of times the top-most call site appears in the
+   stack.  */
+
+int
+call_string::calc_recursion_depth () const
+{
+  if (m_return_edges.is_empty ())
+    return 0;
+  const return_superedge *top_return_sedge
+    = m_return_edges[m_return_edges.length () - 1];
+
+  int result = 0;
+  const return_superedge *e;
+  int i;
+  FOR_EACH_VEC_ELT (m_return_edges, i, e)
+    if (e == top_return_sedge)
+      ++result;
+  return result;
+}
+
+/* Comparator for call strings.
+   Return negative if A is before B.
+   Return positive if B is after A.
+   Return 0 if they are equal.  */
+
+int
+call_string::cmp (const call_string &a,
+		  const call_string &b)
+{
+  int result = cmp_1 (a, b);
+
+  /* Check that the ordering is symmetric  */
+#if CHECKING_P
+  int reversed = cmp_1 (b, a);
+  gcc_assert (reversed == -result);
+#endif
+
+  /* We should only have 0 for equal pairs.  */
+  gcc_assert (result != 0
+	      || a == b);
+
+  return result;
+}
+
+/* Implementation of call_string::cmp.
+   This implements a version of lexicographical order.  */
+
+int
+call_string::cmp_1 (const call_string &a,
+		    const call_string &b)
+{
+  unsigned len_a = a.length ();
+  unsigned len_b = b.length ();
+
+  unsigned i = 0;
+  while (1)
+    {
+      /* Consider index i; the strings have been equal up to it.  */
+
+      /* Have both strings run out?  */
+      if (i >= len_a && i >= len_b)
+	return 0;
+
+      /* Otherwise, has just one of the strings run out?  */
+      if (i >= len_a)
+	return 1;
+      if (i >= len_b)
+	return -1;
+
+      /* Otherwise, compare the edges.  */
+      const return_superedge *edge_a = a[i];
+      const return_superedge *edge_b = b[i];
+      int src_cmp = edge_a->m_src->m_index - edge_b->m_src->m_index;
+      if (src_cmp)
+	return src_cmp;
+      int dest_cmp = edge_a->m_dest->m_index - edge_b->m_dest->m_index;
+      if (dest_cmp)
+	return dest_cmp;
+      i++;
+      // TODO: test coverage for this
+    }
+}
diff --git a/gcc/analyzer/call-string.h b/gcc/analyzer/call-string.h
new file mode 100644
index 0000000..e5657e6
--- /dev/null
+++ b/gcc/analyzer/call-string.h
@@ -0,0 +1,74 @@
+/* Call stacks at program points.
+   Copyright (C) 2019 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/>.  */
+
+#ifndef GCC_ANALYZER_CALL_STRING_H
+#define GCC_ANALYZER_CALL_STRING_H
+
+class supergraph;
+class call_superedge;
+class return_superedge;
+
+/* A string of return_superedge pointers, representing a call stack
+   at a program point.
+
+   This is used to ensure that we generate interprocedurally valid paths
+   i.e. that we return to the same callsite that called us.
+
+   The class actually stores the return edges, rather than the call edges,
+   since that's what we need to compare against.  */
+
+class call_string
+{
+public:
+  call_string () : m_return_edges () {}
+  call_string (const call_string &other);
+  call_string& operator= (const call_string &other);
+
+  bool operator== (const call_string &other) const;
+
+  void print (pretty_printer *pp) const;
+
+  hashval_t hash () const;
+
+  bool empty_p () const { return m_return_edges.is_empty (); }
+
+  void push_call (const supergraph &sg,
+		  const call_superedge *sedge);
+  const return_superedge *pop () { return m_return_edges.pop (); }
+
+  int calc_recursion_depth () const;
+
+  static int cmp (const call_string &a,
+		  const call_string &b);
+
+  unsigned length () const { return m_return_edges.length (); }
+  const return_superedge *operator[] (unsigned idx) const
+  {
+    return m_return_edges[idx];
+  }
+
+private:
+  static int cmp_1 (const call_string &a,
+		    const call_string &b);
+
+  auto_vec<const return_superedge *> m_return_edges;
+};
+
+#endif /* GCC_ANALYZER_CALL_STRING_H */
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 31/49] analyzer: new files: region-model.{cc|h}
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (35 preceding siblings ...)
  2019-11-16  1:21 ` [PATCH 46/49] analyzer: new files: checker-path.{cc|h} David Malcolm
@ 2019-11-16  1:21 ` David Malcolm
  2019-11-16  1:21 ` [PATCH 41/49] analyzer: new files: program-point.{cc|h} David Malcolm
                   ` (13 subsequent siblings)
  50 siblings, 0 replies; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:21 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This patch implements classes for modeling the state of memory,
based on a paper describing how the clang analyzer does this:

  "A Memory Model for Static Analysis of C Programs"
    (Zhongxing Xu, Ted Kremenek, and Jian Zhang)
    http://lcs.ios.ac.cn/~xuzb/canalyze/memmodel.pdf

gcc/ChangeLog:
	* analyzer/region-model.cc: New file.
	* analyzer/region-model.h: New file.
---
 gcc/analyzer/region-model.cc | 7686 ++++++++++++++++++++++++++++++++++++++++++
 gcc/analyzer/region-model.h  | 2076 ++++++++++++
 2 files changed, 9762 insertions(+)
 create mode 100644 gcc/analyzer/region-model.cc
 create mode 100644 gcc/analyzer/region-model.h

diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
new file mode 100644
index 0000000..f28661f
--- /dev/null
+++ b/gcc/analyzer/region-model.cc
@@ -0,0 +1,7686 @@
+/* Classes for modeling the state of memory.
+   Copyright (C) 2019 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 "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "graphviz.h"
+#include "cgraph.h"
+#include "tree-dfa.h"
+#include "stringpool.h"
+#include "selftest.h"
+#include "diagnostic-color.h"
+#include "diagnostic-metadata.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/analyzer-logging.h"
+#include "analyzer/tristate.h"
+#include "analyzer/supergraph.h"
+#include "analyzer/region-model.h"
+#include "analyzer/constraint-manager.h"
+#include "analyzer/pending-diagnostic.h"
+#include "analyzer/analyzer-selftests.h"
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Dump this path_var to PP (which must support %E for trees).
+
+   Express the stack depth using an "@DEPTH" suffix, so e.g. given
+     void foo (int j);
+     void bar (int i)
+     {
+       foo (i);
+     }
+   then:
+   - the "i" in "bar" would be "(i @ 0)"
+   - the "j" in "foo" would be "(j @ 1)".  */
+
+void
+path_var::dump (pretty_printer *pp) const
+{
+PUSH_IGNORE_WFORMAT
+  if (m_tree == NULL_TREE)
+    pp_string (pp, "NULL");
+  if (CONSTANT_CLASS_P (m_tree))
+    pp_printf (pp, "%qE", m_tree);
+  else
+    pp_printf (pp, "(%qE @ %i)", m_tree, m_stack_depth);
+POP_IGNORE_WFORMAT
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* For use in printing a comma-separated list.  */
+
+static void
+dump_separator (pretty_printer *pp, bool *is_first)
+{
+  if (!*is_first)
+    pp_string (pp, ", ");
+  *is_first = false;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Concrete subclass of constraint_manager that wires it up to a region_model
+   (whilst allowing the constraint_manager and region_model to be somewhat
+   at arms length).
+   TODO: revisit this; maybe put the region_model * into the constraint_manager
+   base class.  */
+
+class impl_constraint_manager : public constraint_manager
+{
+ public:
+  impl_constraint_manager (region_model *model)
+  : constraint_manager (),
+    m_model (model)
+  {}
+
+  impl_constraint_manager (const impl_constraint_manager &other,
+			   region_model *model)
+  : constraint_manager (other),
+    m_model (model)
+  {}
+
+  constraint_manager *clone (region_model *model) const
+  {
+    return new impl_constraint_manager (*this, model);
+  }
+
+  tree maybe_get_constant (svalue_id sid) const FINAL OVERRIDE
+  {
+    svalue *svalue = m_model->get_svalue (sid);
+    return svalue->maybe_get_constant ();
+  }
+
+  svalue_id get_sid_for_constant (tree cst) const FINAL OVERRIDE
+  {
+    gcc_assert (CONSTANT_CLASS_P (cst));
+    return m_model->get_rvalue (cst, NULL);
+  }
+
+  int get_num_svalues () const FINAL OVERRIDE
+  {
+    return m_model->get_num_svalues ();
+  }
+
+ private:
+  region_model *m_model;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class svalue_id.  */
+
+/* Print this svalue_id to PP.  */
+
+void
+svalue_id::print (pretty_printer *pp) const
+{
+  if (null_p ())
+    pp_printf (pp, "null");
+  else
+    pp_printf (pp, "sv%i", m_idx);
+}
+
+/* Print this svalue_id in .dot format to PP.  */
+
+void
+svalue_id::dump_node_name_to_pp (pretty_printer *pp) const
+{
+  gcc_assert (!null_p ());
+  pp_printf (pp, "svalue_%i", m_idx);
+}
+
+/* Assert that this object is valid (w.r.t. MODEL).  */
+
+void
+svalue_id::validate (const region_model &model) const
+{
+  gcc_assert (null_p () || m_idx < (int)model.get_num_svalues ());
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class region_id.  */
+
+/* Print this region_id to PP.  */
+
+void
+region_id::print (pretty_printer *pp) const
+{
+  if (null_p ())
+    pp_printf (pp, "null");
+  else
+    pp_printf (pp, "r%i", m_idx);
+}
+
+/* Print this region_id in .dot format to PP.  */
+
+void
+region_id::dump_node_name_to_pp (pretty_printer *pp) const
+{
+  gcc_assert (!null_p ());
+  pp_printf (pp, "region_%i", m_idx);
+}
+
+/* Assert that this object is valid (w.r.t. MODEL).  */
+
+void
+region_id::validate (const region_model &model) const
+{
+  gcc_assert (null_p () || m_idx < (int)model.get_num_regions ());
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class id_set.  */
+
+/* id_set<region_id>'s ctor.  */
+
+template<>
+id_set<region_id>::id_set (const region_model *model)
+: m_bitmap (model->get_num_regions ())
+{
+  bitmap_clear (m_bitmap);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class svalue and its various subclasses.  */
+
+/* class svalue.  */
+
+/* svalue's equality operator.  Most of the work is done by the
+   a "compare_fields" implementation on each subclass.  */
+
+bool
+svalue::operator== (const svalue &other) const
+{
+  enum svalue_kind this_kind = get_kind ();
+  enum svalue_kind other_kind = other.get_kind ();
+  if (this_kind != other_kind)
+    return false;
+
+  if (m_type != other.m_type)
+    return false;
+
+  switch (this_kind)
+    {
+    default:
+      gcc_unreachable ();
+    case SK_REGION:
+      {
+	const region_svalue &this_sub
+	  = (const region_svalue &)*this;
+	const region_svalue &other_sub
+	  = (const region_svalue &)other;
+	return this_sub.compare_fields (other_sub);
+      }
+      break;
+    case SK_CONSTANT:
+      {
+	const constant_svalue &this_sub
+	  = (const constant_svalue &)*this;
+	const constant_svalue &other_sub
+	  = (const constant_svalue &)other;
+	return this_sub.compare_fields (other_sub);
+      }
+      break;
+    case SK_UNKNOWN:
+      {
+	const unknown_svalue &this_sub
+	  = (const unknown_svalue &)*this;
+	const unknown_svalue &other_sub
+	  = (const unknown_svalue &)other;
+	return this_sub.compare_fields (other_sub);
+      }
+      break;
+    case SK_POISONED:
+      {
+	const poisoned_svalue &this_sub
+	  = (const poisoned_svalue &)*this;
+	const poisoned_svalue &other_sub
+	  = (const poisoned_svalue &)other;
+	return this_sub.compare_fields (other_sub);
+      }
+      break;
+    case SK_SETJMP:
+      {
+	const setjmp_svalue &this_sub
+	  = (const setjmp_svalue &)*this;
+	const setjmp_svalue &other_sub
+	  = (const setjmp_svalue &)other;
+	return this_sub.compare_fields (other_sub);
+      }
+      break;
+    }
+}
+
+/* Generate a hash value for this svalue.  Most of the work is done by the
+   add_to_hash vfunc.  */
+
+hashval_t
+svalue::hash () const
+{
+  inchash::hash hstate;
+  if (m_type)
+    hstate.add_int (TYPE_UID (m_type));
+  add_to_hash (hstate);
+  return hstate.end ();
+}
+
+/* Print this svalue and its ID to PP.  */
+
+void
+svalue::print (const region_model &model,
+	       svalue_id this_sid,
+	       pretty_printer *pp) const
+{
+  this_sid.print (pp);
+  pp_string (pp, ": {");
+
+PUSH_IGNORE_WFORMAT
+  if (m_type)
+    {
+      gcc_assert (TYPE_P (m_type));
+      pp_printf (pp, "type: %qT, ", m_type);
+    }
+POP_IGNORE_WFORMAT
+
+  /* vfunc.  */
+  print_details (model, this_sid, pp);
+
+  pp_string (pp, "}");
+}
+
+/* Dump this svalue in the form of a .dot record to PP.  */
+
+void
+svalue::dump_dot_to_pp (const region_model &model,
+			svalue_id this_sid,
+			pretty_printer *pp) const
+{
+  this_sid.dump_node_name_to_pp (pp);
+  pp_printf (pp, " [label=\"");
+  pp_write_text_to_stream (pp);
+  this_sid.print (pp);
+  pp_string (pp, ": {");
+  print (model, this_sid, pp);
+  pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/false);
+  pp_string (pp, "}\"];");
+  pp_newline (pp);
+}
+
+/* Base implementation of svalue::remap_region_ids vfunc.  */
+
+void
+svalue::remap_region_ids (const region_id_map &)
+{
+  /* Empty.  */
+}
+
+/* Base implementation of svalue::walk_for_canonicalization vfunc.  */
+
+void
+svalue::walk_for_canonicalization (canonicalization *) const
+{
+  /* Empty.  */
+}
+
+/* Base implementation of svalue::get_child_sid vfunc.  */
+
+svalue_id
+svalue::get_child_sid (region *parent ATTRIBUTE_UNUSED,
+		       region *child,
+		       region_model &model,
+		       region_model_context *ctxt ATTRIBUTE_UNUSED)
+{
+  svalue *new_child_value = clone ();
+  if (child->get_type ())
+    new_child_value->m_type = child->get_type ();
+  svalue_id new_child_sid = model.add_svalue (new_child_value);
+  return new_child_sid;
+}
+
+/* If this svalue is a constant_svalue, return the underlying tree constant.
+   Otherwise return NULL_TREE.  */
+
+tree
+svalue::maybe_get_constant () const
+{
+  if (const constant_svalue *cst_sval = dyn_cast_constant_svalue ())
+    return cst_sval->get_constant ();
+  else
+    return NULL_TREE;
+}
+
+/* class region_svalue : public svalue.  */
+
+/* Compare the fields of this region_svalue with OTHER, returning true
+   if they are equal.
+   For use by svalue::operator==.  */
+
+bool
+region_svalue::compare_fields (const region_svalue &other) const
+{
+  return m_rid == other.m_rid;
+}
+
+/* Implementation of svalue::add_to_hash vfunc for region_svalue.  */
+
+void
+region_svalue::add_to_hash (inchash::hash &hstate) const
+{
+  inchash::add (m_rid, hstate);
+}
+
+/* Implementation of svalue::print_details vfunc for region_svalue.  */
+
+void
+region_svalue::print_details (const region_model &model ATTRIBUTE_UNUSED,
+			      svalue_id this_sid ATTRIBUTE_UNUSED,
+			      pretty_printer *pp) const
+{
+  if (m_rid.null_p ())
+    pp_string (pp, "NULL");
+  else
+    {
+      pp_string (pp, "&");
+      m_rid.print (pp);
+    }
+}
+
+/* Implementation of svalue::dump_dot_to_pp for region_svalue.  */
+
+void
+region_svalue::dump_dot_to_pp (const region_model &model,
+			       svalue_id this_sid,
+			       pretty_printer *pp) const
+{
+  svalue::dump_dot_to_pp (model, this_sid, pp);
+
+  /* If non-NULL, add an edge to the pointed-to region.  */
+  if (!m_rid.null_p ())
+    {
+      this_sid.dump_node_name_to_pp (pp);
+      pp_string (pp, " -> ");
+      m_rid.dump_node_name_to_pp (pp);
+      pp_string (pp, ";");
+      pp_newline (pp);
+  }
+}
+
+/* Implementation of svalue::remap_region_ids vfunc for region_svalue.  */
+
+void
+region_svalue::remap_region_ids (const region_id_map &map)
+{
+  map.update (&m_rid);
+}
+
+/* Merge REGION_SVAL_A and REGION_SVAL_B using MERGER, writing the result
+   into *MERGED_SID.  */
+
+void
+region_svalue::merge_values (const region_svalue &region_sval_a,
+			     const region_svalue &region_sval_b,
+			     svalue_id *merged_sid,
+			     tree type,
+			     model_merger *merger)
+{
+  region_id a_rid = region_sval_a.get_pointee ();
+  region_id b_rid = region_sval_b.get_pointee ();
+
+  /* Both are non-NULL. */
+  gcc_assert (!a_rid.null_p () && !b_rid.null_p ());
+
+  /* Have these ptr-values already been merged?  */
+
+  region_id a_rid_in_m
+    = merger->m_map_regions_from_a_to_m.get_dst_for_src (a_rid);
+  region_id b_rid_in_m
+    = merger->m_map_regions_from_b_to_m.get_dst_for_src (b_rid);
+
+  /* "null_p" here means "we haven't seen this ptr-value before".
+     If we've seen one but not the other, or we have different
+     regions, then the merged ptr has to be "unknown".  */
+  if (a_rid_in_m != b_rid_in_m)
+    {
+      svalue *merged_sval = new unknown_svalue (type);
+      *merged_sid = merger->m_merged_model->add_svalue (merged_sval);
+      return;
+    }
+
+  /* Have we seen this yet?  If so, reuse the value.  */
+  if (!a_rid_in_m.null_p ())
+    {
+      *merged_sid
+	= merger->m_merged_model->get_or_create_ptr_svalue (type, a_rid_in_m);
+      return;
+    }
+
+  /* Otherwise we have A/B regions that haven't been referenced yet.  */
+
+  /* Are the regions the "same", when seen from the tree point-of-view.
+     If so, create a merged pointer to it.  */
+  path_var pv_a = merger->m_model_a->get_representative_path_var (a_rid);
+  path_var pv_b = merger->m_model_b->get_representative_path_var (b_rid);
+  if (pv_a.m_tree
+      && pv_a == pv_b)
+    {
+      region_id merged_pointee_rid
+	= merger->m_merged_model->get_lvalue (pv_a, NULL);
+      *merged_sid
+	= merger->m_merged_model->get_or_create_ptr_svalue (type,
+							    merged_pointee_rid);
+      merger->record_regions (a_rid, b_rid, merged_pointee_rid);
+      return;
+    }
+
+  /* Handle an A/B pair of ptrs that both point at heap regions.
+     If they both have a heap region in the merger model, merge them.  */
+  region *region_a = merger->m_model_a->get_region (a_rid);
+  region *region_b = merger->m_model_b->get_region (b_rid);
+  region_id a_parent_rid = region_a->get_parent ();
+  region_id b_parent_rid = region_b->get_parent ();
+  region *parent_region_a = merger->m_model_a->get_region (a_parent_rid);
+  region *parent_region_b = merger->m_model_b->get_region (b_parent_rid);
+  if (parent_region_a
+      && parent_region_b
+      && parent_region_a->get_kind () == RK_HEAP
+      && parent_region_b->get_kind () == RK_HEAP)
+    {
+	/* We have an A/B pair of ptrs that both point at heap regions.  */
+	/* presumably we want to see if each A/B heap region already
+	   has a merged region, and, if so, is it the same one.  */
+	// This check is above
+
+	region_id merged_pointee_rid
+	  = merger->m_merged_model->add_new_malloc_region ();
+	*merged_sid
+	  = merger->m_merged_model->get_or_create_ptr_svalue
+	     (type, merged_pointee_rid);
+	merger->record_regions (a_rid, b_rid, merged_pointee_rid);
+	return;
+    }
+
+  /* Two different non-NULL pointers?  Merge to unknown.  */
+  svalue *merged_sval = new unknown_svalue (type);
+  *merged_sid = merger->m_merged_model->add_svalue (merged_sval);
+  return;
+}
+
+/* Implementation of svalue::walk_for_canonicalization vfunc for
+   region_svalue.  */
+
+void
+region_svalue::walk_for_canonicalization (canonicalization *c) const
+{
+  c->walk_rid (m_rid);
+}
+
+/* Evaluate the condition LHS OP RHS.
+   Subroutine of region_model::eval_condition for when we have a pair of
+   pointers.  */
+
+tristate
+region_svalue::eval_condition (region_svalue *lhs,
+			       enum tree_code op,
+			       region_svalue *rhs)
+{
+  /* See if they point to the same region.  */
+  /* TODO: what about child regions where the child is the first child
+     (or descendent)?  */
+  region_id lhs_rid = lhs->get_pointee ();
+  region_id rhs_rid = rhs->get_pointee ();
+  switch (op)
+    {
+    default:
+      gcc_unreachable ();
+
+    case EQ_EXPR:
+      if (lhs_rid == rhs_rid)
+	return tristate::TS_TRUE;
+      else
+	return tristate::TS_FALSE;
+      break;
+
+    case NE_EXPR:
+      if (lhs_rid != rhs_rid)
+	return tristate::TS_TRUE;
+      else
+	return tristate::TS_FALSE;
+      break;
+
+    case GE_EXPR:
+    case LE_EXPR:
+      if (lhs_rid == rhs_rid)
+	return tristate::TS_TRUE;
+      break;
+
+    case GT_EXPR:
+    case LT_EXPR:
+      if (lhs_rid == rhs_rid)
+	return tristate::TS_FALSE;
+      break;
+    }
+
+  return tristate::TS_UNKNOWN;
+}
+
+/* class constant_svalue : public svalue.  */
+
+/* Compare the fields of this constant_svalue with OTHER, returning true
+   if they are equal.
+   For use by svalue::operator==.  */
+
+bool
+constant_svalue::compare_fields (const constant_svalue &other) const
+{
+  return m_cst_expr == other.m_cst_expr;
+}
+
+/* Implementation of svalue::add_to_hash vfunc for constant_svalue.  */
+
+void
+constant_svalue::add_to_hash (inchash::hash &hstate) const
+{
+  inchash::add_expr (m_cst_expr, hstate);
+}
+
+/* Merge the CST_SVAL_A and CST_SVAL_B using MERGER, writing the id of
+   the resulting svalue into *MERGED_SID.  */
+
+void
+constant_svalue::merge_values (const constant_svalue &cst_sval_a,
+			       const constant_svalue &cst_sval_b,
+			       svalue_id *merged_sid,
+			       model_merger *merger)
+{
+  tree cst_a = cst_sval_a.get_constant ();
+  tree cst_b = cst_sval_b.get_constant ();
+  svalue *merged_sval;
+  if (cst_a == cst_b)
+    {
+      /* If they are the same constant, merge as that constant value.  */
+      merged_sval = new constant_svalue (cst_a);
+    }
+  else
+    {
+      /* Otherwise, we have two different constant values.
+	 Merge as an unknown value.
+	 TODO: impose constraints on the value?
+	 (maybe just based on A, to avoid infinite chains)  */
+      merged_sval = new unknown_svalue (TREE_TYPE (cst_a));
+    }
+  *merged_sid = merger->m_merged_model->add_svalue (merged_sval);
+}
+
+/* Evaluate the condition LHS OP RHS.
+   Subroutine of region_model::eval_condition for when we have a pair of
+   constants.  */
+
+tristate
+constant_svalue::eval_condition (constant_svalue *lhs,
+				 enum tree_code op,
+				 constant_svalue *rhs)
+{
+  tree lhs_const = lhs->get_constant ();
+  tree rhs_const = rhs->get_constant ();
+
+  gcc_assert (CONSTANT_CLASS_P (lhs_const));
+  gcc_assert (CONSTANT_CLASS_P (rhs_const));
+
+  tree comparison
+    = fold_build2 (op, boolean_type_node, lhs_const, rhs_const);
+  if (comparison == boolean_true_node)
+    return tristate (tristate::TS_TRUE);
+  if (comparison == boolean_false_node)
+    return tristate (tristate::TS_FALSE);
+  return tristate::TS_UNKNOWN;
+}
+
+/* Implementation of svalue::print_details vfunc for constant_svalue.  */
+
+void
+constant_svalue::print_details (const region_model &model ATTRIBUTE_UNUSED,
+				svalue_id this_sid ATTRIBUTE_UNUSED,
+				pretty_printer *pp) const
+{
+PUSH_IGNORE_WFORMAT
+  pp_printf (pp, "%qE", m_cst_expr);
+POP_IGNORE_WFORMAT
+}
+
+/* Implementation of svalue::get_child_sid vfunc for constant_svalue.  */
+
+svalue_id
+constant_svalue::get_child_sid (region *parent ATTRIBUTE_UNUSED,
+				region *child,
+				region_model &model,
+				region_model_context *ctxt ATTRIBUTE_UNUSED)
+{
+  /* TODO: handle the all-zeroes case by returning an all-zeroes of the
+     child type.  */
+
+  /* Otherwise, we don't have a good way to get a child value out of a
+     constant.
+
+     Handle this case by using an unknown value.  */
+  svalue *unknown_sval = new unknown_svalue (child->get_type ());
+  return model.add_svalue (unknown_sval);
+}
+
+/* class unknown_svalue : public svalue.  */
+
+/* Compare the fields of this unknown_svalue with OTHER, returning true
+   if they are equal.
+   For use by svalue::operator==.  */
+
+bool
+unknown_svalue::compare_fields (const unknown_svalue &) const
+{
+  /* I *think* we want to return true here, in that when comparing
+     two region models, we want two peer unknown_svalue instances
+     to be the "same".  */
+  return true;
+}
+
+/* Implementation of svalue::add_to_hash vfunc for unknown_svalue.  */
+
+void
+unknown_svalue::add_to_hash (inchash::hash &) const
+{
+  /* Empty.  */
+}
+
+/* Implementation of svalue::print_details vfunc for unknown_svalue.  */
+
+void
+unknown_svalue::print_details (const region_model &model ATTRIBUTE_UNUSED,
+			       svalue_id this_sid ATTRIBUTE_UNUSED,
+			       pretty_printer *pp) const
+{
+  pp_string (pp, "unknown");
+}
+
+/* Get a string for KIND for use in debug dumps.  */
+
+const char *
+poison_kind_to_str (enum poison_kind kind)
+{
+  switch (kind)
+    {
+    default:
+      gcc_unreachable ();
+    case POISON_KIND_UNINIT:
+      return "uninit";
+    case POISON_KIND_FREED:
+      return "freed";
+    case POISON_KIND_POPPED_STACK:
+      return "popped stack";
+    }
+}
+
+/* class poisoned_svalue : public svalue.  */
+
+/* Compare the fields of this poisoned_svalue with OTHER, returning true
+   if they are equal.
+   For use by svalue::operator==.  */
+
+bool
+poisoned_svalue::compare_fields (const poisoned_svalue &other) const
+{
+  return m_kind == other.m_kind;
+}
+
+/* Implementation of svalue::add_to_hash vfunc for poisoned_svalue.  */
+
+void
+poisoned_svalue::add_to_hash (inchash::hash &hstate) const
+{
+  hstate.add_int (m_kind);
+}
+
+/* Implementation of svalue::print_details vfunc for poisoned_svalue.  */
+
+void
+poisoned_svalue::print_details (const region_model &model ATTRIBUTE_UNUSED,
+				svalue_id this_sid ATTRIBUTE_UNUSED,
+				pretty_printer *pp) const
+{
+  pp_printf (pp, "poisoned: %s", poison_kind_to_str (m_kind));
+}
+
+/* class setjmp_svalue's implementation is in engine.cc, so that it can use
+   the declaration of exploded_node.  */
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class region and its various subclasses.  */
+
+/* Get a string for KIND for use in debug dumps.  */
+
+const char *
+region_kind_to_str (enum region_kind kind)
+{
+  switch (kind)
+    {
+    default:
+      gcc_unreachable ();
+    case RK_PRIMITIVE:
+      return "primitive";
+    case RK_STRUCT:
+      return "struct";
+    case RK_UNION:
+      return "union";
+    case RK_ARRAY:
+      return "array";
+    case RK_FRAME:
+      return "frame";
+    case RK_GLOBALS:
+      return "globals";
+    case RK_CODE:
+      return "code";
+    case RK_FUNCTION:
+      return "function";
+    case RK_STACK:
+      return "stack";
+    case RK_HEAP:
+      return "heap";
+    case RK_ROOT:
+      return "root";
+    case RK_SYMBOLIC:
+      return "symbolic";
+    }
+}
+
+/* class region.  */
+
+/* Equality operator for region.
+   After comparing base class fields and kind, the rest of the
+   comparison is handled off to a "compare_fields" member function
+   specific to the appropriate subclass.  */
+
+bool
+region::operator== (const region &other) const
+{
+  if (m_parent_rid != other.m_parent_rid)
+    return false;
+  if (m_sval_id != other.m_sval_id)
+    return false;
+  if (m_type != other.m_type)
+    return false;
+
+  enum region_kind this_kind = get_kind ();
+  enum region_kind other_kind = other.get_kind ();
+  if (this_kind != other_kind)
+    return false;
+
+  /* Compare views.  */
+  if (m_view_rids.length () != other.m_view_rids.length ())
+    return false;
+  int i;
+  region_id *rid;
+  FOR_EACH_VEC_ELT (m_view_rids, i, rid)
+    if (! (*rid == other.m_view_rids[i]))
+      return false;
+
+  switch (this_kind)
+    {
+    default:
+      gcc_unreachable ();
+    case RK_PRIMITIVE:
+      {
+#if 1
+	return true;
+#else
+	const primitive_region &this_sub
+	  = (const primitive_region &)*this;
+	const primitive_region &other_sub
+	  = (const primitive_region &)other;
+	return this_sub.compare_fields (other_sub);
+#endif
+      }
+    case RK_STRUCT:
+      {
+	const struct_region &this_sub
+	  = (const struct_region &)*this;
+	const struct_region &other_sub
+	  = (const struct_region &)other;
+	return this_sub.compare_fields (other_sub);
+      }
+    case RK_UNION:
+      {
+	const union_region &this_sub
+	  = (const union_region &)*this;
+	const union_region &other_sub
+	  = (const union_region &)other;
+	return this_sub.compare_fields (other_sub);
+      }
+    case RK_ARRAY:
+      {
+	const array_region &this_sub
+	  = (const array_region &)*this;
+	const array_region &other_sub
+	  = (const array_region &)other;
+	return this_sub.compare_fields (other_sub);
+      }
+    case RK_FRAME:
+      {
+	const frame_region &this_sub
+	  = (const frame_region &)*this;
+	const frame_region &other_sub
+	  = (const frame_region &)other;
+	return this_sub.compare_fields (other_sub);
+      }
+    case RK_GLOBALS:
+      {
+	const globals_region &this_sub
+	  = (const globals_region &)*this;
+	const globals_region &other_sub
+	  = (const globals_region &)other;
+	return this_sub.compare_fields (other_sub);
+      }
+    case RK_CODE:
+      {
+	const code_region &this_sub
+	  = (const code_region &)*this;
+	const code_region &other_sub
+	  = (const code_region &)other;
+	return this_sub.compare_fields (other_sub);
+      }
+    case RK_FUNCTION:
+      {
+	const function_region &this_sub
+	  = (const function_region &)*this;
+	const function_region &other_sub
+	  = (const function_region &)other;
+	return this_sub.compare_fields (other_sub);
+      }
+    case RK_STACK:
+      {
+	const stack_region &this_sub
+	  = (const stack_region &)*this;
+	const stack_region &other_sub
+	  = (const stack_region &)other;
+	return this_sub.compare_fields (other_sub);
+      }
+    case RK_ROOT:
+      {
+	const root_region &this_sub
+	  = (const root_region &)*this;
+	const root_region &other_sub
+	  = (const root_region &)other;
+	return this_sub.compare_fields (other_sub);
+      }
+    case RK_SYMBOLIC:
+      {
+	const symbolic_region &this_sub
+	  = (const symbolic_region &)*this;
+	const symbolic_region &other_sub
+	  = (const symbolic_region &)other;
+	return this_sub.compare_fields (other_sub);
+      }
+    case RK_HEAP:
+      {
+	const heap_region &this_sub
+	  = (const heap_region &)*this;
+	const heap_region &other_sub
+	  = (const heap_region &)other;
+	return this_sub.compare_fields (other_sub);
+      }
+    }
+}
+
+/* Get the parent region of this region.  */
+
+region *
+region::get_parent_region (const region_model &model) const
+{
+  return model.get_region (m_parent_rid);
+}
+
+/* Set this region's value to RHS_SID (or potentially a variant of it,
+   for some kinds of casts).  */
+
+void
+region::set_value (region_model &model, region_id this_rid, svalue_id rhs_sid,
+		   region_model_context *ctxt)
+{
+  /* Handle some kinds of casting.  */
+  if (m_type)
+    {
+      svalue *sval = model.get_svalue (rhs_sid);
+      if (sval->get_type ())
+	rhs_sid = model.maybe_cast (m_type, rhs_sid, ctxt);
+
+      sval = model.get_svalue (rhs_sid);
+      if (sval->get_type ())
+	gcc_assert (m_type == sval->get_type ());
+    }
+
+  m_sval_id = rhs_sid;
+
+  /* Update views.
+     If this is a view, it becomes its parent's active view.
+     If there was already an active views, invalidate its value; otherwise
+     if the parent itself had a value, invalidate it.
+     If it's not a view, then deactivate any view that is active on this
+     region.  */
+  {
+    if (m_is_view)
+      become_active_view (model, this_rid);
+    else
+      {
+	deactivate_any_active_view (model);
+	gcc_assert (m_active_view_rid.null_p ());
+      }
+  }
+}
+
+/* Make this region (with id THIS_RID) the "active" view of its parent.
+   Any other active view has its value set to "unknown" and descendent values
+   cleared.
+   If there wasn't an active view, then set the parent's value to unknown, and
+   clear its descendent values (apart from this view).  */
+
+void
+region::become_active_view (region_model &model, region_id this_rid)
+{
+  gcc_assert (m_is_view);
+
+  region *parent_reg = model.get_region (m_parent_rid);
+  gcc_assert (parent_reg);
+
+  region_id old_active_view_rid = parent_reg->m_active_view_rid;
+
+  if (old_active_view_rid == this_rid)
+    {
+      /* Already the active view: do nothing.  */
+      return;
+    }
+
+  /* We have a change of active view.  */
+  parent_reg->m_active_view_rid = this_rid;
+
+  if (old_active_view_rid.null_p ())
+    {
+      /* No previous active view, but the parent and its other children
+	 might have values.
+	 If so, invalidate those values - but not that of the new view.  */
+      region_id_set below_region (&model);
+      model.get_descendents (m_parent_rid, &below_region, this_rid);
+      for (unsigned i = 0; i < model.get_num_regions (); i++)
+	{
+	  region_id rid (region_id::from_int (i));
+	  if (below_region.region_p (rid))
+	    {
+	      region *other_reg = model.get_region (rid);
+	      other_reg->m_sval_id = svalue_id::null ();
+	    }
+	}
+      region *parent = model.get_region (m_parent_rid);
+      parent->m_sval_id
+	= model.add_svalue (new unknown_svalue (parent->get_type ()));
+    }
+  else
+    {
+      /* If there was an active view, invalidate it.  */
+      region *old_active_view = model.get_region (old_active_view_rid);
+      old_active_view->deactivate_view (model, old_active_view_rid);
+    }
+}
+
+/* If this region (with id THIS_RID) has an active view, deactivate it,
+   clearing m_active_view_rid.  */
+
+void
+region::deactivate_any_active_view (region_model &model)
+{
+  if (m_active_view_rid.null_p ())
+    return;
+  region *view = model.get_region (m_active_view_rid);
+  view->deactivate_view (model, m_active_view_rid);
+  m_active_view_rid = region_id::null ();
+}
+
+/* Clear any values for regions below THIS_RID.
+   Set the view's value to unknown.  */
+
+void
+region::deactivate_view (region_model &model, region_id this_view_rid)
+{
+  gcc_assert (is_view_p ());
+
+  /* Purge values from old_active_this_view_rid and all its
+     descendents.  Potentially we could use a poison value
+     for this, but let's use unknown for now.  */
+  region_id_set below_view (&model);
+  model.get_descendents (this_view_rid, &below_view, region_id::null ());
+
+  for (unsigned i = 0; i < model.get_num_regions (); i++)
+    {
+      region_id rid (region_id::from_int (i));
+      if (below_view.region_p (rid))
+	{
+	  region *other_reg = model.get_region (rid);
+	  other_reg->m_sval_id = svalue_id::null ();
+	}
+    }
+
+  m_sval_id = model.add_svalue (new unknown_svalue (get_type ()));
+}
+
+/* Get a value for this region, either its value if it has one,
+   or, failing that, "inherit" a value from first ancestor with a
+   non-null value.
+
+   For example, when getting the value for a local variable within
+   a stack frame that doesn't have one, the frame doesn't have a value
+   either, but the stack as a whole will have an "uninitialized" poison
+   value, so inherit that.  */
+
+svalue_id
+region::get_value (region_model &model, bool non_null,
+		   region_model_context *ctxt)
+{
+  /* If this region has a value, use it. */
+  if (!m_sval_id.null_p ())
+    return m_sval_id;
+
+  /* Otherwise, "inherit" value from first ancestor with a
+     non-null value. */
+
+  region *parent = model.get_region (m_parent_rid);
+  if (parent)
+    {
+      svalue_id inherited_sid
+	= parent->get_inherited_child_sid (this, model, ctxt);
+      if (!inherited_sid.null_p ())
+	return inherited_sid;
+    }
+
+  /* If a non-null value has been requested, then generate
+     a new unknown value.  Store it, so that repeated reads from this
+     region will yield the same unknown value.  */
+  if (non_null)
+    {
+      svalue_id unknown_sid = model.add_svalue (new unknown_svalue (m_type));
+      m_sval_id = unknown_sid;
+      return unknown_sid;
+    }
+
+  return svalue_id::null ();
+}
+
+/* Get a value for CHILD, inheriting from this region.
+
+   Recurse, so this region will inherit a value if it doesn't already
+   have one.  */
+
+svalue_id
+region::get_inherited_child_sid (region *child,
+				 region_model &model,
+				 region_model_context *ctxt)
+{
+  if (m_sval_id.null_p ())
+    {
+      /* Recurse.  */
+      if (!m_parent_rid.null_p ())
+	{
+	  region *parent = model.get_region (m_parent_rid);
+	  m_sval_id = parent->get_inherited_child_sid (this, model, ctxt);
+	}
+    }
+
+  if (!m_sval_id.null_p ())
+    {
+      /* Clone the parent's value, so that attempts to update it
+	 (e.g giving a specific value to an inherited "uninitialized"
+	 value) touch the child, and not the parent.  */
+      svalue *this_value = model.get_svalue (m_sval_id);
+      svalue_id new_child_sid
+	= this_value->get_child_sid (this, child, model, ctxt);
+      if (ctxt)
+	ctxt->on_inherited_svalue (m_sval_id, new_child_sid);
+      child->m_sval_id = new_child_sid;
+      return new_child_sid;
+    }
+
+  return svalue_id::null ();
+}
+
+/* Generate a hash value for this region.  The work is done by the
+   add_to_hash vfunc.  */
+
+hashval_t
+region::hash () const
+{
+  inchash::hash hstate;
+  add_to_hash (hstate);
+  return hstate.end ();
+}
+
+/* Print a one-liner representation of this region to PP, assuming
+   that this region is within MODEL and its id is THIS_RID.  */
+
+void
+region::print (const region_model &model,
+	       region_id this_rid,
+	       pretty_printer *pp) const
+{
+  this_rid.print (pp);
+  pp_string (pp, ": {");
+
+  /* vfunc.  */
+  print_fields (model, this_rid, pp);
+
+  pp_string (pp, "}");
+}
+
+/* Base class implementation of region::dump_dot_to_pp vfunc.  */
+
+void
+region::dump_dot_to_pp (const region_model &model,
+			region_id this_rid,
+			pretty_printer *pp) const
+{
+  this_rid.dump_node_name_to_pp (pp);
+  pp_printf (pp, " [shape=%s,style=filled,fillcolor=%s,label=\"",
+	     "record", "lightgrey");
+  pp_write_text_to_stream (pp);
+  print (model, this_rid, pp);
+  pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
+  pp_string (pp, "\"];");
+  pp_newline (pp);
+
+  /* Add edge to svalue.  */
+  if (!m_sval_id.null_p ())
+    {
+      this_rid.dump_node_name_to_pp (pp);
+      pp_string (pp, " -> ");
+      m_sval_id.dump_node_name_to_pp (pp);
+      pp_string (pp, ";");
+      pp_newline (pp);
+    }
+
+  /* Add edge to parent.  */
+  if (!m_parent_rid.null_p ())
+    {
+      this_rid.dump_node_name_to_pp (pp);
+      pp_string (pp, " -> ");
+      m_parent_rid.dump_node_name_to_pp (pp);
+      pp_string (pp, ";");
+      pp_newline (pp);
+    }
+}
+
+/* Dump a tree-like ASCII-art representation of this region to PP.  */
+
+void
+region::dump_to_pp (const region_model &model,
+		    region_id this_rid,
+		    pretty_printer *pp,
+		    const char *prefix,
+		    bool is_last_child) const
+{
+  print (model, this_rid, pp);
+  pp_newline (pp);
+
+  const char *new_prefix;
+  if (!m_parent_rid.null_p ())
+    new_prefix = ACONCAT ((prefix, is_last_child ? "  " : "|  ", NULL));
+  else
+    new_prefix = prefix;
+
+  const char *begin_color = colorize_start (pp_show_color (pp), "note");
+  const char *end_color = colorize_stop (pp_show_color (pp));
+  char *field_prefix
+    = ACONCAT ((begin_color, new_prefix, "|:", end_color, NULL));
+
+  if (!m_sval_id.null_p ())
+    {
+      pp_printf (pp, "%s sval: ", field_prefix);
+      model.get_svalue (m_sval_id)->print (model, m_sval_id, pp);
+      pp_newline (pp);
+    }
+  if (m_type)
+    {
+PUSH_IGNORE_WFORMAT
+      pp_printf (pp, "%s type: %qT", field_prefix, m_type);
+POP_IGNORE_WFORMAT
+      pp_newline (pp);
+    }
+
+  /* Find the children.  */
+
+  auto_vec<region_id> child_rids;
+  unsigned i;
+  for (unsigned i = 0; i < model.get_num_regions (); ++i)
+    {
+      region_id rid = region_id::from_int (i);
+      region *child = model.get_region (rid);
+      if (child->m_parent_rid == this_rid)
+	child_rids.safe_push (rid);
+    }
+
+  /* Print the children, using dump_child_label to label them.  */
+
+  region_id *child_rid;
+  FOR_EACH_VEC_ELT (child_rids, i, child_rid)
+    {
+      is_last_child = (i == child_rids.length () - 1);
+      if (!this_rid.null_p ())
+	{
+	  const char *tail = is_last_child ? "`-" : "|-";
+	  pp_printf (pp, "%r%s%s%R", "note", new_prefix, tail);
+	}
+      dump_child_label (model, this_rid, *child_rid, pp);
+      model.get_region (*child_rid)->dump_to_pp (model, *child_rid, pp,
+						 new_prefix,
+						 is_last_child);
+    }
+}
+
+/* Base implementation of region::dump_child_label vfunc.  */
+
+void
+region::dump_child_label (const region_model &model,
+			  region_id this_rid ATTRIBUTE_UNUSED,
+			  region_id child_rid,
+			  pretty_printer *pp) const
+{
+  region *child = model.get_region (child_rid);
+  if (child->m_is_view)
+    {
+      gcc_assert (TYPE_P (child->get_type ()));
+      if (m_active_view_rid == child_rid)
+	pp_string (pp, "active ");
+      else
+	pp_string (pp, "inactive ");
+PUSH_IGNORE_WFORMAT
+      pp_printf (pp, "view as %qT: ", child->get_type ());
+POP_IGNORE_WFORMAT
+    }
+}
+
+/* Assert that this object is valid.  */
+
+void
+region::validate (const region_model *model) const
+{
+  m_parent_rid.validate (*model);
+  m_sval_id.validate (*model);
+  unsigned i;
+  region_id *view_rid;
+  FOR_EACH_VEC_ELT (m_view_rids, i, view_rid)
+    {
+      gcc_assert (!view_rid->null_p ());
+      view_rid->validate (*model);
+    }
+  m_active_view_rid.validate (*model);
+}
+
+/* Apply MAP to svalue_ids to this region.  This updates the value
+   for the region (if any).  */
+
+void
+region::remap_svalue_ids (const svalue_id_map &map)
+{
+  map.update (&m_sval_id);
+}
+
+/* Base implementation of region::remap_region_ids vfunc; subclasses should
+   chain up to this, updating any region_id data.  */
+
+void
+region::remap_region_ids (const region_id_map &map)
+{
+  map.update (&m_parent_rid);
+  unsigned i;
+  region_id *view_rid;
+  FOR_EACH_VEC_ELT (m_view_rids, i, view_rid)
+    map.update (view_rid);
+  map.update (&m_active_view_rid);
+}
+
+/* Add a new region with id VIEW_RID as a view of this region.  */
+
+void
+region::add_view (region_id view_rid, region_model *model)
+{
+  gcc_assert (!view_rid.null_p ());
+  region *new_view = model->get_region (view_rid);
+  new_view->m_is_view = true;
+  gcc_assert (!new_view->m_parent_rid.null_p ());
+  gcc_assert (new_view->m_sval_id.null_p ());
+
+  //gcc_assert (new_view->get_type () != NULL_TREE);
+  // TODO: this can sometimes be NULL, when viewing through a (void *)
+
+  // TODO: the type ought to not be present yet
+
+  m_view_rids.safe_push (view_rid);
+}
+
+/* Look for a view of type TYPE of this region, returning its id if found,
+   or null otherwise.  */
+
+region_id
+region::get_view (tree type, region_model *model) const
+{
+  unsigned i;
+  region_id *view_rid;
+  FOR_EACH_VEC_ELT (m_view_rids, i, view_rid)
+    {
+      region *view = model->get_region (*view_rid);
+      gcc_assert (view->m_is_view);
+      if (view->get_type () == type)
+	return *view_rid;
+    }
+  return region_id::null ();
+}
+
+/* region's ctor.  */
+
+region::region (region_id parent_rid, svalue_id sval_id, tree type)
+: m_parent_rid (parent_rid), m_sval_id (sval_id), m_type (type),
+  m_view_rids (), m_is_view (false), m_active_view_rid (region_id::null ())
+{
+  gcc_assert (type == NULL_TREE || TYPE_P (type));
+}
+
+/* region's copy ctor.  */
+
+region::region (const region &other)
+: m_parent_rid (other.m_parent_rid), m_sval_id (other.m_sval_id),
+  m_type (other.m_type), m_view_rids (other.m_view_rids.length ()),
+  m_is_view (other.m_is_view), m_active_view_rid (other.m_active_view_rid)
+{
+  int i;
+  region_id *rid;
+  FOR_EACH_VEC_ELT (other.m_view_rids, i, rid)
+    m_view_rids.quick_push (*rid);
+}
+
+/* Base implementation of region::add_to_hash vfunc; subclasses should
+   chain up to this.  */
+
+void
+region::add_to_hash (inchash::hash &hstate) const
+{
+  inchash::add (m_parent_rid, hstate);
+  inchash::add (m_sval_id, hstate);
+  hstate.add_ptr (m_type);
+  // TODO: views
+}
+
+/* Base implementation of region::print_fields vfunc.  */
+
+void
+region::print_fields (const region_model &model ATTRIBUTE_UNUSED,
+		      region_id this_rid ATTRIBUTE_UNUSED,
+		      pretty_printer *pp) const
+{
+  pp_printf (pp, "kind: %qs", region_kind_to_str (get_kind ()));
+
+  pp_string (pp, ", parent: ");
+  m_parent_rid.print (pp);
+
+  pp_printf (pp, ", sval: ");
+  m_sval_id.print (pp);
+
+PUSH_IGNORE_WFORMAT
+  if (m_type)
+    pp_printf (pp, ", type: %qT", m_type);
+POP_IGNORE_WFORMAT
+}
+
+/* Determine if a pointer to this region must be non-NULL.
+
+   Generally, pointers to regions must be non-NULL, but pointers
+   to symbolic_regions might, in fact, be NULL.
+
+   This allows us to simulate functions like malloc and calloc with:
+   - only one "outcome" from each statement,
+   - the idea that the pointer is on the heap if non-NULL
+   - the possibility that the pointer could be NULL
+   - the idea that successive values returned from malloc are non-equal
+   - to be able to zero-fill for calloc.  */
+
+bool
+region::non_null_p (const region_model &model) const
+{
+  /* Look through views to get at the underlying region.  */
+  if (is_view_p ())
+    return model.get_region (m_parent_rid)->non_null_p (model);
+
+  /* Are we within a symbolic_region?  If so, it could be NULL.  */
+  if (const symbolic_region *sym_reg = dyn_cast_symbolic_region ())
+    {
+      if (sym_reg->m_possibly_null)
+	return false;
+    }
+
+  return true;
+}
+
+/* class primitive_region : public region.  */
+
+/* Implementation of region::clone vfunc for primitive_region.  */
+
+region *
+primitive_region::clone () const
+{
+  return new primitive_region (*this);
+}
+
+/* Implementation of region::walk_for_canonicalization vfunc for
+   primitive_region.  */
+
+void
+primitive_region::walk_for_canonicalization (canonicalization *) const
+{
+  /* Empty.  */
+}
+
+/* class map_region : public region.  */
+
+/* map_region's copy ctor.  */
+
+map_region::map_region (const map_region &other)
+: region (other),
+  m_map (other.m_map)
+{
+}
+
+/* Compare the fields of this map_region with OTHER, returning true
+   if they are equal.
+   For use by region::operator==.  */
+
+bool
+map_region::compare_fields (const map_region &other) const
+{
+  if (m_map.elements () != other.m_map.elements ())
+    return false;
+
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      tree key = (*iter).first;
+      region_id e = (*iter).second;
+      region_id *other_slot = const_cast <map_t &> (other.m_map).get (key);
+      if (other_slot == NULL)
+	return false;
+      if (e != *other_slot)
+	return false;
+    }
+  return true;
+}
+
+/* Implementation of region::print_fields vfunc for map_region.  */
+
+void
+map_region::print_fields (const region_model &model,
+			  region_id this_rid,
+			  pretty_printer *pp) const
+{
+  region::print_fields (model, this_rid, pp);
+  pp_string (pp, ", map: {");
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      if (iter != m_map.begin ())
+	pp_string (pp, ", ");
+      tree expr = (*iter).first;
+      region_id child_rid = (*iter).second;
+PUSH_IGNORE_WFORMAT
+      pp_printf (pp, "%qE: ", expr);
+POP_IGNORE_WFORMAT
+      child_rid.print (pp);
+    }
+  pp_string (pp, "}");
+}
+
+/* Implementation of region::dump_dot_to_pp vfunc for map_region.  */
+
+void
+map_region::dump_dot_to_pp (const region_model &model,
+			    region_id this_rid,
+			    pretty_printer *pp) const
+{
+  region::dump_dot_to_pp (model, this_rid, pp);
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      // TODO: add nodes/edges to label things
+
+      tree expr = (*iter).first;
+      region_id child_rid = (*iter).second;
+
+      pp_printf (pp, "rid_label_%i [label=\"", child_rid.as_int ());
+      pp_write_text_to_stream (pp);
+PUSH_IGNORE_WFORMAT
+      pp_printf (pp, "%qE", expr);
+POP_IGNORE_WFORMAT
+      pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/false);
+      pp_string (pp, "\"];");
+      pp_newline (pp);
+
+      pp_printf (pp, "rid_label_%i", child_rid.as_int ());
+      pp_string (pp, " -> ");
+      child_rid.dump_node_name_to_pp (pp);
+      pp_string (pp, ";");
+      pp_newline (pp);
+    }
+}
+
+/* Implementation of region::dump_child_label vfunc for map_region.  */
+
+void
+map_region::dump_child_label (const region_model &model,
+			      region_id this_rid,
+			      region_id child_rid,
+			      pretty_printer *pp) const
+{
+  region::dump_child_label (model, this_rid, child_rid, pp);
+
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      if (child_rid == (*iter).second)
+	{
+	  tree key = (*iter).first;
+PUSH_IGNORE_WFORMAT
+	  if (DECL_P (key))
+	    pp_printf (pp, "%qD: ", key);
+	  else
+	    pp_printf (pp, "%qE: ", key);
+POP_IGNORE_WFORMAT
+	}
+    }
+}
+
+/* Look for a child region for KEY within this map_region.
+   If it doesn't already exist, create a child map_region, using TYPE for
+   its type.
+   Return the region_id of the child (whether pre-existing, or
+   newly-created).  */
+
+region_id
+map_region::get_or_create (region_model *model,
+			   region_id this_rid,
+			   tree key,
+			   tree type)
+{
+  gcc_assert (key);
+  gcc_assert (valid_key_p (key));
+  region_id *slot = m_map.get (key);
+  if (slot)
+    return *slot;
+  region_id child_rid = model->add_region_for_type (this_rid, type);
+  m_map.put (key, child_rid);
+  return child_rid;
+}
+
+/* Get the region_id for the child region for KEY within this
+   MAP_REGION, or NULL if there is no such child region.  */
+
+region_id *
+map_region::get (tree key)
+{
+  gcc_assert (key);
+  gcc_assert (valid_key_p (key));
+  region_id *slot = m_map.get (key);
+  return slot;
+}
+
+/* Implementation of region::add_to_hash vfunc for map_region.  */
+
+void
+map_region::add_to_hash (inchash::hash &hstate) const
+{
+  region::add_to_hash (hstate);
+  // TODO
+}
+
+/* Implementation of region::remap_region_ids vfunc for map_region.  */
+
+void
+map_region::remap_region_ids (const region_id_map &map)
+{
+  region::remap_region_ids (map);
+
+  /* Remap the region ids within the map entries.  */
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end (); ++iter)
+    map.update (&(*iter).second);
+}
+
+/* Remove the binding of KEY to its child region (but not the
+   child region itself).
+   For use when purging unneeded SSA names.  */
+
+void
+map_region::unbind (tree key)
+{
+  gcc_assert (key);
+  gcc_assert (valid_key_p (key));
+  m_map.remove (key);
+}
+
+/* Look for a child region with id CHILD_RID within this map_region.
+   If one is found, return its tree key, otherwise return NULL_TREE.  */
+
+tree
+map_region::get_tree_for_child_region (region_id child_rid) const
+{
+  // TODO: do we want to store an inverse map?
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      tree key = (*iter).first;
+      region_id r = (*iter).second;
+      if (r == child_rid)
+	return key;
+    }
+
+  return NULL_TREE;
+}
+
+/* Look for a child region CHILD within this map_region.
+   If one is found, return its tree key, otherwise return NULL_TREE.  */
+
+tree
+map_region::get_tree_for_child_region (region *child,
+				       const region_model &model) const
+{
+  // TODO: do we want to store an inverse map?
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      tree key = (*iter).first;
+      region_id r = (*iter).second;
+      if (model.get_region (r) == child)
+	return key;
+    }
+
+  return NULL_TREE;
+}
+
+/* Comparator for trees to impose a deterministic ordering on
+   T1 and T2.  */
+
+static int
+tree_cmp (const_tree t1, const_tree t2)
+{
+  gcc_assert (t1);
+  gcc_assert (t2);
+
+  /* Test tree codes first.  */
+  if (TREE_CODE (t1) != TREE_CODE (t2))
+    return TREE_CODE (t1) - TREE_CODE (t2);
+
+  /* From this point on, we know T1 and T2 have the same tree code.  */
+
+  if (DECL_P (t1))
+    {
+      if (DECL_NAME (t1) && DECL_NAME (t2))
+	return strcmp (IDENTIFIER_POINTER (DECL_NAME (t1)),
+		       IDENTIFIER_POINTER (DECL_NAME (t2)));
+      else
+	{
+	  if (DECL_NAME (t1))
+	    return -1;
+	  else if (DECL_NAME (t2))
+	    return 1;
+	  else
+	    return DECL_UID (t1) - DECL_UID (t2);
+	}
+    }
+
+  switch (TREE_CODE (t1))
+    {
+    case SSA_NAME:
+      {
+	if (SSA_NAME_VAR (t1) && SSA_NAME_VAR (t2))
+	  {
+	    int var_cmp = tree_cmp (SSA_NAME_VAR (t1), SSA_NAME_VAR (t2));
+	    if (var_cmp)
+	      return var_cmp;
+	    return SSA_NAME_VERSION (t1) - SSA_NAME_VERSION (t2);
+	  }
+	else
+	  {
+	    if (SSA_NAME_VAR (t1))
+	      return -1;
+	    else if (SSA_NAME_VAR (t2))
+	      return 1;
+	    else
+	      return SSA_NAME_VERSION (t1) - SSA_NAME_VERSION (t2);
+	  }
+      }
+      break;
+
+    case INTEGER_CST:
+      return tree_int_cst_compare (t1, t2);
+
+    case REAL_CST:
+      {
+	real_value *rv1 = TREE_REAL_CST_PTR (t1);
+	real_value *rv2 = TREE_REAL_CST_PTR (t2);
+	if (real_compare (LT_EXPR, rv1, rv2))
+	  return -1;
+	if (real_compare (LT_EXPR, rv2, rv1))
+	  return 1;
+	return 0;
+      }
+
+    case STRING_CST:
+      return strcmp (TREE_STRING_POINTER (t1),
+		     TREE_STRING_POINTER (t2));
+
+    default:
+      gcc_unreachable ();
+      break;
+    }
+
+  gcc_unreachable ();
+
+  return 0;
+}
+
+/* qsort comparator for trees to impose a deterministic ordering on
+   P1 and P2.  */
+
+static int
+tree_cmp (const void *p1, const void *p2)
+{
+  const_tree t1 = *(const_tree const *)p1;
+  const_tree t2 = *(const_tree const *)p2;
+
+  int result = tree_cmp (t1, t2);
+
+  /* Check that the ordering is symmetric  */
+#if CHECKING_P
+  int reversed = tree_cmp (t2, t1);
+  gcc_assert (reversed == -result);
+#endif
+
+  /* We should only have 0 for equal pairs.  */
+#if 0
+  gcc_assert (result != 0
+	      || t1 == t2);
+#endif
+
+  return result;
+}
+
+/* Attempt to merge MAP_REGION_A and MAP_REGION_B into MERGED_MAP_REGION,
+   which has region_id MERGED_RID, using MERGER.
+   Return true if the merger is possible, false otherwise.  */
+
+bool
+map_region::can_merge_p (const map_region *map_region_a,
+			 const map_region *map_region_b,
+			 map_region *merged_map_region,
+			 region_id merged_rid,
+			 model_merger *merger)
+{
+  for (map_t::iterator iter = map_region_a->m_map.begin ();
+       iter != map_region_a->m_map.end ();
+       ++iter)
+    {
+      tree key_a = (*iter).first;
+      region_id rid_a = (*iter).second;
+
+      if (const region_id *slot_b
+	  = const_cast<map_region *>(map_region_b)->m_map.get (key_a))
+	{
+	  region_id rid_b = *slot_b;
+
+	  region *child_region_a = merger->get_region_a <region> (rid_a);
+	  region *child_region_b = merger->get_region_b <region> (rid_b);
+
+	  gcc_assert (child_region_a->get_type ()
+		      == child_region_b->get_type ());
+
+	  gcc_assert (child_region_a->get_kind ()
+		      == child_region_b->get_kind ());
+
+	  region_id child_merged_rid
+	    = merged_map_region->get_or_create (merger->m_merged_model,
+						merged_rid,
+						key_a,
+						child_region_a->get_type ());
+
+	  region *child_merged_region
+	    = merger->m_merged_model->get_region (child_merged_rid);
+
+	  /* Consider values.  */
+	  svalue_id child_a_sid = child_region_a->get_value_direct ();
+	  svalue_id child_b_sid = child_region_b->get_value_direct ();
+	  svalue_id child_merged_sid;
+	  if (!merger->can_merge_values_p (child_a_sid, child_b_sid,
+					   &child_merged_sid))
+	    return false;
+	  if (!child_merged_sid.null_p ())
+	    child_merged_region->set_value (*merger->m_merged_model,
+					    child_merged_rid,
+					    child_merged_sid,
+					    NULL);
+
+	  if (map_region *map_region_a = child_region_a->dyn_cast_map_region ())
+	    {
+	      /* Recurse.  */
+	      if (!can_merge_p (map_region_a,
+				as_a <map_region *> (child_region_b),
+				as_a <map_region *> (child_merged_region),
+				child_merged_rid,
+				merger))
+		return false;
+	    }
+
+	}
+      else
+	{
+	  /* TODO: region is present in A, but absent in B.  */
+	}
+    }
+
+  /* TODO: check for keys in B that aren't in A.  */
+
+  return true;
+}
+
+
+/* Implementation of region::walk_for_canonicalization vfunc for
+   map_region.  */
+
+void
+map_region::walk_for_canonicalization (canonicalization *c) const
+{
+  auto_vec<tree> keys (m_map.elements ());
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      tree key_a = (*iter).first;
+      keys.quick_push (key_a);
+    }
+  keys.qsort (tree_cmp);
+
+  unsigned i;
+  tree key;
+  FOR_EACH_VEC_ELT (keys, i, key)
+    {
+      region_id rid = *const_cast<map_region *>(this)->m_map.get (key);
+      c->walk_rid (rid);
+    }
+}
+
+/* For debugging purposes: look for a child region for a decl named
+   IDENTIFIER (or an SSA_NAME for such a decl), returning its value,
+   or svalue_id::null if none are found.  */
+
+svalue_id
+map_region::get_value_by_name (tree identifier,
+			       const region_model &model) const
+{
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      tree key = (*iter).first;
+      if (TREE_CODE (key) == SSA_NAME)
+	if (SSA_NAME_VAR (key))
+	  key = SSA_NAME_VAR (key);
+      if (DECL_P (key))
+	if (DECL_NAME (key) == identifier)
+	  {
+	    region_id rid = (*iter).second;
+	    region *region = model.get_region (rid);
+	    return region->get_value (const_cast<region_model &>(model),
+				      false, NULL);
+	  }
+    }
+  return svalue_id::null ();
+}
+
+/* class struct_or_union_region : public map_region.  */
+
+/* Implementation of map_region::valid_key_p vfunc for
+   struct_or_union_region.  */
+
+bool
+struct_or_union_region::valid_key_p (tree key) const
+{
+  return TREE_CODE (key) == FIELD_DECL;
+}
+
+/* Compare the fields of this struct_or_union_region with OTHER, returning
+   true if they are equal.
+   For use by region::operator==.  */
+
+bool
+struct_or_union_region::compare_fields (const struct_or_union_region &other)
+  const
+{
+  return map_region::compare_fields (other);
+}
+
+/* class struct_region : public struct_or_union_region.  */
+
+/* Implementation of region::clone vfunc for struct_region.  */
+
+region *
+struct_region::clone () const
+{
+  return new struct_region (*this);
+}
+
+/* Compare the fields of this struct_region with OTHER, returning true
+   if they are equal.
+   For use by region::operator==.  */
+
+bool
+struct_region::compare_fields (const struct_region &other) const
+{
+  return struct_or_union_region::compare_fields (other);
+}
+
+/* class union_region : public struct_or_union_region.  */
+
+/* Implementation of region::clone vfunc for union_region.  */
+
+region *
+union_region::clone () const
+{
+  return new union_region (*this);
+}
+
+/* Compare the fields of this union_region with OTHER, returning true
+   if they are equal.
+   For use by region::operator==.  */
+
+bool
+union_region::compare_fields (const union_region &other) const
+{
+  return struct_or_union_region::compare_fields (other);
+}
+
+/* class frame_region : public map_region.  */
+
+/* Compare the fields of this frame_region with OTHER, returning true
+   if they are equal.
+   For use by region::operator==.  */
+
+bool
+frame_region::compare_fields (const frame_region &other) const
+{
+  if (!map_region::compare_fields (other))
+    return false;
+  if (m_fun != other.m_fun)
+    return false;
+  if (m_depth != other.m_depth)
+    return false;
+  return true;
+}
+
+/* Implementation of region::clone vfunc for frame_region.  */
+
+region *
+frame_region::clone () const
+{
+  return new frame_region (*this);
+}
+
+/* Implementation of map_region::valid_key_p vfunc for frame_region.  */
+
+bool
+frame_region::valid_key_p (tree key) const
+{
+  // TODO: could also check that VAR_DECLs are locals
+  return (TREE_CODE (key) == PARM_DECL
+	  || TREE_CODE (key) == VAR_DECL
+	  || TREE_CODE (key) == SSA_NAME
+	  || TREE_CODE (key) == RESULT_DECL);
+}
+
+/* Implementation of region::print_fields vfunc for frame_region.  */
+
+void
+frame_region::print_fields (const region_model &model,
+			    region_id this_rid,
+			    pretty_printer *pp) const
+{
+  map_region::print_fields (model, this_rid, pp);
+  pp_printf (pp, ", function: %qs, depth: %i", function_name (m_fun), m_depth);
+}
+
+/* Implementation of region::add_to_hash vfunc for frame_region.  */
+
+void
+frame_region::add_to_hash (inchash::hash &hstate) const
+{
+  map_region::add_to_hash (hstate);
+  hstate.add_ptr (m_fun);
+  hstate.add_int (m_depth);
+}
+
+/* class globals_region : public scope_region.  */
+
+/* Compare the fields of this globals_region with OTHER, returning true
+   if they are equal.
+   For use by region::operator==.  */
+
+bool
+globals_region::compare_fields (const globals_region &other) const
+{
+  return map_region::compare_fields (other);
+}
+
+/* Implementation of region::clone vfunc for globals_region.  */
+
+region *
+globals_region::clone () const
+{
+  return new globals_region (*this);
+}
+
+/* Implementation of map_region::valid_key_p vfunc for globals_region.  */
+
+bool
+globals_region::valid_key_p (tree key) const
+{
+  return TREE_CODE (key) == VAR_DECL;
+}
+
+/* class code_region : public map_region.  */
+
+/* Compare the fields of this code_region with OTHER, returning true
+   if they are equal.
+   For use by region::operator==.  */
+
+bool
+code_region::compare_fields (const code_region &other) const
+{
+  return map_region::compare_fields (other);
+}
+
+/* Implementation of region::clone vfunc for code_region.  */
+
+region *
+code_region::clone () const
+{
+  return new code_region (*this);
+}
+
+/* Implementation of map_region::valid_key_p vfunc for code_region.  */
+
+bool
+code_region::valid_key_p (tree key) const
+{
+  return TREE_CODE (key) == FUNCTION_DECL;
+}
+
+/* class array_region : public region.  */
+
+/* array_region's copy ctor.  */
+
+array_region::array_region (const array_region &other)
+: region (other),
+  m_map (other.m_map)
+{
+}
+
+/* Get a child region for the element with index INDEX_SID.  */
+
+region_id
+array_region::get_element (region_model *model,
+			   region_id this_rid,
+			   svalue_id index_sid,
+			   region_model_context *ctxt ATTRIBUTE_UNUSED)
+{
+  tree element_type = TREE_TYPE (get_type ());
+  svalue *index_sval = model->get_svalue (index_sid);
+  if (tree cst_index = index_sval->maybe_get_constant ())
+    {
+      key_t key = key_from_constant (cst_index);
+      region_id element_rid
+	= get_or_create (model, this_rid, key, element_type);
+      return element_rid;
+   }
+
+  return model->get_or_create_view (this_rid, element_type);
+}
+
+/* Implementation of region::clone vfunc for array_region.  */
+
+region *
+array_region::clone () const
+{
+  return new array_region (*this);
+}
+
+/* Compare the fields of this array_region with OTHER, returning true
+   if they are equal.
+   For use by region::operator==.  */
+
+bool
+array_region::compare_fields (const array_region &other) const
+{
+  if (m_map.elements () != other.m_map.elements ())
+    return false;
+
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      int key = (*iter).first;
+      region_id e = (*iter).second;
+      region_id *other_slot = const_cast <map_t &> (other.m_map).get (key);
+      if (other_slot == NULL)
+	return false;
+      if (e != *other_slot)
+	return false;
+    }
+  return true;
+}
+
+/* Implementation of region::print_fields vfunc for array_region.  */
+
+void
+array_region::print_fields (const region_model &model,
+			    region_id this_rid,
+			    pretty_printer *pp) const
+{
+  region::print_fields (model, this_rid, pp);
+  pp_string (pp, ", array: {");
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      if (iter != m_map.begin ())
+	pp_string (pp, ", ");
+      int key = (*iter).first;
+      region_id child_rid = (*iter).second;
+PUSH_IGNORE_WFORMAT
+      pp_printf (pp, "[%i]: ", key);
+POP_IGNORE_WFORMAT
+      child_rid.print (pp);
+    }
+  pp_string (pp, "}");
+}
+
+/* Implementation of region::dump_dot_to_pp vfunc for array_region.  */
+
+void
+array_region::dump_dot_to_pp (const region_model &model,
+			      region_id this_rid,
+			      pretty_printer *pp) const
+{
+  region::dump_dot_to_pp (model, this_rid, pp);
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      // TODO: add nodes/edges to label things
+
+      int key = (*iter).first;
+      region_id child_rid = (*iter).second;
+
+      pp_printf (pp, "rid_label_%i [label=\"", child_rid.as_int ());
+      pp_write_text_to_stream (pp);
+PUSH_IGNORE_WFORMAT
+      pp_printf (pp, "%qi", key);
+POP_IGNORE_WFORMAT
+      pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/false);
+      pp_string (pp, "\"];");
+      pp_newline (pp);
+
+      pp_printf (pp, "rid_label_%i", child_rid.as_int ());
+      pp_string (pp, " -> ");
+      child_rid.dump_node_name_to_pp (pp);
+      pp_string (pp, ";");
+      pp_newline (pp);
+    }
+}
+
+/* Implementation of region::dump_child_label vfunc for array_region.  */
+
+void
+array_region::dump_child_label (const region_model &model,
+			      region_id this_rid,
+			      region_id child_rid,
+			      pretty_printer *pp) const
+{
+  region::dump_child_label (model, this_rid, child_rid, pp);
+
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      if (child_rid == (*iter).second)
+	{
+	  int key = (*iter).first;
+	  pp_printf (pp, "[%i]: ", key);
+	}
+    }
+}
+
+/* Look for a child region for KEY within this array_region.
+   If it doesn't already exist, create a child array_region, using TYPE for
+   its type.
+   Return the region_id of the child (whether pre-existing, or
+   newly-created).  */
+
+region_id
+array_region::get_or_create (region_model *model,
+			     region_id this_rid,
+			     key_t key,
+			     tree type)
+{
+  region_id *slot = m_map.get (key);
+  if (slot)
+    return *slot;
+  region_id child_rid = model->add_region_for_type (this_rid, type);
+  m_map.put (key, child_rid);
+  return child_rid;
+}
+
+/* Get the region_id for the child region for KEY within this
+   ARRAY_REGION, or NULL if there is no such child region.  */
+
+region_id *
+array_region::get (key_t key)
+{
+  region_id *slot = m_map.get (key);
+  return slot;
+}
+
+/* Implementation of region::add_to_hash vfunc for array_region.  */
+
+void
+array_region::add_to_hash (inchash::hash &hstate) const
+{
+  region::add_to_hash (hstate);
+  // TODO
+}
+
+/* Implementation of region::remap_region_ids vfunc for array_region.  */
+
+void
+array_region::remap_region_ids (const region_id_map &map)
+{
+  region::remap_region_ids (map);
+
+  /* Remap the region ids within the map entries.  */
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end (); ++iter)
+    map.update (&(*iter).second);
+}
+
+/* Look for a child region with id CHILD_RID within this array_region.
+   If one is found, write its key to *OUT and return true,
+   otherwise return false.  */
+
+bool
+array_region::get_key_for_child_region (region_id child_rid, key_t *out) const
+{
+  // TODO: do we want to store an inverse map?
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      key_t key = (*iter).first;
+      region_id r = (*iter).second;
+      if (r == child_rid)
+	{
+	  *out = key;
+	  return true;
+	}
+    }
+
+  return false;
+}
+
+/* qsort comparator for int.  */
+
+static int
+int_cmp (const void *p1, const void *p2)
+{
+  int i1 = *(const int *)p1;
+  int i2 = *(const int *)p2;
+
+  return i1 - i2;
+}
+
+/* Implementation of region::walk_for_canonicalization vfunc for
+   array_region.  */
+
+void
+array_region::walk_for_canonicalization (canonicalization *c) const
+{
+  auto_vec<int> keys (m_map.elements ());
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      int key_a = (*iter).first;
+      keys.quick_push (key_a);
+    }
+  keys.qsort (int_cmp);
+
+  unsigned i;
+  int key;
+  FOR_EACH_VEC_ELT (keys, i, key)
+    {
+      region_id rid = *const_cast<array_region *>(this)->m_map.get (key);
+      c->walk_rid (rid);
+    }
+}
+
+/* Convert constant CST into an array_region::key_t.  */
+
+array_region::key_t
+array_region::key_from_constant (tree cst)
+{
+  gcc_assert (CONSTANT_CLASS_P (cst));
+  wide_int w = wi::to_wide (cst);
+  key_t result = w.to_shwi ();
+  return result;
+}
+
+/* class function_region : public map_region.  */
+
+/* Compare the fields of this function_region with OTHER, returning true
+   if they are equal.
+   For use by region::operator==.  */
+
+bool
+function_region::compare_fields (const function_region &other) const
+{
+  return map_region::compare_fields (other);
+}
+
+/* Implementation of region::clone vfunc for function_region.  */
+
+region *
+function_region::clone () const
+{
+  return new function_region (*this);
+}
+
+/* Implementation of map_region::valid_key_p vfunc for function_region.  */
+
+bool
+function_region::valid_key_p (tree key) const
+{
+  return TREE_CODE (key) == LABEL_DECL;
+}
+
+/* class stack_region : public region.  */
+
+/* stack_region's copy ctor.  */
+
+stack_region::stack_region (const stack_region &other)
+: region (other),
+  m_frame_rids (other.m_frame_rids.length ())
+{
+  int i;
+  region_id *frame_rid;
+  FOR_EACH_VEC_ELT (other.m_frame_rids, i, frame_rid)
+    m_frame_rids.quick_push (*frame_rid);
+}
+
+/* Compare the fields of this stack_region with OTHER, returning true
+   if they are equal.
+   For use by region::operator==.  */
+
+bool
+stack_region::compare_fields (const stack_region &other) const
+{
+  if (m_frame_rids.length () != other.m_frame_rids.length ())
+    return false;
+
+  int i;
+  region_id *frame_rid;
+  FOR_EACH_VEC_ELT (m_frame_rids, i, frame_rid)
+    if (m_frame_rids[i] != other.m_frame_rids[i])
+      return false;
+
+  return true;
+}
+
+/* Implementation of region::clone vfunc for stack_region.  */
+
+region *
+stack_region::clone () const
+{
+  return new stack_region (*this);
+}
+
+/* Implementation of region::print_fields vfunc for stack_region.  */
+
+void
+stack_region::print_fields (const region_model &model,
+			    region_id this_rid,
+			    pretty_printer *pp) const
+{
+  region::print_fields (model, this_rid, pp);
+  // TODO
+}
+
+/* Implementation of region::dump_child_label vfunc for stack_region.  */
+
+void
+stack_region::dump_child_label (const region_model &model,
+				region_id this_rid ATTRIBUTE_UNUSED,
+				region_id child_rid,
+				pretty_printer *pp) const
+{
+  function *fun = model.get_region<frame_region> (child_rid)->get_function ();
+  pp_printf (pp, "frame for %qs: ", function_name (fun));
+}
+
+/* Push FRAME_RID (for a frame_region) onto this stack.  */
+
+void
+stack_region::push_frame (region_id frame_rid)
+{
+  m_frame_rids.safe_push (frame_rid);
+}
+
+/* Get the region_id of the top-most frame in this stack, if any.  */
+
+region_id
+stack_region::get_current_frame_id () const
+{
+  if (m_frame_rids.length () > 0)
+    return m_frame_rids[m_frame_rids.length () - 1];
+  else
+    return region_id::null ();
+}
+
+/* Pop the topmost frame_region from this stack.
+
+   Purge the frame region and all its descendent regions.
+   Convert any pointers that point into such regions into
+   POISON_KIND_POPPED_STACK svalues.
+
+   Return the ID of any return value from the frame.
+
+   If PURGE, then purge all unused svalues, with the exception of any
+   return value for the frame, which is temporarily
+   preserved in case no regions reference it, so it can
+   be written into a region in the caller.
+
+   Accumulate stats on purged entities into STATS.  */
+
+svalue_id
+stack_region::pop_frame (region_model *model, bool purge, purge_stats *stats,
+			 region_model_context *ctxt)
+{
+  gcc_assert (m_frame_rids.length () > 0);
+
+  region_id frame_rid = get_current_frame_id ();
+  frame_region *frame = model->get_region<frame_region> (frame_rid);
+
+  /* Evaluate the result, within the callee frame.  */
+  svalue_id result_sid;
+  tree fndecl = frame->get_function ()->decl;
+  tree result = DECL_RESULT (fndecl);
+  if (result && TREE_TYPE (result) != void_type_node)
+    result_sid = model->get_rvalue (result, ctxt);
+
+  /* Pop the frame RID.  */
+  m_frame_rids.pop ();
+
+  model->delete_region_and_descendents (frame_rid,
+					POISON_KIND_POPPED_STACK,
+					stats,
+					ctxt ? ctxt->get_logger () : NULL);
+
+  /* Delete unused svalues, but don't delete the return value.  */
+  if (purge)
+    model->purge_unused_svalues (stats, ctxt, &result_sid);
+
+  model->validate ();
+
+  return result_sid;
+}
+
+/* Implementation of region::add_to_hash vfunc for stack_region.  */
+
+void
+stack_region::add_to_hash (inchash::hash &hstate) const
+{
+  region::add_to_hash (hstate);
+
+  int i;
+  region_id *frame_rid;
+  FOR_EACH_VEC_ELT (m_frame_rids, i, frame_rid)
+    inchash::add (*frame_rid, hstate);
+}
+
+/* Implementation of region::remap_region_ids vfunc for stack_region.  */
+
+void
+stack_region::remap_region_ids (const region_id_map &map)
+{
+  region::remap_region_ids (map);
+  int i;
+  region_id *frame_rid;
+  FOR_EACH_VEC_ELT (m_frame_rids, i, frame_rid)
+    map.update (&m_frame_rids[i]);
+}
+
+/* Attempt to merge STACK_REGION_A and STACK_REGION_B using MERGER.
+   Return true if the merger is possible, false otherwise.  */
+
+bool
+stack_region::can_merge_p (const stack_region *stack_region_a,
+			   const stack_region *stack_region_b,
+			   model_merger *merger)
+{
+  if (stack_region_a->get_num_frames ()
+      != stack_region_b->get_num_frames ())
+    return false;
+
+  region_model *merged_model = merger->m_merged_model;
+
+  region_id rid_merged_stack
+    = merged_model->get_root_region ()->ensure_stack_region (merged_model);
+
+  stack_region *merged_stack
+    = merged_model->get_region <stack_region> (rid_merged_stack);
+
+  for (unsigned i = 0; i < stack_region_a->get_num_frames (); i++)
+    {
+      region_id rid_a = stack_region_a->get_frame_rid (i);
+      frame_region *frame_a = merger->get_region_a <frame_region> (rid_a);
+
+      region_id rid_b = stack_region_b->get_frame_rid (i);
+      frame_region *frame_b = merger->get_region_b <frame_region> (rid_b);
+
+      if (frame_a->get_function () != frame_b->get_function ())
+	return false;
+      frame_region *merged_frame = new frame_region (rid_merged_stack,
+						     frame_a->get_function (),
+						     frame_a->get_depth ());
+      region_id rid_merged_frame = merged_model->add_region (merged_frame);
+      merged_stack->push_frame (rid_merged_frame);
+
+      if (!map_region::can_merge_p (frame_a, frame_b,
+				    merged_frame, rid_merged_frame,
+				    merger))
+	return false;
+    }
+
+  return true;
+}
+
+/* Implementation of region::walk_for_canonicalization vfunc for
+   stack_region.  */
+
+void
+stack_region::walk_for_canonicalization (canonicalization *c) const
+{
+  int i;
+  region_id *frame_rid;
+  FOR_EACH_VEC_ELT (m_frame_rids, i, frame_rid)
+    c->walk_rid (*frame_rid);
+}
+
+/* For debugging purposes: look for a grandchild region within one of
+   the child frame regions, where the grandchild is for a decl named
+   IDENTIFIER (or an SSA_NAME for such a decl):
+
+     stack_region
+     `-frame_region
+       `-region for decl named IDENTIFIER
+
+   returning its value, or svalue_id::null if none are found.  */
+
+svalue_id
+stack_region::get_value_by_name (tree identifier,
+				 const region_model &model) const
+{
+  int i;
+  region_id *frame_rid;
+  FOR_EACH_VEC_ELT (m_frame_rids, i, frame_rid)
+    {
+      frame_region *frame = model.get_region<frame_region> (*frame_rid);
+      svalue_id sid = frame->get_value_by_name (identifier, model);
+      if (!sid.null_p ())
+	return sid;
+    }
+
+  return svalue_id::null ();
+}
+
+/* class heap_region : public region.  */
+
+/* heap_region's copy ctor.  */
+
+heap_region::heap_region (const heap_region &other)
+: region (other)
+{
+}
+
+/* Compare the fields of this heap_region with OTHER, returning true
+   if they are equal.
+   For use by region::operator==.  */
+
+bool
+heap_region::compare_fields (const heap_region &) const
+{
+  /* Empty.  */
+  return true;
+}
+
+/* Implementation of region::clone vfunc for heap_region.  */
+
+region *
+heap_region::clone () const
+{
+  return new heap_region (*this);
+}
+
+/* Implementation of region::walk_for_canonicalization vfunc for
+   heap_region.  */
+
+void
+heap_region::walk_for_canonicalization (canonicalization *) const
+{
+  /* Empty.  */
+}
+
+/* class root_region : public region.  */
+
+/* root_region's default ctor.  */
+
+root_region::root_region ()
+: region (region_id::null (),
+	  svalue_id::null (),
+	  NULL_TREE)
+{
+}
+
+/* root_region's copy ctor.  */
+
+root_region::root_region (const root_region &other)
+: region (other),
+  m_stack_rid (other.m_stack_rid),
+  m_globals_rid (other.m_globals_rid),
+  m_code_rid (other.m_code_rid),
+  m_heap_rid (other.m_heap_rid)
+{
+}
+
+/* Compare the fields of this root_region with OTHER, returning true
+   if they are equal.
+   For use by region::operator==.  */
+
+bool
+root_region::compare_fields (const root_region &other) const
+{
+  if (m_stack_rid != other.m_stack_rid)
+    return false;
+  if (m_globals_rid != other.m_globals_rid)
+    return false;
+  if (m_code_rid != other.m_code_rid)
+    return false;
+  if (m_heap_rid != other.m_heap_rid)
+    return false;
+  return true;
+}
+
+/* Implementation of region::clone vfunc for root_region.  */
+
+region *
+root_region::clone () const
+{
+  return new root_region (*this);
+}
+
+/* Implementation of region::print_fields vfunc for root_region.  */
+
+void
+root_region::print_fields (const region_model &model,
+			   region_id this_rid,
+			   pretty_printer *pp) const
+{
+  region::print_fields (model, this_rid, pp);
+  // TODO
+}
+
+/* Implementation of region::dump_child_label vfunc for root_region.  */
+
+void
+root_region::dump_child_label (const region_model &model ATTRIBUTE_UNUSED,
+			       region_id this_rid ATTRIBUTE_UNUSED,
+			       region_id child_rid,
+			       pretty_printer *pp) const
+{
+  if (child_rid == m_stack_rid)
+    pp_printf (pp, "stack: ");
+  else if (child_rid == m_globals_rid)
+    pp_printf (pp, "globals: ");
+  else if (child_rid == m_code_rid)
+    pp_printf (pp, "code: ");
+  else if (child_rid == m_heap_rid)
+    pp_printf (pp, "heap: ");
+}
+
+/* Create a new frame_region for a call to FUN and push it onto
+   the stack.
+
+   If ARG_SIDS is non-NULL, use it to populate the parameters
+   in the new frame.
+   Otherwise, populate them with unknown values.
+
+   Return the region_id of the new frame.  */
+
+region_id
+root_region::push_frame (region_model *model, function *fun,
+			 vec<svalue_id> *arg_sids,
+			 region_model_context *ctxt)
+{
+  gcc_assert (fun);
+  /* arg_sids can be NULL.  */
+
+  ensure_stack_region (model);
+  stack_region *stack = model->get_region <stack_region> (m_stack_rid);
+
+  frame_region *region = new frame_region (m_stack_rid, fun,
+					   stack->get_num_frames ());
+  region_id frame_rid = model->add_region (region);
+
+  // TODO: unify these cases by building a vec of unknown?
+
+  if (arg_sids)
+    {
+      /* Arguments supplied from a caller frame.  */
+
+      tree fndecl = fun->decl;
+      unsigned idx = 0;
+      for (tree iter_parm = DECL_ARGUMENTS (fndecl); iter_parm;
+	   iter_parm = DECL_CHAIN (iter_parm), ++idx)
+	{
+	  /* If there's a mismatching declaration, the call stmt might
+	     not have enough args.  Handle this case by leaving the
+	     rest of the params as uninitialized.  */
+	  if (idx >= arg_sids->length ())
+	    break;
+	  svalue_id arg_sid = (*arg_sids)[idx];
+	  region_id parm_rid
+	    = region->get_or_create (model, frame_rid, iter_parm,
+				     TREE_TYPE (iter_parm));
+	  model->set_value (parm_rid, arg_sid, ctxt);
+
+	  /* Also do it for default SSA name (sharing the same unknown
+	     value).  */
+	  tree parm_default_ssa = ssa_default_def (fun, iter_parm);
+	  if (parm_default_ssa)
+	    {
+	      region_id defssa_rid
+		= region->get_or_create (model, frame_rid, parm_default_ssa,
+					 TREE_TYPE (iter_parm));
+	      model->set_value (defssa_rid, arg_sid, ctxt);
+	    }
+	}
+    }
+  else
+    {
+      /* No known arguments (a top-level call within the analysis).  */
+
+      /* Params have a defined, unknown value; they should not inherit
+	 from the poisoned uninit value.  */
+      tree fndecl = fun->decl;
+      for (tree iter_parm = DECL_ARGUMENTS (fndecl); iter_parm;
+	   iter_parm = DECL_CHAIN (iter_parm))
+	{
+	  region_id parm_rid
+	    = region->get_or_create (model, frame_rid, iter_parm,
+				     TREE_TYPE (iter_parm));
+	  svalue_id parm_sid
+	    = model->set_to_new_unknown_value (parm_rid, TREE_TYPE (iter_parm),
+					       ctxt);
+
+	  /* Also do it for default SSA name (sharing the same unknown
+	     value).  */
+	  tree parm_default_ssa = ssa_default_def (fun, iter_parm);
+	  if (parm_default_ssa)
+	    {
+	      region_id defssa_rid
+		= region->get_or_create (model, frame_rid, parm_default_ssa,
+					 TREE_TYPE (iter_parm));
+	      model->get_region (defssa_rid)->set_value (*model, defssa_rid,
+							 parm_sid, ctxt);
+	    }
+	}
+    }
+
+  stack->push_frame (frame_rid);
+
+  return frame_rid;
+}
+
+/* Get the region_id of the top-most frame in this root_region's stack,
+   if any.  */
+
+region_id
+root_region::get_current_frame_id (const region_model &model) const
+{
+  stack_region *stack = model.get_region <stack_region> (m_stack_rid);
+  if (stack)
+    return stack->get_current_frame_id ();
+  else
+    return region_id::null ();
+}
+
+/* Pop the topmost frame_region from this root_region's stack;
+   see the comment for stack_region::pop_frame.  */
+
+svalue_id
+root_region::pop_frame (region_model *model, bool purge, purge_stats *out,
+			region_model_context *ctxt)
+{
+  stack_region *stack = model->get_region <stack_region> (m_stack_rid);
+  return stack->pop_frame (model, purge, out, ctxt);
+}
+
+/* Return the region_id of the stack region, creating it if doesn't
+   already exist.  */
+
+region_id
+root_region::ensure_stack_region (region_model *model)
+{
+  if (m_stack_rid.null_p ())
+    {
+      svalue_id uninit_sid
+	= model->add_svalue (new poisoned_svalue (POISON_KIND_UNINIT,
+						  NULL_TREE));
+      m_stack_rid
+	= model->add_region (new stack_region (model->get_root_rid (),
+					       uninit_sid));
+    }
+  return m_stack_rid;
+}
+
+/* Return the stack region (which could be NULL).  */
+
+stack_region *
+root_region::get_stack_region (const region_model *model) const
+{
+  return model->get_region <stack_region> (m_stack_rid);
+}
+
+/* Return the region_id of the globals region, creating it if doesn't
+   already exist.  */
+
+region_id
+root_region::ensure_globals_region (region_model *model)
+{
+  if (m_globals_rid.null_p ())
+    m_globals_rid
+      = model->add_region (new globals_region (model->get_root_rid ()));
+  return m_globals_rid;
+}
+
+/* Return the code region (which could be NULL).  */
+
+code_region *
+root_region::get_code_region (const region_model *model) const
+{
+  return model->get_region <code_region> (m_code_rid);
+}
+
+/* Return the region_id of the code region, creating it if doesn't
+   already exist.  */
+
+region_id
+root_region::ensure_code_region (region_model *model)
+{
+  if (m_code_rid.null_p ())
+    m_code_rid
+      = model->add_region (new code_region (model->get_root_rid ()));
+  return m_code_rid;
+}
+
+/* Return the globals region (which could be NULL).  */
+
+globals_region *
+root_region::get_globals_region (const region_model *model) const
+{
+  return model->get_region <globals_region> (m_globals_rid);
+}
+
+/* Return the region_id of the heap region, creating it if doesn't
+   already exist.  */
+
+region_id
+root_region::ensure_heap_region (region_model *model)
+{
+  if (m_heap_rid.null_p ())
+    {
+      svalue_id uninit_sid
+	= model->add_svalue (new poisoned_svalue (POISON_KIND_UNINIT,
+						  NULL_TREE));
+      m_heap_rid
+	= model->add_region (new heap_region (model->get_root_rid (),
+					      uninit_sid));
+    }
+  return m_heap_rid;
+}
+
+/* Return the heap region (which could be NULL).  */
+
+heap_region *
+root_region::get_heap_region (const region_model *model) const
+{
+  return model->get_region <heap_region> (m_heap_rid);
+}
+
+/* Implementation of region::remap_region_ids vfunc for root_region.  */
+
+void
+root_region::remap_region_ids (const region_id_map &map)
+{
+  map.update (&m_stack_rid);
+  map.update (&m_globals_rid);
+  map.update (&m_code_rid);
+  map.update (&m_heap_rid);
+}
+
+/* Attempt to merge ROOT_REGION_A and ROOT_REGION_B into
+   MERGED_ROOT_REGION using MERGER.
+   Return true if the merger is possible, false otherwise.  */
+
+bool
+root_region::can_merge_p (const root_region *root_region_a,
+			  const root_region *root_region_b,
+			  root_region *merged_root_region,
+			  model_merger *merger)
+{
+  /* We can only merge if the stacks are sufficiently similar.  */
+  stack_region *stack_a = root_region_a->get_stack_region (merger->m_model_a);
+  stack_region *stack_b = root_region_b->get_stack_region (merger->m_model_b);
+  if (stack_a && stack_b)
+    {
+      /* If the two models both have a stack, attempt to merge them.  */
+      merged_root_region->ensure_stack_region (merger->m_merged_model);
+      if (!stack_region::can_merge_p (stack_a, stack_b, merger))
+	return false;
+    }
+  else if (stack_a || stack_b)
+    /* Don't attempt to merge if one model has a stack and the other
+       doesn't.  */
+    return false;
+
+  map_region *globals_a = root_region_a->get_globals_region (merger->m_model_a);
+  map_region *globals_b = root_region_b->get_globals_region (merger->m_model_b);
+  if (globals_a && globals_b)
+    {
+      /* If both models have globals regions, attempt to merge them.  */
+      region_id merged_globals_rid
+	= merged_root_region->ensure_globals_region (merger->m_merged_model);
+      map_region *merged_globals
+	= merged_root_region->get_globals_region (merger->m_merged_model);
+      if (!map_region::can_merge_p (globals_a, globals_b,
+				    merged_globals, merged_globals_rid,
+				    merger))
+	return false;
+    }
+  /* otherwise, merge as "no globals".  */
+
+  map_region *code_a = root_region_a->get_code_region (merger->m_model_a);
+  map_region *code_b = root_region_b->get_code_region (merger->m_model_b);
+  if (code_a && code_b)
+    {
+      /* If both models have code regions, attempt to merge them.  */
+      region_id merged_code_rid
+	= merged_root_region->ensure_code_region (merger->m_merged_model);
+      map_region *merged_code
+	= merged_root_region->get_code_region (merger->m_merged_model);
+      if (!map_region::can_merge_p (code_a, code_b,
+				    merged_code, merged_code_rid,
+				    merger))
+	return false;
+    }
+  /* otherwise, merge as "no code".  */
+
+  heap_region *heap_a = root_region_a->get_heap_region (merger->m_model_a);
+  heap_region *heap_b = root_region_b->get_heap_region (merger->m_model_b);
+  if (heap_a && heap_b)
+    {
+      /* If both have a heap, create a "merged" heap.
+	 Actually merging the heap contents happens via the region_svalue
+	 instances, as needed, when seeing pairs of region_svalue instances.  */
+      merged_root_region->ensure_heap_region (merger->m_merged_model);
+    }
+  /* otherwise, merge as "no heap".  */
+
+  return true;
+}
+
+/* Implementation of region::add_to_hash vfunc for root_region.  */
+
+void
+root_region::add_to_hash (inchash::hash &hstate) const
+{
+  region::add_to_hash (hstate);
+  inchash::add (m_stack_rid, hstate);
+  inchash::add (m_globals_rid, hstate);
+  inchash::add (m_code_rid, hstate);
+  inchash::add (m_heap_rid, hstate);
+}
+
+/* Implementation of region::walk_for_canonicalization vfunc for
+   root_region.  */
+
+void
+root_region::walk_for_canonicalization (canonicalization *c) const
+{
+  c->walk_rid (m_stack_rid);
+  c->walk_rid (m_globals_rid);
+  c->walk_rid (m_code_rid);
+  c->walk_rid (m_heap_rid);
+}
+
+/* For debugging purposes: look for a descendant region for a local
+   or global decl named IDENTIFIER (or an SSA_NAME for such a decl),
+   returning its value, or svalue_id::null if none are found.  */
+
+svalue_id
+root_region::get_value_by_name (tree identifier,
+				const region_model &model) const
+{
+  if (stack_region *stack = get_stack_region (&model))
+    {
+      svalue_id sid = stack->get_value_by_name (identifier, model);
+      if (!sid.null_p ())
+	return sid;
+    }
+  if (map_region *globals = get_globals_region (&model))
+    {
+      svalue_id sid = globals->get_value_by_name (identifier, model);
+      if (!sid.null_p ())
+	return sid;
+    }
+  return svalue_id::null ();
+}
+
+/* class symbolic_region : public map_region.  */
+
+/* symbolic_region's copy ctor.  */
+
+symbolic_region::symbolic_region (const symbolic_region &other)
+: region (other),
+  m_possibly_null (other.m_possibly_null)
+{
+}
+
+/* Compare the fields of this symbolic_region with OTHER, returning true
+   if they are equal.
+   For use by region::operator==.  */
+
+bool
+symbolic_region::compare_fields (const symbolic_region &other) const
+{
+  return m_possibly_null == other.m_possibly_null;
+}
+
+/* Implementation of region::clone vfunc for symbolic_region.  */
+
+region *
+symbolic_region::clone () const
+{
+  return new symbolic_region (*this);
+}
+
+/* Implementation of region::walk_for_canonicalization vfunc for
+   symbolic_region.  */
+
+void
+symbolic_region::walk_for_canonicalization (canonicalization *) const
+{
+  /* Empty.  */
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class region_model.  */
+
+/* region_model's default ctor.  */
+
+region_model::region_model ()
+{
+  m_root_rid = add_region (new root_region ());
+  m_constraints = new impl_constraint_manager (this);
+  // TODO
+}
+
+/* region_model's copy ctor.  */
+
+region_model::region_model (const region_model &other)
+: m_svalues (other.m_svalues.length ()),
+  m_regions (other.m_regions.length ()),
+  m_root_rid (other.m_root_rid)
+{
+  /* Clone the svalues and regions.  */
+  int i;
+
+  svalue *svalue;
+  FOR_EACH_VEC_ELT (other.m_svalues, i, svalue)
+    m_svalues.quick_push (svalue->clone ());
+
+  region *region;
+  FOR_EACH_VEC_ELT (other.m_regions, i, region)
+    m_regions.quick_push (region->clone ());
+
+  m_constraints = other.m_constraints->clone (this);
+}
+
+/* region_model's dtor.  */
+
+region_model::~region_model ()
+{
+  delete m_constraints;
+}
+
+/* region_model's assignment operator.  */
+
+region_model &
+region_model::operator= (const region_model &other)
+{
+  unsigned i;
+  svalue *svalue;
+  region *region;
+
+  /* Delete existing content.  */
+  FOR_EACH_VEC_ELT (m_svalues, i, svalue)
+    delete svalue;
+  m_svalues.truncate (0);
+
+  FOR_EACH_VEC_ELT (m_regions, i, region)
+    delete region;
+  m_regions.truncate (0);
+
+  delete m_constraints;
+
+  /* Clone the svalues and regions.  */
+  m_svalues.reserve (other.m_svalues.length (), true);
+  FOR_EACH_VEC_ELT (other.m_svalues, i, svalue)
+    m_svalues.quick_push (svalue->clone ());
+
+  m_regions.reserve (other.m_regions.length (), true);
+  FOR_EACH_VEC_ELT (other.m_regions, i, region)
+    m_regions.quick_push (region->clone ());
+
+  m_root_rid = other.m_root_rid;
+
+  m_constraints = other.m_constraints->clone (this);
+
+  return *this;
+}
+
+/* Equality operator for region_model.
+
+   Amongst other things this directly compares the svalue and region
+   vectors and so for this to be meaningful both this and OTHER should
+   have been canonicalized.  */
+
+bool
+region_model::operator== (const region_model &other) const
+{
+  if (m_root_rid != other.m_root_rid)
+    return false;
+
+  if (m_svalues.length () != other.m_svalues.length ())
+    return false;
+
+  if (m_regions.length () != other.m_regions.length ())
+    return false;
+
+  if (*m_constraints != *other.m_constraints)
+    return false;
+
+  unsigned i;
+  svalue *svalue;
+  FOR_EACH_VEC_ELT (other.m_svalues, i, svalue)
+    if (!(*m_svalues[i] == *other.m_svalues[i]))
+      return false;
+
+  region *region;
+  FOR_EACH_VEC_ELT (other.m_regions, i, region)
+    if (!(*m_regions[i] == *other.m_regions[i]))
+      return false;
+
+  gcc_checking_assert (hash () == other.hash ());
+
+  return true;
+}
+
+/* Generate a hash value for this region_model.  */
+
+hashval_t
+region_model::hash () const
+{
+  hashval_t result = 0;
+  int i;
+
+  svalue *svalue;
+  FOR_EACH_VEC_ELT (m_svalues, i, svalue)
+    result ^= svalue->hash ();
+
+  region *region;
+  FOR_EACH_VEC_ELT (m_regions, i, region)
+    result ^= region->hash ();
+
+  result ^= m_constraints->hash ();
+
+  return result;
+}
+
+/* Print an all-on-one-line representation of this region_model to PP,
+   which must support %E for trees.  */
+
+void
+region_model::print (pretty_printer *pp) const
+{
+  int i;
+
+  pp_string (pp, "svalues: [");
+  svalue *svalue;
+  FOR_EACH_VEC_ELT (m_svalues, i, svalue)
+    {
+      if (i > 0)
+	pp_string (pp, ", ");
+      print_svalue (svalue_id::from_int (i), pp);
+    }
+
+  pp_string (pp, "], regions: [");
+
+  region *region;
+  FOR_EACH_VEC_ELT (m_regions, i, region)
+    {
+      if (i > 0)
+	pp_string (pp, ", ");
+      region->print (*this, region_id::from_int (i), pp);
+    }
+
+  pp_string (pp, "], constraints: ");
+
+  m_constraints->print (pp);
+}
+
+/* Print the svalue with id SID to PP.  */
+
+void
+region_model::print_svalue (svalue_id sid, pretty_printer *pp) const
+{
+  get_svalue (sid)->print (*this, sid, pp);
+}
+
+/* Dump a .dot representation of this region_model to PP, showing
+   the values and the hierarchy of regions.  */
+
+void
+region_model::dump_dot_to_pp (pretty_printer *pp) const
+{
+  graphviz_out gv (pp);
+
+  pp_string (pp, "digraph \"");
+  pp_write_text_to_stream (pp);
+  pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/false);
+  pp_string (pp, "\" {\n");
+
+  gv.indent ();
+
+  pp_string (pp, "overlap=false;\n");
+  pp_string (pp, "compound=true;\n");
+
+  int i;
+
+  svalue *svalue;
+  FOR_EACH_VEC_ELT (m_svalues, i, svalue)
+    svalue->dump_dot_to_pp (*this, svalue_id::from_int (i), pp);
+
+  region *region;
+  FOR_EACH_VEC_ELT (m_regions, i, region)
+    region->dump_dot_to_pp (*this, region_id::from_int (i), pp);
+
+  /* TODO: constraints.  */
+
+  /* Terminate "digraph" */
+  gv.outdent ();
+  pp_string (pp, "}");
+  pp_newline (pp);
+}
+
+/* Dump a .dot representation of this region_model to FP.  */
+
+void
+region_model::dump_dot_to_file (FILE *fp) const
+{
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  pp.buffer->stream = fp;
+  dump_dot_to_pp (&pp);
+  pp_flush (&pp);
+}
+
+/* Dump a .dot representation of this region_model to PATH.  */
+
+void
+region_model::dump_dot (const char *path) const
+{
+  FILE *fp = fopen (path, "w");
+  dump_dot_to_file (fp);
+  fclose (fp);
+}
+
+/* Dump a multiline representation of this model to PP, showing the
+   region hierarchy, the svalues, and any constraints.
+
+   If SUMMARIZE is true, show only the most pertient information,
+   in a form that attempts to be less verbose.
+   Otherwise, show all information.  */
+
+void
+region_model::dump_to_pp (pretty_printer *pp, bool summarize) const
+{
+  if (summarize)
+    {
+      bool is_first = true;
+      region_id frame_id = get_current_frame_id ();
+      frame_region *frame = get_region <frame_region> (frame_id);
+      if (frame)
+	dump_summary_of_map (pp, frame, &is_first);
+
+      region_id globals_id = get_globals_region_id ();
+      map_region *globals = get_region <map_region> (globals_id);
+      if (globals)
+	dump_summary_of_map (pp, globals, &is_first);
+
+      unsigned i;
+
+      equiv_class *ec;
+      FOR_EACH_VEC_ELT (m_constraints->m_equiv_classes, i, ec)
+	{
+	  for (unsigned j = 0; j < ec->m_vars.length (); j++)
+	    {
+	      svalue_id lhs_sid = ec->m_vars[j];
+	      tree lhs_tree = get_representative_tree (lhs_sid);
+	      if (lhs_tree == NULL_TREE)
+		continue;
+	      for (unsigned k = j + 1; k < ec->m_vars.length (); k++)
+		{
+		  svalue_id rhs_sid = ec->m_vars[k];
+		  tree rhs_tree = get_representative_tree (rhs_sid);
+		  if (rhs_tree
+		      && !(CONSTANT_CLASS_P (lhs_tree)
+			   && CONSTANT_CLASS_P (rhs_tree)))
+		    {
+		      dump_separator (pp, &is_first);
+		      PUSH_IGNORE_WFORMAT
+			pp_printf (pp, "%E == %E", lhs_tree, rhs_tree);
+		      POP_IGNORE_WFORMAT
+		    }
+		}
+	    }
+	}
+
+      constraint *c;
+      FOR_EACH_VEC_ELT (m_constraints->m_constraints, i, c)
+	{
+	  const equiv_class &lhs = c->m_lhs.get_obj (*m_constraints);
+	  const equiv_class &rhs = c->m_rhs.get_obj (*m_constraints);
+	  svalue_id lhs_sid = lhs.get_representative ();
+	  svalue_id rhs_sid = rhs.get_representative ();
+	  tree lhs_tree = get_representative_tree (lhs_sid);
+	  tree rhs_tree = get_representative_tree (rhs_sid);
+	  if (lhs_tree && rhs_tree
+	      && !(CONSTANT_CLASS_P (lhs_tree) && CONSTANT_CLASS_P (rhs_tree)))
+	    {
+	      dump_separator (pp, &is_first);
+	      PUSH_IGNORE_WFORMAT
+		pp_printf (pp, "%E %s %E",
+			   lhs_tree, constraint_op_code (c->m_op), rhs_tree);
+	      POP_IGNORE_WFORMAT
+	   }
+	}
+
+      return;
+    }
+
+  get_region (m_root_rid)->dump_to_pp (*this, m_root_rid, pp, "", true);
+
+  pp_string (pp, "svalues:");
+  pp_newline (pp);
+  int i;
+  svalue *svalue;
+  FOR_EACH_VEC_ELT (m_svalues, i, svalue)
+    {
+      pp_string (pp, "  ");
+      svalue_id sid = svalue_id::from_int (i);
+      print_svalue (sid, pp);
+      pp_newline (pp);
+    }
+
+  pp_string (pp, "constraint manager:");
+  pp_newline (pp);
+  m_constraints->dump_to_pp (pp);
+}
+
+/* Dump a multiline representation of this model to FILE.  */
+
+void
+region_model::dump (FILE *fp, bool summarize) const
+{
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  pp_show_color (&pp) = pp_show_color (global_dc->printer);
+  pp.buffer->stream = fp;
+  dump_to_pp (&pp, summarize);
+  pp_flush (&pp);
+}
+
+/* Dump a multiline representation of this model to stderr.  */
+
+DEBUG_FUNCTION void
+region_model::dump (bool summarize) const
+{
+  dump (stderr, summarize);
+}
+
+/* Dump RMODEL fully to stderr (i.e. without summarization).  */
+
+DEBUG_FUNCTION void
+region_model::debug () const
+{
+  dump (false);
+}
+
+/* Dump VEC to PP, in the form "{VEC elements}: LABEL".  */
+
+static void
+dump_vec_of_tree (pretty_printer *pp,
+		  bool *is_first,
+		  const auto_vec<tree> &vec,
+		  const char *label)
+{
+  if (vec.length () == 0)
+    return;
+
+  dump_separator (pp, is_first);
+  pp_printf (pp, "{");
+  unsigned i;
+  tree key;
+  FOR_EACH_VEC_ELT (vec, i, key)
+    {
+      if (i > 0)
+	pp_string (pp, ", ");
+
+      PUSH_IGNORE_WFORMAT
+	pp_printf (pp, "%E", key);
+      POP_IGNORE_WFORMAT
+    }
+  pp_printf (pp, "}: %s", label);
+}
+
+/* Dump *MAP_REGION to PP in compact form, updating *IS_FIRST.
+   Subroutine of region_model::dump_to_pp for use on stack frames and for
+   the "globals" region.  */
+
+void
+region_model::dump_summary_of_map (pretty_printer *pp,
+				   map_region *map_region,
+				   bool *is_first) const
+{
+  /* Get the keys, sorted by tree_cmp.  In particular, this ought
+     to alphabetize any decls.  */
+  auto_vec<tree> keys (map_region->elements ());
+  for (map_region::iterator_t iter = map_region->begin ();
+       iter != map_region->end ();
+       ++iter)
+    {
+      tree key_a = (*iter).first;
+      keys.quick_push (key_a);
+    }
+  keys.qsort (tree_cmp);
+
+  /* Print pointers, constants, and poisoned values that aren't "uninit";
+     gather keys for unknown and uninit values.  */
+  unsigned i;
+  tree key;
+  auto_vec<tree> unknown_keys;
+  auto_vec<tree> uninit_keys;
+  FOR_EACH_VEC_ELT (keys, i, key)
+    {
+      region_id child_rid = *map_region->get (key);
+
+      region *child_region = get_region (child_rid);
+      if (!child_region)
+	continue;
+      svalue_id sid = child_region->get_value_direct ();
+      if (sid.null_p ())
+	continue;
+      svalue *sval = get_svalue (sid);
+      switch (sval->get_kind ())
+	{
+	default:
+	  gcc_unreachable ();
+	case SK_REGION:
+	  {
+	    region_svalue *region_sval = as_a <region_svalue *> (sval);
+	    region_id pointee_rid = region_sval->get_pointee ();
+	    tree pointee = get_representative_path_var (pointee_rid).m_tree;
+	    dump_separator (pp, is_first);
+	    PUSH_IGNORE_WFORMAT
+	      if (pointee)
+		pp_printf (pp, "%E: &%E", key, pointee);
+	      else
+		pp_printf (pp, "%E: NULL", key);
+	    POP_IGNORE_WFORMAT
+	  }
+	  break;
+	case SK_CONSTANT:
+	  dump_separator (pp, is_first);
+	  PUSH_IGNORE_WFORMAT
+	    pp_printf (pp, "%E: %E", key,
+		       sval->dyn_cast_constant_svalue ()->get_constant ());
+	  POP_IGNORE_WFORMAT
+	  break;
+	case SK_UNKNOWN:
+	  unknown_keys.safe_push (key);
+	  break;
+	case SK_POISONED:
+	  {
+	    poisoned_svalue *poisoned_sval = as_a <poisoned_svalue *> (sval);
+	    enum poison_kind pkind = poisoned_sval->get_poison_kind ();
+	    if (pkind == POISON_KIND_UNINIT)
+	      uninit_keys.safe_push (key);
+	    else
+	      {
+		dump_separator (pp, is_first);
+		PUSH_IGNORE_WFORMAT
+		  pp_printf (pp, "%E: %s", key, poison_kind_to_str (pkind));
+		POP_IGNORE_WFORMAT
+	      }
+	  }
+	  break;
+	case SK_SETJMP:
+	  dump_separator (pp, is_first);
+	  pp_printf (pp, "setjmp: EN: %i",
+		     sval->dyn_cast_setjmp_svalue ()->get_index ());
+	  break;
+	}
+    }
+
+  /* Print unknown and uninitialized values in consolidated form.  */
+  dump_vec_of_tree (pp, is_first, unknown_keys, "unknown");
+  dump_vec_of_tree (pp, is_first, uninit_keys, "uninit");
+}
+
+/* Assert that this object is valid.  */
+
+void
+region_model::validate () const
+{
+  /* Skip this in a release build.  */
+#if !CHECKING_P
+  return;
+#endif
+
+  m_constraints->validate ();
+
+  unsigned i;
+  region *r;
+  FOR_EACH_VEC_ELT (m_regions, i, r)
+    r->validate (this);
+
+  // TODO: anything else?
+
+  /* Verify that the stack region (if any) has an "uninitialized" value.  */
+  region *stack_region = get_root_region ()->get_stack_region (this);
+  if (stack_region)
+    {
+      svalue_id stack_value_sid = stack_region->get_value_direct ();
+      svalue *stack_value = get_svalue (stack_value_sid);
+      gcc_assert (stack_value->get_kind () == SK_POISONED);
+      poisoned_svalue *subclass = stack_value->dyn_cast_poisoned_svalue ();
+      gcc_assert (subclass);
+      gcc_assert (subclass->get_poison_kind () == POISON_KIND_UNINIT);
+    }
+}
+
+/* Global data for use by svalue_id_cmp_by_constant_svalue.  */
+
+static region_model *svalue_id_cmp_by_constant_svalue_model = NULL;
+
+/* Comparator for use by region_model::canonicalize.  */
+
+static int
+svalue_id_cmp_by_constant_svalue (const void *p1, const void *p2)
+{
+  const svalue_id *sid1 = (const svalue_id *)p1;
+  const svalue_id *sid2 = (const svalue_id *)p2;
+  gcc_assert (!sid1->null_p ());
+  gcc_assert (!sid2->null_p ());
+  gcc_assert (svalue_id_cmp_by_constant_svalue_model);
+  const svalue &sval1
+    = *svalue_id_cmp_by_constant_svalue_model->get_svalue (*sid1);
+  const svalue &sval2
+    = *svalue_id_cmp_by_constant_svalue_model->get_svalue (*sid2);
+  gcc_assert (sval1.get_kind () == SK_CONSTANT);
+  gcc_assert (sval2.get_kind () == SK_CONSTANT);
+
+  tree cst1 = ((const constant_svalue &)sval1).get_constant ();
+  tree cst2 = ((const constant_svalue &)sval2).get_constant ();
+  return tree_cmp (cst1, cst2);
+}
+
+/* Reorder the regions and svalues into a deterministic "canonical" order,
+   to maximize the chance of equality.
+   If non-NULL, notify CTXT about the svalue id remapping.  */
+
+void
+region_model::canonicalize (region_model_context *ctxt)
+{
+  /* Walk all regions and values in a deterministic order, visiting
+     rids and sids, generating a rid and sid map.  */
+  canonicalization c (*this);
+
+  /* (1): Walk all svalues, putting constants first, sorting the constants
+     (thus imposing an ordering on any constants that are purely referenced
+     by constraints).
+     Ignore other svalues for now.  */
+  {
+    unsigned i;
+    auto_vec<svalue_id> sids;
+    svalue *sval;
+    FOR_EACH_VEC_ELT (m_svalues, i, sval)
+      {
+	if (sval->get_kind () == SK_CONSTANT)
+	  sids.safe_push (svalue_id::from_int (i));
+      }
+    svalue_id_cmp_by_constant_svalue_model = this;
+    sids.qsort (svalue_id_cmp_by_constant_svalue);
+    svalue_id_cmp_by_constant_svalue_model = NULL;
+    svalue_id *sid;
+    FOR_EACH_VEC_ELT (sids, i, sid)
+      c.walk_sid (*sid);
+  }
+
+  /* (2): Walk all regions (and thus their values) in a deterministic
+     order.  */
+  c.walk_rid (m_root_rid);
+
+  /* (3): Ensure we've visited everything, as we don't want to purge
+     at this stage.  Anything we visit for the first time here has
+     arbitrary order.  */
+  {
+    unsigned i;
+    region *region;
+    FOR_EACH_VEC_ELT (m_regions, i, region)
+      c.walk_rid (region_id::from_int (i));
+    svalue *sval;
+    FOR_EACH_VEC_ELT (m_svalues, i, sval)
+      c.walk_sid (svalue_id::from_int (i));
+  }
+
+  /* (4): We now have a reordering of the regions and values.
+     Apply it.  */
+  remap_svalue_ids (c.m_sid_map);
+  remap_region_ids (c.m_rid_map);
+  if (ctxt)
+    ctxt->remap_svalue_ids (c.m_sid_map);
+
+  /* (5): Canonicalize the constraint_manager (it has already had its
+     svalue_ids remapped above).  This makes use of the new svalue_id
+     values, and so must happen last.  */
+  m_constraints->canonicalize (get_num_svalues ());
+
+  validate ();
+}
+
+/* Return true if this region_model is in canonical form.  */
+
+bool
+region_model::canonicalized_p () const
+{
+  region_model copy (*this);
+  copy.canonicalize (NULL);
+  return *this == copy;
+}
+
+/* A subclass of pending_diagnostic for complaining about uses of
+   poisoned values.  */
+
+class poisoned_value_diagnostic
+: public pending_diagnostic_subclass<poisoned_value_diagnostic>
+{
+public:
+  poisoned_value_diagnostic (tree expr, enum poison_kind pkind)
+  : m_expr (expr), m_pkind (pkind)
+  {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "poisoned_value_diagnostic"; }
+
+  bool operator== (const poisoned_value_diagnostic &other) const
+  {
+    return m_expr == other.m_expr;
+  }
+
+  bool emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    switch (m_pkind)
+      {
+      default:
+	gcc_unreachable ();
+      case POISON_KIND_UNINIT:
+	{
+	  diagnostic_metadata m;
+	  m.add_cwe (457); /* "CWE-457: Use of Uninitialized Variable".  */
+	  return warning_at (rich_loc, m,
+			     OPT_Wanalyzer_use_of_uninitialized_value,
+			     "use of uninitialized value %qE",
+			     m_expr);
+	}
+	break;
+      case POISON_KIND_FREED:
+	{
+	  diagnostic_metadata m;
+	  m.add_cwe (416); /* "CWE-416: Use After Free".  */
+	  return warning_at (rich_loc, m,
+			     OPT_Wanalyzer_use_after_free,
+			     "use after %<free%> of %qE",
+			     m_expr);
+	}
+	break;
+      case POISON_KIND_POPPED_STACK:
+	{
+	  diagnostic_metadata m;
+	  /* TODO: which CWE?  */
+	  return warning_at (rich_loc, m,
+			     OPT_Wanalyzer_use_of_pointer_in_stale_stack_frame,
+			     "use of pointer %qE within stale stack frame",
+			     m_expr);
+	}
+	break;
+      }
+  }
+
+  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  {
+    switch (m_pkind)
+      {
+      default:
+	gcc_unreachable ();
+      case POISON_KIND_UNINIT:
+	return ev.formatted_print ("use of uninitialized value %qE here",
+				   m_expr);
+      case POISON_KIND_FREED:
+	return ev.formatted_print ("use after %<free%> of %qE here",
+				   m_expr);
+      case POISON_KIND_POPPED_STACK:
+	return ev.formatted_print
+	  ("use of pointer %qE within stale stack frame here",
+	   m_expr);
+      }
+  }
+
+private:
+  tree m_expr;
+  enum poison_kind m_pkind;
+};
+
+/* Determine if EXPR is poisoned, and if so, queue a diagnostic to CTXT.  */
+
+void
+region_model::check_for_poison (tree expr, region_model_context *ctxt)
+{
+  if (!ctxt)
+    return;
+
+  // TODO: this is disabled for now (too many false positives)
+  return;
+
+  svalue_id expr_sid = get_rvalue (expr, ctxt);
+  gcc_assert (!expr_sid.null_p ());
+  svalue *expr_svalue = get_svalue (expr_sid);
+  gcc_assert (expr_svalue);
+  if (const poisoned_svalue *poisoned_sval
+	= expr_svalue->dyn_cast_poisoned_svalue ())
+    {
+      enum poison_kind pkind = poisoned_sval->get_poison_kind ();
+      ctxt->warn (new poisoned_value_diagnostic (expr, pkind));
+    }
+}
+
+/* Update this model for the ASSIGN stmt, using CTXT to report any
+   diagnostics.  */
+
+void
+region_model::on_assignment (const gassign *assign, region_model_context *ctxt)
+{
+  tree lhs = gimple_assign_lhs (assign);
+  tree rhs1 = gimple_assign_rhs1 (assign);
+
+  region_id lhs_rid = get_lvalue (lhs, ctxt);
+
+  /* Check for uses of poisoned values.  */
+  switch (get_gimple_rhs_class (gimple_expr_code (assign)))
+    {
+    case GIMPLE_INVALID_RHS:
+      gcc_unreachable ();
+      break;
+    case GIMPLE_TERNARY_RHS:
+      check_for_poison (gimple_assign_rhs3 (assign), ctxt);
+      /* Fallthru */
+    case GIMPLE_BINARY_RHS:
+      check_for_poison (gimple_assign_rhs2 (assign), ctxt);
+      /* Fallthru */
+    case GIMPLE_UNARY_RHS:
+    case GIMPLE_SINGLE_RHS:
+      check_for_poison (gimple_assign_rhs1 (assign), ctxt);
+    }
+
+  if (lhs_rid.null_p ())
+    return;
+  // TODO: issue a warning for this case
+
+  enum tree_code op = gimple_assign_rhs_code (assign);
+  switch (op)
+    {
+    default:
+      {
+	if (0)
+	  sorry_at (assign->location, "unhandled assignment op: %qs",
+		    get_tree_code_name (op));
+	set_to_new_unknown_value (lhs_rid, TREE_TYPE (lhs), ctxt);
+      }
+      break;
+
+    case BIT_FIELD_REF:
+      {
+	// TODO
+      }
+      break;
+
+    case CONSTRUCTOR:
+      {
+	/* e.g. "x ={v} {CLOBBER};"  */
+	// TODO
+      }
+      break;
+
+    case POINTER_PLUS_EXPR:
+      {
+	/* e.g. "_1 = a_10(D) + 12;" */
+	tree ptr = rhs1;
+	tree offset = gimple_assign_rhs2 (assign);
+
+	svalue_id ptr_sid = get_rvalue (ptr, ctxt);
+	svalue_id offset_sid = get_rvalue (offset, ctxt);
+	region_id element_rid
+	  = get_or_create_pointer_plus_expr (TREE_TYPE (TREE_TYPE (ptr)),
+					     ptr_sid, offset_sid,
+					     ctxt);
+	svalue_id element_ptr_sid
+	  = get_or_create_ptr_svalue (TREE_TYPE (ptr), element_rid);
+	set_value (lhs_rid, element_ptr_sid, ctxt);
+      }
+      break;
+
+    case POINTER_DIFF_EXPR:
+      {
+	/* e.g. "_1 = p_2(D) - q_3(D);".  */
+
+	/* TODO.  */
+
+	set_to_new_unknown_value (lhs_rid, TREE_TYPE (lhs), ctxt);
+      }
+      break;
+
+    case ADDR_EXPR:
+      {
+	/* LHS = &RHS;  */
+	svalue_id ptr_sid = get_rvalue (rhs1, ctxt);
+	set_value (lhs_rid, ptr_sid, ctxt);
+      }
+      break;
+
+    case MEM_REF:
+      {
+	region_id rhs_rid = get_lvalue (rhs1, ctxt);
+	svalue_id rhs_sid
+	  = get_region (rhs_rid)->get_value (*this, true, ctxt);
+	set_value (lhs_rid, rhs_sid, ctxt);
+      }
+      break;
+
+    case REAL_CST:
+    case INTEGER_CST:
+    case ARRAY_REF:
+      {
+	/* LHS = RHS;  */
+	svalue_id cst_sid = get_rvalue (rhs1, ctxt);
+	set_value (lhs_rid, cst_sid, ctxt);
+      }
+      break;
+
+    case FIX_TRUNC_EXPR:
+    case FLOAT_EXPR:
+    case NOP_EXPR:
+      // cast: TODO
+      // fall though for now
+    case SSA_NAME:
+    case VAR_DECL:
+    case PARM_DECL:
+      {
+	/* LHS = VAR;  */
+	svalue_id var_sid = get_rvalue (rhs1, ctxt);
+	set_value (lhs_rid, var_sid, ctxt);
+      }
+      break;
+
+    case EQ_EXPR:
+    case GE_EXPR:
+    case LE_EXPR:
+    case NE_EXPR:
+    case GT_EXPR:
+    case LT_EXPR:
+      {
+	tree rhs2 = gimple_assign_rhs2 (assign);
+
+	// TODO: constraints between svalues
+	svalue_id rhs1_sid = get_rvalue (rhs1, ctxt);
+	svalue_id rhs2_sid = get_rvalue (rhs2, ctxt);
+
+	tristate t = eval_condition (rhs1_sid, op, rhs2_sid);
+	if (t.is_known ())
+	  set_value (lhs_rid,
+		     get_rvalue (t.is_true ()
+				 ? boolean_true_node
+				 : boolean_false_node,
+				 ctxt),
+		     ctxt);
+	else
+	  set_to_new_unknown_value (lhs_rid, TREE_TYPE (lhs), ctxt);
+      }
+      break;
+
+    case NEGATE_EXPR:
+    case BIT_NOT_EXPR:
+      {
+	// TODO: unary ops
+
+	// TODO: constant?
+
+	set_to_new_unknown_value (lhs_rid, TREE_TYPE (lhs), ctxt);
+      }
+      break;
+
+    case PLUS_EXPR:
+    case MINUS_EXPR:
+    case MULT_EXPR:
+    case TRUNC_DIV_EXPR:
+    case TRUNC_MOD_EXPR:
+    case LSHIFT_EXPR:
+    case RSHIFT_EXPR:
+    case BIT_IOR_EXPR:
+    case BIT_XOR_EXPR:
+    case BIT_AND_EXPR:
+    case MIN_EXPR:
+    case MAX_EXPR:
+      {
+	/* Binary ops.  */
+	tree rhs2 = gimple_assign_rhs2 (assign);
+
+	svalue_id rhs1_sid = get_rvalue (rhs1, ctxt);
+	svalue_id rhs2_sid = get_rvalue (rhs2, ctxt);
+
+	if (tree rhs1_cst = maybe_get_constant (rhs1_sid))
+	  if (tree rhs2_cst = maybe_get_constant (rhs2_sid))
+	    {
+	      tree result = fold_build2 (op, TREE_TYPE (lhs),
+					 rhs1_cst, rhs2_cst);
+	      if (CONSTANT_CLASS_P (result))
+		{
+		  svalue_id result_sid
+		    = get_or_create_constant_svalue (result);
+		  set_value (lhs_rid, result_sid, ctxt);
+		  return;
+		}
+	    }
+	set_to_new_unknown_value (lhs_rid, TREE_TYPE (lhs), ctxt);
+      }
+      break;
+
+    case COMPONENT_REF:
+      {
+	/* LHS = op0.op1;  */
+	region_id child_rid = get_lvalue (rhs1, ctxt);
+	svalue_id child_sid
+	  = get_region (child_rid)->get_value (*this, true, ctxt);
+	set_value (lhs_rid, child_sid, ctxt);
+      }
+      break;
+    }
+}
+
+/* Update this model for the CALL stmt, using CTXT to report any
+   diagnostics - the first half.
+
+   Updates to the region_model that should be made *before* sm-states
+   are updated are done here; other updates to the region_model are done
+   in region_model::on_call_post.  */
+
+void
+region_model::on_call_pre (const gcall *call, region_model_context *ctxt)
+{
+  region_id lhs_rid;
+  tree lhs_type = NULL_TREE;
+  if (tree lhs = gimple_call_lhs (call))
+    {
+      lhs_rid = get_lvalue (lhs, ctxt);
+      lhs_type = TREE_TYPE (lhs);
+    }
+
+  /* Check for uses of poisoned values.
+     For now, special-case "free", to avoid warning about "use-after-free"
+     when "double free" would be more precise.  */
+  if (!is_named_call_p (call, "free", 1))
+    for (unsigned i = 0; i < gimple_call_num_args (call); i++)
+      check_for_poison (gimple_call_arg (call, i), ctxt);
+
+  if (is_named_call_p (call, "malloc", 1))
+    {
+      // TODO: capture size as a svalue?
+      region_id new_rid = add_new_malloc_region ();
+      if (!lhs_rid.null_p ())
+	{
+	  svalue_id ptr_sid
+	    = get_or_create_ptr_svalue (lhs_type, new_rid);
+	  set_value (lhs_rid, ptr_sid, ctxt);
+	}
+      return;
+    }
+  else if (is_named_call_p (call, "__builtin_alloca", 1))
+    {
+      region_id frame_rid = get_current_frame_id ();
+      region_id new_rid = add_region (new symbolic_region (frame_rid, false));
+      if (!lhs_rid.null_p ())
+	{
+	  svalue_id ptr_sid
+	    = get_or_create_ptr_svalue (lhs_type, new_rid);
+	  set_value (lhs_rid, ptr_sid, ctxt);
+	}
+      return;
+    }
+  else if (is_named_call_p (call, "strlen", 1))
+    {
+      region_id buf_rid = deref_rvalue (gimple_call_arg (call, 0), ctxt);
+      svalue_id buf_sid = get_region (buf_rid)->get_value (*this, true, ctxt);
+      if (tree cst_expr = maybe_get_constant (buf_sid))
+	{
+	  if (TREE_CODE (cst_expr) == STRING_CST
+	      && !lhs_rid.null_p ())
+	    {
+	      /* TREE_STRING_LENGTH is sizeof, not strlen.  */
+	      int sizeof_cst = TREE_STRING_LENGTH (cst_expr);
+	      int strlen_cst = sizeof_cst - 1;
+	      tree t_cst = build_int_cst (lhs_type, strlen_cst);
+	      svalue_id result_sid
+		= get_or_create_constant_svalue (t_cst);
+	      set_value (lhs_rid, result_sid, ctxt);
+	      return;
+	    }
+	}
+      /* Otherwise an unknown value.  */
+    }
+  else if (is_named_call_p (call, "__analyzer_dump_num_heap_regions", 0))
+    {
+      /* Handle the builtin "__analyzer_dump_num_heap_regions" by emitting
+	 a warning (for use in DejaGnu tests).  */
+      int num_heap_regions = 0;
+      region_id heap_rid = get_root_region ()->ensure_heap_region (this);
+      unsigned i;
+      region *region;
+      FOR_EACH_VEC_ELT (m_regions, i, region)
+	if (region->get_parent () == heap_rid)
+	  num_heap_regions++;
+      /* Use quotes to ensure the output isn't truncated.  */
+      warning_at (call->location, 0,
+		  "num heap regions: %qi", num_heap_regions);
+    }
+
+  /* Unrecognized call.  */
+
+  /* Unknown return value.  */
+  if (!lhs_rid.null_p ())
+    set_to_new_unknown_value (lhs_rid, lhs_type, ctxt);
+
+  /* TODO: also, any pointer arguments might have been written through,
+     or the things they point to (implying a graph traversal, which
+     presumably we need to do before overwriting the old value).  */
+}
+
+/* Update this model for the CALL stmt, using CTXT to report any
+   diagnostics - the second half.
+
+   Updates to the region_model that should be made *after* sm-states
+   are updated are done here; other updates to the region_model are done
+   in region_model::on_call_pre.  */
+
+void
+region_model::on_call_post (const gcall *call, region_model_context *ctxt)
+{
+  /* Update for "free" here, after sm-handling.
+
+     If the ptr points to an underlying heap region, delete the region,
+     poisoning pointers to it and regions within it.
+
+     We delay this until after sm-state has been updated so that the
+     sm-handling can transition all of the various casts of the pointer
+     to a "freed" state *before* we delete the related region here.
+
+     This has to be done here so that the sm-handling can use the fact
+     that they point to the same region to establish that they are equal
+     (in region_model::eval_condition_without_cm), and thus transition
+     all pointers to the region to the "freed" state together, regardless
+     of casts.  */
+  if (is_named_call_p (call, "free", 1))
+    {
+      tree ptr = gimple_call_arg (call, 0);
+      svalue_id ptr_sid = get_rvalue (ptr, ctxt);
+      svalue *ptr_sval = get_svalue (ptr_sid);
+      if (region_svalue *ptr_to_region_sval
+	  = ptr_sval->dyn_cast_region_svalue ())
+	{
+	  /* If the ptr points to an underlying heap region, delete it,
+	     poisoning pointers.  */
+	  region_id pointee_rid = ptr_to_region_sval->get_pointee ();
+	  region_id heap_rid = get_root_region ()->ensure_heap_region (this);
+	  if (!pointee_rid.null_p ()
+	      && get_region (pointee_rid)->get_parent () == heap_rid)
+	    {
+	      purge_stats stats;
+	      delete_region_and_descendents (pointee_rid,
+					     POISON_KIND_FREED,
+					     &stats, ctxt->get_logger ());
+	      purge_unused_svalues (&stats, ctxt);
+	      validate ();
+	      // TODO: do anything with stats?
+	    }
+	}
+      return;
+    }
+}
+
+/* Update this model for the RETURN_STMT, using CTXT to report any
+   diagnostics.  */
+
+void
+region_model::on_return (const greturn *return_stmt, region_model_context *ctxt)
+{
+  tree callee = get_current_function ()->decl;
+  tree lhs = DECL_RESULT (callee);
+  tree rhs = gimple_return_retval (return_stmt);
+
+  if (lhs && rhs)
+    set_value (get_lvalue (lhs, ctxt), get_rvalue (rhs, ctxt), ctxt);
+}
+
+/* Update this model for a call and return of "setjmp" at CALL within ENODE,
+   using CTXT to report any diagnostics.
+
+   This is for the initial direct invocation of setjmp (which returns 0),
+   as opposed to any second return due to longjmp.  */
+
+void
+region_model::on_setjmp (const gcall *call, const exploded_node *enode,
+			 region_model_context *ctxt)
+{
+  region_id buf_rid = deref_rvalue (gimple_call_arg (call, 0), ctxt);
+  region *buf = get_region (buf_rid);
+
+  /* Create a setjmp_svalue for ENODE and store it in BUF_RID's region.  */
+  if (buf)
+    {
+      svalue *sval = new setjmp_svalue (enode, buf->get_type ());
+      svalue_id new_sid = add_svalue (sval);
+      set_value (buf_rid, new_sid, ctxt);
+    }
+
+  /* Direct calls to setjmp return 0.  */
+  if (tree lhs = gimple_call_lhs (call))
+    {
+      tree zero = build_int_cst (TREE_TYPE (lhs), 0);
+      svalue_id new_sid = get_or_create_constant_svalue (zero);
+      region_id lhs_rid = get_lvalue (lhs, ctxt);
+      set_value (lhs_rid, new_sid, ctxt);
+    }
+}
+
+/* Update this region_model for rewinding from a "longjmp" at LONGJMP_CALL
+   to a "setjmp" at SETJMP_CALL where the final stack depth should be
+   SETJMP_STACK_DEPTH.  Purge any stack frames, potentially reporting on
+   leaks to CTXT.  */
+
+void
+region_model::on_longjmp (const gcall *longjmp_call, const gcall *setjmp_call,
+			  int setjmp_stack_depth,
+			  region_model_context *ctxt)
+{
+  /* Evaluate the val, using the frame of the "longjmp".  */
+  tree fake_retval = gimple_call_arg (longjmp_call, 1);
+  svalue_id fake_retval_sid = get_rvalue (fake_retval, ctxt);
+
+  /* Pop any frames until we reach the stack depth of the function where
+     setjmp was called.  */
+  gcc_assert (get_stack_depth () >= setjmp_stack_depth);
+  while (get_stack_depth () > setjmp_stack_depth)
+    {
+      /* Don't purge unused svalues yet, as we're using fake_retval_sid.  */
+      pop_frame (false, NULL, ctxt);
+    }
+
+  gcc_assert (get_stack_depth () == setjmp_stack_depth);
+
+  /* Assign to LHS of "setjmp" in new_state.  */
+  if (tree lhs = gimple_call_lhs (setjmp_call))
+    {
+      /* Passing 0 as the val to longjmp leads to setjmp returning 1.  */
+      tree t_zero = build_int_cst (TREE_TYPE (fake_retval), 0);
+      svalue_id zero_sid = get_or_create_constant_svalue (t_zero);
+      tristate eq_zero = eval_condition (fake_retval_sid, EQ_EXPR, zero_sid);
+      /* If we have 0, use 1.  */
+      if (eq_zero.is_true ())
+	{
+	  tree t_one = build_int_cst (TREE_TYPE (fake_retval), 1);
+	  svalue_id one_sid = get_or_create_constant_svalue (t_one);
+	  fake_retval_sid = one_sid;
+	}
+      else
+	{
+	  /* Otherwise note that the value is nonzero.  */
+	  m_constraints->add_constraint (fake_retval_sid, NE_EXPR, zero_sid);
+	}
+
+      region_id lhs_rid = get_lvalue (lhs, ctxt);
+      set_value (lhs_rid, fake_retval_sid, ctxt);
+    }
+
+  /* Now that we've assigned the fake_retval, we can purge the unused
+     svalues, which could detect leaks.  */
+  purge_unused_svalues (NULL, ctxt, NULL);
+  validate ();
+}
+
+/* Update this region_model for a phi stmt of the form
+     LHS = PHI <...RHS...>.
+   where RHS is for the appropriate edge.  */
+
+void
+region_model::handle_phi (tree lhs, tree rhs, bool is_back_edge,
+			  region_model_context *ctxt)
+{
+  /* For now, don't bother tracking the .MEM SSA names.  */
+  if (tree var = SSA_NAME_VAR (lhs))
+    if (TREE_CODE (var) == VAR_DECL)
+      if (VAR_DECL_IS_VIRTUAL_OPERAND (var))
+	return;
+
+  svalue_id rhs_sid = get_rvalue (rhs, ctxt);
+
+  if (is_back_edge && get_svalue (rhs_sid)->get_kind () != SK_UNKNOWN)
+    {
+      /* If we have a back edge, we probably have a loop.
+	 Use an unknown value, to avoid effectively unrolling the
+	 loop.
+	 To terminate, we need to avoid generating a series of
+	 models with an unbounded monotonically increasing number of
+	 redundant unknown values; hence we need to purge svalues
+	 before inserting the state into the exploded graph, to
+	 collect unused svalues.  */
+      set_to_new_unknown_value (get_lvalue (lhs, ctxt), TREE_TYPE (lhs), ctxt);
+    }
+  else
+    set_value (get_lvalue (lhs, ctxt), rhs_sid, ctxt);
+}
+
+/* Implementation of region_model::get_lvalue; the latter adds type-checking.
+
+   Get the id of the region for PV within this region_model,
+   emitting any diagnostics to CTXT.  */
+
+region_id
+region_model::get_lvalue_1 (path_var pv, region_model_context *ctxt)
+{
+  tree expr = pv.m_tree;
+
+  gcc_assert (expr);
+
+  switch (TREE_CODE (expr))
+    {
+    default:
+      gcc_unreachable ();
+
+    case ARRAY_REF:
+      {
+	tree array = TREE_OPERAND (expr, 0);
+	tree index = TREE_OPERAND (expr, 1);
+#if 0
+	// TODO: operands 2 and 3, if present:
+	gcc_assert (TREE_OPERAND (expr, 2) == NULL_TREE);
+	gcc_assert (TREE_OPERAND (expr, 3) == NULL_TREE);
+#endif
+
+	region_id array_rid = get_lvalue (array, ctxt);
+	svalue_id index_sid = get_rvalue (index, ctxt);
+	array_region *array_reg = get_region<array_region> (array_rid);
+	return array_reg->get_element (this, array_rid, index_sid, ctxt);
+      }
+      break;
+
+    case MEM_REF:
+      {
+	tree ptr = TREE_OPERAND (expr, 0);
+	tree offset = TREE_OPERAND (expr, 1);
+	svalue_id ptr_sid = get_rvalue (ptr, ctxt);
+	svalue_id offset_sid = get_rvalue (offset, ctxt);
+	return get_or_create_mem_ref (TREE_TYPE (expr), ptr_sid,
+				      offset_sid, ctxt);
+      }
+      break;
+
+    case VAR_DECL:
+      /* Handle globals.  */
+      if (is_global_var (expr))
+	{
+	  region_id globals_rid
+	    = get_root_region ()->ensure_globals_region (this);
+	  map_region *globals = get_region<map_region> (globals_rid);
+	  region_id var_rid = globals->get_or_create (this, globals_rid, expr,
+						      TREE_TYPE (expr));
+	  return var_rid;
+	}
+
+      /* Fall through.  */
+
+    case SSA_NAME:
+    case PARM_DECL:
+    case RESULT_DECL:
+      {
+	gcc_assert (TREE_CODE (expr) == SSA_NAME
+		    || TREE_CODE (expr) == PARM_DECL
+		    || TREE_CODE (expr) == VAR_DECL
+		    || TREE_CODE (expr) == RESULT_DECL);
+
+	int stack_depth = pv.m_stack_depth;
+	stack_region *stack = get_root_region ()->get_stack_region (this);
+	gcc_assert (stack);
+	region_id frame_rid = stack->get_frame_rid (stack_depth);
+	frame_region *frame = get_region <frame_region> (frame_rid);
+	gcc_assert (frame);
+	region_id child_rid = frame->get_or_create (this, frame_rid, expr,
+						    TREE_TYPE (expr));
+	return child_rid;
+      }
+
+    case COMPONENT_REF:
+      {
+	/* obj.field  */
+	tree obj = TREE_OPERAND (expr, 0);
+	tree field = TREE_OPERAND (expr, 1);
+	region_id obj_rid = get_lvalue (obj, ctxt);
+	region_id struct_or_union_rid
+	  = get_or_create_view (obj_rid, TREE_TYPE (obj));
+	return get_field_region (struct_or_union_rid, field);
+      }
+      break;
+
+    case STRING_CST:
+      {
+	tree cst_type = TREE_TYPE (expr);
+	array_region *cst_region = new array_region (m_root_rid, cst_type);
+	region_id cst_rid = add_region (cst_region);
+	svalue_id cst_sid = get_or_create_constant_svalue (expr);
+	cst_region->set_value (*this, cst_rid, cst_sid, ctxt);
+	return cst_rid;
+      }
+      break;
+    }
+}
+
+/* Assert that SRC_TYPE can be converted to DST_TYPE as a no-op.  */
+
+#define ASSERT_COMPAT_TYPES(SRC_TYPE, DST_TYPE) \
+  gcc_checking_assert (useless_type_conversion_p ((SRC_TYPE), (DST_TYPE)))
+
+/* Get the id of the region for PV within this region_model,
+   emitting any diagnostics to CTXT.  */
+
+region_id
+region_model::get_lvalue (path_var pv, region_model_context *ctxt)
+{
+  if (pv.m_tree == NULL_TREE)
+    return region_id::null ();
+
+  region_id result_rid = get_lvalue_1 (pv, ctxt);
+  ASSERT_COMPAT_TYPES (get_region (result_rid)->get_type (),
+		       TREE_TYPE (pv.m_tree));
+  return result_rid;
+}
+
+/* Get the region_id for EXPR within this region_model (assuming the most
+   recent stack frame if it's a local).  */
+
+region_id
+region_model::get_lvalue (tree expr, region_model_context *ctxt)
+{
+  return get_lvalue (path_var (expr, get_stack_depth () - 1), ctxt);
+}
+
+/* Implementation of region_model::get_rvalue; the latter adds type-checking.
+
+   Get the value of PV within this region_model,
+   emitting any diagnostics to CTXT.  */
+
+svalue_id
+region_model::get_rvalue_1 (path_var pv, region_model_context *ctxt)
+{
+  gcc_assert (pv.m_tree);
+
+  switch (TREE_CODE (pv.m_tree))
+    {
+    default:
+      {
+	svalue *unknown_sval = new unknown_svalue (TREE_TYPE (pv.m_tree));
+	return add_svalue (unknown_sval);
+      }
+      break;
+
+    case ADDR_EXPR:
+      {
+	/* "&EXPR".  */
+	tree expr = pv.m_tree;
+	tree op0 = TREE_OPERAND (expr, 0);
+	if (TREE_CODE (op0) == FUNCTION_DECL)
+	  return get_svalue_for_fndecl (TREE_TYPE (expr), op0);
+	else if (TREE_CODE (op0) == LABEL_DECL)
+	  return get_svalue_for_label (TREE_TYPE (expr), op0);
+	region_id expr_rid = get_lvalue (op0, ctxt);
+	return get_or_create_ptr_svalue (TREE_TYPE (expr), expr_rid);
+      }
+      break;
+
+    case ARRAY_REF:
+      {
+	region_id element_rid = get_lvalue (pv, ctxt);
+	return get_region (element_rid)->get_value (*this, true, ctxt);
+      }
+
+    case INTEGER_CST:
+    case REAL_CST:
+    case STRING_CST:
+      return get_or_create_constant_svalue (pv.m_tree);
+
+    case COMPONENT_REF:
+    case MEM_REF:
+    case SSA_NAME:
+    case VAR_DECL:
+    case PARM_DECL:
+    case RESULT_DECL:
+      {
+	region_id var_rid = get_lvalue (pv, ctxt);
+	return get_region (var_rid)->get_value (*this, true, ctxt);
+      }
+    }
+}
+
+/* Get the value of PV within this region_model,
+   emitting any diagnostics to CTXT.  */
+
+svalue_id
+region_model::get_rvalue (path_var pv, region_model_context *ctxt)
+{
+  if (pv.m_tree == NULL_TREE)
+    return svalue_id::null ();
+  svalue_id result_sid = get_rvalue_1 (pv, ctxt);
+
+  ASSERT_COMPAT_TYPES (get_svalue (result_sid)->get_type (),
+		       TREE_TYPE (pv.m_tree));
+
+  return result_sid;
+}
+
+/* Get the value of EXPR within this region_model (assuming the most
+   recent stack frame if it's a local).  */
+
+svalue_id
+region_model::get_rvalue (tree expr, region_model_context *ctxt)
+{
+  return get_rvalue (path_var (expr, get_stack_depth () - 1), ctxt);
+}
+
+/* Return an svalue_id for a pointer to RID of type PTR_TYPE, reusing
+   existing pointer values if one is available.  */
+
+svalue_id
+region_model::get_or_create_ptr_svalue (tree ptr_type, region_id rid)
+{
+  /* Reuse existing region_svalue, if one of the right type is
+     available.  */
+  /* In theory we could stash a svalue_id in "region", but differing
+     pointer types muddles things.
+     For now, just do a linear search through all existing svalues.  */
+  int i;
+  svalue *svalue;
+  FOR_EACH_VEC_ELT (m_svalues, i, svalue)
+    if (region_svalue *ptr_svalue = svalue->dyn_cast_region_svalue ())
+      if (ptr_svalue->get_pointee () == rid
+	  && ptr_svalue->get_type () == ptr_type)
+	return svalue_id::from_int (i);
+
+  return add_svalue (new region_svalue (ptr_type, rid));
+}
+
+/* Return an svalue_id for a constant_svalue for CST_EXPR,
+   creating the constant_svalue if necessary.
+   The constant_svalue instances are reused, based on pointer equality
+   of trees  */
+
+svalue_id
+region_model::get_or_create_constant_svalue (tree cst_expr)
+{
+  gcc_assert (cst_expr);
+
+  /* Reuse one if it already exists.  */
+  // TODO: maybe store a map, rather than do linear search?
+  int i;
+  svalue *svalue;
+  FOR_EACH_VEC_ELT (m_svalues, i, svalue)
+    if (svalue->maybe_get_constant () == cst_expr)
+      return svalue_id::from_int (i);
+
+  svalue_id cst_sid = add_svalue (new constant_svalue (cst_expr));
+  return cst_sid;
+}
+
+/* Return an svalue_id for a region_svalue for FNDECL,
+   creating the function_region if necessary.  */
+
+svalue_id
+region_model::get_svalue_for_fndecl (tree ptr_type, tree fndecl)
+{
+  gcc_assert (TREE_CODE (fndecl) == FUNCTION_DECL);
+  region_id function_rid = get_region_for_fndecl (fndecl);
+  return get_or_create_ptr_svalue (ptr_type, function_rid);
+}
+
+/* Return a region_id for a function_region for FNDECL,
+   creating it if necessary.  */
+
+region_id
+region_model::get_region_for_fndecl (tree fndecl)
+{
+  gcc_assert (TREE_CODE (fndecl) == FUNCTION_DECL);
+
+  region_id code_rid = get_root_region ()->ensure_code_region (this);
+  code_region *code = get_root_region ()->get_code_region (this);
+
+  return code->get_or_create (this, code_rid, fndecl, TREE_TYPE (fndecl));
+}
+
+/* Return an svalue_id for a region_svalue for LABEL,
+   creating the label_region if necessary.  */
+
+svalue_id
+region_model::get_svalue_for_label (tree ptr_type, tree label)
+{
+  gcc_assert (TREE_CODE (label) == LABEL_DECL);
+  region_id label_rid = get_region_for_label (label);
+  return get_or_create_ptr_svalue (ptr_type, label_rid);
+}
+
+/* Return a region_id for a label_region for LABEL,
+   creating it if necessary.  */
+
+region_id
+region_model::get_region_for_label (tree label)
+{
+  gcc_assert (TREE_CODE (label) == LABEL_DECL);
+
+  tree fndecl = DECL_CONTEXT (label);
+  gcc_assert (fndecl && TREE_CODE (fndecl) == FUNCTION_DECL);
+
+  region_id func_rid = get_region_for_fndecl (fndecl);
+  function_region *func_reg = get_region <function_region> (func_rid);
+  return func_reg->get_or_create (this, func_rid, label, TREE_TYPE (label));
+}
+
+/* If the type of SID's underlying value is DST_TYPE, return SID.
+   Otherwise, attempt to create (or reuse) an svalue representing an access
+   of SID as a DST_TYPE and return that value's svalue_id.  */
+
+svalue_id
+region_model::maybe_cast_1 (tree dst_type, svalue_id sid)
+{
+  svalue *sval = get_svalue (sid);
+  tree src_type = sval->get_type ();
+  if (src_type == dst_type)
+    return sid;
+
+  if (POINTER_TYPE_P (dst_type)
+      && POINTER_TYPE_P (src_type))
+    {
+      /* Pointer to region.  */
+      if (region_svalue *ptr_sval = sval->dyn_cast_region_svalue ())
+	return get_or_create_ptr_svalue (dst_type, ptr_sval->get_pointee ());
+
+      /* Unknown pointer?   Get or create a new unknown pointer of the
+	 correct type, preserving the equality between the pointers.  */
+      if (sval->dyn_cast_unknown_svalue ())
+	{
+	  equiv_class &ec = m_constraints->get_equiv_class (sid);
+
+	  /* Look for an existing pointer of the correct type within the EC.  */
+	  int i;
+	  svalue_id *equiv_sid;
+	  FOR_EACH_VEC_ELT (ec.m_vars, i, equiv_sid)
+	    {
+	      svalue *equiv_val = get_svalue (*equiv_sid);
+	      if (equiv_val->get_type () == dst_type)
+		return *equiv_sid;
+	    }
+
+	  /* Otherwise, create a new unknown pointer of the correct type.  */
+	  svalue *unknown_sval = new unknown_svalue (dst_type);
+	  svalue_id new_ptr_sid = add_svalue (unknown_sval);
+	  m_constraints->add_constraint (sid, EQ_EXPR, new_ptr_sid);
+	  return new_ptr_sid;
+	}
+    }
+
+  /* Attempt to cast constants.  */
+  if (tree src_cst = sval->maybe_get_constant ())
+    {
+      tree dst = convert (dst_type, src_cst);
+      if (CONSTANT_CLASS_P (dst))
+	return get_or_create_constant_svalue (dst);
+    }
+
+  /* Otherwise, return a new unknown value.  */
+  svalue *unknown_sval = new unknown_svalue (dst_type);
+  return add_svalue (unknown_sval);
+}
+
+/* If the type of SID's underlying value is DST_TYPE, return SID.
+   Otherwise, attempt to create (or reuse) an svalue representing an access
+   of SID as a DST_TYPE and return that value's svalue_id.
+
+   If the result != SID, then call CTXT's on_cast vfunc (if CTXT is non-NULL),
+   so that sm-state can be propagated from SID to the result.  */
+
+svalue_id
+region_model::maybe_cast (tree dst_type, svalue_id sid,
+			  region_model_context *ctxt)
+{
+  svalue_id result = maybe_cast_1 (dst_type, sid);
+  if (result != sid)
+    if (ctxt)
+      {
+	/* Notify ctxt about a cast, so any sm-state can be copied.  */
+	ctxt->on_cast (sid, result);
+      }
+  return result;
+}
+
+/* Ensure that the region for OBJ_RID has a child region for FIELD;
+   return the child region's region_id.  */
+
+region_id
+region_model::get_field_region (region_id struct_or_union_rid, tree field)
+{
+  struct_or_union_region *sou_reg
+    = get_region<struct_or_union_region> (struct_or_union_rid);
+
+  /* Inherit constness from parent type.  */
+  const int qual_mask = TYPE_QUAL_CONST;
+  int sou_quals = TYPE_QUALS (sou_reg->get_type ()) & qual_mask;
+  tree field_type = TREE_TYPE (field);
+  tree field_type_with_quals = build_qualified_type (field_type, sou_quals);
+
+  // TODO: maybe convert to a vfunc?
+  if (sou_reg->get_kind () == RK_UNION)
+    {
+      /* Union.
+	 Get a view of the union as a whole, with the type of the field.  */
+      region_id view_rid
+	= get_or_create_view (struct_or_union_rid, field_type_with_quals);
+      return view_rid;
+    }
+  else
+    {
+      /* Struct.  */
+      region_id child_rid
+	= sou_reg->get_or_create (this, struct_or_union_rid, field,
+				  field_type_with_quals);
+      return child_rid;
+    }
+}
+
+/* Get a region_id for referencing PTR_SID, creating a region if need be, and
+   potentially generating warnings via CTXT.  */
+
+region_id
+region_model::deref_rvalue (svalue_id ptr_sid, region_model_context *ctxt)
+{
+  gcc_assert (!ptr_sid.null_p ());
+  svalue *ptr_svalue = get_svalue (ptr_sid);
+  gcc_assert (ptr_svalue);
+
+  switch (ptr_svalue->get_kind ())
+    {
+    case SK_REGION:
+      {
+	region_svalue *region_sval = as_a <region_svalue *> (ptr_svalue);
+	return region_sval->get_pointee ();
+      }
+
+    case SK_CONSTANT:
+      goto create_symbolic_region;
+
+    case SK_POISONED:
+      {
+	if (ctxt)
+	  if (tree ptr = get_representative_tree (ptr_sid))
+	    {
+	      poisoned_svalue *poisoned_sval
+		= as_a <poisoned_svalue *> (ptr_svalue);
+	      enum poison_kind pkind = poisoned_sval->get_poison_kind ();
+	      ctxt->warn (new poisoned_value_diagnostic (ptr, pkind));
+	    }
+	goto create_symbolic_region;
+      }
+
+    case SK_UNKNOWN:
+      {
+      create_symbolic_region:
+	/* We need a symbolic_region to represent this unknown region.
+	   We don't know if it on the heap, stack, or a global,
+	   so use the root region as parent.  */
+	region_id new_rid
+	  = add_region (new symbolic_region (m_root_rid, false));
+
+	/* We need to write the region back into the pointer,
+	   or we'll get a new, different region each time.
+	   We do this by changing the meaning of ptr_sid, replacing
+	   the unknown value with the ptr to the new region.
+	   We replace the meaning of the ID rather than simply writing
+	   to PTR's lvalue since there could be several places sharing
+	   the same unknown ptr value.  */
+	svalue *ptr_val
+	  = new region_svalue (ptr_svalue->get_type (), new_rid);
+	replace_svalue (ptr_sid, ptr_val);
+
+	return new_rid;
+      }
+
+    case SK_SETJMP:
+      goto create_symbolic_region;
+    }
+
+  gcc_unreachable ();
+}
+
+/* Get a region_id for referencing PTR, creating a region if need be, and
+   potentially generating warnings via CTXT.  */
+
+region_id
+region_model::deref_rvalue (tree ptr, region_model_context *ctxt)
+{
+  svalue_id ptr_sid = get_rvalue (ptr, ctxt);
+  return deref_rvalue (ptr_sid, ctxt);
+}
+
+/* Set the value of the region given by LHS_RID to the value given
+   by RHS_SID.  */
+
+void
+region_model::set_value (region_id lhs_rid, svalue_id rhs_sid,
+			 region_model_context *ctxt)
+{
+  gcc_assert (!lhs_rid.null_p ());
+  gcc_assert (!rhs_sid.null_p ());
+  get_region (lhs_rid)->set_value (*this, lhs_rid, rhs_sid, ctxt);
+}
+
+/* Determine what is known about the condition "LHS_SID OP RHS_SID" within
+   this model.  */
+
+tristate
+region_model::eval_condition (svalue_id lhs_sid,
+			      enum tree_code op,
+			      svalue_id rhs_sid) const
+{
+  tristate ts = eval_condition_without_cm (lhs_sid, op, rhs_sid);
+
+  if (ts.is_known ())
+    return ts;
+
+  /* Otherwise, try constraints.  */
+  return m_constraints->eval_condition (lhs_sid, op, rhs_sid);
+}
+
+/* Determine what is known about the condition "LHS_SID OP RHS_SID" within
+   this model, without resorting to the constraint_manager.
+
+   This is exposed so that impl_region_model_context::on_state_leak can
+   check for equality part-way through region_model::purge_unused_svalues
+   without risking creating new ECs.  */
+
+tristate
+region_model::eval_condition_without_cm (svalue_id lhs_sid,
+					 enum tree_code op,
+					 svalue_id rhs_sid) const
+{
+  svalue *lhs = get_svalue (lhs_sid);
+  svalue *rhs = get_svalue (rhs_sid);
+  gcc_assert (lhs);
+  gcc_assert (rhs);
+
+  /* See what we know based on the values.  */
+  if (lhs && rhs)
+    {
+      if (lhs == rhs)
+	{
+	  /* If we have the same svalue, then we have equality.
+	     TODO: should this definitely be the case for poisoned values?  */
+	  switch (op)
+	    {
+	    default:
+	      gcc_unreachable ();
+
+	    case EQ_EXPR:
+	    case GE_EXPR:
+	    case LE_EXPR:
+	      return tristate::TS_TRUE;
+
+	    case NE_EXPR:
+	    case GT_EXPR:
+	    case LT_EXPR:
+	      return tristate::TS_FALSE;
+	    }
+	}
+
+      /* If we have a pair of region_svalues, compare them.  */
+      if (region_svalue *lhs_ptr = lhs->dyn_cast_region_svalue ())
+	if (region_svalue *rhs_ptr = rhs->dyn_cast_region_svalue ())
+	  {
+	    tristate res = region_svalue::eval_condition (lhs_ptr, op, rhs_ptr);
+	    if (res.is_known ())
+	      return res;
+	    /* Otherwise, only known through constraints.  */
+	  }
+
+      /* If we have a pair of constants, compare them.  */
+      if (constant_svalue *cst_lhs = lhs->dyn_cast_constant_svalue ())
+	if (constant_svalue *cst_rhs = rhs->dyn_cast_constant_svalue ())
+	  return constant_svalue::eval_condition (cst_lhs, op, cst_rhs);
+
+      /* Handle comparison of a region_svalue against zero.  */
+      if (region_svalue *ptr = lhs->dyn_cast_region_svalue ())
+	if (constant_svalue *cst_rhs = rhs->dyn_cast_constant_svalue ())
+	  if (zerop (cst_rhs->get_constant ()))
+	    {
+	      /* A region_svalue is a non-NULL pointer, except in certain
+		 special cases (see the comment for region::non_null_p.  */
+	      region *pointee = get_region (ptr->get_pointee ());
+	      if (pointee->non_null_p (*this))
+		{
+		  switch (op)
+		    {
+		    default:
+		      gcc_unreachable ();
+
+		    case EQ_EXPR:
+		    case GE_EXPR:
+		    case LE_EXPR:
+		      return tristate::TS_FALSE;
+
+		    case NE_EXPR:
+		    case GT_EXPR:
+		    case LT_EXPR:
+		      return tristate::TS_TRUE;
+		    }
+		}
+	    }
+    }
+
+  return tristate::TS_UNKNOWN;
+}
+
+/* Attempt to add the constraint "LHS OP RHS" to this region_model.
+   If it is consistent with existing constraints, add it, and return true.
+   Return false if it contradicts existing constraints.
+   Use CTXT for reporting any diagnostics associated with the accesses.  */
+
+bool
+region_model::add_constraint (tree lhs, enum tree_code op, tree rhs,
+			      region_model_context *ctxt)
+{
+  svalue_id lhs_sid = get_rvalue (lhs, ctxt);
+  svalue_id rhs_sid = get_rvalue (rhs, ctxt);
+
+  tristate t_cond = eval_condition (lhs_sid, op, rhs_sid);
+
+  /* If we already have the condition, do nothing.  */
+  if (t_cond.is_true ())
+    return true;
+
+  /* Reject a constraint that would contradict existing knowledge, as
+     unsatisfiable.  */
+  if (t_cond.is_false ())
+    return false;
+
+  /* Store the constraint.  */
+  m_constraints->add_constraint (lhs_sid, op, rhs_sid);
+
+  add_any_constraints_from_ssa_def_stmt (lhs, op, rhs, ctxt);
+
+  /* Notify the context, if any.  This exists so that the state machines
+     in a program_state can be notified about the condition, and so can
+     set sm-state for e.g. unchecked->checked, both for cfg-edges, and
+     when synthesizing constraints as above.  */
+  if (ctxt)
+    ctxt->on_condition (lhs, op, rhs);
+
+  return true;
+}
+
+/* Subroutine of region_model::add_constraint for handling optimized
+   && and || conditionals.
+
+   If we have an SSA_NAME for a boolean compared against 0,
+   look at anything implied by the def stmt and call add_constraint
+   for it (which could recurse).
+
+   For example, if we have
+      _1 = p_6 == 0B;
+      _2 = p_8 == 0B
+      _3 = _1 | _2
+    and add the constraint
+      (_3 == 0),
+    then the def stmt for _3 implies that _1 and _2 are both false,
+    and hence we can add the constraints:
+      p_6 != 0B
+      p_8 != 0B.  */
+
+void
+region_model::add_any_constraints_from_ssa_def_stmt (tree lhs,
+						     enum tree_code op,
+						     tree rhs,
+						     region_model_context *ctxt)
+{
+  if (TREE_CODE (lhs) != SSA_NAME)
+    return;
+
+  if (rhs != boolean_false_node)
+    return;
+
+  if (op != NE_EXPR && op != EQ_EXPR)
+    return;
+
+  /* We have either
+     - "LHS != false" (i.e. LHS is true), or
+     - "LHS == false" (i.e. LHS is false).  */
+  bool is_true = op == NE_EXPR;
+
+  gimple *def_stmt = SSA_NAME_DEF_STMT (lhs);
+  gassign *assign = dyn_cast<gassign *> (def_stmt);
+  if (!assign)
+    return;
+
+  enum tree_code rhs_code = gimple_assign_rhs_code (assign);
+
+  switch (rhs_code)
+    {
+    default:
+      break;
+    case BIT_AND_EXPR:
+      {
+	if (is_true)
+	  {
+	    /* ...and "LHS == (rhs1 & rhs2) i.e. "(rhs1 & rhs2)" is true
+	       then both rhs1 and rhs2 must be true.  */
+	    tree rhs1 = gimple_assign_rhs1 (assign);
+	    tree rhs2 = gimple_assign_rhs2 (assign);
+	    add_constraint (rhs1, NE_EXPR, boolean_false_node, ctxt);
+	    add_constraint (rhs2, NE_EXPR, boolean_false_node, ctxt);
+	  }
+      }
+      break;
+
+    case BIT_IOR_EXPR:
+      {
+	if (!is_true)
+	  {
+	    /* ...and "LHS == (rhs1 | rhs2)
+	       i.e. "(rhs1 | rhs2)" is false
+	       then both rhs1 and rhs2 must be false.  */
+	    tree rhs1 = gimple_assign_rhs1 (assign);
+	    tree rhs2 = gimple_assign_rhs2 (assign);
+	    add_constraint (rhs1, EQ_EXPR, boolean_false_node, ctxt);
+	    add_constraint (rhs2, EQ_EXPR, boolean_false_node, ctxt);
+	  }
+      }
+      break;
+
+    case EQ_EXPR:
+    case NE_EXPR:
+      {
+	/* ...and "LHS == (rhs1 OP rhs2)"
+	   then rhs1 OP rhs2 must have the same logical value as LHS.  */
+	tree rhs1 = gimple_assign_rhs1 (assign);
+	tree rhs2 = gimple_assign_rhs2 (assign);
+	if (!is_true)
+	  rhs_code
+	    = invert_tree_comparison (rhs_code, false /* honor_nans */);
+	add_constraint (rhs1, rhs_code, rhs2, ctxt);
+      }
+      break;
+    }
+}
+
+/* Determine what is known about the condition "LHS OP RHS" within
+   this model.
+   Use CTXT for reporting any diagnostics associated with the accesses.  */
+
+tristate
+region_model::eval_condition (tree lhs,
+			      enum tree_code op,
+			      tree rhs,
+			      region_model_context *ctxt)
+{
+  return eval_condition (get_rvalue (lhs, ctxt), op, get_rvalue (rhs, ctxt));
+}
+
+/* If SID is a constant value, return the underlying tree constant.
+   Otherwise, return NULL_TREE.  */
+
+tree
+region_model::maybe_get_constant (svalue_id sid) const
+{
+  gcc_assert (!sid.null_p ());
+  svalue *sval = get_svalue (sid);
+  return sval->maybe_get_constant ();
+}
+
+/* Create a new child region of the heap (creating the heap region if
+   necessary).
+   Return the region_id of the new child region.  */
+
+region_id
+region_model::add_new_malloc_region ()
+{
+  region_id heap_rid
+    = get_root_region ()->ensure_heap_region (this);
+  return add_region (new symbolic_region (heap_rid, true));
+}
+
+/* Attempt to return a tree that represents SID, or return NULL_TREE.
+   Find the first region that stores the value (e.g. a local) and
+   generate a representative tree for it.  */
+
+tree
+region_model::get_representative_tree (svalue_id sid) const
+{
+  if (sid.null_p ())
+    return NULL_TREE;
+
+  unsigned i;
+  region *region;
+  FOR_EACH_VEC_ELT (m_regions, i, region)
+    if (sid == region->get_value_direct ())
+      {
+	path_var pv = get_representative_path_var (region_id::from_int (i));
+	if (pv.m_tree)
+	  return pv.m_tree;
+      }
+
+  return maybe_get_constant (sid);
+}
+
+/* Attempt to return a path_var that represents the region, or return
+   the NULL path_var.
+   For example, a region for a field of a local would be a path_var
+   wrapping a COMPONENT_REF.  */
+
+path_var
+region_model::get_representative_path_var (region_id rid) const
+{
+  region *reg = get_region (rid);
+  region *parent_region = get_region (reg->get_parent ());
+  region_id stack_rid = get_stack_region_id ();
+  if (!stack_rid.null_p ())
+    if (parent_region->get_parent () == stack_rid)
+      {
+	frame_region *parent_frame = (frame_region *)parent_region;
+	tree t = parent_frame->get_tree_for_child_region (rid);
+	return path_var (t, parent_frame->get_depth ());
+    }
+  if (reg->get_parent () == get_globals_region_id ())
+    {
+      map_region *globals = get_root_region ()->get_globals_region (this);
+      if (globals)
+	return path_var (globals->get_tree_for_child_region (rid), -1);
+    }
+
+  /* Handle e.g. fields of a local by recursing.  */
+  region_id parent_rid = reg->get_parent ();
+  region *parent_reg = get_region (parent_rid);
+  if (parent_reg)
+    {
+      if (parent_reg->get_kind () == RK_STRUCT)
+	{
+	  map_region *parent_map_region = (map_region *)parent_reg;
+	  /* This can fail if we have a view, rather than a field.  */
+	  if (tree child_key
+		= parent_map_region->get_tree_for_child_region (rid))
+	    {
+	      path_var parent_pv = get_representative_path_var (parent_rid);
+	      if (parent_pv.m_tree && TREE_CODE (child_key) == FIELD_DECL)
+		return path_var (build3 (COMPONENT_REF,
+					 TREE_TYPE (child_key),
+					 parent_pv.m_tree, child_key,
+					 NULL_TREE),
+				 parent_pv.m_stack_depth);
+	    }
+	}
+    }
+
+  return path_var (NULL_TREE, 0);
+}
+
+/* Locate all regions that directly have value SID and append representative
+   path_var instances for them into *OUT.  */
+
+void
+region_model::get_path_vars_for_svalue (svalue_id sid, vec<path_var> *out) const
+{
+  unsigned i;
+  region *region;
+  FOR_EACH_VEC_ELT (m_regions, i, region)
+    if (sid == region->get_value_direct ())
+      {
+	path_var pv = get_representative_path_var (region_id::from_int (i));
+	if (pv.m_tree)
+	  out->safe_push (pv);
+      }
+}
+
+/* Set DST_RID value to be a new unknown value of type TYPE.  */
+
+svalue_id
+region_model::set_to_new_unknown_value (region_id dst_rid, tree type,
+					region_model_context *ctxt)
+{
+  gcc_assert (!dst_rid.null_p ());
+  svalue_id new_sid = add_svalue (new unknown_svalue (type));
+  set_value (dst_rid, new_sid, ctxt);
+
+  // TODO: presumably purge all child regions too (but do this in set_value?)
+
+  return new_sid;
+}
+
+/* Update this model for any phis in SNODE, assuming we came from
+   LAST_CFG_SUPEREDGE.  */
+
+void
+region_model::update_for_phis (const supernode *snode,
+			       const cfg_superedge *last_cfg_superedge,
+			       region_model_context *ctxt)
+{
+  gcc_assert (last_cfg_superedge);
+
+  for (gphi_iterator gpi = const_cast<supernode *>(snode)->start_phis ();
+       !gsi_end_p (gpi); gsi_next (&gpi))
+    {
+      gphi *phi = gpi.phi ();
+
+      tree src = last_cfg_superedge->get_phi_arg (phi);
+      tree lhs = gimple_phi_result (phi);
+
+      /* Update next_state based on phi.  */
+      bool is_back_edge = last_cfg_superedge->back_edge_p ();
+      handle_phi (lhs, src, is_back_edge, ctxt);
+    }
+}
+
+/* Attempt to update this model for taking EDGE (where the last statement
+   was LAST_STMT), returning true if the edge can be taken, false
+   otherwise.
+
+   For CFG superedges where LAST_STMT is a conditional or a switch
+   statement, attempt to add the relevant conditions for EDGE to this
+   model, returning true if they are feasible, or false if they are
+   impossible.
+
+   For call superedges, push frame information and store arguments
+   into parameters.
+
+   For return superedges, pop frame information and store return
+   values into any lhs.
+
+   Rejection of call/return superedges happens elsewhere, in
+   program_point::on_edge (i.e. based on program point, rather
+   than program state).  */
+
+bool
+region_model::maybe_update_for_edge (const superedge &edge,
+				     const gimple *last_stmt,
+				     region_model_context *ctxt)
+{
+  /* Handle frame updates for interprocedural edges.  */
+  switch (edge.m_kind)
+    {
+    default:
+      break;
+
+    case SUPEREDGE_CALL:
+      {
+	const call_superedge *call_edge = as_a <const call_superedge *> (&edge);
+	update_for_call_superedge (*call_edge, ctxt);
+      }
+      break;
+
+    case SUPEREDGE_RETURN:
+      {
+	const return_superedge *return_edge
+	  = as_a <const return_superedge *> (&edge);
+	update_for_return_superedge (*return_edge, ctxt);
+      }
+      break;
+
+    case SUPEREDGE_INTRAPROCEDURAL_CALL:
+      {
+	const callgraph_superedge *cg_sedge
+	  = as_a <const callgraph_superedge *> (&edge);
+	update_for_call_summary (*cg_sedge, ctxt);
+      }
+      break;
+    }
+
+  if (last_stmt == NULL)
+    return true;
+
+  /* Apply any constraints for conditionals/switch statements.  */
+
+  if (const gcond *cond_stmt = dyn_cast <const gcond *> (last_stmt))
+    {
+      const cfg_superedge *cfg_sedge = as_a <const cfg_superedge *> (&edge);
+      return apply_constraints_for_gcond (*cfg_sedge, cond_stmt, ctxt);
+    }
+
+  if (const gswitch *switch_stmt = dyn_cast <const gswitch *> (last_stmt))
+    {
+      const switch_cfg_superedge *switch_sedge
+	= as_a <const switch_cfg_superedge *> (&edge);
+      return apply_constraints_for_gswitch (*switch_sedge, switch_stmt, ctxt);
+    }
+
+  return true;
+}
+
+/* Push a new frame_region on to the stack region.
+   Populate the frame_region with child regions for the function call's
+   parameters, using values from the arguments at the callsite in the
+   caller's frame.  */
+
+void
+region_model::update_for_call_superedge (const call_superedge &call_edge,
+					 region_model_context *ctxt)
+{
+  /* Build a vec of argument svalue_id, using the current top
+     frame for resolving tree expressions.  */
+  const gcall *call_stmt = call_edge.get_call_stmt ();
+  auto_vec<svalue_id> arg_sids (gimple_call_num_args (call_stmt));
+
+  for (unsigned i = 0; i < gimple_call_num_args (call_stmt); i++)
+    {
+      tree arg = gimple_call_arg (call_stmt, i);
+      arg_sids.quick_push (get_rvalue (arg, ctxt));
+    }
+
+  push_frame (call_edge.get_callee_function (), &arg_sids, ctxt);
+}
+
+/* Pop the top-most frame_region from the stack, and store the svalue
+   for any returned value into the region for the lvalue of the LHS of
+   the call (if any).  */
+
+void
+region_model::update_for_return_superedge (const return_superedge &return_edge,
+					   region_model_context *ctxt)
+{
+  purge_stats stats;
+  svalue_id result_sid = pop_frame (true, &stats, ctxt);
+  // TODO: do something with the stats?
+
+  /* Set the result of the call, within the caller frame.  */
+  const gcall *call_stmt = return_edge.get_call_stmt ();
+  tree lhs = gimple_call_lhs (call_stmt);
+  if (lhs)
+    set_value (get_lvalue (lhs, ctxt), result_sid, ctxt);
+  else if (!result_sid.null_p ())
+    {
+      /* This could be a leak; try purging again, but this time,
+	 don't special-case the result_sid.  */
+      purge_stats stats;
+      purge_unused_svalues (&stats, ctxt);
+    }
+}
+
+/* Update this region_model with a summary of the effect of calling
+   and returning from CG_SEDGE.
+
+   TODO: Currently this is extremely simplistic: we merely set the
+   return value to "unknown".  A proper implementation would e.g. update
+   sm-state, and presumably be reworked to support multiple outcomes.  */
+
+void
+region_model::update_for_call_summary (const callgraph_superedge &cg_sedge,
+				       region_model_context *ctxt)
+{
+  /* For now, set any return value to "unknown".  */
+  const gcall *call_stmt = cg_sedge.get_call_stmt ();
+  tree lhs = gimple_call_lhs (call_stmt);
+  if (lhs)
+    set_to_new_unknown_value (get_lvalue (lhs, ctxt), TREE_TYPE (lhs), ctxt);
+
+  // TODO: actually implement some kind of summary here
+}
+
+/* Given a true or false edge guarded by conditional statement COND_STMT,
+   determine appropriate constraints for the edge to be taken.
+
+   If they are feasible, add the constraints and return true.
+
+   Return false if the constraints contradict existing knowledge
+   (and so the edge should not be taken).  */
+
+bool
+region_model::apply_constraints_for_gcond (const cfg_superedge &sedge,
+					   const gcond *cond_stmt,
+					   region_model_context *ctxt)
+{
+  ::edge cfg_edge = sedge.get_cfg_edge ();
+  gcc_assert (cfg_edge != NULL);
+  gcc_assert (cfg_edge->flags & (EDGE_TRUE_VALUE | EDGE_FALSE_VALUE));
+
+  enum tree_code op = gimple_cond_code (cond_stmt);
+  tree lhs = gimple_cond_lhs (cond_stmt);
+  tree rhs = gimple_cond_rhs (cond_stmt);
+  if (cfg_edge->flags & EDGE_FALSE_VALUE)
+    op = invert_tree_comparison (op, false /* honor_nans */);
+  return add_constraint (lhs, op, rhs, ctxt);
+}
+
+/* Given an EDGE guarded by SWITCH_STMT, determine appropriate constraints
+   for the edge to be taken.
+
+   If they are feasible, add the constraints and return true.
+
+   Return false if the constraints contradict existing knowledge
+   (and so the edge should not be taken).  */
+
+bool
+region_model::apply_constraints_for_gswitch (const switch_cfg_superedge &edge,
+					     const gswitch *switch_stmt,
+					     region_model_context *ctxt)
+{
+  tree index  = gimple_switch_index (switch_stmt);
+  tree case_label = edge.get_case_label ();
+  gcc_assert (TREE_CODE (case_label) == CASE_LABEL_EXPR);
+  tree lower_bound = CASE_LOW (case_label);
+  tree upper_bound = CASE_HIGH (case_label);
+  if (lower_bound)
+    {
+      if (upper_bound)
+	{
+	  /* Range.  */
+	  if (!add_constraint (index, GE_EXPR, lower_bound, ctxt))
+	    return false;
+	  return add_constraint (index, LE_EXPR, upper_bound, ctxt);
+	}
+      else
+	/* Single-value.  */
+	return add_constraint (index, EQ_EXPR, lower_bound, ctxt);
+    }
+  else
+    {
+      /* The default case.
+	 Add exclusions based on the other cases.  */
+      for (unsigned other_idx = 1;
+	   other_idx < gimple_switch_num_labels (switch_stmt);
+	   other_idx++)
+	{
+	  tree other_label = gimple_switch_label (switch_stmt,
+						  other_idx);
+	  tree other_lower_bound = CASE_LOW (other_label);
+	  tree other_upper_bound = CASE_HIGH (other_label);
+	  gcc_assert (other_lower_bound);
+	  if (other_upper_bound)
+	    {
+	      /* Exclude this range-valued case.
+		 For now, we just exclude the boundary values.
+		 TODO: exclude the values within the region.  */
+	      if (!add_constraint (index, NE_EXPR, other_lower_bound, ctxt))
+		return false;
+	      if (!add_constraint (index, NE_EXPR, other_upper_bound, ctxt))
+		return false;
+	    }
+	  else
+	    /* Exclude this single-valued case.  */
+	    if (!add_constraint (index, NE_EXPR, other_lower_bound, ctxt))
+	      return false;
+	}
+      return true;
+    }
+}
+
+/* Get the root_region within this model (guaranteed to be non-null).  */
+
+root_region *
+region_model::get_root_region () const
+{
+  return get_region<root_region> (m_root_rid);
+}
+
+/* Get the region_id of this model's stack region (if any).  */
+
+region_id
+region_model::get_stack_region_id () const
+{
+  return get_root_region ()->get_stack_region_id ();
+}
+
+/* Create a new frame_region for a call to FUN and push it onto
+   the stack.
+
+   If ARG_SIDS is non-NULL, use it to populate the parameters
+   in the new frame.
+   Otherwise, populate them with unknown values.
+
+   Return the region_id of the new frame_region.  */
+
+region_id
+region_model::push_frame (function *fun, vec<svalue_id> *arg_sids,
+			  region_model_context *ctxt)
+{
+  return get_root_region ()->push_frame (this, fun, arg_sids, ctxt);
+}
+
+/* Get the region_id of the top-most frame in this region_model's stack,
+   if any.  */
+
+region_id
+region_model::get_current_frame_id () const
+{
+  return get_root_region ()->get_current_frame_id (*this);
+}
+
+/* Get the function of the top-most frame in this region_model's stack.
+   There must be such a frame.  */
+
+function *
+region_model::get_current_function () const
+{
+  region_id frame_id = get_current_frame_id ();
+  frame_region *frame = get_region<frame_region> (frame_id);
+  return frame->get_function ();
+}
+
+/* Pop the topmost frame_region from this region_model's stack;
+   see the comment for stack_region::pop_frame.  */
+
+svalue_id
+region_model::pop_frame (bool purge, purge_stats *out,
+			 region_model_context *ctxt)
+{
+  return get_root_region ()->pop_frame (this, purge, out, ctxt);
+}
+
+/* Get the number of frames in this region_model's stack.  */
+
+int
+region_model::get_stack_depth () const
+{
+  stack_region *stack = get_root_region ()->get_stack_region (this);
+  if (stack)
+    return stack->get_num_frames ();
+  else
+    return 0;
+}
+
+/* Get the region_id of this model's globals region (if any).  */
+
+region_id
+region_model::get_globals_region_id () const
+{
+  return get_root_region ()->get_globals_region_id ();
+}
+
+/* Add SVAL to this model, taking ownership, and returning its new
+   svalue_id.  */
+
+svalue_id
+region_model::add_svalue (svalue *sval)
+{
+  gcc_assert (sval);
+  m_svalues.safe_push (sval);
+  return svalue_id::from_int (m_svalues.length () - 1);
+}
+
+/* Change the meaning of SID to be NEW_SVAL
+   (e.g. when deferencing an unknown pointer, the pointer
+   becomes a pointer to a symbolic region, so that all users
+   of the former unknown pointer are now effectively pointing
+   at the same region).  */
+
+void
+region_model::replace_svalue (svalue_id sid, svalue *new_sval)
+{
+  gcc_assert (!sid.null_p ());
+  int idx = sid.as_int ();
+
+  gcc_assert (m_svalues[idx]);
+  gcc_assert (m_svalues[idx]->get_type () == new_sval->get_type ());
+  delete m_svalues[idx];
+
+  m_svalues[idx] = new_sval;
+}
+
+/* Add region R to this model, taking ownership, and returning its new
+   region_id.  */
+
+region_id
+region_model::add_region (region *r)
+{
+  gcc_assert (r);
+  m_regions.safe_push (r);
+  return region_id::from_int (m_regions.length () - 1);
+}
+
+/* Return the svalue with id SVAL_ID, or NULL for a null id.  */
+
+svalue *
+region_model::get_svalue (svalue_id sval_id) const
+{
+  if (sval_id.null_p ())
+    return NULL;
+  return m_svalues[sval_id.as_int ()];
+}
+
+/* Return the region with id RID, or NULL for a null id.  */
+
+region *
+region_model::get_region (region_id rid) const
+{
+  if (rid.null_p ())
+    return NULL;
+  return m_regions[rid.as_int ()];
+}
+
+/* Make a region of an appropriate subclass for TYPE,
+   with parent PARENT_RID.  */
+
+static region *
+make_region_for_type (region_id parent_rid, tree type)
+{
+  gcc_assert (TYPE_P (type));
+
+  if (INTEGRAL_TYPE_P (type)
+      || SCALAR_FLOAT_TYPE_P (type)
+      || POINTER_TYPE_P (type)
+      || TREE_CODE (type) == COMPLEX_TYPE)
+    return new primitive_region (parent_rid, type);
+
+  if (TREE_CODE (type) == RECORD_TYPE)
+    return new struct_region (parent_rid, type);
+
+  if (TREE_CODE (type) == ARRAY_TYPE)
+    return new array_region (parent_rid, type);
+
+  if (TREE_CODE (type) == UNION_TYPE)
+    return new union_region (parent_rid, type);
+
+  if (TREE_CODE (type) == FUNCTION_TYPE)
+    return new function_region (parent_rid, type);
+
+  /* If we have a void *, make a new symbolic region.  */
+  if (type == void_type_node)
+    return new symbolic_region (parent_rid, false);
+
+  gcc_unreachable ();
+}
+
+/* Add a region with type TYPE and parent PARENT_RID.  */
+
+region_id
+region_model::add_region_for_type (region_id parent_rid, tree type)
+{
+  gcc_assert (TYPE_P (type));
+
+  region *new_region = make_region_for_type (parent_rid, type);
+  return add_region (new_region);
+}
+
+/* Helper class for region_model::purge_unused_svalues.  */
+
+class restrict_to_used_svalues : public purge_criteria
+{
+public:
+  restrict_to_used_svalues (const auto_sbitmap &used) : m_used (used) {}
+
+  bool should_purge_p (svalue_id sid) const FINAL OVERRIDE
+  {
+    gcc_assert (!sid.null_p ());
+    return !bitmap_bit_p (m_used, sid.as_int ());
+  }
+
+private:
+  const auto_sbitmap &m_used;
+};
+
+/* Remove unused svalues from this model, accumulating stats into STATS.
+   Unused svalues are deleted.  Doing so could reorder the svalues, and
+   thus change the meaning of svalue_ids.
+
+   If CTXT is non-NULL, then it is notified about svalue_id remappings,
+   and about svalue_ids that are about to be deleted.  This allows e.g.
+   for warning about resource leaks, for the case where the svalue
+   represents a resource handle in the user code (e.g. a FILE * or a malloc
+   buffer).
+
+   Amongst other things, removing unused svalues is important for ensuring
+   that the analysis of loops terminates.  Otherwise, we could generate a
+   succession of models with unreferenced "unknown" values, where the
+   number of redundant unknown values could grow without bounds, and each
+   such model would be treated as distinct.
+
+   If KNOWN_USED is non-NULL, treat *KNOWN_USED as used (this is for
+   handling values being returned from functions as their frame is popped,
+   since otherwise we'd have to simultaneously determine both the rvalue
+   of the return expr in the callee frame and the lvalue for the gcall's
+   assignment in the caller frame, and it seems cleaner to express all
+   lvalue and rvalue lookups implicitly relative to a "current" frame).  */
+
+void
+region_model::purge_unused_svalues (purge_stats *stats,
+				    region_model_context *ctxt,
+				    svalue_id *known_used_sid)
+{
+  // TODO: might want to avoid a vfunc call just to do logging here:
+  logger *logger = ctxt ? ctxt->get_logger () : NULL;
+
+  LOG_SCOPE (logger);
+
+  auto_sbitmap used (m_svalues.length ());
+  bitmap_clear (used);
+
+  if (known_used_sid)
+    if (!known_used_sid->null_p ())
+      bitmap_set_bit (used, known_used_sid->as_int ());
+
+  /* Walk the regions, marking sids that are used.  */
+  unsigned i;
+  region *r;
+  FOR_EACH_VEC_ELT (m_regions, i, r)
+    {
+      svalue_id sid = r->get_value_direct ();
+      if (!sid.null_p ())
+	bitmap_set_bit (used, sid.as_int ());
+    }
+
+  /* Now purge any constraints involving svalues we don't care about.  */
+  restrict_to_used_svalues criterion (used);
+  m_constraints->purge (criterion, stats);
+
+  /* Mark any sids that are in constraints that survived.  */
+  {
+    equiv_class *ec;
+    FOR_EACH_VEC_ELT (m_constraints->m_equiv_classes, i, ec)
+      {
+	int j;
+	svalue_id *sid;
+	FOR_EACH_VEC_ELT (ec->m_vars, j, sid)
+	  {
+	    gcc_assert (!sid->null_p ());
+	    bitmap_set_bit (used, sid->as_int ());
+	  }
+      }
+  }
+
+  /* Build a mapping from old-sid to new-sid so that we can preserve
+     order of the used IDs and move all redundant ones to the end.
+     Iterate though svalue IDs, adding used ones to the front of
+     the new list, and unused ones to the back.  */
+  svalue_id_map map (m_svalues.length ());
+  int next_used_new_sid = 0;
+  int after_next_unused_new_sid = m_svalues.length ();
+  for (unsigned i = 0; i < m_svalues.length (); i++)
+    {
+      svalue_id src (svalue_id::from_int (i));
+      if (bitmap_bit_p (used, i))
+	{
+	  if (logger)
+	    logger->log ("sv%i is used", i);
+	  map.put (src, svalue_id::from_int (next_used_new_sid++));
+	}
+      else
+	{
+	  if (logger)
+	    logger->log ("sv%i is unused", i);
+	  map.put (src, svalue_id::from_int (--after_next_unused_new_sid));
+	}
+    }
+  /* The two insertion points should have met.  */
+  gcc_assert (next_used_new_sid == after_next_unused_new_sid);
+
+  /* Now walk the regions and the constraints, remapping sids,
+     so that all the redundant svalues are at the end.  */
+  remap_svalue_ids (map);
+
+  if (logger)
+    {
+      logger->start_log_line ();
+      logger->log_partial ("map: ");
+      map.dump_to_pp (logger->get_printer ());
+      logger->end_log_line ();
+    }
+
+  /* Notify any client about the remapping and pending deletion.
+     Potentially this could trigger leak warnings.  */
+  if (ctxt)
+    {
+      ctxt->remap_svalue_ids (map);
+      int num_client_items_purged
+	= ctxt->on_svalue_purge (svalue_id::from_int (next_used_new_sid), map);
+      if (stats)
+	stats->m_num_client_items += num_client_items_purged;
+    }
+
+  /* Drop the redundant svalues from the end of the vector.  */
+  while ((signed)m_svalues.length () > next_used_new_sid)
+    {
+      if (logger)
+	{
+	  svalue_id victim = svalue_id::from_int (m_svalues.length () - 1);
+	  logger->log ("deleting sv%i (was sv%i)",
+		       victim.as_int (),
+		       map.get_src_for_dst (victim).as_int ());
+	}
+      delete m_svalues.pop ();
+      if (stats)
+	stats->m_num_svalues++;
+    }
+
+  if (known_used_sid)
+    map.update (known_used_sid);
+
+  validate ();
+}
+
+/* Renumber the svalues within this model according to MAP.  */
+
+void
+region_model::remap_svalue_ids (const svalue_id_map &map)
+{
+  /* Update IDs within regions.  */
+  unsigned i;
+  region *r;
+  FOR_EACH_VEC_ELT (m_regions, i, r)
+    r->remap_svalue_ids (map);
+
+  /* Update IDs within ECs within constraints.  */
+  m_constraints->remap_svalue_ids (map);
+
+  /* Build a reordered svalues vector.  */
+  auto_vec<svalue *> new_svalues (m_svalues.length ());
+  for (unsigned i = 0; i < m_svalues.length (); i++)
+    {
+      svalue_id dst (svalue_id::from_int (i));
+      svalue_id src = map.get_src_for_dst (dst);
+      new_svalues.quick_push (get_svalue (src));
+    }
+
+  /* Copy over the reordered vec to m_svalues.  */
+  m_svalues.truncate (0);
+  gcc_assert (m_svalues.space (new_svalues.length ()));
+  svalue *sval;
+  FOR_EACH_VEC_ELT (new_svalues, i, sval)
+    m_svalues.quick_push (sval);
+}
+
+/* Renumber the regions within this model according to MAP.  */
+
+void
+region_model::remap_region_ids (const region_id_map &map)
+{
+  /* Update IDs within regions.  */
+  unsigned i;
+  region *r;
+  FOR_EACH_VEC_ELT (m_regions, i, r)
+    r->remap_region_ids (map);
+
+  /* Update IDs within svalues.  */
+  svalue *sval;
+  FOR_EACH_VEC_ELT (m_svalues, i, sval)
+    sval->remap_region_ids (map);
+
+  /* Build a reordered regions vector.  */
+  auto_vec<region *> new_regions (m_regions.length ());
+  for (unsigned i = 0; i < m_regions.length (); i++)
+    {
+      region_id dst (region_id::from_int (i));
+      region_id src = map.get_src_for_dst (dst);
+      new_regions.quick_push (get_region (src));
+    }
+
+  /* Copy over the reordered vec to m_regions.  */
+  m_regions.truncate (0);
+  gcc_assert (m_regions.space (new_regions.length ()));
+  FOR_EACH_VEC_ELT (new_regions, i, r)
+    m_regions.quick_push (r);
+}
+
+/* Delete all regions within SET_TO_PURGE, remapping region IDs for
+   other regions.  It's required that there are no uses of the
+   regions within the set (or the region IDs will become invalid).
+
+   Accumulate stats to STATS.  */
+
+void
+region_model::purge_regions (const region_id_set &set_to_purge,
+			     purge_stats *stats,
+			     logger *)
+{
+  /* Build a mapping from old-rid to new-rid so that we can preserve
+     order of the used IDs and move all redundant ones to the end.
+     Iterate though region IDs, adding used ones to the front of
+     the new list, and unused ones to the back.  */
+  region_id_map map (m_regions.length ());
+  int next_used_new_rid = 0;
+  int after_next_unused_new_rid = m_regions.length ();
+  for (unsigned i = 0; i < m_regions.length (); i++)
+    {
+      region_id src (region_id::from_int (i));
+      if (set_to_purge.region_p (src))
+	map.put (src, region_id::from_int (--after_next_unused_new_rid));
+      else
+	map.put (src, region_id::from_int (next_used_new_rid++));
+    }
+  /* The two insertion points should have met.  */
+  gcc_assert (next_used_new_rid == after_next_unused_new_rid);
+
+  /* Now walk the regions and svalues, remapping rids,
+     so that all the redundant regions are at the end.  */
+  remap_region_ids (map);
+
+  /* Drop the redundant regions from the end of the vector.  */
+  while ((signed)m_regions.length () > next_used_new_rid)
+    {
+      delete m_regions.pop ();
+      if (stats)
+	stats->m_num_regions++;
+    }
+}
+
+/* Populate *OUT with RID and all of its descendents.
+   If EXCLUDE_RID is non-null, then don't add it or its descendents.  */
+
+void
+region_model::get_descendents (region_id rid, region_id_set *out,
+			       region_id exclude_rid) const
+{
+  out->add_region (rid);
+
+  bool changed = true;
+  while (changed)
+    {
+      changed = false;
+      unsigned i;
+      region *r;
+      FOR_EACH_VEC_ELT (m_regions, i, r)
+	{
+	  region_id iter_rid = region_id::from_int (i);
+	  if (iter_rid == exclude_rid)
+	    continue;
+	  if (!out->region_p (iter_rid))
+	    {
+	      region_id parent_rid = r->get_parent ();
+	      if (!parent_rid.null_p ())
+		if (out->region_p (parent_rid))
+		  {
+		    out->add_region (iter_rid);
+		    changed = true;
+		  }
+	    }
+	}
+    }
+}
+
+/* Delete RID and all descendent regions.
+   Find any pointers to such regions; convert convert them to
+   poisoned values of kind PKIND.
+   Accumulate stats on purged entities into STATS.  */
+
+void
+region_model::delete_region_and_descendents (region_id rid,
+					     enum poison_kind pkind,
+					     purge_stats *stats,
+					     logger *logger)
+{
+  /* Find all child and descendent regions.  */
+  region_id_set descendents (this);
+  get_descendents (rid, &descendents, region_id::null ());
+
+  /* Find any pointers to such regions; convert to poisoned.  */
+  poison_any_pointers_to_bad_regions (descendents, pkind);
+
+  /* Delete all such regions.  */
+  purge_regions (descendents, stats, logger);
+}
+
+/* Find any pointers to regions within BAD_REGIONS; convert them to
+   poisoned values of kind PKIND.  */
+
+void
+region_model::poison_any_pointers_to_bad_regions (const region_id_set &
+						    bad_regions,
+						  enum poison_kind pkind)
+{
+  int i;
+  svalue *sval;
+  FOR_EACH_VEC_ELT (m_svalues, i, sval)
+    if (region_svalue *ptr_sval = sval->dyn_cast_region_svalue ())
+      {
+	region_id ptr_dst = ptr_sval->get_pointee ();
+	if (!ptr_dst.null_p ())
+	  if (bad_regions.region_p (ptr_dst))
+	    replace_svalue
+	      (svalue_id::from_int (i),
+	       new poisoned_svalue (pkind, sval->get_type ()));
+      }
+}
+
+/* Attempt to merge THIS with OTHER_MODEL, writing the result
+   to OUT_MODEL, and populating SID_MAPPING.  */
+
+bool
+region_model::can_merge_with_p (const region_model &other_model,
+				region_model *out_model,
+				svalue_id_merger_mapping *sid_mapping) const
+{
+  gcc_assert (m_root_rid == other_model.m_root_rid);
+  gcc_assert (m_root_rid.as_int () == 0);
+  gcc_assert (sid_mapping);
+  gcc_assert (out_model);
+
+  model_merger merger (this, &other_model, out_model, sid_mapping);
+
+  if (!root_region::can_merge_p (get_root_region (),
+				 other_model.get_root_region (),
+				 out_model->get_root_region (),
+				 &merger))
+    return false;
+
+  /* Merge constraints.  */
+  constraint_manager::merge (*m_constraints,
+			     *other_model.m_constraints,
+			     out_model->m_constraints,
+			     merger);
+
+  out_model->validate ();
+
+  /* The merged model should be simpler (or as simple) as the inputs.  */
+#if 0
+  gcc_assert (out_model->m_svalues.length () <= m_svalues.length ());
+  gcc_assert (out_model->m_svalues.length ()
+	      <= other_model.m_svalues.length ());
+#endif
+  gcc_assert (out_model->m_regions.length () <= m_regions.length ());
+  gcc_assert (out_model->m_regions.length ()
+	      <= other_model.m_regions.length ());
+  // TODO: same, for constraints
+
+  return true;
+}
+
+/* As above, but supply a placeholder svalue_id_merger_mapping
+   instance to be used and receive output.  For use in selftests.  */
+
+bool
+region_model::can_merge_with_p (const region_model &other_model,
+				region_model *out_model) const
+{
+  svalue_id_merger_mapping sid_mapping (*this, other_model);
+  return can_merge_with_p (other_model, out_model, &sid_mapping);
+}
+
+/* For debugging purposes: look for a region within this region_model
+   for a decl named NAME (or an SSA_NAME for such a decl),
+   returning its value, or svalue_id::null if none are found.  */
+
+svalue_id
+region_model::get_value_by_name (const char *name) const
+{
+  gcc_assert (name);
+  tree identifier = get_identifier (name);
+  return get_root_region ()->get_value_by_name (identifier, *this);
+}
+
+/* Generate or reuse an svalue_id within this model for an index
+   into an array of type PTR_TYPE, based on OFFSET_SID.  */
+
+svalue_id
+region_model::convert_byte_offset_to_array_index (tree ptr_type,
+						  svalue_id offset_sid)
+{
+  gcc_assert (POINTER_TYPE_P (ptr_type));
+
+  if (tree offset_cst = maybe_get_constant (offset_sid))
+    {
+      tree elem_type = TREE_TYPE (ptr_type);
+
+      /* Arithmetic on void-pointers is a GNU C extension, treating the size
+	 of a void as 1.
+	 https://gcc.gnu.org/onlinedocs/gcc/Pointer-Arith.html
+
+	 Returning early for this case avoids a diagnostic from within the
+	 call to size_in_bytes.  */
+      if (TREE_CODE (elem_type) == VOID_TYPE)
+	return offset_sid;
+
+      /* This might not be a constant.  */
+      tree byte_size = size_in_bytes (elem_type);
+
+      tree index
+	= fold_build2 (TRUNC_DIV_EXPR, integer_type_node,
+		       offset_cst, byte_size);
+
+      if (CONSTANT_CLASS_P (index))
+	return get_or_create_constant_svalue (index);
+    }
+
+  /* Otherwise, we don't know the array index; generate a new unknown value.
+     TODO: do we need to capture the relationship between two unknown
+     values (the offset and the index)?  */
+  return add_svalue (new unknown_svalue (integer_type_node));
+}
+
+/* Get a region of type TYPE for PTR_SID[OFFSET_SID/sizeof (*PTR_SID)].
+
+   If OFFSET_SID is known to be zero, then dereference PTR_SID.
+   Otherwise, impose a view of "typeof(*PTR_SID)[]" on *PTR_SID,
+   and then get a view of type TYPE on the relevant array element.  */
+
+region_id
+region_model::get_or_create_mem_ref (tree type,
+				     svalue_id ptr_sid,
+				     svalue_id offset_sid,
+				     region_model_context *ctxt)
+{
+  svalue *ptr_sval = get_svalue (ptr_sid);
+  tree ptr_type = ptr_sval->get_type ();
+  gcc_assert (ptr_type);
+
+  region_id raw_rid = deref_rvalue (ptr_sid, ctxt);
+
+  svalue *offset_sval = get_svalue (offset_sid);
+  tree offset_type = offset_sval->get_type ();
+  gcc_assert (offset_type);
+
+  if (constant_svalue *cst_sval = offset_sval->dyn_cast_constant_svalue ())
+    {
+      if (zerop (cst_sval->get_constant ()))
+	{
+	  /* Handle the zero offset case.  */
+	  return get_or_create_view (raw_rid, type);
+	}
+
+      /* If we're already within an array of the correct type,
+	 then we want to reuse that array, rather than starting
+	 a new view.
+	 If so, figure out our raw_rid's offset from its parent,
+	 if we can, and use that to offset OFFSET_SID, and create
+	 the element within the parent region.  */
+      region *raw_reg = get_region (raw_rid);
+      region_id parent_rid = raw_reg->get_parent ();
+      tree parent_type = get_region (parent_rid)->get_type ();
+      if (parent_type
+	  && TREE_CODE (parent_type) == ARRAY_TYPE)
+	{
+	  // TODO: check we have the correct parent type
+	  array_region *parent_array = get_region <array_region> (parent_rid);
+	  array_region::key_t key_for_raw_rid;
+	  if (parent_array->get_key_for_child_region (raw_rid,
+						      &key_for_raw_rid))
+	    {
+	      /* Convert from offset to index.  */
+	      svalue_id index_sid
+		= convert_byte_offset_to_array_index (ptr_type, offset_sid);
+	      if (tree index_cst
+		    = get_svalue (index_sid)->maybe_get_constant ())
+		{
+		  array_region::key_t index_offset
+		    = array_region::key_from_constant (index_cst);
+		  array_region::key_t index_rel_to_parent
+		    = key_for_raw_rid + index_offset;
+		  tree index_rel_to_parent_cst
+		    = wide_int_to_tree (integer_type_node,
+					index_rel_to_parent);
+		  svalue_id index_sid
+		    = get_or_create_constant_svalue (index_rel_to_parent_cst);
+
+		  /* Carry on, using the parent region and adjusted index.  */
+		  region_id element_rid
+		    = parent_array->get_element (this, raw_rid, index_sid,
+						 ctxt);
+		  return get_or_create_view (element_rid, type);
+		}
+	    }
+	}
+    }
+
+  tree array_type = build_array_type (TREE_TYPE (ptr_type),
+				      integer_type_node);
+  region_id array_view_rid = get_or_create_view (raw_rid, array_type);
+  array_region *array_reg = get_region <array_region> (array_view_rid);
+
+  svalue_id index_sid
+    = convert_byte_offset_to_array_index (ptr_type, offset_sid);
+
+  region_id element_rid
+    = array_reg->get_element (this, array_view_rid, index_sid, ctxt);
+
+  return get_or_create_view (element_rid, type);
+}
+
+/* Get a region of type TYPE for PTR_SID + OFFSET_SID.
+
+   If OFFSET_SID is known to be zero, then dereference PTR_SID.
+   Otherwise, impose a view of "typeof(*PTR_SID)[]" on *PTR_SID,
+   and then get a view of type TYPE on the relevant array element.  */
+
+region_id
+region_model::get_or_create_pointer_plus_expr (tree type,
+					       svalue_id ptr_sid,
+					       svalue_id offset_in_bytes_sid,
+					       region_model_context *ctxt)
+{
+  return get_or_create_mem_ref (type,
+				ptr_sid,
+				offset_in_bytes_sid,
+				ctxt);
+}
+
+/* Get or create a view of type TYPE of the region with id RAW_ID.
+   Return the id of the view (or RAW_ID if it of the same type).  */
+
+region_id
+region_model::get_or_create_view (region_id raw_rid, tree type)
+{
+  region *raw_region = get_region (raw_rid);
+
+  gcc_assert (TYPE_P (type));
+  if (type != raw_region->get_type ())
+    {
+      /* If the region already has a view of the requested type,
+	 reuse it.  */
+      region_id existing_view_rid = raw_region->get_view (type, this);
+      if (!existing_view_rid.null_p ())
+	return existing_view_rid;
+
+      /* Otherwise, make one (adding it to the region_model and
+	 to the viewed region).  */
+      region_id view_rid = add_region_for_type (raw_rid, type);
+      raw_region->add_view (view_rid, this);
+      // TODO: something to signify that this is a "view"
+      return view_rid;
+    }
+
+  return raw_rid;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* struct model_merger.  */
+
+/* Dump a multiline representation of this merger to PP.  */
+
+void
+model_merger::dump_to_pp (pretty_printer *pp) const
+{
+  pp_string (pp, "model A:");
+  pp_newline (pp);
+  m_model_a->dump_to_pp (pp, false);
+  pp_newline (pp);
+
+  pp_string (pp, "model B:");
+  pp_newline (pp);
+  m_model_b->dump_to_pp (pp, false);
+  pp_newline (pp);
+
+  pp_string (pp, "merged model:");
+  pp_newline (pp);
+  m_merged_model->dump_to_pp (pp, false);
+  pp_newline (pp);
+
+  pp_string (pp, "region map: model A to merged model:");
+  pp_newline (pp);
+  m_map_regions_from_a_to_m.dump_to_pp (pp);
+  pp_newline (pp);
+
+  pp_string (pp, "region map: model B to merged model:");
+  pp_newline (pp);
+  m_map_regions_from_b_to_m.dump_to_pp (pp);
+  pp_newline (pp);
+
+  m_sid_mapping->dump_to_pp (pp);
+}
+
+/* Dump a multiline representation of this merger to FILE.  */
+
+void
+model_merger::dump (FILE *fp) const
+{
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  pp_show_color (&pp) = pp_show_color (global_dc->printer);
+  pp.buffer->stream = fp;
+  dump_to_pp (&pp);
+  pp_flush (&pp);
+}
+
+/* Dump a multiline representation of this merger to stderr.  */
+
+DEBUG_FUNCTION void
+model_merger::dump () const
+{
+  dump (stderr);
+}
+
+/* Attempt to merge the svalues of SID_A and SID_B (from their
+   respective models), writing the id of the resulting svalue
+   into *MERGED_SID.
+   Return true if the merger is possible, false otherwise.  */
+
+bool
+model_merger::can_merge_values_p (svalue_id sid_a,
+				  svalue_id sid_b,
+				  svalue_id *merged_sid)
+{
+  gcc_assert (merged_sid);
+  svalue *sval_a = m_model_a->get_svalue (sid_a);
+  svalue *sval_b = m_model_b->get_svalue (sid_b);
+
+  /* If both are NULL, then the "values" are trivially mergeable.  */
+  if (!sval_a && !sval_b)
+    return true;
+
+  /* If one is NULL and the other non-NULL, then the "values"
+     are not mergeable.  */
+  if (!(sval_a && sval_b))
+    return false;
+
+  /* Have they both already been mapped to the same new svalue_id?
+     If so, use it.  */
+  svalue_id sid_a_in_m
+    = m_sid_mapping->m_map_from_a_to_m.get_dst_for_src (sid_a);
+  svalue_id sid_b_in_m
+    = m_sid_mapping->m_map_from_b_to_m.get_dst_for_src (sid_b);
+  if (!sid_a_in_m.null_p ()
+      && !sid_b_in_m.null_p ()
+      && sid_a_in_m == sid_b_in_m)
+    {
+      *merged_sid = sid_a_in_m;
+      return true;
+    }
+
+  tree type = sval_a->get_type ();
+  if (type == NULL_TREE)
+    type = sval_b->get_type ();
+
+  /* If the values have different kinds, or are both unknown,
+     then merge as "unknown".  */
+  if (sval_a->get_kind () != sval_b->get_kind ()
+      || sval_a->get_kind () == SK_UNKNOWN)
+    {
+      svalue *merged_sval = new unknown_svalue (type);
+      *merged_sid = m_merged_model->add_svalue (merged_sval);
+      record_svalues (sid_a, sid_b, *merged_sid);
+      return true;
+    }
+
+  gcc_assert (sval_a->get_kind () == sval_b->get_kind ());
+
+  switch (sval_a->get_kind ())
+    {
+    default:
+    case SK_UNKNOWN: /* SK_UNKNOWN handled above.  */
+       gcc_unreachable ();
+
+    case SK_REGION:
+      {
+	/* If we have two region pointers, then we can merge (possibly to
+	   "unknown").  */
+	const region_svalue &region_sval_a = *as_a <region_svalue *> (sval_a);
+	const region_svalue &region_sval_b = *as_a <region_svalue *> (sval_b);
+	region_svalue::merge_values (region_sval_a, region_sval_b,
+				     merged_sid, type,
+				     this);
+	record_svalues (sid_a, sid_b, *merged_sid);
+	return true;
+      }
+      break;
+    case SK_CONSTANT:
+      {
+	/* If we have two constants, then we can merge.  */
+	const constant_svalue &cst_sval_a = *as_a <constant_svalue *> (sval_a);
+	const constant_svalue &cst_sval_b = *as_a <constant_svalue *> (sval_b);
+	constant_svalue::merge_values (cst_sval_a, cst_sval_b,
+				       merged_sid, this);
+	record_svalues (sid_a, sid_b, *merged_sid);
+	return true;
+      }
+      break;
+
+    case SK_POISONED:
+    case SK_SETJMP:
+      return false;
+    }
+}
+
+/* Record that A_RID in model A and B_RID in model B
+   correspond to MERGED_RID in the merged model, so
+   that pointers can be accurately merged.  */
+
+void
+model_merger::record_regions (region_id a_rid,
+			      region_id b_rid,
+			      region_id merged_rid)
+{
+  m_map_regions_from_a_to_m.put (a_rid, merged_rid);
+  m_map_regions_from_b_to_m.put (b_rid, merged_rid);
+}
+
+/* Record that A_SID in model A and B_SID in model B
+   correspond to MERGED_SID in the merged model.  */
+
+void
+model_merger::record_svalues (svalue_id a_sid,
+			      svalue_id b_sid,
+			      svalue_id merged_sid)
+{
+  gcc_assert (m_sid_mapping);
+  m_sid_mapping->m_map_from_a_to_m.put (a_sid, merged_sid);
+  m_sid_mapping->m_map_from_b_to_m.put (b_sid, merged_sid);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* struct svalue_id_merger_mapping.  */
+
+/* svalue_id_merger_mapping's ctor.  */
+
+svalue_id_merger_mapping::svalue_id_merger_mapping (const region_model &a,
+						    const region_model &b)
+: m_map_from_a_to_m (a.get_num_svalues ()),
+  m_map_from_b_to_m (b.get_num_svalues ())
+{
+}
+
+/* Dump a multiline representation of this to PP.  */
+
+void
+svalue_id_merger_mapping::dump_to_pp (pretty_printer *pp) const
+{
+  pp_string (pp, "svalue_id map: model A to merged model:");
+  pp_newline (pp);
+  m_map_from_a_to_m.dump_to_pp (pp);
+  pp_newline (pp);
+
+  pp_string (pp, "svalue_id map: model B to merged model:");
+  pp_newline (pp);
+  m_map_from_b_to_m.dump_to_pp (pp);
+  pp_newline (pp);
+}
+
+/* Dump a multiline representation of this to FILE.  */
+
+void
+svalue_id_merger_mapping::dump (FILE *fp) const
+{
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  pp_show_color (&pp) = pp_show_color (global_dc->printer);
+  pp.buffer->stream = fp;
+  dump_to_pp (&pp);
+  pp_flush (&pp);
+}
+
+/* Dump a multiline representation of this to stderr.  */
+
+DEBUG_FUNCTION void
+svalue_id_merger_mapping::dump () const
+{
+  dump (stderr);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* struct canonicalization.  */
+
+/* canonicalization's ctor.  */
+
+canonicalization::canonicalization (const region_model &model)
+: m_model (model),
+  m_rid_map (model.get_num_regions ()),
+  m_sid_map (model.get_num_svalues ()),
+  m_next_rid_int (0),
+  m_next_sid_int (0)
+{
+}
+
+/* If we've not seen RID yet, assign it a canonicalized region_id,
+   and walk the region's svalue and then the region.  */
+
+void
+canonicalization::walk_rid (region_id rid)
+{
+  /* Stop if we've already seen RID.  */
+  if (!m_rid_map.get_dst_for_src (rid).null_p ())
+    return;
+
+  region *region = m_model.get_region (rid);
+  if (region)
+    {
+      m_rid_map.put (rid, region_id::from_int (m_next_rid_int++));
+      walk_sid (region->get_value_direct ());
+      region->walk_for_canonicalization (this);
+    }
+}
+
+/* If we've not seen SID yet, assign it a canonicalized svalue_id,
+   and walk the svalue (and potentially regions e.g. for ptr values).  */
+
+void
+canonicalization::walk_sid (svalue_id sid)
+{
+  /* Stop if we've already seen SID.  */
+  if (!m_sid_map.get_dst_for_src (sid).null_p ())
+    return;
+
+  svalue *sval = m_model.get_svalue (sid);
+  if (sval)
+    {
+      m_sid_map.put (sid, svalue_id::from_int (m_next_sid_int++));
+      /* Potentially walk regions e.g. for ptrs.  */
+      sval->walk_for_canonicalization (this);
+    }
+}
+
+/* Dump a multiline representation of this to PP.  */
+
+void
+canonicalization::dump_to_pp (pretty_printer *pp) const
+{
+  pp_string (pp, "region_id map:");
+  pp_newline (pp);
+  m_rid_map.dump_to_pp (pp);
+  pp_newline (pp);
+
+  pp_string (pp, "svalue_id map:");
+  pp_newline (pp);
+  m_sid_map.dump_to_pp (pp);
+  pp_newline (pp);
+}
+
+/* Dump a multiline representation of this to FILE.  */
+
+void
+canonicalization::dump (FILE *fp) const
+{
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  pp_show_color (&pp) = pp_show_color (global_dc->printer);
+  pp.buffer->stream = fp;
+  dump_to_pp (&pp);
+  pp_flush (&pp);
+}
+
+/* Dump a multiline representation of this to stderr.  */
+
+DEBUG_FUNCTION void
+canonicalization::dump () const
+{
+  dump (stderr);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Update HSTATE with a hash of SID.  */
+
+void
+inchash::add (svalue_id sid, inchash::hash &hstate)
+{
+  hstate.add_int (sid.as_int ());
+}
+
+/* Update HSTATE with a hash of RID.  */
+
+void
+inchash::add (region_id rid, inchash::hash &hstate)
+{
+  hstate.add_int (rid.as_int ());
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Dump RMODEL fully to stderr (i.e. without summarization).  */
+
+DEBUG_FUNCTION void
+debug (const region_model &rmodel)
+{
+  rmodel.dump (false);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Implementation detail of the ASSERT_CONDITION_* macros.  */
+
+void
+assert_condition (const location &loc,
+		  region_model &model,
+		  tree lhs, tree_code op, tree rhs,
+		  tristate expected)
+{
+  tristate actual = model.eval_condition (lhs, op, rhs, NULL);
+  ASSERT_EQ_AT (loc, actual, expected);
+}
+
+/* Implementation detail of ASSERT_DUMP_EQ.  */
+
+static void
+assert_dump_eq (const location &loc,
+		const region_model &model,
+		bool summarize,
+		const char *expected)
+{
+  auto_fix_quotes sentinel;
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  model.dump_to_pp (&pp, summarize);
+  ASSERT_STREQ_AT (loc, pp_formatted_text (&pp), expected);
+}
+
+/* Assert that MODEL.dump_to_pp (SUMMARIZE) is EXPECTED.  */
+
+#define ASSERT_DUMP_EQ(MODEL, SUMMARIZE, EXPECTED) \
+  SELFTEST_BEGIN_STMT							\
+  assert_dump_eq ((SELFTEST_LOCATION), (MODEL), (SUMMARIZE), (EXPECTED)); \
+  SELFTEST_END_STMT
+
+/* Smoketest for region_model::dump_to_pp.  */
+
+static void
+test_dump ()
+{
+  region_model model;
+  model.get_root_region ()->ensure_stack_region (&model);
+  model.get_root_region ()->ensure_globals_region (&model);
+  model.get_root_region ()->ensure_heap_region (&model);
+
+  ASSERT_DUMP_EQ (model, false,
+		  "r0: {kind: `root', parent: null, sval: null}\n"
+		  "|-stack: r1: {kind: `stack', parent: r0, sval: sv0}\n"
+		  "|  |: sval: sv0: {poisoned: uninit}\n"
+		  "|-globals: r2: {kind: `globals', parent: r0, sval: null, map: {}}\n"
+		  "`-heap: r3: {kind: `heap', parent: r0, sval: sv1}\n"
+		  "  |: sval: sv1: {poisoned: uninit}\n"
+		  "svalues:\n"
+		  "  sv0: {poisoned: uninit}\n"
+		  "  sv1: {poisoned: uninit}\n"
+		  "constraint manager:\n"
+		  "  equiv classes:\n"
+		  "  constraints:\n");
+  ASSERT_DUMP_EQ (model, true, "");
+}
+
+/* Verify that calling region_model::get_rvalue repeatedly on the same
+   tree constant retrieves the same svalue_id.  */
+
+static void
+test_unique_constants ()
+{
+  tree int_0 = build_int_cst (integer_type_node, 0);
+  tree int_42 = build_int_cst (integer_type_node, 42);
+
+  test_region_model_context ctxt;
+  region_model model;
+  ASSERT_EQ (model.get_rvalue (int_0, &ctxt), model.get_rvalue (int_0, &ctxt));
+  ASSERT_EQ (model.get_rvalue (int_42, &ctxt),
+	     model.get_rvalue (int_42, &ctxt));
+  ASSERT_NE (model.get_rvalue (int_0, &ctxt), model.get_rvalue (int_42, &ctxt));
+  ASSERT_EQ (ctxt.get_num_diagnostics (), 0);
+}
+
+/* Check that operator== and hashing works as expected for the
+   various svalue subclasses.  */
+
+static void
+test_svalue_equality ()
+{
+  tree int_42 = build_int_cst (integer_type_node, 42);
+  tree int_0 = build_int_cst (integer_type_node, 0);
+
+  /* Create pairs instances of the various subclasses of svalue,
+     testing for hash and equality between (this, this) and
+     (this, other of same subclass). */
+  svalue *ptr_to_r0
+    = new region_svalue (ptr_type_node, region_id::from_int (0));
+  svalue *ptr_to_r1
+    = new region_svalue (ptr_type_node, region_id::from_int (1));
+
+  ASSERT_EQ (ptr_to_r0->hash (), ptr_to_r0->hash ());
+  ASSERT_EQ (*ptr_to_r0, *ptr_to_r0);
+
+  ASSERT_NE (ptr_to_r0->hash (), ptr_to_r1->hash ());
+  ASSERT_NE (*ptr_to_r0, *ptr_to_r1);
+
+  svalue *cst_int_42 = new constant_svalue (int_42);
+  svalue *cst_int_0 = new constant_svalue (int_0);
+
+  ASSERT_EQ (cst_int_42->hash (), cst_int_42->hash ());
+  ASSERT_EQ (*cst_int_42, *cst_int_42);
+
+  ASSERT_NE (cst_int_42->hash (), cst_int_0->hash ());
+  ASSERT_NE (*cst_int_42, *cst_int_0);
+
+  svalue *uninit = new poisoned_svalue (POISON_KIND_UNINIT, NULL_TREE);
+  svalue *freed = new poisoned_svalue (POISON_KIND_FREED, NULL_TREE);
+
+  ASSERT_EQ (uninit->hash (), uninit->hash ());
+  ASSERT_EQ (*uninit, *uninit);
+
+  ASSERT_NE (uninit->hash (), freed->hash ());
+  ASSERT_NE (*uninit, *freed);
+
+  svalue *unknown_0 = new unknown_svalue (ptr_type_node);
+  svalue *unknown_1 = new unknown_svalue (ptr_type_node);
+  ASSERT_EQ (unknown_0->hash (), unknown_0->hash ());
+  ASSERT_EQ (*unknown_0, *unknown_0);
+  ASSERT_EQ (*unknown_1, *unknown_1);
+
+  /* Comparisons between different kinds of svalue.  */
+  ASSERT_NE (*ptr_to_r0, *cst_int_42);
+  ASSERT_NE (*ptr_to_r0, *uninit);
+  ASSERT_NE (*ptr_to_r0, *unknown_0);
+  ASSERT_NE (*cst_int_42, *ptr_to_r0);
+  ASSERT_NE (*cst_int_42, *uninit);
+  ASSERT_NE (*cst_int_42, *unknown_0);
+  ASSERT_NE (*uninit, *ptr_to_r0);
+  ASSERT_NE (*uninit, *cst_int_42);
+  ASSERT_NE (*uninit, *unknown_0);
+  ASSERT_NE (*unknown_0, *ptr_to_r0);
+  ASSERT_NE (*unknown_0, *cst_int_42);
+  ASSERT_NE (*unknown_0, *uninit);
+
+  delete ptr_to_r0;
+  delete ptr_to_r1;
+  delete cst_int_42;
+  delete cst_int_0;
+  delete uninit;
+  delete freed;
+  delete unknown_0;
+  delete unknown_1;
+}
+
+/* Check that operator== and hashing works as expected for the
+   various region subclasses.  */
+
+static void
+test_region_equality ()
+{
+  region *r0
+    = new primitive_region (region_id::from_int (3), integer_type_node);
+  region *r1
+    = new primitive_region (region_id::from_int (4), integer_type_node);
+
+  ASSERT_EQ (*r0, *r0);
+  ASSERT_EQ (r0->hash (), r0->hash ());
+  ASSERT_NE (*r0, *r1);
+  ASSERT_NE (r0->hash (), r1->hash ());
+
+  delete r0;
+  delete r1;
+
+  // TODO: test coverage for the map within a map_region
+}
+
+/* A subclass of purge_criteria for selftests: purge all svalue_id instances.  */
+
+class purge_all_svalue_ids : public purge_criteria
+{
+public:
+  bool should_purge_p (svalue_id) const FINAL OVERRIDE
+  {
+    return true;
+  }
+};
+
+/* A subclass of purge_criteria: purge a specific svalue_id.  */
+
+class purge_one_svalue_id : public purge_criteria
+{
+public:
+  purge_one_svalue_id (svalue_id victim) : m_victim (victim) {}
+
+  purge_one_svalue_id (region_model model, tree expr)
+  : m_victim (model.get_rvalue (expr, NULL)) {}
+
+  bool should_purge_p (svalue_id sid) const FINAL OVERRIDE
+  {
+    return sid == m_victim;
+  }
+
+private:
+  svalue_id m_victim;
+};
+
+/* Check that constraint_manager::purge works for individual svalue_ids.  */
+
+static void
+test_purging_by_criteria ()
+{
+  tree int_42 = build_int_cst (integer_type_node, 42);
+  tree int_0 = build_int_cst (integer_type_node, 0);
+
+  tree x = build_global_decl ("x", integer_type_node);
+  tree y = build_global_decl ("y", integer_type_node);
+
+  {
+    region_model model0;
+    region_model model1;
+
+    ADD_SAT_CONSTRAINT (model1, x, EQ_EXPR, y);
+    ASSERT_NE (model0, model1);
+
+    purge_stats stats_for_px;
+    purge_one_svalue_id px (model1, x);
+    model1.get_constraints ()->purge (px, &stats_for_px);
+    ASSERT_EQ (stats_for_px.m_num_equiv_classes, 0);
+
+    purge_stats stats_for_py;
+    purge_one_svalue_id py (model1.get_rvalue (y, NULL));
+    model1.get_constraints ()->purge (py, &stats_for_py);
+    ASSERT_EQ (stats_for_py.m_num_equiv_classes, 1);
+
+    ASSERT_EQ (*model0.get_constraints (), *model1.get_constraints ());
+  }
+
+  {
+    region_model model0;
+    region_model model1;
+
+    ADD_SAT_CONSTRAINT (model1, x, EQ_EXPR, int_42);
+    ASSERT_NE (model0, model1);
+    ASSERT_CONDITION_TRUE (model1, x, EQ_EXPR, int_42);
+
+    purge_stats stats;
+    model1.get_constraints ()->purge (purge_one_svalue_id (model1, x), &stats);
+
+    ASSERT_CONDITION_UNKNOWN (model1, x, EQ_EXPR, int_42);
+  }
+
+  {
+    region_model model0;
+    region_model model1;
+
+    ADD_SAT_CONSTRAINT (model1, x, GE_EXPR, int_0);
+    ADD_SAT_CONSTRAINT (model1, x, LE_EXPR, int_42);
+    ASSERT_NE (model0, model1);
+
+    ASSERT_CONDITION_TRUE (model1, x, GE_EXPR, int_0);
+    ASSERT_CONDITION_TRUE (model1, x, LE_EXPR, int_42);
+
+    purge_stats stats;
+    model1.get_constraints ()->purge (purge_one_svalue_id (model1, x), &stats);
+
+    ASSERT_CONDITION_UNKNOWN (model1, x, GE_EXPR, int_0);
+    ASSERT_CONDITION_UNKNOWN (model1, x, LE_EXPR, int_42);
+  }
+
+  {
+    region_model model0;
+    region_model model1;
+
+    ADD_SAT_CONSTRAINT (model1, x, NE_EXPR, int_42);
+    ADD_SAT_CONSTRAINT (model1, y, NE_EXPR, int_0);
+    ASSERT_NE (model0, model1);
+    ASSERT_CONDITION_TRUE (model1, x, NE_EXPR, int_42);
+    ASSERT_CONDITION_TRUE (model1, y, NE_EXPR, int_0);
+
+    purge_stats stats;
+    model1.get_constraints ()->purge (purge_one_svalue_id (model1, x), &stats);
+    ASSERT_NE (model0, model1);
+
+    ASSERT_CONDITION_UNKNOWN (model1, x, NE_EXPR, int_42);
+    ASSERT_CONDITION_TRUE (model1, y, NE_EXPR, int_0);
+  }
+
+  {
+    region_model model0;
+    region_model model1;
+
+    ADD_SAT_CONSTRAINT (model1, x, NE_EXPR, int_42);
+    ADD_SAT_CONSTRAINT (model1, y, NE_EXPR, int_0);
+    ASSERT_NE (model0, model1);
+    ASSERT_CONDITION_TRUE (model1, x, NE_EXPR, int_42);
+    ASSERT_CONDITION_TRUE (model1, y, NE_EXPR, int_0);
+
+    purge_stats stats;
+    model1.get_constraints ()->purge (purge_all_svalue_ids (), &stats);
+    ASSERT_CONDITION_UNKNOWN (model1, x, NE_EXPR, int_42);
+    ASSERT_CONDITION_UNKNOWN (model1, y, NE_EXPR, int_0);
+  }
+
+}
+
+/* Test that region_model::purge_unused_svalues works as expected.  */
+
+static void
+test_purge_unused_svalues ()
+{
+  tree int_42 = build_int_cst (integer_type_node, 42);
+  tree int_0 = build_int_cst (integer_type_node, 0);
+  tree x = build_global_decl ("x", integer_type_node);
+  tree y = build_global_decl ("y", integer_type_node);
+
+  test_region_model_context ctxt;
+  region_model model;
+  model.set_to_new_unknown_value (model.get_lvalue (x, &ctxt), TREE_TYPE (x),
+				  &ctxt);
+  model.set_to_new_unknown_value (model.get_lvalue (x, &ctxt), TREE_TYPE (x),
+				  &ctxt);
+  model.set_to_new_unknown_value (model.get_lvalue (x, &ctxt), TREE_TYPE (x),
+				  &ctxt);
+  model.add_constraint (x, NE_EXPR, int_42, &ctxt);
+
+  model.set_value (model.get_lvalue (x, &ctxt),
+		   model.get_rvalue (int_42, &ctxt),
+		   &ctxt);
+  model.add_constraint (y, GT_EXPR, int_0, &ctxt);
+
+  /* The redundant unknown values should have been purged.  */
+  purge_stats purged;
+  model.purge_unused_svalues (&purged, NULL);
+  ASSERT_EQ (purged.m_num_svalues, 3);
+
+  /* and the redundant constraint on an old, unknown value for x should
+     have been purged.  */
+  ASSERT_EQ (purged.m_num_equiv_classes, 1);
+  ASSERT_EQ (purged.m_num_constraints, 1);
+  ASSERT_EQ (model.get_constraints ()->m_constraints.length (), 2);
+
+  /* ...but we should still have x == 42.  */
+  ASSERT_EQ (model.eval_condition (x, EQ_EXPR, int_42, &ctxt),
+	     tristate::TS_TRUE);
+
+  /* ...and we should still have the constraint on y.  */
+  ASSERT_EQ (model.eval_condition (y, GT_EXPR, int_0, &ctxt),
+	     tristate::TS_TRUE);
+
+  ASSERT_EQ (ctxt.get_num_diagnostics (), 0);
+}
+
+/* Verify that simple assignments work as expected.  */
+
+static void
+test_assignment ()
+{
+  tree int_0 = build_int_cst (integer_type_node, 0);
+  tree x = build_global_decl ("x", integer_type_node);
+  tree y = build_global_decl ("y", integer_type_node);
+
+  /* "x == 0", then use of y, then "y = 0;".  */
+  region_model model;
+  ADD_SAT_CONSTRAINT (model, x, EQ_EXPR, int_0);
+  ASSERT_CONDITION_UNKNOWN (model, y, EQ_EXPR, int_0);
+  model.set_value (model.get_lvalue (y, NULL),
+		   model.get_rvalue (int_0, NULL),
+		   NULL);
+  ASSERT_CONDITION_TRUE (model, y, EQ_EXPR, int_0);
+  ASSERT_CONDITION_TRUE (model, y, EQ_EXPR, x);
+
+  ASSERT_DUMP_EQ (model, true, "y: 0, {x}: unknown, x == y");
+}
+
+/* Verify the details of pushing and popping stack frames.  */
+
+static void
+test_stack_frames ()
+{
+  tree int_42 = build_int_cst (integer_type_node, 42);
+  tree int_10 = build_int_cst (integer_type_node, 10);
+  tree int_5 = build_int_cst (integer_type_node, 5);
+  tree int_0 = build_int_cst (integer_type_node, 0);
+
+  auto_vec <tree> param_types;
+  tree parent_fndecl = make_fndecl (integer_type_node,
+				    "parent_fn",
+				    param_types);
+  allocate_struct_function (parent_fndecl, true);
+
+  tree child_fndecl = make_fndecl (integer_type_node,
+				   "child_fn",
+				   param_types);
+  allocate_struct_function (child_fndecl, true);
+
+  /* "a" and "b" in the parent frame.  */
+  tree a = build_decl (UNKNOWN_LOCATION, PARM_DECL,
+		       get_identifier ("a"),
+		       integer_type_node);
+  tree b = build_decl (UNKNOWN_LOCATION, PARM_DECL,
+		       get_identifier ("b"),
+		       integer_type_node);
+  /* "x" and "y" in a child frame.  */
+  tree x = build_decl (UNKNOWN_LOCATION, PARM_DECL,
+		       get_identifier ("x"),
+		       integer_type_node);
+  tree y = build_decl (UNKNOWN_LOCATION, PARM_DECL,
+		       get_identifier ("y"),
+		       integer_type_node);
+
+  /* "p" global.  */
+  tree p = build_global_decl ("p", ptr_type_node);
+
+  /* "q" global.  */
+  tree q = build_global_decl ("q", ptr_type_node);
+
+  test_region_model_context ctxt;
+  region_model model;
+
+  /* Push stack frame for "parent_fn".  */
+  region_id parent_frame_rid
+    = model.push_frame (DECL_STRUCT_FUNCTION (parent_fndecl), NULL, &ctxt);
+  ASSERT_EQ (model.get_current_frame_id (), parent_frame_rid);
+  region_id a_in_parent_rid = model.get_lvalue (a, &ctxt);
+  model.set_value (a_in_parent_rid, model.get_rvalue (int_42, &ctxt), &ctxt);
+  model.set_to_new_unknown_value (model.get_lvalue (b, &ctxt),
+				  integer_type_node, &ctxt);
+  model.add_constraint (b, LT_EXPR, int_10, &ctxt);
+  ASSERT_EQ (model.eval_condition (b, LT_EXPR, int_10, &ctxt),
+	     tristate (tristate::TS_TRUE));
+
+  /* Push stack frame for "child_fn".  */
+  region_id child_frame_rid
+    = model.push_frame (DECL_STRUCT_FUNCTION (child_fndecl), NULL, &ctxt);
+  ASSERT_EQ (model.get_current_frame_id (), child_frame_rid);
+  region_id x_in_child_rid = model.get_lvalue (x, &ctxt);
+  model.set_value (x_in_child_rid, model.get_rvalue (int_0, &ctxt), &ctxt);
+  model.set_to_new_unknown_value (model.get_lvalue (y, &ctxt),
+				  integer_type_node, &ctxt);
+  model.add_constraint (y, NE_EXPR, int_5, &ctxt);
+  ASSERT_EQ (model.eval_condition (y, NE_EXPR, int_5, &ctxt),
+	     tristate (tristate::TS_TRUE));
+
+  /* Point a global pointer at a local in the child frame:  p = &x.  */
+  region_id p_in_globals_rid = model.get_lvalue (p, &ctxt);
+  model.set_value (p_in_globals_rid,
+		   model.get_or_create_ptr_svalue (ptr_type_node,
+						   x_in_child_rid),
+		   &ctxt);
+
+  /* Point another global pointer at p: q = &p.  */
+  region_id q_in_globals_rid = model.get_lvalue (q, &ctxt);
+  model.set_value (q_in_globals_rid,
+		   model.get_or_create_ptr_svalue (ptr_type_node,
+						   p_in_globals_rid),
+		   &ctxt);
+
+  /* Test get_descendents.  */
+  region_id_set descendents (&model);
+  model.get_descendents (child_frame_rid, &descendents, region_id::null ());
+  ASSERT_TRUE (descendents.region_p (child_frame_rid));
+  ASSERT_TRUE (descendents.region_p (x_in_child_rid));
+  ASSERT_FALSE (descendents.region_p (a_in_parent_rid));
+  ASSERT_EQ (descendents.num_regions (), 3);
+#if 0
+  auto_vec<region_id> test_vec;
+  for (region_id_set::iterator_t iter = descendents.begin ();
+       iter != descendents.end ();
+       ++iter)
+    test_vec.safe_push (*iter);
+  gcc_unreachable (); // TODO
+  //ASSERT_EQ ();
+#endif
+
+  ASSERT_DUMP_EQ (model, true,
+		  "x: 0, {y}: unknown, p: &x, q: &p, b < 10, y != 5");
+
+  /* Pop the "child_fn" frame from the stack.  */
+  purge_stats purged;
+  model.pop_frame (true, &purged, &ctxt);
+
+  /* We should have purged the unknown values for x and y. */
+  ASSERT_EQ (purged.m_num_svalues, 2);
+
+  /* We should have purged the frame region and the regions for x and y. */
+  ASSERT_EQ (purged.m_num_regions, 3);
+
+  /* We should have purged the constraint on y.  */
+  ASSERT_EQ (purged.m_num_equiv_classes, 1);
+  ASSERT_EQ (purged.m_num_constraints, 1);
+
+  /* Verify that p (which was pointing at the local "x" in the popped
+     frame) has been poisoned.  */
+  svalue *new_p_sval = model.get_svalue (model.get_rvalue (p, &ctxt));
+  ASSERT_EQ (new_p_sval->get_kind (), SK_POISONED);
+  ASSERT_EQ (new_p_sval->dyn_cast_poisoned_svalue ()->get_poison_kind (),
+	     POISON_KIND_POPPED_STACK);
+
+  /* Verify that q still points to p, in spite of the region
+     renumbering.  */
+  svalue *new_q_sval = model.get_svalue (model.get_rvalue (q, &ctxt));
+  ASSERT_EQ (new_q_sval->get_kind (), SK_REGION);
+  ASSERT_EQ (new_q_sval->dyn_cast_region_svalue ()->get_pointee (),
+	     model.get_lvalue (p, &ctxt));
+
+  /* Verify that top of stack has been updated.  */
+  ASSERT_EQ (model.get_current_frame_id (), parent_frame_rid);
+
+  /* Verify locals in parent frame.  */
+  /* Verify "a" still has its value.  */
+  svalue *new_a_sval = model.get_svalue (model.get_rvalue (a, &ctxt));
+  ASSERT_EQ (new_a_sval->get_kind (), SK_CONSTANT);
+  ASSERT_EQ (new_a_sval->dyn_cast_constant_svalue ()->get_constant (),
+	     int_42);
+  /* Verify "b" still has its constraint.  */
+  ASSERT_EQ (model.eval_condition (b, LT_EXPR, int_10, &ctxt),
+	     tristate (tristate::TS_TRUE));
+}
+
+/* Verify that get_representative_path_var works as expected, that
+   we can map from region ids to parms and back within a recursive call
+   stack.  */
+
+static void
+test_get_representative_path_var ()
+{
+  auto_vec <tree> param_types;
+  tree fndecl = make_fndecl (integer_type_node,
+			     "factorial",
+			     param_types);
+  allocate_struct_function (fndecl, true);
+
+  /* Parm "n".  */
+  tree n = build_decl (UNKNOWN_LOCATION, PARM_DECL,
+		       get_identifier ("n"),
+		       integer_type_node);
+
+  region_model model;
+
+  /* Push 5 stack frames for "factorial", each with a param  */
+  auto_vec<region_id> parm_rids;
+  auto_vec<svalue_id> parm_sids;
+  for (int depth = 0; depth < 5; depth++)
+    {
+      region_id frame_rid
+	= model.push_frame (DECL_STRUCT_FUNCTION (fndecl), NULL, NULL);
+      region_id rid_n = model.get_lvalue (path_var (n, depth), NULL);
+      parm_rids.safe_push (rid_n);
+
+      ASSERT_EQ (model.get_region (rid_n)->get_parent (), frame_rid);
+
+      svalue_id sid_n
+	= model.set_to_new_unknown_value (rid_n, integer_type_node, NULL);
+      parm_sids.safe_push (sid_n);
+    }
+
+  /* Verify that we can recognize that the regions are the parms,
+     at every depth.  */
+  for (int depth = 0; depth < 5; depth++)
+    {
+      ASSERT_EQ (model.get_representative_path_var (parm_rids[depth]),
+		 path_var (n, depth));
+      /* ...and that we can lookup lvalues for locals for all frames,
+	 not just the top.  */
+      ASSERT_EQ (model.get_lvalue (path_var (n, depth), NULL),
+		 parm_rids[depth]);
+      /* ...and that we can locate the svalues.  */
+      auto_vec<path_var> pvs;
+      model.get_path_vars_for_svalue (parm_sids[depth], &pvs);
+      ASSERT_EQ (pvs.length (), 1);
+      ASSERT_EQ (pvs[0], path_var (n, depth));
+    }
+}
+
+/* Verify that the core regions within a region_model are in a consistent
+   order after canonicalization.  */
+
+static void
+test_canonicalization_1 ()
+{
+  region_model model0;
+  model0.get_root_region ()->ensure_stack_region (&model0);
+  model0.get_root_region ()->ensure_globals_region (&model0);
+
+  region_model model1;
+  model1.get_root_region ()->ensure_globals_region (&model1);
+  model1.get_root_region ()->ensure_stack_region (&model1);
+
+  model0.canonicalize (NULL);
+  model1.canonicalize (NULL);
+  ASSERT_EQ (model0, model1);
+}
+
+/* Verify that region models for
+      x = 42; y = 113;
+   and
+      y = 113; x = 42;
+   are equal after canonicalization.  */
+
+static void
+test_canonicalization_2 ()
+{
+  tree int_42 = build_int_cst (integer_type_node, 42);
+  tree int_113 = build_int_cst (integer_type_node, 113);
+  tree x = build_global_decl ("x", integer_type_node);
+  tree y = build_global_decl ("y", integer_type_node);
+
+  region_model model0;
+  model0.set_value (model0.get_lvalue (x, NULL),
+		    model0.get_rvalue (int_42, NULL),
+		    NULL);
+  model0.set_value (model0.get_lvalue (y, NULL),
+		    model0.get_rvalue (int_113, NULL),
+		    NULL);
+
+  region_model model1;
+  model1.set_value (model1.get_lvalue (y, NULL),
+		    model1.get_rvalue (int_113, NULL),
+		    NULL);
+  model1.set_value (model1.get_lvalue (x, NULL),
+		    model1.get_rvalue (int_42, NULL),
+		    NULL);
+
+  model0.canonicalize (NULL);
+  model1.canonicalize (NULL);
+  ASSERT_EQ (model0, model1);
+}
+
+/* Verify that constraints for
+     x > 3 && y > 42
+   and
+     y > 42 && x > 3
+   are equal after canonicalization.  */
+
+static void
+test_canonicalization_3 ()
+{
+  tree int_3 = build_int_cst (integer_type_node, 3);
+  tree int_42 = build_int_cst (integer_type_node, 42);
+  tree x = build_global_decl ("x", integer_type_node);
+  tree y = build_global_decl ("y", integer_type_node);
+
+  region_model model0;
+  model0.add_constraint (x, GT_EXPR, int_3, NULL);
+  model0.add_constraint (y, GT_EXPR, int_42, NULL);
+
+  region_model model1;
+  model1.add_constraint (y, GT_EXPR, int_42, NULL);
+  model1.add_constraint (x, GT_EXPR, int_3, NULL);
+
+  model0.canonicalize (NULL);
+  model1.canonicalize (NULL);
+  ASSERT_EQ (model0, model1);
+}
+
+/* Assert that if we have two region_model instances
+   with values VAL_A and VAL_B for EXPR that they are
+   mergable.  Write the merged model to *OUT_MERGED_MODEL,
+   and the merged svalue ptr to *OUT_MERGED_SVALUE.
+   If VAL_A or VAL_B are NULL_TREE, don't populate EXPR
+   for that region_model.  */
+
+static void
+assert_region_models_merge (tree expr, tree val_a, tree val_b,
+			    region_model *out_merged_model,
+			    svalue **out_merged_svalue)
+{
+  test_region_model_context ctxt;
+  region_model model0;
+  region_model model1;
+  if (val_a)
+    model0.set_value (model0.get_lvalue (expr, &ctxt),
+		      model0.get_rvalue (val_a, &ctxt),
+		      &ctxt);
+  if (val_b)
+    model1.set_value (model1.get_lvalue (expr, &ctxt),
+		      model1.get_rvalue (val_b, &ctxt),
+		      &ctxt);
+
+  /* They should be mergeable.  */
+  ASSERT_TRUE (model0.can_merge_with_p (model1, out_merged_model));
+
+  svalue_id merged_svalue_sid = out_merged_model->get_rvalue (expr, &ctxt);
+  *out_merged_svalue = out_merged_model->get_svalue (merged_svalue_sid);
+}
+
+/* Verify that we can merge region_model instances.  */
+
+static void
+test_state_merging ()
+{
+  tree int_42 = build_int_cst (integer_type_node, 42);
+  tree int_113 = build_int_cst (integer_type_node, 113);
+  tree x = build_global_decl ("x", integer_type_node);
+  tree y = build_global_decl ("y", integer_type_node);
+  tree z = build_global_decl ("z", integer_type_node);
+  tree p = build_global_decl ("p", ptr_type_node);
+
+  tree addr_of_y = build1 (ADDR_EXPR, ptr_type_node, y);
+  tree addr_of_z = build1 (ADDR_EXPR, ptr_type_node, z);
+
+  auto_vec <tree> param_types;
+  tree test_fndecl = make_fndecl (integer_type_node, "test_fn", param_types);
+  allocate_struct_function (test_fndecl, true);
+
+  /* Param "a".  */
+  tree a = build_decl (UNKNOWN_LOCATION, PARM_DECL,
+		       get_identifier ("a"),
+		       integer_type_node);
+  tree addr_of_a = build1 (ADDR_EXPR, ptr_type_node, a);
+
+  {
+    region_model model0;
+    region_model model1;
+    region_model merged;
+    /* Verify empty models can be merged.  */
+    ASSERT_TRUE (model0.can_merge_with_p (model1, &merged));
+    ASSERT_EQ (model0, merged);
+  }
+
+  /* Verify that we can merge two contradictory constraints on the
+     value for a global.  */
+  /* TODO: verify that the merged model doesn't have a value for
+     the global  */
+  {
+    region_model model0;
+    region_model model1;
+    region_model merged;
+    test_region_model_context ctxt;
+    model0.add_constraint (x, EQ_EXPR, int_42, &ctxt);
+    model1.add_constraint (x, EQ_EXPR, int_113, &ctxt);
+    ASSERT_TRUE (model0.can_merge_with_p (model1, &merged));
+    ASSERT_NE (model0, merged);
+    ASSERT_NE (model1, merged);
+  }
+
+  /* Verify handling of a PARM_DECL.  */
+  {
+    test_region_model_context ctxt;
+    region_model model0;
+    region_model model1;
+    model0.push_frame (DECL_STRUCT_FUNCTION (test_fndecl), NULL, &ctxt);
+    model1.push_frame (DECL_STRUCT_FUNCTION (test_fndecl), NULL, &ctxt);
+    svalue_id sid_a
+      = model0.set_to_new_unknown_value (model0.get_lvalue (a, &ctxt),
+					 integer_type_node, &ctxt);
+    model1.set_to_new_unknown_value (model1.get_lvalue (a, &ctxt),
+				     integer_type_node, &ctxt);
+    ASSERT_EQ (model0, model1);
+
+    /* Check that get_value_by_name works for locals.  */
+    ASSERT_EQ (model0.get_value_by_name ("a"), sid_a);
+
+    /* They should be mergeable, and the result should be the same.  */
+    region_model merged;
+    ASSERT_TRUE (model0.can_merge_with_p (model1, &merged));
+    ASSERT_EQ (model0, merged);
+    /* In particular, there should be an unknown value for "a".  */
+    svalue *merged_a_sval = merged.get_svalue (merged.get_rvalue (a, &ctxt));
+    ASSERT_EQ (merged_a_sval->get_kind (), SK_UNKNOWN);
+  }
+
+  /* Verify handling of a global.  */
+  {
+    test_region_model_context ctxt;
+    region_model model0;
+    region_model model1;
+    svalue_id sid_x
+      = model0.set_to_new_unknown_value (model0.get_lvalue (x, &ctxt),
+					 integer_type_node, &ctxt);
+    model1.set_to_new_unknown_value (model1.get_lvalue (x, &ctxt),
+				     integer_type_node, &ctxt);
+    ASSERT_EQ (model0, model1);
+
+    /* Check that get_value_by_name works for globals.  */
+    ASSERT_EQ (model0.get_value_by_name ("x"), sid_x);
+
+    /* They should be mergeable, and the result should be the same.  */
+    region_model merged;
+    ASSERT_TRUE (model0.can_merge_with_p (model1, &merged));
+    ASSERT_EQ (model0, merged);
+    /* In particular, there should be an unknown value for "x".  */
+    svalue *merged_x_sval = merged.get_svalue (merged.get_rvalue (x, &ctxt));
+    ASSERT_EQ (merged_x_sval->get_kind (), SK_UNKNOWN);
+  }
+
+  /* Use global-handling to verify various combinations of values.  */
+
+  /* Two equal constant values.  */
+  {
+    region_model merged;
+    svalue *merged_x_sval;
+    assert_region_models_merge (x, int_42, int_42, &merged, &merged_x_sval);
+
+    /* In particular, there should be a constant value for "x".  */
+    ASSERT_EQ (merged_x_sval->get_kind (), SK_CONSTANT);
+    ASSERT_EQ (merged_x_sval->dyn_cast_constant_svalue ()->get_constant (),
+	       int_42);
+  }
+
+  /* Two non-equal constant values.  */
+  {
+    region_model merged;
+    svalue *merged_x_sval;
+    assert_region_models_merge (x, int_42, int_113, &merged, &merged_x_sval);
+
+    /* In particular, there should be an unknown value for "x".  */
+    ASSERT_EQ (merged_x_sval->get_kind (), SK_UNKNOWN);
+  }
+
+  /* Uninit and constant.  */
+  {
+    region_model merged;
+    svalue *merged_x_sval;
+    assert_region_models_merge (x, NULL_TREE, int_113, &merged, &merged_x_sval);
+
+    /* In particular, there should be an unknown value for "x".  */
+    ASSERT_EQ (merged_x_sval->get_kind (), SK_UNKNOWN);
+  }
+
+  /* Constant and uninit.  */
+  {
+    region_model merged;
+    svalue *merged_x_sval;
+    assert_region_models_merge (x, int_42, NULL_TREE, &merged, &merged_x_sval);
+
+    /* In particular, there should be an unknown value for "x".  */
+    ASSERT_EQ (merged_x_sval->get_kind (), SK_UNKNOWN);
+  }
+
+  /* Unknown and constant.  */
+  // TODO
+
+  /* Pointers: NULL and NULL.  */
+  // TODO
+
+  /* Pointers: NULL and non-NULL.  */
+  // TODO
+
+  /* Pointers: non-NULL and non-NULL: ptr to a local.  */
+  {
+    region_model model0;
+    model0.push_frame (DECL_STRUCT_FUNCTION (test_fndecl), NULL, NULL);
+    model0.set_to_new_unknown_value (model0.get_lvalue (a, NULL),
+				     integer_type_node, NULL);
+    model0.set_value (model0.get_lvalue (p, NULL),
+		      model0.get_rvalue (addr_of_a, NULL), NULL);
+
+    region_model model1 (model0);
+    ASSERT_EQ (model0, model1);
+
+    /* They should be mergeable, and the result should be the same.  */
+    region_model merged;
+    ASSERT_TRUE (model0.can_merge_with_p (model1, &merged));
+    ASSERT_EQ (model0, merged);
+  }
+
+  /* Pointers: non-NULL and non-NULL: ptr to a global.  */
+  {
+    region_model merged;
+    /* p == &y in both input models.  */
+    svalue *merged_p_sval;
+    assert_region_models_merge (p, addr_of_y, addr_of_y, &merged,
+				&merged_p_sval);
+
+    /* We should get p == &y in the merged model.  */
+    ASSERT_EQ (merged_p_sval->get_kind (), SK_REGION);
+    region_svalue *merged_p_ptr = merged_p_sval->dyn_cast_region_svalue ();
+    region_id merged_p_star_rid = merged_p_ptr->get_pointee ();
+    ASSERT_EQ (merged_p_star_rid, merged.get_lvalue (y, NULL));
+  }
+
+  /* Pointers: non-NULL ptrs to different globals: should be unknown.  */
+  {
+    region_model merged;
+    /* x == &y vs x == &z in the input models.  */
+    svalue *merged_x_sval;
+    assert_region_models_merge (x, addr_of_y, addr_of_z, &merged,
+				&merged_x_sval);
+
+    /* We should get x == unknown in the merged model.  */
+    ASSERT_EQ (merged_x_sval->get_kind (), SK_UNKNOWN);
+  }
+
+  /* Pointers: non-NULL and non-NULL: ptr to a heap region.  */
+  {
+    test_region_model_context ctxt;
+    region_model model0;
+    region_id new_rid = model0.add_new_malloc_region ();
+    svalue_id ptr_sid
+      = model0.get_or_create_ptr_svalue (ptr_type_node, new_rid);
+    model0.set_value (model0.get_lvalue (p, &ctxt),
+		      ptr_sid, &ctxt);
+    model0.canonicalize (&ctxt);
+
+    region_model model1 (model0);
+
+    ASSERT_EQ (model0, model1);
+
+    region_model merged;
+    ASSERT_TRUE (model0.can_merge_with_p (model1, &merged));
+
+    merged.canonicalize (&ctxt);
+
+    /* The merged model ought to be identical (after canonicalization,
+       at least).  */
+    ASSERT_EQ (model0, merged);
+  }
+
+  /* Two regions sharing the same unknown svalue should continue sharing
+     an unknown svalue after self-merger.  */
+  {
+    test_region_model_context ctxt;
+    region_model model0;
+    svalue_id sid
+      = model0.set_to_new_unknown_value (model0.get_lvalue (x, &ctxt),
+					 integer_type_node, &ctxt);
+    model0.set_value (model0.get_lvalue (y, &ctxt), sid, &ctxt);
+    region_model model1 (model0);
+
+    /* They should be mergeable, and the result should be the same.  */
+    region_model merged;
+    ASSERT_TRUE (model0.can_merge_with_p (model1, &merged));
+    ASSERT_EQ (model0, merged);
+
+    /* In particular, we should have x == y.  */
+    ASSERT_EQ (merged.eval_condition (x, EQ_EXPR, y, &ctxt),
+	       tristate (tristate::TS_TRUE));
+  }
+
+#if 0
+  {
+    region_model model0;
+    region_model model1;
+    test_region_model_context ctxt;
+    model0.add_constraint (x, EQ_EXPR, int_42, &ctxt);
+    model1.add_constraint (x, NE_EXPR, int_42, &ctxt);
+    ASSERT_TRUE (model0.can_merge_with_p (model1));
+  }
+
+  {
+    region_model model0;
+    region_model model1;
+    test_region_model_context ctxt;
+    model0.add_constraint (x, EQ_EXPR, int_42, &ctxt);
+    model1.add_constraint (x, NE_EXPR, int_42, &ctxt);
+    model1.add_constraint (x, EQ_EXPR, int_113, &ctxt);
+    ASSERT_TRUE (model0.can_merge_with_p (model1));
+  }
+#endif
+
+  // TODO: what can't we merge? need at least one such test
+
+  /* TODO: various things
+     - heap regions
+     - value merging:
+       - every combination, but in particular
+          - pairs of regions
+   */
+
+  /* Views.  */
+  {
+    test_region_model_context ctxt;
+    region_model model0;
+
+    region_id x_rid = model0.get_lvalue (x, &ctxt);
+    region_id x_as_ptr = model0.get_or_create_view (x_rid, ptr_type_node);
+    model0.set_value (x_as_ptr, model0.get_rvalue (addr_of_y, &ctxt), &ctxt);
+
+    region_model model1 (model0);
+    ASSERT_EQ (model1, model0);
+
+    /* They should be mergeable, and the result should be the same.  */
+    region_model merged;
+    ASSERT_TRUE (model0.can_merge_with_p (model1, &merged));
+  }
+}
+
+/* Verify that constraints are correctly merged when merging region_model
+   instances.  */
+
+static void
+test_constraint_merging ()
+{
+  tree int_0 = build_int_cst (integer_type_node, 0);
+  tree int_5 = build_int_cst (integer_type_node, 5);
+  tree x = build_global_decl ("x", integer_type_node);
+  tree y = build_global_decl ("y", integer_type_node);
+  tree z = build_global_decl ("z", integer_type_node);
+  tree n = build_global_decl ("n", integer_type_node);
+
+  test_region_model_context ctxt;
+
+  /* model0: 0 <= (x == y) < n.  */
+  region_model model0;
+  model0.set_to_new_unknown_value (model0.get_lvalue (x, &ctxt),
+				   integer_type_node, &ctxt);
+  model0.add_constraint (x, EQ_EXPR, y, &ctxt);
+  model0.add_constraint (x, GE_EXPR, int_0, NULL);
+  model0.add_constraint (x, LT_EXPR, n, NULL);
+
+  /* model1: z != 5 && (0 <= x < n).  */
+  region_model model1;
+  model1.set_to_new_unknown_value (model1.get_lvalue (x, &ctxt),
+				   integer_type_node, &ctxt);
+  model1.add_constraint (z, NE_EXPR, int_5, NULL);
+  model1.add_constraint (x, GE_EXPR, int_0, NULL);
+  model1.add_constraint (x, LT_EXPR, n, NULL);
+
+  /* They should be mergeable; the merged constraints should
+     be: (0 <= x < n).  */
+  region_model merged;
+  ASSERT_TRUE (model0.can_merge_with_p (model1, &merged));
+
+  ASSERT_EQ (merged.eval_condition (x, GE_EXPR, int_0, &ctxt),
+	     tristate (tristate::TS_TRUE));
+  ASSERT_EQ (merged.eval_condition (x, LT_EXPR, n, &ctxt),
+	     tristate (tristate::TS_TRUE));
+
+  ASSERT_EQ (merged.eval_condition (z, NE_EXPR, int_5, &ctxt),
+	     tristate (tristate::TS_UNKNOWN));
+  ASSERT_EQ (merged.eval_condition (x, LT_EXPR, y, &ctxt),
+	     tristate (tristate::TS_UNKNOWN));
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+analyzer_region_model_cc_tests ()
+{
+  test_dump ();
+  test_unique_constants ();
+  test_svalue_equality ();
+  test_region_equality ();
+  test_purging_by_criteria ();
+  test_purge_unused_svalues ();
+  test_assignment ();
+  test_stack_frames ();
+  test_get_representative_path_var ();
+  test_canonicalization_1 ();
+  test_canonicalization_2 ();
+  test_canonicalization_3 ();
+  test_state_merging ();
+  test_constraint_merging ();
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
new file mode 100644
index 0000000..6f92e94
--- /dev/null
+++ b/gcc/analyzer/region-model.h
@@ -0,0 +1,2076 @@
+/* Classes for modeling the state of memory.
+   Copyright (C) 2019 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/>.  */
+
+#ifndef GCC_ANALYZER_REGION_MODEL_H
+#define GCC_ANALYZER_REGION_MODEL_H
+
+#include "ordered-hash-map.h"
+#include "pretty-print.h"
+#include "selftest.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/sm.h"
+#include "analyzer/tristate.h"
+
+/* Implementation of the region-based ternary model described in:
+     "A Memory Model for Static Analysis of C Programs"
+      (Zhongxing Xu, Ted Kremenek, and Jian Zhang)
+     http://lcs.ios.ac.cn/~xuzb/canalyze/memmodel.pdf  */
+
+/* A tree, extended with stack frame information for locals, so that
+   we can distinguish between different values of locals within a potentially
+   recursive callstack.  */
+// TODO: would this be better as a new tree code?
+
+class path_var
+{
+public:
+  path_var (tree t, int stack_depth)
+  : m_tree (t), m_stack_depth (stack_depth)
+  {
+    // TODO: ignore stack depth for globals and constants
+  }
+
+  bool operator== (const path_var &other) const
+  {
+    return (m_tree == other.m_tree
+	    && m_stack_depth == other.m_stack_depth);
+  }
+
+  void dump (pretty_printer *pp) const;
+
+  tree m_tree;
+  int m_stack_depth; // or -1 for globals?
+};
+
+namespace inchash
+{
+  extern void add_path_var (path_var pv, hash &hstate);
+} // namespace inchash
+
+
+/* A region_model is effectively a graph of regions and symbolic values.
+   We store per-model IDs rather than pointers to make it easier to clone
+   and to compare graphs.  */
+
+/* An ID for an svalue within a region_model.  Internally, this is an index
+   into a vector of svalue * within the region_model.  */
+
+class svalue_id
+{
+public:
+  static svalue_id null () { return svalue_id (-1); }
+
+  svalue_id () : m_idx (-1) {}
+
+  bool operator== (const svalue_id &other) const
+  {
+    return m_idx == other.m_idx;
+  }
+
+  bool operator!= (const svalue_id &other) const
+  {
+    return m_idx != other.m_idx;
+  }
+
+  bool null_p () const { return m_idx == -1; }
+
+  static svalue_id from_int (int idx) { return svalue_id (idx); }
+  int as_int () const { return m_idx; }
+
+  void print (pretty_printer *pp) const;
+  void dump_node_name_to_pp (pretty_printer *pp) const;
+
+  void validate (const region_model &model) const;
+
+private:
+  svalue_id (int idx) : m_idx (idx) {}
+
+  int m_idx;
+};
+
+/* An ID for a region within a region_model.  Internally, this is an index
+   into a vector of region * within the region_model.  */
+
+class region_id
+{
+public:
+  static region_id null () { return region_id (-1); }
+
+  region_id () : m_idx (-1) {}
+
+  bool operator== (const region_id &other) const
+  {
+    return m_idx == other.m_idx;
+  }
+
+  bool operator!= (const region_id &other) const
+  {
+    return m_idx != other.m_idx;
+  }
+
+  bool null_p () const { return m_idx == -1; }
+
+  static region_id from_int (int idx) { return region_id (idx); }
+  int as_int () const { return m_idx; }
+
+  void print (pretty_printer *pp) const;
+  void dump_node_name_to_pp (pretty_printer *pp) const;
+
+  void validate (const region_model &model) const;
+
+private:
+  region_id (int idx) : m_idx (idx) {}
+
+  int m_idx;
+};
+
+/* A class for renumbering IDs within a region_model, mapping old IDs
+   to new IDs (e.g. when removing one or more elements, thus needing to
+   renumber).  */
+// TODO: could this be useful for equiv_class_ids?
+
+template <typename T>
+class id_map
+{
+ public:
+  id_map (int num_ids);
+  void put (T src, T dst);
+  T get_dst_for_src (T src) const;
+  T get_src_for_dst (T dst) const;
+  void dump_to_pp (pretty_printer *pp) const;
+  void dump () const;
+  void update (T *) const;
+
+ private:
+  auto_vec<T> m_src_to_dst;
+  auto_vec<T> m_dst_to_src;
+};
+
+typedef id_map<svalue_id> svalue_id_map;
+typedef id_map<region_id> region_id_map;
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class id_map.  */
+
+/* id_map's ctor, which populates the map with dummy null values.  */
+
+template <typename T>
+inline id_map<T>::id_map (int num_svalues)
+: m_src_to_dst (num_svalues),
+  m_dst_to_src (num_svalues)
+{
+  for (int i = 0; i < num_svalues; i++)
+    {
+      m_src_to_dst.quick_push (T::null ());
+      m_dst_to_src.quick_push (T::null ());
+    }
+}
+
+/* Record that SRC is to be mapped to DST.  */
+
+template <typename T>
+inline void
+id_map<T>::put (T src, T dst)
+{
+  m_src_to_dst[src.as_int ()] = dst;
+  m_dst_to_src[dst.as_int ()] = src;
+}
+
+/* Get the new value for SRC within the map.  */
+
+template <typename T>
+inline T
+id_map<T>::get_dst_for_src (T src) const
+{
+  if (src.null_p ())
+    return src;
+  return m_src_to_dst[src.as_int ()];
+}
+
+/* Given DST, a new value, determine which old value will be mapped to it
+   (the inverse of the map).  */
+
+template <typename T>
+inline T
+id_map<T>::get_src_for_dst (T dst) const
+{
+  if (dst.null_p ())
+    return dst;
+  return m_dst_to_src[dst.as_int ()];
+}
+
+/* Dump this id_map to PP.  */
+
+template <typename T>
+inline void
+id_map<T>::dump_to_pp (pretty_printer *pp) const
+{
+  pp_string (pp, "src to dst: {");
+  unsigned i;
+  T *dst;
+  FOR_EACH_VEC_ELT (m_src_to_dst, i, dst)
+    {
+      if (i > 0)
+	pp_string (pp, ", ");
+      T src (T::from_int (i));
+      src.print (pp);
+      pp_string (pp, " -> ");
+      dst->print (pp);
+    }
+  pp_string (pp, "}");
+  pp_newline (pp);
+
+  pp_string (pp, "dst to src: {");
+  T *src;
+  FOR_EACH_VEC_ELT (m_dst_to_src, i, src)
+    {
+      if (i > 0)
+	pp_string (pp, ", ");
+      T dst (T::from_int (i));
+      dst.print (pp);
+      pp_string (pp, " <- ");
+      src->print (pp);
+    }
+  pp_string (pp, "}");
+  pp_newline (pp);
+}
+
+/* Dump this id_map to stderr.  */
+
+template <typename T>
+DEBUG_FUNCTION inline void
+id_map<T>::dump () const
+{
+  pretty_printer pp;
+  pp.buffer->stream = stderr;
+  dump_to_pp (&pp);
+  pp_flush (&pp);
+}
+
+/* Update *ID from the old value to its new value in this map.  */
+
+template <typename T>
+inline void
+id_map<T>::update (T *id) const
+{
+  *id = get_dst_for_src (*id);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Variant of the above, which only stores things in one direction.
+   (e.g. for merging, when the number of destination regions is not
+   the same of the src regions, and can grow).  */
+
+template <typename T>
+class one_way_id_map
+{
+ public:
+  one_way_id_map (int num_ids);
+  void put (T src, T dst);
+  T get_dst_for_src (T src) const;
+  void dump_to_pp (pretty_printer *pp) const;
+  void dump () const;
+  void update (T *) const;
+
+ private:
+  auto_vec<T> m_src_to_dst;
+};
+
+typedef one_way_id_map<svalue_id> one_way_svalue_id_map;
+typedef one_way_id_map<region_id> one_way_region_id_map;
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class one_way_id_map.  */
+
+/* one_way_id_map's ctor, which populates the map with dummy null values.  */
+
+template <typename T>
+inline one_way_id_map<T>::one_way_id_map (int num_svalues)
+: m_src_to_dst (num_svalues)
+{
+  for (int i = 0; i < num_svalues; i++)
+    m_src_to_dst.quick_push (T::null ());
+}
+
+/* Record that SRC is to be mapped to DST.  */
+
+template <typename T>
+inline void
+one_way_id_map<T>::put (T src, T dst)
+{
+  m_src_to_dst[src.as_int ()] = dst;
+}
+
+/* Get the new value for SRC within the map.  */
+
+template <typename T>
+inline T
+one_way_id_map<T>::get_dst_for_src (T src) const
+{
+  if (src.null_p ())
+    return src;
+  return m_src_to_dst[src.as_int ()];
+}
+
+/* Dump this map to PP.  */
+
+template <typename T>
+inline void
+one_way_id_map<T>::dump_to_pp (pretty_printer *pp) const
+{
+  pp_string (pp, "src to dst: {");
+  unsigned i;
+  T *dst;
+  FOR_EACH_VEC_ELT (m_src_to_dst, i, dst)
+    {
+      if (i > 0)
+	pp_string (pp, ", ");
+      T src (T::from_int (i));
+      src.print (pp);
+      pp_string (pp, " -> ");
+      dst->print (pp);
+    }
+  pp_string (pp, "}");
+  pp_newline (pp);
+}
+
+/* Dump this map to stderr.  */
+
+template <typename T>
+DEBUG_FUNCTION inline void
+one_way_id_map<T>::dump () const
+{
+  pretty_printer pp;
+  pp.buffer->stream = stderr;
+  dump_to_pp (&pp);
+  pp_flush (&pp);
+}
+
+/* Update *ID from the old value to its new value in this map.  */
+
+template <typename T>
+inline void
+one_way_id_map<T>::update (T *id) const
+{
+  *id = get_dst_for_src (*id);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* A set of IDs within a region_model (either svalue_id or region_id).  */
+
+template <typename T>
+class id_set
+{
+public:
+  id_set (const region_model *model);
+
+  void add_region (T id)
+  {
+    if (!id.null_p ())
+      bitmap_set_bit (m_bitmap, id.as_int ());
+  }
+
+  bool region_p (T id) const
+  {
+    gcc_assert (!id.null_p ());
+    return bitmap_bit_p (const_cast <auto_sbitmap &> (m_bitmap),
+			 id.as_int ());
+  }
+
+  unsigned int num_regions ()
+  {
+    return bitmap_count_bits (m_bitmap);
+  }
+
+private:
+  auto_sbitmap m_bitmap;
+};
+
+typedef id_set<region_id> region_id_set;
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Various operations delete information from a region_model.
+
+   This struct tracks how many of each kind of entity were purged (e.g.
+   for selftests, and for debugging).  */
+
+struct purge_stats
+{
+  purge_stats ()
+  : m_num_svalues (0),
+    m_num_regions (0),
+    m_num_equiv_classes (0),
+    m_num_constraints (0),
+    m_num_client_items (0)
+  {}
+
+  int m_num_svalues;
+  int m_num_regions;
+  int m_num_equiv_classes;
+  int m_num_constraints;
+  int m_num_client_items;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* An enum for discriminating between the different concrete subclasses
+   of svalue.  */
+
+enum svalue_kind
+{
+  SK_REGION,
+  SK_CONSTANT,
+  SK_UNKNOWN,
+  SK_POISONED,
+  SK_SETJMP
+};
+
+/* svalue and its subclasses.
+
+   The class hierarchy looks like this (using indentation to show
+   inheritance, and with svalue_kinds shown for the concrete subclasses):
+
+   svalue
+     region_svalue (SK_REGION)
+     constant_svalue (SK_CONSTANT)
+     unknown_svalue (SK_UNKNOWN)
+     poisoned_svalue (SK_POISONED)
+     setjmp_svalue (SK_SETJMP).  */
+
+/* An abstract base class representing a value held by a region of memory.  */
+
+class svalue
+{
+public:
+  virtual ~svalue () {}
+
+  bool operator== (const svalue &other) const;
+  bool operator!= (const svalue &other) const { return !(*this == other); }
+
+  virtual svalue *clone () const = 0;
+
+  tree get_type () const { return m_type; }
+
+  virtual enum svalue_kind get_kind () const = 0;
+
+  hashval_t hash () const;
+
+  void print (const region_model &model,
+	      svalue_id this_sid,
+	      pretty_printer *pp) const;
+
+  virtual void dump_dot_to_pp (const region_model &model,
+			       svalue_id this_sid,
+			       pretty_printer *pp) const;
+
+  virtual region_svalue *dyn_cast_region_svalue () { return NULL; }
+  virtual constant_svalue *dyn_cast_constant_svalue () { return NULL; }
+  virtual const constant_svalue *dyn_cast_constant_svalue () const
+  { return NULL; }
+  virtual poisoned_svalue *dyn_cast_poisoned_svalue () { return NULL; }
+  virtual unknown_svalue *dyn_cast_unknown_svalue () { return NULL; }
+  virtual setjmp_svalue *dyn_cast_setjmp_svalue () { return NULL; }
+
+  virtual void remap_region_ids (const region_id_map &map);
+
+  virtual void walk_for_canonicalization (canonicalization *c) const;
+
+  virtual svalue_id get_child_sid (region *parent, region *child,
+				   region_model &model,
+				   region_model_context *ctxt);
+
+  tree maybe_get_constant () const;
+
+ protected:
+  svalue (tree type) : m_type (type) {}
+
+  virtual void add_to_hash (inchash::hash &hstate) const = 0;
+
+ private:
+  virtual void print_details (const region_model &model,
+			      svalue_id this_sid,
+			      pretty_printer *pp) const = 0;
+  tree m_type;
+};
+
+/* Concrete subclass of svalue representing a pointer value that points to
+   a known region  */
+
+class region_svalue : public svalue
+{
+public:
+  region_svalue (tree type, region_id rid) : svalue (type), m_rid (rid)
+  {
+    /* Should we support NULL ptrs here?  */
+    gcc_assert (!rid.null_p ());
+  }
+
+  bool compare_fields (const region_svalue &other) const;
+
+  svalue *clone () const FINAL OVERRIDE
+  { return new region_svalue (get_type (), m_rid); }
+
+  enum svalue_kind get_kind () const FINAL OVERRIDE { return SK_REGION; }
+
+  void dump_dot_to_pp (const region_model &model,
+		       svalue_id this_sid,
+		       pretty_printer *pp) const
+    FINAL OVERRIDE;
+
+  region_svalue *dyn_cast_region_svalue () FINAL OVERRIDE { return this; }
+
+  region_id get_pointee () const { return m_rid; }
+
+  void remap_region_ids (const region_id_map &map) FINAL OVERRIDE;
+
+  static void merge_values (const region_svalue &region_sval_a,
+			    const region_svalue &region_sval_b,
+			    svalue_id *merged_sid,
+			    tree type,
+			    model_merger *merger);
+
+  void walk_for_canonicalization (canonicalization *c) const FINAL OVERRIDE;
+
+  static tristate eval_condition (region_svalue *lhs_ptr,
+				  enum tree_code op,
+				  region_svalue *rhs_ptr);
+
+  void add_to_hash (inchash::hash &hstate) const FINAL OVERRIDE;
+
+ private:
+  void print_details (const region_model &model,
+		      svalue_id this_sid,
+		      pretty_printer *pp) const
+    FINAL OVERRIDE;
+
+  region_id m_rid;
+};
+
+template <>
+template <>
+inline bool
+is_a_helper <region_svalue *>::test (svalue *sval)
+{
+  return sval->get_kind () == SK_REGION;
+}
+
+/* Concrete subclass of svalue representing a specific constant value.  */
+
+class constant_svalue : public svalue
+{
+public:
+  constant_svalue (tree cst_expr)
+  : svalue (TREE_TYPE (cst_expr)), m_cst_expr (cst_expr)
+  {
+    gcc_assert (cst_expr);
+    gcc_assert (CONSTANT_CLASS_P (cst_expr));
+  }
+
+  bool compare_fields (const constant_svalue &other) const;
+
+  svalue *clone () const FINAL OVERRIDE
+  { return new constant_svalue (m_cst_expr); }
+
+  enum svalue_kind get_kind () const FINAL OVERRIDE { return SK_CONSTANT; }
+
+  void add_to_hash (inchash::hash &hstate) const FINAL OVERRIDE;
+
+  constant_svalue *dyn_cast_constant_svalue () FINAL OVERRIDE { return this; }
+  const constant_svalue *dyn_cast_constant_svalue () const FINAL OVERRIDE
+  { return this; }
+
+  tree get_constant () const { return m_cst_expr; }
+
+  static void merge_values (const constant_svalue &cst_sval_a,
+			    const constant_svalue &cst_sval_b,
+			    svalue_id *merged_sid,
+			    model_merger *merger);
+
+  static tristate eval_condition (constant_svalue *lhs,
+				  enum tree_code op,
+				  constant_svalue *rhs);
+
+  svalue_id get_child_sid (region *parent, region *child,
+			   region_model &model,
+			   region_model_context *ctxt) FINAL OVERRIDE;
+
+ private:
+  void print_details (const region_model &model,
+		      svalue_id this_sid,
+		      pretty_printer *pp) const
+    FINAL OVERRIDE;
+
+  tree m_cst_expr;
+};
+
+template <>
+template <>
+inline bool
+is_a_helper <constant_svalue *>::test (svalue *sval)
+{
+  return sval->get_kind () == SK_CONSTANT;
+}
+
+/* Concrete subclass of svalue representing a unique but unknown value.
+   Comparisons of variables that share the same unknown value are known
+   to be equal, even if we don't know what the value is.  */
+
+class unknown_svalue : public svalue
+{
+public:
+  unknown_svalue (tree type)
+  : svalue (type)
+  {}
+
+  bool compare_fields (const unknown_svalue &other) const;
+
+  svalue *clone () const FINAL OVERRIDE
+  { return new unknown_svalue (get_type ()); }
+
+  enum svalue_kind get_kind () const FINAL OVERRIDE { return SK_UNKNOWN; }
+
+  void add_to_hash (inchash::hash &hstate) const FINAL OVERRIDE;
+
+  unknown_svalue *dyn_cast_unknown_svalue () FINAL OVERRIDE { return this; }
+
+ private:
+  void print_details (const region_model &model,
+		      svalue_id this_sid,
+		      pretty_printer *pp) const
+    FINAL OVERRIDE;
+};
+
+/* An enum describing a particular kind of "poisoned" value.  */
+
+enum poison_kind
+{
+  /* For use to describe uninitialized memory.  */
+  POISON_KIND_UNINIT,
+
+  /* For use to describe freed memory.  */
+  POISON_KIND_FREED,
+
+  /* For use on pointers to regions within popped stack frames.  */
+  POISON_KIND_POPPED_STACK
+};
+
+extern const char *poison_kind_to_str (enum poison_kind);
+
+/* Concrete subclass of svalue representing a value that should not
+   be used (e.g. uninitialized memory, freed memory).  */
+
+class poisoned_svalue : public svalue
+{
+public:
+  poisoned_svalue (enum poison_kind kind, tree type)
+  : svalue (type), m_kind (kind) {}
+
+  bool compare_fields (const poisoned_svalue &other) const;
+
+  svalue *clone () const FINAL OVERRIDE
+  { return new poisoned_svalue (m_kind, get_type ()); }
+
+  enum svalue_kind get_kind () const FINAL OVERRIDE { return SK_POISONED; }
+
+  void add_to_hash (inchash::hash &hstate) const FINAL OVERRIDE;
+
+  poisoned_svalue *dyn_cast_poisoned_svalue () FINAL OVERRIDE { return this; }
+
+  enum poison_kind get_poison_kind () const { return m_kind; }
+
+ private:
+  void print_details (const region_model &model,
+		      svalue_id this_sid,
+		      pretty_printer *pp) const
+    FINAL OVERRIDE;
+
+  enum poison_kind m_kind;
+};
+
+template <>
+template <>
+inline bool
+is_a_helper <poisoned_svalue *>::test (svalue *sval)
+{
+  return sval->get_kind () == SK_POISONED;
+}
+
+/* Concrete subclass of svalue representing setjmp buffers, so that
+   longjmp can potentially "return" to an entirely different function.  */
+
+class setjmp_svalue : public svalue
+{
+public:
+  setjmp_svalue (const exploded_node *enode, tree type)
+  : svalue (type), m_enode (enode)
+  {}
+
+  bool compare_fields (const setjmp_svalue &other) const;
+
+  svalue *clone () const FINAL OVERRIDE
+  { return new setjmp_svalue (m_enode, get_type ()); }
+
+  enum svalue_kind get_kind () const FINAL OVERRIDE { return SK_SETJMP; }
+
+  void add_to_hash (inchash::hash &hstate) const FINAL OVERRIDE;
+
+  setjmp_svalue *dyn_cast_setjmp_svalue () FINAL OVERRIDE { return this; }
+
+  int get_index () const;
+
+  const exploded_node *get_exploded_node () const { return m_enode; }
+
+ private:
+  void print_details (const region_model &model,
+		      svalue_id this_sid,
+		      pretty_printer *pp) const
+    FINAL OVERRIDE;
+
+  const exploded_node *m_enode;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* An enum for discriminating between the different concrete subclasses
+   of region.  */
+
+enum region_kind
+{
+  RK_PRIMITIVE,
+  RK_STRUCT,
+  RK_UNION,
+  RK_FRAME,
+  RK_GLOBALS,
+  RK_CODE,
+  RK_FUNCTION,
+  RK_ARRAY,
+  RK_STACK,
+  RK_HEAP,
+  RK_ROOT,
+  RK_SYMBOLIC
+};
+
+extern const char *region_kind_to_str (enum region_kind);
+
+/* Region and its subclasses.
+
+   The class hierarchy looks like this (using indentation to show
+   inheritance, and with region_kinds shown for the concrete subclasses):
+
+   region
+     primitive_region (RK_PRIMITIVE)
+     map_region
+       struct_or_union_region
+         struct_region (RK_STRUCT)
+         union_region (RK_UNION)
+       scope_region
+         frame_region (RK_FRAME)
+         globals_region (RK_GLOBALS)
+       code_region (RK_CODE)
+       function_region (RK_FUNCTION)
+     array_region (RK_ARRAY)
+     stack_region (RK_STACK)
+     heap_region (RK_HEAP)
+     root_region (RK_ROOT)
+     label_region (RK_FUNCTION)
+     symbolic_region (RK_SYMBOLIC).  */
+
+/* Abstract base class representing a chunk of memory.
+
+   Regions form a tree-like hierarchy, with a root region at the base,
+   with memory space regions within it, representing the stack and
+   globals, with frames within the stack, and regions for variables
+   within the frames and the "globals" region.  Regions for structs
+   can have subregions for fields.
+
+   A region can optionally have a value, or inherit its value from
+   the first ancestor with a value.  For example, the stack region
+   has a "uninitialized" poison value which is inherited by all
+   descendent regions that don't themselves have a value.  */
+
+class region
+{
+public:
+  virtual ~region () {}
+
+  bool operator== (const region &other) const;
+  bool operator!= (const region &other) const { return !(*this == other); }
+
+  virtual region *clone () const = 0;
+
+  virtual enum region_kind get_kind () const = 0;
+  virtual map_region *dyn_cast_map_region () { return NULL; }
+  virtual const symbolic_region *dyn_cast_symbolic_region () const
+  { return NULL; }
+
+  region_id get_parent () const { return m_parent_rid; }
+  region *get_parent_region (const region_model &model) const;
+
+  void set_value (region_model &model, region_id this_rid, svalue_id rhs_sid,
+		  region_model_context *ctxt);
+  svalue_id get_value (region_model &model, bool non_null,
+		       region_model_context *ctxt);
+  svalue_id get_value_direct () const { return m_sval_id; }
+
+  svalue_id get_inherited_child_sid (region *child,
+				     region_model &model,
+				     region_model_context *ctxt);
+
+  tree get_type () const { return m_type; }
+
+  hashval_t hash () const;
+
+  void print (const region_model &model,
+	      region_id this_rid,
+	      pretty_printer *pp) const;
+
+  virtual void dump_dot_to_pp (const region_model &model,
+			       region_id this_rid,
+			       pretty_printer *pp) const;
+
+  void dump_to_pp (const region_model &model,
+		   region_id this_rid,
+		   pretty_printer *pp,
+		   const char *prefix,
+		   bool is_last_child) const;
+  virtual void dump_child_label (const region_model &model,
+				 region_id this_rid,
+				 region_id child_rid,
+				 pretty_printer *pp) const;
+
+  void remap_svalue_ids (const svalue_id_map &map);
+  virtual void remap_region_ids (const region_id_map &map);
+
+  virtual void walk_for_canonicalization (canonicalization *c) const = 0;
+
+  void add_view (region_id view_rid, region_model *model);
+  region_id get_view (tree type, region_model *model) const;
+  bool is_view_p () const { return m_is_view; }
+
+  void validate (const region_model *model) const;
+
+  bool non_null_p (const region_model &model) const;
+
+ protected:
+  region (region_id parent_rid, svalue_id sval_id, tree type);
+  region (const region &other);
+
+  virtual void add_to_hash (inchash::hash &hstate) const;
+  virtual void print_fields (const region_model &model,
+			     region_id this_rid,
+			     pretty_printer *pp) const;
+
+ private:
+  void become_active_view (region_model &model, region_id this_rid);
+  void deactivate_any_active_view (region_model &model);
+  void deactivate_view (region_model &model, region_id this_view_rid);
+
+  region_id m_parent_rid;
+  svalue_id m_sval_id;
+  tree m_type;
+  /* Child regions that are "views" (one per type).  */
+  auto_vec<region_id> m_view_rids;
+
+  bool m_is_view;
+  region_id m_active_view_rid;
+};
+
+template <>
+template <>
+inline bool
+is_a_helper <region *>::test (region *)
+{
+  return true;
+}
+
+/* Concrete region subclass for storing "primitive" types (integral types,
+   pointers, etc).  */
+
+class primitive_region : public region
+{
+public:
+  primitive_region (region_id parent_rid, tree type)
+  : region (parent_rid, svalue_id::null (), type)
+  {}
+
+  region *clone () const FINAL OVERRIDE;
+
+  enum region_kind get_kind () const FINAL OVERRIDE { return RK_PRIMITIVE; }
+
+  void walk_for_canonicalization (canonicalization *c) const FINAL OVERRIDE;
+};
+
+/* A region that has children identified by tree keys.
+   For example a stack frame has subregions per local, and a region
+   for a struct has subregions per field.  */
+
+class map_region : public region
+{
+public:
+  typedef ordered_hash_map<tree, region_id> map_t;
+  typedef map_t::iterator iterator_t;
+
+  map_region (region_id parent_rid, tree type)
+  : region (parent_rid, svalue_id::null (), type)
+  {}
+  map_region (const map_region &other);
+
+  map_region *dyn_cast_map_region () FINAL OVERRIDE { return this; }
+
+  void dump_dot_to_pp (const region_model &model,
+		       region_id this_rid,
+		       pretty_printer *pp) const
+    FINAL OVERRIDE;
+
+  void dump_child_label (const region_model &model,
+			 region_id this_rid,
+			 region_id child_rid,
+			 pretty_printer *pp) const
+    FINAL OVERRIDE;
+
+  region_id get_or_create (region_model *model,
+			   region_id this_rid,
+			   tree expr, tree type);
+  void unbind (tree expr);
+  region_id *get (tree expr);
+
+  void remap_region_ids (const region_id_map &map) FINAL OVERRIDE;
+
+  tree get_tree_for_child_region (region_id child_rid) const;
+
+  tree get_tree_for_child_region (region *child,
+				  const region_model &model) const;
+
+  static bool can_merge_p (const map_region *map_region_a,
+			   const map_region *map_region_b,
+			   map_region *merged_map_region,
+			   region_id merged_rid,
+			   model_merger *merger);
+
+  void walk_for_canonicalization (canonicalization *c) const FINAL OVERRIDE;
+
+  virtual bool valid_key_p (tree key) const = 0;
+
+  svalue_id get_value_by_name (tree identifier,
+			       const region_model &model) const;
+
+  iterator_t begin () { return m_map.begin (); }
+  iterator_t end () { return m_map.end (); }
+  size_t elements () const { return m_map.elements (); }
+
+ protected:
+  bool compare_fields (const map_region &other) const;
+  void add_to_hash (inchash::hash &hstate) const OVERRIDE;
+  void print_fields (const region_model &model,
+		     region_id this_rid,
+		     pretty_printer *pp) const
+    OVERRIDE;
+
+ private:
+  /* Mapping from tree to child region.  */
+  map_t m_map;
+};
+
+template <>
+template <>
+inline bool
+is_a_helper <map_region *>::test (region *reg)
+{
+  return (reg->dyn_cast_map_region () != NULL);
+}
+
+/* Abstract subclass representing a region with fields
+   (either a struct or a union).  */
+
+class struct_or_union_region : public map_region
+{
+public:
+  bool valid_key_p (tree key) const FINAL OVERRIDE;
+
+ protected:
+  struct_or_union_region (region_id parent_rid, tree type)
+  : map_region (parent_rid, type)
+  {}
+
+  bool compare_fields (const struct_or_union_region &other) const;
+};
+
+template <>
+template <>
+inline bool
+is_a_helper <struct_or_union_region *>::test (region *reg)
+{
+  return (reg->get_kind () == RK_STRUCT
+	  || reg->get_kind () == RK_UNION);
+}
+
+/* Concrete region subclass.  A map_region representing a struct, using
+   FIELD_DECLs for its keys.  */
+
+class struct_region : public struct_or_union_region
+{
+public:
+  struct_region (region_id parent_rid, tree type)
+  : struct_or_union_region (parent_rid, type)
+  {
+    gcc_assert (TREE_CODE (type) == RECORD_TYPE);
+  }
+
+  region *clone () const FINAL OVERRIDE;
+
+  enum region_kind get_kind () const FINAL OVERRIDE { return RK_STRUCT; }
+
+  bool compare_fields (const struct_region &other) const;
+};
+
+template <>
+template <>
+inline bool
+is_a_helper <struct_region *>::test (region *reg)
+{
+  return reg->get_kind () == RK_STRUCT;
+}
+
+/* Concrete region subclass.  A map_region representing a union, using
+   FIELD_DECLs for its keys.  */
+
+class union_region : public struct_or_union_region
+{
+public:
+  union_region (region_id parent_rid, tree type)
+  : struct_or_union_region (parent_rid, type)
+  {
+    gcc_assert (TREE_CODE (type) == UNION_TYPE);
+  }
+
+  region *clone () const FINAL OVERRIDE;
+
+  enum region_kind get_kind () const FINAL OVERRIDE { return RK_UNION; }
+
+  bool compare_fields (const union_region &other) const;
+};
+
+template <>
+template <>
+inline bool
+is_a_helper <union_region *>::test (region *reg)
+{
+  return reg->get_kind () == RK_UNION;
+}
+
+/* Abstract map_region subclass for accessing decls, used as a base class
+   for function frames and for the globals region.  */
+
+class scope_region : public map_region
+{
+ public:
+
+ protected:
+  scope_region (region_id parent_rid)
+  : map_region (parent_rid, NULL_TREE)
+  {}
+
+  scope_region (const scope_region &other)
+  : map_region (other)
+  {
+  }
+
+  bool compare_fields (const scope_region &other) const;
+};
+
+/* Concrete region subclass, representing a function frame on the stack,
+   to contain the locals.  */
+
+class frame_region : public scope_region
+{
+public:
+  frame_region (region_id parent_rid, function *fun, int depth)
+  : scope_region (parent_rid), m_fun (fun), m_depth (depth)
+  {}
+
+  frame_region (const frame_region &other)
+  : scope_region (other), m_fun (other.m_fun), m_depth (other.m_depth)
+  {
+  }
+
+  /* region vfuncs.  */
+  region *clone () const FINAL OVERRIDE;
+  enum region_kind get_kind () const FINAL OVERRIDE { return RK_FRAME; }
+  void print_fields (const region_model &model,
+		     region_id this_rid,
+		     pretty_printer *pp) const
+    FINAL OVERRIDE;
+  void add_to_hash (inchash::hash &hstate) const FINAL OVERRIDE;
+
+  /* map_region vfuncs.  */
+  bool valid_key_p (tree key) const FINAL OVERRIDE;
+
+  /* Accessors.  */
+  function *get_function () const { return m_fun; }
+  int get_depth () const { return m_depth; }
+
+  bool compare_fields (const frame_region &other) const;
+
+ private:
+  function *m_fun;
+  int m_depth;
+};
+
+template <>
+template <>
+inline bool
+is_a_helper <frame_region *>::test (region *reg)
+{
+  return reg->get_kind () == RK_FRAME;
+}
+
+/* Concrete region subclass, to hold global variables (data and bss).  */
+
+class globals_region : public scope_region
+{
+ public:
+  globals_region (region_id parent_rid)
+  : scope_region (parent_rid)
+  {}
+
+  globals_region (const globals_region &other)
+  : scope_region (other)
+  {
+  }
+
+  /* region vfuncs.  */
+  region *clone () const FINAL OVERRIDE;
+  enum region_kind get_kind () const FINAL OVERRIDE { return RK_GLOBALS; }
+
+  /* map_region vfuncs.  */
+  bool valid_key_p (tree key) const FINAL OVERRIDE;
+
+  bool compare_fields (const globals_region &other) const;
+};
+
+template <>
+template <>
+inline bool
+is_a_helper <globals_region *>::test (region *reg)
+{
+  return reg->get_kind () == RK_GLOBALS;
+}
+
+/* Concrete region subclass.  A map_region representing the code, using
+   FUNCTION_DECLs for its keys.  */
+
+class code_region : public map_region
+{
+public:
+  code_region (region_id parent_rid)
+  : map_region (parent_rid, NULL_TREE)
+  {}
+  code_region (const code_region &other)
+  : map_region (other)
+  {}
+
+  /* region vfuncs.  */
+  region *clone () const FINAL OVERRIDE;
+  enum region_kind get_kind () const FINAL OVERRIDE { return RK_CODE; }
+
+  /* map_region vfunc.  */
+  bool valid_key_p (tree key) const FINAL OVERRIDE;
+
+  region_id get_element (region_model *model,
+			 region_id this_rid,
+			 svalue_id index_sid,
+			 region_model_context *ctxt);
+
+  bool compare_fields (const code_region &other) const;
+};
+
+template <>
+template <>
+inline bool
+is_a_helper <code_region *>::test (region *reg)
+{
+  return reg->get_kind () == RK_CODE;
+}
+
+/* Concrete region subclass.  A map_region representing the code for
+   a particular function, using LABEL_DECLs for its keys.  */
+
+class function_region : public map_region
+{
+public:
+  function_region (region_id parent_rid, tree type)
+  : map_region (parent_rid, type)
+  {
+    gcc_assert (TREE_CODE (type) == FUNCTION_TYPE);
+  }
+  function_region (const function_region &other)
+  : map_region (other)
+  {}
+
+  /* region vfuncs.  */
+  region *clone () const FINAL OVERRIDE;
+  enum region_kind get_kind () const FINAL OVERRIDE { return RK_FUNCTION; }
+
+  /* map_region vfunc.  */
+  bool valid_key_p (tree key) const FINAL OVERRIDE;
+
+  region_id get_element (region_model *model,
+			 region_id this_rid,
+			 svalue_id index_sid,
+			 region_model_context *ctxt);
+
+  bool compare_fields (const function_region &other) const;
+};
+
+template <>
+template <>
+inline bool
+is_a_helper <function_region *>::test (region *reg)
+{
+  return reg->get_kind () == RK_FUNCTION;
+}
+
+/* Concrete region subclass representing an array (or an array-like view
+   of a parent region of memory.
+   This can't be a map_region as we can't use trees as the keys: there's
+   no guarantee about the uniqueness of an INTEGER_CST.  */
+
+class array_region : public region
+{
+public:
+#if 0
+  wide_int m_test;
+
+  typedef wide_int key_t;
+  typedef int_hash <wide_int, -1, -2> hash_t;
+  typedef ordered_hash_map<hash_t, region_id> map_t;
+#else
+  typedef int key_t;
+  typedef int_hash <int, -1, -2> int_hash_t;
+  typedef ordered_hash_map<int_hash_t, region_id> map_t;
+#endif
+  typedef map_t::iterator iterator_t;
+
+  array_region (region_id parent_rid, tree type)
+  : region (parent_rid, svalue_id::null (), type)
+  {
+    gcc_assert (TREE_CODE (type) == ARRAY_TYPE);
+  }
+  array_region (const array_region &other);
+
+  void dump_dot_to_pp (const region_model &model,
+		       region_id this_rid,
+		       pretty_printer *pp) const
+    FINAL OVERRIDE;
+
+  void dump_child_label (const region_model &model,
+			 region_id this_rid,
+			 region_id child_rid,
+			 pretty_printer *pp) const
+    FINAL OVERRIDE;
+
+  /* region vfuncs.  */
+  region *clone () const FINAL OVERRIDE;
+  enum region_kind get_kind () const FINAL OVERRIDE { return RK_ARRAY; }
+
+  region_id get_element (region_model *model,
+			 region_id this_rid,
+			 svalue_id index_sid,
+			 region_model_context *ctxt);
+
+  bool compare_fields (const array_region &other) const;
+
+  static bool can_merge_p (const array_region *array_region_a,
+			   const array_region *array_region_b,
+			   array_region *merged_array_region,
+			   region_id merged_rid,
+			   model_merger *merger);
+
+  void walk_for_canonicalization (canonicalization *c) const FINAL OVERRIDE;
+
+  iterator_t begin () { return m_map.begin (); }
+  iterator_t end () { return m_map.end (); }
+  size_t elements () const { return m_map.elements (); }
+
+  region_id get_or_create (region_model *model,
+			   region_id this_rid,
+			   key_t key, tree type);
+//  void unbind (int expr);
+  region_id *get (key_t key);
+
+  void remap_region_ids (const region_id_map &map) FINAL OVERRIDE;
+
+  bool get_key_for_child_region (region_id child_rid,
+				 key_t *out) const;
+
+#if 0
+  bool get_key_for_child_region (region *child,
+				 const region_model &model,
+				 key_t *out) const;
+#endif
+
+  void add_to_hash (inchash::hash &hstate) const OVERRIDE;
+  void print_fields (const region_model &model,
+		     region_id this_rid,
+		     pretty_printer *pp) const
+    OVERRIDE;
+
+  static key_t key_from_constant (tree cst);
+
+ private:
+  /* Mapping from tree to child region.  */
+  map_t m_map;
+};
+
+template <>
+template <>
+inline bool
+is_a_helper <array_region *>::test (region *reg)
+{
+  return reg->get_kind () == RK_ARRAY;
+}
+
+/* Concrete region subclass representing a stack, containing all stack
+   frames, and implicitly providing a POISON_KIND_UNINIT value to all
+   child regions by default.  */
+
+class stack_region : public region
+{
+public:
+  stack_region (region_id parent_rid, svalue_id sval_id)
+  : region (parent_rid, sval_id, NULL_TREE)
+  {}
+
+  stack_region (const stack_region &other);
+
+  bool compare_fields (const stack_region &other) const;
+
+  region *clone () const FINAL OVERRIDE;
+
+  enum region_kind get_kind () const FINAL OVERRIDE { return RK_STACK; }
+
+  void dump_child_label (const region_model &model,
+			 region_id this_rid,
+			 region_id child_rid,
+			 pretty_printer *pp) const
+    FINAL OVERRIDE;
+
+  void push_frame (region_id frame_rid);
+  region_id get_current_frame_id () const;
+  svalue_id pop_frame (region_model *model, bool purge, purge_stats *stats,
+		       region_model_context *ctxt);
+
+  void remap_region_ids (const region_id_map &map) FINAL OVERRIDE;
+
+  unsigned get_num_frames () const { return m_frame_rids.length (); }
+  region_id get_frame_rid (unsigned i) const { return m_frame_rids[i]; }
+
+  static bool can_merge_p (const stack_region *stack_region_a,
+			   const stack_region *stack_region_b,
+			   model_merger *merger);
+
+  void walk_for_canonicalization (canonicalization *c) const FINAL OVERRIDE;
+
+  svalue_id get_value_by_name (tree identifier,
+			       const region_model &model) const;
+
+ private:
+  void add_to_hash (inchash::hash &hstate) const FINAL OVERRIDE;
+  void print_fields (const region_model &model,
+		     region_id this_rid,
+		     pretty_printer *pp) const
+    FINAL OVERRIDE;
+
+  auto_vec<region_id> m_frame_rids;
+};
+
+template <>
+template <>
+inline bool
+is_a_helper <stack_region *>::test (region *reg)
+{
+  return reg->get_kind () == RK_STACK;
+}
+
+/* Concrete region subclass: a region within which regions can be
+   dynamically allocated.  */
+
+class heap_region : public region
+{
+public:
+  heap_region (region_id parent_rid, svalue_id sval_id)
+  : region (parent_rid, sval_id, NULL_TREE)
+  {}
+  heap_region (const heap_region &other);
+
+  bool compare_fields (const heap_region &other) const;
+
+  region *clone () const FINAL OVERRIDE;
+
+  enum region_kind get_kind () const FINAL OVERRIDE { return RK_HEAP; }
+
+  static bool can_merge_p (const heap_region *heap_a, region_id heap_a_rid,
+			   const heap_region *heap_b, region_id heap_b_rid,
+			   heap_region *merged_heap, region_id merged_heap_rid,
+			   model_merger *merger);
+
+  void walk_for_canonicalization (canonicalization *c) const FINAL OVERRIDE;
+
+};
+
+template <>
+template <>
+inline bool
+is_a_helper <heap_region *>::test (region *reg)
+{
+  return reg->get_kind () == RK_HEAP;
+}
+
+/* Concrete region subclass.  The root region, containing all regions
+   (either directly, or as descendents).
+   Unique within a region_model.  */
+
+class root_region : public region
+{
+public:
+  root_region ();
+  root_region (const root_region &other);
+
+  bool compare_fields (const root_region &other) const;
+
+  region *clone () const FINAL OVERRIDE;
+
+  enum region_kind get_kind () const FINAL OVERRIDE { return RK_ROOT; }
+
+  void dump_child_label (const region_model &model,
+			 region_id this_rid,
+			 region_id child_rid,
+			 pretty_printer *pp) const
+    FINAL OVERRIDE;
+
+  region_id push_frame (region_model *model, function *fun,
+			vec<svalue_id> *arg_sids,
+			region_model_context *ctxt);
+  region_id get_current_frame_id (const region_model &model) const;
+  svalue_id pop_frame (region_model *model, bool purge, purge_stats *stats,
+		       region_model_context *ctxt);
+
+  region_id ensure_stack_region (region_model *model);
+  region_id get_stack_region_id () const { return m_stack_rid; }
+  stack_region *get_stack_region (const region_model *model) const;
+
+  region_id ensure_globals_region (region_model *model);
+  region_id get_globals_region_id () const { return m_globals_rid; }
+  globals_region *get_globals_region (const region_model *model) const;
+
+  region_id ensure_code_region (region_model *model);
+  code_region *get_code_region (const region_model *model) const;
+
+  region_id ensure_heap_region (region_model *model);
+  heap_region *get_heap_region (const region_model *model) const;
+
+  void remap_region_ids (const region_id_map &map) FINAL OVERRIDE;
+
+  static bool can_merge_p (const root_region *root_region_a,
+			   const root_region *root_region_b,
+			   root_region *merged_root_region,
+			   model_merger *merger);
+
+  void walk_for_canonicalization (canonicalization *c) const FINAL OVERRIDE;
+
+  svalue_id get_value_by_name (tree identifier,
+			       const region_model &model) const;
+
+private:
+  void add_to_hash (inchash::hash &hstate) const FINAL OVERRIDE;
+  void print_fields (const region_model &model,
+		     region_id this_rid,
+		     pretty_printer *pp) const
+    FINAL OVERRIDE;
+
+  region_id m_stack_rid;
+  region_id m_globals_rid;
+  region_id m_code_rid;
+  region_id m_heap_rid;
+};
+
+template <>
+template <>
+inline bool
+is_a_helper <root_region *>::test (region *reg)
+{
+  return reg->get_kind () == RK_ROOT;
+}
+
+/* Concrete region subclass: a region to use when dereferencing an unknown
+   pointer.  */
+
+class symbolic_region : public region
+{
+public:
+  symbolic_region (region_id parent_rid, bool possibly_null)
+  : region (parent_rid, svalue_id::null (), NULL_TREE),
+    m_possibly_null (possibly_null)
+  {}
+  symbolic_region (const symbolic_region &other);
+
+  const symbolic_region *dyn_cast_symbolic_region () const FINAL OVERRIDE
+  { return this; }
+
+  bool compare_fields (const symbolic_region &other) const;
+
+  region *clone () const FINAL OVERRIDE;
+
+  enum region_kind get_kind () const FINAL OVERRIDE { return RK_SYMBOLIC; }
+
+  void walk_for_canonicalization (canonicalization *c) const FINAL OVERRIDE;
+
+  bool m_possibly_null;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* A region_model encapsulates a representation of the state of memory, with
+   a tree of regions, along with their associated values.
+   The representation is graph-like because values can be pointers to
+   regions.
+   It also stores a constraint_manager, capturing relationships between
+   the values.  */
+
+class region_model
+{
+ public:
+  region_model ();
+  region_model (const region_model &other);
+  ~region_model ();
+
+#if 0//__cplusplus >= 201103
+  region_model (region_model &&other);
+#endif
+
+  region_model &operator= (const region_model &other);
+
+  bool operator== (const region_model &other) const;
+  bool operator!= (const region_model &other) const
+  {
+    return !(*this == other);
+  }
+
+  hashval_t hash () const;
+
+  void print (pretty_printer *pp) const;
+
+  void print_svalue (svalue_id sid, pretty_printer *pp) const;
+
+  void dump_dot_to_pp (pretty_printer *pp) const;
+  void dump_dot_to_file (FILE *fp) const;
+  void dump_dot (const char *path) const;
+
+  void dump_to_pp (pretty_printer *pp, bool summarize) const;
+  void dump (FILE *fp, bool summarize) const;
+  void dump (bool summarize) const;
+
+  void debug () const;
+
+  void validate () const;
+
+  void canonicalize (region_model_context *ctxt);
+  bool canonicalized_p () const;
+
+  void check_for_poison (tree expr, region_model_context *ctxt);
+  void on_assignment (const gassign *stmt, region_model_context *ctxt);
+  void on_call_pre (const gcall *stmt, region_model_context *ctxt);
+  void on_call_post (const gcall *stmt, region_model_context *ctxt);
+  void on_return (const greturn *stmt, region_model_context *ctxt);
+  void on_setjmp (const gcall *stmt, const exploded_node *enode,
+		  region_model_context *ctxt);
+  void on_longjmp (const gcall *longjmp_call, const gcall *setjmp_call,
+		   int setjmp_stack_depth, region_model_context *ctxt);
+
+  void update_for_phis (const supernode *snode,
+			const cfg_superedge *last_cfg_superedge,
+			region_model_context *ctxt);
+
+  void handle_phi (tree lhs, tree rhs, bool is_back_edge,
+		   region_model_context *ctxt);
+
+  bool maybe_update_for_edge (const superedge &edge,
+			      const gimple *last_stmt,
+			      region_model_context *ctxt);
+
+  region_id get_root_rid () const { return m_root_rid; }
+  root_region *get_root_region () const;
+
+  region_id get_stack_region_id () const;
+  region_id push_frame (function *fun, vec<svalue_id> *arg_sids,
+			region_model_context *ctxt);
+  region_id get_current_frame_id () const;
+  function * get_current_function () const;
+  svalue_id pop_frame (bool purge, purge_stats *stats,
+		       region_model_context *ctxt);
+  int get_stack_depth () const;
+
+  region_id get_globals_region_id () const;
+
+  svalue_id add_svalue (svalue *sval);
+  void replace_svalue (svalue_id sid, svalue *new_sval);
+
+  region_id add_region (region *r);
+
+  region_id add_region_for_type (region_id parent_rid, tree type);
+
+  svalue *get_svalue (svalue_id sval_id) const;
+  region *get_region (region_id rid) const;
+
+  template <typename Subclass>
+  Subclass *get_region (region_id rid) const
+  {
+    region *result = get_region (rid);
+    if (result)
+      gcc_assert (is_a<Subclass *> (result));
+    return (Subclass *)result;
+  }
+
+  region_id get_lvalue (path_var pv, region_model_context *ctxt);
+  region_id get_lvalue (tree expr, region_model_context *ctxt);
+  svalue_id get_rvalue (path_var pv, region_model_context *ctxt);
+  svalue_id get_rvalue (tree expr, region_model_context *ctxt);
+
+  svalue_id get_or_create_ptr_svalue (tree ptr_type, region_id id);
+  svalue_id get_or_create_constant_svalue (tree cst_expr);
+  svalue_id get_svalue_for_fndecl (tree ptr_type, tree fndecl);
+  svalue_id get_svalue_for_label (tree ptr_type, tree label);
+
+  region_id get_region_for_fndecl (tree fndecl);
+  region_id get_region_for_label (tree label);
+
+  svalue_id maybe_cast (tree type, svalue_id sid, region_model_context *ctxt);
+  svalue_id maybe_cast_1 (tree type, svalue_id sid);
+
+  region_id get_field_region (region_id rid, tree field);
+
+  region_id deref_rvalue (svalue_id ptr_sid, region_model_context *ctxt);
+  region_id deref_rvalue (tree ptr, region_model_context *ctxt);
+
+  void set_value (region_id lhs_rid, svalue_id rhs_sid,
+		  region_model_context *ctxt);
+  svalue_id set_to_new_unknown_value (region_id dst_rid, tree type,
+				      region_model_context *ctxt);
+
+  tristate eval_condition (svalue_id lhs,
+			   enum tree_code op,
+			   svalue_id rhs) const;
+  tristate eval_condition_without_cm (svalue_id lhs,
+				      enum tree_code op,
+				      svalue_id rhs) const;
+  tristate eval_condition (tree lhs,
+			   enum tree_code op,
+			   tree rhs,
+			   region_model_context *ctxt);
+  bool add_constraint (tree lhs, enum tree_code op, tree rhs,
+		       region_model_context *ctxt);
+
+  tree maybe_get_constant (svalue_id sid) const;
+
+  region_id add_new_malloc_region ();
+
+  tree get_representative_tree (svalue_id sid) const;
+  path_var get_representative_path_var (region_id rid) const;
+  void get_path_vars_for_svalue (svalue_id sid, vec<path_var> *out) const;
+
+  void purge_unused_svalues (purge_stats *out,
+			     region_model_context *ctxt,
+			     svalue_id *known_used_sid = NULL);
+  void remap_svalue_ids (const svalue_id_map &map);
+  void remap_region_ids (const region_id_map &map);
+
+  void purge_regions (const region_id_set &set,
+		      purge_stats *stats,
+		      logger *logger);
+
+  unsigned get_num_svalues () const { return m_svalues.length (); }
+  unsigned get_num_regions () const { return m_regions.length (); }
+
+  /* For selftests.  */
+  constraint_manager *get_constraints ()
+  {
+    return m_constraints;
+  }
+
+  void get_descendents (region_id rid, region_id_set *out,
+			region_id exclude_rid) const;
+
+  void delete_region_and_descendents (region_id rid,
+				      enum poison_kind pkind,
+				      purge_stats *stats,
+				      logger *logger);
+
+  bool can_merge_with_p (const region_model &other_model,
+			 region_model *out_model,
+			 svalue_id_merger_mapping *out) const;
+  bool can_merge_with_p (const region_model &other_model,
+			 region_model *out_model) const;
+
+  svalue_id get_value_by_name (const char *name) const;
+
+  svalue_id convert_byte_offset_to_array_index (tree ptr_type,
+						svalue_id offset_sid);
+
+  region_id get_or_create_mem_ref (tree type,
+				   svalue_id ptr_sid,
+				   svalue_id offset_sid,
+				   region_model_context *ctxt);
+  region_id get_or_create_pointer_plus_expr (tree type,
+					     svalue_id ptr_sid,
+					     svalue_id offset_sid,
+					     region_model_context *ctxt);
+  region_id get_or_create_view (region_id raw_rid, tree type);
+
+ private:
+  region_id get_lvalue_1 (path_var pv, region_model_context *ctxt);
+  svalue_id get_rvalue_1 (path_var pv, region_model_context *ctxt);
+
+  void add_any_constraints_from_ssa_def_stmt (tree lhs,
+					      enum tree_code op,
+					      tree rhs,
+					      region_model_context *ctxt);
+
+  void update_for_call_superedge (const call_superedge &call_edge,
+				  region_model_context *ctxt);
+  void update_for_return_superedge (const return_superedge &return_edge,
+				    region_model_context *ctxt);
+  void update_for_call_summary (const callgraph_superedge &cg_sedge,
+				region_model_context *ctxt);
+  bool apply_constraints_for_gcond (const cfg_superedge &edge,
+				    const gcond *cond_stmt,
+				    region_model_context *ctxt);
+  bool apply_constraints_for_gswitch (const switch_cfg_superedge &edge,
+				      const gswitch *switch_stmt,
+				      region_model_context *ctxt);
+
+  void poison_any_pointers_to_bad_regions (const region_id_set &bad_regions,
+					   enum poison_kind pkind);
+
+  void dump_summary_of_map (pretty_printer *pp, map_region *map_region,
+			    bool *is_first) const;
+
+  auto_delete_vec<svalue> m_svalues;
+  auto_delete_vec<region> m_regions;
+  region_id m_root_rid;
+  constraint_manager *m_constraints; // TODO: embed, rather than dynalloc?
+};
+
+/* Some region_model activity could lead to warnings (e.g. attempts to use an
+   uninitialized value).  This abstract base class encapsulates an interface
+   for the region model to use when emitting such warnings.
+
+   It also provides an interface for being notified about svalue_ids being
+   remapped, and being deleted.
+
+   Having this as an abstract base class allows us to support the various
+   operations needed by program_state in the analyzer within region_model,
+   whilst keeping them somewhat modularized.  */
+
+class region_model_context
+{
+ public:
+  virtual void warn (pending_diagnostic *d) = 0;
+
+  /* Hook for clients that store svalue_id instances, so that they
+     can remap their IDs when the underlying region_model renumbers
+     the IDs.  */
+  virtual void remap_svalue_ids (const svalue_id_map &map) = 0;
+
+#if 0
+  /* Return true if if's OK to purge SID when simplifying state.
+     Subclasses can return false for values that have sm state,
+     to avoid generating "leak" false positives.  */
+  virtual bool can_purge_p (svalue_id sid) = 0;
+#endif
+
+  /* Hook for clients to be notified when a range of SIDs have
+     been purged, so that they can purge state relating to those
+     values (and potentially emit warnings about leaks).
+     All SIDs from FIRST_PURGED_SID numerically upwards are being
+     purged.
+     The return values is a count of how many items of data the client
+     has purged (potentially for use in selftests).
+     MAP has already been applied to the IDs, but is provided in case
+     the client needs to figure out the old IDs.  */
+  virtual int on_svalue_purge (svalue_id first_purged_sid,
+			       const svalue_id_map &map) = 0;
+
+  virtual logger *get_logger () = 0;
+
+  /* Hook for clients to be notified when CHILD_SID is created
+     from PARENT_SID, when "inheriting" a value for a region from a
+     parent region.
+     This exists so that state machines that inherit state can
+     propagate the state from parent to child.  */
+  virtual void on_inherited_svalue (svalue_id parent_sid,
+				    svalue_id child_sid) = 0;
+
+  /* Hook for clients to be notified when DST_SID is created
+     (or reused) as a cast from SRC_SID.
+     This exists so that state machines can propagate the state
+     from SRC_SID to DST_SID.  */
+  virtual void on_cast (svalue_id src_sid,
+			svalue_id dst_sid) = 0;
+
+  /* Hook for clients to be notified when the condition
+     "LHS OP RHS" is added to the region model.
+     This exists so that state machines can detect tests on edges,
+     and use them to trigger sm-state transitions (e.g. transitions due
+     to ptrs becoming known to be NULL or non-NULL, rather than just
+     "unchecked") */
+  virtual void on_condition (tree lhs, enum tree_code op, tree rhs) = 0;
+};
+
+/* A bundle of data for use when attempting to merge two region_model
+   instances to make a third.  */
+
+struct model_merger
+{
+  model_merger (const region_model *model_a,
+		const region_model *model_b,
+		region_model *merged_model,
+		svalue_id_merger_mapping *sid_mapping)
+  : m_model_a (model_a), m_model_b (model_b),
+    m_merged_model (merged_model),
+    m_map_regions_from_a_to_m (model_a->get_num_regions ()),
+    m_map_regions_from_b_to_m (model_b->get_num_regions ()),
+    m_sid_mapping (sid_mapping)
+  {
+    gcc_assert (sid_mapping);
+  }
+
+  void dump_to_pp (pretty_printer *pp) const;
+  void dump (FILE *fp) const;
+  void dump () const;
+
+  template <typename Subclass>
+  Subclass *get_region_a (region_id rid_a) const
+  {
+    return m_model_a->get_region <Subclass> (rid_a);
+  }
+
+  template <typename Subclass>
+  Subclass *get_region_b (region_id rid_b) const
+  {
+    return m_model_b->get_region <Subclass> (rid_b);
+  }
+
+  bool can_merge_values_p (svalue_id sid_a,
+			   svalue_id sid_b,
+			   svalue_id *merged_sid);
+
+  void record_regions (region_id a_rid,
+		       region_id b_rid,
+		       region_id merged_rid);
+
+  void record_svalues (svalue_id a_sid,
+		       svalue_id b_sid,
+		       svalue_id merged_sid);
+
+  const region_model *m_model_a;
+  const region_model *m_model_b;
+  region_model *m_merged_model;
+
+  one_way_region_id_map m_map_regions_from_a_to_m;
+  one_way_region_id_map m_map_regions_from_b_to_m;
+  svalue_id_merger_mapping *m_sid_mapping;
+};
+
+/* A bundle of data that can be optionally generated during merger of two
+   region_models that describes how svalue_ids in each of the two inputs
+   are mapped to svalue_ids in the merged output.
+
+   For use when merging sm-states within program_state.  */
+
+struct svalue_id_merger_mapping
+{
+  svalue_id_merger_mapping (const region_model &a,
+			    const region_model &b);
+
+  void dump_to_pp (pretty_printer *pp) const;
+  void dump (FILE *fp) const;
+  void dump () const;
+
+  one_way_svalue_id_map m_map_from_a_to_m;
+  one_way_svalue_id_map m_map_from_b_to_m;
+};
+
+/* A bundle of data used when canonicalizing a region_model so that the
+   order of regions and svalues is in a predictable order (thus increasing
+   the chance of two region_models being equal).
+
+   This object is used to keep track of a recursive traversal across the
+   svalues and regions within the model, made in a deterministic order,
+   assigning new ids the first time each region or svalue is
+   encountered.  */
+
+struct canonicalization
+{
+  canonicalization (const region_model &model);
+  void walk_rid (region_id rid);
+  void walk_sid (svalue_id sid);
+
+  void dump_to_pp (pretty_printer *pp) const;
+  void dump (FILE *fp) const;
+  void dump () const;
+
+  const region_model &m_model;
+  /* Maps from existing IDs to new IDs.  */
+  region_id_map m_rid_map;
+  svalue_id_map m_sid_map;
+  /* The next IDs to hand out.  */
+  int m_next_rid_int;
+  int m_next_sid_int;
+};
+
+namespace inchash
+{
+  extern void add (svalue_id sid, hash &hstate);
+  extern void add (region_id rid, hash &hstate);
+} // namespace inchash
+
+extern void debug (const region_model &rmodel);
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* An implementation of region_model_context for use in selftests, which
+   stores any pending_diagnostic instances passed to it.  */
+
+class test_region_model_context : public region_model_context
+{
+public:
+  void warn (pending_diagnostic *d) FINAL OVERRIDE
+  {
+    m_diagnostics.safe_push (d);
+  }
+
+  void remap_svalue_ids (const svalue_id_map &) FINAL OVERRIDE
+  {
+    /* Empty.  */
+  }
+
+#if 0
+  bool can_purge_p (svalue_id) FINAL OVERRIDE
+  {
+    return true;
+  }
+#endif
+
+  int on_svalue_purge (svalue_id, const svalue_id_map &) FINAL OVERRIDE
+  {
+    /* Empty.  */
+    return 0;
+  }
+
+  logger *get_logger () FINAL OVERRIDE { return NULL; }
+
+  void on_inherited_svalue (svalue_id parent_sid ATTRIBUTE_UNUSED,
+			    svalue_id child_sid  ATTRIBUTE_UNUSED)
+    FINAL OVERRIDE
+  {
+  }
+
+  void on_cast (svalue_id src_sid ATTRIBUTE_UNUSED,
+		svalue_id dst_sid ATTRIBUTE_UNUSED) FINAL OVERRIDE
+  {
+  }
+
+  unsigned get_num_diagnostics () const { return m_diagnostics.length (); }
+
+  void on_condition (tree lhs ATTRIBUTE_UNUSED,
+		     enum tree_code op ATTRIBUTE_UNUSED,
+		     tree rhs ATTRIBUTE_UNUSED) FINAL OVERRIDE
+  {
+  }
+
+private:
+  /* Implicitly delete any diagnostics in the dtor.  */
+  auto_delete_vec<pending_diagnostic> m_diagnostics;
+};
+
+/* Attempt to add the constraint (LHS OP RHS) to MODEL.
+   Verify that MODEL remains satisfiable.  */
+
+#define ADD_SAT_CONSTRAINT(MODEL, LHS, OP, RHS)	\
+  SELFTEST_BEGIN_STMT					\
+    bool sat = (MODEL).add_constraint (LHS, OP, RHS, NULL);	\
+    ASSERT_TRUE (sat);					\
+  SELFTEST_END_STMT
+
+/* Attempt to add the constraint (LHS OP RHS) to MODEL.
+   Verify that the result is not satisfiable.  */
+
+#define ADD_UNSAT_CONSTRAINT(MODEL, LHS, OP, RHS)	\
+  SELFTEST_BEGIN_STMT					\
+    bool sat = (MODEL).add_constraint (LHS, OP, RHS, NULL);	\
+    ASSERT_FALSE (sat);				\
+  SELFTEST_END_STMT
+
+/* Implementation detail of the ASSERT_CONDITION_* macros.  */
+
+void assert_condition (const location &loc,
+		       region_model &model,
+		       tree lhs, tree_code op, tree rhs,
+		       tristate expected);
+
+/* Assert that REGION_MODEL evaluates the condition "LHS OP RHS"
+   as "true".  */
+
+#define ASSERT_CONDITION_TRUE(REGION_MODEL, LHS, OP, RHS) \
+  SELFTEST_BEGIN_STMT							\
+  assert_condition (SELFTEST_LOCATION, REGION_MODEL, LHS, OP, RHS,	\
+		    tristate (tristate::TS_TRUE));		\
+  SELFTEST_END_STMT
+
+/* Assert that REGION_MODEL evaluates the condition "LHS OP RHS"
+   as "false".  */
+
+#define ASSERT_CONDITION_FALSE(REGION_MODEL, LHS, OP, RHS) \
+  SELFTEST_BEGIN_STMT							\
+  assert_condition (SELFTEST_LOCATION, REGION_MODEL, LHS, OP, RHS,	\
+		    tristate (tristate::TS_FALSE));		\
+  SELFTEST_END_STMT
+
+/* Assert that REGION_MODEL evaluates the condition "LHS OP RHS"
+   as "unknown".  */
+
+#define ASSERT_CONDITION_UNKNOWN(REGION_MODEL, LHS, OP, RHS) \
+  SELFTEST_BEGIN_STMT							\
+  assert_condition (SELFTEST_LOCATION, REGION_MODEL, LHS, OP, RHS,	\
+		    tristate (tristate::TS_UNKNOWN));		\
+  SELFTEST_END_STMT
+
+} /* end of namespace selftest.  */
+
+#endif /* #if CHECKING_P */
+
+#endif /* GCC_ANALYZER_REGION_MODEL_H */
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 46/49] analyzer: new files: checker-path.{cc|h}
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (34 preceding siblings ...)
  2019-11-16  1:21 ` [PATCH 47/49] analyzer: new files: diagnostic-manager.{cc|h} David Malcolm
@ 2019-11-16  1:21 ` David Malcolm
  2019-12-11 20:50   ` Jeff Law
  2019-11-16  1:21 ` [PATCH 31/49] analyzer: new files: region-model.{cc|h} David Malcolm
                   ` (14 subsequent siblings)
  50 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:21 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This patch adds a family of classes for representing paths of events
for analyzer diagnostics.

gcc/ChangeLog:
	* analyzer/checker-path.cc: New file.
	* analyzer/checker-path.h: New file.
---
 gcc/analyzer/checker-path.cc | 899 +++++++++++++++++++++++++++++++++++++++++++
 gcc/analyzer/checker-path.h  | 563 +++++++++++++++++++++++++++
 2 files changed, 1462 insertions(+)
 create mode 100644 gcc/analyzer/checker-path.cc
 create mode 100644 gcc/analyzer/checker-path.h

diff --git a/gcc/analyzer/checker-path.cc b/gcc/analyzer/checker-path.cc
new file mode 100644
index 0000000..5302504
--- /dev/null
+++ b/gcc/analyzer/checker-path.cc
@@ -0,0 +1,899 @@
+/* Subclasses of diagnostic_path and diagnostic_event for analyzer diagnostics.
+   Copyright (C) 2019 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 "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "gimple.h"
+#include "gimple-pretty-print.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/checker-path.h"
+#include "analyzer/supergraph.h"
+#include "analyzer/diagnostic-manager.h"
+#include "analyzer/exploded-graph.h"
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Get a string for EK.  */
+
+const char *
+event_kind_to_string (enum event_kind ek)
+{
+  switch (ek)
+    {
+    default:
+      gcc_unreachable ();
+    case EK_DEBUG:
+      return "EK_DEBUG";
+    case EK_STMT:
+      return "EK_STMT";
+    case EK_FUNCTION_ENTRY:
+      return "EK_FUNCTION_ENTRY";
+    case EK_STATE_CHANGE:
+      return "EK_STATE_CHANGE";
+    case EK_START_CFG_EDGE:
+      return "EK_START_CFG_EDGE";
+    case EK_END_CFG_EDGE:
+      return "EK_END_CFG_EDGE";
+    case EK_CALL_EDGE:
+      return "EK_CALL_EDGE";
+    case EK_RETURN_EDGE:
+      return "EK_RETURN_EDGE";
+    case EK_SETJMP:
+      return "EK_SETJMP";
+    case EK_REWIND_FROM_LONGJMP:
+      return "EK_REWIND_FROM_LONGJMP";
+    case EK_REWIND_TO_SETJMP:
+      return "EK_REWIND_TO_SETJMP";
+    case EK_WARNING:
+      return "EK_WARNING";
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class checker_event : public diagnostic_event.  */
+
+/* Dump this event to PP (for debugging/logging purposes).  */
+
+void
+checker_event::dump (pretty_printer *pp) const
+{
+  label_text event_desc (get_desc (false));
+  pp_printf (pp, "\"%s\" (depth %i, m_loc=%x)",
+	     event_desc.m_buffer,
+	     get_stack_depth (),
+	     get_location ());
+  event_desc.maybe_free ();
+}
+
+/* Hook for being notified when this event has its final id EMISSION_ID
+   and is about to emitted for PD.
+
+   Base implementation of checker_event::prepare_for_emission vfunc;
+   subclasses that override this should chain up to it.
+
+   Record PD and EMISSION_ID, and call the get_desc vfunc, so that any
+   side-effects of the call to get_desc take place before
+   pending_diagnostic::emit is called.
+
+   For example, state_change_event::get_desc can call
+   pending_diagnostic::describe_state_change; free_of_non_heap can use this
+   to tweak the message (TODO: would be neater to simply capture the
+   pertinent data within the sm-state).  */
+
+void
+checker_event::prepare_for_emission (checker_path *,
+				     pending_diagnostic *pd,
+				     diagnostic_event_id_t emission_id)
+{
+  m_pending_diagnostic = pd;
+  m_emission_id = emission_id;
+
+  label_text desc = get_desc (false);
+  desc.maybe_free ();
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class debug_event : public checker_event.  */
+
+/* Implementation of diagnostic_event::get_desc vfunc for
+   debug_event.
+   Use the saved string as the event's description.  */
+
+label_text
+debug_event::get_desc (bool) const
+{
+  return label_text::borrow (m_desc);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class statement_event : public checker_event.  */
+
+/* statement_event's ctor.  */
+
+statement_event::statement_event (const gimple *stmt, tree fndecl, int depth,
+				  const program_state &dst_state)
+: checker_event (EK_STMT, gimple_location (stmt), fndecl, depth),
+  m_stmt (stmt),
+  m_dst_state (dst_state)
+{
+}
+
+/* Implementation of diagnostic_event::get_desc vfunc for
+   statement_event.
+   Use the statement's dump form as the event's description.  */
+
+label_text
+statement_event::get_desc (bool) const
+{
+  pretty_printer pp;
+  pp_string (&pp, "stmt: ");
+  pp_gimple_stmt_1 (&pp, m_stmt, 0, (dump_flags_t)0);
+  return label_text::take (xstrdup (pp_formatted_text (&pp)));
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class function_entry_event : public checker_event.  */
+
+/* Implementation of diagnostic_event::get_desc vfunc for
+   function_entry_event.
+
+   Use a string such as "entry to 'foo'" as the event's description.  */
+
+label_text
+function_entry_event::get_desc (bool can_colorize) const
+{
+  return make_label_text (can_colorize, "entry to %qE", m_fndecl);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class state_change_event : public checker_event.  */
+
+/* state_change_event's ctor.  */
+
+state_change_event::state_change_event (const supernode *node,
+					const gimple *stmt,
+					int stack_depth,
+					const state_machine &sm,
+					tree var,
+					state_machine::state_t from,
+					state_machine::state_t to,
+					tree origin,
+					const program_state &dst_state)
+: checker_event (EK_STATE_CHANGE,
+		 stmt->location, node->m_fun->decl,
+		 stack_depth),
+  m_node (node), m_stmt (stmt), m_sm (sm),
+  m_var (var), m_from (from), m_to (to),
+  m_origin (origin),
+  m_dst_state (dst_state)
+{
+}
+
+/* Implementation of diagnostic_event::get_desc vfunc for
+   state_change_event.
+
+   Attempt to generate a nicer human-readable description.
+   For greatest precision-of-wording, give the pending diagnostic
+   a chance to describe this state change (in terms of the
+   diagnostic).
+   Note that we only have a pending_diagnostic set on the event once
+   the diagnostic is about to being emitted, so the description for
+   an event can change.  */
+
+label_text
+state_change_event::get_desc (bool can_colorize) const
+{
+  if (m_pending_diagnostic)
+    {
+      label_text custom_desc
+	= m_pending_diagnostic->describe_state_change
+	    (evdesc::state_change (can_colorize, m_var, m_origin,
+				   m_from, m_to, m_emission_id));
+      if (custom_desc.m_buffer)
+	{
+	  if (flag_analyzer_verbose_state_changes)
+	    {
+	      /* Append debug version.  */
+	      label_text result;
+	      if (m_origin)
+		result = make_label_text
+		  (can_colorize,
+		   "%s (state of %qE: %qs -> %qs, origin: %qE)",
+		   custom_desc.m_buffer,
+		   m_var,
+		   m_sm.get_state_name (m_from),
+		   m_sm.get_state_name (m_to),
+		   m_origin);
+	      else
+		result = make_label_text
+		  (can_colorize,
+		   "%s (state of %qE: %qs -> %qs, origin: NULL)",
+		   custom_desc.m_buffer,
+		   m_var,
+		   m_sm.get_state_name (m_from),
+		   m_sm.get_state_name (m_to));
+	      custom_desc.maybe_free ();
+	      return result;
+	    }
+	  else
+	    return custom_desc;
+	}
+    }
+
+  /* Fallback description.  */
+  if (m_origin)
+    return make_label_text
+      (can_colorize,
+       "state of %qE: %qs -> %qs (origin: %qE)",
+       m_var,
+       m_sm.get_state_name (m_from),
+       m_sm.get_state_name (m_to),
+       m_origin);
+  else
+    return make_label_text
+      (can_colorize,
+       "state of %qE: %qs -> %qs (origin: NULL)",
+       m_var,
+       m_sm.get_state_name (m_from),
+       m_sm.get_state_name (m_to));
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class superedge_event : public checker_event.  */
+
+/* Get the callgraph_superedge for this superedge_event, which must be
+   for an interprocedural edge, rather than a CFG edge.  */
+
+const callgraph_superedge&
+superedge_event::get_callgraph_superedge () const
+{
+  gcc_assert (m_sedge->m_kind != SUPEREDGE_CFG_EDGE);
+  return *m_sedge->dyn_cast_callgraph_superedge ();
+}
+
+/* Determine if this event should be filtered at the given verbosity
+   level.  */
+
+bool
+superedge_event::should_filter_p (int verbosity) const
+{
+  switch (m_sedge->m_kind)
+    {
+    case SUPEREDGE_CFG_EDGE:
+      {
+	if (verbosity < 2)
+	  return true;
+
+	if (verbosity == 2)
+	  {
+	    /* Filter events with empty descriptions.  This ought to filter
+	       FALLTHRU, but retain true/false/switch edges.  */
+	    label_text desc = get_desc (false);
+	    gcc_assert (desc.m_buffer);
+	    if (desc.m_buffer[0] == '\0')
+	      return true;
+	    desc.maybe_free ();
+	  }
+      }
+      break;
+
+    default:
+      break;
+    }
+  return false;
+}
+
+/* superedge_event's ctor.  */
+
+superedge_event::superedge_event (enum event_kind kind,
+				  const exploded_edge &eedge,
+				  location_t loc, tree fndecl, int depth)
+: checker_event (kind, loc, fndecl, depth),
+  m_eedge (eedge), m_sedge (eedge.m_sedge),
+  m_var (NULL_TREE), m_critical_state (0)
+{
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class cfg_edge_event : public superedge_event.  */
+
+/* Get the cfg_superedge for this cfg_edge_event.  */
+
+const cfg_superedge &
+cfg_edge_event::get_cfg_superedge () const
+{
+  return *m_sedge->dyn_cast_cfg_superedge ();
+}
+
+/* cfg_edge_event's ctor.  */
+
+cfg_edge_event::cfg_edge_event (enum event_kind kind,
+				const exploded_edge &eedge,
+				location_t loc, tree fndecl, int depth)
+: superedge_event (kind, eedge, loc, fndecl, depth)
+{
+  gcc_assert (eedge.m_sedge->m_kind == SUPEREDGE_CFG_EDGE);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class start_cfg_edge_event : public cfg_edge_event.  */
+
+/* Implementation of diagnostic_event::get_desc vfunc for
+   start_cfg_edge_event.
+
+   If -fanalyzer-verbose-edges, then generate low-level descriptions, such
+   as
+     "taking 'true' edge SN:7 -> SN:8".
+
+   Otherwise, generate strings using the label of the underlying CFG if
+   any, such as:
+     "following 'true' branch..." or
+     "following 'case 3' branch..."
+     "following 'default' branch..."
+
+   For conditionals, attempt to supply a description of the condition that
+   holds, such as:
+     "following 'false' branch (when 'ptr' is non-NULL)..."
+
+   Failing that, return an empty description (which will lead to this event
+   being filtered).  */
+
+label_text
+start_cfg_edge_event::get_desc (bool can_colorize) const
+{
+  bool user_facing = !flag_analyzer_verbose_edges;
+  char *edge_desc = m_sedge->get_description (user_facing);
+  if (user_facing)
+    {
+      if (edge_desc && strlen (edge_desc) > 0)
+	{
+	  label_text cond_desc = maybe_describe_condition (can_colorize);
+	  label_text result;
+	  if (cond_desc.m_buffer)
+	    {
+	      result = make_label_text (can_colorize,
+					"following %qs branch (%s)...",
+					edge_desc, cond_desc.m_buffer);
+	      cond_desc.maybe_free ();
+	    }
+	  else
+	    {
+	      result = make_label_text (can_colorize,
+					"following %qs branch...",
+					edge_desc);
+	    }
+	  free (edge_desc);
+	  return result;
+	}
+      else
+	{
+	  free (edge_desc);
+	  return label_text::borrow ("");
+	}
+    }
+  else
+    {
+      if (strlen (edge_desc) > 0)
+	{
+	  label_text result
+	    = make_label_text (can_colorize,
+			       "taking %qs edge SN:%i -> SN:%i",
+			       edge_desc,
+			       m_sedge->m_src->m_index,
+			       m_sedge->m_dest->m_index);
+	  free (edge_desc);
+	  return result;
+	}
+      else
+	{
+	  free (edge_desc);
+	  return make_label_text (can_colorize,
+				  "taking edge SN:%i -> SN:%i",
+				  m_sedge->m_src->m_index,
+				  m_sedge->m_dest->m_index);
+	}
+    }
+}
+
+/* Attempt to generate a description of any condition that holds at this edge.
+
+   The intent is to make the user-facing messages more clear, especially for
+   cases where there's a single or double-negative, such as
+   when describing the false branch of an inverted condition.
+
+   For example, rather than printing just:
+
+      |  if (!ptr)
+      |     ~
+      |     |
+      |     (1) following 'false' branch...
+
+   it's clearer to spell out the condition that holds:
+
+      |  if (!ptr)
+      |     ~
+      |     |
+      |     (1) following 'false' branch (when 'ptr' is non-NULL)...
+                                          ^^^^^^^^^^^^^^^^^^^^^^
+
+   In the above example, this function would generate the highlighted
+   string: "when 'ptr' is non-NULL".
+
+   If the edge is not a condition, or it's not clear that a description of
+   the condition would be helpful to the user, return NULL.  */
+
+label_text
+start_cfg_edge_event::maybe_describe_condition (bool can_colorize) const
+{
+  const cfg_superedge& cfg_sedge = get_cfg_superedge ();
+
+  if (cfg_sedge.true_value_p () || cfg_sedge.false_value_p ())
+    {
+      const gimple *last_stmt = m_sedge->m_src->get_last_stmt ();
+      if (const gcond *cond_stmt = dyn_cast <const gcond *> (last_stmt))
+	{
+	  enum tree_code op = gimple_cond_code (cond_stmt);
+	  tree lhs = gimple_cond_lhs (cond_stmt);
+	  tree rhs = gimple_cond_rhs (cond_stmt);
+	  if (cfg_sedge.false_value_p ())
+	    op = invert_tree_comparison (op, false /* honor_nans */);
+	  return maybe_describe_condition (can_colorize,
+					   lhs, op, rhs);
+	}
+    }
+  return label_text::borrow (NULL);
+}
+
+/* Subroutine of maybe_describe_condition above.
+
+   Attempt to generate a user-facing description of the condition
+   LHS OP RHS, but only if it is likely to make it easier for the
+   user to understand a condition.  */
+
+label_text
+start_cfg_edge_event::maybe_describe_condition (bool can_colorize,
+						tree lhs,
+						enum tree_code op,
+						tree rhs)
+{
+  /* In theory we could just build a tree via
+       fold_build2 (op, boolean_type_node, lhs, rhs)
+     and print it with %qE on it, but this leads to warts such as
+     parenthesizing vars, such as '(i) <= 9', and uses of '<unknown>'.  */
+
+  /* Only attempt to generate text for sufficiently simple expressions.  */
+  if (!should_print_expr_p (lhs))
+    return label_text::borrow (NULL);
+  if (!should_print_expr_p (rhs))
+    return label_text::borrow (NULL);
+
+  /* Special cases for pointer comparisons against NULL.  */
+  if (POINTER_TYPE_P (TREE_TYPE (lhs))
+      && POINTER_TYPE_P (TREE_TYPE (rhs))
+      && zerop (rhs))
+    {
+      if (op == EQ_EXPR)
+	return make_label_text (can_colorize, "when %qE is NULL",
+				lhs);
+      if (op == NE_EXPR)
+	return make_label_text (can_colorize, "when %qE is non-NULL",
+				lhs);
+    }
+
+  return make_label_text (can_colorize, "when %<%E %s %E%>",
+			  lhs, op_symbol_code (op), rhs);
+}
+
+/* Subroutine of maybe_describe_condition.
+
+   Return true if EXPR is we will get suitable user-facing output
+   from %E on it.  */
+
+bool
+start_cfg_edge_event::should_print_expr_p (tree expr)
+{
+  if (TREE_CODE (expr) == SSA_NAME)
+    {
+      if (SSA_NAME_VAR (expr))
+	return should_print_expr_p (SSA_NAME_VAR (expr));
+      else
+	return false;
+    }
+
+  if (DECL_P (expr))
+    return true;
+
+  if (CONSTANT_CLASS_P (expr))
+    return true;
+
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class call_event : public superedge_event.  */
+
+/* call_event's ctor.  */
+
+call_event::call_event (const exploded_edge &eedge,
+			location_t loc, tree fndecl, int depth)
+: superedge_event (EK_CALL_EDGE, eedge, loc, fndecl, depth)
+{
+  gcc_assert (eedge.m_sedge->m_kind == SUPEREDGE_CALL);
+}
+
+/* Implementation of diagnostic_event::get_desc vfunc for
+   call_event.
+
+   If this call event passes critical state for an sm-based warning,
+   allow the diagnostic to generate a precise description, such as:
+
+     "passing freed pointer 'ptr' in call to 'foo' from 'bar'"
+
+   Otherwise, generate a description of the form
+   "calling 'foo' from 'bar'".  */
+
+label_text
+call_event::get_desc (bool can_colorize) const
+{
+  if (m_critical_state && m_pending_diagnostic)
+    {
+      gcc_assert (m_var);
+      label_text custom_desc
+	= m_pending_diagnostic->describe_call_with_state
+	    (evdesc::call_with_state (can_colorize,
+				      m_sedge->m_src->m_fun->decl,
+				      m_sedge->m_dest->m_fun->decl,
+				      m_var,
+				      m_critical_state));
+      if (custom_desc.m_buffer)
+	return custom_desc;
+    }
+
+  return make_label_text (can_colorize,
+			  "calling %qE from %qE",
+			  m_sedge->m_dest->m_fun->decl,
+			  m_sedge->m_src->m_fun->decl);
+}
+
+/* Override of checker_event::is_call_p for calls.  */
+
+bool
+call_event::is_call_p () const
+{
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class return_event : public superedge_event.  */
+
+/* return_event's ctor.  */
+
+return_event::return_event (const exploded_edge &eedge,
+			    location_t loc, tree fndecl, int depth)
+: superedge_event (EK_RETURN_EDGE, eedge, loc, fndecl, depth)
+{
+  gcc_assert (eedge.m_sedge->m_kind == SUPEREDGE_RETURN);
+}
+
+/* Implementation of diagnostic_event::get_desc vfunc for
+   return_event.
+
+   If this return event returns critical state for an sm-based warning,
+   allow the diagnostic to generate a precise description, such as:
+
+      "possible of NULL to 'foo' from 'bar'"
+
+   Otherwise, generate a description of the form
+   "returning to 'foo' from 'bar'.  */
+
+label_text
+return_event::get_desc (bool can_colorize) const
+{
+  /*  For greatest precision-of-wording, if this is returning the
+      state involved in the pending diagnostic, give the pending
+      diagnostic a chance to describe this return (in terms of
+      itself).  */
+  if (m_critical_state && m_pending_diagnostic)
+    {
+      label_text custom_desc
+	= m_pending_diagnostic->describe_return_of_state
+	    (evdesc::return_of_state (can_colorize,
+				      m_sedge->m_dest->m_fun->decl,
+				      m_sedge->m_src->m_fun->decl,
+				      m_critical_state));
+      if (custom_desc.m_buffer)
+	return custom_desc;
+    }
+  return make_label_text (can_colorize,
+			  "returning to %qE from %qE",
+			  m_sedge->m_dest->m_fun->decl,
+			  m_sedge->m_src->m_fun->decl);
+}
+
+/* Override of checker_event::is_return_p for returns.  */
+
+bool
+return_event::is_return_p () const
+{
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class setjmp_event : public checker_event.  */
+
+/* Implementation of diagnostic_event::get_desc vfunc for
+   setjmp_event.  */
+
+label_text
+setjmp_event::get_desc (bool can_colorize) const
+{
+  return make_label_text (can_colorize,
+			  "%qs called here",
+			  "setjmp");
+}
+
+/* Implementation of checker_event::prepare_for_emission vfunc for setjmp_event.
+
+   Record this setjmp's event ID into the path, so that rewind events can
+   use it.  */
+
+void
+setjmp_event::prepare_for_emission (checker_path *path,
+				    pending_diagnostic *pd,
+				    diagnostic_event_id_t emission_id)
+{
+  checker_event::prepare_for_emission (path, pd, emission_id);
+  path->record_setjmp_event (m_enode, emission_id);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class rewind_event : public checker_event.  */
+
+/* Get the fndecl containing the site of the longjmp call.  */
+
+tree
+rewind_event::get_longjmp_caller () const
+{
+  return m_eedge->m_src->get_function ()->decl;
+}
+
+/* Get the fndecl containing the site of the setjmp call.  */
+
+tree
+rewind_event::get_setjmp_caller () const
+{
+  return m_eedge->m_dest->get_function ()->decl;
+}
+
+/* rewind_event's ctor.  */
+
+rewind_event::rewind_event (const exploded_edge *eedge,
+			    enum event_kind kind,
+			    location_t loc, tree fndecl, int depth)
+: checker_event (kind, loc, fndecl, depth),
+  m_eedge (eedge)
+{
+  gcc_assert (m_eedge->m_rewind_info);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class rewind_from_longjmp_event : public rewind_event.  */
+
+/* Implementation of diagnostic_event::get_desc vfunc for
+   rewind_from_longjmp_event.  */
+
+label_text
+rewind_from_longjmp_event::get_desc (bool can_colorize) const
+{
+  const char *src_name = "longjmp";
+
+  if (get_longjmp_caller () == get_setjmp_caller ())
+    /* Special-case: purely intraprocedural rewind.  */
+    return make_label_text (can_colorize,
+			    "rewinding within %qE from %qs...",
+			    get_longjmp_caller (),
+			    src_name);
+  else
+    return make_label_text (can_colorize,
+			    "rewinding from %qs in %qE...",
+			    src_name,
+			    get_longjmp_caller ());
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class rewind_to_setjmp_event : public rewind_event.  */
+
+/* Implementation of diagnostic_event::get_desc vfunc for
+   rewind_to_setjmp_event.  */
+
+label_text
+rewind_to_setjmp_event::get_desc (bool can_colorize) const
+{
+  const char *dst_name = "setjmp";
+
+  /* If we can, identify the ID of the setjmp_event.  */
+  if (m_original_setjmp_event_id.known_p ())
+    {
+      if (get_longjmp_caller () == get_setjmp_caller ())
+	/* Special-case: purely intraprocedural rewind.  */
+	return make_label_text (can_colorize,
+				"...to %qs (saved at %@)",
+				dst_name,
+				&m_original_setjmp_event_id);
+      else
+	return make_label_text (can_colorize,
+				"...to %qs in %qE (saved at %@)",
+				dst_name,
+				get_setjmp_caller (),
+				&m_original_setjmp_event_id);
+    }
+  else
+    {
+      if (get_longjmp_caller () == get_setjmp_caller ())
+	/* Special-case: purely intraprocedural rewind.  */
+	return make_label_text (can_colorize,
+				"...to %qs",
+				dst_name,
+				get_setjmp_caller ());
+      else
+	return make_label_text (can_colorize,
+				"...to %qs in %qE",
+				dst_name,
+				get_setjmp_caller ());
+    }
+}
+
+/* Implementation of checker_event::prepare_for_emission vfunc for
+   rewind_to_setjmp_event.
+
+   Attempt to look up the setjmp event ID that recorded the jmp_buf
+   for this rewind.  */
+
+void
+rewind_to_setjmp_event::prepare_for_emission (checker_path *path,
+					      pending_diagnostic *pd,
+					      diagnostic_event_id_t emission_id)
+{
+  checker_event::prepare_for_emission (path, pd, emission_id);
+  path->get_setjmp_event (get_eedge ()->m_rewind_info->get_enode_origin (),
+			  &m_original_setjmp_event_id);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class warning_event : public checker_event.  */
+
+/* Implementation of diagnostic_event::get_desc vfunc for
+   warning_event.
+
+   If the pending diagnostic implements describe_final_event, use it,
+   generating a precise description e.g.
+     "second 'free' here; first 'free' was at (7)"
+
+   Otherwise generate a generic description.  */
+
+label_text
+warning_event::get_desc (bool can_colorize) const
+{
+  if (m_pending_diagnostic)
+    {
+      label_text ev_desc
+	= m_pending_diagnostic->describe_final_event
+	    (evdesc::final_event (can_colorize, m_var, m_state));
+      if (ev_desc.m_buffer)
+	{
+	  if (m_sm && flag_analyzer_verbose_state_changes)
+	    {
+	      label_text result
+		= make_label_text (can_colorize,
+				   "%s (%qE is in state %qs)",
+				   ev_desc.m_buffer,
+				   m_var,m_sm->get_state_name (m_state));
+	      ev_desc.maybe_free ();
+	      return result;
+	    }
+	  else
+	    return ev_desc;
+	}
+    }
+
+  if (m_sm)
+    return make_label_text (can_colorize,
+			    "here (%qE is in state %qs)",
+			    m_var,
+			    m_sm->get_state_name (m_state));
+  else
+    return label_text::borrow ("here");
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Print a single-line representation of this path to PP.  */
+
+void
+checker_path::dump (pretty_printer *pp) const
+{
+  pp_character (pp, '[');
+
+  checker_event *e;
+  int i;
+  FOR_EACH_VEC_ELT (m_events, i, e)
+    {
+      if (i > 0)
+	pp_string (pp, ", ");
+      label_text event_desc (e->get_desc (false));
+      pp_printf (pp, "\"%s\"", event_desc.m_buffer);
+      event_desc.maybe_free ();
+    }
+  pp_character (pp, ']');
+}
+
+/* Print a multiline form of this path to LOGGER, prefixing it with DESC.  */
+
+void
+checker_path::maybe_log (logger *logger, const char *desc) const
+{
+  if (!logger)
+    return;
+  logger->start_log_line ();
+  logger->log_partial ("%s: ", desc);
+  dump (logger->get_printer ());
+  logger->end_log_line ();
+  for (unsigned i = 0; i < m_events.length (); i++)
+    {
+      logger->start_log_line ();
+      logger->log_partial ("%s[%i]: %s ", desc, i,
+			   event_kind_to_string (m_events[i]->m_kind));
+      m_events[i]->dump (logger->get_printer ());
+      logger->end_log_line ();
+    }
+}
+
+/* Add a warning_event to the end of this path.  */
+
+void
+checker_path::add_final_event (const state_machine *sm,
+			       const exploded_node *enode, const gimple *stmt,
+			       tree var, state_machine::state_t state)
+{
+  checker_event *end_of_path
+    = new warning_event (stmt->location,
+			 enode->get_function ()->decl,
+			 enode->get_stack_depth (),
+			 sm, var, state);
+  add_event (end_of_path);
+}
diff --git a/gcc/analyzer/checker-path.h b/gcc/analyzer/checker-path.h
new file mode 100644
index 0000000..f042f47
--- /dev/null
+++ b/gcc/analyzer/checker-path.h
@@ -0,0 +1,563 @@
+/* Subclasses of diagnostic_path and diagnostic_event for analyzer diagnostics.
+   Copyright (C) 2019 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/>.  */
+
+#ifndef GCC_ANALYZER_CHECKER_PATH_H
+#define GCC_ANALYZER_CHECKER_PATH_H
+
+#include "diagnostic-path.h"
+#include "analyzer/program-state.h"
+
+////////////////////////////////////////////////////////////////////////////
+
+/* An enum for discriminating between the concrete subclasses of
+   checker_event.  */
+
+enum event_kind
+{
+  EK_DEBUG,
+  EK_STMT,
+  EK_FUNCTION_ENTRY,
+  EK_STATE_CHANGE,
+  EK_START_CFG_EDGE,
+  EK_END_CFG_EDGE,
+  EK_CALL_EDGE,
+  EK_RETURN_EDGE,
+  EK_SETJMP,
+  EK_REWIND_FROM_LONGJMP,
+  EK_REWIND_TO_SETJMP,
+  EK_WARNING
+};
+
+extern const char *event_kind_to_string (enum event_kind ek);
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Event subclasses.
+
+   The class hierarchy looks like this (using indentation to show
+   inheritance, and with event_kinds shown for the concrete subclasses):
+
+   diagnostic_event
+     checker_event
+       debug_event (EK_DEBUG)
+       statement_event (EK_STMT)
+       function_entry_event (EK_FUNCTION_ENTRY)
+       state_change_event (EK_STATE_CHANGE)
+       superedge_event
+         cfg_edge_event
+	   start_cfg_edge_event (EK_START_CFG_EDGE)
+	   end_cfg_edge_event (EK_END_CFG_EDGE)
+         call_event (EK_CALL_EDGE)
+         return_edge (EK_RETURN_EDGE)
+       setjmp_event (EK_SETJMP)
+       rewind_event
+         rewind_from_longjmp_event (EK_REWIND_FROM_LONGJMP)
+	 rewind_to_setjmp_event (EK_REWIND_TO_SETJMP)
+       warning_event (EK_WARNING).  */
+
+/* Abstract subclass of diagnostic_event; the base class for use in
+   checker_path (the analyzer's diagnostic_path subclass).  */
+
+class checker_event : public diagnostic_event
+{
+public:
+  checker_event (enum event_kind kind,
+		 location_t loc, tree fndecl, int depth)
+    : m_kind (kind), m_loc (loc), m_fndecl (fndecl), m_depth (depth),
+      m_pending_diagnostic (NULL), m_emission_id ()
+  {
+  }
+
+  /* Implementation of diagnostic_event.  */
+
+  location_t get_location () const FINAL OVERRIDE { return m_loc; }
+  tree get_fndecl () const FINAL OVERRIDE { return m_fndecl; }
+  int get_stack_depth () const FINAL OVERRIDE { return m_depth; }
+
+  /* Additional functionality.  */
+
+  virtual checker_event *clone () const = 0;
+
+  virtual void prepare_for_emission (checker_path *,
+				     pending_diagnostic *pd,
+				     diagnostic_event_id_t emission_id);
+  virtual bool is_call_p () const { return false; }
+  virtual bool is_function_entry_p () const  { return false; }
+  virtual bool is_return_p () const  { return false; }
+
+  void dump (pretty_printer *pp) const;
+
+ public:
+  const enum event_kind m_kind;
+ protected:
+  location_t m_loc;
+  tree m_fndecl;
+  int m_depth;
+  pending_diagnostic *m_pending_diagnostic;
+  diagnostic_event_id_t m_emission_id; // only set once all pruning has occurred
+};
+
+/* A concrete event subclass for a purely textual event, for use in
+   debugging path creation and filtering.  */
+
+class debug_event : public checker_event
+{
+public:
+  debug_event (location_t loc, tree fndecl, int depth,
+	      const char *desc)
+  : checker_event (EK_DEBUG, loc, fndecl, depth),
+    m_desc (xstrdup (desc))
+  {
+  }
+  ~debug_event ()
+  {
+    free (m_desc);
+  }
+
+  label_text get_desc (bool) const FINAL OVERRIDE;
+
+  checker_event *clone () const FINAL OVERRIDE
+  {
+    return new debug_event (m_loc, m_fndecl, m_depth, m_desc);
+  }
+
+private:
+  char *m_desc;
+};
+
+/* A concrete event subclass describing the execution of a gimple statement,
+   for use at high verbosity levels when debugging paths.  */
+
+class statement_event : public checker_event
+{
+public:
+  statement_event (const gimple *stmt, tree fndecl, int depth,
+		   const program_state &dst_state);
+
+  label_text get_desc (bool) const FINAL OVERRIDE;
+
+  checker_event *clone () const FINAL OVERRIDE
+  {
+    return new statement_event (m_stmt, m_fndecl, m_depth, m_dst_state);
+  }
+
+  const gimple * const m_stmt;
+  const program_state m_dst_state;
+};
+
+/* An event subclass describing the entry to a function.  */
+
+class function_entry_event : public checker_event
+{
+public:
+  function_entry_event (location_t loc, tree fndecl, int depth)
+  : checker_event (EK_FUNCTION_ENTRY, loc, fndecl, depth)
+  {
+  }
+
+  label_text get_desc (bool can_colorize) const FINAL OVERRIDE;
+
+  checker_event *clone () const FINAL OVERRIDE
+  {
+    return new function_entry_event (m_loc, m_fndecl, m_depth);
+  }
+
+  bool is_function_entry_p () const FINAL OVERRIDE { return true; }
+};
+
+/* Subclass of checker_event describing a state change.  */
+
+class state_change_event : public checker_event
+{
+public:
+  state_change_event (const supernode *node, const gimple *stmt,
+		      int stack_depth,
+		      const state_machine &sm,
+		      tree var,
+		      state_machine::state_t from,
+		      state_machine::state_t to,
+		      tree origin,
+		      const program_state &dst_state);
+
+  label_text get_desc (bool can_colorize) const FINAL OVERRIDE;
+
+  checker_event *clone () const FINAL OVERRIDE
+  {
+    return new state_change_event (m_node, m_stmt, m_depth,
+				   m_sm, m_var, m_from, m_to, m_origin,
+				   m_dst_state);
+  }
+
+  region_id get_lvalue (tree expr) const
+  {
+    return m_dst_state.m_region_model->get_lvalue (expr, NULL);
+  }
+
+  const supernode *m_node;
+  const gimple *m_stmt;
+  const state_machine &m_sm;
+  tree m_var;
+  state_machine::state_t m_from;
+  state_machine::state_t m_to;
+  tree m_origin;
+  program_state m_dst_state;
+};
+
+/* Subclass of checker_event; parent class for subclasses that relate to
+   a superedge.  */
+
+class superedge_event : public checker_event
+{
+public:
+  /* Mark this edge event as being either an interprocedural call or
+     return in which VAR is in STATE, and that this is critical to the
+     diagnostic (so that get_desc can attempt to get a better description
+     from any pending_diagnostic).  */
+  void record_critical_state (tree var, state_machine::state_t state)
+  {
+    m_var = var;
+    m_critical_state = state;
+  }
+
+  const callgraph_superedge& get_callgraph_superedge () const;
+
+  bool should_filter_p (int verbosity) const;
+
+ protected:
+  superedge_event (enum event_kind kind, const exploded_edge &eedge,
+		   location_t loc, tree fndecl, int depth);
+
+ public:
+  const exploded_edge &m_eedge;
+  const superedge *m_sedge;
+  tree m_var;
+  state_machine::state_t m_critical_state;
+};
+
+/* An abstract event subclass for when a CFG edge is followed; it has two
+   subclasses, representing the start of the edge and the end of the
+   edge, which come in pairs.  */
+
+class cfg_edge_event : public superedge_event
+{
+public:
+  const cfg_superedge& get_cfg_superedge () const;
+
+ protected:
+  cfg_edge_event (enum event_kind kind, const exploded_edge &eedge,
+		  location_t loc, tree fndecl, int depth);
+};
+
+/* A concrete event subclass for the start of a CFG edge
+   e.g. "following 'false' branch...'.  */
+
+class start_cfg_edge_event : public cfg_edge_event
+{
+public:
+  start_cfg_edge_event (const exploded_edge &eedge,
+			location_t loc, tree fndecl, int depth)
+  : cfg_edge_event (EK_START_CFG_EDGE, eedge, loc, fndecl, depth)
+  {
+  }
+
+  label_text get_desc (bool can_colorize) const FINAL OVERRIDE;
+
+  checker_event *clone () const FINAL OVERRIDE
+  {
+    return new start_cfg_edge_event (m_eedge, m_loc, m_fndecl, m_depth);
+  }
+
+ private:
+  label_text maybe_describe_condition (bool can_colorize) const;
+
+  static label_text maybe_describe_condition (bool can_colorize,
+					      tree lhs,
+					      enum tree_code op,
+					      tree rhs);
+  static bool should_print_expr_p (tree);
+};
+
+/* A concrete event subclass for the end of a CFG edge
+   e.g. "...to here'.  */
+
+class end_cfg_edge_event : public cfg_edge_event
+{
+public:
+  end_cfg_edge_event (const exploded_edge &eedge,
+		      location_t loc, tree fndecl, int depth)
+  : cfg_edge_event (EK_END_CFG_EDGE, eedge, loc, fndecl, depth)
+  {
+  }
+
+  label_text get_desc (bool /*can_colorize*/) const FINAL OVERRIDE
+  {
+    return label_text::borrow ("...to here");
+  }
+
+  checker_event *clone () const FINAL OVERRIDE
+  {
+    return new end_cfg_edge_event (m_eedge, m_loc, m_fndecl, m_depth);
+  }
+};
+
+/* A concrete event subclass for an interprocedural call.  */
+
+class call_event : public superedge_event
+{
+public:
+  call_event (const exploded_edge &eedge,
+	      location_t loc, tree fndecl, int depth);
+
+  label_text get_desc (bool can_colorize) const FINAL OVERRIDE;
+
+  checker_event *clone () const FINAL OVERRIDE
+  {
+    return new call_event (m_eedge, m_loc, m_fndecl, m_depth);
+  }
+
+  bool is_call_p () const FINAL OVERRIDE;
+};
+
+/* A concrete event subclass for an interprocedural return.  */
+
+class return_event : public superedge_event
+{
+public:
+  return_event (const exploded_edge &eedge,
+		location_t loc, tree fndecl, int depth);
+
+  label_text get_desc (bool can_colorize) const FINAL OVERRIDE;
+
+  checker_event *clone () const FINAL OVERRIDE
+  {
+    return new return_event (m_eedge, m_loc, m_fndecl, m_depth);
+  }
+
+  bool is_return_p () const FINAL OVERRIDE;
+};
+
+/* A concrete event subclass for a setjmp call.  */
+
+class setjmp_event : public checker_event
+{
+public:
+  setjmp_event (location_t loc, const exploded_node *enode,
+		tree fndecl, int depth)
+  : checker_event (EK_SETJMP, loc, fndecl, depth),
+    m_enode (enode)
+  {
+  }
+
+  setjmp_event *clone () const FINAL OVERRIDE
+  {
+    return new setjmp_event (m_loc, m_enode, m_fndecl, m_depth);
+  }
+
+  label_text get_desc (bool can_colorize) const FINAL OVERRIDE;
+
+  void prepare_for_emission (checker_path *path,
+			     pending_diagnostic *pd,
+			     diagnostic_event_id_t emission_id) FINAL OVERRIDE;
+
+private:
+  const exploded_node *m_enode;
+};
+
+/* An abstract event subclass for rewinding from a longjmp to a setjmp.
+   Base class for two from/to subclasses, showing the two halves of the
+   rewind.  */
+
+class rewind_event : public checker_event
+{
+public:
+  tree get_longjmp_caller () const;
+  tree get_setjmp_caller () const;
+  const exploded_edge *get_eedge () const { return m_eedge; }
+
+ protected:
+  rewind_event (const exploded_edge *eedge,
+		enum event_kind kind,
+		location_t loc, tree fndecl, int depth);
+
+ private:
+  const exploded_edge *m_eedge;
+};
+
+/* A concrete event subclass for rewinding from a longjmp to a setjmp,
+   showing the longjmp.  */
+
+class rewind_from_longjmp_event : public rewind_event
+{
+public:
+  rewind_from_longjmp_event (const exploded_edge *eedge,
+			     location_t loc, tree fndecl, int depth)
+  : rewind_event (eedge, EK_REWIND_FROM_LONGJMP, loc, fndecl, depth)
+  {
+  }
+
+  label_text get_desc (bool can_colorize) const FINAL OVERRIDE;
+
+  rewind_from_longjmp_event *clone () const FINAL OVERRIDE
+  {
+    return new rewind_from_longjmp_event (get_eedge (),
+					  m_loc, m_fndecl, m_depth);
+  }
+};
+
+/* A concrete event subclass for rewinding from a longjmp to a setjmp,
+   showing the setjmp.  */
+
+class rewind_to_setjmp_event : public rewind_event
+{
+public:
+  rewind_to_setjmp_event (const exploded_edge *eedge,
+			  location_t loc, tree fndecl, int depth)
+  : rewind_event (eedge, EK_REWIND_TO_SETJMP, loc, fndecl, depth)
+  {
+  }
+
+  label_text get_desc (bool can_colorize) const FINAL OVERRIDE;
+
+  rewind_to_setjmp_event *clone () const FINAL OVERRIDE
+  {
+    return new rewind_to_setjmp_event (get_eedge (),
+				       m_loc, m_fndecl, m_depth);
+  }
+
+  void prepare_for_emission (checker_path *path,
+			     pending_diagnostic *pd,
+			     diagnostic_event_id_t emission_id) FINAL OVERRIDE;
+
+private:
+  diagnostic_event_id_t m_original_setjmp_event_id;
+};
+
+/* Concrete subclass of checker_event for use at the end of a path:
+   a repeat of the warning message at the end of the path (perhaps with
+   references to pertinent events that occurred on the way), at the point
+   where the problem occurs.  */
+
+class warning_event : public checker_event
+{
+public:
+  warning_event (location_t loc, tree fndecl, int depth,
+		 const state_machine *sm,
+		 tree var, state_machine::state_t state)
+  : checker_event (EK_WARNING, loc, fndecl, depth),
+    m_sm (sm), m_var (var), m_state (state)
+  {
+  }
+
+  label_text get_desc (bool can_colorize) const FINAL OVERRIDE;
+
+  warning_event *clone () const FINAL OVERRIDE
+  {
+    return new warning_event (m_loc, m_fndecl, m_depth, m_sm, m_var, m_state);
+  }
+
+private:
+  const state_machine *m_sm;
+  tree m_var;
+  state_machine::state_t m_state;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Subclass of diagnostic_path for analyzer diagnostics.  */
+
+class checker_path : public diagnostic_path
+{
+public:
+  checker_path () : diagnostic_path () {}
+
+  /* Implementation of diagnostic_path vfuncs.  */
+
+  unsigned num_events () const FINAL OVERRIDE
+  {
+    return m_events.length ();
+  }
+
+  const diagnostic_event & get_event (int idx) const FINAL OVERRIDE
+  {
+    return *m_events[idx];
+  }
+
+
+  void dump (pretty_printer *pp) const;
+
+  void maybe_log (logger *logger, const char *desc) const;
+
+  void add_event (checker_event *event)
+  {
+    m_events.safe_push (event);
+  }
+
+  void delete_event (int idx)
+  {
+    checker_event *event = m_events[idx];
+    m_events.ordered_remove (idx);
+    delete event;
+  }
+
+  void add_final_event (const state_machine *sm,
+			const exploded_node *enode, const gimple *stmt,
+			tree var, state_machine::state_t state);
+
+  /* After all event-pruning, a hook for notifying each event what
+     its ID will be.  The events are notified in order, allowing
+     for later events to refer to the IDs of earlier events in
+     their descriptions.  */
+  void prepare_for_emission (pending_diagnostic *pd)
+  {
+    checker_event *e;
+    int i;
+    FOR_EACH_VEC_ELT (m_events, i, e)
+      e->prepare_for_emission (this, pd, diagnostic_event_id_t (i));
+  }
+
+  void record_setjmp_event (const exploded_node *enode,
+			    diagnostic_event_id_t setjmp_emission_id)
+  {
+    m_setjmp_event_ids.put (enode, setjmp_emission_id);
+  }
+
+  bool get_setjmp_event (const exploded_node *enode,
+			 diagnostic_event_id_t *out_emission_id)
+  {
+    if (diagnostic_event_id_t *emission_id = m_setjmp_event_ids.get (enode))
+      {
+	*out_emission_id = *emission_id;
+	return true;
+      }
+    return false;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+
+  /* The events that have occurred along this path.  */
+  auto_delete_vec<checker_event> m_events;
+
+  /* During prepare_for_emission (and after), the setjmp_event for each
+     exploded_node *, so that rewind events can refer to them in their
+     descriptions.  */
+  hash_map <const exploded_node *, diagnostic_event_id_t> m_setjmp_event_ids;
+};
+
+#endif /* GCC_ANALYZER_CHECKER_PATH_H */
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 13/49] function-tests.c: expose selftest::make_fndecl for use elsewhere
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (41 preceding siblings ...)
  2019-11-16  1:21 ` [PATCH 48/49] gdbinit.in: add break-on-saved-diagnostic David Malcolm
@ 2019-11-16  1:21 ` David Malcolm
  2019-12-07 14:37   ` Jeff Law
  2019-11-16  1:21 ` [PATCH 37/49] analyzer: new file: sm-sensitive.cc David Malcolm
                   ` (7 subsequent siblings)
  50 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:21 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This is used by new selftests later on in the patch kit.

gcc/ChangeLog:
	* function-tests.c (selftest::make_fndecl): Make non-static.
	* selftest.h (selftest::make_fndecl): New decl.
---
 gcc/function-tests.c | 4 ++--
 gcc/selftest.h       | 7 +++++++
 2 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/gcc/function-tests.c b/gcc/function-tests.c
index 2440dd6..f3406c4 100644
--- a/gcc/function-tests.c
+++ b/gcc/function-tests.c
@@ -82,11 +82,11 @@ namespace selftest {
 
 /* Helper function for selftests of function-creation.  */
 
-static tree
+tree
 make_fndecl (tree return_type,
 	     const char *name,
 	     vec <tree> &param_types,
-	     bool is_variadic = false)
+	     bool is_variadic)
 {
   tree fn_type;
   if (is_variadic)
diff --git a/gcc/selftest.h b/gcc/selftest.h
index 165dfff..c67b688 100644
--- a/gcc/selftest.h
+++ b/gcc/selftest.h
@@ -171,6 +171,13 @@ class line_table_test
   ~line_table_test ();
 };
 
+/* Helper function for selftests that need a function decl.  */
+
+extern tree make_fndecl (tree return_type,
+			 const char *name,
+			 vec <tree> &param_types,
+			 bool is_variadic = false);
+
 /* Run TESTCASE multiple times, once for each case in our test matrix.  */
 
 extern void
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 48/49] gdbinit.in: add break-on-saved-diagnostic
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (40 preceding siblings ...)
  2019-11-16  1:21 ` [PATCH 15/49] Add ordered_hash_map David Malcolm
@ 2019-11-16  1:21 ` David Malcolm
  2019-11-16  1:21 ` [PATCH 13/49] function-tests.c: expose selftest::make_fndecl for use elsewhere David Malcolm
                   ` (8 subsequent siblings)
  50 siblings, 0 replies; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:21 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This patch adds a "break-on-saved-diagnostic" command to gdbinit.in,
useful for debugging when a diagnostic is queued by the analyzer.

gcc/ChangeLog:
	* gdbinit.in (break-on-saved-diagnostic): New command.
---
 gcc/gdbinit.in | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/gcc/gdbinit.in b/gcc/gdbinit.in
index 42302ae..c7e4a33 100644
--- a/gcc/gdbinit.in
+++ b/gcc/gdbinit.in
@@ -219,6 +219,16 @@ is emitted (as opposed to those warnings that are suppressed by
 command-line options).
 end
 
+define break-on-saved-diagnostic
+break diagnostic_manager::add_diagnostic
+end
+
+document break-on-saved-diagnostic
+Put a breakpoint on diagnostic_manager::add_diagnostic, called within
+the analyzer whenever a diagnostic is saved for later de-duplication and
+possible emission.
+end
+
 define reload-gdbhooks
 python import imp; imp.reload(gdbhooks)
 end
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 10/49] Add -fdiagnostics-nn-line-numbers
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (37 preceding siblings ...)
  2019-11-16  1:21 ` [PATCH 41/49] analyzer: new files: program-point.{cc|h} David Malcolm
@ 2019-11-16  1:21 ` David Malcolm
  2019-12-04 17:18   ` Martin Sebor
  2019-12-04 17:24   ` Martin Sebor
  2019-11-16  1:21 ` [PATCH 49/49] analyzer: test suite David Malcolm
                   ` (11 subsequent siblings)
  50 siblings, 2 replies; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:21 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This patch implements -fdiagnostics-nn-line-numbers, a new option
which makes diagnostic_show_locus print "NN" rather than specific
line numbers when printing the left margin.

This is intended purely to make it easier to write certain kinds of
DejaGnu test; various integration tests for diagnostic paths later
in the patch kit make use of it.

gcc/ChangeLog:
	* common.opt (fdiagnostics-nn-line-numbers): New option.
	* diagnostic-show-locus.c (layout::m_use_nn_for_line_numbers_p):
	New field.
	(layout::layout): Initialize it and use it when computing
	m_linenum_width.
	(layout::print_source_line): Implement printing "NN" rather than
	the line number.
	(selftest::test_line_numbers_multiline_range): Add a test of "NN"
	printing.
	* diagnostic.c (diagnostic_initialize): Initialize
	use_nn_for_line_numbers_p.
	(num_digits): Add "use_nn_p" param.
	(selftest::test_num_digits): Add a test for use_nn_p==true.
	* diagnostic.h (diagnostic_context::use_nn_for_line_numbers_p):
	New field.
	(num_digits): Add optional "use_nn_p" param.
	* doc/invoke.texi (-fdiagnostics-nn-line-numbers): New option.
	* dwarf2out.c (gen_producer_string): Ignore
	OPT_fdiagnostics_nn_line_numbers.
	* lto-wrapper.c (merge_and_complain): Handle
	OPT_fdiagnostics_nn_line_numbers.
	(append_compiler_options): Likewise.
	(append_diag_options): Likewise.
	* opts.c (common_handle_option): Likewise.
	* toplev.c (general_init): Initialize
	global_dc->use_nn_for_line_numbers_p.
---
 gcc/common.opt              |  4 ++++
 gcc/diagnostic-show-locus.c | 51 ++++++++++++++++++++++++++++++++-------------
 gcc/diagnostic.c            | 13 ++++++++++--
 gcc/diagnostic.h            |  6 +++++-
 gcc/doc/invoke.texi         |  7 +++++++
 gcc/dwarf2out.c             |  1 +
 gcc/lto-wrapper.c           |  3 +++
 gcc/opts.c                  |  4 ++++
 gcc/toplev.c                |  2 ++
 9 files changed, 73 insertions(+), 18 deletions(-)

diff --git a/gcc/common.opt b/gcc/common.opt
index 124e8cf..3c024b3 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1257,6 +1257,10 @@ fdiagnostics-show-line-numbers
 Common Var(flag_diagnostics_show_line_numbers) Init(1)
 Show line numbers in the left margin when showing source.
 
+fdiagnostics-nn-line-numbers
+Common Var(flag_diagnostics_nn_line_numbers) Init(0)
+Replace line numbers with 'NN' when showing source.
+
 fdiagnostics-color
 Common Alias(fdiagnostics-color=,always,never)
 ;
diff --git a/gcc/diagnostic-show-locus.c b/gcc/diagnostic-show-locus.c
index 412b0d4..ec5fdc0 100644
--- a/gcc/diagnostic-show-locus.c
+++ b/gcc/diagnostic-show-locus.c
@@ -303,6 +303,7 @@ class layout
   bool m_colorize_source_p;
   bool m_show_labels_p;
   bool m_show_line_numbers_p;
+  bool m_use_nn_for_line_numbers_p;
   auto_vec <layout_range> m_layout_ranges;
   auto_vec <const fixit_hint *> m_fixit_hints;
   auto_vec <line_span> m_line_spans;
@@ -849,6 +850,7 @@ layout::layout (diagnostic_context * context,
   m_colorize_source_p (context->colorize_source_p),
   m_show_labels_p (context->show_labels_p),
   m_show_line_numbers_p (context->show_line_numbers_p),
+  m_use_nn_for_line_numbers_p (context->use_nn_for_line_numbers_p),
   m_layout_ranges (richloc->get_num_locations ()),
   m_fixit_hints (richloc->get_num_fixit_hints ()),
   m_line_spans (1 + richloc->get_num_locations ()),
@@ -884,7 +886,7 @@ layout::layout (diagnostic_context * context,
   int highest_line = last_span->m_last_line;
   if (highest_line < 0)
     highest_line = 0;
-  m_linenum_width = num_digits (highest_line);
+  m_linenum_width = num_digits (highest_line, m_use_nn_for_line_numbers_p);
   /* If we're showing jumps in the line-numbering, allow at least 3 chars.  */
   if (m_line_spans.length () > 1)
     m_linenum_width = MAX (m_linenum_width, 3);
@@ -1272,10 +1274,13 @@ layout::print_source_line (linenum_type row, const char *line, int line_width,
 
   if (m_show_line_numbers_p)
     {
-      int width = num_digits (row);
+      int width = num_digits (row, m_use_nn_for_line_numbers_p);
       for (int i = 0; i < m_linenum_width - width; i++)
 	pp_space (m_pp);
-      pp_printf (m_pp, "%i | ", row);
+      if (m_use_nn_for_line_numbers_p)
+	pp_printf (m_pp, "%s | ", "NN");
+      else
+	pp_printf (m_pp, "%i | ", row);
     }
   else
     pp_space (m_pp);
@@ -3778,18 +3783,34 @@ test_line_numbers_multiline_range ()
     = linemap_position_for_line_and_column (line_table, ord_map, 11, 4);
   location_t loc = make_location (caret, start, finish);
 
-  test_diagnostic_context dc;
-  dc.show_line_numbers_p = true;
-  dc.min_margin_width = 0;
-  gcc_rich_location richloc (loc);
-  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-  ASSERT_STREQ (" 9 | this is line 9\n"
-		"   |         ~~~~~~\n"
-		"10 | this is line 10\n"
-		"   | ~~~~~^~~~~~~~~~\n"
-		"11 | this is line 11\n"
-		"   | ~~~~  \n",
-		pp_formatted_text (dc.printer));
+  {
+    test_diagnostic_context dc;
+    dc.show_line_numbers_p = true;
+    dc.min_margin_width = 0;
+    gcc_rich_location richloc (loc);
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ (" 9 | this is line 9\n"
+		  "   |         ~~~~~~\n"
+		  "10 | this is line 10\n"
+		  "   | ~~~~~^~~~~~~~~~\n"
+		  "11 | this is line 11\n"
+		  "   | ~~~~  \n",
+		  pp_formatted_text (dc.printer));
+  }
+
+  /* Verify that obscuring line numbers via "NN" works (and always uses
+     at least two columns).  */
+  {
+    test_diagnostic_context dc;
+    dc.show_line_numbers_p = true;
+    dc.use_nn_for_line_numbers_p = true;
+    dc.min_margin_width = 0;
+    gcc_rich_location richloc (start);
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ ("NN | this is line 9\n"
+		  "   |         ^\n",
+		  pp_formatted_text (dc.printer));
+  }
 }
 
 /* Run all of the selftests within this file.  */
diff --git a/gcc/diagnostic.c b/gcc/diagnostic.c
index d6604e6..81e0a10 100644
--- a/gcc/diagnostic.c
+++ b/gcc/diagnostic.c
@@ -209,6 +209,7 @@ diagnostic_initialize (diagnostic_context *context, int n_opts)
   context->colorize_source_p = false;
   context->show_labels_p = false;
   context->show_line_numbers_p = false;
+  context->use_nn_for_line_numbers_p = false;
   context->min_margin_width = 0;
   context->show_ruler_p = false;
   context->parseable_fixits_p = false;
@@ -1078,15 +1079,21 @@ diagnostic_report_diagnostic (diagnostic_context *context,
   return true;
 }
 
-/* Get the number of digits in the decimal representation of VALUE.  */
+/* Get the number of digits in the decimal representation of VALUE.
+
+   If USE_NN_P is true, return 2 (for the case where all numbers are to
+   be printed as just "NN").  */
 
 int
-num_digits (int value)
+num_digits (int value, bool use_nn_p)
 {
   /* Perhaps simpler to use log10 for this, but doing it this way avoids
      using floating point.  */
   gcc_assert (value >= 0);
 
+  if (use_nn_p)
+    return 2;
+
   if (value == 0)
     return 1;
 
@@ -1885,6 +1892,8 @@ test_num_digits ()
   ASSERT_EQ (7, num_digits (9999999));
   ASSERT_EQ (8, num_digits (10000000));
   ASSERT_EQ (8, num_digits (99999999));
+
+  ASSERT_EQ (2, num_digits (1000, true));
 }
 
 /* Run all of the selftests within this file.  */
diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
index 91e4c50..2fb75e6 100644
--- a/gcc/diagnostic.h
+++ b/gcc/diagnostic.h
@@ -233,6 +233,10 @@ struct diagnostic_context
      showing line numbers?  */
   bool show_line_numbers_p;
 
+  /* When printing line numbers, should the actual numbers be replaced with
+     "NN"? (for ease of DejaGnu testing)  */
+  bool use_nn_for_line_numbers_p;
+
   /* If printing source code, what should the minimum width of the margin
      be?  Line numbers will be right-aligned, and padded to this width.  */
   int min_margin_width;
@@ -432,6 +436,6 @@ extern void diagnostic_output_format_init (diagnostic_context *,
 					   enum diagnostics_output_format);
 
 /* Compute the number of digits in the decimal representation of an integer.  */
-extern int num_digits (int);
+extern int num_digits (int, bool use_nn_p = false);
 
 #endif /* ! GCC_DIAGNOSTIC_H */
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 384848a..fc92913 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -278,6 +278,7 @@ Objective-C and Objective-C++ Dialects}.
 -fdiagnostics-format=@r{[}text@r{|}json@r{]}  @gol
 -fno-diagnostics-show-option  -fno-diagnostics-show-caret @gol
 -fno-diagnostics-show-labels  -fno-diagnostics-show-line-numbers @gol
+-fdiagnostics-nn-line-numbers @gol
 -fdiagnostics-minimum-margin-width=@var{width} @gol
 -fdiagnostics-parseable-fixits  -fdiagnostics-generate-patch @gol
 -fdiagnostics-show-template-tree  -fno-elide-type @gol
@@ -4018,6 +4019,12 @@ By default, when printing source code (via @option{-fdiagnostics-show-caret}),
 a left margin is printed, showing line numbers.  This option suppresses this
 left margin.
 
+@item -fdiagnostics-nn-line-numbers
+@opindex fdiagnostics-nn-line-numbers
+When printing source code in diagnostics, replace line numbers ``NN''.
+This option is intended for GCC developers, to make it easier to write
+certain kinds of automated test.
+
 @item -fdiagnostics-minimum-margin-width=@var{width}
 @opindex fdiagnostics-minimum-margin-width
 This option controls the minimum width of the left margin printed by
diff --git a/gcc/dwarf2out.c b/gcc/dwarf2out.c
index bf69ce4..3207040 100644
--- a/gcc/dwarf2out.c
+++ b/gcc/dwarf2out.c
@@ -24490,6 +24490,7 @@ gen_producer_string (void)
       case OPT_fdiagnostics_show_caret:
       case OPT_fdiagnostics_show_labels:
       case OPT_fdiagnostics_show_line_numbers:
+      case OPT_fdiagnostics_nn_line_numbers:
       case OPT_fdiagnostics_color_:
       case OPT_fdiagnostics_format_:
       case OPT_fverbose_asm:
diff --git a/gcc/lto-wrapper.c b/gcc/lto-wrapper.c
index 9a7bbd0..aabfe6d 100644
--- a/gcc/lto-wrapper.c
+++ b/gcc/lto-wrapper.c
@@ -260,6 +260,7 @@ merge_and_complain (struct cl_decoded_option **decoded_options,
 	case OPT_fdiagnostics_show_caret:
 	case OPT_fdiagnostics_show_labels:
 	case OPT_fdiagnostics_show_line_numbers:
+	case OPT_fdiagnostics_nn_line_numbers:
 	case OPT_fdiagnostics_show_option:
 	case OPT_fdiagnostics_show_location_:
 	case OPT_fshow_column:
@@ -605,6 +606,7 @@ append_compiler_options (obstack *argv_obstack, struct cl_decoded_option *opts,
 	case OPT_fdiagnostics_show_caret:
 	case OPT_fdiagnostics_show_labels:
 	case OPT_fdiagnostics_show_line_numbers:
+	case OPT_fdiagnostics_nn_line_numbers:
 	case OPT_fdiagnostics_show_option:
 	case OPT_fdiagnostics_show_location_:
 	case OPT_fshow_column:
@@ -653,6 +655,7 @@ append_diag_options (obstack *argv_obstack, struct cl_decoded_option *opts,
 	case OPT_fdiagnostics_format_:
 	case OPT_fdiagnostics_show_caret:
 	case OPT_fdiagnostics_show_labels:
+	case OPT_fdiagnostics_nn_line_numbers:
 	case OPT_fdiagnostics_show_line_numbers:
 	case OPT_fdiagnostics_show_option:
 	case OPT_fdiagnostics_show_location_:
diff --git a/gcc/opts.c b/gcc/opts.c
index 32869ca..9928d01 100644
--- a/gcc/opts.c
+++ b/gcc/opts.c
@@ -2470,6 +2470,10 @@ common_handle_option (struct gcc_options *opts,
       dc->show_line_numbers_p = value;
       break;
 
+    case OPT_fdiagnostics_nn_line_numbers:
+      dc->use_nn_for_line_numbers_p = value;
+      break;
+
     case OPT_fdiagnostics_color_:
       diagnostic_color_init (dc, value);
       break;
diff --git a/gcc/toplev.c b/gcc/toplev.c
index 1c7002f..2d0a390 100644
--- a/gcc/toplev.c
+++ b/gcc/toplev.c
@@ -1100,6 +1100,8 @@ general_init (const char *argv0, bool init_signals)
     = global_options_init.x_flag_diagnostics_show_labels;
   global_dc->show_line_numbers_p
     = global_options_init.x_flag_diagnostics_show_line_numbers;
+  global_dc->use_nn_for_line_numbers_p
+    = global_options_init.x_flag_diagnostics_nn_line_numbers;
   global_dc->show_option_requested
     = global_options_init.x_flag_diagnostics_show_option;
   global_dc->min_margin_width
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 41/49] analyzer: new files: program-point.{cc|h}
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (36 preceding siblings ...)
  2019-11-16  1:21 ` [PATCH 31/49] analyzer: new files: region-model.{cc|h} David Malcolm
@ 2019-11-16  1:21 ` David Malcolm
  2019-12-11 19:54   ` Jeff Law
  2019-11-16  1:21 ` [PATCH 10/49] Add -fdiagnostics-nn-line-numbers David Malcolm
                   ` (12 subsequent siblings)
  50 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:21 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This patch introduces function_point and program_point, classes
for tracking locations within the program (the latter adding
a call_string for tracking interprocedural location).

gcc/ChangeLog:
	* analyzer/program-point.cc: New file.
	* analyzer/program-point.h: New file.
---
 gcc/analyzer/program-point.cc | 490 ++++++++++++++++++++++++++++++++++++++++++
 gcc/analyzer/program-point.h  | 316 +++++++++++++++++++++++++++
 2 files changed, 806 insertions(+)
 create mode 100644 gcc/analyzer/program-point.cc
 create mode 100644 gcc/analyzer/program-point.h

diff --git a/gcc/analyzer/program-point.cc b/gcc/analyzer/program-point.cc
new file mode 100644
index 0000000..fcf77af
--- /dev/null
+++ b/gcc/analyzer/program-point.cc
@@ -0,0 +1,490 @@
+/* Classes for representing locations within the program.
+   Copyright (C) 2019 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 "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "gimple-pretty-print.h"
+#include "gcc-rich-location.h"
+#include "params.h"
+#include "analyzer/program-point.h"
+#include "analyzer/exploded-graph.h"
+#include "analyzer/analysis-plan.h"
+
+/* Get a string for PK.  */
+
+const char *
+point_kind_to_string (enum point_kind pk)
+{
+  switch (pk)
+    {
+    default:
+      gcc_unreachable ();
+    case PK_ORIGIN:
+      return "PK_ORIGIN";
+    case PK_BEFORE_SUPERNODE:
+      return "PK_BEFORE_SUPERNODE";
+    case PK_BEFORE_STMT:
+      return "PK_BEFORE_STMT";
+    case PK_AFTER_SUPERNODE:
+      return "PK_AFTER_SUPERNODE";
+    case PK_EMPTY:
+      return "PK_EMPTY";
+    case PK_DELETED:
+      return "PK_DELETED";
+    }
+}
+
+/* class function_point.  */
+
+/* Print this function_point to PP.  */
+
+void
+function_point::print (pretty_printer *pp, const format &f) const
+{
+  switch (get_kind ())
+    {
+    default:
+      gcc_unreachable ();
+
+    case PK_ORIGIN:
+      pp_printf (pp, "origin");
+      break;
+
+    case PK_BEFORE_SUPERNODE:
+      {
+	if (m_from_edge)
+	  pp_printf (pp, "before SN: %i (from SN: %i)",
+		     m_supernode->m_index, m_from_edge->m_src->m_index);
+	else
+	  pp_printf (pp, "before SN: %i (NULL from-edge)",
+		     m_supernode->m_index);
+	f.spacer (pp);
+	for (gphi_iterator gpi
+	       = const_cast<supernode *>(get_supernode ())->start_phis ();
+	     !gsi_end_p (gpi); gsi_next (&gpi))
+	  {
+	    const gphi *phi = gpi.phi ();
+	    pp_gimple_stmt_1 (pp, phi, 0, (dump_flags_t)0);
+	  }
+      }
+      break;
+
+    case PK_BEFORE_STMT:
+      pp_printf (pp, "before (SN: %i stmt: %i): ", m_supernode->m_index,
+		 m_stmt_idx);
+      f.spacer (pp);
+      pp_gimple_stmt_1 (pp, get_stmt (), 0, (dump_flags_t)0);
+      if (f.m_newlines)
+	{
+	  pp_newline (pp);
+	  print_source_line (pp);
+	}
+      break;
+
+    case PK_AFTER_SUPERNODE:
+      pp_printf (pp, "after SN: %i", m_supernode->m_index);
+      break;
+    }
+}
+
+/* Generate a hash value for this function_point.  */
+
+hashval_t
+function_point::hash () const
+{
+  inchash::hash hstate;
+  if (m_supernode)
+    hstate.add_int (m_supernode->m_index);
+  hstate.add_ptr (m_from_edge);
+  hstate.add_int (m_stmt_idx);
+  hstate.add_int (m_kind);
+  return hstate.end ();
+}
+
+/* Get the gimple stmt for this function_point, if any.  */
+
+const gimple *
+function_point::get_stmt () const
+{
+  if (m_kind == PK_BEFORE_STMT)
+    return m_supernode->m_stmts[m_stmt_idx];
+  else if (m_kind == PK_AFTER_SUPERNODE)
+    return m_supernode->get_last_stmt ();
+  else
+    return NULL;
+}
+
+/* Get a location for this function_point, if any.  */
+
+location_t
+function_point::get_location () const
+{
+  const gimple *stmt = get_stmt ();
+  if (stmt)
+    return stmt->location;
+
+  return UNKNOWN_LOCATION;
+}
+
+/* A subclass of diagnostic_context for use by
+   program_point::print_source_line.  */
+
+class debug_diagnostic_context : public diagnostic_context
+{
+public:
+  debug_diagnostic_context ()
+  {
+    diagnostic_initialize (this, 0);
+    show_line_numbers_p = true;
+    show_caret = true;
+  }
+  ~debug_diagnostic_context ()
+  {
+    diagnostic_finish (this);
+  }
+};
+
+/* Print the source line (if any) for this function_point to PP.  */
+
+void
+function_point::print_source_line (pretty_printer *pp) const
+{
+  const gimple *stmt = get_stmt ();
+  if (!stmt)
+    return;
+  // TODO: monospace font
+  debug_diagnostic_context tmp_dc;
+  gcc_rich_location richloc (stmt->location);
+  diagnostic_show_locus (&tmp_dc, &richloc, DK_ERROR);
+  pp_string (pp, pp_formatted_text (tmp_dc.printer));
+}
+
+/* class program_point.  */
+
+/* Print this program_point to PP.  */
+
+void
+program_point::print (pretty_printer *pp, const format &f) const
+{
+  pp_string (pp, "callstring: ");
+  m_call_string.print (pp);
+  f.spacer (pp);
+
+  m_function_point.print (pp, f);
+}
+
+/* Dump this point to stderr.  */
+
+DEBUG_FUNCTION void
+program_point::dump () const
+{
+  pretty_printer pp;
+  pp_show_color (&pp) = pp_show_color (global_dc->printer);
+  pp.buffer->stream = stderr;
+  print (&pp, format (true));
+  pp_flush (&pp);
+}
+
+/* Generate a hash value for this program_point.  */
+
+hashval_t
+program_point::hash () const
+{
+  inchash::hash hstate;
+  hstate.merge_hash (m_function_point.hash ());
+  hstate.merge_hash (m_call_string.hash ());
+  return hstate.end ();
+}
+
+/* Check to see if SUCC is a valid edge to take (ensuring that we have
+   interprocedurally valid paths in the exploded graph, and enforcing
+   recursion limits).
+
+   Update the call string if SUCC is a call or a return.
+
+   Return true if SUCC can be taken, or false otherwise.
+
+   This is the "point" half of exploded_node::on_edge.  */
+
+bool
+program_point::on_edge (exploded_graph &eg,
+			const superedge *succ)
+{
+  LOG_FUNC (eg.get_logger ());
+  switch (succ->m_kind)
+    {
+    case SUPEREDGE_CFG_EDGE:
+      {
+	const cfg_superedge *cfg_sedge = as_a <const cfg_superedge *> (succ);
+
+	/* Reject abnormal edges; we special-case setjmp/longjmp.  */
+	if (cfg_sedge->get_flags () & EDGE_ABNORMAL)
+	  return false;
+      }
+      break;
+
+    case SUPEREDGE_CALL:
+      {
+	const call_superedge *call_sedge = as_a <const call_superedge *> (succ);
+
+	if (eg.get_analysis_plan ().use_summary_p (call_sedge->m_cedge))
+	  {
+	    eg.log ("rejecting call edge: using summary instead");
+	    return false;
+	  }
+
+	/* Add the callsite to the call string.  */
+	m_call_string.push_call (eg.get_supergraph (), call_sedge);
+
+	/* Impose a maximum recursion depth and don't analyze paths
+	   that exceed it further.
+	   This is something of a blunt workaround, but it only
+	   applies to recursion (and mutual recursion), not to
+	   general call stacks.  */
+	if (m_call_string.calc_recursion_depth ()
+	    > PARAM_VALUE (PARAM_ANALYZER_MAX_RECURSION_DEPTH))
+	  {
+	    eg.log ("rejecting call edge: recursion limit exceeded");
+	    // TODO: issue a sorry for this?
+	    return false;
+	  }
+      }
+      break;
+
+    case SUPEREDGE_RETURN:
+      {
+	/* Require that we return to the call site in the call string.  */
+	if (m_call_string.empty_p ())
+	  {
+	    eg.log ("rejecting return edge: empty call string");
+	    return false;
+	  }
+	const return_superedge *top_of_stack = m_call_string.pop ();
+	if (top_of_stack != succ)
+	  {
+	    eg.log ("rejecting return edge: return to wrong callsite");
+	    return false;
+	  }
+      }
+      break;
+
+    case SUPEREDGE_INTRAPROCEDURAL_CALL:
+      {
+	const callgraph_superedge *cg_sedge
+	  = as_a <const callgraph_superedge *> (succ);
+	/* Consider turning this edge into a use of an
+	   interprocedural summary.  */
+	if (eg.get_analysis_plan ().use_summary_p (cg_sedge->m_cedge))
+	  {
+	    eg.log ("using function summary for %qE in %qE",
+		    cg_sedge->get_callee_decl (),
+		    cg_sedge->get_caller_decl ());
+	    return true;
+	  }
+	else
+	  {
+	    /* Otherwise, we ignore these edges  */
+	    eg.log ("rejecting interprocedural edge");
+	    return false;
+	  }
+      }
+    }
+
+  return true;
+}
+
+/* Comparator for program points within the same supernode,
+   for implementing worklist::key_t comparison operators.
+   Return negative if POINT_A is before POINT_B
+   Return positive if POINT_A is after POINT_B
+   Return 0 if they are equal.  */
+
+int
+function_point::cmp_within_supernode_1 (const function_point &point_a,
+					const function_point &point_b)
+{
+  gcc_assert (point_a.get_supernode () == point_b.get_supernode ());
+
+  switch (point_a.m_kind)
+    {
+    default:
+      gcc_unreachable ();
+    case PK_BEFORE_SUPERNODE:
+      switch (point_b.m_kind)
+	{
+	default:
+	  gcc_unreachable ();
+	case PK_BEFORE_SUPERNODE:
+	  {
+	    int a_src_idx = -1;
+	    int b_src_idx = -1;
+	    if (point_a.m_from_edge)
+	      a_src_idx = point_a.m_from_edge->m_src->m_index;
+	    if (point_b.m_from_edge)
+	      b_src_idx = point_b.m_from_edge->m_src->m_index;
+	    return a_src_idx - b_src_idx;
+	  }
+	  break;
+
+	case PK_BEFORE_STMT:
+	case PK_AFTER_SUPERNODE:
+	  return -1;
+	}
+      break;
+    case PK_BEFORE_STMT:
+      switch (point_b.m_kind)
+	{
+	default:
+	  gcc_unreachable ();
+	case PK_BEFORE_SUPERNODE:
+	  return 1;
+
+	case PK_BEFORE_STMT:
+	  return point_a.m_stmt_idx - point_b.m_stmt_idx;
+
+	case PK_AFTER_SUPERNODE:
+	  return -1;
+	}
+      break;
+    case PK_AFTER_SUPERNODE:
+      switch (point_b.m_kind)
+	{
+	default:
+	  gcc_unreachable ();
+	case PK_BEFORE_SUPERNODE:
+	case PK_BEFORE_STMT:
+	  return 1;
+
+	case PK_AFTER_SUPERNODE:
+	  return 0;
+	}
+      break;
+    }
+}
+
+/* Comparator for program points within the same supernode,
+   for implementing worklist::key_t comparison operators.
+   Return negative if POINT_A is before POINT_B
+   Return positive if POINT_A is after POINT_B
+   Return 0 if they are equal.  */
+
+int
+function_point::cmp_within_supernode (const function_point &point_a,
+				      const function_point &point_b)
+{
+  int result = cmp_within_supernode_1 (point_a, point_b);
+
+  /* Check that the ordering is symmetric  */
+#if CHECKING_P
+  int reversed = cmp_within_supernode_1 (point_b, point_a);
+  gcc_assert (reversed == -result);
+#endif
+
+  return result;
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Verify that function_point::operator== works as expected.  */
+
+static void
+test_function_point_equality ()
+{
+  const supernode *snode = NULL;
+
+  function_point a = function_point (snode, NULL, 0,
+				     PK_BEFORE_SUPERNODE);
+  function_point b = function_point::before_supernode (snode, NULL);
+  ASSERT_EQ (a, b);
+}
+
+/* Verify that function_point::cmp_within_supernode works as expected.  */
+
+static void
+test_function_point_ordering ()
+{
+  const supernode *snode = NULL;
+  const call_string call_string;
+
+  /* Populate an array with various points within the same
+     snode, in order.  */
+  auto_vec<function_point> points;
+  points.safe_push (function_point::before_supernode (snode, NULL));
+  points.safe_push (function_point::before_stmt (snode, 0));
+  points.safe_push (function_point::before_stmt (snode, 1));
+  points.safe_push (function_point::after_supernode (snode));
+
+  /* Check all pairs.  */
+  unsigned i;
+  function_point *point_a;
+  FOR_EACH_VEC_ELT (points, i, point_a)
+    {
+      unsigned j;
+      function_point *point_b;
+      FOR_EACH_VEC_ELT (points, j, point_b)
+	{
+	  int cmp = function_point::cmp_within_supernode (*point_a, *point_b);
+	  if (i == j)
+	    ASSERT_EQ (cmp, 0);
+	  if (i < j)
+	    ASSERT_TRUE (cmp < 0);
+	  if (i > j)
+	    ASSERT_TRUE (cmp > 0);
+	}
+    }
+}
+
+/* Verify that program_point::operator== works as expected.  */
+
+static void
+test_program_point_equality ()
+{
+  const supernode *snode = NULL;
+
+  const call_string cs;
+
+  program_point a = program_point::before_supernode (snode, NULL,
+						     cs);
+
+  program_point b = program_point::before_supernode (snode, NULL,
+						     cs);
+
+  ASSERT_EQ (a, b);
+  // TODO: verify with non-empty callstrings, with different edges
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+analyzer_program_point_cc_tests ()
+{
+  test_function_point_equality ();
+  test_function_point_ordering ();
+  test_program_point_equality ();
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
diff --git a/gcc/analyzer/program-point.h b/gcc/analyzer/program-point.h
new file mode 100644
index 0000000..ad7b9cd
--- /dev/null
+++ b/gcc/analyzer/program-point.h
@@ -0,0 +1,316 @@
+/* Classes for representing locations within the program.
+   Copyright (C) 2019 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/>.  */
+
+#ifndef GCC_ANALYZER_PROGRAM_POINT_H
+#define GCC_ANALYZER_PROGRAM_POINT_H
+
+#include "analyzer/call-string.h"
+#include "analyzer/supergraph.h"
+
+class exploded_graph;
+
+/* An enum for distinguishing the various kinds of program_point.  */
+
+enum point_kind {
+  /* A "fake" node which has edges to all entrypoints.  */
+  PK_ORIGIN,
+
+  PK_BEFORE_SUPERNODE,
+  PK_BEFORE_STMT,
+  PK_AFTER_SUPERNODE,
+
+  /* Special values used for hash_map:  */
+  PK_EMPTY,
+  PK_DELETED,
+
+  NUM_POINT_KINDS
+};
+
+extern const char *point_kind_to_string (enum point_kind pk);
+
+////////////////////////////////////////////////////////////////////////////
+
+class format
+{
+public:
+  format (bool newlines) : m_newlines (newlines) {}
+
+  void spacer (pretty_printer *pp) const
+  {
+    if (m_newlines)
+      pp_newline (pp);
+    else
+      pp_space (pp);
+  }
+
+  bool m_newlines;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* A class for representing a location within the program, without
+   interprocedural information.
+
+   This represents a fine-grained location within the supergraph (or
+   within one of its nodes).  */
+
+class function_point
+{
+public:
+  function_point (const supernode *supernode,
+		  const superedge *from_edge,
+		  unsigned stmt_idx,
+		  enum point_kind kind)
+  : m_supernode (supernode), m_from_edge (from_edge),
+    m_stmt_idx (stmt_idx), m_kind (kind)
+  {
+    if (from_edge)
+      {
+	gcc_checking_assert (m_kind == PK_BEFORE_SUPERNODE);
+	gcc_checking_assert (from_edge->get_kind () == SUPEREDGE_CFG_EDGE);
+      }
+    if (stmt_idx)
+      gcc_checking_assert (m_kind == PK_BEFORE_STMT);
+  }
+
+  void print (pretty_printer *pp, const format &f) const;
+  void print_source_line (pretty_printer *pp) const;
+  void dump () const;
+
+  hashval_t hash () const;
+  bool operator== (const function_point &other) const
+  {
+    return (m_supernode == other.m_supernode
+	    && m_from_edge == other.m_from_edge
+	    && m_stmt_idx == other.m_stmt_idx
+	    && m_kind == other.m_kind);
+  }
+
+  /* Accessors.  */
+
+  const supernode *get_supernode () const { return m_supernode; }
+  function *get_function () const
+  {
+    if (m_supernode)
+      return m_supernode->m_fun;
+    else
+      return NULL;
+  }
+  const gimple *get_stmt () const;
+  location_t get_location () const;
+  enum point_kind get_kind () const { return m_kind; }
+  const superedge *get_from_edge () const
+  {
+    return m_from_edge;
+  }
+  unsigned get_stmt_idx () const
+  {
+    gcc_assert (m_kind == PK_BEFORE_STMT);
+    return m_stmt_idx;
+  }
+
+  /* Factory functions for making various kinds of program_point.  */
+
+  static function_point from_function_entry (const supergraph &sg,
+					    function *fun)
+  {
+    return before_supernode (sg.get_node_for_function_entry (fun),
+			     NULL);
+  }
+
+  static function_point before_supernode (const supernode *supernode,
+					  const superedge *from_edge)
+  {
+    if (from_edge && from_edge->get_kind () != SUPEREDGE_CFG_EDGE)
+      from_edge = NULL;
+    return function_point (supernode, from_edge, 0, PK_BEFORE_SUPERNODE);
+  }
+
+  static function_point before_stmt (const supernode *supernode,
+				     unsigned stmt_idx)
+  {
+    return function_point (supernode, NULL, stmt_idx, PK_BEFORE_STMT);
+  }
+
+  static function_point after_supernode (const supernode *supernode)
+  {
+    return function_point (supernode, NULL, 0, PK_AFTER_SUPERNODE);
+  }
+
+  /* Support for hash_map.  */
+
+  static function_point empty ()
+  {
+    return function_point (NULL, NULL, 0, PK_EMPTY);
+  }
+  static function_point deleted ()
+  {
+    return function_point (NULL, NULL, 0, PK_DELETED);
+  }
+
+  static int cmp_within_supernode_1 (const function_point &point_a,
+				     const function_point &point_b);
+  static int cmp_within_supernode (const function_point &point_a,
+				   const function_point &point_b);
+
+ private:
+  const supernode *m_supernode;
+
+  /* For PK_BEFORE_SUPERNODE, and only for CFG edges.  */
+  const superedge *m_from_edge;
+
+  /* Only for PK_BEFORE_STMT.  */
+  unsigned m_stmt_idx;
+
+  enum point_kind m_kind;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* A class for representing a location within the program, including
+   interprocedural information.
+
+   This represents a fine-grained location within the supergraph (or
+   within one of its nodes), along with a call string giving the
+   interprocedural context.  */
+
+class program_point
+{
+public:
+  program_point (const function_point &fn_point,
+		 const call_string &call_string)
+  : m_function_point (fn_point),
+    m_call_string (call_string)
+  {
+  }
+
+  void print (pretty_printer *pp, const format &f) const;
+  void print_source_line (pretty_printer *pp) const;
+  void dump () const;
+
+  hashval_t hash () const;
+  bool operator== (const program_point &other) const
+  {
+    return (m_function_point == other.m_function_point
+	    && m_call_string == other.m_call_string);
+  }
+
+  /* Accessors.  */
+
+  const function_point &get_function_point () const { return m_function_point; }
+  const call_string &get_call_string () const { return m_call_string; }
+
+  const supernode *get_supernode () const
+  {
+    return m_function_point.get_supernode ();
+  }
+  function *get_function () const
+  {
+    return m_function_point.get_function ();
+  }
+  tree get_fndecl () const
+  {
+    gcc_assert (get_kind () != PK_ORIGIN);
+    return get_function ()->decl;
+  }
+  const gimple *get_stmt () const
+  {
+    return m_function_point.get_stmt ();
+  }
+  location_t get_location () const
+  {
+    return m_function_point.get_location ();
+  }
+  enum point_kind get_kind () const
+  {
+    return m_function_point.get_kind ();
+  }
+  const superedge *get_from_edge () const
+  {
+    return m_function_point.get_from_edge ();
+  }
+  unsigned get_stmt_idx () const
+  {
+    return m_function_point.get_stmt_idx ();
+  }
+
+  /* Get the number of frames we expect at this program point.
+     This will be one more than the length of the call_string
+     (which stores the parent callsites), apart from the origin
+     node, which doesn't have any frames.  */
+  int get_stack_depth () const
+  {
+    if (get_kind () == PK_ORIGIN)
+      return 0;
+    return m_call_string.length () + 1;
+  }
+
+  /* Factory functions for making various kinds of program_point.  */
+
+  static program_point from_function_entry (const supergraph &sg,
+					    function *fun)
+  {
+    return program_point (function_point::from_function_entry (sg, fun),
+			  call_string ());
+  }
+
+  static program_point before_supernode (const supernode *supernode,
+					 const superedge *from_edge,
+					 const call_string &call_string)
+  {
+    return program_point (function_point::before_supernode (supernode,
+							    from_edge),
+			  call_string);
+  }
+
+  static program_point before_stmt (const supernode *supernode,
+				    unsigned stmt_idx,
+				    const call_string &call_string)
+  {
+    return program_point (function_point::before_stmt (supernode, stmt_idx),
+			  call_string);
+  }
+
+  static program_point after_supernode (const supernode *supernode,
+					const call_string &call_string)
+  {
+    return program_point (function_point::after_supernode (supernode),
+			  call_string);
+  }
+
+  /* Support for hash_map.  */
+
+  static program_point empty ()
+  {
+    return program_point (function_point::empty (), call_string ());
+  }
+  static program_point deleted ()
+  {
+    return program_point (function_point::deleted (), call_string ());
+  }
+
+  bool on_edge (exploded_graph &eg, const superedge *succ);
+
+ private:
+  const function_point m_function_point;
+  call_string m_call_string;
+};
+
+#endif /* GCC_ANALYZER_PROGRAM_POINT_H */
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 15/49] Add ordered_hash_map
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (39 preceding siblings ...)
  2019-11-16  1:21 ` [PATCH 49/49] analyzer: test suite David Malcolm
@ 2019-11-16  1:21 ` David Malcolm
  2019-12-04 17:59   ` Martin Sebor
  2019-11-16  1:21 ` [PATCH 48/49] gdbinit.in: add break-on-saved-diagnostic David Malcolm
                   ` (9 subsequent siblings)
  50 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:21 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This patch adds an ordered_hash_map template, which is similar to
hash_map, but preserves insertion order.

gcc/ChangeLog:
	* Makefile.in (OBJS): Add ordered-hash-map-tests.o.
	* ordered-hash-map-tests.cc: New file.
	* ordered-hash-map.h: New file.
	* selftest-run-tests.c (selftest::run_tests): Call
	selftest::ordered_hash_map_tests_cc_tests.
	* selftest.h (selftest::ordered_hash_map_tests_cc_tests): New
	decl.
---
 gcc/Makefile.in               |   1 +
 gcc/ordered-hash-map-tests.cc | 247 ++++++++++++++++++++++++++++++++++++++++++
 gcc/ordered-hash-map.h        | 184 +++++++++++++++++++++++++++++++
 gcc/selftest-run-tests.c      |   1 +
 gcc/selftest.h                |   1 +
 5 files changed, 434 insertions(+)
 create mode 100644 gcc/ordered-hash-map-tests.cc
 create mode 100644 gcc/ordered-hash-map.h

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index c87b00f..5feef6a 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1443,6 +1443,7 @@ OBJS = \
 	optinfo-emit-json.o \
 	options-save.o \
 	opts-global.o \
+	ordered-hash-map-tests.o \
 	passes.o \
 	plugin.o \
 	postreload-gcse.o \
diff --git a/gcc/ordered-hash-map-tests.cc b/gcc/ordered-hash-map-tests.cc
new file mode 100644
index 0000000..266f60c
--- /dev/null
+++ b/gcc/ordered-hash-map-tests.cc
@@ -0,0 +1,247 @@
+/* Unit tests for ordered-hash-map.h.
+   Copyright (C) 2015-2019 Free Software Foundation, Inc.
+
+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 "tm.h"
+#include "opts.h"
+#include "hash-set.h"
+#include "fixed-value.h"
+#include "alias.h"
+#include "flags.h"
+#include "symtab.h"
+#include "tree-core.h"
+#include "stor-layout.h"
+#include "tree.h"
+#include "stringpool.h"
+#include "ordered-hash-map.h"
+#include "selftest.h"
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Populate *OUT_KVS with the key/value pairs of M.  */
+
+template <typename HashMap, typename Key, typename Value>
+static void
+get_kv_pairs (const HashMap &m,
+	      auto_vec<std::pair<Key, Value> > *out_kvs)
+{
+  for (typename HashMap::iterator iter = m.begin ();
+       iter != m.end ();
+       ++iter)
+    out_kvs->safe_push (std::make_pair ((*iter).first, (*iter).second));
+}
+
+/* Construct an ordered_hash_map <const char *, int> and verify that
+   various operations work correctly.  */
+
+static void
+test_map_of_strings_to_int ()
+{
+  ordered_hash_map <const char *, int> m;
+
+  const char *ostrich = "ostrich";
+  const char *elephant = "elephant";
+  const char *ant = "ant";
+  const char *spider = "spider";
+  const char *millipede = "Illacme plenipes";
+  const char *eric = "half a bee";
+
+  /* A fresh hash_map should be empty.  */
+  ASSERT_EQ (0, m.elements ());
+  ASSERT_EQ (NULL, m.get (ostrich));
+
+  /* Populate the hash_map.  */
+  ASSERT_EQ (false, m.put (ostrich, 2));
+  ASSERT_EQ (false, m.put (elephant, 4));
+  ASSERT_EQ (false, m.put (ant, 6));
+  ASSERT_EQ (false, m.put (spider, 8));
+  ASSERT_EQ (false, m.put (millipede, 750));
+  ASSERT_EQ (false, m.put (eric, 3));
+
+  /* Verify that we can recover the stored values.  */
+  ASSERT_EQ (6, m.elements ());
+  ASSERT_EQ (2, *m.get (ostrich));
+  ASSERT_EQ (4, *m.get (elephant));
+  ASSERT_EQ (6, *m.get (ant));
+  ASSERT_EQ (8, *m.get (spider));
+  ASSERT_EQ (750, *m.get (millipede));
+  ASSERT_EQ (3, *m.get (eric));
+
+  /* Verify that the order of insertion is preserved.  */
+  auto_vec<std::pair<const char *, int> > kvs;
+  get_kv_pairs (m, &kvs);
+  ASSERT_EQ (kvs.length (), 6);
+  ASSERT_EQ (kvs[0].first, ostrich);
+  ASSERT_EQ (kvs[0].second, 2);
+  ASSERT_EQ (kvs[1].first, elephant);
+  ASSERT_EQ (kvs[1].second, 4);
+  ASSERT_EQ (kvs[2].first, ant);
+  ASSERT_EQ (kvs[2].second, 6);
+  ASSERT_EQ (kvs[3].first, spider);
+  ASSERT_EQ (kvs[3].second, 8);
+  ASSERT_EQ (kvs[4].first, millipede);
+  ASSERT_EQ (kvs[4].second, 750);
+  ASSERT_EQ (kvs[5].first, eric);
+  ASSERT_EQ (kvs[5].second, 3);
+}
+
+/* Construct an ordered_hash_map using int_hash and verify that various
+   operations work correctly.  */
+
+static void
+test_map_of_int_to_strings ()
+{
+  const int EMPTY = -1;
+  const int DELETED = -2;
+  typedef int_hash <int, EMPTY, DELETED> int_hash_t;
+  ordered_hash_map <int_hash_t, const char *> m;
+
+  const char *ostrich = "ostrich";
+  const char *elephant = "elephant";
+  const char *ant = "ant";
+  const char *spider = "spider";
+  const char *millipede = "Illacme plenipes";
+  const char *eric = "half a bee";
+
+  /* A fresh hash_map should be empty.  */
+  ASSERT_EQ (0, m.elements ());
+  ASSERT_EQ (NULL, m.get (2));
+
+  /* Populate the hash_map.  */
+  ASSERT_EQ (false, m.put (2, ostrich));
+  ASSERT_EQ (false, m.put (4, elephant));
+  ASSERT_EQ (false, m.put (6, ant));
+  ASSERT_EQ (false, m.put (8, spider));
+  ASSERT_EQ (false, m.put (750, millipede));
+  ASSERT_EQ (false, m.put (3, eric));
+
+  /* Verify that we can recover the stored values.  */
+  ASSERT_EQ (6, m.elements ());
+  ASSERT_EQ (*m.get (2), ostrich);
+  ASSERT_EQ (*m.get (4), elephant);
+  ASSERT_EQ (*m.get (6), ant);
+  ASSERT_EQ (*m.get (8), spider);
+  ASSERT_EQ (*m.get (750), millipede);
+  ASSERT_EQ (*m.get (3), eric);
+
+  /* Verify that the order of insertion is preserved.  */
+  auto_vec<std::pair<int, const char *> > kvs;
+  get_kv_pairs (m, &kvs);
+  ASSERT_EQ (kvs.length (), 6);
+  ASSERT_EQ (kvs[0].first, 2);
+  ASSERT_EQ (kvs[0].second, ostrich);
+  ASSERT_EQ (kvs[1].first, 4);
+  ASSERT_EQ (kvs[1].second, elephant);
+  ASSERT_EQ (kvs[2].first, 6);
+  ASSERT_EQ (kvs[2].second, ant);
+  ASSERT_EQ (kvs[3].first, 8);
+  ASSERT_EQ (kvs[3].second, spider);
+  ASSERT_EQ (kvs[4].first, 750);
+  ASSERT_EQ (kvs[4].second, millipede);
+  ASSERT_EQ (kvs[5].first, 3);
+  ASSERT_EQ (kvs[5].second, eric);
+}
+
+/* Verify that we can remove items from an ordered_hash_map.  */
+
+static void
+test_removal ()
+{
+  ordered_hash_map <const char *, int> m;
+
+  const char *ostrich = "ostrich";
+  ASSERT_EQ (false, m.put (ostrich, 2));
+
+  ASSERT_EQ (1, m.elements ());
+  ASSERT_EQ (2, *m.get (ostrich));
+
+  {
+    auto_vec<std::pair<const char *, int> > kvs;
+    get_kv_pairs (m, &kvs);
+    ASSERT_EQ (kvs.length (), 1);
+    ASSERT_EQ (kvs[0].first, ostrich);
+    ASSERT_EQ (kvs[0].second, 2);
+  }
+
+  m.remove (ostrich);
+
+  ASSERT_EQ (0, m.elements ());
+  {
+    auto_vec<std::pair<const char *, int> > kvs;
+    get_kv_pairs (m, &kvs);
+    ASSERT_EQ (kvs.length (), 0);
+  }
+
+  /* Reinsertion (with a different value).  */
+  ASSERT_EQ (false, m.put (ostrich, 42));
+  ASSERT_EQ (1, m.elements ());
+  ASSERT_EQ (42, *m.get (ostrich));
+  {
+    auto_vec<std::pair<const char *, int> > kvs;
+    get_kv_pairs (m, &kvs);
+    ASSERT_EQ (kvs.length (), 1);
+    ASSERT_EQ (kvs[0].first, ostrich);
+    ASSERT_EQ (kvs[0].second, 42);
+  }
+}
+
+/* Verify that ordered_hash_map's copy-ctor works.  */
+
+static void
+test_copy_ctor ()
+{
+  ordered_hash_map <const char *, int> m;
+
+  const char *ostrich = "ostrich";
+  ASSERT_EQ (false, m.put (ostrich, 2));
+
+  ASSERT_EQ (1, m.elements ());
+  ASSERT_EQ (2, *m.get (ostrich));
+
+  ordered_hash_map <const char *, int> copy (m);
+  ASSERT_EQ (1, copy.elements ());
+  ASSERT_EQ (2, *copy.get (ostrich));
+
+  /* Remove from source.  */
+  m.remove (ostrich);
+  ASSERT_EQ (0, m.elements ());
+
+  /* Copy should be unaffected.  */
+  ASSERT_EQ (1, copy.elements ());
+  ASSERT_EQ (2, *copy.get (ostrich));
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+ordered_hash_map_tests_cc_tests ()
+{
+  test_map_of_strings_to_int ();
+  test_map_of_int_to_strings ();
+  test_removal ();
+  test_copy_ctor ();
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
diff --git a/gcc/ordered-hash-map.h b/gcc/ordered-hash-map.h
new file mode 100644
index 0000000..4e35cad
--- /dev/null
+++ b/gcc/ordered-hash-map.h
@@ -0,0 +1,184 @@
+/* A type-safe hash map that retains the insertion order of keys.
+   Copyright (C) 2019 Free Software Foundation, Inc.
+
+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/>.  */
+
+
+#ifndef GCC_ORDERED_HASH_MAP_H
+#define GCC_ORDERED_HASH_MAP_H
+
+/* Notes:
+   - The keys must be PODs, since vec<> uses assignment to populate slots
+     without properly initializing them.
+   - doesn't have GTY support.
+   - supports removal, but retains order of original insertion.
+     (Removal might be better handled by using a doubly-linked list
+     of nodes, holding the values).  */
+
+template<typename KeyId, typename Value,
+	 typename Traits>
+class ordered_hash_map
+{
+  typedef typename Traits::key_type Key;
+
+public:
+  ordered_hash_map () {}
+
+  ordered_hash_map (const ordered_hash_map &other)
+  : m_map (other.m_map),
+    m_keys (other.m_keys.length ()),
+    m_key_index (other.m_key_index)
+  {
+     unsigned i;
+     Key key;
+     FOR_EACH_VEC_ELT (other.m_keys, i, key)
+       m_keys.quick_push (key);
+  }
+
+  /* If key K isn't already in the map add key K with value V to the map, and
+     return false.  Otherwise set the value of the entry for key K to be V and
+     return true.  */
+
+  bool put (const Key &k, const Value &v)
+  {
+    bool existed = m_map.put (k, v);
+    if (!existed)
+      {
+        bool key_present;
+        int &slot = m_key_index.get_or_insert (k, &key_present);
+        if (!key_present)
+          {
+             slot = m_keys.length ();
+             m_keys.safe_push (k);
+          }
+      }
+    return existed;
+  }
+
+  /* If the passed in key is in the map return its value otherwise NULL.  */
+
+  Value *get (const Key &k)
+  {
+    return m_map.get (k);
+  }
+
+  /* Removing a key removes it from the map, but retains the insertion
+     order.  */
+
+  void remove (const Key &k)
+  {
+     m_map.remove (k);
+  }
+
+  size_t elements () const { return m_map.elements (); }
+
+  class iterator
+  {
+  public:
+    explicit iterator (const ordered_hash_map &map, unsigned idx) :
+      m_ordered_hash_map (map), m_idx (idx) {}
+
+    iterator &operator++ ()
+    {
+       /* Increment m_idx until we find a non-deleted element, or go beyond
+	  the end.  */
+       while (1)
+	 {
+	   ++m_idx;
+	   if (valid_index_p ())
+	     break;
+	}
+      return *this;
+    }
+
+    /* Can't use std::pair here, because GCC before 4.3 don't handle
+       std::pair where template parameters are references well.
+       See PR86739.  */
+    struct reference_pair {
+      const Key &first;
+      Value &second;
+
+      reference_pair (const Key &key, Value &value)
+      : first (key), second (value) {}
+
+      template <typename K, typename V>
+      operator std::pair<K, V> () const { return std::pair<K, V> (first, second); }
+    };
+
+    reference_pair operator* ()
+    {
+      const Key &k = m_ordered_hash_map.m_keys[m_idx];
+      Value *slot
+        = const_cast<ordered_hash_map &> (m_ordered_hash_map).get (k);
+      gcc_assert (slot);
+      return reference_pair (k, *slot);
+    }
+
+    bool
+    operator != (const iterator &other) const
+    {
+      return m_idx != other.m_idx;
+    }
+
+    /* Treat one-beyond-the-end as valid, for handling the "end" case.  */
+
+    bool valid_index_p () const
+    {
+      if (m_idx > m_ordered_hash_map.m_keys.length ())
+	return false;
+      if (m_idx == m_ordered_hash_map.m_keys.length ())
+	return true;
+      const Key &k = m_ordered_hash_map.m_keys[m_idx];
+      Value *slot
+	= const_cast<ordered_hash_map &> (m_ordered_hash_map).get (k);
+      return slot != NULL;
+    }
+
+    const ordered_hash_map &m_ordered_hash_map;
+    unsigned m_idx;
+  };
+
+  /* Standard iterator retrieval methods.  */
+
+  iterator begin () const
+  {
+    iterator i = iterator (*this, 0);
+    while (!i.valid_index_p () && i != end ())
+      ++i;
+    return i;
+  }
+  iterator end () const { return iterator (*this, m_keys.length ()); }
+
+private:
+  /* The underlying map.  */
+  hash_map<KeyId, Value, Traits> m_map;
+
+  /* The ordering of the keys.  */
+  auto_vec<Key> m_keys;
+
+  /* For each key that's ever been in the map, its index within m_keys.  */
+  hash_map<KeyId, int> m_key_index;
+};
+
+/* Two-argument form.  */
+
+template<typename Key, typename Value,
+	 typename Traits = simple_hashmap_traits<default_hash_traits<Key>,
+						 Value> >
+class ordered_hash_map;
+
+#endif /* GCC_ORDERED_HASH_MAP_H */
diff --git a/gcc/selftest-run-tests.c b/gcc/selftest-run-tests.c
index 92c8de7..f41244b 100644
--- a/gcc/selftest-run-tests.c
+++ b/gcc/selftest-run-tests.c
@@ -76,6 +76,7 @@ selftest::run_tests ()
   cgraph_c_tests ();
   optinfo_emit_json_cc_tests ();
   opt_problem_cc_tests ();
+  ordered_hash_map_tests_cc_tests ();
 
   /* Mid-level data structures.  */
   input_c_tests ();
diff --git a/gcc/selftest.h b/gcc/selftest.h
index c67b688..933d1cd 100644
--- a/gcc/selftest.h
+++ b/gcc/selftest.h
@@ -242,6 +242,7 @@ extern void input_c_tests ();
 extern void json_cc_tests ();
 extern void opt_problem_cc_tests ();
 extern void optinfo_emit_json_cc_tests ();
+extern void ordered_hash_map_tests_cc_tests ();
 extern void predict_c_tests ();
 extern void pretty_print_c_tests ();
 extern void read_rtl_function_c_tests ();
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 47/49] analyzer: new files: diagnostic-manager.{cc|h}
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (33 preceding siblings ...)
  2019-11-16  1:21 ` [PATCH 44/49] analyzer: new files: state-purge.{cc|h} David Malcolm
@ 2019-11-16  1:21 ` David Malcolm
  2019-12-11 20:42   ` Jeff Law
  2019-11-16  1:21 ` [PATCH 46/49] analyzer: new files: checker-path.{cc|h} David Malcolm
                   ` (15 subsequent siblings)
  50 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:21 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This patch adds diagnostic_manager and related support classes for
saving, deduplicating, and emitting analyzer diagnostics.

gcc/ChangeLog:
	* analyzer/diagnostic-manager.cc: New file.
	* analyzer/diagnostic-manager.h: New file.
---
 gcc/analyzer/diagnostic-manager.cc | 1117 ++++++++++++++++++++++++++++++++++++
 gcc/analyzer/diagnostic-manager.h  |  116 ++++
 2 files changed, 1233 insertions(+)
 create mode 100644 gcc/analyzer/diagnostic-manager.cc
 create mode 100644 gcc/analyzer/diagnostic-manager.h

diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc
new file mode 100644
index 0000000..fae38c7
--- /dev/null
+++ b/gcc/analyzer/diagnostic-manager.cc
@@ -0,0 +1,1117 @@
+/* Classes for saving, deduplicating, and emitting analyzer diagnostics.
+   Copyright (C) 2019 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 "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "pretty-print.h"
+#include "gcc-rich-location.h"
+#include "gimple-pretty-print.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/diagnostic-manager.h"
+#include "analyzer/exploded-graph.h"
+#include "analyzer/checker-path.h"
+
+/* class saved_diagnostic.  */
+
+/* saved_diagnostic's ctor.
+   Take ownership of D and STMT_FINDER.  */
+
+saved_diagnostic::saved_diagnostic (const state_machine *sm,
+				    const exploded_node *enode,
+				    const supernode *snode, const gimple *stmt,
+				    stmt_finder *stmt_finder,
+				    tree var, state_machine::state_t state,
+				    pending_diagnostic *d)
+: m_sm (sm), m_enode (enode), m_snode (snode), m_stmt (stmt),
+ /* stmt_finder could be on-stack; we want our own copy that can
+    outlive that.  */
+  m_stmt_finder (stmt_finder ? stmt_finder->clone () : NULL),
+  m_var (var), m_state (state),
+  m_d (d)
+{
+  gcc_assert (m_stmt || m_stmt_finder);
+
+  /* We must have an enode in order to be able to look for paths
+     through the exploded_graph to this diagnostic.  */
+  gcc_assert (m_enode);
+}
+
+/* saved_diagnostic's dtor.  */
+
+saved_diagnostic::~saved_diagnostic ()
+{
+  delete m_stmt_finder;
+  delete m_d;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class diagnostic_manager.  */
+
+/* diagnostic_manager's ctor.  */
+
+diagnostic_manager::diagnostic_manager (logger *logger, int verbosity)
+: log_user (logger), m_verbosity (verbosity)
+{
+}
+
+/* Queue pending_diagnostic D at ENODE for later emission.  */
+
+void
+diagnostic_manager::add_diagnostic (const state_machine *sm,
+				    const exploded_node *enode,
+				    const supernode *snode, const gimple *stmt,
+				    stmt_finder *finder,
+				    tree var, state_machine::state_t state,
+				    pending_diagnostic *d)
+{
+  LOG_FUNC (get_logger ());
+
+  /* We must have an enode in order to be able to look for paths
+     through the exploded_graph to the diagnostic.  */
+  gcc_assert (enode);
+
+  saved_diagnostic *sd
+    = new saved_diagnostic (sm, enode, snode, stmt, finder, var, state, d);
+  m_saved_diagnostics.safe_push (sd);
+  if (get_logger ())
+    log ("adding saved diagnostic %i at SN %i: %qs",
+	 m_saved_diagnostics.length () - 1,
+	 snode->m_index, d->get_kind ());
+}
+
+/* Queue pending_diagnostic D at ENODE for later emission.  */
+
+void
+diagnostic_manager::add_diagnostic (const exploded_node *enode,
+				    const supernode *snode, const gimple *stmt,
+				    stmt_finder *finder,
+				    pending_diagnostic *d)
+{
+  gcc_assert (enode);
+  add_diagnostic (NULL, enode, snode, stmt, finder, NULL_TREE, 0, d);
+}
+
+/* A class for identifying sets of duplicated pending_diagnostic.
+
+   We want to find the simplest dedupe_candidate amongst those that share a
+   dedupe_key.  */
+
+class dedupe_key
+{
+public:
+  dedupe_key (const saved_diagnostic &sd,
+	      const exploded_path &epath)
+  : m_sd (sd), m_stmt (sd.m_stmt)
+  {
+    /* Support deferring the choice of stmt until after an emission path has
+     been built, using an optional stmt_finder.  */
+    if (m_stmt == NULL)
+      {
+	gcc_assert (sd.m_stmt_finder);
+	m_stmt = sd.m_stmt_finder->find_stmt (epath);
+      }
+    gcc_assert (m_stmt);
+  }
+
+  hashval_t hash () const
+  {
+    inchash::hash hstate;
+    hstate.add_ptr (m_stmt);
+    // TODO: m_sd
+    return hstate.end ();
+  }
+  bool operator== (const dedupe_key &other) const
+  {
+    return (m_sd == other.m_sd
+	    && m_stmt == other.m_stmt);
+  }
+
+  location_t get_location () const
+  {
+    return m_stmt->location;
+  }
+
+  /* A qsort comparator for use by dedupe_winners::emit_best
+     to sort them into location_t order.  */
+
+  static int
+  comparator (const void *p1, const void *p2)
+  {
+    const dedupe_key *pk1 = *(const dedupe_key * const *)p1;
+    const dedupe_key *pk2 = *(const dedupe_key * const *)p2;
+
+    location_t loc1 = pk1->get_location ();
+    location_t loc2 = pk2->get_location ();
+
+    return linemap_compare_locations (line_table, loc2, loc1);
+  }
+
+  const saved_diagnostic &m_sd;
+  const gimple *m_stmt;
+};
+
+/* The value of a slot for a dedupe_key within dedupe_winners:
+   the exploded_path for the best candidate for that key, and the
+   number of duplicates seen so far.  */
+
+class dedupe_candidate
+{
+public:
+  // has the exploded_path
+  dedupe_candidate (const shortest_exploded_paths &sp,
+		    const saved_diagnostic &sd)
+  : m_epath (sp.get_shortest_path (sd.m_enode)),
+    m_num_dupes (0)
+  {
+  }
+
+  unsigned length () const { return m_epath.length (); }
+  const exploded_path &get_path () const { return m_epath; }
+
+  void add_duplicate () { m_num_dupes++; }
+  int get_num_dupes () const { return m_num_dupes; }
+
+private:
+  exploded_path m_epath;
+public:
+  int m_num_dupes;
+};
+
+/* Traits for use by dedupe_winners.  */
+
+class dedupe_hash_map_traits
+{
+public:
+  typedef const dedupe_key *key_type;
+  typedef dedupe_candidate *value_type;
+  typedef dedupe_candidate *compare_type;
+
+  static inline hashval_t hash (const key_type &v)
+  {
+    return v->hash ();
+  }
+  static inline bool equal_keys (const key_type &k1, const key_type &k2)
+  {
+    return *k1 == *k2;
+  }
+  template <typename T>
+  static inline void remove (T &)
+  {
+    // TODO
+  }
+  template <typename T>
+  static inline void mark_deleted (T &entry)
+  {
+    entry.m_key = reinterpret_cast<key_type> (1);
+  }
+  template <typename T>
+  static inline void mark_empty (T &entry)
+  {
+    entry.m_key = NULL;
+  }
+  template <typename T>
+  static inline bool is_deleted (const T &entry)
+  {
+    return entry.m_key == reinterpret_cast<key_type> (1);
+  }
+  template <typename T>
+  static inline bool is_empty (const T &entry)
+  {
+    return entry.m_key == NULL;
+  }
+
+};
+
+/* A class for deduplicating diagnostics and finding (and emitting) the
+   best diagnostic within each partition.  */
+
+class dedupe_winners
+{
+public:
+  ~dedupe_winners ()
+  {
+    /* Delete all keys and candidates.  */
+    for (map_t::iterator iter = m_map.begin ();
+	 iter != m_map.end ();
+	 ++iter)
+      {
+	delete (*iter).first;
+	delete (*iter).second;
+      }
+  }
+
+  /* Determine an exploded_path for SD using SP and, if it's feasible,
+     determine if it's the best seen so far for its dedupe_key.
+     Retain the winner for each dedupe_key, and discard the rest.  */
+
+  void add (logger *logger,
+	    const shortest_exploded_paths &sp,
+	    const saved_diagnostic &sd)
+  {
+    /* Build a dedupe_candidate for SD.
+       This uses SP to build an exploded_path.  */
+    dedupe_candidate *dc = new dedupe_candidate (sp, sd);
+
+    /* Verify that the epath is feasible.
+       State-merging means that not every path in the epath corresponds
+       to a feasible one w.r.t. states.
+       Here we simply check each duplicate saved_diagnostic's
+       shortest_path, and reject any that aren't feasible.
+       This could introduce false negatives, as there could be longer
+       feasible paths within the egraph.  */
+    if (logger)
+      logger->log ("considering %qs at SN: %i",
+		   sd.m_d->get_kind (), sd.m_snode->m_index);
+    if (!dc->get_path ().feasible_p (logger))
+      {
+	if (logger)
+	  logger->log ("rejecting %qs at SN: %i"
+		       " due to infeasible path",
+		       sd.m_d->get_kind (), sd.m_snode->m_index);
+	delete dc;
+	return;
+      }
+    else
+      if (logger)
+	logger->log ("accepting %qs at SN: %i with feasible path",
+		     sd.m_d->get_kind (), sd.m_snode->m_index);
+
+    dedupe_key *key = new dedupe_key (sd, dc->get_path ());
+    if (dedupe_candidate **slot = m_map.get (key))
+      {
+	(*slot)->add_duplicate ();
+
+	if (dc->length () < (*slot)->length ())
+	  {
+	    /* We've got a shorter path for the key; replace
+	       the current candidate.  */
+	    dc->m_num_dupes = (*slot)->get_num_dupes ();
+	    delete *slot;
+	    *slot = dc;
+	  }
+	else
+	  /* We haven't beaten the current best candidate;
+	     drop the new candidate.  */
+	  delete dc;
+	delete key;
+      }
+    else
+      /* This is the first candidate for this key.  */
+      m_map.put (key, dc);
+  }
+
+ /* Emit the simplest diagnostic within each set.  */
+
+  void emit_best (diagnostic_manager *dm,
+		  const exploded_graph &eg)
+  {
+    LOG_SCOPE (dm->get_logger ());
+
+    /* Get keys into a vec for sorting.  */
+    auto_vec<const dedupe_key *> keys (m_map.elements ());
+    for (map_t::iterator iter = m_map.begin ();
+	 iter != m_map.end ();
+	 ++iter)
+      keys.quick_push ((*iter).first);
+
+    dm->log ("# keys after de-duplication: %i", keys.length ());
+
+    /* Sort into a good emission order.  */
+    keys.qsort (dedupe_key::comparator);
+
+    /* Emit the best candidate for each key.  */
+    int i;
+    const dedupe_key *key;
+    FOR_EACH_VEC_ELT (keys, i, key)
+      {
+	dedupe_candidate **slot = m_map.get (key);
+	gcc_assert (*slot);
+	const dedupe_candidate &dc = **slot;
+
+	dm->emit_saved_diagnostic (eg, key->m_sd,
+				   dc.get_path (), key->m_stmt,
+				   dc.get_num_dupes ());
+      }
+  }
+
+private:
+
+  /* This maps from each dedupe_key to a current best dedupe_candidate.  */
+
+  typedef hash_map<const dedupe_key *, dedupe_candidate *,
+		   dedupe_hash_map_traits> map_t;
+  map_t m_map;
+};
+
+/* Emit all saved diagnostics.  */
+
+void
+diagnostic_manager::emit_saved_diagnostics (const exploded_graph &eg)
+{
+  LOG_SCOPE (get_logger ());
+  auto_client_timevar tv ("emit saved diagnostics");
+  log ("# saved diagnostics: %i", m_saved_diagnostics.length ());
+
+  if (m_saved_diagnostics.length () == 0)
+    return;
+
+  /* Compute the shortest_paths once, sharing it between all diagnostics.  */
+  shortest_exploded_paths sp (eg, eg.get_origin ());
+
+  /* Iterate through all saved diagnostics, adding them to a dedupe_winners
+     instance.  This partitions the saved diagnostics by dedupe_key,
+     generating exploded_paths for them, and retaining the best one in each
+     partition.  */
+  dedupe_winners best_candidates;
+
+  int i;
+  saved_diagnostic *sd;
+  FOR_EACH_VEC_ELT (m_saved_diagnostics, i, sd)
+    best_candidates.add (get_logger (), sp, *sd);
+
+  /* For each dedupe-key, call emit_saved_diagnostic on the "best"
+     saved_diagnostic.  */
+  best_candidates.emit_best (this, eg);
+}
+
+/* Given a saved_diagnostic SD at STMT with feasible path EPATH through EG,
+   create an checker_path of suitable events and use it to call
+   SD's underlying pending_diagnostic "emit" vfunc to emit a diagnostic.  */
+
+void
+diagnostic_manager::emit_saved_diagnostic (const exploded_graph &eg,
+					   const saved_diagnostic &sd,
+					   const exploded_path &epath,
+					   const gimple *stmt,
+					   int num_dupes)
+{
+  LOG_SCOPE (get_logger ());
+  log ("sd: %qs at SN: %i", sd.m_d->get_kind (), sd.m_snode->m_index);
+  log ("num dupes: %i", num_dupes);
+
+  pretty_printer *pp = global_dc->printer->clone ();
+
+  checker_path emission_path;
+
+  /* Populate emission_path with a full description of EPATH.  */
+  build_emission_path (eg, epath, &emission_path);
+
+  /* Now prune it to just cover the most pertinent events.  */
+  prune_path (&emission_path, sd.m_sm, sd.m_var, sd.m_state);
+
+  /* Add a final event to the path, covering the diagnostic itself.
+     We use the final enode from the epath, which might be different from
+     the sd.m_enode, as the dedupe code doesn't care about enodes, just
+     snodes.  */
+  emission_path.add_final_event (sd.m_sm, epath.get_final_enode (), stmt,
+				 sd.m_var, sd.m_state);
+
+  emission_path.prepare_for_emission (sd.m_d);
+
+  gcc_rich_location rich_loc (stmt->location);
+  rich_loc.set_path (&emission_path);
+
+  auto_diagnostic_group d;
+  auto_cfun sentinel (sd.m_snode->m_fun);
+  if (sd.m_d->emit (&rich_loc))
+    {
+      if (num_dupes > 0)
+	inform_n (stmt->location, num_dupes,
+		  "%i duplicate", "%i duplicates",
+		  num_dupes);
+    }
+  delete pp;
+}
+
+/* Given a state change to DST_REP, determine a tree that gives the origin
+   of that state at STMT, using DST_STATE's region model, so that state
+   changes based on assignments can be tracked back to their origins.
+
+   For example, if we have
+
+     (S1) _1 = malloc (64);
+     (S2) EXPR = _1;
+
+   then at stmt S2 we can get the origin of EXPR's state as being _1,
+   and thus track the allocation back to S1.  */
+
+static tree
+get_any_origin (const gimple *stmt,
+		tree dst_rep,
+		const program_state &dst_state)
+{
+  if (!stmt)
+    return NULL_TREE;
+
+  if (const gassign *assign = dyn_cast <const gassign *> (stmt))
+    {
+      tree lhs = gimple_assign_lhs (assign);
+      /* Use region IDs to compare lhs with DST_REP.  */
+      if (dst_state.m_region_model->get_lvalue (lhs, NULL)
+	  == dst_state.m_region_model->get_lvalue (dst_rep, NULL))
+	{
+	  tree rhs1 = gimple_assign_rhs1 (assign);
+	  enum tree_code op = gimple_assign_rhs_code (assign);
+	  switch (op)
+	    {
+	    default:
+	      //gcc_unreachable ();  // TODO
+	      break;
+	    case COMPONENT_REF:
+	    case SSA_NAME:
+	      return rhs1;
+	    }
+	}
+    }
+  return NULL_TREE;
+}
+
+/* Emit a "path" of events to EMISSION_PATH describing the exploded path
+   EPATH within EG.  */
+
+void
+diagnostic_manager::build_emission_path (const exploded_graph &eg,
+					 const exploded_path &epath,
+					 checker_path *emission_path) const
+{
+  LOG_SCOPE (get_logger ());
+  const extrinsic_state &ext_state = eg.get_ext_state ();
+  for (unsigned i = 0; i < epath.m_edges.length (); i++)
+    {
+      const exploded_edge *eedge = epath.m_edges[i];
+      add_events_for_eedge (*eedge, ext_state, emission_path);
+    }
+}
+
+/* Subclass of state_change_visitor that creates state_change_event
+   instances.  */
+
+class state_change_event_creator : public state_change_visitor
+{
+public:
+  state_change_event_creator (const exploded_edge &eedge,
+			      checker_path *emission_path)
+    : m_eedge (eedge),
+      m_emission_path (emission_path)
+  {}
+
+  bool on_state_change (const state_machine &sm,
+			state_machine::state_t src_sm_val,
+			state_machine::state_t dst_sm_val,
+			tree dst_rep,
+			svalue_id dst_origin_sid) FINAL OVERRIDE
+  {
+    const exploded_node *src_node = m_eedge.m_src;
+    const program_point &src_point = src_node->get_point ();
+    const int src_stack_depth = src_point.get_stack_depth ();
+    const exploded_node *dst_node = m_eedge.m_dest;
+    const gimple *stmt = src_point.get_stmt ();
+    const supernode *supernode = src_point.get_supernode ();
+    const program_state &dst_state = dst_node->get_state ();
+
+    int stack_depth = src_stack_depth;
+
+    if (m_eedge.m_sedge
+	&& m_eedge.m_sedge->m_kind == SUPEREDGE_CFG_EDGE)
+      {
+	supernode = src_point.get_supernode ();
+	stmt = supernode->get_last_stmt ();
+	stack_depth = src_stack_depth;
+      }
+
+    /* Bulletproofing for state changes at calls/returns;
+       TODO: is there a better way? */
+    if (!stmt)
+      return false;
+
+    tree origin_rep
+      = dst_state.get_representative_tree (dst_origin_sid);
+
+    if (origin_rep == NULL_TREE)
+      origin_rep = get_any_origin (stmt, dst_rep, dst_state);
+    m_emission_path->add_event (new state_change_event (supernode,
+							stmt,
+							stack_depth,
+							sm,
+							dst_rep,
+							src_sm_val,
+							dst_sm_val,
+							origin_rep,
+							dst_state));
+    return false;
+  }
+
+  const exploded_edge &m_eedge;
+  checker_path *m_emission_path;
+};
+
+/* Compare SRC_STATE and DST_STATE (which use EXT_STATE), and call
+   VISITOR's on_state_change for every sm-state change that occurs
+   to a tree.
+
+   This determines the state changes that ought to be reported to
+   the user: a combination of the effects of changes to sm_state_map
+   (which maps svalues to sm-states), and of region_model changes
+   (which map trees to svalues).
+
+   Bail out early and return true if any call to on_state_change returns
+   true, otherwise return false.
+
+   This is split out to make it easier to experiment with changes to
+   exploded_node granulairy (so that we can observe what state changes
+   lead to state_change_events being emitted).  */
+
+bool
+for_each_state_change (const program_state &src_state,
+		       const program_state &dst_state,
+		       const extrinsic_state &ext_state,
+		       state_change_visitor *visitor)
+{
+  gcc_assert (src_state.m_checker_states.length ()
+	      == ext_state.m_checkers.length ());
+  gcc_assert (dst_state.m_checker_states.length ()
+	      == ext_state.m_checkers.length ());
+  for (unsigned i = 0; i < ext_state.m_checkers.length (); i++)
+    {
+      const state_machine &sm = ext_state.get_sm (i);
+      const sm_state_map &src_smap = *src_state.m_checker_states[i];
+      const sm_state_map &dst_smap = *dst_state.m_checker_states[i];
+      for (sm_state_map::iterator_t iter = dst_smap.begin ();
+	   iter != dst_smap.end ();
+	   ++iter)
+	{
+	  /* Ideally we'd directly compare the SM state between src state
+	     and dst state, but there's no guarantee that the IDs can
+	     be meaningfully compared.  */
+	  svalue_id dst_sid = (*iter).first;
+	  state_machine::state_t dst_sm_val = (*iter).second.m_state;
+
+	  auto_vec<path_var> dst_pvs;
+	  dst_state.m_region_model->get_path_vars_for_svalue (dst_sid,
+							      &dst_pvs);
+
+	  unsigned j;
+	  path_var *dst_pv;
+	  FOR_EACH_VEC_ELT (dst_pvs, j, dst_pv)
+	    {
+	      tree dst_rep = dst_pv->m_tree;
+	      gcc_assert (dst_rep);
+	      if (dst_pv->m_stack_depth
+		  >= src_state.m_region_model->get_stack_depth ())
+		continue;
+	      svalue_id src_sid
+		= src_state.m_region_model->get_rvalue (*dst_pv, NULL);
+	      if (src_sid.null_p ())
+		continue;
+	      state_machine::state_t src_sm_val = src_smap.get_state (src_sid);
+	      if (dst_sm_val != src_sm_val)
+		{
+		  svalue_id dst_origin_sid = (*iter).second.m_origin;
+		  if (visitor->on_state_change (sm, src_sm_val, dst_sm_val,
+						dst_rep, dst_origin_sid))
+		    return true;
+		}
+	    }
+	}
+    }
+  return false;
+}
+
+/* Subroutine of diagnostic_manager::build_emission_path.
+   Add any events for EEDGE to EMISSION_PATH.  */
+
+void
+diagnostic_manager::add_events_for_eedge (const exploded_edge &eedge,
+					  const extrinsic_state &ext_state,
+					  checker_path *emission_path) const
+{
+  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 exploded_node *dst_node = eedge.m_dest;
+  const program_point &dst_point = dst_node->get_point ();
+  const int dst_stack_depth = dst_point.get_stack_depth ();
+  if (get_logger ())
+    {
+      get_logger ()->start_log_line ();
+      pretty_printer *pp = get_logger ()->get_printer ();
+      pp_printf (pp, "EN %i -> EN %i: ",
+		 eedge.m_src->m_index,
+		 eedge.m_dest->m_index);
+      src_point.print (pp, format (false));
+      pp_string (pp, "-> ");
+      dst_point.print (pp, format (false));
+      get_logger ()->end_log_line ();
+    }
+  const program_state &src_state = src_node->get_state ();
+  const program_state &dst_state = dst_node->get_state ();
+
+  /* Add state change events for the states that have changed.
+     We add these before events for superedges, so that if we have a
+     state_change_event due to following an edge, we'll get this sequence
+     of events:
+
+      | if (!ptr)
+      |    ~
+      |    |
+      |    (1) assuming 'ptr' is non-NULL  (state_change_event)
+      |    (2) following 'false' branch... (start_cfg_edge_event)
+     ...
+      | do_something (ptr);
+      | ~~~~~~~~~~~~~^~~~~
+      |              |
+      |              (3) ...to here        (end_cfg_edge_event).  */
+  state_change_event_creator visitor (eedge, emission_path);
+  for_each_state_change (src_state, dst_state, ext_state,
+			 &visitor);
+
+  /* Add events for rewinding from a longjmp to a setjmp.  */
+  if (eedge.m_rewind_info)
+    {
+      emission_path->add_event
+	(new rewind_from_longjmp_event
+	 (&eedge, src_point.get_supernode ()->get_end_location (),
+	  src_point.get_fndecl (),
+	  src_stack_depth));
+      emission_path->add_event
+	(new rewind_to_setjmp_event
+	 (&eedge, eedge.m_rewind_info->get_setjmp_call ()->location,
+	  dst_point.get_fndecl (),
+	  dst_stack_depth));
+    }
+
+  /* Add events for superedges, function entries, and for statements.  */
+  switch (dst_point.get_kind ())
+    {
+    default:
+      break;
+    case PK_BEFORE_SUPERNODE:
+      if (src_point.get_kind () == PK_AFTER_SUPERNODE)
+	{
+	  if (eedge.m_sedge)
+	    add_events_for_superedge (eedge, emission_path);
+	}
+      /* Add function entry events.  */
+      if (dst_point.get_supernode ()->entry_p ())
+	{
+	  emission_path->add_event
+	    (new function_entry_event
+	     (dst_point.get_supernode ()->get_start_location (),
+	      dst_point.get_fndecl (),
+	      dst_stack_depth));
+	}
+      break;
+    case PK_BEFORE_STMT:
+      {
+	const gimple *stmt = dst_point.get_stmt ();
+	if (is_setjmp_call_p (stmt))
+	  emission_path->add_event
+	    (new setjmp_event (stmt->location,
+			       dst_node,
+			       dst_point.get_fndecl (),
+			       dst_stack_depth));
+	else
+	  emission_path->add_event
+	    (new statement_event (stmt,
+				  dst_point.get_fndecl (),
+				  dst_stack_depth, dst_state));
+      }
+      break;
+    }
+}
+
+/* Subroutine of diagnostic_manager::add_events_for_eedge
+   where EEDGE has an underlying superedge i.e. a CFG edge,
+   or an interprocedural call/return.
+   Add any events for the superedge to EMISSION_PATH.  */
+
+void
+diagnostic_manager::add_events_for_superedge (const exploded_edge &eedge,
+					      checker_path *emission_path)
+  const
+{
+  gcc_assert (eedge.m_sedge);
+
+  const exploded_node *src_node = eedge.m_src;
+  const program_point &src_point = src_node->get_point ();
+  const exploded_node *dst_node = eedge.m_dest;
+  const program_point &dst_point = dst_node->get_point ();
+  const int src_stack_depth = src_point.get_stack_depth ();
+  const int dst_stack_depth = dst_point.get_stack_depth ();
+  const gimple *last_stmt = src_point.get_supernode ()->get_last_stmt ();
+
+  switch (eedge.m_sedge->m_kind)
+    {
+    case SUPEREDGE_CFG_EDGE:
+      {
+	emission_path->add_event
+	  (new start_cfg_edge_event (eedge,
+			       (last_stmt
+				? last_stmt->location
+				: UNKNOWN_LOCATION),
+			       src_point.get_fndecl (),
+			       src_stack_depth));
+	emission_path->add_event
+	  (new end_cfg_edge_event (eedge,
+				   dst_point.get_supernode ()->get_start_location (),
+				   dst_point.get_fndecl (),
+				   dst_stack_depth));
+      }
+      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));
+      }
+      break;
+
+    case SUPEREDGE_INTRAPROCEDURAL_CALL:
+      {
+	/* TODO: add a subclass for this, or generate events for the
+	   summary.  */
+	emission_path->add_event
+	  (new debug_event ((last_stmt
+			     ? last_stmt->location
+			     : UNKNOWN_LOCATION),
+			    src_point.get_fndecl (),
+			    src_stack_depth,
+			    "call summary"));
+      }
+      break;
+
+    case SUPEREDGE_RETURN:
+      {
+	const return_superedge *return_edge
+	  = as_a <const return_superedge *> (eedge.m_sedge);
+
+	const gcall *call_stmt = return_edge->get_call_stmt ();
+	emission_path->add_event
+	  (new return_event (eedge,
+			     (call_stmt
+			      ? call_stmt->location
+			      : UNKNOWN_LOCATION),
+			     dst_point.get_fndecl (),
+			     dst_stack_depth));
+      }
+      break;
+    }
+}
+
+/* Prune PATH, based on the verbosity level, to the most pertinent
+   events for a diagnostic that involves VAR ending in state STATE
+   (for state machine SM).
+
+   PATH is updated in place, and the redundant checker_events are deleted.
+
+   As well as deleting events, call record_critical_state on events in
+   which state critical to the pending_diagnostic is being handled, so
+   that the event's get_desc vfunc can potentially supply a more precise
+   description of the event to the user.
+   e.g. improving
+     "calling 'foo' from 'bar'"
+   to
+     "passing possibly-NULL pointer 'ptr' to 'foo' from 'bar' as param 1"
+   when the diagnostic relates to later dereferencing 'ptr'.  */
+
+void
+diagnostic_manager::prune_path (checker_path *path,
+				const state_machine *sm,
+				tree var,
+				state_machine::state_t state) const
+{
+  LOG_FUNC (get_logger ());
+  path->maybe_log (get_logger (), "path");
+
+  /* First pass: apply verbosity level, pruning unrelated state
+     change events.
+     Iterate backwards through PATH, skipping state change events that aren't
+     VAR but update the pertinent VAR when state-copying occurs.  */
+  int idx = path->m_events.length () - 1;
+  while (idx >= 0 && idx < (signed)path->m_events.length ())
+    {
+      checker_event *base_event = path->m_events[idx];
+      if (get_logger ())
+	{
+	  if (sm)
+	    log ("considering event %i, with var: %qE, state: %qs",
+		 idx, var, sm->get_state_name (state));
+	  else
+	    log ("considering event %i", idx);
+	}
+      switch (base_event->m_kind)
+	{
+	default:
+	  gcc_unreachable ();
+
+	case EK_DEBUG:
+	  if (m_verbosity < 3)
+	    {
+	      log ("filtering event %i: debug event", idx);
+	      path->delete_event (idx);
+	    }
+	  break;
+
+	case EK_STMT:
+	  {
+	    /* If this stmt is the origin of "var", update var.  */
+	    statement_event *stmt_event = (statement_event *)base_event;
+	    tree new_var = get_any_origin (stmt_event->m_stmt, var,
+					   stmt_event->m_dst_state);
+	    if (new_var)
+	      {
+		log ("event %i: switching var of interest from %qE to %qE",
+		     idx, var, new_var);
+		var = new_var;
+	      }
+	    if (m_verbosity < 3)
+	      {
+		log ("filtering event %i: statement event", idx);
+		path->delete_event (idx);
+	      }
+	  }
+	  break;
+
+	case EK_FUNCTION_ENTRY:
+	  if (m_verbosity < 1)
+	    {
+	      log ("filtering event %i: function entry", idx);
+	      path->delete_event (idx);
+	    }
+	  break;
+
+	case EK_STATE_CHANGE:
+	  {
+	    state_change_event *state_change = (state_change_event *)base_event;
+	    if (state_change->get_lvalue (state_change->m_var)
+		== state_change->get_lvalue (var))
+	      {
+		if (state_change->m_origin)
+		  {
+		    log ("event %i: switching var of interest from %qE to %qE",
+			 idx, var, state_change->m_origin);
+		    var = state_change->m_origin;
+		  }
+		log ("event %i: switching state of interest from %qs to %qs",
+		     idx, sm->get_state_name (state_change->m_to),
+		     sm->get_state_name (state_change->m_from));
+		state = state_change->m_from;
+	      }
+	    else if (m_verbosity < 3)
+	      {
+		if (var)
+		  log ("filtering event %i:"
+		       " state change to %qE unrelated to %qE",
+		       idx, state_change->m_var, var);
+		else
+		  log ("filtering event %i: state change to %qE",
+		       idx, state_change->m_var);
+		path->delete_event (idx);
+	      }
+	  }
+	  break;
+
+	case EK_START_CFG_EDGE:
+	  {
+	    cfg_edge_event *event = (cfg_edge_event *)base_event;
+	    const cfg_superedge& cfg_superedge
+	      = event->get_cfg_superedge ();
+	    const supernode *dest = event->m_sedge->m_dest;
+	    /* Do we have an SSA_NAME defined via a phi node in
+	       the dest CFG node?  */
+	    if (var && TREE_CODE (var) == SSA_NAME)
+	      if (SSA_NAME_DEF_STMT (var)->bb == dest->m_bb)
+		{
+		  if (gphi *phi
+		      = dyn_cast <gphi *> (SSA_NAME_DEF_STMT (var)))
+		    {
+		      /* Update var based on its phi node.  */
+		      tree old_var = var;
+		      var = cfg_superedge.get_phi_arg (phi);
+		      log ("updating from %qE to %qE based on phi node",
+			   old_var, var);
+		      if (get_logger ())
+			{
+			  pretty_printer pp;
+			  pp_gimple_stmt_1 (&pp, phi, 0, (dump_flags_t)0);
+			  log ("  phi: %s", pp_formatted_text (&pp));
+			}
+		    }
+		}
+
+	    /* TODO: is this edge significant to var?
+	       See if var can be in other states in the dest, but not
+	       in other states in the src?
+	       Must have multiple sibling edges.  */
+
+	    if (event->should_filter_p (m_verbosity))
+	      {
+		log ("filtering event %i: CFG edge", idx);
+		path->delete_event (idx);
+		/* Also delete the corresponding EK_END_CFG_EDGE.  */
+		gcc_assert (path->m_events[idx]->m_kind == EK_END_CFG_EDGE);
+		path->delete_event (idx);
+	      }
+	  }
+	  break;
+
+	case EK_END_CFG_EDGE:
+	  /* These come in pairs with EK_START_CFG_EDGE events and are
+	     filtered when their start event is filtered.  */
+	  break;
+
+	case EK_CALL_EDGE:
+	  {
+	    call_event *event = (call_event *)base_event;
+	    const callgraph_superedge& cg_superedge
+	      = event->get_callgraph_superedge ();
+	    callsite_expr expr;
+	    tree caller_var
+	      = cg_superedge.map_expr_from_callee_to_caller (var, &expr);
+	    if (caller_var)
+	      {
+		log ("event %i:"
+		     " switching var of interest"
+		     " from %qE in callee to %qE in caller",
+		     idx, var, caller_var);
+		var = caller_var;
+		if (expr.param_p ())
+		  event->record_critical_state (var, state);
+	      }
+	  }
+	  break;
+
+	case EK_RETURN_EDGE:
+	  // TODO: potentially update var/state based on return value,
+	  // args etc
+	  {
+	    return_event *event = (return_event *)base_event;
+	    const callgraph_superedge& cg_superedge
+	      = event->get_callgraph_superedge ();
+	    callsite_expr expr;
+	    tree callee_var
+	      = cg_superedge.map_expr_from_caller_to_callee (var, &expr);
+	    if (callee_var)
+	      {
+		if (var)
+		  log ("event %i:"
+		       " switching var of interest"
+		       " from %qE in caller to %qE in callee",
+		       idx, var, callee_var);
+		else
+		  // TODO: how does this happen?
+		  log ("event %i:"
+		       " switching var of interest"
+		       " from NULL in caller to %qE in callee",
+		       idx, callee_var);
+		var = callee_var;
+		if (expr.return_value_p ())
+		  event->record_critical_state (var, state);
+	      }
+	  }
+	  break;
+
+	case EK_SETJMP:
+	  /* TODO: only show setjmp_events that matter i.e. those for which
+	     there is a later rewind event using them.  */
+	case EK_REWIND_FROM_LONGJMP:
+	case EK_REWIND_TO_SETJMP:
+	  break;
+
+	case EK_WARNING:
+	  /* Always show the final "warning" event in the path.  */
+	  break;
+	}
+      idx--;
+    }
+
+  /* Second pass: remove redundant interprocedural information.  For
+     example, given:
+     (1)- calling "f2" from "f1"
+     (2)--- entry to "f2"
+     (3)--- calling "f3" from "f2"
+     (4)----- entry to "f3"
+     (5)--- returning to "f2" to "f3"
+     (6)- returning to "f1" to "f2"
+     with no other intervening events, then none of these events are
+     likely to be interesting to the user.
+
+     Prune [..., call, function-entry, return, ...] triples repeatedly
+     until nothing has changed.  For the example above, this would
+     remove events (3, 4, 5), and then remove events (1, 2, 6).  */
+  bool changed = false;
+  do
+    {
+      changed = false;
+      int idx = path->m_events.length () - 3;
+      while (idx >= 0 && idx < (signed)path->m_events.length ())
+	{
+	  if (path->m_events[idx]->is_call_p ()
+	      && path->m_events[idx + 1]->is_function_entry_p ()
+	      && path->m_events[idx + 2]->is_return_p ())
+	    {
+	      if (get_logger ())
+		{
+		  label_text desc (path->m_events[idx]->get_desc (false));
+		  log ("filtering events %i-%i:"
+		       " irrelevant call/entry/return: %s",
+		       idx, idx + 2, desc.m_buffer);
+		  desc.maybe_free ();
+		}
+	      path->delete_event (idx + 2);
+	      path->delete_event (idx + 1);
+	      path->delete_event (idx);
+	      changed = true;
+	    }
+	  idx--;
+	}
+
+    }
+  while (changed);
+
+  /* Third pass.  If all we're left with is in one function, then
+     filter function entry events.  */
+  if (!path->interprocedural_p ())
+    {
+      int idx = path->m_events.length () - 1;
+      while (idx >= 0 && idx < (signed)path->m_events.length ())
+	{
+	  checker_event *base_event = path->m_events[idx];
+	  if (base_event->m_kind == EK_FUNCTION_ENTRY)
+	    {
+	      log ("filtering event %i:"
+		   " function entry for purely intraprocedural path", idx);
+	      path->delete_event (idx);
+	    }
+	  idx--;
+	}
+    }
+
+  path->maybe_log (get_logger (), "pruned");
+}
diff --git a/gcc/analyzer/diagnostic-manager.h b/gcc/analyzer/diagnostic-manager.h
new file mode 100644
index 0000000..0f4444c
--- /dev/null
+++ b/gcc/analyzer/diagnostic-manager.h
@@ -0,0 +1,116 @@
+/* Classes for saving, deduplicating, and emitting analyzer diagnostics.
+   Copyright (C) 2019 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/>.  */
+
+#ifndef GCC_ANALYZER_DIAGNOSTIC_MANAGER_H
+#define GCC_ANALYZER_DIAGNOSTIC_MANAGER_H
+
+#include "analyzer/pending-diagnostic.h"
+
+/* A to-be-emitted diagnostic stored within diagnostic_manager.  */
+
+class saved_diagnostic
+{
+public:
+  saved_diagnostic (const state_machine *sm,
+		    const exploded_node *enode,
+		    const supernode *snode, const gimple *stmt,
+		    stmt_finder *stmt_finder,
+		    tree var, state_machine::state_t state,
+		    pending_diagnostic *d);
+  ~saved_diagnostic ();
+
+  bool operator== (const saved_diagnostic &other) const
+  {
+    return (m_sm == other.m_sm
+	    /* We don't compare m_enode.  */
+	    && m_snode == other.m_snode
+	    && m_stmt == other.m_stmt
+	    /* We don't compare m_stmt_finder.  */
+	    && m_var == other.m_var
+	    && m_state == other.m_state
+	    && m_d->equal_p (*other.m_d));
+  }
+
+  //private:
+  const state_machine *m_sm;
+  const exploded_node *m_enode;
+  const supernode *m_snode;
+  const gimple *m_stmt;
+  stmt_finder *m_stmt_finder;
+  tree m_var;
+  state_machine::state_t m_state;
+  pending_diagnostic *m_d;
+};
+
+/* A class with responsibility for saving pending diagnostics, so that
+   they can be emitted after the exploded_graph is complete.
+   This lets us de-duplicate diagnostics, and find the shortest path
+   for each similar diagnostic, potentially using edges that might
+   not have been found when each diagnostic was first saved.
+
+   This also lets us compute shortest_paths once, rather than
+   per-diagnostic.  */
+
+class diagnostic_manager : public log_user
+{
+public:
+  diagnostic_manager (logger *logger, int verbosity);
+
+  void add_diagnostic (const state_machine *sm,
+		       const exploded_node *enode,
+		       const supernode *snode, const gimple *stmt,
+		       stmt_finder *finder,
+		       tree var, state_machine::state_t state,
+		       pending_diagnostic *d);
+
+  void add_diagnostic (const exploded_node *enode,
+		       const supernode *snode, const gimple *stmt,
+		       stmt_finder *finder,
+		       pending_diagnostic *d);
+
+  void emit_saved_diagnostics (const exploded_graph &eg);
+
+  void emit_saved_diagnostic (const exploded_graph &eg,
+			      const saved_diagnostic &sd,
+			      const exploded_path &epath,
+			      const gimple *stmt,
+			      int num_dupes);
+
+private:
+  void build_emission_path (const exploded_graph &eg,
+			    const exploded_path &epath,
+			    checker_path *emission_path) const;
+
+  void add_events_for_eedge (const exploded_edge &eedge,
+			     const extrinsic_state &ext_state,
+			     checker_path *emission_path) const;
+
+  void add_events_for_superedge (const exploded_edge &eedge,
+				 checker_path *emission_path) const;
+
+  void prune_path (checker_path *path,
+		   const state_machine *sm,
+		   tree var, state_machine::state_t state) const;
+
+  auto_delete_vec<saved_diagnostic> m_saved_diagnostics;
+  const int m_verbosity;
+};
+
+#endif /* GCC_ANALYZER_DIAGNOSTIC_MANAGER_H */
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 43/49] analyzer: new file: exploded-graph.h
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (30 preceding siblings ...)
  2019-11-16  1:20 ` [PATCH 39/49] analyzer: new files: analysis-plan.{cc|h} David Malcolm
@ 2019-11-16  1:21 ` David Malcolm
  2019-12-11 20:04   ` Jeff Law
  2019-11-16  1:21 ` [PATCH 40/49] analyzer: new files: call-string.{cc|h} David Malcolm
                   ` (18 subsequent siblings)
  50 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:21 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This patch adds exploded_graph and related classes, for managing
exploring paths through the user's code as a directed graph
of <point, state> pairs.

gcc/ChangeLog:
	* analyzer/exploded-graph.h: New file.
---
 gcc/analyzer/exploded-graph.h | 754 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 754 insertions(+)
 create mode 100644 gcc/analyzer/exploded-graph.h

diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h
new file mode 100644
index 0000000..f97d2b6
--- /dev/null
+++ b/gcc/analyzer/exploded-graph.h
@@ -0,0 +1,754 @@
+/* Classes for managing a directed graph of <point, state> pairs.
+   Copyright (C) 2019 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/>.  */
+
+#ifndef GCC_ANALYZER_EXPLODED_GRAPH_H
+#define GCC_ANALYZER_EXPLODED_GRAPH_H
+
+#include "fibonacci_heap.h"
+#include "analyzer/analyzer-logging.h"
+#include "analyzer/constraint-manager.h"
+#include "analyzer/diagnostic-manager.h"
+#include "analyzer/program-point.h"
+#include "analyzer/program-state.h"
+#include "analyzer/shortest-paths.h"
+
+///////////////////////////////////////////////////////////////////////////
+
+/* Concrete implementation of region_model_context, wiring it up to the
+   rest of the analysis engine.  */
+
+class impl_region_model_context : public region_model_context, public log_user
+{
+ public:
+  impl_region_model_context (exploded_graph &eg,
+			     const exploded_node *enode_for_diag,
+
+			     /* TODO: should we be getting the ECs from the
+				old state, rather than the new?  */
+			     const program_state *old_state,
+			     program_state *new_state,
+			     state_change *change,
+
+			     const gimple *stmt,
+			     stmt_finder *stmt_finder = NULL);
+
+  impl_region_model_context (program_state *state,
+			     state_change *change,
+			     const extrinsic_state &ext_state);
+
+  void warn (pending_diagnostic *d) FINAL OVERRIDE;
+
+  void remap_svalue_ids (const svalue_id_map &map) FINAL OVERRIDE;
+
+  int on_svalue_purge (svalue_id first_unused_sid,
+		       const svalue_id_map &map) FINAL OVERRIDE;
+
+  logger *get_logger () FINAL OVERRIDE
+  {
+    return log_user::get_logger ();
+  }
+
+  void on_state_leak (const state_machine &sm,
+		      int sm_idx,
+		      svalue_id sid,
+		      svalue_id first_unused_sid,
+		      const svalue_id_map &map,
+		      state_machine::state_t state);
+
+  void on_inherited_svalue (svalue_id parent_sid,
+			    svalue_id child_sid) FINAL OVERRIDE;
+
+  void on_cast (svalue_id src_sid,
+		svalue_id dst_sid) FINAL OVERRIDE;
+
+  void on_condition (tree lhs, enum tree_code op, tree rhs) FINAL OVERRIDE;
+
+  exploded_graph *m_eg;
+  const exploded_node *m_enode_for_diag;
+  const program_state *m_old_state;
+  program_state *m_new_state;
+  state_change *m_change;
+  const gimple *m_stmt;
+  stmt_finder *m_stmt_finder;
+  const extrinsic_state &m_ext_state;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* A <program_point, program_state> pair, used internally by
+   exploded_node as its immutable data, and as a key for identifying
+   exploded_nodes we've already seen in the graph.  */
+
+struct point_and_state
+{
+  point_and_state (const program_point &point,
+		   const program_state &state)
+  : m_point (point),
+    m_state (state),
+    m_hash (m_point.hash () ^ m_state.hash ())
+  {
+  }
+
+  hashval_t hash () const
+  {
+    return m_hash;
+  }
+  bool operator== (const point_and_state &other) const
+  {
+    return m_point == other.m_point && m_state == other.m_state;
+  }
+
+  void set_state (const program_state &state)
+  {
+    m_state = state;
+    m_hash = m_point.hash () ^ m_state.hash ();
+  }
+
+  program_point m_point;
+  program_state m_state;
+  hashval_t m_hash;
+};
+
+/* A traits class for exploded graphs and their nodes and edges.  */
+
+struct eg_traits
+{
+  typedef exploded_node node_t;
+  typedef exploded_edge edge_t;
+  typedef exploded_graph graph_t;
+  struct dump_args_t
+  {
+    dump_args_t (const exploded_graph &eg) : m_eg (eg) {}
+    const exploded_graph &m_eg;
+  };
+  typedef exploded_cluster cluster_t;
+};
+
+/* An exploded_node is a unique, immutable <point, state> pair within the
+   exploded_graph.
+   Each exploded_node has a unique index within the graph
+   (for ease of debugging).  */
+
+class exploded_node : public dnode<eg_traits>
+{
+ public:
+  exploded_node (point_and_state ps,
+		 int index)
+  : m_ps (ps), m_index (index)
+  {
+    gcc_checking_assert (ps.m_state.m_region_model->canonicalized_p ());
+  }
+
+  hashval_t hash () const { return m_ps.hash (); }
+
+  void dump_dot (graphviz_out *gv, const dump_args_t &args)
+    const FINAL OVERRIDE;
+  void dump_dot_id (pretty_printer *pp) const;
+
+  void dump_to_pp (pretty_printer *pp, const extrinsic_state &ext_state) const;
+  void dump (FILE *fp, const extrinsic_state &ext_state) const;
+  void dump (const extrinsic_state &ext_state) const;
+
+  bool on_stmt (exploded_graph &eg,
+		const supernode *snode,
+		const gimple *stmt,
+		program_state *state,
+		state_change *change) const;
+  bool on_edge (exploded_graph &eg,
+		const superedge *succ,
+		program_point *next_point,
+		program_state *next_state,
+		state_change *change) const;
+  void on_longjmp (exploded_graph &eg,
+		   const gcall *call,
+		   program_state *new_state,
+		   region_model_context *ctxt) const;
+
+  void detect_leaks (exploded_graph &eg) const;
+
+  const program_point &get_point () const { return m_ps.m_point; }
+  const supernode *get_supernode () const
+  {
+    return get_point ().get_supernode ();
+  }
+  function *get_function () const
+  {
+    return get_point ().get_function ();
+  }
+  int get_stack_depth () const
+  {
+    return get_point ().get_stack_depth ();
+  }
+  const gimple *get_stmt () const { return get_point ().get_stmt (); }
+
+  const program_state &get_state () const { return m_ps.m_state; }
+
+  const point_and_state *get_ps_key () const { return &m_ps; }
+  const program_point *get_point_key () const { return &m_ps.m_point; }
+
+  void dump_succs_and_preds (FILE *outf) const;
+
+private:
+  const char * get_dot_fillcolor () const;
+
+  /* The <program_point, program_state> pair.  This is const, as it
+     is immutable once the exploded_node has been created.  */
+  const point_and_state m_ps;
+
+public:
+  /* The index of this exploded_node.  */
+  const int m_index;
+};
+
+/* Extra data for an exploded_edge that represents a rewind from a
+   longjmp to a setjmp.  */
+
+class rewind_info_t
+{
+public:
+  rewind_info_t (const exploded_node *enode_origin)
+  : m_enode_origin (enode_origin)
+  {}
+
+  const program_point &get_setjmp_point () const
+  {
+    const program_point &origin_point = m_enode_origin->get_point ();
+
+    /* "origin_point" ought to be before the call to "setjmp".  */
+    gcc_assert (origin_point.get_kind () == PK_BEFORE_STMT);
+
+    /* TODO: assert that it's the final stmt in its supernode.  */
+
+    return origin_point;
+  }
+
+  const gcall *get_setjmp_call () const
+  {
+    return as_a <const gcall *> (get_setjmp_point ().get_stmt ());
+  }
+
+  const exploded_node *get_enode_origin () const { return m_enode_origin; }
+
+private:
+  const exploded_node *m_enode_origin;
+};
+
+/* An edge within the exploded graph.
+   Some exploded_edges have an underlying superedge; others don't.  */
+
+class exploded_edge : public dedge<eg_traits>
+{
+ public:
+  exploded_edge (exploded_node *src, exploded_node *dest,
+		 const superedge *sedge,
+		 const state_change &change,
+		 rewind_info_t *rewind_info);
+  ~exploded_edge ();
+  void dump_dot (graphviz_out *gv, const dump_args_t &args)
+    const FINAL OVERRIDE;
+
+  //private:
+  const superedge *const m_sedge;
+
+  const state_change m_change;
+
+  /* NULL for most edges; will be non-NULL for an unwind from a longjmp
+     to a setjmp (owned by this class).  */
+  rewind_info_t *m_rewind_info;
+};
+
+/* Statistics about aspects of an exploded_graph.  */
+
+struct stats
+{
+  stats (int num_supernodes);
+  void log (logger *logger) const;
+  void dump (FILE *out) const;
+
+  int m_num_nodes[NUM_POINT_KINDS];
+  int m_node_reuse_count;
+  int m_node_reuse_after_merge_count;
+  int m_num_supernodes;
+};
+
+/* Traits class for ensuring uniqueness of point_and_state data within
+   an exploded_graph.  */
+
+struct eg_hash_map_traits
+{
+  typedef const point_and_state *key_type;
+  typedef exploded_node *value_type;
+  typedef exploded_node *compare_type;
+
+  static inline hashval_t hash (const key_type &k)
+  {
+    gcc_assert (k != NULL);
+    gcc_assert (k != reinterpret_cast<key_type> (1));
+    return k->hash ();
+  }
+  static inline bool equal_keys (const key_type &k1, const key_type &k2)
+  {
+    gcc_assert (k1 != NULL);
+    gcc_assert (k2 != NULL);
+    gcc_assert (k1 != reinterpret_cast<key_type> (1));
+    gcc_assert (k2 != reinterpret_cast<key_type> (1));
+    if (k1 && k2)
+      return *k1 == *k2;
+    else
+      /* Otherwise they must both be non-NULL.  */
+      return k1 == k2;
+  }
+  template <typename T>
+  static inline void remove (T &)
+  {
+    /* empty; the nodes are handled elsewhere.  */
+  }
+  template <typename T>
+  static inline void mark_deleted (T &entry)
+  {
+    entry.m_key = reinterpret_cast<key_type> (1);
+  }
+  template <typename T>
+  static inline void mark_empty (T &entry)
+  {
+    entry.m_key = NULL;
+  }
+  template <typename T>
+  static inline bool is_deleted (const T &entry)
+  {
+    return entry.m_key == reinterpret_cast<key_type> (1);
+  }
+  template <typename T>
+  static inline bool is_empty (const T &entry)
+  {
+    return entry.m_key == NULL;
+  }
+};
+
+/* Per-program_point data for an exploded_graph.  */
+
+struct per_program_point_data
+{
+  per_program_point_data (const program_point &key)
+  : m_key (key)
+  {}
+
+  program_point m_key;
+  auto_vec<exploded_node *> m_enodes;
+};
+
+/* Traits class for storing per-program_point data within
+   an exploded_graph.  */
+
+struct eg_point_hash_map_traits
+{
+  typedef const program_point *key_type;
+  typedef per_program_point_data *value_type;
+  typedef per_program_point_data *compare_type;
+
+  static inline hashval_t hash (const key_type &k)
+  {
+    gcc_assert (k != NULL);
+    gcc_assert (k != reinterpret_cast<key_type> (1));
+    return k->hash ();
+  }
+  static inline bool equal_keys (const key_type &k1, const key_type &k2)
+  {
+    gcc_assert (k1 != NULL);
+    gcc_assert (k2 != NULL);
+    gcc_assert (k1 != reinterpret_cast<key_type> (1));
+    gcc_assert (k2 != reinterpret_cast<key_type> (1));
+    if (k1 && k2)
+      return *k1 == *k2;
+    else
+      /* Otherwise they must both be non-NULL.  */
+      return k1 == k2;
+  }
+  template <typename T>
+  static inline void remove (T &)
+  {
+    /* empty; the nodes are handled elsewhere.  */
+  }
+  template <typename T>
+  static inline void mark_deleted (T &entry)
+  {
+    entry.m_key = reinterpret_cast<key_type> (1);
+  }
+  template <typename T>
+  static inline void mark_empty (T &entry)
+  {
+    entry.m_key = NULL;
+  }
+  template <typename T>
+  static inline bool is_deleted (const T &entry)
+  {
+    return entry.m_key == reinterpret_cast<key_type> (1);
+  }
+  template <typename T>
+  static inline bool is_empty (const T &entry)
+  {
+    return entry.m_key == NULL;
+  }
+};
+
+/* Data about a particular call_string within an exploded_graph.  */
+
+struct per_call_string_data
+{
+  per_call_string_data (const call_string &key, int num_supernodes)
+  : m_key (key), m_stats (num_supernodes)
+  {}
+
+  call_string m_key;
+  stats m_stats;
+};
+
+/* Traits class for storing per-call_string data within
+   an exploded_graph.  */
+
+struct eg_call_string_hash_map_traits
+{
+  typedef const call_string *key_type;
+  typedef per_call_string_data *value_type;
+  typedef per_call_string_data *compare_type;
+
+  static inline hashval_t hash (const key_type &k)
+  {
+    gcc_assert (k != NULL);
+    gcc_assert (k != reinterpret_cast<key_type> (1));
+    return k->hash ();
+  }
+  static inline bool equal_keys (const key_type &k1, const key_type &k2)
+  {
+    gcc_assert (k1 != NULL);
+    gcc_assert (k2 != NULL);
+    gcc_assert (k1 != reinterpret_cast<key_type> (1));
+    gcc_assert (k2 != reinterpret_cast<key_type> (1));
+    if (k1 && k2)
+      return *k1 == *k2;
+    else
+      /* Otherwise they must both be non-NULL.  */
+      return k1 == k2;
+  }
+  template <typename T>
+  static inline void remove (T &)
+  {
+    /* empty; the nodes are handled elsewhere.  */
+  }
+  template <typename T>
+  static inline void mark_deleted (T &entry)
+  {
+    entry.m_key = reinterpret_cast<key_type> (1);
+  }
+  template <typename T>
+  static inline void mark_empty (T &entry)
+  {
+    entry.m_key = NULL;
+  }
+  template <typename T>
+  static inline bool is_deleted (const T &entry)
+  {
+    return entry.m_key == reinterpret_cast<key_type> (1);
+  }
+  template <typename T>
+  static inline bool is_empty (const T &entry)
+  {
+    return entry.m_key == NULL;
+  }
+};
+
+/* Data about a particular function within an exploded_graph.  */
+
+struct per_function_data
+{
+  per_function_data () {}
+
+  void add_call_summary (exploded_node *node)
+  {
+    m_summaries.safe_push (node);
+  }
+
+  auto_vec<exploded_node *> m_summaries;
+};
+
+
+/* The strongly connected components of a supergraph.
+   In particular, this allows us to compute a partial ordering
+   of supernodes.  */
+
+class strongly_connected_components
+{
+public:
+  strongly_connected_components (const supergraph &sg, logger *logger);
+
+  int get_scc_id (int node_index) const
+  {
+    return m_per_node[node_index].m_lowlink;
+  }
+
+  void dump () const;
+
+private:
+  struct per_node_data
+  {
+    per_node_data ()
+      : m_index (-1), m_lowlink (-1), m_on_stack (false)
+    {}
+
+    int m_index;
+    int m_lowlink;
+    bool m_on_stack;
+  };
+
+  void strong_connect (unsigned index);
+
+  const supergraph &m_sg;
+  auto_vec<unsigned> m_stack;
+  auto_vec<per_node_data> m_per_node;
+};
+
+/* The worklist of exploded_node instances that have been added to
+   an exploded_graph, but that haven't yet been processed to find
+   their successors (or warnings).
+
+   The enodes are stored in a priority queue, ordered by a topological
+   sort of the SCCs in the supergraph, so that enodes for the same
+   program_point should appear at the front of the queue together.
+   This allows for state-merging at CFG join-points, so that
+   sufficiently-similar enodes can be merged into one.  */
+
+class worklist
+{
+public:
+  worklist (const exploded_graph &eg, const analysis_plan &plan);
+  unsigned length () const;
+  exploded_node *take_next ();
+  exploded_node *peek_next ();
+  void add_node (exploded_node *enode);
+
+private:
+  class key_t
+  {
+  public:
+    key_t (const worklist &w, exploded_node *enode)
+    : m_worklist (w), m_enode (enode)
+    {}
+
+    bool operator< (const key_t &other) const
+    {
+      return cmp (*this, other) < 0;
+    }
+
+    bool operator== (const key_t &other) const
+    {
+      return cmp (*this, other) == 0;
+    }
+
+    bool operator> (const key_t &other) const
+    {
+      return !(*this == other || *this < other);
+    }
+
+  private:
+    static int cmp_1 (const key_t &ka, const key_t &kb);
+    static int cmp (const key_t &ka, const key_t &kb);
+
+    int get_scc_id (const exploded_node *enode) const
+    {
+      const supernode *snode = enode->get_supernode ();
+      if (snode == NULL)
+	return 0;
+      return m_worklist.m_scc.get_scc_id (snode->m_index);
+    }
+
+    const worklist &m_worklist;
+    exploded_node *m_enode;
+  };
+
+  /* The order is important here: m_scc needs to stick around
+     until after m_queue has finished being cleaned up (the dtor
+     calls the ordering fns).  */
+  const exploded_graph &m_eg;
+  strongly_connected_components m_scc;
+  const analysis_plan &m_plan;
+
+  /* Priority queue, backed by a fibonacci_heap.  */
+  typedef fibonacci_heap<key_t, exploded_node> queue_t;
+  queue_t m_queue;
+};
+
+/* An exploded_graph is a directed graph of unique <point, state> pairs.
+   It also has a worklist of nodes that are waiting for their successors
+   to be added to the graph.  */
+
+class exploded_graph : public digraph<eg_traits>, public log_user
+{
+public:
+  typedef hash_map <const call_string *, per_call_string_data *,
+		    eg_call_string_hash_map_traits> call_string_data_map_t;
+
+  exploded_graph (const supergraph &sg, logger *logger,
+		  const extrinsic_state &ext_state,
+		  const state_purge_map *purge_map,
+		  const analysis_plan &plan,
+		  int verbosity);
+  ~exploded_graph ();
+
+  const supergraph &get_supergraph () const { return m_sg; }
+  const extrinsic_state &get_ext_state () const { return m_ext_state; }
+  const state_purge_map *get_purge_map () const { return m_purge_map; }
+  const analysis_plan &get_analysis_plan () const { return m_plan; }
+
+  exploded_node *get_origin () const { return m_origin; }
+
+  exploded_node *add_function_entry (function *fun);
+
+  void build_initial_worklist ();
+  void process_worklist ();
+  void process_node (exploded_node *node);
+
+  exploded_node *get_or_create_node (const program_point &point,
+				     const program_state &state,
+				     state_change *change);
+  void add_edge (exploded_node *src, exploded_node *dest,
+		 const superedge *sedge,
+		 const state_change &change,
+		 rewind_info_t *rewind_info = NULL);
+
+  per_program_point_data *
+  get_or_create_per_program_point_data (const program_point &);
+
+  per_call_string_data *
+  get_or_create_per_call_string_data (const call_string &);
+
+  per_function_data *
+  get_or_create_per_function_data (function *);
+  per_function_data *get_per_function_data (function *) const;
+
+  void save_diagnostic (const state_machine &sm,
+			const exploded_node *enode,
+			const supernode *node, const gimple *stmt,
+			stmt_finder *finder,
+			tree var, state_machine::state_t state,
+			pending_diagnostic *d);
+
+  diagnostic_manager &get_diagnostic_manager ()
+  {
+    return m_diagnostic_manager;
+  }
+
+  stats *get_global_stats () { return &m_global_stats; }
+  stats *get_or_create_function_stats (function *fn);
+  void log_stats () const;
+  void dump_stats (FILE *) const;
+  void dump_states_for_supernode (FILE *, const supernode *snode) const;
+  void dump_exploded_nodes () const;
+
+  const call_string_data_map_t *get_per_call_string_data () const
+  { return &m_per_call_string_data; }
+
+private:
+  const supergraph &m_sg;
+
+  /* Map from point/state to exploded node.
+     To avoid duplication we store point_and_state
+     *pointers* as keys, rather than point_and_state, using the
+     instance from within the exploded_node, with a custom hasher.  */
+  typedef hash_map <const point_and_state *, exploded_node *,
+		    eg_hash_map_traits> map_t;
+  map_t m_point_and_state_to_node;
+
+  /* Map from program_point to per-program_point data.  */
+  typedef hash_map <const program_point *, per_program_point_data *,
+		    eg_point_hash_map_traits> point_map_t;
+  point_map_t m_per_point_data;
+
+  worklist m_worklist;
+
+  exploded_node *m_origin;
+
+  const extrinsic_state &m_ext_state;
+
+  const state_purge_map *const m_purge_map;
+
+  const analysis_plan &m_plan;
+
+  typedef hash_map<function *, per_function_data *> per_function_data_t;
+  per_function_data_t m_per_function_data;
+
+  diagnostic_manager m_diagnostic_manager;
+
+  /* Stats.  */
+  stats m_global_stats;
+  typedef ordered_hash_map<function *, stats *> function_stat_map_t;
+  function_stat_map_t m_per_function_stats;
+  stats m_functionless_stats;
+
+  call_string_data_map_t m_per_call_string_data;
+
+
+  auto_vec<int> m_PK_AFTER_SUPERNODE_per_snode;
+};
+
+/* A path within an exploded_graph: a sequence of edges.  */
+
+class exploded_path
+{
+public:
+  exploded_path () : m_edges () {}
+  exploded_path (const exploded_path &other);
+  exploded_path & operator= (const exploded_path &other);
+
+  unsigned length () const { return m_edges.length (); }
+
+  bool find_stmt_backwards (const gimple *search_stmt,
+			    int *out_idx) const;
+
+  exploded_node *get_final_enode () const;
+
+  void dump_to_pp (pretty_printer *pp) const;
+  void dump (FILE *fp) const;
+  void dump () const;
+
+  bool feasible_p (logger *logger) const;
+
+  auto_vec<const exploded_edge *> m_edges;
+};
+
+/* Finding the shortest exploded_path within an exploded_graph.  */
+
+typedef shortest_paths<eg_traits, exploded_path> shortest_exploded_paths;
+
+/* Abstract base class for use when passing NULL as the stmt for
+   a possible warning, allowing the choice of stmt to be deferred
+   until after we have an emission path (and know we're emitting a
+   warning).  */
+
+class stmt_finder
+{
+public:
+  virtual ~stmt_finder () {}
+  virtual stmt_finder *clone () const = 0;
+  virtual const gimple *find_stmt (const exploded_path &epath) = 0;
+};
+
+// TODO: split the above up?
+
+#endif /* GCC_ANALYZER_EXPLODED_GRAPH_H */
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 00/49] RFC: Add a static analysis framework to GCC
@ 2019-11-16  1:21 David Malcolm
  2019-11-16  1:17 ` [PATCH 08/49] Introduce pretty_printer::clone vfunc David Malcolm
                   ` (50 more replies)
  0 siblings, 51 replies; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:21 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This patch kit introduces a static analysis pass for GCC that can diagnose
various kinds of problems in C code at compile-time (e.g. double-free,
use-after-free, etc).

The analyzer runs as an IPA pass on the gimple SSA representation.
It associates state machines with data, with transitions at certain
statements and edges.  It finds "interesting" interprocedural paths
through the user's code, in which bogus state transitions happen.

For example, given:

   free (ptr);
   free (ptr);

at the first call, "ptr" transitions to the "freed" state, and
at the second call the analyzer complains, since "ptr" is already in
the "freed" state (unless "ptr" is NULL, in which case it stays in
the NULL state for both calls).

Specific state machines include:
- a checker for malloc/free, for detecting double-free, resource leaks,
  use-after-free, etc (sm-malloc.cc), and
- a checker for stdio's FILE stream API (sm-file.cc)

There are also two state-machine-based checkers that are just
proof-of-concept at this stage:
- a checker for tracking exposure of sensitive data (e.g.
  writing passwords to log files aka CWE-532), and
- a checker for tracking "taint", where data potentially under an
  attacker's control is used without sanitization for things like
  array indices (CWE-129).

There's a separation between the state machines and the analysis
engine, so it ought to be relatively easy to add new warnings.

For any given diagnostic emitted by a state machine, the analysis engine
generates the simplest feasible interprocedural path of control flow for
triggering the diagnostic.


Diagnostic paths
================

The patch kit adds support to GCC's diagnostic subsystem for optionally
associating a "diagnostic_path" with a diagnostic.  A diagnostic path
describes a sequence of events predicted by the compiler that leads to the
problem occurring, with their locations in the user's source, and text
descriptions.

For example, the following warning has a 6-event interprocedural path:

malloc-ipa-8-unchecked.c: In function 'make_boxed_int':
malloc-ipa-8-unchecked.c:21:13: warning: dereference of possibly-NULL 'result' [CWE-690] [-Wanalyzer-possible-null-dereference]
   21 |   result->i = i;
      |   ~~~~~~~~~~^~~
  'make_boxed_int': events 1-2
    |
    |   18 | make_boxed_int (int i)
    |      | ^~~~~~~~~~~~~~
    |      | |
    |      | (1) entry to 'make_boxed_int'
    |   19 | {
    |   20 |   boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
    |      |                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    |      |                                    |
    |      |                                    (2) calling 'wrapped_malloc' from 'make_boxed_int'
    |
    +--> 'wrapped_malloc': events 3-4
           |
           |    7 | void *wrapped_malloc (size_t size)
           |      |       ^~~~~~~~~~~~~~
           |      |       |
           |      |       (3) entry to 'wrapped_malloc'
           |    8 | {
           |    9 |   return malloc (size);
           |      |          ~~~~~~~~~~~~~
           |      |          |
           |      |          (4) this call could return NULL
           |
    <------+
    |
  'make_boxed_int': events 5-6
    |
    |   20 |   boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
    |      |                                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    |      |                                    |
    |      |                                    (5) possible return of NULL to 'make_boxed_int' from 'wrapped_malloc'
    |   21 |   result->i = i;
    |      |   ~~~~~~~~~~~~~
    |      |             |
    |      |             (6) 'result' could be NULL: unchecked value from (4)
    |

The diagnostic-printing code has consolidated the path into 3 runs of events
(where the events are near each other and within the same function), using
ASCII art to show the interprocedural call and return.

A colorized version of the above can be seen at:
  https://dmalcolm.fedorapeople.org/gcc/2019-11-13/test.html

Other examples can be seen at:
  https://dmalcolm.fedorapeople.org/gcc/2019-11-13/malloc-1.c.html
  https://dmalcolm.fedorapeople.org/gcc/2019-11-13/setjmp-4.c.html

An example of detecting a historical double-free CVE can be seen at:
  https://dmalcolm.fedorapeople.org/gcc/2019-11-13/CVE-2005-1689.html
(there are also some false positives in this report)


Diagnostic metadata
===================

The patch kit also adds the ability to associate additional metadata with
a diagnostic. The only such metadata added by the patch kit are CWE
classifications (for the new warnings), such as the CWE-690 in the warning
above, or CWE-401 in this example:

malloc-1.c: In function 'test_42a':
malloc-1.c:466:1: warning: leak of 'p' [CWE-401] [-Wanalyzer-malloc-leak]
  466 | }
      | ^
  'test_42a': events 1-2
       |
       |  463 |   void *p = malloc (1024);
       |      |             ^~~~~~~~~~~~~
       |      |             |
       |      |             (1) allocated here
       |......
       |  466 | }
       |      | ~
       |      | |
       |      | (2) 'p' leaks here; was allocated at (1)
       |

If the terminal supports it, the above "CWE-401" is a clickable hyperlink
to:
  https://cwe.mitre.org/data/definitions/401.html


Scope
=====

The checker is implemented as a GCC plugin.

The patch kit adds support for "in-tree" plugins i.e. GCC plugins that
would live in the GCC source tree and be shipped as part of the GCC tarball,
with a new:
  --enable-plugins=[LIST OF PLUGIN NAMES]
configure option, analogous to --enable-languages (the Makefile/configure
machinery for handling in-tree GCC plugins is adapted from how we support
frontends).

The default is for no such plugins to be enabled, so the default would
be that the checker isn't built - you'd have to opt-in to building it,
with --enable-plugins=analyzer

To mitigate feature creep, I've been focusing on implementing double-free
detection, albeit with an eye to building something that can be developed
into a more fully-featured static analyzer.  For example, I haven't yet
attempted to track buffer overflows in this version, but I believe that
that could be added on top of this foundation.

Many projects implement some kind of wrapper around malloc and free, so
there is enough interprocedural support to cope with that, but only very
primitive support for summarizing larger functions and planning/performing
an efficient interprocedural analysis on non-trivial functions that
have state-machine effects.

In theory the analyzer can work with LTO, and perform cross-TU analysis.
There's a bare-bones prototype of this in the testsuite, which finds a
double-free spanning two TUs; see:

  https://dmalcolm.fedorapeople.org/gcc/2019-11-15/double-free-lto-1.STAR.c.html

However this is just a proof-of-concept at this stage (see the internal docs
for more notes on its limitations).


User interface
==============

--analyzer turns on all the analyzer warnings (it also enables the
expensive traversal that they rely on); individual warnings are all
prefixed "-Wanalyzer-" and can be turned off in the usual way
e.g. -Wno-analyzer-use-after-free.


Rationale
=========

There's benefit in integrating a checker directly into the compiler, so
that the programmer can see the diagnostics as he or she works on the code,
rather than at some later point.  I think that if the analyzer can be
made sufficiently fast that many people would opt-in to deeper but more
expensive warnings.  (I'm aiming for 2x compile time as my rough estimate
of what's reasonable in exchange for being told up-front about various
kinds of pointer snafu).


Correctness
===========

The analyzer is neither sound nor complete, but does attempt to explore
"interesting" paths through the code and generate meaningful diagnostics.
There are no known ICEs, but there are bugs... (see the xfails and TODOs
in the testsuite, and the "Limitations" section of the internal docs).


Performance
===========

Using --analyzer roughly doubles the compile time on various testcases
I've tried (krb5, zlib), but also sometimes takes a lot longer
(again, see the "Limitations" section of the internal docs; there are
bugs...).


Overview of patch kit
=====================

Patch 01 contains user-facing documentation for the analyzer

Patch 02 documents the implementation internals (to avoid having to repeat
it here)

Patches 03-15 are preliminary work.
  Patch 11 adds metadata support to GCC's diagnostic subsystem, so that
  the analyzer can associate CWE identifiers with diagnostics.
  Patch 12 adds diagnostic_path support to to GCC's diagnostic subsystem

Patches 16-17 add support for in-tree plugins

Patches 18-24 add the basics of the analyzer plugin itself

Patches 25-48 adds the analysis "machinery"
  Patches 33-38 add the state machines (somewhat abstracted from the
  rest of the analyzer)

Patch 49 adds the test suite for the analyzer

The patches can also be seen on the git mirror as branch "dmalcolm/analyzer-v1"
  https://gcc.gnu.org/git/?p=gcc.git;a=shortlog;h=refs/heads/dmalcolm/analyzer-v1

This is relative to r276961 (which predates the recent update to how
params work).

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.

The analyzer works on toy examples, and on some moderately-sized real
world codebases (krb5 and zlib).  I'd hoped to have tested it more deeply
at this point (and dogfooded it), but given that GCC stage 1 is closing
shortly I thought I ought to post what I have.

It's not clear to me whether I should focus on:

(a) pruning the scope of the checker so that it works well on
*intra*procedural C examples (and bail on anything more complex), perhaps
targetting GCC 10 as an optional extra hidden behind
--enable-plugins=analyzer, or

(b) work on deeper interprocedural analysis (and fixing efficiency issues
with this).

See also: https://gcc.gnu.org/wiki/DavidMalcolm/StaticAnalyzer
(which currently duplicates much of the above).

Thoughts?
David

David Malcolm (49):
  analyzer: user-facing documentation
  analyzer: internal documentation
  diagnostic_show_locus: move initial newline to callers
  sbitmap.h: add operator const sbitmap & to auto_sbitmap
  vec.h: add auto_delete_vec
  timevar.h: add auto_client_timevar class
  Replace label_text ctor with "borrow" and "take"
  Introduce pretty_printer::clone vfunc
  gimple const-correctness fixes
  Add -fdiagnostics-nn-line-numbers
  Add diagnostic_metadata and CWE support
  Add diagnostic paths
  function-tests.c: expose selftest::make_fndecl for use elsewhere
  hash-map-tests.c: add a selftest involving int_hash
  Add ordered_hash_map
  Add support for in-tree plugins
  Support for adding selftests via a plugin
  Add in-tree plugin: "analyzer"
  analyzer: new files: analyzer-selftests.{cc|h}
  analyzer: new builtins
  analyzer: command-line options
  analyzer: params.def: new parameters
  analyzer: logging support
  analyzer: new file: analyzer-pass.cc
  analyzer: new files: graphviz.{cc|h}
  analyzer: new files: digraph.{cc|h} and shortest-paths.h
  analyzer: new files: supergraph.{cc|h}
  analyzer: new files: analyzer.{cc|h}
  analyzer: new files: tristate.{cc|h}
  analyzer: new files: constraint-manager.{cc|h}
  analyzer: new files: region-model.{cc|h}
  analyzer: new files: pending-diagnostic.{cc|h}
  analyzer: new files: sm.{cc|h}
  analyzer: new file: sm-malloc.cc
  analyzer: new file: sm-file.cc
  analyzer: new file: sm-pattern-test.cc
  analyzer: new file: sm-sensitive.cc
  analyzer: new file: sm-taint.cc
  analyzer: new files: analysis-plan.{cc|h}
  analyzer: new files: call-string.{cc|h}
  analyzer: new files: program-point.{cc|h}
  analyzer: new files: program-state.{cc|h}
  analyzer: new file: exploded-graph.h
  analyzer: new files: state-purge.{cc|h}
  analyzer: new files: engine.{cc|h}
  analyzer: new files: checker-path.{cc|h}
  analyzer: new files: diagnostic-manager.{cc|h}
  gdbinit.in: add break-on-saved-diagnostic
  analyzer: test suite

 configure.ac                                       |    6 +
 gcc/Makefile.in                                    |  102 +-
 gcc/analyzer/Make-plugin.in                        |  181 +
 gcc/analyzer/analysis-plan.cc                      |  115 +
 gcc/analyzer/analysis-plan.h                       |   56 +
 gcc/analyzer/analyzer-logging.cc                   |  220 +
 gcc/analyzer/analyzer-logging.h                    |  256 +
 gcc/analyzer/analyzer-pass.cc                      |  103 +
 gcc/analyzer/analyzer-plugin.cc                    |   63 +
 gcc/analyzer/analyzer-selftests.cc                 |   61 +
 gcc/analyzer/analyzer-selftests.h                  |   46 +
 gcc/analyzer/analyzer.cc                           |  125 +
 gcc/analyzer/analyzer.h                            |  126 +
 gcc/analyzer/call-string.cc                        |  201 +
 gcc/analyzer/call-string.h                         |   74 +
 gcc/analyzer/checker-path.cc                       |  899 +++
 gcc/analyzer/checker-path.h                        |  563 ++
 gcc/analyzer/config-plugin.in                      |   34 +
 gcc/analyzer/constraint-manager.cc                 | 2263 ++++++
 gcc/analyzer/constraint-manager.h                  |  248 +
 gcc/analyzer/diagnostic-manager.cc                 | 1117 +++
 gcc/analyzer/diagnostic-manager.h                  |  116 +
 gcc/analyzer/digraph.cc                            |  189 +
 gcc/analyzer/digraph.h                             |  248 +
 gcc/analyzer/engine.cc                             | 3416 +++++++++
 gcc/analyzer/engine.h                              |   26 +
 gcc/analyzer/exploded-graph.h                      |  754 ++
 gcc/analyzer/graphviz.cc                           |   81 +
 gcc/analyzer/graphviz.h                            |   50 +
 gcc/analyzer/pending-diagnostic.cc                 |   61 +
 gcc/analyzer/pending-diagnostic.h                  |  265 +
 gcc/analyzer/plugin.opt                            |  161 +
 gcc/analyzer/program-point.cc                      |  490 ++
 gcc/analyzer/program-point.h                       |  316 +
 gcc/analyzer/program-state.cc                      | 1284 ++++
 gcc/analyzer/program-state.h                       |  360 +
 gcc/analyzer/region-model.cc                       | 7686 ++++++++++++++++++++
 gcc/analyzer/region-model.h                        | 2076 ++++++
 gcc/analyzer/shortest-paths.h                      |  147 +
 gcc/analyzer/sm-file.cc                            |  338 +
 gcc/analyzer/sm-malloc.cc                          |  799 ++
 gcc/analyzer/sm-pattern-test.cc                    |  165 +
 gcc/analyzer/sm-sensitive.cc                       |  209 +
 gcc/analyzer/sm-taint.cc                           |  338 +
 gcc/analyzer/sm.cc                                 |  135 +
 gcc/analyzer/sm.h                                  |  160 +
 gcc/analyzer/state-purge.cc                        |  516 ++
 gcc/analyzer/state-purge.h                         |  170 +
 gcc/analyzer/supergraph.cc                         |  936 +++
 gcc/analyzer/supergraph.h                          |  560 ++
 gcc/analyzer/tristate.cc                           |  222 +
 gcc/analyzer/tristate.h                            |   82 +
 gcc/builtins.def                                   |   33 +
 gcc/c-family/c-format.c                            |   15 +-
 gcc/c-family/c-format.h                            |    1 +
 gcc/c-family/c-opts.c                              |    1 +
 gcc/c-family/c-pretty-print.c                      |    7 +
 gcc/c-family/c-pretty-print.h                      |    1 +
 gcc/c/c-objc-common.c                              |    4 +-
 gcc/common.opt                                     |   31 +
 gcc/configure.ac                                   |  172 +-
 gcc/coretypes.h                                    |    1 +
 gcc/cp/cxx-pretty-print.c                          |    8 +
 gcc/cp/cxx-pretty-print.h                          |    2 +
 gcc/cp/error.c                                     |    9 +-
 gcc/diagnostic-color.c                             |    3 +-
 gcc/diagnostic-core.h                              |   10 +
 gcc/diagnostic-event-id.h                          |   61 +
 gcc/diagnostic-format-json.cc                      |   33 +-
 gcc/diagnostic-metadata.h                          |   42 +
 gcc/diagnostic-path.h                              |  149 +
 gcc/diagnostic-show-locus.c                        |  219 +-
 gcc/diagnostic.c                                   |  283 +-
 gcc/diagnostic.def                                 |    5 +
 gcc/diagnostic.h                                   |   43 +-
 gcc/doc/analyzer.texi                              |  470 ++
 gcc/doc/gccint.texi                                |    2 +
 gcc/doc/install.texi                               |    9 +
 gcc/doc/invoke.texi                                |  600 +-
 gcc/dwarf2out.c                                    |    1 +
 gcc/fortran/error.c                                |    1 +
 gcc/function-tests.c                               |    4 +-
 gcc/gcc-rich-location.c                            |    2 +-
 gcc/gcc-rich-location.h                            |    6 +-
 gcc/gcc.c                                          |   13 +
 gcc/gdbinit.in                                     |   10 +
 gcc/gimple-predict.h                               |    4 +-
 gcc/gimple-pretty-print.c                          |  159 +-
 gcc/gimple-pretty-print.h                          |    3 +-
 gcc/gimple.h                                       |  156 +-
 gcc/hash-map-tests.c                               |   41 +
 gcc/lto-wrapper.c                                  |    3 +
 gcc/opts.c                                         |   16 +
 gcc/ordered-hash-map-tests.cc                      |  247 +
 gcc/ordered-hash-map.h                             |  184 +
 gcc/params.def                                     |   25 +
 gcc/plugin.c                                       |    2 +
 gcc/plugin.def                                     |    3 +
 gcc/pretty-print.c                                 |   66 +
 gcc/pretty-print.h                                 |    4 +
 gcc/sbitmap.h                                      |    1 +
 gcc/selftest-run-tests.c                           |    6 +
 gcc/selftest.h                                     |    9 +
 .../gcc.dg/analyzer/CVE-2005-1689-minimal.c        |   30 +
 gcc/testsuite/gcc.dg/analyzer/abort.c              |   71 +
 gcc/testsuite/gcc.dg/analyzer/alloca-leak.c        |    8 +
 .../gcc.dg/analyzer/analyzer-verbosity-0.c         |  133 +
 .../gcc.dg/analyzer/analyzer-verbosity-1.c         |  160 +
 .../gcc.dg/analyzer/analyzer-verbosity-2.c         |  191 +
 gcc/testsuite/gcc.dg/analyzer/analyzer.exp         |   49 +
 gcc/testsuite/gcc.dg/analyzer/attribute-nonnull.c  |   57 +
 gcc/testsuite/gcc.dg/analyzer/call-summaries-1.c   |   14 +
 gcc/testsuite/gcc.dg/analyzer/conditionals-2.c     |   44 +
 gcc/testsuite/gcc.dg/analyzer/conditionals-3.c     |   45 +
 .../gcc.dg/analyzer/conditionals-notrans.c         |  158 +
 gcc/testsuite/gcc.dg/analyzer/conditionals-trans.c |  143 +
 gcc/testsuite/gcc.dg/analyzer/data-model-1.c       | 1078 +++
 gcc/testsuite/gcc.dg/analyzer/data-model-10.c      |   17 +
 gcc/testsuite/gcc.dg/analyzer/data-model-11.c      |    6 +
 gcc/testsuite/gcc.dg/analyzer/data-model-12.c      |   13 +
 gcc/testsuite/gcc.dg/analyzer/data-model-13.c      |   21 +
 gcc/testsuite/gcc.dg/analyzer/data-model-14.c      |   24 +
 gcc/testsuite/gcc.dg/analyzer/data-model-15.c      |   34 +
 gcc/testsuite/gcc.dg/analyzer/data-model-16.c      |   50 +
 gcc/testsuite/gcc.dg/analyzer/data-model-17.c      |   20 +
 gcc/testsuite/gcc.dg/analyzer/data-model-18.c      |   20 +
 gcc/testsuite/gcc.dg/analyzer/data-model-19.c      |   31 +
 gcc/testsuite/gcc.dg/analyzer/data-model-2.c       |   13 +
 gcc/testsuite/gcc.dg/analyzer/data-model-3.c       |   15 +
 gcc/testsuite/gcc.dg/analyzer/data-model-4.c       |   16 +
 gcc/testsuite/gcc.dg/analyzer/data-model-5.c       |  100 +
 gcc/testsuite/gcc.dg/analyzer/data-model-5b.c      |   91 +
 gcc/testsuite/gcc.dg/analyzer/data-model-5c.c      |   84 +
 gcc/testsuite/gcc.dg/analyzer/data-model-5d.c      |   63 +
 gcc/testsuite/gcc.dg/analyzer/data-model-6.c       |   13 +
 gcc/testsuite/gcc.dg/analyzer/data-model-7.c       |   19 +
 gcc/testsuite/gcc.dg/analyzer/data-model-8.c       |   24 +
 gcc/testsuite/gcc.dg/analyzer/data-model-9.c       |   32 +
 gcc/testsuite/gcc.dg/analyzer/data-model-path-1.c  |   13 +
 .../gcc.dg/analyzer/double-free-lto-1-a.c          |   16 +
 .../gcc.dg/analyzer/double-free-lto-1-b.c          |    8 +
 gcc/testsuite/gcc.dg/analyzer/double-free-lto-1.h  |    1 +
 gcc/testsuite/gcc.dg/analyzer/equivalence.c        |   29 +
 gcc/testsuite/gcc.dg/analyzer/explode-1.c          |   60 +
 gcc/testsuite/gcc.dg/analyzer/explode-2.c          |   50 +
 gcc/testsuite/gcc.dg/analyzer/factorial.c          |    7 +
 gcc/testsuite/gcc.dg/analyzer/fibonacci.c          |    9 +
 gcc/testsuite/gcc.dg/analyzer/fields.c             |   41 +
 gcc/testsuite/gcc.dg/analyzer/file-1.c             |   37 +
 gcc/testsuite/gcc.dg/analyzer/file-2.c             |   18 +
 gcc/testsuite/gcc.dg/analyzer/function-ptr-1.c     |    8 +
 gcc/testsuite/gcc.dg/analyzer/function-ptr-2.c     |   43 +
 gcc/testsuite/gcc.dg/analyzer/function-ptr-3.c     |   17 +
 gcc/testsuite/gcc.dg/analyzer/gzio-2.c             |   11 +
 gcc/testsuite/gcc.dg/analyzer/gzio-3.c             |   31 +
 gcc/testsuite/gcc.dg/analyzer/gzio-3a.c            |   27 +
 gcc/testsuite/gcc.dg/analyzer/gzio.c               |   17 +
 gcc/testsuite/gcc.dg/analyzer/infinite-recursion.c |   55 +
 gcc/testsuite/gcc.dg/analyzer/loop-2.c             |   36 +
 gcc/testsuite/gcc.dg/analyzer/loop-2a.c            |   39 +
 gcc/testsuite/gcc.dg/analyzer/loop-3.c             |   17 +
 gcc/testsuite/gcc.dg/analyzer/loop-4.c             |   41 +
 gcc/testsuite/gcc.dg/analyzer/loop.c               |   33 +
 gcc/testsuite/gcc.dg/analyzer/malloc-1.c           |  565 ++
 gcc/testsuite/gcc.dg/analyzer/malloc-2.c           |   23 +
 gcc/testsuite/gcc.dg/analyzer/malloc-3.c           |    8 +
 gcc/testsuite/gcc.dg/analyzer/malloc-dce.c         |   12 +
 gcc/testsuite/gcc.dg/analyzer/malloc-dedupe-1.c    |   46 +
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-1.c       |   24 +
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-10.c      |   32 +
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-11.c      |   95 +
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-12.c      |    7 +
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-13.c      |   30 +
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-2.c       |   34 +
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-3.c       |   23 +
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-4.c       |   13 +
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-5.c       |   13 +
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-6.c       |   22 +
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-7.c       |   29 +
 .../gcc.dg/analyzer/malloc-ipa-8-double-free.c     |  172 +
 .../gcc.dg/analyzer/malloc-ipa-8-unchecked.c       |   66 +
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-9.c       |   18 +
 .../gcc.dg/analyzer/malloc-macro-inline-events.c   |   45 +
 .../gcc.dg/analyzer/malloc-macro-separate-events.c |   15 +
 gcc/testsuite/gcc.dg/analyzer/malloc-macro.h       |    2 +
 .../gcc.dg/analyzer/malloc-many-paths-1.c          |   14 +
 .../gcc.dg/analyzer/malloc-many-paths-2.c          |   30 +
 .../gcc.dg/analyzer/malloc-many-paths-3.c          |   36 +
 gcc/testsuite/gcc.dg/analyzer/malloc-paths-1.c     |   15 +
 gcc/testsuite/gcc.dg/analyzer/malloc-paths-10.c    |   19 +
 gcc/testsuite/gcc.dg/analyzer/malloc-paths-2.c     |   13 +
 gcc/testsuite/gcc.dg/analyzer/malloc-paths-3.c     |   14 +
 gcc/testsuite/gcc.dg/analyzer/malloc-paths-4.c     |   20 +
 gcc/testsuite/gcc.dg/analyzer/malloc-paths-5.c     |   43 +
 gcc/testsuite/gcc.dg/analyzer/malloc-paths-6.c     |   11 +
 gcc/testsuite/gcc.dg/analyzer/malloc-paths-7.c     |   21 +
 gcc/testsuite/gcc.dg/analyzer/malloc-paths-8.c     |   54 +
 gcc/testsuite/gcc.dg/analyzer/malloc-paths-9.c     |  298 +
 gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-1a.c |  180 +
 gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-1b.c |  175 +
 gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-2.c  |  178 +
 gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-3.c  |   65 +
 gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-4.c  |   40 +
 gcc/testsuite/gcc.dg/analyzer/operations.c         |   42 +
 gcc/testsuite/gcc.dg/analyzer/params-2.c           |   16 +
 gcc/testsuite/gcc.dg/analyzer/params.c             |   32 +
 gcc/testsuite/gcc.dg/analyzer/paths-1.c            |   16 +
 gcc/testsuite/gcc.dg/analyzer/paths-1a.c           |   16 +
 gcc/testsuite/gcc.dg/analyzer/paths-2.c            |   25 +
 gcc/testsuite/gcc.dg/analyzer/paths-3.c            |   48 +
 gcc/testsuite/gcc.dg/analyzer/paths-4.c            |   49 +
 gcc/testsuite/gcc.dg/analyzer/paths-5.c            |   10 +
 gcc/testsuite/gcc.dg/analyzer/paths-6.c            |  118 +
 gcc/testsuite/gcc.dg/analyzer/paths-7.c            |   58 +
 gcc/testsuite/gcc.dg/analyzer/pattern-test-1.c     |   28 +
 gcc/testsuite/gcc.dg/analyzer/pattern-test-2.c     |   29 +
 gcc/testsuite/gcc.dg/analyzer/pointer-merging.c    |   16 +
 gcc/testsuite/gcc.dg/analyzer/pr61861.c            |    2 +
 gcc/testsuite/gcc.dg/analyzer/pragma-1.c           |   26 +
 gcc/testsuite/gcc.dg/analyzer/scope-1.c            |   23 +
 gcc/testsuite/gcc.dg/analyzer/sensitive-1.c        |   33 +
 gcc/testsuite/gcc.dg/analyzer/setjmp-1.c           |    1 +
 gcc/testsuite/gcc.dg/analyzer/setjmp-2.c           |   97 +
 gcc/testsuite/gcc.dg/analyzer/setjmp-3.c           |  106 +
 gcc/testsuite/gcc.dg/analyzer/setjmp-4.c           |  107 +
 gcc/testsuite/gcc.dg/analyzer/setjmp-5.c           |   65 +
 gcc/testsuite/gcc.dg/analyzer/setjmp-6.c           |   31 +
 gcc/testsuite/gcc.dg/analyzer/setjmp-7.c           |   36 +
 gcc/testsuite/gcc.dg/analyzer/setjmp-8.c           |  107 +
 gcc/testsuite/gcc.dg/analyzer/setjmp-9.c           |  109 +
 gcc/testsuite/gcc.dg/analyzer/switch.c             |   28 +
 gcc/testsuite/gcc.dg/analyzer/taint-1.c            |  128 +
 gcc/testsuite/gcc.dg/analyzer/zlib-1.c             |   67 +
 gcc/testsuite/gcc.dg/analyzer/zlib-2.c             |   51 +
 gcc/testsuite/gcc.dg/analyzer/zlib-3.c             |  214 +
 gcc/testsuite/gcc.dg/analyzer/zlib-4.c             |   20 +
 gcc/testsuite/gcc.dg/analyzer/zlib-5.c             |   49 +
 gcc/testsuite/gcc.dg/analyzer/zlib-6.c             |   47 +
 gcc/testsuite/gcc.dg/format/gcc_diag-10.c          |    6 +-
 .../gcc.dg/plugin/diagnostic-path-format-default.c |  142 +
 .../diagnostic-path-format-inline-events-1.c       |  142 +
 .../diagnostic-path-format-inline-events-2.c       |  154 +
 .../diagnostic-path-format-inline-events-3.c       |  153 +
 .../gcc.dg/plugin/diagnostic-path-format-none.c    |   43 +
 .../diagnostic-path-format-separate-events.c       |   44 +
 .../gcc.dg/plugin/diagnostic-test-paths-1.c        |   38 +
 .../gcc.dg/plugin/diagnostic-test-paths-2.c        |   56 +
 .../gcc.dg/plugin/diagnostic-test-paths-3.c        |   38 +
 .../gcc.dg/plugin/diagnostic_plugin_test_paths.c   |  379 +
 .../plugin/diagnostic_plugin_test_show_locus.c     |    1 +
 gcc/testsuite/gcc.dg/plugin/plugin.exp             |   10 +
 gcc/testsuite/lib/target-supports.exp              |    8 +
 gcc/timevar.h                                      |   33 +
 gcc/toplev.c                                       |    8 +
 gcc/tree-diagnostic-path.cc                        |  809 ++
 gcc/tree-diagnostic.c                              |   12 +-
 gcc/tree-diagnostic.h                              |    8 +
 gcc/tree-eh.c                                      |    6 +-
 gcc/tree-eh.h                                      |    4 +-
 gcc/tree-ssa-alias.h                               |    2 +-
 gcc/tree-ssa-structalias.c                         |    2 +-
 gcc/vec.c                                          |   27 +
 gcc/vec.h                                          |   35 +
 libcpp/include/line-map.h                          |   38 +-
 libcpp/line-map.c                                  |    3 +-
 265 files changed, 42181 insertions(+), 336 deletions(-)
 create mode 100644 gcc/analyzer/Make-plugin.in
 create mode 100644 gcc/analyzer/analysis-plan.cc
 create mode 100644 gcc/analyzer/analysis-plan.h
 create mode 100644 gcc/analyzer/analyzer-logging.cc
 create mode 100644 gcc/analyzer/analyzer-logging.h
 create mode 100644 gcc/analyzer/analyzer-pass.cc
 create mode 100644 gcc/analyzer/analyzer-plugin.cc
 create mode 100644 gcc/analyzer/analyzer-selftests.cc
 create mode 100644 gcc/analyzer/analyzer-selftests.h
 create mode 100644 gcc/analyzer/analyzer.cc
 create mode 100644 gcc/analyzer/analyzer.h
 create mode 100644 gcc/analyzer/call-string.cc
 create mode 100644 gcc/analyzer/call-string.h
 create mode 100644 gcc/analyzer/checker-path.cc
 create mode 100644 gcc/analyzer/checker-path.h
 create mode 100644 gcc/analyzer/config-plugin.in
 create mode 100644 gcc/analyzer/constraint-manager.cc
 create mode 100644 gcc/analyzer/constraint-manager.h
 create mode 100644 gcc/analyzer/diagnostic-manager.cc
 create mode 100644 gcc/analyzer/diagnostic-manager.h
 create mode 100644 gcc/analyzer/digraph.cc
 create mode 100644 gcc/analyzer/digraph.h
 create mode 100644 gcc/analyzer/engine.cc
 create mode 100644 gcc/analyzer/engine.h
 create mode 100644 gcc/analyzer/exploded-graph.h
 create mode 100644 gcc/analyzer/graphviz.cc
 create mode 100644 gcc/analyzer/graphviz.h
 create mode 100644 gcc/analyzer/pending-diagnostic.cc
 create mode 100644 gcc/analyzer/pending-diagnostic.h
 create mode 100644 gcc/analyzer/plugin.opt
 create mode 100644 gcc/analyzer/program-point.cc
 create mode 100644 gcc/analyzer/program-point.h
 create mode 100644 gcc/analyzer/program-state.cc
 create mode 100644 gcc/analyzer/program-state.h
 create mode 100644 gcc/analyzer/region-model.cc
 create mode 100644 gcc/analyzer/region-model.h
 create mode 100644 gcc/analyzer/shortest-paths.h
 create mode 100644 gcc/analyzer/sm-file.cc
 create mode 100644 gcc/analyzer/sm-malloc.cc
 create mode 100644 gcc/analyzer/sm-pattern-test.cc
 create mode 100644 gcc/analyzer/sm-sensitive.cc
 create mode 100644 gcc/analyzer/sm-taint.cc
 create mode 100644 gcc/analyzer/sm.cc
 create mode 100644 gcc/analyzer/sm.h
 create mode 100644 gcc/analyzer/state-purge.cc
 create mode 100644 gcc/analyzer/state-purge.h
 create mode 100644 gcc/analyzer/supergraph.cc
 create mode 100644 gcc/analyzer/supergraph.h
 create mode 100644 gcc/analyzer/tristate.cc
 create mode 100644 gcc/analyzer/tristate.h
 create mode 100644 gcc/diagnostic-event-id.h
 create mode 100644 gcc/diagnostic-metadata.h
 create mode 100644 gcc/diagnostic-path.h
 create mode 100644 gcc/doc/analyzer.texi
 create mode 100644 gcc/ordered-hash-map-tests.cc
 create mode 100644 gcc/ordered-hash-map.h
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/CVE-2005-1689-minimal.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/abort.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/alloca-leak.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-0.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/analyzer.exp
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/attribute-nonnull.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/call-summaries-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/conditionals-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/conditionals-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/conditionals-notrans.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/conditionals-trans.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-10.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-11.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-12.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-13.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-14.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-15.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-16.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-17.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-18.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-19.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-4.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-5.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-5b.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-5c.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-5d.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-6.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-7.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-8.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-9.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-path-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/double-free-lto-1-a.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/double-free-lto-1-b.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/double-free-lto-1.h
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/equivalence.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/explode-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/explode-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/factorial.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fibonacci.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fields.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/file-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/file-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/function-ptr-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/function-ptr-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/function-ptr-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/gzio-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/gzio-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/gzio-3a.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/gzio.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/infinite-recursion.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/loop-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/loop-2a.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/loop-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/loop-4.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/loop.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-dce.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-dedupe-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-10.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-11.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-12.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-13.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-4.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-5.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-6.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-7.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-double-free.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-unchecked.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-9.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-macro-inline-events.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-macro-separate-events.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-macro.h
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-paths-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-paths-10.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-paths-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-paths-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-paths-4.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-paths-5.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-paths-6.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-paths-7.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-paths-8.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-paths-9.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-1a.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-1b.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-4.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/operations.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/params-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/params.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/paths-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/paths-1a.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/paths-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/paths-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/paths-4.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/paths-5.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/paths-6.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/paths-7.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/pattern-test-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/pattern-test-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/pointer-merging.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/pr61861.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/pragma-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/scope-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/sensitive-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/setjmp-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/setjmp-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/setjmp-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/setjmp-4.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/setjmp-5.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/setjmp-6.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/setjmp-7.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/setjmp-8.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/setjmp-9.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/switch.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/taint-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/zlib-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/zlib-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/zlib-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/zlib-4.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/zlib-5.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/zlib-6.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-default.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-1.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-2.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-3.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-none.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-separate-events.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-1.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-3.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c
 create mode 100644 gcc/tree-diagnostic-path.cc

-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 25/49] analyzer: new files: graphviz.{cc|h}
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (45 preceding siblings ...)
  2019-11-16  1:21 ` [PATCH 42/49] analyzer: new files: program-state.{cc|h} David Malcolm
@ 2019-11-16  1:21 ` David Malcolm
  2019-12-07 14:54   ` Jeff Law
  2019-11-16  1:21 ` [PATCH 45/49] analyzer: new files: engine.{cc|h} David Malcolm
                   ` (3 subsequent siblings)
  50 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:21 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This patch adds a simple wrapper class to make it easier to
write human-readable .dot files.

gcc/ChangeLog:
	* analyzer/graphviz.cc: New file.
	* analyzer/graphviz.h: New file.
---
 gcc/analyzer/graphviz.cc | 81 ++++++++++++++++++++++++++++++++++++++++++++++++
 gcc/analyzer/graphviz.h  | 50 ++++++++++++++++++++++++++++++
 2 files changed, 131 insertions(+)
 create mode 100644 gcc/analyzer/graphviz.cc
 create mode 100644 gcc/analyzer/graphviz.h

diff --git a/gcc/analyzer/graphviz.cc b/gcc/analyzer/graphviz.cc
new file mode 100644
index 0000000..46e7194
--- /dev/null
+++ b/gcc/analyzer/graphviz.cc
@@ -0,0 +1,81 @@
+/* Helper code for graphviz output.
+   Copyright (C) 2019 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 "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "analyzer/graphviz.h"
+
+/* graphviz_out's ctor, wrapping PP.  */
+
+graphviz_out::graphviz_out (pretty_printer *pp)
+: m_pp (pp),
+  m_indent (0)
+{
+}
+
+/* Formatted print of FMT.  */
+
+void
+graphviz_out::print (const char *fmt, ...)
+{
+  text_info text;
+  va_list ap;
+
+  va_start (ap, fmt);
+  text.err_no = errno;
+  text.args_ptr = &ap;
+  text.format_spec = fmt;
+  pp_format (m_pp, &text);
+  pp_output_formatted_text (m_pp);
+  va_end (ap);
+}
+
+/* Formatted print of FMT.  The text is indented by the current
+   indent, and a newline is added.  */
+
+void
+graphviz_out::println (const char *fmt, ...)
+{
+  text_info text;
+  va_list ap;
+
+  write_indent ();
+
+  va_start (ap, fmt);
+  text.err_no = errno;
+  text.args_ptr = &ap;
+  text.format_spec = fmt;
+  pp_format (m_pp, &text);
+  pp_output_formatted_text (m_pp);
+  va_end (ap);
+
+  pp_newline (m_pp);
+}
+
+/* Print the current indent to the underlying pp.  */
+
+void
+graphviz_out::write_indent ()
+{
+  for (int i = 0; i < m_indent * 2; ++i)
+    pp_space (m_pp);
+}
diff --git a/gcc/analyzer/graphviz.h b/gcc/analyzer/graphviz.h
new file mode 100644
index 0000000..d097834
--- /dev/null
+++ b/gcc/analyzer/graphviz.h
@@ -0,0 +1,50 @@
+/* Helper code for graphviz output.
+   Copyright (C) 2019 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/>.  */
+
+#ifndef GCC_ANALYZER_GRAPHVIZ_H
+#define GCC_ANALYZER_GRAPHVIZ_H
+
+#include "pretty-print.h" /* for ATTRIBUTE_GCC_PPDIAG.  */
+
+/* A class for writing .dot output to a pretty_printer with
+   indentation to show nesting.  */
+
+class graphviz_out {
+ public:
+  graphviz_out (pretty_printer *pp);
+
+  void print (const char *fmt, ...)
+    ATTRIBUTE_GCC_PPDIAG(2,3);
+  void println (const char *fmt, ...)
+    ATTRIBUTE_GCC_PPDIAG(2,3);
+
+  void indent () { m_indent++; }
+  void outdent () { m_indent--; }
+
+  void write_indent ();
+
+  pretty_printer *get_pp () const { return m_pp; }
+
+ private:
+  pretty_printer *m_pp;
+  int m_indent;
+};
+
+#endif /* GCC_ANALYZER_GRAPHVIZ_H */
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 44/49] analyzer: new files: state-purge.{cc|h}
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (32 preceding siblings ...)
  2019-11-16  1:21 ` [PATCH 40/49] analyzer: new files: call-string.{cc|h} David Malcolm
@ 2019-11-16  1:21 ` David Malcolm
  2019-12-11 20:11   ` Jeff Law
  2019-11-16  1:21 ` [PATCH 47/49] analyzer: new files: diagnostic-manager.{cc|h} David Malcolm
                   ` (16 subsequent siblings)
  50 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:21 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This patch adds classes for tracking what state can be safely purged
at any given point in the program.

gcc/ChangeLog:
	* analyzer/state-purge.cc: New file.
	* analyzer/state-purge.h: New file.
---
 gcc/analyzer/state-purge.cc | 516 ++++++++++++++++++++++++++++++++++++++++++++
 gcc/analyzer/state-purge.h  | 170 +++++++++++++++
 2 files changed, 686 insertions(+)
 create mode 100644 gcc/analyzer/state-purge.cc
 create mode 100644 gcc/analyzer/state-purge.h

diff --git a/gcc/analyzer/state-purge.cc b/gcc/analyzer/state-purge.cc
new file mode 100644
index 0000000..caaa946
--- /dev/null
+++ b/gcc/analyzer/state-purge.cc
@@ -0,0 +1,516 @@
+/* Classes for purging state at function_points.
+   Copyright (C) 2019 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 "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "timevar.h"
+#include "tree-ssa-alias.h"
+#include "gimple.h"
+#include "stringpool.h"
+#include "tree-vrp.h"
+#include "gimple-ssa.h"
+#include "tree-ssanames.h"
+#include "tree-phinodes.h"
+#include "ssa-iterators.h"
+#include "gimple-pretty-print.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/state-purge.h"
+
+/* state_purge_map's ctor.  Walk all SSA names in all functions, building
+   a state_purge_per_ssa_name instance for each.  */
+
+state_purge_map::state_purge_map (const supergraph &sg,
+				  logger *logger)
+: log_user (logger), m_sg (sg)
+{
+  LOG_FUNC (logger);
+
+  auto_client_timevar tv ("state_purge_map ctor");
+
+  cgraph_node *node;
+  FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
+  {
+    function *fun = node->get_fun ();
+    if (logger)
+      log ("function: %s", function_name (fun));
+    //printf ("function: %s\n", function_name (fun));
+    tree name;
+    unsigned int i;;
+    FOR_EACH_SSA_NAME (i, name, fun)
+      {
+	/* For now, don't bother tracking the .MEM SSA names.  */
+	if (tree var = SSA_NAME_VAR (name))
+	  if (TREE_CODE (var) == VAR_DECL)
+	    if (VAR_DECL_IS_VIRTUAL_OPERAND (var))
+	      continue;
+	m_map.put (name, new state_purge_per_ssa_name (*this, name, fun));
+      }
+  }
+}
+
+/* state_purge_map's dtor.  */
+
+state_purge_map::~state_purge_map ()
+{
+  for (iterator iter = m_map.begin (); iter != m_map.end (); ++iter)
+    delete (*iter).second;
+}
+
+/* state_purge_per_ssa_name's ctor.
+
+   Locate all uses of VAR within FUN.
+   Walk backwards from each use, marking program points, until
+   we reach the def stmt, populating m_points_needing_var.
+
+   We have to track program points rather than
+   just stmts since there could be empty basic blocks on the way.  */
+
+state_purge_per_ssa_name::state_purge_per_ssa_name (const state_purge_map &map,
+						    tree name,
+						    function *fun)
+: m_points_needing_name (), m_name (name), m_fun (fun)
+{
+  LOG_FUNC (map.get_logger ());
+
+  if (map.get_logger ())
+    {
+      map.log ("SSA name: %qE within %qD", name, fun->decl);
+
+      /* Show def stmt.  */
+      const gimple *def_stmt = SSA_NAME_DEF_STMT (name);
+      pretty_printer pp;
+      pp_gimple_stmt_1 (&pp, def_stmt, 0, (dump_flags_t)0);
+      map.log ("def stmt: %s", pp_formatted_text (&pp));
+    }
+
+  auto_vec<function_point> worklist;
+
+  /* Add all immediate uses of name to the worklist.
+     Compare with debug_immediate_uses.  */
+  imm_use_iterator iter;
+  use_operand_p use_p;
+  FOR_EACH_IMM_USE_FAST (use_p, iter, name)
+    {
+      if (USE_STMT (use_p))
+	{
+	  const gimple *use_stmt = USE_STMT (use_p);
+	  if (map.get_logger ())
+	    {
+	      pretty_printer pp;
+	      pp_gimple_stmt_1 (&pp, use_stmt, 0, (dump_flags_t)0);
+	      map.log ("used by stmt: %s", pp_formatted_text (&pp));
+	    }
+
+	  const supernode *snode
+	    = map.get_sg ().get_supernode_for_stmt (use_stmt);
+
+	  /* If it's a use within a phi node, then we care about
+	     which in-edge we came from.  */
+	  if (use_stmt->code == GIMPLE_PHI)
+	    {
+	      for (gphi_iterator gpi
+		     = const_cast<supernode *> (snode)->start_phis ();
+		   !gsi_end_p (gpi); gsi_next (&gpi))
+		{
+		  gphi *phi = gpi.phi ();
+		  if (phi == use_stmt)
+		    {
+		      /* Find arguments (and thus in-edges) which use NAME.  */
+		      for (unsigned arg_idx = 0;
+			   arg_idx < gimple_phi_num_args (phi);
+			   ++arg_idx)
+			{
+			  if (name == gimple_phi_arg (phi, arg_idx)->def)
+			    {
+			      edge in_edge = gimple_phi_arg_edge (phi, arg_idx);
+			      const superedge *in_sedge
+				= map.get_sg ().get_edge_for_cfg_edge (in_edge);
+			      function_point point
+				= function_point::before_supernode
+				(snode, in_sedge);
+			      add_to_worklist (point, &worklist,
+					       map.get_logger ());
+			      m_points_needing_name.add (point);
+			    }
+			}
+		    }
+		}
+	    }
+	  else
+	    {
+	      function_point point = before_use_stmt (map, use_stmt);
+	      add_to_worklist (point, &worklist, map.get_logger ());
+	      m_points_needing_name.add (point);
+
+	      /* We also need to add uses for conditionals and switches,
+		 where the stmt "happens" at the after_supernode, for filtering
+		 the out-edges.  */
+	      if (use_stmt == snode->get_last_stmt ())
+		{
+		  if (map.get_logger ())
+		    map.log ("last stmt in BB");
+		  function_point point
+		    = function_point::after_supernode (snode);
+		  add_to_worklist (point, &worklist, map.get_logger ());
+		  m_points_needing_name.add (point);
+		}
+	      else
+		if (map.get_logger ())
+		  map.log ("not last stmt in BB");
+	    }
+	}
+    }
+
+  /* Process worklist by walking backwards until we reach the def stmt.  */
+  {
+    log_scope s (map.get_logger (), "processing worklist");
+    while (worklist.length () > 0)
+      {
+	function_point point = worklist.pop ();
+	process_point (point, &worklist, map);
+    }
+  }
+
+  if (map.get_logger ())
+    {
+      map.log ("%qE in %qD is needed to process:", name, fun->decl);
+      for (point_set_t::iterator iter = m_points_needing_name.begin ();
+	   iter != m_points_needing_name.end ();
+	   ++iter)
+	{
+	  map.start_log_line ();
+	  map.get_logger ()->log_partial ("  point: ");
+	  (*iter).print (map.get_logger ()->get_printer (), format (false));
+	  map.end_log_line ();
+	}
+    }
+}
+
+/* Return true if the SSA name is needed at POINT.  */
+
+bool
+state_purge_per_ssa_name::needed_at_point_p (const function_point &point) const
+{
+  return const_cast <point_set_t &> (m_points_needing_name).contains (point);
+}
+
+/* Get the function_point representing immediately before USE_STMT.
+   Subroutine of ctor.  */
+
+function_point
+state_purge_per_ssa_name::before_use_stmt (const state_purge_map &map,
+					   const gimple *use_stmt)
+{
+  gcc_assert (use_stmt->code != GIMPLE_PHI);
+
+  const supernode *supernode
+    = map.get_sg ().get_supernode_for_stmt (use_stmt);
+  unsigned int stmt_idx = supernode->get_stmt_index (use_stmt);
+  return function_point::before_stmt (supernode, stmt_idx);
+}
+
+/* Add POINT to *WORKLIST if the point has not already been seen.
+   Subroutine of ctor.  */
+
+void
+state_purge_per_ssa_name::add_to_worklist (const function_point &point,
+					   auto_vec<function_point> *worklist,
+					   logger *logger)
+{
+  LOG_FUNC (logger);
+  if (logger)
+    {
+      logger->start_log_line ();
+      logger->log_partial ("point: '");
+      point.print (logger->get_printer (), format (false));
+      logger->log_partial ("' for worklist for %qE", m_name);
+      logger->end_log_line ();
+    }
+
+  gcc_assert (point.get_function () == m_fun);
+  if (point.get_from_edge ())
+    gcc_assert (point.get_from_edge ()->get_kind () == SUPEREDGE_CFG_EDGE);
+
+  if (m_points_needing_name.contains (point))
+    {
+      if (logger)
+	logger->log ("already seen for %qE", m_name);
+    }
+  else
+    {
+      if (logger)
+	logger->log ("not seen; adding to worklist for %qE", m_name);
+      m_points_needing_name.add (point);
+      worklist->safe_push (point);
+    }
+}
+
+/* Process POINT, popped from WORKLIST.
+   Iterate over predecessors of POINT, adding to WORKLIST.  */
+
+void
+state_purge_per_ssa_name::process_point (const function_point &point,
+					 auto_vec<function_point> *worklist,
+					 const state_purge_map &map)
+{
+  logger *logger = map.get_logger ();
+  LOG_FUNC (logger);
+  if (logger)
+    {
+      logger->start_log_line ();
+      logger->log_partial ("considering point: '");
+      point.print (logger->get_printer (), format (false));
+      logger->log_partial ("' for %qE", m_name);
+      logger->end_log_line ();
+    }
+
+  gimple *def_stmt = SSA_NAME_DEF_STMT (m_name);
+
+  const supernode *snode = point.get_supernode ();
+
+  switch (point.get_kind ())
+    {
+    default:
+      gcc_unreachable ();
+
+    case PK_ORIGIN:
+      break;
+
+    case PK_BEFORE_SUPERNODE:
+      {
+	for (gphi_iterator gpi
+	       = const_cast<supernode *> (snode)->start_phis ();
+	     !gsi_end_p (gpi); gsi_next (&gpi))
+	  {
+	    gphi *phi = gpi.phi ();
+	    if (phi == def_stmt)
+	      {
+		if (logger)
+		  logger->log ("def stmt within phis; terminating");
+		return;
+	      }
+	  }
+
+	/* Add given pred to worklist.  */
+	if (point.get_from_edge ())
+	  {
+	    gcc_assert (point.get_from_edge ()->m_src);
+	    add_to_worklist
+	      (function_point::after_supernode (point.get_from_edge ()->m_src),
+	       worklist, logger);
+	  }
+	else
+	  {
+	    /* Add any intraprocedually edge for a call.  */
+	    if (snode->m_returning_call)
+	      {
+		cgraph_edge *cedge
+		  = supergraph_call_edge (snode->m_fun,
+					  snode->m_returning_call);
+		gcc_assert (cedge);
+		superedge *sedge
+		  = map.get_sg ().get_intraprocedural_edge_for_call (cedge);
+		gcc_assert (sedge);
+		add_to_worklist
+		  (function_point::after_supernode (sedge->m_src),
+		   worklist, logger);
+	      }
+	  }
+      }
+      break;
+
+    case PK_BEFORE_STMT:
+      {
+	if (def_stmt == point.get_stmt ())
+	  {
+	    if (logger)
+	      logger->log ("def stmt; terminating");
+	    return;
+	  }
+	if (point.get_stmt_idx () > 0)
+	  add_to_worklist (function_point::before_stmt
+			     (snode, point.get_stmt_idx () - 1),
+			   worklist, logger);
+	else
+	{
+	  /* Add before_supernode to worklist.  This captures the in-edge,
+	     so we have to do it once per in-edge.  */
+	  unsigned i;
+	  superedge *pred;
+	  FOR_EACH_VEC_ELT (snode->m_preds, i, pred)
+	    add_to_worklist (function_point::before_supernode (snode,
+							       pred),
+			     worklist, logger);
+	}
+      }
+      break;
+
+    case PK_AFTER_SUPERNODE:
+      {
+	if (snode->m_stmts.length ())
+	  add_to_worklist
+	    (function_point::before_stmt (snode,
+					  snode->m_stmts.length () - 1),
+	     worklist, logger);
+	else
+	  {
+	    /* Add before_supernode to worklist.  This captures the in-edge,
+	       so we have to do it once per in-edge.  */
+	    unsigned i;
+	    superedge *pred;
+	    FOR_EACH_VEC_ELT (snode->m_preds, i, pred)
+	      add_to_worklist (function_point::before_supernode (snode,
+								 pred),
+			       worklist, logger);
+	    /* If it's the initial BB, add it, to ensure that we
+	       have "before supernode" for the initial ENTRY block, and don't
+	       erroneously purge SSA names for initial values of parameters.  */
+	    if (snode->entry_p ())
+	      {
+		add_to_worklist
+		  (function_point::before_supernode (snode, NULL),
+		   worklist, logger);
+	      }
+	  }
+      }
+      break;
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class state_purge_annotator : public dot_annotator.  */
+
+/* Implementation of dot_annotator::add_node_annotations vfunc for
+   state_purge_annotator.
+
+   Add an additional record showing which names are purged on entry
+   to the supernode N.  */
+
+void
+state_purge_annotator::add_node_annotations (pretty_printer *pp,
+					     const supernode &n) const
+{
+  if (m_map == NULL)
+    return;
+
+   pp_printf (pp, "annotation_for_node_%i", n.m_index);
+   pp_printf (pp, " [shape=%s,style=filled,fillcolor=%s,label=\"",
+       "record", "lightblue");
+   pp_left_brace (pp);
+   pp_write_text_to_stream (pp);
+
+   // FIXME: passing in a NULL in-edge means we get no hits
+   function_point before_supernode
+     (function_point::before_supernode (&n, NULL));
+
+   for (state_purge_map::iterator iter = m_map->begin ();
+	iter != m_map->end ();
+	++iter)
+     {
+       tree name = (*iter).first;
+       state_purge_per_ssa_name *per_name_data = (*iter).second;
+       if (per_name_data->get_function () == n.m_fun)
+	 {
+PUSH_IGNORE_WFORMAT
+	   if (per_name_data->needed_at_point_p (before_supernode))
+	     pp_printf (pp, "%qE needed here", name);
+	   else
+	     pp_printf (pp, "%qE not needed here", name);
+POP_IGNORE_WFORMAT
+	 }
+       pp_newline (pp);
+     }
+
+   pp_right_brace (pp);
+
+   pp_string (pp, "\"];\n\n");
+   pp_flush (pp);
+}
+
+/* Print V to PP as a comma-separated list in braces, titling it
+   with TITLE.
+
+   Subroutine of state_purge_annotator::add_stmt_annotations.  */
+
+static void
+print_vec_of_names (pretty_printer *pp, const char *title,
+		    const auto_vec<tree> &v)
+{
+  tree name;
+  unsigned i;
+  pp_printf (pp, "%s: {", title);
+  FOR_EACH_VEC_ELT (v, i, name)
+    {
+      if (i > 0)
+	pp_string (pp, ", ");
+PUSH_IGNORE_WFORMAT
+      pp_printf (pp, "%qE", name);
+POP_IGNORE_WFORMAT
+    }
+  pp_printf (pp, "}");
+  pp_newline (pp);
+}
+
+/* Implementation of dot_annotator::add_stmt_annotations for
+   state_purge_annotator.
+
+   Add text showing which names are purged at STMT.  */
+
+void
+state_purge_annotator::add_stmt_annotations (pretty_printer *pp,
+					     const gimple *stmt) const
+{
+  if (m_map == NULL)
+    return;
+
+  if (stmt->code == GIMPLE_PHI)
+    return;
+
+  pp_newline (pp);
+
+  const supernode *supernode = m_map->get_sg ().get_supernode_for_stmt (stmt);
+  unsigned int stmt_idx = supernode->get_stmt_index (stmt);
+  function_point before_stmt
+    (function_point::before_stmt (supernode, stmt_idx));
+
+  auto_vec<tree> needed;
+  auto_vec<tree> not_needed;
+  for (state_purge_map::iterator iter = m_map->begin ();
+       iter != m_map->end ();
+       ++iter)
+    {
+      tree name = (*iter).first;
+      state_purge_per_ssa_name *per_name_data = (*iter).second;
+      if (per_name_data->get_function () == supernode->m_fun)
+	{
+	  if (per_name_data->needed_at_point_p (before_stmt))
+	    needed.safe_push (name);
+	  else
+	    not_needed.safe_push (name);
+	}
+    }
+
+  print_vec_of_names (pp, "needed here", needed);
+  print_vec_of_names (pp, "not needed here", not_needed);
+}
diff --git a/gcc/analyzer/state-purge.h b/gcc/analyzer/state-purge.h
new file mode 100644
index 0000000..a86afc2
--- /dev/null
+++ b/gcc/analyzer/state-purge.h
@@ -0,0 +1,170 @@
+/* Classes for purging state at function_points.
+   Copyright (C) 2019 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/>.  */
+
+#ifndef GCC_ANALYZER_STATE_PURGE_H
+#define GCC_ANALYZER_STATE_PURGE_H
+
+#include "analyzer/analyzer-logging.h"
+#include "analyzer/program-point.h"
+
+///////////////////////////////////////////////////////////////////////////
+
+/* Hash traits for function_point.  */
+
+template <> struct default_hash_traits<function_point>
+: public pod_hash_traits<function_point>
+{
+};
+
+template <>
+inline hashval_t
+pod_hash_traits<function_point>::hash (value_type v)
+{
+  return v.hash ();
+}
+
+template <>
+inline bool
+pod_hash_traits<function_point>::equal (const value_type &existing,
+                                 const value_type &candidate)
+{
+  return existing == candidate;
+}
+template <>
+inline void
+pod_hash_traits<function_point>::mark_deleted (value_type &v)
+{
+  v = function_point::deleted ();
+}
+template <>
+inline void
+pod_hash_traits<function_point>::mark_empty (value_type &v)
+{
+  v = function_point::empty ();
+}
+template <>
+inline bool
+pod_hash_traits<function_point>::is_deleted (value_type v)
+{
+  return v.get_kind () == PK_DELETED;
+}
+template <>
+inline bool
+pod_hash_traits<function_point>::is_empty (value_type v)
+{
+  return v.get_kind () == PK_EMPTY;
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+/* The result of analyzing which SSA names can be purged from state at
+   different points in the program, so that we can simplify program_state
+   objects, in the hope of reducing state-blowup.  */
+
+class state_purge_map : public log_user
+{
+public:
+  typedef ordered_hash_map<tree, state_purge_per_ssa_name *> map_t;
+  typedef map_t::iterator iterator;
+
+  state_purge_map (const supergraph &sg, logger *logger);
+  ~state_purge_map ();
+
+  const state_purge_per_ssa_name &get_data_for_ssa_name (tree name) const
+  {
+    gcc_assert (TREE_CODE (name) == SSA_NAME);
+    if (tree var = SSA_NAME_VAR (name))
+      if (TREE_CODE (var) == VAR_DECL)
+	gcc_assert (!VAR_DECL_IS_VIRTUAL_OPERAND (var));
+
+    state_purge_per_ssa_name **slot
+      = const_cast <map_t&> (m_map).get (name);
+    return **slot;
+  }
+
+  const supergraph &get_sg () const { return m_sg; }
+
+  iterator begin () const { return m_map.begin (); }
+  iterator end () const { return m_map.end (); }
+
+private:
+  const supergraph &m_sg;
+  map_t m_map;
+};
+
+///////////////////////////////////////////////////////////////////////////
+
+/* The part of a state_purge_map relating to a specific SSA name.
+
+   The result of analyzing a given SSA name, recording which
+   function_points need to retain state information about it to handle
+   their successor states, so that we can simplify program_state objects,
+   in the hope of reducing state-blowup.  */
+
+class state_purge_per_ssa_name
+{
+public:
+  state_purge_per_ssa_name (const state_purge_map &map,
+			    tree name,
+			    function *fun);
+
+  bool needed_at_point_p (const function_point &point) const;
+
+  function *get_function () const { return m_fun; }
+
+private:
+  static function_point before_use_stmt (const state_purge_map &map,
+					 const gimple *use_stmt);
+
+  void add_to_worklist (const function_point &point,
+			auto_vec<function_point> *worklist,
+			logger *logger);
+
+  void process_point (const function_point &point,
+		      auto_vec<function_point> *worklist,
+		      const state_purge_map &map);
+
+  typedef hash_set<function_point> point_set_t;
+  point_set_t m_points_needing_name;
+  tree m_name;
+  function *m_fun;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Subclass of dot_annotator for use by -fdump-analyzer-state-purge.
+   Annotate the .dot output with state-purge information.  */
+
+class state_purge_annotator : public dot_annotator
+{
+public:
+  state_purge_annotator (const state_purge_map *map) : m_map (map) {}
+
+  void add_node_annotations (pretty_printer *pp, const supernode &n)
+    const FINAL OVERRIDE;
+
+  void add_stmt_annotations (pretty_printer *pp,const gimple *stmt)
+    const FINAL OVERRIDE;
+
+private:
+  const state_purge_map *m_map;
+};
+
+#endif /* GCC_ANALYZER_STATE_PURGE_H */
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 45/49] analyzer: new files: engine.{cc|h}
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (46 preceding siblings ...)
  2019-11-16  1:21 ` [PATCH 25/49] analyzer: new files: graphviz.{cc|h} David Malcolm
@ 2019-11-16  1:21 ` David Malcolm
  2019-11-16  1:23 ` [PATCH 20/49] analyzer: new builtins David Malcolm
                   ` (2 subsequent siblings)
  50 siblings, 0 replies; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:21 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

This patch adds the core analysis code, which explores "interesting"
interprocedual paths in the code, updating state machines to check
for API misuses, and issuing diagnostics for such misuses.

gcc/ChangeLog:
	* analyzer/engine.cc: New file.
	* analyzer/engine.h: New file.
---
 gcc/analyzer/engine.cc | 3416 ++++++++++++++++++++++++++++++++++++++++++++++++
 gcc/analyzer/engine.h  |   26 +
 2 files changed, 3442 insertions(+)
 create mode 100644 gcc/analyzer/engine.cc
 create mode 100644 gcc/analyzer/engine.h

diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
new file mode 100644
index 0000000..34941d2
--- /dev/null
+++ b/gcc/analyzer/engine.cc
@@ -0,0 +1,3416 @@
+/* The analysis "engine".
+   Copyright (C) 2019 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 "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "params.h"
+#include "gcc-rich-location.h"
+#include "analyzer/exploded-graph.h"
+#include "analyzer/analysis-plan.h"
+#include "analyzer/state-purge.h"
+
+/* For an overview, see gcc/doc/analyzer.texi.  */
+
+static int readability_comparator (const void *p1, const void *p2);
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class impl_region_model_context : public region_model_context, public log_user.  */
+
+impl_region_model_context::
+impl_region_model_context (exploded_graph &eg,
+			   const exploded_node *enode_for_diag,
+			   const program_state *old_state,
+			   program_state *new_state,
+			   state_change *change,
+			   const gimple *stmt,
+			   stmt_finder *stmt_finder)
+: log_user (eg.get_logger ()), m_eg (&eg),
+  m_enode_for_diag (enode_for_diag),
+  m_old_state (old_state),
+  m_new_state (new_state),
+  m_change (change),
+  m_stmt (stmt),
+  m_stmt_finder (stmt_finder),
+  m_ext_state (eg.get_ext_state ())
+{
+}
+
+impl_region_model_context::
+impl_region_model_context (program_state *state,
+			   state_change *change,
+			   const extrinsic_state &ext_state)
+: log_user (NULL), m_eg (NULL), m_enode_for_diag (NULL),
+  m_old_state (NULL),
+  m_new_state (state),
+  m_change (change),
+  m_stmt (NULL),
+  m_stmt_finder (NULL),
+  m_ext_state (ext_state)
+{
+}
+
+void
+impl_region_model_context::warn (pending_diagnostic *d)
+{
+  LOG_FUNC (get_logger ());
+  if (m_eg)
+    m_eg->get_diagnostic_manager ().add_diagnostic
+      (m_enode_for_diag, m_enode_for_diag->get_supernode (),
+       m_stmt, m_stmt_finder, d);
+}
+
+void
+impl_region_model_context::remap_svalue_ids (const svalue_id_map &map)
+{
+  m_new_state->remap_svalue_ids (map);
+  if (m_change)
+    m_change->remap_svalue_ids (map);
+}
+
+int
+impl_region_model_context::on_svalue_purge (svalue_id first_unused_sid,
+					    const svalue_id_map &map)
+{
+  int total = 0;
+  int sm_idx;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (m_new_state->m_checker_states, sm_idx, smap)
+    {
+      const state_machine &sm = m_ext_state.get_sm (sm_idx);
+      total += smap->on_svalue_purge (sm, sm_idx, first_unused_sid,
+				      map, this);
+    }
+  if (m_change)
+    total += m_change->on_svalue_purge (first_unused_sid);
+  return total;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class setjmp_svalue : public svalue.  */
+
+/* Compare the fields of this setjmp_svalue with OTHER, returning true
+   if they are equal.
+   For use by svalue::operator==.  */
+
+bool
+setjmp_svalue::compare_fields (const setjmp_svalue &other) const
+{
+  return m_enode == other.m_enode;
+}
+
+/* Implementation of svalue::add_to_hash vfunc for setjmp_svalue.  */
+
+void
+setjmp_svalue::add_to_hash (inchash::hash &hstate) const
+{
+  hstate.add_int (m_enode->m_index);
+}
+
+/* Get the index of the stored exploded_node.  */
+
+int
+setjmp_svalue::get_index () const
+{
+  return m_enode->m_index;
+}
+
+/* Implementation of svalue::print_details vfunc for setjmp_svalue.  */
+
+void
+setjmp_svalue::print_details (const region_model &model ATTRIBUTE_UNUSED,
+			      svalue_id this_sid ATTRIBUTE_UNUSED,
+			      pretty_printer *pp) const
+{
+  pp_printf (pp, "setjmp: EN: %i", m_enode->m_index);
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Concrete implementation of sm_context, wiring it up to the rest of this
+   file.  */
+
+class impl_sm_context : public sm_context, public log_user
+{
+public:
+  impl_sm_context (exploded_graph &eg,
+		   int sm_idx,
+		   const state_machine &sm,
+		   const exploded_node *enode_for_diag,
+		   const program_state *old_state,
+		   program_state *new_state,
+		   state_change *change,
+		   const sm_state_map *old_smap,
+		   sm_state_map *new_smap,
+		   stmt_finder *stmt_finder = NULL)
+  : sm_context (sm_idx, sm),
+    log_user (eg.get_logger ()),
+    m_eg (eg), m_enode_for_diag (enode_for_diag),
+    m_old_state (old_state), m_new_state (new_state),
+    m_change (change),
+    m_old_smap (old_smap), m_new_smap (new_smap),
+    m_stmt_finder (stmt_finder)
+  {
+  }
+
+  void on_transition (const supernode *node  ATTRIBUTE_UNUSED,
+		      const gimple *stmt  ATTRIBUTE_UNUSED,
+		      tree var,
+		      state_machine::state_t from,
+		      state_machine::state_t to,
+		      tree origin) FINAL OVERRIDE
+  {
+    LOG_FUNC (get_logger ());
+    impl_region_model_context old_ctxt
+      (m_eg, m_enode_for_diag, NULL, NULL/*m_enode->get_state ()*/,
+       m_change, stmt);
+    svalue_id var_old_sid
+      = m_old_state->m_region_model->get_rvalue (var, &old_ctxt);
+
+    impl_region_model_context new_ctxt (m_eg, m_enode_for_diag,
+					m_old_state, m_new_state,
+					m_change, NULL);
+    svalue_id var_new_sid
+      = m_new_state->m_region_model->get_rvalue (var, &new_ctxt);
+    svalue_id origin_new_sid
+      = m_new_state->m_region_model->get_rvalue (origin, &new_ctxt);
+
+    state_machine::state_t current = m_old_smap->get_state (var_old_sid);
+    if (current == from)
+      {
+	if (get_logger ())
+	  log ("%s: state transition of %qE: %s -> %s",
+	       m_sm.get_name (),
+	       var,
+	       m_sm.get_state_name (from),
+	       m_sm.get_state_name (to));
+	m_new_smap->set_state (m_new_state->m_region_model, var_new_sid,
+			       to, origin_new_sid);
+	if (m_change)
+	  m_change->add_sm_change (m_sm_idx, var_new_sid, from, to);
+      }
+  }
+
+  void warn_for_state (const supernode *snode, const gimple *stmt,
+		       tree var, state_machine::state_t state,
+		       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, m_change, NULL);
+    svalue_id var_old_sid
+      = m_old_state->m_region_model->get_rvalue (var, &old_ctxt);
+    state_machine::state_t current = m_old_smap->get_state (var_old_sid);
+    if (state == current)
+      {
+	m_eg.get_diagnostic_manager ().add_diagnostic
+	  (&m_sm, m_enode_for_diag, snode, stmt, m_stmt_finder,
+	   var, state, d);
+      }
+    else
+      delete d;
+  }
+
+  /* Hook for picking more readable trees for SSA names of temporaries,
+     so that rather than e.g.
+       "double-free of '<unknown>'"
+     we can print:
+       "double-free of 'inbuf.data'".  */
+
+  tree get_readable_tree (tree expr) FINAL OVERRIDE
+  {
+    /* Only for SSA_NAMEs of temporaries; otherwise, return EXPR, as it's
+       likely to be the least surprising tree to report.  */
+    if (TREE_CODE (expr) != SSA_NAME)
+      return expr;
+    if (SSA_NAME_VAR (expr) != NULL)
+      return expr;
+
+    gcc_assert (m_new_state);
+    svalue_id sid = m_new_state->m_region_model->get_rvalue (expr, NULL);
+    /* Find trees for all regions storing the value.  */
+    auto_vec<path_var> pvs;
+    m_new_state->m_region_model->get_path_vars_for_svalue (sid, &pvs);
+    if (pvs.length () < 1)
+      return expr;
+    /* Pick the "best" such tree.  */
+    // TODO: should we also consider (and consolidate) equiv classes?
+    pvs.qsort (readability_comparator);
+    return pvs[0].m_tree;
+  }
+
+  exploded_graph &m_eg;
+  const exploded_node *m_enode_for_diag;
+  const program_state *m_old_state;
+  program_state *m_new_state;
+  state_change *m_change;
+  const sm_state_map *m_old_smap;
+  sm_state_map *m_new_smap;
+  stmt_finder *m_stmt_finder;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Subclass of stmt_finder for finding the best stmt to report the leak at,
+   given the emission path.  */
+
+class leak_stmt_finder : public stmt_finder
+{
+public:
+  leak_stmt_finder (const exploded_graph &eg, tree var)
+  : m_eg (eg), m_var (var) {}
+
+  stmt_finder *clone () const FINAL OVERRIDE
+  {
+    return new leak_stmt_finder (m_eg, m_var);
+  }
+
+  const gimple *find_stmt (const exploded_path &epath)
+    FINAL OVERRIDE
+  {
+    LOG_FUNC (m_eg.get_logger ());
+
+    if (TREE_CODE (m_var) == SSA_NAME)
+      {
+	/* Locate the final write to this SSA name in the path.  */
+	const gimple *def_stmt = SSA_NAME_DEF_STMT (m_var);
+
+	int idx_of_def_stmt;
+	bool found = epath.find_stmt_backwards (def_stmt, &idx_of_def_stmt);
+	if (!found)
+	  goto not_found;
+
+	/* What was the next write to the underlying var
+	   after the SSA name was set? (if any).  */
+
+	for (unsigned idx = idx_of_def_stmt + 1;
+	     idx < epath.m_edges.length ();
+	     ++idx)
+	  {
+	    const exploded_edge *eedge = epath.m_edges[idx];
+	    if (m_eg.get_logger ())
+	      m_eg.log ("eedge[%i]: EN %i -> EN %i",
+			idx,
+			eedge->m_src->m_index,
+			eedge->m_dest->m_index);
+	    const exploded_node *dst_node = eedge->m_dest;
+	    const program_point &dst_point = dst_node->get_point ();
+	    const gimple *stmt = dst_point.get_stmt ();
+	    if (!stmt)
+	      continue;
+	    if (const gassign *assign = dyn_cast <const gassign *> (stmt))
+	      {
+		tree lhs = gimple_assign_lhs (assign);
+		if (TREE_CODE (lhs) == SSA_NAME
+		    && SSA_NAME_VAR (lhs) == SSA_NAME_VAR (m_var))
+		  return assign;
+	      }
+	  }
+      }
+
+  not_found:
+
+    /* Look backwards for the first statement with a location.  */
+    int i;
+    const exploded_edge *eedge;
+    FOR_EACH_VEC_ELT_REVERSE (epath.m_edges, i, eedge)
+      {
+	if (m_eg.get_logger ())
+	  m_eg.log ("eedge[%i]: EN %i -> EN %i",
+		    i,
+		    eedge->m_src->m_index,
+		    eedge->m_dest->m_index);
+	const exploded_node *dst_node = eedge->m_dest;
+	const program_point &dst_point = dst_node->get_point ();
+	const gimple *stmt = dst_point.get_stmt ();
+	if (stmt)
+	  if (stmt->location != UNKNOWN_LOCATION)
+	    return stmt;
+      }
+
+    gcc_unreachable ();
+    return NULL;
+  }
+
+private:
+  const exploded_graph &m_eg;
+  tree m_var;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* A measurement of how good EXPR is for presenting to the user, so
+   that e.g. we can say prefer printing
+     "leak of 'tmp.m_ptr'"
+   over:
+     "leak of '<unknown>'".  */
+
+static int
+readability (const_tree expr)
+{
+  gcc_assert (expr);
+  switch (TREE_CODE (expr))
+    {
+    case COMPONENT_REF:
+    case MEM_REF:
+      /* Impose a slight readability penalty relative to that of
+	 operand 0.  */
+      return readability (TREE_OPERAND (expr, 0)) - 1;
+
+    case SSA_NAME:
+      {
+	if (tree var = SSA_NAME_VAR (expr))
+	  return readability (var);
+	/* Avoid printing '<unknown>' for SSA names for temporaries.  */
+	return -1;
+      }
+      break;
+
+    case VAR_DECL:
+      /* Arbitrarily-chosen "high readability" value.  */
+      return 256;
+
+    default:
+      return 0;
+    }
+
+  return 0;
+}
+
+/* A qsort comparator for trees to sort them into most user-readable to
+   least user-readable.  */
+
+static int
+readability_comparator (const void *p1, const void *p2)
+{
+  path_var pv1 = *(path_var const *)p1;
+  path_var pv2 = *(path_var const *)p2;
+
+  /* TODO: should we consider stack depths?  */
+  int r1 = readability (pv1.m_tree);
+  int r2 = readability (pv2.m_tree);
+
+  return r2 - r1;
+}
+
+/* Create an sm_context and use it to call SM's on_leak vfunc, so that
+   it can potentially complain about a leak of DST_SID (in a new region_model)
+   in the given STATE, where MAP can be used to map SID back to an "old"
+   region_model.  */
+
+void
+impl_region_model_context::on_state_leak (const state_machine &sm,
+					  int sm_idx,
+					  svalue_id dst_sid,
+					  svalue_id first_unused_sid,
+					  const svalue_id_map &map,
+					  state_machine::state_t state)
+{
+  LOG_SCOPE (get_logger ());
+  if (get_logger ())
+    log ("considering leak of sv%i", dst_sid.as_int ());
+
+  if (!m_eg)
+    return;
+
+  /* m_old_state also needs to be non-NULL so that the sm_ctxt can look
+     up the old state of the sid.  */
+  gcc_assert (m_old_state);
+
+  /* Don't report on sid leaking if it's equal to one of the used sids.
+     For example, given:
+       some_non_trivial_expression = malloc (sizeof (struct foo));
+     we have:
+       _1 = malloc;                         (void *)
+       some_non_trivial_expression = _1;    (struct foo *)
+     and at leak-detection time we may have:
+       sv5: {type: 'struct foo *', &r3}  (used)
+       sv6: {type: 'void *', &r3}         (unused)
+     where both point to the same region.  We don't want to report a
+     leak of sv6, so we reject the report due to its equality with sv5.  */
+  gcc_assert (m_new_state);
+  gcc_assert (!first_unused_sid.null_p ());
+  for (int i = 0; i < first_unused_sid.as_int (); i++)
+    {
+      svalue_id used_sid = svalue_id::from_int (i);
+
+      /* Use the "_without_cm" form of eval_condition, since
+	 we're half-way through purging - we don't want to introduce new
+	 equivalence classes into the constraint_manager for "sid" and
+	 for each of the used_sids.  */
+      const region_model &rm = *m_new_state->m_region_model;
+      tristate eq = rm.eval_condition_without_cm (dst_sid, EQ_EXPR, used_sid);
+      if (eq.is_true ())
+	{
+	  log ("rejecting leak of sv%i due to equality with sv%i",
+	       dst_sid.as_int (), used_sid.as_int ());
+	  return;
+	}
+    }
+
+  /* SID has leaked within the new state: no regions use it.
+     We need to convert it back to a tree, but since no regions use it, we
+     have to use MAP to convert it back to an svalue_id within the old state.
+     We can then look that svalue_id up to locate regions and thus tree(s)
+     that use it.  */
+
+  svalue_id old_sid = map.get_src_for_dst (dst_sid);
+
+  auto_vec<path_var> leaked_pvs;
+  m_old_state->m_region_model->get_path_vars_for_svalue (old_sid, &leaked_pvs);
+
+  if (leaked_pvs.length () < 1)
+    return;
+
+  /* Find "best" leaked tree.
+     Sort the leaks into most human-readable first, through
+     to least user-readable.  Given that we only emit one
+     leak per EC, this ought to ensure that we pick the most
+     user-readable description of each leaking EC.
+     This assumes that all vars in the EC have the same state.  */
+  leaked_pvs.qsort (readability_comparator);
+
+  tree leaked_tree = leaked_pvs[0].m_tree;
+  if (get_logger ())
+    log ("best leaked_tree: %qE", leaked_tree);
+
+  leak_stmt_finder stmt_finder (*m_eg, leaked_tree);
+  impl_sm_context sm_ctxt (*m_eg, sm_idx, sm, m_enode_for_diag,
+			   m_old_state, m_new_state,
+			   m_change,
+			   m_old_state->m_checker_states[sm_idx],
+			   m_new_state->m_checker_states[sm_idx],
+			   &stmt_finder);
+  gcc_assert (m_enode_for_diag);
+
+  /* Don't complain about leaks when returning from "main".  */
+  if (m_enode_for_diag->get_supernode ()
+      && m_enode_for_diag->get_supernode ()->return_p ())
+    {
+      tree fndecl = m_enode_for_diag->get_function ()->decl;
+      if (0 == strcmp (IDENTIFIER_POINTER (DECL_NAME (fndecl)), "main"))
+	{
+	  log ("not reporting leak from main");
+	  return;
+	}
+    }
+
+  sm.on_leak (&sm_ctxt, m_enode_for_diag->get_supernode (), m_stmt,
+	      leaked_tree, state);
+}
+
+/* Implementation of region_model_context::on_inherited_svalue vfunc
+   for impl_region_model_context.
+   Notify all checkers that CHILD_SID has been created from PARENT_SID,
+   so that those state machines that inherit state can propagate the state
+   from parent to child.  */
+
+void
+impl_region_model_context::on_inherited_svalue (svalue_id parent_sid,
+						svalue_id child_sid)
+{
+  if (!m_new_state)
+    return;
+
+  int sm_idx;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (m_new_state->m_checker_states, sm_idx, smap)
+    {
+      const state_machine &sm = m_ext_state.get_sm (sm_idx);
+      if (sm.inherited_state_p ())
+	smap->on_inherited_svalue (parent_sid, child_sid);
+    }
+}
+
+/* Implementation of region_model_context::on_cast vfunc
+   for impl_region_model_context.
+   Notify all checkers that DST_SID is a cast of SRC_SID, so that sm-state
+   can be propagated from src to dst.  */
+
+void
+impl_region_model_context::on_cast (svalue_id src_sid,
+				    svalue_id dst_sid)
+{
+  if (!m_new_state)
+    return;
+
+  int sm_idx;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (m_new_state->m_checker_states, sm_idx, smap)
+    smap->on_cast (src_sid, dst_sid);
+}
+
+/* Implementation of region_model_context::on_condition vfunc.
+   Notify all state machines about the condition, which could lead to
+   state transitions.  */
+
+void
+impl_region_model_context::on_condition (tree lhs, enum tree_code op, tree rhs)
+{
+  int sm_idx;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (m_new_state->m_checker_states, sm_idx, smap)
+    {
+      const state_machine &sm = m_ext_state.get_sm (sm_idx);
+      impl_sm_context sm_ctxt (*m_eg, sm_idx, sm, m_enode_for_diag,
+			       m_old_state, m_new_state,
+			       m_change,
+			       m_old_state->m_checker_states[sm_idx],
+			       m_new_state->m_checker_states[sm_idx]);
+      sm.on_condition (&sm_ctxt,
+		       m_enode_for_diag->get_supernode (), m_stmt,
+		       lhs, op, rhs);
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Subroutine of print_enode_indices: print a run of indices from START_IDX
+   to END_IDX to PP, using and updating *FIRST_RUN.  */
+
+static void
+print_run (pretty_printer *pp, int start_idx, int end_idx,
+	   bool *first_run)
+{
+  if (!(*first_run))
+    pp_string (pp, ", ");
+  *first_run = false;
+  if (start_idx == end_idx)
+    pp_printf (pp, "EN: %i", start_idx);
+  else
+    pp_printf (pp, "EN: %i-%i", start_idx, end_idx);
+}
+
+/* Print the indices within ENODES to PP, collecting them as
+   runs/singletons e.g. "EN: 4-7, EN: 20-23, EN: 42".  */
+
+static void
+print_enode_indices (pretty_printer *pp,
+		     const auto_vec<exploded_node *> &enodes)
+{
+  int cur_start_idx = -1;
+  int cur_finish_idx = -1;
+  bool first_run = true;
+  unsigned i;
+  exploded_node *enode;
+  FOR_EACH_VEC_ELT (enodes, i, enode)
+    {
+      if (cur_start_idx == -1)
+	{
+	  gcc_assert (cur_finish_idx == -1);
+	  cur_start_idx = cur_finish_idx = enode->m_index;
+	}
+      else
+	{
+	  if (enode->m_index == cur_finish_idx + 1)
+	    /* Continuation of a run.  */
+	    cur_finish_idx = enode->m_index;
+	  else
+	    {
+	      /* Finish existing run, start a new one.  */
+	      gcc_assert (cur_start_idx >= 0);
+	      gcc_assert (cur_finish_idx >= 0);
+	      print_run (pp, cur_start_idx, cur_finish_idx,
+			 &first_run);
+	      cur_start_idx = cur_finish_idx = enode->m_index;
+	    }
+	}
+    }
+  /* Finish any existing run.  */
+  if (cur_start_idx >= 0)
+    {
+      gcc_assert (cur_finish_idx >= 0);
+      print_run (pp, cur_start_idx, cur_finish_idx,
+		 &first_run);
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* For use by dump_dot, get a value for the .dot "fillcolor" attribute.
+   Colorize by sm-state, to make it easier to see how sm-state propagates
+   through the exploded_graph.  */
+
+const char *
+exploded_node::get_dot_fillcolor () const
+{
+  const program_state &state = get_state ();
+
+  /* We want to be able to easily distinguish the no-sm-state case,
+     and to be able to distinguish cases where there's a single state
+     from each other.
+
+     Sum the sm_states, and use the result to choose from a table,
+     modulo table-size, special-casing the "no sm-state" case.   */
+  int total_sm_state = 0;
+  int i;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (state.m_checker_states, i, smap)
+    for (sm_state_map::iterator_t iter = smap->begin ();
+	 iter != smap->end ();
+	 ++iter)
+      total_sm_state += (*iter).second.m_state;
+
+  if (total_sm_state > 0)
+    {
+      /* An arbitrarily-picked collection of light colors.  */
+      const char * const colors[]
+	= {"azure", "coral", "cornsilk", "lightblue", "yellow"};
+      const int num_colors = sizeof (colors) / sizeof (colors[0]);
+      return colors[total_sm_state % num_colors];
+    }
+  else
+    /* No sm-state.   */
+    return "lightgrey";
+}
+
+/* Implementation of dnode::dump_dot vfunc for exploded_node.  */
+
+void
+exploded_node::dump_dot (graphviz_out *gv, const dump_args_t &args) const
+{
+  pretty_printer *pp = gv->get_pp ();
+
+  dump_dot_id (pp);
+  pp_printf (pp, " [shape=%s,style=filled,fillcolor=%s,label=\"",
+	     "record",
+	     get_dot_fillcolor ());
+  pp_left_brace (pp);
+  pp_write_text_to_stream (pp);
+
+  pp_printf (pp, "EN: %i", m_index);
+  pp_newline (pp);
+
+  format f (true);
+  m_ps.m_point.print (pp, f);
+  pp_newline (pp);
+
+  const extrinsic_state &ext_state = args.m_eg.get_ext_state ();
+  m_ps.m_state.dump_to_pp (ext_state, true, pp);
+  pp_newline (pp);
+
+  {
+    int i;
+    sm_state_map *smap;
+    FOR_EACH_VEC_ELT (m_ps.m_state.m_checker_states, i, smap)
+      {
+	if (!smap->is_empty_p ())
+	  {
+	    pp_printf (pp, "%s: ", ext_state.get_name (i));
+	    smap->print (ext_state.get_sm (i), pp);
+	    pp_newline (pp);
+	  }
+      }
+  }
+
+  pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
+
+  pp_right_brace (pp);
+  pp_string (pp, "\"];\n\n");
+  pp_flush (pp);
+}
+
+/* Dump this to PP in a form suitable for use as an id in .dot output.  */
+
+void
+exploded_node::dump_dot_id (pretty_printer *pp) const
+{
+  pp_printf (pp, "exploded_node_%i", m_index);
+}
+
+/* Dump a multiline representation of this node to PP.  */
+
+void
+exploded_node::dump_to_pp (pretty_printer *pp,
+			   const extrinsic_state &ext_state) const
+{
+  pp_printf (pp, "EN: %i", m_index);
+  pp_newline (pp);
+
+  format f (true);
+  m_ps.m_point.print (pp, f);
+  pp_newline (pp);
+
+  m_ps.m_state.dump_to_pp (ext_state, false, pp);
+  pp_newline (pp);
+}
+
+/* Dump a multiline representation of this node to FILE.  */
+
+void
+exploded_node::dump (FILE *fp,
+		     const extrinsic_state &ext_state) const
+{
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  pp_show_color (&pp) = pp_show_color (global_dc->printer);
+  pp.buffer->stream = fp;
+  dump_to_pp (&pp, ext_state);
+  pp_flush (&pp);
+}
+
+/* Dump a multiline representation of this node to stderr.  */
+
+DEBUG_FUNCTION void
+exploded_node::dump (const extrinsic_state &ext_state) const
+{
+  dump (stderr, ext_state);
+}
+
+/* Return true if FNDECL has a gimple body.  */
+// TODO: is there a pre-canned way to do this?
+
+static bool
+fndecl_has_gimple_body_p (tree fndecl)
+{
+  if (fndecl == NULL_TREE)
+    return false;
+
+  cgraph_node *n = cgraph_node::get (fndecl);
+  if (!n)
+    return false;
+
+  return n->has_gimple_body_p ();
+}
+
+/* A pending_diagnostic subclass for implementing "__analyzer_dump_path".  */
+
+class dump_path_diagnostic
+  : public pending_diagnostic_subclass<dump_path_diagnostic>
+{
+public:
+  bool emit (rich_location *richloc) FINAL OVERRIDE
+  {
+    inform (richloc, "path");
+    return true;
+  }
+
+  const char *get_kind () const FINAL OVERRIDE { return "dump_path_diagnostic"; }
+
+  bool operator== (const dump_path_diagnostic &) const
+  {
+    return true;
+  }
+};
+
+/* Modify STATE in place, applying the effects of the stmt at this node's
+   point.
+   Return true if there were any sm-state changes.  */
+
+bool
+exploded_node::on_stmt (exploded_graph &eg,
+			const supernode *snode,
+			const gimple *stmt,
+			program_state *state,
+			state_change *change) const
+{
+  /* Preserve the old state.  It is used here for looking
+     up old checker states, for determining state transitions, and
+     also within impl_region_model_context and impl_sm_context for
+     going from tree to svalue_id.  */
+  const program_state old_state (*state);
+
+  impl_region_model_context ctxt (eg, this,
+				  &old_state, state, change,
+				  stmt);
+
+  if (const gassign *assign = dyn_cast <const gassign *> (stmt))
+    state->m_region_model->on_assignment (assign, &ctxt);
+
+  if (const greturn *return_ = dyn_cast <const greturn *> (stmt))
+    state->m_region_model->on_return (return_, &ctxt);
+
+  if (const gcall *call = dyn_cast <const gcall *> (stmt))
+    {
+      /* Debugging/test support.  */
+      if (is_named_call_p (call, "__analyzer_dump", 0))
+	{
+	  /* Handle the builtin "__analyzer_dump" by dumping state
+	     to stderr.  */
+	  dump (eg.get_ext_state ());
+	}
+      else if (is_named_call_p (call, "__analyzer_dump_path", 0))
+	{
+	  /* Handle the builtin "__analyzer_dump_path" by queuing a
+	     diagnostic at this exploded_node.  */
+	  ctxt.warn (new dump_path_diagnostic ());
+	}
+      else if (is_named_call_p (call, "__analyzer_dump_region_model", 0))
+	{
+	  /* Handle the builtin "__analyzer_dump_region_model" by dumping
+	     the region model's state to stderr.  */
+	  state->m_region_model->dump (false);
+	}
+      else if (is_named_call_p (call, "__analyzer_eval", 1))
+	{
+	  /* Handle the builtin "__analyzer_eval" by evaluating the input
+	     and dumping as a dummy warning, so that test cases can use
+	     dg-warning to validate the result (and so unexpected warnings will
+	     lead to DejaGnu failures).  */
+	  tree t_arg = gimple_call_arg (call, 0);
+	  tristate t
+	    = state->m_region_model->eval_condition (t_arg,
+						     NE_EXPR,
+						     integer_zero_node,
+						     &ctxt);
+	  warning_at (call->location, 0, "%s", t.as_string ());
+	}
+      else if (is_named_call_p (call, "__analyzer_break", 0))
+	{
+	  /* Handle the builtin "__analyzer_break" by triggering a
+	     breakpoint.  */
+	  /* TODO: is there a good cross-platform way to do this?  */
+	  raise (SIGINT);
+	}
+      else if (is_setjmp_call_p (stmt))
+	state->m_region_model->on_setjmp (call, this, &ctxt);
+      else if (is_longjmp_call_p (call))
+	{
+	  on_longjmp (eg, call, state, &ctxt);
+	  return true;
+	}
+      else
+	state->m_region_model->on_call_pre (call, &ctxt);
+    }
+
+  bool any_sm_changes = false;
+  int sm_idx;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (old_state.m_checker_states, sm_idx, smap)
+    {
+      const state_machine &sm = eg.get_ext_state ().get_sm (sm_idx);
+      const sm_state_map *old_smap
+	= old_state.m_checker_states[sm_idx];
+      sm_state_map *new_smap = state->m_checker_states[sm_idx];
+      impl_sm_context ctxt (eg, sm_idx, sm, this, &old_state, state,
+			    change,
+			    old_smap, new_smap);
+      /* Allow the state_machine to handle the stmt.  */
+      if (!sm.on_stmt (&ctxt, snode, stmt))
+	{
+	  /* For those stmts that were not handled by the state machine.  */
+	  if (const gcall *call = dyn_cast <const gcall *> (stmt))
+	    {
+	      tree callee_fndecl = gimple_call_fndecl (call);
+	      // TODO: maybe we can be smarter about handling function pointers?
+
+	      if (!fndecl_has_gimple_body_p (callee_fndecl))
+		new_smap->purge_for_unknown_fncall (eg, sm, call, callee_fndecl,
+						    state->m_region_model);
+	    }
+	}
+      if (*old_smap != *new_smap)
+	any_sm_changes = true;
+    }
+
+  if (const gcall *call = dyn_cast <const gcall *> (stmt))
+    state->m_region_model->on_call_post (call, &ctxt);
+
+  return any_sm_changes;
+}
+
+/* Consider the effect of following superedge SUCC from this node.
+
+   Return true if it's feasible to follow the edge, or false
+   if it's infeasible.
+
+   Examples: if it's the "true" branch within
+   a CFG and we know the conditional is false, we know it's infeasible.
+   If it's one of multiple interprocedual "return" edges, then only
+   the edge back to the most recent callsite is feasible.
+
+   Update NEXT_STATE accordingly (e.g. to record that a condition was
+   true or false, or that the NULL-ness of a pointer has been checked,
+   pushing/popping stack frames, etc).
+
+   Update NEXT_POINT accordingly (updating the call string).  */
+
+bool
+exploded_node::on_edge (exploded_graph &eg,
+			const superedge *succ,
+			program_point *next_point,
+			program_state *next_state,
+			state_change *change) const
+{
+  LOG_FUNC (eg.get_logger ());
+
+  if (!next_point->on_edge (eg, succ))
+    return false;
+
+  if (!next_state->on_edge (eg, *this, succ, change))
+    return false;
+
+  return true;
+}
+
+/* Verify that the stack at LONGJMP_POINT is still valid, given a call
+   to "setjmp" at SETJMP_POINT - the stack frame that "setjmp" was
+   called in must still be valid.
+
+   Caveat: this merely checks the call_strings in the points; it doesn't
+   detect the case where a frame returns and is then called again.  */
+
+static bool
+valid_longjmp_stack_p (const program_point &longjmp_point,
+		       const program_point &setjmp_point)
+{
+  const call_string &cs_at_longjmp = longjmp_point.get_call_string ();
+  const call_string &cs_at_setjmp = setjmp_point.get_call_string ();
+
+  if (cs_at_longjmp.length () < cs_at_setjmp.length ())
+    return false;
+
+  /* Check that the call strings match, up to the depth of the
+     setjmp point.  */
+  for (unsigned depth = 0; depth < cs_at_setjmp.length (); depth++)
+    if (cs_at_longjmp[depth] != cs_at_setjmp[depth])
+      return false;
+
+  return true;
+}
+
+/* A pending_diagnostic subclass for complaining about bad longjmps,
+   where the enclosing function of the "setjmp" has returned (and thus
+   the stack frame no longer exists).  */
+
+class stale_jmp_buf : public pending_diagnostic_subclass<dump_path_diagnostic>
+{
+public:
+  stale_jmp_buf (const gcall *setjmp_call, const gcall *longjmp_call)
+  : m_setjmp_call (setjmp_call), m_longjmp_call (longjmp_call)
+  {}
+
+  bool emit (rich_location *richloc) FINAL OVERRIDE
+  {
+    return warning_at
+      (richloc, OPT_Wanalyzer_stale_setjmp_buffer,
+       "%qs called after enclosing function of %qs has returned",
+       "longjmp", "setjmp");
+  }
+
+  const char *get_kind () const FINAL OVERRIDE
+  { return "stale_jmp_buf"; }
+
+  bool operator== (const stale_jmp_buf &other) const
+  {
+    return (m_setjmp_call == other.m_setjmp_call
+	    && m_longjmp_call == other.m_longjmp_call);
+  }
+
+private:
+  const gcall *m_setjmp_call;
+  const gcall *m_longjmp_call;
+};
+
+/* Handle LONGJMP_CALL, a call to "longjmp".
+
+   Attempt to locate where "setjmp" was called on the jmp_buf and build an
+   exploded_node and exploded_edge to it representing a rewind to that frame,
+   handling the various kinds of failure that can occur.  */
+
+void
+exploded_node::on_longjmp (exploded_graph &eg,
+			   const gcall *longjmp_call,
+			   program_state *new_state,
+			   region_model_context *ctxt) const
+{
+  tree buf_ptr = gimple_call_arg (longjmp_call, 0);
+
+  region_model *new_region_model = new_state->m_region_model;
+  region_id buf_rid = new_region_model->deref_rvalue (buf_ptr, ctxt);
+  region *buf = new_region_model->get_region (buf_rid);
+  if (!buf)
+    return;
+
+  svalue_id buf_content_sid
+    = buf->get_value (*new_region_model, false, ctxt);
+  svalue *buf_content_sval = new_region_model->get_svalue (buf_content_sid);
+  if (!buf_content_sval)
+    return;
+  setjmp_svalue *setjmp_sval = buf_content_sval->dyn_cast_setjmp_svalue ();
+  if (!setjmp_sval)
+    return;
+
+  /* Build a custom enode and eedge for rewinding from the longjmp
+     call back to the setjmp.  */
+
+  const exploded_node *enode_origin = setjmp_sval->get_exploded_node ();
+  rewind_info_t rewind_info (enode_origin);
+
+  const gcall *setjmp_call = rewind_info.get_setjmp_call ();
+  const program_point &setjmp_point = rewind_info.get_setjmp_point ();
+
+  const program_point &longjmp_point = get_point ();
+
+  /* Verify that the setjmp's call_stack hasn't been popped.  */
+  if (!valid_longjmp_stack_p (longjmp_point, setjmp_point))
+    {
+      ctxt->warn (new stale_jmp_buf (setjmp_call, longjmp_call));
+      return;
+    }
+
+  gcc_assert (longjmp_point.get_stack_depth ()
+	      >= setjmp_point.get_stack_depth ());
+
+  /* Update the state for use by the destination node.  */
+  new_region_model->on_longjmp (longjmp_call, setjmp_call,
+				setjmp_point.get_stack_depth (), ctxt);
+
+  program_point next_point
+    = program_point::after_supernode (setjmp_point.get_supernode (),
+				      setjmp_point.get_call_string ());
+
+  state_change change;
+  exploded_node *next = eg.get_or_create_node (next_point, *new_state, &change);
+
+  /* Create custom exploded_edge for a longjmp.  */
+  if (next)
+    eg.add_edge (const_cast<exploded_node *> (this), next, NULL,
+		 change,
+		 new rewind_info_t (enode_origin));
+}
+
+/* Subroutine of exploded_graph::process_node for finding the successors
+   of the supernode for a function exit basic block.
+
+   Ensure that pop_frame is called, potentially queuing diagnostics about
+   leaks.  */
+
+void
+exploded_node::detect_leaks (exploded_graph &eg) const
+{
+  LOG_FUNC_1 (eg.get_logger (), "EN: %i", m_index);
+
+  gcc_assert (get_point ().get_supernode ()->return_p ());
+
+  /* If we're not a "top-level" function, do nothing; pop_frame
+     will be called when handling the return superedge.  */
+  if (get_point ().get_stack_depth () > 1)
+    return;
+
+  /* We have a "top-level" function.  */
+  gcc_assert (get_point ().get_stack_depth () == 1);
+
+  const program_state &old_state = get_state ();
+
+  /* Work with a temporary copy of the state: pop the frame, and see
+     what leaks (via purge_unused_svalues).  */
+  program_state new_state (old_state);
+
+  gcc_assert (new_state.m_region_model);
+
+  purge_stats stats;
+  impl_region_model_context ctxt (eg, this,
+				  &old_state, &new_state,
+				  NULL,
+				  get_stmt ());
+  new_state.m_region_model->pop_frame (true, &stats, &ctxt);
+}
+
+/* Dump the successors and predecessors of this enode to OUTF.  */
+
+void
+exploded_node::dump_succs_and_preds (FILE *outf) const
+{
+  unsigned i;
+  exploded_edge *e;
+  {
+    auto_vec<exploded_node *> preds (m_preds.length ());
+    FOR_EACH_VEC_ELT (m_preds, i, e)
+      preds.quick_push (e->m_src);
+    pretty_printer pp;
+    print_enode_indices (&pp, preds);
+    fprintf (outf, "preds: %s\n",
+	     pp_formatted_text (&pp));
+  }
+  {
+    auto_vec<exploded_node *> succs (m_succs.length ());
+    FOR_EACH_VEC_ELT (m_succs, i, e)
+      succs.quick_push (e->m_dest);
+    pretty_printer pp;
+    print_enode_indices (&pp, succs);
+    fprintf (outf, "succs: %s\n",
+	     pp_formatted_text (&pp));
+  }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class exploded_edge : public dedge.  */
+
+/* exploded_edge's ctor.  */
+
+exploded_edge::exploded_edge (exploded_node *src, exploded_node *dest,
+			      const superedge *sedge,
+			      const state_change &change,
+			      rewind_info_t *rewind_info)
+: dedge (src, dest), m_sedge (sedge), m_change (change),
+  m_rewind_info (rewind_info)
+{
+  change.validate (dest->get_state ());
+}
+
+/* exploded_edge's dtor.  */
+
+exploded_edge::~exploded_edge ()
+{
+  delete m_rewind_info;
+}
+
+/* Implementation of dedge::dump_dot vfunc for exploded_edge.
+   Use the label of the underlying superedge, if any.  */
+
+void
+exploded_edge::dump_dot (graphviz_out *gv, const dump_args_t &args) const
+{
+  pretty_printer *pp = gv->get_pp ();
+
+  const char *style = "\"solid,bold\"";
+  const char *color = "black";
+  int weight = 10;
+  const char *constraint = "true";
+
+  if (m_sedge)
+    switch (m_sedge->m_kind)
+      {
+      default:
+	gcc_unreachable ();
+      case SUPEREDGE_CFG_EDGE:
+	break;
+      case SUPEREDGE_CALL:
+	color = "red";
+	//constraint = "false";
+	break;
+      case SUPEREDGE_RETURN:
+	color = "green";
+	//constraint = "false";
+	break;
+      case SUPEREDGE_INTRAPROCEDURAL_CALL:
+	style = "\"dotted\"";
+	break;
+      }
+  if (m_rewind_info)
+    {
+      color = "red";
+      style = "\"dotted\"";
+    }
+
+  m_src->dump_dot_id (pp);
+  pp_string (pp, " -> ");
+  m_dest->dump_dot_id (pp);
+  pp_printf (pp,
+	     (" [style=%s, color=%s, weight=%d, constraint=%s,"
+	      " headlabel=\""),
+	     style, color, weight, constraint);
+
+  if (m_sedge)
+    m_sedge->dump_label_to_pp (pp, false);
+  else if (m_rewind_info)
+    pp_string (pp, "rewind");
+
+  m_change.dump (pp, args.m_eg.get_ext_state ());
+  //pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/false);
+
+  pp_printf (pp, "\"];\n");
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* struct stats.  */
+
+/* stats' ctor.  */
+
+stats::stats (int num_supernodes)
+: m_node_reuse_count (0),
+  m_node_reuse_after_merge_count (0),
+  m_num_supernodes (num_supernodes)
+{
+  for (int i = 0; i < NUM_POINT_KINDS; i++)
+    m_num_nodes[i] = 0;
+}
+
+/* Log these stats in multiline form to LOGGER.  */
+
+void
+stats::log (logger *logger) const
+{
+  gcc_assert (logger);
+  for (int i = 0; i < NUM_POINT_KINDS; i++)
+    logger->log ("m_num_nodes[%s]: %i",
+		 point_kind_to_string (static_cast <enum point_kind> (i)),
+		 m_num_nodes[i]);
+  logger->log ("m_node_reuse_count: %i", m_node_reuse_count);
+  logger->log ("m_node_reuse_after_merge_count: %i",
+	       m_node_reuse_after_merge_count);
+}
+
+/* Dump these stats in multiline form to OUT.  */
+
+void
+stats::dump (FILE *out) const
+{
+  for (int i = 0; i < NUM_POINT_KINDS; i++)
+    fprintf (out, "m_num_nodes[%s]: %i\n",
+	     point_kind_to_string (static_cast <enum point_kind> (i)),
+	     m_num_nodes[i]);
+  fprintf (out, "m_node_reuse_count: %i\n", m_node_reuse_count);
+  fprintf (out, "m_node_reuse_after_merge_count: %i\n",
+	   m_node_reuse_after_merge_count);
+
+  if (m_num_supernodes > 0)
+    fprintf (out, "PK_AFTER_SUPERNODE nodes per supernode: %.2f\n",
+	     (float)m_num_nodes[PK_AFTER_SUPERNODE] / (float)m_num_supernodes);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* strongly_connected_components's ctor.  Tarjan's SCC algorithm.  */
+
+strongly_connected_components::
+strongly_connected_components (const supergraph &sg, logger *logger)
+: m_sg (sg), m_per_node (m_sg.num_nodes ())
+{
+  LOG_SCOPE (logger);
+  auto_client_timevar tv ("computing scc");
+
+  for (int i = 0; i < m_sg.num_nodes (); i++)
+    m_per_node.quick_push (per_node_data ());
+
+  for (int i = 0; i < m_sg.num_nodes (); i++)
+    if (m_per_node[i].m_index == -1)
+      strong_connect (i);
+
+  if (0)
+    dump ();
+}
+
+/* Dump this object to stderr.  */
+
+DEBUG_FUNCTION void
+strongly_connected_components::dump () const
+{
+  for (int i = 0; i < m_sg.num_nodes (); i++)
+    {
+      const per_node_data &v = m_per_node[i];
+      fprintf (stderr, "SN %i: index: %i lowlink: %i on_stack: %i\n",
+	       i, v.m_index, v.m_lowlink, v.m_on_stack);
+    }
+}
+
+/* Subroutine of strongly_connected_components's ctor, part of Tarjan's
+   SCC algorithm.  */
+
+void
+strongly_connected_components::strong_connect (unsigned index)
+{
+  supernode *v_snode = m_sg.get_node_by_index (index);
+
+  /* Set the depth index for v to the smallest unused index.  */
+  per_node_data *v = &m_per_node[index];
+  v->m_index = index;
+  v->m_lowlink = index;
+  m_stack.safe_push (index);
+  v->m_on_stack = true;
+  index++;
+
+  /* Consider successors of v.  */
+  unsigned i;
+  superedge *sedge;
+  FOR_EACH_VEC_ELT (v_snode->m_succs, i, sedge)
+    {
+      supernode *w_snode = sedge->m_dest;
+      per_node_data *w = &m_per_node[w_snode->m_index];
+      if (w->m_index == -1)
+	{
+	  /* Successor w has not yet been visited; recurse on it.  */
+	  strong_connect (w_snode->m_index);
+	  v->m_lowlink = MIN (v->m_lowlink, w->m_lowlink);
+	}
+      else if (w->m_on_stack)
+	{
+	  /* Successor w is in stack S and hence in the current SCC
+	     If w is not on stack, then (v, w) is a cross-edge in the DFS
+	     tree and must be ignored.  */
+	  v->m_lowlink = MIN (v->m_lowlink, w->m_index);
+	}
+    }
+
+  /* If v is a root node, pop the stack and generate an SCC.  */
+
+  if (v->m_lowlink == v->m_index)
+    {
+      per_node_data *w;
+      do {
+	int idx = m_stack.pop ();
+	w = &m_per_node[idx];
+	w->m_on_stack = false;
+      } while (w != v);
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* worklist's ctor.  */
+
+worklist::worklist (const exploded_graph &eg, const analysis_plan &plan)
+: m_eg (eg),
+  m_scc (eg.get_supergraph (), eg.get_logger ()),
+  m_plan (plan),
+  m_queue (key_t (*this, NULL))
+{
+}
+
+/* Return the number of nodes in the worklist.  */
+
+unsigned
+worklist::length () const
+{
+  return m_queue.nodes ();
+}
+
+/* Return the next node in the worklist, removing it.  */
+
+exploded_node *
+worklist::take_next ()
+{
+  return m_queue.extract_min ();
+}
+
+/* Return the next node in the worklist without removing it.  */
+
+exploded_node *
+worklist::peek_next ()
+{
+  return m_queue.min ();
+}
+
+/* Add ENODE to the worklist.  */
+
+void
+worklist::add_node (exploded_node *enode)
+{
+  m_queue.insert (key_t (*this, enode), enode);
+}
+
+/* Comparator for implementing worklist::key_t comparison operators.
+   Return negative if KA is before KB
+   Return positive if KA is after KB
+   Return 0 if they are equal.  */
+
+int
+worklist::key_t::cmp_1 (const worklist::key_t &ka, const worklist::key_t &kb)
+{
+  const program_point &point_a = ka.m_enode->get_point ();
+  const program_point &point_b = kb.m_enode->get_point ();
+  const call_string &call_string_a = point_a.get_call_string ();
+  const call_string &call_string_b = point_b.get_call_string ();
+
+  /* Order empty-callstring points with different functions based on the
+     analysis_plan, so that we generate summaries before they are used.  */
+  if (flag_analyzer_call_summaries
+      && call_string_a.empty_p ()
+      && call_string_b.empty_p ()
+      && point_a.get_function () != NULL
+      && point_b.get_function () != NULL
+      && point_a.get_function () != point_b.get_function ())
+    {
+      return ka.m_worklist.m_plan.cmp_function (point_a.get_function (),
+						point_b.get_function ());
+    }
+
+  /* First, order by SCC.  */
+  int scc_id_a = ka.get_scc_id (ka.m_enode);
+  int scc_id_b = kb.get_scc_id (kb.m_enode);
+  if (scc_id_a != scc_id_b)
+    return scc_id_a - scc_id_b;
+
+  /* If in same SCC, order by supernode index (an arbitrary but stable
+     ordering).  */
+  const supernode *snode_a = ka.m_enode->get_supernode ();
+  const supernode *snode_b = kb.m_enode->get_supernode ();
+  if (snode_a == NULL)
+    {
+      if (snode_b != NULL)
+	/* One is NULL.  */
+	return -1;
+      else
+	/* Both are NULL.  */
+	return 0;
+    }
+  if (snode_b == NULL)
+    /* One is NULL.  */
+    return 1;
+  /* Neither are NULL.  */
+  gcc_assert (snode_a && snode_b);
+  if (snode_a->m_index != snode_b->m_index)
+    return snode_a->m_index - snode_b->m_index;
+
+  gcc_assert (snode_a == snode_b);
+
+  /* Order within supernode via program point.  */
+  int within_snode_cmp
+    = function_point::cmp_within_supernode (point_a.get_function_point (),
+					    point_b.get_function_point ());
+  if (within_snode_cmp)
+    return within_snode_cmp;
+
+  /* The points might vary by callstring; try sorting by callstring.  */
+  int cs_cmp = call_string::cmp (call_string_a, call_string_b);
+  if (cs_cmp)
+    return cs_cmp;
+
+  /* Otherwise, we ought to have the same program_point.  */
+  gcc_assert (point_a == point_b);
+
+  const program_state &state_a = ka.m_enode->get_state ();
+  const program_state &state_b = kb.m_enode->get_state ();
+
+  /* Sort by sm-state, so that identical sm-states are grouped
+     together in the worklist.
+     For now, sort by the hash value (might not be deterministic).  */
+  for (unsigned sm_idx = 0; sm_idx < state_a.m_checker_states.length ();
+       ++sm_idx)
+    {
+      sm_state_map *smap_a = state_a.m_checker_states[sm_idx];
+      sm_state_map *smap_b = state_b.m_checker_states[sm_idx];
+
+      int sm_cmp = smap_a->hash () - smap_b->hash ();
+      if (sm_cmp)
+	return sm_cmp;
+    }
+
+  /* Otherwise, we have two enodes at the same program point but with
+     different states.  We don't have a good total ordering on states,
+     so order them by enode index, so that we have at least have a
+     stable sort.  */
+  return ka.m_enode->m_index - kb.m_enode->m_index;
+}
+
+/* Comparator for implementing worklist::key_t comparison operators.
+   Return negative if KA is before KB
+   Return positive if KA is after KB
+   Return 0 if they are equal.  */
+
+int
+worklist::key_t::cmp (const worklist::key_t &ka, const worklist::key_t &kb)
+{
+  int result = cmp_1 (ka, kb);
+
+  /* Check that the ordering is symmetric  */
+#if CHECKING_P
+  int reversed = cmp_1 (kb, ka);
+  gcc_assert (reversed == -result);
+#endif
+
+  /* We should only have 0 for equal (point, state) pairs.  */
+  gcc_assert (result != 0
+	      || (*ka.m_enode->get_ps_key ()
+		  == *kb.m_enode->get_ps_key ()));
+
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* exploded_graph's ctor.  */
+
+exploded_graph::exploded_graph (const supergraph &sg, logger *logger,
+				const extrinsic_state &ext_state,
+				const state_purge_map *purge_map,
+				const analysis_plan &plan,
+				int verbosity)
+: log_user (logger), m_sg (sg),
+  m_worklist (*this, plan),
+  m_ext_state (ext_state),
+  m_purge_map (purge_map),
+  m_plan (plan),
+  m_diagnostic_manager (logger, verbosity),
+  m_global_stats (m_sg.num_nodes ()),
+  m_functionless_stats (m_sg.num_nodes ()),
+  m_PK_AFTER_SUPERNODE_per_snode (m_sg.num_nodes ())
+{
+  m_origin = get_or_create_node (program_point (function_point (NULL, NULL,
+								0, PK_ORIGIN),
+						call_string ()),
+				 program_state (ext_state), NULL);
+  for (int i = 0; i < m_sg.num_nodes (); i++)
+    m_PK_AFTER_SUPERNODE_per_snode.quick_push (i);
+}
+
+/* exploded_graph's dtor.  */
+
+exploded_graph::~exploded_graph ()
+{
+  for (function_stat_map_t::iterator iter = m_per_function_stats.begin ();
+       iter != m_per_function_stats.end ();
+       ++iter)
+    delete (*iter).second;
+
+  for (point_map_t::iterator iter = m_per_point_data.begin ();
+       iter != m_per_point_data.end ();
+       ++iter)
+    delete (*iter).second;
+}
+
+/* Ensure that there is an exploded_node representing an external call to
+   FUN, adding it to the worklist if creating it.
+
+   Add an edge from the origin exploded_node to the function entrypoint
+   exploded_node.
+
+   Return the exploded_node for the entrypoint to the function.  */
+
+exploded_node *
+exploded_graph::add_function_entry (function *fun)
+{
+  program_point point = program_point::from_function_entry (m_sg, fun);
+  program_state state (m_ext_state);
+  state.m_region_model->push_frame (fun, NULL, NULL);
+
+  exploded_node *enode = get_or_create_node (point, state, NULL);
+  /* We should never fail to add such a node.  */
+  gcc_assert (enode);
+  state_change change;
+  add_edge (m_origin, enode, NULL, change);
+  return enode;
+}
+
+/* Get or create an exploded_node for (POINT, STATE).
+   If a new node is created, it is added to the worklist.
+   If CHANGE is non-NULL, use it to suppress some purging of state,
+   to make generation of state_change_event instances easier.  */
+
+exploded_node *
+exploded_graph::get_or_create_node (const program_point &point,
+				    const program_state &state,
+				    state_change *change)
+{
+  LOG_FUNC (get_logger ());
+  if (get_logger ())
+    {
+      format f (false);
+      pretty_printer *pp = get_logger ()->get_printer ();
+      start_log_line ();
+      pp_string (pp, "point: ");
+      point.print (pp, f);
+      end_log_line ();
+      start_log_line ();
+      pp_string (pp, "state: ");
+      state.dump (m_ext_state, true);
+      end_log_line ();
+    }
+
+  auto_cfun sentinel (point.get_function ());
+
+  state.validate (get_ext_state ());
+
+  //state.dump (get_ext_state ());
+
+  /* Prune state to try to improve the chances of a cache hit,
+     avoiding generating redundant nodes.  */
+  program_state pruned_state = state.prune_for_point (*this, point, change);
+
+  pruned_state.validate (get_ext_state ());
+
+  //pruned_state.dump (get_ext_state ());
+
+  if (get_logger ())
+    {
+      pretty_printer *pp = get_logger ()->get_printer ();
+      start_log_line ();
+      pp_string (pp, "pruned_state: ");
+      pruned_state.dump_to_pp (m_ext_state, true, pp);
+      end_log_line ();
+      pruned_state.m_region_model->dump_to_pp (get_logger_pp (), true);
+    }
+
+  stats *per_fn_stats = get_or_create_function_stats (point.get_function ());
+
+  stats *per_cs_stats
+    = &get_or_create_per_call_string_data (point.get_call_string ())->m_stats;
+
+  point_and_state ps (point, pruned_state);
+  if (exploded_node **slot = m_point_and_state_to_node.get (&ps))
+    {
+      /* An exploded_node for PS already exists.  */
+      log ("reused EN: %i", (*slot)->m_index);
+      m_global_stats.m_node_reuse_count++;
+      per_fn_stats->m_node_reuse_count++;
+      per_cs_stats->m_node_reuse_count++;
+      return *slot;
+    }
+
+  per_program_point_data *per_point_data
+    = get_or_create_per_program_point_data (point);
+
+  /* Consider merging state with another enode at this program_point.  */
+  if (flag_analyzer_state_merge)
+    {
+      exploded_node *existing_enode;
+      unsigned i;
+      FOR_EACH_VEC_ELT (per_point_data->m_enodes, i, existing_enode)
+	{
+	  log ("considering merging with existing EN: %i for point",
+	       existing_enode->m_index);
+	  gcc_assert (existing_enode->get_point () == point);
+	  const program_state &existing_state = existing_enode->get_state ();
+
+	  /* This merges successfully within the loop.  */
+
+	  program_state merged_state (m_ext_state);
+	  if (pruned_state.can_merge_with_p (existing_state, m_ext_state,
+					     &merged_state))
+	    {
+	      log ("merging new state with that of EN: %i",
+		   existing_enode->m_index);
+
+	      /* Try again for a cache hit.  */
+	      ps.set_state (merged_state);
+	      if (exploded_node **slot = m_point_and_state_to_node.get (&ps))
+		{
+		  /* An exploded_node for PS already exists.  */
+		  log ("reused EN: %i", (*slot)->m_index);
+		  m_global_stats.m_node_reuse_after_merge_count++;
+		  per_fn_stats->m_node_reuse_after_merge_count++;
+		  per_cs_stats->m_node_reuse_after_merge_count++;
+		  return *slot;
+		}
+
+	      /* Otherwise, continue, using the merged state in "ps".
+		 Given that merged_state's svalue_ids have no relationship
+		 to those of the input state, and thus to those of CHANGE,
+		 purge any svalue_ids from *CHANGE.  */
+	      if (change)
+		change->on_svalue_purge (svalue_id::from_int (0));
+	    }
+	  else
+	    log ("not merging new state with that of EN: %i",
+		 existing_enode->m_index);
+	}
+    }
+
+  /* Impose a limit on the number of enodes per program point, and
+     simply stop if we exceed it.  */
+  if ((int)per_point_data->m_enodes.length ()
+      > PARAM_VALUE (PARAM_ANALYZER_MAX_ENODES_PER_PROGRAM_POINT))
+    {
+      log ("not creating enode; too many at program point");
+      warning_at (point.get_location (), OPT_Wanalyzer_too_complex,
+		  "terminating analysis for this program point");
+      return NULL;
+    }
+
+  /* An exploded_node for "ps" doesn't already exist; create one.  */
+  exploded_node *node = new exploded_node (ps, m_nodes.length ());
+  add_node (node);
+  m_point_and_state_to_node.put (node->get_ps_key (), node);
+
+  /* Update per-program_point data.  */
+  per_point_data->m_enodes.safe_push (node);
+
+  const enum point_kind node_pk = node->get_point ().get_kind ();
+  m_global_stats.m_num_nodes[node_pk]++;
+  per_fn_stats->m_num_nodes[node_pk]++;
+  per_cs_stats->m_num_nodes[node_pk]++;
+
+  if (node_pk == PK_AFTER_SUPERNODE)
+    m_PK_AFTER_SUPERNODE_per_snode[point.get_supernode ()->m_index]++;
+
+  if (get_logger ())
+    {
+      format f (false);
+      pretty_printer *pp = get_logger ()->get_printer ();
+      log ("created EN: %i", node->m_index);
+      start_log_line ();
+      pp_string (pp, "point: ");
+      point.print (pp, f);
+      end_log_line ();
+      start_log_line ();
+      pp_string (pp, "pruned_state: ");
+      pruned_state.dump_to_pp (m_ext_state, true, pp);
+      end_log_line ();
+    }
+
+  /* Add the new node to the worlist.  */
+  m_worklist.add_node (node);
+  return node;
+}
+
+/* Add an exploded_edge from SRC to DEST, recording its association
+   with SEDGE (which may be NULL), and, if non-NULL, taking ownership
+   of REWIND_INFO.  */
+
+void
+exploded_graph::add_edge (exploded_node *src, exploded_node *dest,
+			  const superedge *sedge,
+			  const state_change &change,
+			  rewind_info_t *rewind_info)
+{
+  exploded_edge *e = new exploded_edge (src, dest, sedge, change, rewind_info);
+  digraph::add_edge (e);
+}
+
+/* Ensure that this graph has per-program_point-data for POINT;
+   borrow a pointer to it.  */
+
+per_program_point_data *
+exploded_graph::
+get_or_create_per_program_point_data (const program_point &point)
+{
+  if (per_program_point_data **slot = m_per_point_data.get (&point))
+    return *slot;
+
+  per_program_point_data *per_point_data = new per_program_point_data (point);
+  m_per_point_data.put (&per_point_data->m_key, per_point_data);
+  return per_point_data;
+}
+
+/* Ensure that this graph has per-call_string-data for CS;
+   borrow a pointer to it.  */
+
+per_call_string_data *
+exploded_graph::get_or_create_per_call_string_data (const call_string &cs)
+{
+  if (per_call_string_data **slot = m_per_call_string_data.get (&cs))
+    return *slot;
+
+  per_call_string_data *data = new per_call_string_data (cs, m_sg.num_nodes ());
+  m_per_call_string_data.put (&data->m_key,
+			      data);
+  return data;
+}
+
+/* Ensure that this graph has per-function-data for FUN;
+   borrow a pointer to it.  */
+
+per_function_data *
+exploded_graph::get_or_create_per_function_data (function *fun)
+{
+  if (per_function_data **slot = m_per_function_data.get (fun))
+    return *slot;
+
+  per_function_data *data = new per_function_data ();
+  m_per_function_data.put (fun, data);
+  return data;
+}
+
+/* Get this graph's per-function-data for FUN if there is any,
+   otherwise NULL.  */
+
+per_function_data *
+exploded_graph::get_per_function_data (function *fun) const
+{
+  if (per_function_data **slot
+        = const_cast <per_function_data_t &> (m_per_function_data).get (fun))
+    return *slot;
+
+  return NULL;
+}
+
+/* Return true if NODE and FUN should be traversed directly, rather than
+   called via other functions.  */
+
+static bool
+toplevel_function_p (cgraph_node *node, function *fun, logger *logger)
+{
+  /* TODO: better logic here
+     e.g. only if more than one caller, and significantly complicated.
+     Perhaps some whole-callgraph analysis to decide if it's worth summarizing
+     an edge, and if so, we need summaries.  */
+  if (flag_analyzer_call_summaries)
+    {
+      int num_call_sites = 0;
+      for (cgraph_edge *edge = node->callers; edge; edge = edge->next_caller)
+	++num_call_sites;
+
+      /* For now, if there's more than one in-edge, and we want call
+	 summaries, do it at the top level so that there's a chance
+	 we'll have a summary when we need one.  */
+      if (num_call_sites > 1)
+	{
+	  if (logger)
+	    logger->log ("traversing %qE (%i call sites)",
+			 fun->decl, num_call_sites);
+	  return true;
+	}
+    }
+
+  if (!TREE_PUBLIC (fun->decl))
+    {
+      if (logger)
+	logger->log ("not traversing %qE (static)", fun->decl);
+      return false;
+    }
+
+  if (logger)
+    logger->log ("traversing %qE (all checks passed)", fun->decl);
+
+  return true;
+}
+
+/* Add initial nodes to EG, with entrypoints for externally-callable
+   functions.  */
+
+void
+exploded_graph::build_initial_worklist ()
+{
+  LOG_SCOPE (get_logger ());
+
+  cgraph_node *node;
+  FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
+  {
+    function *fun = node->get_fun ();
+    if (!toplevel_function_p (node, fun, get_logger ()))
+      continue;
+    exploded_node *enode = add_function_entry (fun);
+    if (get_logger ())
+      log ("created EN %i for %qE entrypoint", enode->m_index, fun->decl);
+  }
+}
+
+/* The main loop of the analysis.
+   Take freshly-created exploded_nodes from the worklist, calling
+   process_node on them to explore the <point, state> graph.
+   Add edges to their successors, potentially creating new successors
+   (which are also added to the worklist).  */
+
+void
+exploded_graph::process_worklist ()
+{
+  LOG_SCOPE (get_logger ());
+  auto_client_timevar tv ("exploded_graph::process_worklist");
+
+  while (m_worklist.length () > 0)
+    {
+      exploded_node *node = m_worklist.take_next ();
+      gcc_assert (node->m_succs.length () == 0
+		  || node == m_origin);
+
+      log ("next to process: EN: %i", node->m_index);
+
+      /* Avoid exponential explosions of nodes by attempting to merge
+	 nodes that are at the same program point and which have
+	 sufficiently similar state.  */
+      if (flag_analyzer_state_merge && node != m_origin)
+	if (exploded_node *node_2 = m_worklist.peek_next ())
+	  {
+	    gcc_assert (node->m_succs.length () == 0);
+	    gcc_assert (node_2->m_succs.length () == 0);
+
+	    gcc_assert (node != node_2);
+
+	    log ("peek worklist: EN: %i", node_2->m_index);
+
+	    if (node->get_point () == node_2->get_point ())
+	      {
+		if (get_logger ())
+		  {
+		    format f (false);
+		    pretty_printer *pp = get_logger ()->get_printer ();
+		    start_log_line ();
+		    get_logger ()->log_partial
+		      ("got potential merge EN: %i and EN: %i at ",
+		       node->m_index, node_2->m_index);
+		    node->get_point ().print (pp, f);
+		    end_log_line ();
+		  }
+
+		const program_state &state = node->get_state ();
+		const program_state &state_2 = node_2->get_state ();
+
+		/* They shouldn't be equal, or we wouldn't have two
+		   separate nodes.  */
+		gcc_assert (state != state_2);
+
+		program_state merged_state (m_ext_state);
+		state_change change;
+		if (state.can_merge_with_p (state_2, m_ext_state,
+					    &merged_state))
+		  {
+		    log ("merging EN: %i and EN: %i",
+			 node->m_index, node_2->m_index);
+
+		    if (merged_state == state)
+		      {
+			/* Then merge node_2 into node by adding an edge.  */
+			add_edge (node_2, node, NULL, change);
+
+			/* Remove node_2 from the worklist.  */
+			m_worklist.take_next ();
+
+			/* Continue processing "node" below.  */
+		      }
+		    else if (merged_state == state_2)
+		      {
+			/* Then merge node into node_2, and leave node_2
+			   in the worklist, to be processed on the next
+			   iteration.  */
+			add_edge (node, node_2, NULL, change);
+			continue;
+		      }
+		    else
+		      {
+			/* We have a merged state that differs from
+			   both state and state_2.  */
+
+			/* Remove node_2 from the worklist.  */
+			m_worklist.take_next ();
+
+			/* Create (or get) an exploded node for the merged
+			   states, adding to the worklist.  */
+			exploded_node *merged_enode
+			  = get_or_create_node (node->get_point (),
+						merged_state, &change);
+			if (merged_enode == NULL)
+			  continue;
+
+			log ("merged EN: %i and EN: %i into EN: %i",
+			     node->m_index, node_2->m_index,
+			     merged_enode->m_index);
+
+			/* "node" and "node_2" have both now been removed
+			   from the worklist; we should not process them.
+
+			   "merged_enode" may be a new node; if so it will be
+			   processed in a subsequent iteration.
+			   Alternatively, "merged_enode" could be an existing
+			   node; one way the latter can
+			   happen is if we end up merging a succession of
+			   similar nodes into one.  */
+
+			/* If merged_node is one of the two we were merging,
+			   add it back to the worklist to ensure it gets
+			   processed.
+
+			   Add edges from the merged nodes to it (but not a
+			   self-edge).  */
+			if (merged_enode == node)
+			  m_worklist.add_node (merged_enode);
+			else
+			  add_edge (node, merged_enode, NULL, change);
+
+			if (merged_enode == node_2)
+			  m_worklist.add_node (merged_enode);
+			else
+			  add_edge (node_2, merged_enode, NULL, change);
+
+			continue;
+		      }
+		  }
+
+		/* TODO: should we attempt more than two nodes,
+		   or just do pairs of nodes?  (and hope that we get
+		   a cascade of mergers).  */
+	      }
+	}
+
+      process_node (node);
+
+      /* Impose a hard limit on the number of exploded nodes, to ensure
+	 that the analysis terminates in the face of pathological state
+	 explosion (or bugs).
+
+	 Specifically, the limit is on the number of PK_AFTER_SUPERNODE
+	 exploded nodes, looking at supernode exit events.
+
+	 We use exit rather than entry since there can be multiple
+	 entry ENs, one per phi; the number of PK_AFTER_SUPERNODE ought
+	 to be equivalent to the number of supernodes multiplied by the
+	 number of states.  */
+      const int limit
+	= m_sg.num_nodes () * PARAM_VALUE (PARAM_ANALYZER_BB_EXPLOSION_FACTOR);
+      if (m_global_stats.m_num_nodes[PK_AFTER_SUPERNODE] > limit)
+	{
+	  log ("bailing out; too many nodes");
+	  warning_at (node->get_point ().get_location (),
+		      OPT_Wanalyzer_too_complex,
+		      "analysis bailed out early"
+		      " (%i 'after-snode' enodes; %i enodes)",
+		      m_global_stats.m_num_nodes[PK_AFTER_SUPERNODE],
+		      m_nodes.length ());
+	  return;
+	}
+    }
+}
+
+/* Return true if STMT must appear at the start of its exploded node, and
+   thus we can't consolidate its effects within a run of other statements,
+   where PREV_STMT was the previous statement.  */
+
+static bool
+stmt_requires_new_enode_p (const gimple *stmt,
+			   const gimple *prev_stmt)
+{
+  /* Stop consolidating at calls to
+     "__analyzer_dump_exploded_nodes", so they always appear at the
+     start of an exploded_node.  */
+  if (const gcall *call = dyn_cast <const gcall *> (stmt))
+    if (is_named_call_p (call, "__analyzer_dump_exploded_nodes",
+			 1))
+      return true;
+
+  /* If we had a PREV_STMT with an unknown location, and this stmt
+     has a known location, then if a state change happens here, it
+     could be consolidated into PREV_STMT, giving us an event with
+     no location.  Ensure that STMT gets its own exploded_node to
+     avoid this.  */
+  if (prev_stmt->location == UNKNOWN_LOCATION
+      && stmt->location != UNKNOWN_LOCATION)
+    return true;
+
+  return false;
+}
+
+/* The core of exploded_graph::process_worklist (the main analysis loop),
+   handling one node in the worklist.
+
+   Get successor <point, state> pairs for NODE, calling get_or_create on
+   them, and adding an exploded_edge to each successors.
+
+   Freshly-created nodes will be added to the worklist.  */
+
+void
+exploded_graph::process_node (exploded_node *node)
+{
+  LOG_FUNC_1 (get_logger (), "EN: %i", node->m_index);
+
+  const program_point &point = node->get_point ();
+
+  /* Update cfun and input_location in case of an ICE: make it easier to
+     track down which source construct we're failing to handle.  */
+  auto_cfun sentinel (node->get_function ());
+  const gimple *stmt = point.get_stmt ();
+  if (stmt)
+    input_location = stmt->location;
+
+  const program_state &state = node->get_state ();
+  if (get_logger ())
+    {
+      pretty_printer *pp = get_logger ()->get_printer ();
+      start_log_line ();
+      pp_string (pp, "point: ");
+      point.print (pp, format (false));
+      pp_string (pp, ", state: ");
+      state.dump_to_pp (m_ext_state, true, pp);
+      end_log_line ();
+    }
+
+  switch (point.get_kind ())
+    {
+    default:
+      gcc_unreachable ();
+    case PK_ORIGIN:
+      /* This node exists to simplify finding the shortest path
+	 to an exploded_node.  */
+      break;
+
+    case PK_BEFORE_SUPERNODE:
+      {
+	program_state next_state (state);
+	state_change change;
+
+	if (point.get_from_edge ())
+	  {
+	    impl_region_model_context ctxt (*this, node,
+					    &state, &next_state, &change,
+					    NULL);
+	    const cfg_superedge *last_cfg_superedge
+	      = point.get_from_edge ()->dyn_cast_cfg_superedge ();
+	    if (last_cfg_superedge)
+	      next_state.m_region_model->update_for_phis
+		(node->get_supernode (),
+		 last_cfg_superedge,
+		 &ctxt);
+	  }
+
+	if (point.get_supernode ()->m_stmts.length () > 0)
+	  {
+	    program_point next_point
+	      = program_point::before_stmt (point.get_supernode (), 0,
+					    point.get_call_string ());
+	    exploded_node *next
+	      = get_or_create_node (next_point, next_state, &change);
+	    if (next)
+	      add_edge (node, next, NULL, change);
+	  }
+	else
+	  {
+	    program_point next_point
+	      = program_point::after_supernode (point.get_supernode (),
+						point.get_call_string ());
+	    exploded_node *next = get_or_create_node (next_point, next_state,
+						      &change);
+	    if (next)
+	      add_edge (node, next, NULL, change);
+	  }
+      }
+      break;
+    case PK_BEFORE_STMT:
+      {
+	/* Determine the effect of a run of one or more statements
+	   within one supernode, generating an edge to the program_point
+	   after the last statement that's processed.
+
+	   Stop iterating statements and thus consolidating into one enode
+	   when:
+	   - reaching the end of the statements in the supernode
+	   - if an sm-state-change occurs (so that it gets its own
+	     exploded_node)
+	   - if "-fanalyzer-fine-grained" is active
+	   - encountering certain statements must appear at the start of
+	   their enode (for which stmt_requires_new_enode_p returns true)
+
+	   Update next_state in-place, to get the result of the one
+	   or more stmts that are processed.  */
+	program_state next_state (state);
+	state_change change;
+	const supernode *snode = point.get_supernode ();
+	unsigned stmt_idx;
+	const gimple *prev_stmt = NULL;
+	for (stmt_idx = point.get_stmt_idx ();
+	     stmt_idx < snode->m_stmts.length ();
+	     stmt_idx++)
+	  {
+	    const gimple *stmt = snode->m_stmts[stmt_idx];
+
+	    if (stmt_idx > point.get_stmt_idx ())
+	      if (stmt_requires_new_enode_p (stmt, prev_stmt))
+		{
+		  stmt_idx--;
+		  break;
+		}
+	    prev_stmt = stmt;
+
+	    /* Process the stmt.  */
+	    bool any_sm_changes
+	      = node->on_stmt (*this, snode, stmt, &next_state, &change);
+
+	    if (any_sm_changes || flag_analyzer_fine_grained)
+	      break;
+	  }
+	unsigned next_idx = stmt_idx + 1;
+	program_point next_point
+	  = (next_idx < point.get_supernode ()->m_stmts.length ()
+	     ? program_point::before_stmt (point.get_supernode (), next_idx,
+					   point.get_call_string ())
+	     : program_point::after_supernode (point.get_supernode (),
+					       point.get_call_string ()));
+	exploded_node *next = get_or_create_node (next_point,
+						  next_state, &change);
+	if (next)
+	  add_edge (node, next, NULL, change);
+      }
+      break;
+    case PK_AFTER_SUPERNODE:
+      {
+	/* If this is an EXIT BB, detect leaks, and potentially
+	   create a function summary.  */
+	if (point.get_supernode ()->return_p ())
+	  {
+	    node->detect_leaks (*this);
+	    if (flag_analyzer_call_summaries
+		&& point.get_call_string ().empty_p ())
+	      {
+		/* TODO: create function summary
+		   There can be more than one; each corresponds to a different
+		   final enode in the function.  */
+		if (get_logger ())
+		  {
+		    pretty_printer *pp = get_logger ()->get_printer ();
+		    start_log_line ();
+		    get_logger ()->log_partial
+		      ("would create function summary for %qE; state: ",
+		       point.get_fndecl ());
+		    state.dump_to_pp (m_ext_state, true, pp);
+		    end_log_line ();
+		  }
+		per_function_data *per_fn_data
+		  = get_or_create_per_function_data (point.get_function ());
+		per_fn_data->add_call_summary (node);
+	      }
+	  }
+	/* Traverse into successors of the supernode.  */
+	int i;
+	superedge *succ;
+	FOR_EACH_VEC_ELT (point.get_supernode ()->m_succs, i, succ)
+	  {
+	    log ("considering SN: %i -> SN: %i",
+		 succ->m_src->m_index, succ->m_dest->m_index);
+
+	    state_change change;
+
+	    program_point next_point
+	      = program_point::before_supernode (succ->m_dest, succ,
+						 point.get_call_string ());
+	    program_state next_state (state);
+
+	    if (!node->on_edge (*this, succ, &next_point, &next_state, &change))
+	      {
+		log ("skipping impossible edge to SN: %i",
+		     succ->m_dest->m_index);
+		continue;
+	      }
+
+	    exploded_node *next = get_or_create_node (next_point, next_state,
+						      &change);
+	    if (next)
+	      add_edge (node, next, succ, change);
+	  }
+      }
+      break;
+    }
+}
+
+/* Ensure that this graph has a stats instance for FN, return it.
+   FN can be NULL, in which case a stats instances is returned covering
+   "functionless" parts of the graph (the origin node).  */
+
+stats *
+exploded_graph::get_or_create_function_stats (function *fn)
+{
+  if (!fn)
+    return &m_functionless_stats;
+
+  if (stats **slot = m_per_function_stats.get (fn))
+    return *slot;
+  else
+    {
+      int num_supernodes = fn ? n_basic_blocks_for_fn (fn) : 0;
+      /* not quite the num supernodes, but nearly.  */
+      stats *new_stats = new stats (num_supernodes);
+      m_per_function_stats.put (fn, new_stats);
+      return new_stats;
+    }
+}
+
+/* Write all stats information to this graph's logger, if any.  */
+
+void
+exploded_graph::log_stats () const
+{
+  if (!get_logger ())
+    return;
+
+  LOG_SCOPE (get_logger ());
+
+  log ("m_sg.num_nodes (): %i", m_sg.num_nodes ());
+  log ("m_nodes.length (): %i", m_nodes.length ());
+  log ("m_edges.length (): %i", m_edges.length ());
+
+  log ("global stats:");
+  m_global_stats.log (get_logger ());
+
+  for (function_stat_map_t::iterator iter = m_per_function_stats.begin ();
+       iter != m_per_function_stats.end ();
+       ++iter)
+    {
+      function *fn = (*iter).first;
+      log_scope s (get_logger (), function_name (fn));
+      (*iter).second->log (get_logger ());
+    }
+}
+
+/* Dump all stats information to OUT.  */
+
+void
+exploded_graph::dump_stats (FILE *out) const
+{
+  fprintf (out, "m_sg.num_nodes (): %i\n", m_sg.num_nodes ());
+  fprintf (out, "m_nodes.length (): %i\n", m_nodes.length ());
+  fprintf (out, "m_edges.length (): %i\n", m_edges.length ());
+
+  fprintf (out, "global stats:\n");
+  m_global_stats.dump (out);
+
+  for (function_stat_map_t::iterator iter = m_per_function_stats.begin ();
+       iter != m_per_function_stats.end ();
+       ++iter)
+    {
+      function *fn = (*iter).first;
+      fprintf (out, "function: %s\n", function_name (fn));
+      (*iter).second->dump (out);
+    }
+
+  fprintf (out, "PK_AFTER_SUPERNODE per supernode:\n");
+  for (unsigned i = 0; i < m_PK_AFTER_SUPERNODE_per_snode.length (); i++)
+    fprintf (out, "  SN %i: %3i\n", i, m_PK_AFTER_SUPERNODE_per_snode[i]);
+}
+
+void
+exploded_graph::dump_states_for_supernode (FILE *out,
+					   const supernode *snode) const
+{
+  fprintf (out, "PK_AFTER_SUPERNODE nodes for SN: %i\n", snode->m_index);
+  int i;
+  exploded_node *enode;
+  int state_idx = 0;
+  FOR_EACH_VEC_ELT (m_nodes, i, enode)
+    {
+      const supernode *iter_snode = enode->get_supernode ();
+      if (enode->get_point ().get_kind () == PK_AFTER_SUPERNODE
+	  && iter_snode == snode)
+	{
+	  pretty_printer pp;
+	  pp_format_decoder (&pp) = default_tree_printer;
+	  enode->get_state ().dump_to_pp (m_ext_state, true, &pp);
+	  fprintf (out, "state %i: EN: %i\n  %s\n",
+		   state_idx++, enode->m_index,
+		   pp_formatted_text (&pp));
+	}
+    }
+  fprintf (out, "#exploded_node for PK_AFTER_SUPERNODE for SN: %i = %i\n",
+	   snode->m_index, state_idx);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Look for the last use of SEARCH_STMT within this path.
+   If found write the edge's index to *OUT_IDX and return true, otherwise
+   return false.  */
+
+bool
+exploded_path::find_stmt_backwards (const gimple *search_stmt,
+				    int *out_idx) const
+{
+  int i;
+  const exploded_edge *eedge;
+  FOR_EACH_VEC_ELT_REVERSE (m_edges, i, eedge)
+    {
+      const exploded_node *dst_node = eedge->m_dest;
+      const program_point &dst_point = dst_node->get_point ();
+      const gimple *stmt = dst_point.get_stmt ();
+      if (stmt == search_stmt)
+	{
+	  *out_idx = i;
+	  return true;
+	}
+    }
+  return false;
+}
+
+/* Get the final exploded_node in this path, which must be non-empty.  */
+
+exploded_node *
+exploded_path::get_final_enode () const
+{
+  gcc_assert (m_edges.length () > 0);
+  return m_edges[m_edges.length () - 1]->m_dest;
+}
+
+/* Check state along this path, returning true if it is feasible.  */
+
+bool
+exploded_path::feasible_p (logger *logger) const
+{
+  LOG_SCOPE (logger);
+
+  /* Traverse the path, updating this model.  */
+  region_model model;
+  for (unsigned i = 0; i < m_edges.length (); i++)
+    {
+      const exploded_edge *eedge = m_edges[i];
+      if (logger)
+	logger->log ("considering edge %i: EN:%i -> EN:%i",
+		     i,
+		     eedge->m_src->m_index,
+		     eedge->m_dest->m_index);
+      const exploded_node &src_enode = *eedge->m_src;
+      const program_point &src_point = src_enode.get_point ();
+      if (logger)
+	{
+	  logger->start_log_line ();
+	  src_point.print (logger->get_printer (), format (false));
+	  logger->end_log_line ();
+	}
+
+      if (const gimple *stmt = src_point.get_stmt ())
+	{
+	  /* Update cfun and input_location in case of ICE: make it easier to
+	     track down which source construct we're failing to handle.  */
+	  auto_cfun sentinel (src_point.get_function ());
+	  input_location = stmt->location;
+
+	  if (const gassign *assign = dyn_cast <const gassign *> (stmt))
+	    model.on_assignment (assign, NULL);
+	  else if (const greturn *return_ = dyn_cast <const greturn *> (stmt))
+	    model.on_return (return_, NULL);
+	}
+
+      const superedge *sedge = eedge->m_sedge;
+      if (sedge)
+	{
+	  if (logger)
+	    logger->log ("  sedge: SN:%i -> SN:%i %s",
+			 sedge->m_src->m_index,
+			 sedge->m_dest->m_index,
+			 sedge->get_description (false));
+
+	  const gimple *last_stmt = src_point.get_supernode ()->get_last_stmt ();
+	  if (!model.maybe_update_for_edge (*sedge, last_stmt, NULL))
+	    {
+	      if (logger)
+		logger->log ("rejecting due to region model");
+	      return false;
+	    }
+	}
+      else
+	{
+	  /* Special-case the initial eedge from the origin node to the
+	     initial function by pushing a frame for it.  */
+	  if (i == 0)
+	    {
+	      gcc_assert (eedge->m_src->m_index == 0);
+	      gcc_assert (src_point.get_kind () == PK_ORIGIN);
+	      gcc_assert (eedge->m_dest->get_point ().get_kind ()
+			  == PK_BEFORE_SUPERNODE);
+	      function *fun = eedge->m_dest->get_function ();
+	      gcc_assert (fun);
+	      model.push_frame (fun, NULL, NULL);
+	      if (logger)
+		logger->log ("  pushing frame for %qD", fun->decl);
+	    }
+	  else if (eedge->m_rewind_info)
+	    {
+	      /* Update state for the special-case of a rewind of a longjmp
+		 to a setjmp (which doesn't have a superedge, but does affect
+		 state).  */
+	      const gimple *last_stmt
+		= src_point.get_supernode ()->get_last_stmt ();
+	      gcc_assert (last_stmt);
+	      const gcall *longjmp_call = as_a <const gcall *> (last_stmt);
+
+	      const program_point &longjmp_point = eedge->m_src->get_point ();
+	      const program_point &setjmp_point = eedge->m_dest->get_point ();
+
+	      gcc_assert (longjmp_point.get_stack_depth ()
+			  >= setjmp_point.get_stack_depth ());
+
+	      model.on_longjmp (longjmp_call,
+				eedge->m_rewind_info->get_setjmp_call (),
+				setjmp_point.get_stack_depth (), NULL);
+	    }
+	}
+
+      /* Handle phi nodes on an edge leaving a PK_BEFORE_SUPERNODE (to
+	 a PK_BEFORE_STMT, or a PK_AFTER_SUPERNODE if no stmts).
+	 This will typically not be associated with a superedge.  */
+      if (src_point.get_from_edge ())
+	{
+	  const cfg_superedge *last_cfg_superedge
+	    = src_point.get_from_edge ()->dyn_cast_cfg_superedge ();
+	  if (last_cfg_superedge)
+	    {
+	      if (logger)
+		logger->log ("  update for phis");
+	      model.update_for_phis (src_enode.get_supernode (),
+				     last_cfg_superedge,
+				     NULL);
+	    }
+	}
+
+      if (logger)
+	{
+	  logger->log ("state after edge %i: EN:%i -> EN:%i",
+		       i,
+		       eedge->m_src->m_index,
+		       eedge->m_dest->m_index);
+	  logger->start_log_line ();
+	  model.dump_to_pp (logger->get_printer (), true);
+	  logger->end_log_line ();
+	}
+    }
+
+  return true;
+}
+
+/* Dump this path in multiline form to PP.  */
+
+void
+exploded_path::dump_to_pp (pretty_printer *pp) const
+{
+  for (unsigned i = 0; i < m_edges.length (); i++)
+    {
+      const exploded_edge *eedge = m_edges[i];
+      pp_printf (pp, "m_edges[%i]: EN %i -> EN %i",
+		 i,
+		 eedge->m_src->m_index,
+		 eedge->m_dest->m_index);
+      pp_newline (pp);
+    }
+}
+
+/* Dump this path in multiline form to FP.  */
+
+void
+exploded_path::dump (FILE *fp) const
+{
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  pp_show_color (&pp) = pp_show_color (global_dc->printer);
+  pp.buffer->stream = fp;
+  dump_to_pp (&pp);
+  pp_flush (&pp);
+}
+
+/* Dump this path in multiline form to stderr.  */
+
+DEBUG_FUNCTION void
+exploded_path::dump () const
+{
+  dump (stderr);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* A family of cluster subclasses for use when generating .dot output for
+   exploded graphs (-fdump-analyzer-exploded-graph), for grouping the
+   enodes into hierarchical boxes.
+
+   All functionless enodes appear in the top-level graph.
+   Every (function, call_string) pair gets its own cluster.  Within that
+   cluster, each supernode gets its own cluster.
+
+   Hence all enodes relating to a particular function with a particular
+   callstring will be be in a cluster together; all enodes for the same
+   function but with a different callstring will be in a different
+   cluster.  */
+
+/* Base class of cluster for clustering exploded_node instances in .dot
+   output, based on various subclass-specific criteria.  */
+
+class exploded_cluster : public cluster<eg_traits>
+{
+};
+
+/* Cluster containing all exploded_node instances for one supernode.  */
+
+class supernode_cluster : public exploded_cluster
+{
+public:
+  supernode_cluster (const supernode *supernode) : m_supernode (supernode) {}
+
+  // TODO: dtor?
+
+  void dump_dot (graphviz_out *gv, const dump_args_t &args) const FINAL OVERRIDE
+  {
+    gv->println ("subgraph \"cluster_supernode_%p\" {",
+		 (const void *)this);
+    gv->indent ();
+    gv->println ("style=\"dashed\";");
+    gv->println ("label=\"SN: %i\";", m_supernode->m_index);
+
+    int i;
+    exploded_node *enode;
+    FOR_EACH_VEC_ELT (m_enodes, i, enode)
+      enode->dump_dot (gv, args);
+
+    /* Terminate subgraph.  */
+    gv->outdent ();
+    gv->println ("}");
+  }
+
+  void add_node (exploded_node *en) FINAL OVERRIDE
+  {
+    m_enodes.safe_push (en);
+  }
+
+private:
+  const supernode *m_supernode;
+  auto_vec <exploded_node *> m_enodes;
+};
+
+/* Cluster containing all supernode_cluster instances for one
+   (function, call_string) pair.  */
+
+class function_call_string_cluster : public exploded_cluster
+{
+public:
+  function_call_string_cluster (function *fun, call_string cs)
+  : m_fun (fun), m_cs (cs) {}
+
+  ~function_call_string_cluster ()
+  {
+    for (map_t::iterator iter = m_map.begin ();
+	 iter != m_map.end ();
+	 ++iter)
+      delete (*iter).second;
+  }
+
+  void dump_dot (graphviz_out *gv, const dump_args_t &args) const FINAL OVERRIDE
+  {
+    const char *funcname = function_name (m_fun);
+
+    gv->println ("subgraph \"cluster_function_%p\" {", (const void *)this);
+    gv->indent ();
+    gv->write_indent ();
+    gv->print ("label=\"call string: ");
+    m_cs.print (gv->get_pp ());
+    gv->print (" function: %s \";", funcname);
+    gv->print ("\n");
+
+    for (map_t::iterator iter = m_map.begin ();
+	 iter != m_map.end ();
+	 ++iter)
+      (*iter).second->dump_dot (gv, args);
+
+    /* Terminate subgraph.  */
+    gv->outdent ();
+    gv->println ("}");
+  }
+
+  void add_node (exploded_node *en) FINAL OVERRIDE
+  {
+    const supernode *supernode = en->get_supernode ();
+    gcc_assert (supernode);
+    supernode_cluster **slot = m_map.get (supernode);
+    if (slot)
+      (*slot)->add_node (en);
+    else
+      {
+	supernode_cluster *child = new supernode_cluster (supernode);
+	m_map.put (supernode, child);
+	child->add_node (en);
+      }
+  }
+
+private:
+  function *m_fun;
+  call_string m_cs;
+  typedef ordered_hash_map<const supernode *, supernode_cluster *> map_t;
+  map_t m_map;
+};
+
+/* Keys for root_cluster.  */
+
+struct function_call_string
+{
+  function_call_string (function *fun, call_string cs)
+  : m_fun (fun), m_cs (cs)
+  {
+    gcc_assert (fun);
+  }
+
+  function *m_fun;
+  call_string m_cs;
+};
+
+template <> struct default_hash_traits<function_call_string>
+: public pod_hash_traits<function_call_string>
+{
+};
+
+template <>
+inline hashval_t
+pod_hash_traits<function_call_string>::hash (value_type v)
+{
+  return pointer_hash <function>::hash (v.m_fun) ^ v.m_cs.hash ();
+}
+
+template <>
+inline bool
+pod_hash_traits<function_call_string>::equal (const value_type &existing,
+					      const value_type &candidate)
+{
+  return existing.m_fun == candidate.m_fun && existing.m_cs == candidate.m_cs;
+}
+template <>
+inline void
+pod_hash_traits<function_call_string>::mark_deleted (value_type &v)
+{
+  v.m_fun = reinterpret_cast<function *> (1);
+}
+template <>
+inline void
+pod_hash_traits<function_call_string>::mark_empty (value_type &v)
+{
+  v.m_fun = reinterpret_cast<function *> (NULL);
+}
+template <>
+inline bool
+pod_hash_traits<function_call_string>::is_deleted (value_type v)
+{
+  return v.m_fun == reinterpret_cast<function *> (1);
+}
+template <>
+inline bool
+pod_hash_traits<function_call_string>::is_empty (value_type v)
+{
+  return v.m_fun == reinterpret_cast<function *> (NULL);
+}
+
+/* Top-level cluster for generating .dot output for exploded graphs,
+   handling the functionless nodes, and grouping the remaining nodes by
+   callstring.  */
+
+class root_cluster : public exploded_cluster
+{
+public:
+  ~root_cluster ()
+  {
+    for (map_t::iterator iter = m_map.begin ();
+	 iter != m_map.end ();
+	 ++iter)
+      delete (*iter).second;
+  }
+
+  void dump_dot (graphviz_out *gv, const dump_args_t &args) const FINAL OVERRIDE
+  {
+    int i;
+    exploded_node *enode;
+    FOR_EACH_VEC_ELT (m_functionless_enodes, i, enode)
+      enode->dump_dot (gv, args);
+
+    for (map_t::iterator iter = m_map.begin ();
+	 iter != m_map.end ();
+	 ++iter)
+      (*iter).second->dump_dot (gv, args);
+  }
+
+  void add_node (exploded_node *en) FINAL OVERRIDE
+  {
+    function *fun = en->get_function ();
+    if (!fun)
+      {
+	m_functionless_enodes.safe_push (en);
+	return;
+      }
+
+    const call_string &cs = en->get_point ().get_call_string ();
+    function_call_string key (fun, cs);
+    function_call_string_cluster **slot = m_map.get (key);
+    if (slot)
+      (*slot)->add_node (en);
+    else
+      {
+	function_call_string_cluster *child
+	  = new function_call_string_cluster (fun, cs);
+	m_map.put (key, child);
+	child->add_node (en);
+      }
+  }
+
+private:
+  /* This can't be an ordered_hash_map, as we can't store vec<call_string>,
+     since it's not a POD; vec<>::quick_push has:
+       *slot = obj;
+     and the slot isn't initialized, so the assignment op dies when cleaning up
+     un-inited *slot (within the truncate call).  */
+  typedef hash_map<function_call_string, function_call_string_cluster *> map_t;
+  map_t m_map;
+
+  /* This should just be the origin exploded_node.  */
+  auto_vec <exploded_node *> m_functionless_enodes;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Subclass of range_label for use within
+   exploded_graph::dump_exploded_nodes for implementing
+   -fdump-analyzer-exploded-nodes: a label for a specific
+   exploded_node.  */
+
+class enode_label : public range_label
+{
+ public:
+  enode_label (const extrinsic_state &ext_state,
+	       exploded_node *enode)
+  : m_ext_state (ext_state), m_enode (enode) {}
+
+  label_text get_text (unsigned) const FINAL OVERRIDE
+  {
+    pretty_printer pp;
+    pp_format_decoder (&pp) = default_tree_printer;
+    m_enode->get_state ().dump_to_pp (m_ext_state, true, &pp);
+    return make_label_text (false, "EN: %i: %s",
+			    m_enode->m_index, pp_formatted_text (&pp));
+  }
+
+private:
+  const extrinsic_state &m_ext_state;
+  exploded_node *m_enode;
+};
+
+/* Postprocessing support for dumping the exploded nodes.
+   Handle -fdump-analyzer-exploded-nodes,
+   -fdump-analyzer-exploded-nodes-2, and the
+   "__analyzer_dump_exploded_nodes" builtin.  */
+
+void
+exploded_graph::dump_exploded_nodes () const
+{
+  // TODO
+  /* Locate calls to __analyzer_dump_exploded_nodes.  */
+  // Print how many egs there are for them?
+  /* Better: log them as we go, and record the exploded nodes
+     in question.  */
+
+  /* Show every enode.  */
+
+  /* Gather them by stmt, so that we can more clearly see the
+     "hotspots" requiring numerous exploded nodes.  */
+
+  /* Alternatively, simply throw them all into one big rich_location
+     and see if the label-printing will sort it out...
+     This requires them all to be in the same source file.  */
+
+  if (flag_dump_analyzer_exploded_nodes)
+    {
+      auto_client_timevar tv ("-fdump-analyzer-exploded-nodes");
+      gcc_rich_location richloc (UNKNOWN_LOCATION);
+      unsigned i;
+      exploded_node *enode;
+      FOR_EACH_VEC_ELT (m_nodes, i, enode)
+	{
+	  if (const gimple *stmt = enode->get_stmt ())
+	    {
+	      if (richloc.get_loc () == UNKNOWN_LOCATION)
+		richloc.set_range (0, stmt->location, SHOW_RANGE_WITH_CARET);
+	      else
+		richloc.add_range (stmt->location,
+				   SHOW_RANGE_WITHOUT_CARET,
+				   new enode_label (m_ext_state, enode));
+	    }
+	}
+      warning_at (&richloc, 0, "%i exploded nodes", m_nodes.length ());
+
+      /* Repeat the warning without all the labels, so that message is visible
+	 (the other one may well have scrolled past the terminal limit).  */
+      warning_at (richloc.get_loc (), 0,
+		  "%i exploded nodes", m_nodes.length ());
+
+      if (m_worklist.length () > 0)
+	warning_at (richloc.get_loc (), 0,
+		    "worklist still contains %i nodes", m_worklist.length ());
+    }
+
+  /* Dump the egraph in textual form to a dump file.  */
+  if (flag_dump_analyzer_exploded_nodes_2)
+    {
+      auto_client_timevar tv ("-fdump-analyzer-exploded-nodes-2");
+      char *filename
+	= concat (dump_base_name, ".eg.txt", NULL);
+      FILE *outf = fopen (filename, "w");
+      if (!outf)
+	error_at (UNKNOWN_LOCATION, "unable to open %qs for writing", filename);
+      free (filename);
+
+      fprintf (outf, "exploded graph for %s\n", dump_base_name);
+      fprintf (outf, "  nodes: %i\n", m_nodes.length ());
+      fprintf (outf, "  edges: %i\n", m_edges.length ());
+
+      unsigned i;
+      exploded_node *enode;
+      FOR_EACH_VEC_ELT (m_nodes, i, enode)
+	{
+	  fprintf (outf, "\nEN %i:\n", enode->m_index);
+	  enode->dump_succs_and_preds (outf);
+	  pretty_printer pp;
+	  enode->get_point ().print (&pp, format (true));
+	  fprintf (outf, "%s\n", pp_formatted_text (&pp));
+	  enode->get_state ().dump_to_file (m_ext_state, false, outf);
+	}
+
+      fclose (outf);
+    }
+
+  /* Dump the egraph in textual form to multiple dump files, one per enode.  */
+  if (flag_dump_analyzer_exploded_nodes_3)
+    {
+      auto_client_timevar tv ("-fdump-analyzer-exploded-nodes-3");
+
+      unsigned i;
+      exploded_node *enode;
+      FOR_EACH_VEC_ELT (m_nodes, i, enode)
+	{
+	  char *filename
+	    = xasprintf ("%s.en-%i.txt", dump_base_name, i);
+	  FILE *outf = fopen (filename, "w");
+	  if (!outf)
+	    error_at (UNKNOWN_LOCATION, "unable to open %qs for writing", filename);
+	  free (filename);
+
+	  fprintf (outf, "EN %i:\n", enode->m_index);
+	  enode->dump_succs_and_preds (outf);
+	  pretty_printer pp;
+	  enode->get_point ().print (&pp, format (true));
+	  fprintf (outf, "%s\n", pp_formatted_text (&pp));
+	  enode->get_state ().dump_to_file (m_ext_state, false, outf);
+
+	  fclose (outf);
+	}
+    }
+
+  /* Emit a warning at any call to "__analyzer_dump_exploded_nodes",
+     giving the number of exploded nodes for "before-stmt", and their
+     IDs.  */
+
+  unsigned i;
+  exploded_node *enode;
+  hash_set<const gimple *> seen;
+  FOR_EACH_VEC_ELT (m_nodes, i, enode)
+    {
+      if (enode->get_point ().get_kind () != PK_BEFORE_STMT)
+	continue;
+
+      if (const gimple *stmt = enode->get_stmt ())
+	if (const gcall *call = dyn_cast <const gcall *> (stmt))
+	  if (is_named_call_p (call, "__analyzer_dump_exploded_nodes", 1))
+	    {
+	      if (seen.contains (stmt))
+		continue;
+
+	      /* This is O(N^2).  */
+	      unsigned j;
+	      auto_vec<exploded_node *> enodes;
+	      exploded_node *other_enode;
+	      FOR_EACH_VEC_ELT (m_nodes, j, other_enode)
+		{
+		  if (other_enode->get_point ().get_kind () != PK_BEFORE_STMT)
+		    continue;
+		  if (other_enode->get_stmt () == stmt)
+		    enodes.safe_push (other_enode);
+		}
+
+	      pretty_printer pp;
+	      print_enode_indices (&pp, enodes);
+
+	      warning_n (stmt->location, 0, enodes.length (),
+			 "%i exploded node: %s",
+			 "%i exploded nodes: %s",
+			 enodes.length (), pp_formatted_text (&pp));
+	      seen.add (stmt);
+
+	      /* If the argument is non-zero, then print all of the states
+		 of the various enodes.  */
+	      tree t_arg = fold (gimple_call_arg (call, 0));
+	      if (TREE_CODE (t_arg) != INTEGER_CST)
+		{
+		  error_at (call->location,
+			    "integer constant required for arg 1");
+		  return;
+		}
+	      int i_arg = TREE_INT_CST_LOW (t_arg);
+	      if (i_arg)
+		{
+		  exploded_node *other_enode;
+		  FOR_EACH_VEC_ELT (enodes, j, other_enode)
+		    {
+		      fprintf (stderr, "%i of %i: EN %i:\n",
+			       j + 1, enodes.length (), other_enode->m_index);
+		      other_enode->dump_succs_and_preds (stderr);
+		      /* Dump state.  */
+		      other_enode->get_state ().dump (m_ext_state, false);
+		    }
+		}
+	    }
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* A collection of classes for visualizing the callgraph in .dot form
+   (as represented in the supergraph).  */
+
+/* Forward decls.  */
+class viz_callgraph_node;
+class viz_callgraph_edge;
+class viz_callgraph;
+class viz_callgraph_cluster;
+
+/* Traits for using "digraph.h" to visualize the callgraph.  */
+
+struct viz_callgraph_traits
+{
+  typedef viz_callgraph_node node_t;
+  typedef viz_callgraph_edge edge_t;
+  typedef viz_callgraph graph_t;
+  struct dump_args_t
+  {
+    dump_args_t (const exploded_graph *eg) : m_eg (eg) {}
+    const exploded_graph *m_eg;
+  };
+  typedef viz_callgraph_cluster cluster_t;
+};
+
+/* Subclass of dnode representing a function within the callgraph.  */
+
+class viz_callgraph_node : public dnode<viz_callgraph_traits>
+{
+  friend class viz_callgraph;
+
+public:
+  viz_callgraph_node (function *fun, int index)
+  : m_fun (fun), m_index (index), m_num_supernodes (0), m_num_superedges (0)
+  {
+    gcc_assert (fun);
+  }
+
+  void dump_dot (graphviz_out *gv, const dump_args_t &args) const FINAL OVERRIDE
+  {
+    pretty_printer *pp = gv->get_pp ();
+
+    dump_dot_id (pp);
+    pp_printf (pp, " [shape=%s,style=filled,fillcolor=%s,label=\"",
+	       "record", "lightgrey");
+    pp_left_brace (pp);
+    pp_write_text_to_stream (pp);
+
+    pp_printf (pp, "VCG: %i: %s", m_index, function_name (m_fun));
+    pp_newline (pp);
+
+    pp_printf (pp, "supernodes: %i\n", m_num_supernodes);
+    pp_newline (pp);
+    pp_printf (pp, "superedges: %i\n", m_num_superedges);
+    pp_newline (pp);
+
+    if (args.m_eg)
+      {
+	unsigned i;
+	exploded_node *enode;
+	unsigned num_enodes = 0;
+	FOR_EACH_VEC_ELT (args.m_eg->m_nodes, i, enode)
+	  {
+	    if (enode->get_point ().get_function () == m_fun)
+	      num_enodes++;
+	  }
+	pp_printf (pp, "enodes: %i\n", num_enodes);
+	pp_newline (pp);
+
+	// TODO: also show the per-callstring breakdown
+	const exploded_graph::call_string_data_map_t *per_cs_data
+	  = args.m_eg->get_per_call_string_data ();
+	for (typename exploded_graph::call_string_data_map_t::iterator iter
+	       = per_cs_data->begin ();
+	     iter != per_cs_data->end ();
+	     ++iter)
+	  {
+	    const call_string *cs = (*iter).first;
+	    //per_call_string_data *data = (*iter).second;
+	    num_enodes = 0;
+	    FOR_EACH_VEC_ELT (args.m_eg->m_nodes, i, enode)
+	      {
+		if (enode->get_point ().get_function () == m_fun
+		    && enode->get_point ().get_call_string () == *cs)
+		  num_enodes++;
+	      }
+	    if (num_enodes > 0)
+	      {
+		cs->print (pp);
+		pp_printf (pp, ": %i\n", num_enodes);
+	      }
+	  }
+
+	/* Show any summaries.  */
+	per_function_data *data = args.m_eg->get_per_function_data (m_fun);
+	if (data)
+	  {
+	    pp_newline (pp);
+	    pp_printf (pp, "summaries: %i\n", data->m_summaries.length ());
+	  }
+      }
+
+    pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
+    pp_right_brace (pp);
+    pp_string (pp, "\"];\n\n");
+    pp_flush (pp);
+  }
+
+  void dump_dot_id (pretty_printer *pp) const
+  {
+    pp_printf (pp, "vcg_%i", m_index);
+  }
+
+private:
+  function *m_fun;
+  int m_index;
+  int m_num_supernodes;
+  int m_num_superedges;
+};
+
+/* Subclass of dedge representing a callgraph edge.  */
+
+class viz_callgraph_edge : public dedge<viz_callgraph_traits>
+{
+public:
+  viz_callgraph_edge (viz_callgraph_node *src, viz_callgraph_node *dest,
+		     const call_superedge *call_sedge)
+  : dedge (src, dest),
+    m_call_sedge (call_sedge)
+  {}
+
+  void dump_dot (graphviz_out *gv, const dump_args_t &) const
+    FINAL OVERRIDE
+  {
+    pretty_printer *pp = gv->get_pp ();
+
+    const char *style = "\"solid,bold\"";
+    const char *color = "black";
+    int weight = 10;
+    const char *constraint = "true";
+
+    m_src->dump_dot_id (pp);
+    pp_string (pp, " -> ");
+    m_dest->dump_dot_id (pp);
+    pp_printf (pp,
+	       (" [style=%s, color=%s, weight=%d, constraint=%s,"
+		" headlabel=\""),
+	       style, color, weight, constraint);
+    pp_printf (pp, "\"];\n");
+  }
+
+private:
+  const call_superedge * const m_call_sedge;
+};
+
+/* Subclass of digraph representing the callgraph.  */
+
+class viz_callgraph : public digraph<viz_callgraph_traits>
+{
+public:
+  viz_callgraph (const supergraph &sg);
+
+  viz_callgraph_node *get_vcg_node_for_function (function *fun)
+  {
+    return *m_map.get (fun);
+  }
+
+  viz_callgraph_node *get_vcg_node_for_snode (supernode *snode)
+  {
+    return get_vcg_node_for_function (snode->m_fun);
+  }
+
+private:
+  const supergraph &m_sg;
+  hash_map<function *, viz_callgraph_node *> m_map;
+};
+
+/* Placeholder subclass of cluster.  */
+
+class viz_callgraph_cluster : public cluster<viz_callgraph_traits>
+{
+};
+
+/* viz_callgraph's ctor.  */
+
+viz_callgraph::viz_callgraph (const supergraph &sg)
+: m_sg (sg)
+{
+  cgraph_node *node;
+  FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
+    {
+      function *fun = node->get_fun ();
+      viz_callgraph_node *vcg_node
+	= new viz_callgraph_node (fun, m_nodes.length ());
+      m_map.put (fun, vcg_node);
+      add_node (vcg_node);
+    }
+
+  unsigned i;
+  superedge *sedge;
+  FOR_EACH_VEC_ELT (sg.m_edges, i, sedge)
+    {
+      viz_callgraph_node *vcg_src = get_vcg_node_for_snode (sedge->m_src);
+      if (vcg_src->m_fun)
+	get_vcg_node_for_function (vcg_src->m_fun)->m_num_superedges++;
+      if (const call_superedge *call_sedge = sedge->dyn_cast_call_superedge ())
+	{
+	  viz_callgraph_node *vcg_dest = get_vcg_node_for_snode (sedge->m_dest);
+	  viz_callgraph_edge *vcg_edge
+	    = new viz_callgraph_edge (vcg_src, vcg_dest, call_sedge);
+	  add_edge (vcg_edge);
+	}
+    }
+
+  supernode *snode;
+  FOR_EACH_VEC_ELT (sg.m_nodes, i, snode)
+    {
+      if (snode->m_fun)
+	get_vcg_node_for_function (snode->m_fun)->m_num_supernodes++;
+    }
+}
+
+/* Dump the callgraph to FILENAME.  */
+
+static void
+dump_callgraph (const supergraph &sg, const char *filename,
+		const exploded_graph *eg)
+{
+  FILE *outf = fopen (filename, "w");
+  if (!outf)
+    return;
+
+  // TODO
+  viz_callgraph vcg (sg);
+  vcg.dump_dot (filename, NULL, viz_callgraph_traits::dump_args_t (eg));
+
+  fclose (outf);
+}
+
+/* Dump the callgraph to "<srcfile>.callgraph.dot".  */
+
+static void
+dump_callgraph (const supergraph &sg, const exploded_graph *eg)
+{
+  auto_client_timevar tv ("writing .callgraph.dot");
+  char *filename = concat (dump_base_name, ".callgraph.dot", NULL);
+  dump_callgraph (sg, filename, eg);
+  free (filename);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Run the analysis "engine".  */
+
+void
+impl_run_checkers (logger *logger)
+{
+  LOG_SCOPE (logger);
+
+  /* If using LTO, ensure that the cgraph nodes have function bodies.  */
+  cgraph_node *node;
+  FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
+    node->get_untransformed_body ();
+
+  /* Create the supergraph.  */
+  supergraph sg (logger);
+
+  state_purge_map *purge_map = NULL;
+
+  if (flag_analyzer_state_purge)
+    purge_map = new state_purge_map (sg, logger);
+
+  if (flag_dump_analyzer_supergraph)
+    {
+      auto_client_timevar tv ("writing .supergraph.dot");
+      char *filename = concat (dump_base_name, ".supergraph.dot", NULL);
+      supergraph::dump_args_t args ((enum supergraph_dot_flags)0, NULL);
+      sg.dump_dot (filename, args);
+      free (filename);
+    }
+
+  if (flag_dump_analyzer_state_purge)
+    {
+      auto_client_timevar tv ("writing .state-purge.dot");
+      state_purge_annotator a (purge_map);
+      char *filename = concat (dump_base_name, ".state-purge.dot", NULL);
+      supergraph::dump_args_t args ((enum supergraph_dot_flags)0, &a);
+      sg.dump_dot (filename, args);
+      free (filename);
+    }
+
+  auto_delete_vec <state_machine> checkers;
+  make_checkers (checkers, logger);
+
+  if (logger)
+    {
+      int i;
+      state_machine *sm;
+      FOR_EACH_VEC_ELT (checkers, i, sm)
+	logger->log ("checkers[%i]: %s", i, sm->get_name ());
+    }
+
+  /* Extrinsic state shared by nodes in the graph.  */
+  const extrinsic_state ext_state (checkers);
+
+  const analysis_plan plan (sg, logger);
+
+  /* The exploded graph.  */
+  exploded_graph eg (sg, logger, ext_state, purge_map, plan,
+		     analyzer_verbosity);
+
+  /* Add entrypoints to the graph for externally-callable functions.  */
+  eg.build_initial_worklist ();
+
+  /* Now process the worklist, exploring the <point, state> graph.  */
+  eg.process_worklist ();
+
+  if (flag_dump_analyzer_exploded_graph)
+    {
+      auto_client_timevar tv ("writing .eg.dot");
+      char *filename
+	= concat (dump_base_name, ".eg.dot", NULL);
+      exploded_graph::dump_args_t args (eg);
+      root_cluster c;
+      eg.dump_dot (filename, &c, args);
+      free (filename);
+    }
+
+  /* Now emit any saved diagnostics.  */
+  eg.get_diagnostic_manager ().emit_saved_diagnostics (eg);
+
+  eg.dump_exploded_nodes ();
+
+  eg.log_stats ();
+
+  if (flag_dump_analyzer_callgraph)
+    dump_callgraph (sg, &eg);
+
+  delete purge_map;
+}
+
+/* External entrypoint to the analysis "engine".
+   Set up any dumps, then call impl_run_checkers.  */
+
+void
+run_checkers ()
+{
+  auto_client_timevar tv ("run_checkers");
+
+  /* Handle -fdump-analyzer and -fdump-analyzer-stderr.  */
+  FILE *dump_fout = NULL;
+  /* Track if we're responsible for closing dump_fout.  */
+  bool owns_dump_fout = false;
+  if (flag_dump_analyzer_stderr)
+    dump_fout = stderr;
+  else if (flag_dump_analyzer)
+    {
+      char *dump_filename = concat (dump_base_name, ".analyzer.txt", NULL);
+      dump_fout = fopen (dump_filename, "w");
+      free (dump_filename);
+      if (dump_fout)
+	owns_dump_fout = true;
+    }
+
+  {
+    log_user the_logger (NULL);
+    if (dump_fout)
+      the_logger.set_logger (new logger (dump_fout, 0, 0,
+					 *global_dc->printer));
+    LOG_SCOPE (the_logger.get_logger ());
+
+    impl_run_checkers (the_logger.get_logger ());
+
+    /* end of lifetime of the_logger (so that dump file is closed after the
+       various dtors run).  */
+  }
+
+  if (owns_dump_fout)
+    fclose (dump_fout);
+}
diff --git a/gcc/analyzer/engine.h b/gcc/analyzer/engine.h
new file mode 100644
index 0000000..b9b7e0b
--- /dev/null
+++ b/gcc/analyzer/engine.h
@@ -0,0 +1,26 @@
+/* The analysis "engine".
+   Copyright (C) 2019 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/>.  */
+
+#ifndef GCC_ANALYZER_ENGINE_H
+#define GCC_ANALYZER_ENGINE_H
+
+extern void run_checkers ();
+
+#endif /* GCC_ANALYZER_ENGINE_H */
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 20/49] analyzer: new builtins
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (47 preceding siblings ...)
  2019-11-16  1:21 ` [PATCH 45/49] analyzer: new files: engine.{cc|h} David Malcolm
@ 2019-11-16  1:23 ` David Malcolm
  2019-12-04 18:11   ` Martin Sebor
  2019-11-16 21:47 ` [PATCH 00/49] RFC: Add a static analysis framework to GCC Thomas Schwinge
  2019-12-04 19:55 ` Martin Sebor
  50 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-11-16  1:23 UTC (permalink / raw)
  To: gcc-patches; +Cc: David Malcolm

gcc/ChangeLog:
	* builtins.def (BUILT_IN_ANALYZER_BREAK): New builtin.
	(BUILT_IN_ANALYZER_DUMP): New builtin.
	(BUILT_IN_ANALYZER_DUMP_EXPLODED_NODES): New builtin.
	(BUILT_IN_ANALYZER_DUMP_NUM_HEAP_REGIONS): New builtin.
	(BUILT_IN_ANALYZER_DUMP_PATH): New builtin.
	(BUILT_IN_ANALYZER_DUMP_REGION_MODEL): New builtin.
	(BUILT_IN_ANALYZER_EVAL): New builtin.
---
 gcc/builtins.def | 33 +++++++++++++++++++++++++++++++++
 1 file changed, 33 insertions(+)

diff --git a/gcc/builtins.def b/gcc/builtins.def
index d8233f5..f34e95f 100644
--- a/gcc/builtins.def
+++ b/gcc/builtins.def
@@ -1107,4 +1107,37 @@ DEF_GCC_BUILTIN (BUILT_IN_LINE, "LINE", BT_FN_INT, ATTR_NOTHROW_LEAF_LIST)
 /* HSAIL/BRIG frontend builtins.  */
 #include "brig-builtins.def"
 
+
+/* Analyzer builtins.  */
+DEF_BUILTIN (BUILT_IN_ANALYZER_BREAK, "__analyzer_break",
+	     BUILT_IN_NORMAL, BT_FN_VOID, BT_LAST,
+	     false, false, false, ATTR_NULL, true, true)
+
+DEF_BUILTIN (BUILT_IN_ANALYZER_DUMP, "__analyzer_dump",
+	     BUILT_IN_NORMAL, BT_FN_VOID, BT_LAST,
+	     false, false, false, ATTR_NULL, true, true)
+
+DEF_BUILTIN (BUILT_IN_ANALYZER_DUMP_EXPLODED_NODES,
+	     "__analyzer_dump_exploded_nodes",
+	     BUILT_IN_NORMAL, BT_FN_VOID_INT, BT_LAST,
+	     false, false, false, ATTR_NULL, true, true)
+
+DEF_BUILTIN (BUILT_IN_ANALYZER_DUMP_NUM_HEAP_REGIONS,
+	     "__analyzer_dump_num_heap_regions",
+	     BUILT_IN_NORMAL, BT_FN_VOID, BT_LAST,
+	     false, false, false, ATTR_NULL, true, true)
+
+DEF_BUILTIN (BUILT_IN_ANALYZER_DUMP_PATH, "__analyzer_dump_path",
+	     BUILT_IN_NORMAL, BT_FN_VOID, BT_LAST,
+	     false, false, false, ATTR_NULL, true, true)
+
+DEF_BUILTIN (BUILT_IN_ANALYZER_DUMP_REGION_MODEL,
+	     "__analyzer_dump_region_model",
+	     BUILT_IN_NORMAL, BT_FN_VOID, BT_LAST,
+	     false, false, false, ATTR_NULL, true, true)
+
+DEF_BUILTIN (BUILT_IN_ANALYZER_EVAL, "__analyzer_eval",
+	     BUILT_IN_NORMAL, BT_FN_VOID_INT, BT_LAST,
+	     false, false, false, ATTR_NULL, true, true)
+
 #undef DEF_BUILTIN
-- 
1.8.5.3

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 00/49] RFC: Add a static analysis framework to GCC
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (48 preceding siblings ...)
  2019-11-16  1:23 ` [PATCH 20/49] analyzer: new builtins David Malcolm
@ 2019-11-16 21:47 ` Thomas Schwinge
  2019-11-19 22:05   ` David Malcolm
  2019-12-02  3:20   ` [PATCH 00/49] RFC: Add a static analysis framework to GCC Eric Gallager
  2019-12-04 19:55 ` Martin Sebor
  50 siblings, 2 replies; 160+ messages in thread
From: Thomas Schwinge @ 2019-11-16 21:47 UTC (permalink / raw)
  To: David Malcolm; +Cc: gcc-patches

Hi David!

On 2019-11-15T20:22:47-0500, David Malcolm <dmalcolm@redhat.com> wrote:
> This patch kit

(I have not looked at the patches.)  ;-)

> introduces a static analysis pass for GCC that can diagnose
> various kinds of problems in C code at compile-time (e.g. double-free,
> use-after-free, etc).

Sounds great from the description!


Would it make sense to add to the wiki page
<https://gcc.gnu.org/wiki/DavidMalcolm/StaticAnalyzer> a (high-level)
comparison to other static analyzers (Coverity, cppcheck,
clang-static-analyzer, others?), in terms of how they work, what their
respective benefits are, what their design goals are, etc.  (Of course
understanding that yours is much less mature at this point; talking about
high-level design rather than current implementation status.)

For example, why do we want that in GCC instead of an external tool -- in
part covered in your Rationale.  Can a compiler-side implementation
benefit from having more information available than an external tool?
GCC-side implementation is readily available (modulo GCC plugin
installation?) vs. external ones need to be installed/set up first.
GCC-side one only works with GCC-supported languages.  GCC-side one
analyzes actual code being compiled -- thinking about preprocessor-level
'#if' etc., which surely are problematic for external tools that are not
actually replicating a real build.  And so on.  (If you don't want to
spell out Coverity, cppcheck, clang-static-analyzer, etc., maybe just
compare yours to external tools.)

Just an idea, because I wondered about these things.


> The analyzer runs as an IPA pass on the gimple SSA representation.
> It associates state machines with data, with transitions at certain
> statements and edges.  It finds "interesting" interprocedural paths
> through the user's code, in which bogus state transitions happen.
>
> For example, given:
>
>    free (ptr);
>    free (ptr);
>
> at the first call, "ptr" transitions to the "freed" state, and
> at the second call the analyzer complains, since "ptr" is already in
> the "freed" state (unless "ptr" is NULL, in which case it stays in
> the NULL state for both calls).
>
> Specific state machines include:
> - a checker for malloc/free, for detecting double-free, resource leaks,
>   use-after-free, etc (sm-malloc.cc), and

I can immediately see how this can be useful for a bunch of
'malloc'/'free'-like etc. OpenACC Runtime Library calls as well as source
code directives.  ..., and this would've flagged existing code in the
libgomp OpenACC tests, which recently has given me some grief. Short
summary/examples:

In addition to host-side 'malloc'/'free', there is device-side (separate
memory space) 'acc_malloc'/'acc_free'.  Static checking example: don't
mix up host-side and device-side pointers.  (Both are normal C/C++
pointers.  Hmm, maybe such checking could easily be implemented even
outside of your checker by annotating the respective function
declarations with an attribute describing which in/out arguments are
host-side vs. device-side pointers.)

Then, there are functions to "map" host-side and device-side memory:
'acc_map_data'/'acc_unmap_data'.  Static checking example: you must not
'acc_free' memory spaces that are still mapped.

Then, there are functions like 'acc_create' (or equivalent directives
like '#pragma acc create') doing both 'acc_malloc', 'acc_map_data'
(plus/depending on internal reference counting).  Static checking
example: for a pointer returned by 'acc_create" etc., you must use
'acc_delete' etc. instead of 'acc_free', which first does
'acc_unmap_data' before interal 'acc_free' (..., and again all that
depending on reference counting).  (Might be "interesting" to teach your
checker about the reference counting -- if that is actually necessary;
needs further thought.)


> The checker is implemented as a GCC plugin.
>
> The patch kit adds support for "in-tree" plugins i.e. GCC plugins that
> would live in the GCC source tree and be shipped as part of the GCC tarball,
> with a new:
>   --enable-plugins=[LIST OF PLUGIN NAMES]
> configure option, analogous to --enable-languages (the Makefile/configure
> machinery for handling in-tree GCC plugins is adapted from how we support
> frontends).

I like that.  Implementing this as a plugin surely must help to either
document the GCC plugin interface as powerful/mature for such a task.  Or
make it so, if it isn't yet.  ;-)

> The default is for no such plugins to be enabled, so the default would
> be that the checker isn't built - you'd have to opt-in to building it,
> with --enable-plugins=analyzer

I'd favor a default of '--enable-plugins=default' which enables the
"usable" plugins.


> It's not clear to me whether I should focus on:
>
> (a) pruning the scope of the checker so that it works well on
> *intra*procedural C examples (and bail on anything more complex), perhaps
> targetting GCC 10 as an optional extra hidden behind
> --enable-plugins=analyzer, or
>
> (b) work on deeper interprocedural analysis (and fixing efficiency issues
> with this).

I personally would be happy to see (a) happen now (without "optional
extra hidden behind" configure-time flag but rather hidden behind GCC
command-line flag, '-fanalyze'?), and then (b) later on.  As
always, doing the incremental thing, (a) first, then (b) later, would
give it more exposure in the wild, which should help to identify design
issues etc. now instead of much later, for example.


> Thoughts?

One very high-level item: you're using the very generic name "analyzer"
('-Wanalyzer', 'gcc/analyzer/' filenames, for example, or the '-fanalyze'
I just proposed).  Might there be potential for confusion (now, or in the
future) what kind of analyzer that is, or is it safe to assume that in
context of a compiler, an analyzer is always a compile-time, static code
analyzer?  After all, the existing run-time ones are known as "checkers":
'-fstack-check', or "sanitizers": '--fsanitize, or "verifiers":
'-fvtable-verify'.


Grüße
 Thomas

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 00/49] RFC: Add a static analysis framework to GCC
  2019-11-16 21:47 ` [PATCH 00/49] RFC: Add a static analysis framework to GCC Thomas Schwinge
@ 2019-11-19 22:05   ` David Malcolm
  2019-11-20 10:26     ` Richard Biener
  2019-12-02  3:20   ` [PATCH 00/49] RFC: Add a static analysis framework to GCC Eric Gallager
  1 sibling, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-11-19 22:05 UTC (permalink / raw)
  To: Thomas Schwinge; +Cc: gcc-patches

On Sat, 2019-11-16 at 21:42 +0100, Thomas Schwinge wrote:
> Hi David!
> 
> On 2019-11-15T20:22:47-0500, David Malcolm <dmalcolm@redhat.com>
> wrote:
> > This patch kit
> 
> (I have not looked at the patches.)  ;-)
> 
> > introduces a static analysis pass for GCC that can diagnose
> > various kinds of problems in C code at compile-time (e.g. double-
> > free,
> > use-after-free, etc).
> 
> Sounds great from the description!

Thanks.

> Would it make sense to add to the wiki page
> <https://gcc.gnu.org/wiki/DavidMalcolm/StaticAnalyzer> a (high-level)
> comparison to other static analyzers (Coverity, cppcheck,
> clang-static-analyzer, others?), in terms of how they work, what
> their
> respective benefits are, what their design goals are, etc.  (Of
> course
> understanding that yours is much less mature at this point; talking
> about
> high-level design rather than current implementation status.)
> 
> For example, why do we want that in GCC instead of an external tool
> -- in
> part covered in your Rationale.  Can a compiler-side implementation
> benefit from having more information available than an external tool?
> GCC-side implementation is readily available (modulo GCC plugin
> installation?) vs. external ones need to be installed/set up first.
> GCC-side one only works with GCC-supported languages.  GCC-side one
> analyzes actual code being compiled -- thinking about preprocessor-
> level
> '#if' etc., which surely are problematic for external tools that are
> not
> actually replicating a real build.  And so on.  (If you don't want to
> spell out Coverity, cppcheck, clang-static-analyzer, etc., maybe just
> compare yours to external tools.)
> 
> Just an idea, because I wondered about these things.

Thanks; I've added some notes to the "Rationale" section of the wiki
page.

A lot of the information you're after is hidden in patch 2 of the kit,
in an analysis.texi (though admittedly that's hard to read in "patch
that adds a .texi file" form).

For now, I've uploaded a prebuilt version of the HTML to:

https://dmalcolm.fedorapeople.org/gcc/2019-11-19/gccint/Static-Analyzer.html


> > The analyzer runs as an IPA pass on the gimple SSA representation.
> > It associates state machines with data, with transitions at certain
> > statements and edges.  It finds "interesting" interprocedural paths
> > through the user's code, in which bogus state transitions happen.
> > 
> > For example, given:
> > 
> >    free (ptr);
> >    free (ptr);
> > 
> > at the first call, "ptr" transitions to the "freed" state, and
> > at the second call the analyzer complains, since "ptr" is already
> > in
> > the "freed" state (unless "ptr" is NULL, in which case it stays in
> > the NULL state for both calls).
> > 
> > Specific state machines include:
> > - a checker for malloc/free, for detecting double-free, resource
> > leaks,
> >   use-after-free, etc (sm-malloc.cc), and
> 
> I can immediately see how this can be useful for a bunch of
> 'malloc'/'free'-like etc. OpenACC Runtime Library calls as well as
> source
> code directives.  ..., and this would've flagged existing code in the
> libgomp OpenACC tests, which recently has given me some grief. Short
> summary/examples:
> 
> In addition to host-side 'malloc'/'free', there is device-side
> (separate
> memory space) 'acc_malloc'/'acc_free'. 

I've been thinking about generalizing the malloc/free checker to cover
resource acquisition/release pairs, adding a "domain" for the
allocation, where we'd complain if the resource release function isn't
of the same domain as the resource acquisition function.

Allocation domains might be:
  malloc/free
  C++ scalar new/delete
  C++ array new/delete
  FILE * (fopen/fclose)
  "foo_alloc"/"foo_release" for libfoo (i.e. user-extensible, via
attributes)

and thus catch things like deleting with scalar delete when the buffer
was allocated using new[], and various kinds of layering violations.

I'm afraid that I'm not very familiar with OpenACC.  Would
acc_malloc/acc_free fit into that pattern, or would more be needed? 
For example, can you e.g. dereference a device-side pointer in host
code, or would we ideally issue a diagnostic about that?

>  Static checking example: don't
> mix up host-side and device-side pointers.  (Both are normal C/C++
> pointers.  Hmm, maybe such checking could easily be implemented even
> outside of your checker by annotating the respective function
> declarations with an attribute describing which in/out arguments are
> host-side vs. device-side pointers.)
> 
> Then, there are functions to "map" host-side and device-side memory:
> 'acc_map_data'/'acc_unmap_data'.  Static checking example: you must
> not
> 'acc_free' memory spaces that are still mapped.

It sounds like this state machine is somewhat more complicated.

Is there a state transition diagram for this somewhere?  I don't have
that for my state machines, but there are at least lists of states; see
e.g. the various state_t within malloc_state_machine
near the top of:
https://gcc.gnu.org/ml/gcc-patches/2019-11/msg01539.html

> Then, there are functions like 'acc_create' (or equivalent directives
> like '#pragma acc create') doing both 'acc_malloc', 'acc_map_data'
> (plus/depending on internal reference counting).  Static checking
> example: for a pointer returned by 'acc_create" etc., you must use
> 'acc_delete' etc. instead of 'acc_free', which first does
> 'acc_unmap_data' before interal 'acc_free' (..., and again all that
> depending on reference counting).  (Might be "interesting" to teach
> your
> checker about the reference counting -- if that is actually
> necessary;
> needs further thought.)

Perhaps acc_create/acc_delete could simply be handled as a different
allocation "domain" in the approach I suggested above.

Reference counting is actually how I got into GCC development, by
writing a static analysis pass for verifying reference counts in
CPython extension modules (as part of the GCC Python plugin):
https://gcc-python-plugin.readthedocs.io/en/latest/cpychecker.html
(sadly, that code is slowly bit-rotting).

The approach taken in that python plugin code has various issues. 
Plus, doing it in Python isn't fast, and it's really helpful having
compile-time type checking while writing an analyzer; doing this one in
C++ has been a big improvement.

> > The checker is implemented as a GCC plugin.
> > 
> > The patch kit adds support for "in-tree" plugins i.e. GCC plugins
> > that
> > would live in the GCC source tree and be shipped as part of the GCC
> > tarball,
> > with a new:
> >   --enable-plugins=[LIST OF PLUGIN NAMES]
> > configure option, analogous to --enable-languages (the
> > Makefile/configure
> > machinery for handling in-tree GCC plugins is adapted from how we
> > support
> > frontends).
> 
> I like that.  Implementing this as a plugin surely must help to
> either
> document the GCC plugin interface as powerful/mature for such a
> task.  Or
> make it so, if it isn't yet.  ;-)

Our plugin "interface" as such is very broad.

> > The default is for no such plugins to be enabled, so the default
> > would
> > be that the checker isn't built - you'd have to opt-in to building
> > it,
> > with --enable-plugins=analyzer
> 
> I'd favor a default of '--enable-plugins=default' which enables the
> "usable" plugins.

> > It's not clear to me whether I should focus on:
> > 
> > (a) pruning the scope of the checker so that it works well on
> > *intra*procedural C examples (and bail on anything more complex),
> > perhaps
> > targetting GCC 10 as an optional extra hidden behind
> > --enable-plugins=analyzer, or
> > 
> > (b) work on deeper interprocedural analysis (and fixing efficiency
> > issues
> > with this).
> 
> I personally would be happy to see (a) happen now (without "optional
> extra hidden behind" configure-time flag but rather hidden behind GCC
> command-line flag, '-fanalyze'?), and then (b) later on.  As
> always, doing the incremental thing, (a) first, then (b) later, would
> give it more exposure in the wild, which should help to identify
> design
> issues etc. now instead of much later, for example.

I'm continuing to work on this code; I've already fixed the biggest
issue with LTO support (in my local copy).

I wonder if I ought to hide the interprocedural stuff behind an option
for the initial release, or only handle the simplest wrappers around
malloc/free.  Though obviously I'd prefer to get it working.  I guess
the thing to do for now is to fix bugs, and test it on increasingly
larger codebases.

> > Thoughts?
> 
> One very high-level item: you're using the very generic name
> "analyzer"
> ('-Wanalyzer', 'gcc/analyzer/' filenames, for example, or the '-
> fanalyze'
> I just proposed).  

I like "-fanalyze".

> Might there be potential for confusion (now, or in the
> future) what kind of analyzer that is, or is it safe to assume that
> in
> context of a compiler, an analyzer is always a compile-time, static
> code
> analyzer?  After all, the existing run-time ones are known as
> "checkers":
> '-fstack-check', or "sanitizers": '--fsanitize, or "verifiers":
> '-fvtable-verify'.

My hope is that long-term the analyzer will be extensible: that clang-
static-analyzer works this way, where there's a general framework, with
various "checkers" within it, potentially themselves as plugins.

For an initial version it's simplest to hardcode the checkers, and only
add generality and extension points once we have something concrete and
useful working.

For naming, I've used "analyzer", but I now realize I've conflated two
things:
  (a) the subdirectory we use internally, and the name for the
configure-time enablement option
  (b) what the user types in command-line options (warning names etc)

Potentially (a) could be "sa" (for static analyzer) rather than
"analyzer", but that might be an abbreviation too far.

Hopefully "analyzer" is OK as a name for now.  I plan to create an
ongoing git branch, probably "dmalcolm/analyzer" (I've got some fixes
queued up in my local copy).

> 
> Grüße
>  Thomas

Thanks for the feedback
Dave


^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 00/49] RFC: Add a static analysis framework to GCC
  2019-11-19 22:05   ` David Malcolm
@ 2019-11-20 10:26     ` Richard Biener
  2019-12-02  3:03       ` Eric Gallager
  2019-12-03 16:52       ` David Malcolm
  0 siblings, 2 replies; 160+ messages in thread
From: Richard Biener @ 2019-11-20 10:26 UTC (permalink / raw)
  To: David Malcolm; +Cc: Thomas Schwinge, GCC Patches

On Tue, Nov 19, 2019 at 11:02 PM David Malcolm <dmalcolm@redhat.com> wrote:
>
> > > The checker is implemented as a GCC plugin.
> > >
> > > The patch kit adds support for "in-tree" plugins i.e. GCC plugins
> > > that
> > > would live in the GCC source tree and be shipped as part of the GCC
> > > tarball,
> > > with a new:
> > >   --enable-plugins=[LIST OF PLUGIN NAMES]
> > > configure option, analogous to --enable-languages (the
> > > Makefile/configure
> > > machinery for handling in-tree GCC plugins is adapted from how we
> > > support
> > > frontends).
> >
> > I like that.  Implementing this as a plugin surely must help to
> > either
> > document the GCC plugin interface as powerful/mature for such a
> > task.  Or
> > make it so, if it isn't yet.  ;-)
>
> Our plugin "interface" as such is very broad.

Just to sneak in here I don't like exposing our current plugin "non-API"
more.  In fact I'd just build the analyzer into GCC with maybe an
option to disable its build (in case it is very fat?).

From what I read it seems the analyzer could do with a proper
plugin API that just exposes introspection - and I really hope
somebody finds the time to complete (or rewrite...) the
proposed introspection API that ideally is even cross-compiler
(proven by implementing said API ontop of both GCC and clang/llvm).
That way the Analyzer would work with both GCC and clang [and golang
and rustc...].

So it would be interesting if you could try to sketch the kind of API
the Analyzer needs?  That is, merely the detail on which it inspects
statements, the CFG and the callgraph.

Richard.

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 00/49] RFC: Add a static analysis framework to GCC
  2019-11-20 10:26     ` Richard Biener
@ 2019-12-02  3:03       ` Eric Gallager
  2019-12-03 16:52       ` David Malcolm
  1 sibling, 0 replies; 160+ messages in thread
From: Eric Gallager @ 2019-12-02  3:03 UTC (permalink / raw)
  To: Richard Biener; +Cc: David Malcolm, Thomas Schwinge, GCC Patches

On 11/20/19, Richard Biener <richard.guenther@gmail.com> wrote:
> On Tue, Nov 19, 2019 at 11:02 PM David Malcolm <dmalcolm@redhat.com> wrote:
>>
>> > > The checker is implemented as a GCC plugin.
>> > >
>> > > The patch kit adds support for "in-tree" plugins i.e. GCC plugins
>> > > that
>> > > would live in the GCC source tree and be shipped as part of the GCC
>> > > tarball,
>> > > with a new:
>> > >   --enable-plugins=[LIST OF PLUGIN NAMES]
>> > > configure option, analogous to --enable-languages (the
>> > > Makefile/configure
>> > > machinery for handling in-tree GCC plugins is adapted from how we
>> > > support
>> > > frontends).
>> >
>> > I like that.  Implementing this as a plugin surely must help to
>> > either
>> > document the GCC plugin interface as powerful/mature for such a
>> > task.  Or
>> > make it so, if it isn't yet.  ;-)
>>
>> Our plugin "interface" as such is very broad.
>
> Just to sneak in here I don't like exposing our current plugin "non-API"
> more.  In fact I'd just build the analyzer into GCC with maybe an
> option to disable its build (in case it is very fat?).
>
> From what I read it seems the analyzer could do with a proper
> plugin API that just exposes introspection - and I really hope
> somebody finds the time to complete (or rewrite...) the
> proposed introspection API that ideally is even cross-compiler
> (proven by implementing said API ontop of both GCC and clang/llvm).
> That way the Analyzer would work with both GCC and clang [and golang
> and rustc...].

That might be a good idea for a long-term goal, but I just hope it
doesn't get in the way too much of the analyzer getting into GCC in
the short-term. The analyzer seems like it could do some really cool
analysis, and I'd like to use it sooner rather than later. Rewriting
the plugin API sounds like it could take a really long time...

>
> So it would be interesting if you could try to sketch the kind of API
> the Analyzer needs?  That is, merely the detail on which it inspects
> statements, the CFG and the callgraph.
>
> Richard.
>

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 00/49] RFC: Add a static analysis framework to GCC
  2019-11-16 21:47 ` [PATCH 00/49] RFC: Add a static analysis framework to GCC Thomas Schwinge
  2019-11-19 22:05   ` David Malcolm
@ 2019-12-02  3:20   ` Eric Gallager
  1 sibling, 0 replies; 160+ messages in thread
From: Eric Gallager @ 2019-12-02  3:20 UTC (permalink / raw)
  To: Thomas Schwinge; +Cc: David Malcolm, gcc-patches, Nick Clifton

On 11/16/19, Thomas Schwinge <thomas@codesourcery.com> wrote:
> Hi David!
>
> On 2019-11-15T20:22:47-0500, David Malcolm <dmalcolm@redhat.com> wrote:
>> This patch kit
>
> (I have not looked at the patches.)  ;-)
>
>> introduces a static analysis pass for GCC that can diagnose
>> various kinds of problems in C code at compile-time (e.g. double-free,
>> use-after-free, etc).
>
> Sounds great from the description!
>
>
> Would it make sense to add to the wiki page
> <https://gcc.gnu.org/wiki/DavidMalcolm/StaticAnalyzer> a (high-level)
> comparison to other static analyzers (Coverity, cppcheck,
> clang-static-analyzer, others?), in terms of how they work, what their
> respective benefits are, what their design goals are, etc.  (Of course
> understanding that yours is much less mature at this point; talking about
> high-level design rather than current implementation status.)
>
> For example, why do we want that in GCC instead of an external tool -- in
> part covered in your Rationale.

There are a lot of bug reports open for requests for warnings that
this analyzer could solve. Users clearly want this in GCC, or else
they wouldn't keep making these requests.

> Can a compiler-side implementation
> benefit from having more information available than an external tool?
> GCC-side implementation is readily available (modulo GCC plugin
> installation?) vs. external ones need to be installed/set up first.
> GCC-side one only works with GCC-supported languages.  GCC-side one
> analyzes actual code being compiled -- thinking about preprocessor-level
> '#if' etc., which surely are problematic for external tools that are not
> actually replicating a real build.  And so on.  (If you don't want to
> spell out Coverity, cppcheck, clang-static-analyzer, etc., maybe just
> compare yours to external tools.)
>
> Just an idea, because I wondered about these things.
>
>
>> The analyzer runs as an IPA pass on the gimple SSA representation.
>> It associates state machines with data, with transitions at certain
>> statements and edges.  It finds "interesting" interprocedural paths
>> through the user's code, in which bogus state transitions happen.
>>
>> For example, given:
>>
>>    free (ptr);
>>    free (ptr);
>>
>> at the first call, "ptr" transitions to the "freed" state, and
>> at the second call the analyzer complains, since "ptr" is already in
>> the "freed" state (unless "ptr" is NULL, in which case it stays in
>> the NULL state for both calls).
>>
>> Specific state machines include:
>> - a checker for malloc/free, for detecting double-free, resource leaks,
>>   use-after-free, etc (sm-malloc.cc), and
>
> I can immediately see how this can be useful for a bunch of
> 'malloc'/'free'-like etc. OpenACC Runtime Library calls as well as source
> code directives.  ..., and this would've flagged existing code in the
> libgomp OpenACC tests, which recently has given me some grief. Short
> summary/examples:
>
> In addition to host-side 'malloc'/'free', there is device-side (separate
> memory space) 'acc_malloc'/'acc_free'.  Static checking example: don't
> mix up host-side and device-side pointers.  (Both are normal C/C++
> pointers.  Hmm, maybe such checking could easily be implemented even
> outside of your checker by annotating the respective function
> declarations with an attribute describing which in/out arguments are
> host-side vs. device-side pointers.)
>
> Then, there are functions to "map" host-side and device-side memory:
> 'acc_map_data'/'acc_unmap_data'.  Static checking example: you must not
> 'acc_free' memory spaces that are still mapped.
>
> Then, there are functions like 'acc_create' (or equivalent directives
> like '#pragma acc create') doing both 'acc_malloc', 'acc_map_data'
> (plus/depending on internal reference counting).  Static checking
> example: for a pointer returned by 'acc_create" etc., you must use
> 'acc_delete' etc. instead of 'acc_free', which first does
> 'acc_unmap_data' before interal 'acc_free' (..., and again all that
> depending on reference counting).  (Might be "interesting" to teach your
> checker about the reference counting -- if that is actually necessary;
> needs further thought.)
>
>
>> The checker is implemented as a GCC plugin.
>>
>> The patch kit adds support for "in-tree" plugins i.e. GCC plugins that
>> would live in the GCC source tree and be shipped as part of the GCC
>> tarball,
>> with a new:
>>   --enable-plugins=[LIST OF PLUGIN NAMES]
>> configure option, analogous to --enable-languages (the Makefile/configure
>> machinery for handling in-tree GCC plugins is adapted from how we support
>> frontends).
>
> I like that.  Implementing this as a plugin surely must help to either
> document the GCC plugin interface as powerful/mature for such a task.  Or
> make it so, if it isn't yet.  ;-)

Nick Clifton was bringing this up as a point in his talk on his
annobin plugin at Cauldron; this should make him happy.

>
>> The default is for no such plugins to be enabled, so the default would
>> be that the checker isn't built - you'd have to opt-in to building it,
>> with --enable-plugins=analyzer
>
> I'd favor a default of '--enable-plugins=default' which enables the
> "usable" plugins.

Agreed.

>
>
>> It's not clear to me whether I should focus on:
>>
>> (a) pruning the scope of the checker so that it works well on
>> *intra*procedural C examples (and bail on anything more complex), perhaps
>> targetting GCC 10 as an optional extra hidden behind
>> --enable-plugins=analyzer, or
>>
>> (b) work on deeper interprocedural analysis (and fixing efficiency issues
>> with this).
>
> I personally would be happy to see (a) happen now (without "optional
> extra hidden behind" configure-time flag but rather hidden behind GCC
> command-line flag, '-fanalyze'?), and then (b) later on.  As
> always, doing the incremental thing, (a) first, then (b) later, would
> give it more exposure in the wild, which should help to identify design
> issues etc. now instead of much later, for example.
>
>
>> Thoughts?
>
> One very high-level item: you're using the very generic name "analyzer"
> ('-Wanalyzer', 'gcc/analyzer/' filenames, for example, or the '-fanalyze'
> I just proposed).  Might there be potential for confusion (now, or in the
> future) what kind of analyzer that is, or is it safe to assume that in
> context of a compiler, an analyzer is always a compile-time, static code
> analyzer?  After all, the existing run-time ones are known as "checkers":
> '-fstack-check', or "sanitizers": '--fsanitize, or "verifiers":
> '-fvtable-verify'.
>
>
> Grüße
>  Thomas
>

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 00/49] RFC: Add a static analysis framework to GCC
  2019-11-20 10:26     ` Richard Biener
  2019-12-02  3:03       ` Eric Gallager
@ 2019-12-03 16:52       ` David Malcolm
  2019-12-03 17:17         ` Jakub Jelinek
                           ` (2 more replies)
  1 sibling, 3 replies; 160+ messages in thread
From: David Malcolm @ 2019-12-03 16:52 UTC (permalink / raw)
  To: Richard Biener; +Cc: Thomas Schwinge, GCC Patches

On Wed, 2019-11-20 at 11:18 +0100, Richard Biener wrote:
> On Tue, Nov 19, 2019 at 11:02 PM David Malcolm <dmalcolm@redhat.com>
> wrote:
> > > > The checker is implemented as a GCC plugin.
> > > > 
> > > > The patch kit adds support for "in-tree" plugins i.e. GCC
> > > > plugins
> > > > that
> > > > would live in the GCC source tree and be shipped as part of the
> > > > GCC
> > > > tarball,
> > > > with a new:
> > > >   --enable-plugins=[LIST OF PLUGIN NAMES]
> > > > configure option, analogous to --enable-languages (the
> > > > Makefile/configure
> > > > machinery for handling in-tree GCC plugins is adapted from how
> > > > we
> > > > support
> > > > frontends).
> > > 
> > > I like that.  Implementing this as a plugin surely must help to
> > > either
> > > document the GCC plugin interface as powerful/mature for such a
> > > task.  Or
> > > make it so, if it isn't yet.  ;-)
> > 
> > Our plugin "interface" as such is very broad.
> 
> Just to sneak in here I don't like exposing our current plugin "non-
> API"
> more.  In fact I'd just build the analyzer into GCC with maybe an
> option to disable its build (in case it is very fat?).

My aim here is to provide a way for distributors to be able to disable
its build - indeed, for now, for it to be disabled by default,
requiring opting-in.

My reasoning here is that the analyzer is middle-end code, but isn't as
mature as the rest of the middle-end (but I'm working on getting it
more mature).

I want some way to label the code as a "technology preview", that
people may want to experiment with, but to set expectations that this
is a lot of new code and there will be bugs - but to make it available
to make it easier for adventurous users to try it out.

I hope that makes sense.

I went down the "in-tree plugin" path by seeing the analogy with
frontends, but yes, it would probably be simpler to just build it into
GCC, guarded with a configure-time variable.  It's many thousand lines
of non-trivial C++ code, and associated selftests and DejaGnu tests.

Building with --enable-checking=release, and stripping the binaries and
the plugin, I see:

$ ls -al cc1 cc1plus plugin/analyzer_plugin.so 
-rwxrwxr-x. 1 david david 25921600 Dec  3 11:22 cc1
-rwxrwxr-x. 1 david david 27473568 Dec  3 11:22 cc1plus
-rwxrwxr-x. 1 david david   645256 Dec  3 11:22
plugin/analyzer_plugin.so

$ ls -alh cc1 cc1plus plugin/analyzer_plugin.so 
-rwxrwxr-x. 1 david david  25M Dec  3 11:22 cc1
-rwxrwxr-x. 1 david david  27M Dec  3 11:22 cc1plus
-rwxrwxr-x. 1 david david 631K Dec  3 11:22 plugin/analyzer_plugin.so

so the plugin is about 2.5% of the size of the existing compiler.

The analysis pass is very time-consuming when enabled via -fanalyzer. 
I'm aiming for "x2 compile-time in exchange for finding lots of bugs"
as a tradeoff that users will be happy to make (by supplying
-fanalyzer) - that's faster than comparable static analyzers I've been
playing with.

> From what I read it seems the analyzer could do with a proper
> plugin API that just exposes introspection - and I really hope
> somebody finds the time to complete (or rewrite...) the
> proposed introspection API that ideally is even cross-compiler
> (proven by implementing said API ontop of both GCC and clang/llvm).
> That way the Analyzer would work with both GCC and clang [and golang
> and rustc...].

We've gone back and forth about what a GCC plugin API should look like;
I'm not sure what the objectives are.

For example, are we hoping to offer some kind of ABI guarantee to
plugins so that we can patch GCC without plugins needing to be rebuilt?
If so, how strong is the ABI guarantee?  For example, do we directly
expose the tree code enums and the gimple code enums?

Or is it more ambitious, and hoping to be cross-compiler, in which case
are these enums themselves hidden?

This feels like opening a massive can of worms, and orthogonal to my
goal of giving GCC a static analysis framework.

> So it would be interesting if you could try to sketch the kind of API
> the Analyzer needs?  That is, merely the detail on which it inspects
> statements, the CFG and the callgraph.

FWIW the symbols consumed by the plugin can be seen at:
 https://dmalcolm.fedorapeople.org/gcc/2019-11-27/symbols-used.txt

This is the result of:
  eu-readelf -s plugin/analyzer_plugin.so |c++filt|grep UNDEF

Surveying that, the plugin:
- creates a pass
- views the callgraph and the functions (e.g. ipa_reverse_postorder)
- views CFGs and SSA representation (including statements)
- uses the diagnostic subsystem (which parts of the patch kit extend,
adding e.g. control flow paths), e.g. creating and subclassing
rich_locations, subclassing diagnostic_path and diagnostic_event
- calls into middle-end support functions like
useless_type_conversion_p
- uses GCC types such as bitmap, inchash, wideint
- creates temporary trees
- has selftests
...etc.

But there are inline uses of various functions that don't show up in
that list (e.g. the various gimple_* accessor functions - grepping the
source shows over a hundred uses of these, but they're all inlined and
so don't show up in the above view).

My gut feeling is that writing a plugin API and then rewriting the
analyzer to use it would be a huge amount of work: I'd strongly prefer
not to do so (and to use the existing API, either as a plugin, or
directly, dropping the plugin machinery from the analyzer).

Perhaps the best way forward is to build this directly into the
compiler, but guard it by a configure-time option?

Dave

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 00/49] RFC: Add a static analysis framework to GCC
  2019-12-03 16:52       ` David Malcolm
@ 2019-12-03 17:17         ` Jakub Jelinek
  2019-12-16 14:33           ` David Malcolm
  2019-12-04 12:41         ` Richard Biener
  2019-12-06 22:25         ` Jeff Law
  2 siblings, 1 reply; 160+ messages in thread
From: Jakub Jelinek @ 2019-12-03 17:17 UTC (permalink / raw)
  To: David Malcolm; +Cc: Richard Biener, Thomas Schwinge, GCC Patches

On Tue, Dec 03, 2019 at 11:52:13AM -0500, David Malcolm wrote:
> > > Our plugin "interface" as such is very broad.
> > 
> > Just to sneak in here I don't like exposing our current plugin "non-
> > API"
> > more.  In fact I'd just build the analyzer into GCC with maybe an
> > option to disable its build (in case it is very fat?).
> 
> My aim here is to provide a way for distributors to be able to disable
> its build - indeed, for now, for it to be disabled by default,
> requiring opting-in.
> 
> My reasoning here is that the analyzer is middle-end code, but isn't as
> mature as the rest of the middle-end (but I'm working on getting it
> more mature).
> 
> I want some way to label the code as a "technology preview", that
> people may want to experiment with, but to set expectations that this
> is a lot of new code and there will be bugs - but to make it available
> to make it easier for adventurous users to try it out.
> 
> I hope that makes sense.
> 
> I went down the "in-tree plugin" path by seeing the analogy with
> frontends, but yes, it would probably be simpler to just build it into
> GCC, guarded with a configure-time variable.  It's many thousand lines
> of non-trivial C++ code, and associated selftests and DejaGnu tests.

I think it is enough to document it as tech preview in the documentation,
no need to have it as an in-tree plugin.  We have lots of options that had
such a state (perhaps undeclared) over the years, I'd consider
-fvtable-verify= to be such an option, or in the past e.g.
-fipa-matrix-reorg or -fipa-struct-reorg.  And 2.5% code growth isn't that
bad.  So, as long as the option isn't enabled by default, I think we'd be
fine.

	Jakub

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 00/49] RFC: Add a static analysis framework to GCC
  2019-12-03 16:52       ` David Malcolm
  2019-12-03 17:17         ` Jakub Jelinek
@ 2019-12-04 12:41         ` Richard Biener
  2019-12-06 22:25         ` Jeff Law
  2 siblings, 0 replies; 160+ messages in thread
From: Richard Biener @ 2019-12-04 12:41 UTC (permalink / raw)
  To: David Malcolm; +Cc: Thomas Schwinge, GCC Patches

On Tue, Dec 3, 2019 at 5:52 PM David Malcolm <dmalcolm@redhat.com> wrote:
>
> On Wed, 2019-11-20 at 11:18 +0100, Richard Biener wrote:
> > On Tue, Nov 19, 2019 at 11:02 PM David Malcolm <dmalcolm@redhat.com>
> > wrote:
> > > > > The checker is implemented as a GCC plugin.
> > > > >
> > > > > The patch kit adds support for "in-tree" plugins i.e. GCC
> > > > > plugins
> > > > > that
> > > > > would live in the GCC source tree and be shipped as part of the
> > > > > GCC
> > > > > tarball,
> > > > > with a new:
> > > > >   --enable-plugins=[LIST OF PLUGIN NAMES]
> > > > > configure option, analogous to --enable-languages (the
> > > > > Makefile/configure
> > > > > machinery for handling in-tree GCC plugins is adapted from how
> > > > > we
> > > > > support
> > > > > frontends).
> > > >
> > > > I like that.  Implementing this as a plugin surely must help to
> > > > either
> > > > document the GCC plugin interface as powerful/mature for such a
> > > > task.  Or
> > > > make it so, if it isn't yet.  ;-)
> > >
> > > Our plugin "interface" as such is very broad.
> >
> > Just to sneak in here I don't like exposing our current plugin "non-
> > API"
> > more.  In fact I'd just build the analyzer into GCC with maybe an
> > option to disable its build (in case it is very fat?).
>
> My aim here is to provide a way for distributors to be able to disable
> its build - indeed, for now, for it to be disabled by default,
> requiring opting-in.
>
> My reasoning here is that the analyzer is middle-end code, but isn't as
> mature as the rest of the middle-end (but I'm working on getting it
> more mature).
>
> I want some way to label the code as a "technology preview", that
> people may want to experiment with, but to set expectations that this
> is a lot of new code and there will be bugs - but to make it available
> to make it easier for adventurous users to try it out.
>
> I hope that makes sense.
>
> I went down the "in-tree plugin" path by seeing the analogy with
> frontends, but yes, it would probably be simpler to just build it into
> GCC, guarded with a configure-time variable.  It's many thousand lines
> of non-trivial C++ code, and associated selftests and DejaGnu tests.
>
> Building with --enable-checking=release, and stripping the binaries and
> the plugin, I see:
>
> $ ls -al cc1 cc1plus plugin/analyzer_plugin.so
> -rwxrwxr-x. 1 david david 25921600 Dec  3 11:22 cc1
> -rwxrwxr-x. 1 david david 27473568 Dec  3 11:22 cc1plus
> -rwxrwxr-x. 1 david david   645256 Dec  3 11:22
> plugin/analyzer_plugin.so
>
> $ ls -alh cc1 cc1plus plugin/analyzer_plugin.so
> -rwxrwxr-x. 1 david david  25M Dec  3 11:22 cc1
> -rwxrwxr-x. 1 david david  27M Dec  3 11:22 cc1plus
> -rwxrwxr-x. 1 david david 631K Dec  3 11:22 plugin/analyzer_plugin.so
>
> so the plugin is about 2.5% of the size of the existing compiler.
>
> The analysis pass is very time-consuming when enabled via -fanalyzer.
> I'm aiming for "x2 compile-time in exchange for finding lots of bugs"
> as a tradeoff that users will be happy to make (by supplying
> -fanalyzer) - that's faster than comparable static analyzers I've been
> playing with.
>
> > From what I read it seems the analyzer could do with a proper
> > plugin API that just exposes introspection - and I really hope
> > somebody finds the time to complete (or rewrite...) the
> > proposed introspection API that ideally is even cross-compiler
> > (proven by implementing said API ontop of both GCC and clang/llvm).
> > That way the Analyzer would work with both GCC and clang [and golang
> > and rustc...].
>
> We've gone back and forth about what a GCC plugin API should look like;
> I'm not sure what the objectives are.
>
> For example, are we hoping to offer some kind of ABI guarantee to
> plugins so that we can patch GCC without plugins needing to be rebuilt?

Yes, I think that's desirable.

> If so, how strong is the ABI guarantee?  For example, do we directly
> expose the tree code enums and the gimple code enums?

No, we'd remap them semantically.

> Or is it more ambitious, and hoping to be cross-compiler, in which case
> are these enums themselves hidden?

Well, my original idea was to see what people really would use
(when just considering introspection and maybe very simple instrumentation).
And then sketch something independent of the underlying compiler.
And then have that API [or even ABI] implemented by more than one
compiler to see if that's viable.

> This feels like opening a massive can of worms, and orthogonal to my
> goal of giving GCC a static analysis framework.

Sure it is orthogonal.  The only reason it comes up here is that you
propose a "plugin" ;)

I'd rather have the current plugin non-API go away so having it
"fixed" by introducing in-tree plugins looks backwards to me in
that regard.

> > So it would be interesting if you could try to sketch the kind of API
> > the Analyzer needs?  That is, merely the detail on which it inspects
> > statements, the CFG and the callgraph.
>
> FWIW the symbols consumed by the plugin can be seen at:
>  https://dmalcolm.fedorapeople.org/gcc/2019-11-27/symbols-used.txt
>
> This is the result of:
>   eu-readelf -s plugin/analyzer_plugin.so |c++filt|grep UNDEF
>
> Surveying that, the plugin:
> - creates a pass
> - views the callgraph and the functions (e.g. ipa_reverse_postorder)
> - views CFGs and SSA representation (including statements)
> - uses the diagnostic subsystem (which parts of the patch kit extend,
> adding e.g. control flow paths), e.g. creating and subclassing
> rich_locations, subclassing diagnostic_path and diagnostic_event
> - calls into middle-end support functions like
> useless_type_conversion_p
> - uses GCC types such as bitmap, inchash, wideint
> - creates temporary trees
> - has selftests
> ...etc.

Ok. So if one rewrote it from scratch on a hypothetical plugin API
then it would need introspection only.  But it would need to re-implement
a lot of stuff like diagnostics (unless that is easy to expose via
a generic compiler plugin API).

> But there are inline uses of various functions that don't show up in
> that list (e.g. the various gimple_* accessor functions - grepping the
> source shows over a hundred uses of these, but they're all inlined and
> so don't show up in the above view).
>
> My gut feeling is that writing a plugin API and then rewriting the
> analyzer to use it would be a huge amount of work: I'd strongly prefer
> not to do so (and to use the existing API, either as a plugin, or
> directly, dropping the plugin machinery from the analyzer).

Understood ;)

> Perhaps the best way forward is to build this directly into the
> compiler, but guard it by a configure-time option?

Yeah, that makes most sense.

> Dave
>

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 05/49] vec.h: add auto_delete_vec
  2019-11-16  1:18 ` [PATCH 05/49] vec.h: add auto_delete_vec David Malcolm
@ 2019-12-04 16:29   ` Martin Sebor
  2019-12-18 16:00     ` David Malcolm
  0 siblings, 1 reply; 160+ messages in thread
From: Martin Sebor @ 2019-12-04 16:29 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On 11/15/19 6:22 PM, David Malcolm wrote:
> This patch adds a class auto_delete_vec<T>, a subclass of auto_vec <T *>
> that deletes all of its elements on destruction; it's used in many
> places later in the kit.
> 
> This is a crude way for a vec to "own" the objects it points to
> and clean up automatically (essentially a workaround for not being able
> to use unique_ptr, due to C++98).
> 
> gcc/ChangeLog:
> 	* vec.c (class selftest::count_dtor): New class.
> 	(selftest::test_auto_delete_vec): New test.
> 	(selftest::vec_c_tests): Call it.
> 	* vec.h (class auto_delete_vec): New class template.
> 	(auto_delete_vec<T>::~auto_delete_vec): New dtor.

Because of slicing, unless preventing the elements from being
deleted in the class dtor is meant to be a feature, it seems that
using a wrapper class rather than public derivation from auto_vec
might be a safer solution.

It might be worth mentioning in a comment that the class isn't
safe to copy or assign (each copy would wind up delete the same
pointers), in addition to making its copy ctor and copy assignment
operator inaccessible or deleted.

Martin

> ---
>   gcc/vec.c | 27 +++++++++++++++++++++++++++
>   gcc/vec.h | 35 +++++++++++++++++++++++++++++++++++
>   2 files changed, 62 insertions(+)
> 
> diff --git a/gcc/vec.c b/gcc/vec.c
> index bac5eb7..a9c840d 100644
> --- a/gcc/vec.c
> +++ b/gcc/vec.c
> @@ -516,6 +516,32 @@ test_reverse ()
>     }
>   }
>   
> +/* A test class that increments a counter every time its dtor is called.  */
> +
> +class count_dtor
> +{
> + public:
> +  count_dtor (int *counter) : m_counter (counter) {}
> +  ~count_dtor () { (*m_counter)++; }
> +
> + private:
> +  int *m_counter;
> +};
> +
> +/* Verify that auto_delete_vec deletes the elements within it.  */
> +
> +static void
> +test_auto_delete_vec ()
> +{
> +  int dtor_count = 0;
> +  {
> +    auto_delete_vec <count_dtor> v;
> +    v.safe_push (new count_dtor (&dtor_count));
> +    v.safe_push (new count_dtor (&dtor_count));
> +  }
> +  ASSERT_EQ (dtor_count, 2);
> +}
> +
>   /* Run all of the selftests within this file.  */
>   
>   void
> @@ -533,6 +559,7 @@ vec_c_tests ()
>     test_block_remove ();
>     test_qsort ();
>     test_reverse ();
> +  test_auto_delete_vec ();
>   }
>   
>   } // namespace selftest
> diff --git a/gcc/vec.h b/gcc/vec.h
> index 091056b..1957739 100644
> --- a/gcc/vec.h
> +++ b/gcc/vec.h
> @@ -1539,6 +1539,28 @@ class auto_string_vec : public auto_vec <char *>
>     ~auto_string_vec ();
>   };
>   
> +/* A subclass of auto_vec <T *> that deletes all of its elements on
> +   destruction.
> +
> +   This is a crude way for a vec to "own" the objects it points to
> +   and clean up automatically.
> +
> +   For example, no attempt is made to delete elements when an item
> +   within the vec is overwritten.
> +
> +   We can't rely on gnu::unique_ptr within a container,
> +   since we can't rely on move semantics in C++98.  */
> +
> +template <typename T>
> +class auto_delete_vec : public auto_vec <T *>
> +{
> + public:
> +  auto_delete_vec () {}
> +  auto_delete_vec (size_t s) : auto_vec <T *> (s) {}
> +
> +  ~auto_delete_vec ();
> +};
> +
>   /* Conditionally allocate heap memory for VEC and its internal vector.  */
>   
>   template<typename T>
> @@ -1643,6 +1665,19 @@ auto_string_vec::~auto_string_vec ()
>       free (str);
>   }
>   
> +/* auto_delete_vec's dtor, deleting all contained items, automatically
> +   chaining up to ~auto_vec <T*>, which frees the internal buffer.  */
> +
> +template <typename T>
> +inline
> +auto_delete_vec<T>::~auto_delete_vec ()
> +{
> +  int i;
> +  T *item;
> +  FOR_EACH_VEC_ELT (*this, i, item)
> +    delete item;
> +}
> +
>   
>   /* Return a copy of this vector.  */
>   
> 

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 06/49] timevar.h: add auto_client_timevar class
  2019-11-16  1:18 ` [PATCH 06/49] timevar.h: add auto_client_timevar class David Malcolm
@ 2019-12-04 16:39   ` Martin Sebor
  2019-12-04 17:28     ` Tom Tromey
  2019-12-07 14:29   ` Jeff Law
  1 sibling, 1 reply; 160+ messages in thread
From: Martin Sebor @ 2019-12-04 16:39 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On 11/15/19 6:22 PM, David Malcolm wrote:
> This patch adds a class "auto_client_timevar", similar to auto_timevar,
> but for plugins to use on their own timing items that aren't in
> timevar.def
> 
> gcc/ChangeLog:
> 	* timevar.h (class auto_client_timevar): New class.
> ---
>   gcc/timevar.h | 33 +++++++++++++++++++++++++++++++++
>   1 file changed, 33 insertions(+)
> 
> diff --git a/gcc/timevar.h b/gcc/timevar.h
> index ef404d0..d053ab7 100644
> --- a/gcc/timevar.h
> +++ b/gcc/timevar.h
> @@ -256,6 +256,39 @@ class auto_timevar
>     timevar_id_t m_tv;
>   };
>   
> +/* An RAII class analogous to auto_timevar, but for a client item,
> +   for use by plugins.  */
> +
> +class auto_client_timevar
> +{
> + public:
> +  auto_client_timevar (timer *t, const char *item_name)
> +    : m_timer (t)
> +  {
> +    if (m_timer)
> +      m_timer->push_client_item (item_name);
> +  }
> +
> +  explicit auto_client_timevar (const char *item_name)
> +    : m_timer (g_timer)
> +  {
> +    if (m_timer)
> +      m_timer->push_client_item (item_name);
> +  }
> +
> +  ~auto_client_timevar ()
> +  {
> +    if (m_timer)
> +      m_timer->pop_client_item ();
> +  }
> +
> + private:
> +  // Private to disallow copies.
> +  auto_client_timevar (const auto_client_timevar &);

I don't know why it's important to disallow making copies of
these classes (they look safe to copy) but usually it goes
along with assignment so I would suggest adding a comment
with a rationale and (perhaps also) disabling assignment.

Martin

PS I see auto_timevar is also assignable but not copyable.
It's a common mistake to make to forget about one or the other
(or both) so if it's intentional it helps to document it.

> +
> +  timer *m_timer;
> +};
> +
>   extern void print_time (const char *, long);
>   
>   #endif /* ! GCC_TIMEVAR_H */
> 

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 04/49] sbitmap.h: add operator const sbitmap & to auto_sbitmap
  2019-11-16  1:20 ` [PATCH 04/49] sbitmap.h: add operator const sbitmap & to auto_sbitmap David Malcolm
@ 2019-12-04 16:54   ` Martin Sebor
  0 siblings, 0 replies; 160+ messages in thread
From: Martin Sebor @ 2019-12-04 16:54 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On 11/15/19 6:22 PM, David Malcolm wrote:
> gcc/ChangeLog:
> 	* sbitmap.h (auto_sbitmap): Add operator const sbitmap &.
> ---
>   gcc/sbitmap.h | 1 +
>   1 file changed, 1 insertion(+)
> 
> diff --git a/gcc/sbitmap.h b/gcc/sbitmap.h
> index a33175d..3c5b0a4 100644
> --- a/gcc/sbitmap.h
> +++ b/gcc/sbitmap.h
> @@ -295,6 +295,7 @@ public:
>   
>     /* Allow calling sbitmap functions on our bitmap.  */
>     operator sbitmap () { return m_bitmap; }
> +  operator const sbitmap &() const { return m_bitmap; }

Did you mean:

   operator const_sbitmap () const { return m_bitmap; }

?

bitmap is a pointer to simple_bitmap_def so const sbitmap& is
a const reference to a non-constant simple_bitmap_def, i.e.,
a modifiable simple_bitmap_def object.  I'm guessing the goal
of the new overload is to prevent the object from being
modified.

Otherwise, if you're just adding the overload to make
conversions from const auto_sbitmap valid, then making
the existing operator const would do that.

Martin

>   
>   private:
>     /* Prevent making a copy that refers to our sbitmap.  */
> 

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 09/49] gimple const-correctness fixes
  2019-11-16  1:17 ` [PATCH 09/49] gimple const-correctness fixes David Malcolm
@ 2019-12-04 17:06   ` Martin Sebor
  2019-12-06 10:52   ` Richard Biener
  2019-12-07 14:28   ` Jeff Law
  2 siblings, 0 replies; 160+ messages in thread
From: Martin Sebor @ 2019-12-04 17:06 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On 11/15/19 6:22 PM, David Malcolm wrote:
> This patch converts various "gimple *" to "const gimple *" and similar
> fixes for gimple subclasses, adding is_a_helper for gimple subclasses
> to support the const form of as_a, and adding a few "const" overloads
> of accessors.
> 
> This is enough to make pp_gimple_stmt_1's stmt const.

I haven't reviewed these changes in much detail but from what
I saw they all look straightforward.  I would hope us to be
able to these types of improvements throughout the code base
without the overhead of code review (both adding the const
and removing the redundant class-key).

I noticed you had to add a few const_casts -- I'm guessing
that's because more const correctness changes are still
necessary.

Thanks for the nice cleanup!

Martin

> 
> gcc/ChangeLog:
> 	* gimple-predict.h (gimple_predict_predictor): Make "gs" param
> 	const.
> 	(gimple_predict_outcome): Likewise.
> 	* gimple-pretty-print.c (do_niy): Likewise.
> 	(dump_unary_rhs): Likewise.
> 	(dump_binary_rhs): Likewise.
> 	(dump_ternary_rhs): Likewise.
> 	(dump_gimple_assign): Likewise.
> 	(dump_gimple_return): Likewise.
> 	(dump_gimple_call_args): Likewise.
> 	(pp_points_to_solution): Make "pt" param const.
> 	(dump_gimple_call): Make "gs" param const.
> 	(dump_gimple_switch): Likewise.
> 	(dump_gimple_cond): Likewise.
> 	(dump_gimple_label): Likewise.
> 	(dump_gimple_goto): Likewise.
> 	(dump_gimple_bind): Likewise.
> 	(dump_gimple_try): Likewise.
> 	(dump_gimple_catch): Likewise.
> 	(dump_gimple_eh_filter): Likewise.
> 	(dump_gimple_eh_must_not_throw): Likewise.
> 	(dump_gimple_eh_else): Likewise.
> 	(dump_gimple_resx): Likewise.
> 	(dump_gimple_eh_dispatch): Likewise.
> 	(dump_gimple_debug): Likewise.
> 	(dump_gimple_omp_for): Likewise.
> 	(dump_gimple_omp_continue): Likewise.
> 	(dump_gimple_omp_single): Likewise.
> 	(dump_gimple_omp_taskgroup): Likewise.
> 	(dump_gimple_omp_target): Likewise.
> 	(dump_gimple_omp_teams): Likewise.
> 	(dump_gimple_omp_sections): Likewise.
> 	(dump_gimple_omp_block): Likewise.
> 	(dump_gimple_omp_critical): Likewise.
> 	(dump_gimple_omp_ordered): Likewise.
> 	(dump_gimple_omp_scan): Likewise.
> 	(dump_gimple_omp_return): Likewise.
> 	(dump_gimple_transaction): Likewise.
> 	(dump_gimple_asm): Likewise.
> 	(dump_gimple_phi): Make "phi" param const.
> 	(dump_gimple_omp_parallel): Make "gs" param const.
> 	(dump_gimple_omp_task): Likewise.
> 	(dump_gimple_omp_atomic_load): Likewise.
> 	(dump_gimple_omp_atomic_store): Likewise.
> 	(dump_gimple_mem_ops): Likewise.
> 	(pp_gimple_stmt_1): Likewise.  Add "const" to the various as_a <>
> 	casts throughout.
> 	* gimple-pretty-print.h (gimple_stmt_1): Make gimple * param const.
> 	* gimple.h (is_a_helper <const gdebug *>::test): New.
> 	(is_a_helper <const ggoto *>::test): New.
> 	(is_a_helper <const glabel *>::test): New.
> 	(is_a_helper <const geh_else *>::test): New.
> 	(is_a_helper <const geh_mnt *>::test): New.
> 	(is_a_helper <const gswitch *>::test): New.
> 	(is_a_helper <const gtry *>::test): New.
> 	(is_a_helper <const greturn *>::test): New.
> 	(gimple_call_tail_p): Make param const.
> 	(gimple_call_return_slot_opt_p): Likewise.
> 	(gimple_call_va_arg_pack_p): Likewise.
> 	(gimple_call_use_set): Add const overload.
> 	(gimple_call_clobber_set): Likewise.
> 	(gimple_has_lhs): Make param const.
> 	(gimple_bind_body): Likewise.
> 	(gimple_catch_handler): Likewise.
> 	(gimple_eh_filter_failure): Likewise.
> 	(gimple_eh_must_not_throw_fndecl): Likewise.
> 	(gimple_eh_else_n_body): Likewise.
> 	(gimple_eh_else_e_body): Likewise.
> 	(gimple_try_eval): Likewise.
> 	(gimple_try_cleanup): Likewise.
> 	(gimple_phi_arg): Add const overload.
> 	(gimple_phi_arg_def): Make param const.
> 	(gimple_phi_arg_edge): Likewise.
> 	(gimple_phi_arg_location): Likewise.
> 	(gimple_phi_arg_has_location): Likewise.
> 	(gimple_debug_bind_get_var): Likewise.
> 	(gimple_debug_bind_get_value): Likewise.
> 	(gimple_debug_source_bind_get_var): Likewise.
> 	(gimple_debug_source_bind_get_value): Likewise.
> 	(gimple_omp_body): Likewise.
> 	(gimple_omp_for_collapse): Likewise.
> 	(gimple_omp_for_pre_body): Likewise.
> 	(gimple_transaction_body): Likewise.
> 	* tree-eh.c (lookup_stmt_eh_lp_fn): Make param "t" const.
> 	(lookup_stmt_eh_lp): Likewise.
> 	* tree-eh.h (lookup_stmt_eh_lp_fn): Make param const.
> 	(lookup_stmt_eh_lp): Likewise.
> 	* tree-ssa-alias.h (pt_solution_empty_p): Make param const.
> 	* tree-ssa-structalias.c (pt_solution_empty_p): Likewise.
> ---
>   gcc/gimple-predict.h       |   4 +-
>   gcc/gimple-pretty-print.c  | 159 +++++++++++++++++++++++----------------------
>   gcc/gimple-pretty-print.h  |   3 +-
>   gcc/gimple.h               | 156 ++++++++++++++++++++++++++++++++++----------
>   gcc/tree-eh.c              |   6 +-
>   gcc/tree-eh.h              |   4 +-
>   gcc/tree-ssa-alias.h       |   2 +-
>   gcc/tree-ssa-structalias.c |   2 +-
>   8 files changed, 213 insertions(+), 123 deletions(-)
> 
> diff --git a/gcc/gimple-predict.h b/gcc/gimple-predict.h
> index 761098b..d976317 100644
> --- a/gcc/gimple-predict.h
> +++ b/gcc/gimple-predict.h
> @@ -26,7 +26,7 @@ along with GCC; see the file COPYING3.  If not see
>   /* Return the predictor of GIMPLE_PREDICT statement GS.  */
>   
>   static inline enum br_predictor
> -gimple_predict_predictor (gimple *gs)
> +gimple_predict_predictor (const gimple *gs)
>   {
>     GIMPLE_CHECK (gs, GIMPLE_PREDICT);
>     return (enum br_predictor) (gs->subcode & ~GF_PREDICT_TAKEN);
> @@ -47,7 +47,7 @@ gimple_predict_set_predictor (gimple *gs, enum br_predictor predictor)
>   /* Return the outcome of GIMPLE_PREDICT statement GS.  */
>   
>   static inline enum prediction
> -gimple_predict_outcome (gimple *gs)
> +gimple_predict_outcome (const gimple *gs)
>   {
>     GIMPLE_CHECK (gs, GIMPLE_PREDICT);
>     return (gs->subcode & GF_PREDICT_TAKEN) ? TAKEN : NOT_TAKEN;
> diff --git a/gcc/gimple-pretty-print.c b/gcc/gimple-pretty-print.c
> index 2d5ece0..f044628 100644
> --- a/gcc/gimple-pretty-print.c
> +++ b/gcc/gimple-pretty-print.c
> @@ -59,7 +59,7 @@ along with GCC; see the file COPYING3.  If not see
>      gimple statement GS.  */
>   
>   static void
> -do_niy (pretty_printer *buffer, gimple *gs)
> +do_niy (pretty_printer *buffer, const gimple *gs)
>   {
>     pp_printf (buffer, "<<< Unknown GIMPLE statement: %s >>>\n",
>   	     gimple_code_name[(int) gimple_code (gs)]);
> @@ -327,7 +327,7 @@ dump_gimple_fmt (pretty_printer *buffer, int spc, dump_flags_t flags,
>      assignment GS.  BUFFER, SPC and FLAGS are as in pp_gimple_stmt_1.  */
>   
>   static void
> -dump_unary_rhs (pretty_printer *buffer, gassign *gs, int spc,
> +dump_unary_rhs (pretty_printer *buffer, const gassign *gs, int spc,
>   		dump_flags_t flags)
>   {
>     enum tree_code rhs_code = gimple_assign_rhs_code (gs);
> @@ -423,7 +423,7 @@ dump_unary_rhs (pretty_printer *buffer, gassign *gs, int spc,
>      assignment GS.  BUFFER, SPC and FLAGS are as in pp_gimple_stmt_1.  */
>   
>   static void
> -dump_binary_rhs (pretty_printer *buffer, gassign *gs, int spc,
> +dump_binary_rhs (pretty_printer *buffer, const gassign *gs, int spc,
>   		 dump_flags_t flags)
>   {
>     const char *p;
> @@ -495,7 +495,7 @@ dump_binary_rhs (pretty_printer *buffer, gassign *gs, int spc,
>      assignment GS.  BUFFER, SPC and FLAGS are as in pp_gimple_stmt_1.  */
>   
>   static void
> -dump_ternary_rhs (pretty_printer *buffer, gassign *gs, int spc,
> +dump_ternary_rhs (pretty_printer *buffer, const gassign *gs, int spc,
>   		  dump_flags_t flags)
>   {
>     const char *p;
> @@ -625,7 +625,7 @@ dump_ternary_rhs (pretty_printer *buffer, gassign *gs, int spc,
>      pp_gimple_stmt_1.  */
>   
>   static void
> -dump_gimple_assign (pretty_printer *buffer, gassign *gs, int spc,
> +dump_gimple_assign (pretty_printer *buffer, const gassign *gs, int spc,
>   		    dump_flags_t flags)
>   {
>     if (flags & TDF_RAW)
> @@ -687,7 +687,7 @@ dump_gimple_assign (pretty_printer *buffer, gassign *gs, int spc,
>      pp_gimple_stmt_1.  */
>   
>   static void
> -dump_gimple_return (pretty_printer *buffer, greturn *gs, int spc,
> +dump_gimple_return (pretty_printer *buffer, const greturn *gs, int spc,
>   		    dump_flags_t flags)
>   {
>     tree t;
> @@ -712,7 +712,8 @@ dump_gimple_return (pretty_printer *buffer, greturn *gs, int spc,
>      dump_gimple_call.  */
>   
>   static void
> -dump_gimple_call_args (pretty_printer *buffer, gcall *gs, dump_flags_t flags)
> +dump_gimple_call_args (pretty_printer *buffer, const gcall *gs,
> +		       dump_flags_t flags)
>   {
>     size_t i = 0;
>   
> @@ -795,7 +796,7 @@ dump_gimple_call_args (pretty_printer *buffer, gcall *gs, dump_flags_t flags)
>   /* Dump the points-to solution *PT to BUFFER.  */
>   
>   static void
> -pp_points_to_solution (pretty_printer *buffer, struct pt_solution *pt)
> +pp_points_to_solution (pretty_printer *buffer, const pt_solution *pt)
>   {
>     if (pt->anything)
>       {
> @@ -868,7 +869,7 @@ pp_points_to_solution (pretty_printer *buffer, struct pt_solution *pt)
>      pp_gimple_stmt_1.  */
>   
>   static void
> -dump_gimple_call (pretty_printer *buffer, gcall *gs, int spc,
> +dump_gimple_call (pretty_printer *buffer, const gcall *gs, int spc,
>   		  dump_flags_t flags)
>   {
>     tree lhs = gimple_call_lhs (gs);
> @@ -876,7 +877,7 @@ dump_gimple_call (pretty_printer *buffer, gcall *gs, int spc,
>   
>     if (flags & TDF_ALIAS)
>       {
> -      struct pt_solution *pt;
> +      const pt_solution *pt;
>         pt = gimple_call_use_set (gs);
>         if (!pt_solution_empty_p (pt))
>   	{
> @@ -1006,7 +1007,7 @@ dump_gimple_call (pretty_printer *buffer, gcall *gs, int spc,
>      pp_gimple_stmt_1.  */
>   
>   static void
> -dump_gimple_switch (pretty_printer *buffer, gswitch *gs, int spc,
> +dump_gimple_switch (pretty_printer *buffer, const gswitch *gs, int spc,
>   		    dump_flags_t flags)
>   {
>     unsigned int i;
> @@ -1064,7 +1065,7 @@ dump_gimple_switch (pretty_printer *buffer, gswitch *gs, int spc,
>      pp_gimple_stmt_1.  */
>   
>   static void
> -dump_gimple_cond (pretty_printer *buffer, gcond *gs, int spc,
> +dump_gimple_cond (pretty_printer *buffer, const gcond *gs, int spc,
>   		  dump_flags_t flags)
>   {
>     if (flags & TDF_RAW)
> @@ -1131,7 +1132,7 @@ dump_gimple_cond (pretty_printer *buffer, gcond *gs, int spc,
>      TDF_* in dumpfils.h).  */
>   
>   static void
> -dump_gimple_label (pretty_printer *buffer, glabel *gs, int spc,
> +dump_gimple_label (pretty_printer *buffer, const glabel *gs, int spc,
>   		   dump_flags_t flags)
>   {
>     tree label = gimple_label_label (gs);
> @@ -1155,7 +1156,7 @@ dump_gimple_label (pretty_printer *buffer, glabel *gs, int spc,
>      TDF_* in dumpfile.h).  */
>   
>   static void
> -dump_gimple_goto (pretty_printer *buffer, ggoto *gs, int spc,
> +dump_gimple_goto (pretty_printer *buffer, const ggoto *gs, int spc,
>   		  dump_flags_t flags)
>   {
>     tree label = gimple_goto_dest (gs);
> @@ -1171,7 +1172,7 @@ dump_gimple_goto (pretty_printer *buffer, ggoto *gs, int spc,
>      TDF_* in dumpfile.h).  */
>   
>   static void
> -dump_gimple_bind (pretty_printer *buffer, gbind *gs, int spc,
> +dump_gimple_bind (pretty_printer *buffer, const gbind *gs, int spc,
>   		  dump_flags_t flags)
>   {
>     if (flags & TDF_RAW)
> @@ -1205,7 +1206,7 @@ dump_gimple_bind (pretty_printer *buffer, gbind *gs, int spc,
>      dumpfile.h).  */
>   
>   static void
> -dump_gimple_try (pretty_printer *buffer, gtry *gs, int spc,
> +dump_gimple_try (pretty_printer *buffer, const gtry *gs, int spc,
>   		 dump_flags_t flags)
>   {
>     if (flags & TDF_RAW)
> @@ -1280,7 +1281,7 @@ dump_gimple_try (pretty_printer *buffer, gtry *gs, int spc,
>      dumpfile.h).  */
>   
>   static void
> -dump_gimple_catch (pretty_printer *buffer, gcatch *gs, int spc,
> +dump_gimple_catch (pretty_printer *buffer, const gcatch *gs, int spc,
>   		   dump_flags_t flags)
>   {
>     if (flags & TDF_RAW)
> @@ -1297,7 +1298,7 @@ dump_gimple_catch (pretty_printer *buffer, gcatch *gs, int spc,
>      dumpfile.h).  */
>   
>   static void
> -dump_gimple_eh_filter (pretty_printer *buffer, geh_filter *gs, int spc,
> +dump_gimple_eh_filter (pretty_printer *buffer, const geh_filter *gs, int spc,
>   		       dump_flags_t flags)
>   {
>     if (flags & TDF_RAW)
> @@ -1315,7 +1316,7 @@ dump_gimple_eh_filter (pretty_printer *buffer, geh_filter *gs, int spc,
>   
>   static void
>   dump_gimple_eh_must_not_throw (pretty_printer *buffer,
> -			       geh_mnt *gs, int spc, dump_flags_t flags)
> +			       const geh_mnt *gs, int spc, dump_flags_t flags)
>   {
>     if (flags & TDF_RAW)
>       dump_gimple_fmt (buffer, spc, flags, "%G <%T>", gs,
> @@ -1331,7 +1332,7 @@ dump_gimple_eh_must_not_throw (pretty_printer *buffer,
>      dumpfile.h).  */
>   
>   static void
> -dump_gimple_eh_else (pretty_printer *buffer, geh_else *gs, int spc,
> +dump_gimple_eh_else (pretty_printer *buffer, const geh_else *gs, int spc,
>   		     dump_flags_t flags)
>   {
>     if (flags & TDF_RAW)
> @@ -1350,7 +1351,7 @@ dump_gimple_eh_else (pretty_printer *buffer, geh_else *gs, int spc,
>      dumpfile.h).  */
>   
>   static void
> -dump_gimple_resx (pretty_printer *buffer, gresx *gs, int spc,
> +dump_gimple_resx (pretty_printer *buffer, const gresx *gs, int spc,
>   		  dump_flags_t flags)
>   {
>     if (flags & TDF_RAW)
> @@ -1363,8 +1364,8 @@ dump_gimple_resx (pretty_printer *buffer, gresx *gs, int spc,
>   /* Dump a GIMPLE_EH_DISPATCH tuple on the pretty_printer BUFFER.  */
>   
>   static void
> -dump_gimple_eh_dispatch (pretty_printer *buffer, geh_dispatch *gs, int spc,
> -			 dump_flags_t flags)
> +dump_gimple_eh_dispatch (pretty_printer *buffer, const geh_dispatch *gs,
> +			 int spc, dump_flags_t flags)
>   {
>     if (flags & TDF_RAW)
>       dump_gimple_fmt (buffer, spc, flags, "%G <%d>", gs,
> @@ -1379,7 +1380,7 @@ dump_gimple_eh_dispatch (pretty_printer *buffer, geh_dispatch *gs, int spc,
>      in dumpfile.h).  */
>   
>   static void
> -dump_gimple_debug (pretty_printer *buffer, gdebug *gs, int spc,
> +dump_gimple_debug (pretty_printer *buffer, const gdebug *gs, int spc,
>   		   dump_flags_t flags)
>   {
>     switch (gs->subcode)
> @@ -1433,7 +1434,7 @@ dump_gimple_debug (pretty_printer *buffer, gdebug *gs, int spc,
>   
>   /* Dump a GIMPLE_OMP_FOR tuple on the pretty_printer BUFFER.  */
>   static void
> -dump_gimple_omp_for (pretty_printer *buffer, gomp_for *gs, int spc,
> +dump_gimple_omp_for (pretty_printer *buffer, const gomp_for *gs, int spc,
>   		     dump_flags_t flags)
>   {
>     size_t i;
> @@ -1566,7 +1567,7 @@ dump_gimple_omp_for (pretty_printer *buffer, gomp_for *gs, int spc,
>   /* Dump a GIMPLE_OMP_CONTINUE tuple on the pretty_printer BUFFER.  */
>   
>   static void
> -dump_gimple_omp_continue (pretty_printer *buffer, gomp_continue *gs,
> +dump_gimple_omp_continue (pretty_printer *buffer, const gomp_continue *gs,
>   			  int spc, dump_flags_t flags)
>   {
>     if (flags & TDF_RAW)
> @@ -1591,7 +1592,7 @@ dump_gimple_omp_continue (pretty_printer *buffer, gomp_continue *gs,
>   /* Dump a GIMPLE_OMP_SINGLE tuple on the pretty_printer BUFFER.  */
>   
>   static void
> -dump_gimple_omp_single (pretty_printer *buffer, gomp_single *gs,
> +dump_gimple_omp_single (pretty_printer *buffer, const gomp_single *gs,
>   			int spc, dump_flags_t flags)
>   {
>     if (flags & TDF_RAW)
> @@ -1620,7 +1621,7 @@ dump_gimple_omp_single (pretty_printer *buffer, gomp_single *gs,
>   /* Dump a GIMPLE_OMP_TASKGROUP tuple on the pretty_printer BUFFER.  */
>   
>   static void
> -dump_gimple_omp_taskgroup (pretty_printer *buffer, gimple *gs,
> +dump_gimple_omp_taskgroup (pretty_printer *buffer, const gimple *gs,
>   			   int spc, dump_flags_t flags)
>   {
>     if (flags & TDF_RAW)
> @@ -1649,7 +1650,7 @@ dump_gimple_omp_taskgroup (pretty_printer *buffer, gimple *gs,
>   /* Dump a GIMPLE_OMP_TARGET tuple on the pretty_printer BUFFER.  */
>   
>   static void
> -dump_gimple_omp_target (pretty_printer *buffer, gomp_target *gs,
> +dump_gimple_omp_target (pretty_printer *buffer, const gomp_target *gs,
>   			int spc, dump_flags_t flags)
>   {
>     const char *kind;
> @@ -1742,7 +1743,7 @@ dump_gimple_omp_target (pretty_printer *buffer, gomp_target *gs,
>   /* Dump a GIMPLE_OMP_TEAMS tuple on the pretty_printer BUFFER.  */
>   
>   static void
> -dump_gimple_omp_teams (pretty_printer *buffer, gomp_teams *gs, int spc,
> +dump_gimple_omp_teams (pretty_printer *buffer, const gomp_teams *gs, int spc,
>   		       dump_flags_t flags)
>   {
>     if (flags & TDF_RAW)
> @@ -1771,7 +1772,7 @@ dump_gimple_omp_teams (pretty_printer *buffer, gomp_teams *gs, int spc,
>   /* Dump a GIMPLE_OMP_SECTIONS tuple on the pretty_printer BUFFER.  */
>   
>   static void
> -dump_gimple_omp_sections (pretty_printer *buffer, gomp_sections *gs,
> +dump_gimple_omp_sections (pretty_printer *buffer, const gomp_sections *gs,
>   			  int spc, dump_flags_t flags)
>   {
>     if (flags & TDF_RAW)
> @@ -1808,7 +1809,7 @@ dump_gimple_omp_sections (pretty_printer *buffer, gomp_sections *gs,
>      pretty_printer BUFFER.  */
>   
>   static void
> -dump_gimple_omp_block (pretty_printer *buffer, gimple *gs, int spc,
> +dump_gimple_omp_block (pretty_printer *buffer, const gimple *gs, int spc,
>   		       dump_flags_t flags)
>   {
>     if (flags & TDF_RAW)
> @@ -1845,7 +1846,7 @@ dump_gimple_omp_block (pretty_printer *buffer, gimple *gs, int spc,
>   /* Dump a GIMPLE_OMP_CRITICAL tuple on the pretty_printer BUFFER.  */
>   
>   static void
> -dump_gimple_omp_critical (pretty_printer *buffer, gomp_critical *gs,
> +dump_gimple_omp_critical (pretty_printer *buffer, const gomp_critical *gs,
>   			  int spc, dump_flags_t flags)
>   {
>     if (flags & TDF_RAW)
> @@ -1877,7 +1878,7 @@ dump_gimple_omp_critical (pretty_printer *buffer, gomp_critical *gs,
>   /* Dump a GIMPLE_OMP_ORDERED tuple on the pretty_printer BUFFER.  */
>   
>   static void
> -dump_gimple_omp_ordered (pretty_printer *buffer, gomp_ordered *gs,
> +dump_gimple_omp_ordered (pretty_printer *buffer, const gomp_ordered *gs,
>   			 int spc, dump_flags_t flags)
>   {
>     if (flags & TDF_RAW)
> @@ -1902,7 +1903,7 @@ dump_gimple_omp_ordered (pretty_printer *buffer, gomp_ordered *gs,
>   /* Dump a GIMPLE_OMP_SCAN tuple on the pretty_printer BUFFER.  */
>   
>   static void
> -dump_gimple_omp_scan (pretty_printer *buffer, gomp_scan *gs,
> +dump_gimple_omp_scan (pretty_printer *buffer, const gomp_scan *gs,
>   		      int spc, dump_flags_t flags)
>   {
>     if (flags & TDF_RAW)
> @@ -1930,7 +1931,7 @@ dump_gimple_omp_scan (pretty_printer *buffer, gomp_scan *gs,
>   /* Dump a GIMPLE_OMP_RETURN tuple on the pretty_printer BUFFER.  */
>   
>   static void
> -dump_gimple_omp_return (pretty_printer *buffer, gimple *gs, int spc,
> +dump_gimple_omp_return (pretty_printer *buffer, const gimple *gs, int spc,
>   			dump_flags_t flags)
>   {
>     if (flags & TDF_RAW)
> @@ -1961,7 +1962,7 @@ dump_gimple_omp_return (pretty_printer *buffer, gimple *gs, int spc,
>   /* Dump a GIMPLE_TRANSACTION tuple on the pretty_printer BUFFER.  */
>   
>   static void
> -dump_gimple_transaction (pretty_printer *buffer, gtransaction *gs,
> +dump_gimple_transaction (pretty_printer *buffer, const gtransaction *gs,
>   			 int spc, dump_flags_t flags)
>   {
>     unsigned subcode = gimple_transaction_subcode (gs);
> @@ -2063,7 +2064,8 @@ dump_gimple_transaction (pretty_printer *buffer, gtransaction *gs,
>      dumpfile.h).  */
>   
>   static void
> -dump_gimple_asm (pretty_printer *buffer, gasm *gs, int spc, dump_flags_t flags)
> +dump_gimple_asm (pretty_printer *buffer, const gasm *gs, int spc,
> +		 dump_flags_t flags)
>   {
>     unsigned int i, n, f, fields;
>   
> @@ -2282,7 +2284,7 @@ dump_ssaname_info_to_file (FILE *file, tree node, int spc)
>      pretty printer.  If COMMENT is true, print this after #.  */
>   
>   static void
> -dump_gimple_phi (pretty_printer *buffer, gphi *phi, int spc, bool comment,
> +dump_gimple_phi (pretty_printer *buffer, const gphi *phi, int spc, bool comment,
>   		 dump_flags_t flags)
>   {
>     size_t i;
> @@ -2339,7 +2341,7 @@ dump_gimple_phi (pretty_printer *buffer, gphi *phi, int spc, bool comment,
>      dumpfile.h).  */
>   
>   static void
> -dump_gimple_omp_parallel (pretty_printer *buffer, gomp_parallel *gs,
> +dump_gimple_omp_parallel (pretty_printer *buffer, const gomp_parallel *gs,
>   			  int spc, dump_flags_t flags)
>   {
>     if (flags & TDF_RAW)
> @@ -2393,7 +2395,7 @@ dump_gimple_omp_parallel (pretty_printer *buffer, gomp_parallel *gs,
>      dumpfile.h).  */
>   
>   static void
> -dump_gimple_omp_task (pretty_printer *buffer, gomp_task *gs, int spc,
> +dump_gimple_omp_task (pretty_printer *buffer, const gomp_task *gs, int spc,
>   		      dump_flags_t flags)
>   {
>     if (flags & TDF_RAW)
> @@ -2455,7 +2457,7 @@ dump_gimple_omp_task (pretty_printer *buffer, gomp_task *gs, int spc,
>      in dumpfile.h).  */
>   
>   static void
> -dump_gimple_omp_atomic_load (pretty_printer *buffer, gomp_atomic_load *gs,
> +dump_gimple_omp_atomic_load (pretty_printer *buffer, const gomp_atomic_load *gs,
>   			     int spc, dump_flags_t flags)
>   {
>     if (flags & TDF_RAW)
> @@ -2489,7 +2491,7 @@ dump_gimple_omp_atomic_load (pretty_printer *buffer, gomp_atomic_load *gs,
>   
>   static void
>   dump_gimple_omp_atomic_store (pretty_printer *buffer,
> -			      gomp_atomic_store *gs, int spc,
> +			      const gomp_atomic_store *gs, int spc,
>   			      dump_flags_t flags)
>   {
>     if (flags & TDF_RAW)
> @@ -2517,7 +2519,7 @@ dump_gimple_omp_atomic_store (pretty_printer *buffer,
>      FLAGS are as in pp_gimple_stmt_1.  */
>   
>   static void
> -dump_gimple_mem_ops (pretty_printer *buffer, gimple *gs, int spc,
> +dump_gimple_mem_ops (pretty_printer *buffer, const gimple *gs, int spc,
>   		     dump_flags_t flags)
>   {
>     tree vdef = gimple_vdef (gs);
> @@ -2548,14 +2550,14 @@ dump_gimple_mem_ops (pretty_printer *buffer, gimple *gs, int spc,
>      pp_flush on BUFFER to finalize the pretty printer.  */
>   
>   void
> -pp_gimple_stmt_1 (pretty_printer *buffer, gimple *gs, int spc,
> +pp_gimple_stmt_1 (pretty_printer *buffer, const gimple *gs, int spc,
>   		  dump_flags_t flags)
>   {
>     if (!gs)
>       return;
>   
>     if (flags & TDF_STMTADDR)
> -    pp_printf (buffer, "<&%p> ", (void *) gs);
> +    pp_printf (buffer, "<&%p> ", (const void *) gs);
>   
>     if ((flags & TDF_LINENO) && gimple_has_location (gs))
>       dump_location (buffer, gimple_location (gs));
> @@ -2580,31 +2582,31 @@ pp_gimple_stmt_1 (pretty_printer *buffer, gimple *gs, int spc,
>     switch (gimple_code (gs))
>       {
>       case GIMPLE_ASM:
> -      dump_gimple_asm (buffer, as_a <gasm *> (gs), spc, flags);
> +      dump_gimple_asm (buffer, as_a <const gasm *> (gs), spc, flags);
>         break;
>   
>       case GIMPLE_ASSIGN:
> -      dump_gimple_assign (buffer, as_a <gassign *> (gs), spc, flags);
> +      dump_gimple_assign (buffer, as_a <const gassign *> (gs), spc, flags);
>         break;
>   
>       case GIMPLE_BIND:
> -      dump_gimple_bind (buffer, as_a <gbind *> (gs), spc, flags);
> +      dump_gimple_bind (buffer, as_a <const gbind *> (gs), spc, flags);
>         break;
>   
>       case GIMPLE_CALL:
> -      dump_gimple_call (buffer, as_a <gcall *> (gs), spc, flags);
> +      dump_gimple_call (buffer, as_a <const gcall *> (gs), spc, flags);
>         break;
>   
>       case GIMPLE_COND:
> -      dump_gimple_cond (buffer, as_a <gcond *> (gs), spc, flags);
> +      dump_gimple_cond (buffer, as_a <const gcond *> (gs), spc, flags);
>         break;
>   
>       case GIMPLE_LABEL:
> -      dump_gimple_label (buffer, as_a <glabel *> (gs), spc, flags);
> +      dump_gimple_label (buffer, as_a <const glabel *> (gs), spc, flags);
>         break;
>   
>       case GIMPLE_GOTO:
> -      dump_gimple_goto (buffer, as_a <ggoto *> (gs), spc, flags);
> +      dump_gimple_goto (buffer, as_a <const ggoto *> (gs), spc, flags);
>         break;
>   
>       case GIMPLE_NOP:
> @@ -2612,62 +2614,62 @@ pp_gimple_stmt_1 (pretty_printer *buffer, gimple *gs, int spc,
>         break;
>   
>       case GIMPLE_RETURN:
> -      dump_gimple_return (buffer, as_a <greturn *> (gs), spc, flags);
> +      dump_gimple_return (buffer, as_a <const greturn *> (gs), spc, flags);
>         break;
>   
>       case GIMPLE_SWITCH:
> -      dump_gimple_switch (buffer, as_a <gswitch *> (gs), spc, flags);
> +      dump_gimple_switch (buffer, as_a <const gswitch *> (gs), spc, flags);
>         break;
>   
>       case GIMPLE_TRY:
> -      dump_gimple_try (buffer, as_a <gtry *> (gs), spc, flags);
> +      dump_gimple_try (buffer, as_a <const gtry *> (gs), spc, flags);
>         break;
>   
>       case GIMPLE_PHI:
> -      dump_gimple_phi (buffer, as_a <gphi *> (gs), spc, false, flags);
> +      dump_gimple_phi (buffer, as_a <const gphi *> (gs), spc, false, flags);
>         break;
>   
>       case GIMPLE_OMP_PARALLEL:
> -      dump_gimple_omp_parallel (buffer, as_a <gomp_parallel *> (gs), spc,
> +      dump_gimple_omp_parallel (buffer, as_a <const gomp_parallel *> (gs), spc,
>   				flags);
>         break;
>   
>       case GIMPLE_OMP_TASK:
> -      dump_gimple_omp_task (buffer, as_a <gomp_task *> (gs), spc, flags);
> +      dump_gimple_omp_task (buffer, as_a <const gomp_task *> (gs), spc, flags);
>         break;
>   
>       case GIMPLE_OMP_ATOMIC_LOAD:
> -      dump_gimple_omp_atomic_load (buffer, as_a <gomp_atomic_load *> (gs),
> +      dump_gimple_omp_atomic_load (buffer, as_a <const gomp_atomic_load *> (gs),
>   				   spc, flags);
>         break;
>   
>       case GIMPLE_OMP_ATOMIC_STORE:
>         dump_gimple_omp_atomic_store (buffer,
> -				    as_a <gomp_atomic_store *> (gs),
> +				    as_a <const gomp_atomic_store *> (gs),
>   				    spc, flags);
>         break;
>   
>       case GIMPLE_OMP_FOR:
> -      dump_gimple_omp_for (buffer, as_a <gomp_for *> (gs), spc, flags);
> +      dump_gimple_omp_for (buffer, as_a <const gomp_for *> (gs), spc, flags);
>         break;
>   
>       case GIMPLE_OMP_CONTINUE:
> -      dump_gimple_omp_continue (buffer, as_a <gomp_continue *> (gs), spc,
> +      dump_gimple_omp_continue (buffer, as_a <const gomp_continue *> (gs), spc,
>   				flags);
>         break;
>   
>       case GIMPLE_OMP_SINGLE:
> -      dump_gimple_omp_single (buffer, as_a <gomp_single *> (gs), spc,
> +      dump_gimple_omp_single (buffer, as_a <const gomp_single *> (gs), spc,
>   			      flags);
>         break;
>   
>       case GIMPLE_OMP_TARGET:
> -      dump_gimple_omp_target (buffer, as_a <gomp_target *> (gs), spc,
> +      dump_gimple_omp_target (buffer, as_a <const gomp_target *> (gs), spc,
>   			      flags);
>         break;
>   
>       case GIMPLE_OMP_TEAMS:
> -      dump_gimple_omp_teams (buffer, as_a <gomp_teams *> (gs), spc,
> +      dump_gimple_omp_teams (buffer, as_a <const gomp_teams *> (gs), spc,
>   			     flags);
>         break;
>   
> @@ -2676,7 +2678,7 @@ pp_gimple_stmt_1 (pretty_printer *buffer, gimple *gs, int spc,
>         break;
>   
>       case GIMPLE_OMP_SECTIONS:
> -      dump_gimple_omp_sections (buffer, as_a <gomp_sections *> (gs),
> +      dump_gimple_omp_sections (buffer, as_a <const gomp_sections *> (gs),
>   				spc, flags);
>         break;
>   
> @@ -2695,49 +2697,50 @@ pp_gimple_stmt_1 (pretty_printer *buffer, gimple *gs, int spc,
>         break;
>   
>       case GIMPLE_OMP_ORDERED:
> -      dump_gimple_omp_ordered (buffer, as_a <gomp_ordered *> (gs), spc,
> +      dump_gimple_omp_ordered (buffer, as_a <const gomp_ordered *> (gs), spc,
>   			       flags);
>         break;
>   
>       case GIMPLE_OMP_SCAN:
> -      dump_gimple_omp_scan (buffer, as_a <gomp_scan *> (gs), spc,
> +      dump_gimple_omp_scan (buffer, as_a <const gomp_scan *> (gs), spc,
>   			    flags);
>         break;
>   
>       case GIMPLE_OMP_CRITICAL:
> -      dump_gimple_omp_critical (buffer, as_a <gomp_critical *> (gs), spc,
> +      dump_gimple_omp_critical (buffer, as_a <const gomp_critical *> (gs), spc,
>   				flags);
>         break;
>   
>       case GIMPLE_CATCH:
> -      dump_gimple_catch (buffer, as_a <gcatch *> (gs), spc, flags);
> +      dump_gimple_catch (buffer, as_a <const gcatch *> (gs), spc, flags);
>         break;
>   
>       case GIMPLE_EH_FILTER:
> -      dump_gimple_eh_filter (buffer, as_a <geh_filter *> (gs), spc, flags);
> +      dump_gimple_eh_filter (buffer, as_a <const geh_filter *> (gs), spc,
> +			     flags);
>         break;
>   
>       case GIMPLE_EH_MUST_NOT_THROW:
>         dump_gimple_eh_must_not_throw (buffer,
> -				     as_a <geh_mnt *> (gs),
> +				     as_a <const geh_mnt *> (gs),
>   				     spc, flags);
>         break;
>   
>       case GIMPLE_EH_ELSE:
> -      dump_gimple_eh_else (buffer, as_a <geh_else *> (gs), spc, flags);
> +      dump_gimple_eh_else (buffer, as_a <const geh_else *> (gs), spc, flags);
>         break;
>   
>       case GIMPLE_RESX:
> -      dump_gimple_resx (buffer, as_a <gresx *> (gs), spc, flags);
> +      dump_gimple_resx (buffer, as_a <const gresx *> (gs), spc, flags);
>         break;
>   
>       case GIMPLE_EH_DISPATCH:
> -      dump_gimple_eh_dispatch (buffer, as_a <geh_dispatch *> (gs), spc,
> +      dump_gimple_eh_dispatch (buffer, as_a <const geh_dispatch *> (gs), spc,
>   			       flags);
>         break;
>   
>       case GIMPLE_DEBUG:
> -      dump_gimple_debug (buffer, as_a <gdebug *> (gs), spc, flags);
> +      dump_gimple_debug (buffer, as_a <const gdebug *> (gs), spc, flags);
>         break;
>   
>       case GIMPLE_PREDICT:
> @@ -2751,7 +2754,7 @@ pp_gimple_stmt_1 (pretty_printer *buffer, gimple *gs, int spc,
>         break;
>   
>       case GIMPLE_TRANSACTION:
> -      dump_gimple_transaction (buffer, as_a <gtransaction *> (gs), spc,
> +      dump_gimple_transaction (buffer, as_a <const gtransaction *> (gs), spc,
>   			       flags);
>         break;
>   
> diff --git a/gcc/gimple-pretty-print.h b/gcc/gimple-pretty-print.h
> index 8d8e105..186f2cf 100644
> --- a/gcc/gimple-pretty-print.h
> +++ b/gcc/gimple-pretty-print.h
> @@ -31,7 +31,8 @@ extern void print_gimple_stmt (FILE *, gimple *, int, dump_flags_t = TDF_NONE);
>   extern void debug (gimple &ref);
>   extern void debug (gimple *ptr);
>   extern void print_gimple_expr (FILE *, gimple *, int, dump_flags_t = TDF_NONE);
> -extern void pp_gimple_stmt_1 (pretty_printer *, gimple *, int, dump_flags_t);
> +extern void pp_gimple_stmt_1 (pretty_printer *, const gimple *, int,
> +			      dump_flags_t);
>   extern void gimple_dump_bb (FILE *, basic_block, int, dump_flags_t);
>   extern void gimple_dump_bb_for_graph (pretty_printer *, basic_block);
>   extern void dump_ssaname_info_to_file (FILE *, tree, int);
> diff --git a/gcc/gimple.h b/gcc/gimple.h
> index cf1f8da..10a4b8b 100644
> --- a/gcc/gimple.h
> +++ b/gcc/gimple.h
> @@ -1017,6 +1017,14 @@ is_a_helper <gdebug *>::test (gimple *gs)
>   template <>
>   template <>
>   inline bool
> +is_a_helper <const gdebug *>::test (const gimple *gs)
> +{
> +  return gs->code == GIMPLE_DEBUG;
> +}
> +
> +template <>
> +template <>
> +inline bool
>   is_a_helper <ggoto *>::test (gimple *gs)
>   {
>     return gs->code == GIMPLE_GOTO;
> @@ -1025,6 +1033,14 @@ is_a_helper <ggoto *>::test (gimple *gs)
>   template <>
>   template <>
>   inline bool
> +is_a_helper <const ggoto *>::test (const gimple *gs)
> +{
> +  return gs->code == GIMPLE_GOTO;
> +}
> +
> +template <>
> +template <>
> +inline bool
>   is_a_helper <glabel *>::test (gimple *gs)
>   {
>     return gs->code == GIMPLE_LABEL;
> @@ -1033,6 +1049,14 @@ is_a_helper <glabel *>::test (gimple *gs)
>   template <>
>   template <>
>   inline bool
> +is_a_helper <const glabel *>::test (const gimple *gs)
> +{
> +  return gs->code == GIMPLE_LABEL;
> +}
> +
> +template <>
> +template <>
> +inline bool
>   is_a_helper <gresx *>::test (gimple *gs)
>   {
>     return gs->code == GIMPLE_RESX;
> @@ -1057,6 +1081,14 @@ is_a_helper <geh_else *>::test (gimple *gs)
>   template <>
>   template <>
>   inline bool
> +is_a_helper <const geh_else *>::test (const gimple *gs)
> +{
> +  return gs->code == GIMPLE_EH_ELSE;
> +}
> +
> +template <>
> +template <>
> +inline bool
>   is_a_helper <geh_filter *>::test (gimple *gs)
>   {
>     return gs->code == GIMPLE_EH_FILTER;
> @@ -1073,6 +1105,14 @@ is_a_helper <geh_mnt *>::test (gimple *gs)
>   template <>
>   template <>
>   inline bool
> +is_a_helper <const geh_mnt *>::test (const gimple *gs)
> +{
> +  return gs->code == GIMPLE_EH_MUST_NOT_THROW;
> +}
> +
> +template <>
> +template <>
> +inline bool
>   is_a_helper <gomp_atomic_load *>::test (gimple *gs)
>   {
>     return gs->code == GIMPLE_OMP_ATOMIC_LOAD;
> @@ -1219,6 +1259,14 @@ is_a_helper <gswitch *>::test (gimple *gs)
>   template <>
>   template <>
>   inline bool
> +is_a_helper <const gswitch *>::test (const gimple *gs)
> +{
> +  return gs->code == GIMPLE_SWITCH;
> +}
> +
> +template <>
> +template <>
> +inline bool
>   is_a_helper <gtransaction *>::test (gimple *gs)
>   {
>     return gs->code == GIMPLE_TRANSACTION;
> @@ -1235,6 +1283,14 @@ is_a_helper <gtry *>::test (gimple *gs)
>   template <>
>   template <>
>   inline bool
> +is_a_helper <const gtry *>::test (const gimple *gs)
> +{
> +  return gs->code == GIMPLE_TRY;
> +}
> +
> +template <>
> +template <>
> +inline bool
>   is_a_helper <gimple_statement_wce *>::test (gimple *gs)
>   {
>     return gs->code == GIMPLE_WITH_CLEANUP_EXPR;
> @@ -1429,6 +1485,14 @@ is_a_helper <const gphi *>::test (const gimple *gs)
>   template <>
>   template <>
>   inline bool
> +is_a_helper <const greturn *>::test (const gimple *gs)
> +{
> +  return gs->code == GIMPLE_RETURN;
> +}
> +
> +template <>
> +template <>
> +inline bool
>   is_a_helper <const gtransaction *>::test (const gimple *gs)
>   {
>     return gs->code == GIMPLE_TRANSACTION;
> @@ -3254,7 +3318,7 @@ gimple_call_set_tail (gcall *s, bool tail_p)
>   /* Return true if GIMPLE_CALL S is marked as a tail call.  */
>   
>   static inline bool
> -gimple_call_tail_p (gcall *s)
> +gimple_call_tail_p (const gcall *s)
>   {
>     return (s->subcode & GF_CALL_TAILCALL) != 0;
>   }
> @@ -3296,7 +3360,7 @@ gimple_call_set_return_slot_opt (gcall *s, bool return_slot_opt_p)
>   /* Return true if S is marked for return slot optimization.  */
>   
>   static inline bool
> -gimple_call_return_slot_opt_p (gcall *s)
> +gimple_call_return_slot_opt_p (const gcall *s)
>   {
>     return (s->subcode & GF_CALL_RETURN_SLOT_OPT) != 0;
>   }
> @@ -3341,7 +3405,7 @@ gimple_call_set_va_arg_pack (gcall *s, bool pass_arg_pack_p)
>      argument pack in its argument list.  */
>   
>   static inline bool
> -gimple_call_va_arg_pack_p (gcall *s)
> +gimple_call_va_arg_pack_p (const gcall *s)
>   {
>     return (s->subcode & GF_CALL_VA_ARG_PACK) != 0;
>   }
> @@ -3443,6 +3507,13 @@ gimple_call_use_set (gcall *call_stmt)
>     return &call_stmt->call_used;
>   }
>   
> +/* As above, but const.  */
> +
> +static inline const pt_solution *
> +gimple_call_use_set (const gcall *call_stmt)
> +{
> +  return &call_stmt->call_used;
> +}
>   
>   /* Return a pointer to the points-to solution for the set of call-used
>      variables of the call CALL_STMT.  */
> @@ -3453,16 +3524,24 @@ gimple_call_clobber_set (gcall *call_stmt)
>     return &call_stmt->call_clobbered;
>   }
>   
> +/* As above, but const.  */
> +
> +static inline const pt_solution *
> +gimple_call_clobber_set (const gcall *call_stmt)
> +{
> +  return &call_stmt->call_clobbered;
> +}
> +
>   
>   /* Returns true if this is a GIMPLE_ASSIGN or a GIMPLE_CALL with a
>      non-NULL lhs.  */
>   
>   static inline bool
> -gimple_has_lhs (gimple *stmt)
> +gimple_has_lhs (const gimple *stmt)
>   {
>     if (is_gimple_assign (stmt))
>       return true;
> -  if (gcall *call = dyn_cast <gcall *> (stmt))
> +  if (const gcall *call = dyn_cast <const gcall *> (stmt))
>       return gimple_call_lhs (call) != NULL_TREE;
>     return false;
>   }
> @@ -3760,9 +3839,9 @@ gimple_bind_body_ptr (gbind *bind_stmt)
>   /* Return the GIMPLE sequence contained in the GIMPLE_BIND statement GS.  */
>   
>   static inline gimple_seq
> -gimple_bind_body (gbind *gs)
> +gimple_bind_body (const gbind *gs)
>   {
> -  return *gimple_bind_body_ptr (gs);
> +  return *gimple_bind_body_ptr (const_cast <gbind *> (gs));
>   }
>   
>   
> @@ -4034,9 +4113,9 @@ gimple_catch_handler_ptr (gcatch *catch_stmt)
>      GIMPLE_CATCH statement CATCH_STMT.  */
>   
>   static inline gimple_seq
> -gimple_catch_handler (gcatch *catch_stmt)
> +gimple_catch_handler (const gcatch *catch_stmt)
>   {
> -  return *gimple_catch_handler_ptr (catch_stmt);
> +  return *gimple_catch_handler_ptr (const_cast <gcatch *> (catch_stmt));
>   }
>   
>   
> @@ -4094,9 +4173,9 @@ gimple_eh_filter_failure_ptr (gimple *gs)
>      statement fails.  */
>   
>   static inline gimple_seq
> -gimple_eh_filter_failure (gimple *gs)
> +gimple_eh_filter_failure (const gimple *gs)
>   {
> -  return *gimple_eh_filter_failure_ptr (gs);
> +  return *gimple_eh_filter_failure_ptr (const_cast <gimple *> (gs));
>   }
>   
>   
> @@ -4123,7 +4202,7 @@ gimple_eh_filter_set_failure (geh_filter *eh_filter_stmt,
>   /* Get the function decl to be called by the MUST_NOT_THROW region.  */
>   
>   static inline tree
> -gimple_eh_must_not_throw_fndecl (geh_mnt *eh_mnt_stmt)
> +gimple_eh_must_not_throw_fndecl (const geh_mnt *eh_mnt_stmt)
>   {
>     return eh_mnt_stmt->fndecl;
>   }
> @@ -4146,9 +4225,9 @@ gimple_eh_else_n_body_ptr (geh_else *eh_else_stmt)
>   }
>   
>   static inline gimple_seq
> -gimple_eh_else_n_body (geh_else *eh_else_stmt)
> +gimple_eh_else_n_body (const geh_else *eh_else_stmt)
>   {
> -  return *gimple_eh_else_n_body_ptr (eh_else_stmt);
> +  return *gimple_eh_else_n_body_ptr (const_cast <geh_else *> (eh_else_stmt));
>   }
>   
>   static inline gimple_seq *
> @@ -4158,9 +4237,9 @@ gimple_eh_else_e_body_ptr (geh_else *eh_else_stmt)
>   }
>   
>   static inline gimple_seq
> -gimple_eh_else_e_body (geh_else *eh_else_stmt)
> +gimple_eh_else_e_body (const geh_else *eh_else_stmt)
>   {
> -  return *gimple_eh_else_e_body_ptr (eh_else_stmt);
> +  return *gimple_eh_else_e_body_ptr (const_cast <geh_else *> (eh_else_stmt));
>   }
>   
>   static inline void
> @@ -4224,9 +4303,9 @@ gimple_try_eval_ptr (gimple *gs)
>   /* Return the sequence of statements used as the body for GIMPLE_TRY GS.  */
>   
>   static inline gimple_seq
> -gimple_try_eval (gimple *gs)
> +gimple_try_eval (const gimple *gs)
>   {
> -  return *gimple_try_eval_ptr (gs);
> +  return *gimple_try_eval_ptr (const_cast <gimple *> (gs));
>   }
>   
>   
> @@ -4245,9 +4324,9 @@ gimple_try_cleanup_ptr (gimple *gs)
>      GIMPLE_TRY GS.  */
>   
>   static inline gimple_seq
> -gimple_try_cleanup (gimple *gs)
> +gimple_try_cleanup (const gimple *gs)
>   {
> -  return *gimple_try_cleanup_ptr (gs);
> +  return *gimple_try_cleanup_ptr (const_cast <gimple *> (gs));
>   }
>   
>   
> @@ -4406,6 +4485,13 @@ gimple_phi_arg (gphi *gs, unsigned index)
>     return &(gs->args[index]);
>   }
>   
> +static inline const phi_arg_d *
> +gimple_phi_arg (const gphi *gs, unsigned index)
> +{
> +  gcc_gimple_checking_assert (index < gs->nargs);
> +  return &(gs->args[index]);
> +}
> +
>   static inline struct phi_arg_d *
>   gimple_phi_arg (gimple *gs, unsigned index)
>   {
> @@ -4445,7 +4531,7 @@ phi_nodes_ptr (basic_block bb)
>   /* Return the tree operand for argument I of PHI node GS.  */
>   
>   static inline tree
> -gimple_phi_arg_def (gphi *gs, size_t index)
> +gimple_phi_arg_def (const gphi *gs, size_t index)
>   {
>     return gimple_phi_arg (gs, index)->def;
>   }
> @@ -4468,7 +4554,7 @@ gimple_phi_arg_def_ptr (gphi *phi, size_t index)
>   /* Return the edge associated with argument I of phi node PHI.  */
>   
>   static inline edge
> -gimple_phi_arg_edge (gphi *phi, size_t i)
> +gimple_phi_arg_edge (const gphi *phi, size_t i)
>   {
>     return EDGE_PRED (gimple_bb (phi), i);
>   }
> @@ -4476,7 +4562,7 @@ gimple_phi_arg_edge (gphi *phi, size_t i)
>   /* Return the source location of gimple argument I of phi node PHI.  */
>   
>   static inline location_t
> -gimple_phi_arg_location (gphi *phi, size_t i)
> +gimple_phi_arg_location (const gphi *phi, size_t i)
>   {
>     return gimple_phi_arg (phi, i)->locus;
>   }
> @@ -4500,7 +4586,7 @@ gimple_phi_arg_set_location (gphi *phi, size_t i, location_t loc)
>   /* Return TRUE if argument I of phi node PHI has a location record.  */
>   
>   static inline bool
> -gimple_phi_arg_has_location (gphi *phi, size_t i)
> +gimple_phi_arg_has_location (const gphi *phi, size_t i)
>   {
>     return gimple_phi_arg_location (phi, i) != UNKNOWN_LOCATION;
>   }
> @@ -4668,7 +4754,7 @@ gimple_debug_bind_p (const gimple *s)
>   /* Return the variable bound in a GIMPLE_DEBUG bind statement.  */
>   
>   static inline tree
> -gimple_debug_bind_get_var (gimple *dbg)
> +gimple_debug_bind_get_var (const gimple *dbg)
>   {
>     GIMPLE_CHECK (dbg, GIMPLE_DEBUG);
>     gcc_gimple_checking_assert (gimple_debug_bind_p (dbg));
> @@ -4679,7 +4765,7 @@ gimple_debug_bind_get_var (gimple *dbg)
>      statement.  */
>   
>   static inline tree
> -gimple_debug_bind_get_value (gimple *dbg)
> +gimple_debug_bind_get_value (const gimple *dbg)
>   {
>     GIMPLE_CHECK (dbg, GIMPLE_DEBUG);
>     gcc_gimple_checking_assert (gimple_debug_bind_p (dbg));
> @@ -4760,7 +4846,7 @@ gimple_debug_source_bind_p (const gimple *s)
>   /* Return the variable bound in a GIMPLE_DEBUG source bind statement.  */
>   
>   static inline tree
> -gimple_debug_source_bind_get_var (gimple *dbg)
> +gimple_debug_source_bind_get_var (const gimple *dbg)
>   {
>     GIMPLE_CHECK (dbg, GIMPLE_DEBUG);
>     gcc_gimple_checking_assert (gimple_debug_source_bind_p (dbg));
> @@ -4771,7 +4857,7 @@ gimple_debug_source_bind_get_var (gimple *dbg)
>      statement.  */
>   
>   static inline tree
> -gimple_debug_source_bind_get_value (gimple *dbg)
> +gimple_debug_source_bind_get_value (const gimple *dbg)
>   {
>     GIMPLE_CHECK (dbg, GIMPLE_DEBUG);
>     gcc_gimple_checking_assert (gimple_debug_source_bind_p (dbg));
> @@ -4872,9 +4958,9 @@ gimple_omp_body_ptr (gimple *gs)
>   /* Return the body for the OMP statement GS.  */
>   
>   static inline gimple_seq
> -gimple_omp_body (gimple *gs)
> +gimple_omp_body (const gimple *gs)
>   {
> -  return *gimple_omp_body_ptr (gs);
> +  return *gimple_omp_body_ptr (const_cast <gimple *> (gs));
>   }
>   
>   /* Set BODY to be the body for the OMP statement GS.  */
> @@ -5139,9 +5225,9 @@ gimple_omp_for_set_clauses (gimple *gs, tree clauses)
>   /* Get the collapse count of the OMP_FOR statement GS.  */
>   
>   static inline size_t
> -gimple_omp_for_collapse (gimple *gs)
> +gimple_omp_for_collapse (const gimple *gs)
>   {
> -  gomp_for *omp_for_stmt = as_a <gomp_for *> (gs);
> +  const gomp_for *omp_for_stmt = as_a <const gomp_for *> (gs);
>     return omp_for_stmt->collapse;
>   }
>   
> @@ -5316,9 +5402,9 @@ gimple_omp_for_pre_body_ptr (gimple *gs)
>      statement GS starts.  */
>   
>   static inline gimple_seq
> -gimple_omp_for_pre_body (gimple *gs)
> +gimple_omp_for_pre_body (const gimple *gs)
>   {
> -  return *gimple_omp_for_pre_body_ptr (gs);
> +  return *gimple_omp_for_pre_body_ptr (const_cast <gimple *> (gs));
>   }
>   
>   
> @@ -6303,7 +6389,7 @@ gimple_transaction_body_ptr (gtransaction *transaction_stmt)
>   /* Return the body for the GIMPLE_TRANSACTION statement TRANSACTION_STMT.  */
>   
>   static inline gimple_seq
> -gimple_transaction_body (gtransaction *transaction_stmt)
> +gimple_transaction_body (const gtransaction *transaction_stmt)
>   {
>     return transaction_stmt->body;
>   }
> diff --git a/gcc/tree-eh.c b/gcc/tree-eh.c
> index 54502e6..b996f49 100644
> --- a/gcc/tree-eh.c
> +++ b/gcc/tree-eh.c
> @@ -139,19 +139,19 @@ remove_stmt_from_eh_lp (gimple *t)
>      statement is not recorded in the region table.  */
>   
>   int
> -lookup_stmt_eh_lp_fn (struct function *ifun, gimple *t)
> +lookup_stmt_eh_lp_fn (struct function *ifun, const gimple *t)
>   {
>     if (ifun->eh->throw_stmt_table == NULL)
>       return 0;
>   
> -  int *lp_nr = ifun->eh->throw_stmt_table->get (t);
> +  int *lp_nr = ifun->eh->throw_stmt_table->get (const_cast <gimple *> (t));
>     return lp_nr ? *lp_nr : 0;
>   }
>   
>   /* Likewise, but always use the current function.  */
>   
>   int
> -lookup_stmt_eh_lp (gimple *t)
> +lookup_stmt_eh_lp (const gimple *t)
>   {
>     /* We can get called from initialized data when -fnon-call-exceptions
>        is on; prevent crash.  */
> diff --git a/gcc/tree-eh.h b/gcc/tree-eh.h
> index 511bb84..ca3b521 100644
> --- a/gcc/tree-eh.h
> +++ b/gcc/tree-eh.h
> @@ -27,8 +27,8 @@ extern void using_eh_for_cleanups (void);
>   extern void add_stmt_to_eh_lp (gimple *, int);
>   extern bool remove_stmt_from_eh_lp_fn (struct function *, gimple *);
>   extern bool remove_stmt_from_eh_lp (gimple *);
> -extern int lookup_stmt_eh_lp_fn (struct function *, gimple *);
> -extern int lookup_stmt_eh_lp (gimple *);
> +extern int lookup_stmt_eh_lp_fn (struct function *, const gimple *);
> +extern int lookup_stmt_eh_lp (const gimple *);
>   extern bool make_eh_dispatch_edges (geh_dispatch *);
>   extern void make_eh_edges (gimple *);
>   extern edge redirect_eh_edge (edge, basic_block);
> diff --git a/gcc/tree-ssa-alias.h b/gcc/tree-ssa-alias.h
> index 6d72c2f..de85312 100644
> --- a/gcc/tree-ssa-alias.h
> +++ b/gcc/tree-ssa-alias.h
> @@ -162,7 +162,7 @@ extern void dump_alias_stats (FILE *);
>   
>   /* In tree-ssa-structalias.c  */
>   extern unsigned int compute_may_aliases (void);
> -extern bool pt_solution_empty_p (struct pt_solution *);
> +extern bool pt_solution_empty_p (const pt_solution *);
>   extern bool pt_solution_singleton_or_null_p (struct pt_solution *, unsigned *);
>   extern bool pt_solution_includes_global (struct pt_solution *);
>   extern bool pt_solution_includes (struct pt_solution *, const_tree);
> diff --git a/gcc/tree-ssa-structalias.c b/gcc/tree-ssa-structalias.c
> index 75c6fae..b248dfa 100644
> --- a/gcc/tree-ssa-structalias.c
> +++ b/gcc/tree-ssa-structalias.c
> @@ -6801,7 +6801,7 @@ pt_solution_ior_into (struct pt_solution *dest, struct pt_solution *src)
>   /* Return true if the points-to solution *PT is empty.  */
>   
>   bool
> -pt_solution_empty_p (struct pt_solution *pt)
> +pt_solution_empty_p (const pt_solution *pt)
>   {
>     if (pt->anything
>         || pt->nonlocal)
> 

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 10/49] Add -fdiagnostics-nn-line-numbers
  2019-11-16  1:21 ` [PATCH 10/49] Add -fdiagnostics-nn-line-numbers David Malcolm
@ 2019-12-04 17:18   ` Martin Sebor
  2019-12-04 17:24   ` Martin Sebor
  1 sibling, 0 replies; 160+ messages in thread
From: Martin Sebor @ 2019-12-04 17:18 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On 11/15/19 6:22 PM, David Malcolm wrote:
> This patch implements -fdiagnostics-nn-line-numbers, a new option
> which makes diagnostic_show_locus print "NN" rather than specific
> line numbers when printing the left margin.
> 
> This is intended purely to make it easier to write certain kinds of
> DejaGnu test; various integration tests for diagnostic paths later
> in the patch kit make use of it.
> 
> gcc/ChangeLog:
> 	* common.opt (fdiagnostics-nn-line-numbers): New option.
> 	* diagnostic-show-locus.c (layout::m_use_nn_for_line_numbers_p):
> 	New field.
> 	(layout::layout): Initialize it and use it when computing
> 	m_linenum_width.
> 	(layout::print_source_line): Implement printing "NN" rather than
> 	the line number.
> 	(selftest::test_line_numbers_multiline_range): Add a test of "NN"
> 	printing.
> 	* diagnostic.c (diagnostic_initialize): Initialize
> 	use_nn_for_line_numbers_p.
> 	(num_digits): Add "use_nn_p" param.
> 	(selftest::test_num_digits): Add a test for use_nn_p==true.
> 	* diagnostic.h (diagnostic_context::use_nn_for_line_numbers_p):
> 	New field.
> 	(num_digits): Add optional "use_nn_p" param.
> 	* doc/invoke.texi (-fdiagnostics-nn-line-numbers): New option.
> 	* dwarf2out.c (gen_producer_string): Ignore
> 	OPT_fdiagnostics_nn_line_numbers.
> 	* lto-wrapper.c (merge_and_complain): Handle
> 	OPT_fdiagnostics_nn_line_numbers.
> 	(append_compiler_options): Likewise.
> 	(append_diag_options): Likewise.
> 	* opts.c (common_handle_option): Likewise.
> 	* toplev.c (general_init): Initialize
> 	global_dc->use_nn_for_line_numbers_p.
> ---
>   gcc/common.opt              |  4 ++++
>   gcc/diagnostic-show-locus.c | 51 ++++++++++++++++++++++++++++++++-------------
>   gcc/diagnostic.c            | 13 ++++++++++--
>   gcc/diagnostic.h            |  6 +++++-
>   gcc/doc/invoke.texi         |  7 +++++++
>   gcc/dwarf2out.c             |  1 +
>   gcc/lto-wrapper.c           |  3 +++
>   gcc/opts.c                  |  4 ++++
>   gcc/toplev.c                |  2 ++
>   9 files changed, 73 insertions(+), 18 deletions(-)
> 
> diff --git a/gcc/common.opt b/gcc/common.opt
> index 124e8cf..3c024b3 100644
> --- a/gcc/common.opt
> +++ b/gcc/common.opt
> @@ -1257,6 +1257,10 @@ fdiagnostics-show-line-numbers
>   Common Var(flag_diagnostics_show_line_numbers) Init(1)
>   Show line numbers in the left margin when showing source.
>   
> +fdiagnostics-nn-line-numbers
> +Common Var(flag_diagnostics_nn_line_numbers) Init(0)
> +Replace line numbers with 'NN' when showing source.
> +
>   fdiagnostics-color
>   Common Alias(fdiagnostics-color=,always,never)
>   ;
> diff --git a/gcc/diagnostic-show-locus.c b/gcc/diagnostic-show-locus.c
> index 412b0d4..ec5fdc0 100644
> --- a/gcc/diagnostic-show-locus.c
> +++ b/gcc/diagnostic-show-locus.c
> @@ -303,6 +303,7 @@ class layout
>     bool m_colorize_source_p;
>     bool m_show_labels_p;
>     bool m_show_line_numbers_p;
> +  bool m_use_nn_for_line_numbers_p;
>     auto_vec <layout_range> m_layout_ranges;
>     auto_vec <const fixit_hint *> m_fixit_hints;
>     auto_vec <line_span> m_line_spans;
> @@ -849,6 +850,7 @@ layout::layout (diagnostic_context * context,
>     m_colorize_source_p (context->colorize_source_p),
>     m_show_labels_p (context->show_labels_p),
>     m_show_line_numbers_p (context->show_line_numbers_p),
> +  m_use_nn_for_line_numbers_p (context->use_nn_for_line_numbers_p),
>     m_layout_ranges (richloc->get_num_locations ()),
>     m_fixit_hints (richloc->get_num_fixit_hints ()),
>     m_line_spans (1 + richloc->get_num_locations ()),
> @@ -884,7 +886,7 @@ layout::layout (diagnostic_context * context,
>     int highest_line = last_span->m_last_line;
>     if (highest_line < 0)
>       highest_line = 0;
> -  m_linenum_width = num_digits (highest_line);
> +  m_linenum_width = num_digits (highest_line, m_use_nn_for_line_numbers_p);
>     /* If we're showing jumps in the line-numbering, allow at least 3 chars.  */
>     if (m_line_spans.length () > 1)
>       m_linenum_width = MAX (m_linenum_width, 3);
> @@ -1272,10 +1274,13 @@ layout::print_source_line (linenum_type row, const char *line, int line_width,
>   
>     if (m_show_line_numbers_p)
>       {
> -      int width = num_digits (row);
> +      int width = num_digits (row, m_use_nn_for_line_numbers_p);
>         for (int i = 0; i < m_linenum_width - width; i++)
>   	pp_space (m_pp);
> -      pp_printf (m_pp, "%i | ", row);
> +      if (m_use_nn_for_line_numbers_p)
> +	pp_printf (m_pp, "%s | ", "NN");
> +      else
> +	pp_printf (m_pp, "%i | ", row);
>       }
>     else
>       pp_space (m_pp);
> @@ -3778,18 +3783,34 @@ test_line_numbers_multiline_range ()
>       = linemap_position_for_line_and_column (line_table, ord_map, 11, 4);
>     location_t loc = make_location (caret, start, finish);
>   
> -  test_diagnostic_context dc;
> -  dc.show_line_numbers_p = true;
> -  dc.min_margin_width = 0;
> -  gcc_rich_location richloc (loc);
> -  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
> -  ASSERT_STREQ (" 9 | this is line 9\n"
> -		"   |         ~~~~~~\n"
> -		"10 | this is line 10\n"
> -		"   | ~~~~~^~~~~~~~~~\n"
> -		"11 | this is line 11\n"
> -		"   | ~~~~  \n",
> -		pp_formatted_text (dc.printer));
> +  {
> +    test_diagnostic_context dc;
> +    dc.show_line_numbers_p = true;
> +    dc.min_margin_width = 0;
> +    gcc_rich_location richloc (loc);
> +    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
> +    ASSERT_STREQ (" 9 | this is line 9\n"
> +		  "   |         ~~~~~~\n"
> +		  "10 | this is line 10\n"
> +		  "   | ~~~~~^~~~~~~~~~\n"
> +		  "11 | this is line 11\n"
> +		  "   | ~~~~  \n",
> +		  pp_formatted_text (dc.printer));
> +  }
> +
> +  /* Verify that obscuring line numbers via "NN" works (and always uses
> +     at least two columns).  */
> +  {
> +    test_diagnostic_context dc;
> +    dc.show_line_numbers_p = true;
> +    dc.use_nn_for_line_numbers_p = true;
> +    dc.min_margin_width = 0;
> +    gcc_rich_location richloc (start);
> +    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
> +    ASSERT_STREQ ("NN | this is line 9\n"
> +		  "   |         ^\n",
> +		  pp_formatted_text (dc.printer));
> +  }
>   }
>   
>   /* Run all of the selftests within this file.  */
> diff --git a/gcc/diagnostic.c b/gcc/diagnostic.c
> index d6604e6..81e0a10 100644
> --- a/gcc/diagnostic.c
> +++ b/gcc/diagnostic.c
> @@ -209,6 +209,7 @@ diagnostic_initialize (diagnostic_context *context, int n_opts)
>     context->colorize_source_p = false;
>     context->show_labels_p = false;
>     context->show_line_numbers_p = false;
> +  context->use_nn_for_line_numbers_p = false;
>     context->min_margin_width = 0;
>     context->show_ruler_p = false;
>     context->parseable_fixits_p = false;
> @@ -1078,15 +1079,21 @@ diagnostic_report_diagnostic (diagnostic_context *context,
>     return true;
>   }
>   
> -/* Get the number of digits in the decimal representation of VALUE.  */
> +/* Get the number of digits in the decimal representation of VALUE.
> +
> +   If USE_NN_P is true, return 2 (for the case where all numbers are to
> +   be printed as just "NN").  */
>   
>   int
> -num_digits (int value)
> +num_digits (int value, bool use_nn_p)
>   {
>     /* Perhaps simpler to use log10 for this, but doing it this way avoids
>        using floating point.  */
>     gcc_assert (value >= 0);
>   
> +  if (use_nn_p)
> +    return 2;
> +

I assume you added the argument to the function to avoid having
to test m_use_nn_for_line_numbers_p at each call site, but, FWIW,
I don't really think it belongs in such a general purpose utility
function.  I don't see it called too many times so it's not a big
deal either way, but it is something that jumped out at me as odd.

Martin

>     if (value == 0)
>       return 1;
>   
> @@ -1885,6 +1892,8 @@ test_num_digits ()
>     ASSERT_EQ (7, num_digits (9999999));
>     ASSERT_EQ (8, num_digits (10000000));
>     ASSERT_EQ (8, num_digits (99999999));
> +
> +  ASSERT_EQ (2, num_digits (1000, true));
>   }
>   
>   /* Run all of the selftests within this file.  */
> diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
> index 91e4c50..2fb75e6 100644
> --- a/gcc/diagnostic.h
> +++ b/gcc/diagnostic.h
> @@ -233,6 +233,10 @@ struct diagnostic_context
>        showing line numbers?  */
>     bool show_line_numbers_p;
>   
> +  /* When printing line numbers, should the actual numbers be replaced with
> +     "NN"? (for ease of DejaGnu testing)  */
> +  bool use_nn_for_line_numbers_p;
> +
>     /* If printing source code, what should the minimum width of the margin
>        be?  Line numbers will be right-aligned, and padded to this width.  */
>     int min_margin_width;
> @@ -432,6 +436,6 @@ extern void diagnostic_output_format_init (diagnostic_context *,
>   					   enum diagnostics_output_format);
>   
>   /* Compute the number of digits in the decimal representation of an integer.  */
> -extern int num_digits (int);
> +extern int num_digits (int, bool use_nn_p = false);
>   
>   #endif /* ! GCC_DIAGNOSTIC_H */
> diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
> index 384848a..fc92913 100644
> --- a/gcc/doc/invoke.texi
> +++ b/gcc/doc/invoke.texi
> @@ -278,6 +278,7 @@ Objective-C and Objective-C++ Dialects}.
>   -fdiagnostics-format=@r{[}text@r{|}json@r{]}  @gol
>   -fno-diagnostics-show-option  -fno-diagnostics-show-caret @gol
>   -fno-diagnostics-show-labels  -fno-diagnostics-show-line-numbers @gol
> +-fdiagnostics-nn-line-numbers @gol
>   -fdiagnostics-minimum-margin-width=@var{width} @gol
>   -fdiagnostics-parseable-fixits  -fdiagnostics-generate-patch @gol
>   -fdiagnostics-show-template-tree  -fno-elide-type @gol
> @@ -4018,6 +4019,12 @@ By default, when printing source code (via @option{-fdiagnostics-show-caret}),
>   a left margin is printed, showing line numbers.  This option suppresses this
>   left margin.
>   
> +@item -fdiagnostics-nn-line-numbers
> +@opindex fdiagnostics-nn-line-numbers
> +When printing source code in diagnostics, replace line numbers ``NN''.
> +This option is intended for GCC developers, to make it easier to write
> +certain kinds of automated test.
> +
>   @item -fdiagnostics-minimum-margin-width=@var{width}
>   @opindex fdiagnostics-minimum-margin-width
>   This option controls the minimum width of the left margin printed by
> diff --git a/gcc/dwarf2out.c b/gcc/dwarf2out.c
> index bf69ce4..3207040 100644
> --- a/gcc/dwarf2out.c
> +++ b/gcc/dwarf2out.c
> @@ -24490,6 +24490,7 @@ gen_producer_string (void)
>         case OPT_fdiagnostics_show_caret:
>         case OPT_fdiagnostics_show_labels:
>         case OPT_fdiagnostics_show_line_numbers:
> +      case OPT_fdiagnostics_nn_line_numbers:
>         case OPT_fdiagnostics_color_:
>         case OPT_fdiagnostics_format_:
>         case OPT_fverbose_asm:
> diff --git a/gcc/lto-wrapper.c b/gcc/lto-wrapper.c
> index 9a7bbd0..aabfe6d 100644
> --- a/gcc/lto-wrapper.c
> +++ b/gcc/lto-wrapper.c
> @@ -260,6 +260,7 @@ merge_and_complain (struct cl_decoded_option **decoded_options,
>   	case OPT_fdiagnostics_show_caret:
>   	case OPT_fdiagnostics_show_labels:
>   	case OPT_fdiagnostics_show_line_numbers:
> +	case OPT_fdiagnostics_nn_line_numbers:
>   	case OPT_fdiagnostics_show_option:
>   	case OPT_fdiagnostics_show_location_:
>   	case OPT_fshow_column:
> @@ -605,6 +606,7 @@ append_compiler_options (obstack *argv_obstack, struct cl_decoded_option *opts,
>   	case OPT_fdiagnostics_show_caret:
>   	case OPT_fdiagnostics_show_labels:
>   	case OPT_fdiagnostics_show_line_numbers:
> +	case OPT_fdiagnostics_nn_line_numbers:
>   	case OPT_fdiagnostics_show_option:
>   	case OPT_fdiagnostics_show_location_:
>   	case OPT_fshow_column:
> @@ -653,6 +655,7 @@ append_diag_options (obstack *argv_obstack, struct cl_decoded_option *opts,
>   	case OPT_fdiagnostics_format_:
>   	case OPT_fdiagnostics_show_caret:
>   	case OPT_fdiagnostics_show_labels:
> +	case OPT_fdiagnostics_nn_line_numbers:
>   	case OPT_fdiagnostics_show_line_numbers:
>   	case OPT_fdiagnostics_show_option:
>   	case OPT_fdiagnostics_show_location_:
> diff --git a/gcc/opts.c b/gcc/opts.c
> index 32869ca..9928d01 100644
> --- a/gcc/opts.c
> +++ b/gcc/opts.c
> @@ -2470,6 +2470,10 @@ common_handle_option (struct gcc_options *opts,
>         dc->show_line_numbers_p = value;
>         break;
>   
> +    case OPT_fdiagnostics_nn_line_numbers:
> +      dc->use_nn_for_line_numbers_p = value;
> +      break;
> +
>       case OPT_fdiagnostics_color_:
>         diagnostic_color_init (dc, value);
>         break;
> diff --git a/gcc/toplev.c b/gcc/toplev.c
> index 1c7002f..2d0a390 100644
> --- a/gcc/toplev.c
> +++ b/gcc/toplev.c
> @@ -1100,6 +1100,8 @@ general_init (const char *argv0, bool init_signals)
>       = global_options_init.x_flag_diagnostics_show_labels;
>     global_dc->show_line_numbers_p
>       = global_options_init.x_flag_diagnostics_show_line_numbers;
> +  global_dc->use_nn_for_line_numbers_p
> +    = global_options_init.x_flag_diagnostics_nn_line_numbers;
>     global_dc->show_option_requested
>       = global_options_init.x_flag_diagnostics_show_option;
>     global_dc->min_margin_width
> 

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 10/49] Add -fdiagnostics-nn-line-numbers
  2019-11-16  1:21 ` [PATCH 10/49] Add -fdiagnostics-nn-line-numbers David Malcolm
  2019-12-04 17:18   ` Martin Sebor
@ 2019-12-04 17:24   ` Martin Sebor
  1 sibling, 0 replies; 160+ messages in thread
From: Martin Sebor @ 2019-12-04 17:24 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On 11/15/19 6:22 PM, David Malcolm wrote:
> This patch implements -fdiagnostics-nn-line-numbers, a new option
> which makes diagnostic_show_locus print "NN" rather than specific
> line numbers when printing the left margin.
> 
> This is intended purely to make it easier to write certain kinds of
> DejaGnu test; various integration tests for diagnostic paths later
> in the patch kit make use of it.

Is there no other way to expose this internal feature than under
a user-visible option?  Or get the tests to work without it?

Martin

> gcc/ChangeLog:
> 	* common.opt (fdiagnostics-nn-line-numbers): New option.
> 	* diagnostic-show-locus.c (layout::m_use_nn_for_line_numbers_p):
> 	New field.
> 	(layout::layout): Initialize it and use it when computing
> 	m_linenum_width.
> 	(layout::print_source_line): Implement printing "NN" rather than
> 	the line number.
> 	(selftest::test_line_numbers_multiline_range): Add a test of "NN"
> 	printing.
> 	* diagnostic.c (diagnostic_initialize): Initialize
> 	use_nn_for_line_numbers_p.
> 	(num_digits): Add "use_nn_p" param.
> 	(selftest::test_num_digits): Add a test for use_nn_p==true.
> 	* diagnostic.h (diagnostic_context::use_nn_for_line_numbers_p):
> 	New field.
> 	(num_digits): Add optional "use_nn_p" param.
> 	* doc/invoke.texi (-fdiagnostics-nn-line-numbers): New option.
> 	* dwarf2out.c (gen_producer_string): Ignore
> 	OPT_fdiagnostics_nn_line_numbers.
> 	* lto-wrapper.c (merge_and_complain): Handle
> 	OPT_fdiagnostics_nn_line_numbers.
> 	(append_compiler_options): Likewise.
> 	(append_diag_options): Likewise.
> 	* opts.c (common_handle_option): Likewise.
> 	* toplev.c (general_init): Initialize
> 	global_dc->use_nn_for_line_numbers_p.
> ---
>   gcc/common.opt              |  4 ++++
>   gcc/diagnostic-show-locus.c | 51 ++++++++++++++++++++++++++++++++-------------
>   gcc/diagnostic.c            | 13 ++++++++++--
>   gcc/diagnostic.h            |  6 +++++-
>   gcc/doc/invoke.texi         |  7 +++++++
>   gcc/dwarf2out.c             |  1 +
>   gcc/lto-wrapper.c           |  3 +++
>   gcc/opts.c                  |  4 ++++
>   gcc/toplev.c                |  2 ++
>   9 files changed, 73 insertions(+), 18 deletions(-)
> 
> diff --git a/gcc/common.opt b/gcc/common.opt
> index 124e8cf..3c024b3 100644
> --- a/gcc/common.opt
> +++ b/gcc/common.opt
> @@ -1257,6 +1257,10 @@ fdiagnostics-show-line-numbers
>   Common Var(flag_diagnostics_show_line_numbers) Init(1)
>   Show line numbers in the left margin when showing source.
>   
> +fdiagnostics-nn-line-numbers
> +Common Var(flag_diagnostics_nn_line_numbers) Init(0)
> +Replace line numbers with 'NN' when showing source.
> +
>   fdiagnostics-color
>   Common Alias(fdiagnostics-color=,always,never)
>   ;
> diff --git a/gcc/diagnostic-show-locus.c b/gcc/diagnostic-show-locus.c
> index 412b0d4..ec5fdc0 100644
> --- a/gcc/diagnostic-show-locus.c
> +++ b/gcc/diagnostic-show-locus.c
> @@ -303,6 +303,7 @@ class layout
>     bool m_colorize_source_p;
>     bool m_show_labels_p;
>     bool m_show_line_numbers_p;
> +  bool m_use_nn_for_line_numbers_p;
>     auto_vec <layout_range> m_layout_ranges;
>     auto_vec <const fixit_hint *> m_fixit_hints;
>     auto_vec <line_span> m_line_spans;
> @@ -849,6 +850,7 @@ layout::layout (diagnostic_context * context,
>     m_colorize_source_p (context->colorize_source_p),
>     m_show_labels_p (context->show_labels_p),
>     m_show_line_numbers_p (context->show_line_numbers_p),
> +  m_use_nn_for_line_numbers_p (context->use_nn_for_line_numbers_p),
>     m_layout_ranges (richloc->get_num_locations ()),
>     m_fixit_hints (richloc->get_num_fixit_hints ()),
>     m_line_spans (1 + richloc->get_num_locations ()),
> @@ -884,7 +886,7 @@ layout::layout (diagnostic_context * context,
>     int highest_line = last_span->m_last_line;
>     if (highest_line < 0)
>       highest_line = 0;
> -  m_linenum_width = num_digits (highest_line);
> +  m_linenum_width = num_digits (highest_line, m_use_nn_for_line_numbers_p);
>     /* If we're showing jumps in the line-numbering, allow at least 3 chars.  */
>     if (m_line_spans.length () > 1)
>       m_linenum_width = MAX (m_linenum_width, 3);
> @@ -1272,10 +1274,13 @@ layout::print_source_line (linenum_type row, const char *line, int line_width,
>   
>     if (m_show_line_numbers_p)
>       {
> -      int width = num_digits (row);
> +      int width = num_digits (row, m_use_nn_for_line_numbers_p);
>         for (int i = 0; i < m_linenum_width - width; i++)
>   	pp_space (m_pp);
> -      pp_printf (m_pp, "%i | ", row);
> +      if (m_use_nn_for_line_numbers_p)
> +	pp_printf (m_pp, "%s | ", "NN");
> +      else
> +	pp_printf (m_pp, "%i | ", row);
>       }
>     else
>       pp_space (m_pp);
> @@ -3778,18 +3783,34 @@ test_line_numbers_multiline_range ()
>       = linemap_position_for_line_and_column (line_table, ord_map, 11, 4);
>     location_t loc = make_location (caret, start, finish);
>   
> -  test_diagnostic_context dc;
> -  dc.show_line_numbers_p = true;
> -  dc.min_margin_width = 0;
> -  gcc_rich_location richloc (loc);
> -  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
> -  ASSERT_STREQ (" 9 | this is line 9\n"
> -		"   |         ~~~~~~\n"
> -		"10 | this is line 10\n"
> -		"   | ~~~~~^~~~~~~~~~\n"
> -		"11 | this is line 11\n"
> -		"   | ~~~~  \n",
> -		pp_formatted_text (dc.printer));
> +  {
> +    test_diagnostic_context dc;
> +    dc.show_line_numbers_p = true;
> +    dc.min_margin_width = 0;
> +    gcc_rich_location richloc (loc);
> +    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
> +    ASSERT_STREQ (" 9 | this is line 9\n"
> +		  "   |         ~~~~~~\n"
> +		  "10 | this is line 10\n"
> +		  "   | ~~~~~^~~~~~~~~~\n"
> +		  "11 | this is line 11\n"
> +		  "   | ~~~~  \n",
> +		  pp_formatted_text (dc.printer));
> +  }
> +
> +  /* Verify that obscuring line numbers via "NN" works (and always uses
> +     at least two columns).  */
> +  {
> +    test_diagnostic_context dc;
> +    dc.show_line_numbers_p = true;
> +    dc.use_nn_for_line_numbers_p = true;
> +    dc.min_margin_width = 0;
> +    gcc_rich_location richloc (start);
> +    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
> +    ASSERT_STREQ ("NN | this is line 9\n"
> +		  "   |         ^\n",
> +		  pp_formatted_text (dc.printer));
> +  }
>   }
>   
>   /* Run all of the selftests within this file.  */
> diff --git a/gcc/diagnostic.c b/gcc/diagnostic.c
> index d6604e6..81e0a10 100644
> --- a/gcc/diagnostic.c
> +++ b/gcc/diagnostic.c
> @@ -209,6 +209,7 @@ diagnostic_initialize (diagnostic_context *context, int n_opts)
>     context->colorize_source_p = false;
>     context->show_labels_p = false;
>     context->show_line_numbers_p = false;
> +  context->use_nn_for_line_numbers_p = false;
>     context->min_margin_width = 0;
>     context->show_ruler_p = false;
>     context->parseable_fixits_p = false;
> @@ -1078,15 +1079,21 @@ diagnostic_report_diagnostic (diagnostic_context *context,
>     return true;
>   }
>   
> -/* Get the number of digits in the decimal representation of VALUE.  */
> +/* Get the number of digits in the decimal representation of VALUE.
> +
> +   If USE_NN_P is true, return 2 (for the case where all numbers are to
> +   be printed as just "NN").  */
>   
>   int
> -num_digits (int value)
> +num_digits (int value, bool use_nn_p)
>   {
>     /* Perhaps simpler to use log10 for this, but doing it this way avoids
>        using floating point.  */
>     gcc_assert (value >= 0);
>   
> +  if (use_nn_p)
> +    return 2;
> +
>     if (value == 0)
>       return 1;
>   
> @@ -1885,6 +1892,8 @@ test_num_digits ()
>     ASSERT_EQ (7, num_digits (9999999));
>     ASSERT_EQ (8, num_digits (10000000));
>     ASSERT_EQ (8, num_digits (99999999));
> +
> +  ASSERT_EQ (2, num_digits (1000, true));
>   }
>   
>   /* Run all of the selftests within this file.  */
> diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
> index 91e4c50..2fb75e6 100644
> --- a/gcc/diagnostic.h
> +++ b/gcc/diagnostic.h
> @@ -233,6 +233,10 @@ struct diagnostic_context
>        showing line numbers?  */
>     bool show_line_numbers_p;
>   
> +  /* When printing line numbers, should the actual numbers be replaced with
> +     "NN"? (for ease of DejaGnu testing)  */
> +  bool use_nn_for_line_numbers_p;
> +
>     /* If printing source code, what should the minimum width of the margin
>        be?  Line numbers will be right-aligned, and padded to this width.  */
>     int min_margin_width;
> @@ -432,6 +436,6 @@ extern void diagnostic_output_format_init (diagnostic_context *,
>   					   enum diagnostics_output_format);
>   
>   /* Compute the number of digits in the decimal representation of an integer.  */
> -extern int num_digits (int);
> +extern int num_digits (int, bool use_nn_p = false);
>   
>   #endif /* ! GCC_DIAGNOSTIC_H */
> diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
> index 384848a..fc92913 100644
> --- a/gcc/doc/invoke.texi
> +++ b/gcc/doc/invoke.texi
> @@ -278,6 +278,7 @@ Objective-C and Objective-C++ Dialects}.
>   -fdiagnostics-format=@r{[}text@r{|}json@r{]}  @gol
>   -fno-diagnostics-show-option  -fno-diagnostics-show-caret @gol
>   -fno-diagnostics-show-labels  -fno-diagnostics-show-line-numbers @gol
> +-fdiagnostics-nn-line-numbers @gol
>   -fdiagnostics-minimum-margin-width=@var{width} @gol
>   -fdiagnostics-parseable-fixits  -fdiagnostics-generate-patch @gol
>   -fdiagnostics-show-template-tree  -fno-elide-type @gol
> @@ -4018,6 +4019,12 @@ By default, when printing source code (via @option{-fdiagnostics-show-caret}),
>   a left margin is printed, showing line numbers.  This option suppresses this
>   left margin.
>   
> +@item -fdiagnostics-nn-line-numbers
> +@opindex fdiagnostics-nn-line-numbers
> +When printing source code in diagnostics, replace line numbers ``NN''.
> +This option is intended for GCC developers, to make it easier to write
> +certain kinds of automated test.
> +
>   @item -fdiagnostics-minimum-margin-width=@var{width}
>   @opindex fdiagnostics-minimum-margin-width
>   This option controls the minimum width of the left margin printed by
> diff --git a/gcc/dwarf2out.c b/gcc/dwarf2out.c
> index bf69ce4..3207040 100644
> --- a/gcc/dwarf2out.c
> +++ b/gcc/dwarf2out.c
> @@ -24490,6 +24490,7 @@ gen_producer_string (void)
>         case OPT_fdiagnostics_show_caret:
>         case OPT_fdiagnostics_show_labels:
>         case OPT_fdiagnostics_show_line_numbers:
> +      case OPT_fdiagnostics_nn_line_numbers:
>         case OPT_fdiagnostics_color_:
>         case OPT_fdiagnostics_format_:
>         case OPT_fverbose_asm:
> diff --git a/gcc/lto-wrapper.c b/gcc/lto-wrapper.c
> index 9a7bbd0..aabfe6d 100644
> --- a/gcc/lto-wrapper.c
> +++ b/gcc/lto-wrapper.c
> @@ -260,6 +260,7 @@ merge_and_complain (struct cl_decoded_option **decoded_options,
>   	case OPT_fdiagnostics_show_caret:
>   	case OPT_fdiagnostics_show_labels:
>   	case OPT_fdiagnostics_show_line_numbers:
> +	case OPT_fdiagnostics_nn_line_numbers:
>   	case OPT_fdiagnostics_show_option:
>   	case OPT_fdiagnostics_show_location_:
>   	case OPT_fshow_column:
> @@ -605,6 +606,7 @@ append_compiler_options (obstack *argv_obstack, struct cl_decoded_option *opts,
>   	case OPT_fdiagnostics_show_caret:
>   	case OPT_fdiagnostics_show_labels:
>   	case OPT_fdiagnostics_show_line_numbers:
> +	case OPT_fdiagnostics_nn_line_numbers:
>   	case OPT_fdiagnostics_show_option:
>   	case OPT_fdiagnostics_show_location_:
>   	case OPT_fshow_column:
> @@ -653,6 +655,7 @@ append_diag_options (obstack *argv_obstack, struct cl_decoded_option *opts,
>   	case OPT_fdiagnostics_format_:
>   	case OPT_fdiagnostics_show_caret:
>   	case OPT_fdiagnostics_show_labels:
> +	case OPT_fdiagnostics_nn_line_numbers:
>   	case OPT_fdiagnostics_show_line_numbers:
>   	case OPT_fdiagnostics_show_option:
>   	case OPT_fdiagnostics_show_location_:
> diff --git a/gcc/opts.c b/gcc/opts.c
> index 32869ca..9928d01 100644
> --- a/gcc/opts.c
> +++ b/gcc/opts.c
> @@ -2470,6 +2470,10 @@ common_handle_option (struct gcc_options *opts,
>         dc->show_line_numbers_p = value;
>         break;
>   
> +    case OPT_fdiagnostics_nn_line_numbers:
> +      dc->use_nn_for_line_numbers_p = value;
> +      break;
> +
>       case OPT_fdiagnostics_color_:
>         diagnostic_color_init (dc, value);
>         break;
> diff --git a/gcc/toplev.c b/gcc/toplev.c
> index 1c7002f..2d0a390 100644
> --- a/gcc/toplev.c
> +++ b/gcc/toplev.c
> @@ -1100,6 +1100,8 @@ general_init (const char *argv0, bool init_signals)
>       = global_options_init.x_flag_diagnostics_show_labels;
>     global_dc->show_line_numbers_p
>       = global_options_init.x_flag_diagnostics_show_line_numbers;
> +  global_dc->use_nn_for_line_numbers_p
> +    = global_options_init.x_flag_diagnostics_nn_line_numbers;
>     global_dc->show_option_requested
>       = global_options_init.x_flag_diagnostics_show_option;
>     global_dc->min_margin_width
> 

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 06/49] timevar.h: add auto_client_timevar class
  2019-12-04 16:39   ` Martin Sebor
@ 2019-12-04 17:28     ` Tom Tromey
  2019-12-04 21:15       ` David Malcolm
  0 siblings, 1 reply; 160+ messages in thread
From: Tom Tromey @ 2019-12-04 17:28 UTC (permalink / raw)
  To: Martin Sebor; +Cc: David Malcolm, gcc-patches

>>>>> "Martin" == Martin Sebor <msebor@gmail.com> writes:

>> + private:
>> +  // Private to disallow copies.
>> +  auto_client_timevar (const auto_client_timevar &);

Martin> I don't know why it's important to disallow making copies of
Martin> these classes (they look safe to copy) but usually it goes
Martin> along with assignment so I would suggest adding a comment
Martin> with a rationale and (perhaps also) disabling assignment.

ansidecl.h has a DISABLE_COPY_AND_ASSIGN macro you can use here.
gdb uses this quite a bit.

Tom

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 11/49] Add diagnostic_metadata and CWE support
  2019-11-16  1:17 ` [PATCH 11/49] Add diagnostic_metadata and CWE support David Malcolm
@ 2019-12-04 17:36   ` Martin Sebor
  2019-12-13  3:00     ` David Malcolm
  0 siblings, 1 reply; 160+ messages in thread
From: Martin Sebor @ 2019-12-04 17:36 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On 11/15/19 6:22 PM, David Malcolm wrote:
> This patch adds support for associating a diagnostic with an optional
> diagnostic_metadata object, so that plugins can add extra data to their
> diagnostics (e.g. mapping a diagnostic to a taxonomy or coding standard
> such as from CERT or MISRA).
> 
> Currently this only supports associating a CWE identifier with a
> diagnostic (which is what I'm using for the analyzer warnings later in
> the patch kit), but adding a diagnostic_metadata class allows for future
> growth in this area without an explosion of further "warning_at" overloads
> for all of the different kinds of custom data that a plugin might want to
> add.

We discussed this in the past so I'll be repeating some of my
comments from that thread.  I like the feature at a high level,
but I'm not sure I see how to make it work in a robust way.

There is no one-to-one mapping between GCC warnings and any of
these classifications.  A single GCC warning might map to several
CERT or MISRA guidelines, and a single guideline might map to two
or more different GCC warnings.  This is an M to N mapping times
the number of coding standards/classifications.  I haven't looked
at the patch kit in enough detail to understand how it tries to
handle it.  Can you explain?

My other concern here is that these guidelines evolve.  New ones
are being added, existing ones moved or broken up, etc.  Does
the infrastructure cope with this evolution (e.g., by reading
the mapping from a file)?

Martin

> 
> gcc/ChangeLog:
> 	* common.opt (fdiagnostics-show-metadata): New option.
> 	* diagnostic-core.h (class diagnostic_metadata): New forward decl.
> 	(warning_at): Add overload taking a const diagnostic_metadata &.
> 	(emit_diagnostic_valist): Add overload taking a
> 	const diagnostic_metadata *.
> 	* diagnostic-format-json.cc: Include "diagnostic-metadata.h".
> 	(json_from_metadata): New function.
> 	(json_end_diagnostic): Call it to add "metadata" child for
> 	diagnostics with metadata.
> 	(diagnostic_output_format_init): Clear context->show_metadata.
> 	* diagnostic-metadata.h: New file.
> 	* diagnostic.c: Include "diagnostic-metadata.h".
> 	(diagnostic_impl): Add const diagnostic_metadata * param.
> 	(diagnostic_n_impl): Likewise.
> 	(diagnostic_initialize): Initialize context->show_metadata.
> 	(diagnostic_set_info_translated): Initialize diagnostic->metadata.
> 	(get_cwe_url): New function.
> 	(print_any_metadata): New function.
> 	(diagnostic_report_diagnostic): Call print_any_metadata if the
> 	diagnostic has non-NULL metadata.
> 	(emit_diagnostic): Pass NULL as the metadata in the call to
> 	diagnostic_impl.
> 	(emit_diagnostic_valist): Likewise.
> 	(emit_diagnostic_valist): New overload taking a
> 	const diagnostic_metadata *.
> 	(inform): Pass NULL as the metadata in the call to
> 	diagnostic_impl.
> 	(inform_n): Likewise for diagnostic_n_impl.
> 	(warning): Likewise.
> 	(warning_at): Likewise.  Add overload that takes a
> 	const diagnostic_metadata &.
> 	(warning_n): Pass NULL as the metadata in the call to
> 	diagnostic_n_impl.
> 	(pedwarn): Likewise for diagnostic_impl.
> 	(permerror): Likewise.
> 	(error): Likewise.
> 	(error_n): Likewise.
> 	(error_at): Likewise.
> 	(sorry): Likewise.
> 	(sorry_at): Likewise.
> 	(fatal_error): Likewise.
> 	(internal_error): Likewise.
> 	(internal_error_no_backtrace): Likewise.
> 	* diagnostic.h (diagnostic_info::metadata): New field.
> 	(diagnostic_context::show_metadata): New field.
> 	* doc/invoke.texi (-fno-diagnostics-show-metadata): New option.
> 	* opts.c (common_handle_option): Handle
> 	OPT_fdiagnostics_show_metadata.
> 	* toplev.c (general_init): Initialize global_dc->show_metadata.
> ---
>   gcc/common.opt                |   4 ++
>   gcc/diagnostic-core.h         |  10 +++
>   gcc/diagnostic-format-json.cc |  23 +++++++
>   gcc/diagnostic-metadata.h     |  42 +++++++++++++
>   gcc/diagnostic.c              | 142 +++++++++++++++++++++++++++++++++---------
>   gcc/diagnostic.h              |   7 +++
>   gcc/doc/invoke.texi           |   8 +++
>   gcc/opts.c                    |   4 ++
>   gcc/toplev.c                  |   2 +
>   9 files changed, 211 insertions(+), 31 deletions(-)
>   create mode 100644 gcc/diagnostic-metadata.h
> 
> diff --git a/gcc/common.opt b/gcc/common.opt
> index 3c024b3..228df32 100644
> --- a/gcc/common.opt
> +++ b/gcc/common.opt
> @@ -1334,6 +1334,10 @@ fdiagnostics-show-option
>   Common Var(flag_diagnostics_show_option) Init(1)
>   Amend appropriate diagnostic messages with the command line option that controls them.
>   
> +fdiagnostics-show-metadata
> +Common Var(flag_diagnostics_show_metadata) Init(1)
> +Print additional metadata for diagnostic messages, where available.
> +
>   fdiagnostics-minimum-margin-width=
>   Common Joined UInteger Var(diagnostics_minimum_margin_width) Init(6)
>   Set minimum width of left margin of source code when showing source.
> diff --git a/gcc/diagnostic-core.h b/gcc/diagnostic-core.h
> index efafde4..2e7f120 100644
> --- a/gcc/diagnostic-core.h
> +++ b/gcc/diagnostic-core.h
> @@ -45,6 +45,9 @@ class auto_diagnostic_group
>     ~auto_diagnostic_group ();
>   };
>   
> +/* Forward decl.  */
> +class diagnostic_metadata; /* See diagnostic-metadata.h.  */
> +
>   extern const char *progname;
>   
>   extern const char *trim_filename (const char *);
> @@ -78,6 +81,9 @@ extern bool warning_at (location_t, int, const char *, ...)
>       ATTRIBUTE_GCC_DIAG(3,4);
>   extern bool warning_at (rich_location *, int, const char *, ...)
>       ATTRIBUTE_GCC_DIAG(3,4);
> +extern bool warning_at (rich_location *, const diagnostic_metadata &, int,
> +			const char *, ...)
> +    ATTRIBUTE_GCC_DIAG(4,5);
>   extern void error (const char *, ...) ATTRIBUTE_GCC_DIAG(1,2);
>   extern void error_n (location_t, unsigned HOST_WIDE_INT, const char *,
>   		     const char *, ...)
> @@ -109,6 +115,10 @@ extern bool emit_diagnostic (diagnostic_t, rich_location *, int,
>   			     const char *, ...) ATTRIBUTE_GCC_DIAG(4,5);
>   extern bool emit_diagnostic_valist (diagnostic_t, location_t, int, const char *,
>   				    va_list *) ATTRIBUTE_GCC_DIAG (4,0);
> +extern bool emit_diagnostic_valist (diagnostic_t, rich_location *,
> +				    const diagnostic_metadata *metadata,
> +				    int, const char *, va_list *)
> +  ATTRIBUTE_GCC_DIAG (5,0);
>   extern bool seen_error (void);
>   
>   #ifdef BUFSIZ
> diff --git a/gcc/diagnostic-format-json.cc b/gcc/diagnostic-format-json.cc
> index eb99952..200e844 100644
> --- a/gcc/diagnostic-format-json.cc
> +++ b/gcc/diagnostic-format-json.cc
> @@ -23,6 +23,7 @@ along with GCC; see the file COPYING3.  If not see
>   #include "system.h"
>   #include "coretypes.h"
>   #include "diagnostic.h"
> +#include "diagnostic-metadata.h"
>   #include "json.h"
>   #include "selftest.h"
>   
> @@ -103,6 +104,19 @@ json_from_fixit_hint (const fixit_hint *hint)
>     return fixit_obj;
>   }
>   
> +/* Generate a JSON object for METADATA.  */
> +
> +static json::object *
> +json_from_metadata (const diagnostic_metadata *metadata)
> +{
> +  json::object *metadata_obj = new json::object ();
> +
> +  if (metadata->get_cwe ())
> +    metadata_obj->set ("cwe", new json::number (metadata->get_cwe ()));
> +
> +  return metadata_obj;
> +}
> +
>   /* No-op implementation of "begin_diagnostic" for JSON output.  */
>   
>   static void
> @@ -211,6 +225,12 @@ json_end_diagnostic (diagnostic_context *context, diagnostic_info *diagnostic,
>        TODO: functions
>        TODO: inlining information
>        TODO: macro expansion information.  */
> +
> +  if (diagnostic->metadata)
> +    {
> +      json::object *metadata_obj = json_from_metadata (diagnostic->metadata);
> +      diag_obj->set ("metadata", metadata_obj);
> +    }
>   }
>   
>   /* No-op implementation of "begin_group_cb" for JSON output.  */
> @@ -268,6 +288,9 @@ diagnostic_output_format_init (diagnostic_context *context,
>   	context->end_group_cb =  json_end_group;
>   	context->final_cb =  json_final_cb;
>   
> +	/* The metadata is handled in JSON format, rather than as text.  */
> +	context->show_metadata = false;
> +
>   	/* The option is handled in JSON format, rather than as text.  */
>   	context->show_option_requested = false;
>   
> diff --git a/gcc/diagnostic-metadata.h b/gcc/diagnostic-metadata.h
> new file mode 100644
> index 0000000..a759d44
> --- /dev/null
> +++ b/gcc/diagnostic-metadata.h
> @@ -0,0 +1,42 @@
> +/* Additional metadata for a diagnostic.
> +   Copyright (C) 2019 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/>.  */
> +
> +#ifndef GCC_DIAGNOSTIC_METADATA_H
> +#define GCC_DIAGNOSTIC_METADATA_H
> +
> +/* A bundle of additional metadata that can be associated with a
> +   diagnostic.
> +
> +   Currently this only supports associating a CWE identifier with a
> +   diagnostic.  */
> +
> +class diagnostic_metadata
> +{
> + public:
> +  diagnostic_metadata () : m_cwe (0) {}
> +
> +  void add_cwe (int cwe) { m_cwe = cwe; }
> +  int get_cwe () const { return m_cwe; }
> +
> + private:
> +  int m_cwe;
> +};
> +
> +#endif /* ! GCC_DIAGNOSTIC_METADATA_H */
> diff --git a/gcc/diagnostic.c b/gcc/diagnostic.c
> index 81e0a10..e8d0613 100644
> --- a/gcc/diagnostic.c
> +++ b/gcc/diagnostic.c
> @@ -32,6 +32,7 @@ along with GCC; see the file COPYING3.  If not see
>   #include "diagnostic.h"
>   #include "diagnostic-color.h"
>   #include "diagnostic-url.h"
> +#include "diagnostic-metadata.h"
>   #include "edit-context.h"
>   #include "selftest.h"
>   #include "selftest-diagnostic.h"
> @@ -58,11 +59,13 @@ along with GCC; see the file COPYING3.  If not see
>   #define permissive_error_option(DC) ((DC)->opt_permissive)
>   
>   /* Prototypes.  */
> -static bool diagnostic_impl (rich_location *, int, const char *,
> -			     va_list *, diagnostic_t) ATTRIBUTE_GCC_DIAG(3,0);
> -static bool diagnostic_n_impl (rich_location *, int, unsigned HOST_WIDE_INT,
> +static bool diagnostic_impl (rich_location *, const diagnostic_metadata *,
> +			     int, const char *,
> +			     va_list *, diagnostic_t) ATTRIBUTE_GCC_DIAG(4,0);
> +static bool diagnostic_n_impl (rich_location *, const diagnostic_metadata *,
> +			       int, unsigned HOST_WIDE_INT,
>   			       const char *, const char *, va_list *,
> -			       diagnostic_t) ATTRIBUTE_GCC_DIAG(5,0);
> +			       diagnostic_t) ATTRIBUTE_GCC_DIAG(6,0);
>   
>   static void error_recursion (diagnostic_context *) ATTRIBUTE_NORETURN;
>   static void real_abort (void) ATTRIBUTE_NORETURN;
> @@ -183,6 +186,7 @@ diagnostic_initialize (diagnostic_context *context, int n_opts)
>     diagnostic_set_caret_max_width (context, pp_line_cutoff (context->printer));
>     for (i = 0; i < rich_location::STATICALLY_ALLOCATED_RANGES; i++)
>       context->caret_chars[i] = '^';
> +  context->show_metadata = false;
>     context->show_option_requested = false;
>     context->abort_on_error = false;
>     context->show_column = false;
> @@ -300,6 +304,7 @@ diagnostic_set_info_translated (diagnostic_info *diagnostic, const char *msg,
>     diagnostic->message.format_spec = msg;
>     diagnostic->message.m_richloc = richloc;
>     diagnostic->richloc = richloc;
> +  diagnostic->metadata = NULL;
>     diagnostic->kind = kind;
>     diagnostic->option_index = 0;
>   }
> @@ -899,6 +904,47 @@ update_effective_level_from_pragmas (diagnostic_context *context,
>     return diag_class;
>   }
>   
> +/* Generate a URL string describing CWE.  The caller is responsible for
> +   freeing the string.  */
> +
> +static char *
> +get_cwe_url (int cwe)
> +{
> +  return xasprintf ("https://cwe.mitre.org/data/definitions/%i.html", cwe);
> +}
> +
> +/* If DIAGNOSTIC has metadata, print it.
> +
> +   For example, if the diagnostic metadata associates it with CWE-119,
> +   " [CWE-119]" will be printed, suitably colorized, and with a URL of a
> +   description of the security issue.  */
> +
> +static void
> +print_any_metadata (diagnostic_context *context,
> +		    const diagnostic_info *diagnostic)
> +{
> +  if (diagnostic->metadata == NULL)
> +    return;
> +
> +  int cwe = diagnostic->metadata->get_cwe ();
> +  if (cwe)
> +    {
> +      pretty_printer *pp = context->printer;
> +      char *saved_prefix = pp_take_prefix (context->printer);
> +      pp_string (pp, " [");
> +      pp_string (pp, colorize_start (pp_show_color (pp),
> +				     diagnostic_kind_color[diagnostic->kind]));
> +      char *cwe_url = get_cwe_url (cwe);
> +      pp_begin_url (pp, cwe_url);
> +      free (cwe_url);
> +      pp_printf (pp, "CWE-%i", cwe);
> +      pp_set_prefix (context->printer, saved_prefix);
> +      pp_end_url (pp);
> +      pp_string (pp, colorize_stop (pp_show_color (pp)));
> +      pp_character (pp, ']');
> +    }
> +}
> +
>   /* Print any metadata about the option used to control DIAGNOSTIC to CONTEXT's
>      printer, e.g. " [-Werror=uninitialized]".
>      Subroutine of diagnostic_report_diagnostic.  */
> @@ -1059,6 +1105,8 @@ diagnostic_report_diagnostic (diagnostic_context *context,
>     pp_format (context->printer, &diagnostic->message);
>     (*diagnostic_starter (context)) (context, diagnostic);
>     pp_output_formatted_text (context->printer);
> +  if (context->show_metadata)
> +    print_any_metadata (context, diagnostic);
>     if (context->show_option_requested)
>       print_option_information (context, diagnostic, orig_diag_kind);
>     (*diagnostic_finalizer (context)) (context, diagnostic, orig_diag_kind);
> @@ -1190,8 +1238,8 @@ diagnostic_append_note (diagnostic_context *context,
>      permerror, error, error_at, error_at, sorry, fatal_error, internal_error,
>      and internal_error_no_backtrace, as documented and defined below.  */
>   static bool
> -diagnostic_impl (rich_location *richloc, int opt,
> -		 const char *gmsgid,
> +diagnostic_impl (rich_location *richloc, const diagnostic_metadata *metadata,
> +		 int opt, const char *gmsgid,
>   		 va_list *ap, diagnostic_t kind)
>   {
>     diagnostic_info diagnostic;
> @@ -1207,13 +1255,15 @@ diagnostic_impl (rich_location *richloc, int opt,
>         if (kind == DK_WARNING || kind == DK_PEDWARN)
>   	diagnostic.option_index = opt;
>       }
> +  diagnostic.metadata = metadata;
>     return diagnostic_report_diagnostic (global_dc, &diagnostic);
>   }
>   
>   /* Implement inform_n, warning_n, and error_n, as documented and
>      defined below.  */
>   static bool
> -diagnostic_n_impl (rich_location *richloc, int opt, unsigned HOST_WIDE_INT n,
> +diagnostic_n_impl (rich_location *richloc, const diagnostic_metadata *metadata,
> +		   int opt, unsigned HOST_WIDE_INT n,
>   		   const char *singular_gmsgid,
>   		   const char *plural_gmsgid,
>   		   va_list *ap, diagnostic_t kind)
> @@ -1233,6 +1283,7 @@ diagnostic_n_impl (rich_location *richloc, int opt, unsigned HOST_WIDE_INT n,
>     diagnostic_set_info_translated (&diagnostic, text, ap, richloc, kind);
>     if (kind == DK_WARNING)
>       diagnostic.option_index = opt;
> +  diagnostic.metadata = metadata;
>     return diagnostic_report_diagnostic (global_dc, &diagnostic);
>   }
>   
> @@ -1246,7 +1297,7 @@ emit_diagnostic (diagnostic_t kind, location_t location, int opt,
>     va_list ap;
>     va_start (ap, gmsgid);
>     rich_location richloc (line_table, location);
> -  bool ret = diagnostic_impl (&richloc, opt, gmsgid, &ap, kind);
> +  bool ret = diagnostic_impl (&richloc, NULL, opt, gmsgid, &ap, kind);
>     va_end (ap);
>     return ret;
>   }
> @@ -1260,7 +1311,7 @@ emit_diagnostic (diagnostic_t kind, rich_location *richloc, int opt,
>     auto_diagnostic_group d;
>     va_list ap;
>     va_start (ap, gmsgid);
> -  bool ret = diagnostic_impl (richloc, opt, gmsgid, &ap, kind);
> +  bool ret = diagnostic_impl (richloc, NULL, opt, gmsgid, &ap, kind);
>     va_end (ap);
>     return ret;
>   }
> @@ -1272,7 +1323,18 @@ emit_diagnostic_valist (diagnostic_t kind, location_t location, int opt,
>   			const char *gmsgid, va_list *ap)
>   {
>     rich_location richloc (line_table, location);
> -  return diagnostic_impl (&richloc, opt, gmsgid, ap, kind);
> +  return diagnostic_impl (&richloc, NULL, opt, gmsgid, ap, kind);
> +}
> +
> +/* Wrapper around diagnostic_impl taking a va_list parameter.  */
> +
> +bool
> +emit_diagnostic_valist (diagnostic_t kind, rich_location *richloc,
> +			const diagnostic_metadata *metadata,
> +			int opt,
> +			const char *gmsgid, va_list *ap)
> +{
> +  return diagnostic_impl (richloc, metadata, opt, gmsgid, ap, kind);
>   }
>   
>   /* An informative note at LOCATION.  Use this for additional details on an error
> @@ -1284,7 +1346,7 @@ inform (location_t location, const char *gmsgid, ...)
>     va_list ap;
>     va_start (ap, gmsgid);
>     rich_location richloc (line_table, location);
> -  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_NOTE);
> +  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_NOTE);
>     va_end (ap);
>   }
>   
> @@ -1297,7 +1359,7 @@ inform (rich_location *richloc, const char *gmsgid, ...)
>     auto_diagnostic_group d;
>     va_list ap;
>     va_start (ap, gmsgid);
> -  diagnostic_impl (richloc, -1, gmsgid, &ap, DK_NOTE);
> +  diagnostic_impl (richloc, NULL, -1, gmsgid, &ap, DK_NOTE);
>     va_end (ap);
>   }
>   
> @@ -1311,7 +1373,7 @@ inform_n (location_t location, unsigned HOST_WIDE_INT n,
>     va_start (ap, plural_gmsgid);
>     auto_diagnostic_group d;
>     rich_location richloc (line_table, location);
> -  diagnostic_n_impl (&richloc, -1, n, singular_gmsgid, plural_gmsgid,
> +  diagnostic_n_impl (&richloc, NULL, -1, n, singular_gmsgid, plural_gmsgid,
>   		     &ap, DK_NOTE);
>     va_end (ap);
>   }
> @@ -1326,7 +1388,7 @@ warning (int opt, const char *gmsgid, ...)
>     va_list ap;
>     va_start (ap, gmsgid);
>     rich_location richloc (line_table, input_location);
> -  bool ret = diagnostic_impl (&richloc, opt, gmsgid, &ap, DK_WARNING);
> +  bool ret = diagnostic_impl (&richloc, NULL, opt, gmsgid, &ap, DK_WARNING);
>     va_end (ap);
>     return ret;
>   }
> @@ -1342,7 +1404,7 @@ warning_at (location_t location, int opt, const char *gmsgid, ...)
>     va_list ap;
>     va_start (ap, gmsgid);
>     rich_location richloc (line_table, location);
> -  bool ret = diagnostic_impl (&richloc, opt, gmsgid, &ap, DK_WARNING);
> +  bool ret = diagnostic_impl (&richloc, NULL, opt, gmsgid, &ap, DK_WARNING);
>     va_end (ap);
>     return ret;
>   }
> @@ -1357,7 +1419,25 @@ warning_at (rich_location *richloc, int opt, const char *gmsgid, ...)
>     auto_diagnostic_group d;
>     va_list ap;
>     va_start (ap, gmsgid);
> -  bool ret = diagnostic_impl (richloc, opt, gmsgid, &ap, DK_WARNING);
> +  bool ret = diagnostic_impl (richloc, NULL, opt, gmsgid, &ap, DK_WARNING);
> +  va_end (ap);
> +  return ret;
> +}
> +
> +/* Same as "warning at" above, but using METADATA.  */
> +
> +bool
> +warning_at (rich_location *richloc, const diagnostic_metadata &metadata,
> +	    int opt, const char *gmsgid, ...)
> +{
> +  gcc_assert (richloc);
> +
> +  auto_diagnostic_group d;
> +  va_list ap;
> +  va_start (ap, gmsgid);
> +  bool ret
> +    = diagnostic_impl (richloc, &metadata, opt, gmsgid, &ap,
> +		       DK_WARNING);
>     va_end (ap);
>     return ret;
>   }
> @@ -1373,7 +1453,7 @@ warning_n (rich_location *richloc, int opt, unsigned HOST_WIDE_INT n,
>     auto_diagnostic_group d;
>     va_list ap;
>     va_start (ap, plural_gmsgid);
> -  bool ret = diagnostic_n_impl (richloc, opt, n,
> +  bool ret = diagnostic_n_impl (richloc, NULL, opt, n,
>   				singular_gmsgid, plural_gmsgid,
>   				&ap, DK_WARNING);
>     va_end (ap);
> @@ -1392,7 +1472,7 @@ warning_n (location_t location, int opt, unsigned HOST_WIDE_INT n,
>     va_list ap;
>     va_start (ap, plural_gmsgid);
>     rich_location richloc (line_table, location);
> -  bool ret = diagnostic_n_impl (&richloc, opt, n,
> +  bool ret = diagnostic_n_impl (&richloc, NULL, opt, n,
>   				singular_gmsgid, plural_gmsgid,
>   				&ap, DK_WARNING);
>     va_end (ap);
> @@ -1419,7 +1499,7 @@ pedwarn (location_t location, int opt, const char *gmsgid, ...)
>     va_list ap;
>     va_start (ap, gmsgid);
>     rich_location richloc (line_table, location);
> -  bool ret = diagnostic_impl (&richloc, opt, gmsgid, &ap, DK_PEDWARN);
> +  bool ret = diagnostic_impl (&richloc, NULL, opt, gmsgid, &ap, DK_PEDWARN);
>     va_end (ap);
>     return ret;
>   }
> @@ -1434,7 +1514,7 @@ pedwarn (rich_location *richloc, int opt, const char *gmsgid, ...)
>     auto_diagnostic_group d;
>     va_list ap;
>     va_start (ap, gmsgid);
> -  bool ret = diagnostic_impl (richloc, opt, gmsgid, &ap, DK_PEDWARN);
> +  bool ret = diagnostic_impl (richloc, NULL, opt, gmsgid, &ap, DK_PEDWARN);
>     va_end (ap);
>     return ret;
>   }
> @@ -1453,7 +1533,7 @@ permerror (location_t location, const char *gmsgid, ...)
>     va_list ap;
>     va_start (ap, gmsgid);
>     rich_location richloc (line_table, location);
> -  bool ret = diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_PERMERROR);
> +  bool ret = diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_PERMERROR);
>     va_end (ap);
>     return ret;
>   }
> @@ -1468,7 +1548,7 @@ permerror (rich_location *richloc, const char *gmsgid, ...)
>     auto_diagnostic_group d;
>     va_list ap;
>     va_start (ap, gmsgid);
> -  bool ret = diagnostic_impl (richloc, -1, gmsgid, &ap, DK_PERMERROR);
> +  bool ret = diagnostic_impl (richloc, NULL, -1, gmsgid, &ap, DK_PERMERROR);
>     va_end (ap);
>     return ret;
>   }
> @@ -1482,7 +1562,7 @@ error (const char *gmsgid, ...)
>     va_list ap;
>     va_start (ap, gmsgid);
>     rich_location richloc (line_table, input_location);
> -  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_ERROR);
> +  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_ERROR);
>     va_end (ap);
>   }
>   
> @@ -1496,7 +1576,7 @@ error_n (location_t location, unsigned HOST_WIDE_INT n,
>     va_list ap;
>     va_start (ap, plural_gmsgid);
>     rich_location richloc (line_table, location);
> -  diagnostic_n_impl (&richloc, -1, n, singular_gmsgid, plural_gmsgid,
> +  diagnostic_n_impl (&richloc, NULL, -1, n, singular_gmsgid, plural_gmsgid,
>   		     &ap, DK_ERROR);
>     va_end (ap);
>   }
> @@ -1509,7 +1589,7 @@ error_at (location_t loc, const char *gmsgid, ...)
>     va_list ap;
>     va_start (ap, gmsgid);
>     rich_location richloc (line_table, loc);
> -  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_ERROR);
> +  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_ERROR);
>     va_end (ap);
>   }
>   
> @@ -1523,7 +1603,7 @@ error_at (rich_location *richloc, const char *gmsgid, ...)
>     auto_diagnostic_group d;
>     va_list ap;
>     va_start (ap, gmsgid);
> -  diagnostic_impl (richloc, -1, gmsgid, &ap, DK_ERROR);
> +  diagnostic_impl (richloc, NULL, -1, gmsgid, &ap, DK_ERROR);
>     va_end (ap);
>   }
>   
> @@ -1537,7 +1617,7 @@ sorry (const char *gmsgid, ...)
>     va_list ap;
>     va_start (ap, gmsgid);
>     rich_location richloc (line_table, input_location);
> -  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_SORRY);
> +  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_SORRY);
>     va_end (ap);
>   }
>   
> @@ -1549,7 +1629,7 @@ sorry_at (location_t loc, const char *gmsgid, ...)
>     va_list ap;
>     va_start (ap, gmsgid);
>     rich_location richloc (line_table, loc);
> -  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_SORRY);
> +  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_SORRY);
>     va_end (ap);
>   }
>   
> @@ -1571,7 +1651,7 @@ fatal_error (location_t loc, const char *gmsgid, ...)
>     va_list ap;
>     va_start (ap, gmsgid);
>     rich_location richloc (line_table, loc);
> -  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_FATAL);
> +  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_FATAL);
>     va_end (ap);
>   
>     gcc_unreachable ();
> @@ -1588,7 +1668,7 @@ internal_error (const char *gmsgid, ...)
>     va_list ap;
>     va_start (ap, gmsgid);
>     rich_location richloc (line_table, input_location);
> -  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_ICE);
> +  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_ICE);
>     va_end (ap);
>   
>     gcc_unreachable ();
> @@ -1604,7 +1684,7 @@ internal_error_no_backtrace (const char *gmsgid, ...)
>     va_list ap;
>     va_start (ap, gmsgid);
>     rich_location richloc (line_table, input_location);
> -  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_ICE_NOBT);
> +  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_ICE_NOBT);
>     va_end (ap);
>   
>     gcc_unreachable ();
> diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
> index 2fb75e6..0e4b6ad 100644
> --- a/gcc/diagnostic.h
> +++ b/gcc/diagnostic.h
> @@ -46,6 +46,10 @@ struct diagnostic_info
>     /* The location at which the diagnostic is to be reported.  */
>     rich_location *richloc;
>   
> +  /* An optional bundle of metadata associated with the diagnostic
> +     (or NULL).  */
> +  const diagnostic_metadata *metadata;
> +
>     /* Auxiliary data for client.  */
>     void *x_data;
>     /* The kind of diagnostic it is about.  */
> @@ -126,6 +130,9 @@ struct diagnostic_context
>     /* Character used for caret diagnostics.  */
>     char caret_chars[rich_location::STATICALLY_ALLOCATED_RANGES];
>   
> +  /* True if we should print any metadata associated with diagnostics.  */
> +  bool show_metadata;
> +
>     /* True if we should print the command line option which controls
>        each diagnostic, if known.  */
>     bool show_option_requested;
> diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
> index fc92913..209e2c6 100644
> --- a/gcc/doc/invoke.texi
> +++ b/gcc/doc/invoke.texi
> @@ -279,6 +279,7 @@ Objective-C and Objective-C++ Dialects}.
>   -fno-diagnostics-show-option  -fno-diagnostics-show-caret @gol
>   -fno-diagnostics-show-labels  -fno-diagnostics-show-line-numbers @gol
>   -fdiagnostics-nn-line-numbers @gol
> +-fno-diagnostics-show-metadata  @gol
>   -fdiagnostics-minimum-margin-width=@var{width} @gol
>   -fdiagnostics-parseable-fixits  -fdiagnostics-generate-patch @gol
>   -fdiagnostics-show-template-tree  -fno-elide-type @gol
> @@ -4012,6 +4013,13 @@ as the types of expressions:
>   This option suppresses the printing of these labels (in the example above,
>   the vertical bars and the ``char *'' and ``long int'' text).
>   
> +@item -fno-diagnostics-show-metadata
> +@opindex fno-diagnostics-show-metadata
> +@opindex fdiagnostics-show-metadata
> +Diagnostics can optionally contain @url{https://cwe.mitre.org/index.html, CWE}
> +metadata.  By default, if this information is present, it will be printed with
> +the diagnostic.  This option suppresses the printing of this metadata.
> +
>   @item -fno-diagnostics-show-line-numbers
>   @opindex fno-diagnostics-show-line-numbers
>   @opindex fdiagnostics-show-line-numbers
> diff --git a/gcc/opts.c b/gcc/opts.c
> index 9928d01..0caaf04 100644
> --- a/gcc/opts.c
> +++ b/gcc/opts.c
> @@ -2491,6 +2491,10 @@ common_handle_option (struct gcc_options *opts,
>         dc->parseable_fixits_p = value;
>         break;
>   
> +    case OPT_fdiagnostics_show_metadata:
> +      dc->show_metadata = value;
> +      break;
> +
>       case OPT_fdiagnostics_show_option:
>         dc->show_option_requested = value;
>         break;
> diff --git a/gcc/toplev.c b/gcc/toplev.c
> index 2d0a390..7136164 100644
> --- a/gcc/toplev.c
> +++ b/gcc/toplev.c
> @@ -1102,6 +1102,8 @@ general_init (const char *argv0, bool init_signals)
>       = global_options_init.x_flag_diagnostics_show_line_numbers;
>     global_dc->use_nn_for_line_numbers_p
>       = global_options_init.x_flag_diagnostics_nn_line_numbers;
> +  global_dc->show_metadata
> +    = global_options_init.x_flag_diagnostics_show_metadata;
>     global_dc->show_option_requested
>       = global_options_init.x_flag_diagnostics_show_option;
>     global_dc->min_margin_width
> 

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 15/49] Add ordered_hash_map
  2019-11-16  1:21 ` [PATCH 15/49] Add ordered_hash_map David Malcolm
@ 2019-12-04 17:59   ` Martin Sebor
  2020-01-08 23:47     ` David Malcolm
  0 siblings, 1 reply; 160+ messages in thread
From: Martin Sebor @ 2019-12-04 17:59 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On 11/15/19 6:23 PM, David Malcolm wrote:
> This patch adds an ordered_hash_map template, which is similar to
> hash_map, but preserves insertion order.
> 
> gcc/ChangeLog:
> 	* Makefile.in (OBJS): Add ordered-hash-map-tests.o.
> 	* ordered-hash-map-tests.cc: New file.
> 	* ordered-hash-map.h: New file.
> 	* selftest-run-tests.c (selftest::run_tests): Call
> 	selftest::ordered_hash_map_tests_cc_tests.
> 	* selftest.h (selftest::ordered_hash_map_tests_cc_tests): New
> 	decl.
> ---
>   gcc/Makefile.in               |   1 +
>   gcc/ordered-hash-map-tests.cc | 247 ++++++++++++++++++++++++++++++++++++++++++
>   gcc/ordered-hash-map.h        | 184 +++++++++++++++++++++++++++++++
>   gcc/selftest-run-tests.c      |   1 +
>   gcc/selftest.h                |   1 +
>   5 files changed, 434 insertions(+)
>   create mode 100644 gcc/ordered-hash-map-tests.cc
>   create mode 100644 gcc/ordered-hash-map.h
> 
> diff --git a/gcc/Makefile.in b/gcc/Makefile.in
> index c87b00f..5feef6a 100644
> --- a/gcc/Makefile.in
> +++ b/gcc/Makefile.in
> @@ -1443,6 +1443,7 @@ OBJS = \
>   	optinfo-emit-json.o \
>   	options-save.o \
>   	opts-global.o \
> +	ordered-hash-map-tests.o \
>   	passes.o \
>   	plugin.o \
>   	postreload-gcse.o \
> diff --git a/gcc/ordered-hash-map-tests.cc b/gcc/ordered-hash-map-tests.cc
> new file mode 100644
> index 0000000..266f60c
> --- /dev/null
> +++ b/gcc/ordered-hash-map-tests.cc
> @@ -0,0 +1,247 @@
> +/* Unit tests for ordered-hash-map.h.
> +   Copyright (C) 2015-2019 Free Software Foundation, Inc.
> +
> +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 "tm.h"
> +#include "opts.h"
> +#include "hash-set.h"
> +#include "fixed-value.h"
> +#include "alias.h"
> +#include "flags.h"
> +#include "symtab.h"
> +#include "tree-core.h"
> +#include "stor-layout.h"
> +#include "tree.h"
> +#include "stringpool.h"
> +#include "ordered-hash-map.h"
> +#include "selftest.h"
> +
> +#if CHECKING_P
> +
> +namespace selftest {
> +
> +/* Populate *OUT_KVS with the key/value pairs of M.  */
> +
> +template <typename HashMap, typename Key, typename Value>
> +static void
> +get_kv_pairs (const HashMap &m,
> +	      auto_vec<std::pair<Key, Value> > *out_kvs)
> +{
> +  for (typename HashMap::iterator iter = m.begin ();
> +       iter != m.end ();
> +       ++iter)
> +    out_kvs->safe_push (std::make_pair ((*iter).first, (*iter).second));
> +}
> +
> +/* Construct an ordered_hash_map <const char *, int> and verify that
> +   various operations work correctly.  */
> +
> +static void
> +test_map_of_strings_to_int ()
> +{
> +  ordered_hash_map <const char *, int> m;
> +
> +  const char *ostrich = "ostrich";
> +  const char *elephant = "elephant";
> +  const char *ant = "ant";
> +  const char *spider = "spider";
> +  const char *millipede = "Illacme plenipes";
> +  const char *eric = "half a bee";
> +
> +  /* A fresh hash_map should be empty.  */
> +  ASSERT_EQ (0, m.elements ());
> +  ASSERT_EQ (NULL, m.get (ostrich));
> +
> +  /* Populate the hash_map.  */
> +  ASSERT_EQ (false, m.put (ostrich, 2));
> +  ASSERT_EQ (false, m.put (elephant, 4));
> +  ASSERT_EQ (false, m.put (ant, 6));
> +  ASSERT_EQ (false, m.put (spider, 8));
> +  ASSERT_EQ (false, m.put (millipede, 750));
> +  ASSERT_EQ (false, m.put (eric, 3));
> +
> +  /* Verify that we can recover the stored values.  */
> +  ASSERT_EQ (6, m.elements ());
> +  ASSERT_EQ (2, *m.get (ostrich));
> +  ASSERT_EQ (4, *m.get (elephant));
> +  ASSERT_EQ (6, *m.get (ant));
> +  ASSERT_EQ (8, *m.get (spider));
> +  ASSERT_EQ (750, *m.get (millipede));
> +  ASSERT_EQ (3, *m.get (eric));
> +
> +  /* Verify that the order of insertion is preserved.  */
> +  auto_vec<std::pair<const char *, int> > kvs;
> +  get_kv_pairs (m, &kvs);
> +  ASSERT_EQ (kvs.length (), 6);
> +  ASSERT_EQ (kvs[0].first, ostrich);
> +  ASSERT_EQ (kvs[0].second, 2);
> +  ASSERT_EQ (kvs[1].first, elephant);
> +  ASSERT_EQ (kvs[1].second, 4);
> +  ASSERT_EQ (kvs[2].first, ant);
> +  ASSERT_EQ (kvs[2].second, 6);
> +  ASSERT_EQ (kvs[3].first, spider);
> +  ASSERT_EQ (kvs[3].second, 8);
> +  ASSERT_EQ (kvs[4].first, millipede);
> +  ASSERT_EQ (kvs[4].second, 750);
> +  ASSERT_EQ (kvs[5].first, eric);
> +  ASSERT_EQ (kvs[5].second, 3);
> +}
> +
> +/* Construct an ordered_hash_map using int_hash and verify that various
> +   operations work correctly.  */
> +
> +static void
> +test_map_of_int_to_strings ()
> +{
> +  const int EMPTY = -1;
> +  const int DELETED = -2;
> +  typedef int_hash <int, EMPTY, DELETED> int_hash_t;
> +  ordered_hash_map <int_hash_t, const char *> m;
> +
> +  const char *ostrich = "ostrich";
> +  const char *elephant = "elephant";
> +  const char *ant = "ant";
> +  const char *spider = "spider";
> +  const char *millipede = "Illacme plenipes";
> +  const char *eric = "half a bee";
> +
> +  /* A fresh hash_map should be empty.  */
> +  ASSERT_EQ (0, m.elements ());
> +  ASSERT_EQ (NULL, m.get (2));
> +
> +  /* Populate the hash_map.  */
> +  ASSERT_EQ (false, m.put (2, ostrich));
> +  ASSERT_EQ (false, m.put (4, elephant));
> +  ASSERT_EQ (false, m.put (6, ant));
> +  ASSERT_EQ (false, m.put (8, spider));
> +  ASSERT_EQ (false, m.put (750, millipede));
> +  ASSERT_EQ (false, m.put (3, eric));
> +
> +  /* Verify that we can recover the stored values.  */
> +  ASSERT_EQ (6, m.elements ());
> +  ASSERT_EQ (*m.get (2), ostrich);
> +  ASSERT_EQ (*m.get (4), elephant);
> +  ASSERT_EQ (*m.get (6), ant);
> +  ASSERT_EQ (*m.get (8), spider);
> +  ASSERT_EQ (*m.get (750), millipede);
> +  ASSERT_EQ (*m.get (3), eric);
> +
> +  /* Verify that the order of insertion is preserved.  */
> +  auto_vec<std::pair<int, const char *> > kvs;
> +  get_kv_pairs (m, &kvs);
> +  ASSERT_EQ (kvs.length (), 6);
> +  ASSERT_EQ (kvs[0].first, 2);
> +  ASSERT_EQ (kvs[0].second, ostrich);
> +  ASSERT_EQ (kvs[1].first, 4);
> +  ASSERT_EQ (kvs[1].second, elephant);
> +  ASSERT_EQ (kvs[2].first, 6);
> +  ASSERT_EQ (kvs[2].second, ant);
> +  ASSERT_EQ (kvs[3].first, 8);
> +  ASSERT_EQ (kvs[3].second, spider);
> +  ASSERT_EQ (kvs[4].first, 750);
> +  ASSERT_EQ (kvs[4].second, millipede);
> +  ASSERT_EQ (kvs[5].first, 3);
> +  ASSERT_EQ (kvs[5].second, eric);
> +}
> +
> +/* Verify that we can remove items from an ordered_hash_map.  */
> +
> +static void
> +test_removal ()
> +{
> +  ordered_hash_map <const char *, int> m;
> +
> +  const char *ostrich = "ostrich";
> +  ASSERT_EQ (false, m.put (ostrich, 2));
> +
> +  ASSERT_EQ (1, m.elements ());
> +  ASSERT_EQ (2, *m.get (ostrich));
> +
> +  {
> +    auto_vec<std::pair<const char *, int> > kvs;
> +    get_kv_pairs (m, &kvs);
> +    ASSERT_EQ (kvs.length (), 1);
> +    ASSERT_EQ (kvs[0].first, ostrich);
> +    ASSERT_EQ (kvs[0].second, 2);
> +  }
> +
> +  m.remove (ostrich);
> +
> +  ASSERT_EQ (0, m.elements ());
> +  {
> +    auto_vec<std::pair<const char *, int> > kvs;
> +    get_kv_pairs (m, &kvs);
> +    ASSERT_EQ (kvs.length (), 0);
> +  }
> +
> +  /* Reinsertion (with a different value).  */
> +  ASSERT_EQ (false, m.put (ostrich, 42));
> +  ASSERT_EQ (1, m.elements ());
> +  ASSERT_EQ (42, *m.get (ostrich));
> +  {
> +    auto_vec<std::pair<const char *, int> > kvs;
> +    get_kv_pairs (m, &kvs);
> +    ASSERT_EQ (kvs.length (), 1);
> +    ASSERT_EQ (kvs[0].first, ostrich);
> +    ASSERT_EQ (kvs[0].second, 42);
> +  }
> +}
> +
> +/* Verify that ordered_hash_map's copy-ctor works.  */
> +
> +static void
> +test_copy_ctor ()
> +{
> +  ordered_hash_map <const char *, int> m;
> +
> +  const char *ostrich = "ostrich";
> +  ASSERT_EQ (false, m.put (ostrich, 2));
> +
> +  ASSERT_EQ (1, m.elements ());
> +  ASSERT_EQ (2, *m.get (ostrich));
> +
> +  ordered_hash_map <const char *, int> copy (m);
> +  ASSERT_EQ (1, copy.elements ());
> +  ASSERT_EQ (2, *copy.get (ostrich));
> +
> +  /* Remove from source.  */
> +  m.remove (ostrich);
> +  ASSERT_EQ (0, m.elements ());
> +
> +  /* Copy should be unaffected.  */
> +  ASSERT_EQ (1, copy.elements ());
> +  ASSERT_EQ (2, *copy.get (ostrich));
> +}
> +
> +/* Run all of the selftests within this file.  */
> +
> +void
> +ordered_hash_map_tests_cc_tests ()
> +{
> +  test_map_of_strings_to_int ();
> +  test_map_of_int_to_strings ();
> +  test_removal ();
> +  test_copy_ctor ();
> +}
> +
> +} // namespace selftest
> +
> +#endif /* CHECKING_P */
> diff --git a/gcc/ordered-hash-map.h b/gcc/ordered-hash-map.h
> new file mode 100644
> index 0000000..4e35cad
> --- /dev/null
> +++ b/gcc/ordered-hash-map.h
> @@ -0,0 +1,184 @@
> +/* A type-safe hash map that retains the insertion order of keys.
> +   Copyright (C) 2019 Free Software Foundation, Inc.
> +
> +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/>.  */
> +
> +
> +#ifndef GCC_ORDERED_HASH_MAP_H
> +#define GCC_ORDERED_HASH_MAP_H
> +
> +/* Notes:
> +   - The keys must be PODs, since vec<> uses assignment to populate slots
> +     without properly initializing them.
> +   - doesn't have GTY support.
> +   - supports removal, but retains order of original insertion.
> +     (Removal might be better handled by using a doubly-linked list
> +     of nodes, holding the values).  */
> +
> +template<typename KeyId, typename Value,
> +	 typename Traits>
> +class ordered_hash_map
> +{
> +  typedef typename Traits::key_type Key;
> +
> +public:
> +  ordered_hash_map () {}
> +
> +  ordered_hash_map (const ordered_hash_map &other)

The container defines a copy-constructor but no copy assignment.
Is it safely assignable? (I don't think auto_vec is safely copyable
or assignable due to PR 90904.  It looks like the copy ctor works
around it below.)

I don't think I've made use of the hash_map copy ctor or copy
assignment but if it's anything like other GCC containers I'd
worry about it not doing the right thing, especially for non-
PODs.  I spent too much time chasing down miscompilations and
other problems due to bugs (or design limitations) in these
classes.

I'd far prefer to see us use libstdc++ containers in new code
than introduce new ones of our own.  They are better designed
and much better tested than these.  (I realize we're still
hampered by targeting C++ 98.)

Martin

> +  : m_map (other.m_map),
> +    m_keys (other.m_keys.length ()),
> +    m_key_index (other.m_key_index)
> +  {
> +     unsigned i;
> +     Key key;
> +     FOR_EACH_VEC_ELT (other.m_keys, i, key)
> +       m_keys.quick_push (key);
> +  }
> +
> +  /* If key K isn't already in the map add key K with value V to the map, and
> +     return false.  Otherwise set the value of the entry for key K to be V and
> +     return true.  */
> +
> +  bool put (const Key &k, const Value &v)
> +  {
> +    bool existed = m_map.put (k, v);
> +    if (!existed)
> +      {
> +        bool key_present;
> +        int &slot = m_key_index.get_or_insert (k, &key_present);
> +        if (!key_present)
> +          {
> +             slot = m_keys.length ();
> +             m_keys.safe_push (k);
> +          }
> +      }
> +    return existed;
> +  }
> +
> +  /* If the passed in key is in the map return its value otherwise NULL.  */
> +
> +  Value *get (const Key &k)
> +  {
> +    return m_map.get (k);
> +  }
> +
> +  /* Removing a key removes it from the map, but retains the insertion
> +     order.  */
> +
> +  void remove (const Key &k)
> +  {
> +     m_map.remove (k);
> +  }
> +
> +  size_t elements () const { return m_map.elements (); }
> +
> +  class iterator
> +  {
> +  public:
> +    explicit iterator (const ordered_hash_map &map, unsigned idx) :
> +      m_ordered_hash_map (map), m_idx (idx) {}
> +
> +    iterator &operator++ ()
> +    {
> +       /* Increment m_idx until we find a non-deleted element, or go beyond
> +	  the end.  */
> +       while (1)
> +	 {
> +	   ++m_idx;
> +	   if (valid_index_p ())
> +	     break;
> +	}
> +      return *this;
> +    }
> +
> +    /* Can't use std::pair here, because GCC before 4.3 don't handle
> +       std::pair where template parameters are references well.
> +       See PR86739.  */
> +    struct reference_pair {
> +      const Key &first;
> +      Value &second;
> +
> +      reference_pair (const Key &key, Value &value)
> +      : first (key), second (value) {}
> +
> +      template <typename K, typename V>
> +      operator std::pair<K, V> () const { return std::pair<K, V> (first, second); }
> +    };
> +
> +    reference_pair operator* ()
> +    {
> +      const Key &k = m_ordered_hash_map.m_keys[m_idx];
> +      Value *slot
> +        = const_cast<ordered_hash_map &> (m_ordered_hash_map).get (k);
> +      gcc_assert (slot);
> +      return reference_pair (k, *slot);
> +    }
> +
> +    bool
> +    operator != (const iterator &other) const
> +    {
> +      return m_idx != other.m_idx;
> +    }
> +
> +    /* Treat one-beyond-the-end as valid, for handling the "end" case.  */
> +
> +    bool valid_index_p () const
> +    {
> +      if (m_idx > m_ordered_hash_map.m_keys.length ())
> +	return false;
> +      if (m_idx == m_ordered_hash_map.m_keys.length ())
> +	return true;
> +      const Key &k = m_ordered_hash_map.m_keys[m_idx];
> +      Value *slot
> +	= const_cast<ordered_hash_map &> (m_ordered_hash_map).get (k);
> +      return slot != NULL;
> +    }
> +
> +    const ordered_hash_map &m_ordered_hash_map;
> +    unsigned m_idx;
> +  };
> +
> +  /* Standard iterator retrieval methods.  */
> +
> +  iterator begin () const
> +  {
> +    iterator i = iterator (*this, 0);
> +    while (!i.valid_index_p () && i != end ())
> +      ++i;
> +    return i;
> +  }
> +  iterator end () const { return iterator (*this, m_keys.length ()); }
> +
> +private:
> +  /* The underlying map.  */
> +  hash_map<KeyId, Value, Traits> m_map;
> +
> +  /* The ordering of the keys.  */
> +  auto_vec<Key> m_keys;
> +
> +  /* For each key that's ever been in the map, its index within m_keys.  */
> +  hash_map<KeyId, int> m_key_index;
> +};
> +
> +/* Two-argument form.  */
> +
> +template<typename Key, typename Value,
> +	 typename Traits = simple_hashmap_traits<default_hash_traits<Key>,
> +						 Value> >
> +class ordered_hash_map;
> +
> +#endif /* GCC_ORDERED_HASH_MAP_H */
> diff --git a/gcc/selftest-run-tests.c b/gcc/selftest-run-tests.c
> index 92c8de7..f41244b 100644
> --- a/gcc/selftest-run-tests.c
> +++ b/gcc/selftest-run-tests.c
> @@ -76,6 +76,7 @@ selftest::run_tests ()
>     cgraph_c_tests ();
>     optinfo_emit_json_cc_tests ();
>     opt_problem_cc_tests ();
> +  ordered_hash_map_tests_cc_tests ();
>   
>     /* Mid-level data structures.  */
>     input_c_tests ();
> diff --git a/gcc/selftest.h b/gcc/selftest.h
> index c67b688..933d1cd 100644
> --- a/gcc/selftest.h
> +++ b/gcc/selftest.h
> @@ -242,6 +242,7 @@ extern void input_c_tests ();
>   extern void json_cc_tests ();
>   extern void opt_problem_cc_tests ();
>   extern void optinfo_emit_json_cc_tests ();
> +extern void ordered_hash_map_tests_cc_tests ();
>   extern void predict_c_tests ();
>   extern void pretty_print_c_tests ();
>   extern void read_rtl_function_c_tests ();
> 

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 20/49] analyzer: new builtins
  2019-11-16  1:23 ` [PATCH 20/49] analyzer: new builtins David Malcolm
@ 2019-12-04 18:11   ` Martin Sebor
  2019-12-19 15:16     ` David Malcolm
  0 siblings, 1 reply; 160+ messages in thread
From: Martin Sebor @ 2019-12-04 18:11 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On 11/15/19 6:23 PM, David Malcolm wrote:
> gcc/ChangeLog:
> 	* builtins.def (BUILT_IN_ANALYZER_BREAK): New builtin.
> 	(BUILT_IN_ANALYZER_DUMP): New builtin.
> 	(BUILT_IN_ANALYZER_DUMP_EXPLODED_NODES): New builtin.
> 	(BUILT_IN_ANALYZER_DUMP_NUM_HEAP_REGIONS): New builtin.
> 	(BUILT_IN_ANALYZER_DUMP_PATH): New builtin.
> 	(BUILT_IN_ANALYZER_DUMP_REGION_MODEL): New builtin.
> 	(BUILT_IN_ANALYZER_EVAL): New builtin.


I have two comments:

1) builtins.def is getting very large and I think it would make
it easier to work with if new related sets of built-ins were added
in their own files.  (In full disclosure, I tried breaking it up
once and ran into weird build errors and gave up, so it may not
be entirely straightforward, not like making other changes to
the compiler ;)

2) Since there are no changes to the manual documenting these,
I assume they are internal only, not to be called by user code?
(Should they be marked as internal functions then, to prevent
them from being called?)  Even so, it would help GCC developers
to document them somewhere and add a comment pointing at
the documentation here.  If they can be called by user code,
I think they should be mentioned in the manual even if (and
I'd say especially if) we don't users calling them directly.

Martin

> ---
>   gcc/builtins.def | 33 +++++++++++++++++++++++++++++++++
>   1 file changed, 33 insertions(+)
> 
> diff --git a/gcc/builtins.def b/gcc/builtins.def
> index d8233f5..f34e95f 100644
> --- a/gcc/builtins.def
> +++ b/gcc/builtins.def
> @@ -1107,4 +1107,37 @@ DEF_GCC_BUILTIN (BUILT_IN_LINE, "LINE", BT_FN_INT, ATTR_NOTHROW_LEAF_LIST)
>   /* HSAIL/BRIG frontend builtins.  */
>   #include "brig-builtins.def"
>   
> +
> +/* Analyzer builtins.  */
> +DEF_BUILTIN (BUILT_IN_ANALYZER_BREAK, "__analyzer_break",
> +	     BUILT_IN_NORMAL, BT_FN_VOID, BT_LAST,
> +	     false, false, false, ATTR_NULL, true, true)
> +
> +DEF_BUILTIN (BUILT_IN_ANALYZER_DUMP, "__analyzer_dump",
> +	     BUILT_IN_NORMAL, BT_FN_VOID, BT_LAST,
> +	     false, false, false, ATTR_NULL, true, true)
> +
> +DEF_BUILTIN (BUILT_IN_ANALYZER_DUMP_EXPLODED_NODES,
> +	     "__analyzer_dump_exploded_nodes",
> +	     BUILT_IN_NORMAL, BT_FN_VOID_INT, BT_LAST,
> +	     false, false, false, ATTR_NULL, true, true)
> +
> +DEF_BUILTIN (BUILT_IN_ANALYZER_DUMP_NUM_HEAP_REGIONS,
> +	     "__analyzer_dump_num_heap_regions",
> +	     BUILT_IN_NORMAL, BT_FN_VOID, BT_LAST,
> +	     false, false, false, ATTR_NULL, true, true)
> +
> +DEF_BUILTIN (BUILT_IN_ANALYZER_DUMP_PATH, "__analyzer_dump_path",
> +	     BUILT_IN_NORMAL, BT_FN_VOID, BT_LAST,
> +	     false, false, false, ATTR_NULL, true, true)
> +
> +DEF_BUILTIN (BUILT_IN_ANALYZER_DUMP_REGION_MODEL,
> +	     "__analyzer_dump_region_model",
> +	     BUILT_IN_NORMAL, BT_FN_VOID, BT_LAST,
> +	     false, false, false, ATTR_NULL, true, true)
> +
> +DEF_BUILTIN (BUILT_IN_ANALYZER_EVAL, "__analyzer_eval",
> +	     BUILT_IN_NORMAL, BT_FN_VOID_INT, BT_LAST,
> +	     false, false, false, ATTR_NULL, true, true)
> +
>   #undef DEF_BUILTIN
> 

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 21/49] analyzer: command-line options
  2019-11-16  1:18 ` [PATCH 21/49] analyzer: command-line options David Malcolm
@ 2019-12-04 18:35   ` Martin Sebor
  2019-12-06 18:14     ` Eric Gallager
  0 siblings, 1 reply; 160+ messages in thread
From: Martin Sebor @ 2019-12-04 18:35 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On 11/15/19 6:23 PM, David Malcolm wrote:
> This patch contains the command-line options for the analyzer.
> 

Some of the -Wanalyzer- options sounds like they control similar
warnings as existing options (e.g., -Wanalyzer-null-argument sounds
like -Wnonnull and -Wanalyzer-null-dereference like -Wnull-dereference).
There are also options whose names suggest they are in the same class
as -Wmaybe-xxx (e.g., -Wanalyzer-possible-null-argument sounds like
it would correspond to -Wmaybe-null-argument if it existed).

I have a more general question about the apparent overlap of
functionality that I will save for later but here I wonder about
the names: that introducing a new set of similar sounding options
might make them confusing, and might make the analyzer seem more
like an add-on rather than an integral part of GCC.  I realize
the existing option names don't use any particular convention so
this is an opportunity to put one in place, but at a cost of
divergence.  Unless you expect the existing options to go away,
having consistent names would make for a more cohesive feel.

My other concern is the verbosity of some these options:
-Wanalyzer-use-of-pointer-in-stale-stack-frame is a mouthful and
will take up a lot of terminal space.  It sounds like the option
controls warnings about uses of dangling pointers to auto objects,
similar to -Wreturn-local-addr.  My impression that conventionally
GCC options have tended to be brief, and I personally would prefer
shorter option names.

Martin

> gcc/ChangeLog:
> 	* analyzer/plugin.opt: New file.
> 	* common.opt (--analyzer): New driver option.
> ---
>   gcc/analyzer/plugin.opt | 161 ++++++++++++++++++++++++++++++++++++++++++++++++
>   gcc/common.opt          |   3 +
>   2 files changed, 164 insertions(+)
>   create mode 100644 gcc/analyzer/plugin.opt
> 
> diff --git a/gcc/analyzer/plugin.opt b/gcc/analyzer/plugin.opt
> new file mode 100644
> index 0000000..55f54bb
> --- /dev/null
> +++ b/gcc/analyzer/plugin.opt
> @@ -0,0 +1,161 @@
> +; plugin.opt -- Options for the analyzer.
> +
> +; Copyright (C) 2019 Free Software Foundation, Inc.
> +;
> +; 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/>.
> +
> +; See the GCC internals manual for a description of this file's format.
> +
> +; Please try to keep this file in ASCII collating order.
> +
> +Wanalyzer-double-fclose
> +Common Var(warn_analyzer_double_fclose) Init(1) Warning
> +Warn about code paths in which a stdio FILE can be closed more than once.
> +
> +Wanalyzer-double-free
> +Common Var(warn_analyzer_double_free) Init(1) Warning
> +Warn about code paths in which a pointer can be freed more than once.
> +
> +Wanalyzer-exposure-through-output-file
> +Common Var(warn_analyzer_exposure_through_output_file) Init(1) Warning
> +Warn about code paths in which sensitive data is written to a file.
> +
> +Wanalyzer-file-leak
> +Common Var(warn_analyzer_file_leak) Init(1) Warning
> +Warn about code paths in which a stdio FILE is not closed.
> +
> +Wanalyzer-free-of-non-heap
> +Common Var(warn_analyzer_free_of_non_heap) Init(1) Warning
> +Warn about code paths in which a non-heap pointer is freed.
> +
> +Wanalyzer-malloc-leak
> +Common Var(warn_analyzer_malloc_leak) Init(1) Warning
> +Warn about code paths in which a heap-allocated pointer leaks.
> +
> +Wanalyzer-possible-null-argument
> +Common Var(warn_analyzer_possible_null_argument) Init(1) Warning
> +Warn about code paths in which a possibly-NULL value is passed to a must-not-be-NULL function argument.
> +
> +Wanalyzer-possible-null-dereference
> +Common Var(warn_analyzer_possible_null_dereference) Init(1) Warning
> +Warn about code paths in which a possibly-NULL pointer is dereferenced.
> +
> +Wanalyzer-null-argument
> +Common Var(warn_analyzer_null_argument) Init(1) Warning
> +Warn about code paths in which NULL is passed to a must-not-be-NULL function argument.
> +
> +Wanalyzer-null-dereference
> +Common Var(warn_analyzer_null_dereference) Init(1) Warning
> +Warn about code paths in which a NULL pointer is dereferenced.
> +
> +Wanalyzer-stale-setjmp-buffer
> +Common Var(warn_analyzer_stale_setjmp_buffer) Init(1) Warning
> +Warn about code paths in which a longjmp rewinds to a jmp_buf saved in a stack frame that has returned.
> +
> +Wanalyzer-tainted-array-index
> +Common Var(warn_analyzer_tainted_array_index) Init(1) Warning
> +Warn about code paths in which an unsanitized value is used as an array index.
> +
> +Wanalyzer-use-after-free
> +Common Var(warn_analyzer_use_after_free) Init(1) Warning
> +Warn about code paths in which a freed value is used.
> +
> +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-use-of-uninitialized-value
> +Common Var(warn_analyzer_use_of_uninitialized_value) Init(1) Warning
> +Warn about code paths in which an initialized value is used.
> +
> +Wanalyzer-too-complex
> +Common Var(warn_analyzer_too_complex) Init(0) Warning
> +Warn if the code is too complicated for the analyzer to fully explore.
> +
> +fanalyzer-checker=
> +Common Joined RejectNegative Var(flag_analyzer_checker)
> +Restrict the analyzer to run just the named checker.
> +
> +fanalyzer-fine-grained
> +Common Var(flag_analyzer_fine_grained) Init(0)
> +Avoid combining multiple statements into one exploded edge.
> +
> +fanalyzer-state-purge
> +Common Var(flag_analyzer_state_purge) Init(1)
> +Purge unneeded state during analysis.
> +
> +fanalyzer-state-merge
> +Common Var(flag_analyzer_state_merge) Init(1)
> +Merge similar-enough states during analysis.
> +
> +fanalyzer-transitivity
> +Common Var(flag_analyzer_transitivity) Init(0)
> +Enable transitivity of constraints during analysis.
> +
> +fanalyzer-call-summaries
> +Common Var(flag_analyzer_call_summaries) Init(0)
> +Approximate the effect of function calls to simplify analysis.
> +
> +fanalyzer-verbose-edges
> +Common Var(flag_analyzer_verbose_edges) Init(0)
> +Emit more verbose descriptions of control flow in diagnostics.
> +
> +fanalyzer-verbose-state-changes
> +Common Var(flag_analyzer_verbose_state_changes) Init(0)
> +Emit more verbose descriptions of state changes in diagnostics.
> +
> +fanalyzer-verbosity=
> +Common Joined UInteger Var(analyzer_verbosity) Init(2)
> +Control which events are displayed in diagnostic paths.
> +
> +fdump-analyzer
> +Common RejectNegative Var(flag_dump_analyzer)
> +Dump internal details about what the analyzer is doing to SRCFILE.analyzer.txt.
> +
> +fdump-analyzer-stderr
> +Common RejectNegative Var(flag_dump_analyzer_stderr)
> +Dump internal details about what the analyzer is doing to stderr.
> +
> +fdump-analyzer-callgraph
> +Common RejectNegative Var(flag_dump_analyzer_callgraph)
> +Dump the analyzer supergraph to a SRCFILE.callgraph.dot file.
> +
> +fdump-analyzer-exploded-graph
> +Common RejectNegative Var(flag_dump_analyzer_exploded_graph)
> +Dump the analyzer exploded graph to a SRCFILE.eg.dot file.
> +
> +fdump-analyzer-exploded-nodes
> +Common RejectNegative Var(flag_dump_analyzer_exploded_nodes)
> +Emit diagnostics showing the location of nodes in the exploded graph.
> +
> +fdump-analyzer-exploded-nodes-2
> +Common RejectNegative Var(flag_dump_analyzer_exploded_nodes_2)
> +Dump a textual representation of the exploded graph to SRCFILE.eg.txt.
> +
> +fdump-analyzer-exploded-nodes-3
> +Common RejectNegative Var(flag_dump_analyzer_exploded_nodes_3)
> +Dump a textual representation of the exploded graph to SRCFILE.eg-ID.txt.
> +
> +fdump-analyzer-state-purge
> +Common RejectNegative Var(flag_dump_analyzer_state_purge)
> +Dump state-purging information to a SRCFILE.state-purge.dot file.
> +
> +fdump-analyzer-supergraph
> +Common RejectNegative Var(flag_dump_analyzer_supergraph)
> +Dump the analyzer supergraph to a SRCFILE.supergraph.dot file.
> +
> +; This comment is to ensure we retain the blank line above.
> diff --git a/gcc/common.opt b/gcc/common.opt
> index adc9931..0c352a3 100644
> --- a/gcc/common.opt
> +++ b/gcc/common.opt
> @@ -269,6 +269,9 @@ Driver Joined Alias(e)
>   -extra-warnings
>   Common Warning Alias(Wextra)
>   
> +-analyzer
> +Driver Var(analyzer_flag)
> +
>   -for-assembler
>   Driver Separate Alias(Xassembler)
>   
> 

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 23/49] analyzer: logging support
  2019-11-16  1:18 ` [PATCH 23/49] analyzer: logging support David Malcolm
@ 2019-12-04 18:56   ` Martin Sebor
  0 siblings, 0 replies; 160+ messages in thread
From: Martin Sebor @ 2019-12-04 18:56 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On 11/15/19 6:23 PM, David Malcolm wrote:
> This patch adds a logging framework to the analyzer which handles
> hierarchical messages (showing the nested structure of the calls).
> 
> This code is largely based on that in the "jit" subdirectory (with
> a few changes).  An alternative would be to generalize that code
> and move it to the gcc parent directory.
> 
> gcc/ChangeLog:
> 	* analyzer/analyzer-logging.cc: New file.
> 	* analyzer/analyzer-logging.h: New file.
> ---
>   gcc/analyzer/analyzer-logging.cc | 220 +++++++++++++++++++++++++++++++++
>   gcc/analyzer/analyzer-logging.h  | 256 +++++++++++++++++++++++++++++++++++++++
>   2 files changed, 476 insertions(+)
>   create mode 100644 gcc/analyzer/analyzer-logging.cc
>   create mode 100644 gcc/analyzer/analyzer-logging.h
> 
> diff --git a/gcc/analyzer/analyzer-logging.cc b/gcc/analyzer/analyzer-logging.cc
> new file mode 100644
> index 0000000..575435c
> --- /dev/null
> +++ b/gcc/analyzer/analyzer-logging.cc
> @@ -0,0 +1,220 @@
> +/* Hierarchical log messages for the analyzer.
> +   Copyright (C) 2014-2019 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 "toplev.h" /* for print_version */
> +#include "pretty-print.h" /* for print_version */
> +#include "diagnostic.h"
> +#include "tree-diagnostic.h"
> +
> +#include "analyzer/analyzer-logging.h"
> +
> +/* Implementation of class logger.  */
> +
> +/* ctor for logger.  */
> +
> +logger::logger (FILE *f_out,
> +		int, /* flags */
> +		int /* verbosity */,
> +		const pretty_printer &reference_pp) :
> +  m_refcount (0),
> +  m_f_out (f_out),
> +  m_indent_level (0),
> +  m_log_refcount_changes (false),
> +  m_pp (reference_pp.clone ())
> +{
> +  pp_show_color (m_pp) = 0;
> +  pp_buffer (m_pp)->stream = f_out;
> +
> +  /* %qE in logs for SSA_NAMEs should show the ssa names, rather than
> +     trying to prettify things by showing the underlying var.  */
> +  pp_format_decoder (m_pp) = default_tree_printer;
> +
> +  /* Begin the log by writing the GCC version.  */
> +  print_version (f_out, "", false);
> +}
> +
> +/* The destructor for logger, invoked via
> +   the decref method when the refcount hits zero.
> +   Note that we do not close the underlying FILE * (m_f_out).  */
> +
> +logger::~logger ()
> +{
> +  /* This should be the last message emitted.  */
> +  log ("%s", __PRETTY_FUNCTION__);
> +  gcc_assert (m_refcount == 0);
> +  delete m_pp;
> +}
> +
> +/* Increment the reference count of the logger.  */
> +
> +void
> +logger::incref (const char *reason)
> +{
> +  m_refcount++;
> +  if (m_log_refcount_changes)
> +    log ("%s: reason: %s refcount now %i ",
> +	 __PRETTY_FUNCTION__, reason, m_refcount);
> +}
> +
> +/* Decrement the reference count of the logger,
> +   deleting it if nothing is referring to it.  */
> +
> +void
> +logger::decref (const char *reason)
> +{
> +  gcc_assert (m_refcount > 0);
> +  --m_refcount;
> +  if (m_log_refcount_changes)
> +    log ("%s: reason: %s refcount now %i",
> +	 __PRETTY_FUNCTION__, reason, m_refcount);
> +  if (m_refcount == 0)
> +    delete this;
> +}
> +
> +/* Write a formatted message to the log, by calling the log_va method.  */
> +
> +void
> +logger::log (const char *fmt, ...)
> +{
> +  va_list ap;
> +  va_start (ap, fmt);
> +  log_va (fmt, &ap);
> +  va_end (ap);
> +}
> +
> +/* Write an indented line to the log file.
> +
> +   We explicitly flush after each line: if something crashes the process,
> +   we want the logfile/stream to contain the most up-to-date hint about the
> +   last thing that was happening, without it being hidden in an in-process
> +   buffer.  */
> +
> +void
> +logger::log_va (const char *fmt, va_list *ap)
> +{
> +  start_log_line ();
> +  log_va_partial (fmt, ap);
> +  end_log_line ();
> +}
> +
> +void
> +logger::start_log_line ()
> +{
> +  for (int i = 0; i < m_indent_level; i++)
> +    fputc (' ', m_f_out);
> +}
> +
> +void
> +logger::log_partial (const char *fmt, ...)
> +{
> +  va_list ap;
> +  va_start (ap, fmt);
> +  log_va_partial (fmt, &ap);
> +  va_end (ap);
> +}
> +
> +void
> +logger::log_va_partial (const char *fmt, va_list *ap)
> +{
> +  text_info text;
> +  text.format_spec = fmt;
> +  text.args_ptr = ap;
> +  text.err_no = 0;
> +  pp_format (m_pp, &text);
> +  pp_output_formatted_text (m_pp);
> +}
> +
> +void
> +logger::end_log_line ()
> +{
> +  pp_flush (m_pp);
> +  pp_clear_output_area (m_pp);
> +  fprintf (m_f_out, "\n");
> +  fflush (m_f_out);
> +}
> +
> +/* Record the entry within a particular scope, indenting subsequent
> +   log lines accordingly.  */
> +
> +void
> +logger::enter_scope (const char *scope_name)
> +{
> +  log ("entering: %s", scope_name);
> +  m_indent_level += 1;
> +}
> +
> +void
> +logger::enter_scope (const char *scope_name, const char *fmt, va_list *ap)
> +{
> +  start_log_line ();
> +  log_partial ("entering: %s: ", scope_name);
> +  log_va_partial (fmt, ap);
> +  end_log_line ();
> +
> +  m_indent_level += 1;
> +}
> +
> +
> +/* Record the exit from a particular scope, restoring the indent level to
> +   before the scope was entered.  */
> +
> +void
> +logger::exit_scope (const char *scope_name)
> +{
> +  if (m_indent_level)
> +    m_indent_level -= 1;
> +  else
> +    log ("(mismatching indentation)");
> +  log ("exiting: %s", scope_name);
> +}
> +
> +/* Implementation of class log_user.  */
> +
> +/* The constructor for log_user.  */
> +
> +log_user::log_user (logger *logger) : m_logger (logger)
> +{
> +  if (m_logger)
> +    m_logger->incref("log_user ctor");
> +}
> +
> +/* The destructor for log_user.  */
> +
> +log_user::~log_user ()
> +{
> +  if (m_logger)
> +    m_logger->decref("log_user dtor");
> +}
> +
> +/* Set the logger for a log_user, managing the reference counts
> +   of the old and new logger (either of which might be NULL).  */
> +
> +void
> +log_user::set_logger (logger *logger)
> +{
> +  if (logger)
> +    logger->incref ("log_user::set_logger");
> +  if (m_logger)
> +    m_logger->decref ("log_user::set_logger");
> +  m_logger = logger;
> +}
> diff --git a/gcc/analyzer/analyzer-logging.h b/gcc/analyzer/analyzer-logging.h
> new file mode 100644
> index 0000000..3e000b6
> --- /dev/null
> +++ b/gcc/analyzer/analyzer-logging.h
> @@ -0,0 +1,256 @@
> +/* Hierarchical log messages for the analyzer.
> +   Copyright (C) 2014-2019 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/>.  */
> +
> +/* Adapted from jit-logging.h.  */
> +
> +#ifndef ANALYZER_LOGGING_H
> +#define ANALYZER_LOGGING_H
> +
> +#include "diagnostic-core.h"
> +
> +/* A logger encapsulates a logging stream: a way to send
> +   lines of pertinent information to a FILE *.  */
> +
> +class logger
> +{
> + public:
> +  logger (FILE *f_out, int flags, int verbosity, const pretty_printer &reference_pp);
> +  ~logger ();
> +
> +  void incref (const char *reason);
> +  void decref (const char *reason);
> +
> +  void log (const char *fmt, ...)
> +    ATTRIBUTE_GCC_DIAG(2, 3);
> +  void log_va (const char *fmt, va_list *ap)
> +    ATTRIBUTE_GCC_DIAG(2, 0);
> +  void start_log_line ();
> +  void log_partial (const char *fmt, ...)
> +    ATTRIBUTE_GCC_DIAG(2, 3);
> +  void log_va_partial (const char *fmt, va_list *ap)
> +    ATTRIBUTE_GCC_DIAG(2, 0);
> +  void end_log_line ();
> +
> +  void enter_scope (const char *scope_name);
> +  void enter_scope (const char *scope_name, const char *fmt, va_list *ap)
> +    ATTRIBUTE_GCC_DIAG(3, 0);
> +  void exit_scope (const char *scope_name);
> +
> +  pretty_printer *get_printer () const { return m_pp; }
> +  FILE *get_file () const { return m_f_out; }
> +
> +private:
> +  int m_refcount;
> +  FILE *m_f_out;
> +  int m_indent_level;
> +  bool m_log_refcount_changes;
> +  pretty_printer *m_pp;

Some of the members here looks self-explanatory but others less
so.  For instance, I'm not sure what m_refcount is for or
m_log_refcount_changes.  I would welcome at least brief comments
explaining what they're for.


> +};
> +
> +/* The class log_scope is an RAII-style class intended to make
> +   it easy to notify a logger about entering and exiting the body of a
> +   given function.  */

Sorry to be a nag about this but I have been burned recently
(and many times before) by this and it's still very fresh in
my mind.  Is the class meant to be copyable/assignable?  (With
what semantics?)  If not, I suggest declaring its copy ctor
and assignment private/deleted.

> +
> +class log_scope
> +{
> +public:
> +  log_scope (logger *logger, const char *name);
> +  log_scope (logger *logger, const char *name, const char *fmt, ...)
> +    ATTRIBUTE_GCC_DIAG(4, 5);
> +  ~log_scope ();
> +
> + private:
> +  logger *m_logger;
> +  const char *m_name;
> +};
> +
> +/* The constructor for log_scope.
> +
> +   The normal case is that the logger is NULL, in which case this should
> +   be largely a no-op.
> +
> +   If we do have a logger, notify it that we're entering the given scope.
> +   We also need to hold a reference on it, to avoid a use-after-free
> +   when logging the cleanup of the owner of the logger.  */
> +
> +inline
> +log_scope::log_scope (logger *logger, const char *name) :
> + m_logger (logger),
> + m_name (name)
> +{
> +  if (m_logger)
> +    {
> +      m_logger->incref ("log_scope ctor");
> +      m_logger->enter_scope (m_name);
> +    }
> +}
> +
> +inline
> +log_scope::log_scope (logger *logger, const char *name, const char *fmt, ...):
> + m_logger (logger),
> + m_name (name)
> +{
> +  if (m_logger)
> +    {
> +      m_logger->incref ("log_scope ctor");
> +      va_list ap;
> +      va_start (ap, fmt);
> +      m_logger->enter_scope (m_name, fmt, &ap);
> +      va_end (ap);
> +    }
> +}
> +
> +
> +/* The destructor for log_scope; essentially the opposite of
> +   the constructor.  */
> +
> +inline
> +log_scope::~log_scope ()
> +{
> +  if (m_logger)
> +    {
> +      m_logger->exit_scope (m_name);
> +      m_logger->decref ("log_scope dtor");
> +    }
> +}
> +
> +/* A log_user is something that potentially uses a logger (which could be NULL).
> +
> +   The log_user class keeps the reference-count of a logger up-to-date.  

Just the same high-level comment about copying: it's not obvious
to me if this class is or isn't safe to copy or assign.  If it
is despite the absence of copy ctor/assignment I'd suggest to
mention it in a comment.  If it isn't meant to be copied then
declaring its copy ctor and assignment private or delete would
prevent accidentally using it unsafely.

*/
> +
> +class log_user
> +{
> + public:
> +  log_user (logger *logger);
> +  ~log_user ();
> +
> +  logger * get_logger () const { return m_logger; }
> +  void set_logger (logger * logger);
> +
> +  void log (const char *fmt, ...) const
> +    ATTRIBUTE_GCC_DIAG(2, 3);
> +
> +  void start_log_line () const;
> +  void end_log_line () const;
> +
> +  void enter_scope (const char *scope_name);
> +  void exit_scope (const char *scope_name);
> +
> +  pretty_printer *get_logger_pp () const
> +  {
> +    gcc_assert (m_logger);
> +    return m_logger->get_printer ();
> +  }
> +
> +  FILE *get_logger_file () const
> +  {
> +    if (m_logger == NULL)
> +      return NULL;
> +    return m_logger->get_file ();
> +  }
> +
> + private:
> +  logger *m_logger;
> +};
> +
> +/* A shortcut for calling log from a log_user, handling the common
> +   case where the underlying logger is NULL via a no-op.  */
> +
> +inline void
> +log_user::log (const char *fmt, ...) const
> +{
> +  if (m_logger)
> +    {
> +      va_list ap;
> +      va_start (ap, fmt);
> +      m_logger->log_va (fmt, &ap);
> +      va_end (ap);
> +    }
> +}
> +
> +/* A shortcut for starting a log line from a log_user,
> +   handling the common case where the underlying logger is NULL via
> +   a no-op.  */
> +
> +inline void
> +log_user::start_log_line () const
> +{
> +  if (m_logger)
> +    m_logger->start_log_line ();
> +}
> +
> +/* A shortcut for ending a log line from a log_user,
> +   handling the common case where the underlying logger is NULL via
> +   a no-op.  */
> +
> +inline void
> +log_user::end_log_line () const
> +{
> +  if (m_logger)
> +    m_logger->end_log_line ();
> +}
> +
> +/* A shortcut for recording entry into a scope from a log_user,
> +   handling the common case where the underlying logger is NULL via
> +   a no-op.  */
> +
> +inline void
> +log_user::enter_scope (const char *scope_name)
> +{
> +  if (m_logger)
> +    m_logger->enter_scope (scope_name);
> +}
> +
> +/* A shortcut for recording exit from a scope from a log_user,
> +   handling the common case where the underlying logger is NULL via
> +   a no-op.  */
> +
> +inline void
> +log_user::exit_scope (const char *scope_name)
> +{
> +  if (m_logger)
> +    m_logger->exit_scope (scope_name);
> +}
> +
> +/* If the given logger is non-NULL, log entry/exit of this scope to
> +   it, identifying it using __PRETTY_FUNCTION__.  */
> +
> +#define LOG_SCOPE(LOGGER)		\
> +  log_scope s (LOGGER, __PRETTY_FUNCTION__)

's' seems like a poor choice for the name of a local variable hidden
behind a macro (due to the risk of collision with other variables of
the same name).  I usually use a token pasting trick to avoid this
risk, along these lines:

   #define CAT(a, b) a ## b
   #define CONCAT(a, b) CAT (a, b)
   #define UNIQUE_NAME(name) CAT (name, __LINE__)

   #define LOG_SCOPE(LOGGER)		\
     log_scope UNIQUE_NAME (local_logger) (LOGGER, __PRETTY_FUNCTION__)

> +
> +/* If the given logger is non-NULL, log entry/exit of this scope to
> +   it, identifying it using __func__.  */
> +
> +#define LOG_FUNC(LOGGER) \
> +  log_scope s (LOGGER, __func__)
> +
> +#define LOG_FUNC_1(LOGGER, FMT, A0)	\
> +  log_scope s (LOGGER, __func__, FMT, A0)
> +
> +#define LOG_FUNC_2(LOGGER, FMT, A0, A1)		\
> +  log_scope s (LOGGER, __func__, FMT, A0, A1)
> +
> +#define LOG_FUNC_3(LOGGER, FMT, A0, A1, A2)	\
> +  log_scope s (LOGGER, __func__, FMT, A0, A1, A2)
> +
> +#define LOG_FUNC_4(LOGGER, FMT, A0, A1, A2, A3) \
> +  log_scope s (LOGGER, __func__, FMT, A0, A1, A2, A3)

Same comment as above about the name.  I assume the "overloads"
re necessary because variadic macros are off-limits?  (Isn't
there some C++ 98 trick to make this work without the macros?)

Martin

> +
> +#endif /* ANALYZER_LOGGING_H */
> 

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 00/49] RFC: Add a static analysis framework to GCC
  2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
                   ` (49 preceding siblings ...)
  2019-11-16 21:47 ` [PATCH 00/49] RFC: Add a static analysis framework to GCC Thomas Schwinge
@ 2019-12-04 19:55 ` Martin Sebor
  2019-12-06 22:31   ` Jeff Law
  50 siblings, 1 reply; 160+ messages in thread
From: Martin Sebor @ 2019-12-04 19:55 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On 11/15/19 6:22 PM, David Malcolm wrote:
> This patch kit introduces a static analysis pass for GCC that can diagnose
> various kinds of problems in C code at compile-time (e.g. double-free,
> use-after-free, etc).

I haven't looked at the analyzer bits in any detail yet so I have
just some very high-level questions.  But first let me say I'm
excited to see this project! :)

It looks like the analyzer detects some of the same problems as
some existing middle-end warnings (e.g., -Wnonnull, -Wuninitialized),
and some that I have been working toward implementing (invalid uses
of freed pointers such as returning them from functions or passing
them to others), and others still that I have been thinking about
as possible future projects (e.g., detecting uses of uninitialized
arrays in string functions).

What are your thoughts about this sort of overlap?  Do you expect
us to enhance both sets of warnings in parallel, or do you see us
moving away from issuing warnings in the middle-end and toward
making the analyzer the main source of these kinds of diagnostics?
Maybe even replace some of the problematic middle-end warnings
with the analyzer?  What (if anything) should we do about warnings
issued for the same problems by both the middle-end and the analyzer?
Or about false negatives?  E.g., a bug detected by the middle-end
but not the analyzer or vice versa.

What do you see as the biggest pros and cons of either approach?
(Middle-end vs analyzer.)  What limitations is the analyzer
approach inherently subject to that the middle-end warnings aren't,
and vice versa?

How do we prioritize between the two approaches (e.g., choose
where to add a new warning)?

Martin

> The analyzer runs as an IPA pass on the gimple SSA representation.
> It associates state machines with data, with transitions at certain
> statements and edges.  It finds "interesting" interprocedural paths
> through the user's code, in which bogus state transitions happen.
> 
> For example, given:
> 
>     free (ptr);
>     free (ptr);
> 
> at the first call, "ptr" transitions to the "freed" state, and
> at the second call the analyzer complains, since "ptr" is already in
> the "freed" state (unless "ptr" is NULL, in which case it stays in
> the NULL state for both calls).
> 
> Specific state machines include:
> - a checker for malloc/free, for detecting double-free, resource leaks,
>    use-after-free, etc (sm-malloc.cc), and
> - a checker for stdio's FILE stream API (sm-file.cc)
> 
> There are also two state-machine-based checkers that are just
> proof-of-concept at this stage:
> - a checker for tracking exposure of sensitive data (e.g.
>    writing passwords to log files aka CWE-532), and
> - a checker for tracking "taint", where data potentially under an
>    attacker's control is used without sanitization for things like
>    array indices (CWE-129).
> 
> There's a separation between the state machines and the analysis
> engine, so it ought to be relatively easy to add new warnings.
> 
> For any given diagnostic emitted by a state machine, the analysis engine
> generates the simplest feasible interprocedural path of control flow for
> triggering the diagnostic.
> 
> 
> Diagnostic paths
> ================
> 
> The patch kit adds support to GCC's diagnostic subsystem for optionally
> associating a "diagnostic_path" with a diagnostic.  A diagnostic path
> describes a sequence of events predicted by the compiler that leads to the
> problem occurring, with their locations in the user's source, and text
> descriptions.
> 
> For example, the following warning has a 6-event interprocedural path:
> 
> malloc-ipa-8-unchecked.c: In function 'make_boxed_int':
> malloc-ipa-8-unchecked.c:21:13: warning: dereference of possibly-NULL 'result' [CWE-690] [-Wanalyzer-possible-null-dereference]
>     21 |   result->i = i;
>        |   ~~~~~~~~~~^~~
>    'make_boxed_int': events 1-2
>      |
>      |   18 | make_boxed_int (int i)
>      |      | ^~~~~~~~~~~~~~
>      |      | |
>      |      | (1) entry to 'make_boxed_int'
>      |   19 | {
>      |   20 |   boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
>      |      |                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>      |      |                                    |
>      |      |                                    (2) calling 'wrapped_malloc' from 'make_boxed_int'
>      |
>      +--> 'wrapped_malloc': events 3-4
>             |
>             |    7 | void *wrapped_malloc (size_t size)
>             |      |       ^~~~~~~~~~~~~~
>             |      |       |
>             |      |       (3) entry to 'wrapped_malloc'
>             |    8 | {
>             |    9 |   return malloc (size);
>             |      |          ~~~~~~~~~~~~~
>             |      |          |
>             |      |          (4) this call could return NULL
>             |
>      <------+
>      |
>    'make_boxed_int': events 5-6
>      |
>      |   20 |   boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
>      |      |                                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>      |      |                                    |
>      |      |                                    (5) possible return of NULL to 'make_boxed_int' from 'wrapped_malloc'
>      |   21 |   result->i = i;
>      |      |   ~~~~~~~~~~~~~
>      |      |             |
>      |      |             (6) 'result' could be NULL: unchecked value from (4)
>      |
> 
> The diagnostic-printing code has consolidated the path into 3 runs of events
> (where the events are near each other and within the same function), using
> ASCII art to show the interprocedural call and return.
> 
> A colorized version of the above can be seen at:
>    https://dmalcolm.fedorapeople.org/gcc/2019-11-13/test.html
> 
> Other examples can be seen at:
>    https://dmalcolm.fedorapeople.org/gcc/2019-11-13/malloc-1.c.html
>    https://dmalcolm.fedorapeople.org/gcc/2019-11-13/setjmp-4.c.html
> 
> An example of detecting a historical double-free CVE can be seen at:
>    https://dmalcolm.fedorapeople.org/gcc/2019-11-13/CVE-2005-1689.html
> (there are also some false positives in this report)
> 
> 
> Diagnostic metadata
> ===================
> 
> The patch kit also adds the ability to associate additional metadata with
> a diagnostic. The only such metadata added by the patch kit are CWE
> classifications (for the new warnings), such as the CWE-690 in the warning
> above, or CWE-401 in this example:
> 
> malloc-1.c: In function 'test_42a':
> malloc-1.c:466:1: warning: leak of 'p' [CWE-401] [-Wanalyzer-malloc-leak]
>    466 | }
>        | ^
>    'test_42a': events 1-2
>         |
>         |  463 |   void *p = malloc (1024);
>         |      |             ^~~~~~~~~~~~~
>         |      |             |
>         |      |             (1) allocated here
>         |......
>         |  466 | }
>         |      | ~
>         |      | |
>         |      | (2) 'p' leaks here; was allocated at (1)
>         |
> 
> If the terminal supports it, the above "CWE-401" is a clickable hyperlink
> to:
>    https://cwe.mitre.org/data/definitions/401.html
> 
> 
> Scope
> =====
> 
> The checker is implemented as a GCC plugin.
> 
> The patch kit adds support for "in-tree" plugins i.e. GCC plugins that
> would live in the GCC source tree and be shipped as part of the GCC tarball,
> with a new:
>    --enable-plugins=[LIST OF PLUGIN NAMES]
> configure option, analogous to --enable-languages (the Makefile/configure
> machinery for handling in-tree GCC plugins is adapted from how we support
> frontends).
> 
> The default is for no such plugins to be enabled, so the default would
> be that the checker isn't built - you'd have to opt-in to building it,
> with --enable-plugins=analyzer
> 
> To mitigate feature creep, I've been focusing on implementing double-free
> detection, albeit with an eye to building something that can be developed
> into a more fully-featured static analyzer.  For example, I haven't yet
> attempted to track buffer overflows in this version, but I believe that
> that could be added on top of this foundation.
> 
> Many projects implement some kind of wrapper around malloc and free, so
> there is enough interprocedural support to cope with that, but only very
> primitive support for summarizing larger functions and planning/performing
> an efficient interprocedural analysis on non-trivial functions that
> have state-machine effects.
> 
> In theory the analyzer can work with LTO, and perform cross-TU analysis.
> There's a bare-bones prototype of this in the testsuite, which finds a
> double-free spanning two TUs; see:
> 
>    https://dmalcolm.fedorapeople.org/gcc/2019-11-15/double-free-lto-1.STAR.c.html
> 
> However this is just a proof-of-concept at this stage (see the internal docs
> for more notes on its limitations).
> 
> 
> User interface
> ==============
> 
> --analyzer turns on all the analyzer warnings (it also enables the
> expensive traversal that they rely on); individual warnings are all
> prefixed "-Wanalyzer-" and can be turned off in the usual way
> e.g. -Wno-analyzer-use-after-free.
> 
> 
> Rationale
> =========
> 
> There's benefit in integrating a checker directly into the compiler, so
> that the programmer can see the diagnostics as he or she works on the code,
> rather than at some later point.  I think that if the analyzer can be
> made sufficiently fast that many people would opt-in to deeper but more
> expensive warnings.  (I'm aiming for 2x compile time as my rough estimate
> of what's reasonable in exchange for being told up-front about various
> kinds of pointer snafu).
> 
> 
> Correctness
> ===========
> 
> The analyzer is neither sound nor complete, but does attempt to explore
> "interesting" paths through the code and generate meaningful diagnostics.
> There are no known ICEs, but there are bugs... (see the xfails and TODOs
> in the testsuite, and the "Limitations" section of the internal docs).
> 
> 
> Performance
> ===========
> 
> Using --analyzer roughly doubles the compile time on various testcases
> I've tried (krb5, zlib), but also sometimes takes a lot longer
> (again, see the "Limitations" section of the internal docs; there are
> bugs...).
> 
> 
> Overview of patch kit
> =====================
> 
> Patch 01 contains user-facing documentation for the analyzer
> 
> Patch 02 documents the implementation internals (to avoid having to repeat
> it here)
> 
> Patches 03-15 are preliminary work.
>    Patch 11 adds metadata support to GCC's diagnostic subsystem, so that
>    the analyzer can associate CWE identifiers with diagnostics.
>    Patch 12 adds diagnostic_path support to to GCC's diagnostic subsystem
> 
> Patches 16-17 add support for in-tree plugins
> 
> Patches 18-24 add the basics of the analyzer plugin itself
> 
> Patches 25-48 adds the analysis "machinery"
>    Patches 33-38 add the state machines (somewhat abstracted from the
>    rest of the analyzer)
> 
> Patch 49 adds the test suite for the analyzer
> 
> The patches can also be seen on the git mirror as branch "dmalcolm/analyzer-v1"
>    https://gcc.gnu.org/git/?p=gcc.git;a=shortlog;h=refs/heads/dmalcolm/analyzer-v1
> 
> This is relative to r276961 (which predates the recent update to how
> params work).
> 
> Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
> 
> The analyzer works on toy examples, and on some moderately-sized real
> world codebases (krb5 and zlib).  I'd hoped to have tested it more deeply
> at this point (and dogfooded it), but given that GCC stage 1 is closing
> shortly I thought I ought to post what I have.
> 
> It's not clear to me whether I should focus on:
> 
> (a) pruning the scope of the checker so that it works well on
> *intra*procedural C examples (and bail on anything more complex), perhaps
> targetting GCC 10 as an optional extra hidden behind
> --enable-plugins=analyzer, or
> 
> (b) work on deeper interprocedural analysis (and fixing efficiency issues
> with this).
> 
> See also: https://gcc.gnu.org/wiki/DavidMalcolm/StaticAnalyzer
> (which currently duplicates much of the above).
> 
> Thoughts?
> David
> 
> David Malcolm (49):
>    analyzer: user-facing documentation
>    analyzer: internal documentation
>    diagnostic_show_locus: move initial newline to callers
>    sbitmap.h: add operator const sbitmap & to auto_sbitmap
>    vec.h: add auto_delete_vec
>    timevar.h: add auto_client_timevar class
>    Replace label_text ctor with "borrow" and "take"
>    Introduce pretty_printer::clone vfunc
>    gimple const-correctness fixes
>    Add -fdiagnostics-nn-line-numbers
>    Add diagnostic_metadata and CWE support
>    Add diagnostic paths
>    function-tests.c: expose selftest::make_fndecl for use elsewhere
>    hash-map-tests.c: add a selftest involving int_hash
>    Add ordered_hash_map
>    Add support for in-tree plugins
>    Support for adding selftests via a plugin
>    Add in-tree plugin: "analyzer"
>    analyzer: new files: analyzer-selftests.{cc|h}
>    analyzer: new builtins
>    analyzer: command-line options
>    analyzer: params.def: new parameters
>    analyzer: logging support
>    analyzer: new file: analyzer-pass.cc
>    analyzer: new files: graphviz.{cc|h}
>    analyzer: new files: digraph.{cc|h} and shortest-paths.h
>    analyzer: new files: supergraph.{cc|h}
>    analyzer: new files: analyzer.{cc|h}
>    analyzer: new files: tristate.{cc|h}
>    analyzer: new files: constraint-manager.{cc|h}
>    analyzer: new files: region-model.{cc|h}
>    analyzer: new files: pending-diagnostic.{cc|h}
>    analyzer: new files: sm.{cc|h}
>    analyzer: new file: sm-malloc.cc
>    analyzer: new file: sm-file.cc
>    analyzer: new file: sm-pattern-test.cc
>    analyzer: new file: sm-sensitive.cc
>    analyzer: new file: sm-taint.cc
>    analyzer: new files: analysis-plan.{cc|h}
>    analyzer: new files: call-string.{cc|h}
>    analyzer: new files: program-point.{cc|h}
>    analyzer: new files: program-state.{cc|h}
>    analyzer: new file: exploded-graph.h
>    analyzer: new files: state-purge.{cc|h}
>    analyzer: new files: engine.{cc|h}
>    analyzer: new files: checker-path.{cc|h}
>    analyzer: new files: diagnostic-manager.{cc|h}
>    gdbinit.in: add break-on-saved-diagnostic
>    analyzer: test suite
> 
>   configure.ac                                       |    6 +
>   gcc/Makefile.in                                    |  102 +-
>   gcc/analyzer/Make-plugin.in                        |  181 +
>   gcc/analyzer/analysis-plan.cc                      |  115 +
>   gcc/analyzer/analysis-plan.h                       |   56 +
>   gcc/analyzer/analyzer-logging.cc                   |  220 +
>   gcc/analyzer/analyzer-logging.h                    |  256 +
>   gcc/analyzer/analyzer-pass.cc                      |  103 +
>   gcc/analyzer/analyzer-plugin.cc                    |   63 +
>   gcc/analyzer/analyzer-selftests.cc                 |   61 +
>   gcc/analyzer/analyzer-selftests.h                  |   46 +
>   gcc/analyzer/analyzer.cc                           |  125 +
>   gcc/analyzer/analyzer.h                            |  126 +
>   gcc/analyzer/call-string.cc                        |  201 +
>   gcc/analyzer/call-string.h                         |   74 +
>   gcc/analyzer/checker-path.cc                       |  899 +++
>   gcc/analyzer/checker-path.h                        |  563 ++
>   gcc/analyzer/config-plugin.in                      |   34 +
>   gcc/analyzer/constraint-manager.cc                 | 2263 ++++++
>   gcc/analyzer/constraint-manager.h                  |  248 +
>   gcc/analyzer/diagnostic-manager.cc                 | 1117 +++
>   gcc/analyzer/diagnostic-manager.h                  |  116 +
>   gcc/analyzer/digraph.cc                            |  189 +
>   gcc/analyzer/digraph.h                             |  248 +
>   gcc/analyzer/engine.cc                             | 3416 +++++++++
>   gcc/analyzer/engine.h                              |   26 +
>   gcc/analyzer/exploded-graph.h                      |  754 ++
>   gcc/analyzer/graphviz.cc                           |   81 +
>   gcc/analyzer/graphviz.h                            |   50 +
>   gcc/analyzer/pending-diagnostic.cc                 |   61 +
>   gcc/analyzer/pending-diagnostic.h                  |  265 +
>   gcc/analyzer/plugin.opt                            |  161 +
>   gcc/analyzer/program-point.cc                      |  490 ++
>   gcc/analyzer/program-point.h                       |  316 +
>   gcc/analyzer/program-state.cc                      | 1284 ++++
>   gcc/analyzer/program-state.h                       |  360 +
>   gcc/analyzer/region-model.cc                       | 7686 ++++++++++++++++++++
>   gcc/analyzer/region-model.h                        | 2076 ++++++
>   gcc/analyzer/shortest-paths.h                      |  147 +
>   gcc/analyzer/sm-file.cc                            |  338 +
>   gcc/analyzer/sm-malloc.cc                          |  799 ++
>   gcc/analyzer/sm-pattern-test.cc                    |  165 +
>   gcc/analyzer/sm-sensitive.cc                       |  209 +
>   gcc/analyzer/sm-taint.cc                           |  338 +
>   gcc/analyzer/sm.cc                                 |  135 +
>   gcc/analyzer/sm.h                                  |  160 +
>   gcc/analyzer/state-purge.cc                        |  516 ++
>   gcc/analyzer/state-purge.h                         |  170 +
>   gcc/analyzer/supergraph.cc                         |  936 +++
>   gcc/analyzer/supergraph.h                          |  560 ++
>   gcc/analyzer/tristate.cc                           |  222 +
>   gcc/analyzer/tristate.h                            |   82 +
>   gcc/builtins.def                                   |   33 +
>   gcc/c-family/c-format.c                            |   15 +-
>   gcc/c-family/c-format.h                            |    1 +
>   gcc/c-family/c-opts.c                              |    1 +
>   gcc/c-family/c-pretty-print.c                      |    7 +
>   gcc/c-family/c-pretty-print.h                      |    1 +
>   gcc/c/c-objc-common.c                              |    4 +-
>   gcc/common.opt                                     |   31 +
>   gcc/configure.ac                                   |  172 +-
>   gcc/coretypes.h                                    |    1 +
>   gcc/cp/cxx-pretty-print.c                          |    8 +
>   gcc/cp/cxx-pretty-print.h                          |    2 +
>   gcc/cp/error.c                                     |    9 +-
>   gcc/diagnostic-color.c                             |    3 +-
>   gcc/diagnostic-core.h                              |   10 +
>   gcc/diagnostic-event-id.h                          |   61 +
>   gcc/diagnostic-format-json.cc                      |   33 +-
>   gcc/diagnostic-metadata.h                          |   42 +
>   gcc/diagnostic-path.h                              |  149 +
>   gcc/diagnostic-show-locus.c                        |  219 +-
>   gcc/diagnostic.c                                   |  283 +-
>   gcc/diagnostic.def                                 |    5 +
>   gcc/diagnostic.h                                   |   43 +-
>   gcc/doc/analyzer.texi                              |  470 ++
>   gcc/doc/gccint.texi                                |    2 +
>   gcc/doc/install.texi                               |    9 +
>   gcc/doc/invoke.texi                                |  600 +-
>   gcc/dwarf2out.c                                    |    1 +
>   gcc/fortran/error.c                                |    1 +
>   gcc/function-tests.c                               |    4 +-
>   gcc/gcc-rich-location.c                            |    2 +-
>   gcc/gcc-rich-location.h                            |    6 +-
>   gcc/gcc.c                                          |   13 +
>   gcc/gdbinit.in                                     |   10 +
>   gcc/gimple-predict.h                               |    4 +-
>   gcc/gimple-pretty-print.c                          |  159 +-
>   gcc/gimple-pretty-print.h                          |    3 +-
>   gcc/gimple.h                                       |  156 +-
>   gcc/hash-map-tests.c                               |   41 +
>   gcc/lto-wrapper.c                                  |    3 +
>   gcc/opts.c                                         |   16 +
>   gcc/ordered-hash-map-tests.cc                      |  247 +
>   gcc/ordered-hash-map.h                             |  184 +
>   gcc/params.def                                     |   25 +
>   gcc/plugin.c                                       |    2 +
>   gcc/plugin.def                                     |    3 +
>   gcc/pretty-print.c                                 |   66 +
>   gcc/pretty-print.h                                 |    4 +
>   gcc/sbitmap.h                                      |    1 +
>   gcc/selftest-run-tests.c                           |    6 +
>   gcc/selftest.h                                     |    9 +
>   .../gcc.dg/analyzer/CVE-2005-1689-minimal.c        |   30 +
>   gcc/testsuite/gcc.dg/analyzer/abort.c              |   71 +
>   gcc/testsuite/gcc.dg/analyzer/alloca-leak.c        |    8 +
>   .../gcc.dg/analyzer/analyzer-verbosity-0.c         |  133 +
>   .../gcc.dg/analyzer/analyzer-verbosity-1.c         |  160 +
>   .../gcc.dg/analyzer/analyzer-verbosity-2.c         |  191 +
>   gcc/testsuite/gcc.dg/analyzer/analyzer.exp         |   49 +
>   gcc/testsuite/gcc.dg/analyzer/attribute-nonnull.c  |   57 +
>   gcc/testsuite/gcc.dg/analyzer/call-summaries-1.c   |   14 +
>   gcc/testsuite/gcc.dg/analyzer/conditionals-2.c     |   44 +
>   gcc/testsuite/gcc.dg/analyzer/conditionals-3.c     |   45 +
>   .../gcc.dg/analyzer/conditionals-notrans.c         |  158 +
>   gcc/testsuite/gcc.dg/analyzer/conditionals-trans.c |  143 +
>   gcc/testsuite/gcc.dg/analyzer/data-model-1.c       | 1078 +++
>   gcc/testsuite/gcc.dg/analyzer/data-model-10.c      |   17 +
>   gcc/testsuite/gcc.dg/analyzer/data-model-11.c      |    6 +
>   gcc/testsuite/gcc.dg/analyzer/data-model-12.c      |   13 +
>   gcc/testsuite/gcc.dg/analyzer/data-model-13.c      |   21 +
>   gcc/testsuite/gcc.dg/analyzer/data-model-14.c      |   24 +
>   gcc/testsuite/gcc.dg/analyzer/data-model-15.c      |   34 +
>   gcc/testsuite/gcc.dg/analyzer/data-model-16.c      |   50 +
>   gcc/testsuite/gcc.dg/analyzer/data-model-17.c      |   20 +
>   gcc/testsuite/gcc.dg/analyzer/data-model-18.c      |   20 +
>   gcc/testsuite/gcc.dg/analyzer/data-model-19.c      |   31 +
>   gcc/testsuite/gcc.dg/analyzer/data-model-2.c       |   13 +
>   gcc/testsuite/gcc.dg/analyzer/data-model-3.c       |   15 +
>   gcc/testsuite/gcc.dg/analyzer/data-model-4.c       |   16 +
>   gcc/testsuite/gcc.dg/analyzer/data-model-5.c       |  100 +
>   gcc/testsuite/gcc.dg/analyzer/data-model-5b.c      |   91 +
>   gcc/testsuite/gcc.dg/analyzer/data-model-5c.c      |   84 +
>   gcc/testsuite/gcc.dg/analyzer/data-model-5d.c      |   63 +
>   gcc/testsuite/gcc.dg/analyzer/data-model-6.c       |   13 +
>   gcc/testsuite/gcc.dg/analyzer/data-model-7.c       |   19 +
>   gcc/testsuite/gcc.dg/analyzer/data-model-8.c       |   24 +
>   gcc/testsuite/gcc.dg/analyzer/data-model-9.c       |   32 +
>   gcc/testsuite/gcc.dg/analyzer/data-model-path-1.c  |   13 +
>   .../gcc.dg/analyzer/double-free-lto-1-a.c          |   16 +
>   .../gcc.dg/analyzer/double-free-lto-1-b.c          |    8 +
>   gcc/testsuite/gcc.dg/analyzer/double-free-lto-1.h  |    1 +
>   gcc/testsuite/gcc.dg/analyzer/equivalence.c        |   29 +
>   gcc/testsuite/gcc.dg/analyzer/explode-1.c          |   60 +
>   gcc/testsuite/gcc.dg/analyzer/explode-2.c          |   50 +
>   gcc/testsuite/gcc.dg/analyzer/factorial.c          |    7 +
>   gcc/testsuite/gcc.dg/analyzer/fibonacci.c          |    9 +
>   gcc/testsuite/gcc.dg/analyzer/fields.c             |   41 +
>   gcc/testsuite/gcc.dg/analyzer/file-1.c             |   37 +
>   gcc/testsuite/gcc.dg/analyzer/file-2.c             |   18 +
>   gcc/testsuite/gcc.dg/analyzer/function-ptr-1.c     |    8 +
>   gcc/testsuite/gcc.dg/analyzer/function-ptr-2.c     |   43 +
>   gcc/testsuite/gcc.dg/analyzer/function-ptr-3.c     |   17 +
>   gcc/testsuite/gcc.dg/analyzer/gzio-2.c             |   11 +
>   gcc/testsuite/gcc.dg/analyzer/gzio-3.c             |   31 +
>   gcc/testsuite/gcc.dg/analyzer/gzio-3a.c            |   27 +
>   gcc/testsuite/gcc.dg/analyzer/gzio.c               |   17 +
>   gcc/testsuite/gcc.dg/analyzer/infinite-recursion.c |   55 +
>   gcc/testsuite/gcc.dg/analyzer/loop-2.c             |   36 +
>   gcc/testsuite/gcc.dg/analyzer/loop-2a.c            |   39 +
>   gcc/testsuite/gcc.dg/analyzer/loop-3.c             |   17 +
>   gcc/testsuite/gcc.dg/analyzer/loop-4.c             |   41 +
>   gcc/testsuite/gcc.dg/analyzer/loop.c               |   33 +
>   gcc/testsuite/gcc.dg/analyzer/malloc-1.c           |  565 ++
>   gcc/testsuite/gcc.dg/analyzer/malloc-2.c           |   23 +
>   gcc/testsuite/gcc.dg/analyzer/malloc-3.c           |    8 +
>   gcc/testsuite/gcc.dg/analyzer/malloc-dce.c         |   12 +
>   gcc/testsuite/gcc.dg/analyzer/malloc-dedupe-1.c    |   46 +
>   gcc/testsuite/gcc.dg/analyzer/malloc-ipa-1.c       |   24 +
>   gcc/testsuite/gcc.dg/analyzer/malloc-ipa-10.c      |   32 +
>   gcc/testsuite/gcc.dg/analyzer/malloc-ipa-11.c      |   95 +
>   gcc/testsuite/gcc.dg/analyzer/malloc-ipa-12.c      |    7 +
>   gcc/testsuite/gcc.dg/analyzer/malloc-ipa-13.c      |   30 +
>   gcc/testsuite/gcc.dg/analyzer/malloc-ipa-2.c       |   34 +
>   gcc/testsuite/gcc.dg/analyzer/malloc-ipa-3.c       |   23 +
>   gcc/testsuite/gcc.dg/analyzer/malloc-ipa-4.c       |   13 +
>   gcc/testsuite/gcc.dg/analyzer/malloc-ipa-5.c       |   13 +
>   gcc/testsuite/gcc.dg/analyzer/malloc-ipa-6.c       |   22 +
>   gcc/testsuite/gcc.dg/analyzer/malloc-ipa-7.c       |   29 +
>   .../gcc.dg/analyzer/malloc-ipa-8-double-free.c     |  172 +
>   .../gcc.dg/analyzer/malloc-ipa-8-unchecked.c       |   66 +
>   gcc/testsuite/gcc.dg/analyzer/malloc-ipa-9.c       |   18 +
>   .../gcc.dg/analyzer/malloc-macro-inline-events.c   |   45 +
>   .../gcc.dg/analyzer/malloc-macro-separate-events.c |   15 +
>   gcc/testsuite/gcc.dg/analyzer/malloc-macro.h       |    2 +
>   .../gcc.dg/analyzer/malloc-many-paths-1.c          |   14 +
>   .../gcc.dg/analyzer/malloc-many-paths-2.c          |   30 +
>   .../gcc.dg/analyzer/malloc-many-paths-3.c          |   36 +
>   gcc/testsuite/gcc.dg/analyzer/malloc-paths-1.c     |   15 +
>   gcc/testsuite/gcc.dg/analyzer/malloc-paths-10.c    |   19 +
>   gcc/testsuite/gcc.dg/analyzer/malloc-paths-2.c     |   13 +
>   gcc/testsuite/gcc.dg/analyzer/malloc-paths-3.c     |   14 +
>   gcc/testsuite/gcc.dg/analyzer/malloc-paths-4.c     |   20 +
>   gcc/testsuite/gcc.dg/analyzer/malloc-paths-5.c     |   43 +
>   gcc/testsuite/gcc.dg/analyzer/malloc-paths-6.c     |   11 +
>   gcc/testsuite/gcc.dg/analyzer/malloc-paths-7.c     |   21 +
>   gcc/testsuite/gcc.dg/analyzer/malloc-paths-8.c     |   54 +
>   gcc/testsuite/gcc.dg/analyzer/malloc-paths-9.c     |  298 +
>   gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-1a.c |  180 +
>   gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-1b.c |  175 +
>   gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-2.c  |  178 +
>   gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-3.c  |   65 +
>   gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-4.c  |   40 +
>   gcc/testsuite/gcc.dg/analyzer/operations.c         |   42 +
>   gcc/testsuite/gcc.dg/analyzer/params-2.c           |   16 +
>   gcc/testsuite/gcc.dg/analyzer/params.c             |   32 +
>   gcc/testsuite/gcc.dg/analyzer/paths-1.c            |   16 +
>   gcc/testsuite/gcc.dg/analyzer/paths-1a.c           |   16 +
>   gcc/testsuite/gcc.dg/analyzer/paths-2.c            |   25 +
>   gcc/testsuite/gcc.dg/analyzer/paths-3.c            |   48 +
>   gcc/testsuite/gcc.dg/analyzer/paths-4.c            |   49 +
>   gcc/testsuite/gcc.dg/analyzer/paths-5.c            |   10 +
>   gcc/testsuite/gcc.dg/analyzer/paths-6.c            |  118 +
>   gcc/testsuite/gcc.dg/analyzer/paths-7.c            |   58 +
>   gcc/testsuite/gcc.dg/analyzer/pattern-test-1.c     |   28 +
>   gcc/testsuite/gcc.dg/analyzer/pattern-test-2.c     |   29 +
>   gcc/testsuite/gcc.dg/analyzer/pointer-merging.c    |   16 +
>   gcc/testsuite/gcc.dg/analyzer/pr61861.c            |    2 +
>   gcc/testsuite/gcc.dg/analyzer/pragma-1.c           |   26 +
>   gcc/testsuite/gcc.dg/analyzer/scope-1.c            |   23 +
>   gcc/testsuite/gcc.dg/analyzer/sensitive-1.c        |   33 +
>   gcc/testsuite/gcc.dg/analyzer/setjmp-1.c           |    1 +
>   gcc/testsuite/gcc.dg/analyzer/setjmp-2.c           |   97 +
>   gcc/testsuite/gcc.dg/analyzer/setjmp-3.c           |  106 +
>   gcc/testsuite/gcc.dg/analyzer/setjmp-4.c           |  107 +
>   gcc/testsuite/gcc.dg/analyzer/setjmp-5.c           |   65 +
>   gcc/testsuite/gcc.dg/analyzer/setjmp-6.c           |   31 +
>   gcc/testsuite/gcc.dg/analyzer/setjmp-7.c           |   36 +
>   gcc/testsuite/gcc.dg/analyzer/setjmp-8.c           |  107 +
>   gcc/testsuite/gcc.dg/analyzer/setjmp-9.c           |  109 +
>   gcc/testsuite/gcc.dg/analyzer/switch.c             |   28 +
>   gcc/testsuite/gcc.dg/analyzer/taint-1.c            |  128 +
>   gcc/testsuite/gcc.dg/analyzer/zlib-1.c             |   67 +
>   gcc/testsuite/gcc.dg/analyzer/zlib-2.c             |   51 +
>   gcc/testsuite/gcc.dg/analyzer/zlib-3.c             |  214 +
>   gcc/testsuite/gcc.dg/analyzer/zlib-4.c             |   20 +
>   gcc/testsuite/gcc.dg/analyzer/zlib-5.c             |   49 +
>   gcc/testsuite/gcc.dg/analyzer/zlib-6.c             |   47 +
>   gcc/testsuite/gcc.dg/format/gcc_diag-10.c          |    6 +-
>   .../gcc.dg/plugin/diagnostic-path-format-default.c |  142 +
>   .../diagnostic-path-format-inline-events-1.c       |  142 +
>   .../diagnostic-path-format-inline-events-2.c       |  154 +
>   .../diagnostic-path-format-inline-events-3.c       |  153 +
>   .../gcc.dg/plugin/diagnostic-path-format-none.c    |   43 +
>   .../diagnostic-path-format-separate-events.c       |   44 +
>   .../gcc.dg/plugin/diagnostic-test-paths-1.c        |   38 +
>   .../gcc.dg/plugin/diagnostic-test-paths-2.c        |   56 +
>   .../gcc.dg/plugin/diagnostic-test-paths-3.c        |   38 +
>   .../gcc.dg/plugin/diagnostic_plugin_test_paths.c   |  379 +
>   .../plugin/diagnostic_plugin_test_show_locus.c     |    1 +
>   gcc/testsuite/gcc.dg/plugin/plugin.exp             |   10 +
>   gcc/testsuite/lib/target-supports.exp              |    8 +
>   gcc/timevar.h                                      |   33 +
>   gcc/toplev.c                                       |    8 +
>   gcc/tree-diagnostic-path.cc                        |  809 ++
>   gcc/tree-diagnostic.c                              |   12 +-
>   gcc/tree-diagnostic.h                              |    8 +
>   gcc/tree-eh.c                                      |    6 +-
>   gcc/tree-eh.h                                      |    4 +-
>   gcc/tree-ssa-alias.h                               |    2 +-
>   gcc/tree-ssa-structalias.c                         |    2 +-
>   gcc/vec.c                                          |   27 +
>   gcc/vec.h                                          |   35 +
>   libcpp/include/line-map.h                          |   38 +-
>   libcpp/line-map.c                                  |    3 +-
>   265 files changed, 42181 insertions(+), 336 deletions(-)
>   create mode 100644 gcc/analyzer/Make-plugin.in
>   create mode 100644 gcc/analyzer/analysis-plan.cc
>   create mode 100644 gcc/analyzer/analysis-plan.h
>   create mode 100644 gcc/analyzer/analyzer-logging.cc
>   create mode 100644 gcc/analyzer/analyzer-logging.h
>   create mode 100644 gcc/analyzer/analyzer-pass.cc
>   create mode 100644 gcc/analyzer/analyzer-plugin.cc
>   create mode 100644 gcc/analyzer/analyzer-selftests.cc
>   create mode 100644 gcc/analyzer/analyzer-selftests.h
>   create mode 100644 gcc/analyzer/analyzer.cc
>   create mode 100644 gcc/analyzer/analyzer.h
>   create mode 100644 gcc/analyzer/call-string.cc
>   create mode 100644 gcc/analyzer/call-string.h
>   create mode 100644 gcc/analyzer/checker-path.cc
>   create mode 100644 gcc/analyzer/checker-path.h
>   create mode 100644 gcc/analyzer/config-plugin.in
>   create mode 100644 gcc/analyzer/constraint-manager.cc
>   create mode 100644 gcc/analyzer/constraint-manager.h
>   create mode 100644 gcc/analyzer/diagnostic-manager.cc
>   create mode 100644 gcc/analyzer/diagnostic-manager.h
>   create mode 100644 gcc/analyzer/digraph.cc
>   create mode 100644 gcc/analyzer/digraph.h
>   create mode 100644 gcc/analyzer/engine.cc
>   create mode 100644 gcc/analyzer/engine.h
>   create mode 100644 gcc/analyzer/exploded-graph.h
>   create mode 100644 gcc/analyzer/graphviz.cc
>   create mode 100644 gcc/analyzer/graphviz.h
>   create mode 100644 gcc/analyzer/pending-diagnostic.cc
>   create mode 100644 gcc/analyzer/pending-diagnostic.h
>   create mode 100644 gcc/analyzer/plugin.opt
>   create mode 100644 gcc/analyzer/program-point.cc
>   create mode 100644 gcc/analyzer/program-point.h
>   create mode 100644 gcc/analyzer/program-state.cc
>   create mode 100644 gcc/analyzer/program-state.h
>   create mode 100644 gcc/analyzer/region-model.cc
>   create mode 100644 gcc/analyzer/region-model.h
>   create mode 100644 gcc/analyzer/shortest-paths.h
>   create mode 100644 gcc/analyzer/sm-file.cc
>   create mode 100644 gcc/analyzer/sm-malloc.cc
>   create mode 100644 gcc/analyzer/sm-pattern-test.cc
>   create mode 100644 gcc/analyzer/sm-sensitive.cc
>   create mode 100644 gcc/analyzer/sm-taint.cc
>   create mode 100644 gcc/analyzer/sm.cc
>   create mode 100644 gcc/analyzer/sm.h
>   create mode 100644 gcc/analyzer/state-purge.cc
>   create mode 100644 gcc/analyzer/state-purge.h
>   create mode 100644 gcc/analyzer/supergraph.cc
>   create mode 100644 gcc/analyzer/supergraph.h
>   create mode 100644 gcc/analyzer/tristate.cc
>   create mode 100644 gcc/analyzer/tristate.h
>   create mode 100644 gcc/diagnostic-event-id.h
>   create mode 100644 gcc/diagnostic-metadata.h
>   create mode 100644 gcc/diagnostic-path.h
>   create mode 100644 gcc/doc/analyzer.texi
>   create mode 100644 gcc/ordered-hash-map-tests.cc
>   create mode 100644 gcc/ordered-hash-map.h
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/CVE-2005-1689-minimal.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/abort.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/alloca-leak.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-0.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-1.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-2.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/analyzer.exp
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/attribute-nonnull.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/call-summaries-1.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/conditionals-2.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/conditionals-3.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/conditionals-notrans.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/conditionals-trans.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-1.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-10.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-11.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-12.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-13.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-14.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-15.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-16.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-17.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-18.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-19.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-2.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-3.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-4.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-5.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-5b.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-5c.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-5d.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-6.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-7.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-8.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-9.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-path-1.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/double-free-lto-1-a.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/double-free-lto-1-b.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/double-free-lto-1.h
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/equivalence.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/explode-1.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/explode-2.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/factorial.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/fibonacci.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/fields.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/file-1.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/file-2.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/function-ptr-1.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/function-ptr-2.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/function-ptr-3.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/gzio-2.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/gzio-3.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/gzio-3a.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/gzio.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/infinite-recursion.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/loop-2.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/loop-2a.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/loop-3.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/loop-4.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/loop.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-1.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-2.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-3.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-dce.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-dedupe-1.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-1.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-10.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-11.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-12.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-13.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-2.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-3.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-4.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-5.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-6.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-7.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-double-free.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-unchecked.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-9.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-macro-inline-events.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-macro-separate-events.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-macro.h
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-1.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-2.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-3.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-paths-1.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-paths-10.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-paths-2.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-paths-3.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-paths-4.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-paths-5.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-paths-6.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-paths-7.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-paths-8.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-paths-9.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-1a.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-1b.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-2.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-3.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-4.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/operations.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/params-2.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/params.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/paths-1.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/paths-1a.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/paths-2.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/paths-3.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/paths-4.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/paths-5.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/paths-6.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/paths-7.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/pattern-test-1.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/pattern-test-2.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/pointer-merging.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/pr61861.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/pragma-1.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/scope-1.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/sensitive-1.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/setjmp-1.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/setjmp-2.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/setjmp-3.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/setjmp-4.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/setjmp-5.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/setjmp-6.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/setjmp-7.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/setjmp-8.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/setjmp-9.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/switch.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/taint-1.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/zlib-1.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/zlib-2.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/zlib-3.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/zlib-4.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/zlib-5.c
>   create mode 100644 gcc/testsuite/gcc.dg/analyzer/zlib-6.c
>   create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-default.c
>   create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-1.c
>   create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-2.c
>   create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-3.c
>   create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-none.c
>   create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-separate-events.c
>   create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-1.c
>   create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.c
>   create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-3.c
>   create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c
>   create mode 100644 gcc/tree-diagnostic-path.cc
> 

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 06/49] timevar.h: add auto_client_timevar class
  2019-12-04 17:28     ` Tom Tromey
@ 2019-12-04 21:15       ` David Malcolm
  0 siblings, 0 replies; 160+ messages in thread
From: David Malcolm @ 2019-12-04 21:15 UTC (permalink / raw)
  To: Tom Tromey, Martin Sebor; +Cc: gcc-patches

On Wed, 2019-12-04 at 10:27 -0700, Tom Tromey wrote:
> > > > > > "Martin" == Martin Sebor <msebor@gmail.com> writes:
> > > + private:
> > > +  // Private to disallow copies.
> > > +  auto_client_timevar (const auto_client_timevar &);
> 
> Martin> I don't know why it's important to disallow making copies of
> Martin> these classes (they look safe to copy) but usually it goes
> Martin> along with assignment so I would suggest adding a comment
> Martin> with a rationale and (perhaps also) disabling assignment.
> 
> ansidecl.h has a DISABLE_COPY_AND_ASSIGN macro you can use here.
> gdb uses this quite a bit.
> 
> Tom

Thanks Martin and Tom; I'm currently adding DISABLE_COPY_AND_ASSIGN in
various places.

Dave

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 09/49] gimple const-correctness fixes
  2019-11-16  1:17 ` [PATCH 09/49] gimple const-correctness fixes David Malcolm
  2019-12-04 17:06   ` Martin Sebor
@ 2019-12-06 10:52   ` Richard Biener
  2019-12-06 17:47     ` David Malcolm
  2019-12-07 14:28   ` Jeff Law
  2 siblings, 1 reply; 160+ messages in thread
From: Richard Biener @ 2019-12-06 10:52 UTC (permalink / raw)
  To: David Malcolm; +Cc: GCC Patches

On Sat, Nov 16, 2019 at 2:20 AM David Malcolm <dmalcolm@redhat.com> wrote:
>
> This patch converts various "gimple *" to "const gimple *" and similar
> fixes for gimple subclasses, adding is_a_helper for gimple subclasses
> to support the const form of as_a, and adding a few "const" overloads
> of accessors.
>
> This is enough to make pp_gimple_stmt_1's stmt const.

Hum.  Can't the const is-a variants be somehow magically implemented
generally?  If something is a T then it is also a const T, no?  I guess
if something is a const T it isn't a T though?

Richard.

> gcc/ChangeLog:
>         * gimple-predict.h (gimple_predict_predictor): Make "gs" param
>         const.
>         (gimple_predict_outcome): Likewise.
>         * gimple-pretty-print.c (do_niy): Likewise.
>         (dump_unary_rhs): Likewise.
>         (dump_binary_rhs): Likewise.
>         (dump_ternary_rhs): Likewise.
>         (dump_gimple_assign): Likewise.
>         (dump_gimple_return): Likewise.
>         (dump_gimple_call_args): Likewise.
>         (pp_points_to_solution): Make "pt" param const.
>         (dump_gimple_call): Make "gs" param const.
>         (dump_gimple_switch): Likewise.
>         (dump_gimple_cond): Likewise.
>         (dump_gimple_label): Likewise.
>         (dump_gimple_goto): Likewise.
>         (dump_gimple_bind): Likewise.
>         (dump_gimple_try): Likewise.
>         (dump_gimple_catch): Likewise.
>         (dump_gimple_eh_filter): Likewise.
>         (dump_gimple_eh_must_not_throw): Likewise.
>         (dump_gimple_eh_else): Likewise.
>         (dump_gimple_resx): Likewise.
>         (dump_gimple_eh_dispatch): Likewise.
>         (dump_gimple_debug): Likewise.
>         (dump_gimple_omp_for): Likewise.
>         (dump_gimple_omp_continue): Likewise.
>         (dump_gimple_omp_single): Likewise.
>         (dump_gimple_omp_taskgroup): Likewise.
>         (dump_gimple_omp_target): Likewise.
>         (dump_gimple_omp_teams): Likewise.
>         (dump_gimple_omp_sections): Likewise.
>         (dump_gimple_omp_block): Likewise.
>         (dump_gimple_omp_critical): Likewise.
>         (dump_gimple_omp_ordered): Likewise.
>         (dump_gimple_omp_scan): Likewise.
>         (dump_gimple_omp_return): Likewise.
>         (dump_gimple_transaction): Likewise.
>         (dump_gimple_asm): Likewise.
>         (dump_gimple_phi): Make "phi" param const.
>         (dump_gimple_omp_parallel): Make "gs" param const.
>         (dump_gimple_omp_task): Likewise.
>         (dump_gimple_omp_atomic_load): Likewise.
>         (dump_gimple_omp_atomic_store): Likewise.
>         (dump_gimple_mem_ops): Likewise.
>         (pp_gimple_stmt_1): Likewise.  Add "const" to the various as_a <>
>         casts throughout.
>         * gimple-pretty-print.h (gimple_stmt_1): Make gimple * param const.
>         * gimple.h (is_a_helper <const gdebug *>::test): New.
>         (is_a_helper <const ggoto *>::test): New.
>         (is_a_helper <const glabel *>::test): New.
>         (is_a_helper <const geh_else *>::test): New.
>         (is_a_helper <const geh_mnt *>::test): New.
>         (is_a_helper <const gswitch *>::test): New.
>         (is_a_helper <const gtry *>::test): New.
>         (is_a_helper <const greturn *>::test): New.
>         (gimple_call_tail_p): Make param const.
>         (gimple_call_return_slot_opt_p): Likewise.
>         (gimple_call_va_arg_pack_p): Likewise.
>         (gimple_call_use_set): Add const overload.
>         (gimple_call_clobber_set): Likewise.
>         (gimple_has_lhs): Make param const.
>         (gimple_bind_body): Likewise.
>         (gimple_catch_handler): Likewise.
>         (gimple_eh_filter_failure): Likewise.
>         (gimple_eh_must_not_throw_fndecl): Likewise.
>         (gimple_eh_else_n_body): Likewise.
>         (gimple_eh_else_e_body): Likewise.
>         (gimple_try_eval): Likewise.
>         (gimple_try_cleanup): Likewise.
>         (gimple_phi_arg): Add const overload.
>         (gimple_phi_arg_def): Make param const.
>         (gimple_phi_arg_edge): Likewise.
>         (gimple_phi_arg_location): Likewise.
>         (gimple_phi_arg_has_location): Likewise.
>         (gimple_debug_bind_get_var): Likewise.
>         (gimple_debug_bind_get_value): Likewise.
>         (gimple_debug_source_bind_get_var): Likewise.
>         (gimple_debug_source_bind_get_value): Likewise.
>         (gimple_omp_body): Likewise.
>         (gimple_omp_for_collapse): Likewise.
>         (gimple_omp_for_pre_body): Likewise.
>         (gimple_transaction_body): Likewise.
>         * tree-eh.c (lookup_stmt_eh_lp_fn): Make param "t" const.
>         (lookup_stmt_eh_lp): Likewise.
>         * tree-eh.h (lookup_stmt_eh_lp_fn): Make param const.
>         (lookup_stmt_eh_lp): Likewise.
>         * tree-ssa-alias.h (pt_solution_empty_p): Make param const.
>         * tree-ssa-structalias.c (pt_solution_empty_p): Likewise.
> ---
>  gcc/gimple-predict.h       |   4 +-
>  gcc/gimple-pretty-print.c  | 159 +++++++++++++++++++++++----------------------
>  gcc/gimple-pretty-print.h  |   3 +-
>  gcc/gimple.h               | 156 ++++++++++++++++++++++++++++++++++----------
>  gcc/tree-eh.c              |   6 +-
>  gcc/tree-eh.h              |   4 +-
>  gcc/tree-ssa-alias.h       |   2 +-
>  gcc/tree-ssa-structalias.c |   2 +-
>  8 files changed, 213 insertions(+), 123 deletions(-)
>
> diff --git a/gcc/gimple-predict.h b/gcc/gimple-predict.h
> index 761098b..d976317 100644
> --- a/gcc/gimple-predict.h
> +++ b/gcc/gimple-predict.h
> @@ -26,7 +26,7 @@ along with GCC; see the file COPYING3.  If not see
>  /* Return the predictor of GIMPLE_PREDICT statement GS.  */
>
>  static inline enum br_predictor
> -gimple_predict_predictor (gimple *gs)
> +gimple_predict_predictor (const gimple *gs)
>  {
>    GIMPLE_CHECK (gs, GIMPLE_PREDICT);
>    return (enum br_predictor) (gs->subcode & ~GF_PREDICT_TAKEN);
> @@ -47,7 +47,7 @@ gimple_predict_set_predictor (gimple *gs, enum br_predictor predictor)
>  /* Return the outcome of GIMPLE_PREDICT statement GS.  */
>
>  static inline enum prediction
> -gimple_predict_outcome (gimple *gs)
> +gimple_predict_outcome (const gimple *gs)
>  {
>    GIMPLE_CHECK (gs, GIMPLE_PREDICT);
>    return (gs->subcode & GF_PREDICT_TAKEN) ? TAKEN : NOT_TAKEN;
> diff --git a/gcc/gimple-pretty-print.c b/gcc/gimple-pretty-print.c
> index 2d5ece0..f044628 100644
> --- a/gcc/gimple-pretty-print.c
> +++ b/gcc/gimple-pretty-print.c
> @@ -59,7 +59,7 @@ along with GCC; see the file COPYING3.  If not see
>     gimple statement GS.  */
>
>  static void
> -do_niy (pretty_printer *buffer, gimple *gs)
> +do_niy (pretty_printer *buffer, const gimple *gs)
>  {
>    pp_printf (buffer, "<<< Unknown GIMPLE statement: %s >>>\n",
>              gimple_code_name[(int) gimple_code (gs)]);
> @@ -327,7 +327,7 @@ dump_gimple_fmt (pretty_printer *buffer, int spc, dump_flags_t flags,
>     assignment GS.  BUFFER, SPC and FLAGS are as in pp_gimple_stmt_1.  */
>
>  static void
> -dump_unary_rhs (pretty_printer *buffer, gassign *gs, int spc,
> +dump_unary_rhs (pretty_printer *buffer, const gassign *gs, int spc,
>                 dump_flags_t flags)
>  {
>    enum tree_code rhs_code = gimple_assign_rhs_code (gs);
> @@ -423,7 +423,7 @@ dump_unary_rhs (pretty_printer *buffer, gassign *gs, int spc,
>     assignment GS.  BUFFER, SPC and FLAGS are as in pp_gimple_stmt_1.  */
>
>  static void
> -dump_binary_rhs (pretty_printer *buffer, gassign *gs, int spc,
> +dump_binary_rhs (pretty_printer *buffer, const gassign *gs, int spc,
>                  dump_flags_t flags)
>  {
>    const char *p;
> @@ -495,7 +495,7 @@ dump_binary_rhs (pretty_printer *buffer, gassign *gs, int spc,
>     assignment GS.  BUFFER, SPC and FLAGS are as in pp_gimple_stmt_1.  */
>
>  static void
> -dump_ternary_rhs (pretty_printer *buffer, gassign *gs, int spc,
> +dump_ternary_rhs (pretty_printer *buffer, const gassign *gs, int spc,
>                   dump_flags_t flags)
>  {
>    const char *p;
> @@ -625,7 +625,7 @@ dump_ternary_rhs (pretty_printer *buffer, gassign *gs, int spc,
>     pp_gimple_stmt_1.  */
>
>  static void
> -dump_gimple_assign (pretty_printer *buffer, gassign *gs, int spc,
> +dump_gimple_assign (pretty_printer *buffer, const gassign *gs, int spc,
>                     dump_flags_t flags)
>  {
>    if (flags & TDF_RAW)
> @@ -687,7 +687,7 @@ dump_gimple_assign (pretty_printer *buffer, gassign *gs, int spc,
>     pp_gimple_stmt_1.  */
>
>  static void
> -dump_gimple_return (pretty_printer *buffer, greturn *gs, int spc,
> +dump_gimple_return (pretty_printer *buffer, const greturn *gs, int spc,
>                     dump_flags_t flags)
>  {
>    tree t;
> @@ -712,7 +712,8 @@ dump_gimple_return (pretty_printer *buffer, greturn *gs, int spc,
>     dump_gimple_call.  */
>
>  static void
> -dump_gimple_call_args (pretty_printer *buffer, gcall *gs, dump_flags_t flags)
> +dump_gimple_call_args (pretty_printer *buffer, const gcall *gs,
> +                      dump_flags_t flags)
>  {
>    size_t i = 0;
>
> @@ -795,7 +796,7 @@ dump_gimple_call_args (pretty_printer *buffer, gcall *gs, dump_flags_t flags)
>  /* Dump the points-to solution *PT to BUFFER.  */
>
>  static void
> -pp_points_to_solution (pretty_printer *buffer, struct pt_solution *pt)
> +pp_points_to_solution (pretty_printer *buffer, const pt_solution *pt)
>  {
>    if (pt->anything)
>      {
> @@ -868,7 +869,7 @@ pp_points_to_solution (pretty_printer *buffer, struct pt_solution *pt)
>     pp_gimple_stmt_1.  */
>
>  static void
> -dump_gimple_call (pretty_printer *buffer, gcall *gs, int spc,
> +dump_gimple_call (pretty_printer *buffer, const gcall *gs, int spc,
>                   dump_flags_t flags)
>  {
>    tree lhs = gimple_call_lhs (gs);
> @@ -876,7 +877,7 @@ dump_gimple_call (pretty_printer *buffer, gcall *gs, int spc,
>
>    if (flags & TDF_ALIAS)
>      {
> -      struct pt_solution *pt;
> +      const pt_solution *pt;
>        pt = gimple_call_use_set (gs);
>        if (!pt_solution_empty_p (pt))
>         {
> @@ -1006,7 +1007,7 @@ dump_gimple_call (pretty_printer *buffer, gcall *gs, int spc,
>     pp_gimple_stmt_1.  */
>
>  static void
> -dump_gimple_switch (pretty_printer *buffer, gswitch *gs, int spc,
> +dump_gimple_switch (pretty_printer *buffer, const gswitch *gs, int spc,
>                     dump_flags_t flags)
>  {
>    unsigned int i;
> @@ -1064,7 +1065,7 @@ dump_gimple_switch (pretty_printer *buffer, gswitch *gs, int spc,
>     pp_gimple_stmt_1.  */
>
>  static void
> -dump_gimple_cond (pretty_printer *buffer, gcond *gs, int spc,
> +dump_gimple_cond (pretty_printer *buffer, const gcond *gs, int spc,
>                   dump_flags_t flags)
>  {
>    if (flags & TDF_RAW)
> @@ -1131,7 +1132,7 @@ dump_gimple_cond (pretty_printer *buffer, gcond *gs, int spc,
>     TDF_* in dumpfils.h).  */
>
>  static void
> -dump_gimple_label (pretty_printer *buffer, glabel *gs, int spc,
> +dump_gimple_label (pretty_printer *buffer, const glabel *gs, int spc,
>                    dump_flags_t flags)
>  {
>    tree label = gimple_label_label (gs);
> @@ -1155,7 +1156,7 @@ dump_gimple_label (pretty_printer *buffer, glabel *gs, int spc,
>     TDF_* in dumpfile.h).  */
>
>  static void
> -dump_gimple_goto (pretty_printer *buffer, ggoto *gs, int spc,
> +dump_gimple_goto (pretty_printer *buffer, const ggoto *gs, int spc,
>                   dump_flags_t flags)
>  {
>    tree label = gimple_goto_dest (gs);
> @@ -1171,7 +1172,7 @@ dump_gimple_goto (pretty_printer *buffer, ggoto *gs, int spc,
>     TDF_* in dumpfile.h).  */
>
>  static void
> -dump_gimple_bind (pretty_printer *buffer, gbind *gs, int spc,
> +dump_gimple_bind (pretty_printer *buffer, const gbind *gs, int spc,
>                   dump_flags_t flags)
>  {
>    if (flags & TDF_RAW)
> @@ -1205,7 +1206,7 @@ dump_gimple_bind (pretty_printer *buffer, gbind *gs, int spc,
>     dumpfile.h).  */
>
>  static void
> -dump_gimple_try (pretty_printer *buffer, gtry *gs, int spc,
> +dump_gimple_try (pretty_printer *buffer, const gtry *gs, int spc,
>                  dump_flags_t flags)
>  {
>    if (flags & TDF_RAW)
> @@ -1280,7 +1281,7 @@ dump_gimple_try (pretty_printer *buffer, gtry *gs, int spc,
>     dumpfile.h).  */
>
>  static void
> -dump_gimple_catch (pretty_printer *buffer, gcatch *gs, int spc,
> +dump_gimple_catch (pretty_printer *buffer, const gcatch *gs, int spc,
>                    dump_flags_t flags)
>  {
>    if (flags & TDF_RAW)
> @@ -1297,7 +1298,7 @@ dump_gimple_catch (pretty_printer *buffer, gcatch *gs, int spc,
>     dumpfile.h).  */
>
>  static void
> -dump_gimple_eh_filter (pretty_printer *buffer, geh_filter *gs, int spc,
> +dump_gimple_eh_filter (pretty_printer *buffer, const geh_filter *gs, int spc,
>                        dump_flags_t flags)
>  {
>    if (flags & TDF_RAW)
> @@ -1315,7 +1316,7 @@ dump_gimple_eh_filter (pretty_printer *buffer, geh_filter *gs, int spc,
>
>  static void
>  dump_gimple_eh_must_not_throw (pretty_printer *buffer,
> -                              geh_mnt *gs, int spc, dump_flags_t flags)
> +                              const geh_mnt *gs, int spc, dump_flags_t flags)
>  {
>    if (flags & TDF_RAW)
>      dump_gimple_fmt (buffer, spc, flags, "%G <%T>", gs,
> @@ -1331,7 +1332,7 @@ dump_gimple_eh_must_not_throw (pretty_printer *buffer,
>     dumpfile.h).  */
>
>  static void
> -dump_gimple_eh_else (pretty_printer *buffer, geh_else *gs, int spc,
> +dump_gimple_eh_else (pretty_printer *buffer, const geh_else *gs, int spc,
>                      dump_flags_t flags)
>  {
>    if (flags & TDF_RAW)
> @@ -1350,7 +1351,7 @@ dump_gimple_eh_else (pretty_printer *buffer, geh_else *gs, int spc,
>     dumpfile.h).  */
>
>  static void
> -dump_gimple_resx (pretty_printer *buffer, gresx *gs, int spc,
> +dump_gimple_resx (pretty_printer *buffer, const gresx *gs, int spc,
>                   dump_flags_t flags)
>  {
>    if (flags & TDF_RAW)
> @@ -1363,8 +1364,8 @@ dump_gimple_resx (pretty_printer *buffer, gresx *gs, int spc,
>  /* Dump a GIMPLE_EH_DISPATCH tuple on the pretty_printer BUFFER.  */
>
>  static void
> -dump_gimple_eh_dispatch (pretty_printer *buffer, geh_dispatch *gs, int spc,
> -                        dump_flags_t flags)
> +dump_gimple_eh_dispatch (pretty_printer *buffer, const geh_dispatch *gs,
> +                        int spc, dump_flags_t flags)
>  {
>    if (flags & TDF_RAW)
>      dump_gimple_fmt (buffer, spc, flags, "%G <%d>", gs,
> @@ -1379,7 +1380,7 @@ dump_gimple_eh_dispatch (pretty_printer *buffer, geh_dispatch *gs, int spc,
>     in dumpfile.h).  */
>
>  static void
> -dump_gimple_debug (pretty_printer *buffer, gdebug *gs, int spc,
> +dump_gimple_debug (pretty_printer *buffer, const gdebug *gs, int spc,
>                    dump_flags_t flags)
>  {
>    switch (gs->subcode)
> @@ -1433,7 +1434,7 @@ dump_gimple_debug (pretty_printer *buffer, gdebug *gs, int spc,
>
>  /* Dump a GIMPLE_OMP_FOR tuple on the pretty_printer BUFFER.  */
>  static void
> -dump_gimple_omp_for (pretty_printer *buffer, gomp_for *gs, int spc,
> +dump_gimple_omp_for (pretty_printer *buffer, const gomp_for *gs, int spc,
>                      dump_flags_t flags)
>  {
>    size_t i;
> @@ -1566,7 +1567,7 @@ dump_gimple_omp_for (pretty_printer *buffer, gomp_for *gs, int spc,
>  /* Dump a GIMPLE_OMP_CONTINUE tuple on the pretty_printer BUFFER.  */
>
>  static void
> -dump_gimple_omp_continue (pretty_printer *buffer, gomp_continue *gs,
> +dump_gimple_omp_continue (pretty_printer *buffer, const gomp_continue *gs,
>                           int spc, dump_flags_t flags)
>  {
>    if (flags & TDF_RAW)
> @@ -1591,7 +1592,7 @@ dump_gimple_omp_continue (pretty_printer *buffer, gomp_continue *gs,
>  /* Dump a GIMPLE_OMP_SINGLE tuple on the pretty_printer BUFFER.  */
>
>  static void
> -dump_gimple_omp_single (pretty_printer *buffer, gomp_single *gs,
> +dump_gimple_omp_single (pretty_printer *buffer, const gomp_single *gs,
>                         int spc, dump_flags_t flags)
>  {
>    if (flags & TDF_RAW)
> @@ -1620,7 +1621,7 @@ dump_gimple_omp_single (pretty_printer *buffer, gomp_single *gs,
>  /* Dump a GIMPLE_OMP_TASKGROUP tuple on the pretty_printer BUFFER.  */
>
>  static void
> -dump_gimple_omp_taskgroup (pretty_printer *buffer, gimple *gs,
> +dump_gimple_omp_taskgroup (pretty_printer *buffer, const gimple *gs,
>                            int spc, dump_flags_t flags)
>  {
>    if (flags & TDF_RAW)
> @@ -1649,7 +1650,7 @@ dump_gimple_omp_taskgroup (pretty_printer *buffer, gimple *gs,
>  /* Dump a GIMPLE_OMP_TARGET tuple on the pretty_printer BUFFER.  */
>
>  static void
> -dump_gimple_omp_target (pretty_printer *buffer, gomp_target *gs,
> +dump_gimple_omp_target (pretty_printer *buffer, const gomp_target *gs,
>                         int spc, dump_flags_t flags)
>  {
>    const char *kind;
> @@ -1742,7 +1743,7 @@ dump_gimple_omp_target (pretty_printer *buffer, gomp_target *gs,
>  /* Dump a GIMPLE_OMP_TEAMS tuple on the pretty_printer BUFFER.  */
>
>  static void
> -dump_gimple_omp_teams (pretty_printer *buffer, gomp_teams *gs, int spc,
> +dump_gimple_omp_teams (pretty_printer *buffer, const gomp_teams *gs, int spc,
>                        dump_flags_t flags)
>  {
>    if (flags & TDF_RAW)
> @@ -1771,7 +1772,7 @@ dump_gimple_omp_teams (pretty_printer *buffer, gomp_teams *gs, int spc,
>  /* Dump a GIMPLE_OMP_SECTIONS tuple on the pretty_printer BUFFER.  */
>
>  static void
> -dump_gimple_omp_sections (pretty_printer *buffer, gomp_sections *gs,
> +dump_gimple_omp_sections (pretty_printer *buffer, const gomp_sections *gs,
>                           int spc, dump_flags_t flags)
>  {
>    if (flags & TDF_RAW)
> @@ -1808,7 +1809,7 @@ dump_gimple_omp_sections (pretty_printer *buffer, gomp_sections *gs,
>     pretty_printer BUFFER.  */
>
>  static void
> -dump_gimple_omp_block (pretty_printer *buffer, gimple *gs, int spc,
> +dump_gimple_omp_block (pretty_printer *buffer, const gimple *gs, int spc,
>                        dump_flags_t flags)
>  {
>    if (flags & TDF_RAW)
> @@ -1845,7 +1846,7 @@ dump_gimple_omp_block (pretty_printer *buffer, gimple *gs, int spc,
>  /* Dump a GIMPLE_OMP_CRITICAL tuple on the pretty_printer BUFFER.  */
>
>  static void
> -dump_gimple_omp_critical (pretty_printer *buffer, gomp_critical *gs,
> +dump_gimple_omp_critical (pretty_printer *buffer, const gomp_critical *gs,
>                           int spc, dump_flags_t flags)
>  {
>    if (flags & TDF_RAW)
> @@ -1877,7 +1878,7 @@ dump_gimple_omp_critical (pretty_printer *buffer, gomp_critical *gs,
>  /* Dump a GIMPLE_OMP_ORDERED tuple on the pretty_printer BUFFER.  */
>
>  static void
> -dump_gimple_omp_ordered (pretty_printer *buffer, gomp_ordered *gs,
> +dump_gimple_omp_ordered (pretty_printer *buffer, const gomp_ordered *gs,
>                          int spc, dump_flags_t flags)
>  {
>    if (flags & TDF_RAW)
> @@ -1902,7 +1903,7 @@ dump_gimple_omp_ordered (pretty_printer *buffer, gomp_ordered *gs,
>  /* Dump a GIMPLE_OMP_SCAN tuple on the pretty_printer BUFFER.  */
>
>  static void
> -dump_gimple_omp_scan (pretty_printer *buffer, gomp_scan *gs,
> +dump_gimple_omp_scan (pretty_printer *buffer, const gomp_scan *gs,
>                       int spc, dump_flags_t flags)
>  {
>    if (flags & TDF_RAW)
> @@ -1930,7 +1931,7 @@ dump_gimple_omp_scan (pretty_printer *buffer, gomp_scan *gs,
>  /* Dump a GIMPLE_OMP_RETURN tuple on the pretty_printer BUFFER.  */
>
>  static void
> -dump_gimple_omp_return (pretty_printer *buffer, gimple *gs, int spc,
> +dump_gimple_omp_return (pretty_printer *buffer, const gimple *gs, int spc,
>                         dump_flags_t flags)
>  {
>    if (flags & TDF_RAW)
> @@ -1961,7 +1962,7 @@ dump_gimple_omp_return (pretty_printer *buffer, gimple *gs, int spc,
>  /* Dump a GIMPLE_TRANSACTION tuple on the pretty_printer BUFFER.  */
>
>  static void
> -dump_gimple_transaction (pretty_printer *buffer, gtransaction *gs,
> +dump_gimple_transaction (pretty_printer *buffer, const gtransaction *gs,
>                          int spc, dump_flags_t flags)
>  {
>    unsigned subcode = gimple_transaction_subcode (gs);
> @@ -2063,7 +2064,8 @@ dump_gimple_transaction (pretty_printer *buffer, gtransaction *gs,
>     dumpfile.h).  */
>
>  static void
> -dump_gimple_asm (pretty_printer *buffer, gasm *gs, int spc, dump_flags_t flags)
> +dump_gimple_asm (pretty_printer *buffer, const gasm *gs, int spc,
> +                dump_flags_t flags)
>  {
>    unsigned int i, n, f, fields;
>
> @@ -2282,7 +2284,7 @@ dump_ssaname_info_to_file (FILE *file, tree node, int spc)
>     pretty printer.  If COMMENT is true, print this after #.  */
>
>  static void
> -dump_gimple_phi (pretty_printer *buffer, gphi *phi, int spc, bool comment,
> +dump_gimple_phi (pretty_printer *buffer, const gphi *phi, int spc, bool comment,
>                  dump_flags_t flags)
>  {
>    size_t i;
> @@ -2339,7 +2341,7 @@ dump_gimple_phi (pretty_printer *buffer, gphi *phi, int spc, bool comment,
>     dumpfile.h).  */
>
>  static void
> -dump_gimple_omp_parallel (pretty_printer *buffer, gomp_parallel *gs,
> +dump_gimple_omp_parallel (pretty_printer *buffer, const gomp_parallel *gs,
>                           int spc, dump_flags_t flags)
>  {
>    if (flags & TDF_RAW)
> @@ -2393,7 +2395,7 @@ dump_gimple_omp_parallel (pretty_printer *buffer, gomp_parallel *gs,
>     dumpfile.h).  */
>
>  static void
> -dump_gimple_omp_task (pretty_printer *buffer, gomp_task *gs, int spc,
> +dump_gimple_omp_task (pretty_printer *buffer, const gomp_task *gs, int spc,
>                       dump_flags_t flags)
>  {
>    if (flags & TDF_RAW)
> @@ -2455,7 +2457,7 @@ dump_gimple_omp_task (pretty_printer *buffer, gomp_task *gs, int spc,
>     in dumpfile.h).  */
>
>  static void
> -dump_gimple_omp_atomic_load (pretty_printer *buffer, gomp_atomic_load *gs,
> +dump_gimple_omp_atomic_load (pretty_printer *buffer, const gomp_atomic_load *gs,
>                              int spc, dump_flags_t flags)
>  {
>    if (flags & TDF_RAW)
> @@ -2489,7 +2491,7 @@ dump_gimple_omp_atomic_load (pretty_printer *buffer, gomp_atomic_load *gs,
>
>  static void
>  dump_gimple_omp_atomic_store (pretty_printer *buffer,
> -                             gomp_atomic_store *gs, int spc,
> +                             const gomp_atomic_store *gs, int spc,
>                               dump_flags_t flags)
>  {
>    if (flags & TDF_RAW)
> @@ -2517,7 +2519,7 @@ dump_gimple_omp_atomic_store (pretty_printer *buffer,
>     FLAGS are as in pp_gimple_stmt_1.  */
>
>  static void
> -dump_gimple_mem_ops (pretty_printer *buffer, gimple *gs, int spc,
> +dump_gimple_mem_ops (pretty_printer *buffer, const gimple *gs, int spc,
>                      dump_flags_t flags)
>  {
>    tree vdef = gimple_vdef (gs);
> @@ -2548,14 +2550,14 @@ dump_gimple_mem_ops (pretty_printer *buffer, gimple *gs, int spc,
>     pp_flush on BUFFER to finalize the pretty printer.  */
>
>  void
> -pp_gimple_stmt_1 (pretty_printer *buffer, gimple *gs, int spc,
> +pp_gimple_stmt_1 (pretty_printer *buffer, const gimple *gs, int spc,
>                   dump_flags_t flags)
>  {
>    if (!gs)
>      return;
>
>    if (flags & TDF_STMTADDR)
> -    pp_printf (buffer, "<&%p> ", (void *) gs);
> +    pp_printf (buffer, "<&%p> ", (const void *) gs);
>
>    if ((flags & TDF_LINENO) && gimple_has_location (gs))
>      dump_location (buffer, gimple_location (gs));
> @@ -2580,31 +2582,31 @@ pp_gimple_stmt_1 (pretty_printer *buffer, gimple *gs, int spc,
>    switch (gimple_code (gs))
>      {
>      case GIMPLE_ASM:
> -      dump_gimple_asm (buffer, as_a <gasm *> (gs), spc, flags);
> +      dump_gimple_asm (buffer, as_a <const gasm *> (gs), spc, flags);
>        break;
>
>      case GIMPLE_ASSIGN:
> -      dump_gimple_assign (buffer, as_a <gassign *> (gs), spc, flags);
> +      dump_gimple_assign (buffer, as_a <const gassign *> (gs), spc, flags);
>        break;
>
>      case GIMPLE_BIND:
> -      dump_gimple_bind (buffer, as_a <gbind *> (gs), spc, flags);
> +      dump_gimple_bind (buffer, as_a <const gbind *> (gs), spc, flags);
>        break;
>
>      case GIMPLE_CALL:
> -      dump_gimple_call (buffer, as_a <gcall *> (gs), spc, flags);
> +      dump_gimple_call (buffer, as_a <const gcall *> (gs), spc, flags);
>        break;
>
>      case GIMPLE_COND:
> -      dump_gimple_cond (buffer, as_a <gcond *> (gs), spc, flags);
> +      dump_gimple_cond (buffer, as_a <const gcond *> (gs), spc, flags);
>        break;
>
>      case GIMPLE_LABEL:
> -      dump_gimple_label (buffer, as_a <glabel *> (gs), spc, flags);
> +      dump_gimple_label (buffer, as_a <const glabel *> (gs), spc, flags);
>        break;
>
>      case GIMPLE_GOTO:
> -      dump_gimple_goto (buffer, as_a <ggoto *> (gs), spc, flags);
> +      dump_gimple_goto (buffer, as_a <const ggoto *> (gs), spc, flags);
>        break;
>
>      case GIMPLE_NOP:
> @@ -2612,62 +2614,62 @@ pp_gimple_stmt_1 (pretty_printer *buffer, gimple *gs, int spc,
>        break;
>
>      case GIMPLE_RETURN:
> -      dump_gimple_return (buffer, as_a <greturn *> (gs), spc, flags);
> +      dump_gimple_return (buffer, as_a <const greturn *> (gs), spc, flags);
>        break;
>
>      case GIMPLE_SWITCH:
> -      dump_gimple_switch (buffer, as_a <gswitch *> (gs), spc, flags);
> +      dump_gimple_switch (buffer, as_a <const gswitch *> (gs), spc, flags);
>        break;
>
>      case GIMPLE_TRY:
> -      dump_gimple_try (buffer, as_a <gtry *> (gs), spc, flags);
> +      dump_gimple_try (buffer, as_a <const gtry *> (gs), spc, flags);
>        break;
>
>      case GIMPLE_PHI:
> -      dump_gimple_phi (buffer, as_a <gphi *> (gs), spc, false, flags);
> +      dump_gimple_phi (buffer, as_a <const gphi *> (gs), spc, false, flags);
>        break;
>
>      case GIMPLE_OMP_PARALLEL:
> -      dump_gimple_omp_parallel (buffer, as_a <gomp_parallel *> (gs), spc,
> +      dump_gimple_omp_parallel (buffer, as_a <const gomp_parallel *> (gs), spc,
>                                 flags);
>        break;
>
>      case GIMPLE_OMP_TASK:
> -      dump_gimple_omp_task (buffer, as_a <gomp_task *> (gs), spc, flags);
> +      dump_gimple_omp_task (buffer, as_a <const gomp_task *> (gs), spc, flags);
>        break;
>
>      case GIMPLE_OMP_ATOMIC_LOAD:
> -      dump_gimple_omp_atomic_load (buffer, as_a <gomp_atomic_load *> (gs),
> +      dump_gimple_omp_atomic_load (buffer, as_a <const gomp_atomic_load *> (gs),
>                                    spc, flags);
>        break;
>
>      case GIMPLE_OMP_ATOMIC_STORE:
>        dump_gimple_omp_atomic_store (buffer,
> -                                   as_a <gomp_atomic_store *> (gs),
> +                                   as_a <const gomp_atomic_store *> (gs),
>                                     spc, flags);
>        break;
>
>      case GIMPLE_OMP_FOR:
> -      dump_gimple_omp_for (buffer, as_a <gomp_for *> (gs), spc, flags);
> +      dump_gimple_omp_for (buffer, as_a <const gomp_for *> (gs), spc, flags);
>        break;
>
>      case GIMPLE_OMP_CONTINUE:
> -      dump_gimple_omp_continue (buffer, as_a <gomp_continue *> (gs), spc,
> +      dump_gimple_omp_continue (buffer, as_a <const gomp_continue *> (gs), spc,
>                                 flags);
>        break;
>
>      case GIMPLE_OMP_SINGLE:
> -      dump_gimple_omp_single (buffer, as_a <gomp_single *> (gs), spc,
> +      dump_gimple_omp_single (buffer, as_a <const gomp_single *> (gs), spc,
>                               flags);
>        break;
>
>      case GIMPLE_OMP_TARGET:
> -      dump_gimple_omp_target (buffer, as_a <gomp_target *> (gs), spc,
> +      dump_gimple_omp_target (buffer, as_a <const gomp_target *> (gs), spc,
>                               flags);
>        break;
>
>      case GIMPLE_OMP_TEAMS:
> -      dump_gimple_omp_teams (buffer, as_a <gomp_teams *> (gs), spc,
> +      dump_gimple_omp_teams (buffer, as_a <const gomp_teams *> (gs), spc,
>                              flags);
>        break;
>
> @@ -2676,7 +2678,7 @@ pp_gimple_stmt_1 (pretty_printer *buffer, gimple *gs, int spc,
>        break;
>
>      case GIMPLE_OMP_SECTIONS:
> -      dump_gimple_omp_sections (buffer, as_a <gomp_sections *> (gs),
> +      dump_gimple_omp_sections (buffer, as_a <const gomp_sections *> (gs),
>                                 spc, flags);
>        break;
>
> @@ -2695,49 +2697,50 @@ pp_gimple_stmt_1 (pretty_printer *buffer, gimple *gs, int spc,
>        break;
>
>      case GIMPLE_OMP_ORDERED:
> -      dump_gimple_omp_ordered (buffer, as_a <gomp_ordered *> (gs), spc,
> +      dump_gimple_omp_ordered (buffer, as_a <const gomp_ordered *> (gs), spc,
>                                flags);
>        break;
>
>      case GIMPLE_OMP_SCAN:
> -      dump_gimple_omp_scan (buffer, as_a <gomp_scan *> (gs), spc,
> +      dump_gimple_omp_scan (buffer, as_a <const gomp_scan *> (gs), spc,
>                             flags);
>        break;
>
>      case GIMPLE_OMP_CRITICAL:
> -      dump_gimple_omp_critical (buffer, as_a <gomp_critical *> (gs), spc,
> +      dump_gimple_omp_critical (buffer, as_a <const gomp_critical *> (gs), spc,
>                                 flags);
>        break;
>
>      case GIMPLE_CATCH:
> -      dump_gimple_catch (buffer, as_a <gcatch *> (gs), spc, flags);
> +      dump_gimple_catch (buffer, as_a <const gcatch *> (gs), spc, flags);
>        break;
>
>      case GIMPLE_EH_FILTER:
> -      dump_gimple_eh_filter (buffer, as_a <geh_filter *> (gs), spc, flags);
> +      dump_gimple_eh_filter (buffer, as_a <const geh_filter *> (gs), spc,
> +                            flags);
>        break;
>
>      case GIMPLE_EH_MUST_NOT_THROW:
>        dump_gimple_eh_must_not_throw (buffer,
> -                                    as_a <geh_mnt *> (gs),
> +                                    as_a <const geh_mnt *> (gs),
>                                      spc, flags);
>        break;
>
>      case GIMPLE_EH_ELSE:
> -      dump_gimple_eh_else (buffer, as_a <geh_else *> (gs), spc, flags);
> +      dump_gimple_eh_else (buffer, as_a <const geh_else *> (gs), spc, flags);
>        break;
>
>      case GIMPLE_RESX:
> -      dump_gimple_resx (buffer, as_a <gresx *> (gs), spc, flags);
> +      dump_gimple_resx (buffer, as_a <const gresx *> (gs), spc, flags);
>        break;
>
>      case GIMPLE_EH_DISPATCH:
> -      dump_gimple_eh_dispatch (buffer, as_a <geh_dispatch *> (gs), spc,
> +      dump_gimple_eh_dispatch (buffer, as_a <const geh_dispatch *> (gs), spc,
>                                flags);
>        break;
>
>      case GIMPLE_DEBUG:
> -      dump_gimple_debug (buffer, as_a <gdebug *> (gs), spc, flags);
> +      dump_gimple_debug (buffer, as_a <const gdebug *> (gs), spc, flags);
>        break;
>
>      case GIMPLE_PREDICT:
> @@ -2751,7 +2754,7 @@ pp_gimple_stmt_1 (pretty_printer *buffer, gimple *gs, int spc,
>        break;
>
>      case GIMPLE_TRANSACTION:
> -      dump_gimple_transaction (buffer, as_a <gtransaction *> (gs), spc,
> +      dump_gimple_transaction (buffer, as_a <const gtransaction *> (gs), spc,
>                                flags);
>        break;
>
> diff --git a/gcc/gimple-pretty-print.h b/gcc/gimple-pretty-print.h
> index 8d8e105..186f2cf 100644
> --- a/gcc/gimple-pretty-print.h
> +++ b/gcc/gimple-pretty-print.h
> @@ -31,7 +31,8 @@ extern void print_gimple_stmt (FILE *, gimple *, int, dump_flags_t = TDF_NONE);
>  extern void debug (gimple &ref);
>  extern void debug (gimple *ptr);
>  extern void print_gimple_expr (FILE *, gimple *, int, dump_flags_t = TDF_NONE);
> -extern void pp_gimple_stmt_1 (pretty_printer *, gimple *, int, dump_flags_t);
> +extern void pp_gimple_stmt_1 (pretty_printer *, const gimple *, int,
> +                             dump_flags_t);
>  extern void gimple_dump_bb (FILE *, basic_block, int, dump_flags_t);
>  extern void gimple_dump_bb_for_graph (pretty_printer *, basic_block);
>  extern void dump_ssaname_info_to_file (FILE *, tree, int);
> diff --git a/gcc/gimple.h b/gcc/gimple.h
> index cf1f8da..10a4b8b 100644
> --- a/gcc/gimple.h
> +++ b/gcc/gimple.h
> @@ -1017,6 +1017,14 @@ is_a_helper <gdebug *>::test (gimple *gs)
>  template <>
>  template <>
>  inline bool
> +is_a_helper <const gdebug *>::test (const gimple *gs)
> +{
> +  return gs->code == GIMPLE_DEBUG;
> +}
> +
> +template <>
> +template <>
> +inline bool
>  is_a_helper <ggoto *>::test (gimple *gs)
>  {
>    return gs->code == GIMPLE_GOTO;
> @@ -1025,6 +1033,14 @@ is_a_helper <ggoto *>::test (gimple *gs)
>  template <>
>  template <>
>  inline bool
> +is_a_helper <const ggoto *>::test (const gimple *gs)
> +{
> +  return gs->code == GIMPLE_GOTO;
> +}
> +
> +template <>
> +template <>
> +inline bool
>  is_a_helper <glabel *>::test (gimple *gs)
>  {
>    return gs->code == GIMPLE_LABEL;
> @@ -1033,6 +1049,14 @@ is_a_helper <glabel *>::test (gimple *gs)
>  template <>
>  template <>
>  inline bool
> +is_a_helper <const glabel *>::test (const gimple *gs)
> +{
> +  return gs->code == GIMPLE_LABEL;
> +}
> +
> +template <>
> +template <>
> +inline bool
>  is_a_helper <gresx *>::test (gimple *gs)
>  {
>    return gs->code == GIMPLE_RESX;
> @@ -1057,6 +1081,14 @@ is_a_helper <geh_else *>::test (gimple *gs)
>  template <>
>  template <>
>  inline bool
> +is_a_helper <const geh_else *>::test (const gimple *gs)
> +{
> +  return gs->code == GIMPLE_EH_ELSE;
> +}
> +
> +template <>
> +template <>
> +inline bool
>  is_a_helper <geh_filter *>::test (gimple *gs)
>  {
>    return gs->code == GIMPLE_EH_FILTER;
> @@ -1073,6 +1105,14 @@ is_a_helper <geh_mnt *>::test (gimple *gs)
>  template <>
>  template <>
>  inline bool
> +is_a_helper <const geh_mnt *>::test (const gimple *gs)
> +{
> +  return gs->code == GIMPLE_EH_MUST_NOT_THROW;
> +}
> +
> +template <>
> +template <>
> +inline bool
>  is_a_helper <gomp_atomic_load *>::test (gimple *gs)
>  {
>    return gs->code == GIMPLE_OMP_ATOMIC_LOAD;
> @@ -1219,6 +1259,14 @@ is_a_helper <gswitch *>::test (gimple *gs)
>  template <>
>  template <>
>  inline bool
> +is_a_helper <const gswitch *>::test (const gimple *gs)
> +{
> +  return gs->code == GIMPLE_SWITCH;
> +}
> +
> +template <>
> +template <>
> +inline bool
>  is_a_helper <gtransaction *>::test (gimple *gs)
>  {
>    return gs->code == GIMPLE_TRANSACTION;
> @@ -1235,6 +1283,14 @@ is_a_helper <gtry *>::test (gimple *gs)
>  template <>
>  template <>
>  inline bool
> +is_a_helper <const gtry *>::test (const gimple *gs)
> +{
> +  return gs->code == GIMPLE_TRY;
> +}
> +
> +template <>
> +template <>
> +inline bool
>  is_a_helper <gimple_statement_wce *>::test (gimple *gs)
>  {
>    return gs->code == GIMPLE_WITH_CLEANUP_EXPR;
> @@ -1429,6 +1485,14 @@ is_a_helper <const gphi *>::test (const gimple *gs)
>  template <>
>  template <>
>  inline bool
> +is_a_helper <const greturn *>::test (const gimple *gs)
> +{
> +  return gs->code == GIMPLE_RETURN;
> +}
> +
> +template <>
> +template <>
> +inline bool
>  is_a_helper <const gtransaction *>::test (const gimple *gs)
>  {
>    return gs->code == GIMPLE_TRANSACTION;
> @@ -3254,7 +3318,7 @@ gimple_call_set_tail (gcall *s, bool tail_p)
>  /* Return true if GIMPLE_CALL S is marked as a tail call.  */
>
>  static inline bool
> -gimple_call_tail_p (gcall *s)
> +gimple_call_tail_p (const gcall *s)
>  {
>    return (s->subcode & GF_CALL_TAILCALL) != 0;
>  }
> @@ -3296,7 +3360,7 @@ gimple_call_set_return_slot_opt (gcall *s, bool return_slot_opt_p)
>  /* Return true if S is marked for return slot optimization.  */
>
>  static inline bool
> -gimple_call_return_slot_opt_p (gcall *s)
> +gimple_call_return_slot_opt_p (const gcall *s)
>  {
>    return (s->subcode & GF_CALL_RETURN_SLOT_OPT) != 0;
>  }
> @@ -3341,7 +3405,7 @@ gimple_call_set_va_arg_pack (gcall *s, bool pass_arg_pack_p)
>     argument pack in its argument list.  */
>
>  static inline bool
> -gimple_call_va_arg_pack_p (gcall *s)
> +gimple_call_va_arg_pack_p (const gcall *s)
>  {
>    return (s->subcode & GF_CALL_VA_ARG_PACK) != 0;
>  }
> @@ -3443,6 +3507,13 @@ gimple_call_use_set (gcall *call_stmt)
>    return &call_stmt->call_used;
>  }
>
> +/* As above, but const.  */
> +
> +static inline const pt_solution *
> +gimple_call_use_set (const gcall *call_stmt)
> +{
> +  return &call_stmt->call_used;
> +}
>
>  /* Return a pointer to the points-to solution for the set of call-used
>     variables of the call CALL_STMT.  */
> @@ -3453,16 +3524,24 @@ gimple_call_clobber_set (gcall *call_stmt)
>    return &call_stmt->call_clobbered;
>  }
>
> +/* As above, but const.  */
> +
> +static inline const pt_solution *
> +gimple_call_clobber_set (const gcall *call_stmt)
> +{
> +  return &call_stmt->call_clobbered;
> +}
> +
>
>  /* Returns true if this is a GIMPLE_ASSIGN or a GIMPLE_CALL with a
>     non-NULL lhs.  */
>
>  static inline bool
> -gimple_has_lhs (gimple *stmt)
> +gimple_has_lhs (const gimple *stmt)
>  {
>    if (is_gimple_assign (stmt))
>      return true;
> -  if (gcall *call = dyn_cast <gcall *> (stmt))
> +  if (const gcall *call = dyn_cast <const gcall *> (stmt))
>      return gimple_call_lhs (call) != NULL_TREE;
>    return false;
>  }
> @@ -3760,9 +3839,9 @@ gimple_bind_body_ptr (gbind *bind_stmt)
>  /* Return the GIMPLE sequence contained in the GIMPLE_BIND statement GS.  */
>
>  static inline gimple_seq
> -gimple_bind_body (gbind *gs)
> +gimple_bind_body (const gbind *gs)
>  {
> -  return *gimple_bind_body_ptr (gs);
> +  return *gimple_bind_body_ptr (const_cast <gbind *> (gs));
>  }
>
>
> @@ -4034,9 +4113,9 @@ gimple_catch_handler_ptr (gcatch *catch_stmt)
>     GIMPLE_CATCH statement CATCH_STMT.  */
>
>  static inline gimple_seq
> -gimple_catch_handler (gcatch *catch_stmt)
> +gimple_catch_handler (const gcatch *catch_stmt)
>  {
> -  return *gimple_catch_handler_ptr (catch_stmt);
> +  return *gimple_catch_handler_ptr (const_cast <gcatch *> (catch_stmt));
>  }
>
>
> @@ -4094,9 +4173,9 @@ gimple_eh_filter_failure_ptr (gimple *gs)
>     statement fails.  */
>
>  static inline gimple_seq
> -gimple_eh_filter_failure (gimple *gs)
> +gimple_eh_filter_failure (const gimple *gs)
>  {
> -  return *gimple_eh_filter_failure_ptr (gs);
> +  return *gimple_eh_filter_failure_ptr (const_cast <gimple *> (gs));
>  }
>
>
> @@ -4123,7 +4202,7 @@ gimple_eh_filter_set_failure (geh_filter *eh_filter_stmt,
>  /* Get the function decl to be called by the MUST_NOT_THROW region.  */
>
>  static inline tree
> -gimple_eh_must_not_throw_fndecl (geh_mnt *eh_mnt_stmt)
> +gimple_eh_must_not_throw_fndecl (const geh_mnt *eh_mnt_stmt)
>  {
>    return eh_mnt_stmt->fndecl;
>  }
> @@ -4146,9 +4225,9 @@ gimple_eh_else_n_body_ptr (geh_else *eh_else_stmt)
>  }
>
>  static inline gimple_seq
> -gimple_eh_else_n_body (geh_else *eh_else_stmt)
> +gimple_eh_else_n_body (const geh_else *eh_else_stmt)
>  {
> -  return *gimple_eh_else_n_body_ptr (eh_else_stmt);
> +  return *gimple_eh_else_n_body_ptr (const_cast <geh_else *> (eh_else_stmt));
>  }
>
>  static inline gimple_seq *
> @@ -4158,9 +4237,9 @@ gimple_eh_else_e_body_ptr (geh_else *eh_else_stmt)
>  }
>
>  static inline gimple_seq
> -gimple_eh_else_e_body (geh_else *eh_else_stmt)
> +gimple_eh_else_e_body (const geh_else *eh_else_stmt)
>  {
> -  return *gimple_eh_else_e_body_ptr (eh_else_stmt);
> +  return *gimple_eh_else_e_body_ptr (const_cast <geh_else *> (eh_else_stmt));
>  }
>
>  static inline void
> @@ -4224,9 +4303,9 @@ gimple_try_eval_ptr (gimple *gs)
>  /* Return the sequence of statements used as the body for GIMPLE_TRY GS.  */
>
>  static inline gimple_seq
> -gimple_try_eval (gimple *gs)
> +gimple_try_eval (const gimple *gs)
>  {
> -  return *gimple_try_eval_ptr (gs);
> +  return *gimple_try_eval_ptr (const_cast <gimple *> (gs));
>  }
>
>
> @@ -4245,9 +4324,9 @@ gimple_try_cleanup_ptr (gimple *gs)
>     GIMPLE_TRY GS.  */
>
>  static inline gimple_seq
> -gimple_try_cleanup (gimple *gs)
> +gimple_try_cleanup (const gimple *gs)
>  {
> -  return *gimple_try_cleanup_ptr (gs);
> +  return *gimple_try_cleanup_ptr (const_cast <gimple *> (gs));
>  }
>
>
> @@ -4406,6 +4485,13 @@ gimple_phi_arg (gphi *gs, unsigned index)
>    return &(gs->args[index]);
>  }
>
> +static inline const phi_arg_d *
> +gimple_phi_arg (const gphi *gs, unsigned index)
> +{
> +  gcc_gimple_checking_assert (index < gs->nargs);
> +  return &(gs->args[index]);
> +}
> +
>  static inline struct phi_arg_d *
>  gimple_phi_arg (gimple *gs, unsigned index)
>  {
> @@ -4445,7 +4531,7 @@ phi_nodes_ptr (basic_block bb)
>  /* Return the tree operand for argument I of PHI node GS.  */
>
>  static inline tree
> -gimple_phi_arg_def (gphi *gs, size_t index)
> +gimple_phi_arg_def (const gphi *gs, size_t index)
>  {
>    return gimple_phi_arg (gs, index)->def;
>  }
> @@ -4468,7 +4554,7 @@ gimple_phi_arg_def_ptr (gphi *phi, size_t index)
>  /* Return the edge associated with argument I of phi node PHI.  */
>
>  static inline edge
> -gimple_phi_arg_edge (gphi *phi, size_t i)
> +gimple_phi_arg_edge (const gphi *phi, size_t i)
>  {
>    return EDGE_PRED (gimple_bb (phi), i);
>  }
> @@ -4476,7 +4562,7 @@ gimple_phi_arg_edge (gphi *phi, size_t i)
>  /* Return the source location of gimple argument I of phi node PHI.  */
>
>  static inline location_t
> -gimple_phi_arg_location (gphi *phi, size_t i)
> +gimple_phi_arg_location (const gphi *phi, size_t i)
>  {
>    return gimple_phi_arg (phi, i)->locus;
>  }
> @@ -4500,7 +4586,7 @@ gimple_phi_arg_set_location (gphi *phi, size_t i, location_t loc)
>  /* Return TRUE if argument I of phi node PHI has a location record.  */
>
>  static inline bool
> -gimple_phi_arg_has_location (gphi *phi, size_t i)
> +gimple_phi_arg_has_location (const gphi *phi, size_t i)
>  {
>    return gimple_phi_arg_location (phi, i) != UNKNOWN_LOCATION;
>  }
> @@ -4668,7 +4754,7 @@ gimple_debug_bind_p (const gimple *s)
>  /* Return the variable bound in a GIMPLE_DEBUG bind statement.  */
>
>  static inline tree
> -gimple_debug_bind_get_var (gimple *dbg)
> +gimple_debug_bind_get_var (const gimple *dbg)
>  {
>    GIMPLE_CHECK (dbg, GIMPLE_DEBUG);
>    gcc_gimple_checking_assert (gimple_debug_bind_p (dbg));
> @@ -4679,7 +4765,7 @@ gimple_debug_bind_get_var (gimple *dbg)
>     statement.  */
>
>  static inline tree
> -gimple_debug_bind_get_value (gimple *dbg)
> +gimple_debug_bind_get_value (const gimple *dbg)
>  {
>    GIMPLE_CHECK (dbg, GIMPLE_DEBUG);
>    gcc_gimple_checking_assert (gimple_debug_bind_p (dbg));
> @@ -4760,7 +4846,7 @@ gimple_debug_source_bind_p (const gimple *s)
>  /* Return the variable bound in a GIMPLE_DEBUG source bind statement.  */
>
>  static inline tree
> -gimple_debug_source_bind_get_var (gimple *dbg)
> +gimple_debug_source_bind_get_var (const gimple *dbg)
>  {
>    GIMPLE_CHECK (dbg, GIMPLE_DEBUG);
>    gcc_gimple_checking_assert (gimple_debug_source_bind_p (dbg));
> @@ -4771,7 +4857,7 @@ gimple_debug_source_bind_get_var (gimple *dbg)
>     statement.  */
>
>  static inline tree
> -gimple_debug_source_bind_get_value (gimple *dbg)
> +gimple_debug_source_bind_get_value (const gimple *dbg)
>  {
>    GIMPLE_CHECK (dbg, GIMPLE_DEBUG);
>    gcc_gimple_checking_assert (gimple_debug_source_bind_p (dbg));
> @@ -4872,9 +4958,9 @@ gimple_omp_body_ptr (gimple *gs)
>  /* Return the body for the OMP statement GS.  */
>
>  static inline gimple_seq
> -gimple_omp_body (gimple *gs)
> +gimple_omp_body (const gimple *gs)
>  {
> -  return *gimple_omp_body_ptr (gs);
> +  return *gimple_omp_body_ptr (const_cast <gimple *> (gs));
>  }
>
>  /* Set BODY to be the body for the OMP statement GS.  */
> @@ -5139,9 +5225,9 @@ gimple_omp_for_set_clauses (gimple *gs, tree clauses)
>  /* Get the collapse count of the OMP_FOR statement GS.  */
>
>  static inline size_t
> -gimple_omp_for_collapse (gimple *gs)
> +gimple_omp_for_collapse (const gimple *gs)
>  {
> -  gomp_for *omp_for_stmt = as_a <gomp_for *> (gs);
> +  const gomp_for *omp_for_stmt = as_a <const gomp_for *> (gs);
>    return omp_for_stmt->collapse;
>  }
>
> @@ -5316,9 +5402,9 @@ gimple_omp_for_pre_body_ptr (gimple *gs)
>     statement GS starts.  */
>
>  static inline gimple_seq
> -gimple_omp_for_pre_body (gimple *gs)
> +gimple_omp_for_pre_body (const gimple *gs)
>  {
> -  return *gimple_omp_for_pre_body_ptr (gs);
> +  return *gimple_omp_for_pre_body_ptr (const_cast <gimple *> (gs));
>  }
>
>
> @@ -6303,7 +6389,7 @@ gimple_transaction_body_ptr (gtransaction *transaction_stmt)
>  /* Return the body for the GIMPLE_TRANSACTION statement TRANSACTION_STMT.  */
>
>  static inline gimple_seq
> -gimple_transaction_body (gtransaction *transaction_stmt)
> +gimple_transaction_body (const gtransaction *transaction_stmt)
>  {
>    return transaction_stmt->body;
>  }
> diff --git a/gcc/tree-eh.c b/gcc/tree-eh.c
> index 54502e6..b996f49 100644
> --- a/gcc/tree-eh.c
> +++ b/gcc/tree-eh.c
> @@ -139,19 +139,19 @@ remove_stmt_from_eh_lp (gimple *t)
>     statement is not recorded in the region table.  */
>
>  int
> -lookup_stmt_eh_lp_fn (struct function *ifun, gimple *t)
> +lookup_stmt_eh_lp_fn (struct function *ifun, const gimple *t)
>  {
>    if (ifun->eh->throw_stmt_table == NULL)
>      return 0;
>
> -  int *lp_nr = ifun->eh->throw_stmt_table->get (t);
> +  int *lp_nr = ifun->eh->throw_stmt_table->get (const_cast <gimple *> (t));
>    return lp_nr ? *lp_nr : 0;
>  }
>
>  /* Likewise, but always use the current function.  */
>
>  int
> -lookup_stmt_eh_lp (gimple *t)
> +lookup_stmt_eh_lp (const gimple *t)
>  {
>    /* We can get called from initialized data when -fnon-call-exceptions
>       is on; prevent crash.  */
> diff --git a/gcc/tree-eh.h b/gcc/tree-eh.h
> index 511bb84..ca3b521 100644
> --- a/gcc/tree-eh.h
> +++ b/gcc/tree-eh.h
> @@ -27,8 +27,8 @@ extern void using_eh_for_cleanups (void);
>  extern void add_stmt_to_eh_lp (gimple *, int);
>  extern bool remove_stmt_from_eh_lp_fn (struct function *, gimple *);
>  extern bool remove_stmt_from_eh_lp (gimple *);
> -extern int lookup_stmt_eh_lp_fn (struct function *, gimple *);
> -extern int lookup_stmt_eh_lp (gimple *);
> +extern int lookup_stmt_eh_lp_fn (struct function *, const gimple *);
> +extern int lookup_stmt_eh_lp (const gimple *);
>  extern bool make_eh_dispatch_edges (geh_dispatch *);
>  extern void make_eh_edges (gimple *);
>  extern edge redirect_eh_edge (edge, basic_block);
> diff --git a/gcc/tree-ssa-alias.h b/gcc/tree-ssa-alias.h
> index 6d72c2f..de85312 100644
> --- a/gcc/tree-ssa-alias.h
> +++ b/gcc/tree-ssa-alias.h
> @@ -162,7 +162,7 @@ extern void dump_alias_stats (FILE *);
>
>  /* In tree-ssa-structalias.c  */
>  extern unsigned int compute_may_aliases (void);
> -extern bool pt_solution_empty_p (struct pt_solution *);
> +extern bool pt_solution_empty_p (const pt_solution *);
>  extern bool pt_solution_singleton_or_null_p (struct pt_solution *, unsigned *);
>  extern bool pt_solution_includes_global (struct pt_solution *);
>  extern bool pt_solution_includes (struct pt_solution *, const_tree);
> diff --git a/gcc/tree-ssa-structalias.c b/gcc/tree-ssa-structalias.c
> index 75c6fae..b248dfa 100644
> --- a/gcc/tree-ssa-structalias.c
> +++ b/gcc/tree-ssa-structalias.c
> @@ -6801,7 +6801,7 @@ pt_solution_ior_into (struct pt_solution *dest, struct pt_solution *src)
>  /* Return true if the points-to solution *PT is empty.  */
>
>  bool
> -pt_solution_empty_p (struct pt_solution *pt)
> +pt_solution_empty_p (const pt_solution *pt)
>  {
>    if (pt->anything
>        || pt->nonlocal)
> --
> 1.8.5.3
>

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 09/49] gimple const-correctness fixes
  2019-12-06 10:52   ` Richard Biener
@ 2019-12-06 17:47     ` David Malcolm
  2019-12-09  8:27       ` Richard Biener
  0 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-12-06 17:47 UTC (permalink / raw)
  To: Richard Biener; +Cc: GCC Patches

On Fri, 2019-12-06 at 11:52 +0100, Richard Biener wrote:
> On Sat, Nov 16, 2019 at 2:20 AM David Malcolm <dmalcolm@redhat.com>
> wrote:
> > This patch converts various "gimple *" to "const gimple *" and
> > similar
> > fixes for gimple subclasses, adding is_a_helper for gimple
> > subclasses
> > to support the const form of as_a, and adding a few "const"
> > overloads
> > of accessors.
> > 
> > This is enough to make pp_gimple_stmt_1's stmt const.
> 
> Hum.  Can't the const is-a variants be somehow magically implemented
> generally?  If something is a T then it is also a const T, no?  I
> guess
> if something is a const T it isn't a T though?
> 
> Richard.

It is something of a wart to need new is_a_helper<>::test functions for
the const variants.

I tried poking at is-a.h to do this in a more generic way, but I'm not
sure it's doable without an invasive change:  is_a_helper's T is
already a pointer, so AIUI, if we apply "const" to it, we're making the
pointer const, rather than the thing being pointed to.

Maybe someone else can see a way?

In the meantime, is this patch OK?  (I use "const gimple *" etc
throughout the analyzer, to emphasize that I'm not changing them)

Thanks
Dave


> > gcc/ChangeLog:
> >         * gimple-predict.h (gimple_predict_predictor): Make "gs"
> > param
> >         const.
> >         (gimple_predict_outcome): Likewise.
> >         * gimple-pretty-print.c (do_niy): Likewise.
> >         (dump_unary_rhs): Likewise.
> >         (dump_binary_rhs): Likewise.
> >         (dump_ternary_rhs): Likewise.
> >         (dump_gimple_assign): Likewise.
> >         (dump_gimple_return): Likewise.
> >         (dump_gimple_call_args): Likewise.
> >         (pp_points_to_solution): Make "pt" param const.
> >         (dump_gimple_call): Make "gs" param const.
> >         (dump_gimple_switch): Likewise.
> >         (dump_gimple_cond): Likewise.
> >         (dump_gimple_label): Likewise.
> >         (dump_gimple_goto): Likewise.
> >         (dump_gimple_bind): Likewise.
> >         (dump_gimple_try): Likewise.
> >         (dump_gimple_catch): Likewise.
> >         (dump_gimple_eh_filter): Likewise.
> >         (dump_gimple_eh_must_not_throw): Likewise.
> >         (dump_gimple_eh_else): Likewise.
> >         (dump_gimple_resx): Likewise.
> >         (dump_gimple_eh_dispatch): Likewise.
> >         (dump_gimple_debug): Likewise.
> >         (dump_gimple_omp_for): Likewise.
> >         (dump_gimple_omp_continue): Likewise.
> >         (dump_gimple_omp_single): Likewise.
> >         (dump_gimple_omp_taskgroup): Likewise.
> >         (dump_gimple_omp_target): Likewise.
> >         (dump_gimple_omp_teams): Likewise.
> >         (dump_gimple_omp_sections): Likewise.
> >         (dump_gimple_omp_block): Likewise.
> >         (dump_gimple_omp_critical): Likewise.
> >         (dump_gimple_omp_ordered): Likewise.
> >         (dump_gimple_omp_scan): Likewise.
> >         (dump_gimple_omp_return): Likewise.
> >         (dump_gimple_transaction): Likewise.
> >         (dump_gimple_asm): Likewise.
> >         (dump_gimple_phi): Make "phi" param const.
> >         (dump_gimple_omp_parallel): Make "gs" param const.
> >         (dump_gimple_omp_task): Likewise.
> >         (dump_gimple_omp_atomic_load): Likewise.
> >         (dump_gimple_omp_atomic_store): Likewise.
> >         (dump_gimple_mem_ops): Likewise.
> >         (pp_gimple_stmt_1): Likewise.  Add "const" to the various
> > as_a <>
> >         casts throughout.
> >         * gimple-pretty-print.h (gimple_stmt_1): Make gimple *
> > param const.
> >         * gimple.h (is_a_helper <const gdebug *>::test): New.
> >         (is_a_helper <const ggoto *>::test): New.
> >         (is_a_helper <const glabel *>::test): New.
> >         (is_a_helper <const geh_else *>::test): New.
> >         (is_a_helper <const geh_mnt *>::test): New.
> >         (is_a_helper <const gswitch *>::test): New.
> >         (is_a_helper <const gtry *>::test): New.
> >         (is_a_helper <const greturn *>::test): New.
> >         (gimple_call_tail_p): Make param const.
> >         (gimple_call_return_slot_opt_p): Likewise.
> >         (gimple_call_va_arg_pack_p): Likewise.
> >         (gimple_call_use_set): Add const overload.
> >         (gimple_call_clobber_set): Likewise.
> >         (gimple_has_lhs): Make param const.
> >         (gimple_bind_body): Likewise.
> >         (gimple_catch_handler): Likewise.
> >         (gimple_eh_filter_failure): Likewise.
> >         (gimple_eh_must_not_throw_fndecl): Likewise.
> >         (gimple_eh_else_n_body): Likewise.
> >         (gimple_eh_else_e_body): Likewise.
> >         (gimple_try_eval): Likewise.
> >         (gimple_try_cleanup): Likewise.
> >         (gimple_phi_arg): Add const overload.
> >         (gimple_phi_arg_def): Make param const.
> >         (gimple_phi_arg_edge): Likewise.
> >         (gimple_phi_arg_location): Likewise.
> >         (gimple_phi_arg_has_location): Likewise.
> >         (gimple_debug_bind_get_var): Likewise.
> >         (gimple_debug_bind_get_value): Likewise.
> >         (gimple_debug_source_bind_get_var): Likewise.
> >         (gimple_debug_source_bind_get_value): Likewise.
> >         (gimple_omp_body): Likewise.
> >         (gimple_omp_for_collapse): Likewise.
> >         (gimple_omp_for_pre_body): Likewise.
> >         (gimple_transaction_body): Likewise.
> >         * tree-eh.c (lookup_stmt_eh_lp_fn): Make param "t" const.
> >         (lookup_stmt_eh_lp): Likewise.
> >         * tree-eh.h (lookup_stmt_eh_lp_fn): Make param const.
> >         (lookup_stmt_eh_lp): Likewise.
> >         * tree-ssa-alias.h (pt_solution_empty_p): Make param const.
> >         * tree-ssa-structalias.c (pt_solution_empty_p): Likewise.
> > ---
> >  gcc/gimple-predict.h       |   4 +-
> >  gcc/gimple-pretty-print.c  | 159 +++++++++++++++++++++++--------
> > --------------
> >  gcc/gimple-pretty-print.h  |   3 +-
> >  gcc/gimple.h               | 156
> > ++++++++++++++++++++++++++++++++++----------
> >  gcc/tree-eh.c              |   6 +-
> >  gcc/tree-eh.h              |   4 +-
> >  gcc/tree-ssa-alias.h       |   2 +-
> >  gcc/tree-ssa-structalias.c |   2 +-
> >  8 files changed, 213 insertions(+), 123 deletions(-)
> > 
> > diff --git a/gcc/gimple-predict.h b/gcc/gimple-predict.h
> > index 761098b..d976317 100644
> > --- a/gcc/gimple-predict.h
> > +++ b/gcc/gimple-predict.h
> > @@ -26,7 +26,7 @@ along with GCC; see the file COPYING3.  If not
> > see
> >  /* Return the predictor of GIMPLE_PREDICT statement GS.  */
> > 
> >  static inline enum br_predictor
> > -gimple_predict_predictor (gimple *gs)
> > +gimple_predict_predictor (const gimple *gs)
> >  {
> >    GIMPLE_CHECK (gs, GIMPLE_PREDICT);
> >    return (enum br_predictor) (gs->subcode & ~GF_PREDICT_TAKEN);
> > @@ -47,7 +47,7 @@ gimple_predict_set_predictor (gimple *gs, enum
> > br_predictor predictor)
> >  /* Return the outcome of GIMPLE_PREDICT statement GS.  */
> > 
> >  static inline enum prediction
> > -gimple_predict_outcome (gimple *gs)
> > +gimple_predict_outcome (const gimple *gs)
> >  {
> >    GIMPLE_CHECK (gs, GIMPLE_PREDICT);
> >    return (gs->subcode & GF_PREDICT_TAKEN) ? TAKEN : NOT_TAKEN;
> > diff --git a/gcc/gimple-pretty-print.c b/gcc/gimple-pretty-print.c
> > index 2d5ece0..f044628 100644
> > --- a/gcc/gimple-pretty-print.c
> > +++ b/gcc/gimple-pretty-print.c
> > @@ -59,7 +59,7 @@ along with GCC; see the file COPYING3.  If not
> > see
> >     gimple statement GS.  */
> > 
> >  static void
> > -do_niy (pretty_printer *buffer, gimple *gs)
> > +do_niy (pretty_printer *buffer, const gimple *gs)
> >  {
> >    pp_printf (buffer, "<<< Unknown GIMPLE statement: %s >>>\n",
> >              gimple_code_name[(int) gimple_code (gs)]);
> > @@ -327,7 +327,7 @@ dump_gimple_fmt (pretty_printer *buffer, int
> > spc, dump_flags_t flags,
> >     assignment GS.  BUFFER, SPC and FLAGS are as in
> > pp_gimple_stmt_1.  */
> > 
> >  static void
> > -dump_unary_rhs (pretty_printer *buffer, gassign *gs, int spc,
> > +dump_unary_rhs (pretty_printer *buffer, const gassign *gs, int
> > spc,
> >                 dump_flags_t flags)
> >  {
> >    enum tree_code rhs_code = gimple_assign_rhs_code (gs);
> > @@ -423,7 +423,7 @@ dump_unary_rhs (pretty_printer *buffer, gassign
> > *gs, int spc,
> >     assignment GS.  BUFFER, SPC and FLAGS are as in
> > pp_gimple_stmt_1.  */
> > 
> >  static void
> > -dump_binary_rhs (pretty_printer *buffer, gassign *gs, int spc,
> > +dump_binary_rhs (pretty_printer *buffer, const gassign *gs, int
> > spc,
> >                  dump_flags_t flags)
> >  {
> >    const char *p;
> > @@ -495,7 +495,7 @@ dump_binary_rhs (pretty_printer *buffer,
> > gassign *gs, int spc,
> >     assignment GS.  BUFFER, SPC and FLAGS are as in
> > pp_gimple_stmt_1.  */
> > 
> >  static void
> > -dump_ternary_rhs (pretty_printer *buffer, gassign *gs, int spc,
> > +dump_ternary_rhs (pretty_printer *buffer, const gassign *gs, int
> > spc,
> >                   dump_flags_t flags)
> >  {
> >    const char *p;
> > @@ -625,7 +625,7 @@ dump_ternary_rhs (pretty_printer *buffer,
> > gassign *gs, int spc,
> >     pp_gimple_stmt_1.  */
> > 
> >  static void
> > -dump_gimple_assign (pretty_printer *buffer, gassign *gs, int spc,
> > +dump_gimple_assign (pretty_printer *buffer, const gassign *gs, int
> > spc,
> >                     dump_flags_t flags)
> >  {
> >    if (flags & TDF_RAW)
> > @@ -687,7 +687,7 @@ dump_gimple_assign (pretty_printer *buffer,
> > gassign *gs, int spc,
> >     pp_gimple_stmt_1.  */
> > 
> >  static void
> > -dump_gimple_return (pretty_printer *buffer, greturn *gs, int spc,
> > +dump_gimple_return (pretty_printer *buffer, const greturn *gs, int
> > spc,
> >                     dump_flags_t flags)
> >  {
> >    tree t;
> > @@ -712,7 +712,8 @@ dump_gimple_return (pretty_printer *buffer,
> > greturn *gs, int spc,
> >     dump_gimple_call.  */
> > 
> >  static void
> > -dump_gimple_call_args (pretty_printer *buffer, gcall *gs,
> > dump_flags_t flags)
> > +dump_gimple_call_args (pretty_printer *buffer, const gcall *gs,
> > +                      dump_flags_t flags)
> >  {
> >    size_t i = 0;
> > 
> > @@ -795,7 +796,7 @@ dump_gimple_call_args (pretty_printer *buffer,
> > gcall *gs, dump_flags_t flags)
> >  /* Dump the points-to solution *PT to BUFFER.  */
> > 
> >  static void
> > -pp_points_to_solution (pretty_printer *buffer, struct pt_solution
> > *pt)
> > +pp_points_to_solution (pretty_printer *buffer, const pt_solution
> > *pt)
> >  {
> >    if (pt->anything)
> >      {
> > @@ -868,7 +869,7 @@ pp_points_to_solution (pretty_printer *buffer,
> > struct pt_solution *pt)
> >     pp_gimple_stmt_1.  */
> > 
> >  static void
> > -dump_gimple_call (pretty_printer *buffer, gcall *gs, int spc,
> > +dump_gimple_call (pretty_printer *buffer, const gcall *gs, int
> > spc,
> >                   dump_flags_t flags)
> >  {
> >    tree lhs = gimple_call_lhs (gs);
> > @@ -876,7 +877,7 @@ dump_gimple_call (pretty_printer *buffer, gcall
> > *gs, int spc,
> > 
> >    if (flags & TDF_ALIAS)
> >      {
> > -      struct pt_solution *pt;
> > +      const pt_solution *pt;
> >        pt = gimple_call_use_set (gs);
> >        if (!pt_solution_empty_p (pt))
> >         {
> > @@ -1006,7 +1007,7 @@ dump_gimple_call (pretty_printer *buffer,
> > gcall *gs, int spc,
> >     pp_gimple_stmt_1.  */
> > 
> >  static void
> > -dump_gimple_switch (pretty_printer *buffer, gswitch *gs, int spc,
> > +dump_gimple_switch (pretty_printer *buffer, const gswitch *gs, int
> > spc,
> >                     dump_flags_t flags)
> >  {
> >    unsigned int i;
> > @@ -1064,7 +1065,7 @@ dump_gimple_switch (pretty_printer *buffer,
> > gswitch *gs, int spc,
> >     pp_gimple_stmt_1.  */
> > 
> >  static void
> > -dump_gimple_cond (pretty_printer *buffer, gcond *gs, int spc,
> > +dump_gimple_cond (pretty_printer *buffer, const gcond *gs, int
> > spc,
> >                   dump_flags_t flags)
> >  {
> >    if (flags & TDF_RAW)
> > @@ -1131,7 +1132,7 @@ dump_gimple_cond (pretty_printer *buffer,
> > gcond *gs, int spc,
> >     TDF_* in dumpfils.h).  */
> > 
> >  static void
> > -dump_gimple_label (pretty_printer *buffer, glabel *gs, int spc,
> > +dump_gimple_label (pretty_printer *buffer, const glabel *gs, int
> > spc,
> >                    dump_flags_t flags)
> >  {
> >    tree label = gimple_label_label (gs);
> > @@ -1155,7 +1156,7 @@ dump_gimple_label (pretty_printer *buffer,
> > glabel *gs, int spc,
> >     TDF_* in dumpfile.h).  */
> > 
> >  static void
> > -dump_gimple_goto (pretty_printer *buffer, ggoto *gs, int spc,
> > +dump_gimple_goto (pretty_printer *buffer, const ggoto *gs, int
> > spc,
> >                   dump_flags_t flags)
> >  {
> >    tree label = gimple_goto_dest (gs);
> > @@ -1171,7 +1172,7 @@ dump_gimple_goto (pretty_printer *buffer,
> > ggoto *gs, int spc,
> >     TDF_* in dumpfile.h).  */
> > 
> >  static void
> > -dump_gimple_bind (pretty_printer *buffer, gbind *gs, int spc,
> > +dump_gimple_bind (pretty_printer *buffer, const gbind *gs, int
> > spc,
> >                   dump_flags_t flags)
> >  {
> >    if (flags & TDF_RAW)
> > @@ -1205,7 +1206,7 @@ dump_gimple_bind (pretty_printer *buffer,
> > gbind *gs, int spc,
> >     dumpfile.h).  */
> > 
> >  static void
> > -dump_gimple_try (pretty_printer *buffer, gtry *gs, int spc,
> > +dump_gimple_try (pretty_printer *buffer, const gtry *gs, int spc,
> >                  dump_flags_t flags)
> >  {
> >    if (flags & TDF_RAW)
> > @@ -1280,7 +1281,7 @@ dump_gimple_try (pretty_printer *buffer, gtry
> > *gs, int spc,
> >     dumpfile.h).  */
> > 
> >  static void
> > -dump_gimple_catch (pretty_printer *buffer, gcatch *gs, int spc,
> > +dump_gimple_catch (pretty_printer *buffer, const gcatch *gs, int
> > spc,
> >                    dump_flags_t flags)
> >  {
> >    if (flags & TDF_RAW)
> > @@ -1297,7 +1298,7 @@ dump_gimple_catch (pretty_printer *buffer,
> > gcatch *gs, int spc,
> >     dumpfile.h).  */
> > 
> >  static void
> > -dump_gimple_eh_filter (pretty_printer *buffer, geh_filter *gs, int
> > spc,
> > +dump_gimple_eh_filter (pretty_printer *buffer, const geh_filter
> > *gs, int spc,
> >                        dump_flags_t flags)
> >  {
> >    if (flags & TDF_RAW)
> > @@ -1315,7 +1316,7 @@ dump_gimple_eh_filter (pretty_printer
> > *buffer, geh_filter *gs, int spc,
> > 
> >  static void
> >  dump_gimple_eh_must_not_throw (pretty_printer *buffer,
> > -                              geh_mnt *gs, int spc, dump_flags_t
> > flags)
> > +                              const geh_mnt *gs, int spc,
> > dump_flags_t flags)
> >  {
> >    if (flags & TDF_RAW)
> >      dump_gimple_fmt (buffer, spc, flags, "%G <%T>", gs,
> > @@ -1331,7 +1332,7 @@ dump_gimple_eh_must_not_throw (pretty_printer
> > *buffer,
> >     dumpfile.h).  */
> > 
> >  static void
> > -dump_gimple_eh_else (pretty_printer *buffer, geh_else *gs, int
> > spc,
> > +dump_gimple_eh_else (pretty_printer *buffer, const geh_else *gs,
> > int spc,
> >                      dump_flags_t flags)
> >  {
> >    if (flags & TDF_RAW)
> > @@ -1350,7 +1351,7 @@ dump_gimple_eh_else (pretty_printer *buffer,
> > geh_else *gs, int spc,
> >     dumpfile.h).  */
> > 
> >  static void
> > -dump_gimple_resx (pretty_printer *buffer, gresx *gs, int spc,
> > +dump_gimple_resx (pretty_printer *buffer, const gresx *gs, int
> > spc,
> >                   dump_flags_t flags)
> >  {
> >    if (flags & TDF_RAW)
> > @@ -1363,8 +1364,8 @@ dump_gimple_resx (pretty_printer *buffer,
> > gresx *gs, int spc,
> >  /* Dump a GIMPLE_EH_DISPATCH tuple on the pretty_printer
> > BUFFER.  */
> > 
> >  static void
> > -dump_gimple_eh_dispatch (pretty_printer *buffer, geh_dispatch *gs,
> > int spc,
> > -                        dump_flags_t flags)
> > +dump_gimple_eh_dispatch (pretty_printer *buffer, const
> > geh_dispatch *gs,
> > +                        int spc, dump_flags_t flags)
> >  {
> >    if (flags & TDF_RAW)
> >      dump_gimple_fmt (buffer, spc, flags, "%G <%d>", gs,
> > @@ -1379,7 +1380,7 @@ dump_gimple_eh_dispatch (pretty_printer
> > *buffer, geh_dispatch *gs, int spc,
> >     in dumpfile.h).  */
> > 
> >  static void
> > -dump_gimple_debug (pretty_printer *buffer, gdebug *gs, int spc,
> > +dump_gimple_debug (pretty_printer *buffer, const gdebug *gs, int
> > spc,
> >                    dump_flags_t flags)
> >  {
> >    switch (gs->subcode)
> > @@ -1433,7 +1434,7 @@ dump_gimple_debug (pretty_printer *buffer,
> > gdebug *gs, int spc,
> > 
> >  /* Dump a GIMPLE_OMP_FOR tuple on the pretty_printer BUFFER.  */
> >  static void
> > -dump_gimple_omp_for (pretty_printer *buffer, gomp_for *gs, int
> > spc,
> > +dump_gimple_omp_for (pretty_printer *buffer, const gomp_for *gs,
> > int spc,
> >                      dump_flags_t flags)
> >  {
> >    size_t i;
> > @@ -1566,7 +1567,7 @@ dump_gimple_omp_for (pretty_printer *buffer,
> > gomp_for *gs, int spc,
> >  /* Dump a GIMPLE_OMP_CONTINUE tuple on the pretty_printer
> > BUFFER.  */
> > 
> >  static void
> > -dump_gimple_omp_continue (pretty_printer *buffer, gomp_continue
> > *gs,
> > +dump_gimple_omp_continue (pretty_printer *buffer, const
> > gomp_continue *gs,
> >                           int spc, dump_flags_t flags)
> >  {
> >    if (flags & TDF_RAW)
> > @@ -1591,7 +1592,7 @@ dump_gimple_omp_continue (pretty_printer
> > *buffer, gomp_continue *gs,
> >  /* Dump a GIMPLE_OMP_SINGLE tuple on the pretty_printer
> > BUFFER.  */
> > 
> >  static void
> > -dump_gimple_omp_single (pretty_printer *buffer, gomp_single *gs,
> > +dump_gimple_omp_single (pretty_printer *buffer, const gomp_single
> > *gs,
> >                         int spc, dump_flags_t flags)
> >  {
> >    if (flags & TDF_RAW)
> > @@ -1620,7 +1621,7 @@ dump_gimple_omp_single (pretty_printer
> > *buffer, gomp_single *gs,
> >  /* Dump a GIMPLE_OMP_TASKGROUP tuple on the pretty_printer
> > BUFFER.  */
> > 
> >  static void
> > -dump_gimple_omp_taskgroup (pretty_printer *buffer, gimple *gs,
> > +dump_gimple_omp_taskgroup (pretty_printer *buffer, const gimple
> > *gs,
> >                            int spc, dump_flags_t flags)
> >  {
> >    if (flags & TDF_RAW)
> > @@ -1649,7 +1650,7 @@ dump_gimple_omp_taskgroup (pretty_printer
> > *buffer, gimple *gs,
> >  /* Dump a GIMPLE_OMP_TARGET tuple on the pretty_printer
> > BUFFER.  */
> > 
> >  static void
> > -dump_gimple_omp_target (pretty_printer *buffer, gomp_target *gs,
> > +dump_gimple_omp_target (pretty_printer *buffer, const gomp_target
> > *gs,
> >                         int spc, dump_flags_t flags)
> >  {
> >    const char *kind;
> > @@ -1742,7 +1743,7 @@ dump_gimple_omp_target (pretty_printer
> > *buffer, gomp_target *gs,
> >  /* Dump a GIMPLE_OMP_TEAMS tuple on the pretty_printer BUFFER.  */
> > 
> >  static void
> > -dump_gimple_omp_teams (pretty_printer *buffer, gomp_teams *gs, int
> > spc,
> > +dump_gimple_omp_teams (pretty_printer *buffer, const gomp_teams
> > *gs, int spc,
> >                        dump_flags_t flags)
> >  {
> >    if (flags & TDF_RAW)
> > @@ -1771,7 +1772,7 @@ dump_gimple_omp_teams (pretty_printer
> > *buffer, gomp_teams *gs, int spc,
> >  /* Dump a GIMPLE_OMP_SECTIONS tuple on the pretty_printer
> > BUFFER.  */
> > 
> >  static void
> > -dump_gimple_omp_sections (pretty_printer *buffer, gomp_sections
> > *gs,
> > +dump_gimple_omp_sections (pretty_printer *buffer, const
> > gomp_sections *gs,
> >                           int spc, dump_flags_t flags)
> >  {
> >    if (flags & TDF_RAW)
> > @@ -1808,7 +1809,7 @@ dump_gimple_omp_sections (pretty_printer
> > *buffer, gomp_sections *gs,
> >     pretty_printer BUFFER.  */
> > 
> >  static void
> > -dump_gimple_omp_block (pretty_printer *buffer, gimple *gs, int
> > spc,
> > +dump_gimple_omp_block (pretty_printer *buffer, const gimple *gs,
> > int spc,
> >                        dump_flags_t flags)
> >  {
> >    if (flags & TDF_RAW)
> > @@ -1845,7 +1846,7 @@ dump_gimple_omp_block (pretty_printer
> > *buffer, gimple *gs, int spc,
> >  /* Dump a GIMPLE_OMP_CRITICAL tuple on the pretty_printer
> > BUFFER.  */
> > 
> >  static void
> > -dump_gimple_omp_critical (pretty_printer *buffer, gomp_critical
> > *gs,
> > +dump_gimple_omp_critical (pretty_printer *buffer, const
> > gomp_critical *gs,
> >                           int spc, dump_flags_t flags)
> >  {
> >    if (flags & TDF_RAW)
> > @@ -1877,7 +1878,7 @@ dump_gimple_omp_critical (pretty_printer
> > *buffer, gomp_critical *gs,
> >  /* Dump a GIMPLE_OMP_ORDERED tuple on the pretty_printer
> > BUFFER.  */
> > 
> >  static void
> > -dump_gimple_omp_ordered (pretty_printer *buffer, gomp_ordered *gs,
> > +dump_gimple_omp_ordered (pretty_printer *buffer, const
> > gomp_ordered *gs,
> >                          int spc, dump_flags_t flags)
> >  {
> >    if (flags & TDF_RAW)
> > @@ -1902,7 +1903,7 @@ dump_gimple_omp_ordered (pretty_printer
> > *buffer, gomp_ordered *gs,
> >  /* Dump a GIMPLE_OMP_SCAN tuple on the pretty_printer BUFFER.  */
> > 
> >  static void
> > -dump_gimple_omp_scan (pretty_printer *buffer, gomp_scan *gs,
> > +dump_gimple_omp_scan (pretty_printer *buffer, const gomp_scan *gs,
> >                       int spc, dump_flags_t flags)
> >  {
> >    if (flags & TDF_RAW)
> > @@ -1930,7 +1931,7 @@ dump_gimple_omp_scan (pretty_printer *buffer,
> > gomp_scan *gs,
> >  /* Dump a GIMPLE_OMP_RETURN tuple on the pretty_printer
> > BUFFER.  */
> > 
> >  static void
> > -dump_gimple_omp_return (pretty_printer *buffer, gimple *gs, int
> > spc,
> > +dump_gimple_omp_return (pretty_printer *buffer, const gimple *gs,
> > int spc,
> >                         dump_flags_t flags)
> >  {
> >    if (flags & TDF_RAW)
> > @@ -1961,7 +1962,7 @@ dump_gimple_omp_return (pretty_printer
> > *buffer, gimple *gs, int spc,
> >  /* Dump a GIMPLE_TRANSACTION tuple on the pretty_printer
> > BUFFER.  */
> > 
> >  static void
> > -dump_gimple_transaction (pretty_printer *buffer, gtransaction *gs,
> > +dump_gimple_transaction (pretty_printer *buffer, const
> > gtransaction *gs,
> >                          int spc, dump_flags_t flags)
> >  {
> >    unsigned subcode = gimple_transaction_subcode (gs);
> > @@ -2063,7 +2064,8 @@ dump_gimple_transaction (pretty_printer
> > *buffer, gtransaction *gs,
> >     dumpfile.h).  */
> > 
> >  static void
> > -dump_gimple_asm (pretty_printer *buffer, gasm *gs, int spc,
> > dump_flags_t flags)
> > +dump_gimple_asm (pretty_printer *buffer, const gasm *gs, int spc,
> > +                dump_flags_t flags)
> >  {
> >    unsigned int i, n, f, fields;
> > 
> > @@ -2282,7 +2284,7 @@ dump_ssaname_info_to_file (FILE *file, tree
> > node, int spc)
> >     pretty printer.  If COMMENT is true, print this after #.  */
> > 
> >  static void
> > -dump_gimple_phi (pretty_printer *buffer, gphi *phi, int spc, bool
> > comment,
> > +dump_gimple_phi (pretty_printer *buffer, const gphi *phi, int spc,
> > bool comment,
> >                  dump_flags_t flags)
> >  {
> >    size_t i;
> > @@ -2339,7 +2341,7 @@ dump_gimple_phi (pretty_printer *buffer, gphi
> > *phi, int spc, bool comment,
> >     dumpfile.h).  */
> > 
> >  static void
> > -dump_gimple_omp_parallel (pretty_printer *buffer, gomp_parallel
> > *gs,
> > +dump_gimple_omp_parallel (pretty_printer *buffer, const
> > gomp_parallel *gs,
> >                           int spc, dump_flags_t flags)
> >  {
> >    if (flags & TDF_RAW)
> > @@ -2393,7 +2395,7 @@ dump_gimple_omp_parallel (pretty_printer
> > *buffer, gomp_parallel *gs,
> >     dumpfile.h).  */
> > 
> >  static void
> > -dump_gimple_omp_task (pretty_printer *buffer, gomp_task *gs, int
> > spc,
> > +dump_gimple_omp_task (pretty_printer *buffer, const gomp_task *gs,
> > int spc,
> >                       dump_flags_t flags)
> >  {
> >    if (flags & TDF_RAW)
> > @@ -2455,7 +2457,7 @@ dump_gimple_omp_task (pretty_printer *buffer,
> > gomp_task *gs, int spc,
> >     in dumpfile.h).  */
> > 
> >  static void
> > -dump_gimple_omp_atomic_load (pretty_printer *buffer,
> > gomp_atomic_load *gs,
> > +dump_gimple_omp_atomic_load (pretty_printer *buffer, const
> > gomp_atomic_load *gs,
> >                              int spc, dump_flags_t flags)
> >  {
> >    if (flags & TDF_RAW)
> > @@ -2489,7 +2491,7 @@ dump_gimple_omp_atomic_load (pretty_printer
> > *buffer, gomp_atomic_load *gs,
> > 
> >  static void
> >  dump_gimple_omp_atomic_store (pretty_printer *buffer,
> > -                             gomp_atomic_store *gs, int spc,
> > +                             const gomp_atomic_store *gs, int spc,
> >                               dump_flags_t flags)
> >  {
> >    if (flags & TDF_RAW)
> > @@ -2517,7 +2519,7 @@ dump_gimple_omp_atomic_store (pretty_printer
> > *buffer,
> >     FLAGS are as in pp_gimple_stmt_1.  */
> > 
> >  static void
> > -dump_gimple_mem_ops (pretty_printer *buffer, gimple *gs, int spc,
> > +dump_gimple_mem_ops (pretty_printer *buffer, const gimple *gs, int
> > spc,
> >                      dump_flags_t flags)
> >  {
> >    tree vdef = gimple_vdef (gs);
> > @@ -2548,14 +2550,14 @@ dump_gimple_mem_ops (pretty_printer
> > *buffer, gimple *gs, int spc,
> >     pp_flush on BUFFER to finalize the pretty printer.  */
> > 
> >  void
> > -pp_gimple_stmt_1 (pretty_printer *buffer, gimple *gs, int spc,
> > +pp_gimple_stmt_1 (pretty_printer *buffer, const gimple *gs, int
> > spc,
> >                   dump_flags_t flags)
> >  {
> >    if (!gs)
> >      return;
> > 
> >    if (flags & TDF_STMTADDR)
> > -    pp_printf (buffer, "<&%p> ", (void *) gs);
> > +    pp_printf (buffer, "<&%p> ", (const void *) gs);
> > 
> >    if ((flags & TDF_LINENO) && gimple_has_location (gs))
> >      dump_location (buffer, gimple_location (gs));
> > @@ -2580,31 +2582,31 @@ pp_gimple_stmt_1 (pretty_printer *buffer,
> > gimple *gs, int spc,
> >    switch (gimple_code (gs))
> >      {
> >      case GIMPLE_ASM:
> > -      dump_gimple_asm (buffer, as_a <gasm *> (gs), spc, flags);
> > +      dump_gimple_asm (buffer, as_a <const gasm *> (gs), spc,
> > flags);
> >        break;
> > 
> >      case GIMPLE_ASSIGN:
> > -      dump_gimple_assign (buffer, as_a <gassign *> (gs), spc,
> > flags);
> > +      dump_gimple_assign (buffer, as_a <const gassign *> (gs),
> > spc, flags);
> >        break;
> > 
> >      case GIMPLE_BIND:
> > -      dump_gimple_bind (buffer, as_a <gbind *> (gs), spc, flags);
> > +      dump_gimple_bind (buffer, as_a <const gbind *> (gs), spc,
> > flags);
> >        break;
> > 
> >      case GIMPLE_CALL:
> > -      dump_gimple_call (buffer, as_a <gcall *> (gs), spc, flags);
> > +      dump_gimple_call (buffer, as_a <const gcall *> (gs), spc,
> > flags);
> >        break;
> > 
> >      case GIMPLE_COND:
> > -      dump_gimple_cond (buffer, as_a <gcond *> (gs), spc, flags);
> > +      dump_gimple_cond (buffer, as_a <const gcond *> (gs), spc,
> > flags);
> >        break;
> > 
> >      case GIMPLE_LABEL:
> > -      dump_gimple_label (buffer, as_a <glabel *> (gs), spc,
> > flags);
> > +      dump_gimple_label (buffer, as_a <const glabel *> (gs), spc,
> > flags);
> >        break;
> > 
> >      case GIMPLE_GOTO:
> > -      dump_gimple_goto (buffer, as_a <ggoto *> (gs), spc, flags);
> > +      dump_gimple_goto (buffer, as_a <const ggoto *> (gs), spc,
> > flags);
> >        break;
> > 
> >      case GIMPLE_NOP:
> > @@ -2612,62 +2614,62 @@ pp_gimple_stmt_1 (pretty_printer *buffer,
> > gimple *gs, int spc,
> >        break;
> > 
> >      case GIMPLE_RETURN:
> > -      dump_gimple_return (buffer, as_a <greturn *> (gs), spc,
> > flags);
> > +      dump_gimple_return (buffer, as_a <const greturn *> (gs),
> > spc, flags);
> >        break;
> > 
> >      case GIMPLE_SWITCH:
> > -      dump_gimple_switch (buffer, as_a <gswitch *> (gs), spc,
> > flags);
> > +      dump_gimple_switch (buffer, as_a <const gswitch *> (gs),
> > spc, flags);
> >        break;
> > 
> >      case GIMPLE_TRY:
> > -      dump_gimple_try (buffer, as_a <gtry *> (gs), spc, flags);
> > +      dump_gimple_try (buffer, as_a <const gtry *> (gs), spc,
> > flags);
> >        break;
> > 
> >      case GIMPLE_PHI:
> > -      dump_gimple_phi (buffer, as_a <gphi *> (gs), spc, false,
> > flags);
> > +      dump_gimple_phi (buffer, as_a <const gphi *> (gs), spc,
> > false, flags);
> >        break;
> > 
> >      case GIMPLE_OMP_PARALLEL:
> > -      dump_gimple_omp_parallel (buffer, as_a <gomp_parallel *>
> > (gs), spc,
> > +      dump_gimple_omp_parallel (buffer, as_a <const gomp_parallel
> > *> (gs), spc,
> >                                 flags);
> >        break;
> > 
> >      case GIMPLE_OMP_TASK:
> > -      dump_gimple_omp_task (buffer, as_a <gomp_task *> (gs), spc,
> > flags);
> > +      dump_gimple_omp_task (buffer, as_a <const gomp_task *> (gs),
> > spc, flags);
> >        break;
> > 
> >      case GIMPLE_OMP_ATOMIC_LOAD:
> > -      dump_gimple_omp_atomic_load (buffer, as_a <gomp_atomic_load
> > *> (gs),
> > +      dump_gimple_omp_atomic_load (buffer, as_a <const
> > gomp_atomic_load *> (gs),
> >                                    spc, flags);
> >        break;
> > 
> >      case GIMPLE_OMP_ATOMIC_STORE:
> >        dump_gimple_omp_atomic_store (buffer,
> > -                                   as_a <gomp_atomic_store *>
> > (gs),
> > +                                   as_a <const gomp_atomic_store
> > *> (gs),
> >                                     spc, flags);
> >        break;
> > 
> >      case GIMPLE_OMP_FOR:
> > -      dump_gimple_omp_for (buffer, as_a <gomp_for *> (gs), spc,
> > flags);
> > +      dump_gimple_omp_for (buffer, as_a <const gomp_for *> (gs),
> > spc, flags);
> >        break;
> > 
> >      case GIMPLE_OMP_CONTINUE:
> > -      dump_gimple_omp_continue (buffer, as_a <gomp_continue *>
> > (gs), spc,
> > +      dump_gimple_omp_continue (buffer, as_a <const gomp_continue
> > *> (gs), spc,
> >                                 flags);
> >        break;
> > 
> >      case GIMPLE_OMP_SINGLE:
> > -      dump_gimple_omp_single (buffer, as_a <gomp_single *> (gs),
> > spc,
> > +      dump_gimple_omp_single (buffer, as_a <const gomp_single *>
> > (gs), spc,
> >                               flags);
> >        break;
> > 
> >      case GIMPLE_OMP_TARGET:
> > -      dump_gimple_omp_target (buffer, as_a <gomp_target *> (gs),
> > spc,
> > +      dump_gimple_omp_target (buffer, as_a <const gomp_target *>
> > (gs), spc,
> >                               flags);
> >        break;
> > 
> >      case GIMPLE_OMP_TEAMS:
> > -      dump_gimple_omp_teams (buffer, as_a <gomp_teams *> (gs),
> > spc,
> > +      dump_gimple_omp_teams (buffer, as_a <const gomp_teams *>
> > (gs), spc,
> >                              flags);
> >        break;
> > 
> > @@ -2676,7 +2678,7 @@ pp_gimple_stmt_1 (pretty_printer *buffer,
> > gimple *gs, int spc,
> >        break;
> > 
> >      case GIMPLE_OMP_SECTIONS:
> > -      dump_gimple_omp_sections (buffer, as_a <gomp_sections *>
> > (gs),
> > +      dump_gimple_omp_sections (buffer, as_a <const gomp_sections
> > *> (gs),
> >                                 spc, flags);
> >        break;
> > 
> > @@ -2695,49 +2697,50 @@ pp_gimple_stmt_1 (pretty_printer *buffer,
> > gimple *gs, int spc,
> >        break;
> > 
> >      case GIMPLE_OMP_ORDERED:
> > -      dump_gimple_omp_ordered (buffer, as_a <gomp_ordered *> (gs),
> > spc,
> > +      dump_gimple_omp_ordered (buffer, as_a <const gomp_ordered *>
> > (gs), spc,
> >                                flags);
> >        break;
> > 
> >      case GIMPLE_OMP_SCAN:
> > -      dump_gimple_omp_scan (buffer, as_a <gomp_scan *> (gs), spc,
> > +      dump_gimple_omp_scan (buffer, as_a <const gomp_scan *> (gs),
> > spc,
> >                             flags);
> >        break;
> > 
> >      case GIMPLE_OMP_CRITICAL:
> > -      dump_gimple_omp_critical (buffer, as_a <gomp_critical *>
> > (gs), spc,
> > +      dump_gimple_omp_critical (buffer, as_a <const gomp_critical
> > *> (gs), spc,
> >                                 flags);
> >        break;
> > 
> >      case GIMPLE_CATCH:
> > -      dump_gimple_catch (buffer, as_a <gcatch *> (gs), spc,
> > flags);
> > +      dump_gimple_catch (buffer, as_a <const gcatch *> (gs), spc,
> > flags);
> >        break;
> > 
> >      case GIMPLE_EH_FILTER:
> > -      dump_gimple_eh_filter (buffer, as_a <geh_filter *> (gs),
> > spc, flags);
> > +      dump_gimple_eh_filter (buffer, as_a <const geh_filter *>
> > (gs), spc,
> > +                            flags);
> >        break;
> > 
> >      case GIMPLE_EH_MUST_NOT_THROW:
> >        dump_gimple_eh_must_not_throw (buffer,
> > -                                    as_a <geh_mnt *> (gs),
> > +                                    as_a <const geh_mnt *> (gs),
> >                                      spc, flags);
> >        break;
> > 
> >      case GIMPLE_EH_ELSE:
> > -      dump_gimple_eh_else (buffer, as_a <geh_else *> (gs), spc,
> > flags);
> > +      dump_gimple_eh_else (buffer, as_a <const geh_else *> (gs),
> > spc, flags);
> >        break;
> > 
> >      case GIMPLE_RESX:
> > -      dump_gimple_resx (buffer, as_a <gresx *> (gs), spc, flags);
> > +      dump_gimple_resx (buffer, as_a <const gresx *> (gs), spc,
> > flags);
> >        break;
> > 
> >      case GIMPLE_EH_DISPATCH:
> > -      dump_gimple_eh_dispatch (buffer, as_a <geh_dispatch *> (gs),
> > spc,
> > +      dump_gimple_eh_dispatch (buffer, as_a <const geh_dispatch *>
> > (gs), spc,
> >                                flags);
> >        break;
> > 
> >      case GIMPLE_DEBUG:
> > -      dump_gimple_debug (buffer, as_a <gdebug *> (gs), spc,
> > flags);
> > +      dump_gimple_debug (buffer, as_a <const gdebug *> (gs), spc,
> > flags);
> >        break;
> > 
> >      case GIMPLE_PREDICT:
> > @@ -2751,7 +2754,7 @@ pp_gimple_stmt_1 (pretty_printer *buffer,
> > gimple *gs, int spc,
> >        break;
> > 
> >      case GIMPLE_TRANSACTION:
> > -      dump_gimple_transaction (buffer, as_a <gtransaction *> (gs),
> > spc,
> > +      dump_gimple_transaction (buffer, as_a <const gtransaction *>
> > (gs), spc,
> >                                flags);
> >        break;
> > 
> > diff --git a/gcc/gimple-pretty-print.h b/gcc/gimple-pretty-print.h
> > index 8d8e105..186f2cf 100644
> > --- a/gcc/gimple-pretty-print.h
> > +++ b/gcc/gimple-pretty-print.h
> > @@ -31,7 +31,8 @@ extern void print_gimple_stmt (FILE *, gimple *,
> > int, dump_flags_t = TDF_NONE);
> >  extern void debug (gimple &ref);
> >  extern void debug (gimple *ptr);
> >  extern void print_gimple_expr (FILE *, gimple *, int, dump_flags_t
> > = TDF_NONE);
> > -extern void pp_gimple_stmt_1 (pretty_printer *, gimple *, int,
> > dump_flags_t);
> > +extern void pp_gimple_stmt_1 (pretty_printer *, const gimple *,
> > int,
> > +                             dump_flags_t);
> >  extern void gimple_dump_bb (FILE *, basic_block, int,
> > dump_flags_t);
> >  extern void gimple_dump_bb_for_graph (pretty_printer *,
> > basic_block);
> >  extern void dump_ssaname_info_to_file (FILE *, tree, int);
> > diff --git a/gcc/gimple.h b/gcc/gimple.h
> > index cf1f8da..10a4b8b 100644
> > --- a/gcc/gimple.h
> > +++ b/gcc/gimple.h
> > @@ -1017,6 +1017,14 @@ is_a_helper <gdebug *>::test (gimple *gs)
> >  template <>
> >  template <>
> >  inline bool
> > +is_a_helper <const gdebug *>::test (const gimple *gs)
> > +{
> > +  return gs->code == GIMPLE_DEBUG;
> > +}
> > +
> > +template <>
> > +template <>
> > +inline bool
> >  is_a_helper <ggoto *>::test (gimple *gs)
> >  {
> >    return gs->code == GIMPLE_GOTO;
> > @@ -1025,6 +1033,14 @@ is_a_helper <ggoto *>::test (gimple *gs)
> >  template <>
> >  template <>
> >  inline bool
> > +is_a_helper <const ggoto *>::test (const gimple *gs)
> > +{
> > +  return gs->code == GIMPLE_GOTO;
> > +}
> > +
> > +template <>
> > +template <>
> > +inline bool
> >  is_a_helper <glabel *>::test (gimple *gs)
> >  {
> >    return gs->code == GIMPLE_LABEL;
> > @@ -1033,6 +1049,14 @@ is_a_helper <glabel *>::test (gimple *gs)
> >  template <>
> >  template <>
> >  inline bool
> > +is_a_helper <const glabel *>::test (const gimple *gs)
> > +{
> > +  return gs->code == GIMPLE_LABEL;
> > +}
> > +
> > +template <>
> > +template <>
> > +inline bool
> >  is_a_helper <gresx *>::test (gimple *gs)
> >  {
> >    return gs->code == GIMPLE_RESX;
> > @@ -1057,6 +1081,14 @@ is_a_helper <geh_else *>::test (gimple *gs)
> >  template <>
> >  template <>
> >  inline bool
> > +is_a_helper <const geh_else *>::test (const gimple *gs)
> > +{
> > +  return gs->code == GIMPLE_EH_ELSE;
> > +}
> > +
> > +template <>
> > +template <>
> > +inline bool
> >  is_a_helper <geh_filter *>::test (gimple *gs)
> >  {
> >    return gs->code == GIMPLE_EH_FILTER;
> > @@ -1073,6 +1105,14 @@ is_a_helper <geh_mnt *>::test (gimple *gs)
> >  template <>
> >  template <>
> >  inline bool
> > +is_a_helper <const geh_mnt *>::test (const gimple *gs)
> > +{
> > +  return gs->code == GIMPLE_EH_MUST_NOT_THROW;
> > +}
> > +
> > +template <>
> > +template <>
> > +inline bool
> >  is_a_helper <gomp_atomic_load *>::test (gimple *gs)
> >  {
> >    return gs->code == GIMPLE_OMP_ATOMIC_LOAD;
> > @@ -1219,6 +1259,14 @@ is_a_helper <gswitch *>::test (gimple *gs)
> >  template <>
> >  template <>
> >  inline bool
> > +is_a_helper <const gswitch *>::test (const gimple *gs)
> > +{
> > +  return gs->code == GIMPLE_SWITCH;
> > +}
> > +
> > +template <>
> > +template <>
> > +inline bool
> >  is_a_helper <gtransaction *>::test (gimple *gs)
> >  {
> >    return gs->code == GIMPLE_TRANSACTION;
> > @@ -1235,6 +1283,14 @@ is_a_helper <gtry *>::test (gimple *gs)
> >  template <>
> >  template <>
> >  inline bool
> > +is_a_helper <const gtry *>::test (const gimple *gs)
> > +{
> > +  return gs->code == GIMPLE_TRY;
> > +}
> > +
> > +template <>
> > +template <>
> > +inline bool
> >  is_a_helper <gimple_statement_wce *>::test (gimple *gs)
> >  {
> >    return gs->code == GIMPLE_WITH_CLEANUP_EXPR;
> > @@ -1429,6 +1485,14 @@ is_a_helper <const gphi *>::test (const
> > gimple *gs)
> >  template <>
> >  template <>
> >  inline bool
> > +is_a_helper <const greturn *>::test (const gimple *gs)
> > +{
> > +  return gs->code == GIMPLE_RETURN;
> > +}
> > +
> > +template <>
> > +template <>
> > +inline bool
> >  is_a_helper <const gtransaction *>::test (const gimple *gs)
> >  {
> >    return gs->code == GIMPLE_TRANSACTION;
> > @@ -3254,7 +3318,7 @@ gimple_call_set_tail (gcall *s, bool tail_p)
> >  /* Return true if GIMPLE_CALL S is marked as a tail call.  */
> > 
> >  static inline bool
> > -gimple_call_tail_p (gcall *s)
> > +gimple_call_tail_p (const gcall *s)
> >  {
> >    return (s->subcode & GF_CALL_TAILCALL) != 0;
> >  }
> > @@ -3296,7 +3360,7 @@ gimple_call_set_return_slot_opt (gcall *s,
> > bool return_slot_opt_p)
> >  /* Return true if S is marked for return slot optimization.  */
> > 
> >  static inline bool
> > -gimple_call_return_slot_opt_p (gcall *s)
> > +gimple_call_return_slot_opt_p (const gcall *s)
> >  {
> >    return (s->subcode & GF_CALL_RETURN_SLOT_OPT) != 0;
> >  }
> > @@ -3341,7 +3405,7 @@ gimple_call_set_va_arg_pack (gcall *s, bool
> > pass_arg_pack_p)
> >     argument pack in its argument list.  */
> > 
> >  static inline bool
> > -gimple_call_va_arg_pack_p (gcall *s)
> > +gimple_call_va_arg_pack_p (const gcall *s)
> >  {
> >    return (s->subcode & GF_CALL_VA_ARG_PACK) != 0;
> >  }
> > @@ -3443,6 +3507,13 @@ gimple_call_use_set (gcall *call_stmt)
> >    return &call_stmt->call_used;
> >  }
> > 
> > +/* As above, but const.  */
> > +
> > +static inline const pt_solution *
> > +gimple_call_use_set (const gcall *call_stmt)
> > +{
> > +  return &call_stmt->call_used;
> > +}
> > 
> >  /* Return a pointer to the points-to solution for the set of call-
> > used
> >     variables of the call CALL_STMT.  */
> > @@ -3453,16 +3524,24 @@ gimple_call_clobber_set (gcall *call_stmt)
> >    return &call_stmt->call_clobbered;
> >  }
> > 
> > +/* As above, but const.  */
> > +
> > +static inline const pt_solution *
> > +gimple_call_clobber_set (const gcall *call_stmt)
> > +{
> > +  return &call_stmt->call_clobbered;
> > +}
> > +
> > 
> >  /* Returns true if this is a GIMPLE_ASSIGN or a GIMPLE_CALL with a
> >     non-NULL lhs.  */
> > 
> >  static inline bool
> > -gimple_has_lhs (gimple *stmt)
> > +gimple_has_lhs (const gimple *stmt)
> >  {
> >    if (is_gimple_assign (stmt))
> >      return true;
> > -  if (gcall *call = dyn_cast <gcall *> (stmt))
> > +  if (const gcall *call = dyn_cast <const gcall *> (stmt))
> >      return gimple_call_lhs (call) != NULL_TREE;
> >    return false;
> >  }
> > @@ -3760,9 +3839,9 @@ gimple_bind_body_ptr (gbind *bind_stmt)
> >  /* Return the GIMPLE sequence contained in the GIMPLE_BIND
> > statement GS.  */
> > 
> >  static inline gimple_seq
> > -gimple_bind_body (gbind *gs)
> > +gimple_bind_body (const gbind *gs)
> >  {
> > -  return *gimple_bind_body_ptr (gs);
> > +  return *gimple_bind_body_ptr (const_cast <gbind *> (gs));
> >  }
> > 
> > 
> > @@ -4034,9 +4113,9 @@ gimple_catch_handler_ptr (gcatch *catch_stmt)
> >     GIMPLE_CATCH statement CATCH_STMT.  */
> > 
> >  static inline gimple_seq
> > -gimple_catch_handler (gcatch *catch_stmt)
> > +gimple_catch_handler (const gcatch *catch_stmt)
> >  {
> > -  return *gimple_catch_handler_ptr (catch_stmt);
> > +  return *gimple_catch_handler_ptr (const_cast <gcatch *>
> > (catch_stmt));
> >  }
> > 
> > 
> > @@ -4094,9 +4173,9 @@ gimple_eh_filter_failure_ptr (gimple *gs)
> >     statement fails.  */
> > 
> >  static inline gimple_seq
> > -gimple_eh_filter_failure (gimple *gs)
> > +gimple_eh_filter_failure (const gimple *gs)
> >  {
> > -  return *gimple_eh_filter_failure_ptr (gs);
> > +  return *gimple_eh_filter_failure_ptr (const_cast <gimple *>
> > (gs));
> >  }
> > 
> > 
> > @@ -4123,7 +4202,7 @@ gimple_eh_filter_set_failure (geh_filter
> > *eh_filter_stmt,
> >  /* Get the function decl to be called by the MUST_NOT_THROW
> > region.  */
> > 
> >  static inline tree
> > -gimple_eh_must_not_throw_fndecl (geh_mnt *eh_mnt_stmt)
> > +gimple_eh_must_not_throw_fndecl (const geh_mnt *eh_mnt_stmt)
> >  {
> >    return eh_mnt_stmt->fndecl;
> >  }
> > @@ -4146,9 +4225,9 @@ gimple_eh_else_n_body_ptr (geh_else
> > *eh_else_stmt)
> >  }
> > 
> >  static inline gimple_seq
> > -gimple_eh_else_n_body (geh_else *eh_else_stmt)
> > +gimple_eh_else_n_body (const geh_else *eh_else_stmt)
> >  {
> > -  return *gimple_eh_else_n_body_ptr (eh_else_stmt);
> > +  return *gimple_eh_else_n_body_ptr (const_cast <geh_else *>
> > (eh_else_stmt));
> >  }
> > 
> >  static inline gimple_seq *
> > @@ -4158,9 +4237,9 @@ gimple_eh_else_e_body_ptr (geh_else
> > *eh_else_stmt)
> >  }
> > 
> >  static inline gimple_seq
> > -gimple_eh_else_e_body (geh_else *eh_else_stmt)
> > +gimple_eh_else_e_body (const geh_else *eh_else_stmt)
> >  {
> > -  return *gimple_eh_else_e_body_ptr (eh_else_stmt);
> > +  return *gimple_eh_else_e_body_ptr (const_cast <geh_else *>
> > (eh_else_stmt));
> >  }
> > 
> >  static inline void
> > @@ -4224,9 +4303,9 @@ gimple_try_eval_ptr (gimple *gs)
> >  /* Return the sequence of statements used as the body for
> > GIMPLE_TRY GS.  */
> > 
> >  static inline gimple_seq
> > -gimple_try_eval (gimple *gs)
> > +gimple_try_eval (const gimple *gs)
> >  {
> > -  return *gimple_try_eval_ptr (gs);
> > +  return *gimple_try_eval_ptr (const_cast <gimple *> (gs));
> >  }
> > 
> > 
> > @@ -4245,9 +4324,9 @@ gimple_try_cleanup_ptr (gimple *gs)
> >     GIMPLE_TRY GS.  */
> > 
> >  static inline gimple_seq
> > -gimple_try_cleanup (gimple *gs)
> > +gimple_try_cleanup (const gimple *gs)
> >  {
> > -  return *gimple_try_cleanup_ptr (gs);
> > +  return *gimple_try_cleanup_ptr (const_cast <gimple *> (gs));
> >  }
> > 
> > 
> > @@ -4406,6 +4485,13 @@ gimple_phi_arg (gphi *gs, unsigned index)
> >    return &(gs->args[index]);
> >  }
> > 
> > +static inline const phi_arg_d *
> > +gimple_phi_arg (const gphi *gs, unsigned index)
> > +{
> > +  gcc_gimple_checking_assert (index < gs->nargs);
> > +  return &(gs->args[index]);
> > +}
> > +
> >  static inline struct phi_arg_d *
> >  gimple_phi_arg (gimple *gs, unsigned index)
> >  {
> > @@ -4445,7 +4531,7 @@ phi_nodes_ptr (basic_block bb)
> >  /* Return the tree operand for argument I of PHI node GS.  */
> > 
> >  static inline tree
> > -gimple_phi_arg_def (gphi *gs, size_t index)
> > +gimple_phi_arg_def (const gphi *gs, size_t index)
> >  {
> >    return gimple_phi_arg (gs, index)->def;
> >  }
> > @@ -4468,7 +4554,7 @@ gimple_phi_arg_def_ptr (gphi *phi, size_t
> > index)
> >  /* Return the edge associated with argument I of phi node PHI.  */
> > 
> >  static inline edge
> > -gimple_phi_arg_edge (gphi *phi, size_t i)
> > +gimple_phi_arg_edge (const gphi *phi, size_t i)
> >  {
> >    return EDGE_PRED (gimple_bb (phi), i);
> >  }
> > @@ -4476,7 +4562,7 @@ gimple_phi_arg_edge (gphi *phi, size_t i)
> >  /* Return the source location of gimple argument I of phi node
> > PHI.  */
> > 
> >  static inline location_t
> > -gimple_phi_arg_location (gphi *phi, size_t i)
> > +gimple_phi_arg_location (const gphi *phi, size_t i)
> >  {
> >    return gimple_phi_arg (phi, i)->locus;
> >  }
> > @@ -4500,7 +4586,7 @@ gimple_phi_arg_set_location (gphi *phi,
> > size_t i, location_t loc)
> >  /* Return TRUE if argument I of phi node PHI has a location
> > record.  */
> > 
> >  static inline bool
> > -gimple_phi_arg_has_location (gphi *phi, size_t i)
> > +gimple_phi_arg_has_location (const gphi *phi, size_t i)
> >  {
> >    return gimple_phi_arg_location (phi, i) != UNKNOWN_LOCATION;
> >  }
> > @@ -4668,7 +4754,7 @@ gimple_debug_bind_p (const gimple *s)
> >  /* Return the variable bound in a GIMPLE_DEBUG bind statement.  */
> > 
> >  static inline tree
> > -gimple_debug_bind_get_var (gimple *dbg)
> > +gimple_debug_bind_get_var (const gimple *dbg)
> >  {
> >    GIMPLE_CHECK (dbg, GIMPLE_DEBUG);
> >    gcc_gimple_checking_assert (gimple_debug_bind_p (dbg));
> > @@ -4679,7 +4765,7 @@ gimple_debug_bind_get_var (gimple *dbg)
> >     statement.  */
> > 
> >  static inline tree
> > -gimple_debug_bind_get_value (gimple *dbg)
> > +gimple_debug_bind_get_value (const gimple *dbg)
> >  {
> >    GIMPLE_CHECK (dbg, GIMPLE_DEBUG);
> >    gcc_gimple_checking_assert (gimple_debug_bind_p (dbg));
> > @@ -4760,7 +4846,7 @@ gimple_debug_source_bind_p (const gimple *s)
> >  /* Return the variable bound in a GIMPLE_DEBUG source bind
> > statement.  */
> > 
> >  static inline tree
> > -gimple_debug_source_bind_get_var (gimple *dbg)
> > +gimple_debug_source_bind_get_var (const gimple *dbg)
> >  {
> >    GIMPLE_CHECK (dbg, GIMPLE_DEBUG);
> >    gcc_gimple_checking_assert (gimple_debug_source_bind_p (dbg));
> > @@ -4771,7 +4857,7 @@ gimple_debug_source_bind_get_var (gimple
> > *dbg)
> >     statement.  */
> > 
> >  static inline tree
> > -gimple_debug_source_bind_get_value (gimple *dbg)
> > +gimple_debug_source_bind_get_value (const gimple *dbg)
> >  {
> >    GIMPLE_CHECK (dbg, GIMPLE_DEBUG);
> >    gcc_gimple_checking_assert (gimple_debug_source_bind_p (dbg));
> > @@ -4872,9 +4958,9 @@ gimple_omp_body_ptr (gimple *gs)
> >  /* Return the body for the OMP statement GS.  */
> > 
> >  static inline gimple_seq
> > -gimple_omp_body (gimple *gs)
> > +gimple_omp_body (const gimple *gs)
> >  {
> > -  return *gimple_omp_body_ptr (gs);
> > +  return *gimple_omp_body_ptr (const_cast <gimple *> (gs));
> >  }
> > 
> >  /* Set BODY to be the body for the OMP statement GS.  */
> > @@ -5139,9 +5225,9 @@ gimple_omp_for_set_clauses (gimple *gs, tree
> > clauses)
> >  /* Get the collapse count of the OMP_FOR statement GS.  */
> > 
> >  static inline size_t
> > -gimple_omp_for_collapse (gimple *gs)
> > +gimple_omp_for_collapse (const gimple *gs)
> >  {
> > -  gomp_for *omp_for_stmt = as_a <gomp_for *> (gs);
> > +  const gomp_for *omp_for_stmt = as_a <const gomp_for *> (gs);
> >    return omp_for_stmt->collapse;
> >  }
> > 
> > @@ -5316,9 +5402,9 @@ gimple_omp_for_pre_body_ptr (gimple *gs)
> >     statement GS starts.  */
> > 
> >  static inline gimple_seq
> > -gimple_omp_for_pre_body (gimple *gs)
> > +gimple_omp_for_pre_body (const gimple *gs)
> >  {
> > -  return *gimple_omp_for_pre_body_ptr (gs);
> > +  return *gimple_omp_for_pre_body_ptr (const_cast <gimple *>
> > (gs));
> >  }
> > 
> > 
> > @@ -6303,7 +6389,7 @@ gimple_transaction_body_ptr (gtransaction
> > *transaction_stmt)
> >  /* Return the body for the GIMPLE_TRANSACTION statement
> > TRANSACTION_STMT.  */
> > 
> >  static inline gimple_seq
> > -gimple_transaction_body (gtransaction *transaction_stmt)
> > +gimple_transaction_body (const gtransaction *transaction_stmt)
> >  {
> >    return transaction_stmt->body;
> >  }
> > diff --git a/gcc/tree-eh.c b/gcc/tree-eh.c
> > index 54502e6..b996f49 100644
> > --- a/gcc/tree-eh.c
> > +++ b/gcc/tree-eh.c
> > @@ -139,19 +139,19 @@ remove_stmt_from_eh_lp (gimple *t)
> >     statement is not recorded in the region table.  */
> > 
> >  int
> > -lookup_stmt_eh_lp_fn (struct function *ifun, gimple *t)
> > +lookup_stmt_eh_lp_fn (struct function *ifun, const gimple *t)
> >  {
> >    if (ifun->eh->throw_stmt_table == NULL)
> >      return 0;
> > 
> > -  int *lp_nr = ifun->eh->throw_stmt_table->get (t);
> > +  int *lp_nr = ifun->eh->throw_stmt_table->get (const_cast <gimple
> > *> (t));
> >    return lp_nr ? *lp_nr : 0;
> >  }
> > 
> >  /* Likewise, but always use the current function.  */
> > 
> >  int
> > -lookup_stmt_eh_lp (gimple *t)
> > +lookup_stmt_eh_lp (const gimple *t)
> >  {
> >    /* We can get called from initialized data when -fnon-call-
> > exceptions
> >       is on; prevent crash.  */
> > diff --git a/gcc/tree-eh.h b/gcc/tree-eh.h
> > index 511bb84..ca3b521 100644
> > --- a/gcc/tree-eh.h
> > +++ b/gcc/tree-eh.h
> > @@ -27,8 +27,8 @@ extern void using_eh_for_cleanups (void);
> >  extern void add_stmt_to_eh_lp (gimple *, int);
> >  extern bool remove_stmt_from_eh_lp_fn (struct function *, gimple
> > *);
> >  extern bool remove_stmt_from_eh_lp (gimple *);
> > -extern int lookup_stmt_eh_lp_fn (struct function *, gimple *);
> > -extern int lookup_stmt_eh_lp (gimple *);
> > +extern int lookup_stmt_eh_lp_fn (struct function *, const gimple
> > *);
> > +extern int lookup_stmt_eh_lp (const gimple *);
> >  extern bool make_eh_dispatch_edges (geh_dispatch *);
> >  extern void make_eh_edges (gimple *);
> >  extern edge redirect_eh_edge (edge, basic_block);
> > diff --git a/gcc/tree-ssa-alias.h b/gcc/tree-ssa-alias.h
> > index 6d72c2f..de85312 100644
> > --- a/gcc/tree-ssa-alias.h
> > +++ b/gcc/tree-ssa-alias.h
> > @@ -162,7 +162,7 @@ extern void dump_alias_stats (FILE *);
> > 
> >  /* In tree-ssa-structalias.c  */
> >  extern unsigned int compute_may_aliases (void);
> > -extern bool pt_solution_empty_p (struct pt_solution *);
> > +extern bool pt_solution_empty_p (const pt_solution *);
> >  extern bool pt_solution_singleton_or_null_p (struct pt_solution *,
> > unsigned *);
> >  extern bool pt_solution_includes_global (struct pt_solution *);
> >  extern bool pt_solution_includes (struct pt_solution *,
> > const_tree);
> > diff --git a/gcc/tree-ssa-structalias.c b/gcc/tree-ssa-
> > structalias.c
> > index 75c6fae..b248dfa 100644
> > --- a/gcc/tree-ssa-structalias.c
> > +++ b/gcc/tree-ssa-structalias.c
> > @@ -6801,7 +6801,7 @@ pt_solution_ior_into (struct pt_solution
> > *dest, struct pt_solution *src)
> >  /* Return true if the points-to solution *PT is empty.  */
> > 
> >  bool
> > -pt_solution_empty_p (struct pt_solution *pt)
> > +pt_solution_empty_p (const pt_solution *pt)
> >  {
> >    if (pt->anything
> >        || pt->nonlocal)
> > --
> > 1.8.5.3
> > 

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 21/49] analyzer: command-line options
  2019-12-04 18:35   ` Martin Sebor
@ 2019-12-06 18:14     ` Eric Gallager
  0 siblings, 0 replies; 160+ messages in thread
From: Eric Gallager @ 2019-12-06 18:14 UTC (permalink / raw)
  To: Martin Sebor; +Cc: David Malcolm, gcc-patches

On 12/4/19, Martin Sebor <msebor@gmail.com> wrote:
> On 11/15/19 6:23 PM, David Malcolm wrote:
>> This patch contains the command-line options for the analyzer.
>>
>
> Some of the -Wanalyzer- options sounds like they control similar
> warnings as existing options (e.g., -Wanalyzer-null-argument sounds
> like -Wnonnull and -Wanalyzer-null-dereference like -Wnull-dereference).
> There are also options whose names suggest they are in the same class
> as -Wmaybe-xxx (e.g., -Wanalyzer-possible-null-argument sounds like
> it would correspond to -Wmaybe-null-argument if it existed).

This is something I wondered about too, but I think when you compare
it to how clang does it (their analyzer options don't even have normal
-W names; you have to toggle them in a different way), the approach
David is suggesting for GCC here is better.

>
> I have a more general question about the apparent overlap of
> functionality that I will save for later but here I wonder about
> the names: that introducing a new set of similar sounding options
> might make them confusing, and might make the analyzer seem more
> like an add-on rather than an integral part of GCC.

Clang has this problem, too.

> I realize the existing option names don't use any particular convention so
> this is an opportunity to put one in place, but at a cost of
> divergence.  Unless you expect the existing options to go away,
> having consistent names would make for a more cohesive feel.
>
> My other concern is the verbosity of some these options:
> -Wanalyzer-use-of-pointer-in-stale-stack-frame is a mouthful and
> will take up a lot of terminal space.  It sounds like the option
> controls warnings about uses of dangling pointers to auto objects,
> similar to -Wreturn-local-addr.  My impression that conventionally
> GCC options have tended to be brief, and I personally would prefer
> shorter option names.

While I get this preference, to compare to clang again, some of their
warning names are even longer than that...
-Wanalyzer-use-of-pointer-in-stale-stack-frame really isn't that much
worse than, say, -Wincompatible-pointer-types-discards-qualifiers or
-Wtautological-constant-out-of-range-compare, for example.

>
> Martin
>
>> gcc/ChangeLog:
>> 	* analyzer/plugin.opt: New file.
>> 	* common.opt (--analyzer): New driver option.
>> ---
>>   gcc/analyzer/plugin.opt | 161
>> ++++++++++++++++++++++++++++++++++++++++++++++++
>>   gcc/common.opt          |   3 +
>>   2 files changed, 164 insertions(+)
>>   create mode 100644 gcc/analyzer/plugin.opt
>>
>> diff --git a/gcc/analyzer/plugin.opt b/gcc/analyzer/plugin.opt
>> new file mode 100644
>> index 0000000..55f54bb
>> --- /dev/null
>> +++ b/gcc/analyzer/plugin.opt
>> @@ -0,0 +1,161 @@
>> +; plugin.opt -- Options for the analyzer.
>> +
>> +; Copyright (C) 2019 Free Software Foundation, Inc.
>> +;
>> +; 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/>.
>> +
>> +; See the GCC internals manual for a description of this file's format.
>> +
>> +; Please try to keep this file in ASCII collating order.
>> +
>> +Wanalyzer-double-fclose
>> +Common Var(warn_analyzer_double_fclose) Init(1) Warning
>> +Warn about code paths in which a stdio FILE can be closed more than
>> once.
>> +
>> +Wanalyzer-double-free
>> +Common Var(warn_analyzer_double_free) Init(1) Warning
>> +Warn about code paths in which a pointer can be freed more than once.
>> +
>> +Wanalyzer-exposure-through-output-file
>> +Common Var(warn_analyzer_exposure_through_output_file) Init(1) Warning
>> +Warn about code paths in which sensitive data is written to a file.
>> +
>> +Wanalyzer-file-leak
>> +Common Var(warn_analyzer_file_leak) Init(1) Warning
>> +Warn about code paths in which a stdio FILE is not closed.
>> +
>> +Wanalyzer-free-of-non-heap
>> +Common Var(warn_analyzer_free_of_non_heap) Init(1) Warning
>> +Warn about code paths in which a non-heap pointer is freed.
>> +
>> +Wanalyzer-malloc-leak
>> +Common Var(warn_analyzer_malloc_leak) Init(1) Warning
>> +Warn about code paths in which a heap-allocated pointer leaks.
>> +
>> +Wanalyzer-possible-null-argument
>> +Common Var(warn_analyzer_possible_null_argument) Init(1) Warning
>> +Warn about code paths in which a possibly-NULL value is passed to a
>> must-not-be-NULL function argument.
>> +
>> +Wanalyzer-possible-null-dereference
>> +Common Var(warn_analyzer_possible_null_dereference) Init(1) Warning
>> +Warn about code paths in which a possibly-NULL pointer is dereferenced.
>> +
>> +Wanalyzer-null-argument
>> +Common Var(warn_analyzer_null_argument) Init(1) Warning
>> +Warn about code paths in which NULL is passed to a must-not-be-NULL
>> function argument.
>> +
>> +Wanalyzer-null-dereference
>> +Common Var(warn_analyzer_null_dereference) Init(1) Warning
>> +Warn about code paths in which a NULL pointer is dereferenced.
>> +
>> +Wanalyzer-stale-setjmp-buffer
>> +Common Var(warn_analyzer_stale_setjmp_buffer) Init(1) Warning
>> +Warn about code paths in which a longjmp rewinds to a jmp_buf saved in a
>> stack frame that has returned.
>> +
>> +Wanalyzer-tainted-array-index
>> +Common Var(warn_analyzer_tainted_array_index) Init(1) Warning
>> +Warn about code paths in which an unsanitized value is used as an array
>> index.
>> +
>> +Wanalyzer-use-after-free
>> +Common Var(warn_analyzer_use_after_free) Init(1) Warning
>> +Warn about code paths in which a freed value is used.
>> +
>> +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-use-of-uninitialized-value
>> +Common Var(warn_analyzer_use_of_uninitialized_value) Init(1) Warning
>> +Warn about code paths in which an initialized value is used.
>> +
>> +Wanalyzer-too-complex
>> +Common Var(warn_analyzer_too_complex) Init(0) Warning
>> +Warn if the code is too complicated for the analyzer to fully explore.
>> +
>> +fanalyzer-checker=
>> +Common Joined RejectNegative Var(flag_analyzer_checker)
>> +Restrict the analyzer to run just the named checker.
>> +
>> +fanalyzer-fine-grained
>> +Common Var(flag_analyzer_fine_grained) Init(0)
>> +Avoid combining multiple statements into one exploded edge.
>> +
>> +fanalyzer-state-purge
>> +Common Var(flag_analyzer_state_purge) Init(1)
>> +Purge unneeded state during analysis.
>> +
>> +fanalyzer-state-merge
>> +Common Var(flag_analyzer_state_merge) Init(1)
>> +Merge similar-enough states during analysis.
>> +
>> +fanalyzer-transitivity
>> +Common Var(flag_analyzer_transitivity) Init(0)
>> +Enable transitivity of constraints during analysis.
>> +
>> +fanalyzer-call-summaries
>> +Common Var(flag_analyzer_call_summaries) Init(0)
>> +Approximate the effect of function calls to simplify analysis.
>> +
>> +fanalyzer-verbose-edges
>> +Common Var(flag_analyzer_verbose_edges) Init(0)
>> +Emit more verbose descriptions of control flow in diagnostics.
>> +
>> +fanalyzer-verbose-state-changes
>> +Common Var(flag_analyzer_verbose_state_changes) Init(0)
>> +Emit more verbose descriptions of state changes in diagnostics.
>> +
>> +fanalyzer-verbosity=
>> +Common Joined UInteger Var(analyzer_verbosity) Init(2)
>> +Control which events are displayed in diagnostic paths.
>> +
>> +fdump-analyzer
>> +Common RejectNegative Var(flag_dump_analyzer)
>> +Dump internal details about what the analyzer is doing to
>> SRCFILE.analyzer.txt.
>> +
>> +fdump-analyzer-stderr
>> +Common RejectNegative Var(flag_dump_analyzer_stderr)
>> +Dump internal details about what the analyzer is doing to stderr.
>> +
>> +fdump-analyzer-callgraph
>> +Common RejectNegative Var(flag_dump_analyzer_callgraph)
>> +Dump the analyzer supergraph to a SRCFILE.callgraph.dot file.
>> +
>> +fdump-analyzer-exploded-graph
>> +Common RejectNegative Var(flag_dump_analyzer_exploded_graph)
>> +Dump the analyzer exploded graph to a SRCFILE.eg.dot file.
>> +
>> +fdump-analyzer-exploded-nodes
>> +Common RejectNegative Var(flag_dump_analyzer_exploded_nodes)
>> +Emit diagnostics showing the location of nodes in the exploded graph.
>> +
>> +fdump-analyzer-exploded-nodes-2
>> +Common RejectNegative Var(flag_dump_analyzer_exploded_nodes_2)
>> +Dump a textual representation of the exploded graph to SRCFILE.eg.txt.
>> +
>> +fdump-analyzer-exploded-nodes-3
>> +Common RejectNegative Var(flag_dump_analyzer_exploded_nodes_3)
>> +Dump a textual representation of the exploded graph to
>> SRCFILE.eg-ID.txt.
>> +
>> +fdump-analyzer-state-purge
>> +Common RejectNegative Var(flag_dump_analyzer_state_purge)
>> +Dump state-purging information to a SRCFILE.state-purge.dot file.
>> +
>> +fdump-analyzer-supergraph
>> +Common RejectNegative Var(flag_dump_analyzer_supergraph)
>> +Dump the analyzer supergraph to a SRCFILE.supergraph.dot file.
>> +
>> +; This comment is to ensure we retain the blank line above.
>> diff --git a/gcc/common.opt b/gcc/common.opt
>> index adc9931..0c352a3 100644
>> --- a/gcc/common.opt
>> +++ b/gcc/common.opt
>> @@ -269,6 +269,9 @@ Driver Joined Alias(e)
>>   -extra-warnings
>>   Common Warning Alias(Wextra)
>>
>> +-analyzer
>> +Driver Var(analyzer_flag)
>> +
>>   -for-assembler
>>   Driver Separate Alias(Xassembler)
>>
>>
>
>

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 01/49] analyzer: user-facing documentation
  2019-11-16  1:17 ` [PATCH 01/49] analyzer: user-facing documentation David Malcolm
@ 2019-12-06 19:57   ` Eric Gallager
  2019-12-07  1:41     ` David Malcolm
  2019-12-09  5:36   ` Sandra Loosemore
  1 sibling, 1 reply; 160+ messages in thread
From: Eric Gallager @ 2019-12-06 19:57 UTC (permalink / raw)
  To: David Malcolm; +Cc: gcc-patches

While I get that this is just the documentation, having a good list
like this will make it easy to use to respond to the individual items
it documents, so that's what I'm going to use it for:

On 11/15/19, David Malcolm <dmalcolm@redhat.com> wrote:
> gcc/ChangeLog:
> 	* doc/invoke.texi ("Static Analyzer Options"): New list and new section.
> 	("Warning Options"): Add static analysis warnings to the list.
> 	(-Wno-analyzer-double-fclose): New option.
> 	(-Wno-analyzer-double-free): New option.
> 	(-Wno-analyzer-exposure-through-output-file): New option.
> 	(-Wno-analyzer-file-leak): New option.
> 	(-Wno-analyzer-free-of-non-heap): New option.
> 	(-Wno-analyzer-malloc-leak): New option.
> 	(-Wno-analyzer-possible-null-argument): New option.
> 	(-Wno-analyzer-possible-null-dereference): New option.
> 	(-Wno-analyzer-null-argument): New option.
> 	(-Wno-analyzer-null-dereference): New option.
> 	(-Wno-analyzer-stale-setjmp-buffer): New option.
> 	(-Wno-analyzer-tainted-array-index): New option.
> 	(-Wno-analyzer-use-after-free): New option.
> 	(-Wno-analyzer-use-of-pointer-in-stale-stack-frame): New option.
> 	(-Wno-analyzer-use-of-uninitialized-value): New option.
> 	(-Wanalyzer-too-complex): New option.
> 	(-fanalyzer-call-summaries): New warning.
> 	(-fanalyzer-checker=): New warning.
> 	(-fanalyzer-fine-grained): New warning.
> 	(-fno-analyzer-state-merge): New warning.
> 	(-fno-analyzer-state-purge): New warning.
> 	(-fanalyzer-transitivity): New warning.
> 	(-fanalyzer-verbose-edges): New warning.
> 	(-fanalyzer-verbose-state-changes): New warning.
> 	(-fanalyzer-verbosity=): New warning.
> 	(-fdump-analyzer): New warning.
> 	(-fdump-analyzer-callgraph): New warning.
> 	(-fdump-analyzer-exploded-graph): New warning.
> 	(-fdump-analyzer-exploded-nodes): New warning.
> 	(-fdump-analyzer-exploded-nodes-2): New warning.
> 	(-fdump-analyzer-exploded-nodes-3): New warning.
> 	(-fdump-analyzer-supergraph): New warning.
> ---
>  gcc/doc/invoke.texi | 420
> +++++++++++++++++++++++++++++++++++++++++++++++++++-
>  1 file changed, 418 insertions(+), 2 deletions(-)
>
> diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
> index 085e8b0..384848a 100644
> --- a/gcc/doc/invoke.texi
> +++ b/gcc/doc/invoke.texi
> @@ -153,6 +153,7 @@ listing and explanation of the binary and decimal byte
> size prefixes.
>  * Diagnostic Message Formatting Options:: Controlling how diagnostics
> should
>                          be formatted.
>  * Warning Options::     How picky should the compiler be?
> +* Static Analyzer Options:: More expensive warnings.
>  * Debugging Options::   Producing debuggable code.
>  * Optimize Options::    How much optimization?
>  * Instrumentation Options:: Enabling profiling and extra run-time error
> checking.
> @@ -284,13 +285,30 @@ Objective-C and Objective-C++ Dialects}.
>
>  @item Warning Options
>  @xref{Warning Options,,Options to Request or Suppress Warnings}.
> -@gccoptlist{-fsyntax-only  -fmax-errors=@var{n}  -Wpedantic @gol
> +@gccoptlist{--analyzer -fsyntax-only  -fmax-errors=@var{n}  -Wpedantic
> @gol
>  -pedantic-errors @gol
>  -w  -Wextra  -Wall  -Waddress  -Waddress-of-packed-member @gol
>  -Waggregate-return  -Waligned-new @gol
>  -Walloc-zero  -Walloc-size-larger-than=@var{byte-size} @gol
>  -Walloca  -Walloca-larger-than=@var{byte-size} @gol
> --Wno-aggressive-loop-optimizations  -Warray-bounds  -Warray-bounds=@var{n}
> @gol
> +-Wno-aggressive-loop-optimizations @gol
> +-Wno-analyzer-double-fclose @gol
> +-Wno-analyzer-double-free @gol
> +-Wno-analyzer-exposure-through-output-file @gol
> +-Wno-analyzer-file-leak @gol
> +-Wno-analyzer-free-of-non-heap @gol
> +-Wno-analyzer-malloc-leak @gol
> +-Wno-analyzer-possible-null-argument @gol
> +-Wno-analyzer-possible-null-dereference @gol
> +-Wno-analyzer-null-argument @gol
> +-Wno-analyzer-null-dereference @gol
> +-Wno-analyzer-stale-setjmp-buffer @gol
> +-Wno-analyzer-tainted-array-index @gol
> +-Wno-analyzer-use-after-free @gol
> +-Wno-analyzer-use-of-pointer-in-stale-stack-frame @gol
> +-Wno-analyzer-use-of-uninitialized-value @gol
> +-Wanalyzer-too-complex @gol
> +-Warray-bounds  -Warray-bounds=@var{n} @gol
>  -Wno-attributes  -Wattribute-alias=@var{n}  @gol
>  -Wbool-compare  -Wbool-operation @gol
>  -Wno-builtin-declaration-mismatch @gol
> @@ -370,6 +388,43 @@ Objective-C and Objective-C++ Dialects}.
>  -Wwrite-strings @gol
>  -Wzero-as-null-pointer-constant}
>
> +@item Static Analyzer Options
> +@gccoptlist{-Wanalyzer-double-fclose @gol
> +-Wanalyzer-double-free @gol
> +-Wanalyzer-exposure-through-output-file @gol
> +-Wanalyzer-file-leak @gol
> +-Wanalyzer-free-of-non-heap @gol
> +-Wanalyzer-malloc-leak @gol
> +-Wanalyzer-null-argument @gol
> +-Wanalyzer-null-dereference @gol
> +-Wanalyzer-possible-null-argument @gol
> +-Wanalyzer-possible-null-dereference @gol
> +-Wanalyzer-stale-setjmp-buffer @gol
> +-Wanalyzer-tainted-array-index @gol
> +-Wanalyzer-use-after-free @gol
> +-Wanalyzer-use-of-pointer-in-stale-stack-frame @gol
> +-Wanalyzer-use-of-uninitialized-value @gol
> +-Wanalyzer-too-complex  @gol
> +-fanalyzer-call-summaries @gol
> +-fanalyzer-checker=@var{name} @gol
> +-fanalyzer-fine-grained @gol
> +-fanalyzer-state-merge @gol
> +-fanalyzer-state-purge @gol
> +-fanalyzer-transitivity @gol
> +-fanalyzer-verbose-edges @gol
> +-fanalyzer-verbose-state-changes @gol
> +-fanalyzer-verbosity=@var{level} @gol
> +-fdump-analyzer @gol
> +-fdump-analyzer-stderr @gol
> +-fdump-analyzer-callgraph @gol
> +-fdump-analyzer-exploded-graph @gol
> +-fdump-analyzer-exploded-nodes @gol
> +-fdump-analyzer-exploded-nodes-2 @gol
> +-fdump-analyzer-exploded-nodes-3 @gol
> +-fdump-analyzer-state-purge @gol
> +-fdump-analyzer-supergraph @gol
> +}
> +
>  @item C and Objective-C-only Warning Options
>  @gccoptlist{-Wbad-function-cast  -Wmissing-declarations @gol
>  -Wmissing-parameter-type  -Wmissing-prototypes  -Wnested-externs @gol
> @@ -6184,6 +6239,159 @@ See also
> @option{-Wvla-larger-than=}@samp{byte-size}.
>  Disable @option{-Walloca-larger-than=} warnings.  The option is
>  equivalent to @option{-Walloca-larger-than=}@samp{SIZE_MAX} or larger.
>
> +@item -Wno-analyzer-double-fclose
> +@opindex Wanalyzer-double-fclose
> +@opindex Wno-analyzer-double-fclose
> +This warning requires @option{--analyzer}, which enables it; use
> +@option{-Wno-analyzer-double-fclose} to disable it.
> +
> +This diagnostic warns for paths through the code in which a @code{FILE *}
> +can have @code{fclose} called on it more than once.
> +



> +@item -Wno-analyzer-double-free
> +@opindex Wanalyzer-double-free
> +@opindex Wno-analyzer-double-free
> +This warning requires @option{--analyzer}, which enables it; use
> +@option{-Wno-analyzer-double-free} to disable it.
> +
> +This diagnostic warns for paths through the code in which a pointer
> +can have @code{free} called on it more than once.
> +
> +@item -Wno-analyzer-exposure-through-output-file
> +@opindex Wanalyzer-exposure-through-output-file
> +@opindex Wno-analyzer-exposure-through-output-file
> +This warning requires @option{--analyzer}, which enables it; use
> +@option{-Wno-analyzer-exposure-through-output-file}
> +to disable it.
> +
> +This diagnostic warns for paths through the code in which a
> +security-sensitive value is written to an output file
> +(such as writing a password to a log file).
> +
> +@item -Wno-analyzer-file-leak
> +@opindex Wanalyzer-file-leak
> +@opindex Wno-analyzer-file-leak
> +This warning requires @option{--analyzer}, which enables it; use
> +@option{-Wno-analyzer-file-leak}
> +to disable it.
> +
> +This diagnostic warns for paths through the code in which a
> +@code{<stdio.h>} @code{FILE *} stream object is leaked.
> +

So, to clarify, this is the opposite of -Wanalyzer-double-fclose,
right? i.e., instead of pointing out cases where there are too many
fclose()s, it points out ones where there are not enough? If so, these
bugs might be worth checking with it:
bug 47170: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47170
bug 58237: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=58237

> +@item -Wno-analyzer-free-of-non-heap
> +@opindex Wanalyzer-free-of-non-heap
> +@opindex Wno-analyzer-free-of-non-heap
> +This warning requires @option{--analyzer}, which enables it; use
> +@option{-Wno-analyzer-free-of-non-heap}
> +to disable it.
> +
> +This diagnostic warns for paths through the code in which @code{free}
> +is called on a non-heap pointer (e.g. an on-stack buffer, or a global).

How does this compare with -Wfree-nonheap-object? (also we might want
to harmonize whether "nonheap" is hyphenated or not)

> +
> +@item -Wno-analyzer-malloc-leak
> +@opindex Wanalyzer-malloc-leak
> +@opindex Wno-analyzer-malloc-leak
> +This warning requires @option{--analyzer}, which enables it; use
> +@option{-Wno-analyzer-malloc-leak}
> +to disable it.
> +
> +This diagnostic warns for paths through the code in which a
> +pointer allocated via @code{malloc} is leaked.

I think there's a bug for this, but I forget which it is right now...

> +
> +@item -Wno-analyzer-possible-null-argument
> +@opindex Wanalyzer-possible-null-argument
> +@opindex Wno-analyzer-possible-null-argument
> +This warning requires @option{--analyzer}, which enables it; use
> +@option{-Wno-analyzer-possible-null-argument} to disable it.
> +
> +This diagnostic warns for paths through the code in which a
> +possibly-NULL value is passed to a function argument marked
> +with @code{__attribute__((nonnull))} as requiring a non-NULL
> +value.

Might be worth checking some bugs filed against -Wnonnull with that, like:
bug 78917: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=78917
bug 78998: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=78998

> +
> +@item -Wno-analyzer-possible-null-dereference
> +@opindex Wanalyzer-possible-null-dereference
> +@opindex Wno-analyzer-possible-null-dereference
> +This warning requires @option{--analyzer}, which enables it; use
> +@option{-Wno-analyzer-possible-null-dereference} to disable it.
> +
> +This diagnostic warns for paths through the code in which a
> +possibly-NULL value is dereferenced.

In the meta-bug for -Wnull-dereference (bug 86172), Segher suggested
splitting off a separate -Wmaybe-null-dereference:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86172
...so maybe that's what this could be used for here.

> +
> +@item -Wno-analyzer-null-argument
> +@opindex Wanalyzer-null-argument
> +@opindex Wno-analyzer-null-argument
> +This warning requires @option{--analyzer}, which enables it; use
> +@option{-Wno-analyzer-null-argument} to disable it.
> +
> +This diagnostic warns for paths through the code in which a
> +value known to be NULL is passed to a function argument marked
> +with @code{__attribute__((nonnull))} as requiring a non-NULL
> +value.
> +
> +@item -Wno-analyzer-null-dereference
> +@opindex Wanalyzer-null-dereference
> +@opindex Wno-analyzer-null-dereference
> +This warning requires @option{--analyzer}, which enables it; use
> +@option{-Wno-analyzer-null-dereference} to disable it.
> +
> +This diagnostic warns for paths through the code in which a
> +value known to be NULL is dereferenced.
> +
> +@item -Wno-analyzer-stale-setjmp-buffer
> +@opindex Wanalyzer-stale-setjmp-buffer
> +@opindex Wno-analyzer-stale-setjmp-buffer
> +This warning requires @option{--analyzer}, which enables it; use
> +@option{-Wno-analyzer-stale-setjmp-buffer} to disable it.
> +
> +This diagnostic warns for paths through the code in which
> +@code{longjmp} is called to rewind to a @code{jmp_buf} relating
> +to a @code{setjmp} call in a function that has returned.
> +
> +When @code{setjmp} is called on a @code{jmp_buf} to record a rewind
> +location, it records the stack frame.  The stack frame becomes invalid
> +when the function containing the @code{setjmp} call returns.  Attempting
> +to rewind to it via @code{longjmp} would reference a stack frame that
> +no longer exists, and likely lead to a crash (or worse).
> +
> +@item -Wno-analyzer-tainted-array-index
> +@opindex Wanalyzer-tainted-array-index
> +@opindex Wno-analyzer-tainted-array-index
> +This warning requires @option{--analyzer}, which enables it; use
> +@option{-Wno-analyzer-tainted-array-index} to disable it.
> +
> +This diagnostic warns for paths through the code in which a value
> +that could be under an attacker's control is used as the index
> +of an array access without being sanitized.
> +
> +@item -Wno-analyzer-use-after-free
> +@opindex Wanalyzer-use-after-free
> +@opindex Wno-analyzer-use-after-free
> +This warning requires @option{--analyzer}, which enables it; use
> +@option{-Wno-analyzer-use-after-free} to disable it.
> +
> +This diagnostic warns for paths through the code in which a
> +pointer is used after @code{free} is called on it.

This is bug 80532: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80532

> +
> +@item -Wno-analyzer-use-of-pointer-in-stale-stack-frame
> +@opindex Wanalyzer-use-of-pointer-in-stale-stack-frame
> +@opindex Wno-analyzer-use-of-pointer-in-stale-stack-frame
> +This warning requires @option{--analyzer}, which enables it; use
> +@option{-Wno-analyzer-use-of-pointer-in-stale-stack-frame}
> +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-use-of-uninitialized-value
> +@opindex Wanalyzer-use-of-uninitialized-value
> +@opindex Wno-analyzer-use-of-uninitialized-value
> +This warning requires @option{--analyzer}, which enables it; use
> +@option{-Wno-analyzer-use-of-uninitialized-value} to disable it.
> +
> +This diagnostic warns for paths through the code in which an uninitialized
> +value is used.
> +

I'd like to see how many of the bugs under the -Wuninitialized
meta-bug (bug 24639) this catches:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=24639
Also, maybe update the wiki page on better uninitialized warnings:
https://gcc.gnu.org/wiki/Better_Uninitialized_Warnings (...with links
to the analyzer's wiki page, for instance)

>  @item -Warray-bounds
>  @itemx -Warray-bounds=@var{n}
>  @opindex Wno-array-bounds
> @@ -7808,6 +8016,214 @@ OpenMP construct.
>
>  @end table
>
> +@node Static Analyzer Options
> +@section Options That Control Static Analysis
> +
> +@table @gcctabopt
> +@item --analyzer
> +@opindex analyzer
> +This option enables an static analysis of program flow which looks
> +for ``interesting'' interprocedural paths through the
> +code, and issues warnings for problems found on them.
> +
> +This analysis is much more expensive than other GCC warnings.
> +
> +Enabling this option effectively enables the following warnings:
> +
> +@gccoptlist{ @gol
> +-Wanalyzer-double-fclose @gol
> +-Wanalyzer-double-free @gol
> +-Wanalyzer-exposure-through-output-file @gol
> +-Wanalyzer-file-leak @gol
> +-Wanalyzer-free-of-non-heap @gol
> +-Wanalyzer-malloc-leak @gol
> +-Wanalyzer-possible-null-argument @gol
> +-Wanalyzer-possible-null-dereference @gol
> +-Wanalyzer-null-argument @gol
> +-Wanalyzer-null-dereference @gol
> +-Wanalyzer-tainted-array-index @gol
> +-Wanalyzer-use-after-free @gol
> +-Wanalyzer-use-of-uninitialized-value @gol
> +-Wanalyzer-use-of-pointer-in-stale-stack-frame @gol
> +}
> +
> +This option is only available if GCC was configured with analyzer
> +support enabled.
> +
> +@item -Wanalyzer-too-complex
> +@opindex Wanalyzer-too-complex
> +@opindex Wno-analyzer-too-complex
> +If @option{--analyzer} is enabled, the analyzer uses various heuristics
> +to attempt to explore the control flow and data flow in the program,
> +but these can be defeated by sufficiently complicated code.
> +
> +By default, the analysis will silently stop if the code is too
> +complicated for the analyzer to fully explore and it reaches an internal
> +limit.
> +
> +The @option{-Wanalyzer-too-complex} option will warn if this occurs.
> +

What type of complexity are we talking about specifically here?
Cyclomatic complexity? If so it might be worth comparing against some
other cyclomatic complexity checkers that exist out there... (I can't
find the one I was thinking of, unfortunately...)

> +@end table
> +
> +Pertinent parameters for controlling the exploration are:
> +@option{--param analyzer-bb-explosion-factor=@var{value}},
> +@option{--param analyzer-max-enodes-per-program-point=@var{value}},
> +@option{--param analyzer-max-recursion-depth=@var{value}}, and
> +@option{--param analyzer-min-snodes-for-call-summary=@var{value}}.
> +
> +The following options control the analyzer.
> +
> +@table @gcctabopt
> +
> +@item -fanalyzer-call-summaries
> +@opindex fanalyzer-call-summaries
> +@opindex fno-analyzer-call-summaries
> +Simplify interprocedural analysis by computing the effect of certain calls,
> +rather than exploring all paths through the function from callsite to each
> +possible return.
> +
> +If enabled, call summaries are only used for functions with more than one
> +call site, and that are sufficiently complicated (as per
> +@option{--param analyzer-min-snodes-for-call-summary=@var{value}}).
> +
> +@item -fanalyzer-checker=@var{name}
> +@opindex fanalyzer-checker
> +Restrict the analyzer to run just the named checker.
> +
> +@item -fanalyzer-fine-grained
> +@opindex fanalyzer-fine-grained
> +@opindex fno-analyzer-fine-grained
> +This option is intended for analyzer developers.
> +
> +Internally the analyzer builds an ``exploded graph'' that combines
> +control flow graphs with data flow information.
> +
> +By default, an edge in this graph can contain the effects of a run
> +of multiple statements within a basic block.  With
> +@option{-fanalyzer-fine-grained}, each statement gets its own edge.
> +
> +@item -fno-analyzer-state-merge
> +@opindex fanalyzer-state-merge
> +@opindex fno-analyzer-state-merge
> +This option is intended for analyzer developers.
> +
> +By default the analyzer will attempt to simplify analysis by merging
> +sufficiently similar states at each program point as it builds its
> +``exploded graph''.  With @option{-fno-analyzer-state-merge} this
> +merging can be suppressed, for debugging state-handling issues.
> +
> +@item -fno-analyzer-state-purge
> +@opindex fanalyzer-state-purge
> +@opindex fno-analyzer-state-purge
> +This option is intended for analyzer developers.
> +
> +By default the analyzer will attempt to simplify analysis by purging
> +aspects of state at a program point that appear to no longer be relevant
> +e.g. the values of locals that aren't accessed later in the function
> +and which aren't relevant to leak analysis.
> +
> +With @option{-fno-analyzer-state-purge} this purging of state can
> +be suppressed, for debugging state-handling issues.
> +
> +@item -fanalyzer-transitivity
> +@opindex fanalyzer-transitivity
> +@opindex fno-analyzer-transitivity
> +This option enables transitivity of constraints within the analyzer.
> +
> +@item -fanalyzer-verbose-edges
> +This option is intended for analyzer developers.  It enables more
> +verbose, lower-level detail in the descriptions of control flow
> +within diagnostic paths.
> +
> +@item -fanalyzer-verbose-state-changes
> +This option is intended for analyzer developers.  It enables more
> +verbose, lower-level detail in the descriptions of events relating
> +to state machines within diagnostic paths.
> +
> +@item -fanalyzer-verbosity=@var{level}
> +This option controls the complexity of the control flow paths that are
> +emitted for analyzer diagnostics.
> +
> +The @var{level} can be one of:
> +
> +@table @samp
> +@item 0
> +At this level, interprocedural call and return events are displayed,
> +along with the most pertinent state-change events relating to
> +a diagnostic.  For example, for a double-@code{free} diagnostic,
> +both calls to @code{free} will be shown.
> +
> +@item 1
> +As per the previous level, but also show events for the entry
> +to each function.
> +
> +@item 2
> +As per the previous level, but also show  events relating to
> +control flow (e.g. ``true path taken'' at a conditional).
> +
> +This level is the default.
> +
> +@item 3
> +This level is intended for analyzer developers; it adds various
> +other events intended for debugging the analyzer.
> +
> +@end table
> +
> +@item -fdump-analyzer
> +@opindex fdump-analyzer
> +Dump internal details about what the analyzer is doing to
> +@file{@var{file}.analyzer.txt}.
> +This option is overridden by @option{-fdump-analyzer-stderr}.
> +
> +@item -fdump-analyzer-stderr
> +@opindex fdump-analyzer-stderr
> +Dump internal details about what the analyzer is doing to stderr.
> +This option overrides @option{-fdump-analyzer}.
> +
> +@item -fdump-analyzer-callgraph
> +@opindex fdump-analyzer-callgraph
> +Dump a representation of the call graph suitable for viewing with
> +GraphViz to @file{@var{file}.callgraph.dot}.
> +
> +@item -fdump-analyzer-exploded-graph
> +@opindex fdump-analyzer-exploded-graph
> +Dump a representation of the ``exploded graph'' suitable for viewing with
> +GraphViz to @file{@var{file}.eg.dot}.
> +Nodes are color-coded based on state-machine states to emphasize
> +state changes.
> +
> +@item -fdump-analyzer-exploded-nodes
> +@opindex dump-analyzer-exploded-nodes
> +Emit diagnostics showing where nodes in the ``exploded graph'' are
> +in relation to the program source.
> +
> +@item -fdump-analyzer-exploded-nodes-2
> +@opindex dump-analyzer-exploded-nodes-2
> +Dump a textual representation of the ``exploded graph'' to
> +@file{@var{file}.eg.txt}.
> +
> +@item -fdump-analyzer-exploded-nodes-3
> +@opindex dump-analyzer-exploded-nodes-3
> +Dump a textual representation of the ``exploded graph'' to
> +one dump file per node, to @file{@var{file}.eg-@var{id}.txt}.
> +This is typically a large number of dump files.
> +
> +@item -fdump-analyzer-state-purge
> +@opindex fdump-analyzer-state-purge
> +As per @option{-fdump-analyzer-supergraph}, dump a representation of the
> +``supergraph'' suitable for viewing with GraphViz, but annotate the
> +graph with information on what state will be purged at each node.
> +The graph is written to @file{@var{file}.state-purge.dot}.
> +
> +@item -fdump-analyzer-supergraph
> +@opindex fdump-analyzer-supergraph
> +Dump a representation of the ``supergraph'' suitable for viewing with
> +GraphViz to @file{@var{file}.supergraph.dot}.  This shows all of the
> +control flow graphs in the program, with interprocedural edges for
> +calls and returns.
> +
> +@end table
> +
>  @node Debugging Options
>  @section Options for Debugging Your Program
>  @cindex options, debugging
> --
> 1.8.5.3
>
>

OK, that's all I think I have for that part of the patchset, onto the next...

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 00/49] RFC: Add a static analysis framework to GCC
  2019-12-03 16:52       ` David Malcolm
  2019-12-03 17:17         ` Jakub Jelinek
  2019-12-04 12:41         ` Richard Biener
@ 2019-12-06 22:25         ` Jeff Law
  2019-12-08 14:28           ` [PATCH 0/2] v3 of analyzer patch kit (unsquashed) David Malcolm
  2 siblings, 1 reply; 160+ messages in thread
From: Jeff Law @ 2019-12-06 22:25 UTC (permalink / raw)
  To: David Malcolm, Richard Biener; +Cc: Thomas Schwinge, GCC Patches

On Tue, 2019-12-03 at 11:52 -0500, David Malcolm wrote:
> On Wed, 2019-11-20 at 11:18 +0100, Richard Biener wrote:
> > On Tue, Nov 19, 2019 at 11:02 PM David Malcolm <dmalcolm@redhat.com
> > >
> > wrote:
> > > > > The checker is implemented as a GCC plugin.
> > > > > 
> > > > > The patch kit adds support for "in-tree" plugins i.e. GCC
> > > > > plugins
> > > > > that
> > > > > would live in the GCC source tree and be shipped as part of
> > > > > the
> > > > > GCC
> > > > > tarball,
> > > > > with a new:
> > > > >   --enable-plugins=[LIST OF PLUGIN NAMES]
> > > > > configure option, analogous to --enable-languages (the
> > > > > Makefile/configure
> > > > > machinery for handling in-tree GCC plugins is adapted from
> > > > > how
> > > > > we
> > > > > support
> > > > > frontends).
> > > > 
> > > > I like that.  Implementing this as a plugin surely must help to
> > > > either
> > > > document the GCC plugin interface as powerful/mature for such a
> > > > task.  Or
> > > > make it so, if it isn't yet.  ;-)
> > > 
> > > Our plugin "interface" as such is very broad.
> > 
> > Just to sneak in here I don't like exposing our current plugin
> > "non-
> > API"
> > more.  In fact I'd just build the analyzer into GCC with maybe an
> > option to disable its build (in case it is very fat?).
> 
> My aim here is to provide a way for distributors to be able to
> disable
> its build - indeed, for now, for it to be disabled by default,
> requiring opting-in.
It seems like there's some move to have this as part of the core
compiler rather than as a plug-in.  That's a bit of a surprise, but a
good one.


> I want some way to label the code as a "technology preview", that
> people may want to experiment with, but to set expectations that this
> is a lot of new code and there will be bugs - but to make it
> available
> to make it easier for adventurous users to try it out.
> 
> I hope that makes sense.
> 
> I went down the "in-tree plugin" path by seeing the analogy with
> frontends, but yes, it would probably be simpler to just build it
> into
> GCC, guarded with a configure-time variable.  It's many thousand
> lines
> of non-trivial C++ code, and associated selftests and DejaGnu tests.
Given the overall feedback, core component with an opt-out seems like
it'd be best.

jeff
> 

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 00/49] RFC: Add a static analysis framework to GCC
  2019-12-04 19:55 ` Martin Sebor
@ 2019-12-06 22:31   ` Jeff Law
  2019-12-09  8:10     ` Richard Biener
  0 siblings, 1 reply; 160+ messages in thread
From: Jeff Law @ 2019-12-06 22:31 UTC (permalink / raw)
  To: Martin Sebor, David Malcolm, gcc-patches

On Wed, 2019-12-04 at 12:55 -0700, Martin Sebor wrote:
> On 11/15/19 6:22 PM, David Malcolm wrote:
> > This patch kit introduces a static analysis pass for GCC that can
> > diagnose
> > various kinds of problems in C code at compile-time (e.g. double-
> > free,
> > use-after-free, etc).
> 
> I haven't looked at the analyzer bits in any detail yet so I have
> just some very high-level questions.  But first let me say I'm
> excited to see this project! :)
> 
> It looks like the analyzer detects some of the same problems as
> some existing middle-end warnings (e.g., -Wnonnull, -Wuninitialized),
> and some that I have been working toward implementing (invalid uses
> of freed pointers such as returning them from functions or passing
> them to others), and others still that I have been thinking about
> as possible future projects (e.g., detecting uses of uninitialized
> arrays in string functions).
> 
> What are your thoughts about this sort of overlap?  Do you expect
> us to enhance both sets of warnings in parallel, or do you see us
> moving away from issuing warnings in the middle-end and toward
> making the analyzer the main source of these kinds of diagnostics?
> Maybe even replace some of the problematic middle-end warnings
> with the analyzer?  What (if anything) should we do about warnings
> issued for the same problems by both the middle-end and the analyzer?
> Or about false negatives?  E.g., a bug detected by the middle-end
> but not the analyzer or vice versa.
> 
> What do you see as the biggest pros and cons of either approach?
> (Middle-end vs analyzer.)  What limitations is the analyzer
> approach inherently subject to that the middle-end warnings aren't,
> and vice versa?
> 
> How do we prioritize between the two approaches (e.g., choose
> where to add a new warning)?
Given the cost of David's analyzer, I would tend to prioritize the more
localized analysis.  Also note that because of the compile-time
complexities we end up pruning paths from the search space and lose
precision when we have to merge nodes.   These issues are inherent in
the depth of analysis we're looking to do.

So the way to think about things is David's work is a slower, deeper
analysis than what we usually do.  So things that are reasonable
candidates for -Wall would need to use the traditional mechansisms. 
Things that require deeper analysis would be done in David's framework.

Also note that part of David's work is to bring a fairly generic engine
that we can expand with different domain specific analyzers.  It just
happens to be the case that the first place he's focused is on double-
free and use-after-free.  But (IMHO) the gem is really the generic
engine.

jeff

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 16/49] Add support for in-tree plugins
  2019-11-16  1:18 ` [PATCH 16/49] Add support for in-tree plugins David Malcolm
@ 2019-12-06 22:41   ` Eric Gallager
  2019-12-07 14:39   ` Jeff Law
  1 sibling, 0 replies; 160+ messages in thread
From: Eric Gallager @ 2019-12-06 22:41 UTC (permalink / raw)
  To: David Malcolm; +Cc: gcc-patches, Nick Clifton

On 11/15/19, David Malcolm <dmalcolm@redhat.com> wrote:
> This patch adds support for "in-tree" plugins i.e. GCC plugins that live
> in the GCC source tree and are shipped as part of the GCC tarball.

Nick Clifton was asking us to do something like this anyways in his
talk at Cauldron on his annobin plugin; it might be worthwhile to add
support for in-tree plugins regardless of whether the analyzer ends up
being an in-tree plugin itself or not.

>
> The patch adds Makefile/configure machinery for handling in-tree GCC
> plugins, adapted from how we support frontends.
>
> Each in-tree plugin should provide a Make-plugin.in and config-plugin.in,
> analogous to the Make-lang.in and config-lang.in provided by a frontend;
> they can also supply options via a plugin.opt, analogous to a frontend's
> lang.opt.
>
> The patch adds a --enable-plugins=[LIST OF PLUGIN NAMES] configure option,
> analogous to --enable-languages.  The default is for no such plugins to be
> enabled.
>
> ChangeLog:
> 	* configure.ac: Handle --enable-plugins.
>
> gcc/ChangeLog:
> 	* Makefile.in (CONFIG_PLUGINS): New.
> 	(SUBDIRS, subdirs): Generalize to cover plugins as well as
> 	languages.
> 	(plugin_opt_files): New.
> 	(ALL_OPT_FILES): Add plugin_opt_files.
> 	(ALL_HOST_PLUGIN_OBJS): New.
> 	(ALL_HOST_OBJS): Add ALL_HOST_PLUGIN_OBJS.
> 	(plugin_hooks): New.
> 	(PLUGIN_MAKEFRAGS): New; include them.
> 	(Makefile): Add dependency on PLUGIN_MAKEFRAGS.
> 	(all.cross): Add dependency on plugin.all.cross.
> 	(start.encap): Add plugin.start.encap.
> 	(rest.encap): Add plugin.rest.encap.
> 	(SELFTEST_TARGETS): Add selftest_plugins.
> 	(info): Add dependency on lang.info.
> 	(dvi): Add dependency on plugin.dvi.
> 	(pdf): Add dependency on plugin.pdf.
> 	(HTMLS_BUILD): Add plugin.html.
> 	(man): Add plugin.man.
> 	(mostlyclean): Add plugin.mostlyclean.
> 	(clean): Add plugin.clean.
> 	(distclean): Update for renaming of Make-hooks to Make-lang-hooks;
> 	add Make-plugin-hooks.  Add plugin.distclean dependency.
> 	(maintainer-clean): Add plugin.maintainer-clean.
> 	(install-plugin): Add plugin.install-plugin.
> 	(install-common): Add plugin.install-common.
> 	(install-info): Add plugin.install-info.
> 	(install-pdf): Add plugin.install-pdf.
> 	(install-html): Add plugin.install-html.
> 	(install-man): Add plugin.install-man.
> 	(uninstall): Add plugin.uninstall.
> 	(TAGS): Add plugin.tags.
> 	* configure.ac (Make-hooks): Rename to Make-lang-hooks.
> 	(plugin_opt_files): New.
> 	(plugin_specs_files): New.
> 	(plugin_tree_files): New.
> 	(all_plugins): New.
> 	(all_plugin_makefrags): New.
> 	(all_selected_plugins): New.
> 	(plugin_hooks): New.
> 	("Plugin hooks"): New section.  Iterate through config-plugin.in,
> 	analogously to config-lang.in.
> 	(check_plugins): New.
> 	(selftest_plugins): New.
> 	(Make-plugin-hooks): Emit.
> 	* doc/install.texi (--enable-plugins): New option.
> ---
>  configure.ac         |   6 ++
>  gcc/Makefile.in      |  98 ++++++++++++++++++++---------
>  gcc/configure.ac     | 172
> +++++++++++++++++++++++++++++++++++++++++++++++++--
>  gcc/doc/install.texi |   9 +++
>  4 files changed, 251 insertions(+), 34 deletions(-)
>
> diff --git a/configure.ac b/configure.ac
> index b8ce2ad..9b8b8ab 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -2188,6 +2188,12 @@ Supported languages are: ${potential_languages}])
>    ac_configure_args=`echo " $ac_configure_args" | sed -e "s/
> '--enable-languages=[[^ ]]*'//g" -e "s/$/
> '--enable-languages="$enable_languages"'/" `
>  fi
>
> +# Look if the user specified --enable-plugins="..."
> +if test -d ${srcdir}/gcc; then
> +  enable_plugins=`echo "${enable_plugins}" | sed -e 's/[[ 	,]][[ 	,]]*/,/g'
> -e 's/,$//'`
> +  ac_configure_args=`echo " $ac_configure_args" | sed -e "s/
> '--enable-plugins=[[^ ]]*'//g" -e "s/$/
> '--enable-plugins="$enable_plugins"'/" `
> +fi
> +
>  # Handle --disable-<component> generically.
>  for dir in $configdirs $build_configdirs $target_configdirs ; do
>    dirname=`echo $dir | sed -e s/target-//g -e s/build-//g -e s/-/_/g`
> diff --git a/gcc/Makefile.in b/gcc/Makefile.in
> index 5feef6a..c0dfbde 100644
> --- a/gcc/Makefile.in
> +++ b/gcc/Makefile.in
> @@ -132,7 +132,7 @@ CROSS=@CROSS@
>  # Variables that exist for you to override.
>  # See below for how to change them for certain systems.
>
> -# List of language subdirectories.
> +# List of language and plugin subdirectories.
>  SUBDIRS =@subdirs@ build
>
>  # Selection of languages to be made.
> @@ -142,6 +142,8 @@ ifeq (@enable_gcov@,yes)
>  LANGUAGES += gcov$(exeext) gcov-dump$(exeext) gcov-tool$(exeext)
>  endif
>
> +CONFIG_PLUGINS = @all_selected_plugins@
> +
>  # Default values for variables overridden in Makefile fragments.
>  # CFLAGS is for the user to override to, e.g., do a cross build with -O2.
>  # TCFLAGS is used for compilations with the GCC just built.
> @@ -570,6 +572,7 @@ lang_checks_parallelized=
>  lang_opt_files=@lang_opt_files@ $(srcdir)/c-family/c.opt
> $(srcdir)/common.opt
>  lang_specs_files=@lang_specs_files@
>  lang_tree_files=@lang_tree_files@
> +plugin_opt_files=@plugin_opt_files@
>  target_cpu_default=@target_cpu_default@
>  OBJC_BOEHM_GC=@objc_boehm_gc@
>  extra_modes_file=@extra_modes_file@
> @@ -1186,7 +1189,7 @@ FLAGS_TO_PASS = \
>  # Lists of files for various purposes.
>
>  # All option source files
> -ALL_OPT_FILES=$(lang_opt_files) $(extra_opt_files)
> +ALL_OPT_FILES=$(lang_opt_files) $(plugin_opt_files) $(extra_opt_files)
>
>  # Target specific, C specific object file
>  C_TARGET_OBJS=@c_target_objs@
> @@ -1639,6 +1642,8 @@ OBJS-libcommon-target = $(common_out_object_file)
> prefix.o params.o \
>  # This lists all host objects for the front ends.
>  ALL_HOST_FRONTEND_OBJS = $(foreach v,$(CONFIG_LANGUAGES),$($(v)_OBJS))
>
> +ALL_HOST_PLUGIN_OBJS = $(foreach v,$(CONFIG_PLUGINS),$($(v)_OBJS))
> +
>  ALL_HOST_BACKEND_OBJS = $(GCC_OBJS) $(OBJS) $(OBJS-libcommon) \
>    $(OBJS-libcommon-target) main.o c-family/cppspec.o \
>    $(COLLECT2_OBJS) $(EXTRA_GCC_OBJS) $(GCOV_OBJS) $(GCOV_DUMP_OBJS) \
> @@ -1655,7 +1660,10 @@ endif
>
>  # This lists all host object files, whether they are included in this
>  # compilation or not.
> -ALL_HOST_OBJS = $(ALL_HOST_FRONTEND_OBJS) $(ALL_HOST_BACKEND_OBJS)
> +ALL_HOST_OBJS = \
> +  $(ALL_HOST_FRONTEND_OBJS) \
> +  $(ALL_HOST_PLUGIN_OBJS) \
> +  $(ALL_HOST_BACKEND_OBJS)
>
>  BACKEND = libbackend.a main.o libcommon-target.a libcommon.a \
>  	$(CPPLIB) $(LIBDECNUMBER)
> @@ -1752,6 +1760,32 @@ s-alltree: Makefile
>  $(foreach file,$(ALL_HOST_FRONTEND_OBJS),$(eval CFLAGS-$(file) +=
> -DIN_GCC_FRONTEND))
>
>  #\f
> +# Plugin makefile fragments.
> +
> +# The following targets define the interface between us and the
> +# in-tree plugins.
> +#
> +# all.cross, start.encap, rest.encap,
> +# install-common, install-info, install-man,
> +# uninstall,
> +# mostlyclean, clean, distclean, maintainer-clean,
> +#
> +# Each plugin is linked in with a series of hooks.  The name of each
> +# hooked is "plugin.${target_name}" (eg: plugin.info).  Configure computes
> +# and adds these here.  We use double-colon rules for some of the hooks;
> +# double-colon rules should be preferred for any new hooks.
> +
> +# plugin hooks, generated by configure
> +@plugin_hooks@
> +
> +# per-plugin makefile fragments
> +PLUGIN_MAKEFRAGS = @all_plugin_makefrags@
> +
> +ifneq ($(PLUGIN_MAKEFRAGS),)
> +include $(PLUGIN_MAKEFRAGS)
> +endif
> +
> +#\f
>
>  # -----------------------------
>  # Rebuilding this configuration
> @@ -1793,8 +1827,9 @@ $(foreach file,$(ALL_HOST_FRONTEND_OBJS),$(eval
> CFLAGS-$(file) += -DIN_GCC_FRONT
>  # This is what is done in this makefile. Note that mkconfig.sh has a
>  # move-if-change built-in
>
> -Makefile: config.status $(srcdir)/Makefile.in $(LANG_MAKEFRAGS)
> +Makefile: config.status $(srcdir)/Makefile.in $(LANG_MAKEFRAGS)
> $(PLUGIN_MAKEFRAGS)
>  	LANGUAGES="$(CONFIG_LANGUAGES)" \
> +	PLUGINS="$(CONFIG_PLUGINS)" \
>  	CONFIG_HEADERS= \
>  	CONFIG_SHELL="$(SHELL)" \
>  	CONFIG_FILES=$@ $(SHELL) config.status
> @@ -1924,12 +1959,13 @@ quickstrap: all
>  all.internal: start.encap rest.encap doc selftest
>  # This is what to compile if making a cross-compiler.
>  all.cross: native gcc-cross$(exeext) cpp$(exeext) specs \
> -	libgcc-support lang.all.cross doc selftest @GENINSRC@ srcextra
> +	libgcc-support lang.all.cross plugin.all.cross doc \
> +	selftest @GENINSRC@ srcextra
>  # This is what must be made before installing GCC and converting
> libraries.
>  start.encap: native xgcc$(exeext) cpp$(exeext) specs \
> -	libgcc-support lang.start.encap @GENINSRC@ srcextra
> +	libgcc-support lang.start.encap plugin.start.encap @GENINSRC@ srcextra
>  # These can't be made until after GCC can run.
> -rest.encap: lang.rest.encap
> +rest.encap: lang.rest.encap plugin.rest.encap
>  # This is what is made with the host's compiler
>  # whether making a cross compiler or not.
>  native: config.status auto-host.h build-@POSUB@ $(LANGUAGES) \
> @@ -1967,11 +2003,12 @@ SELFTEST_DEPS = $(GCC_PASSES) stmp-int-hdrs
> $(srcdir)/testsuite/selftests
>  # driver, frontend, or selftest data change.
>  .PHONY: selftest
>
> -# Potentially run all selftest-<LANG>.  The various <LANG>/Make-lang.in
> can
> -# require the selftests to be run by defining their selftest-<LANG> as
> -# s-selftest-<LANG>.  Otherwise, they should define it as empty.
> +# Potentially run all selftest-<LANG> and selftest-<PLUGIN>.
> +# The various <LANG>/Make-lang.in and <PLUGIN>/Make-plugin.in can
> +# require the selftests to be run by defining their selftest-<NAME> as
> +# s-selftest-<NAME>.  Otherwise, they should define it as empty.
>
> -SELFTEST_TARGETS = @selftest_languages@
> +SELFTEST_TARGETS = @selftest_languages@ @selftest_plugins@
>  selftest: $(SELFTEST_TARGETS)
>
>  # Recompile all the language-independent object files.
> @@ -3157,7 +3194,8 @@ doc: $(BUILD_INFO) $(GENERATED_MANPAGES)
>  INFOFILES = doc/cpp.info doc/gcc.info doc/gccint.info \
>              doc/gccinstall.info doc/cppinternals.info
>
> -info: $(INFOFILES) lang.info @GENINSRC@ srcinfo lang.srcinfo
> +info: $(INFOFILES) lang.info plugin.info @GENINSRC@ srcinfo lang.srcinfo \
> +	plugin.srcinfo
>
>  srcinfo: $(INFOFILES)
>  	-cp -p $^ $(srcdir)/doc
> @@ -3244,7 +3282,7 @@ $(build_htmldir)/gccint/index.html:
> $(TEXI_GCCINT_FILES)
>  $(build_htmldir)/cppinternals/index.html: $(TEXI_CPPINT_FILES)
>
>  dvi:: doc/gcc.dvi doc/gccint.dvi doc/gccinstall.dvi doc/cpp.dvi \
> -      doc/cppinternals.dvi lang.dvi
> +      doc/cppinternals.dvi lang.dvi plugin.dvi

So, right, this reminds me, if we're going to be adding new dvi
files... has anyone gotten `make install-dvi` from the top-level to
work recently? I had a patch for it a few years ago but never
completed it: https://gcc.gnu.org/ml/gcc-patches/2016-10/msg00380.html

>
>  doc/%.dvi: %.texi
>  	$(TEXI2DVI) -I . -I $(abs_docdir) -I $(abs_docdir)/include -o $@ $<
> @@ -3256,7 +3294,7 @@ doc/gccinstall.dvi: $(TEXI_GCCINSTALL_FILES)
>  PDFFILES = doc/gcc.pdf doc/gccint.pdf doc/gccinstall.pdf doc/cpp.pdf \
>             doc/cppinternals.pdf
>
> -pdf:: $(PDFFILES) lang.pdf
> +pdf:: $(PDFFILES) lang.pdf plugin.pdf
>
>  doc/%.pdf: %.texi
>  	$(TEXI2PDF) -I . -I $(abs_docdir) -I $(abs_docdir)/include -o $@ $<
> @@ -3275,7 +3313,7 @@ HTMLS_INSTALL=$(build_htmldir)/cpp
> $(build_htmldir)/gcc \
>  # List the html file targets.
>  HTMLS_BUILD=$(build_htmldir)/cpp/index.html $(build_htmldir)/gcc/index.html
> \
>         $(build_htmldir)/gccinstall/index.html
> $(build_htmldir)/gccint/index.html \
> -       $(build_htmldir)/cppinternals/index.html lang.html
> +       $(build_htmldir)/cppinternals/index.html lang.html plugin.html
>
>  html:: $(HTMLS_BUILD)
>
> @@ -3297,7 +3335,8 @@ MANFILES = doc/gcov.1 doc/cpp.1 doc/gcc.1 doc/gfdl.7
> doc/gpl.7 \
>
>  generated-manpages: man
>
> -man: $(MANFILES) lang.man @GENINSRC@ srcman lang.srcman
> +man: $(MANFILES) lang.man plugin.man @GENINSRC@ srcman lang.srcman \
> +	plugin.srcman
>
>  srcman: $(MANFILES)
>  	-cp -p $^ $(srcdir)/doc
> @@ -3351,7 +3390,7 @@ gpl.pod: gpl_v3.texi
>  # We remove as much from the language subdirectories as we can
>  # (less duplicated code).
>
> -mostlyclean: lang.mostlyclean
> +mostlyclean: lang.mostlyclean plugin.mostlyclean
>  	-rm -f $(MOSTLYCLEANFILES)
>  	-rm -f *$(objext) c-family/*$(objext)
>  	-rm -f *$(coverageexts)
> @@ -3388,7 +3427,7 @@ mostlyclean: lang.mostlyclean
>
>  # Delete all files made by compilation
>  # that don't exist in the distribution.
> -clean: mostlyclean lang.clean
> +clean: mostlyclean lang.clean plugin.clean
>  	-rm -f libgcc.a libgcc_eh.a libgcov.a
>  	-rm -f libgcc_s*
>  	-rm -f libunwind*
> @@ -3409,11 +3448,11 @@ clean: mostlyclean lang.clean
>
>  # Delete all files that users would normally create
>  # while building and installing GCC.
> -distclean: clean lang.distclean
> +distclean: clean lang.distclean plugin.distclean
>  	-rm -f auto-host.h auto-build.h
>  	-rm -f cstamp-h
>  	-rm -f config.status config.run config.cache config.bak
> -	-rm -f Make-lang Make-hooks Make-host Make-target
> +	-rm -f Make-lang Make-lang-hooks Make-plugin-hooks Make-host Make-target
>  	-rm -f Makefile *.oaux
>  	-rm -f gthr-default.h
>  	-rm -f TAGS */TAGS
> @@ -3435,7 +3474,7 @@ distclean: clean lang.distclean
>  maintainer-clean:
>  	@echo 'This command is intended for maintainers to use; it'
>  	@echo 'deletes files that may need special tools to rebuild.'
> -	$(MAKE) lang.maintainer-clean distclean
> +	$(MAKE) lang.maintainer-clean plugin.maintainer-clean distclean
>  	-rm -f cpp.??s cpp.*aux
>  	-rm -f gcc.??s gcc.*aux
>  	-rm -f $(gcc_docdir)/*.info $(gcc_docdir)/*.1 $(gcc_docdir)/*.7
> $(gcc_docdir)/*.dvi $(gcc_docdir)/*.pdf
> @@ -3546,7 +3585,7 @@ install-gengtype: installdirs gengtype$(exeext)
> gtype.state
>  	$(INSTALL_PROGRAM) gengtype$(exeext)
> $(DESTDIR)$(plugin_bindir)/gengtype$(exeext)
>
>  # Install the headers needed to build a plugin.
> -install-plugin: installdirs lang.install-plugin s-header-vars
> install-gengtype
> +install-plugin: installdirs lang.install-plugin plugin.install-plugin
> s-header-vars install-gengtype
>  # We keep the directory structure for files in config or c-family and .def
>  # files. All other files are flattened to a single directory.
>  	$(mkinstalldirs) $(DESTDIR)$(plugin_includedir)
> @@ -3573,7 +3612,7 @@ install-plugin: installdirs lang.install-plugin
> s-header-vars install-gengtype
>  	$(INSTALL_DATA) b-header-vars
> $(DESTDIR)$(plugin_includedir)/b-header-vars
>
>  # Install the compiler executables built during cross compilation.
> -install-common: native lang.install-common installdirs
> +install-common: native lang.install-common plugin.install-common
> installdirs
>  	for file in $(COMPILERS); do \
>  	  if [ -f $$file ] ; then \
>  	    rm -f $(DESTDIR)$(libexecsubdir)/$$file; \
> @@ -3648,7 +3687,8 @@ install-info:: doc installdirs \
>  	$(DESTDIR)$(infodir)/cppinternals.info \
>  	$(DESTDIR)$(infodir)/gccinstall.info \
>  	$(DESTDIR)$(infodir)/gccint.info \
> -	lang.install-info
> +	lang.install-info \
> +	plugin.install-info
>
>  $(DESTDIR)$(infodir)/%.info: doc/%.info installdirs
>  	rm -f $@
> @@ -3667,7 +3707,7 @@ $(DESTDIR)$(infodir)/%.info: doc/%.info installdirs
>
>  pdf__strip_dir = `echo $$p | sed -e 's|^.*/||'`;
>
> -install-pdf: $(PDFFILES) lang.install-pdf
> +install-pdf: $(PDFFILES) lang.install-pdf plugin.install-pdf
>  	@$(NORMAL_INSTALL)
>  	test -z "$(pdfdir)/gcc" || $(mkinstalldirs) "$(DESTDIR)$(pdfdir)/gcc"
>  	@list='$(PDFFILES)'; for p in $$list; do \
> @@ -3679,7 +3719,7 @@ install-pdf: $(PDFFILES) lang.install-pdf
>
>  html__strip_dir = `echo $$p | sed -e 's|^.*/||'`;
>
> -install-html: $(HTMLS_BUILD) lang.install-html
> +install-html: $(HTMLS_BUILD) lang.install-html plugin.install-html
>  	@$(NORMAL_INSTALL)
>  	test -z "$(htmldir)" || $(mkinstalldirs) "$(DESTDIR)$(htmldir)"
>  	@list='$(HTMLS_INSTALL)'; for p in $$list; do \
> @@ -3697,7 +3737,7 @@ install-html: $(HTMLS_BUILD) lang.install-html
>  	done
>
>  # Install the man pages.
> -install-man: lang.install-man \
> +install-man: lang.install-man plugin.install-man \
>  	$(DESTDIR)$(man1dir)/$(GCC_INSTALL_NAME)$(man1ext) \
>  	$(DESTDIR)$(man1dir)/$(CPP_INSTALL_NAME)$(man1ext) \
>  	$(DESTDIR)$(man1dir)/$(GCOV_INSTALL_NAME)$(man1ext) \
> @@ -3857,7 +3897,7 @@ install-gcc-ar: installdirs gcc-ar$(exeext)
> gcc-nm$(exeext) gcc-ranlib$(exeext)
>  	fi
>
>  # Cancel installation by deleting the installed files.
> -uninstall: lang.uninstall
> +uninstall: lang.uninstall plugin.uninstall
>  	-rm -rf $(DESTDIR)$(libsubdir)
>  	-rm -rf $(DESTDIR)$(libexecsubdir)
>  	-rm -rf $(DESTDIR)$(bindir)/$(GCC_INSTALL_NAME)$(exeext)
> @@ -4164,7 +4204,7 @@ paranoia: paranoia.o real.o $(LIBIBERTY)
>  # These exist for maintenance purposes.
>
>  # Update the tags table.
> -TAGS: lang.tags
> +TAGS: lang.tags plugin.tags
>  	(cd $(srcdir);					\
>  	incs= ;						\
>  	list='$(SUBDIRS)'; for dir in $$list; do	\
> diff --git a/gcc/configure.ac b/gcc/configure.ac
> index 62f4b26..537d3d8 100644
> --- a/gcc/configure.ac
> +++ b/gcc/configure.ac
> @@ -6300,7 +6300,7 @@ all_selected_languages=
>  # The other mechanism is a set of hooks for each of the main targets
>  # like `clean', `install', etc.
>
> -language_hooks="Make-hooks"
> +language_hooks="Make-lang-hooks"
>
>  for lang in ${srcdir}/*/config-lang.in
>  do
> @@ -6409,8 +6409,8 @@ done
>  # We link each language in with a set of hooks, reached indirectly via
>  # lang.${target}.  Only do so for selected languages.
>
> -rm -f Make-hooks
> -touch Make-hooks
> +rm -f Make-lang-hooks
> +touch Make-lang-hooks
>  target_list="all.cross start.encap rest.encap tags \
>  	install-common install-man install-info install-pdf install-html dvi \
>  	pdf html uninstall info man srcextra srcman srcinfo \
> @@ -6423,7 +6423,160 @@ do
>  	do
>  		x="$x $lang.$t"
>  	done
> -	echo "lang.$t: $x" >> Make-hooks
> +	echo "lang.$t: $x" >> Make-lang-hooks
> +done
> +
> +# --------------
> +# Plugin hooks
> +# --------------
> +
> +# Make empty files to contain the specs and options for each plugin.
> +# Then add #include lines to for a compiler that has specs and/or options.
> +
> +plugin_opt_files=
> +plugin_specs_files=
> +plugin_tree_files=
> +# These (without "all_") are set in each config-plugin.in.
> +# `plugin' must be a single word so is spelled singularly.
> +all_plugins=
> +# List of plugin makefile fragments.
> +all_plugin_makefrags=
> +
> +# These are the plugins that are set in --enable-plugins,
> +# and are available in the GCC tree.
> +all_selected_plugins=
> +
> +# Add the plugin fragments.
> +# Plugins are added via two mechanisms.  Some information must be
> +# recorded in makefile variables, these are defined in config-plugin.in.
> +# We accumulate them and plug them into the main Makefile.
> +# The other mechanism is a set of hooks for each of the main targets
> +# like `clean', `install', etc.
> +
> +plugin_hooks="Make-plugin-hooks"
> +
> +for plugin in ${srcdir}/*/config-plugin.in
> +do
> +changequote(,)dnl
> +	test "$plugin" = "${srcdir}/*/config-plugin.in" && continue
> +
> +        plugin_alias=`sed -n -e
> 's,^plugin=['"'"'"'"]\(.*\)["'"'"'"'].*$,\1,p' -e 's,^plugin=\([^
> ]*\).*$,\1,p' $plugin`
> +        if test "x$plugin_alias" = x
> +        then
> +              echo "$plugin doesn't set \$plugin." 1>&2
> +              exit 1
> +        fi
> +        subdir="`echo $plugin | sed -e
> 's,^.*/\([^/]*\)/config-plugin.in$,\1,'`"
> +        subdirs="$subdirs $subdir"
> +
> +	# $gcc_subdir is where the gcc integration files are to be found
> +	# for a plugin, both for internal compiler purposes (compiler
> +	# sources implementing front-end to GCC tree converters), and for
> +	# build infrastructure purposes (Make-plugin.in, etc.)
> +	#
> +	# This will be <subdir> (relative to $srcdir) if a line like
> +	# gcc_subdir="<subdir>" or gcc_subdir=<subdir>
> +	# is found in <plugindir>/config-plugin.in, and will remain <plugindir>
> +	# otherwise.
> +	#
> +	# Except for the plugin alias (fetched above), the regular
> +	# "config-plugin.in" contents are always retrieved from $gcc_subdir,
> +	# so a <plugindir>/config-plugin.in setting gcc_subdir typically sets
> +	# only this and the plugin alias.
> +
> +        gcc_subdir=`sed -n -e
> 's,^gcc_subdir=['"'"'"'"]\(.*\)["'"'"'"'].*$,\1,p' -e 's,^gcc_subdir=\([^
> ]*\).*$,\1,p' $plugin`
> +        if [ "$gcc_subdir" = "" ]; then
> +           gcc_subdir="$subdir"
> +        fi
> +
> +        case ",$enable_plugins," in
> +        *,$plugin_alias,*)
> +            all_selected_plugins="$all_selected_plugins $plugin_alias"
> +            if test -f $srcdir/$gcc_subdir/plugin-specs.h; then
> +                plugin_specs_files="$plugin_specs_files
> $srcdir/$gcc_subdir/plugin-specs.h"
> +	    fi
> +	    ;;
> +        esac
> +changequote([,])dnl
> +
> +	plugin=
> +	boot_plugin=
> +	compilers=
> +	outputs=
> +	gtfiles=
> +	subdir_requires=
> +	. ${srcdir}/$gcc_subdir/config-plugin.in
> +	if test "x$plugin" = x
> +	then
> +		echo "${srcdir}/$gcc_subdir/config-plugin.in doesn't set \$plugin." 1>&2
> +		exit 1
> +	fi
> +
> +	ok=:
> +        case ",$enable_plugins," in
> +        	*,$plugin_alias,*) ;;
> +		*)
> +			for i in $subdir_requires; do
> +				test -f "${srcdir}/$i/config-plugin.in" && continue
> +				ok=false
> +				break
> +			done
> +		;;
> +	esac
> +	$ok || continue
> +
> +	all_plugin_makefrags="$all_plugin_makefrags
> \$(srcdir)/$gcc_subdir/Make-plugin.in"
> +	if test -f $srcdir/$gcc_subdir/plugin.opt; then
> +	    plugin_opt_files="$plugin_opt_files $srcdir/$gcc_subdir/plugin.opt"
> +	    all_opt_files="$all_opt_files $srcdir/$gcc_subdir/plugin.opt"
> +	fi
> +	if test -f $srcdir/$gcc_subdir/$subdir-tree.def; then
> +	    plugin_tree_files="$plugin_tree_files
> $srcdir/$gcc_subdir/$subdir-tree.def"
> +	fi
> +	all_plugins="$all_plugins $plugin"
> +	all_compilers="$all_compilers $compilers"
> +	all_outputs="$all_outputs $outputs"
> +	all_gtfiles="$all_gtfiles [[$subdir]] $gtfiles"
> +	case ",$enable_plugins," in
> +		*,analyzer,*)
> +		    AC_DEFINE(ENABLE_ANALYZER, 1, [Define to enable static analyzer.])
> +		    enable_analyzer=yes
> +		    AC_SUBST(enable_analyzer)
> +		    ;;
> +		*) ;;
> +	esac
> +done
> +
> +check_plugins=
> +for plugin in $all_selected_plugins
> +do
> +	check_plugins="$check_plugins check-$plugin"
> +done
> +
> +selftest_plugins=
> +for plugin in $all_selected_plugins
> +do
> +	selftest_plugins="$selftest_plugins selftest-$plugin"
> +done
> +
> +# We link each plugin in with a set of hooks, reached indirectly via
> +# plugin.${target}.  Only do so for selected plugins.
> +
> +rm -f Make-plugin-hooks
> +touch Make-plugin-hooks
> +target_list="all.cross start.encap rest.encap tags \
> +	install-common install-man install-info install-pdf install-html dvi \
> +	pdf html uninstall info man srcextra srcman srcinfo \
> +	mostlyclean clean distclean maintainer-clean install-plugin"
> +
> +for t in $target_list
> +do
> +	x=
> +	for plugin in $all_selected_plugins
> +	do
> +		x="$x $plugin.$t"
> +	done
> +	echo "plugin.$t: $x" >> Make-plugin-hooks
>  done
>
>  # --------
> @@ -6481,7 +6634,10 @@ AC_SUBST(all_compilers)
>  AC_SUBST(all_gtfiles)
>  AC_SUBST(all_lang_makefrags)
>  AC_SUBST(all_languages)
> +AC_SUBST(all_plugin_makefrags)
>  AC_SUBST(all_selected_languages)
> +AC_SUBST(all_plugins)
> +AC_SUBST(all_selected_plugins)
>  AC_SUBST(build_exeext)
>  AC_SUBST(build_install_headers_dir)
>  AC_SUBST(build_xm_file_list)
> @@ -6490,6 +6646,8 @@ AC_SUBST(build_xm_defines)
>  AC_SUBST(build_file_translate)
>  AC_SUBST(check_languages)
>  AC_SUBST(selftest_languages)
> +AC_SUBST(check_plugins)
> +AC_SUBST(selftest_plugins)
>  AC_SUBST(cpp_install_dir)
>  AC_SUBST(xmake_file)
>  AC_SUBST(tmake_file)
> @@ -6515,6 +6673,9 @@ AC_SUBST(install)
>  AC_SUBST(lang_opt_files)
>  AC_SUBST(lang_specs_files)
>  AC_SUBST(lang_tree_files)
> +AC_SUBST(plugin_opt_files)
> +AC_SUBST(plugin_specs_files)
> +AC_SUBST(plugin_tree_files)
>  AC_SUBST(local_prefix)
>  AC_SUBST(md_file)
>  AC_SUBST(objc_boehm_gc)
> @@ -6540,6 +6701,7 @@ AC_SUBST(d_target_objs)
>  AC_SUBST(target_cpu_default)
>
>  AC_SUBST_FILE(language_hooks)
> +AC_SUBST_FILE(plugin_hooks)
>
>  # Echo link setup.
>  if test x${build} = x${host} ; then
> @@ -6742,7 +6904,7 @@ AC_MSG_RESULT($ld_pushpopstate_support)
>  # AC_CONFIG_SUBDIRS($subdirs)
>
>  # Create the Makefile
> -# and configure language subdirectories
> +# and configure language and plugin subdirectories
>  AC_CONFIG_FILES($all_outputs)
>
>  AC_CONFIG_COMMANDS([default],
> diff --git a/gcc/doc/install.texi b/gcc/doc/install.texi
> index dd36a1e..c27807f 100644
> --- a/gcc/doc/install.texi
> +++ b/gcc/doc/install.texi
> @@ -1707,6 +1707,15 @@ specified languages with the stage-1 compiler by
> using @command{make
>  stage1-bubble all-target}, or run the testsuite on the stage-1 compiler
>  for the specified languages using @command{make stage1-start check-gcc}.
>
> +@item --enable-plugins=@var{plugin1},@var{plugin2},@dots{}
> +Specify that a particular subset of in-tree plugins should be built.
> +For a list of valid values for @var{pluginN} you can issue the following
> +command in the @file{gcc} directory of your GCC source tree:@*
> +@smallexample
> +grep ^plugin= */config-plugin.in
> +@end smallexample
> +If you do not pass this flag, then no in-tree plugins will be built.
> +
>  @item --disable-libada
>  Specify that the run-time libraries and tools used by GNAT should not
>  be built.  This can be useful for debugging, or for compatibility with
> --
> 1.8.5.3
>
>

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 01/49] analyzer: user-facing documentation
  2019-12-06 19:57   ` Eric Gallager
@ 2019-12-07  1:41     ` David Malcolm
  0 siblings, 0 replies; 160+ messages in thread
From: David Malcolm @ 2019-12-07  1:41 UTC (permalink / raw)
  To: Eric Gallager; +Cc: gcc-patches

On Fri, 2019-12-06 at 14:57 -0500, Eric Gallager wrote:
> While I get that this is just the documentation, having a good list
> like this will make it easy to use to respond to the individual items
> it documents, so that's what I'm going to use it for:

Thanks; I'm slowly working though the bugzilla links you provided.

Dave

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 28/49] analyzer: new files: analyzer.{cc|h}
  2019-11-16  1:18 ` [PATCH 28/49] analyzer: new files: analyzer.{cc|h} David Malcolm
@ 2019-12-07  3:38   ` Eric Gallager
  2019-12-09 23:22     ` David Malcolm
  2019-12-07 15:02   ` Jeff Law
  2019-12-10 17:17   ` [PATCH 28/49] analyzer: new files: analyzer.{cc|h} Martin Sebor
  2 siblings, 1 reply; 160+ messages in thread
From: Eric Gallager @ 2019-12-07  3:38 UTC (permalink / raw)
  To: David Malcolm; +Cc: gcc-patches

On 11/15/19, David Malcolm <dmalcolm@redhat.com> wrote:
> gcc/ChangeLog:
> 	* analyzer/analyzer.cc: New file.
> 	* analyzer/analyzer.h: New file.
> ---
>  gcc/analyzer/analyzer.cc | 125
> ++++++++++++++++++++++++++++++++++++++++++++++
>  gcc/analyzer/analyzer.h  | 126
> +++++++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 251 insertions(+)
>  create mode 100644 gcc/analyzer/analyzer.cc
>  create mode 100644 gcc/analyzer/analyzer.h
>
> diff --git a/gcc/analyzer/analyzer.cc b/gcc/analyzer/analyzer.cc
> new file mode 100644
> index 0000000..399925c
> --- /dev/null
> +++ b/gcc/analyzer/analyzer.cc
> @@ -0,0 +1,125 @@
> +/* Utility functions for the analyzer.
> +   Copyright (C) 2019 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 "gcc-plugin.h"
> +#include "system.h"
> +#include "coretypes.h"
> +#include "tree.h"
> +#include "gimple.h"
> +#include "diagnostic.h"
> +#include "intl.h"
> +#include "analyzer/analyzer.h"
> +
> +/* Helper function for checkers.  Is the CALL to the given function name?
> */
> +
> +bool
> +is_named_call_p (const gcall *call, const char *funcname)
> +{
> +  gcc_assert (funcname);
> +
> +  tree fndecl = gimple_call_fndecl (call);
> +  if (!fndecl)
> +    return false;
> +
> +  return 0 == strcmp (IDENTIFIER_POINTER (DECL_NAME (fndecl)), funcname);
> +}
> +
> +/* Helper function for checkers.  Is the CALL to the given function name,
> +   and with the given number of arguments?  */
> +
> +bool
> +is_named_call_p (const gcall *call, const char *funcname,
> +		 unsigned int num_args)
> +{
> +  gcc_assert (funcname);
> +
> +  if (!is_named_call_p (call, funcname))
> +    return false;
> +
> +  if (gimple_call_num_args (call) != num_args)
> +    return false;
> +
> +  return true;
> +}
> +
> +/* Return true if stmt is a setjmp call.  */
> +
> +bool
> +is_setjmp_call_p (const gimple *stmt)
> +{
> +  /* TODO: is there a less hacky way to check for "setjmp"?  */
> +  if (const gcall *call = dyn_cast <const gcall *> (stmt))
> +    if (is_named_call_p (call, "_setjmp", 1))
> +      return true;
> +
> +  return false;
> +}
> +
> +/* Return true if stmt is a longjmp call.  */
> +
> +bool
> +is_longjmp_call_p (const gcall *call)
> +{
> +  /* TODO: is there a less hacky way to check for "longjmp"?  */
> +  if (is_named_call_p (call, "longjmp", 2))
> +    return true;
> +
> +  return false;
> +}
> +
> +/* Generate a label_text instance by formatting FMT, using a
> +   temporary clone of the global_dc's printer (thus using its
> +   formatting callbacks).
> +
> +   Colorize if the global_dc supports colorization and CAN_COLORIZE is
> +   true.  */
> +
> +label_text
> +make_label_text (bool can_colorize, const char *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, fmt);
> +
> +  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;
> +}
> diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h
> new file mode 100644
> index 0000000..ace8924
> --- /dev/null
> +++ b/gcc/analyzer/analyzer.h
> @@ -0,0 +1,126 @@
> +/* Utility functions for the analyzer.
> +   Copyright (C) 2019 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/>.  */
> +
> +#ifndef GCC_ANALYZER_ANALYZER_H
> +#define GCC_ANALYZER_ANALYZER_H
> +
> +/* Forward decls of common types, with indentation to show inheritance. */

I'm wondering about the "with indentation to show inheritance" part...
does that require tweaking any editor configuration files or adding
/*INDENT-OFF*/ comments or anything to prevent automatic formatting
tools from "fixing" the indentation to go back to the normal style of
having everything be aligned?

> +
> +class graphviz_out;
> +class supergraph;
> +class supernode;
> +class superedge;
> +  class cfg_superedge;
> +    class switch_cfg_superedge;
> +  class callgraph_superedge;
> +    class call_superedge;
> +    class return_superedge;
> +class svalue;
> +  class region_svalue;
> +  class constant_svalue;
> +  class poisoned_svalue;
> +  class unknown_svalue;
> +  class setjmp_svalue;
> +class region;
> +  class map_region;
> +  class symbolic_region;
> +class region_model;
> +class region_model_context;
> +  class impl_region_model_context;
> +class constraint_manager;
> +class equiv_class;
> +struct model_merger;
> +struct svalue_id_merger_mapping;
> +struct canonicalization;
> +class pending_diagnostic;
> +class checker_path;
> +class extrinsic_state;
> +class sm_state_map;
> +class stmt_finder;
> +class program_point;
> +class program_state;
> +class exploded_graph;
> +class exploded_node;
> +class exploded_edge;
> +class exploded_cluster;
> +class exploded_path;
> +class analysis_plan;
> +class state_purge_map;
> +class state_purge_per_ssa_name;
> +class state_change;
> +
> +////////////////////////////////////////////////////////////////////////////
> +
> +extern bool is_named_call_p (const gcall *call, const char *funcname);
> +extern bool is_named_call_p (const gcall *call, const char *funcname,
> +			     unsigned int num_args);
> +extern bool is_setjmp_call_p (const gimple *stmt);
> +extern bool is_longjmp_call_p (const gcall *call);
> +
> +extern void register_analyzer_pass ();
> +
> +extern label_text make_label_text (bool can_colorize, const char *fmt,
> ...);
> +
> +////////////////////////////////////////////////////////////////////////////
> +
> +/* An RAII-style class for pushing/popping cfun within a scope.
> +   Doing so ensures we get "In function " announcements
> +   from the diagnostics subsystem.  */
> +
> +class auto_cfun
> +{
> +public:
> +  auto_cfun (function *fun) { push_cfun (fun); }
> +  ~auto_cfun () { pop_cfun (); }
> +};
> +
> +////////////////////////////////////////////////////////////////////////////
> +
> +/* Begin suppressing -Wformat and -Wformat-extra-args.  */
> +
> +#define PUSH_IGNORE_WFORMAT \
> +  _Pragma("GCC diagnostic push") \
> +  _Pragma("GCC diagnostic ignored \"-Wformat\"") \
> +  _Pragma("GCC diagnostic ignored \"-Wformat-extra-args\"")
> +
> +/* Finish suppressing -Wformat and -Wformat-extra-args.  */
> +
> +#define POP_IGNORE_WFORMAT \
> +  _Pragma("GCC diagnostic pop")
> +
> +////////////////////////////////////////////////////////////////////////////
> +
> +/* A template for creating hash traits for a POD type.  */
> +
> +template <typename Type>
> +struct pod_hash_traits : typed_noop_remove<Type>
> +{
> +  typedef Type value_type;
> +  typedef Type compare_type;
> +  static inline hashval_t hash (value_type);
> +  static inline bool equal (const value_type &existing,
> +			    const value_type &candidate);
> +  static inline void mark_deleted (Type &);
> +  static inline void mark_empty (Type &);
> +  static inline bool is_deleted (Type);
> +  static inline bool is_empty (Type);
> +};
> +
> +#endif /* GCC_ANALYZER_ANALYZER_H */
> --
> 1.8.5.3
>
>

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 09/49] gimple const-correctness fixes
  2019-11-16  1:17 ` [PATCH 09/49] gimple const-correctness fixes David Malcolm
  2019-12-04 17:06   ` Martin Sebor
  2019-12-06 10:52   ` Richard Biener
@ 2019-12-07 14:28   ` Jeff Law
  2019-12-09  8:18     ` Richard Biener
  2 siblings, 1 reply; 160+ messages in thread
From: Jeff Law @ 2019-12-07 14:28 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On Fri, 2019-11-15 at 20:22 -0500, David Malcolm wrote:
> This patch converts various "gimple *" to "const gimple *" and
> similar
> fixes for gimple subclasses, adding is_a_helper for gimple subclasses
> to support the const form of as_a, and adding a few "const" overloads
> of accessors.
> 
> This is enough to make pp_gimple_stmt_1's stmt const.
> 
> gcc/ChangeLog:
> 	* gimple-predict.h (gimple_predict_predictor): Make "gs" param
> 	const.
> 	(gimple_predict_outcome): Likewise.
> 	* gimple-pretty-print.c (do_niy): Likewise.
> 	(dump_unary_rhs): Likewise.
> 	(dump_binary_rhs): Likewise.
> 	(dump_ternary_rhs): Likewise.
> 	(dump_gimple_assign): Likewise.
> 	(dump_gimple_return): Likewise.
> 	(dump_gimple_call_args): Likewise.
> 	(pp_points_to_solution): Make "pt" param const.
> 	(dump_gimple_call): Make "gs" param const.
> 	(dump_gimple_switch): Likewise.
> 	(dump_gimple_cond): Likewise.
> 	(dump_gimple_label): Likewise.
> 	(dump_gimple_goto): Likewise.
> 	(dump_gimple_bind): Likewise.
> 	(dump_gimple_try): Likewise.
> 	(dump_gimple_catch): Likewise.
> 	(dump_gimple_eh_filter): Likewise.
> 	(dump_gimple_eh_must_not_throw): Likewise.
> 	(dump_gimple_eh_else): Likewise.
> 	(dump_gimple_resx): Likewise.
> 	(dump_gimple_eh_dispatch): Likewise.
> 	(dump_gimple_debug): Likewise.
> 	(dump_gimple_omp_for): Likewise.
> 	(dump_gimple_omp_continue): Likewise.
> 	(dump_gimple_omp_single): Likewise.
> 	(dump_gimple_omp_taskgroup): Likewise.
> 	(dump_gimple_omp_target): Likewise.
> 	(dump_gimple_omp_teams): Likewise.
> 	(dump_gimple_omp_sections): Likewise.
> 	(dump_gimple_omp_block): Likewise.
> 	(dump_gimple_omp_critical): Likewise.
> 	(dump_gimple_omp_ordered): Likewise.
> 	(dump_gimple_omp_scan): Likewise.
> 	(dump_gimple_omp_return): Likewise.
> 	(dump_gimple_transaction): Likewise.
> 	(dump_gimple_asm): Likewise.
> 	(dump_gimple_phi): Make "phi" param const.
> 	(dump_gimple_omp_parallel): Make "gs" param const.
> 	(dump_gimple_omp_task): Likewise.
> 	(dump_gimple_omp_atomic_load): Likewise.
> 	(dump_gimple_omp_atomic_store): Likewise.
> 	(dump_gimple_mem_ops): Likewise.
> 	(pp_gimple_stmt_1): Likewise.  Add "const" to the various as_a
> <>
> 	casts throughout.
> 	* gimple-pretty-print.h (gimple_stmt_1): Make gimple * param
> const.
> 	* gimple.h (is_a_helper <const gdebug *>::test): New.
> 	(is_a_helper <const ggoto *>::test): New.
> 	(is_a_helper <const glabel *>::test): New.
> 	(is_a_helper <const geh_else *>::test): New.
> 	(is_a_helper <const geh_mnt *>::test): New.
> 	(is_a_helper <const gswitch *>::test): New.
> 	(is_a_helper <const gtry *>::test): New.
> 	(is_a_helper <const greturn *>::test): New.
> 	(gimple_call_tail_p): Make param const.
> 	(gimple_call_return_slot_opt_p): Likewise.
> 	(gimple_call_va_arg_pack_p): Likewise.
> 	(gimple_call_use_set): Add const overload.
> 	(gimple_call_clobber_set): Likewise.
> 	(gimple_has_lhs): Make param const.
> 	(gimple_bind_body): Likewise.
> 	(gimple_catch_handler): Likewise.
> 	(gimple_eh_filter_failure): Likewise.
> 	(gimple_eh_must_not_throw_fndecl): Likewise.
> 	(gimple_eh_else_n_body): Likewise.
> 	(gimple_eh_else_e_body): Likewise.
> 	(gimple_try_eval): Likewise.
> 	(gimple_try_cleanup): Likewise.
> 	(gimple_phi_arg): Add const overload.
> 	(gimple_phi_arg_def): Make param const.
> 	(gimple_phi_arg_edge): Likewise.
> 	(gimple_phi_arg_location): Likewise.
> 	(gimple_phi_arg_has_location): Likewise.
> 	(gimple_debug_bind_get_var): Likewise.
> 	(gimple_debug_bind_get_value): Likewise.
> 	(gimple_debug_source_bind_get_var): Likewise.
> 	(gimple_debug_source_bind_get_value): Likewise.
> 	(gimple_omp_body): Likewise.
> 	(gimple_omp_for_collapse): Likewise.
> 	(gimple_omp_for_pre_body): Likewise.
> 	(gimple_transaction_body): Likewise.
> 	* tree-eh.c (lookup_stmt_eh_lp_fn): Make param "t" const.
> 	(lookup_stmt_eh_lp): Likewise.
> 	* tree-eh.h (lookup_stmt_eh_lp_fn): Make param const.
> 	(lookup_stmt_eh_lp): Likewise.
> 	* tree-ssa-alias.h (pt_solution_empty_p): Make param const.
> 	* tree-ssa-structalias.c (pt_solution_empty_p): Likewise.
OK.  And more generally, adding "const" to generally improve our const-
correctness shouldn't require a review cycle.  Just to the normal
testing and install 'em.

Similarly for dropping unnecessary "struct", "class" or "union" like we
see in the tree-ssa-structalias.c changes. I'm terrible about adding
extraneous struct/class keywords when they're not needed.  Anyone
cleaning that up doesn't need explicit approvals either.

Jeff
> 

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 06/49] timevar.h: add auto_client_timevar class
  2019-11-16  1:18 ` [PATCH 06/49] timevar.h: add auto_client_timevar class David Malcolm
  2019-12-04 16:39   ` Martin Sebor
@ 2019-12-07 14:29   ` Jeff Law
  2019-12-08 14:30     ` David Malcolm
  1 sibling, 1 reply; 160+ messages in thread
From: Jeff Law @ 2019-12-07 14:29 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On Fri, 2019-11-15 at 20:22 -0500, David Malcolm wrote:
> This patch adds a class "auto_client_timevar", similar to
> auto_timevar,
> but for plugins to use on their own timing items that aren't in
> timevar.def
> 
> gcc/ChangeLog:
> 	* timevar.h (class auto_client_timevar): New class.
So if we move away from a plug-in architecture to an integrated first
class analyzer, do we still want this change?

jeff
> 

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 03/49] diagnostic_show_locus: move initial newline to callers
  2019-11-16  1:18 ` [PATCH 03/49] diagnostic_show_locus: move initial newline to callers David Malcolm
@ 2019-12-07 14:30   ` Jeff Law
  2019-12-10  1:51     ` David Malcolm
  0 siblings, 1 reply; 160+ messages in thread
From: Jeff Law @ 2019-12-07 14:30 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On Fri, 2019-11-15 at 20:22 -0500, David Malcolm wrote:
> diagnostic_show_locus adds a newline before doing anything (including
> the do-nothing-else case).
> 
> This patch removes this initial newline, adding it to all callers
> of diagnostic_show_locus instead.
> 
> Doing so makes diagnostic_show_locus more flexible, allowing it to be
> used later in this patch kit when printing diagnostic paths.
> 
> gcc/c-family/ChangeLog:
> 	* c-format.c (test_type_mismatch_range_labels): Remove initial
> 	newline from expected outputs.
> 	* c-opts.c (c_diagnostic_finalizer): Add pp_newline call before
> 	call to diagnostic_show_locus.
> 
> gcc/ChangeLog:
> 	* diagnostic-show-locus.c (diagnostic_show_locus): Remove
> initial
> 	newline.
> 	(selftest::test_diagnostic_show_locus_unknown_location): Remove
> 	initial newline from expected output.
> 	(selftest::test_one_liner_simple_caret): Likewise.
> 	(selftest::test_one_liner_caret_and_range): Likewise.
> 	(selftest::test_one_liner_multiple_carets_and_ranges):
> Likewise.
> 	(selftest::test_one_liner_fixit_insert_before): Likewise.
> 	(selftest::test_one_liner_fixit_insert_after): Likewise.
> 	(selftest::test_one_liner_fixit_remove): Likewise.
> 	(selftest::test_one_liner_fixit_replace): Likewise.
> 	(selftest::test_one_liner_fixit_replace_non_equal_range):
> 	Likewise.
> 	(selftest::test_one_liner_fixit_replace_equal_secondary_range):
> 	Likewise.
> 	(selftest::test_one_liner_fixit_validation_adhoc_locations):
> 	Likewise.
> 	(selftest::test_one_liner_many_fixits_1): Likewise.
> 	(selftest::test_one_liner_many_fixits_2): Likewise.
> 	(selftest::test_one_liner_labels): Likewise.
> 	(selftest::test_add_location_if_nearby): Likewise.
> 	(selftest::test_diagnostic_show_locus_fixit_lines): Likewise.
> 	(selftest::test_overlapped_fixit_printing): Likewise.
> 	(selftest::test_overlapped_fixit_printing_2): Likewise.
> 	(selftest::test_fixit_insert_containing_newline): Likewise.
> 	(selftest::test_fixit_insert_containing_newline_2): Likewise.
> 	(selftest::test_fixit_replace_containing_newline): Likewise.
> 	(selftest::test_fixit_deletion_affecting_newline): Likewise.
> 	(selftest::test_line_numbers_multiline_range): Likewise.
> 	* diagnostic.c (default_diagnostic_finalizer): Add pp_newline
> call
> 	before call to diagnostic_show_locus.
> 	(diagnostic_append_note): Likewise.
> 
> gcc/fortran/ChangeLog:
> 	* error.c (gfc_diagnostic_starter): Add pp_newline call
> 	before call to diagnostic_show_locus.
> 
> gcc/testsuite/ChangeLog:
> 	* gcc.dg/plugin/diagnostic_plugin_test_show_locus.c
> 	(custom_diagnostic_finalizer): Add pp_newline call before call
> to
> 	diagnostic_show_locus.
OK
jeff
> 

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 07/49] Replace label_text ctor with "borrow" and "take"
  2019-11-16  1:18 ` [PATCH 07/49] Replace label_text ctor with "borrow" and "take" David Malcolm
@ 2019-12-07 14:34   ` Jeff Law
  0 siblings, 0 replies; 160+ messages in thread
From: Jeff Law @ 2019-12-07 14:34 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On Fri, 2019-11-15 at 20:22 -0500, David Malcolm wrote:
> libcpp's label_text class wraps a text buffer, along with a flag to
> determine if it "owns" the buffer.
> 
> The existing ctor exposed this directly, but I found it difficult
> to remember the sense of flag, so this patch hides the ctor, in
> favor of static member functions "borrow" and "take", to make
> the effect on ownership explicit in the name.
> 
> gcc/c-family/ChangeLog:
> 	* c-format.c (range_label_for_format_type_mismatch::get_text):
> 	Replace label_text ctor called with true with label_text::take.
> 
> gcc/c/ChangeLog:
> 	* c-objc-common.c (range_label_for_type_mismatch::get_text):
> 	Replace label_text ctor calls.
> 
> gcc/cp/ChangeLog:
> 	* error.c (range_label_for_type_mismatch::get_text): Replace
> 	label_text ctor calls with label_text::borrow.
> 
> gcc/ChangeLog:
> 	* gcc-rich-location.c
> 	(maybe_range_label_for_tree_type_mismatch::get_text): Replace
> 	label_text ctor call with label_text::borrow.
> 	* gcc-rich-location.h (text_range_label::get_text): Replace
> 	label_text ctor called with false with label_text::borrow.
> 
> libcpp/ChangeLog:
> 	* include/line-map.h (label_text::label_text): Make private.
> 	(label_text::borrow): New.
> 	(label_text::take): New.
> 	(label_text::take_or_copy): New.
OK
jeff

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 08/49] Introduce pretty_printer::clone vfunc
  2019-11-16  1:17 ` [PATCH 08/49] Introduce pretty_printer::clone vfunc David Malcolm
@ 2019-12-07 14:36   ` Jeff Law
  0 siblings, 0 replies; 160+ messages in thread
From: Jeff Law @ 2019-12-07 14:36 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On Fri, 2019-11-15 at 20:22 -0500, David Malcolm wrote:
> This patch provides a way to clone a pretty_printer.
> 
> This is needed so that we can capture text in a label_text and make
> layout decisions based on it, using the policy of global_dc's
> printer,
> whilst within a call to diagnostic_show_locus.  We can't print with
> the pretty_printer itself within a call to diagnostic_show_locus
> since
> it has partly-buffered content.
> 
> gcc/c-family/ChangeLog:
> 	* c-pretty-print.c (c_pretty_printer::clone): New vfunc
> 	implementation.
> 	* c-pretty-print.h (c_pretty_printer::clone): New vfunc decl.
> 
> gcc/cp/ChangeLog:
> 	* cxx-pretty-print.c (cxx_pretty_printer::clone): New vfunc
> 	implementation.
> 	* cxx-pretty-print.h (cxx_pretty_printer::clone): New vfunc
> decl.
> 	* error.c (cxx_format_postprocessor::clone): New vfunc.
> 
> gcc/ChangeLog:
> 	* pretty-print.c (pretty_printer::pretty_printer): New copy-
> ctor.
> 	(pretty_printer::clone): New vfunc implementation.
> 	* pretty-print.h (format_postprocessor::clone): New pure vfunc
> 	decl.
> 	(pretty_printer::pretty_printer): New copy-ctor decl.
> 	(pretty_printer::clone): New vfunc decl.
OK
jeff
> 

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 13/49] function-tests.c: expose selftest::make_fndecl for use elsewhere
  2019-11-16  1:21 ` [PATCH 13/49] function-tests.c: expose selftest::make_fndecl for use elsewhere David Malcolm
@ 2019-12-07 14:37   ` Jeff Law
  0 siblings, 0 replies; 160+ messages in thread
From: Jeff Law @ 2019-12-07 14:37 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> This is used by new selftests later on in the patch kit.
> 
> gcc/ChangeLog:
> 	* function-tests.c (selftest::make_fndecl): Make non-static.
> 	* selftest.h (selftest::make_fndecl): New decl.
OK
jeff


^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 14/49] hash-map-tests.c: add a selftest involving int_hash
  2019-11-16  1:17 ` [PATCH 14/49] hash-map-tests.c: add a selftest involving int_hash David Malcolm
@ 2019-12-07 14:38   ` Jeff Law
  0 siblings, 0 replies; 160+ messages in thread
From: Jeff Law @ 2019-12-07 14:38 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> gcc/ChangeLog:
> 	* hash-map-tests.c (selftest::test_map_of_int_to_strings): New
> 	selftest.
> 	(selftest::hash_map_tests_c_tests): Call it.
OK
jeff
> 

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 16/49] Add support for in-tree plugins
  2019-11-16  1:18 ` [PATCH 16/49] Add support for in-tree plugins David Malcolm
  2019-12-06 22:41   ` Eric Gallager
@ 2019-12-07 14:39   ` Jeff Law
  2019-12-08 14:32     ` David Malcolm
  1 sibling, 1 reply; 160+ messages in thread
From: Jeff Law @ 2019-12-07 14:39 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> This patch adds support for "in-tree" plugins i.e. GCC plugins that
> live
> in the GCC source tree and are shipped as part of the GCC tarball.
> 
> The patch adds Makefile/configure machinery for handling in-tree GCC
> plugins, adapted from how we support frontends.
> 
> Each in-tree plugin should provide a Make-plugin.in and config-
> plugin.in,
> analogous to the Make-lang.in and config-lang.in provided by a
> frontend;
> they can also supply options via a plugin.opt, analogous to a
> frontend's
> lang.opt.
> 
> The patch adds a --enable-plugins=[LIST OF PLUGIN NAMES] configure
> option,
> analogous to --enable-languages.  The default is for no such plugins
> to be
> enabled.
> 
> ChangeLog:
> 	* configure.ac: Handle --enable-plugins.
> 
> gcc/ChangeLog:
> 	* Makefile.in (CONFIG_PLUGINS): New.
> 	(SUBDIRS, subdirs): Generalize to cover plugins as well as
> 	languages.
> 	(plugin_opt_files): New.
> 	(ALL_OPT_FILES): Add plugin_opt_files.
> 	(ALL_HOST_PLUGIN_OBJS): New.
> 	(ALL_HOST_OBJS): Add ALL_HOST_PLUGIN_OBJS.
> 	(plugin_hooks): New.
> 	(PLUGIN_MAKEFRAGS): New; include them.
> 	(Makefile): Add dependency on PLUGIN_MAKEFRAGS.
> 	(all.cross): Add dependency on plugin.all.cross.
> 	(start.encap): Add plugin.start.encap.
> 	(rest.encap): Add plugin.rest.encap.
> 	(SELFTEST_TARGETS): Add selftest_plugins.
> 	(info): Add dependency on lang.info.
> 	(dvi): Add dependency on plugin.dvi.
> 	(pdf): Add dependency on plugin.pdf.
> 	(HTMLS_BUILD): Add plugin.html.
> 	(man): Add plugin.man.
> 	(mostlyclean): Add plugin.mostlyclean.
> 	(clean): Add plugin.clean.
> 	(distclean): Update for renaming of Make-hooks to Make-lang-
> hooks;
> 	add Make-plugin-hooks.  Add plugin.distclean dependency.
> 	(maintainer-clean): Add plugin.maintainer-clean.
> 	(install-plugin): Add plugin.install-plugin.
> 	(install-common): Add plugin.install-common.
> 	(install-info): Add plugin.install-info.
> 	(install-pdf): Add plugin.install-pdf.
> 	(install-html): Add plugin.install-html.
> 	(install-man): Add plugin.install-man.
> 	(uninstall): Add plugin.uninstall.
> 	(TAGS): Add plugin.tags.
> 	* configure.ac (Make-hooks): Rename to Make-lang-hooks.
> 	(plugin_opt_files): New.
> 	(plugin_specs_files): New.
> 	(plugin_tree_files): New.
> 	(all_plugins): New.
> 	(all_plugin_makefrags): New.
> 	(all_selected_plugins): New.
> 	(plugin_hooks): New.
> 	("Plugin hooks"): New section.  Iterate through config-
> plugin.in,
> 	analogously to config-lang.in.
> 	(check_plugins): New.
> 	(selftest_plugins): New.
> 	(Make-plugin-hooks): Emit.
> 	* doc/install.texi (--enable-plugins): New option.
So do we want to push on the concept of in-tree plugins for somethign
like annobin?  If not, given the general desire to not use plugins for
teh analyzer, would we still want this?

jeff


^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 17/49] Support for adding selftests via a plugin
  2019-11-16  1:18 ` [PATCH 17/49] Support for adding selftests via a plugin David Malcolm
@ 2019-12-07 14:41   ` Jeff Law
  2019-12-09 23:18     ` David Malcolm
  0 siblings, 1 reply; 160+ messages in thread
From: Jeff Law @ 2019-12-07 14:41 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> This patch provides a plugin callback for invoking selftests, so that
> a
> plugin can add tests to those run by -fself-test=DIR.  The callback
> invocation uses invoke_plugin_callbacks, which is a no-op if plugin
> support is disabled.
> 
> gcc/ChangeLog:
> 	* plugin.c (register_callback): Add case for
> PLUGIN_RUN_SELFTESTS.
> 	(invoke_plugin_callbacks_full): Likewise.
> 	* plugin.def (PLUGIN_RUN_SELFTESTS): New event.
> 	* selftest-run-tests.c: Include "plugin.h".
> 	(selftest::run_tests): Run any plugin-provided selftests.
I'm generally in favor of having the ability for plugins to invoke
selftests.  But do we want to push on this now given the preferred
direction for this kit?

jeff


^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 12/49] Add diagnostic paths
  2019-11-16  1:18 ` [PATCH 12/49] Add diagnostic paths David Malcolm
@ 2019-12-07 14:45   ` Jeff Law
  2019-12-19  2:49     ` David Malcolm
  0 siblings, 1 reply; 160+ messages in thread
From: Jeff Law @ 2019-12-07 14:45 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On Fri, 2019-11-15 at 20:22 -0500, David Malcolm wrote:
> This patch adds support for associating a "diagnostic_path" with a
> diagnostic: a sequence of events predicted by the compiler that leads
> to
> the problem occurring, with their locations in the user's source,
> text descriptions, and stack information (for handling
> interprocedural
> paths).
> 
> For example, the following (hypothetical) error has a 3-event
> intraprocedural path:
> 
> test.c: In function 'demo':
> test.c:29:5: error: passing NULL as argument 1 to 'PyList_Append'
> which
>   requires a non-NULL parameter
>    29 |     PyList_Append(list, item);
>       |     ^~~~~~~~~~~~~~~~~~~~~~~~~
>   'demo': events 1-3
>      |
>      |   25 |   list = PyList_New(0);
>      |      |          ^~~~~~~~~~~~~
>      |      |          |
>      |      |          (1) when 'PyList_New' fails, returning NULL
>      |   26 |
>      |   27 |   for (i = 0; i < count; i++) {
>      |      |   ~~~
>      |      |   |
>      |      |   (2) when 'i < count'
>      |   28 |     item = PyLong_FromLong(random());
>      |   29 |     PyList_Append(list, item);
>      |      |     ~~~~~~~~~~~~~~~~~~~~~~~~~
>      |      |     |
>      |      |     (3) when calling 'PyList_Append', passing NULL from
> (1) as argument 1
>      |
> 
> The patch adds a new "%@" format code for printing event IDs, so that
> in the above, the description of event (3) mentions event (1),
> showing
> the user where the bogus NULL value comes from (the event IDs are
> colorized to draw the user's attention to them).
> 
> There is a separation between data vs presentation: the above shows
> how
> the diagnostic-printing code has consolidated the path into a single
> run
> of events, since all the events are near each other and within the
> same
> function; more complicated examples (such as interprocedural paths)
> might be printed as multiple runs of events.
> 
> Examples of how interprocedural paths are printed can be seen in the
> test suite (which uses a plugin to exercise the code without relying
> on specific warnings using this functionality).
> 
> Other output formats include
> - JSON,
> - printing each event as a separate "note", and
> - to not emit paths.
> 
> (I have a separate script that can generate HTML from the JSON, but
> HTML
> is not my speciality; help from a web front-end expert to make it
> look
> good would be appreciated).
> 
> gcc/ChangeLog:
> 	* Makefile.in (OBJS): Add tree-diagnostic-path.o.
> 	* common.opt (fdiagnostics-path-format=): New option.
> 	(diagnostic_path_format): New enum.
> 	(fdiagnostics-show-path-depths): New option.
> 	* coretypes.h (diagnostic_event_id_t): New forward decl.
> 	* diagnostic-color.c (color_dict): Add "path".
> 	* diagnostic-event-id.h: New file.
> 	* diagnostic-format-json.cc (json_from_expanded_location): Make
> 	non-static.
> 	(json_end_diagnostic): Call context->make_json_for_path if it
> 	exists and the diagnostic has a path.
> 	(diagnostic_output_format_init): Clear context->print_path.
> 	* diagnostic-path.h: New file.
> 	* diagnostic-show-locus.c (colorizer::set_range): Special-case
> 	when printing a run of events in a diagnostic_path so that they
> 	all get the same color.
> 	(layout::m_diagnostic_path_p): New field.
> 	(layout::layout): Initialize it.
> 	(layout::print_any_labels): Don't colorize the label text for
> an
> 	event in a diagnostic_path.
> 	(gcc_rich_location::add_location_if_nearby): Add
> 	"restrict_to_current_line_spans" and "label" params.  Pass the
> 	former to layout.maybe_add_location_range; pass the latter
> 	when calling add_range.
> 	* diagnostic.c: Include "diagnostic-path.h".
> 	(diagnostic_initialize): Initialize context->path_format and
> 	context->show_path_depths.
> 	(diagnostic_show_any_path): New function.
> 	(diagnostic_path::interprocedural_p): New function.
> 	(diagnostic_report_diagnostic): Call diagnostic_show_any_path.
> 	(simple_diagnostic_path::num_events): New function.
> 	(simple_diagnostic_path::get_event): New function.
> 	(simple_diagnostic_path::add_event): New function.
> 	(simple_diagnostic_event::simple_diagnostic_event): New ctor.
> 	(simple_diagnostic_event::~simple_diagnostic_event): New dtor.
> 	(debug): New overload taking a diagnostic_path *.
> 	* diagnostic.def (DK_DIAGNOSTIC_PATH): New.
> 	* diagnostic.h (enum diagnostic_path_format): New enum.
> 	(json::value): New forward decl.
> 	(diagnostic_context::path_format): New field.
> 	(diagnostic_context::show_path_depths): New field.
> 	(diagnostic_context::print_path): New callback field.
> 	(diagnostic_context::make_json_for_path): New callback field.
> 	(diagnostic_show_any_path): New decl.
> 	(json_from_expanded_location): New decl.
> 	* doc/invoke.texi (-fdiagnostics-path-format=): New option.
> 	(-fdiagnostics-show-path-depths): New option.
> 	(-fdiagnostics-color): Add "path" to description of default
> 	GCC_COLORS; describe it.
> 	(-fdiagnostics-format=json): Document how diagnostic paths are
> 	represented in the JSON output format.
> 	* gcc-rich-location.h
> (gcc_rich_location::add_location_if_nearby):
> 	Add optional params "restrict_to_current_line_spans" and
> "label".
> 	* opts.c (common_handle_option): Handle
> 	OPT_fdiagnostics_path_format_ and
> 	OPT_fdiagnostics_show_path_depths.
> 	* pretty-print.c: Include "diagnostic-event-id.h".
> 	(pp_format): Implement "%@" format code for printing
> 	diagnostic_event_id_t *.
> 	(selftest::test_pp_format): Add tests for "%@".
> 	* selftest-run-tests.c (selftest::run_tests): Call
> 	selftest::tree_diagnostic_path_cc_tests.
> 	* selftest.h (selftest::tree_diagnostic_path_cc_tests): New
> decl.
> 	* toplev.c (general_init): Initialize global_dc->path_format
> and
> 	global_dc->show_path_depths.
> 	* tree-diagnostic-path.cc: New file.
> 	* tree-diagnostic.c (maybe_unwind_expanded_macro_loc): Make
> 	non-static.  Drop "diagnostic" param in favor of storing the
> 	original value of "where" and re-using it.
> 	(virt_loc_aware_diagnostic_finalizer): Update for dropped param
> of
> 	maybe_unwind_expanded_macro_loc.
> 	(tree_diagnostics_defaults): Initialize context->print_path and
> 	context->make_json_for_path.
> 	* tree-diagnostic.h (default_tree_diagnostic_path_printer): New
> 	decl.
> 	(default_tree_make_json_for_path): New decl.
> 	(maybe_unwind_expanded_macro_loc): New decl.
> 
> gcc/c-family/ChangeLog:
> 	* c-format.c (local_event_ptr_node): New.
> 	(PP_FORMAT_CHAR_TABLE): Add entry for "%@".
> 	(init_dynamic_diag_info): Initialize local_event_ptr_node.
> 	* c-format.h (T_EVENT_PTR): New define.
> 
> gcc/testsuite/ChangeLog:
> 	* gcc.dg/format/gcc_diag-10.c (diagnostic_event_id_t): New
> 	typedef.
> 	(test_diag): Add coverage of "%@".
> 	* gcc.dg/plugin/diagnostic-path-format-default.c: New test.
> 	* gcc.dg/plugin/diagnostic-path-format-inline-events-1.c: New
> test.
> 	* gcc.dg/plugin/diagnostic-path-format-inline-events-2.c: New
> test.
> 	* gcc.dg/plugin/diagnostic-path-format-inline-events-3.c: New
> test.
> 	* gcc.dg/plugin/diagnostic-path-format-none.c: New test.
> 	* gcc.dg/plugin/diagnostic-test-paths-1.c: New test.
> 	* gcc.dg/plugin/diagnostic-test-paths-2.c: New test.
> 	* gcc.dg/plugin/diagnostic-test-paths-3.c: New test.
> 	* gcc.dg/plugin/diagnostic_plugin_test_paths.c: New.
> 	* gcc.dg/plugin/plugin.exp: Add the new plugin and test cases.
> 
> libcpp/ChangeLog:
> 	* include/line-map.h (class diagnostic_path): New forward decl.
> 	(rich_location::get_path): New accessor.
> 	(rich_location::set_path): New function.
> 	(rich_location::m_path): New field.
> 	* line-map.c (rich_location::rich_location): Initialize m_path.
Seems like we could make an argument to include this in an expanded
"diagnostic framework" maintainership which would allow self-approval.

jeff


^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 19/49] analyzer: new files: analyzer-selftests.{cc|h}
  2019-11-16  1:18 ` [PATCH 19/49] analyzer: new files: analyzer-selftests.{cc|h} David Malcolm
@ 2019-12-07 14:48   ` Jeff Law
  0 siblings, 0 replies; 160+ messages in thread
From: Jeff Law @ 2019-12-07 14:48 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> gcc/ChangeLog:
> 	* analyzer/analyzer-selftests.cc: New file.
> 	* analyzer/analyzer-selftests.h: New file.
This obviously can't stand on its own, but it's fine once prereqs are
approved.

jeff


^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 22/49] analyzer: params.def: new parameters
  2019-11-16  1:18 ` [PATCH 22/49] analyzer: params.def: new parameters David Malcolm
@ 2019-12-07 14:49   ` Jeff Law
  2019-12-08  5:42     ` Eric Gallager
  0 siblings, 1 reply; 160+ messages in thread
From: Jeff Law @ 2019-12-07 14:49 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> gcc/ChangeLog:
> 	* params.def (PARAM_ANALYZER_BB_EXPLOSION_FACTOR): New param.
> 	(PARAM_ANALYZER_MAX_ENODES_PER_PROGRAM_POINT): New param.
> 	(PARAM_ANALYZER_MAX_RECURSION_DEPTH): New param.
> 	(PARAM_ANALYZER_MIN_SNODES_FOR_CALL_SUMMARY): New param.
Doesn't this need a rethink/reimplementation after M. Liska's changes?

jeff

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 24/49] analyzer: new file: analyzer-pass.cc
  2019-11-16  1:18 ` [PATCH 24/49] analyzer: new file: analyzer-pass.cc David Malcolm
@ 2019-12-07 14:51   ` Jeff Law
  2019-12-08 14:38     ` David Malcolm
  0 siblings, 1 reply; 160+ messages in thread
From: Jeff Law @ 2019-12-07 14:51 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> This patch adds the IPA pass boilerplate for the analyzer.
> 
> gcc/ChangeLog:
> 	* analyzer/analyzer-pass.cc: New file.
Nothing I see controversial here.  But obviously will need some
adjustment if we're moving away from using the plugin facilities.

jeff

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 25/49] analyzer: new files: graphviz.{cc|h}
  2019-11-16  1:21 ` [PATCH 25/49] analyzer: new files: graphviz.{cc|h} David Malcolm
@ 2019-12-07 14:54   ` Jeff Law
  2019-12-09 23:13     ` David Malcolm
  0 siblings, 1 reply; 160+ messages in thread
From: Jeff Law @ 2019-12-07 14:54 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> This patch adds a simple wrapper class to make it easier to
> write human-readable .dot files.
> 
> gcc/ChangeLog:
> 	* analyzer/graphviz.cc: New file.
> 	* analyzer/graphviz.h: New file.
This doesn't seem specific to the analyzer at all.  Can we move it into
a more generic place for others to see and be able to use?

jeff


^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 26/49] analyzer: new files: digraph.{cc|h} and shortest-paths.h
  2019-11-16  1:18 ` [PATCH 26/49] analyzer: new files: digraph.{cc|h} and shortest-paths.h David Malcolm
@ 2019-12-07 14:58   ` Jeff Law
  2019-12-09 23:15     ` David Malcolm
  2019-12-10  0:45   ` Martin Sebor
  1 sibling, 1 reply; 160+ messages in thread
From: Jeff Law @ 2019-12-07 14:58 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> This patch adds template classes for directed graphs, their nodes
> and edges, and for finding the shortest path through such a graph.
> 
> gcc/ChangeLog:
> 	* analyzer/digraph.cc: New file.
> 	* analyzer/digraph.h: New file.
> 	* analyzer/shortest-paths.h: New file.
Nothing too worrisome here.  I'm kindof surprised if haven't needed
some of these capabilities before now.  Thoughts on moving them outside
of analyzer into a more generic location?

jeff
> ---
>  gcc/analyzer/digraph.cc       | 189 ++++++++++++++++++++++++++++++++
>  gcc/analyzer/digraph.h        | 248
> ++++++++++++++++++++++++++++++++++++++++++
>  gcc/analyzer/shortest-paths.h | 147 +++++++++++++++++++++++++
>  3 files changed, 584 insertions(+)
>  create mode 100644 gcc/analyzer/digraph.cc
>  create mode 100644 gcc/analyzer/digraph.h
>  create mode 100644 gcc/analyzer/shortest-paths.h
> 
> diff --git a/gcc/analyzer/digraph.cc b/gcc/analyzer/digraph.cc
> new file mode 100644
> index 0000000..c1fa46e
> --- /dev/null
> +++ b/gcc/analyzer/digraph.cc
> @@ -0,0 +1,189 @@
> +/* Template classes for directed graphs.
> +   Copyright (C) 2019 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 "gcc-plugin.h"
> +#include "system.h"
> +#include "coretypes.h"
> +#include "diagnostic.h"
> +#include "analyzer/graphviz.h"
> +#include "analyzer/digraph.h"
> +#include "analyzer/shortest-paths.h"
> +#include "selftest.h"
> +
> +#if CHECKING_P
> +
> +namespace selftest {
> +
> +/* A family of digraph classes for writing selftests.  */
> +
> +struct test_node;
> +struct test_edge;
> +struct test_graph;
> +struct test_dump_args_t {};
> +struct test_cluster;
> +
> +struct test_graph_traits
> +{
> +  typedef test_node node_t;
> +  typedef test_edge edge_t;
> +  typedef test_graph graph_t;
> +  typedef test_dump_args_t dump_args_t;
> +  typedef test_cluster cluster_t;
> +};
> +
> +struct test_node : public dnode<test_graph_traits>
> +{
> +  test_node (const char *name, int index) : m_name (name), m_index
> (index) {}
> +  void dump_dot (graphviz_out *, const dump_args_t &) const OVERRIDE
> +  {
> +  }
> +
> +  const char *m_name;
> +  int m_index;
> +};
> +
> +struct test_edge : public dedge<test_graph_traits>
> +{
> +  test_edge (node_t *src, node_t *dest)
> +  : dedge (src, dest)
> +  {}
> +
> +  void dump_dot (graphviz_out *gv, const dump_args_t &) const
> OVERRIDE
> +  {
> +    gv->println ("%s -> %s;", m_src->m_name, m_dest->m_name);
> +  }
> +};
> +
> +struct test_graph : public digraph<test_graph_traits>
> +{
> +  test_node *add_test_node (const char *name)
> +  {
> +    test_node *result = new test_node (name, m_nodes.length ());
> +    add_node (result);
> +    return result;
> +  }
> +
> +  test_edge *add_test_edge (test_node *src, test_node *dst)
> +  {
> +    test_edge *result = new test_edge (src, dst);
> +    add_edge (result);
> +    return result;
> +  }
> +};
> +
> +struct test_cluster : public cluster<test_graph_traits>
> +{
> +};
> +
> +struct test_path
> +{
> +  auto_vec<const test_edge *> m_edges;
> +};
> +
> +/* Smoketest of digraph dumping.  */
> +
> +static void
> +test_dump_to_dot ()
> +{
> +  test_graph g;
> +  test_node *a = g.add_test_node ("a");
> +  test_node *b = g.add_test_node ("b");
> +  g.add_test_edge (a, b);
> +
> +  pretty_printer pp;
> +  pp.buffer->stream = NULL;
> +  test_dump_args_t dump_args;
> +  g.dump_dot_to_pp (&pp, NULL, dump_args);
> +
> +  ASSERT_STR_CONTAINS (pp_formatted_text (&pp),
> +		       "a -> b;\n");
> +}
> +
> +/* Test shortest paths from A in this digraph,
> +   where edges run top-to-bottom if not otherwise labeled:
> +
> +      A
> +     / \
> +    B   C-->D
> +    |   |
> +    E   |
> +     \ /
> +      F.  */
> +
> +static void
> +test_shortest_paths ()
> +{
> +  test_graph g;
> +  test_node *a = g.add_test_node ("a");
> +  test_node *b = g.add_test_node ("b");
> +  test_node *c = g.add_test_node ("d");
> +  test_node *d = g.add_test_node ("d");
> +  test_node *e = g.add_test_node ("e");
> +  test_node *f = g.add_test_node ("f");
> +
> +  test_edge *ab = g.add_test_edge (a, b);
> +  test_edge *ac = g.add_test_edge (a, c);
> +  test_edge *cd = g.add_test_edge (c, d);
> +  test_edge *be = g.add_test_edge (b, e);
> +  g.add_test_edge (e, f);
> +  test_edge *cf = g.add_test_edge (c, f);
> +
> +  shortest_paths<test_graph_traits, test_path> sp (g, a);
> +
> +  test_path path_to_a = sp.get_shortest_path (a);
> +  ASSERT_EQ (path_to_a.m_edges.length (), 0);
> +
> +  test_path path_to_b = sp.get_shortest_path (b);
> +  ASSERT_EQ (path_to_b.m_edges.length (), 1);
> +  ASSERT_EQ (path_to_b.m_edges[0], ab);
> +
> +  test_path path_to_c = sp.get_shortest_path (c);
> +  ASSERT_EQ (path_to_c.m_edges.length (), 1);
> +  ASSERT_EQ (path_to_c.m_edges[0], ac);
> +
> +  test_path path_to_d = sp.get_shortest_path (d);
> +  ASSERT_EQ (path_to_d.m_edges.length (), 2);
> +  ASSERT_EQ (path_to_d.m_edges[0], ac);
> +  ASSERT_EQ (path_to_d.m_edges[1], cd);
> +
> +  test_path path_to_e = sp.get_shortest_path (e);
> +  ASSERT_EQ (path_to_e.m_edges.length (), 2);
> +  ASSERT_EQ (path_to_e.m_edges[0], ab);
> +  ASSERT_EQ (path_to_e.m_edges[1], be);
> +
> +  test_path path_to_f = sp.get_shortest_path (f);
> +  ASSERT_EQ (path_to_f.m_edges.length (), 2);
> +  ASSERT_EQ (path_to_f.m_edges[0], ac);
> +  ASSERT_EQ (path_to_f.m_edges[1], cf);
> +}
> +
> +/* Run all of the selftests within this file.  */
> +
> +void
> +analyzer_digraph_cc_tests ()
> +{
> +  test_dump_to_dot ();
> +  test_shortest_paths ();
> +}
> +
> +} // namespace selftest
> +
> +#endif /* #if CHECKING_P */
> diff --git a/gcc/analyzer/digraph.h b/gcc/analyzer/digraph.h
> new file mode 100644
> index 0000000..5c6a496
> --- /dev/null
> +++ b/gcc/analyzer/digraph.h
> @@ -0,0 +1,248 @@
> +/* Template classes for directed graphs.
> +   Copyright (C) 2019 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/>;.  */
> +
> +#ifndef GCC_ANALYZER_DIGRAPH_H
> +#define GCC_ANALYZER_DIGRAPH_H
> +
> +#include "diagnostic.h"
> +#include "tree-diagnostic.h" /* for default_tree_printer.  */
> +#include "analyzer/graphviz.h"
> +
> +/* Templates for a family of classes: digraph, node, edge, and
> cluster.
> +   This assumes a traits type with the following typedefs:
> +   node_t: the node class
> +   edge_t: the edge class
> +   dump_args_t: additional args for dot-dumps
> +   cluster_t: the cluster class (for use when generating .dot
> files).
> +
> +   Using a template allows for typesafe nodes and edges: a node's
> +   predecessor and successor edges can be of a node-specific edge
> +   subclass, without needing casting.  */
> +
> +/* Abstract base class for a node in a directed graph.  */
> +
> +template <typename GraphTraits>
> +class dnode
> +{
> + public:
> +  typedef typename GraphTraits::edge_t edge_t;
> +  typedef typename GraphTraits::dump_args_t dump_args_t;
> +
> +  virtual ~dnode () {}
> +  virtual void dump_dot (graphviz_out *gv, const dump_args_t &args)
> const = 0;
> +
> +  auto_vec<edge_t *> m_preds;
> +  auto_vec<edge_t *> m_succs;
> +};
> +
> +/* Abstract base class for an edge in a directed graph.  */
> +
> +template <typename GraphTraits>
> +class dedge
> +{
> + public:
> +  typedef typename GraphTraits::node_t node_t;
> +  typedef typename GraphTraits::dump_args_t dump_args_t;
> +
> +  dedge (node_t *src, node_t *dest)
> +  : m_src (src), m_dest (dest) {}
> +
> +  virtual ~dedge () {}
> +
> +  virtual void dump_dot (graphviz_out *gv, const dump_args_t &args)
> const = 0;
> +
> +  node_t *const m_src;
> +  node_t *const m_dest;
> +};
> +
> +/* Abstract base class for a directed graph.
> +   This class maintains the vectors of nodes and edges,
> +   and owns the nodes and edges.  */
> +
> +template <typename GraphTraits>
> +class digraph
> +{
> + public:
> +  typedef typename GraphTraits::node_t node_t;
> +  typedef typename GraphTraits::edge_t edge_t;
> +  typedef typename GraphTraits::dump_args_t dump_args_t;
> +  typedef typename GraphTraits::cluster_t cluster_t;
> +
> +  digraph () {}
> +  virtual ~digraph () {}
> +
> +  void dump_dot_to_pp (pretty_printer *pp,
> +		       cluster_t *root_cluster,
> +		       const dump_args_t &args) const;
> +  void dump_dot_to_file (FILE *fp,
> +			 cluster_t *root_cluster,
> +			 const dump_args_t &args) const;
> +  void dump_dot (const char *path,
> +		 cluster_t *root_cluster,
> +		 const dump_args_t &args) const;
> +
> +  void add_node (node_t *node);
> +  void add_edge (edge_t *edge);
> +
> +  auto_delete_vec<node_t> m_nodes;
> +  auto_delete_vec<edge_t> m_edges;
> +};
> +
> +/* Abstract base class for splitting dnodes into hierarchical
> clusters
> +   in the generated .dot file.
> +
> +   See "Subgraphs and Clusters" within
> +     https://www.graphviz.org/doc/info/lang.html
> +   and e.g.
> +     https://graphviz.gitlab.io/_pages/Gallery/directed/cluster.html
> +
> +   If a root_cluster is passed to dump_dot*, then all nodes will be
> +   added to it at the start of dumping, via calls to add_node.
> +
> +   The root cluster can organize the nodes into a hierarchy of
> +   child clusters.
> +
> +   After all nodes are added to the root cluster, dump_dot will then
> +   be called on it (and not on the nodes themselves).  */
> +
> +template <typename GraphTraits>
> +class cluster
> +{
> + public:
> +  typedef typename GraphTraits::node_t node_t;
> +  typedef typename GraphTraits::dump_args_t dump_args_t;
> +
> +  virtual ~cluster () {}
> +
> +  virtual void add_node (node_t *node) = 0;
> +
> +  /* Recursively dump the cluster, all nodes, and child
> clusters.  */
> +  virtual void dump_dot (graphviz_out *gv, const dump_args_t &)
> const = 0;
> +};
> +
> +////////////////////////////////////////////////////////////////////
> ////////
> +
> +/* Write .dot information for this graph to PP, passing ARGS to the
> nodes
> +   and edges.
> +   If ROOT_CLUSTER is non-NULL, use it to organize the nodes into
> clusters.  */
> +
> +template <typename GraphTraits>
> +inline void
> +digraph<GraphTraits>::dump_dot_to_pp (pretty_printer *pp,
> +				      cluster_t *root_cluster,
> +				      const dump_args_t &args) const
> +{
> +  graphviz_out gv (pp);
> +
> +  pp_string (pp, "digraph \"");
> +  pp_string (pp, "base");
> +  pp_string (pp, "\" {\n");
> +
> +  gv.indent ();
> +
> +  pp_string (pp, "overlap=false;\n");
> +  pp_string (pp, "compound=true;\n");
> +
> +  /* If using clustering, emit all nodes via clusters.  */
> +  if (root_cluster)
> +    {
> +      int i;
> +      node_t *n;
> +      FOR_EACH_VEC_ELT (m_nodes, i, n)
> +	root_cluster->add_node (n);
> +      root_cluster->dump_dot (&gv, args);
> +    }
> +  else
> +    {
> +      /* Otherwise, display all nodes at top level.  */
> +      int i;
> +      node_t *n;
> +      FOR_EACH_VEC_ELT (m_nodes, i, n)
> +	n->dump_dot (&gv, args);
> +    }
> +
> +  /* Edges.  */
> +  int i;
> +  edge_t *e;
> +  FOR_EACH_VEC_ELT (m_edges, i, e)
> +    e->dump_dot (&gv, args);
> +
> +  /* Terminate "digraph" */
> +  gv.outdent ();
> +  pp_string (pp, "}");
> +  pp_newline (pp);
> +}
> +
> +/* Write .dot information for this graph to FP, passing ARGS to the
> nodes
> +   and edges.
> +   If ROOT_CLUSTER is non-NULL, use it to organize the nodes into
> clusters.  */
> +
> +template <typename GraphTraits>
> +inline void
> +digraph<GraphTraits>::dump_dot_to_file (FILE *fp,
> +					cluster_t *root_cluster,
> +					const dump_args_t &args) const
> +{
> +  pretty_printer pp;
> +  // TODO:
> +  pp_format_decoder (&pp) = default_tree_printer;
> +  pp.buffer->stream = fp;
> +  dump_dot_to_pp (&pp, root_cluster, args);
> +  pp_flush (&pp);
> +}
> +
> +/* Write .dot information for this graph to a file at PATH, passing
> ARGS
> +   to the nodes and edges.
> +   If ROOT_CLUSTER is non-NULL, use it to organize the nodes into
> clusters.  */
> +
> +template <typename GraphTraits>
> +inline void
> +digraph<GraphTraits>::dump_dot (const char *path,
> +				cluster_t *root_cluster,
> +				const dump_args_t &args) const
> +{
> +  FILE *fp = fopen (path, "w");
> +  dump_dot_to_file (fp, root_cluster, args);
> +  fclose (fp);
> +}
> +
> +/* Add NODE to this DIGRAPH, taking ownership.  */
> +
> +template <typename GraphTraits>
> +inline void
> +digraph<GraphTraits>::add_node (node_t *node)
> +{
> +  m_nodes.safe_push (node);
> +}
> +
> +/* Add EDGE to this digraph, and to the preds/succs of its
> endpoints.
> +   Take ownership of EDGE.  */
> +
> +template <typename GraphTraits>
> +inline void
> +digraph<GraphTraits>::add_edge (edge_t *edge)
> +{
> +  m_edges.safe_push (edge);
> +  edge->m_dest->m_preds.safe_push (edge);
> +  edge->m_src->m_succs.safe_push (edge);
> +
> +}
> +
> +#endif /* GCC_ANALYZER_DIGRAPH_H */
> diff --git a/gcc/analyzer/shortest-paths.h b/gcc/analyzer/shortest-
> paths.h
> new file mode 100644
> index 0000000..4738f18
> --- /dev/null
> +++ b/gcc/analyzer/shortest-paths.h
> @@ -0,0 +1,147 @@
> +/* Template class for Dijkstra's algorithm on directed graphs.
> +   Copyright (C) 2019 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/>;.  */
> +
> +#ifndef GCC_ANALYZER_SHORTEST_PATHS_H
> +#define GCC_ANALYZER_SHORTEST_PATHS_H
> +
> +#include "timevar.h"
> +
> +/* A record of the shortest path to each node in an graph
> +   from the origin node.
> +   The constructor runs Dijkstra's algorithm, and the results are
> +   stored in this class.  */
> +
> +template <typename GraphTraits, typename Path_t>
> +class shortest_paths
> +{
> +public:
> +  typedef typename GraphTraits::graph_t graph_t;
> +  typedef typename GraphTraits::node_t node_t;
> +  typedef typename GraphTraits::edge_t edge_t;
> +  typedef Path_t path_t;
> +
> +  shortest_paths (const graph_t &graph, const node_t *origin);
> +
> +  path_t get_shortest_path (const node_t *to) const;
> +
> +private:
> +  const graph_t &m_graph;
> +
> +  /* For each node (by index), the minimal distance to that node
> from the
> +     origin.  */
> +  auto_vec<int> m_dist;
> +
> +  /* For each exploded_node (by index), the previous edge in the
> shortest
> +     path from the origin.  */
> +  auto_vec<const edge_t *> m_prev;
> +};
> +
> +////////////////////////////////////////////////////////////////////
> ////////
> +
> +/* shortest_paths's constructor.
> +
> +   Use Dijkstra's algorithm relative to ORIGIN to populate m_dist
> and
> +   m_prev with enough information to be able to generate Path_t
> instances
> +   to give the shortest path to any node in GRAPH from ORIGIN.  */
> +
> +template <typename GraphTraits, typename Path_t>
> +inline
> +shortest_paths<GraphTraits, Path_t>::shortest_paths (const graph_t
> &graph,
> +						     const node_t
> *origin)
> +: m_graph (graph),
> +  m_dist (graph.m_nodes.length ()),
> +  m_prev (graph.m_nodes.length ())
> +{
> +  auto_client_timevar tv ("shortest_paths");
> +
> +  auto_vec<int> queue (graph.m_nodes.length ());
> +
> +  for (unsigned i = 0; i < graph.m_nodes.length (); i++)
> +    {
> +      m_dist.quick_push (INT_MAX);
> +      m_prev.quick_push (NULL);
> +      queue.quick_push (i);
> +    }
> +  m_dist[origin->m_index] = 0;
> +
> +  while (queue.length () > 0)
> +    {
> +      /* Get minimal distance in queue.
> +	 FIXME: this is O(N^2); replace with a priority queue.  */
> +      int idx_with_min_dist = -1;
> +      int idx_in_queue_with_min_dist = -1;
> +      int min_dist = INT_MAX;
> +      for (unsigned i = 0; i < queue.length (); i++)
> +	{
> +	  int idx = queue[i];
> +	  if (m_dist[queue[i]] < min_dist)
> +	    {
> +	      min_dist = m_dist[idx];
> +	      idx_with_min_dist = idx;
> +	      idx_in_queue_with_min_dist = i;
> +	    }
> +	}
> +      gcc_assert (idx_with_min_dist != -1);
> +      gcc_assert (idx_in_queue_with_min_dist != -1);
> +
> +      // FIXME: this is confusing: there are two indices here
> +
> +      queue.unordered_remove (idx_in_queue_with_min_dist);
> +
> +      node_t *n
> +	= static_cast <node_t *> (m_graph.m_nodes[idx_with_min_dist]);
> +
> +      int i;
> +      edge_t *succ;
> +      FOR_EACH_VEC_ELT (n->m_succs, i, succ)
> +	{
> +	  // TODO: only for dest still in queue
> +	  node_t *dest = succ->m_dest;
> +	  int alt = m_dist[n->m_index] + 1;
> +	  if (alt < m_dist[dest->m_index])
> +	    {
> +	      m_dist[dest->m_index] = alt;
> +	      m_prev[dest->m_index] = succ;
> +	    }
> +	}
> +   }
> +}
> +
> +/* Generate an Path_t instance giving the shortest path to the node
> +   TO from the origin node.  */
> +
> +template <typename GraphTraits, typename Path_t>
> +inline Path_t
> +shortest_paths<GraphTraits, Path_t>::get_shortest_path (const node_t
> *to) const
> +{
> +  Path_t result;
> +
> +  while (m_prev[to->m_index])
> +    {
> +      result.m_edges.safe_push (m_prev[to->m_index]);
> +      to = m_prev[to->m_index]->m_src;
> +    }
> +
> +  result.m_edges.reverse ();
> +
> +  return result;
> +}
> +
> +#endif /* GCC_ANALYZER_SHORTEST_PATHS_H */

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 28/49] analyzer: new files: analyzer.{cc|h}
  2019-11-16  1:18 ` [PATCH 28/49] analyzer: new files: analyzer.{cc|h} David Malcolm
  2019-12-07  3:38   ` Eric Gallager
@ 2019-12-07 15:02   ` Jeff Law
  2019-12-11 19:49     ` David Malcolm
  2019-12-10 17:17   ` [PATCH 28/49] analyzer: new files: analyzer.{cc|h} Martin Sebor
  2 siblings, 1 reply; 160+ messages in thread
From: Jeff Law @ 2019-12-07 15:02 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> gcc/ChangeLog:
> 	* analyzer/analyzer.cc: New file.
> 	* analyzer/analyzer.h: New file.
> ---
>  gcc/analyzer/analyzer.cc | 125
> ++++++++++++++++++++++++++++++++++++++++++++++
>  gcc/analyzer/analyzer.h  | 126
> +++++++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 251 insertions(+)
>  create mode 100644 gcc/analyzer/analyzer.cc
>  create mode 100644 gcc/analyzer/analyzer.h
> 
> diff --git a/gcc/analyzer/analyzer.cc b/gcc/analyzer/analyzer.cc
> new file mode 100644
> index 0000000..399925c
> --- /dev/null
> +++ b/gcc/analyzer/analyzer.cc
> @@ -0,0 +1,125 @@
> +/* Utility functions for the analyzer.
> +   Copyright (C) 2019 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 "gcc-plugin.h"
> +#include "system.h"
> +#include "coretypes.h"
> +#include "tree.h"
> +#include "gimple.h"
> +#include "diagnostic.h"
> +#include "intl.h"
> +#include "analyzer/analyzer.h"
> +
> +/* Helper function for checkers.  Is the CALL to the given function
> name?  */
> +
> +bool
> +is_named_call_p (const gcall *call, const char *funcname)
> +{
> +  gcc_assert (funcname);
> +
> +  tree fndecl = gimple_call_fndecl (call);
> +  if (!fndecl)
> +    return false;
> +
> +  return 0 == strcmp (IDENTIFIER_POINTER (DECL_NAME (fndecl)),
> funcname);
> +}
> +
> +/* Helper function for checkers.  Is the CALL to the given function
> name,
> +   and with the given number of arguments?  */
> +
> +bool
> +is_named_call_p (const gcall *call, const char *funcname,
> +		 unsigned int num_args)
> +{
> +  gcc_assert (funcname);
> +
> +  if (!is_named_call_p (call, funcname))
> +    return false;
> +
> +  if (gimple_call_num_args (call) != num_args)
> +    return false;
> +
> +  return true;
These seem generic enough to live elsewhere.

> +}
> +
> +/* Return true if stmt is a setjmp call.  */
> +
> +bool
> +is_setjmp_call_p (const gimple *stmt)
> +{
> +  /* TODO: is there a less hacky way to check for "setjmp"?  */
> +  if (const gcall *call = dyn_cast <const gcall *> (stmt))
> +    if (is_named_call_p (call, "_setjmp", 1))
> +      return true;
> +
> +  return false;
> +}
> +
> +/* Return true if stmt is a longjmp call.  */
> +
> +bool
> +is_longjmp_call_p (const gcall *call)
> +{
> +  /* TODO: is there a less hacky way to check for "longjmp"?  */
> +  if (is_named_call_p (call, "longjmp", 2))
> +    return true;
> +
> +  return false;
> +}
I thought we already had routines to detect these.  We certainly have
*code* to detect them.   If it's the former we really just want one
implementation (that hands the various permutations we've seen through
the years).  If it's the latter, then we probably want to use these
routines to simplify those implementations.


> +
> +/* Generate a label_text instance by formatting FMT, using a
> +   temporary clone of the global_dc's printer (thus using its
> +   formatting callbacks).
> +
> +   Colorize if the global_dc supports colorization and CAN_COLORIZE
> is
> +   true.  */
> +
> +label_text
> +make_label_text (bool can_colorize, const char *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, fmt);
> +
> +  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;
> +}
Is this better in the pretty-printer infrastructure?  

Jeff

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 29/49] analyzer: new files: tristate.{cc|h}
  2019-11-16  1:18 ` [PATCH 29/49] analyzer: new files: tristate.{cc|h} David Malcolm
@ 2019-12-07 15:03   ` Jeff Law
  2019-12-09 23:16     ` David Malcolm
  2019-12-10  0:59   ` Martin Sebor
  1 sibling, 1 reply; 160+ messages in thread
From: Jeff Law @ 2019-12-07 15:03 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> gcc/ChangeLog:
> 	* analyzer/tristate.cc: New file.
> 	* analyzer/tristate.h: New file.
Nothing really concerning here.  Seems like a generic facility we'd
like to be able to use elsewhere.   Move outside the analyzer?

jeff


^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 32/49] analyzer: new files: pending-diagnostic.{cc|h}
  2019-11-16  1:18 ` [PATCH 32/49] analyzer: new files: pending-diagnostic.{cc|h} David Malcolm
@ 2019-12-07 15:05   ` Jeff Law
  2019-12-08 14:43     ` David Malcolm
  0 siblings, 1 reply; 160+ messages in thread
From: Jeff Law @ 2019-12-07 15:05 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> This patch adds classes used by the analyzer for handling its
> diagnostics
> (queueing them, deduplicating them, precision-of-wording hooks).
> 
> gcc/ChangeLog:
> 	* analyzer/pending-diagnostic.cc: New file.
> 	* analyzer/pending-diagnostic.h: New file.
Definitely want to be using this elsewhere.  So again, perhaps move out
of the analyzer into a generic location?

Jeff


^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 34/49] analyzer: new file: sm-malloc.cc
  2019-11-16  1:20 ` [PATCH 34/49] analyzer: new file: sm-malloc.cc David Malcolm
@ 2019-12-07 15:11   ` Jeff Law
  0 siblings, 0 replies; 160+ messages in thread
From: Jeff Law @ 2019-12-07 15:11 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> This patch adds a state machine checker for malloc/free.
> 
> gcc/ChangeLog:
> 	* analyzer/sm-malloc.cc: New file.
FWIW, I could easily see someone using this as a template for other
checkers.  It shows several key concepts that I suspect all kinds of
other checkers might want to use.

This goes well beyond what we were originally targeting -- which begs
the question, what's the state of the other checkers in here?

Jeff

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 35/49] analyzer: new file: sm-file.cc
  2019-11-16  1:18 ` [PATCH 35/49] analyzer: new file: sm-file.cc David Malcolm
@ 2019-12-07 15:15   ` Jeff Law
  0 siblings, 0 replies; 160+ messages in thread
From: Jeff Law @ 2019-12-07 15:15 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> This patch adds a state machine checker for stdio's FILE stream API.
> 
> gcc/ChangeLog:
> 	* analyzer/sm-file.cc: New file.
I note this seems somewhat incomplete -- which is fine given my
recommendation was to focus on the double-free analyzer.  The biggest
question is do we want to include this in the first iteration?  Perhaps
as an example that others can flesh out to capture the missing stuff
(like operations on released FD or file pointers?)

The similarities with double-free, use-after-free are significant.  But
I hesitate to suggest trying to generaize and merge them at this point.

jeff



^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 36/49] analyzer: new file: sm-pattern-test.cc
  2019-11-16  1:18 ` [PATCH 36/49] analyzer: new file: sm-pattern-test.cc David Malcolm
@ 2019-12-07 15:16   ` Jeff Law
  0 siblings, 0 replies; 160+ messages in thread
From: Jeff Law @ 2019-12-07 15:16 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> This patch adds a custom state machine checker intended purely for
> DejaGnu
> testing of the sm "machinery".
> 
> gcc/ChangeLog:
> 	* analyzer/sm-pattern-test.cc: New file.
OK when prereqs are all approved.

jeff


^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 37/49] analyzer: new file: sm-sensitive.cc
  2019-11-16  1:21 ` [PATCH 37/49] analyzer: new file: sm-sensitive.cc David Malcolm
@ 2019-12-07 15:18   ` Jeff Law
  0 siblings, 0 replies; 160+ messages in thread
From: Jeff Law @ 2019-12-07 15:18 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> This patch adds a state machine checker for tracking exposure of
> sensitive data (e.g. writing passwords to log files).
> 
> This checker isn't ready for production, and is presented as a
> proof-of-concept of the sm-based approach.
> 
> gcc/ChangeLog:
> 	* analyzer/sm-sensitive.cc: New file.
Given it's not ready for production, fine.  Presumably one of the areas
for improvement is a better answer to the "what constitutes exposure"
question ;-)


jeff
> 

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 38/49] analyzer: new file: sm-taint.cc
  2019-11-16  1:21 ` [PATCH 38/49] analyzer: new file: sm-taint.cc David Malcolm
@ 2019-12-07 15:22   ` Jeff Law
  0 siblings, 0 replies; 160+ messages in thread
From: Jeff Law @ 2019-12-07 15:22 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> This patch adds a state machine checker for tracking "taint",
> where data potentially under an attacker's control is used for
> things like array indices without sanitization (CWE-129).
> 
> This checker isn't ready for production, and is presented as a
> proof-of-concept of the sm-based approach.
> 
> gcc/ChangeLog:
> 	* analyzer/sm-taint.cc: New file.
As you know, I'm a big fan of getting some kind of taint analysis into
GCC.  So I'd certainly encourage someone to pick up on this as well as
the more localized work.  I'm particularly interested in things like a
tainted length/count which is then used to calculate how much memory to
allocate in malloc/alloca.  If an attacker can control that they can do
some nasty things.

I do think we're likely to end up with some more traditional warnings
in that space as well.  So figuring out the balance/guidelines for when
to improve the traditional warnings vs those from the static analyzer
will need to be figured out.

jeff
> 

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 39/49] analyzer: new files: analysis-plan.{cc|h}
  2019-11-16  1:20 ` [PATCH 39/49] analyzer: new files: analysis-plan.{cc|h} David Malcolm
@ 2019-12-07 15:24   ` Jeff Law
  0 siblings, 0 replies; 160+ messages in thread
From: Jeff Law @ 2019-12-07 15:24 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> This patch adds an analysis_plan class, which encapsulate decisions
> about
> how the analysis should happen (e.g. the order in which functions
> should
> be traversed).
> 
> gcc/ChangeLog:
> 	* analyzer/analysis-plan.cc: New file.
> 	* analyzer/analysis-plan.h: New file.
No concerns here.
jeff


^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 22/49] analyzer: params.def: new parameters
  2019-12-07 14:49   ` Jeff Law
@ 2019-12-08  5:42     ` Eric Gallager
  2019-12-08 14:34       ` David Malcolm
  0 siblings, 1 reply; 160+ messages in thread
From: Eric Gallager @ 2019-12-08  5:42 UTC (permalink / raw)
  To: law; +Cc: David Malcolm, gcc-patches

On 12/7/19, Jeff Law <law@redhat.com> wrote:
> On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
>> gcc/ChangeLog:
>> 	* params.def (PARAM_ANALYZER_BB_EXPLOSION_FACTOR): New param.
>> 	(PARAM_ANALYZER_MAX_ENODES_PER_PROGRAM_POINT): New param.
>> 	(PARAM_ANALYZER_MAX_RECURSION_DEPTH): New param.
>> 	(PARAM_ANALYZER_MIN_SNODES_FOR_CALL_SUMMARY): New param.
> Doesn't this need a rethink/reimplementation after M. Liska's changes?
>
> jeff
>
>

I think he did that in a followup patch from a later patchset...

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 2/2] Rework as a non-plugin
  2019-12-08 14:28           ` [PATCH 0/2] v3 of analyzer patch kit (unsquashed) David Malcolm
@ 2019-12-08 14:28             ` David Malcolm
  2019-12-08 14:28             ` [PATCH 1/2] Fixup for rebase: c-format.c: get_pointer_to_named_type -> get_named_type David Malcolm
  1 sibling, 0 replies; 160+ messages in thread
From: David Malcolm @ 2019-12-08 14:28 UTC (permalink / raw)
  To: gcc-patches; +Cc: law, Richard Biener, Thomas Schwinge, David Malcolm

gcc/ChangeLog:
	* Makefile.in (lang_opt_files): Add analyzer.opt.
	(ANALYZER_OBJS): New.
	(OBJS): Add ANALYZER_OBJS.
	* analyzer/Make-plugin.in: Delete file.
	* analyzer/analysis-plan.cc: Rework headers.  Guard with
	#if ENABLE_ANALYZER.
	(analysis_plan::analysis_plan): Convert from auto_client_timevar
	to auto_timevar.
	* analyzer/analyzer-logging.cc: Guard with #if ENABLE_ANALYZER.
	* analyzer/analyzer-pass.cc: Rework headers.  Guard with
	#if ENABLE_ANALYZER.
	(pass_data_analyzer): Use TV_ANALYZER.
	(pass_analyzer::gate): New.
	(pass_analyzer::execute): Move the check for #if ENABLE_ANALYZER
	here.
	(make_pass_analyzer): Make non-static.
	(register_analyzer_pass): Delete.
	* analyzer/analyzer-plugin.cc: Delete file.
	* analyzer/analyzer-selftests.cc: Rework headers.
	(selftest::run_analyzer_selftests): Guard body with
	#if ENABLE_ANALYZER.
	* analyzer/analyzer-selftests.h: Fix comment.
	* analyzer/analyzer.cc: Rework headers.  Guard with
	#if ENABLE_ANALYZER.
	* analyzer/analyzer.h: Include "function.h".
	* analyzer/call-string.cc: Rework headers.  Guard with
	#if ENABLE_ANALYZER.
	* analyzer/checker-path.cc: Rework headers.  Guard with
	#if ENABLE_ANALYZER.
	* analyzer/config-plugin.in: Delete file.
	* analyzer/constraint-manager.cc: Rework headers.  Guard with
	#if ENABLE_ANALYZER.
	* analyzer/diagnostic-manager.cc: Rework headers.  Guard with
	#if ENABLE_ANALYZER.
	(diagnostic_manager::emit_saved_diagnostics): Convert from
	auto_client_timevar to auto_timevar.
	* analyzer/digraph.cc: Rework headers.  Guard with
	#if ENABLE_ANALYZER.
	* analyzer/engine.cc: Rework headers.  Guard with
	#if ENABLE_ANALYZER.
	(strongly_connected_components::strongly_connected_components):
	Convert from auto_client_timevar to auto_timevar.
	(exploded_graph::process_worklist): Likewise.
	(exploded_graph::dump_exploded_nodes): Likewise.
	(dump_callgraph): Likewise.
	(impl_run_checkers): Likewise.
	(run_checkers): Drop top-level auto_client_timevar in favor of
	the tv_id of the pass as a whole.
	* analyzer/exploded-graph.h: Include "alloc-pool.h".
	* analyzer/graphviz.cc: Rework headers.  Guard with
	#if ENABLE_ANALYZER.
	* analyzer/pending-diagnostic.cc: Rework headers.  Guard with
	#if ENABLE_ANALYZER.
	* analyzer/program-point.cc: Rework headers.  Guard with
	#if ENABLE_ANALYZER.
	* analyzer/program-state.cc: Rework headers.  Guard with
	#if ENABLE_ANALYZER.
	* analyzer/region-model.cc: Rework headers.  Guard with
	#if ENABLE_ANALYZER.
	(dump_tree): New function.
	(region_model::dump_to_pp): Use dump_tree rather than %E.
	(dump_vec_of_tree): Likewise.
	(region_model::dump_summary_of_map): Likewise.
	* analyzer/region-model.h: Include "sbitmap.h".
	* analyzer/shortest-paths.h (shortest_paths::shortest_paths):
	Convert from auto_client_timevar to auto_timevar.
	* analyzer/sm-file.cc: Rework headers.  Guard with
	#if ENABLE_ANALYZER.
	* analyzer/sm-malloc.cc: Rework headers.  Guard with
	#if ENABLE_ANALYZER.
	* analyzer/sm-pattern-test.cc: Rework headers.  Guard with
	#if ENABLE_ANALYZER.
	* analyzer/sm-sensitive.cc: Rework headers.  Guard with
	#if ENABLE_ANALYZER.
	* analyzer/sm-signal.cc: Rework headers.  Guard with
	#if ENABLE_ANALYZER.
	* analyzer/sm-taint.cc: Rework headers.  Guard with
	#if ENABLE_ANALYZER.
	* analyzer/sm.cc: Rework headers.  Guard with
	#if ENABLE_ANALYZER.
	* analyzer/state-purge.cc: Rework headers.  Guard with
	#if ENABLE_ANALYZER.
	(state_purge_map::state_purge_map): Convert from
	auto_client_timevar to auto_timevar.
	* analyzer/supergraph.cc: Rework headers.  Guard with
	#if ENABLE_ANALYZER.
	(supergraph::supergraph): Convert from auto_client_timevar to
	auto_timevar.
	* analyzer/supergraph.h: Include "options.h", "function.h",
	"cfg.h", and "basic-block.h".
	* analyzer/tristate.cc: Rework headers.  Guard with
	#if ENABLE_ANALYZER.
	* common.opt (fanalyzer): Convert from Driver to Common.  Add
	description.
	* configure.ac (--disable-analyzer, ENABLE_ANALYZER): New option.
	(gccdepdir): Also created depdir for "analyzer" subdir.
	* gcc.c (driver_handle_option): Drop case OPT_fanalyzer.
	(process_command): Drop handling of flag_analyzer; this is now
	done in the gate vfunc of the pass.
	* passes.def (pass_analyzer): Add before
	pass_ipa_whole_program_visibility.
	* selftest-run-tests.c: Include "analyzer/analyzer-selftests.h".
	(selftest::run_tests): Call selftest::run_analyzer_selftests.
	* timevar.def (TV_ANALYZER): New timevar.
	(TV_ANALYZER_SUPERGRAPH): Likewise.
	(TV_ANALYZER_STATE_PURGE): Likewise.
	(TV_ANALYZER_PLAN): Likewise.
	(TV_ANALYZER_SCC): Likewise.
	(TV_ANALYZER_WORKLIST): Likewise.
	(TV_ANALYZER_DUMP): Likewise.
	(TV_ANALYZER_DIAGNOSTICS): Likewise.
	(TV_ANALYZER_SHORTEST_PATHS): Likewise.
	* tree-pass.h (make_pass_analyzer): New decl.
---
 gcc/Makefile.in                           |  32 +++-
 gcc/analyzer/Make-plugin.in               | 182 ----------------------
 gcc/analyzer/analysis-plan.cc             |   8 +-
 gcc/analyzer/analyzer-logging.cc          |   4 +
 gcc/analyzer/analyzer-pass.cc             |  41 +++--
 gcc/analyzer/analyzer-plugin.cc           |  63 --------
 gcc/analyzer/analyzer-selftests.cc        |   3 +-
 gcc/analyzer/analyzer-selftests.h         |   2 +-
 gcc/analyzer/analyzer.cc                  |   7 +-
 gcc/analyzer/analyzer.h                   |   2 +
 gcc/analyzer/{plugin.opt => analyzer.opt} |   0
 gcc/analyzer/call-string.cc               |   6 +-
 gcc/analyzer/checker-path.cc              |   8 +-
 gcc/analyzer/config-plugin.in             |  34 ----
 gcc/analyzer/constraint-manager.cc        |  10 +-
 gcc/analyzer/diagnostic-manager.cc        |   7 +-
 gcc/analyzer/digraph.cc                   |   5 +-
 gcc/analyzer/engine.cc                    |  26 ++--
 gcc/analyzer/exploded-graph.h             |   1 +
 gcc/analyzer/graphviz.cc                  |   5 +-
 gcc/analyzer/pending-diagnostic.cc        |   5 +-
 gcc/analyzer/program-point.cc             |   5 +-
 gcc/analyzer/program-state.cc             |   5 +-
 gcc/analyzer/region-model.cc              |  66 ++++----
 gcc/analyzer/region-model.h               |   1 +
 gcc/analyzer/shortest-paths.h             |   2 +-
 gcc/analyzer/sm-file.cc                   |   8 +-
 gcc/analyzer/sm-malloc.cc                 |   9 +-
 gcc/analyzer/sm-pattern-test.cc           |   7 +-
 gcc/analyzer/sm-sensitive.cc              |   9 +-
 gcc/analyzer/sm-signal.cc                 |   7 +-
 gcc/analyzer/sm-taint.cc                  |   8 +-
 gcc/analyzer/sm.cc                        |   8 +-
 gcc/analyzer/state-purge.cc               |  10 +-
 gcc/analyzer/supergraph.cc                |   9 +-
 gcc/analyzer/supergraph.h                 |   4 +
 gcc/analyzer/tristate.cc                  |   5 +-
 gcc/common.opt                            |   3 +-
 gcc/configure                             |  25 ++-
 gcc/configure.ac                          |  14 +-
 gcc/gcc.c                                 |  12 --
 gcc/passes.def                            |   1 +
 gcc/selftest-run-tests.c                  |   4 +
 gcc/timevar.def                           |  11 ++
 gcc/tree-pass.h                           |   1 +
 45 files changed, 300 insertions(+), 385 deletions(-)
 delete mode 100644 gcc/analyzer/Make-plugin.in
 delete mode 100644 gcc/analyzer/analyzer-plugin.cc
 rename gcc/analyzer/{plugin.opt => analyzer.opt} (100%)
 delete mode 100644 gcc/analyzer/config-plugin.in

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 77a12d5c7258..62ec973563b7 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -567,7 +567,7 @@ xm_include_list=@xm_include_list@
 xm_defines=@xm_defines@
 lang_checks=
 lang_checks_parallelized=
-lang_opt_files=@lang_opt_files@ $(srcdir)/c-family/c.opt $(srcdir)/common.opt $(srcdir)/params.opt
+lang_opt_files=@lang_opt_files@ $(srcdir)/c-family/c.opt $(srcdir)/common.opt $(srcdir)/params.opt $(srcdir)/analyzer/analyzer.opt
 lang_specs_files=@lang_specs_files@
 lang_tree_files=@lang_tree_files@
 target_cpu_default=@target_cpu_default@
@@ -1214,6 +1214,35 @@ C_COMMON_OBJS = c-family/c-common.o c-family/c-cppbuiltin.o c-family/c-dump.o \
   c-family/c-ubsan.o c-family/known-headers.o \
   c-family/c-attribs.o c-family/c-warn.o c-family/c-spellcheck.o
 
+# Analyzer object files
+ANALYZER_OBJS = \
+	analyzer/analysis-plan.o \
+	analyzer/analyzer.o \
+	analyzer/analyzer-logging.o \
+	analyzer/analyzer-pass.o \
+	analyzer/analyzer-selftests.o \
+	analyzer/call-string.o \
+	analyzer/checker-path.o \
+	analyzer/constraint-manager.o \
+	analyzer/diagnostic-manager.o \
+	analyzer/digraph.o \
+	analyzer/graphviz.o \
+	analyzer/engine.o \
+	analyzer/pending-diagnostic.o \
+	analyzer/program-point.o \
+	analyzer/program-state.o \
+	analyzer/region-model.o \
+	analyzer/sm.o \
+	analyzer/sm-file.o \
+	analyzer/sm-malloc.o \
+	analyzer/sm-pattern-test.o \
+	analyzer/sm-sensitive.o \
+	analyzer/sm-signal.o \
+	analyzer/sm-taint.o \
+	analyzer/state-purge.o \
+	analyzer/supergraph.o \
+	analyzer/tristate.o
+
 # Language-independent object files.
 # We put the *-match.o and insn-*.o files first so that a parallel make
 # will build them sooner, because they are large and otherwise tend to be
@@ -1618,6 +1647,7 @@ OBJS = \
 	wide-int-print.o \
 	xcoffout.o \
 	$(out_object_file) \
+	$(ANALYZER_OBJS) \
 	$(EXTRA_OBJS) \
 	$(host_hook_obj)
 
diff --git a/gcc/analyzer/Make-plugin.in b/gcc/analyzer/Make-plugin.in
deleted file mode 100644
index f679686783fa..000000000000
--- a/gcc/analyzer/Make-plugin.in
+++ /dev/null
@@ -1,182 +0,0 @@
-# Top level -*- makefile -*- fragment for analyzer
-#   Copyright (C) 2013-2019 Free Software Foundation, Inc.
-
-#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/>.
-
-# This file provides the plugin dependent support in the main Makefile.
-# Each plugin makefile fragment must provide the following targets:
-#
-# foo.all.cross, foo.start.encap, foo.rest.encap,
-# foo.install-common, foo.install-man, foo.install-info, foo.install-pdf,
-# foo.install-html, foo.info, foo.dvi, foo.pdf, foo.html, foo.uninstall,
-# foo.mostlyclean, foo.clean, foo.distclean,
-# foo.maintainer-clean, foo.stage1, foo.stage2, foo.stage3, foo.stage4
-#
-# where `foo' is the name of the plugin.
-#
-# It should also provide rules for:
-#
-# - making any compiler driver (eg: g++)
-# - the plugin proper (eg: analyzer_plugin.so)
-# - define the names for selecting the plugin in PLUGINS.
-
-#\f
-# Define the names for selecting analyzer in PLUGINS.
-# Note that it would be nice to move the dependency on g++
-# into the analyzer rule, but that needs a little bit of work
-# to do the right thing within all.cross.
-
-plugin_builddir = plugin
-
-ANALYZER_PLUGIN_SO = $(plugin_builddir)/analyzer_plugin.so
-
-analyzer: \
-	$(FULL_DRIVER_NAME) \
-	$(ANALYZER_PLUGIN_SO)
-
-# Tell GNU make to ignore these if they exist.
-.PHONY: analyzer
-
-# Files that are linked into analyzer plugin
-
-analyzer_OBJS = \
-	analyzer/analysis-plan.o \
-	analyzer/analyzer.o \
-	analyzer/analyzer-logging.o \
-	analyzer/analyzer-pass.o \
-	analyzer/analyzer-plugin.o \
-	analyzer/analyzer-selftests.o \
-	analyzer/call-string.o \
-	analyzer/checker-path.o \
-	analyzer/constraint-manager.o \
-	analyzer/diagnostic-manager.o \
-	analyzer/digraph.o \
-	analyzer/graphviz.o \
-	analyzer/engine.o \
-	analyzer/pending-diagnostic.o \
-	analyzer/program-point.o \
-	analyzer/program-state.o \
-	analyzer/region-model.o \
-	analyzer/sm.o \
-	analyzer/sm-file.o \
-	analyzer/sm-malloc.o \
-	analyzer/sm-pattern-test.o \
-	analyzer/sm-sensitive.o \
-	analyzer/sm-signal.o \
-	analyzer/sm-taint.o \
-	analyzer/state-purge.o \
-	analyzer/supergraph.o \
-	analyzer/tristate.o \
-
-# Use strict warnings for this plugin.
-analyzer-warn = $(STRICT_WARN)
-
-#\f
-# Build hooks:
-
-analyzer.all.cross:
-analyzer.start.encap:
-analyzer.rest.encap:
-
-$(plugin_builddir):
-	mkdir $@
-
-$(ANALYZER_PLUGIN_SO): $(analyzer_OBJS) $(plugin_builddir)
-	$(LINKER) $(ALL_LINKERFLAGS) $(LDFLAGS) \
-	  -o $@ \
-	  -shared \
-	  $(analyzer_OBJS) \
-	-lpthread
-
-# If the analyzer is enabled, require the selftests to be run for it
-# at each stage of the build:
-selftest-analyzer: s-selftest-analyzer
-
-ANALYZER_SELFTEST_FLAGS = -xc $(SELFTEST_FLAGS) -fanalyzer
-ANALYZER_SELFTEST_DEPS = cc1$(exeext) $(SELFTEST_DEPS) $(ANALYZER_PLUGIN_SO)
-
-# Run the analyzer selftests:
-s-selftest-analyzer: $(ANALYZER_SELFTEST_DEPS)
-	$(GCC_FOR_TARGET) $(ANALYZER_SELFTEST_FLAGS)
-	$(STAMP) $@
-
-# Convenience methods for running analyzer selftests under gdb:
-.PHONY: selftest-analyzer-gdb
-selftest-analyzer-gdb: $(ANALYZER_SELFTEST_DEPS)
-	$(GCC_FOR_TARGET) $(ANALYZER_SELFTEST_FLAGS) \
-	  -wrapper gdb,--args
-
-# Convenience methods for running analyzer selftests under valgrind:
-.PHONY: selftest-analyzer-valgrind
-selftest-analyzer-valgrind: $(ANALYZER_SELFTEST_DEPS)
-	$(GCC_FOR_TARGET) $(ANALYZER_SELFTEST_FLAGS) \
-	  -wrapper valgrind,--leak-check=full
-
-# Documentation build hooks.
-analyzer.info:
-analyzer.dvi:
-analyzer.pdf:
-analyzer.html:
-analyzer.srcinfo:
-analyzer.man:
-analyzer.srcman:
-
-# Testing hooks:
-lang_checks += check-analyzer
-
-#\f
-# Install hooks:
-analyzer.install-common:
-analyzer.install-man:
-analyzer.install-info:
-analyzer.install-pdf:
-analyzer.install-html:
-
-analyzer.install-plugin: installdirs
-	$(INSTALL_DATA) $(ANALYZER_PLUGIN_SO) $(DESTDIR)/$(plugin_resourcesdir)/analyzer_plugin.so
-
-analyzer.uninstall:
-
-#\f
-# Clean hooks:
-# A lot of the ancillary files are deleted by the main makefile.
-# We just have to delete files specific to us.
-
-analyzer.mostlyclean:
-
-analyzer.clean:
-
-analyzer.distclean:
-
-analyzer.maintainer-clean:
-
-#\f
-# Stage hooks:
-# The main makefile has already created stage?/analyzer.
-
-analyzer.stage1: stage1-start
-	-mv analyzer/*$(objext) stage1/analyzer
-analyzer.stage2: stage2-start
-	-mv analyzer/*$(objext) stage2/analyzer
-analyzer.stage3: stage3-start
-	-mv analyzer/*$(objext) stage3/analyzer
-analyzer.stage4: stage4-start
-	-mv analyzer/*$(objext) stage4/analyzer
-analyzer.stageprofile: stageprofile-start
-	-mv analyzer/*$(objext) stageprofile/analyzer
-analyzer.stagefeedback: stagefeedback-start
-	-mv analyzer/*$(objext) stagefeedback/analyzer
diff --git a/gcc/analyzer/analysis-plan.cc b/gcc/analyzer/analysis-plan.cc
index 38123ffb13ba..ca9b01b32c96 100644
--- a/gcc/analyzer/analysis-plan.cc
+++ b/gcc/analyzer/analysis-plan.cc
@@ -19,10 +19,10 @@ along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
 #include "config.h"
-#include "gcc-plugin.h"
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
+#include "options.h"
 #include "cgraph.h"
 #include "timevar.h"
 #include "ipa-utils.h"
@@ -30,6 +30,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "analyzer/analysis-plan.h"
 #include "analyzer/supergraph.h"
 
+#if ENABLE_ANALYZER
+
 /* class analysis_plan.  */
 
 /* analysis_plan's ctor.  */
@@ -41,7 +43,7 @@ analysis_plan::analysis_plan (const supergraph &sg, logger *logger)
   m_index_by_uid (symtab->cgraph_max_uid)
 {
   LOG_SCOPE (logger);
-  auto_client_timevar tv ("creating analysis plan");
+  auto_timevar time (TV_ANALYZER_PLAN);
 
   m_num_cgraph_nodes = ipa_reverse_postorder (m_cgraph_node_postorder);
   gcc_assert (m_num_cgraph_nodes == symtab->cgraph_count);
@@ -112,3 +114,5 @@ analysis_plan::use_summary_p (const cgraph_edge *edge) const
 
   return true;
 }
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/analyzer-logging.cc b/gcc/analyzer/analyzer-logging.cc
index 575435cbceaa..2010cb78a176 100644
--- a/gcc/analyzer/analyzer-logging.cc
+++ b/gcc/analyzer/analyzer-logging.cc
@@ -28,6 +28,8 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "analyzer/analyzer-logging.h"
 
+#if ENABLE_ANALYZER
+
 /* Implementation of class logger.  */
 
 /* ctor for logger.  */
@@ -218,3 +220,5 @@ log_user::set_logger (logger *logger)
     m_logger->decref ("log_user::set_logger");
   m_logger = logger;
 }
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/analyzer-pass.cc b/gcc/analyzer/analyzer-pass.cc
index 54b8a5ca6db3..60535a944dc9 100644
--- a/gcc/analyzer/analyzer-pass.cc
+++ b/gcc/analyzer/analyzer-pass.cc
@@ -19,11 +19,12 @@ along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
 #include "config.h"
-#include "gcc-plugin.h"
 #include "system.h"
 #include "coretypes.h"
 #include "context.h"
 #include "tree-pass.h"
+#include "diagnostic.h"
+#include "options.h"
 #include "analyzer/engine.h"
 
 namespace {
@@ -35,7 +36,7 @@ const pass_data pass_data_analyzer =
   IPA_PASS, /* type */
   "analyzer", /* name */
   OPTGROUP_NONE, /* optinfo_flags */
-  TV_NONE, /* tv_id */
+  TV_ANALYZER, /* tv_id */
   PROP_ssa, /* properties_required */
   0, /* properties_provided */
   0, /* properties_destroyed */
@@ -62,15 +63,31 @@ public:
   {}
 
   /* opt_pass methods: */
+  bool gate (function *) FINAL OVERRIDE;
   unsigned int execute (function *) FINAL OVERRIDE;
 }; // class pass_analyzer
 
+/* Only run the analyzer if -fanalyzer.  */
+
+bool
+pass_analyzer::gate (function *)
+{
+  return flag_analyzer != 0;
+}
+
 /* Entrypoint for the analyzer pass.  */
 
 unsigned int
 pass_analyzer::execute (function *)
 {
+#if ENABLE_ANALYZER
   run_checkers ();
+#else
+  sorry ("%qs was not enabled in this build of GCC"
+	 " (missing configure-time option %qs)",
+	 "-fanalyzer", "--enable-analyzer");
+#endif
+
   return 0;
 }
 
@@ -78,26 +95,8 @@ pass_analyzer::execute (function *)
 
 /* Make an instance of the analyzer pass.  */
 
-static ipa_opt_pass_d *
+ipa_opt_pass_d *
 make_pass_analyzer (gcc::context *ctxt)
 {
   return new pass_analyzer (ctxt);
 }
-
-/* Register the analyzer pass with GCC's pass manager.
-   Called by plugin_init.  */
-
-void
-register_analyzer_pass ()
-{
-  static struct register_pass_info pass_info;
-
-  /* IPA-LTO pass.  */
-  pass_info.pass = make_pass_analyzer (g);
-  pass_info.reference_pass_name = "whole-program";
-  pass_info.ref_pass_instance_number = 1;
-  pass_info.pos_op = PASS_POS_INSERT_BEFORE;
-
-  register_callback ("analyzer", PLUGIN_PASS_MANAGER_SETUP, NULL,
-		     &pass_info);
-}
diff --git a/gcc/analyzer/analyzer-plugin.cc b/gcc/analyzer/analyzer-plugin.cc
deleted file mode 100644
index 1e7c56e50c56..000000000000
--- a/gcc/analyzer/analyzer-plugin.cc
+++ /dev/null
@@ -1,63 +0,0 @@
-/* Top-level interface to the analyzer, as a GCC plugin.
-   Copyright (C) 2019 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 "gcc-plugin.h"
-#include "system.h"
-#include "coretypes.h"
-#include "analyzer/analyzer.h"
-#include "analyzer/analyzer-selftests.h"
-
-int plugin_is_GPL_compatible;
-
-#if CHECKING_P
-
-namespace selftest {
-
-/* Callback for running the analyzer's selftests.  */
-
-static void
-analyzer_selftests_cb (void */*gcc_data*/, void */*user_data*/)
-{
-  run_analyzer_selftests ();
-}
-
-} /* end of namespace selftest.  */
-
-#endif /* #if CHECKING_P */
-
-/* Entrypoint to the pass.  */
-
-int
-plugin_init (struct plugin_name_args *plugin_info ATTRIBUTE_UNUSED,
-	     struct plugin_gcc_version */*version*/)
-{
-  /* Register our selftests.  */
-#if CHECKING_P
-  register_callback (plugin_info->base_name,
-		     PLUGIN_RUN_SELFTESTS,
-		     selftest::analyzer_selftests_cb, NULL);
-#endif /* #if CHECKING_P */
-
-  /* Register our pass.  */
-  register_analyzer_pass ();
-
-  return 0;
-}
diff --git a/gcc/analyzer/analyzer-selftests.cc b/gcc/analyzer/analyzer-selftests.cc
index 5696af5762df..487cd81fe211 100644
--- a/gcc/analyzer/analyzer-selftests.cc
+++ b/gcc/analyzer/analyzer-selftests.cc
@@ -19,7 +19,6 @@ along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
 #include "config.h"
-#include "gcc-plugin.h"
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
@@ -48,12 +47,14 @@ build_global_decl (const char *name, tree type)
 void
 run_analyzer_selftests ()
 {
+#if ENABLE_ANALYZER
   analyzer_constraint_manager_cc_tests ();
   analyzer_digraph_cc_tests ();
   analyzer_program_point_cc_tests ();
   analyzer_program_state_cc_tests ();
   analyzer_region_model_cc_tests ();
   analyzer_tristate_cc_tests ();
+#endif /* #if ENABLE_ANALYZER */
 }
 
 } /* end of namespace selftest.  */
diff --git a/gcc/analyzer/analyzer-selftests.h b/gcc/analyzer/analyzer-selftests.h
index 1c26f25616a3..0724ce74c9c2 100644
--- a/gcc/analyzer/analyzer-selftests.h
+++ b/gcc/analyzer/analyzer-selftests.h
@@ -30,7 +30,7 @@ extern tree build_global_decl (const char *name, tree type);
 extern void run_analyzer_selftests ();
 
 /* Declarations for specific families of tests (by source file), in
-   alphabetical order, for the checker plugin.  */
+   alphabetical order.  */
 extern void analyzer_checker_script_cc_tests ();
 extern void analyzer_constraint_manager_cc_tests ();
 extern void analyzer_digraph_cc_tests ();
diff --git a/gcc/analyzer/analyzer.cc b/gcc/analyzer/analyzer.cc
index 5405facf5e86..3f073371f936 100644
--- a/gcc/analyzer/analyzer.cc
+++ b/gcc/analyzer/analyzer.cc
@@ -19,15 +19,18 @@ along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
 #include "config.h"
-#include "gcc-plugin.h"
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
+#include "function.h"
+#include "basic-block.h"
 #include "gimple.h"
 #include "diagnostic.h"
 #include "intl.h"
 #include "analyzer/analyzer.h"
 
+#if ENABLE_ANALYZER
+
 /* Helper function for checkers.  Is the CALL to the given function name,
    and with the given number of arguments?
 
@@ -143,3 +146,5 @@ make_label_text (bool can_colorize, const char *fmt, ...)
   delete pp;
   return result;
 }
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h
index 353285c35c36..0595822dee6f 100644
--- a/gcc/analyzer/analyzer.h
+++ b/gcc/analyzer/analyzer.h
@@ -21,6 +21,8 @@ along with GCC; see the file COPYING3.  If not see
 #ifndef GCC_ANALYZER_ANALYZER_H
 #define GCC_ANALYZER_ANALYZER_H
 
+#include "function.h"
+
 /* Forward decls of common types, with indentation to show inheritance.  */
 
 class graphviz_out;
diff --git a/gcc/analyzer/plugin.opt b/gcc/analyzer/analyzer.opt
similarity index 100%
rename from gcc/analyzer/plugin.opt
rename to gcc/analyzer/analyzer.opt
diff --git a/gcc/analyzer/call-string.cc b/gcc/analyzer/call-string.cc
index a9a9ce51ea52..a9ff9cbfca44 100644
--- a/gcc/analyzer/call-string.cc
+++ b/gcc/analyzer/call-string.cc
@@ -19,14 +19,16 @@ along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
 #include "config.h"
-#include "gcc-plugin.h"
 #include "system.h"
 #include "coretypes.h"
 #include "pretty-print.h"
 #include "tree.h"
+#include "options.h"
 #include "analyzer/call-string.h"
 #include "analyzer/supergraph.h"
 
+#if ENABLE_ANALYZER
+
 /* class call_string.  */
 
 /* call_string's copy ctor.  */
@@ -218,3 +220,5 @@ call_string::validate () const
       gcc_assert (e->get_caller_function ()
 		  == m_return_edges[i - 1]->get_callee_function ());
 }
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/checker-path.cc b/gcc/analyzer/checker-path.cc
index 554a20f31bec..6eb3dff62c6e 100644
--- a/gcc/analyzer/checker-path.cc
+++ b/gcc/analyzer/checker-path.cc
@@ -19,18 +19,22 @@ along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
 #include "config.h"
-#include "gcc-plugin.h"
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
+#include "function.h"
+#include "basic-block.h"
 #include "gimple.h"
 #include "gimple-pretty-print.h"
+#include "fold-const.h"
 #include "analyzer/analyzer.h"
 #include "analyzer/checker-path.h"
 #include "analyzer/supergraph.h"
 #include "analyzer/diagnostic-manager.h"
 #include "analyzer/exploded-graph.h"
 
+#if ENABLE_ANALYZER
+
 ////////////////////////////////////////////////////////////////////////////
 
 /* Get a string for EK.  */
@@ -944,3 +948,5 @@ checker_path::add_final_event (const state_machine *sm,
 			 sm, var, state);
   add_event (end_of_path);
 }
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/config-plugin.in b/gcc/analyzer/config-plugin.in
deleted file mode 100644
index 283e8e93e498..000000000000
--- a/gcc/analyzer/config-plugin.in
+++ /dev/null
@@ -1,34 +0,0 @@
-# Top level configure fragment for static analyzer
-#   Copyright (C) 2013-2015 Free Software Foundation, Inc.
-
-#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/>.
-
-# Configure looks for the existence of this file to auto-config each plugin.
-# We define several parameters used by configure:
-#
-# plugin	- name of plugin as it would appear in $(PLUGINS)
-# compilers	- value to add to $(COMPILERS)
-
-plugin="analyzer"
-
-compilers=""
-
-target_libs=""
-
-gtfiles=""
-
-build_by_default="no"
diff --git a/gcc/analyzer/constraint-manager.cc b/gcc/analyzer/constraint-manager.cc
index 12846f2c18b6..8a9bb5df0c9b 100644
--- a/gcc/analyzer/constraint-manager.cc
+++ b/gcc/analyzer/constraint-manager.cc
@@ -19,19 +19,23 @@ along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
 #include "config.h"
-#include "gcc-plugin.h"
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
+#include "function.h"
+#include "basic-block.h"
 #include "gimple.h"
 #include "gimple-iterator.h"
-#include "graphviz.h"
+#include "fold-const.h"
 #include "selftest.h"
 #include "analyzer/analyzer.h"
+#include "analyzer/graphviz.h"
 #include "analyzer/supergraph.h"
 #include "analyzer/constraint-manager.h"
 #include "analyzer/analyzer-selftests.h"
 
+#if ENABLE_ANALYZER
+
 /* One of the end-points of a range.  */
 
 struct bound
@@ -2261,3 +2265,5 @@ analyzer_constraint_manager_cc_tests ()
 } // namespace selftest
 
 #endif /* CHECKING_P */
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc
index cbbba4e8de40..258d3187acc8 100644
--- a/gcc/analyzer/diagnostic-manager.cc
+++ b/gcc/analyzer/diagnostic-manager.cc
@@ -19,7 +19,6 @@ along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
 #include "config.h"
-#include "gcc-plugin.h"
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
@@ -31,6 +30,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "analyzer/exploded-graph.h"
 #include "analyzer/checker-path.h"
 
+#if ENABLE_ANALYZER
+
 /* class saved_diagnostic.  */
 
 /* saved_diagnostic's ctor.
@@ -370,7 +371,7 @@ void
 diagnostic_manager::emit_saved_diagnostics (const exploded_graph &eg)
 {
   LOG_SCOPE (get_logger ());
-  auto_client_timevar tv ("emit saved diagnostics");
+  auto_timevar tv (TV_ANALYZER_DIAGNOSTICS);
   log ("# saved diagnostics: %i", m_saved_diagnostics.length ());
 
   if (m_saved_diagnostics.length () == 0)
@@ -1185,3 +1186,5 @@ diagnostic_manager::prune_path (checker_path *path,
 
   path->maybe_log (get_logger (), "pruned");
 }
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/digraph.cc b/gcc/analyzer/digraph.cc
index c1fa46ee0c3a..04a4249f0119 100644
--- a/gcc/analyzer/digraph.cc
+++ b/gcc/analyzer/digraph.cc
@@ -19,7 +19,6 @@ along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
 #include "config.h"
-#include "gcc-plugin.h"
 #include "system.h"
 #include "coretypes.h"
 #include "diagnostic.h"
@@ -28,6 +27,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "analyzer/shortest-paths.h"
 #include "selftest.h"
 
+#if ENABLE_ANALYZER
+
 #if CHECKING_P
 
 namespace selftest {
@@ -187,3 +188,5 @@ analyzer_digraph_cc_tests ()
 } // namespace selftest
 
 #endif /* #if CHECKING_P */
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index f739af779ef3..66f6a39739fd 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -19,10 +19,10 @@ along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
 #include "config.h"
-#include "gcc-plugin.h"
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
+#include "fold-const.h"
 #include "gcc-rich-location.h"
 #include "analyzer/exploded-graph.h"
 #include "analyzer/analysis-plan.h"
@@ -31,6 +31,8 @@ along with GCC; see the file COPYING3.  If not see
 
 /* For an overview, see gcc/doc/analyzer.texi.  */
 
+#if ENABLE_ANALYZER
+
 static int readability_comparator (const void *p1, const void *p2);
 
 ////////////////////////////////////////////////////////////////////////////
@@ -1470,7 +1472,7 @@ strongly_connected_components (const supergraph &sg, logger *logger)
 : m_sg (sg), m_per_node (m_sg.num_nodes ())
 {
   LOG_SCOPE (logger);
-  auto_client_timevar tv ("computing scc");
+  auto_timevar tv (TV_ANALYZER_SCC);
 
   for (int i = 0; i < m_sg.num_nodes (); i++)
     m_per_node.quick_push (per_node_data ());
@@ -2082,7 +2084,7 @@ void
 exploded_graph::process_worklist ()
 {
   LOG_SCOPE (get_logger ());
-  auto_client_timevar tv ("exploded_graph::process_worklist");
+  auto_timevar tv (TV_ANALYZER_WORKLIST);
 
   while (m_worklist.length () > 0)
     {
@@ -3048,7 +3050,7 @@ exploded_graph::dump_exploded_nodes () const
 
   if (flag_dump_analyzer_exploded_nodes)
     {
-      auto_client_timevar tv ("-fdump-analyzer-exploded-nodes");
+      auto_timevar tv (TV_ANALYZER_DUMP);
       gcc_rich_location richloc (UNKNOWN_LOCATION);
       unsigned i;
       exploded_node *enode;
@@ -3079,7 +3081,7 @@ exploded_graph::dump_exploded_nodes () const
   /* Dump the egraph in textual form to a dump file.  */
   if (flag_dump_analyzer_exploded_nodes_2)
     {
-      auto_client_timevar tv ("-fdump-analyzer-exploded-nodes-2");
+      auto_timevar tv (TV_ANALYZER_DUMP);
       char *filename
 	= concat (dump_base_name, ".eg.txt", NULL);
       FILE *outf = fopen (filename, "w");
@@ -3109,7 +3111,7 @@ exploded_graph::dump_exploded_nodes () const
   /* Dump the egraph in textual form to multiple dump files, one per enode.  */
   if (flag_dump_analyzer_exploded_nodes_3)
     {
-      auto_client_timevar tv ("-fdump-analyzer-exploded-nodes-3");
+      auto_timevar tv (TV_ANALYZER_DUMP);
 
       unsigned i;
       exploded_node *enode;
@@ -3458,7 +3460,7 @@ dump_callgraph (const supergraph &sg, const char *filename,
 static void
 dump_callgraph (const supergraph &sg, const exploded_graph *eg)
 {
-  auto_client_timevar tv ("writing .callgraph.dot");
+  auto_timevar tv (TV_ANALYZER_DUMP);
   char *filename = concat (dump_base_name, ".callgraph.dot", NULL);
   dump_callgraph (sg, filename, eg);
   free (filename);
@@ -3488,7 +3490,7 @@ impl_run_checkers (logger *logger)
 
   if (flag_dump_analyzer_supergraph)
     {
-      auto_client_timevar tv ("writing .supergraph.dot");
+      auto_timevar tv (TV_ANALYZER_DUMP);
       char *filename = concat (dump_base_name, ".supergraph.dot", NULL);
       supergraph::dump_args_t args ((enum supergraph_dot_flags)0, NULL);
       sg.dump_dot (filename, args);
@@ -3497,7 +3499,7 @@ impl_run_checkers (logger *logger)
 
   if (flag_dump_analyzer_state_purge)
     {
-      auto_client_timevar tv ("writing .state-purge.dot");
+      auto_timevar tv (TV_ANALYZER_DUMP);
       state_purge_annotator a (purge_map);
       char *filename = concat (dump_base_name, ".state-purge.dot", NULL);
       supergraph::dump_args_t args ((enum supergraph_dot_flags)0, &a);
@@ -3533,7 +3535,7 @@ impl_run_checkers (logger *logger)
 
   if (flag_dump_analyzer_exploded_graph)
     {
-      auto_client_timevar tv ("writing .eg.dot");
+      auto_timevar tv (TV_ANALYZER_DUMP);
       char *filename
 	= concat (dump_base_name, ".eg.dot", NULL);
       exploded_graph::dump_args_t args (eg);
@@ -3561,8 +3563,6 @@ impl_run_checkers (logger *logger)
 void
 run_checkers ()
 {
-  auto_client_timevar tv ("run_checkers");
-
   /* Handle -fdump-analyzer and -fdump-analyzer-stderr.  */
   FILE *dump_fout = NULL;
   /* Track if we're responsible for closing dump_fout.  */
@@ -3594,3 +3594,5 @@ run_checkers ()
   if (owns_dump_fout)
     fclose (dump_fout);
 }
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h
index c8784acd8d91..95c7b985eca8 100644
--- a/gcc/analyzer/exploded-graph.h
+++ b/gcc/analyzer/exploded-graph.h
@@ -21,6 +21,7 @@ along with GCC; see the file COPYING3.  If not see
 #ifndef GCC_ANALYZER_EXPLODED_GRAPH_H
 #define GCC_ANALYZER_EXPLODED_GRAPH_H
 
+#include "alloc-pool.h"
 #include "fibonacci_heap.h"
 #include "analyzer/analyzer-logging.h"
 #include "analyzer/constraint-manager.h"
diff --git a/gcc/analyzer/graphviz.cc b/gcc/analyzer/graphviz.cc
index 4ba9330cd8c8..54b8a036ae41 100644
--- a/gcc/analyzer/graphviz.cc
+++ b/gcc/analyzer/graphviz.cc
@@ -19,11 +19,12 @@ along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
 #include "config.h"
-#include "gcc-plugin.h"
 #include "system.h"
 #include "coretypes.h"
 #include "analyzer/graphviz.h"
 
+#if ENABLE_ANALYZER
+
 /* graphviz_out's ctor, wrapping PP.  */
 
 graphviz_out::graphviz_out (pretty_printer *pp)
@@ -99,3 +100,5 @@ graphviz_out::end_tr ()
   pp_string (m_pp, "</TD></TR>");
   pp_write_text_to_stream (m_pp);
 }
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/pending-diagnostic.cc b/gcc/analyzer/pending-diagnostic.cc
index 96ab82403978..ae61b47bef66 100644
--- a/gcc/analyzer/pending-diagnostic.cc
+++ b/gcc/analyzer/pending-diagnostic.cc
@@ -19,7 +19,6 @@ along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
 #include "config.h"
-#include "gcc-plugin.h"
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
@@ -28,6 +27,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "analyzer/analyzer.h"
 #include "analyzer/pending-diagnostic.h"
 
+#if ENABLE_ANALYZER
+
 /* Generate a label_text by printing FMT.
 
    Use a clone of the global_dc for formatting callbacks.
@@ -59,3 +60,5 @@ evdesc::event_desc::formatted_print (const char *fmt, ...) const
   delete pp;
   return result;
 }
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/program-point.cc b/gcc/analyzer/program-point.cc
index c46aa51eb43b..4ccb79a95128 100644
--- a/gcc/analyzer/program-point.cc
+++ b/gcc/analyzer/program-point.cc
@@ -19,7 +19,6 @@ along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
 #include "config.h"
-#include "gcc-plugin.h"
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
@@ -29,6 +28,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "analyzer/exploded-graph.h"
 #include "analyzer/analysis-plan.h"
 
+#if ENABLE_ANALYZER
+
 /* Get a string for PK.  */
 
 const char *
@@ -517,3 +518,5 @@ analyzer_program_point_cc_tests ()
 } // namespace selftest
 
 #endif /* CHECKING_P */
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc
index 6313174e99ad..bd2309c4b627 100644
--- a/gcc/analyzer/program-state.cc
+++ b/gcc/analyzer/program-state.cc
@@ -19,7 +19,6 @@ along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
 #include "config.h"
-#include "gcc-plugin.h"
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
@@ -31,6 +30,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "analyzer/state-purge.h"
 #include "analyzer/analyzer-selftests.h"
 
+#if ENABLE_ANALYZER
+
 ////////////////////////////////////////////////////////////////////////////
 
 /* class sm_state_map.  */
@@ -1316,3 +1317,5 @@ analyzer_program_state_cc_tests ()
 } // namespace selftest
 
 #endif /* CHECKING_P */
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index dccd4eec37c7..56e45f20b637 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -19,21 +19,25 @@ along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
 #include "config.h"
-#include "gcc-plugin.h"
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
+#include "function.h"
+#include "basic-block.h"
 #include "gimple.h"
 #include "gimple-iterator.h"
 #include "graphviz.h"
+#include "options.h"
 #include "cgraph.h"
 #include "tree-dfa.h"
 #include "stringpool.h"
 #include "convert.h"
 #include "target.h"
-#include "selftest.h"
+#include "fold-const.h"
+#include "tree-pretty-print.h"
 #include "diagnostic-color.h"
 #include "diagnostic-metadata.h"
+#include "selftest.h"
 #include "analyzer/analyzer.h"
 #include "analyzer/analyzer-logging.h"
 #include "analyzer/tristate.h"
@@ -43,8 +47,19 @@ along with GCC; see the file COPYING3.  If not see
 #include "analyzer/pending-diagnostic.h"
 #include "analyzer/analyzer-selftests.h"
 
+#if ENABLE_ANALYZER
+
 ////////////////////////////////////////////////////////////////////////////
 
+/* Dump T to PP in language-independent form, for debugging/logging/dumping
+   purposes.  */
+
+static void
+dump_tree (pretty_printer *pp, tree t)
+{
+  dump_generic_node (pp, t, 0, TDF_SLIM, 0);
+}
+
 /* Dump this path_var to PP (which must support %E for trees).
 
    Express the stack depth using an "@DEPTH" suffix, so e.g. given
@@ -3456,9 +3471,9 @@ region_model::dump_to_pp (pretty_printer *pp, bool summarize) const
 			   && CONSTANT_CLASS_P (rhs_tree)))
 		    {
 		      dump_separator (pp, &is_first);
-		      PUSH_IGNORE_WFORMAT
-			pp_printf (pp, "%E == %E", lhs_tree, rhs_tree);
-		      POP_IGNORE_WFORMAT
+		      dump_tree (pp, lhs_tree);
+		      pp_string (pp, " == ");
+		      dump_tree (pp, rhs_tree);
 		    }
 		}
 	    }
@@ -3477,10 +3492,9 @@ region_model::dump_to_pp (pretty_printer *pp, bool summarize) const
 	      && !(CONSTANT_CLASS_P (lhs_tree) && CONSTANT_CLASS_P (rhs_tree)))
 	    {
 	      dump_separator (pp, &is_first);
-	      PUSH_IGNORE_WFORMAT
-		pp_printf (pp, "%E %s %E",
-			   lhs_tree, constraint_op_code (c->m_op), rhs_tree);
-	      POP_IGNORE_WFORMAT
+	      dump_tree (pp, lhs_tree);
+	      pp_printf (pp, " %s ", constraint_op_code (c->m_op));
+	      dump_tree (pp, rhs_tree);
 	   }
 	}
 
@@ -3554,10 +3568,7 @@ dump_vec_of_tree (pretty_printer *pp,
     {
       if (i > 0)
 	pp_string (pp, ", ");
-
-      PUSH_IGNORE_WFORMAT
-	pp_printf (pp, "%E", key);
-      POP_IGNORE_WFORMAT
+      dump_tree (pp, key);
     }
   pp_printf (pp, "}: %s", label);
 }
@@ -3610,20 +3621,22 @@ region_model::dump_summary_of_map (pretty_printer *pp,
 	    region_id pointee_rid = region_sval->get_pointee ();
 	    tree pointee = get_representative_path_var (pointee_rid).m_tree;
 	    dump_separator (pp, is_first);
-	    PUSH_IGNORE_WFORMAT
-	      if (pointee)
-		pp_printf (pp, "%E: &%E", key, pointee);
-	      else
-		pp_printf (pp, "%E: NULL", key);
-	    POP_IGNORE_WFORMAT
+	    dump_tree (pp, key);
+	    pp_string (pp, ": ");
+	    if (pointee)
+	      {
+		pp_character (pp, '&');
+		dump_tree (pp, pointee);
+	      }
+	    else
+	      pp_string (pp, "NULL");
 	  }
 	  break;
 	case SK_CONSTANT:
 	  dump_separator (pp, is_first);
-	  PUSH_IGNORE_WFORMAT
-	    pp_printf (pp, "%E: %E", key,
-		       sval->dyn_cast_constant_svalue ()->get_constant ());
-	  POP_IGNORE_WFORMAT
+	  dump_tree (pp, key);
+	  pp_string (pp, ": ");
+	  dump_tree (pp, sval->dyn_cast_constant_svalue ()->get_constant ());
 	  break;
 	case SK_UNKNOWN:
 	  unknown_keys.safe_push (key);
@@ -3637,9 +3650,8 @@ region_model::dump_summary_of_map (pretty_printer *pp,
 	    else
 	      {
 		dump_separator (pp, is_first);
-		PUSH_IGNORE_WFORMAT
-		  pp_printf (pp, "%E: %s", key, poison_kind_to_str (pkind));
-		POP_IGNORE_WFORMAT
+		dump_tree (pp, key);
+		pp_printf (pp, ": %s", poison_kind_to_str (pkind));
 	      }
 	  }
 	  break;
@@ -7787,3 +7799,5 @@ analyzer_region_model_cc_tests ()
 } // namespace selftest
 
 #endif /* CHECKING_P */
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index 5cb3a9a15ab3..cf41cd88b6a9 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -21,6 +21,7 @@ along with GCC; see the file COPYING3.  If not see
 #ifndef GCC_ANALYZER_REGION_MODEL_H
 #define GCC_ANALYZER_REGION_MODEL_H
 
+#include "sbitmap.h"
 #include "ordered-hash-map.h"
 #include "pretty-print.h"
 #include "selftest.h"
diff --git a/gcc/analyzer/shortest-paths.h b/gcc/analyzer/shortest-paths.h
index 4738f182b854..4dd48367afd3 100644
--- a/gcc/analyzer/shortest-paths.h
+++ b/gcc/analyzer/shortest-paths.h
@@ -69,7 +69,7 @@ shortest_paths<GraphTraits, Path_t>::shortest_paths (const graph_t &graph,
   m_dist (graph.m_nodes.length ()),
   m_prev (graph.m_nodes.length ())
 {
-  auto_client_timevar tv ("shortest_paths");
+  auto_timevar tv (TV_ANALYZER_SHORTEST_PATHS);
 
   auto_vec<int> queue (graph.m_nodes.length ());
 
diff --git a/gcc/analyzer/sm-file.cc b/gcc/analyzer/sm-file.cc
index a96971464af7..f7f065429386 100644
--- a/gcc/analyzer/sm-file.cc
+++ b/gcc/analyzer/sm-file.cc
@@ -19,17 +19,21 @@ along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
 #include "config.h"
-#include "gcc-plugin.h"
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
+#include "function.h"
+#include "basic-block.h"
 #include "gimple.h"
+#include "options.h"
 #include "diagnostic-path.h"
 #include "diagnostic-metadata.h"
 #include "analyzer/analyzer.h"
 #include "analyzer/pending-diagnostic.h"
 #include "analyzer/sm.h"
 
+#if ENABLE_ANALYZER
+
 namespace {
 
 /* A state machine for detecting misuses of <stdio.h>'s FILE * API.  */
@@ -326,3 +330,5 @@ make_fileptr_state_machine (logger *logger)
 {
   return new fileptr_state_machine (logger);
 }
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc
index 4253d2b42535..5b3ae1d2d7e4 100644
--- a/gcc/analyzer/sm-malloc.cc
+++ b/gcc/analyzer/sm-malloc.cc
@@ -19,17 +19,22 @@ along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
 #include "config.h"
-#include "gcc-plugin.h"
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
+#include "function.h"
+#include "basic-block.h"
 #include "gimple.h"
+#include "options.h"
+#include "bitmap.h"
 #include "diagnostic-path.h"
 #include "diagnostic-metadata.h"
 #include "analyzer/analyzer.h"
 #include "analyzer/pending-diagnostic.h"
 #include "analyzer/sm.h"
 
+#if ENABLE_ANALYZER
+
 namespace {
 
 ////////////////////////////////////////////////////////////////////////////
@@ -788,3 +793,5 @@ make_malloc_state_machine (logger *logger)
 {
   return new malloc_state_machine (logger);
 }
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/sm-pattern-test.cc b/gcc/analyzer/sm-pattern-test.cc
index 746f2fa64d41..b8863120cfe9 100644
--- a/gcc/analyzer/sm-pattern-test.cc
+++ b/gcc/analyzer/sm-pattern-test.cc
@@ -21,10 +21,11 @@ along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
 #include "config.h"
-#include "gcc-plugin.h"
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
+#include "function.h"
+#include "basic-block.h"
 #include "gimple.h"
 #include "tree-pretty-print.h"
 #include "diagnostic-path.h"
@@ -33,6 +34,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "analyzer/pending-diagnostic.h"
 #include "analyzer/sm.h"
 
+#if ENABLE_ANALYZER
+
 namespace {
 
 /* A state machine for use in DejaGnu tests, to check that
@@ -146,3 +149,5 @@ make_pattern_test_state_machine (logger *logger)
 {
   return new pattern_test_state_machine (logger);
 }
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/sm-sensitive.cc b/gcc/analyzer/sm-sensitive.cc
index 08b1b32b7933..dfb6a05ef5f0 100644
--- a/gcc/analyzer/sm-sensitive.cc
+++ b/gcc/analyzer/sm-sensitive.cc
@@ -20,17 +20,22 @@ along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
 #include "config.h"
-#include "gcc-plugin.h"
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
+#include "function.h"
+#include "function.h"
+#include "basic-block.h"
 #include "gimple.h"
+#include "options.h"
 #include "diagnostic-path.h"
 #include "diagnostic-metadata.h"
 #include "analyzer/analyzer.h"
 #include "analyzer/pending-diagnostic.h"
 #include "analyzer/sm.h"
 
+#if ENABLE_ANALYZER
+
 namespace {
 
 /* An experimental state machine, for tracking exposure of sensitive
@@ -192,3 +197,5 @@ make_sensitive_state_machine (logger *logger)
 {
   return new sensitive_state_machine (logger);
 }
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/sm-signal.cc b/gcc/analyzer/sm-signal.cc
index 6b7b5f7b5327..73448db08405 100644
--- a/gcc/analyzer/sm-signal.cc
+++ b/gcc/analyzer/sm-signal.cc
@@ -21,10 +21,11 @@ along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
 #include "config.h"
-#include "gcc-plugin.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 "diagnostic-metadata.h"
@@ -34,6 +35,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "analyzer/pending-diagnostic.h"
 #include "analyzer/sm.h"
 
+#if ENABLE_ANALYZER
+
 namespace {
 
 /* An experimental state machine, for tracking calls to async-signal-unsafe
@@ -303,3 +306,5 @@ make_signal_state_machine (logger *logger)
 {
   return new signal_state_machine (logger);
 }
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/sm-taint.cc b/gcc/analyzer/sm-taint.cc
index 1c110119b2d6..e4e701213d6e 100644
--- a/gcc/analyzer/sm-taint.cc
+++ b/gcc/analyzer/sm-taint.cc
@@ -21,17 +21,21 @@ along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
 #include "config.h"
-#include "gcc-plugin.h"
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
+#include "function.h"
+#include "basic-block.h"
 #include "gimple.h"
+#include "options.h"
 #include "diagnostic-path.h"
 #include "diagnostic-metadata.h"
 #include "analyzer/analyzer.h"
 #include "analyzer/pending-diagnostic.h"
 #include "analyzer/sm.h"
 
+#if ENABLE_ANALYZER
+
 namespace {
 
 /* An experimental state machine, for tracking "taint": unsanitized uses
@@ -321,3 +325,5 @@ make_taint_state_machine (logger *logger)
 {
   return new taint_state_machine (logger);
 }
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/sm.cc b/gcc/analyzer/sm.cc
index 39360c03f6cc..9822eedc43f2 100644
--- a/gcc/analyzer/sm.cc
+++ b/gcc/analyzer/sm.cc
@@ -19,14 +19,18 @@ along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
 #include "config.h"
-#include "gcc-plugin.h"
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
+#include "function.h"
+#include "basic-block.h"
 #include "gimple.h"
+#include "options.h"
 #include "analyzer/analyzer.h"
 #include "analyzer/sm.h"
 
+#if ENABLE_ANALYZER
+
 ////////////////////////////////////////////////////////////////////////////
 
 /* If STMT is an assignment to zero, return the LHS.  */
@@ -134,3 +138,5 @@ make_checkers (auto_delete_vec <state_machine> &out, logger *logger)
 					  (*sm)->get_name ()));
     }
 }
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/state-purge.cc b/gcc/analyzer/state-purge.cc
index 79eb5549197c..75b8548e4c5d 100644
--- a/gcc/analyzer/state-purge.cc
+++ b/gcc/analyzer/state-purge.cc
@@ -19,23 +19,27 @@ along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
 #include "config.h"
-#include "gcc-plugin.h"
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
 #include "timevar.h"
 #include "tree-ssa-alias.h"
+#include "function.h"
+#include "basic-block.h"
 #include "gimple.h"
 #include "stringpool.h"
 #include "tree-vrp.h"
 #include "gimple-ssa.h"
 #include "tree-ssanames.h"
 #include "tree-phinodes.h"
+#include "options.h"
 #include "ssa-iterators.h"
 #include "gimple-pretty-print.h"
 #include "analyzer/analyzer.h"
 #include "analyzer/state-purge.h"
 
+#if ENABLE_ANALYZER
+
 /* state_purge_map's ctor.  Walk all SSA names in all functions, building
    a state_purge_per_ssa_name instance for each.  */
 
@@ -45,7 +49,7 @@ state_purge_map::state_purge_map (const supergraph &sg,
 {
   LOG_FUNC (logger);
 
-  auto_client_timevar tv ("state_purge_map ctor");
+  auto_timevar tv (TV_ANALYZER_STATE_PURGE);
 
   cgraph_node *node;
   FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
@@ -519,3 +523,5 @@ state_purge_annotator::add_stmt_annotations (graphviz_out *gv,
   print_vec_of_names (gv, "needed here", needed);
   print_vec_of_names (gv, "not needed here", not_needed);
 }
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/supergraph.cc b/gcc/analyzer/supergraph.cc
index 17cfe5d5f2d7..59c9264d5a08 100644
--- a/gcc/analyzer/supergraph.cc
+++ b/gcc/analyzer/supergraph.cc
@@ -19,7 +19,6 @@ along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
 #include "config.h"
-#include "gcc-plugin.h"
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
@@ -29,6 +28,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "vec.h"
 #include "ggc.h"
 #include "basic-block.h"
+#include "function.h"
 #include "gimple-fold.h"
 #include "tree-eh.h"
 #include "gimple-expr.h"
@@ -41,10 +41,13 @@ along with GCC; see the file COPYING3.  If not see
 #include "graphviz.h"
 #include "cgraph.h"
 #include "tree-dfa.h"
+#include "cfganal.h"
 #include "analyzer/analyzer.h"
 #include "analyzer/supergraph.h"
 #include "analyzer/analyzer-logging.h"
 
+#if ENABLE_ANALYZER
+
 /* Get the cgraph_edge, but only if there's an underlying function body.  */
 
 cgraph_edge *
@@ -70,7 +73,7 @@ supergraph_call_edge (function *fun, gimple *stmt)
 
 supergraph::supergraph (logger *logger)
 {
-  auto_client_timevar tv ("building supergraph");
+  auto_timevar tv (TV_ANALYZER_SUPERGRAPH);
 
   LOG_FUNC (logger);
 
@@ -948,3 +951,5 @@ callgraph_superedge::map_expr_from_callee_to_caller (tree callee_expr,
 
   return NULL_TREE;
 }
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/supergraph.h b/gcc/analyzer/supergraph.h
index a13d6730b3ca..bc7b8d681a56 100644
--- a/gcc/analyzer/supergraph.h
+++ b/gcc/analyzer/supergraph.h
@@ -22,7 +22,11 @@ along with GCC; see the file COPYING3.  If not see
 #define GCC_ANALYZER_SUPERGRAPH_H
 
 #include "ordered-hash-map.h"
+#include "options.h"
 #include "cgraph.h"
+#include "function.h"
+#include "cfg.h"
+#include "basic-block.h"
 #include "gimple.h"
 #include "gimple-iterator.h"
 #include "analyzer/digraph.h"
diff --git a/gcc/analyzer/tristate.cc b/gcc/analyzer/tristate.cc
index ac161292f1c4..1875a674eb3f 100644
--- a/gcc/analyzer/tristate.cc
+++ b/gcc/analyzer/tristate.cc
@@ -19,12 +19,13 @@ along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
 #include "config.h"
-#include "gcc-plugin.h"
 #include "system.h"
 #include "coretypes.h"
 #include "analyzer/tristate.h"
 #include "selftest.h"
 
+#if ENABLE_ANALYZER
+
 const char *
 tristate::as_string () const
 {
@@ -220,3 +221,5 @@ analyzer_tristate_cc_tests ()
 } // namespace selftest
 
 #endif /* CHECKING_P */
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/common.opt b/gcc/common.opt
index c6512e6ead2e..2e055c5969fc 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -990,7 +990,8 @@ Common Report Var(flag_store_data_races) Optimization
 Allow the compiler to introduce new data races on stores.
 
 fanalyzer
-Driver Var(flag_analyzer)
+Common Var(flag_analyzer)
+Enable static analysis pass.
 
 fargument-alias
 Common Ignore
diff --git a/gcc/configure b/gcc/configure
index 24d27d3e3cba..d5c015e5867c 100755
--- a/gcc/configure
+++ b/gcc/configure
@@ -958,6 +958,7 @@ enable_fixed_point
 enable_threads
 enable_tls
 enable_vtable_verify
+enable_analyzer
 enable_objc_gc
 with_dwarf2
 enable_shared
@@ -1685,6 +1686,7 @@ Optional Features:
   --enable-tls            enable or disable generation of tls code overriding
                           the assembler check for tls support
   --enable-vtable-verify  enable vtable verification feature
+  --enable-analyzer       enable -fanalyzer static analyzer
   --enable-objc-gc        enable the use of Boehm's garbage collector with the
                           GNU Objective-C runtime
   --disable-shared        don't provide a shared libgcc
@@ -7703,6 +7705,23 @@ cat >>confdefs.h <<_ACEOF
 _ACEOF
 
 
+# Check whether --enable-analyzer was given.
+if test "${enable_analyzer+set}" = set; then :
+  enableval=$enable_analyzer; if test x$enable_analyzer = xno; then
+	analyzer=0
+else
+	analyzer=1
+fi
+else
+  analyzer=0
+fi
+
+
+cat >>confdefs.h <<_ACEOF
+#define ENABLE_ANALYZER $analyzer
+_ACEOF
+
+
 # Check whether --enable-objc-gc was given.
 if test "${enable_objc_gc+set}" = set; then :
   enableval=$enable_objc_gc; if test x$enable_objc_gc = xno; then
@@ -18938,7 +18957,7 @@ else
   lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
   lt_status=$lt_dlunknown
   cat > conftest.$ac_ext <<_LT_EOF
-#line 18941 "configure"
+#line 18960 "configure"
 #include "confdefs.h"
 
 #if HAVE_DLFCN_H
@@ -19044,7 +19063,7 @@ else
   lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
   lt_status=$lt_dlunknown
   cat > conftest.$ac_ext <<_LT_EOF
-#line 19047 "configure"
+#line 19066 "configure"
 #include "confdefs.h"
 
 #if HAVE_DLFCN_H
@@ -31960,7 +31979,7 @@ $as_echo "$as_me: executing $ac_file commands" >&6;}
     "depdir":C) $SHELL $ac_aux_dir/mkinstalldirs $DEPDIR ;;
     "gccdepdir":C)
   ${CONFIG_SHELL-/bin/sh} $ac_aux_dir/mkinstalldirs build/$DEPDIR
-  for lang in $subdirs c-family common
+  for lang in $subdirs c-family common analyzer
   do
       ${CONFIG_SHELL-/bin/sh} $ac_aux_dir/mkinstalldirs $lang/$DEPDIR
   done ;;
diff --git a/gcc/configure.ac b/gcc/configure.ac
index 954c1496fb1c..dcd5e8dd38cc 100644
--- a/gcc/configure.ac
+++ b/gcc/configure.ac
@@ -909,6 +909,18 @@ vtable_verify=`if test x$enable_vtable_verify = xyes; then echo 1; else echo 0;
 AC_DEFINE_UNQUOTED(ENABLE_VTABLE_VERIFY, $vtable_verify,
 [Define 0/1 if vtable verification feature is enabled.])
 
+AC_ARG_ENABLE(analyzer,
+[AS_HELP_STRING([--disable-analyzer],
+		[disable -fanalyzer static analyzer])],
+if test x$enable_analyzer = xno; then
+	analyzer=0
+else
+	analyzer=1
+fi,
+analyzer=1)
+AC_DEFINE_UNQUOTED(ENABLE_ANALYZER, $analyzer,
+[Define 0/1 if static analyzer feature is enabled.])
+
 AC_ARG_ENABLE(objc-gc,
 [AS_HELP_STRING([--enable-objc-gc],
 		[enable the use of Boehm's garbage collector with
@@ -1214,7 +1226,7 @@ AC_CHECK_HEADERS(ext/hash_map)
 ZW_CREATE_DEPDIR
 AC_CONFIG_COMMANDS([gccdepdir],[
   ${CONFIG_SHELL-/bin/sh} $ac_aux_dir/mkinstalldirs build/$DEPDIR
-  for lang in $subdirs c-family common
+  for lang in $subdirs c-family common analyzer
   do
       ${CONFIG_SHELL-/bin/sh} $ac_aux_dir/mkinstalldirs $lang/$DEPDIR
   done], [subdirs="$subdirs" ac_aux_dir=$ac_aux_dir DEPDIR=$DEPDIR])
diff --git a/gcc/gcc.c b/gcc/gcc.c
index d96e568a0b5a..4428d50c3906 100644
--- a/gcc/gcc.c
+++ b/gcc/gcc.c
@@ -3965,7 +3965,6 @@ driver_handle_option (struct gcc_options *opts,
       add_linker_option ("--target-help", 13);
       break;
 
-    case OPT_fanalyzer:
     case OPT__no_sysroot_suffix:
     case OPT_pass_exit_codes:
     case OPT_print_search_dirs:
@@ -4611,17 +4610,6 @@ process_command (unsigned int decoded_options_count,
 			   CL_DRIVER, &handlers, global_dc);
     }
 
-  if (flag_analyzer)
-    {
-#ifdef ENABLE_ANALYZER
-      save_switch ("-fplugin=analyzer_plugin", 0, NULL, true, true);
-#else
-      sorry ("%qs was not enabled in this build of GCC"
-	     " (missing configure-time option %qs)",
-	     "-fanalyzer", "--enable-plugins=analyzer");
-#endif
-    }
-
   /* If the user didn't specify any, default to all configured offload
      targets.  */
   if (ENABLE_OFFLOADING && offload_targets == NULL)
diff --git a/gcc/passes.def b/gcc/passes.def
index 798a391bd351..0ee8b621016c 100644
--- a/gcc/passes.def
+++ b/gcc/passes.def
@@ -142,6 +142,7 @@ along with GCC; see the file COPYING3.  If not see
   TERMINATE_PASS_LIST (all_small_ipa_passes)
 
   INSERT_PASSES_AFTER (all_regular_ipa_passes)
+  NEXT_PASS (pass_analyzer);
   NEXT_PASS (pass_ipa_whole_program_visibility);
   NEXT_PASS (pass_ipa_profile);
   NEXT_PASS (pass_ipa_icf);
diff --git a/gcc/selftest-run-tests.c b/gcc/selftest-run-tests.c
index 156575a6695c..883fb104c101 100644
--- a/gcc/selftest-run-tests.c
+++ b/gcc/selftest-run-tests.c
@@ -27,6 +27,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "options.h"
 #include "stringpool.h"
 #include "attribs.h"
+#include "analyzer/analyzer-selftests.h"
 
 /* This function needed to be split out from selftest.c as it references
    tests from the whole source tree, and so is within
@@ -115,6 +116,9 @@ selftest::run_tests ()
   /* Run any lang-specific selftests.  */
   lang_hooks.run_lang_selftests ();
 
+  /* Run the analyzer selftests (if enabled).  */
+  run_analyzer_selftests ();
+
   /* Force a GC at the end of the selftests, to shake out GC-related
      issues.  For example, if any GC-managed items have buggy (or missing)
      finalizers, this last collection will ensure that things that were
diff --git a/gcc/timevar.def b/gcc/timevar.def
index 357fcfd65c59..7fca9286d748 100644
--- a/gcc/timevar.def
+++ b/gcc/timevar.def
@@ -322,3 +322,14 @@ DEFTIMEVAR (TV_LINK		     , "link JIT code")
 DEFTIMEVAR (TV_LOAD		     , "load JIT result")
 DEFTIMEVAR (TV_JIT_ACQUIRING_MUTEX   , "acquiring JIT mutex")
 DEFTIMEVAR (TV_JIT_CLIENT_CODE   , "JIT client code")
+
+/* Analyzer timevars.  */
+DEFTIMEVAR (TV_ANALYZER              , "analyzer")
+DEFTIMEVAR (TV_ANALYZER_SUPERGRAPH   , "analyzer: supergraph")
+DEFTIMEVAR (TV_ANALYZER_STATE_PURGE  , "analyzer: state purge")
+DEFTIMEVAR (TV_ANALYZER_PLAN         , "analyzer: planning")
+DEFTIMEVAR (TV_ANALYZER_SCC          , "analyzer: scc")
+DEFTIMEVAR (TV_ANALYZER_WORKLIST     , "analyzer: processing worklist")
+DEFTIMEVAR (TV_ANALYZER_DUMP         , "analyzer: dump")
+DEFTIMEVAR (TV_ANALYZER_DIAGNOSTICS  , "analyzer: emitting diagnostics")
+DEFTIMEVAR (TV_ANALYZER_SHORTEST_PATHS, "analyzer: shortest paths")
diff --git a/gcc/tree-pass.h b/gcc/tree-pass.h
index a987661530ea..59a5fa6ad4a5 100644
--- a/gcc/tree-pass.h
+++ b/gcc/tree-pass.h
@@ -490,6 +490,7 @@ extern simple_ipa_opt_pass *make_pass_build_ssa_passes (gcc::context *ctxt);
 extern simple_ipa_opt_pass *make_pass_local_optimization_passes (gcc::context *ctxt);
 extern simple_ipa_opt_pass *make_pass_ipa_remove_symbols (gcc::context *ctxt);
 
+extern ipa_opt_pass_d *make_pass_analyzer (gcc::context *ctxt);
 extern ipa_opt_pass_d *make_pass_ipa_whole_program_visibility (gcc::context
 							       *ctxt);
 extern simple_ipa_opt_pass *make_pass_ipa_increase_alignment (gcc::context
-- 
2.21.0

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 1/2] Fixup for rebase: c-format.c: get_pointer_to_named_type -> get_named_type
  2019-12-08 14:28           ` [PATCH 0/2] v3 of analyzer patch kit (unsquashed) David Malcolm
  2019-12-08 14:28             ` [PATCH 2/2] Rework as a non-plugin David Malcolm
@ 2019-12-08 14:28             ` David Malcolm
  1 sibling, 0 replies; 160+ messages in thread
From: David Malcolm @ 2019-12-08 14:28 UTC (permalink / raw)
  To: gcc-patches; +Cc: law, Richard Biener, Thomas Schwinge, David Malcolm

r278634 changed this, so need to apply this when rebasing

gcc/c-family/ChangeLog:
	* c-format.c (init_dynamic_diag_info): Use get_named_type
	rather than get_pointer_to_named_type, reflecting r278634.
---
 gcc/c-family/c-format.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gcc/c-family/c-format.c b/gcc/c-family/c-format.c
index c5ca71216633..be092b1f3b62 100644
--- a/gcc/c-family/c-format.c
+++ b/gcc/c-family/c-format.c
@@ -4993,7 +4993,7 @@ init_dynamic_diag_info (void)
   /* Similar to the above but for diagnostic_event_id_t*.  */
   if (!local_event_ptr_node
       || local_event_ptr_node == void_type_node)
-    local_event_ptr_node = get_pointer_to_named_type ("diagnostic_event_id_t");
+    local_event_ptr_node = get_named_type ("diagnostic_event_id_t");
 
   static tree hwi;
 
-- 
2.21.0

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 0/2] v3 of analyzer patch kit (unsquashed)
  2019-12-06 22:25         ` Jeff Law
@ 2019-12-08 14:28           ` David Malcolm
  2019-12-08 14:28             ` [PATCH 2/2] Rework as a non-plugin David Malcolm
  2019-12-08 14:28             ` [PATCH 1/2] Fixup for rebase: c-format.c: get_pointer_to_named_type -> get_named_type David Malcolm
  0 siblings, 2 replies; 160+ messages in thread
From: David Malcolm @ 2019-12-08 14:28 UTC (permalink / raw)
  To: gcc-patches; +Cc: law, Richard Biener, Thomas Schwinge, David Malcolm

I've most of the way through a v3 of the patch kit, but given the
ongoing discussion/review on this mailing list, I thought I'd post
what I have so far.

I'm going to work on squashing the changes, to produce a v3 relative to
trunk.  This drops the "in-tree plugin" idea, and the
auto_client_timevar, but in the meantime here are the changes relative
to the branch.

I've rebased from r278495 (2019-11-20) to r279013 (2019-12-05).

Patch 1 contains the only change needed by the rebase.

Patch 2 contains the fixups *relative to the analyzer branch* to
convert it from being an in-tree plugin to being built into gcc,
with an opt-out configure option (--disable-analyzer). Changes
include:
  * I kept the new source files in the new "gcc/analyzer" subdirectory,
    although they're still built by gcc/Makefile.in - we have a huge
    number of source files in the "gcc" subdir, and we already have
    subdirs that complicate grep, so keeping a subdir for the source
    files seemed cleanest.  (though some e.g. tristate.h/cc might be
    better in the gcc subdir, as noted in review)
  * I kept a separate .opt file for the new command line options
  * I dropped #include "gcc-plugin.h" from the source files, and
    add includes of the things that are needed (which was rather tedious
    as e.g. gimple.h doesn't include what it needs, requiring manually
    included its dependences everywhere, which seems like a violation
    of DRY - but that's an orthogonal issue)
  * I added #if ENABLE_ANALYZER guards around the bodies of the source
    files so that it ought to compile away with --disable-analyzer
    I haven't yet measured the effect on
  * I dropped the auto_client_timevar in favor of new TV_*.
  * Putting everything in the core middle-end exposed a breakage in
    the selftests for C++ due to the %E in some dumps, which is
    FE-specific.  I fixed this by rewriting the dumps to avoid %E.
See the ChangeLog of patch 2 for more details.

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.

Pushed to branch "dmalcolm/analyzer-v3-unsquashed" on the GCC git
mirror.

I may integrate Lewis's fix for multicolumn characters into trunk
first, then rebase my patch, since our changes to
diagnostic-show-locus.c have numerous conflicts, albeit ones that ought
to be mechanical (if tedious) to resolve.

Dave



David Malcolm (2):
  Fixup for rebase: c-format.c: get_pointer_to_named_type ->
    get_named_type
  Rework as a non-plugin

 gcc/Makefile.in                           |  32 +++-
 gcc/analyzer/Make-plugin.in               | 182 ----------------------
 gcc/analyzer/analysis-plan.cc             |   8 +-
 gcc/analyzer/analyzer-logging.cc          |   4 +
 gcc/analyzer/analyzer-pass.cc             |  41 +++--
 gcc/analyzer/analyzer-plugin.cc           |  63 --------
 gcc/analyzer/analyzer-selftests.cc        |   3 +-
 gcc/analyzer/analyzer-selftests.h         |   2 +-
 gcc/analyzer/analyzer.cc                  |   7 +-
 gcc/analyzer/analyzer.h                   |   2 +
 gcc/analyzer/{plugin.opt => analyzer.opt} |   0
 gcc/analyzer/call-string.cc               |   6 +-
 gcc/analyzer/checker-path.cc              |   8 +-
 gcc/analyzer/config-plugin.in             |  34 ----
 gcc/analyzer/constraint-manager.cc        |  10 +-
 gcc/analyzer/diagnostic-manager.cc        |   7 +-
 gcc/analyzer/digraph.cc                   |   5 +-
 gcc/analyzer/engine.cc                    |  26 ++--
 gcc/analyzer/exploded-graph.h             |   1 +
 gcc/analyzer/graphviz.cc                  |   5 +-
 gcc/analyzer/pending-diagnostic.cc        |   5 +-
 gcc/analyzer/program-point.cc             |   5 +-
 gcc/analyzer/program-state.cc             |   5 +-
 gcc/analyzer/region-model.cc              |  66 ++++----
 gcc/analyzer/region-model.h               |   1 +
 gcc/analyzer/shortest-paths.h             |   2 +-
 gcc/analyzer/sm-file.cc                   |   8 +-
 gcc/analyzer/sm-malloc.cc                 |   9 +-
 gcc/analyzer/sm-pattern-test.cc           |   7 +-
 gcc/analyzer/sm-sensitive.cc              |   9 +-
 gcc/analyzer/sm-signal.cc                 |   7 +-
 gcc/analyzer/sm-taint.cc                  |   8 +-
 gcc/analyzer/sm.cc                        |   8 +-
 gcc/analyzer/state-purge.cc               |  10 +-
 gcc/analyzer/supergraph.cc                |   9 +-
 gcc/analyzer/supergraph.h                 |   4 +
 gcc/analyzer/tristate.cc                  |   5 +-
 gcc/c-family/c-format.c                   |   2 +-
 gcc/common.opt                            |   3 +-
 gcc/configure                             |  25 ++-
 gcc/configure.ac                          |  14 +-
 gcc/gcc.c                                 |  12 --
 gcc/passes.def                            |   1 +
 gcc/selftest-run-tests.c                  |   4 +
 gcc/timevar.def                           |  11 ++
 gcc/tree-pass.h                           |   1 +
 46 files changed, 301 insertions(+), 386 deletions(-)
 delete mode 100644 gcc/analyzer/Make-plugin.in
 delete mode 100644 gcc/analyzer/analyzer-plugin.cc
 rename gcc/analyzer/{plugin.opt => analyzer.opt} (100%)
 delete mode 100644 gcc/analyzer/config-plugin.in

-- 
2.21.0

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 06/49] timevar.h: add auto_client_timevar class
  2019-12-07 14:29   ` Jeff Law
@ 2019-12-08 14:30     ` David Malcolm
  0 siblings, 0 replies; 160+ messages in thread
From: David Malcolm @ 2019-12-08 14:30 UTC (permalink / raw)
  To: law, gcc-patches

On Sat, 2019-12-07 at 07:29 -0700, Jeff Law wrote:
> On Fri, 2019-11-15 at 20:22 -0500, David Malcolm wrote:
> > This patch adds a class "auto_client_timevar", similar to
> > auto_timevar,
> > but for plugins to use on their own timing items that aren't in
> > timevar.def
> > 
> > gcc/ChangeLog:
> > 	* timevar.h (class auto_client_timevar): New class.
> So if we move away from a plug-in architecture to an integrated first
> class analyzer, do we still want this change?

I've dropped this change in the v3 of the kit, in favor of new TV_*
values; see:
  https://gcc.gnu.org/ml/gcc-patches/2019-12/msg00529.html

Dave

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 16/49] Add support for in-tree plugins
  2019-12-07 14:39   ` Jeff Law
@ 2019-12-08 14:32     ` David Malcolm
  0 siblings, 0 replies; 160+ messages in thread
From: David Malcolm @ 2019-12-08 14:32 UTC (permalink / raw)
  To: law, gcc-patches

On Sat, 2019-12-07 at 07:39 -0700, Jeff Law wrote:
> On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> > This patch adds support for "in-tree" plugins i.e. GCC plugins that
> > live
> > in the GCC source tree and are shipped as part of the GCC tarball.
> > 
> > The patch adds Makefile/configure machinery for handling in-tree
> > GCC
> > plugins, adapted from how we support frontends.
> > 
> > Each in-tree plugin should provide a Make-plugin.in and config-
> > plugin.in,
> > analogous to the Make-lang.in and config-lang.in provided by a
> > frontend;
> > they can also supply options via a plugin.opt, analogous to a
> > frontend's
> > lang.opt.
> > 
> > The patch adds a --enable-plugins=[LIST OF PLUGIN NAMES] configure
> > option,
> > analogous to --enable-languages.  The default is for no such
> > plugins
> > to be
> > enabled.
> > 
> > ChangeLog:
> > 	* configure.ac: Handle --enable-plugins.
> > 
> > gcc/ChangeLog:
> > 	* Makefile.in (CONFIG_PLUGINS): New.
> > 	(SUBDIRS, subdirs): Generalize to cover plugins as well as
> > 	languages.
> > 	(plugin_opt_files): New.
> > 	(ALL_OPT_FILES): Add plugin_opt_files.
> > 	(ALL_HOST_PLUGIN_OBJS): New.
> > 	(ALL_HOST_OBJS): Add ALL_HOST_PLUGIN_OBJS.
> > 	(plugin_hooks): New.
> > 	(PLUGIN_MAKEFRAGS): New; include them.
> > 	(Makefile): Add dependency on PLUGIN_MAKEFRAGS.
> > 	(all.cross): Add dependency on plugin.all.cross.
> > 	(start.encap): Add plugin.start.encap.
> > 	(rest.encap): Add plugin.rest.encap.
> > 	(SELFTEST_TARGETS): Add selftest_plugins.
> > 	(info): Add dependency on lang.info.
> > 	(dvi): Add dependency on plugin.dvi.
> > 	(pdf): Add dependency on plugin.pdf.
> > 	(HTMLS_BUILD): Add plugin.html.
> > 	(man): Add plugin.man.
> > 	(mostlyclean): Add plugin.mostlyclean.
> > 	(clean): Add plugin.clean.
> > 	(distclean): Update for renaming of Make-hooks to Make-lang-
> > hooks;
> > 	add Make-plugin-hooks.  Add plugin.distclean dependency.
> > 	(maintainer-clean): Add plugin.maintainer-clean.
> > 	(install-plugin): Add plugin.install-plugin.
> > 	(install-common): Add plugin.install-common.
> > 	(install-info): Add plugin.install-info.
> > 	(install-pdf): Add plugin.install-pdf.
> > 	(install-html): Add plugin.install-html.
> > 	(install-man): Add plugin.install-man.
> > 	(uninstall): Add plugin.uninstall.
> > 	(TAGS): Add plugin.tags.
> > 	* configure.ac (Make-hooks): Rename to Make-lang-hooks.
> > 	(plugin_opt_files): New.
> > 	(plugin_specs_files): New.
> > 	(plugin_tree_files): New.
> > 	(all_plugins): New.
> > 	(all_plugin_makefrags): New.
> > 	(all_selected_plugins): New.
> > 	(plugin_hooks): New.
> > 	("Plugin hooks"): New section.  Iterate through config-
> > plugin.in,
> > 	analogously to config-lang.in.
> > 	(check_plugins): New.
> > 	(selftest_plugins): New.
> > 	(Make-plugin-hooks): Emit.
> > 	* doc/install.texi (--enable-plugins): New option.
> So do we want to push on the concept of in-tree plugins for somethign
> like annobin?  If not, given the general desire to not use plugins
> for
> teh analyzer, would we still want this?
> 
> jeff

As this becomes orthogonal to the analyzer work, I've dropped this
patch from v3 of the kit, but someone else is welcome to pick this up
and run with it.

Dave

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 22/49] analyzer: params.def: new parameters
  2019-12-08  5:42     ` Eric Gallager
@ 2019-12-08 14:34       ` David Malcolm
  0 siblings, 0 replies; 160+ messages in thread
From: David Malcolm @ 2019-12-08 14:34 UTC (permalink / raw)
  To: Eric Gallager, law; +Cc: gcc-patches

On Sun, 2019-12-08 at 00:42 -0500, Eric Gallager wrote:
> On 12/7/19, Jeff Law <law@redhat.com> wrote:
> > On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> > > gcc/ChangeLog:
> > > 	* params.def (PARAM_ANALYZER_BB_EXPLOSION_FACTOR): New param.
> > > 	(PARAM_ANALYZER_MAX_ENODES_PER_PROGRAM_POINT): New param.
> > > 	(PARAM_ANALYZER_MAX_RECURSION_DEPTH): New param.
> > > 	(PARAM_ANALYZER_MIN_SNODES_FOR_CALL_SUMMARY): New param.
> > Doesn't this need a rethink/reimplementation after M. Liska's
> > changes?
> > 
> > jeff
> > 
> > 
> 
> I think he did that in a followup patch from a later patchset...

Yes:
  https://gcc.gnu.org/ml/gcc-patches/2019-11/msg02030.html


^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 24/49] analyzer: new file: analyzer-pass.cc
  2019-12-07 14:51   ` Jeff Law
@ 2019-12-08 14:38     ` David Malcolm
  0 siblings, 0 replies; 160+ messages in thread
From: David Malcolm @ 2019-12-08 14:38 UTC (permalink / raw)
  To: law, gcc-patches

On Sat, 2019-12-07 at 07:51 -0700, Jeff Law wrote:
> On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> > This patch adds the IPA pass boilerplate for the analyzer.
> > 
> > gcc/ChangeLog:
> > 	* analyzer/analyzer-pass.cc: New file.
> Nothing I see controversial here.  But obviously will need some
> adjustment if we're moving away from using the plugin facilities.
> 
> jeff

Indeed, in v3 it gains a "gate" vfunc, uses a timevar, and the "sorry"
for not-enabled-at-configure-time moves from the driver to this pass's
execute "vfunc":
  https://gcc.gnu.org/ml/gcc-patches/2019-12/msg00531.html

Dave

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 32/49] analyzer: new files: pending-diagnostic.{cc|h}
  2019-12-07 15:05   ` Jeff Law
@ 2019-12-08 14:43     ` David Malcolm
  2019-12-08 22:25       ` Jeff Law
  0 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-12-08 14:43 UTC (permalink / raw)
  To: law, gcc-patches

On Sat, 2019-12-07 at 08:05 -0700, Jeff Law wrote:
> On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> > This patch adds classes used by the analyzer for handling its
> > diagnostics
> > (queueing them, deduplicating them, precision-of-wording hooks).
> > 
> > gcc/ChangeLog:
> > 	* analyzer/pending-diagnostic.cc: New file.
> > 	* analyzer/pending-diagnostic.h: New file.
> Definitely want to be using this elsewhere.  So again, perhaps move
> out
> of the analyzer into a generic location?
> 
> Jeff

This one I'd prefer to keep in the analyzer subdir, as it actually uses
some analyzer-specific types, for the precision-of-wording vfuncs; this
is the interface within the analyzer between the analyzer's path/event
building logic [1] and the specific descriptions implemented in the
various sm-* files.

Dave

[1] which is specific to the analyzer, although it hands off types that
can be consumed by the generic diagnostic subsystem.

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 32/49] analyzer: new files: pending-diagnostic.{cc|h}
  2019-12-08 14:43     ` David Malcolm
@ 2019-12-08 22:25       ` Jeff Law
  0 siblings, 0 replies; 160+ messages in thread
From: Jeff Law @ 2019-12-08 22:25 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On Sun, 2019-12-08 at 09:43 -0500, David Malcolm wrote:
> On Sat, 2019-12-07 at 08:05 -0700, Jeff Law wrote:
> > On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> > > This patch adds classes used by the analyzer for handling its
> > > diagnostics
> > > (queueing them, deduplicating them, precision-of-wording hooks).
> > > 
> > > gcc/ChangeLog:
> > > 	* analyzer/pending-diagnostic.cc: New file.
> > > 	* analyzer/pending-diagnostic.h: New file.
> > Definitely want to be using this elsewhere.  So again, perhaps move
> > out
> > of the analyzer into a generic location?
> > 
> > Jeff
> 
> This one I'd prefer to keep in the analyzer subdir, as it actually
> uses
> some analyzer-specific types, for the precision-of-wording vfuncs;
> this
> is the interface within the analyzer between the analyzer's
> path/event
> building logic [1] and the specific descriptions implemented in the
> various sm-* files.
There may be parts we want to generalize, particularly with the
introduction of __bulitin_warning.  But if you want to defer making
this infrastructure a bit more generic now, I won't strenously object.

jeff
> 

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 01/49] analyzer: user-facing documentation
  2019-11-16  1:17 ` [PATCH 01/49] analyzer: user-facing documentation David Malcolm
  2019-12-06 19:57   ` Eric Gallager
@ 2019-12-09  5:36   ` Sandra Loosemore
  1 sibling, 0 replies; 160+ messages in thread
From: Sandra Loosemore @ 2019-12-09  5:36 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On 11/15/19 6:22 PM, David Malcolm wrote:
> gcc/ChangeLog:
> 	* doc/invoke.texi ("Static Analyzer Options"): New list and new section.
> 	("Warning Options"): Add static analysis warnings to the list.
> 	(-Wno-analyzer-double-fclose): New option.
> 	(-Wno-analyzer-double-free): New option.
> 	(-Wno-analyzer-exposure-through-output-file): New option.
> 	(-Wno-analyzer-file-leak): New option.
> 	(-Wno-analyzer-free-of-non-heap): New option.
> 	(-Wno-analyzer-malloc-leak): New option.
> 	(-Wno-analyzer-possible-null-argument): New option.
> 	(-Wno-analyzer-possible-null-dereference): New option.
> 	(-Wno-analyzer-null-argument): New option.
> 	(-Wno-analyzer-null-dereference): New option.
> 	(-Wno-analyzer-stale-setjmp-buffer): New option.
> 	(-Wno-analyzer-tainted-array-index): New option.
> 	(-Wno-analyzer-use-after-free): New option.
> 	(-Wno-analyzer-use-of-pointer-in-stale-stack-frame): New option.
> 	(-Wno-analyzer-use-of-uninitialized-value): New option.
> 	(-Wanalyzer-too-complex): New option.
> 	(-fanalyzer-call-summaries): New warning.
> 	(-fanalyzer-checker=): New warning.
> 	(-fanalyzer-fine-grained): New warning.
> 	(-fno-analyzer-state-merge): New warning.
> 	(-fno-analyzer-state-purge): New warning.
> 	(-fanalyzer-transitivity): New warning.
> 	(-fanalyzer-verbose-edges): New warning.
> 	(-fanalyzer-verbose-state-changes): New warning.
> 	(-fanalyzer-verbosity=): New warning.
> 	(-fdump-analyzer): New warning.
> 	(-fdump-analyzer-callgraph): New warning.
> 	(-fdump-analyzer-exploded-graph): New warning.
> 	(-fdump-analyzer-exploded-nodes): New warning.
> 	(-fdump-analyzer-exploded-nodes-2): New warning.
> 	(-fdump-analyzer-exploded-nodes-3): New warning.
> 	(-fdump-analyzer-supergraph): New warning.

FAOD, I looked this over when the patch set first went by, but since it 
was marked as "RFC" I didn't do a detailed review.  On first glance I 
thought the content is generally fine, but the organization could use 
some work.  E.g. the -fdump-analyzer* options and things that mess with 
internal parameters probably belong in the GCC Developer Options 
section, and I'd like to introduce some substructure into the Warning 
Options section instead of adding a new section at the same level.  I 
can take another look when the patches are closer to being ready to commit.

-Sandra

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 00/49] RFC: Add a static analysis framework to GCC
  2019-12-06 22:31   ` Jeff Law
@ 2019-12-09  8:10     ` Richard Biener
  2019-12-11 20:11       ` David Malcolm
  0 siblings, 1 reply; 160+ messages in thread
From: Richard Biener @ 2019-12-09  8:10 UTC (permalink / raw)
  To: Jeff Law; +Cc: Martin Sebor, David Malcolm, GCC Patches

On Fri, Dec 6, 2019 at 11:31 PM Jeff Law <law@redhat.com> wrote:
>
> On Wed, 2019-12-04 at 12:55 -0700, Martin Sebor wrote:
> > On 11/15/19 6:22 PM, David Malcolm wrote:
> > > This patch kit introduces a static analysis pass for GCC that can
> > > diagnose
> > > various kinds of problems in C code at compile-time (e.g. double-
> > > free,
> > > use-after-free, etc).
> >
> > I haven't looked at the analyzer bits in any detail yet so I have
> > just some very high-level questions.  But first let me say I'm
> > excited to see this project! :)
> >
> > It looks like the analyzer detects some of the same problems as
> > some existing middle-end warnings (e.g., -Wnonnull, -Wuninitialized),
> > and some that I have been working toward implementing (invalid uses
> > of freed pointers such as returning them from functions or passing
> > them to others), and others still that I have been thinking about
> > as possible future projects (e.g., detecting uses of uninitialized
> > arrays in string functions).
> >
> > What are your thoughts about this sort of overlap?  Do you expect
> > us to enhance both sets of warnings in parallel, or do you see us
> > moving away from issuing warnings in the middle-end and toward
> > making the analyzer the main source of these kinds of diagnostics?
> > Maybe even replace some of the problematic middle-end warnings
> > with the analyzer?  What (if anything) should we do about warnings
> > issued for the same problems by both the middle-end and the analyzer?
> > Or about false negatives?  E.g., a bug detected by the middle-end
> > but not the analyzer or vice versa.
> >
> > What do you see as the biggest pros and cons of either approach?
> > (Middle-end vs analyzer.)  What limitations is the analyzer
> > approach inherently subject to that the middle-end warnings aren't,
> > and vice versa?
> >
> > How do we prioritize between the two approaches (e.g., choose
> > where to add a new warning)?
> Given the cost of David's analyzer, I would tend to prioritize the more
> localized analysis.  Also note that because of the compile-time
> complexities we end up pruning paths from the search space and lose
> precision when we have to merge nodes.   These issues are inherent in
> the depth of analysis we're looking to do.
>
> So the way to think about things is David's work is a slower, deeper
> analysis than what we usually do.  So things that are reasonable
> candidates for -Wall would need to use the traditional mechansisms.
> Things that require deeper analysis would be done in David's framework.
>
> Also note that part of David's work is to bring a fairly generic engine
> that we can expand with different domain specific analyzers.  It just
> happens to be the case that the first place he's focused is on double-
> free and use-after-free.  But (IMHO) the gem is really the generic
> engine.

So if the "generic engine" lives inside GCC can the actual analyzers
be plugins on a (stable) "analyzer plugin API"?

Does the analyzer work with LTO at whole-program scope btw?

Richard.

> jeff
>

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 09/49] gimple const-correctness fixes
  2019-12-07 14:28   ` Jeff Law
@ 2019-12-09  8:18     ` Richard Biener
  0 siblings, 0 replies; 160+ messages in thread
From: Richard Biener @ 2019-12-09  8:18 UTC (permalink / raw)
  To: Jeff Law; +Cc: David Malcolm, GCC Patches

On Sat, Dec 7, 2019 at 3:28 PM Jeff Law <law@redhat.com> wrote:
>
> On Fri, 2019-11-15 at 20:22 -0500, David Malcolm wrote:
> > This patch converts various "gimple *" to "const gimple *" and
> > similar
> > fixes for gimple subclasses, adding is_a_helper for gimple subclasses
> > to support the const form of as_a, and adding a few "const" overloads
> > of accessors.
> >
> > This is enough to make pp_gimple_stmt_1's stmt const.
> >
> > gcc/ChangeLog:
> >       * gimple-predict.h (gimple_predict_predictor): Make "gs" param
> >       const.
> >       (gimple_predict_outcome): Likewise.
> >       * gimple-pretty-print.c (do_niy): Likewise.
> >       (dump_unary_rhs): Likewise.
> >       (dump_binary_rhs): Likewise.
> >       (dump_ternary_rhs): Likewise.
> >       (dump_gimple_assign): Likewise.
> >       (dump_gimple_return): Likewise.
> >       (dump_gimple_call_args): Likewise.
> >       (pp_points_to_solution): Make "pt" param const.
> >       (dump_gimple_call): Make "gs" param const.
> >       (dump_gimple_switch): Likewise.
> >       (dump_gimple_cond): Likewise.
> >       (dump_gimple_label): Likewise.
> >       (dump_gimple_goto): Likewise.
> >       (dump_gimple_bind): Likewise.
> >       (dump_gimple_try): Likewise.
> >       (dump_gimple_catch): Likewise.
> >       (dump_gimple_eh_filter): Likewise.
> >       (dump_gimple_eh_must_not_throw): Likewise.
> >       (dump_gimple_eh_else): Likewise.
> >       (dump_gimple_resx): Likewise.
> >       (dump_gimple_eh_dispatch): Likewise.
> >       (dump_gimple_debug): Likewise.
> >       (dump_gimple_omp_for): Likewise.
> >       (dump_gimple_omp_continue): Likewise.
> >       (dump_gimple_omp_single): Likewise.
> >       (dump_gimple_omp_taskgroup): Likewise.
> >       (dump_gimple_omp_target): Likewise.
> >       (dump_gimple_omp_teams): Likewise.
> >       (dump_gimple_omp_sections): Likewise.
> >       (dump_gimple_omp_block): Likewise.
> >       (dump_gimple_omp_critical): Likewise.
> >       (dump_gimple_omp_ordered): Likewise.
> >       (dump_gimple_omp_scan): Likewise.
> >       (dump_gimple_omp_return): Likewise.
> >       (dump_gimple_transaction): Likewise.
> >       (dump_gimple_asm): Likewise.
> >       (dump_gimple_phi): Make "phi" param const.
> >       (dump_gimple_omp_parallel): Make "gs" param const.
> >       (dump_gimple_omp_task): Likewise.
> >       (dump_gimple_omp_atomic_load): Likewise.
> >       (dump_gimple_omp_atomic_store): Likewise.
> >       (dump_gimple_mem_ops): Likewise.
> >       (pp_gimple_stmt_1): Likewise.  Add "const" to the various as_a
> > <>
> >       casts throughout.
> >       * gimple-pretty-print.h (gimple_stmt_1): Make gimple * param
> > const.
> >       * gimple.h (is_a_helper <const gdebug *>::test): New.
> >       (is_a_helper <const ggoto *>::test): New.
> >       (is_a_helper <const glabel *>::test): New.
> >       (is_a_helper <const geh_else *>::test): New.
> >       (is_a_helper <const geh_mnt *>::test): New.
> >       (is_a_helper <const gswitch *>::test): New.
> >       (is_a_helper <const gtry *>::test): New.
> >       (is_a_helper <const greturn *>::test): New.
> >       (gimple_call_tail_p): Make param const.
> >       (gimple_call_return_slot_opt_p): Likewise.
> >       (gimple_call_va_arg_pack_p): Likewise.
> >       (gimple_call_use_set): Add const overload.
> >       (gimple_call_clobber_set): Likewise.
> >       (gimple_has_lhs): Make param const.
> >       (gimple_bind_body): Likewise.
> >       (gimple_catch_handler): Likewise.
> >       (gimple_eh_filter_failure): Likewise.
> >       (gimple_eh_must_not_throw_fndecl): Likewise.
> >       (gimple_eh_else_n_body): Likewise.
> >       (gimple_eh_else_e_body): Likewise.
> >       (gimple_try_eval): Likewise.
> >       (gimple_try_cleanup): Likewise.
> >       (gimple_phi_arg): Add const overload.
> >       (gimple_phi_arg_def): Make param const.
> >       (gimple_phi_arg_edge): Likewise.
> >       (gimple_phi_arg_location): Likewise.
> >       (gimple_phi_arg_has_location): Likewise.
> >       (gimple_debug_bind_get_var): Likewise.
> >       (gimple_debug_bind_get_value): Likewise.
> >       (gimple_debug_source_bind_get_var): Likewise.
> >       (gimple_debug_source_bind_get_value): Likewise.
> >       (gimple_omp_body): Likewise.
> >       (gimple_omp_for_collapse): Likewise.
> >       (gimple_omp_for_pre_body): Likewise.
> >       (gimple_transaction_body): Likewise.
> >       * tree-eh.c (lookup_stmt_eh_lp_fn): Make param "t" const.
> >       (lookup_stmt_eh_lp): Likewise.
> >       * tree-eh.h (lookup_stmt_eh_lp_fn): Make param const.
> >       (lookup_stmt_eh_lp): Likewise.
> >       * tree-ssa-alias.h (pt_solution_empty_p): Make param const.
> >       * tree-ssa-structalias.c (pt_solution_empty_p): Likewise.
> OK.  And more generally, adding "const" to generally improve our const-
> correctness shouldn't require a review cycle.  Just to the normal
> testing and install 'em.
>
> Similarly for dropping unnecessary "struct", "class" or "union" like we
> see in the tree-ssa-structalias.c changes. I'm terrible about adding
> extraneous struct/class keywords when they're not needed.  Anyone
> cleaning that up doesn't need explicit approvals either.

Seconded.

Richard.

> Jeff
> >
>

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 09/49] gimple const-correctness fixes
  2019-12-06 17:47     ` David Malcolm
@ 2019-12-09  8:27       ` Richard Biener
  0 siblings, 0 replies; 160+ messages in thread
From: Richard Biener @ 2019-12-09  8:27 UTC (permalink / raw)
  To: David Malcolm; +Cc: GCC Patches

On Fri, Dec 6, 2019 at 6:47 PM David Malcolm <dmalcolm@redhat.com> wrote:
>
> On Fri, 2019-12-06 at 11:52 +0100, Richard Biener wrote:
> > On Sat, Nov 16, 2019 at 2:20 AM David Malcolm <dmalcolm@redhat.com>
> > wrote:
> > > This patch converts various "gimple *" to "const gimple *" and
> > > similar
> > > fixes for gimple subclasses, adding is_a_helper for gimple
> > > subclasses
> > > to support the const form of as_a, and adding a few "const"
> > > overloads
> > > of accessors.
> > >
> > > This is enough to make pp_gimple_stmt_1's stmt const.
> >
> > Hum.  Can't the const is-a variants be somehow magically implemented
> > generally?  If something is a T then it is also a const T, no?  I
> > guess
> > if something is a const T it isn't a T though?
> >
> > Richard.
>
> It is something of a wart to need new is_a_helper<>::test functions for
> the const variants.
>
> I tried poking at is-a.h to do this in a more generic way, but I'm not
> sure it's doable without an invasive change:  is_a_helper's T is
> already a pointer, so AIUI, if we apply "const" to it, we're making the
> pointer const, rather than the thing being pointed to.
>
> Maybe someone else can see a way?

I guess is_a_helper should always be specialized for CV-unqualified
_non_-pointer types and is_a can then transparently provide
indirect and qualified variants?

We can also do some pointer strippers like

template <typename T>
struct strip_pointer { typedef T type; typedef const T const_type; };
template <typename T>
struct strip_pointer<T *> { typedef T type; typedef const T * const_type; };

and use

template <typename T>
struct is_a_helper<strip_pointer<T>::const_type> : is_a_helper<T> {};

?  Just my quick idea as a non-C++ literate.

> In the meantime, is this patch OK?  (I use "const gimple *" etc
> throughout the analyzer, to emphasize that I'm not changing them)
>
> Thanks
> Dave
>
>
> > > gcc/ChangeLog:
> > >         * gimple-predict.h (gimple_predict_predictor): Make "gs"
> > > param
> > >         const.
> > >         (gimple_predict_outcome): Likewise.
> > >         * gimple-pretty-print.c (do_niy): Likewise.
> > >         (dump_unary_rhs): Likewise.
> > >         (dump_binary_rhs): Likewise.
> > >         (dump_ternary_rhs): Likewise.
> > >         (dump_gimple_assign): Likewise.
> > >         (dump_gimple_return): Likewise.
> > >         (dump_gimple_call_args): Likewise.
> > >         (pp_points_to_solution): Make "pt" param const.
> > >         (dump_gimple_call): Make "gs" param const.
> > >         (dump_gimple_switch): Likewise.
> > >         (dump_gimple_cond): Likewise.
> > >         (dump_gimple_label): Likewise.
> > >         (dump_gimple_goto): Likewise.
> > >         (dump_gimple_bind): Likewise.
> > >         (dump_gimple_try): Likewise.
> > >         (dump_gimple_catch): Likewise.
> > >         (dump_gimple_eh_filter): Likewise.
> > >         (dump_gimple_eh_must_not_throw): Likewise.
> > >         (dump_gimple_eh_else): Likewise.
> > >         (dump_gimple_resx): Likewise.
> > >         (dump_gimple_eh_dispatch): Likewise.
> > >         (dump_gimple_debug): Likewise.
> > >         (dump_gimple_omp_for): Likewise.
> > >         (dump_gimple_omp_continue): Likewise.
> > >         (dump_gimple_omp_single): Likewise.
> > >         (dump_gimple_omp_taskgroup): Likewise.
> > >         (dump_gimple_omp_target): Likewise.
> > >         (dump_gimple_omp_teams): Likewise.
> > >         (dump_gimple_omp_sections): Likewise.
> > >         (dump_gimple_omp_block): Likewise.
> > >         (dump_gimple_omp_critical): Likewise.
> > >         (dump_gimple_omp_ordered): Likewise.
> > >         (dump_gimple_omp_scan): Likewise.
> > >         (dump_gimple_omp_return): Likewise.
> > >         (dump_gimple_transaction): Likewise.
> > >         (dump_gimple_asm): Likewise.
> > >         (dump_gimple_phi): Make "phi" param const.
> > >         (dump_gimple_omp_parallel): Make "gs" param const.
> > >         (dump_gimple_omp_task): Likewise.
> > >         (dump_gimple_omp_atomic_load): Likewise.
> > >         (dump_gimple_omp_atomic_store): Likewise.
> > >         (dump_gimple_mem_ops): Likewise.
> > >         (pp_gimple_stmt_1): Likewise.  Add "const" to the various
> > > as_a <>
> > >         casts throughout.
> > >         * gimple-pretty-print.h (gimple_stmt_1): Make gimple *
> > > param const.
> > >         * gimple.h (is_a_helper <const gdebug *>::test): New.
> > >         (is_a_helper <const ggoto *>::test): New.
> > >         (is_a_helper <const glabel *>::test): New.
> > >         (is_a_helper <const geh_else *>::test): New.
> > >         (is_a_helper <const geh_mnt *>::test): New.
> > >         (is_a_helper <const gswitch *>::test): New.
> > >         (is_a_helper <const gtry *>::test): New.
> > >         (is_a_helper <const greturn *>::test): New.
> > >         (gimple_call_tail_p): Make param const.
> > >         (gimple_call_return_slot_opt_p): Likewise.
> > >         (gimple_call_va_arg_pack_p): Likewise.
> > >         (gimple_call_use_set): Add const overload.
> > >         (gimple_call_clobber_set): Likewise.
> > >         (gimple_has_lhs): Make param const.
> > >         (gimple_bind_body): Likewise.
> > >         (gimple_catch_handler): Likewise.
> > >         (gimple_eh_filter_failure): Likewise.
> > >         (gimple_eh_must_not_throw_fndecl): Likewise.
> > >         (gimple_eh_else_n_body): Likewise.
> > >         (gimple_eh_else_e_body): Likewise.
> > >         (gimple_try_eval): Likewise.
> > >         (gimple_try_cleanup): Likewise.
> > >         (gimple_phi_arg): Add const overload.
> > >         (gimple_phi_arg_def): Make param const.
> > >         (gimple_phi_arg_edge): Likewise.
> > >         (gimple_phi_arg_location): Likewise.
> > >         (gimple_phi_arg_has_location): Likewise.
> > >         (gimple_debug_bind_get_var): Likewise.
> > >         (gimple_debug_bind_get_value): Likewise.
> > >         (gimple_debug_source_bind_get_var): Likewise.
> > >         (gimple_debug_source_bind_get_value): Likewise.
> > >         (gimple_omp_body): Likewise.
> > >         (gimple_omp_for_collapse): Likewise.
> > >         (gimple_omp_for_pre_body): Likewise.
> > >         (gimple_transaction_body): Likewise.
> > >         * tree-eh.c (lookup_stmt_eh_lp_fn): Make param "t" const.
> > >         (lookup_stmt_eh_lp): Likewise.
> > >         * tree-eh.h (lookup_stmt_eh_lp_fn): Make param const.
> > >         (lookup_stmt_eh_lp): Likewise.
> > >         * tree-ssa-alias.h (pt_solution_empty_p): Make param const.
> > >         * tree-ssa-structalias.c (pt_solution_empty_p): Likewise.
> > > ---
> > >  gcc/gimple-predict.h       |   4 +-
> > >  gcc/gimple-pretty-print.c  | 159 +++++++++++++++++++++++--------
> > > --------------
> > >  gcc/gimple-pretty-print.h  |   3 +-
> > >  gcc/gimple.h               | 156
> > > ++++++++++++++++++++++++++++++++++----------
> > >  gcc/tree-eh.c              |   6 +-
> > >  gcc/tree-eh.h              |   4 +-
> > >  gcc/tree-ssa-alias.h       |   2 +-
> > >  gcc/tree-ssa-structalias.c |   2 +-
> > >  8 files changed, 213 insertions(+), 123 deletions(-)
> > >
> > > diff --git a/gcc/gimple-predict.h b/gcc/gimple-predict.h
> > > index 761098b..d976317 100644
> > > --- a/gcc/gimple-predict.h
> > > +++ b/gcc/gimple-predict.h
> > > @@ -26,7 +26,7 @@ along with GCC; see the file COPYING3.  If not
> > > see
> > >  /* Return the predictor of GIMPLE_PREDICT statement GS.  */
> > >
> > >  static inline enum br_predictor
> > > -gimple_predict_predictor (gimple *gs)
> > > +gimple_predict_predictor (const gimple *gs)
> > >  {
> > >    GIMPLE_CHECK (gs, GIMPLE_PREDICT);
> > >    return (enum br_predictor) (gs->subcode & ~GF_PREDICT_TAKEN);
> > > @@ -47,7 +47,7 @@ gimple_predict_set_predictor (gimple *gs, enum
> > > br_predictor predictor)
> > >  /* Return the outcome of GIMPLE_PREDICT statement GS.  */
> > >
> > >  static inline enum prediction
> > > -gimple_predict_outcome (gimple *gs)
> > > +gimple_predict_outcome (const gimple *gs)
> > >  {
> > >    GIMPLE_CHECK (gs, GIMPLE_PREDICT);
> > >    return (gs->subcode & GF_PREDICT_TAKEN) ? TAKEN : NOT_TAKEN;
> > > diff --git a/gcc/gimple-pretty-print.c b/gcc/gimple-pretty-print.c
> > > index 2d5ece0..f044628 100644
> > > --- a/gcc/gimple-pretty-print.c
> > > +++ b/gcc/gimple-pretty-print.c
> > > @@ -59,7 +59,7 @@ along with GCC; see the file COPYING3.  If not
> > > see
> > >     gimple statement GS.  */
> > >
> > >  static void
> > > -do_niy (pretty_printer *buffer, gimple *gs)
> > > +do_niy (pretty_printer *buffer, const gimple *gs)
> > >  {
> > >    pp_printf (buffer, "<<< Unknown GIMPLE statement: %s >>>\n",
> > >              gimple_code_name[(int) gimple_code (gs)]);
> > > @@ -327,7 +327,7 @@ dump_gimple_fmt (pretty_printer *buffer, int
> > > spc, dump_flags_t flags,
> > >     assignment GS.  BUFFER, SPC and FLAGS are as in
> > > pp_gimple_stmt_1.  */
> > >
> > >  static void
> > > -dump_unary_rhs (pretty_printer *buffer, gassign *gs, int spc,
> > > +dump_unary_rhs (pretty_printer *buffer, const gassign *gs, int
> > > spc,
> > >                 dump_flags_t flags)
> > >  {
> > >    enum tree_code rhs_code = gimple_assign_rhs_code (gs);
> > > @@ -423,7 +423,7 @@ dump_unary_rhs (pretty_printer *buffer, gassign
> > > *gs, int spc,
> > >     assignment GS.  BUFFER, SPC and FLAGS are as in
> > > pp_gimple_stmt_1.  */
> > >
> > >  static void
> > > -dump_binary_rhs (pretty_printer *buffer, gassign *gs, int spc,
> > > +dump_binary_rhs (pretty_printer *buffer, const gassign *gs, int
> > > spc,
> > >                  dump_flags_t flags)
> > >  {
> > >    const char *p;
> > > @@ -495,7 +495,7 @@ dump_binary_rhs (pretty_printer *buffer,
> > > gassign *gs, int spc,
> > >     assignment GS.  BUFFER, SPC and FLAGS are as in
> > > pp_gimple_stmt_1.  */
> > >
> > >  static void
> > > -dump_ternary_rhs (pretty_printer *buffer, gassign *gs, int spc,
> > > +dump_ternary_rhs (pretty_printer *buffer, const gassign *gs, int
> > > spc,
> > >                   dump_flags_t flags)
> > >  {
> > >    const char *p;
> > > @@ -625,7 +625,7 @@ dump_ternary_rhs (pretty_printer *buffer,
> > > gassign *gs, int spc,
> > >     pp_gimple_stmt_1.  */
> > >
> > >  static void
> > > -dump_gimple_assign (pretty_printer *buffer, gassign *gs, int spc,
> > > +dump_gimple_assign (pretty_printer *buffer, const gassign *gs, int
> > > spc,
> > >                     dump_flags_t flags)
> > >  {
> > >    if (flags & TDF_RAW)
> > > @@ -687,7 +687,7 @@ dump_gimple_assign (pretty_printer *buffer,
> > > gassign *gs, int spc,
> > >     pp_gimple_stmt_1.  */
> > >
> > >  static void
> > > -dump_gimple_return (pretty_printer *buffer, greturn *gs, int spc,
> > > +dump_gimple_return (pretty_printer *buffer, const greturn *gs, int
> > > spc,
> > >                     dump_flags_t flags)
> > >  {
> > >    tree t;
> > > @@ -712,7 +712,8 @@ dump_gimple_return (pretty_printer *buffer,
> > > greturn *gs, int spc,
> > >     dump_gimple_call.  */
> > >
> > >  static void
> > > -dump_gimple_call_args (pretty_printer *buffer, gcall *gs,
> > > dump_flags_t flags)
> > > +dump_gimple_call_args (pretty_printer *buffer, const gcall *gs,
> > > +                      dump_flags_t flags)
> > >  {
> > >    size_t i = 0;
> > >
> > > @@ -795,7 +796,7 @@ dump_gimple_call_args (pretty_printer *buffer,
> > > gcall *gs, dump_flags_t flags)
> > >  /* Dump the points-to solution *PT to BUFFER.  */
> > >
> > >  static void
> > > -pp_points_to_solution (pretty_printer *buffer, struct pt_solution
> > > *pt)
> > > +pp_points_to_solution (pretty_printer *buffer, const pt_solution
> > > *pt)
> > >  {
> > >    if (pt->anything)
> > >      {
> > > @@ -868,7 +869,7 @@ pp_points_to_solution (pretty_printer *buffer,
> > > struct pt_solution *pt)
> > >     pp_gimple_stmt_1.  */
> > >
> > >  static void
> > > -dump_gimple_call (pretty_printer *buffer, gcall *gs, int spc,
> > > +dump_gimple_call (pretty_printer *buffer, const gcall *gs, int
> > > spc,
> > >                   dump_flags_t flags)
> > >  {
> > >    tree lhs = gimple_call_lhs (gs);
> > > @@ -876,7 +877,7 @@ dump_gimple_call (pretty_printer *buffer, gcall
> > > *gs, int spc,
> > >
> > >    if (flags & TDF_ALIAS)
> > >      {
> > > -      struct pt_solution *pt;
> > > +      const pt_solution *pt;
> > >        pt = gimple_call_use_set (gs);
> > >        if (!pt_solution_empty_p (pt))
> > >         {
> > > @@ -1006,7 +1007,7 @@ dump_gimple_call (pretty_printer *buffer,
> > > gcall *gs, int spc,
> > >     pp_gimple_stmt_1.  */
> > >
> > >  static void
> > > -dump_gimple_switch (pretty_printer *buffer, gswitch *gs, int spc,
> > > +dump_gimple_switch (pretty_printer *buffer, const gswitch *gs, int
> > > spc,
> > >                     dump_flags_t flags)
> > >  {
> > >    unsigned int i;
> > > @@ -1064,7 +1065,7 @@ dump_gimple_switch (pretty_printer *buffer,
> > > gswitch *gs, int spc,
> > >     pp_gimple_stmt_1.  */
> > >
> > >  static void
> > > -dump_gimple_cond (pretty_printer *buffer, gcond *gs, int spc,
> > > +dump_gimple_cond (pretty_printer *buffer, const gcond *gs, int
> > > spc,
> > >                   dump_flags_t flags)
> > >  {
> > >    if (flags & TDF_RAW)
> > > @@ -1131,7 +1132,7 @@ dump_gimple_cond (pretty_printer *buffer,
> > > gcond *gs, int spc,
> > >     TDF_* in dumpfils.h).  */
> > >
> > >  static void
> > > -dump_gimple_label (pretty_printer *buffer, glabel *gs, int spc,
> > > +dump_gimple_label (pretty_printer *buffer, const glabel *gs, int
> > > spc,
> > >                    dump_flags_t flags)
> > >  {
> > >    tree label = gimple_label_label (gs);
> > > @@ -1155,7 +1156,7 @@ dump_gimple_label (pretty_printer *buffer,
> > > glabel *gs, int spc,
> > >     TDF_* in dumpfile.h).  */
> > >
> > >  static void
> > > -dump_gimple_goto (pretty_printer *buffer, ggoto *gs, int spc,
> > > +dump_gimple_goto (pretty_printer *buffer, const ggoto *gs, int
> > > spc,
> > >                   dump_flags_t flags)
> > >  {
> > >    tree label = gimple_goto_dest (gs);
> > > @@ -1171,7 +1172,7 @@ dump_gimple_goto (pretty_printer *buffer,
> > > ggoto *gs, int spc,
> > >     TDF_* in dumpfile.h).  */
> > >
> > >  static void
> > > -dump_gimple_bind (pretty_printer *buffer, gbind *gs, int spc,
> > > +dump_gimple_bind (pretty_printer *buffer, const gbind *gs, int
> > > spc,
> > >                   dump_flags_t flags)
> > >  {
> > >    if (flags & TDF_RAW)
> > > @@ -1205,7 +1206,7 @@ dump_gimple_bind (pretty_printer *buffer,
> > > gbind *gs, int spc,
> > >     dumpfile.h).  */
> > >
> > >  static void
> > > -dump_gimple_try (pretty_printer *buffer, gtry *gs, int spc,
> > > +dump_gimple_try (pretty_printer *buffer, const gtry *gs, int spc,
> > >                  dump_flags_t flags)
> > >  {
> > >    if (flags & TDF_RAW)
> > > @@ -1280,7 +1281,7 @@ dump_gimple_try (pretty_printer *buffer, gtry
> > > *gs, int spc,
> > >     dumpfile.h).  */
> > >
> > >  static void
> > > -dump_gimple_catch (pretty_printer *buffer, gcatch *gs, int spc,
> > > +dump_gimple_catch (pretty_printer *buffer, const gcatch *gs, int
> > > spc,
> > >                    dump_flags_t flags)
> > >  {
> > >    if (flags & TDF_RAW)
> > > @@ -1297,7 +1298,7 @@ dump_gimple_catch (pretty_printer *buffer,
> > > gcatch *gs, int spc,
> > >     dumpfile.h).  */
> > >
> > >  static void
> > > -dump_gimple_eh_filter (pretty_printer *buffer, geh_filter *gs, int
> > > spc,
> > > +dump_gimple_eh_filter (pretty_printer *buffer, const geh_filter
> > > *gs, int spc,
> > >                        dump_flags_t flags)
> > >  {
> > >    if (flags & TDF_RAW)
> > > @@ -1315,7 +1316,7 @@ dump_gimple_eh_filter (pretty_printer
> > > *buffer, geh_filter *gs, int spc,
> > >
> > >  static void
> > >  dump_gimple_eh_must_not_throw (pretty_printer *buffer,
> > > -                              geh_mnt *gs, int spc, dump_flags_t
> > > flags)
> > > +                              const geh_mnt *gs, int spc,
> > > dump_flags_t flags)
> > >  {
> > >    if (flags & TDF_RAW)
> > >      dump_gimple_fmt (buffer, spc, flags, "%G <%T>", gs,
> > > @@ -1331,7 +1332,7 @@ dump_gimple_eh_must_not_throw (pretty_printer
> > > *buffer,
> > >     dumpfile.h).  */
> > >
> > >  static void
> > > -dump_gimple_eh_else (pretty_printer *buffer, geh_else *gs, int
> > > spc,
> > > +dump_gimple_eh_else (pretty_printer *buffer, const geh_else *gs,
> > > int spc,
> > >                      dump_flags_t flags)
> > >  {
> > >    if (flags & TDF_RAW)
> > > @@ -1350,7 +1351,7 @@ dump_gimple_eh_else (pretty_printer *buffer,
> > > geh_else *gs, int spc,
> > >     dumpfile.h).  */
> > >
> > >  static void
> > > -dump_gimple_resx (pretty_printer *buffer, gresx *gs, int spc,
> > > +dump_gimple_resx (pretty_printer *buffer, const gresx *gs, int
> > > spc,
> > >                   dump_flags_t flags)
> > >  {
> > >    if (flags & TDF_RAW)
> > > @@ -1363,8 +1364,8 @@ dump_gimple_resx (pretty_printer *buffer,
> > > gresx *gs, int spc,
> > >  /* Dump a GIMPLE_EH_DISPATCH tuple on the pretty_printer
> > > BUFFER.  */
> > >
> > >  static void
> > > -dump_gimple_eh_dispatch (pretty_printer *buffer, geh_dispatch *gs,
> > > int spc,
> > > -                        dump_flags_t flags)
> > > +dump_gimple_eh_dispatch (pretty_printer *buffer, const
> > > geh_dispatch *gs,
> > > +                        int spc, dump_flags_t flags)
> > >  {
> > >    if (flags & TDF_RAW)
> > >      dump_gimple_fmt (buffer, spc, flags, "%G <%d>", gs,
> > > @@ -1379,7 +1380,7 @@ dump_gimple_eh_dispatch (pretty_printer
> > > *buffer, geh_dispatch *gs, int spc,
> > >     in dumpfile.h).  */
> > >
> > >  static void
> > > -dump_gimple_debug (pretty_printer *buffer, gdebug *gs, int spc,
> > > +dump_gimple_debug (pretty_printer *buffer, const gdebug *gs, int
> > > spc,
> > >                    dump_flags_t flags)
> > >  {
> > >    switch (gs->subcode)
> > > @@ -1433,7 +1434,7 @@ dump_gimple_debug (pretty_printer *buffer,
> > > gdebug *gs, int spc,
> > >
> > >  /* Dump a GIMPLE_OMP_FOR tuple on the pretty_printer BUFFER.  */
> > >  static void
> > > -dump_gimple_omp_for (pretty_printer *buffer, gomp_for *gs, int
> > > spc,
> > > +dump_gimple_omp_for (pretty_printer *buffer, const gomp_for *gs,
> > > int spc,
> > >                      dump_flags_t flags)
> > >  {
> > >    size_t i;
> > > @@ -1566,7 +1567,7 @@ dump_gimple_omp_for (pretty_printer *buffer,
> > > gomp_for *gs, int spc,
> > >  /* Dump a GIMPLE_OMP_CONTINUE tuple on the pretty_printer
> > > BUFFER.  */
> > >
> > >  static void
> > > -dump_gimple_omp_continue (pretty_printer *buffer, gomp_continue
> > > *gs,
> > > +dump_gimple_omp_continue (pretty_printer *buffer, const
> > > gomp_continue *gs,
> > >                           int spc, dump_flags_t flags)
> > >  {
> > >    if (flags & TDF_RAW)
> > > @@ -1591,7 +1592,7 @@ dump_gimple_omp_continue (pretty_printer
> > > *buffer, gomp_continue *gs,
> > >  /* Dump a GIMPLE_OMP_SINGLE tuple on the pretty_printer
> > > BUFFER.  */
> > >
> > >  static void
> > > -dump_gimple_omp_single (pretty_printer *buffer, gomp_single *gs,
> > > +dump_gimple_omp_single (pretty_printer *buffer, const gomp_single
> > > *gs,
> > >                         int spc, dump_flags_t flags)
> > >  {
> > >    if (flags & TDF_RAW)
> > > @@ -1620,7 +1621,7 @@ dump_gimple_omp_single (pretty_printer
> > > *buffer, gomp_single *gs,
> > >  /* Dump a GIMPLE_OMP_TASKGROUP tuple on the pretty_printer
> > > BUFFER.  */
> > >
> > >  static void
> > > -dump_gimple_omp_taskgroup (pretty_printer *buffer, gimple *gs,
> > > +dump_gimple_omp_taskgroup (pretty_printer *buffer, const gimple
> > > *gs,
> > >                            int spc, dump_flags_t flags)
> > >  {
> > >    if (flags & TDF_RAW)
> > > @@ -1649,7 +1650,7 @@ dump_gimple_omp_taskgroup (pretty_printer
> > > *buffer, gimple *gs,
> > >  /* Dump a GIMPLE_OMP_TARGET tuple on the pretty_printer
> > > BUFFER.  */
> > >
> > >  static void
> > > -dump_gimple_omp_target (pretty_printer *buffer, gomp_target *gs,
> > > +dump_gimple_omp_target (pretty_printer *buffer, const gomp_target
> > > *gs,
> > >                         int spc, dump_flags_t flags)
> > >  {
> > >    const char *kind;
> > > @@ -1742,7 +1743,7 @@ dump_gimple_omp_target (pretty_printer
> > > *buffer, gomp_target *gs,
> > >  /* Dump a GIMPLE_OMP_TEAMS tuple on the pretty_printer BUFFER.  */
> > >
> > >  static void
> > > -dump_gimple_omp_teams (pretty_printer *buffer, gomp_teams *gs, int
> > > spc,
> > > +dump_gimple_omp_teams (pretty_printer *buffer, const gomp_teams
> > > *gs, int spc,
> > >                        dump_flags_t flags)
> > >  {
> > >    if (flags & TDF_RAW)
> > > @@ -1771,7 +1772,7 @@ dump_gimple_omp_teams (pretty_printer
> > > *buffer, gomp_teams *gs, int spc,
> > >  /* Dump a GIMPLE_OMP_SECTIONS tuple on the pretty_printer
> > > BUFFER.  */
> > >
> > >  static void
> > > -dump_gimple_omp_sections (pretty_printer *buffer, gomp_sections
> > > *gs,
> > > +dump_gimple_omp_sections (pretty_printer *buffer, const
> > > gomp_sections *gs,
> > >                           int spc, dump_flags_t flags)
> > >  {
> > >    if (flags & TDF_RAW)
> > > @@ -1808,7 +1809,7 @@ dump_gimple_omp_sections (pretty_printer
> > > *buffer, gomp_sections *gs,
> > >     pretty_printer BUFFER.  */
> > >
> > >  static void
> > > -dump_gimple_omp_block (pretty_printer *buffer, gimple *gs, int
> > > spc,
> > > +dump_gimple_omp_block (pretty_printer *buffer, const gimple *gs,
> > > int spc,
> > >                        dump_flags_t flags)
> > >  {
> > >    if (flags & TDF_RAW)
> > > @@ -1845,7 +1846,7 @@ dump_gimple_omp_block (pretty_printer
> > > *buffer, gimple *gs, int spc,
> > >  /* Dump a GIMPLE_OMP_CRITICAL tuple on the pretty_printer
> > > BUFFER.  */
> > >
> > >  static void
> > > -dump_gimple_omp_critical (pretty_printer *buffer, gomp_critical
> > > *gs,
> > > +dump_gimple_omp_critical (pretty_printer *buffer, const
> > > gomp_critical *gs,
> > >                           int spc, dump_flags_t flags)
> > >  {
> > >    if (flags & TDF_RAW)
> > > @@ -1877,7 +1878,7 @@ dump_gimple_omp_critical (pretty_printer
> > > *buffer, gomp_critical *gs,
> > >  /* Dump a GIMPLE_OMP_ORDERED tuple on the pretty_printer
> > > BUFFER.  */
> > >
> > >  static void
> > > -dump_gimple_omp_ordered (pretty_printer *buffer, gomp_ordered *gs,
> > > +dump_gimple_omp_ordered (pretty_printer *buffer, const
> > > gomp_ordered *gs,
> > >                          int spc, dump_flags_t flags)
> > >  {
> > >    if (flags & TDF_RAW)
> > > @@ -1902,7 +1903,7 @@ dump_gimple_omp_ordered (pretty_printer
> > > *buffer, gomp_ordered *gs,
> > >  /* Dump a GIMPLE_OMP_SCAN tuple on the pretty_printer BUFFER.  */
> > >
> > >  static void
> > > -dump_gimple_omp_scan (pretty_printer *buffer, gomp_scan *gs,
> > > +dump_gimple_omp_scan (pretty_printer *buffer, const gomp_scan *gs,
> > >                       int spc, dump_flags_t flags)
> > >  {
> > >    if (flags & TDF_RAW)
> > > @@ -1930,7 +1931,7 @@ dump_gimple_omp_scan (pretty_printer *buffer,
> > > gomp_scan *gs,
> > >  /* Dump a GIMPLE_OMP_RETURN tuple on the pretty_printer
> > > BUFFER.  */
> > >
> > >  static void
> > > -dump_gimple_omp_return (pretty_printer *buffer, gimple *gs, int
> > > spc,
> > > +dump_gimple_omp_return (pretty_printer *buffer, const gimple *gs,
> > > int spc,
> > >                         dump_flags_t flags)
> > >  {
> > >    if (flags & TDF_RAW)
> > > @@ -1961,7 +1962,7 @@ dump_gimple_omp_return (pretty_printer
> > > *buffer, gimple *gs, int spc,
> > >  /* Dump a GIMPLE_TRANSACTION tuple on the pretty_printer
> > > BUFFER.  */
> > >
> > >  static void
> > > -dump_gimple_transaction (pretty_printer *buffer, gtransaction *gs,
> > > +dump_gimple_transaction (pretty_printer *buffer, const
> > > gtransaction *gs,
> > >                          int spc, dump_flags_t flags)
> > >  {
> > >    unsigned subcode = gimple_transaction_subcode (gs);
> > > @@ -2063,7 +2064,8 @@ dump_gimple_transaction (pretty_printer
> > > *buffer, gtransaction *gs,
> > >     dumpfile.h).  */
> > >
> > >  static void
> > > -dump_gimple_asm (pretty_printer *buffer, gasm *gs, int spc,
> > > dump_flags_t flags)
> > > +dump_gimple_asm (pretty_printer *buffer, const gasm *gs, int spc,
> > > +                dump_flags_t flags)
> > >  {
> > >    unsigned int i, n, f, fields;
> > >
> > > @@ -2282,7 +2284,7 @@ dump_ssaname_info_to_file (FILE *file, tree
> > > node, int spc)
> > >     pretty printer.  If COMMENT is true, print this after #.  */
> > >
> > >  static void
> > > -dump_gimple_phi (pretty_printer *buffer, gphi *phi, int spc, bool
> > > comment,
> > > +dump_gimple_phi (pretty_printer *buffer, const gphi *phi, int spc,
> > > bool comment,
> > >                  dump_flags_t flags)
> > >  {
> > >    size_t i;
> > > @@ -2339,7 +2341,7 @@ dump_gimple_phi (pretty_printer *buffer, gphi
> > > *phi, int spc, bool comment,
> > >     dumpfile.h).  */
> > >
> > >  static void
> > > -dump_gimple_omp_parallel (pretty_printer *buffer, gomp_parallel
> > > *gs,
> > > +dump_gimple_omp_parallel (pretty_printer *buffer, const
> > > gomp_parallel *gs,
> > >                           int spc, dump_flags_t flags)
> > >  {
> > >    if (flags & TDF_RAW)
> > > @@ -2393,7 +2395,7 @@ dump_gimple_omp_parallel (pretty_printer
> > > *buffer, gomp_parallel *gs,
> > >     dumpfile.h).  */
> > >
> > >  static void
> > > -dump_gimple_omp_task (pretty_printer *buffer, gomp_task *gs, int
> > > spc,
> > > +dump_gimple_omp_task (pretty_printer *buffer, const gomp_task *gs,
> > > int spc,
> > >                       dump_flags_t flags)
> > >  {
> > >    if (flags & TDF_RAW)
> > > @@ -2455,7 +2457,7 @@ dump_gimple_omp_task (pretty_printer *buffer,
> > > gomp_task *gs, int spc,
> > >     in dumpfile.h).  */
> > >
> > >  static void
> > > -dump_gimple_omp_atomic_load (pretty_printer *buffer,
> > > gomp_atomic_load *gs,
> > > +dump_gimple_omp_atomic_load (pretty_printer *buffer, const
> > > gomp_atomic_load *gs,
> > >                              int spc, dump_flags_t flags)
> > >  {
> > >    if (flags & TDF_RAW)
> > > @@ -2489,7 +2491,7 @@ dump_gimple_omp_atomic_load (pretty_printer
> > > *buffer, gomp_atomic_load *gs,
> > >
> > >  static void
> > >  dump_gimple_omp_atomic_store (pretty_printer *buffer,
> > > -                             gomp_atomic_store *gs, int spc,
> > > +                             const gomp_atomic_store *gs, int spc,
> > >                               dump_flags_t flags)
> > >  {
> > >    if (flags & TDF_RAW)
> > > @@ -2517,7 +2519,7 @@ dump_gimple_omp_atomic_store (pretty_printer
> > > *buffer,
> > >     FLAGS are as in pp_gimple_stmt_1.  */
> > >
> > >  static void
> > > -dump_gimple_mem_ops (pretty_printer *buffer, gimple *gs, int spc,
> > > +dump_gimple_mem_ops (pretty_printer *buffer, const gimple *gs, int
> > > spc,
> > >                      dump_flags_t flags)
> > >  {
> > >    tree vdef = gimple_vdef (gs);
> > > @@ -2548,14 +2550,14 @@ dump_gimple_mem_ops (pretty_printer
> > > *buffer, gimple *gs, int spc,
> > >     pp_flush on BUFFER to finalize the pretty printer.  */
> > >
> > >  void
> > > -pp_gimple_stmt_1 (pretty_printer *buffer, gimple *gs, int spc,
> > > +pp_gimple_stmt_1 (pretty_printer *buffer, const gimple *gs, int
> > > spc,
> > >                   dump_flags_t flags)
> > >  {
> > >    if (!gs)
> > >      return;
> > >
> > >    if (flags & TDF_STMTADDR)
> > > -    pp_printf (buffer, "<&%p> ", (void *) gs);
> > > +    pp_printf (buffer, "<&%p> ", (const void *) gs);
> > >
> > >    if ((flags & TDF_LINENO) && gimple_has_location (gs))
> > >      dump_location (buffer, gimple_location (gs));
> > > @@ -2580,31 +2582,31 @@ pp_gimple_stmt_1 (pretty_printer *buffer,
> > > gimple *gs, int spc,
> > >    switch (gimple_code (gs))
> > >      {
> > >      case GIMPLE_ASM:
> > > -      dump_gimple_asm (buffer, as_a <gasm *> (gs), spc, flags);
> > > +      dump_gimple_asm (buffer, as_a <const gasm *> (gs), spc,
> > > flags);
> > >        break;
> > >
> > >      case GIMPLE_ASSIGN:
> > > -      dump_gimple_assign (buffer, as_a <gassign *> (gs), spc,
> > > flags);
> > > +      dump_gimple_assign (buffer, as_a <const gassign *> (gs),
> > > spc, flags);
> > >        break;
> > >
> > >      case GIMPLE_BIND:
> > > -      dump_gimple_bind (buffer, as_a <gbind *> (gs), spc, flags);
> > > +      dump_gimple_bind (buffer, as_a <const gbind *> (gs), spc,
> > > flags);
> > >        break;
> > >
> > >      case GIMPLE_CALL:
> > > -      dump_gimple_call (buffer, as_a <gcall *> (gs), spc, flags);
> > > +      dump_gimple_call (buffer, as_a <const gcall *> (gs), spc,
> > > flags);
> > >        break;
> > >
> > >      case GIMPLE_COND:
> > > -      dump_gimple_cond (buffer, as_a <gcond *> (gs), spc, flags);
> > > +      dump_gimple_cond (buffer, as_a <const gcond *> (gs), spc,
> > > flags);
> > >        break;
> > >
> > >      case GIMPLE_LABEL:
> > > -      dump_gimple_label (buffer, as_a <glabel *> (gs), spc,
> > > flags);
> > > +      dump_gimple_label (buffer, as_a <const glabel *> (gs), spc,
> > > flags);
> > >        break;
> > >
> > >      case GIMPLE_GOTO:
> > > -      dump_gimple_goto (buffer, as_a <ggoto *> (gs), spc, flags);
> > > +      dump_gimple_goto (buffer, as_a <const ggoto *> (gs), spc,
> > > flags);
> > >        break;
> > >
> > >      case GIMPLE_NOP:
> > > @@ -2612,62 +2614,62 @@ pp_gimple_stmt_1 (pretty_printer *buffer,
> > > gimple *gs, int spc,
> > >        break;
> > >
> > >      case GIMPLE_RETURN:
> > > -      dump_gimple_return (buffer, as_a <greturn *> (gs), spc,
> > > flags);
> > > +      dump_gimple_return (buffer, as_a <const greturn *> (gs),
> > > spc, flags);
> > >        break;
> > >
> > >      case GIMPLE_SWITCH:
> > > -      dump_gimple_switch (buffer, as_a <gswitch *> (gs), spc,
> > > flags);
> > > +      dump_gimple_switch (buffer, as_a <const gswitch *> (gs),
> > > spc, flags);
> > >        break;
> > >
> > >      case GIMPLE_TRY:
> > > -      dump_gimple_try (buffer, as_a <gtry *> (gs), spc, flags);
> > > +      dump_gimple_try (buffer, as_a <const gtry *> (gs), spc,
> > > flags);
> > >        break;
> > >
> > >      case GIMPLE_PHI:
> > > -      dump_gimple_phi (buffer, as_a <gphi *> (gs), spc, false,
> > > flags);
> > > +      dump_gimple_phi (buffer, as_a <const gphi *> (gs), spc,
> > > false, flags);
> > >        break;
> > >
> > >      case GIMPLE_OMP_PARALLEL:
> > > -      dump_gimple_omp_parallel (buffer, as_a <gomp_parallel *>
> > > (gs), spc,
> > > +      dump_gimple_omp_parallel (buffer, as_a <const gomp_parallel
> > > *> (gs), spc,
> > >                                 flags);
> > >        break;
> > >
> > >      case GIMPLE_OMP_TASK:
> > > -      dump_gimple_omp_task (buffer, as_a <gomp_task *> (gs), spc,
> > > flags);
> > > +      dump_gimple_omp_task (buffer, as_a <const gomp_task *> (gs),
> > > spc, flags);
> > >        break;
> > >
> > >      case GIMPLE_OMP_ATOMIC_LOAD:
> > > -      dump_gimple_omp_atomic_load (buffer, as_a <gomp_atomic_load
> > > *> (gs),
> > > +      dump_gimple_omp_atomic_load (buffer, as_a <const
> > > gomp_atomic_load *> (gs),
> > >                                    spc, flags);
> > >        break;
> > >
> > >      case GIMPLE_OMP_ATOMIC_STORE:
> > >        dump_gimple_omp_atomic_store (buffer,
> > > -                                   as_a <gomp_atomic_store *>
> > > (gs),
> > > +                                   as_a <const gomp_atomic_store
> > > *> (gs),
> > >                                     spc, flags);
> > >        break;
> > >
> > >      case GIMPLE_OMP_FOR:
> > > -      dump_gimple_omp_for (buffer, as_a <gomp_for *> (gs), spc,
> > > flags);
> > > +      dump_gimple_omp_for (buffer, as_a <const gomp_for *> (gs),
> > > spc, flags);
> > >        break;
> > >
> > >      case GIMPLE_OMP_CONTINUE:
> > > -      dump_gimple_omp_continue (buffer, as_a <gomp_continue *>
> > > (gs), spc,
> > > +      dump_gimple_omp_continue (buffer, as_a <const gomp_continue
> > > *> (gs), spc,
> > >                                 flags);
> > >        break;
> > >
> > >      case GIMPLE_OMP_SINGLE:
> > > -      dump_gimple_omp_single (buffer, as_a <gomp_single *> (gs),
> > > spc,
> > > +      dump_gimple_omp_single (buffer, as_a <const gomp_single *>
> > > (gs), spc,
> > >                               flags);
> > >        break;
> > >
> > >      case GIMPLE_OMP_TARGET:
> > > -      dump_gimple_omp_target (buffer, as_a <gomp_target *> (gs),
> > > spc,
> > > +      dump_gimple_omp_target (buffer, as_a <const gomp_target *>
> > > (gs), spc,
> > >                               flags);
> > >        break;
> > >
> > >      case GIMPLE_OMP_TEAMS:
> > > -      dump_gimple_omp_teams (buffer, as_a <gomp_teams *> (gs),
> > > spc,
> > > +      dump_gimple_omp_teams (buffer, as_a <const gomp_teams *>
> > > (gs), spc,
> > >                              flags);
> > >        break;
> > >
> > > @@ -2676,7 +2678,7 @@ pp_gimple_stmt_1 (pretty_printer *buffer,
> > > gimple *gs, int spc,
> > >        break;
> > >
> > >      case GIMPLE_OMP_SECTIONS:
> > > -      dump_gimple_omp_sections (buffer, as_a <gomp_sections *>
> > > (gs),
> > > +      dump_gimple_omp_sections (buffer, as_a <const gomp_sections
> > > *> (gs),
> > >                                 spc, flags);
> > >        break;
> > >
> > > @@ -2695,49 +2697,50 @@ pp_gimple_stmt_1 (pretty_printer *buffer,
> > > gimple *gs, int spc,
> > >        break;
> > >
> > >      case GIMPLE_OMP_ORDERED:
> > > -      dump_gimple_omp_ordered (buffer, as_a <gomp_ordered *> (gs),
> > > spc,
> > > +      dump_gimple_omp_ordered (buffer, as_a <const gomp_ordered *>
> > > (gs), spc,
> > >                                flags);
> > >        break;
> > >
> > >      case GIMPLE_OMP_SCAN:
> > > -      dump_gimple_omp_scan (buffer, as_a <gomp_scan *> (gs), spc,
> > > +      dump_gimple_omp_scan (buffer, as_a <const gomp_scan *> (gs),
> > > spc,
> > >                             flags);
> > >        break;
> > >
> > >      case GIMPLE_OMP_CRITICAL:
> > > -      dump_gimple_omp_critical (buffer, as_a <gomp_critical *>
> > > (gs), spc,
> > > +      dump_gimple_omp_critical (buffer, as_a <const gomp_critical
> > > *> (gs), spc,
> > >                                 flags);
> > >        break;
> > >
> > >      case GIMPLE_CATCH:
> > > -      dump_gimple_catch (buffer, as_a <gcatch *> (gs), spc,
> > > flags);
> > > +      dump_gimple_catch (buffer, as_a <const gcatch *> (gs), spc,
> > > flags);
> > >        break;
> > >
> > >      case GIMPLE_EH_FILTER:
> > > -      dump_gimple_eh_filter (buffer, as_a <geh_filter *> (gs),
> > > spc, flags);
> > > +      dump_gimple_eh_filter (buffer, as_a <const geh_filter *>
> > > (gs), spc,
> > > +                            flags);
> > >        break;
> > >
> > >      case GIMPLE_EH_MUST_NOT_THROW:
> > >        dump_gimple_eh_must_not_throw (buffer,
> > > -                                    as_a <geh_mnt *> (gs),
> > > +                                    as_a <const geh_mnt *> (gs),
> > >                                      spc, flags);
> > >        break;
> > >
> > >      case GIMPLE_EH_ELSE:
> > > -      dump_gimple_eh_else (buffer, as_a <geh_else *> (gs), spc,
> > > flags);
> > > +      dump_gimple_eh_else (buffer, as_a <const geh_else *> (gs),
> > > spc, flags);
> > >        break;
> > >
> > >      case GIMPLE_RESX:
> > > -      dump_gimple_resx (buffer, as_a <gresx *> (gs), spc, flags);
> > > +      dump_gimple_resx (buffer, as_a <const gresx *> (gs), spc,
> > > flags);
> > >        break;
> > >
> > >      case GIMPLE_EH_DISPATCH:
> > > -      dump_gimple_eh_dispatch (buffer, as_a <geh_dispatch *> (gs),
> > > spc,
> > > +      dump_gimple_eh_dispatch (buffer, as_a <const geh_dispatch *>
> > > (gs), spc,
> > >                                flags);
> > >        break;
> > >
> > >      case GIMPLE_DEBUG:
> > > -      dump_gimple_debug (buffer, as_a <gdebug *> (gs), spc,
> > > flags);
> > > +      dump_gimple_debug (buffer, as_a <const gdebug *> (gs), spc,
> > > flags);
> > >        break;
> > >
> > >      case GIMPLE_PREDICT:
> > > @@ -2751,7 +2754,7 @@ pp_gimple_stmt_1 (pretty_printer *buffer,
> > > gimple *gs, int spc,
> > >        break;
> > >
> > >      case GIMPLE_TRANSACTION:
> > > -      dump_gimple_transaction (buffer, as_a <gtransaction *> (gs),
> > > spc,
> > > +      dump_gimple_transaction (buffer, as_a <const gtransaction *>
> > > (gs), spc,
> > >                                flags);
> > >        break;
> > >
> > > diff --git a/gcc/gimple-pretty-print.h b/gcc/gimple-pretty-print.h
> > > index 8d8e105..186f2cf 100644
> > > --- a/gcc/gimple-pretty-print.h
> > > +++ b/gcc/gimple-pretty-print.h
> > > @@ -31,7 +31,8 @@ extern void print_gimple_stmt (FILE *, gimple *,
> > > int, dump_flags_t = TDF_NONE);
> > >  extern void debug (gimple &ref);
> > >  extern void debug (gimple *ptr);
> > >  extern void print_gimple_expr (FILE *, gimple *, int, dump_flags_t
> > > = TDF_NONE);
> > > -extern void pp_gimple_stmt_1 (pretty_printer *, gimple *, int,
> > > dump_flags_t);
> > > +extern void pp_gimple_stmt_1 (pretty_printer *, const gimple *,
> > > int,
> > > +                             dump_flags_t);
> > >  extern void gimple_dump_bb (FILE *, basic_block, int,
> > > dump_flags_t);
> > >  extern void gimple_dump_bb_for_graph (pretty_printer *,
> > > basic_block);
> > >  extern void dump_ssaname_info_to_file (FILE *, tree, int);
> > > diff --git a/gcc/gimple.h b/gcc/gimple.h
> > > index cf1f8da..10a4b8b 100644
> > > --- a/gcc/gimple.h
> > > +++ b/gcc/gimple.h
> > > @@ -1017,6 +1017,14 @@ is_a_helper <gdebug *>::test (gimple *gs)
> > >  template <>
> > >  template <>
> > >  inline bool
> > > +is_a_helper <const gdebug *>::test (const gimple *gs)
> > > +{
> > > +  return gs->code == GIMPLE_DEBUG;
> > > +}
> > > +
> > > +template <>
> > > +template <>
> > > +inline bool
> > >  is_a_helper <ggoto *>::test (gimple *gs)
> > >  {
> > >    return gs->code == GIMPLE_GOTO;
> > > @@ -1025,6 +1033,14 @@ is_a_helper <ggoto *>::test (gimple *gs)
> > >  template <>
> > >  template <>
> > >  inline bool
> > > +is_a_helper <const ggoto *>::test (const gimple *gs)
> > > +{
> > > +  return gs->code == GIMPLE_GOTO;
> > > +}
> > > +
> > > +template <>
> > > +template <>
> > > +inline bool
> > >  is_a_helper <glabel *>::test (gimple *gs)
> > >  {
> > >    return gs->code == GIMPLE_LABEL;
> > > @@ -1033,6 +1049,14 @@ is_a_helper <glabel *>::test (gimple *gs)
> > >  template <>
> > >  template <>
> > >  inline bool
> > > +is_a_helper <const glabel *>::test (const gimple *gs)
> > > +{
> > > +  return gs->code == GIMPLE_LABEL;
> > > +}
> > > +
> > > +template <>
> > > +template <>
> > > +inline bool
> > >  is_a_helper <gresx *>::test (gimple *gs)
> > >  {
> > >    return gs->code == GIMPLE_RESX;
> > > @@ -1057,6 +1081,14 @@ is_a_helper <geh_else *>::test (gimple *gs)
> > >  template <>
> > >  template <>
> > >  inline bool
> > > +is_a_helper <const geh_else *>::test (const gimple *gs)
> > > +{
> > > +  return gs->code == GIMPLE_EH_ELSE;
> > > +}
> > > +
> > > +template <>
> > > +template <>
> > > +inline bool
> > >  is_a_helper <geh_filter *>::test (gimple *gs)
> > >  {
> > >    return gs->code == GIMPLE_EH_FILTER;
> > > @@ -1073,6 +1105,14 @@ is_a_helper <geh_mnt *>::test (gimple *gs)
> > >  template <>
> > >  template <>
> > >  inline bool
> > > +is_a_helper <const geh_mnt *>::test (const gimple *gs)
> > > +{
> > > +  return gs->code == GIMPLE_EH_MUST_NOT_THROW;
> > > +}
> > > +
> > > +template <>
> > > +template <>
> > > +inline bool
> > >  is_a_helper <gomp_atomic_load *>::test (gimple *gs)
> > >  {
> > >    return gs->code == GIMPLE_OMP_ATOMIC_LOAD;
> > > @@ -1219,6 +1259,14 @@ is_a_helper <gswitch *>::test (gimple *gs)
> > >  template <>
> > >  template <>
> > >  inline bool
> > > +is_a_helper <const gswitch *>::test (const gimple *gs)
> > > +{
> > > +  return gs->code == GIMPLE_SWITCH;
> > > +}
> > > +
> > > +template <>
> > > +template <>
> > > +inline bool
> > >  is_a_helper <gtransaction *>::test (gimple *gs)
> > >  {
> > >    return gs->code == GIMPLE_TRANSACTION;
> > > @@ -1235,6 +1283,14 @@ is_a_helper <gtry *>::test (gimple *gs)
> > >  template <>
> > >  template <>
> > >  inline bool
> > > +is_a_helper <const gtry *>::test (const gimple *gs)
> > > +{
> > > +  return gs->code == GIMPLE_TRY;
> > > +}
> > > +
> > > +template <>
> > > +template <>
> > > +inline bool
> > >  is_a_helper <gimple_statement_wce *>::test (gimple *gs)
> > >  {
> > >    return gs->code == GIMPLE_WITH_CLEANUP_EXPR;
> > > @@ -1429,6 +1485,14 @@ is_a_helper <const gphi *>::test (const
> > > gimple *gs)
> > >  template <>
> > >  template <>
> > >  inline bool
> > > +is_a_helper <const greturn *>::test (const gimple *gs)
> > > +{
> > > +  return gs->code == GIMPLE_RETURN;
> > > +}
> > > +
> > > +template <>
> > > +template <>
> > > +inline bool
> > >  is_a_helper <const gtransaction *>::test (const gimple *gs)
> > >  {
> > >    return gs->code == GIMPLE_TRANSACTION;
> > > @@ -3254,7 +3318,7 @@ gimple_call_set_tail (gcall *s, bool tail_p)
> > >  /* Return true if GIMPLE_CALL S is marked as a tail call.  */
> > >
> > >  static inline bool
> > > -gimple_call_tail_p (gcall *s)
> > > +gimple_call_tail_p (const gcall *s)
> > >  {
> > >    return (s->subcode & GF_CALL_TAILCALL) != 0;
> > >  }
> > > @@ -3296,7 +3360,7 @@ gimple_call_set_return_slot_opt (gcall *s,
> > > bool return_slot_opt_p)
> > >  /* Return true if S is marked for return slot optimization.  */
> > >
> > >  static inline bool
> > > -gimple_call_return_slot_opt_p (gcall *s)
> > > +gimple_call_return_slot_opt_p (const gcall *s)
> > >  {
> > >    return (s->subcode & GF_CALL_RETURN_SLOT_OPT) != 0;
> > >  }
> > > @@ -3341,7 +3405,7 @@ gimple_call_set_va_arg_pack (gcall *s, bool
> > > pass_arg_pack_p)
> > >     argument pack in its argument list.  */
> > >
> > >  static inline bool
> > > -gimple_call_va_arg_pack_p (gcall *s)
> > > +gimple_call_va_arg_pack_p (const gcall *s)
> > >  {
> > >    return (s->subcode & GF_CALL_VA_ARG_PACK) != 0;
> > >  }
> > > @@ -3443,6 +3507,13 @@ gimple_call_use_set (gcall *call_stmt)
> > >    return &call_stmt->call_used;
> > >  }
> > >
> > > +/* As above, but const.  */
> > > +
> > > +static inline const pt_solution *
> > > +gimple_call_use_set (const gcall *call_stmt)
> > > +{
> > > +  return &call_stmt->call_used;
> > > +}
> > >
> > >  /* Return a pointer to the points-to solution for the set of call-
> > > used
> > >     variables of the call CALL_STMT.  */
> > > @@ -3453,16 +3524,24 @@ gimple_call_clobber_set (gcall *call_stmt)
> > >    return &call_stmt->call_clobbered;
> > >  }
> > >
> > > +/* As above, but const.  */
> > > +
> > > +static inline const pt_solution *
> > > +gimple_call_clobber_set (const gcall *call_stmt)
> > > +{
> > > +  return &call_stmt->call_clobbered;
> > > +}
> > > +
> > >
> > >  /* Returns true if this is a GIMPLE_ASSIGN or a GIMPLE_CALL with a
> > >     non-NULL lhs.  */
> > >
> > >  static inline bool
> > > -gimple_has_lhs (gimple *stmt)
> > > +gimple_has_lhs (const gimple *stmt)
> > >  {
> > >    if (is_gimple_assign (stmt))
> > >      return true;
> > > -  if (gcall *call = dyn_cast <gcall *> (stmt))
> > > +  if (const gcall *call = dyn_cast <const gcall *> (stmt))
> > >      return gimple_call_lhs (call) != NULL_TREE;
> > >    return false;
> > >  }
> > > @@ -3760,9 +3839,9 @@ gimple_bind_body_ptr (gbind *bind_stmt)
> > >  /* Return the GIMPLE sequence contained in the GIMPLE_BIND
> > > statement GS.  */
> > >
> > >  static inline gimple_seq
> > > -gimple_bind_body (gbind *gs)
> > > +gimple_bind_body (const gbind *gs)
> > >  {
> > > -  return *gimple_bind_body_ptr (gs);
> > > +  return *gimple_bind_body_ptr (const_cast <gbind *> (gs));
> > >  }
> > >
> > >
> > > @@ -4034,9 +4113,9 @@ gimple_catch_handler_ptr (gcatch *catch_stmt)
> > >     GIMPLE_CATCH statement CATCH_STMT.  */
> > >
> > >  static inline gimple_seq
> > > -gimple_catch_handler (gcatch *catch_stmt)
> > > +gimple_catch_handler (const gcatch *catch_stmt)
> > >  {
> > > -  return *gimple_catch_handler_ptr (catch_stmt);
> > > +  return *gimple_catch_handler_ptr (const_cast <gcatch *>
> > > (catch_stmt));
> > >  }
> > >
> > >
> > > @@ -4094,9 +4173,9 @@ gimple_eh_filter_failure_ptr (gimple *gs)
> > >     statement fails.  */
> > >
> > >  static inline gimple_seq
> > > -gimple_eh_filter_failure (gimple *gs)
> > > +gimple_eh_filter_failure (const gimple *gs)
> > >  {
> > > -  return *gimple_eh_filter_failure_ptr (gs);
> > > +  return *gimple_eh_filter_failure_ptr (const_cast <gimple *>
> > > (gs));
> > >  }
> > >
> > >
> > > @@ -4123,7 +4202,7 @@ gimple_eh_filter_set_failure (geh_filter
> > > *eh_filter_stmt,
> > >  /* Get the function decl to be called by the MUST_NOT_THROW
> > > region.  */
> > >
> > >  static inline tree
> > > -gimple_eh_must_not_throw_fndecl (geh_mnt *eh_mnt_stmt)
> > > +gimple_eh_must_not_throw_fndecl (const geh_mnt *eh_mnt_stmt)
> > >  {
> > >    return eh_mnt_stmt->fndecl;
> > >  }
> > > @@ -4146,9 +4225,9 @@ gimple_eh_else_n_body_ptr (geh_else
> > > *eh_else_stmt)
> > >  }
> > >
> > >  static inline gimple_seq
> > > -gimple_eh_else_n_body (geh_else *eh_else_stmt)
> > > +gimple_eh_else_n_body (const geh_else *eh_else_stmt)
> > >  {
> > > -  return *gimple_eh_else_n_body_ptr (eh_else_stmt);
> > > +  return *gimple_eh_else_n_body_ptr (const_cast <geh_else *>
> > > (eh_else_stmt));
> > >  }
> > >
> > >  static inline gimple_seq *
> > > @@ -4158,9 +4237,9 @@ gimple_eh_else_e_body_ptr (geh_else
> > > *eh_else_stmt)
> > >  }
> > >
> > >  static inline gimple_seq
> > > -gimple_eh_else_e_body (geh_else *eh_else_stmt)
> > > +gimple_eh_else_e_body (const geh_else *eh_else_stmt)
> > >  {
> > > -  return *gimple_eh_else_e_body_ptr (eh_else_stmt);
> > > +  return *gimple_eh_else_e_body_ptr (const_cast <geh_else *>
> > > (eh_else_stmt));
> > >  }
> > >
> > >  static inline void
> > > @@ -4224,9 +4303,9 @@ gimple_try_eval_ptr (gimple *gs)
> > >  /* Return the sequence of statements used as the body for
> > > GIMPLE_TRY GS.  */
> > >
> > >  static inline gimple_seq
> > > -gimple_try_eval (gimple *gs)
> > > +gimple_try_eval (const gimple *gs)
> > >  {
> > > -  return *gimple_try_eval_ptr (gs);
> > > +  return *gimple_try_eval_ptr (const_cast <gimple *> (gs));
> > >  }
> > >
> > >
> > > @@ -4245,9 +4324,9 @@ gimple_try_cleanup_ptr (gimple *gs)
> > >     GIMPLE_TRY GS.  */
> > >
> > >  static inline gimple_seq
> > > -gimple_try_cleanup (gimple *gs)
> > > +gimple_try_cleanup (const gimple *gs)
> > >  {
> > > -  return *gimple_try_cleanup_ptr (gs);
> > > +  return *gimple_try_cleanup_ptr (const_cast <gimple *> (gs));
> > >  }
> > >
> > >
> > > @@ -4406,6 +4485,13 @@ gimple_phi_arg (gphi *gs, unsigned index)
> > >    return &(gs->args[index]);
> > >  }
> > >
> > > +static inline const phi_arg_d *
> > > +gimple_phi_arg (const gphi *gs, unsigned index)
> > > +{
> > > +  gcc_gimple_checking_assert (index < gs->nargs);
> > > +  return &(gs->args[index]);
> > > +}
> > > +
> > >  static inline struct phi_arg_d *
> > >  gimple_phi_arg (gimple *gs, unsigned index)
> > >  {
> > > @@ -4445,7 +4531,7 @@ phi_nodes_ptr (basic_block bb)
> > >  /* Return the tree operand for argument I of PHI node GS.  */
> > >
> > >  static inline tree
> > > -gimple_phi_arg_def (gphi *gs, size_t index)
> > > +gimple_phi_arg_def (const gphi *gs, size_t index)
> > >  {
> > >    return gimple_phi_arg (gs, index)->def;
> > >  }
> > > @@ -4468,7 +4554,7 @@ gimple_phi_arg_def_ptr (gphi *phi, size_t
> > > index)
> > >  /* Return the edge associated with argument I of phi node PHI.  */
> > >
> > >  static inline edge
> > > -gimple_phi_arg_edge (gphi *phi, size_t i)
> > > +gimple_phi_arg_edge (const gphi *phi, size_t i)
> > >  {
> > >    return EDGE_PRED (gimple_bb (phi), i);
> > >  }
> > > @@ -4476,7 +4562,7 @@ gimple_phi_arg_edge (gphi *phi, size_t i)
> > >  /* Return the source location of gimple argument I of phi node
> > > PHI.  */
> > >
> > >  static inline location_t
> > > -gimple_phi_arg_location (gphi *phi, size_t i)
> > > +gimple_phi_arg_location (const gphi *phi, size_t i)
> > >  {
> > >    return gimple_phi_arg (phi, i)->locus;
> > >  }
> > > @@ -4500,7 +4586,7 @@ gimple_phi_arg_set_location (gphi *phi,
> > > size_t i, location_t loc)
> > >  /* Return TRUE if argument I of phi node PHI has a location
> > > record.  */
> > >
> > >  static inline bool
> > > -gimple_phi_arg_has_location (gphi *phi, size_t i)
> > > +gimple_phi_arg_has_location (const gphi *phi, size_t i)
> > >  {
> > >    return gimple_phi_arg_location (phi, i) != UNKNOWN_LOCATION;
> > >  }
> > > @@ -4668,7 +4754,7 @@ gimple_debug_bind_p (const gimple *s)
> > >  /* Return the variable bound in a GIMPLE_DEBUG bind statement.  */
> > >
> > >  static inline tree
> > > -gimple_debug_bind_get_var (gimple *dbg)
> > > +gimple_debug_bind_get_var (const gimple *dbg)
> > >  {
> > >    GIMPLE_CHECK (dbg, GIMPLE_DEBUG);
> > >    gcc_gimple_checking_assert (gimple_debug_bind_p (dbg));
> > > @@ -4679,7 +4765,7 @@ gimple_debug_bind_get_var (gimple *dbg)
> > >     statement.  */
> > >
> > >  static inline tree
> > > -gimple_debug_bind_get_value (gimple *dbg)
> > > +gimple_debug_bind_get_value (const gimple *dbg)
> > >  {
> > >    GIMPLE_CHECK (dbg, GIMPLE_DEBUG);
> > >    gcc_gimple_checking_assert (gimple_debug_bind_p (dbg));
> > > @@ -4760,7 +4846,7 @@ gimple_debug_source_bind_p (const gimple *s)
> > >  /* Return the variable bound in a GIMPLE_DEBUG source bind
> > > statement.  */
> > >
> > >  static inline tree
> > > -gimple_debug_source_bind_get_var (gimple *dbg)
> > > +gimple_debug_source_bind_get_var (const gimple *dbg)
> > >  {
> > >    GIMPLE_CHECK (dbg, GIMPLE_DEBUG);
> > >    gcc_gimple_checking_assert (gimple_debug_source_bind_p (dbg));
> > > @@ -4771,7 +4857,7 @@ gimple_debug_source_bind_get_var (gimple
> > > *dbg)
> > >     statement.  */
> > >
> > >  static inline tree
> > > -gimple_debug_source_bind_get_value (gimple *dbg)
> > > +gimple_debug_source_bind_get_value (const gimple *dbg)
> > >  {
> > >    GIMPLE_CHECK (dbg, GIMPLE_DEBUG);
> > >    gcc_gimple_checking_assert (gimple_debug_source_bind_p (dbg));
> > > @@ -4872,9 +4958,9 @@ gimple_omp_body_ptr (gimple *gs)
> > >  /* Return the body for the OMP statement GS.  */
> > >
> > >  static inline gimple_seq
> > > -gimple_omp_body (gimple *gs)
> > > +gimple_omp_body (const gimple *gs)
> > >  {
> > > -  return *gimple_omp_body_ptr (gs);
> > > +  return *gimple_omp_body_ptr (const_cast <gimple *> (gs));
> > >  }
> > >
> > >  /* Set BODY to be the body for the OMP statement GS.  */
> > > @@ -5139,9 +5225,9 @@ gimple_omp_for_set_clauses (gimple *gs, tree
> > > clauses)
> > >  /* Get the collapse count of the OMP_FOR statement GS.  */
> > >
> > >  static inline size_t
> > > -gimple_omp_for_collapse (gimple *gs)
> > > +gimple_omp_for_collapse (const gimple *gs)
> > >  {
> > > -  gomp_for *omp_for_stmt = as_a <gomp_for *> (gs);
> > > +  const gomp_for *omp_for_stmt = as_a <const gomp_for *> (gs);
> > >    return omp_for_stmt->collapse;
> > >  }
> > >
> > > @@ -5316,9 +5402,9 @@ gimple_omp_for_pre_body_ptr (gimple *gs)
> > >     statement GS starts.  */
> > >
> > >  static inline gimple_seq
> > > -gimple_omp_for_pre_body (gimple *gs)
> > > +gimple_omp_for_pre_body (const gimple *gs)
> > >  {
> > > -  return *gimple_omp_for_pre_body_ptr (gs);
> > > +  return *gimple_omp_for_pre_body_ptr (const_cast <gimple *>
> > > (gs));
> > >  }
> > >
> > >
> > > @@ -6303,7 +6389,7 @@ gimple_transaction_body_ptr (gtransaction
> > > *transaction_stmt)
> > >  /* Return the body for the GIMPLE_TRANSACTION statement
> > > TRANSACTION_STMT.  */
> > >
> > >  static inline gimple_seq
> > > -gimple_transaction_body (gtransaction *transaction_stmt)
> > > +gimple_transaction_body (const gtransaction *transaction_stmt)
> > >  {
> > >    return transaction_stmt->body;
> > >  }
> > > diff --git a/gcc/tree-eh.c b/gcc/tree-eh.c
> > > index 54502e6..b996f49 100644
> > > --- a/gcc/tree-eh.c
> > > +++ b/gcc/tree-eh.c
> > > @@ -139,19 +139,19 @@ remove_stmt_from_eh_lp (gimple *t)
> > >     statement is not recorded in the region table.  */
> > >
> > >  int
> > > -lookup_stmt_eh_lp_fn (struct function *ifun, gimple *t)
> > > +lookup_stmt_eh_lp_fn (struct function *ifun, const gimple *t)
> > >  {
> > >    if (ifun->eh->throw_stmt_table == NULL)
> > >      return 0;
> > >
> > > -  int *lp_nr = ifun->eh->throw_stmt_table->get (t);
> > > +  int *lp_nr = ifun->eh->throw_stmt_table->get (const_cast <gimple
> > > *> (t));
> > >    return lp_nr ? *lp_nr : 0;
> > >  }
> > >
> > >  /* Likewise, but always use the current function.  */
> > >
> > >  int
> > > -lookup_stmt_eh_lp (gimple *t)
> > > +lookup_stmt_eh_lp (const gimple *t)
> > >  {
> > >    /* We can get called from initialized data when -fnon-call-
> > > exceptions
> > >       is on; prevent crash.  */
> > > diff --git a/gcc/tree-eh.h b/gcc/tree-eh.h
> > > index 511bb84..ca3b521 100644
> > > --- a/gcc/tree-eh.h
> > > +++ b/gcc/tree-eh.h
> > > @@ -27,8 +27,8 @@ extern void using_eh_for_cleanups (void);
> > >  extern void add_stmt_to_eh_lp (gimple *, int);
> > >  extern bool remove_stmt_from_eh_lp_fn (struct function *, gimple
> > > *);
> > >  extern bool remove_stmt_from_eh_lp (gimple *);
> > > -extern int lookup_stmt_eh_lp_fn (struct function *, gimple *);
> > > -extern int lookup_stmt_eh_lp (gimple *);
> > > +extern int lookup_stmt_eh_lp_fn (struct function *, const gimple
> > > *);
> > > +extern int lookup_stmt_eh_lp (const gimple *);
> > >  extern bool make_eh_dispatch_edges (geh_dispatch *);
> > >  extern void make_eh_edges (gimple *);
> > >  extern edge redirect_eh_edge (edge, basic_block);
> > > diff --git a/gcc/tree-ssa-alias.h b/gcc/tree-ssa-alias.h
> > > index 6d72c2f..de85312 100644
> > > --- a/gcc/tree-ssa-alias.h
> > > +++ b/gcc/tree-ssa-alias.h
> > > @@ -162,7 +162,7 @@ extern void dump_alias_stats (FILE *);
> > >
> > >  /* In tree-ssa-structalias.c  */
> > >  extern unsigned int compute_may_aliases (void);
> > > -extern bool pt_solution_empty_p (struct pt_solution *);
> > > +extern bool pt_solution_empty_p (const pt_solution *);
> > >  extern bool pt_solution_singleton_or_null_p (struct pt_solution *,
> > > unsigned *);
> > >  extern bool pt_solution_includes_global (struct pt_solution *);
> > >  extern bool pt_solution_includes (struct pt_solution *,
> > > const_tree);
> > > diff --git a/gcc/tree-ssa-structalias.c b/gcc/tree-ssa-
> > > structalias.c
> > > index 75c6fae..b248dfa 100644
> > > --- a/gcc/tree-ssa-structalias.c
> > > +++ b/gcc/tree-ssa-structalias.c
> > > @@ -6801,7 +6801,7 @@ pt_solution_ior_into (struct pt_solution
> > > *dest, struct pt_solution *src)
> > >  /* Return true if the points-to solution *PT is empty.  */
> > >
> > >  bool
> > > -pt_solution_empty_p (struct pt_solution *pt)
> > > +pt_solution_empty_p (const pt_solution *pt)
> > >  {
> > >    if (pt->anything
> > >        || pt->nonlocal)
> > > --
> > > 1.8.5.3
> > >
>

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 25/49] analyzer: new files: graphviz.{cc|h}
  2019-12-07 14:54   ` Jeff Law
@ 2019-12-09 23:13     ` David Malcolm
  0 siblings, 0 replies; 160+ messages in thread
From: David Malcolm @ 2019-12-09 23:13 UTC (permalink / raw)
  To: law, gcc-patches

On Sat, 2019-12-07 at 07:54 -0700, Jeff Law wrote:
> On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> > This patch adds a simple wrapper class to make it easier to
> > write human-readable .dot files.
> > 
> > gcc/ChangeLog:
> > 	* analyzer/graphviz.cc: New file.
> > 	* analyzer/graphviz.h: New file.
> This doesn't seem specific to the analyzer at all.  Can we move it
> into
> a more generic place for others to see and be able to use?
> 
> jeff

Will do for next iteration.

Dave

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 26/49] analyzer: new files: digraph.{cc|h} and shortest-paths.h
  2019-12-07 14:58   ` Jeff Law
@ 2019-12-09 23:15     ` David Malcolm
  0 siblings, 0 replies; 160+ messages in thread
From: David Malcolm @ 2019-12-09 23:15 UTC (permalink / raw)
  To: law, gcc-patches

On Sat, 2019-12-07 at 07:58 -0700, Jeff Law wrote:
> On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> > This patch adds template classes for directed graphs, their nodes
> > and edges, and for finding the shortest path through such a graph.
> > 
> > gcc/ChangeLog:
> > 	* analyzer/digraph.cc: New file.
> > 	* analyzer/digraph.h: New file.
> > 	* analyzer/shortest-paths.h: New file.
> Nothing too worrisome here.  I'm kindof surprised if haven't needed
> some of these capabilities before now.  Thoughts on moving them
> outside
> of analyzer into a more generic location?
> 
> jeff

Will do for next iteration - nothing is analyzer-specific here (but
it's used in two different places in the analyzer code, via
subclassing).

Dave

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 29/49] analyzer: new files: tristate.{cc|h}
  2019-12-07 15:03   ` Jeff Law
@ 2019-12-09 23:16     ` David Malcolm
  0 siblings, 0 replies; 160+ messages in thread
From: David Malcolm @ 2019-12-09 23:16 UTC (permalink / raw)
  To: law, gcc-patches

On Sat, 2019-12-07 at 08:03 -0700, Jeff Law wrote:
> On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> > gcc/ChangeLog:
> > 	* analyzer/tristate.cc: New file.
> > 	* analyzer/tristate.h: New file.
> Nothing really concerning here.  Seems like a generic facility we'd
> like to be able to use elsewhere.   Move outside the analyzer?

Will do for next iteration.

Dave

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 17/49] Support for adding selftests via a plugin
  2019-12-07 14:41   ` Jeff Law
@ 2019-12-09 23:18     ` David Malcolm
  0 siblings, 0 replies; 160+ messages in thread
From: David Malcolm @ 2019-12-09 23:18 UTC (permalink / raw)
  To: law, gcc-patches

On Sat, 2019-12-07 at 07:40 -0700, Jeff Law wrote:
> On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> > This patch provides a plugin callback for invoking selftests, so
> > that
> > a
> > plugin can add tests to those run by -fself-test=DIR.  The callback
> > invocation uses invoke_plugin_callbacks, which is a no-op if plugin
> > support is disabled.
> > 
> > gcc/ChangeLog:
> > 	* plugin.c (register_callback): Add case for
> > PLUGIN_RUN_SELFTESTS.
> > 	(invoke_plugin_callbacks_full): Likewise.
> > 	* plugin.def (PLUGIN_RUN_SELFTESTS): New event.
> > 	* selftest-run-tests.c: Include "plugin.h".
> > 	(selftest::run_tests): Run any plugin-provided selftests.
> I'm generally in favor of having the ability for plugins to invoke
> selftests.  But do we want to push on this now given the preferred
> direction for this kit?
> 
> jeff

I'm dropping this for the sake of simplicity/YAGNI in the next
iteration of the kit, as it's no longer needed by the kit.

Dave

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 28/49] analyzer: new files: analyzer.{cc|h}
  2019-12-07  3:38   ` Eric Gallager
@ 2019-12-09 23:22     ` David Malcolm
  2019-12-10 19:59       ` Eric Gallager
  0 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-12-09 23:22 UTC (permalink / raw)
  To: Eric Gallager; +Cc: gcc-patches

On Fri, 2019-12-06 at 22:38 -0500, Eric Gallager wrote:
> On 11/15/19, David Malcolm <dmalcolm@redhat.com> wrote:
[...]
> > diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h
> > new file mode 100644
> > index 0000000..ace8924
> > --- /dev/null
> > +++ b/gcc/analyzer/analyzer.h
> > @@ -0,0 +1,126 @@
> > +/* Utility functions for the analyzer.
> > +   Copyright (C) 2019 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/>;.  */
> > +
> > +#ifndef GCC_ANALYZER_ANALYZER_H
> > +#define GCC_ANALYZER_ANALYZER_H
> > +
> > +/* Forward decls of common types, with indentation to show
> > inheritance. */
> 
> I'm wondering about the "with indentation to show inheritance"
> part...
> does that require tweaking any editor configuration files or adding
> /*INDENT-OFF*/ comments or anything to prevent automatic formatting
> tools from "fixing" the indentation to go back to the normal style of
> having everything be aligned?

If we had some kind of automatic formatting then I guess it would, but
I don't think we have such a system in place.

[...]

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 26/49] analyzer: new files: digraph.{cc|h} and shortest-paths.h
  2019-11-16  1:18 ` [PATCH 26/49] analyzer: new files: digraph.{cc|h} and shortest-paths.h David Malcolm
  2019-12-07 14:58   ` Jeff Law
@ 2019-12-10  0:45   ` Martin Sebor
  1 sibling, 0 replies; 160+ messages in thread
From: Martin Sebor @ 2019-12-10  0:45 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On 11/15/19 6:23 PM, David Malcolm wrote:
> This patch adds template classes for directed graphs, their nodes
> and edges, and for finding the shortest path through such a graph.

Just a few mostly minor comments from me, in a similar vein as
on some of the other patches.

> 
> gcc/ChangeLog:
> 	* analyzer/digraph.cc: New file.
> 	* analyzer/digraph.h: New file.
> 	* analyzer/shortest-paths.h: New file.
> ---
>   gcc/analyzer/digraph.cc       | 189 ++++++++++++++++++++++++++++++++
>   gcc/analyzer/digraph.h        | 248 ++++++++++++++++++++++++++++++++++++++++++
>   gcc/analyzer/shortest-paths.h | 147 +++++++++++++++++++++++++
>   3 files changed, 584 insertions(+)
>   create mode 100644 gcc/analyzer/digraph.cc
>   create mode 100644 gcc/analyzer/digraph.h
>   create mode 100644 gcc/analyzer/shortest-paths.h
> 
> diff --git a/gcc/analyzer/digraph.cc b/gcc/analyzer/digraph.cc
> new file mode 100644
> index 0000000..c1fa46e
> --- /dev/null
> +++ b/gcc/analyzer/digraph.cc
> @@ -0,0 +1,189 @@
> +/* Template classes for directed graphs.
> +   Copyright (C) 2019 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 "gcc-plugin.h"
> +#include "system.h"
> +#include "coretypes.h"
> +#include "diagnostic.h"
> +#include "analyzer/graphviz.h"
> +#include "analyzer/digraph.h"
> +#include "analyzer/shortest-paths.h"
> +#include "selftest.h"
> +
> +#if CHECKING_P
> +
> +namespace selftest {
> +
> +/* A family of digraph classes for writing selftests.  */
> +
> +struct test_node;
> +struct test_edge;
> +struct test_graph;
> +struct test_dump_args_t {};
> +struct test_cluster;
> +
> +struct test_graph_traits
> +{
> +  typedef test_node node_t;
> +  typedef test_edge edge_t;
> +  typedef test_graph graph_t;
> +  typedef test_dump_args_t dump_args_t;
> +  typedef test_cluster cluster_t;
> +};
> +
> +struct test_node : public dnode<test_graph_traits>
> +{
> +  test_node (const char *name, int index) : m_name (name), m_index (index) {}
> +  void dump_dot (graphviz_out *, const dump_args_t &) const OVERRIDE
> +  {
> +  }
> +
> +  const char *m_name;
> +  int m_index;
> +};
> +
> +struct test_edge : public dedge<test_graph_traits>
> +{
> +  test_edge (node_t *src, node_t *dest)
> +  : dedge (src, dest)
> +  {}
> +
> +  void dump_dot (graphviz_out *gv, const dump_args_t &) const OVERRIDE
> +  {
> +    gv->println ("%s -> %s;", m_src->m_name, m_dest->m_name);
> +  }
> +};
> +
> +struct test_graph : public digraph<test_graph_traits>
> +{
> +  test_node *add_test_node (const char *name)
> +  {
> +    test_node *result = new test_node (name, m_nodes.length ());
> +    add_node (result);
> +    return result;
> +  }
> +
> +  test_edge *add_test_edge (test_node *src, test_node *dst)
> +  {
> +    test_edge *result = new test_edge (src, dst);
> +    add_edge (result);
> +    return result;
> +  }
> +};
> +
> +struct test_cluster : public cluster<test_graph_traits>
> +{
> +};
> +
> +struct test_path
> +{
> +  auto_vec<const test_edge *> m_edges;
> +};
> +
> +/* Smoketest of digraph dumping.  */
> +
> +static void
> +test_dump_to_dot ()
> +{
> +  test_graph g;
> +  test_node *a = g.add_test_node ("a");
> +  test_node *b = g.add_test_node ("b");
> +  g.add_test_edge (a, b);
> +
> +  pretty_printer pp;
> +  pp.buffer->stream = NULL;
> +  test_dump_args_t dump_args;
> +  g.dump_dot_to_pp (&pp, NULL, dump_args);
> +
> +  ASSERT_STR_CONTAINS (pp_formatted_text (&pp),
> +		       "a -> b;\n");
> +}
> +
> +/* Test shortest paths from A in this digraph,
> +   where edges run top-to-bottom if not otherwise labeled:
> +
> +      A
> +     / \
> +    B   C-->D
> +    |   |
> +    E   |
> +     \ /
> +      F.  */
> +
> +static void
> +test_shortest_paths ()
> +{
> +  test_graph g;
> +  test_node *a = g.add_test_node ("a");
> +  test_node *b = g.add_test_node ("b");
> +  test_node *c = g.add_test_node ("d");
> +  test_node *d = g.add_test_node ("d");
> +  test_node *e = g.add_test_node ("e");
> +  test_node *f = g.add_test_node ("f");
> +
> +  test_edge *ab = g.add_test_edge (a, b);
> +  test_edge *ac = g.add_test_edge (a, c);
> +  test_edge *cd = g.add_test_edge (c, d);
> +  test_edge *be = g.add_test_edge (b, e);
> +  g.add_test_edge (e, f);
> +  test_edge *cf = g.add_test_edge (c, f);
> +
> +  shortest_paths<test_graph_traits, test_path> sp (g, a);
> +
> +  test_path path_to_a = sp.get_shortest_path (a);
> +  ASSERT_EQ (path_to_a.m_edges.length (), 0);
> +
> +  test_path path_to_b = sp.get_shortest_path (b);
> +  ASSERT_EQ (path_to_b.m_edges.length (), 1);
> +  ASSERT_EQ (path_to_b.m_edges[0], ab);
> +
> +  test_path path_to_c = sp.get_shortest_path (c);
> +  ASSERT_EQ (path_to_c.m_edges.length (), 1);
> +  ASSERT_EQ (path_to_c.m_edges[0], ac);
> +
> +  test_path path_to_d = sp.get_shortest_path (d);
> +  ASSERT_EQ (path_to_d.m_edges.length (), 2);
> +  ASSERT_EQ (path_to_d.m_edges[0], ac);
> +  ASSERT_EQ (path_to_d.m_edges[1], cd);
> +
> +  test_path path_to_e = sp.get_shortest_path (e);
> +  ASSERT_EQ (path_to_e.m_edges.length (), 2);
> +  ASSERT_EQ (path_to_e.m_edges[0], ab);
> +  ASSERT_EQ (path_to_e.m_edges[1], be);
> +
> +  test_path path_to_f = sp.get_shortest_path (f);
> +  ASSERT_EQ (path_to_f.m_edges.length (), 2);
> +  ASSERT_EQ (path_to_f.m_edges[0], ac);
> +  ASSERT_EQ (path_to_f.m_edges[1], cf);
> +}
> +
> +/* Run all of the selftests within this file.  */
> +
> +void
> +analyzer_digraph_cc_tests ()
> +{
> +  test_dump_to_dot ();
> +  test_shortest_paths ();
> +}
> +
> +} // namespace selftest
> +
> +#endif /* #if CHECKING_P */
> diff --git a/gcc/analyzer/digraph.h b/gcc/analyzer/digraph.h
> new file mode 100644
> index 0000000..5c6a496
> --- /dev/null
> +++ b/gcc/analyzer/digraph.h
> @@ -0,0 +1,248 @@
> +/* Template classes for directed graphs.
> +   Copyright (C) 2019 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/>.  */
> +
> +#ifndef GCC_ANALYZER_DIGRAPH_H
> +#define GCC_ANALYZER_DIGRAPH_H
> +
> +#include "diagnostic.h"
> +#include "tree-diagnostic.h" /* for default_tree_printer.  */
> +#include "analyzer/graphviz.h"
> +
> +/* Templates for a family of classes: digraph, node, edge, and cluster.
> +   This assumes a traits type with the following typedefs:
> +   node_t: the node class
> +   edge_t: the edge class
> +   dump_args_t: additional args for dot-dumps
> +   cluster_t: the cluster class (for use when generating .dot files).
> +
> +   Using a template allows for typesafe nodes and edges: a node's
> +   predecessor and successor edges can be of a node-specific edge
> +   subclass, without needing casting.  */
> +
> +/* Abstract base class for a node in a directed graph.  */
> +
> +template <typename GraphTraits>
> +class dnode
> +{
> + public:
> +  typedef typename GraphTraits::edge_t edge_t;
> +  typedef typename GraphTraits::dump_args_t dump_args_t;
> +
> +  virtual ~dnode () {}

More out of curiosity than anything else: why does this class (and
some of the others in this patch) define a virtual dtor when it
doesn't define any other virtual functions?

> +  virtual void dump_dot (graphviz_out *gv, const dump_args_t &args) const = 0;
> +
> +  auto_vec<edge_t *> m_preds;
> +  auto_vec<edge_t *> m_succs;

As in my other comments, embedding an auto_vec in a class makes
it unsafe to copy and assign (because of bug 90904), so until
the bug is fixed, such classes should disable copying and assignment).


> +};
> +
> +/* Abstract base class for an edge in a directed graph.  */
> +
> +template <typename GraphTraits>
> +class dedge
> +{
> + public:
> +  typedef typename GraphTraits::node_t node_t;
> +  typedef typename GraphTraits::dump_args_t dump_args_t;
> +
> +  dedge (node_t *src, node_t *dest)
> +  : m_src (src), m_dest (dest) {}
> +
> +  virtual ~dedge () {}
> +
> +  virtual void dump_dot (graphviz_out *gv, const dump_args_t &args) const = 0;
> +
> +  node_t *const m_src;
> +  node_t *const m_dest;

The const members prevent the class from being assigned to.  If
that's okay here, it seems that it should be okay for the other
classes as well.  So maybe the simplest solution is also the right
one: make all of then non-copyable and non-assignable.

> +};
> +
> +/* Abstract base class for a directed graph.
> +   This class maintains the vectors of nodes and edges,
> +   and owns the nodes and edges.  */
> +
> +template <typename GraphTraits>
> +class digraph
> +{
> + public:
> +  typedef typename GraphTraits::node_t node_t;
> +  typedef typename GraphTraits::edge_t edge_t;
> +  typedef typename GraphTraits::dump_args_t dump_args_t;
> +  typedef typename GraphTraits::cluster_t cluster_t;
> +
> +  digraph () {}
> +  virtual ~digraph () {}
> +
> +  void dump_dot_to_pp (pretty_printer *pp,
> +		       cluster_t *root_cluster,
> +		       const dump_args_t &args) const;
> +  void dump_dot_to_file (FILE *fp,
> +			 cluster_t *root_cluster,
> +			 const dump_args_t &args) const;
> +  void dump_dot (const char *path,
> +		 cluster_t *root_cluster,
> +		 const dump_args_t &args) const;
> +
> +  void add_node (node_t *node);
> +  void add_edge (edge_t *edge);
> +
> +  auto_delete_vec<node_t> m_nodes;
> +  auto_delete_vec<edge_t> m_edges;

Same comment about copying here.

> +};
> +
> +/* Abstract base class for splitting dnodes into hierarchical clusters
> +   in the generated .dot file.
> +
> +   See "Subgraphs and Clusters" within
> +     https://www.graphviz.org/doc/info/lang.html
> +   and e.g.
> +     https://graphviz.gitlab.io/_pages/Gallery/directed/cluster.html
> +
> +   If a root_cluster is passed to dump_dot*, then all nodes will be
> +   added to it at the start of dumping, via calls to add_node.
> +
> +   The root cluster can organize the nodes into a hierarchy of
> +   child clusters.
> +
> +   After all nodes are added to the root cluster, dump_dot will then
> +   be called on it (and not on the nodes themselves).  */
> +
> +template <typename GraphTraits>
> +class cluster
> +{
> + public:
> +  typedef typename GraphTraits::node_t node_t;
> +  typedef typename GraphTraits::dump_args_t dump_args_t;
> +
> +  virtual ~cluster () {}
> +
> +  virtual void add_node (node_t *node) = 0;
> +
> +  /* Recursively dump the cluster, all nodes, and child clusters.  */
> +  virtual void dump_dot (graphviz_out *gv, const dump_args_t &) const = 0;
> +};
> +
> +////////////////////////////////////////////////////////////////////////////
> +
> +/* Write .dot information for this graph to PP, passing ARGS to the nodes
> +   and edges.
> +   If ROOT_CLUSTER is non-NULL, use it to organize the nodes into clusters.  */
> +
> +template <typename GraphTraits>
> +inline void
> +digraph<GraphTraits>::dump_dot_to_pp (pretty_printer *pp,
> +				      cluster_t *root_cluster,
> +				      const dump_args_t &args) const
> +{
> +  graphviz_out gv (pp);
> +
> +  pp_string (pp, "digraph \"");
> +  pp_string (pp, "base");
> +  pp_string (pp, "\" {\n");
> +
> +  gv.indent ();
> +
> +  pp_string (pp, "overlap=false;\n");
> +  pp_string (pp, "compound=true;\n");
> +
> +  /* If using clustering, emit all nodes via clusters.  */
> +  if (root_cluster)
> +    {
> +      int i;
> +      node_t *n;
> +      FOR_EACH_VEC_ELT (m_nodes, i, n)
> +	root_cluster->add_node (n);
> +      root_cluster->dump_dot (&gv, args);
> +    }
> +  else
> +    {
> +      /* Otherwise, display all nodes at top level.  */
> +      int i;
> +      node_t *n;
> +      FOR_EACH_VEC_ELT (m_nodes, i, n)
> +	n->dump_dot (&gv, args);
> +    }
> +
> +  /* Edges.  */
> +  int i;
> +  edge_t *e;
> +  FOR_EACH_VEC_ELT (m_edges, i, e)
> +    e->dump_dot (&gv, args);
> +
> +  /* Terminate "digraph" */
> +  gv.outdent ();
> +  pp_string (pp, "}");
> +  pp_newline (pp);
> +}
> +
> +/* Write .dot information for this graph to FP, passing ARGS to the nodes
> +   and edges.
> +   If ROOT_CLUSTER is non-NULL, use it to organize the nodes into clusters.  */
> +
> +template <typename GraphTraits>
> +inline void
> +digraph<GraphTraits>::dump_dot_to_file (FILE *fp,
> +					cluster_t *root_cluster,
> +					const dump_args_t &args) const
> +{
> +  pretty_printer pp;
> +  // TODO:
> +  pp_format_decoder (&pp) = default_tree_printer;
> +  pp.buffer->stream = fp;
> +  dump_dot_to_pp (&pp, root_cluster, args);
> +  pp_flush (&pp);
> +}
> +
> +/* Write .dot information for this graph to a file at PATH, passing ARGS
> +   to the nodes and edges.
> +   If ROOT_CLUSTER is non-NULL, use it to organize the nodes into clusters.  */
> +
> +template <typename GraphTraits>
> +inline void
> +digraph<GraphTraits>::dump_dot (const char *path,
> +				cluster_t *root_cluster,
> +				const dump_args_t &args) const
> +{
> +  FILE *fp = fopen (path, "w");

I realize this is a proof of concept/preview, but eventually this
should probably handle failures more gracefully than by crashing
later.

> +  dump_dot_to_file (fp, root_cluster, args);
> +  fclose (fp);
> +}
> +
> +/* Add NODE to this DIGRAPH, taking ownership.  */
> +
> +template <typename GraphTraits>
> +inline void
> +digraph<GraphTraits>::add_node (node_t *node)
> +{
> +  m_nodes.safe_push (node);
> +}
> +
> +/* Add EDGE to this digraph, and to the preds/succs of its endpoints.
> +   Take ownership of EDGE.  */
> +
> +template <typename GraphTraits>
> +inline void
> +digraph<GraphTraits>::add_edge (edge_t *edge)
> +{
> +  m_edges.safe_push (edge);
> +  edge->m_dest->m_preds.safe_push (edge);
> +  edge->m_src->m_succs.safe_push (edge);
> +
> +}
> +
> +#endif /* GCC_ANALYZER_DIGRAPH_H */
> diff --git a/gcc/analyzer/shortest-paths.h b/gcc/analyzer/shortest-paths.h
> new file mode 100644
> index 0000000..4738f18
> --- /dev/null
> +++ b/gcc/analyzer/shortest-paths.h
> @@ -0,0 +1,147 @@
> +/* Template class for Dijkstra's algorithm on directed graphs.
> +   Copyright (C) 2019 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/>.  */
> +
> +#ifndef GCC_ANALYZER_SHORTEST_PATHS_H
> +#define GCC_ANALYZER_SHORTEST_PATHS_H
> +
> +#include "timevar.h"
> +
> +/* A record of the shortest path to each node in an graph
> +   from the origin node.
> +   The constructor runs Dijkstra's algorithm, and the results are
> +   stored in this class.  */
> +
> +template <typename GraphTraits, typename Path_t>
> +class shortest_paths
> +{
> +public:
> +  typedef typename GraphTraits::graph_t graph_t;
> +  typedef typename GraphTraits::node_t node_t;
> +  typedef typename GraphTraits::edge_t edge_t;
> +  typedef Path_t path_t;
> +
> +  shortest_paths (const graph_t &graph, const node_t *origin);

Just as a matter of consistency (and being aware of no dominant
convention in GCC one way or the other), the first argument is
a reference and the second pointer.  Since they're both const
and the latter must not be null, it seems that they should both
be references.  At least in the same function, if not throughout.

> +
> +  path_t get_shortest_path (const node_t *to) const;
> +
> +private:
> +  const graph_t &m_graph;
> +
> +  /* For each node (by index), the minimal distance to that node from the
> +     origin.  */
> +  auto_vec<int> m_dist;
> +
> +  /* For each exploded_node (by index), the previous edge in the shortest
> +     path from the origin.  */
> +  auto_vec<const edge_t *> m_prev;

Same concern about copying auto_vec as above.

Martin

> +};
> +
> +////////////////////////////////////////////////////////////////////////////
> +
> +/* shortest_paths's constructor.
> +
> +   Use Dijkstra's algorithm relative to ORIGIN to populate m_dist and
> +   m_prev with enough information to be able to generate Path_t instances
> +   to give the shortest path to any node in GRAPH from ORIGIN.  */
> +
> +template <typename GraphTraits, typename Path_t>
> +inline
> +shortest_paths<GraphTraits, Path_t>::shortest_paths (const graph_t &graph,
> +						     const node_t *origin)
> +: m_graph (graph),
> +  m_dist (graph.m_nodes.length ()),
> +  m_prev (graph.m_nodes.length ())
> +{
> +  auto_client_timevar tv ("shortest_paths");
> +
> +  auto_vec<int> queue (graph.m_nodes.length ());
> +
> +  for (unsigned i = 0; i < graph.m_nodes.length (); i++)
> +    {
> +      m_dist.quick_push (INT_MAX);
> +      m_prev.quick_push (NULL);
> +      queue.quick_push (i);
> +    }
> +  m_dist[origin->m_index] = 0;
> +
> +  while (queue.length () > 0)
> +    {
> +      /* Get minimal distance in queue.
> +	 FIXME: this is O(N^2); replace with a priority queue.  */
> +      int idx_with_min_dist = -1;
> +      int idx_in_queue_with_min_dist = -1;
> +      int min_dist = INT_MAX;
> +      for (unsigned i = 0; i < queue.length (); i++)
> +	{
> +	  int idx = queue[i];
> +	  if (m_dist[queue[i]] < min_dist)
> +	    {
> +	      min_dist = m_dist[idx];
> +	      idx_with_min_dist = idx;
> +	      idx_in_queue_with_min_dist = i;
> +	    }
> +	}
> +      gcc_assert (idx_with_min_dist != -1);
> +      gcc_assert (idx_in_queue_with_min_dist != -1);
> +
> +      // FIXME: this is confusing: there are two indices here
> +
> +      queue.unordered_remove (idx_in_queue_with_min_dist);
> +
> +      node_t *n
> +	= static_cast <node_t *> (m_graph.m_nodes[idx_with_min_dist]);
> +
> +      int i;
> +      edge_t *succ;
> +      FOR_EACH_VEC_ELT (n->m_succs, i, succ)
> +	{
> +	  // TODO: only for dest still in queue
> +	  node_t *dest = succ->m_dest;
> +	  int alt = m_dist[n->m_index] + 1;
> +	  if (alt < m_dist[dest->m_index])
> +	    {
> +	      m_dist[dest->m_index] = alt;
> +	      m_prev[dest->m_index] = succ;
> +	    }
> +	}
> +   }
> +}
> +
> +/* Generate an Path_t instance giving the shortest path to the node
> +   TO from the origin node.  */
> +
> +template <typename GraphTraits, typename Path_t>
> +inline Path_t
> +shortest_paths<GraphTraits, Path_t>::get_shortest_path (const node_t *to) const
> +{
> +  Path_t result;
> +
> +  while (m_prev[to->m_index])
> +    {
> +      result.m_edges.safe_push (m_prev[to->m_index]);
> +      to = m_prev[to->m_index]->m_src;
> +    }
> +
> +  result.m_edges.reverse ();
> +
> +  return result;
> +}
> +
> +#endif /* GCC_ANALYZER_SHORTEST_PATHS_H */
> 

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 29/49] analyzer: new files: tristate.{cc|h}
  2019-11-16  1:18 ` [PATCH 29/49] analyzer: new files: tristate.{cc|h} David Malcolm
  2019-12-07 15:03   ` Jeff Law
@ 2019-12-10  0:59   ` Martin Sebor
  1 sibling, 0 replies; 160+ messages in thread
From: Martin Sebor @ 2019-12-10  0:59 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On 11/15/19 6:23 PM, David Malcolm wrote:
> gcc/ChangeLog:
> 	* analyzer/tristate.cc: New file.
> 	* analyzer/tristate.h: New file.
> ---
>   gcc/analyzer/tristate.cc | 222 +++++++++++++++++++++++++++++++++++++++++++++++
>   gcc/analyzer/tristate.h  |  82 +++++++++++++++++
>   2 files changed, 304 insertions(+)
>   create mode 100644 gcc/analyzer/tristate.cc
>   create mode 100644 gcc/analyzer/tristate.h
> 
> diff --git a/gcc/analyzer/tristate.cc b/gcc/analyzer/tristate.cc
> new file mode 100644
> index 0000000..ac16129
> --- /dev/null
> +++ b/gcc/analyzer/tristate.cc
> @@ -0,0 +1,222 @@
> +/* "True" vs "False" vs "Unknown".
> +   Copyright (C) 2019 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 "gcc-plugin.h"
> +#include "system.h"
> +#include "coretypes.h"
> +#include "analyzer/tristate.h"
> +#include "selftest.h"
> +
> +const char *
> +tristate::as_string () const
> +{
> +  switch (m_value)
> +    {
> +    default:
> +      gcc_unreachable ();
> +    case TS_UNKNOWN:
> +      return "UNKNOWN";
> +    case TS_TRUE:
> +      return "TRUE";
> +    case TS_FALSE:
> +      return "FALSE";
> +    }
> +}
> +
> +tristate
> +tristate::not_ () const
> +{
> +  switch (m_value)
> +    {
> +    default:
> +      gcc_unreachable ();
> +    case TS_UNKNOWN:
> +      return tristate (TS_UNKNOWN);
> +    case TS_TRUE:
> +      return tristate (TS_FALSE);
> +    case TS_FALSE:
> +      return tristate (TS_TRUE);
> +    }
> +}
> +
> +tristate
> +tristate::or_ (tristate other) const
> +{
> +  switch (m_value)
> +    {
> +    default:
> +      gcc_unreachable ();
> +    case TS_UNKNOWN:
> +      if (other.is_true ())
> +	return tristate (TS_TRUE);
> +      else
> +	return tristate (TS_UNKNOWN);
> +    case TS_FALSE:
> +      return other;
> +    case TS_TRUE:
> +      return tristate (TS_TRUE);
> +    }
> +}
> +
> +tristate
> +tristate::and_ (tristate other) const
> +{
> +  switch (m_value)
> +    {
> +    default:
> +      gcc_unreachable ();
> +    case TS_UNKNOWN:
> +      if (other.is_false ())
> +	return tristate (TS_FALSE);
> +      else
> +	return tristate (TS_UNKNOWN);
> +    case TS_TRUE:
> +      return other;
> +    case TS_FALSE:
> +      return tristate (TS_FALSE);
> +    }
> +}
> +
> +#if CHECKING_P
> +
> +namespace selftest {
> +
> +#define ASSERT_TRISTATE_TRUE(TRISTATE) \
> +  SELFTEST_BEGIN_STMT					\
> +  ASSERT_EQ (TRISTATE, tristate (tristate::TS_TRUE));	\
> +  SELFTEST_END_STMT
> +
> +#define ASSERT_TRISTATE_FALSE(TRISTATE) \
> +  SELFTEST_BEGIN_STMT					\
> +  ASSERT_EQ (TRISTATE, tristate (tristate::TS_FALSE));	\
> +  SELFTEST_END_STMT
> +
> +#define ASSERT_TRISTATE_UNKNOWN(TRISTATE) \
> +  SELFTEST_BEGIN_STMT						\
> +  ASSERT_EQ (TRISTATE, tristate (tristate::TS_UNKNOWN));	\
> +  SELFTEST_END_STMT
> +
> +/* Test tristate's ctors, along with is_*, as_string, operator==, and
> +   operator!=.  */
> +
> +static void
> +test_ctors ()
> +{
> +  tristate u (tristate::TS_UNKNOWN);
> +  ASSERT_FALSE (u.is_known ());
> +  ASSERT_FALSE (u.is_true ());
> +  ASSERT_FALSE (u.is_false ());
> +  ASSERT_STREQ (u.as_string (), "UNKNOWN");
> +
> +  tristate t (tristate::TS_TRUE);
> +  ASSERT_TRUE (t.is_known ());
> +  ASSERT_TRUE (t.is_true ());
> +  ASSERT_FALSE (t.is_false ());
> +  ASSERT_STREQ (t.as_string (), "TRUE");
> +
> +  tristate f (tristate::TS_FALSE);
> +  ASSERT_TRUE (f.is_known ());
> +  ASSERT_FALSE (f.is_true ());
> +  ASSERT_TRUE (f.is_false ());
> +  ASSERT_STREQ (f.as_string (), "FALSE");
> +
> +  ASSERT_EQ (u, u);
> +  ASSERT_EQ (t, t);
> +  ASSERT_EQ (f, f);
> +  ASSERT_NE (u, t);
> +  ASSERT_NE (u, f);
> +  ASSERT_NE (t, f);
> +
> +  tristate t2 (true);
> +  ASSERT_TRUE (t2.is_true ());
> +  ASSERT_EQ (t, t2);
> +
> +  tristate f2 (false);
> +  ASSERT_TRUE (f2.is_false ());
> +  ASSERT_EQ (f, f2);
> +
> +  tristate u2 (tristate::unknown ());
> +  ASSERT_TRUE (!u2.is_known ());
> +  ASSERT_EQ (u, u2);
> +}
> +
> +/* Test && on tristate instances.  */
> +
> +static void
> +test_and ()
> +{
> +  ASSERT_TRISTATE_UNKNOWN (tristate::unknown () && tristate::unknown ());
> +
> +  ASSERT_TRISTATE_FALSE (tristate (false) && tristate (false));
> +  ASSERT_TRISTATE_FALSE (tristate (false) && tristate (true));
> +  ASSERT_TRISTATE_FALSE (tristate (true) && tristate (false));
> +  ASSERT_TRISTATE_TRUE (tristate (true) && tristate (true));
> +
> +  ASSERT_TRISTATE_UNKNOWN (tristate::unknown () && tristate (true));
> +  ASSERT_TRISTATE_UNKNOWN (tristate (true) && tristate::unknown ());
> +
> +  ASSERT_TRISTATE_FALSE (tristate::unknown () && tristate (false));
> +  ASSERT_TRISTATE_FALSE (tristate (false) && tristate::unknown ());
> +}
> +
> +/* Test || on tristate instances.  */
> +
> +static void
> +test_or ()
> +{
> +  ASSERT_TRISTATE_UNKNOWN (tristate::unknown () || tristate::unknown ());
> +
> +  ASSERT_TRISTATE_FALSE (tristate (false) || tristate (false));
> +  ASSERT_TRISTATE_TRUE (tristate (false) || tristate (true));
> +  ASSERT_TRISTATE_TRUE (tristate (true) || tristate (false));
> +  ASSERT_TRISTATE_TRUE (tristate (true) || tristate (true));
> +
> +  ASSERT_TRISTATE_TRUE (tristate::unknown () || tristate (true));
> +  ASSERT_TRISTATE_TRUE (tristate (true) || tristate::unknown ());
> +
> +  ASSERT_TRISTATE_UNKNOWN (tristate::unknown () || tristate (false));
> +  ASSERT_TRISTATE_UNKNOWN (tristate (false) || tristate::unknown ());
> +}
> +
> +/* Test ! on tristate instances.  */
> +
> +static void
> +test_not ()
> +{
> +  ASSERT_TRISTATE_UNKNOWN (!tristate::unknown ());
> +  ASSERT_TRISTATE_FALSE (!tristate (true));
> +  ASSERT_TRISTATE_TRUE (!tristate (false));
> +}
> +
> +/* Run all of the selftests within this file.  */
> +
> +void
> +analyzer_tristate_cc_tests ()
> +{
> +  test_ctors ();
> +  test_and ();
> +  test_or ();
> +  test_not ();
> +}
> +
> +} // namespace selftest
> +
> +#endif /* CHECKING_P */
> diff --git a/gcc/analyzer/tristate.h b/gcc/analyzer/tristate.h
> new file mode 100644
> index 0000000..88b9657
> --- /dev/null
> +++ b/gcc/analyzer/tristate.h
> @@ -0,0 +1,82 @@
> +/* "True" vs "False" vs "Unknown".
> +   Copyright (C) 2019 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/>.  */
> +
> +#ifndef GCC_ANALYZER_TRISTATE_H
> +#define GCC_ANALYZER_TRISTATE_H
> +
> +/* "True" vs "False" vs "Unknown".  */

We've all needed or used a type like this a bunch of times, but
not necessarily to denote these three states.  If this becomes
a general-purpose utility I would welcome if it made it possible
to customize the meaning of the states.  If not all of them then
at least the third one.

I don't know if there is a class like this in the C++ Standard
Library but if not, Boost has a tribool class that does let
users redefine the third state.  It might be a model to consider.

Martin

> +
> +class tristate {
> + public:
> +  enum value {
> +    TS_UNKNOWN,
> +    TS_TRUE,
> +    TS_FALSE
> +  };
> +
> +  tristate (enum value val) : m_value (val) {}
> +  tristate (bool val) : m_value (val ? TS_TRUE : TS_FALSE) {}
> +  static tristate unknown () { return tristate (TS_UNKNOWN); }
> +
> +  const char *as_string () const;
> +
> +  bool is_known () const { return m_value != TS_UNKNOWN; }
> +  bool is_true () const { return m_value == TS_TRUE; }
> +  bool is_false () const { return m_value == TS_FALSE; }
> +
> +  tristate not_ () const;
> +  tristate or_ (tristate other) const;
> +  tristate and_ (tristate other) const;
> +
> +  bool operator== (const tristate &other) const

Consider making these operators non-members so that both

   TS_TRUE == state

and

   state == TS_TRUE

are valid.

Martin

> +  {
> +    return m_value == other.m_value;
> +  }
> +
> +  bool operator!= (const tristate &other) const
> +  {
> +    return m_value != other.m_value;
> +  }
> +
> + private:
> +  enum value m_value;
> +};
> +
> +/* Overloaded boolean operators on tristates.  */
> +
> +inline tristate
> +operator ! (tristate t)
> +{
> +  return t.not_ ();
> +}
> +
> +inline tristate
> +operator || (tristate a, tristate b)
> +{
> +  return a.or_ (b);
> +}
> +
> +inline tristate
> +operator && (tristate a, tristate b)
> +{
> +  return a.and_ (b);
> +}
> +
> +#endif /* GCC_ANALYZER_TRISTATE_H */
> 

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 03/49] diagnostic_show_locus: move initial newline to callers
  2019-12-07 14:30   ` Jeff Law
@ 2019-12-10  1:51     ` David Malcolm
  0 siblings, 0 replies; 160+ messages in thread
From: David Malcolm @ 2019-12-10  1:51 UTC (permalink / raw)
  To: law, gcc-patches

On Sat, 2019-12-07 at 07:30 -0700, Jeff Law wrote:
> On Fri, 2019-11-15 at 20:22 -0500, David Malcolm wrote:
> > diagnostic_show_locus adds a newline before doing anything
> > (including
> > the do-nothing-else case).
> > 
> > This patch removes this initial newline, adding it to all callers
> > of diagnostic_show_locus instead.
> > 
> > Doing so makes diagnostic_show_locus more flexible, allowing it to
> > be
> > used later in this patch kit when printing diagnostic paths.

[...]

> > OK
> jeff

FWIW I've fixed up the conflicts in this patch with Lewis's fix for PR
49973, retested the result, and committed it as r279152 (to reduce the
size of the next version of the analyzer patch kit).

Dave

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 28/49] analyzer: new files: analyzer.{cc|h}
  2019-11-16  1:18 ` [PATCH 28/49] analyzer: new files: analyzer.{cc|h} David Malcolm
  2019-12-07  3:38   ` Eric Gallager
  2019-12-07 15:02   ` Jeff Law
@ 2019-12-10 17:17   ` Martin Sebor
  2 siblings, 0 replies; 160+ messages in thread
From: Martin Sebor @ 2019-12-10 17:17 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On 11/15/19 6:23 PM, David Malcolm wrote:
> gcc/ChangeLog:
> 	* analyzer/analyzer.cc: New file.
> 	* analyzer/analyzer.h: New file.

Here are a few more comments/suggestions as I'm slowly making my way
through the patch, partly for my own benefit.  Hopefully they're at
least moderately useful.

> ---
>   gcc/analyzer/analyzer.cc | 125 ++++++++++++++++++++++++++++++++++++++++++++++
>   gcc/analyzer/analyzer.h  | 126 +++++++++++++++++++++++++++++++++++++++++++++++
>   2 files changed, 251 insertions(+)
>   create mode 100644 gcc/analyzer/analyzer.cc
>   create mode 100644 gcc/analyzer/analyzer.h
> 
> diff --git a/gcc/analyzer/analyzer.cc b/gcc/analyzer/analyzer.cc
> new file mode 100644
> index 0000000..399925c
> --- /dev/null
> +++ b/gcc/analyzer/analyzer.cc
> @@ -0,0 +1,125 @@
> +/* Utility functions for the analyzer.
> +   Copyright (C) 2019 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 "gcc-plugin.h"
> +#include "system.h"
> +#include "coretypes.h"
> +#include "tree.h"
> +#include "gimple.h"
> +#include "diagnostic.h"
> +#include "intl.h"
> +#include "analyzer/analyzer.h"
> +
> +/* Helper function for checkers.  Is the CALL to the given function name?  */
> +
> +bool
> +is_named_call_p (const gcall *call, const char *funcname)
> +{
> +  gcc_assert (funcname);
> +
> +  tree fndecl = gimple_call_fndecl (call);
> +  if (!fndecl)
> +    return false;
> +
> +  return 0 == strcmp (IDENTIFIER_POINTER (DECL_NAME (fndecl)), funcname);
> +}
> +
> +/* Helper function for checkers.  Is the CALL to the given function name,
> +   and with the given number of arguments?  */
> +
> +bool
> +is_named_call_p (const gcall *call, const char *funcname,
> +		 unsigned int num_args)
> +{
> +  gcc_assert (funcname);
> +
> +  if (!is_named_call_p (call, funcname))
> +    return false;
> +
> +  if (gimple_call_num_args (call) != num_args)
> +    return false;
> +
> +  return true;
> +}
> +
> +/* Return true if stmt is a setjmp call.  */
> +
> +bool
> +is_setjmp_call_p (const gimple *stmt)
> +{
> +  /* TODO: is there a less hacky way to check for "setjmp"?  */
> +  if (const gcall *call = dyn_cast <const gcall *> (stmt))
> +    if (is_named_call_p (call, "_setjmp", 1))
> +      return true;
> +
> +  return false;
> +}
> +
> +/* Return true if stmt is a longjmp call.  */
> +
> +bool
> +is_longjmp_call_p (const gcall *call)
> +{
> +  /* TODO: is there a less hacky way to check for "longjmp"?  */
> +  if (is_named_call_p (call, "longjmp", 2))
> +    return true;
> +
> +  return false;
> +}

I haven't actually needed these helper functions myself but they seem
quite generic and might make good additions to gimple.h.

> +
> +/* Generate a label_text instance by formatting FMT, using a
> +   temporary clone of the global_dc's printer (thus using its
> +   formatting callbacks).
> +
> +   Colorize if the global_dc supports colorization and CAN_COLORIZE is
> +   true.  */
> +
> +label_text
> +make_label_text (bool can_colorize, const char *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, fmt);
> +
> +  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;
> +}
> diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h
> new file mode 100644
> index 0000000..ace8924
> --- /dev/null
> +++ b/gcc/analyzer/analyzer.h
> @@ -0,0 +1,126 @@
> +/* Utility functions for the analyzer.
> +   Copyright (C) 2019 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/>.  */
> +
> +#ifndef GCC_ANALYZER_ANALYZER_H
> +#define GCC_ANALYZER_ANALYZER_H
> +
> +/* Forward decls of common types, with indentation to show inheritance.  */
> +
> +class graphviz_out;
> +class supergraph;
> +class supernode;
> +class superedge;
> +  class cfg_superedge;
> +    class switch_cfg_superedge;
> +  class callgraph_superedge;
> +    class call_superedge;
> +    class return_superedge;
> +class svalue;
> +  class region_svalue;
> +  class constant_svalue;
> +  class poisoned_svalue;
> +  class unknown_svalue;
> +  class setjmp_svalue;
> +class region;
> +  class map_region;
> +  class symbolic_region;
> +class region_model;
> +class region_model_context;
> +  class impl_region_model_context;
> +class constraint_manager;
> +class equiv_class;
> +struct model_merger;
> +struct svalue_id_merger_mapping;
> +struct canonicalization;
> +class pending_diagnostic;
> +class checker_path;
> +class extrinsic_state;
> +class sm_state_map;
> +class stmt_finder;
> +class program_point;
> +class program_state;
> +class exploded_graph;
> +class exploded_node;
> +class exploded_edge;
> +class exploded_cluster;
> +class exploded_path;
> +class analysis_plan;
> +class state_purge_map;
> +class state_purge_per_ssa_name;
> +class state_change;
> +
> +////////////////////////////////////////////////////////////////////////////
> +
> +extern bool is_named_call_p (const gcall *call, const char *funcname);
> +extern bool is_named_call_p (const gcall *call, const char *funcname,
> +			     unsigned int num_args);
> +extern bool is_setjmp_call_p (const gimple *stmt);
> +extern bool is_longjmp_call_p (const gcall *call);

There are functions in GCC that match either is_xxx() and xxx_p() but
by in my experience (and now also by my count) the latter is far more
prevalent: 538 vs 3,172.  I count just 28 functions that combine
the two into is_xxx_p().

I think it would make sense to choose either of the two dominant forms
over the is_xxx_p() combination here.  (Ideally, there'd be just one
style to make it easy to remember.)

> +
> +extern void register_analyzer_pass ();
> +
> +extern label_text make_label_text (bool can_colorize, const char *fmt, ...);
> +
> +////////////////////////////////////////////////////////////////////////////
> +
> +/* An RAII-style class for pushing/popping cfun within a scope.
> +   Doing so ensures we get "In function " announcements
> +   from the diagnostics subsystem.  */
> +
> +class auto_cfun
> +{
> +public:
> +  auto_cfun (function *fun) { push_cfun (fun); }
> +  ~auto_cfun () { pop_cfun (); }
> +};

This also seems like a nice general-purpose utility that might be
worth adding someplace more central.  I thought I'd seen code like
this in the compiler already but all I can find now is a plenty of
calls to push_cfun and but just one to pop.

> +
> +////////////////////////////////////////////////////////////////////////////
> +
> +/* Begin suppressing -Wformat and -Wformat-extra-args.  */
> +
> +#define PUSH_IGNORE_WFORMAT \
> +  _Pragma("GCC diagnostic push") \
> +  _Pragma("GCC diagnostic ignored \"-Wformat\"") \
> +  _Pragma("GCC diagnostic ignored \"-Wformat-extra-args\"")
> +
> +/* Finish suppressing -Wformat and -Wformat-extra-args.  */
> +
> +#define POP_IGNORE_WFORMAT \
> +  _Pragma("GCC diagnostic pop")
> +
> +////////////////////////////////////////////////////////////////////////////
> +
> +/* A template for creating hash traits for a POD type.  */
> +
> +template <typename Type>
> +struct pod_hash_traits : typed_noop_remove<Type>

Would it make sense to add this to hash-traits.h?

Martin


> +{
> +  typedef Type value_type;
> +  typedef Type compare_type;
> +  static inline hashval_t hash (value_type);
> +  static inline bool equal (const value_type &existing,
> +			    const value_type &candidate);
> +  static inline void mark_deleted (Type &);
> +  static inline void mark_empty (Type &);
> +  static inline bool is_deleted (Type);
> +  static inline bool is_empty (Type);
> +};
> +
> +#endif /* GCC_ANALYZER_ANALYZER_H */
> 

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 28/49] analyzer: new files: analyzer.{cc|h}
  2019-12-09 23:22     ` David Malcolm
@ 2019-12-10 19:59       ` Eric Gallager
  0 siblings, 0 replies; 160+ messages in thread
From: Eric Gallager @ 2019-12-10 19:59 UTC (permalink / raw)
  To: David Malcolm; +Cc: gcc-patches

On 12/9/19, David Malcolm <dmalcolm@redhat.com> wrote:
> On Fri, 2019-12-06 at 22:38 -0500, Eric Gallager wrote:
>> On 11/15/19, David Malcolm <dmalcolm@redhat.com> wrote:
> [...]
>> > diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h
>> > new file mode 100644
>> > index 0000000..ace8924
>> > --- /dev/null
>> > +++ b/gcc/analyzer/analyzer.h
>> > @@ -0,0 +1,126 @@
>> > +/* Utility functions for the analyzer.
>> > +   Copyright (C) 2019 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/>;.  */
>> > +
>> > +#ifndef GCC_ANALYZER_ANALYZER_H
>> > +#define GCC_ANALYZER_ANALYZER_H
>> > +
>> > +/* Forward decls of common types, with indentation to show
>> > inheritance. */
>>
>> I'm wondering about the "with indentation to show inheritance"
>> part...
>> does that require tweaking any editor configuration files or adding
>> /*INDENT-OFF*/ comments or anything to prevent automatic formatting
>> tools from "fixing" the indentation to go back to the normal style of
>> having everything be aligned?
>
> If we had some kind of automatic formatting then I guess it would, but
> I don't think we have such a system in place.
>

Check the contrib directory; there's a clang-format file and a vimrc
file in there that provide automatic formatting; do `make vimrc` and
`make clang-format` from the top-level to use them. There's also the
check_GNU_style scripts, but those just check & don't actually
reformat, AFAIK...

> [...]
>
>

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 33/49] analyzer: new files: sm.{cc|h}
  2019-11-16  1:18 ` [PATCH 33/49] analyzer: new files: sm.{cc|h} David Malcolm
@ 2019-12-11 19:24   ` Jeff Law
  2020-01-10  2:28     ` David Malcolm
  0 siblings, 1 reply; 160+ messages in thread
From: Jeff Law @ 2019-12-11 19:24 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> This patch adds a "state_machine" base class for describing
> API checkers in terms of state machine transitions.  Followup
> patches use this to add specific API checkers.
> 
> gcc/ChangeLog:
> 	* analyzer/sm.cc: New file.
> 	* analyzer/sm.h: New file.
> ---
>  gcc/analyzer/sm.cc | 135
> ++++++++++++++++++++++++++++++++++++++++++++
>  gcc/analyzer/sm.h  | 160
> +++++++++++++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 295 insertions(+)
>  create mode 100644 gcc/analyzer/sm.cc
>  create mode 100644 gcc/analyzer/sm.h
> 
> diff --git a/gcc/analyzer/sm.cc b/gcc/analyzer/sm.cc
> new file mode 100644
> index 0000000..eda9350
> --- /dev/null
> +++ b/gcc/analyzer/sm.cc
> @@ -0,0 +1,135 @@
> +/* Modeling API uses and misuses via state machines.
> +   Copyright (C) 2019 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 "gcc-plugin.h"
> +#include "system.h"
> +#include "coretypes.h"
> +#include "tree.h"
> +#include "gimple.h"
> +#include "analyzer/analyzer.h"
> +#include "analyzer/sm.h"
> +
> +////////////////////////////////////////////////////////////////////
> ////////
> +
> +/* If STMT is an assignment to zero, return the LHS.  */
> +
> +tree
> +is_zero_assignment (const gimple *stmt)
> +{
> +  const gassign *assign_stmt = dyn_cast <const gassign *> (stmt);
> +  if (!assign_stmt)
> +    return NULL_TREE;
> +
> +  enum tree_code op = gimple_assign_rhs_code (assign_stmt);
> +  if (op != INTEGER_CST)
> +    return NULL_TREE;
> +
> +  if (!zerop (gimple_assign_rhs1 (assign_stmt)))
> +    return NULL_TREE;
> +
> +  return gimple_assign_lhs (assign_stmt);
> +}
"assignment from zero" rather than "assignment to zero" I think.

I think you'd want to use "integer_zerop" rather than an open-coded
check.  That'll also allow you to pick up other forms such as
COMPLEX_CST and VECTOR_CST.




> +
> +/* If COND_STMT is a comparison against zero of the form (LHS OP 0),
> +   return true and write what's being compared to *OUT_LHS and the
> kind of
> +   the comparison to *OUT_OP.  */
> +
> +bool
> +is_comparison_against_zero (const gcond *cond_stmt,
> +			    tree *out_lhs, enum tree_code *out_op)
> +{
> +  enum tree_code op = gimple_cond_code (cond_stmt);
> +  tree lhs = gimple_cond_lhs (cond_stmt);
> +  tree rhs = gimple_cond_rhs (cond_stmt);
> +  if (!zerop (rhs))
> +    return false;
> +  // TODO: make it symmetric?
> +
> +  switch (op)
> +    {
> +    case NE_EXPR:
> +    case EQ_EXPR:
> +      *out_lhs = lhs;
> +      *out_op = op;
> +      return true;
> +
> +    default:
> +      return false;
> +    }
> +}
Seems like this might be useful to make generically available.




> +
> +bool
> +any_pointer_p (tree var)
> +{
> +  if (TREE_CODE (TREE_TYPE (var)) != POINTER_TYPE)
> +    return false;
> +
> +  return true;
> +}
I believe what you really want is POINTER_TYPE_P which will also happen
to help C++ since it'll include things that are REFERENCE_TYPEs.


It looks like some of the methods don't have comments on their
definitions.  Please check those and add them as necessary.

Otherwise it looks reasonable.

jeff

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 40/49] analyzer: new files: call-string.{cc|h}
  2019-11-16  1:21 ` [PATCH 40/49] analyzer: new files: call-string.{cc|h} David Malcolm
@ 2019-12-11 19:28   ` Jeff Law
  0 siblings, 0 replies; 160+ messages in thread
From: Jeff Law @ 2019-12-11 19:28 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> This patch adds call_string, a class for representing the
> call stacks at a program_point, so that we can ensure that
> paths through the code are interprocedurally valid.
> 
> gcc/ChangeLog:
> 	* analyzer/call-string.cc: New file.
> 	* analyzer/call-string.h: New file.
Looks reasonable.  THere may be some scalability issues in here, but I
guess with all the work you've been doing any particularly bad ones for
the examples you're working with have been fixed.

jeff
> 

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 28/49] analyzer: new files: analyzer.{cc|h}
  2019-12-07 15:02   ` Jeff Law
@ 2019-12-11 19:49     ` David Malcolm
  2019-12-20  1:21       ` [PATCH 0/4] analyzer: add class function_set and use in various places David Malcolm
  0 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-12-11 19:49 UTC (permalink / raw)
  To: law, gcc-patches

On Sat, 2019-12-07 at 08:01 -0700, Jeff Law wrote:
> On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
[...]
> > diff --git a/gcc/analyzer/analyzer.cc b/gcc/analyzer/analyzer.cc
> > new file mode 100644
> > index 0000000..399925c
> > --- /dev/null
> > +++ b/gcc/analyzer/analyzer.cc
[...]
> > +
> > +/* Helper function for checkers.  Is the CALL to the given
> > function
> > name?  */
> > +
> > +bool
> > +is_named_call_p (const gcall *call, const char *funcname)
> > +{
> > +  gcc_assert (funcname);
> > +
> > +  tree fndecl = gimple_call_fndecl (call);
> > +  if (!fndecl)
> > +    return false;
> > +
> > +  return 0 == strcmp (IDENTIFIER_POINTER (DECL_NAME (fndecl)),
> > funcname);
> > +}
> > +
> > +/* Helper function for checkers.  Is the CALL to the given
> > function
> > name,
> > +   and with the given number of arguments?  */
> > +
> > +bool
> > +is_named_call_p (const gcall *call, const char *funcname,
> > +		 unsigned int num_args)
> > +{
> > +  gcc_assert (funcname);
> > +
> > +  if (!is_named_call_p (call, funcname))
> > +    return false;
> > +
> > +  if (gimple_call_num_args (call) != num_args)
> > +    return false;
> > +
> > +  return true;
> These seem generic enough to live elsewhere.

There's a potential can of worms here: these functions are used by the
checkers to detect fndecls by name, so that checkers can associate
behavior with them.  Examples:

* sm-malloc.c recognizes "malloc" and "free" as being special

* sm-signal.c knows that fprintf is async-signal-unsafe (but currently
doesn't know about any other fns; it's a proof-of-concept)

* sm-file.c currently doesn't know anything about "__fsetlocking",
which is one of the reasons the analyzer currently doesn't detect the
leak in intl/localealias.c reported as PR 47170, as it assumes that the
FILE * might have been closed and stops tracking state for it.

etc.

So we're going to have a lot more "recognize this function by name"
just to finish the existing proof-of-concept checkers.


In a perfect world, perhaps we'd have attributes for all of this, and
the user's code and their system headers would have dutifully marked
the pertinent decls with attributes, and the use of is_named_call_p
could be thought of as a bug (or wart)...  but we don't live in that
perfect world, and a good static analyzer shouldn't need to rely on the
user marking their code.

There's also integration and chicken-and-egg issues with attributes
where if we rely e.g. on the user's libc headers having attributes for
the checker, then we need to coordinate with e.g. glibc to add the
attributes, and implement them, and then the checker doesn't work if
someone is using a different libc, etc, etc.

So I think we want a concept of "known functions" in the analyzer,
where the analyzer can have its own "on the side" model of APIs -
perhaps a mixture of baked-in (e.g. for malloc/free), perhaps from an
overridable config file, but I'm not quite sure what form it should
take.  Maybe even a pragma that lets us tag named functions with
attributes, or somesuch.

For now, however, I want to take the pragmatic approach, and use these
functions as needed (and to review this as we gain experience with the
analyzer).

So I think I prefer to keep them in the analyzer subdir (but I'm happy
to move them if you'd prefer that)

Does the above sound sane?


> > +}
> > +
> > +/* Return true if stmt is a setjmp call.  */
> > +
> > +bool
> > +is_setjmp_call_p (const gimple *stmt)
> > +{
> > +  /* TODO: is there a less hacky way to check for "setjmp"?  */
> > +  if (const gcall *call = dyn_cast <const gcall *> (stmt))
> > +    if (is_named_call_p (call, "_setjmp", 1))
> > +      return true;
> > +
> > +  return false;
> > +}
> > +
> > +/* Return true if stmt is a longjmp call.  */
> > +
> > +bool
> > +is_longjmp_call_p (const gcall *call)
> > +{
> > +  /* TODO: is there a less hacky way to check for "longjmp"?  */
> > +  if (is_named_call_p (call, "longjmp", 2))
> > +    return true;
> > +
> > +  return false;
> > +}
> I thought we already had routines to detect these.  We certainly have
> *code* to detect them.   If it's the former we really just want one
> implementation (that hands the various permutations we've seen
> through
> the years).  If it's the latter, then we probably want to use these
> routines to simplify those implementations.

We have several:

* calls.c has:
  * setjmp_call_p (const_tree fndecl)
  * special_function_p which does name matching on "setjmp",
"sigsetjmp", "savectx", "vfork", "getcontext" and sets
ECF_RETURNS_TWICE (dropping leading single and double underscores)
* except.c has "tree setjmp_fn;" used with #ifdef
DONT_USE_BUILTIN_SETJMP
* omp-low.c has: setjmp_or_longjmp_p (const_tree fndecl)

(and maybe more).

I reviewed where I'm using the two the patch proposed adding above:

* I use is_setjmp_call_p in diagnostic_manager::add_events_for_eedge
for PK_BEFORE_STMT on the gcall to capture recording the setjmp buf (so
that the event can be cross-referenced at the longjmp call), and in
engine.cc for identifying that initial "store" call

* I use is_longjmp_call_p in only one place, in engine.cc, for
detecting the fn and handling the non-standard control flow

As for the other functions identified in calls.c special_function_p:
* "sigsetjmp": looks like I can generalize my code to handle this
* "savectx": https://gcc.gnu.org/bugzilla/show_bug.cgi?id=71876
suggests it's Solaris-specific.  I have no idea of the signature or semantics, so I'm inclined not to handle it in the analyzer.
* "vfork": appears to me to be an old performance hack from before COW
pages.  Semantics appear sufficiently different from setjmp/longjmp
that it would need its own special-casing (and isn't a priority for me)
* "getcontext"/"setcontext": have slightly different semantics to
setjmp/longjmp (e.g. no val is passed like for longjmp; they can fail
and set errno); again, would need their own special-casing, and again,
these are not a priority for me)

I think the requirements for the analyzer here differ from that of the
rest of the compiler: the analyzer needs to capture semantics of the
fns in terms of the effects on states and paths, which is rather
different to that needed by code generation.  For example the
analyzer's special-casing captures subtleties like "Passing 0 as the
val to longjmp leads to setjmp returning 1".

So I'm thinking that for the next iteration of the kit I'll drop these
two functions in favor of:

  setjmp_like_p
  longjmp_like_p

to detect setjmp/sigsetjmp  and longjmp/siglongjmp, specifically for
the analyzer, so that it can trigger the special-casing for these.

Does that sound reasonable?

> > +/* Generate a label_text instance by formatting FMT, using a
> > +   temporary clone of the global_dc's printer (thus using its
> > +   formatting callbacks).
> > +
> > +   Colorize if the global_dc supports colorization and
> > CAN_COLORIZE
> > is
> > +   true.  */
> > +
> > +label_text
> > +make_label_text (bool can_colorize, const char *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, fmt);
> > +
> > +  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;
> > +}
> Is this better in the pretty-printer infrastructure?  

This touches on another can of worms... it's rather clunky to be
cloning the printer and using it to generate a temporary string.  I've
been thinking that maybe diagnostic_event::get_desc should instead
print to a pp, which might avoid a lot of printer cloning, but I've
attempted that before, unsuccessfully.  I guess I'll try again; it
feels cleaner (I think it required cloning the printer if we're going
to use it from within a rich_location label, since otherwise the
printing of the description interferes with that of
diagnostic_show_locus, and we end up with garbled text).

Dave

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 41/49] analyzer: new files: program-point.{cc|h}
  2019-11-16  1:21 ` [PATCH 41/49] analyzer: new files: program-point.{cc|h} David Malcolm
@ 2019-12-11 19:54   ` Jeff Law
  2019-12-11 19:58     ` David Malcolm
  0 siblings, 1 reply; 160+ messages in thread
From: Jeff Law @ 2019-12-11 19:54 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> This patch introduces function_point and program_point, classes
> for tracking locations within the program (the latter adding
> a call_string for tracking interprocedural location).
> 
> gcc/ChangeLog:
> 	* analyzer/program-point.cc: New file.
> 	* analyzer/program-point.h: New file.
> ---
> 
> 
> diff --git a/gcc/analyzer/program-point.h b/gcc/analyzer/program-
> point.h
> new file mode 100644
> index 0000000..ad7b9cd
> --- /dev/null
> +++ b/gcc/analyzer/program-point.h
> @@ -0,0 +1,316 @@
> +/* Classes for representing locations within the program.
> +   Copyright (C) 2019 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/>;.  */
> +
> +#ifndef GCC_ANALYZER_PROGRAM_POINT_H
> +#define GCC_ANALYZER_PROGRAM_POINT_H
> +
> +#include "analyzer/call-string.h"
> +#include "analyzer/supergraph.h"
> +
> +class exploded_graph;
> +
> +/* An enum for distinguishing the various kinds of
> program_point.  */
> +
> +enum point_kind {
> +  /* A "fake" node which has edges to all entrypoints.  */
> +  PK_ORIGIN,
> +
> +  PK_BEFORE_SUPERNODE,
> +  PK_BEFORE_STMT,
> +  PK_AFTER_SUPERNODE,
> +
> +  /* Special values used for hash_map:  */
> +  PK_EMPTY,
> +  PK_DELETED,
> +
> +  NUM_POINT_KINDS
> +};
Isn't this the cause of the hash_map stuff we're discussing with
Martin?  (PK_EMPTY is a non-zero value)?

Regardless, I don't see anything there to object to.  We have to nail
down the hash_map issues though.

jeff

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 41/49] analyzer: new files: program-point.{cc|h}
  2019-12-11 19:54   ` Jeff Law
@ 2019-12-11 19:58     ` David Malcolm
  0 siblings, 0 replies; 160+ messages in thread
From: David Malcolm @ 2019-12-11 19:58 UTC (permalink / raw)
  To: law, gcc-patches

On Wed, 2019-12-11 at 12:54 -0700, Jeff Law wrote:
> On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> > This patch introduces function_point and program_point, classes
> > for tracking locations within the program (the latter adding
> > a call_string for tracking interprocedural location).
> > 
> > gcc/ChangeLog:
> > 	* analyzer/program-point.cc: New file.
> > 	* analyzer/program-point.h: New file.
> > ---
> > 
> > 
> > diff --git a/gcc/analyzer/program-point.h b/gcc/analyzer/program-
> > point.h
> > new file mode 100644
> > index 0000000..ad7b9cd
> > --- /dev/null
> > +++ b/gcc/analyzer/program-point.h
> > @@ -0,0 +1,316 @@
> > +/* Classes for representing locations within the program.
> > +   Copyright (C) 2019 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/>;;.  */
> > +
> > +#ifndef GCC_ANALYZER_PROGRAM_POINT_H
> > +#define GCC_ANALYZER_PROGRAM_POINT_H
> > +
> > +#include "analyzer/call-string.h"
> > +#include "analyzer/supergraph.h"
> > +
> > +class exploded_graph;
> > +
> > +/* An enum for distinguishing the various kinds of
> > program_point.  */
> > +
> > +enum point_kind {
> > +  /* A "fake" node which has edges to all entrypoints.  */
> > +  PK_ORIGIN,
> > +
> > +  PK_BEFORE_SUPERNODE,
> > +  PK_BEFORE_STMT,
> > +  PK_AFTER_SUPERNODE,
> > +
> > +  /* Special values used for hash_map:  */
> > +  PK_EMPTY,
> > +  PK_DELETED,
> > +
> > +  NUM_POINT_KINDS
> > +};
> Isn't this the cause of the hash_map stuff we're discussing with
> Martin?  (PK_EMPTY is a non-zero value)?

Well spotted - the one in sm_state_map in program-state.cc was the one
that I ran into, as it broke a selftest (turning it into an infinite
loop).  But I guess these could be reordered to put PK_EMPTY at the
top, if we're going to require or specialize for that.

> Regardless, I don't see anything there to object to.  We have to nail
> down the hash_map issues though.

Indeed


Dave

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 43/49] analyzer: new file: exploded-graph.h
  2019-11-16  1:21 ` [PATCH 43/49] analyzer: new file: exploded-graph.h David Malcolm
@ 2019-12-11 20:04   ` Jeff Law
  2019-12-12 15:29     ` David Malcolm
  2020-01-10  2:41     ` David Malcolm
  0 siblings, 2 replies; 160+ messages in thread
From: Jeff Law @ 2019-12-11 20:04 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> This patch adds exploded_graph and related classes, for managing
> exploring paths through the user's code as a directed graph
> of <point, state> pairs.
> 
> gcc/ChangeLog:
> 	* analyzer/exploded-graph.h: New file.
> ---
>  gcc/analyzer/exploded-graph.h | 754
> ++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 754 insertions(+)
>  create mode 100644 gcc/analyzer/exploded-graph.h
> 
> diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-
> graph.h
> new file mode 100644
> index 0000000..f97d2b6
> --- /dev/null
> +++ b/gcc/analyzer/exploded-graph.h
> @@ -0,0 +1,754 @@
> +/* Classes for managing a directed graph of <point, state> pairs.
> +   Copyright (C) 2019 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/>;.  */
> +
> +#ifndef GCC_ANALYZER_EXPLODED_GRAPH_H
> +#define GCC_ANALYZER_EXPLODED_GRAPH_H
> +
> +#include "fibonacci_heap.h"
> +#include "analyzer/analyzer-logging.h"
> +#include "analyzer/constraint-manager.h"
> +#include "analyzer/diagnostic-manager.h"
> +#include "analyzer/program-point.h"
> +#include "analyzer/program-state.h"
> +#include "analyzer/shortest-paths.h"
> +
> +////////////////////////////////////////////////////////////////////
NIT.  Is there some reason you don't just use whitespace for these kind
of vertical separators.  THe more I see them, the more they bug me,
probably because that's not been the style we've used for GCC.



> ///////
> +
> +/* Concrete implementation of region_model_context, wiring it up to
> the
> +   rest of the analysis engine.  */
> +
> +class impl_region_model_context : public region_model_context,
> public log_user
Multiple inheritance?  Is it really needed?  Can we handle via
composition instead?



> +/* A <program_point, program_state> pair, used internally by
> +   exploded_node as its immutable data, and as a key for identifying
> +   exploded_nodes we've already seen in the graph.  */
> +
> +struct point_and_state
Shouldn't this be a class?

+
> +/* Per-program_point data for an exploded_graph.  */
> +
> +struct per_program_point_data
Here too?  THere may be others.  I'd suggest reviewing all your
"structs" and determine if we're better off calling them "class".  I'm
not going to insist on it though since I think the last discussion in
this space was to relax the conventions :-)

> +
> +class exploded_graph : public digraph<eg_traits>, public log_user
Multiple inheritance again?

Jeff

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 00/49] RFC: Add a static analysis framework to GCC
  2019-12-09  8:10     ` Richard Biener
@ 2019-12-11 20:11       ` David Malcolm
  0 siblings, 0 replies; 160+ messages in thread
From: David Malcolm @ 2019-12-11 20:11 UTC (permalink / raw)
  To: Richard Biener, Jeff Law; +Cc: Martin Sebor, GCC Patches

On Mon, 2019-12-09 at 09:10 +0100, Richard Biener wrote:
> On Fri, Dec 6, 2019 at 11:31 PM Jeff Law <law@redhat.com> wrote:
> > On Wed, 2019-12-04 at 12:55 -0700, Martin Sebor wrote:
> > > On 11/15/19 6:22 PM, David Malcolm wrote:
> > > > This patch kit introduces a static analysis pass for GCC that
> > > > can
> > > > diagnose
> > > > various kinds of problems in C code at compile-time (e.g.
> > > > double-
> > > > free,
> > > > use-after-free, etc).
> > > 
> > > I haven't looked at the analyzer bits in any detail yet so I have
> > > just some very high-level questions.  But first let me say I'm
> > > excited to see this project! :)
> > > 
> > > It looks like the analyzer detects some of the same problems as
> > > some existing middle-end warnings (e.g., -Wnonnull,
> > > -Wuninitialized),
> > > and some that I have been working toward implementing (invalid
> > > uses
> > > of freed pointers such as returning them from functions or
> > > passing
> > > them to others), and others still that I have been thinking about
> > > as possible future projects (e.g., detecting uses of
> > > uninitialized
> > > arrays in string functions).
> > > 
> > > What are your thoughts about this sort of overlap?  Do you expect
> > > us to enhance both sets of warnings in parallel, or do you see us
> > > moving away from issuing warnings in the middle-end and toward
> > > making the analyzer the main source of these kinds of
> > > diagnostics?
> > > Maybe even replace some of the problematic middle-end warnings
> > > with the analyzer?  What (if anything) should we do about
> > > warnings
> > > issued for the same problems by both the middle-end and the
> > > analyzer?
> > > Or about false negatives?  E.g., a bug detected by the middle-end
> > > but not the analyzer or vice versa.
> > > 
> > > What do you see as the biggest pros and cons of either approach?
> > > (Middle-end vs analyzer.)  What limitations is the analyzer
> > > approach inherently subject to that the middle-end warnings
> > > aren't,
> > > and vice versa?
> > > 
> > > How do we prioritize between the two approaches (e.g., choose
> > > where to add a new warning)?
> > Given the cost of David's analyzer, I would tend to prioritize the
> > more
> > localized analysis.  Also note that because of the compile-time
> > complexities we end up pruning paths from the search space and lose
> > precision when we have to merge nodes.   These issues are inherent
> > in
> > the depth of analysis we're looking to do.
> > 
> > So the way to think about things is David's work is a slower,
> > deeper
> > analysis than what we usually do.  So things that are reasonable
> > candidates for -Wall would need to use the traditional mechansisms.
> > Things that require deeper analysis would be done in David's
> > framework.
> > 
> > Also note that part of David's work is to bring a fairly generic
> > engine
> > that we can expand with different domain specific analyzers.  It
> > just
> > happens to be the case that the first place he's focused is on
> > double-
> > free and use-after-free.  But (IMHO) the gem is really the generic
> > engine.
> 
> So if the "generic engine" lives inside GCC can the actual analyzers
> be plugins on a (stable) "analyzer plugin API"?

I like the idea of having plugins be able to support the analyzer
itself, so that new checkers can be registered by a plugin, analogous
to plugins that register new passes.  AIUI the clang static analyzer
works in such a fashion.

However, speaking to the "(stable)" part of your question: to do
anything useful, the checkers have to query GCC's IR (as well as
interact with the state of the analyzer), and so this reopens the
question of what the plugin API to GCC's IR is.

I'm focusing on building a concrete example of a checker (double-free)
and a few other examples; trying to generalize it into something
pluggable feels very much like something not to attempt in the initial
version.

> Does the analyzer work with LTO at whole-program scope btw?

My understanding of LTO is a little hazy, but yes, I think.

The first thing the analyzer does (in engine.cc) is:

  /* If using LTO, ensure that the cgraph nodes have function bodies.  */
  cgraph_node *node;
  FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
    node->get_untransformed_body ();

before then building a "supergraph" that combines CFGs and the callgraph.

BTW, for more on implementation details, prebuilt HTML of the internal
docs are at:
https://dmalcolm.fedorapeople.org/gcc/static-analyzer/gccint/Static-Analyzer.html

Dave

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 44/49] analyzer: new files: state-purge.{cc|h}
  2019-11-16  1:21 ` [PATCH 44/49] analyzer: new files: state-purge.{cc|h} David Malcolm
@ 2019-12-11 20:11   ` Jeff Law
  0 siblings, 0 replies; 160+ messages in thread
From: Jeff Law @ 2019-12-11 20:11 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> This patch adds classes for tracking what state can be safely purged
> at any given point in the program.
> 
> gcc/ChangeLog:
> 	* analyzer/state-purge.cc: New file.
> 	* analyzer/state-purge.h: New file.
> ---
>  gcc/analyzer/state-purge.cc | 516
> ++++++++++++++++++++++++++++++++++++++++++++
>  gcc/analyzer/state-purge.h  | 170 +++++++++++++++
>  2 files changed, 686 insertions(+)
>  create mode 100644 gcc/analyzer/state-purge.cc
>  create mode 100644 gcc/analyzer/state-purge.h

> +
> +/* state_purge_map's ctor.  Walk all SSA names in all functions,
> building
> +   a state_purge_per_ssa_name instance for each.  */
> +
> +state_purge_map::state_purge_map (const supergraph &sg,
> +				  logger *logger)
> +: log_user (logger), m_sg (sg)
> +{
> +  LOG_FUNC (logger);
> +
> +  auto_client_timevar tv ("state_purge_map ctor");
> +
> +  cgraph_node *node;
> +  FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
> +  {
> +    function *fun = node->get_fun ();
> +    if (logger)
> +      log ("function: %s", function_name (fun));
> +    //printf ("function: %s\n", function_name (fun));
Debugging leftover.  Kill it.

Otherwise it seems pretty reasonable.

jeff

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 47/49] analyzer: new files: diagnostic-manager.{cc|h}
  2019-11-16  1:21 ` [PATCH 47/49] analyzer: new files: diagnostic-manager.{cc|h} David Malcolm
@ 2019-12-11 20:42   ` Jeff Law
  2019-12-11 21:11     ` David Malcolm
  0 siblings, 1 reply; 160+ messages in thread
From: Jeff Law @ 2019-12-11 20:42 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> This patch adds diagnostic_manager and related support classes for
> saving, deduplicating, and emitting analyzer diagnostics.
> 
> gcc/ChangeLog:
> 	* analyzer/diagnostic-manager.cc: New file.
> 	* analyzer/diagnostic-manager.h: New file.
I was originally going to suggest we look to bring this out of the
analyzer subdir, but it looks like there's a lot of tie-ins to the
static analyzer, so let's not try that right now.


> 
> +
> +/* Prune PATH, based on the verbosity level, to the most pertinent
> +   events for a diagnostic that involves VAR ending in state STATE
> +   (for state machine SM).
> +
> +   PATH is updated in place, and the redundant checker_events are
> deleted.
> +
> +   As well as deleting events, call record_critical_state on events
> in
> +   which state critical to the pending_diagnostic is being handled,
> so
> +   that the event's get_desc vfunc can potentially supply a more
> precise
> +   description of the event to the user.
> +   e.g. improving
> +     "calling 'foo' from 'bar'"
> +   to
> +     "passing possibly-NULL pointer 'ptr' to 'foo' from 'bar' as
> param 1"
> +   when the diagnostic relates to later dereferencing 'ptr'.  */
> +
> +void
> +diagnostic_manager::prune_path (checker_path *path,
> +				const state_machine *sm,
> +				tree var,
> +				state_machine::state_t state) const
You might consider breaking this up a bit.  I guess it stands out
because it's one of the few places (so far) where the function won't
fit in my portrait window :-)


Jeff

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 46/49] analyzer: new files: checker-path.{cc|h}
  2019-11-16  1:21 ` [PATCH 46/49] analyzer: new files: checker-path.{cc|h} David Malcolm
@ 2019-12-11 20:50   ` Jeff Law
  0 siblings, 0 replies; 160+ messages in thread
From: Jeff Law @ 2019-12-11 20:50 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> This patch adds a family of classes for representing paths of events
> for analyzer diagnostics.
> 
> gcc/ChangeLog:
> 	* analyzer/checker-path.cc: New file.
> 	* analyzer/checker-path.h: New file.
> 
> 
No significant concerns here.  My brain is turning to mush.  That's
probably it for today.

jeff

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 47/49] analyzer: new files: diagnostic-manager.{cc|h}
  2019-12-11 20:42   ` Jeff Law
@ 2019-12-11 21:11     ` David Malcolm
  0 siblings, 0 replies; 160+ messages in thread
From: David Malcolm @ 2019-12-11 21:11 UTC (permalink / raw)
  To: law, gcc-patches

On Wed, 2019-12-11 at 13:42 -0700, Jeff Law wrote:
> On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> > This patch adds diagnostic_manager and related support classes for
> > saving, deduplicating, and emitting analyzer diagnostics.
> > 
> > gcc/ChangeLog:
> > 	* analyzer/diagnostic-manager.cc: New file.
> > 	* analyzer/diagnostic-manager.h: New file.
> I was originally going to suggest we look to bring this out of the
> analyzer subdir, but it looks like there's a lot of tie-ins to the
> static analyzer, so let's not try that right now.
> 
> 
> > +
> > +/* Prune PATH, based on the verbosity level, to the most pertinent
> > +   events for a diagnostic that involves VAR ending in state STATE
> > +   (for state machine SM).
> > +
> > +   PATH is updated in place, and the redundant checker_events are
> > deleted.
> > +
> > +   As well as deleting events, call record_critical_state on
> > events
> > in
> > +   which state critical to the pending_diagnostic is being
> > handled,
> > so
> > +   that the event's get_desc vfunc can potentially supply a more
> > precise
> > +   description of the event to the user.
> > +   e.g. improving
> > +     "calling 'foo' from 'bar'"
> > +   to
> > +     "passing possibly-NULL pointer 'ptr' to 'foo' from 'bar' as
> > param 1"
> > +   when the diagnostic relates to later dereferencing 'ptr'.  */
> > +
> > +void
> > +diagnostic_manager::prune_path (checker_path *path,
> > +				const state_machine *sm,
> > +				tree var,
> > +				state_machine::state_t state) const
> You might consider breaking this up a bit.  I guess it stands out
> because it's one of the few places (so far) where the function won't
> fit in my portrait window :-)

Thanks.  Have done so locally, so will be in next iteration once I
finish squashing everything.

Dave

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 43/49] analyzer: new file: exploded-graph.h
  2019-12-11 20:04   ` Jeff Law
@ 2019-12-12 15:29     ` David Malcolm
  2019-12-12 18:22       ` David Malcolm
  2020-01-10  2:41     ` David Malcolm
  1 sibling, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-12-12 15:29 UTC (permalink / raw)
  To: law, gcc-patches

On Wed, 2019-12-11 at 13:04 -0700, Jeff Law wrote:
> On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> > This patch adds exploded_graph and related classes, for managing
> > exploring paths through the user's code as a directed graph
> > of <point, state> pairs.
> > 
> > gcc/ChangeLog:
> > 	* analyzer/exploded-graph.h: New file.
> > ---
> >  gcc/analyzer/exploded-graph.h | 754
> > ++++++++++++++++++++++++++++++++++++++++++
> >  1 file changed, 754 insertions(+)
> >  create mode 100644 gcc/analyzer/exploded-graph.h
> > 
> > diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-
> > graph.h
> > new file mode 100644
> > index 0000000..f97d2b6
> > --- /dev/null
> > +++ b/gcc/analyzer/exploded-graph.h
> > @@ -0,0 +1,754 @@
> > +/* Classes for managing a directed graph of <point, state> pairs.
> > +   Copyright (C) 2019 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/>;;.  */
> > +
> > +#ifndef GCC_ANALYZER_EXPLODED_GRAPH_H
> > +#define GCC_ANALYZER_EXPLODED_GRAPH_H
> > +
> > +#include "fibonacci_heap.h"
> > +#include "analyzer/analyzer-logging.h"
> > +#include "analyzer/constraint-manager.h"
> > +#include "analyzer/diagnostic-manager.h"
> > +#include "analyzer/program-point.h"
> > +#include "analyzer/program-state.h"
> > +#include "analyzer/shortest-paths.h"
> > +
> > +//////////////////////////////////////////////////////////////////
> > //
> NIT.  Is there some reason you don't just use whitespace for these
> kind
> of vertical separators.  THe more I see them, the more they bug me,
> probably because that's not been the style we've used for GCC.

I've been using them to highlight new "chapters" in a source file;
typically there's another comment following them.  I'm happy to drop
the one where there isn't a trailing comment, but, I'm hoping there's
an acceptable way to have an "section-header"-style comment.

We have "^L" in a bunch of places, but my editor (emacs) doesn't do a
great job of highlighting them.

There's some precedence for: e.g. 

gengtype.c:5094:/******* Manage input files.  ******/

lto-section-in.c has e.g.:

/*****************************************************************************/
/* Record renamings of static declarations                                   */
/*****************************************************************************/

tree-vect-loop-manip.c has:

/*************************************************************************
  Simple Loop Peeling Utilities

  Utilities to support loop peeling for vectorization purposes.
 *************************************************************************/

but I suspect given how sporadic these are that this stuff snuck past
review.

Alternatively I can just drop these and retain the trailing comments,
keeping their formatting.

> > ///////
> > +
> > +/* Concrete implementation of region_model_context, wiring it up
> > to
> > the
> > +   rest of the analysis engine.  */
> > +
> > +class impl_region_model_context : public region_model_context,
> > public log_user
> Multiple inheritance?  Is it really needed?  Can we handle via
> composition instead?

It's handy here, but I can probably eliminate it.

> 
> > +/* A <program_point, program_state> pair, used internally by
> > +   exploded_node as its immutable data, and as a key for
> > identifying
> > +   exploded_nodes we've already seen in the graph.  */
> > +
> > +struct point_and_state
> Shouldn't this be a class?

Will fix (originally was just a pair of public fields, but then I added
the cached hash value, so there's an argument for "class-ifying" this).

> +
> > +/* Per-program_point data for an exploded_graph.  */
> > +
> > +struct per_program_point_data
> Here too?  THere may be others.  I'd suggest reviewing all your
> "structs" and determine if we're better off calling them
> "class".  I'm
> not going to insist on it though since I think the last discussion in
> this space was to relax the conventions :-)

Both fields are public, but they're not-POD.

I'll look into them.

> > +
> > +class exploded_graph : public digraph<eg_traits>, public log_user
> Multiple inheritance again?

Again, it's handy here, but I think I can probably eliminate it.



Dave

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 43/49] analyzer: new file: exploded-graph.h
  2019-12-12 15:29     ` David Malcolm
@ 2019-12-12 18:22       ` David Malcolm
  0 siblings, 0 replies; 160+ messages in thread
From: David Malcolm @ 2019-12-12 18:22 UTC (permalink / raw)
  To: law, gcc-patches

On Thu, 2019-12-12 at 10:28 -0500, David Malcolm wrote:
> On Wed, 2019-12-11 at 13:04 -0700, Jeff Law wrote:
> > On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:

[...]

> > > +
> > > +////////////////////////////////////////////////////////////////
> > > //
> > > //
> > NIT.  Is there some reason you don't just use whitespace for these
> > kind
> > of vertical separators.  THe more I see them, the more they bug me,
> > probably because that's not been the style we've used for GCC.
> 
> I've been using them to highlight new "chapters" in a source file;
> typically there's another comment following them.  I'm happy to drop
> the one where there isn't a trailing comment, but, I'm hoping there's
> an acceptable way to have an "section-header"-style comment.
> 
> We have "^L" in a bunch of places, but my editor (emacs) doesn't do a
> great job of highlighting them.
> 
> There's some precedence for: e.g. 
> 
> gengtype.c:5094:/******* Manage input files.  ******/
> 
> lto-section-in.c has e.g.:
> 
> /********************************************************************
> *********/
> /* Record renamings of static
> declarations                                   */
> /********************************************************************
> *********/
> 
> tree-vect-loop-manip.c has:
> 
> /********************************************************************
> *****
>   Simple Loop Peeling Utilities
> 
>   Utilities to support loop peeling for vectorization purposes.
>  ********************************************************************
> *****/
> 
> but I suspect given how sporadic these are that this stuff snuck past
> review.
> 
> Alternatively I can just drop these and retain the trailing comments,
> keeping their formatting.

I went ahead and did that, and the result is growing on me; sorry for
the noise.


Dave


^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 11/49] Add diagnostic_metadata and CWE support
  2019-12-04 17:36   ` Martin Sebor
@ 2019-12-13  3:00     ` David Malcolm
  2019-12-13 16:44       ` Martin Sebor
  0 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-12-13  3:00 UTC (permalink / raw)
  To: Martin Sebor, gcc-patches

On Wed, 2019-12-04 at 10:36 -0700, Martin Sebor wrote:
> On 11/15/19 6:22 PM, David Malcolm wrote:
> > This patch adds support for associating a diagnostic with an
> > optional
> > diagnostic_metadata object, so that plugins can add extra data to
> > their
> > diagnostics (e.g. mapping a diagnostic to a taxonomy or coding
> > standard
> > such as from CERT or MISRA).
> > 
> > Currently this only supports associating a CWE identifier with a
> > diagnostic (which is what I'm using for the analyzer warnings later
> > in
> > the patch kit), but adding a diagnostic_metadata class allows for
> > future
> > growth in this area without an explosion of further "warning_at"
> > overloads
> > for all of the different kinds of custom data that a plugin might
> > want to
> > add.
> 
> We discussed this in the past so I'll be repeating some of my
> comments from that thread.  I like the feature at a high level,
> but I'm not sure I see how to make it work in a robust way.
> 
> There is no one-to-one mapping between GCC warnings and any of
> these classifications.  A single GCC warning might map to several
> CERT or MISRA guidelines, and a single guideline might map to two
> or more different GCC warnings.  This is an M to N mapping times
> the number of coding standards/classifications.  I haven't looked
> at the patch kit in enough detail to understand how it tries to
> handle it.  Can you explain?

The patch kit gives the ability to (optionally) specify a single CWE
identifier when emitting a diagnostic.

The patch kit uses this in various places for its new warnings for the
cases where there is a well-defined CWE weakness identifier (and
doesn't otherwise).

It doesn't attempt to classify any existing warnings.

It doesn't attempt to support other classifications in this patch, but
it does introduce a diagnostic_metadata object that could gain this
functionality at some later time, if we come up with a scheme we're
happy with (a slight violation of YAGNI, but it's such a pain touching
all the diagnostic fns that I prefer to do it once).

> My other concern here is that these guidelines evolve.  New ones
> are being added, existing ones moved or broken up, etc.  Does
> the infrastructure cope with this evolution (e.g., by reading
> the mapping from a file)?

Let's cross that bridge when we come to it.  If it becomes a problem,
then we can come up with a fix (e.g. a command-line option to specify
the CWE version).

Dave

> Martin
> 
> > gcc/ChangeLog:
> > 	* common.opt (fdiagnostics-show-metadata): New option.
> > 	* diagnostic-core.h (class diagnostic_metadata): New forward
> > decl.
> > 	(warning_at): Add overload taking a const diagnostic_metadata
> > &.
> > 	(emit_diagnostic_valist): Add overload taking a
> > 	const diagnostic_metadata *.
> > 	* diagnostic-format-json.cc: Include "diagnostic-metadata.h".
> > 	(json_from_metadata): New function.
> > 	(json_end_diagnostic): Call it to add "metadata" child for
> > 	diagnostics with metadata.
> > 	(diagnostic_output_format_init): Clear context->show_metadata.
> > 	* diagnostic-metadata.h: New file.
> > 	* diagnostic.c: Include "diagnostic-metadata.h".
> > 	(diagnostic_impl): Add const diagnostic_metadata * param.
> > 	(diagnostic_n_impl): Likewise.
> > 	(diagnostic_initialize): Initialize context->show_metadata.
> > 	(diagnostic_set_info_translated): Initialize diagnostic-
> > >metadata.
> > 	(get_cwe_url): New function.
> > 	(print_any_metadata): New function.
> > 	(diagnostic_report_diagnostic): Call print_any_metadata if the
> > 	diagnostic has non-NULL metadata.
> > 	(emit_diagnostic): Pass NULL as the metadata in the call to
> > 	diagnostic_impl.
> > 	(emit_diagnostic_valist): Likewise.
> > 	(emit_diagnostic_valist): New overload taking a
> > 	const diagnostic_metadata *.
> > 	(inform): Pass NULL as the metadata in the call to
> > 	diagnostic_impl.
> > 	(inform_n): Likewise for diagnostic_n_impl.
> > 	(warning): Likewise.
> > 	(warning_at): Likewise.  Add overload that takes a
> > 	const diagnostic_metadata &.
> > 	(warning_n): Pass NULL as the metadata in the call to
> > 	diagnostic_n_impl.
> > 	(pedwarn): Likewise for diagnostic_impl.
> > 	(permerror): Likewise.
> > 	(error): Likewise.
> > 	(error_n): Likewise.
> > 	(error_at): Likewise.
> > 	(sorry): Likewise.
> > 	(sorry_at): Likewise.
> > 	(fatal_error): Likewise.
> > 	(internal_error): Likewise.
> > 	(internal_error_no_backtrace): Likewise.
> > 	* diagnostic.h (diagnostic_info::metadata): New field.
> > 	(diagnostic_context::show_metadata): New field.
> > 	* doc/invoke.texi (-fno-diagnostics-show-metadata): New option.
> > 	* opts.c (common_handle_option): Handle
> > 	OPT_fdiagnostics_show_metadata.
> > 	* toplev.c (general_init): Initialize global_dc->show_metadata.
> > ---
> >   gcc/common.opt                |   4 ++
> >   gcc/diagnostic-core.h         |  10 +++
> >   gcc/diagnostic-format-json.cc |  23 +++++++
> >   gcc/diagnostic-metadata.h     |  42 +++++++++++++
> >   gcc/diagnostic.c              | 142
> > +++++++++++++++++++++++++++++++++---------
> >   gcc/diagnostic.h              |   7 +++
> >   gcc/doc/invoke.texi           |   8 +++
> >   gcc/opts.c                    |   4 ++
> >   gcc/toplev.c                  |   2 +
> >   9 files changed, 211 insertions(+), 31 deletions(-)
> >   create mode 100644 gcc/diagnostic-metadata.h
> > 
> > diff --git a/gcc/common.opt b/gcc/common.opt
> > index 3c024b3..228df32 100644
> > --- a/gcc/common.opt
> > +++ b/gcc/common.opt
> > @@ -1334,6 +1334,10 @@ fdiagnostics-show-option
> >   Common Var(flag_diagnostics_show_option) Init(1)
> >   Amend appropriate diagnostic messages with the command line
> > option that controls them.
> >   
> > +fdiagnostics-show-metadata
> > +Common Var(flag_diagnostics_show_metadata) Init(1)
> > +Print additional metadata for diagnostic messages, where
> > available.
> > +
> >   fdiagnostics-minimum-margin-width=
> >   Common Joined UInteger Var(diagnostics_minimum_margin_width)
> > Init(6)
> >   Set minimum width of left margin of source code when showing
> > source.
> > diff --git a/gcc/diagnostic-core.h b/gcc/diagnostic-core.h
> > index efafde4..2e7f120 100644
> > --- a/gcc/diagnostic-core.h
> > +++ b/gcc/diagnostic-core.h
> > @@ -45,6 +45,9 @@ class auto_diagnostic_group
> >     ~auto_diagnostic_group ();
> >   };
> >   
> > +/* Forward decl.  */
> > +class diagnostic_metadata; /* See diagnostic-metadata.h.  */
> > +
> >   extern const char *progname;
> >   
> >   extern const char *trim_filename (const char *);
> > @@ -78,6 +81,9 @@ extern bool warning_at (location_t, int, const
> > char *, ...)
> >       ATTRIBUTE_GCC_DIAG(3,4);
> >   extern bool warning_at (rich_location *, int, const char *, ...)
> >       ATTRIBUTE_GCC_DIAG(3,4);
> > +extern bool warning_at (rich_location *, const diagnostic_metadata
> > &, int,
> > +			const char *, ...)
> > +    ATTRIBUTE_GCC_DIAG(4,5);
> >   extern void error (const char *, ...) ATTRIBUTE_GCC_DIAG(1,2);
> >   extern void error_n (location_t, unsigned HOST_WIDE_INT, const
> > char *,
> >   		     const char *, ...)
> > @@ -109,6 +115,10 @@ extern bool emit_diagnostic (diagnostic_t,
> > rich_location *, int,
> >   			     const char *, ...)
> > ATTRIBUTE_GCC_DIAG(4,5);
> >   extern bool emit_diagnostic_valist (diagnostic_t, location_t,
> > int, const char *,
> >   				    va_list *) ATTRIBUTE_GCC_DIAG
> > (4,0);
> > +extern bool emit_diagnostic_valist (diagnostic_t, rich_location *,
> > +				    const diagnostic_metadata
> > *metadata,
> > +				    int, const char *, va_list *)
> > +  ATTRIBUTE_GCC_DIAG (5,0);
> >   extern bool seen_error (void);
> >   
> >   #ifdef BUFSIZ
> > diff --git a/gcc/diagnostic-format-json.cc b/gcc/diagnostic-format-
> > json.cc
> > index eb99952..200e844 100644
> > --- a/gcc/diagnostic-format-json.cc
> > +++ b/gcc/diagnostic-format-json.cc
> > @@ -23,6 +23,7 @@ along with GCC; see the file COPYING3.  If not
> > see
> >   #include "system.h"
> >   #include "coretypes.h"
> >   #include "diagnostic.h"
> > +#include "diagnostic-metadata.h"
> >   #include "json.h"
> >   #include "selftest.h"
> >   
> > @@ -103,6 +104,19 @@ json_from_fixit_hint (const fixit_hint *hint)
> >     return fixit_obj;
> >   }
> >   
> > +/* Generate a JSON object for METADATA.  */
> > +
> > +static json::object *
> > +json_from_metadata (const diagnostic_metadata *metadata)
> > +{
> > +  json::object *metadata_obj = new json::object ();
> > +
> > +  if (metadata->get_cwe ())
> > +    metadata_obj->set ("cwe", new json::number (metadata->get_cwe
> > ()));
> > +
> > +  return metadata_obj;
> > +}
> > +
> >   /* No-op implementation of "begin_diagnostic" for JSON
> > output.  */
> >   
> >   static void
> > @@ -211,6 +225,12 @@ json_end_diagnostic (diagnostic_context
> > *context, diagnostic_info *diagnostic,
> >        TODO: functions
> >        TODO: inlining information
> >        TODO: macro expansion information.  */
> > +
> > +  if (diagnostic->metadata)
> > +    {
> > +      json::object *metadata_obj = json_from_metadata (diagnostic-
> > >metadata);
> > +      diag_obj->set ("metadata", metadata_obj);
> > +    }
> >   }
> >   
> >   /* No-op implementation of "begin_group_cb" for JSON output.  */
> > @@ -268,6 +288,9 @@ diagnostic_output_format_init
> > (diagnostic_context *context,
> >   	context->end_group_cb =  json_end_group;
> >   	context->final_cb =  json_final_cb;
> >   
> > +	/* The metadata is handled in JSON format, rather than as
> > text.  */
> > +	context->show_metadata = false;
> > +
> >   	/* The option is handled in JSON format, rather than as
> > text.  */
> >   	context->show_option_requested = false;
> >   
> > diff --git a/gcc/diagnostic-metadata.h b/gcc/diagnostic-metadata.h
> > new file mode 100644
> > index 0000000..a759d44
> > --- /dev/null
> > +++ b/gcc/diagnostic-metadata.h
> > @@ -0,0 +1,42 @@
> > +/* Additional metadata for a diagnostic.
> > +   Copyright (C) 2019 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/>;.  */
> > +
> > +#ifndef GCC_DIAGNOSTIC_METADATA_H
> > +#define GCC_DIAGNOSTIC_METADATA_H
> > +
> > +/* A bundle of additional metadata that can be associated with a
> > +   diagnostic.
> > +
> > +   Currently this only supports associating a CWE identifier with
> > a
> > +   diagnostic.  */
> > +
> > +class diagnostic_metadata
> > +{
> > + public:
> > +  diagnostic_metadata () : m_cwe (0) {}
> > +
> > +  void add_cwe (int cwe) { m_cwe = cwe; }
> > +  int get_cwe () const { return m_cwe; }
> > +
> > + private:
> > +  int m_cwe;
> > +};
> > +
> > +#endif /* ! GCC_DIAGNOSTIC_METADATA_H */
> > diff --git a/gcc/diagnostic.c b/gcc/diagnostic.c
> > index 81e0a10..e8d0613 100644
> > --- a/gcc/diagnostic.c
> > +++ b/gcc/diagnostic.c
> > @@ -32,6 +32,7 @@ along with GCC; see the file COPYING3.  If not
> > see
> >   #include "diagnostic.h"
> >   #include "diagnostic-color.h"
> >   #include "diagnostic-url.h"
> > +#include "diagnostic-metadata.h"
> >   #include "edit-context.h"
> >   #include "selftest.h"
> >   #include "selftest-diagnostic.h"
> > @@ -58,11 +59,13 @@ along with GCC; see the file COPYING3.  If not
> > see
> >   #define permissive_error_option(DC) ((DC)->opt_permissive)
> >   
> >   /* Prototypes.  */
> > -static bool diagnostic_impl (rich_location *, int, const char *,
> > -			     va_list *, diagnostic_t)
> > ATTRIBUTE_GCC_DIAG(3,0);
> > -static bool diagnostic_n_impl (rich_location *, int, unsigned
> > HOST_WIDE_INT,
> > +static bool diagnostic_impl (rich_location *, const
> > diagnostic_metadata *,
> > +			     int, const char *,
> > +			     va_list *, diagnostic_t)
> > ATTRIBUTE_GCC_DIAG(4,0);
> > +static bool diagnostic_n_impl (rich_location *, const
> > diagnostic_metadata *,
> > +			       int, unsigned HOST_WIDE_INT,
> >   			       const char *, const char *, va_list *,
> > -			       diagnostic_t) ATTRIBUTE_GCC_DIAG(5,0);
> > +			       diagnostic_t) ATTRIBUTE_GCC_DIAG(6,0);
> >   
> >   static void error_recursion (diagnostic_context *)
> > ATTRIBUTE_NORETURN;
> >   static void real_abort (void) ATTRIBUTE_NORETURN;
> > @@ -183,6 +186,7 @@ diagnostic_initialize (diagnostic_context
> > *context, int n_opts)
> >     diagnostic_set_caret_max_width (context, pp_line_cutoff
> > (context->printer));
> >     for (i = 0; i < rich_location::STATICALLY_ALLOCATED_RANGES;
> > i++)
> >       context->caret_chars[i] = '^';
> > +  context->show_metadata = false;
> >     context->show_option_requested = false;
> >     context->abort_on_error = false;
> >     context->show_column = false;
> > @@ -300,6 +304,7 @@ diagnostic_set_info_translated (diagnostic_info
> > *diagnostic, const char *msg,
> >     diagnostic->message.format_spec = msg;
> >     diagnostic->message.m_richloc = richloc;
> >     diagnostic->richloc = richloc;
> > +  diagnostic->metadata = NULL;
> >     diagnostic->kind = kind;
> >     diagnostic->option_index = 0;
> >   }
> > @@ -899,6 +904,47 @@ update_effective_level_from_pragmas
> > (diagnostic_context *context,
> >     return diag_class;
> >   }
> >   
> > +/* Generate a URL string describing CWE.  The caller is
> > responsible for
> > +   freeing the string.  */
> > +
> > +static char *
> > +get_cwe_url (int cwe)
> > +{
> > +  return xasprintf ("
> > https://cwe.mitre.org/data/definitions/%i.html", cwe);
> > +}
> > +
> > +/* If DIAGNOSTIC has metadata, print it.
> > +
> > +   For example, if the diagnostic metadata associates it with CWE-
> > 119,
> > +   " [CWE-119]" will be printed, suitably colorized, and with a
> > URL of a
> > +   description of the security issue.  */
> > +
> > +static void
> > +print_any_metadata (diagnostic_context *context,
> > +		    const diagnostic_info *diagnostic)
> > +{
> > +  if (diagnostic->metadata == NULL)
> > +    return;
> > +
> > +  int cwe = diagnostic->metadata->get_cwe ();
> > +  if (cwe)
> > +    {
> > +      pretty_printer *pp = context->printer;
> > +      char *saved_prefix = pp_take_prefix (context->printer);
> > +      pp_string (pp, " [");
> > +      pp_string (pp, colorize_start (pp_show_color (pp),
> > +				     diagnostic_kind_color[diagnostic-
> > >kind]));
> > +      char *cwe_url = get_cwe_url (cwe);
> > +      pp_begin_url (pp, cwe_url);
> > +      free (cwe_url);
> > +      pp_printf (pp, "CWE-%i", cwe);
> > +      pp_set_prefix (context->printer, saved_prefix);
> > +      pp_end_url (pp);
> > +      pp_string (pp, colorize_stop (pp_show_color (pp)));
> > +      pp_character (pp, ']');
> > +    }
> > +}
> > +
> >   /* Print any metadata about the option used to control DIAGNOSTIC
> > to CONTEXT's
> >      printer, e.g. " [-Werror=uninitialized]".
> >      Subroutine of diagnostic_report_diagnostic.  */
> > @@ -1059,6 +1105,8 @@ diagnostic_report_diagnostic
> > (diagnostic_context *context,
> >     pp_format (context->printer, &diagnostic->message);
> >     (*diagnostic_starter (context)) (context, diagnostic);
> >     pp_output_formatted_text (context->printer);
> > +  if (context->show_metadata)
> > +    print_any_metadata (context, diagnostic);
> >     if (context->show_option_requested)
> >       print_option_information (context, diagnostic,
> > orig_diag_kind);
> >     (*diagnostic_finalizer (context)) (context, diagnostic,
> > orig_diag_kind);
> > @@ -1190,8 +1238,8 @@ diagnostic_append_note (diagnostic_context
> > *context,
> >      permerror, error, error_at, error_at, sorry, fatal_error,
> > internal_error,
> >      and internal_error_no_backtrace, as documented and defined
> > below.  */
> >   static bool
> > -diagnostic_impl (rich_location *richloc, int opt,
> > -		 const char *gmsgid,
> > +diagnostic_impl (rich_location *richloc, const diagnostic_metadata
> > *metadata,
> > +		 int opt, const char *gmsgid,
> >   		 va_list *ap, diagnostic_t kind)
> >   {
> >     diagnostic_info diagnostic;
> > @@ -1207,13 +1255,15 @@ diagnostic_impl (rich_location *richloc,
> > int opt,
> >         if (kind == DK_WARNING || kind == DK_PEDWARN)
> >   	diagnostic.option_index = opt;
> >       }
> > +  diagnostic.metadata = metadata;
> >     return diagnostic_report_diagnostic (global_dc, &diagnostic);
> >   }
> >   
> >   /* Implement inform_n, warning_n, and error_n, as documented and
> >      defined below.  */
> >   static bool
> > -diagnostic_n_impl (rich_location *richloc, int opt, unsigned
> > HOST_WIDE_INT n,
> > +diagnostic_n_impl (rich_location *richloc, const
> > diagnostic_metadata *metadata,
> > +		   int opt, unsigned HOST_WIDE_INT n,
> >   		   const char *singular_gmsgid,
> >   		   const char *plural_gmsgid,
> >   		   va_list *ap, diagnostic_t kind)
> > @@ -1233,6 +1283,7 @@ diagnostic_n_impl (rich_location *richloc,
> > int opt, unsigned HOST_WIDE_INT n,
> >     diagnostic_set_info_translated (&diagnostic, text, ap, richloc,
> > kind);
> >     if (kind == DK_WARNING)
> >       diagnostic.option_index = opt;
> > +  diagnostic.metadata = metadata;
> >     return diagnostic_report_diagnostic (global_dc, &diagnostic);
> >   }
> >   
> > @@ -1246,7 +1297,7 @@ emit_diagnostic (diagnostic_t kind,
> > location_t location, int opt,
> >     va_list ap;
> >     va_start (ap, gmsgid);
> >     rich_location richloc (line_table, location);
> > -  bool ret = diagnostic_impl (&richloc, opt, gmsgid, &ap, kind);
> > +  bool ret = diagnostic_impl (&richloc, NULL, opt, gmsgid, &ap,
> > kind);
> >     va_end (ap);
> >     return ret;
> >   }
> > @@ -1260,7 +1311,7 @@ emit_diagnostic (diagnostic_t kind,
> > rich_location *richloc, int opt,
> >     auto_diagnostic_group d;
> >     va_list ap;
> >     va_start (ap, gmsgid);
> > -  bool ret = diagnostic_impl (richloc, opt, gmsgid, &ap, kind);
> > +  bool ret = diagnostic_impl (richloc, NULL, opt, gmsgid, &ap,
> > kind);
> >     va_end (ap);
> >     return ret;
> >   }
> > @@ -1272,7 +1323,18 @@ emit_diagnostic_valist (diagnostic_t kind,
> > location_t location, int opt,
> >   			const char *gmsgid, va_list *ap)
> >   {
> >     rich_location richloc (line_table, location);
> > -  return diagnostic_impl (&richloc, opt, gmsgid, ap, kind);
> > +  return diagnostic_impl (&richloc, NULL, opt, gmsgid, ap, kind);
> > +}
> > +
> > +/* Wrapper around diagnostic_impl taking a va_list parameter.  */
> > +
> > +bool
> > +emit_diagnostic_valist (diagnostic_t kind, rich_location *richloc,
> > +			const diagnostic_metadata *metadata,
> > +			int opt,
> > +			const char *gmsgid, va_list *ap)
> > +{
> > +  return diagnostic_impl (richloc, metadata, opt, gmsgid, ap,
> > kind);
> >   }
> >   
> >   /* An informative note at LOCATION.  Use this for additional
> > details on an error
> > @@ -1284,7 +1346,7 @@ inform (location_t location, const char
> > *gmsgid, ...)
> >     va_list ap;
> >     va_start (ap, gmsgid);
> >     rich_location richloc (line_table, location);
> > -  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_NOTE);
> > +  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_NOTE);
> >     va_end (ap);
> >   }
> >   
> > @@ -1297,7 +1359,7 @@ inform (rich_location *richloc, const char
> > *gmsgid, ...)
> >     auto_diagnostic_group d;
> >     va_list ap;
> >     va_start (ap, gmsgid);
> > -  diagnostic_impl (richloc, -1, gmsgid, &ap, DK_NOTE);
> > +  diagnostic_impl (richloc, NULL, -1, gmsgid, &ap, DK_NOTE);
> >     va_end (ap);
> >   }
> >   
> > @@ -1311,7 +1373,7 @@ inform_n (location_t location, unsigned
> > HOST_WIDE_INT n,
> >     va_start (ap, plural_gmsgid);
> >     auto_diagnostic_group d;
> >     rich_location richloc (line_table, location);
> > -  diagnostic_n_impl (&richloc, -1, n, singular_gmsgid,
> > plural_gmsgid,
> > +  diagnostic_n_impl (&richloc, NULL, -1, n, singular_gmsgid,
> > plural_gmsgid,
> >   		     &ap, DK_NOTE);
> >     va_end (ap);
> >   }
> > @@ -1326,7 +1388,7 @@ warning (int opt, const char *gmsgid, ...)
> >     va_list ap;
> >     va_start (ap, gmsgid);
> >     rich_location richloc (line_table, input_location);
> > -  bool ret = diagnostic_impl (&richloc, opt, gmsgid, &ap,
> > DK_WARNING);
> > +  bool ret = diagnostic_impl (&richloc, NULL, opt, gmsgid, &ap,
> > DK_WARNING);
> >     va_end (ap);
> >     return ret;
> >   }
> > @@ -1342,7 +1404,7 @@ warning_at (location_t location, int opt,
> > const char *gmsgid, ...)
> >     va_list ap;
> >     va_start (ap, gmsgid);
> >     rich_location richloc (line_table, location);
> > -  bool ret = diagnostic_impl (&richloc, opt, gmsgid, &ap,
> > DK_WARNING);
> > +  bool ret = diagnostic_impl (&richloc, NULL, opt, gmsgid, &ap,
> > DK_WARNING);
> >     va_end (ap);
> >     return ret;
> >   }
> > @@ -1357,7 +1419,25 @@ warning_at (rich_location *richloc, int opt,
> > const char *gmsgid, ...)
> >     auto_diagnostic_group d;
> >     va_list ap;
> >     va_start (ap, gmsgid);
> > -  bool ret = diagnostic_impl (richloc, opt, gmsgid, &ap,
> > DK_WARNING);
> > +  bool ret = diagnostic_impl (richloc, NULL, opt, gmsgid, &ap,
> > DK_WARNING);
> > +  va_end (ap);
> > +  return ret;
> > +}
> > +
> > +/* Same as "warning at" above, but using METADATA.  */
> > +
> > +bool
> > +warning_at (rich_location *richloc, const diagnostic_metadata
> > &metadata,
> > +	    int opt, const char *gmsgid, ...)
> > +{
> > +  gcc_assert (richloc);
> > +
> > +  auto_diagnostic_group d;
> > +  va_list ap;
> > +  va_start (ap, gmsgid);
> > +  bool ret
> > +    = diagnostic_impl (richloc, &metadata, opt, gmsgid, &ap,
> > +		       DK_WARNING);
> >     va_end (ap);
> >     return ret;
> >   }
> > @@ -1373,7 +1453,7 @@ warning_n (rich_location *richloc, int opt,
> > unsigned HOST_WIDE_INT n,
> >     auto_diagnostic_group d;
> >     va_list ap;
> >     va_start (ap, plural_gmsgid);
> > -  bool ret = diagnostic_n_impl (richloc, opt, n,
> > +  bool ret = diagnostic_n_impl (richloc, NULL, opt, n,
> >   				singular_gmsgid, plural_gmsgid,
> >   				&ap, DK_WARNING);
> >     va_end (ap);
> > @@ -1392,7 +1472,7 @@ warning_n (location_t location, int opt,
> > unsigned HOST_WIDE_INT n,
> >     va_list ap;
> >     va_start (ap, plural_gmsgid);
> >     rich_location richloc (line_table, location);
> > -  bool ret = diagnostic_n_impl (&richloc, opt, n,
> > +  bool ret = diagnostic_n_impl (&richloc, NULL, opt, n,
> >   				singular_gmsgid, plural_gmsgid,
> >   				&ap, DK_WARNING);
> >     va_end (ap);
> > @@ -1419,7 +1499,7 @@ pedwarn (location_t location, int opt, const
> > char *gmsgid, ...)
> >     va_list ap;
> >     va_start (ap, gmsgid);
> >     rich_location richloc (line_table, location);
> > -  bool ret = diagnostic_impl (&richloc, opt, gmsgid, &ap,
> > DK_PEDWARN);
> > +  bool ret = diagnostic_impl (&richloc, NULL, opt, gmsgid, &ap,
> > DK_PEDWARN);
> >     va_end (ap);
> >     return ret;
> >   }
> > @@ -1434,7 +1514,7 @@ pedwarn (rich_location *richloc, int opt,
> > const char *gmsgid, ...)
> >     auto_diagnostic_group d;
> >     va_list ap;
> >     va_start (ap, gmsgid);
> > -  bool ret = diagnostic_impl (richloc, opt, gmsgid, &ap,
> > DK_PEDWARN);
> > +  bool ret = diagnostic_impl (richloc, NULL, opt, gmsgid, &ap,
> > DK_PEDWARN);
> >     va_end (ap);
> >     return ret;
> >   }
> > @@ -1453,7 +1533,7 @@ permerror (location_t location, const char
> > *gmsgid, ...)
> >     va_list ap;
> >     va_start (ap, gmsgid);
> >     rich_location richloc (line_table, location);
> > -  bool ret = diagnostic_impl (&richloc, -1, gmsgid, &ap,
> > DK_PERMERROR);
> > +  bool ret = diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap,
> > DK_PERMERROR);
> >     va_end (ap);
> >     return ret;
> >   }
> > @@ -1468,7 +1548,7 @@ permerror (rich_location *richloc, const char
> > *gmsgid, ...)
> >     auto_diagnostic_group d;
> >     va_list ap;
> >     va_start (ap, gmsgid);
> > -  bool ret = diagnostic_impl (richloc, -1, gmsgid, &ap,
> > DK_PERMERROR);
> > +  bool ret = diagnostic_impl (richloc, NULL, -1, gmsgid, &ap,
> > DK_PERMERROR);
> >     va_end (ap);
> >     return ret;
> >   }
> > @@ -1482,7 +1562,7 @@ error (const char *gmsgid, ...)
> >     va_list ap;
> >     va_start (ap, gmsgid);
> >     rich_location richloc (line_table, input_location);
> > -  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_ERROR);
> > +  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_ERROR);
> >     va_end (ap);
> >   }
> >   
> > @@ -1496,7 +1576,7 @@ error_n (location_t location, unsigned
> > HOST_WIDE_INT n,
> >     va_list ap;
> >     va_start (ap, plural_gmsgid);
> >     rich_location richloc (line_table, location);
> > -  diagnostic_n_impl (&richloc, -1, n, singular_gmsgid,
> > plural_gmsgid,
> > +  diagnostic_n_impl (&richloc, NULL, -1, n, singular_gmsgid,
> > plural_gmsgid,
> >   		     &ap, DK_ERROR);
> >     va_end (ap);
> >   }
> > @@ -1509,7 +1589,7 @@ error_at (location_t loc, const char *gmsgid,
> > ...)
> >     va_list ap;
> >     va_start (ap, gmsgid);
> >     rich_location richloc (line_table, loc);
> > -  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_ERROR);
> > +  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_ERROR);
> >     va_end (ap);
> >   }
> >   
> > @@ -1523,7 +1603,7 @@ error_at (rich_location *richloc, const char
> > *gmsgid, ...)
> >     auto_diagnostic_group d;
> >     va_list ap;
> >     va_start (ap, gmsgid);
> > -  diagnostic_impl (richloc, -1, gmsgid, &ap, DK_ERROR);
> > +  diagnostic_impl (richloc, NULL, -1, gmsgid, &ap, DK_ERROR);
> >     va_end (ap);
> >   }
> >   
> > @@ -1537,7 +1617,7 @@ sorry (const char *gmsgid, ...)
> >     va_list ap;
> >     va_start (ap, gmsgid);
> >     rich_location richloc (line_table, input_location);
> > -  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_SORRY);
> > +  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_SORRY);
> >     va_end (ap);
> >   }
> >   
> > @@ -1549,7 +1629,7 @@ sorry_at (location_t loc, const char *gmsgid,
> > ...)
> >     va_list ap;
> >     va_start (ap, gmsgid);
> >     rich_location richloc (line_table, loc);
> > -  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_SORRY);
> > +  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_SORRY);
> >     va_end (ap);
> >   }
> >   
> > @@ -1571,7 +1651,7 @@ fatal_error (location_t loc, const char
> > *gmsgid, ...)
> >     va_list ap;
> >     va_start (ap, gmsgid);
> >     rich_location richloc (line_table, loc);
> > -  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_FATAL);
> > +  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_FATAL);
> >     va_end (ap);
> >   
> >     gcc_unreachable ();
> > @@ -1588,7 +1668,7 @@ internal_error (const char *gmsgid, ...)
> >     va_list ap;
> >     va_start (ap, gmsgid);
> >     rich_location richloc (line_table, input_location);
> > -  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_ICE);
> > +  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_ICE);
> >     va_end (ap);
> >   
> >     gcc_unreachable ();
> > @@ -1604,7 +1684,7 @@ internal_error_no_backtrace (const char
> > *gmsgid, ...)
> >     va_list ap;
> >     va_start (ap, gmsgid);
> >     rich_location richloc (line_table, input_location);
> > -  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_ICE_NOBT);
> > +  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_ICE_NOBT);
> >     va_end (ap);
> >   
> >     gcc_unreachable ();
> > diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
> > index 2fb75e6..0e4b6ad 100644
> > --- a/gcc/diagnostic.h
> > +++ b/gcc/diagnostic.h
> > @@ -46,6 +46,10 @@ struct diagnostic_info
> >     /* The location at which the diagnostic is to be reported.  */
> >     rich_location *richloc;
> >   
> > +  /* An optional bundle of metadata associated with the diagnostic
> > +     (or NULL).  */
> > +  const diagnostic_metadata *metadata;
> > +
> >     /* Auxiliary data for client.  */
> >     void *x_data;
> >     /* The kind of diagnostic it is about.  */
> > @@ -126,6 +130,9 @@ struct diagnostic_context
> >     /* Character used for caret diagnostics.  */
> >     char caret_chars[rich_location::STATICALLY_ALLOCATED_RANGES];
> >   
> > +  /* True if we should print any metadata associated with
> > diagnostics.  */
> > +  bool show_metadata;
> > +
> >     /* True if we should print the command line option which
> > controls
> >        each diagnostic, if known.  */
> >     bool show_option_requested;
> > diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
> > index fc92913..209e2c6 100644
> > --- a/gcc/doc/invoke.texi
> > +++ b/gcc/doc/invoke.texi
> > @@ -279,6 +279,7 @@ Objective-C and Objective-C++ Dialects}.
> >   -fno-diagnostics-show-option  -fno-diagnostics-show-caret @gol
> >   -fno-diagnostics-show-labels  -fno-diagnostics-show-line-numbers
> > @gol
> >   -fdiagnostics-nn-line-numbers @gol
> > +-fno-diagnostics-show-metadata  @gol
> >   -fdiagnostics-minimum-margin-width=@var{width} @gol
> >   -fdiagnostics-parseable-fixits  -fdiagnostics-generate-patch @gol
> >   -fdiagnostics-show-template-tree  -fno-elide-type @gol
> > @@ -4012,6 +4013,13 @@ as the types of expressions:
> >   This option suppresses the printing of these labels (in the
> > example above,
> >   the vertical bars and the ``char *'' and ``long int'' text).
> >   
> > +@item -fno-diagnostics-show-metadata
> > +@opindex fno-diagnostics-show-metadata
> > +@opindex fdiagnostics-show-metadata
> > +Diagnostics can optionally contain @url{
> > https://cwe.mitre.org/index.html, CWE}
> > +metadata.  By default, if this information is present, it will be
> > printed with
> > +the diagnostic.  This option suppresses the printing of this
> > metadata.
> > +
> >   @item -fno-diagnostics-show-line-numbers
> >   @opindex fno-diagnostics-show-line-numbers
> >   @opindex fdiagnostics-show-line-numbers
> > diff --git a/gcc/opts.c b/gcc/opts.c
> > index 9928d01..0caaf04 100644
> > --- a/gcc/opts.c
> > +++ b/gcc/opts.c
> > @@ -2491,6 +2491,10 @@ common_handle_option (struct gcc_options
> > *opts,
> >         dc->parseable_fixits_p = value;
> >         break;
> >   
> > +    case OPT_fdiagnostics_show_metadata:
> > +      dc->show_metadata = value;
> > +      break;
> > +
> >       case OPT_fdiagnostics_show_option:
> >         dc->show_option_requested = value;
> >         break;
> > diff --git a/gcc/toplev.c b/gcc/toplev.c
> > index 2d0a390..7136164 100644
> > --- a/gcc/toplev.c
> > +++ b/gcc/toplev.c
> > @@ -1102,6 +1102,8 @@ general_init (const char *argv0, bool
> > init_signals)
> >       = global_options_init.x_flag_diagnostics_show_line_numbers;
> >     global_dc->use_nn_for_line_numbers_p
> >       = global_options_init.x_flag_diagnostics_nn_line_numbers;
> > +  global_dc->show_metadata
> > +    = global_options_init.x_flag_diagnostics_show_metadata;
> >     global_dc->show_option_requested
> >       = global_options_init.x_flag_diagnostics_show_option;
> >     global_dc->min_margin_width
> > 

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 11/49] Add diagnostic_metadata and CWE support
  2019-12-13  3:00     ` David Malcolm
@ 2019-12-13 16:44       ` Martin Sebor
  2019-12-13 17:32         ` David Malcolm
  0 siblings, 1 reply; 160+ messages in thread
From: Martin Sebor @ 2019-12-13 16:44 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

On 12/12/19 7:59 PM, David Malcolm wrote:
> On Wed, 2019-12-04 at 10:36 -0700, Martin Sebor wrote:
>> On 11/15/19 6:22 PM, David Malcolm wrote:
>>> This patch adds support for associating a diagnostic with an
>>> optional
>>> diagnostic_metadata object, so that plugins can add extra data to
>>> their
>>> diagnostics (e.g. mapping a diagnostic to a taxonomy or coding
>>> standard
>>> such as from CERT or MISRA).
>>>
>>> Currently this only supports associating a CWE identifier with a
>>> diagnostic (which is what I'm using for the analyzer warnings later
>>> in
>>> the patch kit), but adding a diagnostic_metadata class allows for
>>> future
>>> growth in this area without an explosion of further "warning_at"
>>> overloads
>>> for all of the different kinds of custom data that a plugin might
>>> want to
>>> add.
>>
>> We discussed this in the past so I'll be repeating some of my
>> comments from that thread.  I like the feature at a high level,
>> but I'm not sure I see how to make it work in a robust way.
>>
>> There is no one-to-one mapping between GCC warnings and any of
>> these classifications.  A single GCC warning might map to several
>> CERT or MISRA guidelines, and a single guideline might map to two
>> or more different GCC warnings.  This is an M to N mapping times
>> the number of coding standards/classifications.  I haven't looked
>> at the patch kit in enough detail to understand how it tries to
>> handle it.  Can you explain?
> 
> The patch kit gives the ability to (optionally) specify a single CWE
> identifier when emitting a diagnostic.
> 
> The patch kit uses this in various places for its new warnings for the
> cases where there is a well-defined CWE weakness identifier (and
> doesn't otherwise).
> 
> It doesn't attempt to classify any existing warnings.
> 
> It doesn't attempt to support other classifications in this patch, but
> it does introduce a diagnostic_metadata object that could gain this
> functionality at some later time, if we come up with a scheme we're
> happy with (a slight violation of YAGNI, but it's such a pain touching
> all the diagnostic fns that I prefer to do it once).

I wasn't necessarily asking about any classifications in particular,
or even about pre-existing warnings.  I think it would be nice to be
able to eventually make it work for them but I obviously realize that
will take quite a bit retrofitting.

What I'd like to understand how the warning X classification-id
mapping for the new analyzer warnings is supported in the design
of the (new) diagnostic infrastructure (or how you envision it
will be when it's more complete).

You said the analyzer currently only supports associating a CWE
identifier with a diagnostic.  By "diagnostic" do you mean
a particular instance of a given warning or all its instances?

Picking an uninitialized use as an example, there are three CWE's
that discuss it:

   CWE-457: Use of Uninitialized Variable
   CWE-824: Access of Uninitialized Pointer
   CWE-908: Use of Uninitialized Resource

but only one -Wanalyzer-use-of-uninitialized-value.  I'd like to
see how the mapping works (or will work) between these three CWE's
and instances of this analyzer option/warning.

I see on Goldbolt that for either the use of an uninitialized
pointer (CWE-457) or for its dereference (CWE-824) the analyzer
issues the same warning with the same CWE ID:

   use of uninitialized value 'p' [CWE-457] 
[-Wanalyzer-use-of-uninitialized-value]

It doesn't differentiate between the two CWE's.

If this limitation is baked into the design of the infrastructure
then that's an obvious flaw that needs to be fixed.  There is no
point in issuing warnings with the wrong CWE's.  Anyone who cares
about CWE's would view those as bugs, and those who don't care
don't need to see them.

>> My other concern here is that these guidelines evolve.  New ones
>> are being added, existing ones moved or broken up, etc.  Does
>> the infrastructure cope with this evolution (e.g., by reading
>> the mapping from a file)?
> 
> Let's cross that bridge when we come to it.  If it becomes a problem,
> then we can come up with a fix (e.g. a command-line option to specify
> the CWE version).
I agree that dealing with evolving classifications is a secondary
goal that can probably be fairly easily met.  I wanted to know if
there already was a solution for it now.

But referencing the right IDs in the diagnostics seems like a basic
design requirement.  Unless the above example is due to the mapping
not being fully populated yet it seems clear that issuing a warning
with one CWE ID for a bug described under another CWE ID is
a problem.

Martin

> 
> Dave
> 
>> Martin
>>
>>> gcc/ChangeLog:
>>> 	* common.opt (fdiagnostics-show-metadata): New option.
>>> 	* diagnostic-core.h (class diagnostic_metadata): New forward
>>> decl.
>>> 	(warning_at): Add overload taking a const diagnostic_metadata
>>> &.
>>> 	(emit_diagnostic_valist): Add overload taking a
>>> 	const diagnostic_metadata *.
>>> 	* diagnostic-format-json.cc: Include "diagnostic-metadata.h".
>>> 	(json_from_metadata): New function.
>>> 	(json_end_diagnostic): Call it to add "metadata" child for
>>> 	diagnostics with metadata.
>>> 	(diagnostic_output_format_init): Clear context->show_metadata.
>>> 	* diagnostic-metadata.h: New file.
>>> 	* diagnostic.c: Include "diagnostic-metadata.h".
>>> 	(diagnostic_impl): Add const diagnostic_metadata * param.
>>> 	(diagnostic_n_impl): Likewise.
>>> 	(diagnostic_initialize): Initialize context->show_metadata.
>>> 	(diagnostic_set_info_translated): Initialize diagnostic-
>>>> metadata.
>>> 	(get_cwe_url): New function.
>>> 	(print_any_metadata): New function.
>>> 	(diagnostic_report_diagnostic): Call print_any_metadata if the
>>> 	diagnostic has non-NULL metadata.
>>> 	(emit_diagnostic): Pass NULL as the metadata in the call to
>>> 	diagnostic_impl.
>>> 	(emit_diagnostic_valist): Likewise.
>>> 	(emit_diagnostic_valist): New overload taking a
>>> 	const diagnostic_metadata *.
>>> 	(inform): Pass NULL as the metadata in the call to
>>> 	diagnostic_impl.
>>> 	(inform_n): Likewise for diagnostic_n_impl.
>>> 	(warning): Likewise.
>>> 	(warning_at): Likewise.  Add overload that takes a
>>> 	const diagnostic_metadata &.
>>> 	(warning_n): Pass NULL as the metadata in the call to
>>> 	diagnostic_n_impl.
>>> 	(pedwarn): Likewise for diagnostic_impl.
>>> 	(permerror): Likewise.
>>> 	(error): Likewise.
>>> 	(error_n): Likewise.
>>> 	(error_at): Likewise.
>>> 	(sorry): Likewise.
>>> 	(sorry_at): Likewise.
>>> 	(fatal_error): Likewise.
>>> 	(internal_error): Likewise.
>>> 	(internal_error_no_backtrace): Likewise.
>>> 	* diagnostic.h (diagnostic_info::metadata): New field.
>>> 	(diagnostic_context::show_metadata): New field.
>>> 	* doc/invoke.texi (-fno-diagnostics-show-metadata): New option.
>>> 	* opts.c (common_handle_option): Handle
>>> 	OPT_fdiagnostics_show_metadata.
>>> 	* toplev.c (general_init): Initialize global_dc->show_metadata.
>>> ---
>>>    gcc/common.opt                |   4 ++
>>>    gcc/diagnostic-core.h         |  10 +++
>>>    gcc/diagnostic-format-json.cc |  23 +++++++
>>>    gcc/diagnostic-metadata.h     |  42 +++++++++++++
>>>    gcc/diagnostic.c              | 142
>>> +++++++++++++++++++++++++++++++++---------
>>>    gcc/diagnostic.h              |   7 +++
>>>    gcc/doc/invoke.texi           |   8 +++
>>>    gcc/opts.c                    |   4 ++
>>>    gcc/toplev.c                  |   2 +
>>>    9 files changed, 211 insertions(+), 31 deletions(-)
>>>    create mode 100644 gcc/diagnostic-metadata.h
>>>
>>> diff --git a/gcc/common.opt b/gcc/common.opt
>>> index 3c024b3..228df32 100644
>>> --- a/gcc/common.opt
>>> +++ b/gcc/common.opt
>>> @@ -1334,6 +1334,10 @@ fdiagnostics-show-option
>>>    Common Var(flag_diagnostics_show_option) Init(1)
>>>    Amend appropriate diagnostic messages with the command line
>>> option that controls them.
>>>    
>>> +fdiagnostics-show-metadata
>>> +Common Var(flag_diagnostics_show_metadata) Init(1)
>>> +Print additional metadata for diagnostic messages, where
>>> available.
>>> +
>>>    fdiagnostics-minimum-margin-width=
>>>    Common Joined UInteger Var(diagnostics_minimum_margin_width)
>>> Init(6)
>>>    Set minimum width of left margin of source code when showing
>>> source.
>>> diff --git a/gcc/diagnostic-core.h b/gcc/diagnostic-core.h
>>> index efafde4..2e7f120 100644
>>> --- a/gcc/diagnostic-core.h
>>> +++ b/gcc/diagnostic-core.h
>>> @@ -45,6 +45,9 @@ class auto_diagnostic_group
>>>      ~auto_diagnostic_group ();
>>>    };
>>>    
>>> +/* Forward decl.  */
>>> +class diagnostic_metadata; /* See diagnostic-metadata.h.  */
>>> +
>>>    extern const char *progname;
>>>    
>>>    extern const char *trim_filename (const char *);
>>> @@ -78,6 +81,9 @@ extern bool warning_at (location_t, int, const
>>> char *, ...)
>>>        ATTRIBUTE_GCC_DIAG(3,4);
>>>    extern bool warning_at (rich_location *, int, const char *, ...)
>>>        ATTRIBUTE_GCC_DIAG(3,4);
>>> +extern bool warning_at (rich_location *, const diagnostic_metadata
>>> &, int,
>>> +			const char *, ...)
>>> +    ATTRIBUTE_GCC_DIAG(4,5);
>>>    extern void error (const char *, ...) ATTRIBUTE_GCC_DIAG(1,2);
>>>    extern void error_n (location_t, unsigned HOST_WIDE_INT, const
>>> char *,
>>>    		     const char *, ...)
>>> @@ -109,6 +115,10 @@ extern bool emit_diagnostic (diagnostic_t,
>>> rich_location *, int,
>>>    			     const char *, ...)
>>> ATTRIBUTE_GCC_DIAG(4,5);
>>>    extern bool emit_diagnostic_valist (diagnostic_t, location_t,
>>> int, const char *,
>>>    				    va_list *) ATTRIBUTE_GCC_DIAG
>>> (4,0);
>>> +extern bool emit_diagnostic_valist (diagnostic_t, rich_location *,
>>> +				    const diagnostic_metadata
>>> *metadata,
>>> +				    int, const char *, va_list *)
>>> +  ATTRIBUTE_GCC_DIAG (5,0);
>>>    extern bool seen_error (void);
>>>    
>>>    #ifdef BUFSIZ
>>> diff --git a/gcc/diagnostic-format-json.cc b/gcc/diagnostic-format-
>>> json.cc
>>> index eb99952..200e844 100644
>>> --- a/gcc/diagnostic-format-json.cc
>>> +++ b/gcc/diagnostic-format-json.cc
>>> @@ -23,6 +23,7 @@ along with GCC; see the file COPYING3.  If not
>>> see
>>>    #include "system.h"
>>>    #include "coretypes.h"
>>>    #include "diagnostic.h"
>>> +#include "diagnostic-metadata.h"
>>>    #include "json.h"
>>>    #include "selftest.h"
>>>    
>>> @@ -103,6 +104,19 @@ json_from_fixit_hint (const fixit_hint *hint)
>>>      return fixit_obj;
>>>    }
>>>    
>>> +/* Generate a JSON object for METADATA.  */
>>> +
>>> +static json::object *
>>> +json_from_metadata (const diagnostic_metadata *metadata)
>>> +{
>>> +  json::object *metadata_obj = new json::object ();
>>> +
>>> +  if (metadata->get_cwe ())
>>> +    metadata_obj->set ("cwe", new json::number (metadata->get_cwe
>>> ()));
>>> +
>>> +  return metadata_obj;
>>> +}
>>> +
>>>    /* No-op implementation of "begin_diagnostic" for JSON
>>> output.  */
>>>    
>>>    static void
>>> @@ -211,6 +225,12 @@ json_end_diagnostic (diagnostic_context
>>> *context, diagnostic_info *diagnostic,
>>>         TODO: functions
>>>         TODO: inlining information
>>>         TODO: macro expansion information.  */
>>> +
>>> +  if (diagnostic->metadata)
>>> +    {
>>> +      json::object *metadata_obj = json_from_metadata (diagnostic-
>>>> metadata);
>>> +      diag_obj->set ("metadata", metadata_obj);
>>> +    }
>>>    }
>>>    
>>>    /* No-op implementation of "begin_group_cb" for JSON output.  */
>>> @@ -268,6 +288,9 @@ diagnostic_output_format_init
>>> (diagnostic_context *context,
>>>    	context->end_group_cb =  json_end_group;
>>>    	context->final_cb =  json_final_cb;
>>>    
>>> +	/* The metadata is handled in JSON format, rather than as
>>> text.  */
>>> +	context->show_metadata = false;
>>> +
>>>    	/* The option is handled in JSON format, rather than as
>>> text.  */
>>>    	context->show_option_requested = false;
>>>    
>>> diff --git a/gcc/diagnostic-metadata.h b/gcc/diagnostic-metadata.h
>>> new file mode 100644
>>> index 0000000..a759d44
>>> --- /dev/null
>>> +++ b/gcc/diagnostic-metadata.h
>>> @@ -0,0 +1,42 @@
>>> +/* Additional metadata for a diagnostic.
>>> +   Copyright (C) 2019 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/>;.  */
>>> +
>>> +#ifndef GCC_DIAGNOSTIC_METADATA_H
>>> +#define GCC_DIAGNOSTIC_METADATA_H
>>> +
>>> +/* A bundle of additional metadata that can be associated with a
>>> +   diagnostic.
>>> +
>>> +   Currently this only supports associating a CWE identifier with
>>> a
>>> +   diagnostic.  */
>>> +
>>> +class diagnostic_metadata
>>> +{
>>> + public:
>>> +  diagnostic_metadata () : m_cwe (0) {}
>>> +
>>> +  void add_cwe (int cwe) { m_cwe = cwe; }
>>> +  int get_cwe () const { return m_cwe; }
>>> +
>>> + private:
>>> +  int m_cwe;
>>> +};
>>> +
>>> +#endif /* ! GCC_DIAGNOSTIC_METADATA_H */
>>> diff --git a/gcc/diagnostic.c b/gcc/diagnostic.c
>>> index 81e0a10..e8d0613 100644
>>> --- a/gcc/diagnostic.c
>>> +++ b/gcc/diagnostic.c
>>> @@ -32,6 +32,7 @@ along with GCC; see the file COPYING3.  If not
>>> see
>>>    #include "diagnostic.h"
>>>    #include "diagnostic-color.h"
>>>    #include "diagnostic-url.h"
>>> +#include "diagnostic-metadata.h"
>>>    #include "edit-context.h"
>>>    #include "selftest.h"
>>>    #include "selftest-diagnostic.h"
>>> @@ -58,11 +59,13 @@ along with GCC; see the file COPYING3.  If not
>>> see
>>>    #define permissive_error_option(DC) ((DC)->opt_permissive)
>>>    
>>>    /* Prototypes.  */
>>> -static bool diagnostic_impl (rich_location *, int, const char *,
>>> -			     va_list *, diagnostic_t)
>>> ATTRIBUTE_GCC_DIAG(3,0);
>>> -static bool diagnostic_n_impl (rich_location *, int, unsigned
>>> HOST_WIDE_INT,
>>> +static bool diagnostic_impl (rich_location *, const
>>> diagnostic_metadata *,
>>> +			     int, const char *,
>>> +			     va_list *, diagnostic_t)
>>> ATTRIBUTE_GCC_DIAG(4,0);
>>> +static bool diagnostic_n_impl (rich_location *, const
>>> diagnostic_metadata *,
>>> +			       int, unsigned HOST_WIDE_INT,
>>>    			       const char *, const char *, va_list *,
>>> -			       diagnostic_t) ATTRIBUTE_GCC_DIAG(5,0);
>>> +			       diagnostic_t) ATTRIBUTE_GCC_DIAG(6,0);
>>>    
>>>    static void error_recursion (diagnostic_context *)
>>> ATTRIBUTE_NORETURN;
>>>    static void real_abort (void) ATTRIBUTE_NORETURN;
>>> @@ -183,6 +186,7 @@ diagnostic_initialize (diagnostic_context
>>> *context, int n_opts)
>>>      diagnostic_set_caret_max_width (context, pp_line_cutoff
>>> (context->printer));
>>>      for (i = 0; i < rich_location::STATICALLY_ALLOCATED_RANGES;
>>> i++)
>>>        context->caret_chars[i] = '^';
>>> +  context->show_metadata = false;
>>>      context->show_option_requested = false;
>>>      context->abort_on_error = false;
>>>      context->show_column = false;
>>> @@ -300,6 +304,7 @@ diagnostic_set_info_translated (diagnostic_info
>>> *diagnostic, const char *msg,
>>>      diagnostic->message.format_spec = msg;
>>>      diagnostic->message.m_richloc = richloc;
>>>      diagnostic->richloc = richloc;
>>> +  diagnostic->metadata = NULL;
>>>      diagnostic->kind = kind;
>>>      diagnostic->option_index = 0;
>>>    }
>>> @@ -899,6 +904,47 @@ update_effective_level_from_pragmas
>>> (diagnostic_context *context,
>>>      return diag_class;
>>>    }
>>>    
>>> +/* Generate a URL string describing CWE.  The caller is
>>> responsible for
>>> +   freeing the string.  */
>>> +
>>> +static char *
>>> +get_cwe_url (int cwe)
>>> +{
>>> +  return xasprintf ("
>>> https://cwe.mitre.org/data/definitions/%i.html", cwe);
>>> +}
>>> +
>>> +/* If DIAGNOSTIC has metadata, print it.
>>> +
>>> +   For example, if the diagnostic metadata associates it with CWE-
>>> 119,
>>> +   " [CWE-119]" will be printed, suitably colorized, and with a
>>> URL of a
>>> +   description of the security issue.  */
>>> +
>>> +static void
>>> +print_any_metadata (diagnostic_context *context,
>>> +		    const diagnostic_info *diagnostic)
>>> +{
>>> +  if (diagnostic->metadata == NULL)
>>> +    return;
>>> +
>>> +  int cwe = diagnostic->metadata->get_cwe ();
>>> +  if (cwe)
>>> +    {
>>> +      pretty_printer *pp = context->printer;
>>> +      char *saved_prefix = pp_take_prefix (context->printer);
>>> +      pp_string (pp, " [");
>>> +      pp_string (pp, colorize_start (pp_show_color (pp),
>>> +				     diagnostic_kind_color[diagnostic-
>>>> kind]));
>>> +      char *cwe_url = get_cwe_url (cwe);
>>> +      pp_begin_url (pp, cwe_url);
>>> +      free (cwe_url);
>>> +      pp_printf (pp, "CWE-%i", cwe);
>>> +      pp_set_prefix (context->printer, saved_prefix);
>>> +      pp_end_url (pp);
>>> +      pp_string (pp, colorize_stop (pp_show_color (pp)));
>>> +      pp_character (pp, ']');
>>> +    }
>>> +}
>>> +
>>>    /* Print any metadata about the option used to control DIAGNOSTIC
>>> to CONTEXT's
>>>       printer, e.g. " [-Werror=uninitialized]".
>>>       Subroutine of diagnostic_report_diagnostic.  */
>>> @@ -1059,6 +1105,8 @@ diagnostic_report_diagnostic
>>> (diagnostic_context *context,
>>>      pp_format (context->printer, &diagnostic->message);
>>>      (*diagnostic_starter (context)) (context, diagnostic);
>>>      pp_output_formatted_text (context->printer);
>>> +  if (context->show_metadata)
>>> +    print_any_metadata (context, diagnostic);
>>>      if (context->show_option_requested)
>>>        print_option_information (context, diagnostic,
>>> orig_diag_kind);
>>>      (*diagnostic_finalizer (context)) (context, diagnostic,
>>> orig_diag_kind);
>>> @@ -1190,8 +1238,8 @@ diagnostic_append_note (diagnostic_context
>>> *context,
>>>       permerror, error, error_at, error_at, sorry, fatal_error,
>>> internal_error,
>>>       and internal_error_no_backtrace, as documented and defined
>>> below.  */
>>>    static bool
>>> -diagnostic_impl (rich_location *richloc, int opt,
>>> -		 const char *gmsgid,
>>> +diagnostic_impl (rich_location *richloc, const diagnostic_metadata
>>> *metadata,
>>> +		 int opt, const char *gmsgid,
>>>    		 va_list *ap, diagnostic_t kind)
>>>    {
>>>      diagnostic_info diagnostic;
>>> @@ -1207,13 +1255,15 @@ diagnostic_impl (rich_location *richloc,
>>> int opt,
>>>          if (kind == DK_WARNING || kind == DK_PEDWARN)
>>>    	diagnostic.option_index = opt;
>>>        }
>>> +  diagnostic.metadata = metadata;
>>>      return diagnostic_report_diagnostic (global_dc, &diagnostic);
>>>    }
>>>    
>>>    /* Implement inform_n, warning_n, and error_n, as documented and
>>>       defined below.  */
>>>    static bool
>>> -diagnostic_n_impl (rich_location *richloc, int opt, unsigned
>>> HOST_WIDE_INT n,
>>> +diagnostic_n_impl (rich_location *richloc, const
>>> diagnostic_metadata *metadata,
>>> +		   int opt, unsigned HOST_WIDE_INT n,
>>>    		   const char *singular_gmsgid,
>>>    		   const char *plural_gmsgid,
>>>    		   va_list *ap, diagnostic_t kind)
>>> @@ -1233,6 +1283,7 @@ diagnostic_n_impl (rich_location *richloc,
>>> int opt, unsigned HOST_WIDE_INT n,
>>>      diagnostic_set_info_translated (&diagnostic, text, ap, richloc,
>>> kind);
>>>      if (kind == DK_WARNING)
>>>        diagnostic.option_index = opt;
>>> +  diagnostic.metadata = metadata;
>>>      return diagnostic_report_diagnostic (global_dc, &diagnostic);
>>>    }
>>>    
>>> @@ -1246,7 +1297,7 @@ emit_diagnostic (diagnostic_t kind,
>>> location_t location, int opt,
>>>      va_list ap;
>>>      va_start (ap, gmsgid);
>>>      rich_location richloc (line_table, location);
>>> -  bool ret = diagnostic_impl (&richloc, opt, gmsgid, &ap, kind);
>>> +  bool ret = diagnostic_impl (&richloc, NULL, opt, gmsgid, &ap,
>>> kind);
>>>      va_end (ap);
>>>      return ret;
>>>    }
>>> @@ -1260,7 +1311,7 @@ emit_diagnostic (diagnostic_t kind,
>>> rich_location *richloc, int opt,
>>>      auto_diagnostic_group d;
>>>      va_list ap;
>>>      va_start (ap, gmsgid);
>>> -  bool ret = diagnostic_impl (richloc, opt, gmsgid, &ap, kind);
>>> +  bool ret = diagnostic_impl (richloc, NULL, opt, gmsgid, &ap,
>>> kind);
>>>      va_end (ap);
>>>      return ret;
>>>    }
>>> @@ -1272,7 +1323,18 @@ emit_diagnostic_valist (diagnostic_t kind,
>>> location_t location, int opt,
>>>    			const char *gmsgid, va_list *ap)
>>>    {
>>>      rich_location richloc (line_table, location);
>>> -  return diagnostic_impl (&richloc, opt, gmsgid, ap, kind);
>>> +  return diagnostic_impl (&richloc, NULL, opt, gmsgid, ap, kind);
>>> +}
>>> +
>>> +/* Wrapper around diagnostic_impl taking a va_list parameter.  */
>>> +
>>> +bool
>>> +emit_diagnostic_valist (diagnostic_t kind, rich_location *richloc,
>>> +			const diagnostic_metadata *metadata,
>>> +			int opt,
>>> +			const char *gmsgid, va_list *ap)
>>> +{
>>> +  return diagnostic_impl (richloc, metadata, opt, gmsgid, ap,
>>> kind);
>>>    }
>>>    
>>>    /* An informative note at LOCATION.  Use this for additional
>>> details on an error
>>> @@ -1284,7 +1346,7 @@ inform (location_t location, const char
>>> *gmsgid, ...)
>>>      va_list ap;
>>>      va_start (ap, gmsgid);
>>>      rich_location richloc (line_table, location);
>>> -  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_NOTE);
>>> +  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_NOTE);
>>>      va_end (ap);
>>>    }
>>>    
>>> @@ -1297,7 +1359,7 @@ inform (rich_location *richloc, const char
>>> *gmsgid, ...)
>>>      auto_diagnostic_group d;
>>>      va_list ap;
>>>      va_start (ap, gmsgid);
>>> -  diagnostic_impl (richloc, -1, gmsgid, &ap, DK_NOTE);
>>> +  diagnostic_impl (richloc, NULL, -1, gmsgid, &ap, DK_NOTE);
>>>      va_end (ap);
>>>    }
>>>    
>>> @@ -1311,7 +1373,7 @@ inform_n (location_t location, unsigned
>>> HOST_WIDE_INT n,
>>>      va_start (ap, plural_gmsgid);
>>>      auto_diagnostic_group d;
>>>      rich_location richloc (line_table, location);
>>> -  diagnostic_n_impl (&richloc, -1, n, singular_gmsgid,
>>> plural_gmsgid,
>>> +  diagnostic_n_impl (&richloc, NULL, -1, n, singular_gmsgid,
>>> plural_gmsgid,
>>>    		     &ap, DK_NOTE);
>>>      va_end (ap);
>>>    }
>>> @@ -1326,7 +1388,7 @@ warning (int opt, const char *gmsgid, ...)
>>>      va_list ap;
>>>      va_start (ap, gmsgid);
>>>      rich_location richloc (line_table, input_location);
>>> -  bool ret = diagnostic_impl (&richloc, opt, gmsgid, &ap,
>>> DK_WARNING);
>>> +  bool ret = diagnostic_impl (&richloc, NULL, opt, gmsgid, &ap,
>>> DK_WARNING);
>>>      va_end (ap);
>>>      return ret;
>>>    }
>>> @@ -1342,7 +1404,7 @@ warning_at (location_t location, int opt,
>>> const char *gmsgid, ...)
>>>      va_list ap;
>>>      va_start (ap, gmsgid);
>>>      rich_location richloc (line_table, location);
>>> -  bool ret = diagnostic_impl (&richloc, opt, gmsgid, &ap,
>>> DK_WARNING);
>>> +  bool ret = diagnostic_impl (&richloc, NULL, opt, gmsgid, &ap,
>>> DK_WARNING);
>>>      va_end (ap);
>>>      return ret;
>>>    }
>>> @@ -1357,7 +1419,25 @@ warning_at (rich_location *richloc, int opt,
>>> const char *gmsgid, ...)
>>>      auto_diagnostic_group d;
>>>      va_list ap;
>>>      va_start (ap, gmsgid);
>>> -  bool ret = diagnostic_impl (richloc, opt, gmsgid, &ap,
>>> DK_WARNING);
>>> +  bool ret = diagnostic_impl (richloc, NULL, opt, gmsgid, &ap,
>>> DK_WARNING);
>>> +  va_end (ap);
>>> +  return ret;
>>> +}
>>> +
>>> +/* Same as "warning at" above, but using METADATA.  */
>>> +
>>> +bool
>>> +warning_at (rich_location *richloc, const diagnostic_metadata
>>> &metadata,
>>> +	    int opt, const char *gmsgid, ...)
>>> +{
>>> +  gcc_assert (richloc);
>>> +
>>> +  auto_diagnostic_group d;
>>> +  va_list ap;
>>> +  va_start (ap, gmsgid);
>>> +  bool ret
>>> +    = diagnostic_impl (richloc, &metadata, opt, gmsgid, &ap,
>>> +		       DK_WARNING);
>>>      va_end (ap);
>>>      return ret;
>>>    }
>>> @@ -1373,7 +1453,7 @@ warning_n (rich_location *richloc, int opt,
>>> unsigned HOST_WIDE_INT n,
>>>      auto_diagnostic_group d;
>>>      va_list ap;
>>>      va_start (ap, plural_gmsgid);
>>> -  bool ret = diagnostic_n_impl (richloc, opt, n,
>>> +  bool ret = diagnostic_n_impl (richloc, NULL, opt, n,
>>>    				singular_gmsgid, plural_gmsgid,
>>>    				&ap, DK_WARNING);
>>>      va_end (ap);
>>> @@ -1392,7 +1472,7 @@ warning_n (location_t location, int opt,
>>> unsigned HOST_WIDE_INT n,
>>>      va_list ap;
>>>      va_start (ap, plural_gmsgid);
>>>      rich_location richloc (line_table, location);
>>> -  bool ret = diagnostic_n_impl (&richloc, opt, n,
>>> +  bool ret = diagnostic_n_impl (&richloc, NULL, opt, n,
>>>    				singular_gmsgid, plural_gmsgid,
>>>    				&ap, DK_WARNING);
>>>      va_end (ap);
>>> @@ -1419,7 +1499,7 @@ pedwarn (location_t location, int opt, const
>>> char *gmsgid, ...)
>>>      va_list ap;
>>>      va_start (ap, gmsgid);
>>>      rich_location richloc (line_table, location);
>>> -  bool ret = diagnostic_impl (&richloc, opt, gmsgid, &ap,
>>> DK_PEDWARN);
>>> +  bool ret = diagnostic_impl (&richloc, NULL, opt, gmsgid, &ap,
>>> DK_PEDWARN);
>>>      va_end (ap);
>>>      return ret;
>>>    }
>>> @@ -1434,7 +1514,7 @@ pedwarn (rich_location *richloc, int opt,
>>> const char *gmsgid, ...)
>>>      auto_diagnostic_group d;
>>>      va_list ap;
>>>      va_start (ap, gmsgid);
>>> -  bool ret = diagnostic_impl (richloc, opt, gmsgid, &ap,
>>> DK_PEDWARN);
>>> +  bool ret = diagnostic_impl (richloc, NULL, opt, gmsgid, &ap,
>>> DK_PEDWARN);
>>>      va_end (ap);
>>>      return ret;
>>>    }
>>> @@ -1453,7 +1533,7 @@ permerror (location_t location, const char
>>> *gmsgid, ...)
>>>      va_list ap;
>>>      va_start (ap, gmsgid);
>>>      rich_location richloc (line_table, location);
>>> -  bool ret = diagnostic_impl (&richloc, -1, gmsgid, &ap,
>>> DK_PERMERROR);
>>> +  bool ret = diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap,
>>> DK_PERMERROR);
>>>      va_end (ap);
>>>      return ret;
>>>    }
>>> @@ -1468,7 +1548,7 @@ permerror (rich_location *richloc, const char
>>> *gmsgid, ...)
>>>      auto_diagnostic_group d;
>>>      va_list ap;
>>>      va_start (ap, gmsgid);
>>> -  bool ret = diagnostic_impl (richloc, -1, gmsgid, &ap,
>>> DK_PERMERROR);
>>> +  bool ret = diagnostic_impl (richloc, NULL, -1, gmsgid, &ap,
>>> DK_PERMERROR);
>>>      va_end (ap);
>>>      return ret;
>>>    }
>>> @@ -1482,7 +1562,7 @@ error (const char *gmsgid, ...)
>>>      va_list ap;
>>>      va_start (ap, gmsgid);
>>>      rich_location richloc (line_table, input_location);
>>> -  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_ERROR);
>>> +  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_ERROR);
>>>      va_end (ap);
>>>    }
>>>    
>>> @@ -1496,7 +1576,7 @@ error_n (location_t location, unsigned
>>> HOST_WIDE_INT n,
>>>      va_list ap;
>>>      va_start (ap, plural_gmsgid);
>>>      rich_location richloc (line_table, location);
>>> -  diagnostic_n_impl (&richloc, -1, n, singular_gmsgid,
>>> plural_gmsgid,
>>> +  diagnostic_n_impl (&richloc, NULL, -1, n, singular_gmsgid,
>>> plural_gmsgid,
>>>    		     &ap, DK_ERROR);
>>>      va_end (ap);
>>>    }
>>> @@ -1509,7 +1589,7 @@ error_at (location_t loc, const char *gmsgid,
>>> ...)
>>>      va_list ap;
>>>      va_start (ap, gmsgid);
>>>      rich_location richloc (line_table, loc);
>>> -  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_ERROR);
>>> +  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_ERROR);
>>>      va_end (ap);
>>>    }
>>>    
>>> @@ -1523,7 +1603,7 @@ error_at (rich_location *richloc, const char
>>> *gmsgid, ...)
>>>      auto_diagnostic_group d;
>>>      va_list ap;
>>>      va_start (ap, gmsgid);
>>> -  diagnostic_impl (richloc, -1, gmsgid, &ap, DK_ERROR);
>>> +  diagnostic_impl (richloc, NULL, -1, gmsgid, &ap, DK_ERROR);
>>>      va_end (ap);
>>>    }
>>>    
>>> @@ -1537,7 +1617,7 @@ sorry (const char *gmsgid, ...)
>>>      va_list ap;
>>>      va_start (ap, gmsgid);
>>>      rich_location richloc (line_table, input_location);
>>> -  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_SORRY);
>>> +  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_SORRY);
>>>      va_end (ap);
>>>    }
>>>    
>>> @@ -1549,7 +1629,7 @@ sorry_at (location_t loc, const char *gmsgid,
>>> ...)
>>>      va_list ap;
>>>      va_start (ap, gmsgid);
>>>      rich_location richloc (line_table, loc);
>>> -  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_SORRY);
>>> +  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_SORRY);
>>>      va_end (ap);
>>>    }
>>>    
>>> @@ -1571,7 +1651,7 @@ fatal_error (location_t loc, const char
>>> *gmsgid, ...)
>>>      va_list ap;
>>>      va_start (ap, gmsgid);
>>>      rich_location richloc (line_table, loc);
>>> -  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_FATAL);
>>> +  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_FATAL);
>>>      va_end (ap);
>>>    
>>>      gcc_unreachable ();
>>> @@ -1588,7 +1668,7 @@ internal_error (const char *gmsgid, ...)
>>>      va_list ap;
>>>      va_start (ap, gmsgid);
>>>      rich_location richloc (line_table, input_location);
>>> -  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_ICE);
>>> +  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_ICE);
>>>      va_end (ap);
>>>    
>>>      gcc_unreachable ();
>>> @@ -1604,7 +1684,7 @@ internal_error_no_backtrace (const char
>>> *gmsgid, ...)
>>>      va_list ap;
>>>      va_start (ap, gmsgid);
>>>      rich_location richloc (line_table, input_location);
>>> -  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_ICE_NOBT);
>>> +  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_ICE_NOBT);
>>>      va_end (ap);
>>>    
>>>      gcc_unreachable ();
>>> diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
>>> index 2fb75e6..0e4b6ad 100644
>>> --- a/gcc/diagnostic.h
>>> +++ b/gcc/diagnostic.h
>>> @@ -46,6 +46,10 @@ struct diagnostic_info
>>>      /* The location at which the diagnostic is to be reported.  */
>>>      rich_location *richloc;
>>>    
>>> +  /* An optional bundle of metadata associated with the diagnostic
>>> +     (or NULL).  */
>>> +  const diagnostic_metadata *metadata;
>>> +
>>>      /* Auxiliary data for client.  */
>>>      void *x_data;
>>>      /* The kind of diagnostic it is about.  */
>>> @@ -126,6 +130,9 @@ struct diagnostic_context
>>>      /* Character used for caret diagnostics.  */
>>>      char caret_chars[rich_location::STATICALLY_ALLOCATED_RANGES];
>>>    
>>> +  /* True if we should print any metadata associated with
>>> diagnostics.  */
>>> +  bool show_metadata;
>>> +
>>>      /* True if we should print the command line option which
>>> controls
>>>         each diagnostic, if known.  */
>>>      bool show_option_requested;
>>> diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
>>> index fc92913..209e2c6 100644
>>> --- a/gcc/doc/invoke.texi
>>> +++ b/gcc/doc/invoke.texi
>>> @@ -279,6 +279,7 @@ Objective-C and Objective-C++ Dialects}.
>>>    -fno-diagnostics-show-option  -fno-diagnostics-show-caret @gol
>>>    -fno-diagnostics-show-labels  -fno-diagnostics-show-line-numbers
>>> @gol
>>>    -fdiagnostics-nn-line-numbers @gol
>>> +-fno-diagnostics-show-metadata  @gol
>>>    -fdiagnostics-minimum-margin-width=@var{width} @gol
>>>    -fdiagnostics-parseable-fixits  -fdiagnostics-generate-patch @gol
>>>    -fdiagnostics-show-template-tree  -fno-elide-type @gol
>>> @@ -4012,6 +4013,13 @@ as the types of expressions:
>>>    This option suppresses the printing of these labels (in the
>>> example above,
>>>    the vertical bars and the ``char *'' and ``long int'' text).
>>>    
>>> +@item -fno-diagnostics-show-metadata
>>> +@opindex fno-diagnostics-show-metadata
>>> +@opindex fdiagnostics-show-metadata
>>> +Diagnostics can optionally contain @url{
>>> https://cwe.mitre.org/index.html, CWE}
>>> +metadata.  By default, if this information is present, it will be
>>> printed with
>>> +the diagnostic.  This option suppresses the printing of this
>>> metadata.
>>> +
>>>    @item -fno-diagnostics-show-line-numbers
>>>    @opindex fno-diagnostics-show-line-numbers
>>>    @opindex fdiagnostics-show-line-numbers
>>> diff --git a/gcc/opts.c b/gcc/opts.c
>>> index 9928d01..0caaf04 100644
>>> --- a/gcc/opts.c
>>> +++ b/gcc/opts.c
>>> @@ -2491,6 +2491,10 @@ common_handle_option (struct gcc_options
>>> *opts,
>>>          dc->parseable_fixits_p = value;
>>>          break;
>>>    
>>> +    case OPT_fdiagnostics_show_metadata:
>>> +      dc->show_metadata = value;
>>> +      break;
>>> +
>>>        case OPT_fdiagnostics_show_option:
>>>          dc->show_option_requested = value;
>>>          break;
>>> diff --git a/gcc/toplev.c b/gcc/toplev.c
>>> index 2d0a390..7136164 100644
>>> --- a/gcc/toplev.c
>>> +++ b/gcc/toplev.c
>>> @@ -1102,6 +1102,8 @@ general_init (const char *argv0, bool
>>> init_signals)
>>>        = global_options_init.x_flag_diagnostics_show_line_numbers;
>>>      global_dc->use_nn_for_line_numbers_p
>>>        = global_options_init.x_flag_diagnostics_nn_line_numbers;
>>> +  global_dc->show_metadata
>>> +    = global_options_init.x_flag_diagnostics_show_metadata;
>>>      global_dc->show_option_requested
>>>        = global_options_init.x_flag_diagnostics_show_option;
>>>      global_dc->min_margin_width
>>>
> 

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 11/49] Add diagnostic_metadata and CWE support
  2019-12-13 16:44       ` Martin Sebor
@ 2019-12-13 17:32         ` David Malcolm
  0 siblings, 0 replies; 160+ messages in thread
From: David Malcolm @ 2019-12-13 17:32 UTC (permalink / raw)
  To: Martin Sebor, gcc-patches

On Fri, 2019-12-13 at 09:43 -0700, Martin Sebor wrote:
> On 12/12/19 7:59 PM, David Malcolm wrote:
> > On Wed, 2019-12-04 at 10:36 -0700, Martin Sebor wrote:
> > > On 11/15/19 6:22 PM, David Malcolm wrote:
> > > > This patch adds support for associating a diagnostic with an
> > > > optional
> > > > diagnostic_metadata object, so that plugins can add extra data
> > > > to
> > > > their
> > > > diagnostics (e.g. mapping a diagnostic to a taxonomy or coding
> > > > standard
> > > > such as from CERT or MISRA).
> > > > 
> > > > Currently this only supports associating a CWE identifier with
> > > > a
> > > > diagnostic (which is what I'm using for the analyzer warnings
> > > > later
> > > > in
> > > > the patch kit), but adding a diagnostic_metadata class allows
> > > > for
> > > > future
> > > > growth in this area without an explosion of further
> > > > "warning_at"
> > > > overloads
> > > > for all of the different kinds of custom data that a plugin
> > > > might
> > > > want to
> > > > add.
> > > 
> > > We discussed this in the past so I'll be repeating some of my
> > > comments from that thread.  I like the feature at a high level,
> > > but I'm not sure I see how to make it work in a robust way.
> > > 
> > > There is no one-to-one mapping between GCC warnings and any of
> > > these classifications.  A single GCC warning might map to several
> > > CERT or MISRA guidelines, and a single guideline might map to two
> > > or more different GCC warnings.  This is an M to N mapping times
> > > the number of coding standards/classifications.  I haven't looked
> > > at the patch kit in enough detail to understand how it tries to
> > > handle it.  Can you explain?
> > 
> > The patch kit gives the ability to (optionally) specify a single
> > CWE
> > identifier when emitting a diagnostic.
> > 
> > The patch kit uses this in various places for its new warnings for
> > the
> > cases where there is a well-defined CWE weakness identifier (and
> > doesn't otherwise).
> > 
> > It doesn't attempt to classify any existing warnings.
> > 
> > It doesn't attempt to support other classifications in this patch,
> > but
> > it does introduce a diagnostic_metadata object that could gain this
> > functionality at some later time, if we come up with a scheme we're
> > happy with (a slight violation of YAGNI, but it's such a pain
> > touching
> > all the diagnostic fns that I prefer to do it once).
> 
> I wasn't necessarily asking about any classifications in particular,
> or even about pre-existing warnings.  I think it would be nice to be
> able to eventually make it work for them but I obviously realize that
> will take quite a bit retrofitting.
> 
> What I'd like to understand how the warning X classification-id
> mapping for the new analyzer warnings is supported in the design
> of the (new) diagnostic infrastructure (or how you envision it
> will be when it's more complete).
> 
> You said the analyzer currently only supports associating a CWE
> identifier with a diagnostic.  By "diagnostic" do you mean
> a particular instance of a given warning or all its instances?
> 
> Picking an uninitialized use as an example, there are three CWE's
> that discuss it:
> 
>    CWE-457: Use of Uninitialized Variable
>    CWE-824: Access of Uninitialized Pointer
>    CWE-908: Use of Uninitialized Resource
> 
> but only one -Wanalyzer-use-of-uninitialized-value.  I'd like to
> see how the mapping works (or will work) between these three CWE's
> and instances of this analyzer option/warning.
> 
> I see on Goldbolt that for either the use of an uninitialized
> pointer (CWE-457) or for its dereference (CWE-824) the analyzer
> issues the same warning with the same CWE ID:
> 
>    use of uninitialized value 'p' [CWE-457] 
> [-Wanalyzer-use-of-uninitialized-value]
> 
> It doesn't differentiate between the two CWE's.

That warning is implemented in analyzer/region-model.cc (see:
  https://gcc.gnu.org/ml/gcc-patches/2019-11/msg01554.html )

The code in question is in poisoned_value_diagnostic::emit

      case POISON_KIND_UNINIT:
	{
	  diagnostic_metadata m;
	  m.add_cwe (457); /* "CWE-457: Use of Uninitialized Variable".  */
	  return warning_at (rich_loc, m,
			     OPT_Wanalyzer_use_of_uninitialized_value,
			     "use of uninitialized value %qE",
			     m_expr);
	}
	break;

and currently that's code is only called when detecting a dereference
of an uninitialized pointer.

So, yes, this one probably should be CWE-824, but that's merely a bug
in my code, not a problem with the "infrastructure".

It looks like I'll need an extra attribute for that class to
distinguish the type of access (read from local seems to be CWE-457,
deref pointer seems to be CWE-824; CWE-908 would be emitted from a
different place, if I'm understanding the definition correctly).


> If this limitation is baked into the design of the infrastructure
> then that's an obvious flaw that needs to be fixed.  There is no
> point in issuing warnings with the wrong CWE's.  Anyone who cares
> about CWE's would view those as bugs, and those who don't care
> don't need to see them.
> 
> > > My other concern here is that these guidelines evolve.  New ones
> > > are being added, existing ones moved or broken up, etc.  Does
> > > the infrastructure cope with this evolution (e.g., by reading
> > > the mapping from a file)?
> > 
> > Let's cross that bridge when we come to it.  If it becomes a
> > problem,
> > then we can come up with a fix (e.g. a command-line option to
> > specify
> > the CWE version).
> I agree that dealing with evolving classifications is a secondary
> goal that can probably be fairly easily met.  I wanted to know if
> there already was a solution for it now.
> 
> But referencing the right IDs in the diagnostics seems like a basic
> design requirement.  

If you find that I'm using the wrong IDs, then tell me, and I'll fix
them.

> Unless the above example is due to the mapping
> not being fully populated yet it seems clear that issuing a warning
> with one CWE ID for a bug described under another CWE ID is
> a problem.

I'm not sure what you mean by a "mapping" being "populated".  It's
effectively just an optional int value that we can supply at diagnostic
calls.  It's different from the option ID, so yes, we can support
different CWEs for the same option ID if need be.  There isn't support
for multiple CWEs at the same instance of a warning (that could be
added if need be, but I wasn't planning to).

Dave

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 00/49] RFC: Add a static analysis framework to GCC
  2019-12-03 17:17         ` Jakub Jelinek
@ 2019-12-16 14:33           ` David Malcolm
  0 siblings, 0 replies; 160+ messages in thread
From: David Malcolm @ 2019-12-16 14:33 UTC (permalink / raw)
  To: Jakub Jelinek; +Cc: Richard Biener, Thomas Schwinge, GCC Patches

On Tue, 2019-12-03 at 18:17 +0100, Jakub Jelinek wrote:
> On Tue, Dec 03, 2019 at 11:52:13AM -0500, David Malcolm wrote:
> > > > Our plugin "interface" as such is very broad.
> > > 
> > > Just to sneak in here I don't like exposing our current plugin
> > > "non-
> > > API"
> > > more.  In fact I'd just build the analyzer into GCC with maybe an
> > > option to disable its build (in case it is very fat?).
> > 
> > My aim here is to provide a way for distributors to be able to
> > disable
> > its build - indeed, for now, for it to be disabled by default,
> > requiring opting-in.
> > 
> > My reasoning here is that the analyzer is middle-end code, but
> > isn't as
> > mature as the rest of the middle-end (but I'm working on getting it
> > more mature).
> > 
> > I want some way to label the code as a "technology preview", that
> > people may want to experiment with, but to set expectations that
> > this
> > is a lot of new code and there will be bugs - but to make it
> > available
> > to make it easier for adventurous users to try it out.
> > 
> > I hope that makes sense.
> > 
> > I went down the "in-tree plugin" path by seeing the analogy with
> > frontends, but yes, it would probably be simpler to just build it
> > into
> > GCC, guarded with a configure-time variable.  It's many thousand
> > lines
> > of non-trivial C++ code, and associated selftests and DejaGnu
> > tests.
> 
> I think it is enough to document it as tech preview in the
> documentation,
> no need to have it as an in-tree plugin.  We have lots of options
> that had
> such a state (perhaps undeclared) over the years, I'd consider
> -fvtable-verify= to be such an option, or in the past e.g.
> -fipa-matrix-reorg or -fipa-struct-reorg.  And 2.5% code growth isn't
> that
> bad.  So, as long as the option isn't enabled by default, I think
> we'd be
> fine.

FWIW I did some testing of v4 of the patch kit [1], which drops the in-
tree plugin idea in favor of simply building the analyzer into the
compiler as a regular IPA pass.  The pass is disabled by default
(enabled by -fanalyzer).  There is also a configure-time option to
disable building it (it's built by default).

I did 3 bootstraps of a release build of x86_64-pc-linux-gnu:
- unpatched,
- with the kit but with --disable-analyzer, and
- with the kit, with the analyzer enabled.

Here are the sizes of cc1 and cc1plus in bytes in each build, after
stripping debuginfo (and showing the change relative to the unpatched
build:

          Unpatched:   With kit:
                       Disabled  change:         Enabled   change:
cc1       25778720     25815672  +36952 (+0.1%)  26270328  +491608 (+1.9%)
cc1plus   27355296     27388152  +32856 (+0.1%)  27842808  +487512 (+1.8%)


So it's a little less than 2% code growth.

Dave

[1] see https://gcc.gnu.org/wiki/DavidMalcolm/StaticAnalyzer for the
various links

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 05/49] vec.h: add auto_delete_vec
  2019-12-04 16:29   ` Martin Sebor
@ 2019-12-18 16:00     ` David Malcolm
  2020-01-08 21:52       ` Jeff Law
  0 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2019-12-18 16:00 UTC (permalink / raw)
  To: Martin Sebor, gcc-patches

On Wed, 2019-12-04 at 09:29 -0700, Martin Sebor wrote:
> On 11/15/19 6:22 PM, David Malcolm wrote:
> > This patch adds a class auto_delete_vec<T>, a subclass of auto_vec
> > <T *>
> > that deletes all of its elements on destruction; it's used in many
> > places later in the kit.
> > 
> > This is a crude way for a vec to "own" the objects it points to
> > and clean up automatically (essentially a workaround for not being
> > able
> > to use unique_ptr, due to C++98).
> > 
> > gcc/ChangeLog:
> > 	* vec.c (class selftest::count_dtor): New class.
> > 	(selftest::test_auto_delete_vec): New test.
> > 	(selftest::vec_c_tests): Call it.
> > 	* vec.h (class auto_delete_vec): New class template.
> > 	(auto_delete_vec<T>::~auto_delete_vec): New dtor.
> 
> Because of slicing, unless preventing the elements from being
> deleted in the class dtor is meant to be a feature, it seems that
> using a wrapper class rather than public derivation from auto_vec
> might be a safer solution.
> 
> It might be worth mentioning in a comment that the class isn't
> safe to copy or assign (each copy would wind up delete the same
> pointers), in addition to making its copy ctor and copy assignment
> operator inaccessible or deleted.
> 
> Martin

In the version of the patch in the v4 kit:
  https://gcc.gnu.org/ml/gcc-patches/2019-12/msg01035.html
I added:
  private:
     DISABLE_COPY_AND_ASSIGN(auto_delete_vec<T>);
to the class.

Does that satisfy your concerns about slicing? (and, indeed, about
copying and assigning)

Thanks
Dave

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 12/49] Add diagnostic paths
  2019-12-07 14:45   ` Jeff Law
@ 2019-12-19  2:49     ` David Malcolm
  0 siblings, 0 replies; 160+ messages in thread
From: David Malcolm @ 2019-12-19  2:49 UTC (permalink / raw)
  To: law, gcc-patches

On Sat, 2019-12-07 at 07:45 -0700, Jeff Law wrote:
> On Fri, 2019-11-15 at 20:22 -0500, David Malcolm wrote:
> > This patch adds support for associating a "diagnostic_path" with a
> > diagnostic: a sequence of events predicted by the compiler that
> > leads
> > to
> > the problem occurring, with their locations in the user's source,
> > text descriptions, and stack information (for handling
> > interprocedural
> > paths).
> > 
> > For example, the following (hypothetical) error has a 3-event
> > intraprocedural path:
> > 
> > test.c: In function 'demo':
> > test.c:29:5: error: passing NULL as argument 1 to 'PyList_Append'
> > which
> >   requires a non-NULL parameter
> >    29 |     PyList_Append(list, item);
> >       |     ^~~~~~~~~~~~~~~~~~~~~~~~~
> >   'demo': events 1-3
> >      |
> >      |   25 |   list = PyList_New(0);
> >      |      |          ^~~~~~~~~~~~~
> >      |      |          |
> >      |      |          (1) when 'PyList_New' fails, returning NULL
> >      |   26 |
> >      |   27 |   for (i = 0; i < count; i++) {
> >      |      |   ~~~
> >      |      |   |
> >      |      |   (2) when 'i < count'
> >      |   28 |     item = PyLong_FromLong(random());
> >      |   29 |     PyList_Append(list, item);
> >      |      |     ~~~~~~~~~~~~~~~~~~~~~~~~~
> >      |      |     |
> >      |      |     (3) when calling 'PyList_Append', passing NULL
> > from
> > (1) as argument 1
> >      |
> > 
> > The patch adds a new "%@" format code for printing event IDs, so
> > that
> > in the above, the description of event (3) mentions event (1),
> > showing
> > the user where the bogus NULL value comes from (the event IDs are
> > colorized to draw the user's attention to them).
> > 
> > There is a separation between data vs presentation: the above shows
> > how
> > the diagnostic-printing code has consolidated the path into a
> > single
> > run
> > of events, since all the events are near each other and within the
> > same
> > function; more complicated examples (such as interprocedural paths)
> > might be printed as multiple runs of events.
> > 
> > Examples of how interprocedural paths are printed can be seen in
> > the
> > test suite (which uses a plugin to exercise the code without
> > relying
> > on specific warnings using this functionality).
> > 
> > Other output formats include
> > - JSON,
> > - printing each event as a separate "note", and
> > - to not emit paths.
> > 
> > (I have a separate script that can generate HTML from the JSON, but
> > HTML
> > is not my speciality; help from a web front-end expert to make it
> > look
> > good would be appreciated).
> > 
> > gcc/ChangeLog:
> > 	* Makefile.in (OBJS): Add tree-diagnostic-path.o.
> > 	* common.opt (fdiagnostics-path-format=): New option.
> > 	(diagnostic_path_format): New enum.
> > 	(fdiagnostics-show-path-depths): New option.
> > 	* coretypes.h (diagnostic_event_id_t): New forward decl.
> > 	* diagnostic-color.c (color_dict): Add "path".
> > 	* diagnostic-event-id.h: New file.
> > 	* diagnostic-format-json.cc (json_from_expanded_location): Make
> > 	non-static.
> > 	(json_end_diagnostic): Call context->make_json_for_path if it
> > 	exists and the diagnostic has a path.
> > 	(diagnostic_output_format_init): Clear context->print_path.
> > 	* diagnostic-path.h: New file.
> > 	* diagnostic-show-locus.c (colorizer::set_range): Special-case
> > 	when printing a run of events in a diagnostic_path so that they
> > 	all get the same color.
> > 	(layout::m_diagnostic_path_p): New field.
> > 	(layout::layout): Initialize it.
> > 	(layout::print_any_labels): Don't colorize the label text for
> > an
> > 	event in a diagnostic_path.
> > 	(gcc_rich_location::add_location_if_nearby): Add
> > 	"restrict_to_current_line_spans" and "label" params.  Pass the
> > 	former to layout.maybe_add_location_range; pass the latter
> > 	when calling add_range.
> > 	* diagnostic.c: Include "diagnostic-path.h".
> > 	(diagnostic_initialize): Initialize context->path_format and
> > 	context->show_path_depths.
> > 	(diagnostic_show_any_path): New function.
> > 	(diagnostic_path::interprocedural_p): New function.
> > 	(diagnostic_report_diagnostic): Call diagnostic_show_any_path.
> > 	(simple_diagnostic_path::num_events): New function.
> > 	(simple_diagnostic_path::get_event): New function.
> > 	(simple_diagnostic_path::add_event): New function.
> > 	(simple_diagnostic_event::simple_diagnostic_event): New ctor.
> > 	(simple_diagnostic_event::~simple_diagnostic_event): New dtor.
> > 	(debug): New overload taking a diagnostic_path *.
> > 	* diagnostic.def (DK_DIAGNOSTIC_PATH): New.
> > 	* diagnostic.h (enum diagnostic_path_format): New enum.
> > 	(json::value): New forward decl.
> > 	(diagnostic_context::path_format): New field.
> > 	(diagnostic_context::show_path_depths): New field.
> > 	(diagnostic_context::print_path): New callback field.
> > 	(diagnostic_context::make_json_for_path): New callback field.
> > 	(diagnostic_show_any_path): New decl.
> > 	(json_from_expanded_location): New decl.
> > 	* doc/invoke.texi (-fdiagnostics-path-format=): New option.
> > 	(-fdiagnostics-show-path-depths): New option.
> > 	(-fdiagnostics-color): Add "path" to description of default
> > 	GCC_COLORS; describe it.
> > 	(-fdiagnostics-format=json): Document how diagnostic paths are
> > 	represented in the JSON output format.
> > 	* gcc-rich-location.h
> > (gcc_rich_location::add_location_if_nearby):
> > 	Add optional params "restrict_to_current_line_spans" and
> > "label".
> > 	* opts.c (common_handle_option): Handle
> > 	OPT_fdiagnostics_path_format_ and
> > 	OPT_fdiagnostics_show_path_depths.
> > 	* pretty-print.c: Include "diagnostic-event-id.h".
> > 	(pp_format): Implement "%@" format code for printing
> > 	diagnostic_event_id_t *.
> > 	(selftest::test_pp_format): Add tests for "%@".
> > 	* selftest-run-tests.c (selftest::run_tests): Call
> > 	selftest::tree_diagnostic_path_cc_tests.
> > 	* selftest.h (selftest::tree_diagnostic_path_cc_tests): New
> > decl.
> > 	* toplev.c (general_init): Initialize global_dc->path_format
> > and
> > 	global_dc->show_path_depths.
> > 	* tree-diagnostic-path.cc: New file.
> > 	* tree-diagnostic.c (maybe_unwind_expanded_macro_loc): Make
> > 	non-static.  Drop "diagnostic" param in favor of storing the
> > 	original value of "where" and re-using it.
> > 	(virt_loc_aware_diagnostic_finalizer): Update for dropped param
> > of
> > 	maybe_unwind_expanded_macro_loc.
> > 	(tree_diagnostics_defaults): Initialize context->print_path and
> > 	context->make_json_for_path.
> > 	* tree-diagnostic.h (default_tree_diagnostic_path_printer): New
> > 	decl.
> > 	(default_tree_make_json_for_path): New decl.
> > 	(maybe_unwind_expanded_macro_loc): New decl.
> > 
> > gcc/c-family/ChangeLog:
> > 	* c-format.c (local_event_ptr_node): New.
> > 	(PP_FORMAT_CHAR_TABLE): Add entry for "%@".
> > 	(init_dynamic_diag_info): Initialize local_event_ptr_node.
> > 	* c-format.h (T_EVENT_PTR): New define.
> > 
> > gcc/testsuite/ChangeLog:
> > 	* gcc.dg/format/gcc_diag-10.c (diagnostic_event_id_t): New
> > 	typedef.
> > 	(test_diag): Add coverage of "%@".
> > 	* gcc.dg/plugin/diagnostic-path-format-default.c: New test.
> > 	* gcc.dg/plugin/diagnostic-path-format-inline-events-1.c: New
> > test.
> > 	* gcc.dg/plugin/diagnostic-path-format-inline-events-2.c: New
> > test.
> > 	* gcc.dg/plugin/diagnostic-path-format-inline-events-3.c: New
> > test.
> > 	* gcc.dg/plugin/diagnostic-path-format-none.c: New test.
> > 	* gcc.dg/plugin/diagnostic-test-paths-1.c: New test.
> > 	* gcc.dg/plugin/diagnostic-test-paths-2.c: New test.
> > 	* gcc.dg/plugin/diagnostic-test-paths-3.c: New test.
> > 	* gcc.dg/plugin/diagnostic_plugin_test_paths.c: New.
> > 	* gcc.dg/plugin/plugin.exp: Add the new plugin and test cases.
> > 
> > libcpp/ChangeLog:
> > 	* include/line-map.h (class diagnostic_path): New forward decl.
> > 	(rich_location::get_path): New accessor.
> > 	(rich_location::set_path): New function.
> > 	(rich_location::m_path): New field.
> > 	* line-map.c (rich_location::rich_location): Initialize m_path.
> Seems like we could make an argument to include this in an expanded
> "diagnostic framework" maintainership which would allow self-
> approval.

I think I can self-approve this (and there's an argument that it could
be useful to plugin authors even without the analyzer itself).

That said, it has a (slight) dependency on the auto_delete_vec patch,
which I can't self-approve; see:
  https://gcc.gnu.org/ml/gcc-patches/2019-12/msg01035.html

Dave

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 20/49] analyzer: new builtins
  2019-12-04 18:11   ` Martin Sebor
@ 2019-12-19 15:16     ` David Malcolm
  0 siblings, 0 replies; 160+ messages in thread
From: David Malcolm @ 2019-12-19 15:16 UTC (permalink / raw)
  To: Martin Sebor, gcc-patches

On Wed, 2019-12-04 at 11:11 -0700, Martin Sebor wrote:
> On 11/15/19 6:23 PM, David Malcolm wrote:
> > gcc/ChangeLog:
> > 	* builtins.def (BUILT_IN_ANALYZER_BREAK): New builtin.
> > 	(BUILT_IN_ANALYZER_DUMP): New builtin.
> > 	(BUILT_IN_ANALYZER_DUMP_EXPLODED_NODES): New builtin.
> > 	(BUILT_IN_ANALYZER_DUMP_NUM_HEAP_REGIONS): New builtin.
> > 	(BUILT_IN_ANALYZER_DUMP_PATH): New builtin.
> > 	(BUILT_IN_ANALYZER_DUMP_REGION_MODEL): New builtin.
> > 	(BUILT_IN_ANALYZER_EVAL): New builtin.
> 
> I have two comments:
> 
> 1) builtins.def is getting very large and I think it would make
> it easier to work with if new related sets of built-ins were added
> in their own files.  (In full disclosure, I tried breaking it up
> once and ran into weird build errors and gave up, so it may not
> be entirely straightforward, not like making other changes to
> the compiler ;)
> 
> 2) Since there are no changes to the manual documenting these,
> I assume they are internal only, not to be called by user code?
> (Should they be marked as internal functions then, to prevent
> them from being called?)  Even so, it would help GCC developers
> to document them somewhere and add a comment pointing at
> the documentation here.  If they can be called by user code,
> I think they should be mentioned in the manual even if (and
> I'd say especially if) we don't users calling them directly.

Thanks for the feedback.  FWIW  I've reworked things so that the
analyzer no longer has builtins, but instead recognizes certain "magic"
function names (for use in DejaGnu and when debugging):
  https://gcc.gnu.org/ml/gcc-patches/2019-12/msg01310.html

Dave

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 0/4] analyzer: add class function_set and use in various places
  2019-12-11 19:49     ` David Malcolm
@ 2019-12-20  1:21       ` David Malcolm
  2019-12-20  1:22         ` [PATCH 2/4] analyzer: introduce a set of known async-signal-unsafe functions David Malcolm
                           ` (3 more replies)
  0 siblings, 4 replies; 160+ messages in thread
From: David Malcolm @ 2019-12-20  1:21 UTC (permalink / raw)
  To: law, gcc-patches; +Cc: David Malcolm

On Wed, 2019-12-11 at 14:48 -0500, David Malcolm wrote:
> On Sat, 2019-12-07 at 08:01 -0700, Jeff Law wrote:
> > On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> [...]
> > > diff --git a/gcc/analyzer/analyzer.cc b/gcc/analyzer/analyzer.cc
> > > new file mode 100644
> > > index 0000000..399925c
> > > --- /dev/null
> > > +++ b/gcc/analyzer/analyzer.cc
> [...]
> > > +
> > > +/* Helper function for checkers.  Is the CALL to the given
> > > function
> > > name?  */
> > > +
> > > +bool
> > > +is_named_call_p (const gcall *call, const char *funcname)
> > > +{
> > > +  gcc_assert (funcname);
> > > +
> > > +  tree fndecl = gimple_call_fndecl (call);
> > > +  if (!fndecl)
> > > +    return false;
> > > +
> > > +  return 0 == strcmp (IDENTIFIER_POINTER (DECL_NAME (fndecl)),
> > > funcname);
> > > +}
> > > +
> > > +/* Helper function for checkers.  Is the CALL to the given
> > > function
> > > name,
> > > +   and with the given number of arguments?  */
> > > +
> > > +bool
> > > +is_named_call_p (const gcall *call, const char *funcname,
> > > +		 unsigned int num_args)
> > > +{
> > > +  gcc_assert (funcname);
> > > +
> > > +  if (!is_named_call_p (call, funcname))
> > > +    return false;
> > > +
> > > +  if (gimple_call_num_args (call) != num_args)
> > > +    return false;
> > > +
> > > +  return true;
> > These seem generic enough to live elsewhere.
> 
> There's a potential can of worms here: these functions are used by
> the
> checkers to detect fndecls by name, so that checkers can associate
> behavior with them.  Examples:
> 
> * sm-malloc.c recognizes "malloc" and "free" as being special
> 
> * sm-signal.c knows that fprintf is async-signal-unsafe (but
> currently
> doesn't know about any other fns; it's a proof-of-concept)
> 
> * sm-file.c currently doesn't know anything about "__fsetlocking",
> which is one of the reasons the analyzer currently doesn't detect the
> leak in intl/localealias.c reported as PR 47170, as it assumes that
> the
> FILE * might have been closed and stops tracking state for it.
> 
> etc.
> 
> So we're going to have a lot more "recognize this function by name"
> just to finish the existing proof-of-concept checkers.
> 
> 
> In a perfect world, perhaps we'd have attributes for all of this, and
> the user's code and their system headers would have dutifully marked
> the pertinent decls with attributes, and the use of is_named_call_p
> could be thought of as a bug (or wart)...  but we don't live in that
> perfect world, and a good static analyzer shouldn't need to rely on
> the
> user marking their code.
> 
> There's also integration and chicken-and-egg issues with attributes
> where if we rely e.g. on the user's libc headers having attributes
> for
> the checker, then we need to coordinate with e.g. glibc to add the
> attributes, and implement them, and then the checker doesn't work if
> someone is using a different libc, etc, etc.
> 
> So I think we want a concept of "known functions" in the analyzer,
> where the analyzer can have its own "on the side" model of APIs -
> perhaps a mixture of baked-in (e.g. for malloc/free), perhaps from an
> overridable config file, but I'm not quite sure what form it should
> take.  Maybe even a pragma that lets us tag named functions with
> attributes, or somesuch.
> 
> For now, however, I want to take the pragmatic approach, and use
> these
> functions as needed (and to review this as we gain experience with
> the
> analyzer).
> 
> So I think I prefer to keep them in the analyzer subdir (but I'm
> happy
> to move them if you'd prefer that)
> 
> Does the above sound sane?
 
FWIW I followed up on the above ideas with the following patches.

They add a simple "function_set" class to the analyzer, based on
hardcoded arrays of strings (but they could be loaded from files if need
be), and then use the mechanism in a few places (adding a new
-Wanalyzer-use-of-closed-file warning to sm-file.cc for good
measure).

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
I've pushed these patches to dmalcolm/analyzer on the GCC git mirror.

Dave

David Malcolm (4):
  analyzer: add function-set.cc/h
  analyzer: introduce a set of known async-signal-unsafe functions
  analyzer: add known stdio functions to sm-file.cc (PR analyzer/58237)
  analyzer: add -Wanalyzer-use-of-closed-file

 gcc/Makefile.in                              |   1 +
 gcc/analyzer/analyzer-selftests.cc           |   3 +
 gcc/analyzer/analyzer-selftests.h            |   3 +
 gcc/analyzer/analyzer.opt                    |   4 +
 gcc/analyzer/function-set.cc                 | 191 ++++++++++++++++++
 gcc/analyzer/function-set.h                  |  46 +++++
 gcc/analyzer/sm-file.cc                      | 192 ++++++++++++++++++-
 gcc/analyzer/sm-signal.cc                    |  57 +++++-
 gcc/doc/invoke.texi                          |  13 ++
 gcc/testsuite/gcc.dg/analyzer/file-1.c       |  20 ++
 gcc/testsuite/gcc.dg/analyzer/file-pr58237.c |  72 +++++++
 gcc/testsuite/gcc.dg/analyzer/signal-5.c     |  21 ++
 12 files changed, 615 insertions(+), 8 deletions(-)
 create mode 100644 gcc/analyzer/function-set.cc
 create mode 100644 gcc/analyzer/function-set.h
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/file-pr58237.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/signal-5.c

-- 
2.21.0

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 2/4] analyzer: introduce a set of known async-signal-unsafe functions
  2019-12-20  1:21       ` [PATCH 0/4] analyzer: add class function_set and use in various places David Malcolm
@ 2019-12-20  1:22         ` David Malcolm
  2019-12-20  1:22         ` [PATCH 1/4] analyzer: add function-set.cc/h David Malcolm
                           ` (2 subsequent siblings)
  3 siblings, 0 replies; 160+ messages in thread
From: David Malcolm @ 2019-12-20  1:22 UTC (permalink / raw)
  To: law, gcc-patches; +Cc: David Malcolm

This patch uses the class function_set from the previous patch to
generalize the test for an fprintf inside a signal handler to
check for a set of known async-signal-unsafe functions.

gcc/analyzer/ChangeLog:
	* analyzer-selftests.cc (selftest::run_analyzer_selftests): Call
	selftest::analyzer_sm_signal_cc_tests.
	* analyzer-selftests.h (selftest::analyzer_sm_signal_cc_tests):
	New decl.
	* sm-signal.cc: Include "analyzer/function-set.h" and
	"analyzer/analyzer-selftests.h".
	(get_async_signal_unsafe_fns): New function.
	(signal_unsafe_p): Reimplement in terms of the above.
	(selftest::analyzer_sm_signal_cc_tests): New function.

gcc/testsuite/ChangeLog:
	* gcc.dg/analyzer/signal-5.c: New test.
---
 gcc/analyzer/analyzer-selftests.cc       |  1 +
 gcc/analyzer/analyzer-selftests.h        |  1 +
 gcc/analyzer/sm-signal.cc                | 57 +++++++++++++++++++++---
 gcc/testsuite/gcc.dg/analyzer/signal-5.c | 21 +++++++++
 4 files changed, 73 insertions(+), 7 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/signal-5.c

diff --git a/gcc/analyzer/analyzer-selftests.cc b/gcc/analyzer/analyzer-selftests.cc
index 8201c21525c4..f90dad8e991f 100644
--- a/gcc/analyzer/analyzer-selftests.cc
+++ b/gcc/analyzer/analyzer-selftests.cc
@@ -53,6 +53,7 @@ run_analyzer_selftests ()
   analyzer_program_point_cc_tests ();
   analyzer_program_state_cc_tests ();
   analyzer_region_model_cc_tests ();
+  analyzer_sm_signal_cc_tests ();
 #endif /* #if ENABLE_ANALYZER */
 }
 
diff --git a/gcc/analyzer/analyzer-selftests.h b/gcc/analyzer/analyzer-selftests.h
index de9bb3130df0..225b717c9d13 100644
--- a/gcc/analyzer/analyzer-selftests.h
+++ b/gcc/analyzer/analyzer-selftests.h
@@ -37,6 +37,7 @@ extern void analyzer_function_set_cc_tests ();
 extern void analyzer_program_point_cc_tests ();
 extern void analyzer_program_state_cc_tests ();
 extern void analyzer_region_model_cc_tests ();
+extern void analyzer_sm_signal_cc_tests ();
 
 } /* end of namespace selftest.  */
 
diff --git a/gcc/analyzer/sm-signal.cc b/gcc/analyzer/sm-signal.cc
index e01ff30d9172..98105b4f60fb 100644
--- a/gcc/analyzer/sm-signal.cc
+++ b/gcc/analyzer/sm-signal.cc
@@ -32,8 +32,10 @@ along with GCC; see the file COPYING3.  If not see
 #include "analyzer/analyzer.h"
 #include "analyzer/checker-path.h"
 #include "analyzer/exploded-graph.h"
+#include "analyzer/function-set.h"
 #include "analyzer/pending-diagnostic.h"
 #include "analyzer/sm.h"
+#include "analyzer/analyzer-selftests.h"
 
 #if ENABLE_ANALYZER
 
@@ -224,16 +226,40 @@ public:
   tree m_fndecl;
 };
 
-/* Return true if CALL is known to be unsafe to call from a signal handler.  */
+/* Get a set of functions that are known to be unsafe to call from an
+   async signal handler.  */
 
-static bool
-signal_unsafe_p (tree callee_fndecl)
+static function_set
+get_async_signal_unsafe_fns ()
 {
-  // TODO: maintain a list of known unsafe functions
-  if (is_named_call_p (callee_fndecl, "fprintf"))
-    return true;
+  // TODO: populate this list more fully
+  static const char * const async_signal_unsafe_fns[] = {
+    /* This array must be kept sorted.  */
+    "fprintf",
+    "free",
+    "malloc",
+    "printf",
+    "snprintf",
+    "sprintf",
+    "vfprintf",
+    "vprintf",
+    "vsnprintf",
+    "vsprintf"
+  };
+  const size_t count
+    = sizeof(async_signal_unsafe_fns) / sizeof (async_signal_unsafe_fns[0]);
+  function_set fs (async_signal_unsafe_fns, count);
+  return fs;
+};
 
-  return false;
+/* Return true if FNDECL is known to be unsafe to call from a signal
+   handler.  */
+
+static bool
+signal_unsafe_p (tree fndecl)
+{
+  function_set fs = get_async_signal_unsafe_fns ();
+  return fs.contains_decl_p (fndecl);
 }
 
 /* Implementation of state_machine::on_stmt vfunc for signal_state_machine.  */
@@ -303,4 +329,21 @@ make_signal_state_machine (logger *logger)
   return new signal_state_machine (logger);
 }
 
+#if CHECKING_P
+
+namespace selftest {
+
+/* Run all of the selftests within this file.  */
+
+void
+analyzer_sm_signal_cc_tests ()
+{
+  function_set fs = get_async_signal_unsafe_fns ();
+  fs.assert_sorted ();
+  fs.assert_sane ();
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
 #endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/testsuite/gcc.dg/analyzer/signal-5.c b/gcc/testsuite/gcc.dg/analyzer/signal-5.c
new file mode 100644
index 000000000000..4e464fffda54
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/signal-5.c
@@ -0,0 +1,21 @@
+/* Example of other bad calls within a signal handler.  */
+
+#include <stdlib.h>
+#include <signal.h>
+
+extern void do_stuff (void *ptr);
+extern void body_of_program(void);
+
+static void handler(int signum)
+{
+  void *ptr = malloc (1024); /* { dg-warning "call to 'malloc' from within signal handler" } */
+  do_stuff (ptr);
+  free (ptr); /* { dg-warning "call to 'free' from within signal handler" } */
+}
+
+int main(int argc, const char *argv)
+{
+  signal(SIGINT, handler); /* { dg-message "registering 'handler' as signal handler" } */
+  body_of_program();
+  return 0;
+}
-- 
2.21.0

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 3/4] analyzer: add known stdio functions to sm-file.cc (PR analyzer/58237)
  2019-12-20  1:21       ` [PATCH 0/4] analyzer: add class function_set and use in various places David Malcolm
  2019-12-20  1:22         ` [PATCH 2/4] analyzer: introduce a set of known async-signal-unsafe functions David Malcolm
  2019-12-20  1:22         ` [PATCH 1/4] analyzer: add function-set.cc/h David Malcolm
@ 2019-12-20  1:22         ` David Malcolm
  2019-12-20  6:34         ` [PATCH 4/4] analyzer: add -Wanalyzer-use-of-closed-file David Malcolm
  3 siblings, 0 replies; 160+ messages in thread
From: David Malcolm @ 2019-12-20  1:22 UTC (permalink / raw)
  To: law, gcc-patches; +Cc: David Malcolm

The analyzer ought to report various file leaks for the reproducer in
PR analyzer/58237, such as:

  void f1(const char *str)
  {
    FILE * fp = fopen(str, "r");
    char buf[10];
    while (fgets(buf, 10, fp) != NULL)
    {
      /* Do something with buf */
    }
    /* Missing call to fclose. Need warning here for resource leak */
  }

but fails to do so, due to not recognizing fgets, and thus
conservatively assuming that it could close "fp".

This patch adds a function_set to sm-file.cc of numerous stdio.h
functions that are known to not close the file (and which require a
valid FILE *, but that's a matter for a followup), fixing the issue.

gcc/analyzer/ChangeLog:
	PR analyzer/58237
	* analyzer-selftests.cc (selftest::run_analyzer_selftests): Call
	selftest::analyzer_sm_file_cc_tests.
	* analyzer-selftests.h (selftest::analyzer_sm_file_cc_tests): New
	decl.
	* sm-file.cc: Include "analyzer/function-set.h" and
	"analyzer/analyzer-selftests.h".
	(get_file_using_fns): New function.
	(is_file_using_fn_p): New function.
	(fileptr_state_machine::on_stmt): Return true for known functions.
	(selftest::analyzer_sm_file_cc_tests): New function.

gcc/testsuite/ChangeLog:
	PR analyzer/58237
	* gcc.dg/analyzer/file-1.c (test_4): New.
	* gcc.dg/analyzer/file-pr58237.c: New test.
---
 gcc/analyzer/analyzer-selftests.cc           |   1 +
 gcc/analyzer/analyzer-selftests.h            |   1 +
 gcc/analyzer/sm-file.cc                      | 102 ++++++++++++++++++-
 gcc/testsuite/gcc.dg/analyzer/file-1.c       |  12 +++
 gcc/testsuite/gcc.dg/analyzer/file-pr58237.c |  72 +++++++++++++
 5 files changed, 187 insertions(+), 1 deletion(-)
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/file-pr58237.c

diff --git a/gcc/analyzer/analyzer-selftests.cc b/gcc/analyzer/analyzer-selftests.cc
index f90dad8e991f..182862428e31 100644
--- a/gcc/analyzer/analyzer-selftests.cc
+++ b/gcc/analyzer/analyzer-selftests.cc
@@ -53,6 +53,7 @@ run_analyzer_selftests ()
   analyzer_program_point_cc_tests ();
   analyzer_program_state_cc_tests ();
   analyzer_region_model_cc_tests ();
+  analyzer_sm_file_cc_tests ();
   analyzer_sm_signal_cc_tests ();
 #endif /* #if ENABLE_ANALYZER */
 }
diff --git a/gcc/analyzer/analyzer-selftests.h b/gcc/analyzer/analyzer-selftests.h
index 225b717c9d13..ce336c0b2b41 100644
--- a/gcc/analyzer/analyzer-selftests.h
+++ b/gcc/analyzer/analyzer-selftests.h
@@ -37,6 +37,7 @@ extern void analyzer_function_set_cc_tests ();
 extern void analyzer_program_point_cc_tests ();
 extern void analyzer_program_state_cc_tests ();
 extern void analyzer_region_model_cc_tests ();
+extern void analyzer_sm_file_cc_tests ();
 extern void analyzer_sm_signal_cc_tests ();
 
 } /* end of namespace selftest.  */
diff --git a/gcc/analyzer/sm-file.cc b/gcc/analyzer/sm-file.cc
index e4eeb1f41be9..9a8ce4911d07 100644
--- a/gcc/analyzer/sm-file.cc
+++ b/gcc/analyzer/sm-file.cc
@@ -31,6 +31,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "analyzer/analyzer.h"
 #include "analyzer/pending-diagnostic.h"
 #include "analyzer/sm.h"
+#include "analyzer/function-set.h"
+#include "analyzer/analyzer-selftests.h"
 
 #if ENABLE_ANALYZER
 
@@ -213,6 +215,82 @@ fileptr_state_machine::fileptr_state_machine (logger *logger)
   m_stop = add_state ("stop");
 }
 
+/* Get a set of functions that are known to take a FILE * that must be open,
+   and are known to not close it.  */
+
+static function_set
+get_file_using_fns ()
+{
+  // TODO: populate this list more fully
+  static const char * const funcnames[] = {
+    /* This array must be kept sorted.  */
+    "__fbufsize",
+    "__flbf",
+    "__fpending",
+    "__fpurge"
+    "__freadable",
+    "__freading",
+    "__fsetlocking",
+    "__fwritable",
+    "__fwriting",
+    "clearerr",
+    "clearerr_unlocked",
+    "feof",
+    "feof_unlocked",
+    "ferror",
+    "ferror_unlocked",
+    "fflush", // safe to call with NULL
+    "fflush_unlocked",  // safe to call with NULL
+    "fgetc",
+    "fgetc_unlocked",
+    "fgetpos",
+    "fgets",
+    "fgets_unlocked",
+    "fgetwc_unlocked",
+    "fgetws_unlocked",
+    "fileno",
+    "fileno_unlocked",
+    "fprintf",
+    "fputc",
+    "fputc_unlocked",
+    "fputs",
+    "fputs_unlocked",
+    "fputwc_unlocked",
+    "fputws_unlocked",
+    "fread_unlocked",
+    "fseek",
+    "fsetpos",
+    "ftell",
+    "fwrite_unlocked",
+    "getc",
+    "getc_unlocked",
+    "getwc_unlocked",
+    "putc",
+    "putc_unlocked",
+    "rewind",
+    "setbuf",
+    "setbuffer",
+    "setlinebuf",
+    "setvbuf",
+    "ungetc",
+    "vfprintf"
+  };
+  const size_t count
+    = sizeof(funcnames) / sizeof (funcnames[0]);
+  function_set fs (funcnames, count);
+  return fs;
+}
+
+/* Return true if FNDECL is known to require an open FILE *, and is known
+   to not close it.  */
+
+static bool
+is_file_using_fn_p (tree fndecl)
+{
+  function_set fs = get_file_using_fns ();
+  return fs.contains_decl_p (fndecl);
+}
+
 /* Implementation of state_machine::on_stmt vfunc for fileptr_state_machine.  */
 
 bool
@@ -257,7 +335,11 @@ fileptr_state_machine::on_stmt (sm_context *sm_ctxt,
 	    return true;
 	  }
 
-	// TODO: operations on closed file
+	if (is_file_using_fn_p (callee_fndecl))
+	  {
+	    // TODO: operations on unchecked file
+	    return true;
+	  }
 	// etc
       }
 
@@ -331,4 +413,22 @@ make_fileptr_state_machine (logger *logger)
   return new fileptr_state_machine (logger);
 }
 
+#if CHECKING_P
+
+namespace selftest {
+
+/* Run all of the selftests within this file.  */
+
+void
+analyzer_sm_file_cc_tests ()
+{
+  function_set fs = get_file_using_fns ();
+  fs.assert_sorted ();
+  fs.assert_sane ();
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
+
 #endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/testsuite/gcc.dg/analyzer/file-1.c b/gcc/testsuite/gcc.dg/analyzer/file-1.c
index 91d9685d9e9e..ba516afc8af0 100644
--- a/gcc/testsuite/gcc.dg/analyzer/file-1.c
+++ b/gcc/testsuite/gcc.dg/analyzer/file-1.c
@@ -35,3 +35,15 @@ test_3 (const char *path)
   FILE *f = fopen (path, "r"); /* { dg-message "opened here" } */
   return; /* { dg-warning "leak of FILE 'f'" } */ 
 }
+
+void
+test_4 (const char *path)
+{
+  FILE *f = fopen (path, "r"); /* { dg-message "opened here" } */
+
+  /* Ensure we know about common fns that are known to not close the
+     file (e.g. "fseek").  */
+  fseek (f, 1024, SEEK_SET);
+
+  return; /* { dg-warning "leak of FILE 'f'" } */ 
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/file-pr58237.c b/gcc/testsuite/gcc.dg/analyzer/file-pr58237.c
new file mode 100644
index 000000000000..68f49c25607a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/file-pr58237.c
@@ -0,0 +1,72 @@
+#include <stdio.h>
+
+void f0(const char *str)
+{
+  FILE * fp = fopen(str, "r"); /* { dg-message "opened here" } */
+  char buf[10];
+  fgets(buf, 10, fp);
+} /* { dg-warning "leak of FILE 'fp'" } */
+
+void f1(const char *str)
+{
+  FILE * fp = fopen(str, "r"); /* { dg-message "opened here" } */
+  char buf[10];
+
+  while (fgets(buf, 10, fp) != NULL)
+    {
+      /* Do something with buf */
+    }
+} /* { dg-warning "leak of FILE 'fp'" } */
+
+void f2(const char *str, int flag)
+{
+  FILE * fp = fopen(str, "r"); /* { dg-message "opened here" } */
+  char buf[10];
+
+  while (fgets(buf, 10, fp) != NULL)
+    {
+      /* Do something with buf */
+    }
+  if (flag) /* { dg-message "when 'flag == 0'" } */
+    fclose(fp);
+} /* { dg-warning "leak of FILE 'fp'" } */
+
+extern void called_by_f3( FILE * fp);
+
+void f3(const char *str)
+{
+  FILE * fp = fopen(str, "r");
+  char buf[10];
+
+  while (fgets(buf, 10, fp) != NULL)
+    {
+      /* Do something with buf */
+    }
+  /* Not sure if fclose executed by called_by_f3 or not. Say nothing */
+  called_by_f3(fp);
+}
+
+void f4(const char *str)
+{
+  FILE * fp = fopen(str, "r");
+  char buf[10];
+
+  while (fgets(buf, 10, fp) != NULL)
+    {
+      /* Do something with buf */
+    }
+  /* Nothing to say here. */
+  fclose(fp);
+}
+
+void main(int argc, const char * argv[])
+{
+  FILE * fp = fopen(argv[0], "r");
+  char buf[10];
+
+  while (fgets(buf, 10, fp) != NULL)
+    {
+      /* Do something with buf */
+    }
+  /* Nothing to say here, because we are in main. */
+}
-- 
2.21.0

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 1/4] analyzer: add function-set.cc/h
  2019-12-20  1:21       ` [PATCH 0/4] analyzer: add class function_set and use in various places David Malcolm
  2019-12-20  1:22         ` [PATCH 2/4] analyzer: introduce a set of known async-signal-unsafe functions David Malcolm
@ 2019-12-20  1:22         ` David Malcolm
  2019-12-20  1:22         ` [PATCH 3/4] analyzer: add known stdio functions to sm-file.cc (PR analyzer/58237) David Malcolm
  2019-12-20  6:34         ` [PATCH 4/4] analyzer: add -Wanalyzer-use-of-closed-file David Malcolm
  3 siblings, 0 replies; 160+ messages in thread
From: David Malcolm @ 2019-12-20  1:22 UTC (permalink / raw)
  To: law, gcc-patches; +Cc: David Malcolm

This patch adds a simple mechanism for tracking sets of functions
for which a particular property holds, as a pragmatic way to build
knowledge about important APIs into the analyzer without requiring
markup of the user's libc.

gcc/ChangeLog:
	* Makefile.in (ANALYZER_OBJS): Add analyzer/function-set.o.

gcc/analyzer/ChangeLog:
	* analyzer-selftests.cc (selftest::run_analyzer_selftests): Call
	selftest::analyzer_function_set_cc_tests.
	* analyzer-selftests.h (selftest::analyzer_function_set_cc_tests):
	New decl.
	* function-set.cc: New file.
	* function-set.h: New file.
---
 gcc/Makefile.in                    |   1 +
 gcc/analyzer/analyzer-selftests.cc |   1 +
 gcc/analyzer/analyzer-selftests.h  |   1 +
 gcc/analyzer/function-set.cc       | 191 +++++++++++++++++++++++++++++
 gcc/analyzer/function-set.h        |  46 +++++++
 5 files changed, 240 insertions(+)
 create mode 100644 gcc/analyzer/function-set.cc
 create mode 100644 gcc/analyzer/function-set.h

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index e7a8cf1aa90e..e9e84853e9e7 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1226,6 +1226,7 @@ ANALYZER_OBJS = \
 	analyzer/constraint-manager.o \
 	analyzer/diagnostic-manager.o \
 	analyzer/engine.o \
+	analyzer/function-set.o \
 	analyzer/pending-diagnostic.o \
 	analyzer/program-point.o \
 	analyzer/program-state.o \
diff --git a/gcc/analyzer/analyzer-selftests.cc b/gcc/analyzer/analyzer-selftests.cc
index 3a1f5ada3776..8201c21525c4 100644
--- a/gcc/analyzer/analyzer-selftests.cc
+++ b/gcc/analyzer/analyzer-selftests.cc
@@ -49,6 +49,7 @@ run_analyzer_selftests ()
 {
 #if ENABLE_ANALYZER
   analyzer_constraint_manager_cc_tests ();
+  analyzer_function_set_cc_tests ();
   analyzer_program_point_cc_tests ();
   analyzer_program_state_cc_tests ();
   analyzer_region_model_cc_tests ();
diff --git a/gcc/analyzer/analyzer-selftests.h b/gcc/analyzer/analyzer-selftests.h
index 513df3702385..de9bb3130df0 100644
--- a/gcc/analyzer/analyzer-selftests.h
+++ b/gcc/analyzer/analyzer-selftests.h
@@ -33,6 +33,7 @@ extern void run_analyzer_selftests ();
    alphabetical order.  */
 extern void analyzer_checker_script_cc_tests ();
 extern void analyzer_constraint_manager_cc_tests ();
+extern void analyzer_function_set_cc_tests ();
 extern void analyzer_program_point_cc_tests ();
 extern void analyzer_program_state_cc_tests ();
 extern void analyzer_region_model_cc_tests ();
diff --git a/gcc/analyzer/function-set.cc b/gcc/analyzer/function-set.cc
new file mode 100644
index 000000000000..0da44b3213bb
--- /dev/null
+++ b/gcc/analyzer/function-set.cc
@@ -0,0 +1,191 @@
+/* Sets of function names.
+   Copyright (C) 2019 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 "selftest.h"
+#include "analyzer/function-set.h"
+
+#if ENABLE_ANALYZER
+
+/* Return true if NAME is within this set.  */
+
+bool
+function_set::contains_name_p (const char *name) const
+{
+  /* Binary search.  */
+  int min = 0;
+  int max = m_count - 1;
+  while (true)
+    {
+      if (min > max)
+	return false;
+      int midpoint = (min + max) / 2;
+      gcc_assert ((size_t)midpoint < m_count);
+      int cmp = strcmp (name, m_names[midpoint]);
+      if (cmp == 0)
+	return true;
+      else if (cmp < 0)
+	max = midpoint - 1;
+      else
+	min = midpoint + 1;
+    }
+}
+
+/* Return true if FNDECL is within this set.  */
+
+bool
+function_set::contains_decl_p (tree fndecl) const
+{
+  gcc_assert (fndecl && DECL_P (fndecl));
+  return contains_name_p (IDENTIFIER_POINTER (DECL_NAME (fndecl)));
+}
+
+/* Assert that the list of names is in sorted order.  */
+
+void
+function_set::assert_sorted () const
+{
+#if CHECKING_P
+  for (size_t idx = 1; idx < m_count; idx++)
+    gcc_assert (strcmp (m_names[idx - 1], m_names[idx]) < 0);
+#endif /* #if CHECKING_P  */
+}
+
+/* Assert that contains_p is true for all members of the set.  */
+
+void
+function_set::assert_sane () const
+{
+#if CHECKING_P
+  for (size_t i = 0; i < m_count; i++)
+    gcc_assert (contains_name_p (m_names[i]));
+#endif /* #if CHECKING_P  */
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Verify that an empty function_set works as expected.  */
+
+static void
+test_empty ()
+{
+  function_set fs (NULL, 0);
+  fs.assert_sorted ();
+  fs.assert_sane ();
+  ASSERT_FALSE (fs.contains_name_p (""));
+  ASSERT_FALSE (fs.contains_name_p ("haystack"));
+}
+
+/* Verify that a function_set with an odd number of elements works as
+   expected.  */
+
+static void
+test_odd ()
+{
+  static const char * const names[3] = {"alpha", "beta", "gamma"};
+  function_set fs (names, 3);
+  fs.assert_sorted ();
+  fs.assert_sane ();
+  ASSERT_FALSE (fs.contains_name_p (""));
+  ASSERT_FALSE (fs.contains_name_p ("haystack"));
+}
+
+/* Verify that a function_set with an even number of elements works as
+   expected.  */
+
+static void
+test_even ()
+{
+  static const char * const names[3] = {"alpha", "beta"};
+  function_set fs (names, 2);
+  fs.assert_sorted ();
+  fs.assert_sane ();
+  ASSERT_FALSE (fs.contains_name_p (""));
+  ASSERT_FALSE (fs.contains_name_p ("haystack"));
+}
+
+/* Verify that a function_set with some nontrivial stdio.h data works as
+   expected.  */
+
+static void
+test_stdio_example ()
+{
+  static const char * const example[] = {
+    "__fbufsize",
+    "__flbf",
+    "__fpending",
+    "__fpurge",
+    "__freadable",
+    "__freading",
+    "__fsetlocking",
+    "__fwritable",
+    "__fwriting",
+    "clearerr_unlocked",
+    "feof_unlocked",
+    "ferror_unlocked",
+    "fflush_unlocked",
+    "fgetc_unlocked",
+    "fgets",
+    "fgets_unlocked",
+    "fgetwc_unlocked",
+    "fgetws_unlocked",
+    "fileno_unlocked",
+    "fputc_unlocked",
+    "fputs_unlocked",
+    "fputwc_unlocked",
+    "fputws_unlocked",
+    "fread_unlocked",
+    "fwrite_unlocked",
+    "getc_unlocked",
+    "getwc_unlocked",
+    "putc_unlocked"
+  };
+  const size_t count = sizeof(example) / sizeof (example[0]);
+  function_set fs (example, count);
+  fs.assert_sorted ();
+  fs.assert_sane ();
+  /* Examples of strings not present: before, after and alongside the
+     sorted list.  */
+  ASSERT_FALSE (fs.contains_name_p ("___"));
+  ASSERT_FALSE (fs.contains_name_p ("Z"));
+  ASSERT_FALSE (fs.contains_name_p ("fgets_WITH_A_PREFIX"));
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+analyzer_function_set_cc_tests ()
+{
+  test_empty ();
+  test_odd ();
+  test_even ();
+  test_stdio_example ();
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/function-set.h b/gcc/analyzer/function-set.h
new file mode 100644
index 000000000000..71a25aaca65e
--- /dev/null
+++ b/gcc/analyzer/function-set.h
@@ -0,0 +1,46 @@
+/* Sets of function names.
+   Copyright (C) 2019 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/>.  */
+
+#ifndef GCC_ANALYZER_FUNCTION_SET_H
+#define GCC_ANALYZER_FUNCTION_SET_H
+
+/* A set of names.  */
+
+class function_set
+{
+public:
+  /* Construct from a sorted array NAMES of size COUNT.  */
+  function_set (const char * const *names, size_t count)
+  : m_names (names), m_count (count)
+  {
+  }
+
+  bool contains_name_p (const char *name) const;
+  bool contains_decl_p (tree fndecl) const;
+
+  void assert_sorted () const;
+  void assert_sane () const;
+
+private:
+  const char * const *m_names; // must be sorted
+  size_t m_count;
+};
+
+#endif /* GCC_ANALYZER_FUNCTION_SET_H */
-- 
2.21.0

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH 4/4] analyzer: add -Wanalyzer-use-of-closed-file
  2019-12-20  1:21       ` [PATCH 0/4] analyzer: add class function_set and use in various places David Malcolm
                           ` (2 preceding siblings ...)
  2019-12-20  1:22         ` [PATCH 3/4] analyzer: add known stdio functions to sm-file.cc (PR analyzer/58237) David Malcolm
@ 2019-12-20  6:34         ` David Malcolm
  3 siblings, 0 replies; 160+ messages in thread
From: David Malcolm @ 2019-12-20  6:34 UTC (permalink / raw)
  To: law, gcc-patches; +Cc: David Malcolm

gcc/analyzer/ChangeLog:
	* analyzer.opt (Wanalyzer-use-of-closed-file): New option.
	* sm-file.cc (class use_of_closed_file): New file_diagnostic subclass.
	(find_file_param): New function.
	(fileptr_state_machine::on_stmt): Complain about operations on
	closed files.

gcc/ChangeLog:
	* doc/invoke.texi (-Wanalyzer-use-of-closed-file): Document new
	option.

gcc/testsuite/ChangeLog:
	* gcc.dg/analyzer/file-1.c (test_5): New test.
---
 gcc/analyzer/analyzer.opt              |  4 ++
 gcc/analyzer/sm-file.cc                | 92 +++++++++++++++++++++++++-
 gcc/doc/invoke.texi                    | 13 ++++
 gcc/testsuite/gcc.dg/analyzer/file-1.c |  8 +++
 4 files changed, 116 insertions(+), 1 deletion(-)

diff --git a/gcc/analyzer/analyzer.opt b/gcc/analyzer/analyzer.opt
index d722667dea09..2f5008d1ea3b 100644
--- a/gcc/analyzer/analyzer.opt
+++ b/gcc/analyzer/analyzer.opt
@@ -94,6 +94,10 @@ Wanalyzer-use-after-free
 Common Var(warn_analyzer_use_after_free) Init(1) Warning
 Warn about code paths in which a freed value is used.
 
+Wanalyzer-use-of-closed-file
+Common Var(warn_analyzer_use_of_closed_file) Init(1) Warning
+Warn about code paths in which a FILE * is used after being closed.
+
 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.
diff --git a/gcc/analyzer/sm-file.cc b/gcc/analyzer/sm-file.cc
index 9a8ce4911d07..3205474b03d3 100644
--- a/gcc/analyzer/sm-file.cc
+++ b/gcc/analyzer/sm-file.cc
@@ -158,6 +158,46 @@ private:
   diagnostic_event_id_t m_first_fclose_event;
 };
 
+class use_of_closed_file : public file_diagnostic
+{
+public:
+  use_of_closed_file (const fileptr_state_machine &sm, tree arg)
+    : file_diagnostic (sm, arg)
+  {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "use_of_closed_file"; }
+
+  bool emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    // FIXME: CWE?
+    return warning_at (rich_loc, OPT_Wanalyzer_use_of_closed_file,
+		       "use of closed FILE %qE",
+		       m_arg);
+  }
+
+  label_text describe_state_change (const evdesc::state_change &change)
+    OVERRIDE
+  {
+    if (change.m_new_state == m_sm.m_closed)
+      {
+	m_fclose_event = change.m_event_id;
+	return change.formatted_print ("file closed here");
+      }
+    return file_diagnostic::describe_state_change (change);
+  }
+
+  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  {
+    if (m_fclose_event.known_p ())
+      return ev.formatted_print ("use of closed FILE %qE; closed at %@",
+				 ev.m_expr, &m_fclose_event);
+    return ev.formatted_print ("use of closed FILE %qE", ev.m_expr);
+  }
+
+private:
+  diagnostic_event_id_t m_fclose_event;
+};
+
 class file_leak : public file_diagnostic
 {
 public:
@@ -291,6 +331,41 @@ is_file_using_fn_p (tree fndecl)
   return fs.contains_decl_p (fndecl);
 }
 
+/* If FNDECL takes a FILE * param, write the 0-based index of the first
+   such param to *OUT_IDX and return true.  */
+
+static bool
+find_file_param (tree fndecl, int *out_idx)
+{
+  int idx = 0;
+  for (tree iter_param_types = TYPE_ARG_TYPES (TREE_TYPE (fndecl));
+       iter_param_types;
+       iter_param_types = TREE_CHAIN (iter_param_types), idx++)
+    {
+      tree param_type = TREE_VALUE (iter_param_types);
+
+      /* Looks like we can't rely on fileptr_type_node being set up;
+	 instead, look for a ptr to record called "FILE".  */
+
+      if (!POINTER_TYPE_P (param_type))
+	continue;
+      tree type = TREE_TYPE (param_type);
+      if (TREE_CODE (type) == RECORD_TYPE
+	  && TYPE_NAME (type)
+	  && TREE_CODE (TYPE_NAME (type)) == TYPE_DECL)
+	{
+	  tree tdecl = TYPE_NAME (type);
+	  if (DECL_NAME (tdecl)
+	      && strcmp (IDENTIFIER_POINTER (DECL_NAME (tdecl)), "FILE") == 0)
+	    {
+	      *out_idx = idx;
+	      return true;
+	    }
+	}
+    }
+  return false;
+}
+
 /* Implementation of state_machine::on_stmt vfunc for fileptr_state_machine.  */
 
 bool
@@ -337,12 +412,27 @@ fileptr_state_machine::on_stmt (sm_context *sm_ctxt,
 
 	if (is_file_using_fn_p (callee_fndecl))
 	  {
-	    // TODO: operations on unchecked file
+	    int param_idx;
+	    if (find_file_param (callee_fndecl, &param_idx))
+	      {
+		/* Look up FILE * param.  */
+		tree arg = gimple_call_arg (call, param_idx);
+		arg = sm_ctxt->get_readable_tree (arg);
+
+		// TODO: operations on unchecked file
+
+		/* Complain about operations on closed files.  */
+		sm_ctxt->warn_for_state (node, stmt, arg, m_closed,
+					 new use_of_closed_file (*this, arg));
+		sm_ctxt->on_transition (node, stmt, arg, m_closed, m_stop);
+	      }
 	    return true;
 	  }
 	// etc
       }
 
+  // TODO: fcloseall() (a GNU extension)
+
   return false;
 }
 
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 3a8c83592ef8..dc6d5a14ed41 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -310,6 +310,7 @@ Objective-C and Objective-C++ Dialects}.
 -Wno-analyzer-tainted-array-index @gol
 -Wno-analyzer-unsafe-call-within-signal-handler @gol
 -Wno-analyzer-use-after-free @gol
+-Wno-analyzer-use-of-closed-file @gol
 -Wno-analyzer-use-of-pointer-in-stale-stack-frame @gol
 -Wno-analyzer-use-of-uninitialized-value @gol
 -Wanalyzer-too-complex @gol
@@ -410,6 +411,7 @@ Objective-C and Objective-C++ Dialects}.
 -Wanalyzer-tainted-array-index @gol
 -Wanalyzer-unsafe-call-within-signal-handler @gol
 -Wanalyzer-use-after-free @gol
+-Wanalyzer-use-of-closed-file @gol
 -Wanalyzer-use-of-pointer-in-stale-stack-frame @gol
 -Wanalyzer-use-of-uninitialized-value @gol
 -Wanalyzer-too-complex  @gol
@@ -6575,6 +6577,16 @@ This warning requires @option{-fanalyzer}, which enables it; use
 This diagnostic warns for paths through the code in which a
 pointer is used after @code{free} is called on it.
 
+@item -Wno-analyzer-use-of-closed-file
+@opindex Wanalyzer-use-of-closed-file
+@opindex Wno-analyzer-use-of-closed-file
+This warning requires @option{-fanalyzer}, which enables it; use
+@option{-Wno-analyzer-use-of-closed-file} to disable it.
+
+This diagnostic warns for paths through the code in which a
+@code{<stdio.h>} @code{FILE *} stream object is used after
+@code{fclose} is called on it.
+
 @item -Wno-analyzer-use-of-pointer-in-stale-stack-frame
 @opindex Wanalyzer-use-of-pointer-in-stale-stack-frame
 @opindex Wno-analyzer-use-of-pointer-in-stale-stack-frame
@@ -8290,6 +8302,7 @@ Enabling this option effectively enables the following warnings:
 -Wanalyzer-tainted-array-index @gol
 -Wanalyzer-unsafe-call-within-signal-handler @gol
 -Wanalyzer-use-after-free @gol
+-Wanalyzer-use-of-closed-file @gol
 -Wanalyzer-use-of-uninitialized-value @gol
 -Wanalyzer-use-of-pointer-in-stale-stack-frame @gol
 }
diff --git a/gcc/testsuite/gcc.dg/analyzer/file-1.c b/gcc/testsuite/gcc.dg/analyzer/file-1.c
index ba516afc8af0..8531fb0296c9 100644
--- a/gcc/testsuite/gcc.dg/analyzer/file-1.c
+++ b/gcc/testsuite/gcc.dg/analyzer/file-1.c
@@ -47,3 +47,11 @@ test_4 (const char *path)
 
   return; /* { dg-warning "leak of FILE 'f'" } */ 
 }
+
+void
+test_5 (FILE *f, const char *msg)
+{
+  fclose (f); /* { dg-message "\\(1\\) file closed here" } */
+  fprintf (f, "foo: %s", msg); /* { dg-warning "use of closed FILE 'f'" } */
+  /* { dg-message "\\(2\\) use of closed FILE 'f'; closed at \\(1\\)" "" { target *-*-* } .-1 } */
+}
-- 
2.21.0

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 05/49] vec.h: add auto_delete_vec
  2019-12-18 16:00     ` David Malcolm
@ 2020-01-08 21:52       ` Jeff Law
  0 siblings, 0 replies; 160+ messages in thread
From: Jeff Law @ 2020-01-08 21:52 UTC (permalink / raw)
  To: David Malcolm, Martin Sebor, gcc-patches

On Wed, 2019-12-18 at 10:59 -0500, David Malcolm wrote:
> On Wed, 2019-12-04 at 09:29 -0700, Martin Sebor wrote:
> > On 11/15/19 6:22 PM, David Malcolm wrote:
> > > This patch adds a class auto_delete_vec<T>, a subclass of auto_vec
> > > <T *>
> > > that deletes all of its elements on destruction; it's used in many
> > > places later in the kit.
> > > 
> > > This is a crude way for a vec to "own" the objects it points to
> > > and clean up automatically (essentially a workaround for not being
> > > able
> > > to use unique_ptr, due to C++98).
> > > 
> > > gcc/ChangeLog:
> > > 	* vec.c (class selftest::count_dtor): New class.
> > > 	(selftest::test_auto_delete_vec): New test.
> > > 	(selftest::vec_c_tests): Call it.
> > > 	* vec.h (class auto_delete_vec): New class template.
> > > 	(auto_delete_vec<T>::~auto_delete_vec): New dtor.
> > 
> > Because of slicing, unless preventing the elements from being
> > deleted in the class dtor is meant to be a feature, it seems that
> > using a wrapper class rather than public derivation from auto_vec
> > might be a safer solution.
> > 
> > It might be worth mentioning in a comment that the class isn't
> > safe to copy or assign (each copy would wind up delete the same
> > pointers), in addition to making its copy ctor and copy assignment
> > operator inaccessible or deleted.
> > 
> > Martin
> 
> In the version of the patch in the v4 kit:
>   https://gcc.gnu.org/ml/gcc-patches/2019-12/msg01035.html
> I added:
>   private:
>      DISABLE_COPY_AND_ASSIGN(auto_delete_vec<T>);
> to the class.
> 
> Does that satisfy your concerns about slicing? (and, indeed, about
> copying and assigning)
I think this can and should go forward at this point.

jeff

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 15/49] Add ordered_hash_map
  2019-12-04 17:59   ` Martin Sebor
@ 2020-01-08 23:47     ` David Malcolm
  2020-01-09 11:56       ` Jonathan Wakely
  0 siblings, 1 reply; 160+ messages in thread
From: David Malcolm @ 2020-01-08 23:47 UTC (permalink / raw)
  To: Martin Sebor, gcc-patches; +Cc: Jonathan Wakely

On Wed, 2019-12-04 at 10:59 -0700, Martin Sebor wrote:
> On 11/15/19 6:23 PM, David Malcolm wrote:
> > This patch adds an ordered_hash_map template, which is similar to
> > hash_map, but preserves insertion order.
> > 
> > gcc/ChangeLog:
> > 	* Makefile.in (OBJS): Add ordered-hash-map-tests.o.
> > 	* ordered-hash-map-tests.cc: New file.
> > 	* ordered-hash-map.h: New file.
> > 	* selftest-run-tests.c (selftest::run_tests): Call
> > 	selftest::ordered_hash_map_tests_cc_tests.
> > 	* selftest.h (selftest::ordered_hash_map_tests_cc_tests): New
> > 	decl.
> > ---
> >   gcc/Makefile.in               |   1 +
> >   gcc/ordered-hash-map-tests.cc | 247
> > ++++++++++++++++++++++++++++++++++++++++++
> >   gcc/ordered-hash-map.h        | 184
> > +++++++++++++++++++++++++++++++
> >   gcc/selftest-run-tests.c      |   1 +
> >   gcc/selftest.h                |   1 +
> >   5 files changed, 434 insertions(+)
> >   create mode 100644 gcc/ordered-hash-map-tests.cc
> >   create mode 100644 gcc/ordered-hash-map.h
> > 

[...]

> The container defines a copy-constructor but no copy assignment.
> Is it safely assignable? (I don't think auto_vec is safely copyable
> or assignable due to PR 90904.  It looks like the copy ctor works
> around it below.)

It's not safely assignable; I don't believe I'm using that (I am using
the copy ctor).

I can make it private or similar to ensure it's not used.

> I don't think I've made use of the hash_map copy ctor or copy
> assignment but if it's anything like other GCC containers I'd
> worry about it not doing the right thing, especially for non-
> PODs.  I spent too much time chasing down miscompilations and
> other problems due to bugs (or design limitations) in these
> classes.
> 
> I'd far prefer to see us use libstdc++ containers in new code
> than introduce new ones of our own.  They are better designed
> and much better tested than these.  (I realize we're still
> hampered by targeting C++ 98.)
> 
> Martin

[CCing Jonathan for libstdc++ expertise]

Is there such a container in libstdc++?

This patch implements a map that preserves insertion order when
iterating, without requiring the Key type to be comparable.

As far as I can tell, std::map instead is a map that requires the Key
type to be comparable, and uses that to implement a red-black tree
(rather than via hashing), and doesn't preserve insertion ordering.

I use this ordered_hash_map class later in various places in the
analyzer patch kit to ensure more deterministic results, so that
results aren't affected by hash values of possibly-changing pointer
values.

[...]

Dave

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 15/49] Add ordered_hash_map
  2020-01-08 23:47     ` David Malcolm
@ 2020-01-09 11:56       ` Jonathan Wakely
  2020-01-10  2:58         ` [PATCH] Add ordered_hash_map (v6) David Malcolm
  0 siblings, 1 reply; 160+ messages in thread
From: Jonathan Wakely @ 2020-01-09 11:56 UTC (permalink / raw)
  To: David Malcolm; +Cc: Martin Sebor, gcc-patches

On 08/01/20 18:47 -0500, David Malcolm wrote:
>On Wed, 2019-12-04 at 10:59 -0700, Martin Sebor wrote:
>> On 11/15/19 6:23 PM, David Malcolm wrote:
>> > This patch adds an ordered_hash_map template, which is similar to
>> > hash_map, but preserves insertion order.
>> >
>> > gcc/ChangeLog:
>> > 	* Makefile.in (OBJS): Add ordered-hash-map-tests.o.
>> > 	* ordered-hash-map-tests.cc: New file.
>> > 	* ordered-hash-map.h: New file.
>> > 	* selftest-run-tests.c (selftest::run_tests): Call
>> > 	selftest::ordered_hash_map_tests_cc_tests.
>> > 	* selftest.h (selftest::ordered_hash_map_tests_cc_tests): New
>> > 	decl.
>> > ---
>> >   gcc/Makefile.in               |   1 +
>> >   gcc/ordered-hash-map-tests.cc | 247
>> > ++++++++++++++++++++++++++++++++++++++++++
>> >   gcc/ordered-hash-map.h        | 184
>> > +++++++++++++++++++++++++++++++
>> >   gcc/selftest-run-tests.c      |   1 +
>> >   gcc/selftest.h                |   1 +
>> >   5 files changed, 434 insertions(+)
>> >   create mode 100644 gcc/ordered-hash-map-tests.cc
>> >   create mode 100644 gcc/ordered-hash-map.h
>> >
>
>[...]
>
>> The container defines a copy-constructor but no copy assignment.
>> Is it safely assignable? (I don't think auto_vec is safely copyable
>> or assignable due to PR 90904.  It looks like the copy ctor works
>> around it below.)
>
>It's not safely assignable; I don't believe I'm using that (I am using
>the copy ctor).
>
>I can make it private or similar to ensure it's not used.

Yes, if the compiler-generated assignment isn't safe, please declare
(but don't define) a private assignment operator, to suppress the
compiler-generated one.

>> I don't think I've made use of the hash_map copy ctor or copy
>> assignment but if it's anything like other GCC containers I'd
>> worry about it not doing the right thing, especially for non-
>> PODs.  I spent too much time chasing down miscompilations and
>> other problems due to bugs (or design limitations) in these
>> classes.
>>
>> I'd far prefer to see us use libstdc++ containers in new code
>> than introduce new ones of our own.  They are better designed
>> and much better tested than these.  (I realize we're still
>> hampered by targeting C++ 98.)
>>
>> Martin
>
>[CCing Jonathan for libstdc++ expertise]
>
>Is there such a container in libstdc++?


>This patch implements a map that preserves insertion order when
>iterating, without requiring the Key type to be comparable.
>
>As far as I can tell, std::map instead is a map that requires the Key
>type to be comparable,

Right.

>and uses that to implement a red-black tree
>(rather than via hashing),

Right.

>and doesn't preserve insertion ordering.

std::map has unique keys anyway, so insertion order is irrelevant.
Iteration is done by key order.

std::multimap is the non-unique version, and preserves insertion order
for equivalent keys, but only since C++11. Before that it was
unspecified whether or not a duplicate key was inserted after existing
elements with equivalent keys.

>I use this ordered_hash_map class later in various places in the
>analyzer patch kit to ensure more deterministic results, so that
>results aren't affected by hash values of possibly-changing pointer
>values.

Nothing in the standard library provides that functionality out of the
box. The C++11 hash containers don't preserve order at all, even
equivalent elements can be reordered when insertion triggers
rehashing.

You could build something using a sequence container (vector, deque or
list) that preserves a stable order according to insertion order, and
then manually maintain a separate index of hash values used for
lookup. That might not make things any simpler (I haven't looked at
your actual patch).


^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 33/49] analyzer: new files: sm.{cc|h}
  2019-12-11 19:24   ` Jeff Law
@ 2020-01-10  2:28     ` David Malcolm
  0 siblings, 0 replies; 160+ messages in thread
From: David Malcolm @ 2020-01-10  2:28 UTC (permalink / raw)
  To: law, gcc-patches; +Cc: David Malcolm

On Wed, 2019-12-11 at 12:23 -0700, Jeff Law wrote:
> On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> > This patch adds a "state_machine" base class for describing
> > API checkers in terms of state machine transitions.  Followup
> > patches use this to add specific API checkers.
> > 
> > gcc/ChangeLog:
> > 	* analyzer/sm.cc: New file.
> > 	* analyzer/sm.h: New file.
> > ---
> >  gcc/analyzer/sm.cc | 135
> > ++++++++++++++++++++++++++++++++++++++++++++
> >  gcc/analyzer/sm.h  | 160
> > +++++++++++++++++++++++++++++++++++++++++++++++++++++
> >  2 files changed, 295 insertions(+)
> >  create mode 100644 gcc/analyzer/sm.cc
> >  create mode 100644 gcc/analyzer/sm.h
> > 
> > diff --git a/gcc/analyzer/sm.cc b/gcc/analyzer/sm.cc
> > new file mode 100644
> > index 0000000..eda9350
> > --- /dev/null
> > +++ b/gcc/analyzer/sm.cc
> > @@ -0,0 +1,135 @@
> > +/* Modeling API uses and misuses via state machines.
> > +   Copyright (C) 2019 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 "gcc-plugin.h"
> > +#include "system.h"
> > +#include "coretypes.h"
> > +#include "tree.h"
> > +#include "gimple.h"
> > +#include "analyzer/analyzer.h"
> > +#include "analyzer/sm.h"
> > +
> > +//////////////////////////////////////////////////////////////////
> > //
> > ////////
> > +
> > +/* If STMT is an assignment to zero, return the LHS.  */
> > +
> > +tree
> > +is_zero_assignment (const gimple *stmt)
> > +{
> > +  const gassign *assign_stmt = dyn_cast <const gassign *> (stmt);
> > +  if (!assign_stmt)
> > +    return NULL_TREE;
> > +
> > +  enum tree_code op = gimple_assign_rhs_code (assign_stmt);
> > +  if (op != INTEGER_CST)
> > +    return NULL_TREE;
> > +
> > +  if (!zerop (gimple_assign_rhs1 (assign_stmt)))
> > +    return NULL_TREE;
> > +
> > +  return gimple_assign_lhs (assign_stmt);
> > +}
> "assignment from zero" rather than "assignment to zero" I think.

Fixed.

> I think you'd want to use "integer_zerop" rather than an open-coded
> check.  That'll also allow you to pick up other forms such as
> COMPLEX_CST and VECTOR_CST.

zero_p calls integer_zero_p, and also the equivalents for real and
fixed, so ought to be as flexible as you suggest.

I think what's needed is to replace that op != INTEGER_CST check with
  TREE_CODE_CLASS (op) == tcc_constant
to keep the generality you suggest, whilst rejecting e.g. a binary op
with a zero as arg 1.

I've done that in the following patch.
 
> > +
> > +/* If COND_STMT is a comparison against zero of the form (LHS OP
> > 0),
> > +   return true and write what's being compared to *OUT_LHS and the
> > kind of
> > +   the comparison to *OUT_OP.  */
> > +
> > +bool
> > +is_comparison_against_zero (const gcond *cond_stmt,
> > +			    tree *out_lhs, enum tree_code *out_op)
> > +{
> > +  enum tree_code op = gimple_cond_code (cond_stmt);
> > +  tree lhs = gimple_cond_lhs (cond_stmt);
> > +  tree rhs = gimple_cond_rhs (cond_stmt);
> > +  if (!zerop (rhs))
> > +    return false;
> > +  // TODO: make it symmetric?
> > +
> > +  switch (op)
> > +    {
> > +    case NE_EXPR:
> > +    case EQ_EXPR:
> > +      *out_lhs = lhs;
> > +      *out_op = op;
> > +      return true;
> > +
> > +    default:
> > +      return false;
> > +    }
> > +}
> Seems like this might be useful to make generically available.

It turned out that I'm not using this anymore so I've deleted it
in the following version of the patch (for the curious, I think I
stopped using it when I added the state_machine::on_condition vfunc,
which is more flexible than just looking at a cond_stmt, since
it captures the additional cases where a condition can be discovered
e.g. by walking chains of SSA defs when handling optimized && and ||;
see the comment for region_model::add_any_constraints_from_ssa_def_stmt
in the region-model.cc patch for an example).

> > +
> > +bool
> > +any_pointer_p (tree var)
> > +{
> > +  if (TREE_CODE (TREE_TYPE (var)) != POINTER_TYPE)
> > +    return false;
> > +
> > +  return true;
> > +}
> I believe what you really want is POINTER_TYPE_P which will also
> happen
> to help C++ since it'll include things that are REFERENCE_TYPEs.

Done.

> 
> It looks like some of the methods don't have comments on their
> definitions.  Please check those and add them as necessary.

Added.

 
> Otherwise it looks reasonable.
> 
> jeff

Thanks.  Here's an updated patch (v6) which I've successfully
bootstrapped & regrtested on x86_64-pc-linux-gnu.

OK for trunk? (assuming the rest of the kit is approved)

Thanks
Dave


Changed in v6:
- updated per Jeff's review:
  - is_zero_assignment: use check against
      TREE_CODE_CLASS (op) == tcc_constant
  - delete is_comparison_against_zero, as it's a holdover from older
    code
  - simplified any_pointer_p
  - added comment to defns

Changed in v5:
- update ChangeLog path
- updated copyright years to include 2020

Changed in v4:
- Remove include of gcc-plugin.h, reworking includes accordingly.
- Wrap everything in #if ENABLE_ANALYZER
- Remove /// comment lines
- Add call to make_signal_state_machine:
    https://gcc.gnu.org/ml/gcc-patches/2019-12/msg00214.html
- Rework on_leak vfunc:
    https://gcc.gnu.org/ml/gcc-patches/2019-11/msg02028.html
- Add DISABLE_COPY_AND_ASSIGN to state_machine
- Add support for global states and custom transitions:
    https://gcc.gnu.org/ml/gcc-patches/2019-12/msg00217.html

This patch adds a "state_machine" base class for describing
API checkers in terms of state machine transitions.  Followup
patches use this to add specific API checkers.

gcc/analyzer/ChangeLog:
	* sm.cc: New file.
	* sm.h: New file.
---
 gcc/analyzer/sm.cc | 119 ++++++++++++++++++++++++++++++
 gcc/analyzer/sm.h  | 180 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 299 insertions(+)
 create mode 100644 gcc/analyzer/sm.cc
 create mode 100644 gcc/analyzer/sm.h

diff --git a/gcc/analyzer/sm.cc b/gcc/analyzer/sm.cc
new file mode 100644
index 00000000000..c8a78f9eb72
--- /dev/null
+++ b/gcc/analyzer/sm.cc
@@ -0,0 +1,119 @@
+/* Modeling API uses and misuses via state machines.
+   Copyright (C) 2019-2020 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 "options.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/sm.h"
+
+#if ENABLE_ANALYZER
+
+/* If STMT is an assignment from zero, return the LHS.  */
+
+tree
+is_zero_assignment (const gimple *stmt)
+{
+  const gassign *assign_stmt = dyn_cast <const gassign *> (stmt);
+  if (!assign_stmt)
+    return NULL_TREE;
+
+  enum tree_code op = gimple_assign_rhs_code (assign_stmt);
+  if (TREE_CODE_CLASS (op) != tcc_constant)
+    return NULL_TREE;
+
+  if (!zerop (gimple_assign_rhs1 (assign_stmt)))
+    return NULL_TREE;
+
+  return gimple_assign_lhs (assign_stmt);
+}
+
+/* Return true if VAR has pointer or reference type.  */
+
+bool
+any_pointer_p (tree var)
+{
+  return POINTER_TYPE_P (TREE_TYPE (var));
+}
+
+/* Add a state with name NAME to this state_machine.
+   The string is required to outlive the state_machine.
+
+   Return the state_t for the new state.  */
+
+state_machine::state_t
+state_machine::add_state (const char *name)
+{
+  m_state_names.safe_push (name);
+  return m_state_names.length () - 1;
+}
+
+/* Get the name of state S within this state_machine.  */
+
+const char *
+state_machine::get_state_name (state_t s) const
+{
+  return m_state_names[s];
+}
+
+/* Assert that S is a valid state for this state_machine.  */
+
+void
+state_machine::validate (state_t s) const
+{
+  gcc_assert (s < m_state_names.length ());
+}
+
+/* Create instances of the various state machines, each using LOGGER,
+   and populate OUT with them.  */
+
+void
+make_checkers (auto_delete_vec <state_machine> &out, logger *logger)
+{
+  out.safe_push (make_malloc_state_machine (logger));
+  out.safe_push (make_fileptr_state_machine (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));
+
+  /* We only attempt to run the pattern tests if it might have been manually
+     enabled (for DejaGnu purposes).  */
+  if (flag_analyzer_checker)
+    out.safe_push (make_pattern_test_state_machine (logger));
+
+  if (flag_analyzer_checker)
+    {
+      unsigned read_index, write_index;
+      state_machine **sm;
+
+      /* TODO: this leaks the machines
+	 Would be nice to log the things that were removed.  */
+      VEC_ORDERED_REMOVE_IF (out, read_index, write_index, sm,
+			     0 != strcmp (flag_analyzer_checker,
+					  (*sm)->get_name ()));
+    }
+}
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/sm.h b/gcc/analyzer/sm.h
new file mode 100644
index 00000000000..0f72520100f
--- /dev/null
+++ b/gcc/analyzer/sm.h
@@ -0,0 +1,180 @@
+/* Modeling API uses and misuses via state machines.
+   Copyright (C) 2019-2020 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/>.  */
+
+#ifndef GCC_ANALYZER_SM_H
+#define GCC_ANALYZER_SM_H
+
+#include "analyzer/analyzer-logging.h"
+
+/* Utility functions for use by state machines.  */
+
+extern tree is_zero_assignment (const gimple *stmt);
+extern bool any_pointer_p (tree var);
+
+class state_machine;
+class sm_context;
+class pending_diagnostic;
+
+/* An abstract base class for a state machine describing an API.
+   A mapping from state IDs to names, and various virtual functions
+   for pattern-matching on statements.  */
+
+class state_machine : public log_user
+{
+public:
+  typedef unsigned state_t;
+
+  state_machine (const char *name, logger *logger)
+  : log_user (logger), m_name (name) {}
+
+  virtual ~state_machine () {}
+
+  /* Should states be inherited from a parent region to a child region,
+     when first accessing a child region?
+     For example we should inherit the taintedness of a subregion,
+     but we should not inherit the "malloc:non-null" state of a field
+     within a heap-allocated struct.  */
+  virtual bool inherited_state_p () const = 0;
+
+  const char *get_name () const { return m_name; }
+
+  const char *get_state_name (state_t s) const;
+
+  /* Return true if STMT is a function call recognized by this sm.  */
+  virtual bool on_stmt (sm_context *sm_ctxt,
+			const supernode *node,
+			const gimple *stmt) const = 0;
+
+  virtual void on_condition (sm_context *sm_ctxt,
+			     const supernode *node,
+			     const gimple *stmt,
+			     tree lhs, enum tree_code op, tree rhs) const = 0;
+
+  /* Return true if it safe to discard the given state (to help
+     when simplifying state objects).
+     States that need leak detection should return false.  */
+  virtual bool can_purge_p (state_t s) const = 0;
+
+  /* Called when VAR leaks (and !can_purge_p).  */
+  virtual pending_diagnostic *on_leak (tree var ATTRIBUTE_UNUSED) const
+  {
+    return NULL;
+  }
+
+  void validate (state_t s) const;
+
+protected:
+  state_t add_state (const char *name);
+
+private:
+  DISABLE_COPY_AND_ASSIGN (state_machine);
+
+  const char *m_name;
+  auto_vec<const char *> m_state_names;
+};
+
+/* Is STATE the start state?  (zero is hardcoded as the start state).  */
+
+static inline bool
+start_start_p (state_machine::state_t state)
+{
+  return state == 0;
+}
+
+/* Abstract base class for state machines to pass to
+   sm_context::on_custom_transition for handling non-standard transitions
+   (e.g. adding a node and edge to simulate registering a callback and having
+   the callback be called later).  */
+
+class custom_transition
+{
+public:
+  virtual ~custom_transition () {}
+  virtual void impl_transition (exploded_graph *eg,
+				exploded_node *src_enode,
+				int sm_idx) = 0;
+};
+
+/* Abstract base class giving an interface for the state machine to call
+   the checker engine, at a particular stmt.  */
+
+class sm_context
+{
+public:
+  virtual ~sm_context () {}
+
+  /* Get the fndecl used at call, or NULL_TREE.
+     Use in preference to gimple_call_fndecl (and gimple_call_addr_fndecl),
+     since it can look through function pointer assignments and
+     other callback handling.  */
+  virtual tree get_fndecl_for_call (const gcall *call) = 0;
+
+  /* Called by state_machine in response to pattern matches:
+     if VAR is in state FROM, transition it to state TO, potentially
+     recording the "origin" of the state as ORIGIN.
+     Use NODE and STMT for location information.  */
+   virtual void on_transition (const supernode *node, const gimple *stmt,
+			      tree var,
+			      state_machine::state_t from,
+			      state_machine::state_t to,
+			      tree origin = NULL_TREE) = 0;
+
+  /* Called by state_machine in response to pattern matches:
+     issue a diagnostic D if VAR is in state STATE, using NODE and STMT
+     for location information.  */
+  virtual void warn_for_state (const supernode *node, const gimple *stmt,
+			       tree var, state_machine::state_t state,
+			       pending_diagnostic *d) = 0;
+
+  virtual tree get_readable_tree (tree expr)
+  {
+    return expr;
+  }
+
+  virtual state_machine::state_t get_global_state () const = 0;
+  virtual void set_global_state (state_machine::state_t) = 0;
+
+  /* A vfunc for handling custom transitions, such as when registering
+     a signal handler.  */
+  virtual void on_custom_transition (custom_transition *transition) = 0;
+
+protected:
+  sm_context (int sm_idx, const state_machine &sm)
+  : m_sm_idx (sm_idx), m_sm (sm) {}
+
+  int m_sm_idx;
+  const state_machine &m_sm;
+};
+
+
+/* The various state_machine subclasses are hidden in their respective
+   implementation files.  */
+
+extern void make_checkers (auto_delete_vec <state_machine> &out,
+			   logger *logger);
+
+extern state_machine *make_malloc_state_machine (logger *logger);
+extern state_machine *make_fileptr_state_machine (logger *logger);
+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);
+
+#endif /* GCC_ANALYZER_SM_H */
-- 
2.21.0

^ permalink raw reply	[flat|nested] 160+ messages in thread

* Re: [PATCH 43/49] analyzer: new file: exploded-graph.h
  2019-12-11 20:04   ` Jeff Law
  2019-12-12 15:29     ` David Malcolm
@ 2020-01-10  2:41     ` David Malcolm
  1 sibling, 0 replies; 160+ messages in thread
From: David Malcolm @ 2020-01-10  2:41 UTC (permalink / raw)
  To: law, gcc-patches; +Cc: David Malcolm

On Wed, 2019-12-11 at 13:04 -0700, Jeff Law wrote:
> On Fri, 2019-11-15 at 20:23 -0500, David Malcolm wrote:
> > This patch adds exploded_graph and related classes, for managing
> > exploring paths through the user's code as a directed graph
> > of <point, state> pairs.
> > 
> > gcc/ChangeLog:
> > 	* analyzer/exploded-graph.h: New file.
> > ---
> >  gcc/analyzer/exploded-graph.h | 754
> > ++++++++++++++++++++++++++++++++++++++++++
> >  1 file changed, 754 insertions(+)
> >  create mode 100644 gcc/analyzer/exploded-graph.h
> > 
> > diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-
> > graph.h
> > new file mode 100644
> > index 0000000..f97d2b6
> > --- /dev/null
> > +++ b/gcc/analyzer/exploded-graph.h
> > @@ -0,0 +1,754 @@
> > +/* Classes for managing a directed graph of <point, state> pairs.
> > +   Copyright (C) 2019 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/>;;.  */
> > +
> > +#ifndef GCC_ANALYZER_EXPLODED_GRAPH_H
> > +#define GCC_ANALYZER_EXPLODED_GRAPH_H
> > +
> > +#include "fibonacci_heap.h"
> > +#include "analyzer/analyzer-logging.h"
> > +#include "analyzer/constraint-manager.h"
> > +#include "analyzer/diagnostic-manager.h"
> > +#include "analyzer/program-point.h"
> > +#include "analyzer/program-state.h"
> > +#include "analyzer/shortest-paths.h"
> > +
> > +//////////////////////////////////////////////////////////////////
> > //
> NIT.  Is there some reason you don't just use whitespace for these
> kind
> of vertical separators.  THe more I see them, the more they bug me,
> probably because that's not been the style we've used for GCC.

Removed throughout.

> > ///////
> > +
> > +/* Concrete implementation of region_model_context, wiring it up
> > to
> > the
> > +   rest of the analysis engine.  */
> > +
> > +class impl_region_model_context : public region_model_context,
> > public log_user
> Multiple inheritance?  Is it really needed?  Can we handle via
> composition instead?

I've reworked things throughout to avoid multiple inheritance.

> > +/* A <program_point, program_state> pair, used internally by
> > +   exploded_node as its immutable data, and as a key for
> > identifying
> > +   exploded_nodes we've already seen in the graph.  */
> > +
> > +struct point_and_state
> Shouldn't this be a class?

Fixed.

> +
> > +/* Per-program_point data for an exploded_graph.  */
> > +
> > +struct per_program_point_data
> Here too?  THere may be others.  I'd suggest reviewing all your
> "structs" and determine if we're better off calling them
> "class".  I'm
> not going to insist on it though since I think the last discussion in
> this space was to relax the conventions :-)

I went through these; mostly they're a bundle of fields, so making
them classes and adding getters and setters seemed pointless.
I made m_key "const" within struct per_program_point_data and
struct per_call_string_data as they cannot change after construction.


> > +
> > +class exploded_graph : public digraph<eg_traits>, public log_user
> Multiple inheritance again?

Fixed.

> Jeff
>

Here's an updated patch (v6) which I've successfully bootstrapped &
regrtested (with the rest of the kit) on x86_64-pc-linux-gnu.

OK for trunk? (assuming the rest of the kit is approved)

Thanks
Dave


Changed in v6:
- updates per Jeff's comments here
    https://gcc.gnu.org/ml/gcc-patches/2019-12/msg00813.html
  about considering converting structs to classes:
  - converted point_and_state from a struct to a class (given the
    cached hash value)
  - made m_key "const" within struct per_program_point_data and
    struct per_call_string_data
  Otherwise, converting the various other structs to classes would
  have meant simply adding getters and setter for all the fields, so
  I didn't.

Changed in v5:
- update ChangeLog path
- updated copyright years to include 2020

Changed in v4:
- Remove /// comment lines
- Don't use multiple inheritance, instead adding a log_user member.
- Add more validation, part of:
    https://gcc.gnu.org/ml/gcc-patches/2019-11/msg02517.html
- Generalize rewind_info_t to exploded_edge::custom_info_t
    https://gcc.gnu.org/ml/gcc-patches/2019-12/msg00219.html
- Add DISABLE_COPY_AND_ASSIGN (exploded_node);
- Add DISABLE_COPY_AND_ASSIGN (exploded_edge);
- Add DISABLE_COPY_AND_ASSIGN (exploded_graph);

This patch adds exploded_graph and related classes, for managing
exploring paths through the user's code as a directed graph
of <point, state> pairs.

gcc/analyzer/ChangeLog:
	* exploded-graph.h: New file.
---
 gcc/analyzer/exploded-graph.h | 835 ++++++++++++++++++++++++++++++++++
 1 file changed, 835 insertions(+)
 create mode 100644 gcc/analyzer/exploded-graph.h

diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h
new file mode 100644
index 00000000000..c81b65c2428
--- /dev/null
+++ b/gcc/analyzer/exploded-graph.h
@@ -0,0 +1,835 @@
+/* Classes for managing a directed graph of <point, state> pairs.
+   Copyright (C) 2019-2020 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/>.  */
+
+#ifndef GCC_ANALYZER_EXPLODED_GRAPH_H
+#define GCC_ANALYZER_EXPLODED_GRAPH_H
+
+#include "alloc-pool.h"
+#include "fibonacci_heap.h"
+#include "shortest-paths.h"
+#include "analyzer/analyzer-logging.h"
+#include "analyzer/constraint-manager.h"
+#include "analyzer/diagnostic-manager.h"
+#include "analyzer/program-point.h"
+#include "analyzer/program-state.h"
+
+/* Concrete implementation of region_model_context, wiring it up to the
+   rest of the analysis engine.  */
+
+class impl_region_model_context : public region_model_context
+{
+ public:
+  impl_region_model_context (exploded_graph &eg,
+			     const exploded_node *enode_for_diag,
+
+			     /* TODO: should we be getting the ECs from the
+				old state, rather than the new?  */
+			     const program_state *old_state,
+			     program_state *new_state,
+			     state_change *change,
+
+			     const gimple *stmt,
+			     stmt_finder *stmt_finder = NULL);
+
+  impl_region_model_context (program_state *state,
+			     state_change *change,
+			     const extrinsic_state &ext_state);
+
+  void warn (pending_diagnostic *d) FINAL OVERRIDE;
+
+  void remap_svalue_ids (const svalue_id_map &map) FINAL OVERRIDE;
+
+  int on_svalue_purge (svalue_id first_unused_sid,
+		       const svalue_id_map &map) FINAL OVERRIDE;
+
+  logger *get_logger () FINAL OVERRIDE
+  {
+    return m_logger.get_logger ();
+  }
+
+  void on_state_leak (const state_machine &sm,
+		      int sm_idx,
+		      svalue_id sid,
+		      svalue_id first_unused_sid,
+		      const svalue_id_map &map,
+		      state_machine::state_t state);
+
+  void on_inherited_svalue (svalue_id parent_sid,
+			    svalue_id child_sid) FINAL OVERRIDE;
+
+  void on_cast (svalue_id src_sid,
+		svalue_id dst_sid) FINAL OVERRIDE;
+
+  void on_condition (tree lhs, enum tree_code op, tree rhs) FINAL OVERRIDE;
+
+  exploded_graph *m_eg;
+  log_user m_logger;
+  const exploded_node *m_enode_for_diag;
+  const program_state *m_old_state;
+  program_state *m_new_state;
+  state_change *m_change;
+  const gimple *m_stmt;
+  stmt_finder *m_stmt_finder;
+  const extrinsic_state &m_ext_state;
+};
+
+/* A <program_point, program_state> pair, used internally by
+   exploded_node as its immutable data, and as a key for identifying
+   exploded_nodes we've already seen in the graph.  */
+
+class point_and_state
+{
+public:
+  point_and_state (const program_point &point,
+		   const program_state &state)
+  : m_point (point),
+    m_state (state),
+    m_hash (m_point.hash () ^ m_state.hash ())
+  {
+  }
+
+  hashval_t hash () const
+  {
+    return m_hash;
+  }
+  bool operator== (const point_and_state &other) const
+  {
+    return m_point == other.m_point && m_state == other.m_state;
+  }
+
+  const program_point &get_point () const { return m_point; }
+  const program_state &get_state () const { return m_state; }
+
+  void set_state (const program_state &state)
+  {
+    m_state = state;
+    m_hash = m_point.hash () ^ m_state.hash ();
+  }
+
+  void validate (const extrinsic_state &ext_state) const;
+
+private:
+  program_point m_point;
+  program_state m_state;
+  hashval_t m_hash;
+};
+
+/* A traits class for exploded graphs and their nodes and edges.  */
+
+struct eg_traits
+{
+  typedef exploded_node node_t;
+  typedef exploded_edge edge_t;
+  typedef exploded_graph graph_t;
+  struct dump_args_t
+  {
+    dump_args_t (const exploded_graph &eg) : m_eg (eg) {}
+    const exploded_graph &m_eg;
+  };
+  typedef exploded_cluster cluster_t;
+};
+
+/* An exploded_node is a unique, immutable <point, state> pair within the
+   exploded_graph.
+   Each exploded_node has a unique index within the graph
+   (for ease of debugging).  */
+
+class exploded_node : public dnode<eg_traits>
+{
+ public:
+  exploded_node (point_and_state ps,
+		 int index)
+  : m_ps (ps), m_index (index)
+  {
+    gcc_checking_assert (ps.get_state ().m_region_model->canonicalized_p ());
+  }
+
+  hashval_t hash () const { return m_ps.hash (); }
+
+  void dump_dot (graphviz_out *gv, const dump_args_t &args)
+    const FINAL OVERRIDE;
+  void dump_dot_id (pretty_printer *pp) const;
+
+  void dump_to_pp (pretty_printer *pp, const extrinsic_state &ext_state) const;
+  void dump (FILE *fp, const extrinsic_state &ext_state) const;
+  void dump (const extrinsic_state &ext_state) const;
+
+  /* The result of on_stmt.  */
+  struct on_stmt_flags
+  {
+    on_stmt_flags (bool sm_changes)
+    : m_sm_changes (sm_changes),
+      m_terminate_path (false)
+    {}
+
+    static on_stmt_flags terminate_path ()
+    {
+      return on_stmt_flags (true, true);
+    }
+
+    static on_stmt_flags state_change (bool any_sm_changes)
+    {
+      return on_stmt_flags (any_sm_changes, false);
+    }
+
+    /* Did any sm-changes occur handling the stmt.  */
+    bool m_sm_changes : 1;
+
+    /* Should we stop analyzing this path (on_stmt may have already
+       added nodes/edges, e.g. when handling longjmp).  */
+    bool m_terminate_path : 1;
+
+  private:
+    on_stmt_flags (bool sm_changes,
+		   bool terminate_path)
+    : m_sm_changes (sm_changes),
+      m_terminate_path (terminate_path)
+    {}
+  };
+
+  on_stmt_flags on_stmt (exploded_graph &eg,
+			 const supernode *snode,
+			 const gimple *stmt,
+			 program_state *state,
+			 state_change *change) const;
+  bool on_edge (exploded_graph &eg,
+		const superedge *succ,
+		program_point *next_point,
+		program_state *next_state,
+		state_change *change) const;
+  void on_longjmp (exploded_graph &eg,
+		   const gcall *call,
+		   program_state *new_state,
+		   region_model_context *ctxt) const;
+
+  void detect_leaks (exploded_graph &eg) const;
+
+  const program_point &get_point () const { return m_ps.get_point (); }
+  const supernode *get_supernode () const
+  {
+    return get_point ().get_supernode ();
+  }
+  function *get_function () const
+  {
+    return get_point ().get_function ();
+  }
+  int get_stack_depth () const
+  {
+    return get_point ().get_stack_depth ();
+  }
+  const gimple *get_stmt () const { return get_point ().get_stmt (); }
+
+  const program_state &get_state () const { return m_ps.get_state (); }
+
+  const point_and_state *get_ps_key () const { return &m_ps; }
+  const program_point *get_point_key () const { return &m_ps.get_point (); }
+
+  void dump_succs_and_preds (FILE *outf) const;
+
+private:
+  DISABLE_COPY_AND_ASSIGN (exploded_node);
+
+  const char * get_dot_fillcolor () const;
+
+  /* The <program_point, program_state> pair.  This is const, as it
+     is immutable once the exploded_node has been created.  */
+  const point_and_state m_ps;
+
+public:
+  /* The index of this exploded_node.  */
+  const int m_index;
+};
+
+/* An edge within the exploded graph.
+   Some exploded_edges have an underlying superedge; others don't.  */
+
+class exploded_edge : public dedge<eg_traits>
+{
+ public:
+  /* Abstract base class for associating custom data with an
+     exploded_edge, for handling non-standard edges such as
+     rewinding from a longjmp, signal handlers, etc.  */
+  class custom_info_t
+  {
+  public:
+    virtual ~custom_info_t () {}
+
+    /* Hook for making .dot label more readable .  */
+    virtual void print (pretty_printer *pp) = 0;
+
+    /* Hook for updating MODEL within exploded_path::feasible_p.  */
+    virtual void update_model (region_model *model,
+			       const exploded_edge &eedge) = 0;
+
+    virtual void add_events_to_path (checker_path *emission_path,
+				     const exploded_edge &eedge) = 0;
+  };
+
+  exploded_edge (exploded_node *src, exploded_node *dest,
+		 const superedge *sedge,
+		 const state_change &change,
+		 custom_info_t *custom_info);
+  ~exploded_edge ();
+  void dump_dot (graphviz_out *gv, const dump_args_t &args)
+    const FINAL OVERRIDE;
+
+  //private:
+  const superedge *const m_sedge;
+
+  const state_change m_change;
+
+  /* NULL for most edges; will be non-NULL for special cases
+     such as an unwind from a longjmp to a setjmp, or when
+     a signal is delivered to a signal-handler.
+
+     Owned by this class.  */
+  custom_info_t *m_custom_info;
+
+private:
+  DISABLE_COPY_AND_ASSIGN (exploded_edge);
+};
+
+/* Extra data for an exploded_edge that represents a rewind from a
+   longjmp to a setjmp.  */
+
+class rewind_info_t : public exploded_edge::custom_info_t
+{
+public:
+  rewind_info_t (const exploded_node *enode_origin)
+  : m_enode_origin (enode_origin)
+  {}
+
+  void print (pretty_printer *pp) FINAL OVERRIDE
+  {
+    pp_string (pp, "rewind");
+  }
+
+  void update_model (region_model *model,
+		     const exploded_edge &eedge) FINAL OVERRIDE;
+
+  void add_events_to_path (checker_path *emission_path,
+			   const exploded_edge &eedge) FINAL OVERRIDE;
+
+  const program_point &get_setjmp_point () const
+  {
+    const program_point &origin_point = m_enode_origin->get_point ();
+
+    /* "origin_point" ought to be before the call to "setjmp".  */
+    gcc_assert (origin_point.get_kind () == PK_BEFORE_STMT);
+
+    /* TODO: assert that it's the final stmt in its supernode.  */
+
+    return origin_point;
+  }
+
+  const gcall *get_setjmp_call () const
+  {
+    return as_a <const gcall *> (get_setjmp_point ().get_stmt ());
+  }
+
+  const exploded_node *get_enode_origin () const { return m_enode_origin; }
+
+private:
+  const exploded_node *m_enode_origin;
+};
+
+/* Statistics about aspects of an exploded_graph.  */
+
+struct stats
+{
+  stats (int num_supernodes);
+  void log (logger *logger) const;
+  void dump (FILE *out) const;
+
+  int m_num_nodes[NUM_POINT_KINDS];
+  int m_node_reuse_count;
+  int m_node_reuse_after_merge_count;
+  int m_num_supernodes;
+};
+
+/* Traits class for ensuring uniqueness of point_and_state data within
+   an exploded_graph.  */
+
+struct eg_hash_map_traits
+{
+  typedef const point_and_state *key_type;
+  typedef exploded_node *value_type;
+  typedef exploded_node *compare_type;
+
+  static inline hashval_t hash (const key_type &k)
+  {
+    gcc_assert (k != NULL);
+    gcc_assert (k != reinterpret_cast<key_type> (1));
+    return k->hash ();
+  }
+  static inline bool equal_keys (const key_type &k1, const key_type &k2)
+  {
+    gcc_assert (k1 != NULL);
+    gcc_assert (k2 != NULL);
+    gcc_assert (k1 != reinterpret_cast<key_type> (1));
+    gcc_assert (k2 != reinterpret_cast<key_type> (1));
+    if (k1 && k2)
+      return *k1 == *k2;
+    else
+      /* Otherwise they must both be non-NULL.  */
+      return k1 == k2;
+  }
+  template <typename T>
+  static inline void remove (T &)
+  {
+    /* empty; the nodes are handled elsewhere.  */
+  }
+  template <typename T>
+  static inline void mark_deleted (T &entry)
+  {
+    entry.m_key = reinterpret_cast<key_type> (1);
+  }
+  template <typename T>
+  static inline void mark_empty (T &entry)
+  {
+    entry.m_key = NULL;
+  }
+  template <typename T>
+  static inline bool is_deleted (const T &entry)
+  {
+    return entry.m_key == reinterpret_cast<key_type> (1);
+  }
+  template <typename T>
+  static inline bool is_empty (const T &entry)
+  {
+    return entry.m_key == NULL;
+  }
+};
+
+/* Per-program_point data for an exploded_graph.  */
+
+struct per_program_point_data
+{
+  per_program_point_data (const program_point &key)
+  : m_key (key)
+  {}
+
+  const program_point m_key;
+  auto_vec<exploded_node *> m_enodes;
+};
+
+/* Traits class for storing per-program_point data within
+   an exploded_graph.  */
+
+struct eg_point_hash_map_traits
+{
+  typedef const program_point *key_type;
+  typedef per_program_point_data *value_type;
+  typedef per_program_point_data *compare_type;
+
+  static inline hashval_t hash (const key_type &k)
+  {
+    gcc_assert (k != NULL);
+    gcc_assert (k != reinterpret_cast<key_type> (1));
+    return k->hash ();
+  }
+  static inline bool equal_keys (const key_type &k1, const key_type &k2)
+  {
+    gcc_assert (k1 != NULL);
+    gcc_assert (k2 != NULL);
+    gcc_assert (k1 != reinterpret_cast<key_type> (1));
+    gcc_assert (k2 != reinterpret_cast<key_type> (1));
+    if (k1 && k2)
+      return *k1 == *k2;
+    else
+      /* Otherwise they must both be non-NULL.  */
+      return k1 == k2;
+  }
+  template <typename T>
+  static inline void remove (T &)
+  {
+    /* empty; the nodes are handled elsewhere.  */
+  }
+  template <typename T>
+  static inline void mark_deleted (T &entry)
+  {
+    entry.m_key = reinterpret_cast<key_type> (1);
+  }
+  template <typename T>
+  static inline void mark_empty (T &entry)
+  {
+    entry.m_key = NULL;
+  }
+  template <typename T>
+  static inline bool is_deleted (const T &entry)
+  {
+    return entry.m_key == reinterpret_cast<key_type> (1);
+  }
+  template <typename T>
+  static inline bool is_empty (const T &entry)
+  {
+    return entry.m_key == NULL;
+  }
+};
+
+/* Data about a particular call_string within an exploded_graph.  */
+
+struct per_call_string_data
+{
+  per_call_string_data (const call_string &key, int num_supernodes)
+  : m_key (key), m_stats (num_supernodes)
+  {}
+
+  const call_string m_key;
+  stats m_stats;
+};
+
+/* Traits class for storing per-call_string data within
+   an exploded_graph.  */
+
+struct eg_call_string_hash_map_traits
+{
+  typedef const call_string *key_type;
+  typedef per_call_string_data *value_type;
+  typedef per_call_string_data *compare_type;
+
+  static inline hashval_t hash (const key_type &k)
+  {
+    gcc_assert (k != NULL);
+    gcc_assert (k != reinterpret_cast<key_type> (1));
+    return k->hash ();
+  }
+  static inline bool equal_keys (const key_type &k1, const key_type &k2)
+  {
+    gcc_assert (k1 != NULL);
+    gcc_assert (k2 != NULL);
+    gcc_assert (k1 != reinterpret_cast<key_type> (1));
+    gcc_assert (k2 != reinterpret_cast<key_type> (1));
+    if (k1 && k2)
+      return *k1 == *k2;
+    else
+      /* Otherwise they must both be non-NULL.  */
+      return k1 == k2;
+  }
+  template <typename T>
+  static inline void remove (T &)
+  {
+    /* empty; the nodes are handled elsewhere.  */
+  }
+  template <typename T>
+  static inline void mark_deleted (T &entry)
+  {
+    entry.m_key = reinterpret_cast<key_type> (1);
+  }
+  template <typename T>
+  static inline void mark_empty (T &entry)
+  {
+    entry.m_key = NULL;
+  }
+  template <typename T>
+  static inline bool is_deleted (const T &entry)
+  {
+    return entry.m_key == reinterpret_cast<key_type> (1);
+  }
+  template <typename T>
+  static inline bool is_empty (const T &entry)
+  {
+    return entry.m_key == NULL;
+  }
+};
+
+/* Data about a particular function within an exploded_graph.  */
+
+struct per_function_data
+{
+  per_function_data () {}
+
+  void add_call_summary (exploded_node *node)
+  {
+    m_summaries.safe_push (node);
+  }
+
+  auto_vec<exploded_node *> m_summaries;
+};
+
+
+/* The strongly connected components of a supergraph.
+   In particular, this allows us to compute a partial ordering
+   of supernodes.  */
+
+class strongly_connected_components
+{
+public:
+  strongly_connected_components (const supergraph &sg, logger *logger);
+
+  int get_scc_id (int node_index) const
+  {
+    return m_per_node[node_index].m_lowlink;
+  }
+
+  void dump () const;
+
+private:
+  struct per_node_data
+  {
+    per_node_data ()
+      : m_index (-1), m_lowlink (-1), m_on_stack (false)
+    {}
+
+    int m_index;
+    int m_lowlink;
+    bool m_on_stack;
+  };
+
+  void strong_connect (unsigned index);
+
+  const supergraph &m_sg;
+  auto_vec<unsigned> m_stack;
+  auto_vec<per_node_data> m_per_node;
+};
+
+/* The worklist of exploded_node instances that have been added to
+   an exploded_graph, but that haven't yet been processed to find
+   their successors (or warnings).
+
+   The enodes are stored in a priority queue, ordered by a topological
+   sort of the SCCs in the supergraph, so that enodes for the same
+   program_point should appear at the front of the queue together.
+   This allows for state-merging at CFG join-points, so that
+   sufficiently-similar enodes can be merged into one.  */
+
+class worklist
+{
+public:
+  worklist (const exploded_graph &eg, const analysis_plan &plan);
+  unsigned length () const;
+  exploded_node *take_next ();
+  exploded_node *peek_next ();
+  void add_node (exploded_node *enode);
+
+private:
+  class key_t
+  {
+  public:
+    key_t (const worklist &w, exploded_node *enode)
+    : m_worklist (w), m_enode (enode)
+    {}
+
+    bool operator< (const key_t &other) const
+    {
+      return cmp (*this, other) < 0;
+    }
+
+    bool operator== (const key_t &other) const
+    {
+      return cmp (*this, other) == 0;
+    }
+
+    bool operator> (const key_t &other) const
+    {
+      return !(*this == other || *this < other);
+    }
+
+  private:
+    static int cmp_1 (const key_t &ka, const key_t &kb);
+    static int cmp (const key_t &ka, const key_t &kb);
+
+    int get_scc_id (const exploded_node *enode) const
+    {
+      const supernode *snode = enode->get_supernode ();
+      if (snode == NULL)
+	return 0;
+      return m_worklist.m_scc.get_scc_id (snode->m_index);
+    }
+
+    const worklist &m_worklist;
+    exploded_node *m_enode;
+  };
+
+  /* The order is important here: m_scc needs to stick around
+     until after m_queue has finished being cleaned up (the dtor
+     calls the ordering fns).  */
+  const exploded_graph &m_eg;
+  strongly_connected_components m_scc;
+  const analysis_plan &m_plan;
+
+  /* Priority queue, backed by a fibonacci_heap.  */
+  typedef fibonacci_heap<key_t, exploded_node> queue_t;
+  queue_t m_queue;
+};
+
+/* An exploded_graph is a directed graph of unique <point, state> pairs.
+   It also has a worklist of nodes that are waiting for their successors
+   to be added to the graph.  */
+
+class exploded_graph : public digraph<eg_traits>
+{
+public:
+  typedef hash_map <const call_string *, per_call_string_data *,
+		    eg_call_string_hash_map_traits> call_string_data_map_t;
+
+  exploded_graph (const supergraph &sg, logger *logger,
+		  const extrinsic_state &ext_state,
+		  const state_purge_map *purge_map,
+		  const analysis_plan &plan,
+		  int verbosity);
+  ~exploded_graph ();
+
+  logger *get_logger () const { return m_logger.get_logger (); }
+
+  const supergraph &get_supergraph () const { return m_sg; }
+  const extrinsic_state &get_ext_state () const { return m_ext_state; }
+  const state_purge_map *get_purge_map () const { return m_purge_map; }
+  const analysis_plan &get_analysis_plan () const { return m_plan; }
+
+  exploded_node *get_origin () const { return m_origin; }
+
+  exploded_node *add_function_entry (function *fun);
+
+  void build_initial_worklist ();
+  void process_worklist ();
+  void process_node (exploded_node *node);
+
+  exploded_node *get_or_create_node (const program_point &point,
+				     const program_state &state,
+				     state_change *change);
+  exploded_edge *add_edge (exploded_node *src, exploded_node *dest,
+			   const superedge *sedge,
+			   const state_change &change,
+			   exploded_edge::custom_info_t *custom = NULL);
+
+  per_program_point_data *
+  get_or_create_per_program_point_data (const program_point &);
+
+  per_call_string_data *
+  get_or_create_per_call_string_data (const call_string &);
+
+  per_function_data *
+  get_or_create_per_function_data (function *);
+  per_function_data *get_per_function_data (function *) const;
+
+  void save_diagnostic (const state_machine &sm,
+			const exploded_node *enode,
+			const supernode *node, const gimple *stmt,
+			stmt_finder *finder,
+			tree var, state_machine::state_t state,
+			pending_diagnostic *d);
+
+  diagnostic_manager &get_diagnostic_manager ()
+  {
+    return m_diagnostic_manager;
+  }
+
+  stats *get_global_stats () { return &m_global_stats; }
+  stats *get_or_create_function_stats (function *fn);
+  void log_stats () const;
+  void dump_stats (FILE *) const;
+  void dump_states_for_supernode (FILE *, const supernode *snode) const;
+  void dump_exploded_nodes () const;
+
+  const call_string_data_map_t *get_per_call_string_data () const
+  { return &m_per_call_string_data; }
+
+private:
+  DISABLE_COPY_AND_ASSIGN (exploded_graph);
+
+  const supergraph &m_sg;
+
+  log_user m_logger;
+
+  /* Map from point/state to exploded node.
+     To avoid duplication we store point_and_state
+     *pointers* as keys, rather than point_and_state, using the
+     instance from within the exploded_node, with a custom hasher.  */
+  typedef hash_map <const point_and_state *, exploded_node *,
+		    eg_hash_map_traits> map_t;
+  map_t m_point_and_state_to_node;
+
+  /* Map from program_point to per-program_point data.  */
+  typedef hash_map <const program_point *, per_program_point_data *,
+		    eg_point_hash_map_traits> point_map_t;
+  point_map_t m_per_point_data;
+
+  worklist m_worklist;
+
+  exploded_node *m_origin;
+
+  const extrinsic_state &m_ext_state;
+
+  const state_purge_map *const m_purge_map;
+
+  const analysis_plan &m_plan;
+
+  typedef hash_map<function *, per_function_data *> per_function_data_t;
+  per_function_data_t m_per_function_data;
+
+  diagnostic_manager m_diagnostic_manager;
+
+  /* Stats.  */
+  stats m_global_stats;
+  typedef ordered_hash_map<function *, stats *> function_stat_map_t;
+  function_stat_map_t m_per_function_stats;
+  stats m_functionless_stats;
+
+  call_string_data_map_t m_per_call_string_data;
+
+  auto_vec<int> m_PK_AFTER_SUPERNODE_per_snode;
+};
+
+/* A path within an exploded_graph: a sequence of edges.  */
+
+class exploded_path
+{
+public:
+  exploded_path () : m_edges () {}
+  exploded_path (const exploded_path &other);
+  exploded_path & operator= (const exploded_path &other);
+
+  unsigned length () const { return m_edges.length (); }
+
+  bool find_stmt_backwards (const gimple *search_stmt,
+			    int *out_idx) const;
+
+  exploded_node *get_final_enode () const;
+
+  void dump_to_pp (pretty_printer *pp) const;
+  void dump (FILE *fp) const;
+  void dump () const;
+
+  bool feasible_p (logger *logger) const;
+
+  auto_vec<const exploded_edge *> m_edges;
+};
+
+/* Finding the shortest exploded_path within an exploded_graph.  */
+
+typedef shortest_paths<eg_traits, exploded_path> shortest_exploded_paths;
+
+/* Abstract base class for use when passing NULL as the stmt for
+   a possible warning, allowing the choice of stmt to be deferred
+   until after we have an emission path (and know we're emitting a
+   warning).  */
+
+class stmt_finder
+{
+public:
+  virtual ~stmt_finder () {}
+  virtual stmt_finder *clone () const = 0;
+  virtual const gimple *find_stmt (const exploded_path &epath) = 0;
+};
+
+// TODO: split the above up?
+
+#endif /* GCC_ANALYZER_EXPLODED_GRAPH_H */
-- 
2.21.0

^ permalink raw reply	[flat|nested] 160+ messages in thread

* [PATCH] Add ordered_hash_map (v6)
  2020-01-09 11:56       ` Jonathan Wakely
@ 2020-01-10  2:58         ` David Malcolm
  0 siblings, 0 replies; 160+ messages in thread
From: David Malcolm @ 2020-01-10  2:58 UTC (permalink / raw)
  To: Jonathan Wakely, gcc-patches; +Cc: Martin Sebor, David Malcolm

On Thu, 2020-01-09 at 11:56 +0000, Jonathan Wakely wrote:
> On 08/01/20 18:47 -0500, David Malcolm wrote:
> > On Wed, 2019-12-04 at 10:59 -0700, Martin Sebor wrote:
> > > On 11/15/19 6:23 PM, David Malcolm wrote:
> > > > This patch adds an ordered_hash_map template, which is similar
> > > > to
> > > > hash_map, but preserves insertion order.
> > > > 
> > > > gcc/ChangeLog:
> > > > 	* Makefile.in (OBJS): Add ordered-hash-map-tests.o.
> > > > 	* ordered-hash-map-tests.cc: New file.
> > > > 	* ordered-hash-map.h: New file.
> > > > 	* selftest-run-tests.c (selftest::run_tests): Call
> > > > 	selftest::ordered_hash_map_tests_cc_tests.
> > > > 	* selftest.h
> > > > (selftest::ordered_hash_map_tests_cc_tests): New
> > > > 	decl.
> > > > ---
> > > >   gcc/Makefile.in               |   1 +
> > > >   gcc/ordered-hash-map-tests.cc | 247
> > > > ++++++++++++++++++++++++++++++++++++++++++
> > > >   gcc/ordered-hash-map.h        | 184
> > > > +++++++++++++++++++++++++++++++
> > > >   gcc/selftest-run-tests.c      |   1 +
> > > >   gcc/selftest.h                |   1 +
> > > >   5 files changed, 434 insertions(+)
> > > >   create mode 100644 gcc/ordered-hash-map-tests.cc
> > > >   create mode 100644 gcc/ordered-hash-map.h
> > > > 
> > 
> > [...]
> > 
> > > The container defines a copy-constructor but no copy assignment.
> > > Is it safely assignable? (I don't think auto_vec is safely
> > > copyable
> > > or assignable due to PR 90904.  It looks like the copy ctor works
> > > around it below.)
> > 
> > It's not safely assignable; I don't believe I'm using that (I am
> > using
> > the copy ctor).
> > 
> > I can make it private or similar to ensure it's not used.
> 
> Yes, if the compiler-generated assignment isn't safe, please declare
> (but don't define) a private assignment operator, to suppress the
> compiler-generated one.

Done.

> > > I don't think I've made use of the hash_map copy ctor or copy
> > > assignment but if it's anything like other GCC containers I'd
> > > worry about it not doing the right thing, especially for non-
> > > PODs.  I spent too much time chasing down miscompilations and
> > > other problems due to bugs (or design limitations) in these
> > > classes.
> > > 
> > > I'd far prefer to see us use libstdc++ containers in new code
> > > than introduce new ones of our own.  They are better designed
> > > and much better tested than these.  (I realize we're still
> > > hampered by targeting C++ 98.)
> > > 
> > > Martin
> > 
> > [CCing Jonathan for libstdc++ expertise]
> > 
> > Is there such a container in libstdc++?
> > This patch implements a map that preserves insertion order when
> > iterating, without requiring the Key type to be comparable.
> > 
> > As far as I can tell, std::map instead is a map that requires the
> > Key
> > type to be comparable,
> 
> Right.
> 
> > and uses that to implement a red-black tree
> > (rather than via hashing),
> 
> Right.
> 
> > and doesn't preserve insertion ordering.
> 
> std::map has unique keys anyway, so insertion order is irrelevant.
> Iteration is done by key order.
> 
> std::multimap is the non-unique version, and preserves insertion
> order
> for equivalent keys, but only since C++11. Before that it was
> unspecified whether or not a duplicate key was inserted after
> existing
> elements with equivalent keys.
> 
> > I use this ordered_hash_map class later in various places in the
> > analyzer patch kit to ensure more deterministic results, so that
> > results aren't affected by hash values of possibly-changing pointer
> > values.

(My class is similar to Python's OrderedDict, FWIW)

> Nothing in the standard library provides that functionality out of
> the
> box. The C++11 hash containers don't preserve order at all, even
> equivalent elements can be reordered when insertion triggers
> rehashing.

Thanks.  

> You could build something using a sequence container (vector, deque
> or
> list) that preserves a stable order according to insertion order, and
> then manually maintain a separate index of hash values used for
> lookup. That might not make things any simpler (I haven't looked at
> your actual patch).
 
I think that's basically what my patch does.

Thanks
Dave


Changed in v6:
- added private unimplemented assignment operator to suppress
bogus compiler-generated one

Changed in v5:
- updated copyright years to include 2020

This patch adds an ordered_hash_map template, which is similar to
hash_map, but preserves insertion order.

gcc/ChangeLog:
	* Makefile.in (OBJS): Add ordered-hash-map-tests.o.
	* ordered-hash-map-tests.cc: New file.
	* ordered-hash-map.h: New file.
	* selftest-run-tests.c (selftest::run_tests): Call
	selftest::ordered_hash_map_tests_cc_tests.
	* selftest.h (selftest::ordered_hash_map_tests_cc_tests): New
	decl.
---
 gcc/Makefile.in               |   1 +
 gcc/ordered-hash-map-tests.cc | 247 ++++++++++++++++++++++++++++++++++
 gcc/ordered-hash-map.h        | 188 ++++++++++++++++++++++++++
 gcc/selftest-run-tests.c      |   1 +
 gcc/selftest.h                |   1 +
 5 files changed, 438 insertions(+)
 create mode 100644 gcc/ordered-hash-map-tests.cc
 create mode 100644 gcc/ordered-hash-map.h

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 4e23c0da47f..208d0e2ada8 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1443,6 +1443,7 @@ OBJS = \
 	optinfo-emit-json.o \
 	options-save.o \
 	opts-global.o \
+	ordered-hash-map-tests.o \
 	passes.o \
 	plugin.o \
 	postreload-gcse.o \
diff --git a/gcc/ordered-hash-map-tests.cc b/gcc/ordered-hash-map-tests.cc
new file mode 100644
index 00000000000..2bc5f6ed715
--- /dev/null
+++ b/gcc/ordered-hash-map-tests.cc
@@ -0,0 +1,247 @@
+/* Unit tests for ordered-hash-map.h.
+   Copyright (C) 2015-2020 Free Software Foundation, Inc.
+
+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 "tm.h"
+#include "opts.h"
+#include "hash-set.h"
+#include "fixed-value.h"
+#include "alias.h"
+#include "flags.h"
+#include "symtab.h"
+#include "tree-core.h"
+#include "stor-layout.h"
+#include "tree.h"
+#include "stringpool.h"
+#include "ordered-hash-map.h"
+#include "selftest.h"
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Populate *OUT_KVS with the key/value pairs of M.  */
+
+template <typename HashMap, typename Key, typename Value>
+static void
+get_kv_pairs (const HashMap &m,
+	      auto_vec<std::pair<Key, Value> > *out_kvs)
+{
+  for (typename HashMap::iterator iter = m.begin ();
+       iter != m.end ();
+       ++iter)
+    out_kvs->safe_push (std::make_pair ((*iter).first, (*iter).second));
+}
+
+/* Construct an ordered_hash_map <const char *, int> and verify that
+   various operations work correctly.  */
+
+static void
+test_map_of_strings_to_int ()
+{
+  ordered_hash_map <const char *, int> m;
+
+  const char *ostrich = "ostrich";
+  const char *elephant = "elephant";
+  const char *ant = "ant";
+  const char *spider = "spider";
+  const char *millipede = "Illacme plenipes";
+  const char *eric = "half a bee";
+
+  /* A fresh hash_map should be empty.  */
+  ASSERT_EQ (0, m.elements ());
+  ASSERT_EQ (NULL, m.get (ostrich));
+
+  /* Populate the hash_map.  */
+  ASSERT_EQ (false, m.put (ostrich, 2));
+  ASSERT_EQ (false, m.put (elephant, 4));
+  ASSERT_EQ (false, m.put (ant, 6));
+  ASSERT_EQ (false, m.put (spider, 8));
+  ASSERT_EQ (false, m.put (millipede, 750));
+  ASSERT_EQ (false, m.put (eric, 3));
+
+  /* Verify that we can recover the stored values.  */
+  ASSERT_EQ (6, m.elements ());
+  ASSERT_EQ (2, *m.get (ostrich));
+  ASSERT_EQ (4, *m.get (elephant));
+  ASSERT_EQ (6, *m.get (ant));
+  ASSERT_EQ (8, *m.get (spider));
+  ASSERT_EQ (750, *m.get (millipede));
+  ASSERT_EQ (3, *m.get (eric));
+
+  /* Verify that the order of insertion is preserved.  */
+  auto_vec<std::pair<const char *, int> > kvs;
+  get_kv_pairs (m, &kvs);
+  ASSERT_EQ (kvs.length (), 6);
+  ASSERT_EQ (kvs[0].first, ostrich);
+  ASSERT_EQ (kvs[0].second, 2);
+  ASSERT_EQ (kvs[1].first, elephant);
+  ASSERT_EQ (kvs[1].second, 4);
+  ASSERT_EQ (kvs[2].first, ant);
+  ASSERT_EQ (kvs[2].second, 6);
+  ASSERT_EQ (kvs[3].first, spider);
+  ASSERT_EQ (kvs[3].second, 8);
+  ASSERT_EQ (kvs[4].first, millipede);
+  ASSERT_EQ (kvs[4].second, 750);
+  ASSERT_EQ (kvs[5].first, eric);
+  ASSERT_EQ (kvs[5].second, 3);
+}
+
+/* Construct an ordered_hash_map using int_hash and verify that various
+   operations work correctly.  */
+
+static void
+test_map_of_int_to_strings ()
+{
+  const int EMPTY = -1;
+  const int DELETED = -2;
+  typedef int_hash <int, EMPTY, DELETED> int_hash_t;
+  ordered_hash_map <int_hash_t, const char *> m;
+
+  const char *ostrich = "ostrich";
+  const char *elephant = "elephant";
+  const char *ant = "ant";
+  const char *spider = "spider";
+  const char *millipede = "Illacme plenipes";
+  const char *eric = "half a bee";
+
+  /* A fresh hash_map should be empty.  */
+  ASSERT_EQ (0, m.elements ());
+  ASSERT_EQ (NULL, m.get (2));
+
+  /* Populate the hash_map.  */
+  ASSERT_EQ (false, m.put (2, ostrich));
+  ASSERT_EQ (false, m.put (4, elephant));
+  ASSERT_EQ (false, m.put (6, ant));
+  ASSERT_EQ (false, m.put (8, spider));
+  ASSERT_EQ (false, m.put (750, millipede));
+  ASSERT_EQ (false, m.put (3, eric));
+
+  /* Verify that we can recover the stored values.  */
+  ASSERT_EQ (6, m.elements ());
+  ASSERT_EQ (*m.get (2), ostrich);
+  ASSERT_EQ (*m.get (4), elephant);
+  ASSERT_EQ (*m.get (6), ant);
+  ASSERT_EQ (*m.get (8), spider);
+  ASSERT_EQ (*m.get (750), millipede);
+  ASSERT_EQ (*m.get (3), eric);
+
+  /* Verify that the order of insertion is preserved.  */
+  auto_vec<std::pair<int, const char *> > kvs;
+  get_kv_pairs (m, &kvs);
+  ASSERT_EQ (kvs.length (), 6);
+  ASSERT_EQ (kvs[0].first, 2);
+  ASSERT_EQ (kvs[0].second, ostrich);
+  ASSERT_EQ (kvs[1].first, 4);
+  ASSERT_EQ (kvs[1].second, elephant);
+  ASSERT_EQ (kvs[2].first, 6);
+  ASSERT_EQ (kvs[2].second, ant);
+  ASSERT_EQ (kvs[3].first, 8);
+  ASSERT_EQ (kvs[3].second, spider);
+  ASSERT_EQ (kvs[4].first, 750);
+  ASSERT_EQ (kvs[4].second, millipede);
+  ASSERT_EQ (kvs[5].first, 3);
+  ASSERT_EQ (kvs[5].second, eric);
+}
+
+/* Verify that we can remove items from an ordered_hash_map.  */
+
+static void
+test_removal ()
+{
+  ordered_hash_map <const char *, int> m;
+
+  const char *ostrich = "ostrich";
+  ASSERT_EQ (false, m.put (ostrich, 2));
+
+  ASSERT_EQ (1, m.elements ());
+  ASSERT_EQ (2, *m.get (ostrich));
+
+  {
+    auto_vec<std::pair<const char *, int> > kvs;
+    get_kv_pairs (m, &kvs);
+    ASSERT_EQ (kvs.length (), 1);
+    ASSERT_EQ (kvs[0].first, ostrich);
+    ASSERT_EQ (kvs[0].second, 2);
+  }
+
+  m.remove (ostrich);
+
+  ASSERT_EQ (0, m.elements ());
+  {
+    auto_vec<std::pair<const char *, int> > kvs;
+    get_kv_pairs (m, &kvs);
+    ASSERT_EQ (kvs.length (), 0);
+  }
+
+  /* Reinsertion (with a different value).  */
+  ASSERT_EQ (false, m.put (ostrich, 42));
+  ASSERT_EQ (1, m.elements ());
+  ASSERT_EQ (42, *m.get (ostrich));
+  {
+    auto_vec<std::pair<const char *, int> > kvs;
+    get_kv_pairs (m, &kvs);
+    ASSERT_EQ (kvs.length (), 1);
+    ASSERT_EQ (kvs[0].first, ostrich);
+    ASSERT_EQ (kvs[0].second, 42);
+  }
+}
+
+/* Verify that ordered_hash_map's copy-ctor works.  */
+
+static void
+test_copy_ctor ()
+{
+  ordered_hash_map <const char *, int> m;
+
+  const char *ostrich = "ostrich";
+  ASSERT_EQ (false, m.put (ostrich, 2));
+
+  ASSERT_EQ (1, m.elements ());
+  ASSERT_EQ (2, *m.get (ostrich));
+
+  ordered_hash_map <const char *, int> copy (m);
+  ASSERT_EQ (1, copy.elements ());
+  ASSERT_EQ (2, *copy.get (ostrich));
+
+  /* Remove from source.  */
+  m.remove (ostrich);
+  ASSERT_EQ (0, m.elements ());
+
+  /* Copy should be unaffected.  */
+  ASSERT_EQ (1, copy.elements ());
+  ASSERT_EQ (2, *copy.get (ostrich));
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+ordered_hash_map_tests_cc_tests ()
+{
+  test_map_of_strings_to_int ();
+  test_map_of_int_to_strings ();
+  test_removal ();
+  test_copy_ctor ();
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
diff --git a/gcc/ordered-hash-map.h b/gcc/ordered-hash-map.h
new file mode 100644
index 00000000000..188519fd3dc
--- /dev/null
+++ b/gcc/ordered-hash-map.h
@@ -0,0 +1,188 @@
+/* A type-safe hash map that retains the insertion order of keys.
+   Copyright (C) 2019-2020 Free Software Foundation, Inc.
+
+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/>.  */
+
+
+#ifndef GCC_ORDERED_HASH_MAP_H
+#define GCC_ORDERED_HASH_MAP_H
+
+/* Notes:
+   - The keys must be PODs, since vec<> uses assignment to populate slots
+     without properly initializing them.
+   - doesn't have GTY support.
+   - supports removal, but retains order of original insertion.
+     (Removal might be better handled by using a doubly-linked list
+     of nodes, holding the values).  */
+
+template<typename KeyId, typename Value,
+	 typename Traits>
+class ordered_hash_map
+{
+  typedef typename Traits::key_type Key;
+
+public:
+  ordered_hash_map () {}
+
+  ordered_hash_map (const ordered_hash_map &other)
+  : m_map (other.m_map),
+    m_keys (other.m_keys.length ()),
+    m_key_index (other.m_key_index)
+  {
+     unsigned i;
+     Key key;
+     FOR_EACH_VEC_ELT (other.m_keys, i, key)
+       m_keys.quick_push (key);
+  }
+
+  /* If key K isn't already in the map add key K with value V to the map, and
+     return false.  Otherwise set the value of the entry for key K to be V and
+     return true.  */
+
+  bool put (const Key &k, const Value &v)
+  {
+    bool existed = m_map.put (k, v);
+    if (!existed)
+      {
+        bool key_present;
+        int &slot = m_key_index.get_or_insert (k, &key_present);
+        if (!key_present)
+          {
+             slot = m_keys.length ();
+             m_keys.safe_push (k);
+          }
+      }
+    return existed;
+  }
+
+  /* If the passed in key is in the map return its value otherwise NULL.  */
+
+  Value *get (const Key &k)
+  {
+    return m_map.get (k);
+  }
+
+  /* Removing a key removes it from the map, but retains the insertion
+     order.  */
+
+  void remove (const Key &k)
+  {
+     m_map.remove (k);
+  }
+
+  size_t elements () const { return m_map.elements (); }
+
+  class iterator
+  {
+  public:
+    explicit iterator (const ordered_hash_map &map, unsigned idx) :
+      m_ordered_hash_map (map), m_idx (idx) {}
+
+    iterator &operator++ ()
+    {
+       /* Increment m_idx until we find a non-deleted element, or go beyond
+	  the end.  */
+       while (1)
+	 {
+	   ++m_idx;
+	   if (valid_index_p ())
+	     break;
+	}
+      return *this;
+    }
+
+    /* Can't use std::pair here, because GCC before 4.3 don't handle
+       std::pair where template parameters are references well.
+       See PR86739.  */
+    struct reference_pair {
+      const Key &first;
+      Value &second;
+
+      reference_pair (const Key &key, Value &value)
+      : first (key), second (value) {}
+
+      template <typename K, typename V>
+      operator std::pair<K, V> () const { return std::pair<K, V> (first, second); }
+    };
+
+    reference_pair operator* ()
+    {
+      const Key &k = m_ordered_hash_map.m_keys[m_idx];
+      Value *slot
+        = const_cast<ordered_hash_map &> (m_ordered_hash_map).get (k);
+      gcc_assert (slot);
+      return reference_pair (k, *slot);
+    }
+
+    bool
+    operator != (const iterator &other) const
+    {
+      return m_idx != other.m_idx;
+    }
+
+    /* Treat one-beyond-the-end as valid, for handling the "end" case.  */
+
+    bool valid_index_p () const
+    {
+      if (m_idx > m_ordered_hash_map.m_keys.length ())
+	return false;
+      if (m_idx == m_ordered_hash_map.m_keys.length ())
+	return true;
+      const Key &k = m_ordered_hash_map.m_keys[m_idx];
+      Value *slot
+	= const_cast<ordered_hash_map &> (m_ordered_hash_map).get (k);
+      return slot != NULL;
+    }
+
+    const ordered_hash_map &m_ordered_hash_map;
+    unsigned m_idx;
+  };
+
+  /* Standard iterator retrieval methods.  */
+
+  iterator begin () const
+  {
+    iterator i = iterator (*this, 0);
+    while (!i.valid_index_p () && i != end ())
+      ++i;
+    return i;
+  }
+  iterator end () const { return iterator (*this, m_keys.length ()); }
+
+private:
+  /* The assignment operator is not yet implemented; prevent erroneous
+     usage of unsafe compiler-generated one.  */
+  void operator= (const ordered_hash_map &);
+
+  /* The underlying map.  */
+  hash_map<KeyId, Value, Traits> m_map;
+
+  /* The ordering of the keys.  */
+  auto_vec<Key> m_keys;
+
+  /* For each key that's ever been in the map, its index within m_keys.  */
+  hash_map<KeyId, int> m_key_index;
+};
+
+/* Two-argument form.  */
+
+template<typename Key, typename Value,
+	 typename Traits = simple_hashmap_traits<default_hash_traits<Key>,
+						 Value> >
+class ordered_hash_map;
+
+#endif /* GCC_ORDERED_HASH_MAP_H */
diff --git a/gcc/selftest-run-tests.c b/gcc/selftest-run-tests.c
index f7f0bd34953..b468e8799d4 100644
--- a/gcc/selftest-run-tests.c
+++ b/gcc/selftest-run-tests.c
@@ -76,6 +76,7 @@ selftest::run_tests ()
   cgraph_c_tests ();
   optinfo_emit_json_cc_tests ();
   opt_problem_cc_tests ();
+  ordered_hash_map_tests_cc_tests ();
 
   /* Mid-level data structures.  */
   input_c_tests ();
diff --git a/gcc/selftest.h b/gcc/selftest.h
index 140784d6c14..e697c8da2e2 100644
--- a/gcc/selftest.h
+++ b/gcc/selftest.h
@@ -242,6 +242,7 @@ extern void input_c_tests ();
 extern void json_cc_tests ();
 extern void opt_problem_cc_tests ();
 extern void optinfo_emit_json_cc_tests ();
+extern void ordered_hash_map_tests_cc_tests ();
 extern void predict_c_tests ();
 extern void pretty_print_c_tests ();
 extern void range_tests ();
-- 
2.21.0

^ permalink raw reply	[flat|nested] 160+ messages in thread

end of thread, other threads:[~2020-01-10  2:48 UTC | newest]

Thread overview: 160+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-11-16  1:21 [PATCH 00/49] RFC: Add a static analysis framework to GCC David Malcolm
2019-11-16  1:17 ` [PATCH 08/49] Introduce pretty_printer::clone vfunc David Malcolm
2019-12-07 14:36   ` Jeff Law
2019-11-16  1:17 ` [PATCH 11/49] Add diagnostic_metadata and CWE support David Malcolm
2019-12-04 17:36   ` Martin Sebor
2019-12-13  3:00     ` David Malcolm
2019-12-13 16:44       ` Martin Sebor
2019-12-13 17:32         ` David Malcolm
2019-11-16  1:17 ` [PATCH 02/49] analyzer: internal documentation David Malcolm
2019-11-16  1:17 ` [PATCH 14/49] hash-map-tests.c: add a selftest involving int_hash David Malcolm
2019-12-07 14:38   ` Jeff Law
2019-11-16  1:17 ` [PATCH 09/49] gimple const-correctness fixes David Malcolm
2019-12-04 17:06   ` Martin Sebor
2019-12-06 10:52   ` Richard Biener
2019-12-06 17:47     ` David Malcolm
2019-12-09  8:27       ` Richard Biener
2019-12-07 14:28   ` Jeff Law
2019-12-09  8:18     ` Richard Biener
2019-11-16  1:17 ` [PATCH 01/49] analyzer: user-facing documentation David Malcolm
2019-12-06 19:57   ` Eric Gallager
2019-12-07  1:41     ` David Malcolm
2019-12-09  5:36   ` Sandra Loosemore
2019-11-16  1:18 ` [PATCH 32/49] analyzer: new files: pending-diagnostic.{cc|h} David Malcolm
2019-12-07 15:05   ` Jeff Law
2019-12-08 14:43     ` David Malcolm
2019-12-08 22:25       ` Jeff Law
2019-11-16  1:18 ` [PATCH 06/49] timevar.h: add auto_client_timevar class David Malcolm
2019-12-04 16:39   ` Martin Sebor
2019-12-04 17:28     ` Tom Tromey
2019-12-04 21:15       ` David Malcolm
2019-12-07 14:29   ` Jeff Law
2019-12-08 14:30     ` David Malcolm
2019-11-16  1:18 ` [PATCH 18/49] Add in-tree plugin: "analyzer" David Malcolm
2019-11-16  1:18 ` [PATCH 23/49] analyzer: logging support David Malcolm
2019-12-04 18:56   ` Martin Sebor
2019-11-16  1:18 ` [PATCH 26/49] analyzer: new files: digraph.{cc|h} and shortest-paths.h David Malcolm
2019-12-07 14:58   ` Jeff Law
2019-12-09 23:15     ` David Malcolm
2019-12-10  0:45   ` Martin Sebor
2019-11-16  1:18 ` [PATCH 03/49] diagnostic_show_locus: move initial newline to callers David Malcolm
2019-12-07 14:30   ` Jeff Law
2019-12-10  1:51     ` David Malcolm
2019-11-16  1:18 ` [PATCH 12/49] Add diagnostic paths David Malcolm
2019-12-07 14:45   ` Jeff Law
2019-12-19  2:49     ` David Malcolm
2019-11-16  1:18 ` [PATCH 30/49] analyzer: new files: constraint-manager.{cc|h} David Malcolm
2019-11-16  1:18 ` [PATCH 19/49] analyzer: new files: analyzer-selftests.{cc|h} David Malcolm
2019-12-07 14:48   ` Jeff Law
2019-11-16  1:18 ` [PATCH 22/49] analyzer: params.def: new parameters David Malcolm
2019-12-07 14:49   ` Jeff Law
2019-12-08  5:42     ` Eric Gallager
2019-12-08 14:34       ` David Malcolm
2019-11-16  1:18 ` [PATCH 21/49] analyzer: command-line options David Malcolm
2019-12-04 18:35   ` Martin Sebor
2019-12-06 18:14     ` Eric Gallager
2019-11-16  1:18 ` [PATCH 24/49] analyzer: new file: analyzer-pass.cc David Malcolm
2019-12-07 14:51   ` Jeff Law
2019-12-08 14:38     ` David Malcolm
2019-11-16  1:18 ` [PATCH 28/49] analyzer: new files: analyzer.{cc|h} David Malcolm
2019-12-07  3:38   ` Eric Gallager
2019-12-09 23:22     ` David Malcolm
2019-12-10 19:59       ` Eric Gallager
2019-12-07 15:02   ` Jeff Law
2019-12-11 19:49     ` David Malcolm
2019-12-20  1:21       ` [PATCH 0/4] analyzer: add class function_set and use in various places David Malcolm
2019-12-20  1:22         ` [PATCH 2/4] analyzer: introduce a set of known async-signal-unsafe functions David Malcolm
2019-12-20  1:22         ` [PATCH 1/4] analyzer: add function-set.cc/h David Malcolm
2019-12-20  1:22         ` [PATCH 3/4] analyzer: add known stdio functions to sm-file.cc (PR analyzer/58237) David Malcolm
2019-12-20  6:34         ` [PATCH 4/4] analyzer: add -Wanalyzer-use-of-closed-file David Malcolm
2019-12-10 17:17   ` [PATCH 28/49] analyzer: new files: analyzer.{cc|h} Martin Sebor
2019-11-16  1:18 ` [PATCH 17/49] Support for adding selftests via a plugin David Malcolm
2019-12-07 14:41   ` Jeff Law
2019-12-09 23:18     ` David Malcolm
2019-11-16  1:18 ` [PATCH 29/49] analyzer: new files: tristate.{cc|h} David Malcolm
2019-12-07 15:03   ` Jeff Law
2019-12-09 23:16     ` David Malcolm
2019-12-10  0:59   ` Martin Sebor
2019-11-16  1:18 ` [PATCH 27/49] analyzer: new files: supergraph.{cc|h} David Malcolm
2019-11-16  1:18 ` [PATCH 35/49] analyzer: new file: sm-file.cc David Malcolm
2019-12-07 15:15   ` Jeff Law
2019-11-16  1:18 ` [PATCH 07/49] Replace label_text ctor with "borrow" and "take" David Malcolm
2019-12-07 14:34   ` Jeff Law
2019-11-16  1:18 ` [PATCH 33/49] analyzer: new files: sm.{cc|h} David Malcolm
2019-12-11 19:24   ` Jeff Law
2020-01-10  2:28     ` David Malcolm
2019-11-16  1:18 ` [PATCH 05/49] vec.h: add auto_delete_vec David Malcolm
2019-12-04 16:29   ` Martin Sebor
2019-12-18 16:00     ` David Malcolm
2020-01-08 21:52       ` Jeff Law
2019-11-16  1:18 ` [PATCH 36/49] analyzer: new file: sm-pattern-test.cc David Malcolm
2019-12-07 15:16   ` Jeff Law
2019-11-16  1:18 ` [PATCH 16/49] Add support for in-tree plugins David Malcolm
2019-12-06 22:41   ` Eric Gallager
2019-12-07 14:39   ` Jeff Law
2019-12-08 14:32     ` David Malcolm
2019-11-16  1:20 ` [PATCH 04/49] sbitmap.h: add operator const sbitmap & to auto_sbitmap David Malcolm
2019-12-04 16:54   ` Martin Sebor
2019-11-16  1:20 ` [PATCH 34/49] analyzer: new file: sm-malloc.cc David Malcolm
2019-12-07 15:11   ` Jeff Law
2019-11-16  1:20 ` [PATCH 39/49] analyzer: new files: analysis-plan.{cc|h} David Malcolm
2019-12-07 15:24   ` Jeff Law
2019-11-16  1:21 ` [PATCH 43/49] analyzer: new file: exploded-graph.h David Malcolm
2019-12-11 20:04   ` Jeff Law
2019-12-12 15:29     ` David Malcolm
2019-12-12 18:22       ` David Malcolm
2020-01-10  2:41     ` David Malcolm
2019-11-16  1:21 ` [PATCH 40/49] analyzer: new files: call-string.{cc|h} David Malcolm
2019-12-11 19:28   ` Jeff Law
2019-11-16  1:21 ` [PATCH 44/49] analyzer: new files: state-purge.{cc|h} David Malcolm
2019-12-11 20:11   ` Jeff Law
2019-11-16  1:21 ` [PATCH 47/49] analyzer: new files: diagnostic-manager.{cc|h} David Malcolm
2019-12-11 20:42   ` Jeff Law
2019-12-11 21:11     ` David Malcolm
2019-11-16  1:21 ` [PATCH 46/49] analyzer: new files: checker-path.{cc|h} David Malcolm
2019-12-11 20:50   ` Jeff Law
2019-11-16  1:21 ` [PATCH 31/49] analyzer: new files: region-model.{cc|h} David Malcolm
2019-11-16  1:21 ` [PATCH 41/49] analyzer: new files: program-point.{cc|h} David Malcolm
2019-12-11 19:54   ` Jeff Law
2019-12-11 19:58     ` David Malcolm
2019-11-16  1:21 ` [PATCH 10/49] Add -fdiagnostics-nn-line-numbers David Malcolm
2019-12-04 17:18   ` Martin Sebor
2019-12-04 17:24   ` Martin Sebor
2019-11-16  1:21 ` [PATCH 49/49] analyzer: test suite David Malcolm
2019-11-16  1:21 ` [PATCH 15/49] Add ordered_hash_map David Malcolm
2019-12-04 17:59   ` Martin Sebor
2020-01-08 23:47     ` David Malcolm
2020-01-09 11:56       ` Jonathan Wakely
2020-01-10  2:58         ` [PATCH] Add ordered_hash_map (v6) David Malcolm
2019-11-16  1:21 ` [PATCH 48/49] gdbinit.in: add break-on-saved-diagnostic David Malcolm
2019-11-16  1:21 ` [PATCH 13/49] function-tests.c: expose selftest::make_fndecl for use elsewhere David Malcolm
2019-12-07 14:37   ` Jeff Law
2019-11-16  1:21 ` [PATCH 37/49] analyzer: new file: sm-sensitive.cc David Malcolm
2019-12-07 15:18   ` Jeff Law
2019-11-16  1:21 ` [PATCH 38/49] analyzer: new file: sm-taint.cc David Malcolm
2019-12-07 15:22   ` Jeff Law
2019-11-16  1:21 ` [PATCH 42/49] analyzer: new files: program-state.{cc|h} David Malcolm
2019-11-16  1:21 ` [PATCH 25/49] analyzer: new files: graphviz.{cc|h} David Malcolm
2019-12-07 14:54   ` Jeff Law
2019-12-09 23:13     ` David Malcolm
2019-11-16  1:21 ` [PATCH 45/49] analyzer: new files: engine.{cc|h} David Malcolm
2019-11-16  1:23 ` [PATCH 20/49] analyzer: new builtins David Malcolm
2019-12-04 18:11   ` Martin Sebor
2019-12-19 15:16     ` David Malcolm
2019-11-16 21:47 ` [PATCH 00/49] RFC: Add a static analysis framework to GCC Thomas Schwinge
2019-11-19 22:05   ` David Malcolm
2019-11-20 10:26     ` Richard Biener
2019-12-02  3:03       ` Eric Gallager
2019-12-03 16:52       ` David Malcolm
2019-12-03 17:17         ` Jakub Jelinek
2019-12-16 14:33           ` David Malcolm
2019-12-04 12:41         ` Richard Biener
2019-12-06 22:25         ` Jeff Law
2019-12-08 14:28           ` [PATCH 0/2] v3 of analyzer patch kit (unsquashed) David Malcolm
2019-12-08 14:28             ` [PATCH 2/2] Rework as a non-plugin David Malcolm
2019-12-08 14:28             ` [PATCH 1/2] Fixup for rebase: c-format.c: get_pointer_to_named_type -> get_named_type David Malcolm
2019-12-02  3:20   ` [PATCH 00/49] RFC: Add a static analysis framework to GCC Eric Gallager
2019-12-04 19:55 ` Martin Sebor
2019-12-06 22:31   ` Jeff Law
2019-12-09  8:10     ` Richard Biener
2019-12-11 20:11       ` David Malcolm

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).