From 23311be097d258e576bd9d678d2e1c98476861a5 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Thu, 20 Jun 2019 06:21:20 -0400
Subject: [PATCH 29/47] FIXME: better paths (showing pertinent conditionals)

---
 gcc/analyzer/impl-2.cc                             |   3 +-
 gcc/analyzer/impl-3.cc                             |   3 +-
 gcc/analyzer/impl-4.cc                             |  85 +++++++---
 gcc/analyzer/region-model.h                        |   5 +-
 gcc/analyzer/supergraph.cc                         |  18 ++-
 gcc/analyzer/supergraph.h                          |  12 +-
 gcc/common.opt                                     |   6 +-
 .../NOT_FOR_UPSTREAM-CVE-2005-1689-krb5-recvauth.c | 117 +++++++++++++-
 .../gcc.dg/analyzer/analyzer-verbosity-0.c         | 131 +++++++++++++++
 .../gcc.dg/analyzer/analyzer-verbosity-1.c         | 158 ++++++++++++++++++
 .../gcc.dg/analyzer/analyzer-verbosity-2.c         | 179 +++++++++++++++++++++
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-11.c      |  26 ++-
 .../gcc.dg/analyzer/malloc-ipa-8-double-free.c     |  64 +++++---
 gcc/testsuite/gcc.dg/analyzer/malloc-paths-4.c     |   5 +-
 gcc/testsuite/gcc.dg/analyzer/malloc-paths-5.c     |   9 +-
 gcc/testsuite/gcc.dg/analyzer/malloc-paths-7.c     |   4 +-
 gcc/testsuite/gcc.dg/analyzer/taint-1.c            |   5 +-
 notes.txt                                          |  21 +++
 18 files changed, 772 insertions(+), 79 deletions(-)
 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

diff --git a/gcc/analyzer/impl-2.cc b/gcc/analyzer/impl-2.cc
index 4db6701..5a581c7 100644
--- a/gcc/analyzer/impl-2.cc
+++ b/gcc/analyzer/impl-2.cc
@@ -762,7 +762,8 @@ public:
 	gcc_unreachable ();
       case SUPEREDGE_CFG_EDGE:
 	{
-	  char *edge_desc = m_edge->get_description ();
+	  bool user_facing = !flag_analyzer_verbose_edges;
+	  char *edge_desc = m_edge->get_description (user_facing);
 	  if (strlen (edge_desc) > 0)
 	    {
 	      label_text result
diff --git a/gcc/analyzer/impl-3.cc b/gcc/analyzer/impl-3.cc
index a5e79bc..b1a71e7 100644
--- a/gcc/analyzer/impl-3.cc
+++ b/gcc/analyzer/impl-3.cc
@@ -410,7 +410,8 @@ public:
 	gcc_unreachable ();
       case SUPEREDGE_CFG_EDGE:
 	{
-	  char *edge_desc = m_edge->get_description ();
+	  bool user_facing = !flag_analyzer_verbose_edges;
+	  char *edge_desc = m_edge->get_description (user_facing);
 	  if (strlen (edge_desc) > 0)
 	    {
 	      label_text result
diff --git a/gcc/analyzer/impl-4.cc b/gcc/analyzer/impl-4.cc
index 2de9ff7..4e2da12 100644
--- a/gcc/analyzer/impl-4.cc
+++ b/gcc/analyzer/impl-4.cc
@@ -1448,23 +1448,43 @@ public:
 	gcc_unreachable ();
       case SUPEREDGE_CFG_EDGE:
 	{
-	  char *edge_desc = m_edge->get_description ();
-	  if (strlen (edge_desc) > 0)
+	  bool user_facing = !flag_analyzer_verbose_edges;
+	  char *edge_desc = m_edge->get_description (user_facing);
+	  if (user_facing)
 	    {
-	      label_text result
-		= label_text::take (print ("taking %qs edge SN:%i -> SN:%i",
-					   edge_desc,
-					   m_edge->m_src->m_index,
-					   m_edge->m_dest->m_index));
-	      free (edge_desc);
-	      return result;
+	      if (edge_desc && strlen (edge_desc) > 0)
+		{
+		  label_text result
+		    = label_text::take (print ("following %qs branch",
+					       edge_desc));
+		  free (edge_desc);
+		  return result;
+		}
+	      else
+		{
+		  free (edge_desc);
+		  return label_text::borrow (NULL);
+		}
 	    }
 	  else
 	    {
-	      free (edge_desc);
-	      return label_text::take (print ("taking edge SN:%i -> SN:%i",
-					      m_edge->m_src->m_index,
-					      m_edge->m_dest->m_index));
+	      if (strlen (edge_desc) > 0)
+		{
+		  label_text result
+		    = label_text::take (print ("taking %qs edge SN:%i -> SN:%i",
+					       edge_desc,
+					       m_edge->m_src->m_index,
+					       m_edge->m_dest->m_index));
+		  free (edge_desc);
+		  return result;
+		}
+	      else
+		{
+		  free (edge_desc);
+		  return label_text::take (print ("taking edge SN:%i -> SN:%i",
+						  m_edge->m_src->m_index,
+						  m_edge->m_dest->m_index));
+		}
 	    }
 	}
       case SUPEREDGE_CALL:
@@ -1515,6 +1535,33 @@ public:
     return m_edge->m_kind == SUPEREDGE_RETURN;
   }
 
+  bool should_filter_p (int verbosity) const
+  {
+    switch (m_edge->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 ();
+	      if (desc.m_buffer == NULL)
+		return true;
+	      desc.maybe_free ();
+	    }
+	}
+	break;
+
+	default:
+	  break;
+      }
+    return false;
+  }
+
   const superedge *m_edge;
 };
 
@@ -3535,7 +3582,7 @@ exploded_edge::dump_dot (graphviz_out *gv) const
 	     style, color, weight, constraint);
 
   if (m_sedge)
-    m_sedge->dump_label_to_pp (pp);
+    m_sedge->dump_label_to_pp (pp, false);
 
   pp_printf (pp, "\"];\n");
 }
@@ -4239,7 +4286,7 @@ diagnostic_manager::prune_path (checker4_path *path,
 	  gcc_unreachable ();
 
 	case EK_DEBUG:
-	  if (m_verbosity < 2)
+	  if (m_verbosity < 3)
 	    {
 	      log ("filtering event %i: debug event", idx);
 	      path->delete_event (idx);
@@ -4258,7 +4305,7 @@ diagnostic_manager::prune_path (checker4_path *path,
 		     idx, var, new_var);
 		var = new_var;
 	      }
-	    if (m_verbosity < 2)
+	    if (m_verbosity < 3)
 	      {
 		log ("filtering event %i: statement event", idx);
 		path->delete_event (idx);
@@ -4287,7 +4334,7 @@ diagnostic_manager::prune_path (checker4_path *path,
 		    var = state_change->m_origin;
 		  }
 	      }
-	    else if (m_verbosity < 2)
+	    else if (m_verbosity < 3)
 	      {
 		if (var)
 		  log ("filtering event %i:"
@@ -4342,8 +4389,8 @@ diagnostic_manager::prune_path (checker4_path *path,
 		     See if var can be in other states in the dest, but not
 		     in other states in the src?
 		     Must have multiple sibling edges.  */
-		  // TODO: for now, filter all intraprocedural edges:
-		  if (m_verbosity < 2)
+
+		  if (event->should_filter_p (m_verbosity))
 		    {
 		      log ("filtering event %i: CFG edge", idx);
 		      path->delete_event (idx);
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index 9498a87..961cf3b 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -967,8 +967,9 @@ public:
   {
   }
 
-  void on_inherited_svalue (svalue_id parent_sid,
-			    svalue_id child_sid) FINAL OVERRIDE
+  void on_inherited_svalue (svalue_id parent_sid ATTRIBUTE_UNUSED,
+			    svalue_id child_sid  ATTRIBUTE_UNUSED)
+    FINAL OVERRIDE
   {
   }
 
diff --git a/gcc/analyzer/supergraph.cc b/gcc/analyzer/supergraph.cc
index b7213ed..1733fe6 100644
--- a/gcc/analyzer/supergraph.cc
+++ b/gcc/analyzer/supergraph.cc
@@ -687,7 +687,7 @@ superedge::dump_dot (graphviz_out *gv,
 	     style, color, weight, constraint,
 	     m_src->m_index, m_dest->m_index);
 
-  dump_label_to_pp (gv->m_pp);
+  dump_label_to_pp (gv->m_pp, false);
 
   pp_printf (gv->m_pp, "\"];\n");
 }
@@ -725,10 +725,10 @@ superedge::get_callgraph_edge () const
 /* FIXME.  */
 
 char *
-superedge::get_description () const
+superedge::get_description (bool user_facing) const
 {
   pretty_printer pp;
-  dump_label_to_pp (&pp);
+  dump_label_to_pp (&pp, user_facing);
   return xstrdup (pp_formatted_text (&pp));
 }
 
@@ -745,13 +745,17 @@ superedge::to_str () const
 /* FIXME.  */
 
 void
-cfg_superedge::dump_label_to_pp (pretty_printer *pp) const
+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 ())
@@ -799,7 +803,8 @@ cfg_superedge::get_phi_arg (const gphi *phi) const
 /* FIXME.  */
 
 void
-switch_cfg_superedge::dump_label_to_pp (pretty_printer *pp) const
+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);
@@ -831,7 +836,8 @@ switch_cfg_superedge::get_case_label () const
 /* FIXME.  */
 
 void
-callgraph_superedge::dump_label_to_pp (pretty_printer *pp) const
+callgraph_superedge::dump_label_to_pp (pretty_printer *pp,
+				       bool user_facing ATTRIBUTE_UNUSED) const
 {
   /* FIXME.  */
   switch (m_kind)
diff --git a/gcc/analyzer/supergraph.h b/gcc/analyzer/supergraph.h
index 3b7bf70..d6e93aa 100644
--- a/gcc/analyzer/supergraph.h
+++ b/gcc/analyzer/supergraph.h
@@ -262,7 +262,8 @@ class superedge
 
   void dump_dot (graphviz_out *gv, enum supergraph_dot_flags flags) const;
 
-  virtual void dump_label_to_pp (pretty_printer *pp) const = 0;
+  virtual void dump_label_to_pp (pretty_printer *pp,
+				 bool user_facing) const = 0;
   virtual cfg_superedge *dyn_cast_cfg_superedge () { return NULL; }
   virtual const cfg_superedge *dyn_cast_cfg_superedge () const { return NULL; }
   virtual callgraph_superedge *dyn_cast_callgraph_superedge () { return NULL; }
@@ -276,7 +277,7 @@ class superedge
   cgraph_edge *get_any_callgraph_edge () const;
   cgraph_edge *get_callgraph_edge () const;
 
-  char *get_description () const;
+  char *get_description (bool user_facing) const;
 
   std::string to_str () const;
 
@@ -302,7 +303,7 @@ class callgraph_superedge : public superedge
     m_cedge (cedge)
   {}
 
-  void dump_label_to_pp (pretty_printer *pp) const OVERRIDE;
+  void dump_label_to_pp (pretty_printer *pp, bool user_facing) const OVERRIDE;
 
   function *get_callee_function () const;
   function *get_caller_function () const;
@@ -386,7 +387,7 @@ class cfg_superedge : public superedge
     m_cfg_edge (e)
   {}
 
-  void dump_label_to_pp (pretty_printer *pp) const OVERRIDE;
+  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; }
 
@@ -411,7 +412,8 @@ class switch_cfg_superedge : public cfg_superedge {
     m_idx (idx)
   {}
 
-  void dump_label_to_pp (pretty_printer *pp) const FINAL OVERRIDE;
+  void dump_label_to_pp (pretty_printer *pp, bool user_facing) const
+    FINAL OVERRIDE;
 
   gswitch *get_switch_stmt () const
   {
diff --git a/gcc/common.opt b/gcc/common.opt
index e38d5a9..ed12144 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1449,7 +1449,11 @@ fdump-analyzer-exploded-graph
 Common RejectNegative Var(flag_dump_analyzer_exploded_graph)
 
 fanalyzer-verbosity=
-Common Joined UInteger Var(analyzer_verbosity) Init(1)
+Common Joined UInteger Var(analyzer_verbosity) Init(2)
+FIXME
+
+fanalyzer-verbose-edges
+Common Var(flag_analyzer_verbose_edges) Init(0)
 FIXME
 
 fanalyzer-verbose-state-changes
diff --git a/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-CVE-2005-1689-krb5-recvauth.c b/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-CVE-2005-1689-krb5-recvauth.c
index c2a0dc8..ae1ba3a 100644
--- a/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-CVE-2005-1689-krb5-recvauth.c
+++ b/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-CVE-2005-1689-krb5-recvauth.c
@@ -1,8 +1,4 @@
-
-
-
-
-
+/* { dg-additional-options "-fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fdiagnostics-path-summary-format=inline-events" } */
 
 typedef unsigned char __u_char;
 typedef unsigned short int __u_short;
@@ -10670,7 +10666,7 @@ recvauth_common(krb5_context context,
   error.error = 60;
  message = error_message(problem);
  error.text.length = strlen(message) + 1;
- if (!(error.text.data = malloc(error.text.length))) {  /* { dg-bogus "analysis bailed out early" "" { xfail *-*-* } } */
+ if (!(error.text.data = malloc(error.text.length))) {
      retval = 
              12
                    ;
@@ -10698,7 +10694,7 @@ recvauth_common(krb5_context context,
      goto cleanup;
     }
     if (retval)
- goto cleanup;
+ goto cleanup; /* { dg-bogus "analysis bailed out early" "" { xfail *-*-* } } */
 
 
     if ((ap_option & 0x20000000)) {
@@ -10747,3 +10743,110 @@ krb5_recvauth_version(krb5_context context,
     return recvauth_common (context, auth_context, fd, 0,
        server, flags, keytab, ticket, version);
 }
+
+/* { dg-begin-multiline-output "" }
+  'krb5_recvauth': events 1-2
+         |
+         |   NN | krb5_recvauth(krb5_context context, krb5_auth_context *auth_context, krb5_pointer fd, char *appl_version, krb5_principal server, krb5_int32 flags, krb5_keytab keytab, krb5_ticket **ticket)
+         |      | ^~~~~~~~~~~~~
+         |      | |
+         |      | (1) entry to 'krb5_recvauth'
+         |   NN | {
+         |   NN |     return recvauth_common (context, auth_context, fd, appl_version,
+         |      |            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+         |      |            |
+         |      |            (2) calling 'recvauth_common' from 'krb5_recvauth'
+         |   NN |        server, flags, keytab, ticket, 0);
+         |      |        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+         |
+         +--> 'recvauth_common': events 3-20
+                      |
+                      |   NN | recvauth_common(krb5_context context,
+                      |      | ^~~~~~~~~~~~~~~
+                      |      | |
+                      |      | (3) entry to 'recvauth_common'
+                      |......
+                      |   NN |  if (!(flags & 0x0001)) {
+                      |      |     ~
+                      |      |     |
+                      |      |     (4) following 'false' branch
+                      |......
+                      |   NN |  if (flags & 0x0002)
+                      |      |     ~
+                      |      |     |
+                      |      |     (5) following 'false' branch
+                      |......
+                      |   NN |  if ((retval = krb5_read_message(context, fd, &inbuf)))
+                      |      |     ~
+                      |      |     |
+                      |      |     (6) following 'false' branch
+                      |   NN |   return(retval);
+                      |   NN |  if (appl_version &&
+                      |      |     ~
+                      |      |     |
+                      |      |     (7) following 'false' branch
+                      |......
+                      |   NN |  if (version && !problem)
+                      |      |     ~
+                      |      |     |
+                      |      |     (8) following 'false' branch
+                      |......
+                      |   NN |      free((char *)(inbuf.data));
+                      |      |      ~~~~~~~~~~~~~~~~~~~~~~~~~~
+                      |      |      |
+                      |      |      (9) first 'free' here
+                      |......
+                      |   NN |  switch (problem) {
+                      |      |  ~~~~~~
+                      |      |  |
+                      |      |  (10) following 'case 0:' branch
+                      |......
+                      |   NN |  if ((krb5_net_write(context, *((int *)fd), (char *)&response, 1)) < 0) {
+                      |      |     ~
+                      |      |     |
+                      |      |     (11) following 'false' branch
+                      |......
+                      |   NN |  if (problem)
+                      |      |     ~
+                      |      |     |
+                      |      |     (12) following 'false' branch
+                      |......
+                      |   NN |     if ((retval = krb5_read_message(context, fd, &inbuf)))
+                      |      |        ~
+                      |      |        |
+                      |      |        (13) following 'false' branch
+                      |......
+                      |   NN |     if (*auth_context ==
+                      |      |        ~
+                      |      |        |
+                      |      |        (14) following 'false' branch
+                      |......
+                      |   NN |     if ((!problem) && rcache ==
+                      |      |        ~~~~~~~~~~~~~~~~~~~~~~~~
+                      |      |        |           |
+                      |      |        |           (16) following 'true' branch
+                      |      |        (15) following 'true' branch
+                      |   NN |                                ((void *)0)
+                      |      |                                ~~~~~~~~~~~
+                      |......
+                      |   NN |         if (server) {
+                      |      |            ~
+                      |      |            |
+                      |      |            (17) following 'false' branch
+                      |......
+                      |   NN |         if (!problem)
+                      |      |            ~
+                      |      |            |
+                      |      |            (18) following 'true' branch
+                      |......
+                      |   NN |     if (!problem) {
+                      |      |        ~
+                      |      |        |
+                      |      |        (19) following 'true' branch
+                      |......
+                      |   NN |  free((char *)(inbuf.data));
+                      |      |  ~~~~~~~~~~~~~~~~~~~~~~~~~~
+                      |      |  |
+                      |      |  (20) second 'free' here
+                      |
+  { dg-end-multiline-output "" } */
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..14478cf
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-0.c
@@ -0,0 +1,131 @@
+/* { dg-additional-options "-fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fdiagnostics-path-summary-format=inline-events -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 "" }
+  '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) calling 'calls_free_1' from 'test_1'
+      |
+      +--> 'calls_free_1': event 5
+                  |
+                  |   NN |   free (ptr);
+                  |      |   ^~~~~~~~~~
+                  |      |   |
+                  |      |   (5) second 'free' here
+                  |
+  { 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 "" }
+  '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) calling 'calls_free_2' from 'test_2'
+      |
+      +--> 'calls_free_2': event 5
+                  |
+                  |   NN |   free (ptr);
+                  |      |   ^~~~~~~~~~
+                  |      |   |
+                  |      |   (5) second 'free' here
+                  |
+  { dg-end-multiline-output "" } */
+
+// TODO: range cases
+
+// FIXME: show the inline view even when it's not interprocedural?
+
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..6551588
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-1.c
@@ -0,0 +1,158 @@
+/* { dg-additional-options "-fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fdiagnostics-path-summary-format=inline-events -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 "" }
+  '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) calling '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
+                  |
+  { 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 "" }
+  '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) calling '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
+                  |
+  { dg-end-multiline-output "" } */
+
+// FIXME: show the inline view even when it's not interprocedural?
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..3a10497
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-2.c
@@ -0,0 +1,179 @@
+/* { dg-additional-options "-fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fdiagnostics-path-summary-format=inline-events -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 "" }
+  'test_1': events 1-3
+      |
+      |   NN | void test_1 (void *ptr, int a, int b)
+      |      |      ^~~~~~
+      |      |      |
+      |      |      (1) entry to 'test_1'
+      |   NN | {
+      |   NN |   if (a)
+      |      |      ~
+      |      |      |
+      |      |      (2) following 'true' branch
+      |   NN |     calls_free_1 (ptr);
+      |      |     ~~~~~~~~~~~~~~~~~~
+      |      |     |
+      |      |     (3) calling 'calls_free_1' from 'test_1'
+      |
+      +--> 'calls_free_1': events 4-5
+                  |
+                  |   NN | void calls_free_1 (void *ptr)
+                  |      |      ^~~~~~~~~~~~
+                  |      |      |
+                  |      |      (4) entry to 'calls_free_1'
+                  |   NN | {
+                  |   NN |   free (ptr);
+                  |      |   ~~~~~~~~~~
+                  |      |   |
+                  |      |   (5) first 'free' here
+                  |
+      <-----------+
+      |
+  'test_1': events 6-8
+      |
+      |   NN |     calls_free_1 (ptr);
+      |      |     ^~~~~~~~~~~~~~~~~~
+      |      |     |
+      |      |     (6) returning to 'test_1' from 'calls_free_1'
+      |   NN | 
+      |   NN |   if (b)
+      |      |      ~
+      |      |      |
+      |      |      (7) following 'false' branch
+      |......
+      |   NN |     calls_free_1 (ptr);
+      |      |     ~~~~~~~~~~~~~~~~~~
+      |      |     |
+      |      |     (8) calling 'calls_free_1' from 'test_1'
+      |
+      +--> 'calls_free_1': events 9-10
+                  |
+                  |   NN | void calls_free_1 (void *ptr)
+                  |      |      ^~~~~~~~~~~~
+                  |      |      |
+                  |      |      (9) entry to 'calls_free_1'
+                  |   NN | {
+                  |   NN |   free (ptr);
+                  |      |   ~~~~~~~~~~
+                  |      |   |
+                  |      |   (10) second 'free' here
+                  |
+  { 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 "" }
+  'test_2': events 1-3
+      |
+      |   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 |       calls_free_2 (ptr);
+      |      |       ~~~~~~~~~~~~~~~~~~
+      |      |       |
+      |      |       (3) calling 'calls_free_2' from 'test_2'
+      |
+      +--> 'calls_free_2': events 4-5
+                  |
+                  |   NN | void calls_free_2 (void *ptr)
+                  |      |      ^~~~~~~~~~~~
+                  |      |      |
+                  |      |      (4) entry to 'calls_free_2'
+                  |   NN | {
+                  |   NN |   free (ptr);
+                  |      |   ~~~~~~~~~~
+                  |      |   |
+                  |      |   (5) first 'free' here
+                  |
+      <-----------+
+      |
+  'test_2': events 6-8
+      |
+      |   NN |       calls_free_2 (ptr);
+      |      |       ^~~~~~~~~~~~~~~~~~
+      |      |       |
+      |      |       (6) returning to 'test_2' from 'calls_free_2'
+      |......
+      |   NN |   switch (b)
+      |      |   ~~~~~~
+      |      |   |
+      |      |   (7) following 'default:' branch
+      |......
+      |   NN |       calls_free_2 (ptr);
+      |      |       ~~~~~~~~~~~~~~~~~~
+      |      |       |
+      |      |       (8) calling 'calls_free_2' from 'test_2'
+      |
+      +--> 'calls_free_2': events 9-10
+                  |
+                  |   NN | void calls_free_2 (void *ptr)
+                  |      |      ^~~~~~~~~~~~
+                  |      |      |
+                  |      |      (9) entry to 'calls_free_2'
+                  |   NN | {
+                  |   NN |   free (ptr);
+                  |      |   ~~~~~~~~~~
+                  |      |   |
+                  |      |   (10) second 'free' here
+                  |
+  { dg-end-multiline-output "" } */
+
+// TODO: range cases
+
+// FIXME: show the inline view even when it's not interprocedural?
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-11.c b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-11.c
index 3bdd662..835d3b5 100644
--- a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-11.c
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-11.c
@@ -42,42 +42,52 @@ void test (void *ptr)
      |      |   |
      |      |   (2) calling 'may_call_free' from 'test'
      |
-     +--> 'may_call_free': events 3-4
+     +--> 'may_call_free': events 3-5
                  |
                  |   NN | may_call_free (void *victim)
                  |      | ^~~~~~~~~~~~~
                  |      | |
                  |      | (3) entry to 'may_call_free'
+                 |   NN | {
+                 |   NN |   if (some_condition ())
+                 |      |      ~
+                 |      |      |
+                 |      |      (4) following 'false' branch
                  |......
                  |   NN |   free (victim);
                  |      |   ~~~~~~~~~~~~~
                  |      |   |
-                 |      |   (4) first 'free' here
+                 |      |   (5) first 'free' here
                  |
      <-----------+
      |
-  'test': events 5-6
+  'test': events 6-7
      |
      |   NN |   may_call_free (ptr);
      |      |   ^~~~~~~~~~~~~~~~~~~
      |      |   |
-     |      |   (5) returning to 'test' from 'may_call_free'
+     |      |   (6) returning to 'test' from 'may_call_free'
      |......
      |   NN |   may_call_free (ptr);
      |      |   ~~~~~~~~~~~~~~~~~~~
      |      |   |
-     |      |   (6) calling 'may_call_free' from 'test'
+     |      |   (7) calling 'may_call_free' from 'test'
      |
-     +--> 'may_call_free': events 7-8
+     +--> 'may_call_free': events 8-10
                  |
                  |   NN | may_call_free (void *victim)
                  |      | ^~~~~~~~~~~~~
                  |      | |
-                 |      | (7) entry to 'may_call_free'
+                 |      | (8) entry to 'may_call_free'
+                 |   NN | {
+                 |   NN |   if (some_condition ())
+                 |      |      ~
+                 |      |      |
+                 |      |      (9) following 'false' branch
                  |......
                  |   NN |   free (victim);
                  |      |   ~~~~~~~~~~~~~
                  |      |   |
-                 |      |   (8) second 'free' here
+                 |      |   (10) second 'free' here
                  |
   { dg-end-multiline-output "" } */
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
index 7c27998..b88dd5d 100644
--- a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-double-free.c
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-double-free.c
@@ -52,81 +52,107 @@ 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-4
+                  |
+                  |   NN | make_boxed_int (int i)
+                  |      | ^~~~~~~~~~~~~~
+                  |      | |
+                  |      | (3) entry to 'make_boxed_int'
+                  |......
+                  |   NN |   if (!result)
+                  |      |      ~
+                  |      |      |
+                  |      |      (4) following 'false' branch
+                  |
+     <------------+
+     |
+  'test': events 5-6
+     |
+     |   NN |   boxed_int *obj = make_boxed_int (i);
+     |      |                    ^~~~~~~~~~~~~~~~~~
+     |      |                    |
+     |      |                    (5) returning to 'test' from 'make_boxed_int'
+     |   NN | 
      |   NN |   free_boxed_int (obj);
      |      |   ~~~~~~~~~~~~~~~~~~~~
      |      |   |
-     |      |   (2) calling 'free_boxed_int' from 'test'
+     |      |   (6) calling 'free_boxed_int' from 'test'
      |
-     +--> 'free_boxed_int': events 3-4
+     +--> 'free_boxed_int': events 7-8
                   |
                   |   NN | free_boxed_int (boxed_int *bi)
                   |      | ^~~~~~~~~~~~~~
                   |      | |
-                  |      | (3) entry to 'free_boxed_int'
+                  |      | (7) entry to 'free_boxed_int'
                   |   NN | {
                   |   NN |   wrapped_free (bi);
                   |      |   ~~~~~~~~~~~~~~~~~
                   |      |   |
-                  |      |   (4) calling 'wrapped_free' from 'free_boxed_int'
+                  |      |   (8) calling 'wrapped_free' from 'free_boxed_int'
                   |
-                  +--> 'wrapped_free': events 5-6
+                  +--> 'wrapped_free': events 9-10
                               |
                               |   NN | void wrapped_free (void *ptr)
                               |      |      ^~~~~~~~~~~~
                               |      |      |
-                              |      |      (5) entry to 'wrapped_free'
+                              |      |      (9) entry to 'wrapped_free'
                               |   NN | {
                               |   NN |   free (ptr);
                               |      |   ~~~~~~~~~~
                               |      |   |
-                              |      |   (6) first 'free' here (state of 'ptr': 'nonnull' -> 'free', origin: NULL)
+                              |      |   (10) first 'free' here (state of 'ptr': 'nonnull' -> 'free', origin: NULL)
                               |
                   <-----------+
                   |
-          'free_boxed_int': event 7
+          'free_boxed_int': event 11
                   |
                   |   NN |   wrapped_free (bi);
                   |      |   ^~~~~~~~~~~~~~~~~
                   |      |   |
-                  |      |   (7) returning to 'free_boxed_int' from 'wrapped_free'
+                  |      |   (11) returning to 'free_boxed_int' from 'wrapped_free'
                   |
      <------------+
      |
-  'test': events 8-9
+  'test': events 12-13
      |
      |   NN |   free_boxed_int (obj);
      |      |   ^~~~~~~~~~~~~~~~~~~~
      |      |   |
-     |      |   (8) returning to 'test' from 'free_boxed_int'
+     |      |   (12) returning to 'test' from 'free_boxed_int'
      |   NN | 
      |   NN |   free_boxed_int (obj);
      |      |   ~~~~~~~~~~~~~~~~~~~~
      |      |   |
-     |      |   (9) calling 'free_boxed_int' from 'test'
+     |      |   (13) calling 'free_boxed_int' from 'test'
      |
-     +--> 'free_boxed_int': events 10-11
+     +--> 'free_boxed_int': events 14-15
                   |
                   |   NN | free_boxed_int (boxed_int *bi)
                   |      | ^~~~~~~~~~~~~~
                   |      | |
-                  |      | (10) entry to 'free_boxed_int'
+                  |      | (14) entry to 'free_boxed_int'
                   |   NN | {
                   |   NN |   wrapped_free (bi);
                   |      |   ~~~~~~~~~~~~~~~~~
                   |      |   |
-                  |      |   (11) calling 'wrapped_free' from 'free_boxed_int'
+                  |      |   (15) calling 'wrapped_free' from 'free_boxed_int'
                   |
-                  +--> 'wrapped_free': events 12-13
+                  +--> 'wrapped_free': events 16-17
                               |
                               |   NN | void wrapped_free (void *ptr)
                               |      |      ^~~~~~~~~~~~
                               |      |      |
-                              |      |      (12) entry to 'wrapped_free'
+                              |      |      (16) entry to 'wrapped_free'
                               |   NN | {
                               |   NN |   free (ptr);
                               |      |   ~~~~~~~~~~
                               |      |   |
-                              |      |   (13) second 'free' here ('ptr' is in state 'free')
+                              |      |   (17) second 'free' here ('ptr' is in state 'free')
                               |
    { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-paths-4.c b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-4.c
index 02de853..1efea6e 100644
--- a/gcc/testsuite/gcc.dg/analyzer/malloc-paths-4.c
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-4.c
@@ -13,6 +13,7 @@ int *test_4 (void)
 
   /* { dg-warning "use of NULL 'ptr'" "" { target *-*-* } on_null_ptr } */
   /* { dg-message "\\(1\\) state of 'ptr': 'start' -> 'unchecked'" "" { target *-*-* } malloc } */
-  /* { dg-message "\\(2\\) state of 'ptr': 'unchecked' -> 'null'" "" { target *-*-* } cond } */
-  /* { dg-message "\\(3\\) here \\('ptr' is in state 'null'\\)" "" { target *-*-* } on_null_ptr } */
+  /* { dg-message "\\(2\\) following 'false' branch" "" { target *-*-* } cond } */
+  /* { dg-message "\\(3\\) state of 'ptr': 'unchecked' -> 'null'" "" { target *-*-* } cond } */
+  /* { dg-message "\\(4\\) here \\('ptr' is in state 'null'\\)" "" { 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
index 7fdc8a1..76f7d7f 100644
--- a/gcc/testsuite/gcc.dg/analyzer/malloc-paths-5.c
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-5.c
@@ -31,10 +31,11 @@ int test (const char *filename, int flag)
 
   /* { dg-warning "double-free of 'p'" "" { target *-*-* } second_free_of_p } */
   /* { dg-message "\\(1\\) state of 'p': 'start' -> 'unchecked'" "" { target *-*-* } malloc_of_p } */
-  /* { dg-message "\\(2\\) state of 'p': 'unchecked' -> 'nonnull'" "" { target *-*-* } test_of_p } */
-  /* { dg-message "\\(3\\) first 'free' here" "" { target *-*-* } first_free_of_p } */
-  /* { dg-message "\\(4\\) second 'free' here" "" { target *-*-* } second_free_of_p } */
+  /* { dg-message "\\(2\\) following 'false' branch" "" { target *-*-* } test_of_p } */
+  /* { dg-message "\\(3\\) state of 'p': 'unchecked' -> 'nonnull'" "" { target *-*-* } test_of_p } */
+  /* { dg-message "\\(4\\) following 'true' branch" "" { target *-*-* } test_of_q } */
+  /* { dg-message "\\(5\\) first 'free' here" "" { target *-*-* } first_free_of_p } */
+  /* { dg-message "\\(6\\) second 'free' here" "" { target *-*-* } second_free_of_p } */
 
   /* We don't care about the state changes to q.  */
-  /* TODO: we do care about the test of q.  */
 }
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-paths-7.c b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-7.c
index 82f54c1..01d9cf9 100644
--- a/gcc/testsuite/gcc.dg/analyzer/malloc-paths-7.c
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-7.c
@@ -9,11 +9,11 @@ void test (void)
   void *q = malloc (1024);
 
   foo ();
-  if (!q)
+  if (!q) /* { dg-message "\\(2\\) following 'true' branch" } */ 
     {
       free (q);
       return; /* { dg-warning "leak of 'p'" } */ 
-      /* { dg-message "\\(2\\) 'p' leaks here" "" { target *-*-* } .-1 } */
+      /* { dg-message "\\(3\\) 'p' leaks here" "" { target *-*-* } .-1 } */
     }
   bar ();
   free (q);
diff --git a/gcc/testsuite/gcc.dg/analyzer/taint-1.c b/gcc/testsuite/gcc.dg/analyzer/taint-1.c
index c23a337..22f01dc 100644
--- a/gcc/testsuite/gcc.dg/analyzer/taint-1.c
+++ b/gcc/testsuite/gcc.dg/analyzer/taint-1.c
@@ -13,11 +13,12 @@ char test_1(FILE *f)
   struct foo tmp;
 
   if (1 == fread(&tmp, sizeof(tmp), 1, f)) { /* { dg-message "\\(1\\) state of 'tmp': 'start' -> 'tainted'" } */
+                                             /* { 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: \\(2\\) state of 'tmp.i': 'start' -> 'tainted'" "" { target *-*-* } .-1 } */
-    /* { dg-message "\\(3\\) here \\('tmp.i' is in state 'tainted'\\)" "" { target *-*-* } .-2 } */
+    /* { dg-message "23: \\(3\\) state of 'tmp.i': 'start' -> 'tainted'" "" { target *-*-* } .-1 } */
+    /* { dg-message "\\(4\\) here \\('tmp.i' is in state 'tainted'\\)" "" { target *-*-* } .-2 } */
     
     // TOOD: better messages for state changes
   }
diff --git a/notes.txt b/notes.txt
index efa3017..39f0b0a 100644
--- a/notes.txt
+++ b/notes.txt
@@ -10,6 +10,11 @@ Paths ought to be:
 * feasible
 ...but it's not clear to what extent we can enforce this.
 
+We collate warnings and only emit them for 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).
+
 
 Useful debugging tips
 =====================
@@ -32,3 +37,19 @@ User-facing Docs (putting them here to avoid the tedious texinfo rebuild times):
 -fanalyzer
 Enables a more detailed, but much more time-consuming analysis pass.
 
+-fanalyzer-verbosity=0
+  Only show interprocedural calls and the most critical state-change
+  events.
+
+-fanalyzer-verbosity=1
+  Show interprocedural calls and entrypoints, and the most critical
+  state-change events.
+
+-fanalyzer-verbosity=2 (the default)
+  Show interprocedural calls and entrypoints, branches taken, and the
+  most critical state-change events.
+
+-fanalyzer-verbosity=3
+  Show all events, including statements, and state-change events that
+  are unrelated to the diagnostic in question.  This is more intended
+  for debugging the analyzer itself.
-- 
1.8.5.3

