From 71cb301e2489ce9210ff7af14e01121a87ab7e3f Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Wed, 26 Jun 2019 10:27:23 -0400
Subject: [PATCH 047/108] FIXME: WIP on enode merging

---
 gcc/analyzer/impl-4.cc                             | 496 ++++++++++++++++++++-
 gcc/common.opt                                     |  10 +-
 gcc/fibonacci_heap.h                               |   6 +-
 gcc/params.def                                     |   5 +
 .../NOT_FOR_UPSTREAM-CVE-2005-1689-krb5-recvauth.c |   4 +-
 .../NOT_FOR_UPSTREAM-zlib-1.1.3-infblock.c         |   4 +-
 gcc/testsuite/gcc.dg/analyzer/fibonacci.c          |   6 +-
 .../gcc.dg/analyzer/malloc-many-paths-2.c          |   5 +-
 gcc/testsuite/gcc.dg/analyzer/paths-1.c            |  18 +
 gcc/testsuite/gcc.dg/analyzer/paths-2.c            |  23 +
 gcc/testsuite/gcc.dg/analyzer/zlib-1.c             |  28 +-
 11 files changed, 575 insertions(+), 30 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/paths-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/paths-2.c

diff --git a/gcc/analyzer/impl-4.cc b/gcc/analyzer/impl-4.cc
index b272f4a..7ad0516 100644
--- a/gcc/analyzer/impl-4.cc
+++ b/gcc/analyzer/impl-4.cc
@@ -53,6 +53,8 @@
 #include "tree-diagnostic.h"
 #include "constraint-manager.h"
 #include "pending-diagnostic.h"
+#include "fibonacci_heap.h"
+#include "params.h"
 
 /* Overview:
 
@@ -737,6 +739,8 @@ public:
     return true;
   }
 
+  bool can_merge_with_p (const program_state &other) const;
+
   void validate (const extrinsic_state &ext_state) const;
 
   //private:
@@ -1006,6 +1010,102 @@ private:
   const int m_verbosity;
 };
 
+/* 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;
+};
+
+/* FIXME.  */
+// FIXME: currently just a stack
+// FIXME: becoming a priority queue, backed by a fibonacci_heap
+
+class worklist
+{
+public:
+  worklist (const exploded_graph &eg);
+  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 (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;
+
+  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.  */
@@ -1065,7 +1165,7 @@ private:
 		    eg_hash_map_traits> map_t;
   map_t m_point_and_state_to_node;
 
-  auto_vec<exploded_node *> m_worklist;
+  worklist m_worklist;
 
   exploded_node *m_origin;
 
@@ -3145,6 +3245,15 @@ program_state::get_representative_tree (svalue_id sid) const
 
 /* FIXME.  */
 
+bool
+program_state::can_merge_with_p (const program_state &other) const
+{
+  // TODO: are the states similar enough?
+  return false;
+}
+
+/* FIXME.  */
+
 void
 program_state::validate (const extrinsic_state &ext_state) const
 {
@@ -4645,13 +4754,264 @@ diagnostic_manager::prune_path (checker4_path *path,
 
 ////////////////////////////////////////////////////////////////////////////
 
+/* strongly_connected_components's ctor.  Tarjan's SCC algorithm.  */
+
+strongly_connected_components::
+strongly_connected_components (const supergraph &sg, logger *logger)
+: m_sg (sg)
+{
+  JIT_LOG_SCOPE (logger);
+  auto_client_timevar tv ("computing scc");
+
+  for (int i = 0; i < m_sg.num_nodes (); i++)
+    m_per_node.safe_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 ();
+}
+
+/* FIXME.  */
+
+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);
+    }
+}
+
+/* FIXME.  */
+
+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 = std::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 = std::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)
+    {
+      /* FIXME: Start a new strongly connected component.  */
+      per_node_data *w;
+      do {
+	int idx = m_stack.pop ();
+	w = &m_per_node[idx];
+	w->m_on_stack = false;
+	/* FIXME: add w to current strongly connected component.  */
+      } while (w != v);
+      /* FIXME: output the current strongly connected component.  */
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* worklist's ctor.  */
+
+worklist::worklist (const exploded_graph &eg)
+: m_eg (eg),
+  m_scc (eg.get_supergraph (), eg.get_logger ()),
+  m_queue (key_t (*this, NULL))
+{
+}
+
+/* FIXME.  */
+
+unsigned
+worklist::length () const
+{
+  return m_queue.nodes ();
+}
+
+/* FIXME.  */
+
+exploded_node *
+worklist::take_next ()
+{
+  return m_queue.extract_min ();
+}
+
+/* FIXME.  */
+
+exploded_node *
+worklist::peek_next ()
+{
+  return m_queue.min ();
+}
+
+/* FIXME.  */
+
+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 (const worklist::key_t &ka, const worklist::key_t &kb)
+{
+  /* 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;
+
+  /* Order within supernode via program point.  */
+  const program_point &point_a = ka.m_enode->get_point ();
+  const program_point &point_b = kb.m_enode->get_point ();
+  switch (point_a.m_kind)
+    {
+    default:
+      gcc_unreachable ();
+    case PK_BEFORE_SUPERNODE:
+      switch (point_b.m_kind)
+	{
+	default:
+	  gcc_unreachable ();
+	case PK_BEFORE_SUPERNODE:
+	  return 0;
+
+	case PK_BEFORE_STMT:
+	case PK_AFTER_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_STMT:
+	  if (point_a.m_stmt_idx != point_b.m_stmt_idx)
+	    return point_a.m_stmt_idx - point_b.m_stmt_idx;
+	  else
+	    return -1;
+
+	case PK_AFTER_SUPERNODE:
+	  return 1;
+	}
+      break;
+    case PK_AFTER_STMT:
+      switch (point_b.m_kind)
+	{
+	default:
+	  gcc_unreachable ();
+	case PK_BEFORE_SUPERNODE:
+	  return 1;
+
+	case PK_BEFORE_STMT:
+	  if (point_a.m_stmt_idx != point_b.m_stmt_idx)
+	    return point_a.m_stmt_idx - point_b.m_stmt_idx;
+	  else
+	    return 1;
+
+	case PK_AFTER_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:
+	case PK_AFTER_STMT:
+	  return 1;
+
+	case PK_AFTER_SUPERNODE:
+	  return 0;
+	}
+      break;
+    }
+
+
+  return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
 /* exploded_graph's ctor.  */
 
 exploded_graph::exploded_graph (const supergraph &sg, logger *logger,
 				const extrinsic_state &ext_state,
 				const state_purge_map *purge_map,
 				int verbosity)
-: log_user (logger), m_sg (sg), m_ext_state (ext_state),
+: log_user (logger), m_sg (sg),
+  m_worklist (*this),
+  m_ext_state (ext_state),
   m_purge_map (purge_map),
   m_diagnostic_manager (logger, verbosity),
   m_global_stats (m_sg.num_nodes ()),
@@ -4764,7 +5124,7 @@ exploded_graph::get_or_create_node (const program_point &point,
     }
 
   /* Add the new node to the worlist.  */
-  m_worklist.safe_push (node);
+  m_worklist.add_node (node);
   return node;
 }
 
@@ -4781,7 +5141,7 @@ exploded_graph::add_edge (exploded_node *src, exploded_node *dest,
 }
 
 /* The main loop of the analysis.
-   Pop freshly-created exploded_nodes from the worklist, calling
+   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).  */
@@ -4794,16 +5154,65 @@ exploded_graph::process_worklist ()
 
   while (m_worklist.length () > 0)
     {
-      exploded_node *node = m_worklist.pop ();
+      exploded_node *node = m_worklist.take_next ();
+      gcc_assert (node->m_succs.length () == 0
+		  || node == m_origin);
+      // FIXME: avoid the "origin" specialcase?
+
+      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);
+
+	    log ("peek worklist: EN: %i", node_2->m_index);
+
+	    if (node->get_point () == node_2->get_point ())
+	      {
+		if (get_logger ())
+		  {
+		    format f (false);
+		    log ("got potential merge EN: %i and EN: %i at %s",
+			 node->m_index, node_2->m_index,
+			 node->get_point ().to_str (f).c_str ());
+		  }
+
+		if (node->get_state ().can_merge_with_p (node_2->get_state ()))
+		  {
+		    log ("would merge here");
+		    // TODO
+		  }
+
+		// TODO: are the states similar enough?
+		// TODO: attempt the merger
+		/* 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);
 
-      /* FIXME: make this less ad-hoc?  e.g. a param */
-      /* we use exit rather than entry since there can be multiple
+      /* 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.  */
       // FIXME: do per function
-      if (m_global_stats.m_num_nodes[PK_AFTER_SUPERNODE]> m_sg.num_nodes () * 5)
+      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 (0, "analysis bailed out early (%i nodes)",
@@ -6110,6 +6519,66 @@ private:
   exploded_node *m_enode;
 };
 
+/* FIXME.  */
+
+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);
+    }
+}
+
+
 /* Handle __analyzer_dump_exploded_nodes.  */
 
 void
@@ -6182,7 +6651,6 @@ exploded_graph::dump_exploded_nodes () const
 		continue;
 
 	      /* This is O(N^2).  */
-	      pretty_printer pp;
 	      unsigned j;
 	      auto_vec<exploded_node *> enodes;
 	      dnode *other_node;
@@ -6192,14 +6660,12 @@ exploded_graph::dump_exploded_nodes () const
 		  if (other_enode->get_point ().m_kind != PK_BEFORE_STMT)
 		    continue;
 		  if (other_enode->get_stmt () == stmt)
-		    {
-		      if (enodes.length () > 0)
-			pp_string (&pp, ", ");
-		      pp_printf (&pp, "EN: %i", other_enode->m_index);
-		      enodes.safe_push (other_enode);
-		    }
+		    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",
diff --git a/gcc/common.opt b/gcc/common.opt
index 0613746..85a8ae9 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1054,15 +1054,19 @@ Common RejectNegative Joined Var(str_align_loops) Optimization
 
 fanalyzer-checker=
 Common Joined RejectNegative Var(flag_analyzer_checker)
-FIXME
+Restrict the analyzer to run just the named checker (TODO: docs)
 
 fanalyzer-impl=
 Common Joined RejectNegative UInteger Var(flag_analyzer_impl)
-FIXME
+FIXME: for selecting older implementations of the analyzer, to be removed
 
 fanalyzer-state-purge
 Common Var(flag_analyzer_state_purge) Init(1)
-FIXME
+Purge unneeded state during analysis (TODO: docs)
+
+fanalyzer-state-merge
+Common Var(flag_analyzer_state_merge) Init(0)
+FIXME: merge similar-enough states during analysis (TODO: docs)
 
 fargument-alias
 Common Ignore
diff --git a/gcc/fibonacci_heap.h b/gcc/fibonacci_heap.h
index 6e0a216..16159a3 100644
--- a/gcc/fibonacci_heap.h
+++ b/gcc/fibonacci_heap.h
@@ -162,19 +162,19 @@ public:
   fibonacci_node_t *insert (K key, V *data);
 
   /* Return true if no entry is present.  */
-  bool empty ()
+  bool empty () const
   {
     return m_nodes == 0;
   }
 
   /* Return the number of nodes.  */
-  size_t nodes ()
+  size_t nodes () const
   {
     return m_nodes;
   }
 
   /* Return minimal key presented in the heap.  */
-  K min_key ()
+  K min_key () const
   {
     if (m_min == NULL)
       gcc_unreachable ();
diff --git a/gcc/params.def b/gcc/params.def
index 00389d8..5677f68 100644
--- a/gcc/params.def
+++ b/gcc/params.def
@@ -1378,6 +1378,11 @@ DEFPARAM(PARAM_LOOP_VERSIONING_MAX_OUTER_INSNS,
 	 " loops.",
 	 100, 0, 0)
 
+DEFPARAM(PARAM_ANALYZER_BB_EXPLOSION_FACTOR,
+	 "analyzer-bb-explosion-factor",
+	 "FIXME.",
+	 5, 1, 0)
+
 /*
 
 Local variables:
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 d3fa2ec..4ccc144 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
@@ -10694,7 +10694,7 @@ recvauth_common(krb5_context context,
      goto cleanup;
     }
     if (retval)
- goto cleanup; /* { dg-bogus "analysis bailed out early" "" { xfail *-*-* } } */
+ goto cleanup;
 
 
     if ((ap_option & 0x20000000)) {
@@ -10850,3 +10850,5 @@ krb5_recvauth_version(krb5_context context,
                       |      |  (20) second 'free' here; first 'free' was at (9)
                       |
   { dg-end-multiline-output "" } */
+
+/* { dg-regexp "\[^\n\r\]+: warning: analysis bailed out early \\(\[0-9\]+ nodes\\)" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-zlib-1.1.3-infblock.c b/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-zlib-1.1.3-infblock.c
index 1984512..9499e9a 100644
--- a/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-zlib-1.1.3-infblock.c
+++ b/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-zlib-1.1.3-infblock.c
@@ -3299,7 +3299,7 @@ int r;
         return inflate_flush(s, z, r);
       r = 0;
       inflate_codes_free(s->sub.decode.codes, z);
-      {{p=z->next_in;n=z->avail_in;b=s->bitb;k=s->bitk;} {q=s->write;m=(uInt)(uInt)(q<s->read?s->read-q-1:s->end-q);}} /* { dg-warning "analysis bailed out early" } */
+      {{p=z->next_in;n=z->avail_in;b=s->bitb;k=s->bitk;} {q=s->write;m=(uInt)(uInt)(q<s->read?s->read-q-1:s->end-q);}}
      
 
                                                      ;
@@ -3359,3 +3359,5 @@ inflate_blocks_statef *s;
 {
   return s->mode == LENS;
 }
+
+/* { dg-regexp "\[^\n\r\]+: warning: analysis bailed out early \\(\[0-9\]+ nodes\\)" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/fibonacci.c b/gcc/testsuite/gcc.dg/analyzer/fibonacci.c
index 3af90b1..160dd2c 100644
--- a/gcc/testsuite/gcc.dg/analyzer/fibonacci.c
+++ b/gcc/testsuite/gcc.dg/analyzer/fibonacci.c
@@ -1,8 +1,10 @@
 int fib (int n)
 {
-  if (n > 1) /* { dg-bogus "analysis bailed out early" "" { xfail *-*-* } } */
+  if (n > 1)
     return fib (n - 1) + fib (n - 2);
   else
     return n;
-  /* TODO(xfail) */
 } 
+
+/* { dg-regexp "\[^\n\r\]+: warning: analysis bailed out early \\(\[0-9\]+ nodes\\)" } */
+/* FIXME.  */
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-2.c b/gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-2.c
index 4bc487a..3b51d10 100644
--- a/gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-2.c
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-2.c
@@ -27,7 +27,10 @@ int test (PyObject *obj_01, PyObject *obj_02, PyObject *obj_03,
   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);
-} /* { dg-bogus "analysis bailed out early" "" { xfail *-*-* } } */
+}
+
+/* { dg-regexp "\[^\n\r\]+: warning: analysis bailed out early \\(\[0-9\]+ nodes\\)" } */
+/* FIXME.  */
 
 /* FIXME: why the explosion with the region model?
 
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..966ecb5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/paths-1.c
@@ -0,0 +1,18 @@
+/* { dg-additional-options "-fanalyzer-state-merge" } */
+
+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 "2 exploded nodes" } */
+  // TODO: make this be 1 node, if merging paths
+}
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..0b017b5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/paths-2.c
@@ -0,0 +1,23 @@
+int test (int a)
+{
+  if (a != 42 && a != 113) {
+    return (-2);
+  }
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  /* TODO: consolidate them into one.  */
+
+  return 0;
+}
+
+int test_2 (int a)
+{
+  if (a != 42 && a != 113 && a != 666) {
+    return (-2);
+  }
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "3 exploded nodes" } */
+  /* TODO: consolidate them into one.  */
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/zlib-1.c b/gcc/testsuite/gcc.dg/analyzer/zlib-1.c
index b1f665b..d40a178 100644
--- a/gcc/testsuite/gcc.dg/analyzer/zlib-1.c
+++ b/gcc/testsuite/gcc.dg/analyzer/zlib-1.c
@@ -1,3 +1,7 @@
+/* { dg-additional-options "--param analyzer-bb-explosion-factor=20" } */
+/* TODO: we ought go be able to fully analyze this function without needing
+   the limit.  */
+
 typedef void (*free_func)(void *opaque, void *address);
 
 typedef struct z_stream_s {
@@ -19,34 +23,50 @@ 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 "3 exploded nodes" } */
+  /* TODO: consolidate them; see paths-2.c.  */
+
   if (strm->state->pending_buf)
     (*(strm->zfree))(strm->opaque, (void *)(strm->state->pending_buf));
 
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "6 exploded nodes" } */
+  /* TODO: consolidate them.  */
+
   if (strm->state->head)
       (*(strm->zfree))(strm->opaque, (void *)(strm->state->head));
 
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "12 exploded nodes" } */
+  /* TODO: consolidate them.  */
+
   if (strm->state->prev)
     (*(strm->zfree))(strm->opaque, (void *)(strm->state->prev));
 
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "24 exploded nodes" } */
+  /* TODO: consolidate them.  */
+
   if (strm->state->window)
     (*(strm->zfree))(strm->opaque, (void *)(strm->state->window));
 
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "48 exploded nodes" } */
+  /* TODO: consolidate them.  */
+
   (*(strm->zfree))(strm->opaque, (void *)(strm->state));
   strm->state = 0;
 
-  __analyzer_dump_exploded_nodes (0); /* { dg-warning "16 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "48 exploded nodes" } */
   /* TODO: consolidate them; we ought to be able to get away with just 1.  */
 
   return status == 113 ? (-3) : 0;
 }
-
-/* { dg-regexp "\[^\n\r\]+: warning: analysis bailed out early \\(\[0-9\]+ nodes\\)" } */
-/* TODO: we ought go be able to fully analyze this function.  */
-- 
1.8.5.3

