From 0578d35f3d037df21a1ff6bd3bd68384ab2ed03c Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Tue, 2 Jul 2019 11:02:29 -0400
Subject: [PATCH 56/85] FIXME: implement merging of states based on existing
 enodes for program_point

---
 gcc/analyzer/impl-4.cc                          | 145 ++++++++++++++++++++++--
 gcc/analyzer/region-model.cc                    |  33 ++++++
 gcc/testsuite/gcc.dg/analyzer/loop-2.c          |  22 ++--
 gcc/testsuite/gcc.dg/analyzer/loop.c            |  20 ++--
 gcc/testsuite/gcc.dg/analyzer/malloc-vs-local.c |  15 +--
 gcc/testsuite/gcc.dg/analyzer/paths-1.c         |   3 +-
 gcc/testsuite/gcc.dg/analyzer/paths-2.c         |   6 +-
 gcc/testsuite/gcc.dg/analyzer/paths-3.c         |  12 +-
 gcc/testsuite/gcc.dg/analyzer/paths-4.c         |  12 +-
 gcc/testsuite/gcc.dg/analyzer/paths-5.c         |  14 +++
 gcc/testsuite/gcc.dg/analyzer/zlib-1.c          |  10 +-
 11 files changed, 237 insertions(+), 55 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/paths-5.c

diff --git a/gcc/analyzer/impl-4.cc b/gcc/analyzer/impl-4.cc
index d457630..720f239 100644
--- a/gcc/analyzer/impl-4.cc
+++ b/gcc/analyzer/impl-4.cc
@@ -779,6 +779,12 @@ struct point_and_state
     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;
@@ -845,7 +851,8 @@ class exploded_node : public dnode<eg_traits>
 
   const program_state &get_state () const { return m_ps.m_state; }
 
-  const point_and_state *get_key () const { return &m_ps; }
+  const point_and_state *get_ps_key () const { return &m_ps; }
+  const program_point *get_point_key () const { return &m_ps.m_point; }
 
 private:
   /* The <program_point, program_state> pair.  This is const, as it
@@ -887,10 +894,12 @@ struct stats
 
   int m_num_nodes[NUM_POINT_KINDS];
   int m_node_reuse_count;
+  int m_node_reuse_after_merge_count;
   int m_num_supernodes;
 };
 
-/* FIXME.  */
+/* Traits class for ensuring uniqueness of point_and_state data within
+   an exploded_graph.  */
 
 struct eg_hash_map_traits
 {
@@ -943,6 +952,60 @@ struct eg_hash_map_traits
   }
 };
 
+/* 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 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;
+  }
+};
+
 /* A to-be-emitted diagnostic stored within diagnostic_manager.  */
 
 class saved_diagnostic
@@ -1187,6 +1250,13 @@ private:
 		    eg_hash_map_traits> map_t;
   map_t m_point_and_state_to_node;
 
+  /* Map from program_point to the first exploded_node at
+     that point.
+     FIXME: why?*/
+  typedef hash_map <const program_point *, exploded_node *,
+		    eg_point_hash_map_traits> point_to_enode_map_t;
+  point_to_enode_map_t m_point_to_enode;
+
   worklist m_worklist;
 
   exploded_node *m_origin;
@@ -3850,6 +3920,7 @@ exploded_edge::dump_dot (graphviz_out *gv, const dump_args_t &) const
 
 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++)
@@ -3867,6 +3938,8 @@ stats::log (logger *logger) const
 		 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);
 }
 
 /* FIXME.  */
@@ -3879,6 +3952,8 @@ stats::dump (FILE *out) const
 	     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",
@@ -4708,10 +4783,17 @@ diagnostic_manager::prune_path (checker4_path *path,
 		    = cg_superedge.map_expr_from_caller_to_callee (var, &expr);
 		  if (callee_var)
 		    {
-		      log ("event %i:"
-			   " switching var of interest"
-			   " from %qE in caller to %qE in callee",
-			   idx, var, callee_var);
+		      if (var)
+			log ("event %i:"
+			     " switching var of interest"
+			     " from %qE in caller to %qE in callee",
+			     idx, var, callee_var);
+		      else
+			// FIXME: 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);
@@ -5141,10 +5223,56 @@ exploded_graph::get_or_create_node (const program_point &point,
       return *slot;
     }
 
-  /* An exploded_node for PS doesn't already exist; create one.  */
+  /* Consider merging state with another enode at this program_point.  */
+  if (flag_analyzer_state_merge)
+    if (exploded_node **slot = m_point_to_enode.get (&point))
+      {
+	exploded_node *existing_enode = (*slot);
+	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 ();
+
+	/* They shouldn't be equal, or we would have found and returned
+	   it above.  */
+	gcc_assert (pruned_state != existing_state);
+
+	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++;
+		return *slot;
+	      }
+
+	    /* Otherwise, continue, using the merged state in "ps".  */
+	  }
+	else
+	  log ("not merging new state with that of EN: %i",
+	       existing_enode->m_index);
+      }
+
+  /* An exploded_node for "ps" doesn't already exist; create one.  */
   exploded_node *node = new exploded_node (ps, m_ext_state, m_nodes.length ());
   add_node (node);
-  m_point_and_state_to_node.put (node->get_key (), node);
+  m_point_and_state_to_node.put (node->get_ps_key (), node);
+
+  /* Update per-program_point data.  */
+  /* FIXME: doing this stores the last enode with the given program_point.
+     Should we store more than one enode (perhaps a vec, and bail
+     out if we have too many states per program_point?  */
+  m_point_to_enode.put (node->get_point_key (), node);
+
   m_global_stats.m_num_nodes[node->get_point ().m_kind]++;
   per_fn_stats->m_num_nodes[node->get_point ().m_kind]++;
 
@@ -6714,6 +6842,7 @@ exploded_graph::dump_exploded_nodes () const
 
   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;
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index 8501c32..5634e0b 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -4149,6 +4149,39 @@ test_state_merging ()
 #endif
   }
 
+  /* Verify handling of a PARM_DECL.  */
+  {
+    auto_vec <tree> param_types;
+    tree parent_fndecl = make_fndecl (integer_type_node,
+				      "test_fn",
+				      param_types);
+    allocate_struct_function (parent_fndecl, true);
+
+    /* Param "a".  */
+    tree a = build_decl (UNKNOWN_LOCATION, PARM_DECL,
+			 get_identifier ("a"),
+			 integer_type_node);
+
+    test_region_model_context ctxt;
+    region_model model0;
+    region_model model1;
+    model0.push_frame (DECL_STRUCT_FUNCTION (parent_fndecl), NULL);
+    model1.push_frame (DECL_STRUCT_FUNCTION (parent_fndecl), NULL);
+    model0.set_to_new_unknown_value (model0.get_lvalue (a, &ctxt),
+				     integer_type_node);
+    model1.set_to_new_unknown_value (model1.get_lvalue (a, &ctxt),
+				     integer_type_node);
+    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));
+#if 0
+    // FIXME: currently we're losing the value for "a".
+    ASSERT_EQ (model0, merged);
+#endif
+  }
+
 #if 0
   {
     region_model model0;
diff --git a/gcc/testsuite/gcc.dg/analyzer/loop-2.c b/gcc/testsuite/gcc.dg/analyzer/loop-2.c
index 589b78d..e2ccc5a 100644
--- a/gcc/testsuite/gcc.dg/analyzer/loop-2.c
+++ b/gcc/testsuite/gcc.dg/analyzer/loop-2.c
@@ -21,8 +21,12 @@ void test(void)
   __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
 
 
-  for (s.i=0; s.i<256; s.i++) { /* { dg-warning "analysis bailed out early" } */
-    //__analyzer_eval (s.i < 256); /* { d-todo-g-warning "TRUE" } */
+  for (s.i=0; s.i<256; s.i++) {
+    __analyzer_eval (s.i < 256); /* { dg-warning "TRUE" } */
+    /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+    /* TODO: this should report TRUE on the 2nd time, also.  */
+
+    __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 } */
@@ -32,11 +36,13 @@ void test(void)
       //__analyzer_eval (s.i >= 0); /* { d-todo-g-warning "TRUE" } */
   }
 
-  //__analyzer_eval (s.i >= 256); /* { d-todo-g-warning "TRUE" } */
-  //__analyzer_eval (s.i == 256); /* { d-todo-g-warning "TRUE" "" { xfail *-*-* } } */
-  /* { d-todo-g-warning "UNKNOWN" "" { target *-*-* } .-1 } */
-  /* TODO(xfail^^^): it only figures out i >= 256, rather than i == 256.  */
+  __analyzer_eval (s.i >= 256); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  /* TODO(xfail^^^): it should figures out i >= 256.  */
+
+  __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" "" { xfail *-*-* } } */
-  // FIXME: it bails out before making it this far
+  __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
index 839e6b2..5a61a3e 100644
--- a/gcc/testsuite/gcc.dg/analyzer/loop.c
+++ b/gcc/testsuite/gcc.dg/analyzer/loop.c
@@ -9,21 +9,23 @@ void test(void)
 
   for (i=0; i<256; i++) {
       __analyzer_eval (i < 256); /* { dg-warning "TRUE" } */
+      /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+      /* TODO: this should report TRUE on the 2nd time, also.  */
 
-      __analyzer_eval (i == 0); /* { dg-warning "UNKNOWN" "" { xfail *-*-* } } */
-      /* { dg-warning "TRUE" "" { target *-*-* } .-1 } */
-      /* TODO(xfail^^^): we're only capturing the first iteration, so
-	 we erroneously get i == 0.  */
+      __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 "3 exploded nodes" } */
-      // FIXME: why 3 nodes here?
+      __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
   }
 
-  __analyzer_eval (i >= 256); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
-  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
-  /* TODO(xfail^^^): with state-merging, this becomes unknown; why?  */
+  __analyzer_eval (i >= 256); /* { dg-warning "TRUE" } */
 
   __analyzer_eval (i == 256); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
   /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local.c b/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local.c
index a429eef..32c7191 100644
--- a/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local.c
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local.c
@@ -12,7 +12,8 @@ do_stuff (int *p, int n)
   for (i = 0; i < n; i++)
     p[i] = i;
   for (i = 0; i < n; i++)
-    sum += foo (p[i]);
+    sum += foo (p[i]); /* { dg-bogus "uninitialized" "" { xfail *-*-* } } */
+  /* FIXME: overzealous state-merging is eliminating p, I think.  */
   return sum;
 }
 
@@ -47,8 +48,8 @@ int test_repeated_predicate_1 (int n)
 
   result = do_stuff (ptr, n);
 
-  __analyzer_dump_exploded_nodes (0); /* { dg-warning "4 exploded nodes" } */
-  // FIXME: why 4 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" } */
@@ -102,8 +103,8 @@ int test_explicit_flag (int n)
 
   result = do_stuff (ptr, n);
 
-  __analyzer_dump_exploded_nodes (0); /* { dg-warning "4 exploded nodes" } */
-  // FIXME: why 4 here?
+  __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" } */
@@ -128,8 +129,8 @@ int test_pointer_comparison (int n)
 
   result = do_stuff (ptr, n);
 
-  __analyzer_dump_exploded_nodes (0); /* { dg-warning "5 exploded nodes" } */
-  // FIXME: why 5 here?
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "7 exploded nodes" } */
+  // FIXME: why 7 here?
 
   if (ptr != buf)
     free (ptr); /* { dg-bogus "not on the heap" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/paths-1.c b/gcc/testsuite/gcc.dg/analyzer/paths-1.c
index 45a83e7..27f2606 100644
--- a/gcc/testsuite/gcc.dg/analyzer/paths-1.c
+++ b/gcc/testsuite/gcc.dg/analyzer/paths-1.c
@@ -13,7 +13,6 @@ void test (struct foo *pf)
     bar (0);
   else
     bar (1);
-  __analyzer_dump_exploded_nodes (0); /* { dg-warning "3 exploded nodes" } */
-  /* TODO: consolidating 2 nodes into 1 leads to 2 + 1 == 3 resulting nodes.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 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
index babdeb0..29e05e0 100644
--- a/gcc/testsuite/gcc.dg/analyzer/paths-2.c
+++ b/gcc/testsuite/gcc.dg/analyzer/paths-2.c
@@ -6,8 +6,7 @@ int test (int a)
     return (-2);
   }
 
-  __analyzer_dump_exploded_nodes (0); /* { dg-warning "3 exploded nodes" } */
-  /* TODO: consolidating 2 nodes into 1 leads to 2 + 1 == 3 resulting nodes.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
 
   __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
 
@@ -20,8 +19,7 @@ int test_2 (int a)
     return (-2);
   }
 
-  __analyzer_dump_exploded_nodes (0); /* { dg-warning "4 exploded nodes" } */
-  /* TODO: consolidating 3 nodes into 1 leads to 3 + 1 == 4.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
 
   __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
 
diff --git a/gcc/testsuite/gcc.dg/analyzer/paths-3.c b/gcc/testsuite/gcc.dg/analyzer/paths-3.c
index ffbc3c9..b6dea2d 100644
--- a/gcc/testsuite/gcc.dg/analyzer/paths-3.c
+++ b/gcc/testsuite/gcc.dg/analyzer/paths-3.c
@@ -12,8 +12,8 @@ int test_1 (int a, int b)
     else
       p = malloc (32);
 
-  __analyzer_dump_exploded_nodes (0); /* { dg-warning "4 exploded nodes" } */
-  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "3 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "3 exploded node" } */
 
   if (a > 5)
     {
@@ -34,8 +34,8 @@ int test_2 (int a, int b)
     else
       p = malloc (32);
 
-  __analyzer_dump_exploded_nodes (0); /* { dg-warning "3 exploded nodes" } */
-  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
 
   if (a > 6) /* different condition */
     {
@@ -43,6 +43,6 @@ int test_2 (int a, int b)
       __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
     }
 
-  return 0; /* { dg-warning "leak of 'p'" "" { xfail *-*-* } } */
-  /* FIXME.  There's a leak here when a == 5.  */
+  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
index c1e3ee5..d085f00 100644
--- a/gcc/testsuite/gcc.dg/analyzer/paths-4.c
+++ b/gcc/testsuite/gcc.dg/analyzer/paths-4.c
@@ -14,7 +14,7 @@ int test_1 (struct state *s)
   while (1)
     {
       __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
-      __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);
     }
 }
@@ -24,20 +24,20 @@ 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 "5 exploded nodes" } */
-      __analyzer_dump_exploded_nodes (0); /* { dg-warning "5 exploded nodes" } */
+      __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+      __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
       switch (s->mode)
 	{
 	case 0:
-	  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+	  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
 	  do_stuff (s, 0);
 	  break;
 	case 1:
-	  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+	  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
 	  do_stuff (s, 17);
 	  break;
 	case 2:
-	  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+	  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
 	  do_stuff (s, 5);
 	  break;
 	case 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..9b5b5af
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/paths-5.c
@@ -0,0 +1,14 @@
+/* { dg-additional-options "-fanalyzer-state-merge" } */
+
+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" "" { xfail *-*-* } } */
+      /* FIXME: overzealous state-merging is eliminating p, I think.  */
+
+      __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+    }
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/zlib-1.c b/gcc/testsuite/gcc.dg/analyzer/zlib-1.c
index b74c9f1..5adb8f3 100644
--- a/gcc/testsuite/gcc.dg/analyzer/zlib-1.c
+++ b/gcc/testsuite/gcc.dg/analyzer/zlib-1.c
@@ -35,31 +35,31 @@ int deflateEnd(z_stream *strm)
     return (-2);
   }
 
-  __analyzer_dump_exploded_nodes (0); /* { dg-warning "4 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 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 "3 exploded nodes" } */
+  __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 "3 exploded nodes" } */
+  __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 "3 exploded nodes" } */
+  __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 "3 exploded nodes" } */
+  __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));
-- 
1.8.5.3

