From f632395f702b458980fc83bb80d9cd8fe3232e74 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Wed, 3 Jul 2019 11:32:55 -0400
Subject: [PATCH 60/63] FIXME: further WIP on region merging

---
 gcc/analyzer/region-model.cc | 247 ++++++++++++++++++++++++++++++++++++++++---
 gcc/analyzer/region-model.h  |  48 +++++++--
 2 files changed, 271 insertions(+), 24 deletions(-)

diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index 34bbdb9..b264dd5 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -1082,7 +1082,11 @@ POP_IGNORE_WFORMAT
     }
 }
 
-/* FIXME.  */
+/* Look for a child region for EXPR within this map_region.
+   If it doesn't already exist, create a child map_region, using TYPE for
+   its type.
+   Return the region_id of the child (whether pre-existing, or
+   newly-created).  */
 
 region_id
 map_region::get_or_create (region_model *model,
@@ -1155,6 +1159,84 @@ map_region::get_tree_for_child_region (region_id child_rid) const
   return NULL_TREE;
 }
 
+/* Attempt to merge MAP_REGION_A and MAP_REGION_B into MERGED_MAP_REGION,
+   which has region_id MERGED_RID, using MERGER.
+   Return true if the merger is possible, false otherwise.  */
+
+bool
+map_region::can_merge_p (const map_region *map_region_a,
+			 const map_region *map_region_b,
+			 map_region *merged_map_region,
+			 region_id merged_rid,
+			 const model_merger &merger)
+{
+  // TODO:
+  for (map_t::iterator iter = map_region_a->m_map.begin ();
+       iter != map_region_a->m_map.end ();
+       ++iter)
+    {
+      tree key_a = (*iter).first;
+      region_id rid_a = (*iter).second;
+
+      if (const region_id *slot_b
+	  = const_cast<map_region *>(map_region_b)->m_map.get (key_a))
+	{
+	  region_id rid_b = *slot_b;
+
+	  /* FIXME: assume that they too are map regions.
+	     We want to recurse.
+	     We also want to do something about values.  */
+	  map_region *child_region_a
+	    = merger.get_region_a <map_region> (rid_a);
+	  map_region *child_region_b
+	    = merger.get_region_b <map_region> (rid_b);
+
+	  gcc_assert (child_region_a->get_type ()
+		      == child_region_b->get_type ());
+
+	  region_id child_merged_rid
+	    = merged_map_region->get_or_create (merger.m_merged_model,
+						merged_rid,
+						key_a,
+						child_region_a->get_type ());
+
+	  map_region *child_merged_region
+	    = merger.m_merged_model->get_region<map_region> (child_merged_rid);
+
+	  /* TODO: consider values.  */
+	  svalue_id child_a_sid = child_region_a->get_value_direct ();
+	  svalue_id child_b_sid = child_region_b->get_value_direct ();
+	  svalue_id child_merged_sid;
+	  if (!merger.can_merge_values_p (child_a_sid, child_b_sid,
+					  &child_merged_sid))
+	    return false;
+	  if (!child_merged_sid.null_p ())
+	    child_merged_region->set_value (*merger.m_merged_model,
+					    child_merged_sid);
+
+	  /* Recurse.  */
+	  if (!can_merge_p (child_region_a,
+			    child_region_b,
+			    child_merged_region,
+			    child_merged_rid,
+			    merger))
+	    return false;
+	}
+      else
+	{
+	  /* region is present in A, but absent in B.  */
+	  // TODO
+	}
+
+      // TODO
+    }
+
+  // TODO: should probably also check for things in b that aren't in a.
+
+  return true;
+}
+
+
 /* FIXME.  */
 
 bool
@@ -1347,12 +1429,13 @@ stack_region::remap_region_ids (const region_id_map &map)
     map.update (&m_frame_rids[i]);
 }
 
-/* FIXME.  */
+/* Attempt to merge STACK_REGION_A and STACK_REGION_B using MERGER.
+   Return true if the merger is possible, false otherwise.  */
 
 bool
 stack_region::can_merge_p (const stack_region *stack_region_a,
 			   const stack_region *stack_region_b,
-			   const region_merger &merger)
+			   const model_merger &merger)
 {
   if (stack_region_a->get_num_frames ()
       != stack_region_b->get_num_frames ())
@@ -1361,16 +1444,23 @@ stack_region::can_merge_p (const stack_region *stack_region_a,
   for (unsigned i = 0; i < stack_region_a->get_num_frames (); i++)
     {
       region_id rid_a = stack_region_a->get_frame_rid (i);
-      frame_region *frame_a
-	= merger.m_model_a->get_region <frame_region> (rid_a);
+      frame_region *frame_a = merger.get_region_a <frame_region> (rid_a);
 
       region_id rid_b = stack_region_b->get_frame_rid (i);
-      frame_region *frame_b
-	= merger.m_model_b->get_region <frame_region> (rid_b);
+      frame_region *frame_b = merger.get_region_b <frame_region> (rid_b);
 
       if (frame_a->get_function () != frame_b->get_function ())
 	return false;
       merger.m_merged_model->push_frame (frame_a->get_function (), NULL);
+
+      region_id rid_merged = merger.m_merged_model->get_current_frame_id ();
+      frame_region *merged_frame
+	= merger.m_merged_model->get_region <frame_region> (rid_merged);
+
+      if (!map_region::can_merge_p (frame_a, frame_b,
+				    merged_frame, rid_merged,
+				    merger))
+	return false;
     }
 
   return true;
@@ -1647,13 +1737,15 @@ root_region::remap_region_ids (const region_id_map &map)
   map.update (&m_heap_rid);
 }
 
-/* FIXME.  */
+/* Attempt to merge ROOT_REGION_A and ROOT_REGION_B into
+   MERGED_ROOT_REGION using MERGER.
+   Return true if the merger is possible, false otherwise.  */
 
 bool
 root_region::can_merge_p (const root_region *root_region_a,
 			  const root_region *root_region_b,
 			  root_region *merged_root_region,
-			  const region_merger &merger)
+			  const model_merger &merger)
 {
   /* We can only merge if the stacks are sufficiently similar.  */
   stack_region *stack_a = root_region_a->get_stack_region (merger.m_model_a);
@@ -1674,7 +1766,15 @@ root_region::can_merge_p (const root_region *root_region_a,
   map_region *globals_b = root_region_b->get_globals_region (merger.m_model_b);
   if (globals_a && globals_b)
     {
-      // FIXME: attempt to merge, or fail
+      /* If both models have globals regions, attempt to merge them.  */
+      region_id merged_globals_rid
+	= merged_root_region->ensure_globals_region (merger.m_merged_model);
+      map_region *merged_globals
+	= merged_root_region->get_globals_region (merger.m_merged_model);
+      if (!map_region::can_merge_p (globals_a, globals_b,
+				    merged_globals, merged_globals_rid,
+				    merger))
+	return false;
     }
   /* otherwise, merge as "no globals".  */
 
@@ -3664,7 +3764,7 @@ region_model::can_merge_with_p (const region_model &other_model,
 #if 1
 #if 1
   region_model result;
-  region_merger merger (this, &other_model, &result);
+  model_merger merger (this, &other_model, &result);
 
   if (!root_region::can_merge_p (get_root_region (),
 				 other_model.get_root_region (),
@@ -3767,6 +3867,97 @@ region_model::can_merge_with_p (const region_model &other_model,
   return true;
 }
 
+////////////////////////////////////////////////////////////////////////////
+
+/* struct model_merger.  */
+
+/* Dump a multiline representation of this merger to PP.  */
+
+void
+model_merger::dump_to_pp (pretty_printer *pp) const
+{
+  pp_string (pp, "model A:");
+  pp_newline (pp);
+  m_model_a->dump_to_pp (pp);
+  pp_newline (pp);
+
+  pp_string (pp, "model B:");
+  pp_newline (pp);
+  m_model_b->dump_to_pp (pp);
+  pp_newline (pp);
+
+  pp_string (pp, "merged model:");
+  pp_newline (pp);
+  m_merged_model->dump_to_pp (pp);
+  pp_newline (pp);
+}
+
+/* Dump a multiline representation of this merger to FILE.  */
+
+void
+model_merger::dump (FILE *fp) const
+{
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  pp_show_color (&pp) = pp_show_color (global_dc->printer);
+  pp.buffer->stream = fp;
+  dump_to_pp (&pp);
+  pp_flush (&pp);
+}
+
+/* Dump a multiline representation of this merger to stderr.  */
+
+DEBUG_FUNCTION void
+model_merger::dump () const
+{
+  dump (stderr);
+}
+
+/* Attempt to merge the svalues of SID_A and SID_B (from their
+   respective models), writing the id of the resulting svalue
+   into *MERGED_SID.
+   Return true if the merger is possible, false otherwise.  */
+
+bool
+model_merger::can_merge_values_p (svalue_id sid_a,
+				  svalue_id sid_b,
+				  svalue_id *merged_sid) const
+{
+  gcc_assert (merged_sid);
+  svalue *sval_a = m_model_a->get_svalue (sid_a);
+  svalue *sval_b = m_model_b->get_svalue (sid_b);
+
+  /* If both are NULL, then the "values" are trivially mergeable.  */
+  if (!sval_a && !sval_b)
+    return true;
+
+  // FIXME:
+  /* If one is NULL and the other non-NULL, then the "values"
+     are not mergeable.  */
+  if (!(sval_a && sval_b))
+    return false;
+
+  gcc_assert (sval_a->get_type () == sval_b->get_type ());
+
+  /* If one of the values is unknown, then we can merge,
+     using an unknown value.  */
+  if (sval_a->get_kind () == SK_UNKNOWN
+      || sval_b->get_kind () == SK_UNKNOWN)
+    {
+      /* FIXME: this loses information about unknown values
+	 that are shared between multiple regions.  */
+      svalue *merged_sval = new unknown_svalue (sval_a->get_type ());
+      *merged_sid = m_merged_model->add_svalue (merged_sval);
+      return true;
+    }
+
+  // TODO: what about other kinds of value?
+
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
 /* FIXME.  */
 
 void
@@ -3783,6 +3974,8 @@ inchash::add (region_id rid, inchash::hash &hstate)
   hstate.add_int (rid.as_int ());
 }
 
+////////////////////////////////////////////////////////////////////////////
+
 // FIXME:
 
 DEBUG_FUNCTION void
@@ -3791,6 +3984,8 @@ debug (const region_model &rmodel)
   fprintf (stderr, "%s\n", rmodel.to_str ().c_str ());
 }
 
+////////////////////////////////////////////////////////////////////////////
+
 #if CHECKING_P
 
 namespace selftest {
@@ -4284,10 +4479,30 @@ test_state_merging ()
     /* 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
+    /* In particular, there should be an unknown value for "a".  */
+    svalue *merged_a_sval = merged.get_svalue (merged.get_rvalue (a, &ctxt));
+    ASSERT_EQ (merged_a_sval->get_kind (), SK_UNKNOWN);
+  }
+
+  /* Verify handling of a global.  */
+  {
+    test_region_model_context ctxt;
+    region_model model0;
+    region_model model1;
+    model0.set_to_new_unknown_value (model0.get_lvalue (x, &ctxt),
+				     integer_type_node);
+    model1.set_to_new_unknown_value (model1.get_lvalue (x, &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));
+    ASSERT_EQ (model0, merged);
+    /* In particular, there should be an unknown value for "x".  */
+    svalue *merged_a_sval = merged.get_svalue (merged.get_rvalue (x, &ctxt));
+    ASSERT_EQ (merged_a_sval->get_kind (), SK_UNKNOWN);
   }
 
 #if 0
@@ -4313,7 +4528,9 @@ test_state_merging ()
 
   // TODO: what can't we merge? need at least one such test
 
-  // TODO: various things
+  /* TODO: various things
+     - heap regions
+   */
 }
 
 /* Run all of the selftests within this file.  */
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index bd5e2ed..f9d8a57 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -38,7 +38,7 @@ class svalue;
 class region;
 class region_model;
 class region_model_context;
-struct region_merger;
+struct model_merger;
 class pending_diagnostic;
 
 class constraint_manager;
@@ -482,7 +482,10 @@ public:
 
  protected:
   region (region_id parent_rid, svalue_id sval_id, tree type)
-  : m_parent_rid (parent_rid), m_sval_id (sval_id), m_type (type) {}
+  : m_parent_rid (parent_rid), m_sval_id (sval_id), m_type (type)
+  {
+    gcc_assert (type == NULL_TREE || TYPE_P (type));
+  }
 
   region (const region &other)
   : m_parent_rid (other.m_parent_rid), m_sval_id (other.m_sval_id),
@@ -539,6 +542,12 @@ public:
 
   tree get_tree_for_child_region (region_id child_rid) const;
 
+  static bool can_merge_p (const map_region *map_region_a,
+			   const map_region *map_region_b,
+			   map_region *merged_map_region,
+			   region_id merged_rid,
+			   const model_merger &merger);
+
  protected:
   void add_to_hash (inchash::hash &hstate) const OVERRIDE;
   void print_fields (const region_model &model,
@@ -622,7 +631,7 @@ public:
 
   static bool can_merge_p (const stack_region *stack_region_a,
 			   const stack_region *stack_region_b,
-			   const region_merger &merger);
+			   const model_merger &merger);
 
  private:
   void add_to_hash (inchash::hash &hstate) const FINAL OVERRIDE;
@@ -694,7 +703,7 @@ public:
   static bool can_merge_p (const root_region *root_region_a,
 			   const root_region *root_region_b,
 			   root_region *merged_root_region,
-			   const region_merger &merger);
+			   const model_merger &merger);
 
 private:
   void add_to_hash (inchash::hash &hstate) const FINAL OVERRIDE;
@@ -942,17 +951,38 @@ class region_model_context
 
 };
 
-/* FIXME.  */
+/* A bundle of data for use when attempting to merge two region_model
+   instances to make a third.  */
 
-struct region_merger
+struct model_merger
 {
-  region_merger (const region_model *model_a,
-		 const region_model *model_b,
-		 region_model *merged_model)
+  model_merger (const region_model *model_a,
+		const region_model *model_b,
+		region_model *merged_model)
   : m_model_a (model_a), m_model_b (model_b),
     m_merged_model (merged_model)
   {}
 
+  void dump_to_pp (pretty_printer *pp) const;
+  void dump (FILE *fp) const;
+  void dump () const;
+
+  template <typename Subclass>
+  Subclass *get_region_a (region_id rid_a) const
+  {
+    return m_model_a->get_region <Subclass> (rid_a);
+  }
+
+  template <typename Subclass>
+  Subclass *get_region_b (region_id rid_b) const
+  {
+    return m_model_b->get_region <Subclass> (rid_b);
+  }
+
+  bool can_merge_values_p (svalue_id sid_a,
+			   svalue_id sid_b,
+			   svalue_id *merged_sid) const;
+
   const region_model *m_model_a;
   const region_model *m_model_b;
   region_model *m_merged_model;
-- 
1.8.5.3

