From 78a0a32b381deaa965db9490a5dcb5feae83e591 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Mon, 15 Jul 2019 20:55:02 -0400
Subject: [PATCH 089/108] FIXME: implement constraint_manager merging

---
 gcc/analyzer/constraint-manager.cc                | 224 +++++++++++++++++-----
 gcc/analyzer/constraint-manager.h                 |  24 ++-
 gcc/analyzer/region-model.cc                      |  11 +-
 gcc/testsuite/gcc.dg/analyzer/loop.c              |   3 +-
 gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-2.c |  22 +--
 gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-3.c |   2 +-
 gcc/testsuite/gcc.dg/analyzer/malloc-vs-local.c   |  16 +-
 gcc/testsuite/gcc.dg/analyzer/paths-1.c           |   2 +-
 gcc/testsuite/gcc.dg/analyzer/paths-2.c           |   4 +-
 gcc/testsuite/gcc.dg/analyzer/paths-3.c           |   8 +-
 gcc/testsuite/gcc.dg/analyzer/paths-4.c           |   2 +-
 gcc/testsuite/gcc.dg/analyzer/paths-6.c           |   2 +-
 gcc/testsuite/gcc.dg/analyzer/paths-7.c           |   8 +-
 gcc/testsuite/gcc.dg/analyzer/zlib-1.c            |   2 +-
 14 files changed, 241 insertions(+), 89 deletions(-)

diff --git a/gcc/analyzer/constraint-manager.cc b/gcc/analyzer/constraint-manager.cc
index b1618e2..2ea3163 100644
--- a/gcc/analyzer/constraint-manager.cc
+++ b/gcc/analyzer/constraint-manager.cc
@@ -76,7 +76,8 @@ struct bound
   bool m_closed;
 };
 
-/* FIXME.  */
+/* A range of values, used for determining if a value has been
+   constrained to just one possible constant value.  */
 
 struct range
 {
@@ -394,6 +395,21 @@ constraint_op_code (enum constraint_op c_op)
     }
 }
 
+/* FIXME.  */
+
+enum tree_code
+constraint_tree_code (enum constraint_op c_op)
+{
+  switch (c_op)
+    {
+    default:
+      gcc_unreachable ();
+    case CONSTRAINT_NE: return NE_EXPR;
+    case CONSTRAINT_LT: return LT_EXPR;
+    case CONSTRAINT_LE: return LE_EXPR;
+    }
+}
+
 /* Given "lhs C_OP rhs", determine "lhs T_OP rhs".
 
    For example, given "x < y", then "x > y" is false.  */
@@ -1404,7 +1420,67 @@ constraint_manager::canonicalize (unsigned num_svalue_ids)
   m_constraints.qsort (constraint_cmp);
 }
 
-/* FIXME.  */
+/* A concrete subclass of constraint_manager for use when
+   merging two constraint_manager into a third constraint_manager,
+   each of which has its own region_model.
+   Calls are delegated to the constraint_manager for the merged model.  */
+
+class cleaned_constraint_manager : public constraint_manager
+{
+public:
+  cleaned_constraint_manager (constraint_manager *merged) : m_merged (merged) {}
+
+  constraint_manager *clone (region_model *) const FINAL OVERRIDE
+  {
+    gcc_unreachable ();
+  }
+  tree maybe_get_constant (svalue_id sid) const FINAL OVERRIDE
+  {
+    return m_merged->maybe_get_constant (sid);
+  }
+  svalue_id get_sid_for_constant (tree cst) const FINAL OVERRIDE
+  {
+    return m_merged->get_sid_for_constant (cst);
+  }
+  virtual int get_num_svalues () const FINAL OVERRIDE
+  {
+    return m_merged->get_num_svalues ();
+  }
+private:
+  constraint_manager *m_merged;
+};
+
+/* Concrete subclass of fact_visitor for use by constraint_manager::merge.
+   For every fact in CM_A, see if it is also true in *CM_B.  Add such
+   facts to *OUT.  */
+
+class merger_fact_visitor : public fact_visitor
+{
+public:
+  merger_fact_visitor (constraint_manager *cm_b,
+		       constraint_manager *out)
+  : m_cm_b (cm_b), m_out (out)
+  {}
+
+  void on_fact (svalue_id lhs, enum tree_code code, svalue_id rhs)
+    FINAL OVERRIDE
+  {
+    if (m_cm_b->eval_condition (lhs, code, rhs).is_true ())
+      {
+	bool sat = m_out->add_constraint (lhs, code, rhs);
+	gcc_assert (sat);
+      }
+  }
+
+private:
+  constraint_manager *m_cm_b;
+  constraint_manager *m_out;
+};
+
+/* Use MERGER to merge CM_A and CM_B into *OUT.
+   If one thinks of a constraint_manager as a subset of N-dimensional
+   space, this takes the union of the points of CM_A and CM_B, and
+   expresses that into *OUT.  */
 
 void
 constraint_manager::merge (const constraint_manager &cm_a,
@@ -1416,50 +1492,42 @@ constraint_manager::merge (const constraint_manager &cm_a,
   if (!merger.m_svalue_id_mapping)
     return;
 
-
-  // TODO
-  //auto_vec<equiv_class *> m_equiv_classes;
-  //auto_vec<constraint> m_constraints;
-  /* TODO:
-     Map svalue_ids in each equiv class from src to merged, dropping ids
-     that don't survive merger.
-     Try to find correspondences between such ECs, find the intersection
-     of svalues in the merged.
-     This gives us our ECs, then somehow add constraints between them.
-
-     Alternate approach:
-     Having mapped svalue IDs, try to add constraints to OUT
-     until you get a contradiction - but this doesn't work; we want
-     to find things that are true in *both* inputs.
-  */
-  //constraint_manager cleaned_cm_a;
-  // FIXME: move to subroutine, call it for a and for b:
+  /* Map svalue_ids in each equiv class from both sources
+     to the merged region_model, dropping ids that don't survive merger,
+     and potentially creating svalues in *OUT for constants.  */
+  cleaned_constraint_manager cleaned_cm_a (out);
   const one_way_svalue_id_map &map_a_to_m
     = merger.m_svalue_id_mapping->m_map_from_a_to_m;
-  auto_vec<equiv_class *> cleaned_ecs_a;
-  auto_vec<constraint> cleaned_constraints_a;
-  clean_merger_input (cm_a, out, map_a_to_m, &cleaned_ecs_a,
-		      &cleaned_constraints_a);
+  clean_merger_input (cm_a, map_a_to_m, &cleaned_cm_a);
 
+  cleaned_constraint_manager cleaned_cm_b (out);
   const one_way_svalue_id_map &map_b_to_m
     = merger.m_svalue_id_mapping->m_map_from_b_to_m;
-  auto_vec<equiv_class *> cleaned_ecs_b;
-  auto_vec<constraint> cleaned_constraints_b;
-  clean_merger_input (cm_b, out, map_b_to_m, &cleaned_ecs_b,
-		      &cleaned_constraints_b);
-
-  // TODO: do something with the above
+  clean_merger_input (cm_b, map_b_to_m, &cleaned_cm_b);
+
+  /* At this point, the two cleaned CMs have ECs and constraints referring
+     to svalues in the merged region model, but both of them have separate
+     ECs.  */
+
+  /* Merge the equivalence classes and constraints.
+     The easiest way to do this seems to be to enumerate all of the facts
+     in cleaned_cm_a, see which are also true in cleaned_cm_b,
+     and add those to *OUT.  */
+  merger_fact_visitor v (&cleaned_cm_b, out);
+  cleaned_cm_a.for_each_fact (&v);
 }
 
-/* FIXME.  */
+/* Use MAP_SID_TO_M to map equivalence classes and constraints from
+   SM_IN to *OUT.  Purge any non-constant svalue_id that don't appear
+   in the result of MAP_SID_TO_M, purging any ECs and their constraints
+   that become empty as a result.  Potentially create svalues in
+   the merged region_model for constants that weren't already in use there.  */
 
 void
 constraint_manager::
 clean_merger_input (const constraint_manager &cm_in,
-		    constraint_manager *out,
 		    const one_way_svalue_id_map &map_sid_to_m,
-		    auto_vec<equiv_class *> *out_cleaned_ecs,
-		    auto_vec<constraint> *out_cleaned_constraints)
+		    constraint_manager *out)
 {
   one_way_id_map<equiv_class_id> map_ec_to_m
     (cm_in.m_equiv_classes.length ());
@@ -1472,10 +1540,7 @@ clean_merger_input (const constraint_manager &cm_in,
 	{
 	  cleaned_ec.m_constant = cst;
 	  /* Lazily create the constant in the out region_model.  */
-	  // TODO: enabling this causes lots of churn in the DejaGnu testcases:
-#if 0
 	  cleaned_ec.m_cst_sid = out->get_sid_for_constant (cst);
-#endif
 	}
       unsigned var_idx;
       svalue_id *var_in_sid;
@@ -1487,13 +1552,13 @@ clean_merger_input (const constraint_manager &cm_in,
 	}
       if (cleaned_ec.get_any_constant () || !cleaned_ec.m_vars.is_empty ())
 	{
-	  map_ec_to_m.put (ec_idx, out_cleaned_ecs->length ());
-	  out_cleaned_ecs->safe_push (new equiv_class (cleaned_ec));
+	  map_ec_to_m.put (ec_idx, out->m_equiv_classes.length ());
+	  out->m_equiv_classes.safe_push (new equiv_class (cleaned_ec));
 	}
     }
 
-  /* Write out to *OUT_CLEANED_CONSTRAINTS any constraints for which both
-     sides survived cleaning, using the new EC IDs.  */
+  /* Write out to *OUT any constraints for which both sides survived
+     cleaning, using the new EC IDs.  */
   unsigned con_idx;
   constraint *c;
   FOR_EACH_VEC_ELT (cm_in.m_constraints, con_idx, c)
@@ -1504,9 +1569,80 @@ clean_merger_input (const constraint_manager &cm_in,
       equiv_class_id new_rhs = map_ec_to_m.get_dst_for_src (c->m_rhs);
       if (new_rhs.null_p ())
 	continue;
-      out_cleaned_constraints->safe_push (constraint (new_lhs,
-						      c->m_op,
-						      new_rhs));
+      out->m_constraints.safe_push (constraint (new_lhs,
+						c->m_op,
+						new_rhs));
+    }
+}
+
+/* FIXME.  */
+
+#if 0
+void
+constraint_manager::get_ec_map (auto_vec<equiv_class_id> *out_ec_for_sid) const
+{
+  const unsigned num_svalues = get_num_svalues ();
+  for (unsigned i = 0; i < num_svalues; i++)
+    out_ec_for_sid->safe_push (equiv_class_id::null ());
+  unsigned ec_idx;
+  equiv_class *ec;
+  FOR_EACH_VEC_ELT (m_equiv_classes, ec_idx, ec)
+    {
+      if (!ec->m_cst_sid.null_p ())
+	(*out_ec_for_sid)[ec->m_cst_sid.as_int ()] = ec_idx;
+      unsigned i;
+      svalue_id *sid;
+      FOR_EACH_VEC_ELT (ec->m_vars, i, sid)
+	(*out_ec_for_sid)[sid->as_int ()] = ec_idx;
+    }
+}
+#endif
+
+/* FIXME.  */
+
+void
+constraint_manager::for_each_fact (fact_visitor *visitor) const
+{
+  /* First, call EQ_EXPR within the various equivalence classes.  */
+  unsigned ec_idx;
+  equiv_class *ec;
+  FOR_EACH_VEC_ELT (m_equiv_classes, ec_idx, ec)
+    {
+      if (!ec->m_cst_sid.null_p ())
+	{
+	  unsigned i;
+	  svalue_id *sid;
+	  FOR_EACH_VEC_ELT (ec->m_vars, i, sid)
+	    visitor->on_fact (ec->m_cst_sid, EQ_EXPR, *sid);
+	}
+      for (unsigned i = 0; i < ec->m_vars.length (); i++)
+	for (unsigned j = i + 1; j < ec->m_vars.length (); j++)
+	  visitor->on_fact (ec->m_vars[i], EQ_EXPR, ec->m_vars[j]);
+    }
+
+  /* Now, express the various constraints.  */
+  unsigned con_idx;
+  constraint *c;
+  FOR_EACH_VEC_ELT (m_constraints, con_idx, c)
+    {
+      const equiv_class &ec_lhs = c->m_lhs.get_obj (*this);
+      const equiv_class &ec_rhs = c->m_rhs.get_obj (*this);
+      enum tree_code code = constraint_tree_code (c->m_op);
+
+      if (!ec_lhs.m_cst_sid.null_p ())
+	{
+	  for (unsigned j = 0; j < ec_rhs.m_vars.length (); j++)
+	    {
+	      visitor->on_fact (ec_lhs.m_cst_sid, code, ec_rhs.m_vars[j]);
+	    }
+	}
+      for (unsigned i = 0; i < ec_lhs.m_vars.length (); i++)
+	{
+	  if (!ec_rhs.m_cst_sid.null_p ())
+	    visitor->on_fact (ec_lhs.m_vars[i], code, ec_rhs.m_cst_sid);
+	  for (unsigned j = 0; j < ec_rhs.m_vars.length (); j++)
+	    visitor->on_fact (ec_lhs.m_vars[i], code, ec_rhs.m_vars[j]);
+	}
     }
 }
 
diff --git a/gcc/analyzer/constraint-manager.h b/gcc/analyzer/constraint-manager.h
index d685889..b2e290c 100644
--- a/gcc/analyzer/constraint-manager.h
+++ b/gcc/analyzer/constraint-manager.h
@@ -151,7 +151,20 @@ class constraint
   equiv_class_id m_rhs;
 };
 
-/* A collection of equivalence classes and constraints on them.  */
+/* An abstract base class for use with constraint_manager::for_each_fact.  */
+
+class fact_visitor
+{
+ public:
+  virtual ~fact_visitor () {}
+  virtual void on_fact (svalue_id lhs, enum tree_code, svalue_id rhs) = 0;
+};
+
+/* A collection of equivalence classes and constraints on them.
+
+   Given N svalues, this can be thought of as representing a subset of
+   N-dimensional space.  When we call add_constraint,
+   we are effectively taking an intersection with that constraint.  */
 
 class constraint_manager
 {
@@ -227,6 +240,8 @@ public:
 		     constraint_manager *out,
 		     const model_merger &merger);
 
+  void for_each_fact (fact_visitor *) const;
+
   void validate () const;
 
   auto_vec<equiv_class *> m_equiv_classes;
@@ -234,10 +249,11 @@ public:
 
  private:
   static void clean_merger_input (const constraint_manager &cm_in,
-				  constraint_manager *out,
 				  const one_way_svalue_id_map &map_sid_to_m,
-				  auto_vec<equiv_class *> *out_cleaned_ecs,
-				  auto_vec<constraint> *out_cleaned_constraints);
+				  constraint_manager *out);
+#if 0
+  void get_ec_map (auto_vec<equiv_class_id> *out_ec_for_sid) const;
+#endif
 
   void add_constraint_internal (equiv_class_id lhs_id,
 				enum constraint_op c_op,
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index 7a64249..f90c56c 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -5522,7 +5522,6 @@ test_state_merging ()
 static void
 test_constraint_merging ()
 {
-
   tree int_0 = build_int_cst (integer_type_node, 0);
   tree int_5 = build_int_cst (integer_type_node, 5);
   tree x = build_global_decl ("x", integer_type_node);
@@ -5554,7 +5553,15 @@ test_constraint_merging ()
   svalue_id_merger_mapping sid_mapping (model0, model1);
   ASSERT_TRUE (model0.can_merge_with_p (model1, &merged, &sid_mapping));
 
-  // TODO
+  ASSERT_EQ (merged.eval_condition (x, GE_EXPR, int_0, &ctxt),
+	     tristate (tristate::TS_TRUE));
+  ASSERT_EQ (merged.eval_condition (x, LT_EXPR, n, &ctxt),
+	     tristate (tristate::TS_TRUE));
+
+  ASSERT_EQ (merged.eval_condition (z, NE_EXPR, int_5, &ctxt),
+	     tristate (tristate::TS_UNKNOWN));
+  ASSERT_EQ (merged.eval_condition (x, LT_EXPR, y, &ctxt),
+	     tristate (tristate::TS_UNKNOWN));
 }
 
 /* Run all of the selftests within this file.  */
diff --git a/gcc/testsuite/gcc.dg/analyzer/loop.c b/gcc/testsuite/gcc.dg/analyzer/loop.c
index 5a61a3e..6ef404f 100644
--- a/gcc/testsuite/gcc.dg/analyzer/loop.c
+++ b/gcc/testsuite/gcc.dg/analyzer/loop.c
@@ -9,8 +9,7 @@ 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.  */
+      /* { d-g-warning "TRUE" "" { target *-*-* } .-1 } */
 
       __analyzer_eval (i == 0); /* { dg-warning "TRUE" } */
       /* { dg-warning "FALSE" "" { xfail *-*-* } .-1 } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-2.c b/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-2.c
index 216a5bc..6fa2c130 100644
--- a/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-2.c
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-2.c
@@ -39,18 +39,14 @@ int test_repeated_predicate_1 (int n)
     result = sum;
   }
 
-  __analyzer_dump_exploded_nodes (0); /* { dg-warning "7 exploded nodes" } */
-  __analyzer_dump_exploded_nodes (0); /* { dg-warning "6 exploded nodes" } */
-  // FIXME: why 7/6 here?
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "5 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "5 exploded nodes" } */
+  // FIXME: why 5 here?
 
   if (n > 10)
-    free (ptr); /* { dg-bogus "not on the heap" "" { xfail *-*-* } } */
-  /* TODO(xfail): this emits a bogus message due to state-merging
-     losing the (n > 10) condition, I think.  */
+    free (ptr); /* { dg-bogus "not on the heap" } */
 
-  return result; /* { dg-bogus "leak" "" { xfail *-*-* } } */
-  /* TODO(xfail): this emits a bogus leak message due to state-merging
-     losing the (n > 10) condition, I think.  */
+  return result; /* { dg-bogus "leak" } */
 }
 
 /* A simpler version of the above.  */
@@ -108,8 +104,8 @@ int test_explicit_flag (int n)
     result = sum;
   }
 
-  __analyzer_dump_exploded_nodes (0); /* { dg-warning "7 exploded nodes" } */
-  // FIXME: why 7 here?
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "6 exploded nodes" } */
+  // FIXME: why 6 here?
 
   if (need_to_free)
     free (ptr); /* { dg-bogus "not on the heap" } */
@@ -143,8 +139,8 @@ int test_pointer_comparison (int n)
     result = sum;
   }
 
-  __analyzer_dump_exploded_nodes (0); /* { dg-warning "7 exploded nodes" } */
-  // FIXME: why 7 here?
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "6 exploded nodes" } */
+  // FIXME: why 6 here?
 
   if (ptr != buf)
     free (ptr); /* { dg-bogus "not on the heap" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-3.c b/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-3.c
index f70a91b..4eeec0c 100644
--- a/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-3.c
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-3.c
@@ -39,7 +39,7 @@ int test_1 (int n)
   }
 
   __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" "" { xfail *-*-* } } */
-  /* { dg-bogus "6 exploded nodes" "" { xfail *-*-* } .-1 } */
+  /* { dg-bogus "4 exploded nodes" "" { xfail *-*-* } .-1 } */
   /* TODO(xfail): should be 2 nodes here, for the malloc-vs-non-malloc case; erroneously getting more.  */
 
   return result; /* { dg-message "leak of 'ptr'" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local.c b/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local.c
index fea68e7..35ebe7f 100644
--- a/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local.c
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local.c
@@ -48,10 +48,10 @@ int test_repeated_predicate_1 (int n)
 
   result = do_stuff (ptr, n);
 
-  __analyzer_dump_exploded_nodes (0); /* { dg-warning "7 exploded nodes" } */
-  // FIXME: why 7 here?
-  __analyzer_dump_exploded_nodes (0); /* { dg-warning "6 exploded nodes" } */
-  // FIXME: why 6 here?
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "5 exploded nodes" } */
+  // FIXME: why 5 here?
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "5 exploded nodes" } */
+  // FIXME: why 5 here?
 
   if (n > 10)
     free (ptr); /* { dg-bogus "not on the heap" } */
@@ -107,8 +107,8 @@ int test_explicit_flag (int n)
 
   result = do_stuff (ptr, n);
 
-  __analyzer_dump_exploded_nodes (0); /* { dg-warning "7 exploded nodes" } */
-  // FIXME: why 7 here?
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "5 exploded nodes" } */
+  // FIXME: why 5 here?
 
   if (need_to_free)
     free (ptr); /* { dg-bogus "not on the heap" } */
@@ -133,8 +133,8 @@ int test_pointer_comparison (int n)
 
   result = do_stuff (ptr, n);
 
-  __analyzer_dump_exploded_nodes (0); /* { dg-warning "7 exploded nodes" } */
-  // FIXME: why 7 here?
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "5 exploded nodes" } */
+  // FIXME: why 5 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 27f2606..80f46fc 100644
--- a/gcc/testsuite/gcc.dg/analyzer/paths-1.c
+++ b/gcc/testsuite/gcc.dg/analyzer/paths-1.c
@@ -13,6 +13,6 @@ void test (struct foo *pf)
     bar (0);
   else
     bar (1);
-  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "3 exploded nodes" } */
   __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
 }
diff --git a/gcc/testsuite/gcc.dg/analyzer/paths-2.c b/gcc/testsuite/gcc.dg/analyzer/paths-2.c
index 29e05e0..99181b8 100644
--- a/gcc/testsuite/gcc.dg/analyzer/paths-2.c
+++ b/gcc/testsuite/gcc.dg/analyzer/paths-2.c
@@ -6,7 +6,7 @@ int test (int a)
     return (-2);
   }
 
-  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "3 exploded nodes" } */
 
   __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
 
@@ -19,7 +19,7 @@ int test_2 (int a)
     return (-2);
   }
 
-  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "4 exploded nodes" } */
 
   __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
 
diff --git a/gcc/testsuite/gcc.dg/analyzer/paths-3.c b/gcc/testsuite/gcc.dg/analyzer/paths-3.c
index 99de266..efdeb51 100644
--- a/gcc/testsuite/gcc.dg/analyzer/paths-3.c
+++ b/gcc/testsuite/gcc.dg/analyzer/paths-3.c
@@ -12,7 +12,7 @@ int test_1 (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 "4 exploded nodes" } */
   __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
 
   if (a > 5)
@@ -21,9 +21,7 @@ int test_1 (int a, int b)
       __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
     }
 
-  return 0; /* { dg-bogus "leak" "" { xfail *-*-* } } */
-  /* TODO(xfail): this emits a bogus leak message due to state-merging
-     losing the (a > 5) condition, I think.  */
+  return 0; /* { dg-bogus "leak" } */
 }
 
 int test_2 (int a, int b)
@@ -36,7 +34,7 @@ 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 "4 exploded nodes" } */
   __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
 
   if (a > 6) /* different condition */
diff --git a/gcc/testsuite/gcc.dg/analyzer/paths-4.c b/gcc/testsuite/gcc.dg/analyzer/paths-4.c
index aedf963..7a55f27 100644
--- a/gcc/testsuite/gcc.dg/analyzer/paths-4.c
+++ b/gcc/testsuite/gcc.dg/analyzer/paths-4.c
@@ -24,7 +24,7 @@ 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 "2 exploded nodes" } */
+      __analyzer_dump_exploded_nodes (0); /* { dg-warning "3 exploded nodes" } */
       __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
       switch (s->mode)
 	{
diff --git a/gcc/testsuite/gcc.dg/analyzer/paths-6.c b/gcc/testsuite/gcc.dg/analyzer/paths-6.c
index 2eab417..b72111e 100644
--- a/gcc/testsuite/gcc.dg/analyzer/paths-6.c
+++ b/gcc/testsuite/gcc.dg/analyzer/paths-6.c
@@ -93,7 +93,7 @@ void test_3 (int i)
       break;
     }
 
-  __analyzer_dump_exploded_nodes (0); /* { dg-warning "7 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "8 exploded nodes" } */
   __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
   __analyzer_eval (f == 3); /* { dg-warning "TRUE" } */
   __analyzer_eval (g == 4); /* { dg-warning "TRUE" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/paths-7.c b/gcc/testsuite/gcc.dg/analyzer/paths-7.c
index def592d..6ed2f51 100644
--- a/gcc/testsuite/gcc.dg/analyzer/paths-7.c
+++ b/gcc/testsuite/gcc.dg/analyzer/paths-7.c
@@ -23,8 +23,8 @@ int test (int flag, void *ptr, int *p, int n)
     sum += foo (p[i]); /* { dg-bogus "uninitialized" } */
   result = sum;
 
-  __analyzer_dump_exploded_nodes (0); /* { dg-warning "7 exploded nodes" } */
-  // FIXME: why 7 here?
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "6 exploded nodes" } */
+  // FIXME: why 6 here?
 
   if (flag)
     free (ptr); /* { dg-warning "double-free of 'ptr'" } */
@@ -52,8 +52,8 @@ int test_2 (int flag, int *p, int n)
     sum += foo (p[i]); /* { dg-bogus "uninitialized" } */
   result = sum;
 
-  __analyzer_dump_exploded_nodes (0); /* { dg-warning "6 exploded nodes" } */
-  // FIXME: why 6 here?
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "5 exploded nodes" } */
+  // FIXME: why 5 here?
 
   free (ptr); /* { dg-warning "double-free of 'ptr'" } */
   return result;
diff --git a/gcc/testsuite/gcc.dg/analyzer/zlib-1.c b/gcc/testsuite/gcc.dg/analyzer/zlib-1.c
index 5adb8f3..5f34f16 100644
--- a/gcc/testsuite/gcc.dg/analyzer/zlib-1.c
+++ b/gcc/testsuite/gcc.dg/analyzer/zlib-1.c
@@ -35,7 +35,7 @@ int deflateEnd(z_stream *strm)
     return (-2);
   }
 
-  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "4 exploded nodes" } */
   __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
 
   if (strm->state->pending_buf)
-- 
1.8.5.3

