From 4a40bbe8af13ab58716fdf6698e7e67d8e057a7c Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Fri, 24 May 2019 09:18:58 -0400
Subject: [PATCH 66/67] FIXME: 2019-05-24 work on region model

---
 gcc/analyzer/constraint-manager.cc              |  25 +-
 gcc/analyzer/constraint-manager.h               |   6 +-
 gcc/analyzer/impl-4.cc                          | 138 +++++-
 gcc/analyzer/region-model.cc                    | 554 ++++++++++++++++++++----
 gcc/analyzer/region-model.h                     | 140 ++++--
 gcc/function-tests.c                            |   4 +-
 gcc/selftest.h                                  |   7 +
 gcc/testsuite/gcc.dg/analyzer/malloc-1.c        |  15 +
 gcc/testsuite/gcc.dg/analyzer/malloc-dedupe-1.c |   3 +-
 notes.txt                                       |   5 +
 10 files changed, 757 insertions(+), 140 deletions(-)

diff --git a/gcc/analyzer/constraint-manager.cc b/gcc/analyzer/constraint-manager.cc
index 26325e9..5685c48 100644
--- a/gcc/analyzer/constraint-manager.cc
+++ b/gcc/analyzer/constraint-manager.cc
@@ -217,7 +217,7 @@ equiv_class::get_representative () const
 }
 
 void
-equiv_class::remap_svalues (const svalue_id_map &map)
+equiv_class::remap_svalue_ids (const svalue_id_map &map)
 {
   int i;
   svalue_id *iv;
@@ -1069,13 +1069,11 @@ constraint_manager::calc_escaped_p (svalue_id pv)
 /* Delete any information about svalue_id instances identified by P.
    Such instances are removed from equivalence classes, and any
    redundant ECs and constraints are also removed.
-   Return the number of svalue_ids that are purged.  */
+   Accumulate stats into STATS.  */
 
-int
-constraint_manager::purge (const purge_criteria &p)
+void
+constraint_manager::purge (const purge_criteria &p, purge_stats *stats)
 {
-  int num_purged = 0;
-
   /* Delete any svalue_ids identified by P within the various equivalence
      classes.  */
   for (unsigned ec_idx = 0; ec_idx < m_equiv_classes.length (); )
@@ -1091,7 +1089,6 @@ constraint_manager::purge (const purge_criteria &p)
 	    continue;
 	  if (p.should_purge_p (*pv))
 	    {
-	      num_purged++;
 	      if (ec->del (*pv))
 		if (!ec->m_constant)
 		  delete_ec = true;
@@ -1102,6 +1099,7 @@ constraint_manager::purge (const purge_criteria &p)
 	{
 	  delete ec;
 	  m_equiv_classes.ordered_remove (ec_idx);
+	  stats->m_num_equiv_classes++;
 
 	  /* Update the constraints, potentially removing some.  */
 	  for (unsigned con_idx = 0; con_idx < m_constraints.length (); )
@@ -1113,6 +1111,7 @@ constraint_manager::purge (const purge_criteria &p)
 		  || c->m_rhs == ec_idx)
 		{
 		  m_constraints.ordered_remove (con_idx);
+		  stats->m_num_constraints++;
 		}
 	      else
 		{
@@ -1135,7 +1134,10 @@ constraint_manager::purge (const purge_criteria &p)
       constraint *c = &m_constraints[con_idx];
       if (m_equiv_classes[c->m_lhs.m_idx]->m_vars.length () == 0
 	  && m_equiv_classes[c->m_rhs.m_idx]->m_vars.length () == 0)
-	m_constraints.ordered_remove (con_idx);
+	{
+	  m_constraints.ordered_remove (con_idx);
+	  stats->m_num_constraints++;
+	}
       else
 	{
 	  con_idx++;
@@ -1165,6 +1167,7 @@ constraint_manager::purge (const purge_criteria &p)
 	    {
 	      delete ec;
 	      m_equiv_classes.ordered_remove (ec_idx);
+	      stats->m_num_equiv_classes++;
 
 	      /* Renumber constraints that refer to ECs that have
 		 had their idx changed.  */
@@ -1182,19 +1185,17 @@ constraint_manager::purge (const purge_criteria &p)
     }
 
   validate ();
-
-  return num_purged;
 }
 
 /* FIXME.  */
 
 void
-constraint_manager::remap_svalues (const svalue_id_map &map)
+constraint_manager::remap_svalue_ids (const svalue_id_map &map)
 {
   int i;
   equiv_class *ec;
   FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
-    ec->remap_svalues (map);
+    ec->remap_svalue_ids (map);
 }
 
 /* FIXME.  */
diff --git a/gcc/analyzer/constraint-manager.h b/gcc/analyzer/constraint-manager.h
index 2e07dc3..5f0d3ca 100644
--- a/gcc/analyzer/constraint-manager.h
+++ b/gcc/analyzer/constraint-manager.h
@@ -88,7 +88,7 @@ public:
 
   svalue_id get_representative () const;
 
-  void remap_svalues (const svalue_id_map &map);
+  void remap_svalue_ids (const svalue_id_map &map);
 
   /* An equivalence class can contain at most one constant.  */
   tree m_constant;
@@ -225,9 +225,9 @@ public:
   void recursively_mark_as_escaping (equiv_class_id ec_id);
   bool calc_escaped_p (svalue_id sid);
 
-  int purge (const purge_criteria &p);
+  void purge (const purge_criteria &p, purge_stats *stats);
 
-  void remap_svalues (const svalue_id_map &map);
+  void remap_svalue_ids (const svalue_id_map &map);
 
   void validate () const;
 
diff --git a/gcc/analyzer/impl-4.cc b/gcc/analyzer/impl-4.cc
index 90a9e97..1aff8f6 100644
--- a/gcc/analyzer/impl-4.cc
+++ b/gcc/analyzer/impl-4.cc
@@ -682,6 +682,9 @@ public:
 	      pretty_printer *pp) const;
   std::string to_str (const extrinsic_state &ext_state) const;
 
+  void dump (const extrinsic_state &ext_state,
+	     pretty_printer *pp) const;
+
   psn::equiv_class &get_ec (path_var pv)
   {
     psn::equiv_class_id ec_id = m_path_state.get_or_add_equiv_class (pv);
@@ -751,6 +754,10 @@ class exploded_node : public dnode
   void dump_dot (graphviz_out *gv) const FINAL OVERRIDE;
   void dump_dot_id (pretty_printer *pp) const;
 
+  void dump_to_pp (pretty_printer *pp) const;
+  void dump (FILE *fp) const;
+  void dump () const;
+
   program_state on_stmt (exploded_graph &eg) const;
   bool on_edge (exploded_graph &eg,
 		const superedge *succ,
@@ -2317,7 +2324,7 @@ program_state::operator== (const program_state &other) const
   return true;
 }
 
-/* FIXME.  */
+/* Print a compact representation of this state to PP.  */
 
 void
 program_state::print (const extrinsic_state &ext_state,
@@ -2357,6 +2364,35 @@ program_state::to_str (const extrinsic_state &ext_state) const
   return std::string (pp_formatted_text (&pp));
 }
 
+/* Dump a multiline representation of this state to PP.  */
+
+void
+program_state::dump (const extrinsic_state &ext_state,
+		     pretty_printer *pp) const
+{
+  // FIXME: multiline format?
+  pp_printf (pp, "ps: %s", m_path_state.to_str ().c_str ());
+  pp_newline (pp);
+
+  if (m_region_model)
+    {
+      pp_printf (pp, "rmodel: ");
+      m_region_model->dump_to_pp (pp);
+    }
+
+  int i;
+  sm_state_map<path_var> *smap;
+  FOR_EACH_VEC_ELT (m_checker_states, i, smap)
+    {
+      if (!smap->is_empty_p ())
+	{
+	  pp_printf (pp, "%s: ", ext_state.get_name (i));
+	  smap->print (ext_state.get_sm (i), pp);
+	  pp_newline (pp);
+	}
+    }
+}
+
 /* Implementation of purge_criteria for using state_purge_map.
 
    Purge state within a path_state using PURGE_MAP for POINT.
@@ -2466,8 +2502,58 @@ program_state::prune_for_point (exploded_graph &eg,
       if (eg.get_logger ())
 	new_state.m_region_model->dump_to_pp (eg.get_logger_pp ());
 #endif
-      int num_svals_purged = new_state.m_region_model->purge_svalues ();
-      eg.log ("num_svals_purged: %i", num_svals_purged);
+      purge_stats stats;
+
+      region_id_set purgeable_ssa_regions (new_state.m_region_model);
+      region_id frame_rid = new_state.m_region_model->get_current_frame_id ();
+      frame_region *frame
+	= new_state.m_region_model->get_region <frame_region>(frame_rid);
+      const state_purge_map *pm = eg.get_purge_map ();
+      if (pm)
+	{
+	  /* FIXME: move to a member of region_model, once the state_purge_map
+	     etc decls are visible there.  */
+	  auto_vec<tree> ssa_to_purge;
+	  for (frame_region::map_t::iterator iter = frame->m_map.begin ();
+	       iter != frame->m_map.end ();
+	       ++iter)
+	    {
+	      tree var = (*iter).first;
+	      region_id rid = (*iter).second;
+	      if (TREE_CODE (var) == SSA_NAME)
+		{
+		  const state_purge_per_ssa_name &per_ssa
+		    = pm->get_data_for_ssa_name (var);
+		  if (!per_ssa.needed_at_point_p (flat_point))
+		    {
+		      purgeable_ssa_regions.add_region (rid);
+		      ssa_to_purge.safe_push (var);
+		      eg.log ("would purge RID: %i for %qE", rid.as_int (), var);
+		      // FIXME: we also need to remove the region from the map
+		      // note that we're mid-traversal
+		    }
+		}
+	    }
+
+#if 0
+	  int i;
+	  tree var;
+	  FOR_EACH_VEC_ELT (ssa_to_purge, i, var)
+	    frame->unbind (var);
+
+	  // TODO: actually purge the regions
+	  new_state.m_region_model->purge_regions (purgeable_ssa_regions,
+						   &stats);
+#endif
+	}
+
+      /* Purge unused svalues.  */
+      new_state.m_region_model->purge_svalues (&stats);
+
+      eg.log ("num svalues purged: %i", stats.m_num_svalues);
+      eg.log ("num regions purged: %i", stats.m_num_regions);
+      eg.log ("num equiv_classes purged: %i", stats.m_num_equiv_classes);
+      eg.log ("num constraints purged: %i", stats.m_num_constraints);
     }
 
   return new_state;
@@ -2614,6 +2700,43 @@ exploded_node::dump_dot_id (pretty_printer *pp) const
   pp_printf (pp, "exploded_node_%i", m_index);
 }
 
+/* Dump a multiline representation of this node to PP.  */
+
+void
+exploded_node::dump_to_pp (pretty_printer *pp) const
+{
+  pp_printf (pp, "EN: %i", m_index);
+  pp_newline (pp);
+
+  format f (true);
+  m_ps.m_point.print (pp, f);
+  pp_newline (pp);
+
+  m_ps.m_state.dump (m_ext_state, pp);
+  pp_newline (pp);
+}
+
+/* Dump a multiline representation of this node to FILE.  */
+
+void
+exploded_node::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 node to stderr.  */
+
+DEBUG_FUNCTION void
+exploded_node::dump () const
+{
+  dump (stderr);
+}
+
 /* FIXME.  */
 // is there a pre-canned way to do this?
 
@@ -2764,6 +2887,12 @@ exploded_node::on_stmt (exploded_graph &eg) const
 	  inform (stmt->location, "__dump_constraints: %s",
 		  new_ps->to_str ().c_str ());
 	}
+      else if (is_named_call_p (call, "__analyzer_dump", 0))
+	{
+	  /* Handle the builtin "__analyzer_dump" by dumping state
+	     to stderr.  */
+	  dump ();
+	}
       else if (is_named_call_p (call, "__analyzer_eval", 1))
 	{
 	  /* Handle the builtin "__analyzer_eval" by evaluating the input
@@ -4328,7 +4457,8 @@ exploded_graph::process_worklist ()
       if (m_global_stats.m_num_nodes[PK_AFTER_SUPERNODE]> m_sg.num_nodes () * 5)
 	{
 	  log ("bailing out; too many nodes");
-	  warning (0, "analysis bailed out early");
+	  warning (0, "analysis bailed out early (%i nodes)",
+		   m_global_stats.m_num_nodes[PK_AFTER_SUPERNODE]);
 	  // TODO: better location??
 	  return;
 	}
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index b214d17..57ce0c4 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -127,25 +127,49 @@ svalue_id::to_str () const
 
 ////////////////////////////////////////////////////////////////////////////
 
-/* class svalue_id_map.  */
+/* FIXME.  */
+
+void
+region_id::print (pretty_printer *pp) const
+{
+  if (null_p ())
+    pp_printf (pp, "null");
+  else
+    pp_printf (pp, "r%i", m_idx);
+}
+
+/* FIXME.  */
+
+void
+region_id::dump_node_name_to_pp (pretty_printer *pp) const
+{
+  gcc_assert (!null_p ());
+  pp_printf (pp, "region_%i", m_idx);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class id_map.  */
 
 /* FIXME.  */
 
-svalue_id_map::svalue_id_map (int num_svalues)
+template <typename T>
+id_map<T>::id_map (int num_svalues)
 : m_src_to_dst (num_svalues),
   m_dst_to_src (num_svalues)
 {
   for (int i = 0; i < num_svalues; i++)
     {
-      m_src_to_dst.quick_push (svalue_id::null ());
-      m_dst_to_src.quick_push (svalue_id::null ());
+      m_src_to_dst.quick_push (T::null ());
+      m_dst_to_src.quick_push (T::null ());
     }
 }
 
 /* FIXME.  */
 
+template <typename T>
 void
-svalue_id_map::put (svalue_id src, svalue_id dst)
+id_map<T>::put (T src, T dst)
 {
   m_src_to_dst[src.as_int ()] = dst;
   m_dst_to_src[dst.as_int ()] = src;
@@ -153,8 +177,9 @@ svalue_id_map::put (svalue_id src, svalue_id dst)
 
 /* FIXME.  */
 
-svalue_id
-svalue_id_map::get_dst_for_src (svalue_id src) const
+template <typename T>
+T
+id_map<T>::get_dst_for_src (T src) const
 {
   if (src.null_p ())
     return src;
@@ -163,8 +188,9 @@ svalue_id_map::get_dst_for_src (svalue_id src) const
 
 /* FIXME.  */
 
-svalue_id
-svalue_id_map::get_src_for_dst (svalue_id dst) const
+template <typename T>
+T
+id_map<T>::get_src_for_dst (T dst) const
 {
   if (dst.null_p ())
     return dst;
@@ -173,17 +199,18 @@ svalue_id_map::get_src_for_dst (svalue_id dst) const
 
 /* FIXME.  */
 
+template <typename T>
 void
-svalue_id_map::dump_to_pp (pretty_printer *pp) const
+id_map<T>::dump_to_pp (pretty_printer *pp) const
 {
   pp_string (pp, "src to dst: {");
   unsigned i;
-  svalue_id *dst;
+  T *dst;
   FOR_EACH_VEC_ELT (m_src_to_dst, i, dst)
     {
       if (i > 0)
 	pp_string (pp, ", ");
-      svalue_id src (svalue_id::from_int (i));
+      T src (T::from_int (i));
       src.print (pp);
       pp_string (pp, " -> ");
       dst->print (pp);
@@ -192,12 +219,12 @@ svalue_id_map::dump_to_pp (pretty_printer *pp) const
   pp_newline (pp);
 
   pp_string (pp, "dst to src: {");
-  svalue_id *src;
+  T *src;
   FOR_EACH_VEC_ELT (m_dst_to_src, i, src)
     {
       if (i > 0)
 	pp_string (pp, ", ");
-      svalue_id dst (svalue_id::from_int (i));
+      T dst (T::from_int (i));
       dst.print (pp);
       pp_string (pp, " <- ");
       src->print (pp);
@@ -208,8 +235,9 @@ svalue_id_map::dump_to_pp (pretty_printer *pp) const
 
 /* FIXME.  */
 
+template <typename T>
 DEBUG_FUNCTION void
-svalue_id_map::dump () const
+id_map<T>::dump () const
 {
   pretty_printer pp;
   pp.buffer->stream = stderr;
@@ -217,26 +245,24 @@ svalue_id_map::dump () const
   pp_flush (&pp);
 }
 
-////////////////////////////////////////////////////////////////////////////
-
 /* FIXME.  */
 
+template <typename T>
 void
-region_id::print (pretty_printer *pp) const
+id_map<T>::update (T &id) const
 {
-  if (null_p ())
-    pp_printf (pp, "null");
-  else
-    pp_printf (pp, "r%i", m_idx);
+  id = get_dst_for_src (id);
 }
 
-/* FIXME.  */
+////////////////////////////////////////////////////////////////////////////
 
-void
-region_id::dump_node_name_to_pp (pretty_printer *pp) const
+/* class id_set.  */
+
+template<>
+id_set<region_id>::id_set (const region_model *model)
+: m_bitmap (model->m_regions.length ())
 {
-  gcc_assert (!null_p ());
-  pp_printf (pp, "region_%i", m_idx);
+  bitmap_clear (m_bitmap);
 }
 
 ////////////////////////////////////////////////////////////////////////////
@@ -359,6 +385,14 @@ svalue::to_str () const
 }
 #endif
 
+/* FIXME.  */
+
+void
+svalue::remap_region_ids (const region_id_map &map)
+{
+  /* Empty.  */
+}
+
 /* class region_svalue : public svalue.  */
 
 /* FIXME.  */
@@ -413,6 +447,14 @@ region_svalue::dump_dot_to_pp (const region_model &model,
   }
 }
 
+/* FIXME.  */
+
+void
+region_svalue::remap_region_ids (const region_id_map &map)
+{
+  m_rid = map.get_dst_for_src (m_rid);
+}
+
 /* class constant_svalue : public svalue.  */
 
 /* FIXME.  */
@@ -494,6 +536,8 @@ poison_kind_to_str (enum poison_kind kind)
       return "uninit";
     case POISON_KIND_FREED:
       return "freed";
+    case POISON_KIND_POPPED_STACK:
+      return "popped stack";
     }
 }
 
@@ -830,7 +874,7 @@ region::to_str () const
 /* FIXME.  */
 
 void
-region::remap_svalues (const svalue_id_map &map)
+region::remap_svalue_ids (const svalue_id_map &map)
 {
   m_sval_id = map.get_dst_for_src (m_sval_id);
 }
@@ -838,6 +882,14 @@ region::remap_svalues (const svalue_id_map &map)
 /* FIXME.  */
 
 void
+region::remap_region_ids (const region_id_map &map)
+{
+  map.update (m_parent_rid);
+}
+
+/* FIXME.  */
+
+void
 region::add_to_hash (inchash::hash &hstate) const
 {
   inchash::add (m_parent_rid, hstate);
@@ -1017,6 +1069,32 @@ map_region::add_to_hash (inchash::hash &hstate) const
 
 /* FIXME.  */
 
+void
+map_region::remap_region_ids (const region_id_map &map)
+{
+  region::remap_region_ids (map);
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      tree expr = (*iter).first;
+      region_id child_rid = (*iter).second;
+      m_map.put (expr, map.get_dst_for_src (child_rid));
+    }
+}
+
+/* Remove the binding of EXPR to its child region (but not the
+   child region itself).
+   For use when purging unneeded SSA names.  */
+
+void
+map_region::unbind (tree expr)
+{
+  m_map.remove (expr);
+}
+
+/* FIXME.  */
+
 bool
 frame_region::compare_fields (const frame_region &other) const
 {
@@ -1136,16 +1214,68 @@ stack_region::get_current_frame_id () const
     return region_id::null ();
 }
 
-/* FIXME.  */
+/* Pop the topmost frame_region from this stack.
 
-void
-stack_region::pop_frame (region_model *model)
+   Purge the frame region and all its descendent regions.
+   Convert any pointers that point into such regions into
+   POISON_KIND_POPPED_STACK svalues.
+
+   Return the ID of any return value from the frame.
+
+   Purge all unused svalues, with the exception of any
+   return value for the frame, which is temporarily
+   preserved in case no regions reference it, so it can
+   be written into a region in the caller.
+
+   Accumulate stats on purged entities into STATS.  */
+
+svalue_id
+stack_region::pop_frame (region_model *model, purge_stats *stats)
 {
   gcc_assert (m_frame_rids.length () > 0);
-  region_id frame_id = m_frame_rids.pop ();
 
-  // TODO: poison all regions in the frame
-  //gcc_unreachable ();
+  region_id frame_rid = get_current_frame_id ();
+  frame_region *frame = model->get_region<frame_region> (frame_rid);
+
+  /* Evaluate the result, within the callee frame.  */
+  svalue_id result_sid;
+  tree fndecl = frame->get_function ()->decl;
+  tree result = DECL_RESULT (fndecl);
+  if (result)
+    result_sid = model->get_rvalue (result);
+
+  /* Pop the frame.  */
+  m_frame_rids.pop ();
+
+  // FIXME: maybe make the rest be a member of region_model
+
+  /* Find all child and descendent regions.  */
+  region_id_set descendents (model);
+  model->get_descendents (frame_rid, &descendents);
+
+  /* Find any pointers to such regions; convert to poisoned.  */
+  int i;
+  svalue *sval;
+  FOR_EACH_VEC_ELT (model->m_svalues, i, sval)
+    if (region_svalue *ptr_sval = sval->dyn_cast_region_svalue ())
+      {
+	region_id ptr_dst = ptr_sval->get_pointee ();
+	if (!ptr_dst.null_p ())
+	  if (descendents.region_p (ptr_dst))
+	    model->replace_svalue
+	      (svalue_id::from_int (i),
+	       new poisoned_svalue (POISON_KIND_POPPED_STACK));
+      }
+
+  /* Delete all such regions.  */
+  model->purge_regions (descendents, stats);
+
+  /* Delete unused svalues, but don't delete the return value.  */
+  model->purge_svalues (stats, &result_sid);
+
+  model->validate ();
+
+  return result_sid;
 }
 
 /* FIXME.  */
@@ -1161,6 +1291,18 @@ stack_region::add_to_hash (inchash::hash &hstate) const
     inchash::add (*frame_rid, hstate);
 }
 
+/* FIXME.  */
+
+void
+stack_region::remap_region_ids (const region_id_map &map)
+{
+  region::remap_region_ids (map);
+  int i;
+  region_id *frame_rid;
+  FOR_EACH_VEC_ELT (m_frame_rids, i, frame_rid)
+    map.update (m_frame_rids[i]);
+}
+
 /* class heap_region : public region.  */
 
 /* FIXME.  */
@@ -1258,7 +1400,7 @@ root_region::dump_child_label (const region_model &model ATTRIBUTE_UNUSED,
 
 /* FIXME.  */
 
-void
+region_id
 root_region::push_frame (region_model *model, function *fun,
 			 vec<svalue_id> *arg_sids)
 {
@@ -1329,6 +1471,8 @@ root_region::push_frame (region_model *model, function *fun,
 
   stack_region *stack = model->get_region <stack_region> (m_stack_rid);
   stack->push_frame (frame_rid);
+
+  return frame_rid;
 }
 
 /* FIXME.  */
@@ -1345,11 +1489,11 @@ root_region::get_current_frame_id (const region_model &model) const
 
 /* FIXME.  */
 
-void
-root_region::pop_frame (region_model *model)
+svalue_id
+root_region::pop_frame (region_model *model, purge_stats *out)
 {
   stack_region *stack = model->get_region <stack_region> (m_stack_rid);
-  stack->pop_frame (model);
+  return stack->pop_frame (model, out);
 }
 
 /* FIXME.  */
@@ -1398,6 +1542,16 @@ root_region::ensure_heap_region (region_model *model)
 /* FIXME.  */
 
 void
+root_region::remap_region_ids (const region_id_map &map)
+{
+  map.update (m_stack_rid);
+  map.update (m_globals_rid);
+  map.update (m_heap_rid);
+}
+
+/* FIXME.  */
+
+void
 root_region::add_to_hash (inchash::hash &hstate) const
 {
   region::add_to_hash (hstate);
@@ -1965,6 +2119,12 @@ region_model::on_return (const greturn *return_stmt)
 void
 region_model::handle_phi (tree lhs, tree rhs, bool is_back_edge)
 {
+  /* For now, don't bother tracking the .MEM SSA names.  */
+  if (tree var = SSA_NAME_VAR (lhs))
+    if (TREE_CODE (var) == VAR_DECL)
+      if (VAR_DECL_IS_VIRTUAL_OPERAND (var))
+	return;
+
   svalue_id rhs_sid = get_rvalue (rhs);
 
   if (is_back_edge && get_svalue (rhs_sid)->get_kind () != SK_UNKNOWN)
@@ -2213,6 +2373,9 @@ region_model::deref_rvalue (tree ptr)
 	  case POISON_KIND_FREED:
 	    warning (0, "use-after-free");
 	    break;
+	  case POISON_KIND_POPPED_STACK:
+	    warning (0, "use of memory within popped stack");
+	    break;
 	  }
 	goto create_symbolic_region;
       }
@@ -2522,18 +2685,16 @@ region_model::update_for_call_superedge (const call_superedge &call_edge)
 void
 region_model::update_for_return_superedge (const return_superedge &return_edge)
 {
-  tree callee = return_edge.get_callee_decl ();
-  tree result = DECL_RESULT (callee);
-  gcall *call_stmt = return_edge.get_call_stmt ();
-  tree lhs = gimple_call_lhs (call_stmt);
+  purge_stats stats;
+  svalue_id result_sid = pop_frame (&stats);
+  // TODO: do something with the stats?
 
-  /* Evaluate the result, within the callee frame.  */
-  svalue_id result_sid;
-  if (lhs)
-    result_sid = get_rvalue (result);
-  pop_frame ();
+  // FIXME: result_sid could change meaning within pop_frame;
+  // maybe we can return the new sid for the result (if any)
 
   /* Set the result of the call, within the caller frame.  */
+  gcall *call_stmt = return_edge.get_call_stmt ();
+  tree lhs = gimple_call_lhs (call_stmt);
   if (lhs)
     set_value (get_lvalue (lhs), result_sid);
 }
@@ -2622,12 +2783,13 @@ region_model::get_root_region () const
   return get_region<root_region> (m_root_rid);
 }
 
-/* FIXME.  */
+/* FIXME.
+   Return the region_id of the new frame_region.  */
 
-void
+region_id
 region_model::push_frame (function *fun, vec<svalue_id> *arg_sids)
 {
-  get_root_region ()->push_frame (this, fun, arg_sids);
+  return get_root_region ()->push_frame (this, fun, arg_sids);
 }
 
 /* FIXME.  */
@@ -2650,10 +2812,10 @@ region_model::get_current_function () const
 
 /* FIXME.  */
 
-void
-region_model::pop_frame ()
+svalue_id
+region_model::pop_frame (purge_stats *out)
 {
-  get_root_region ()->pop_frame (this);
+  return get_root_region ()->pop_frame (this, out);
 }
 
 /* FIXME.  */
@@ -2731,18 +2893,26 @@ private:
   const sbitmap &m_used;
 };
 
-/* Remove unused svalues from this model, returning the number of
-   svalues that were removed.
+/* Remove unused svalues from this model, accumulating stats into STATS.
+
+   Treat KNOWN_USED as used (this is for handling values being returned
+   from functions as their frame is popped).
 
    This is important for ensuring that the analysis of loops terminates.
    Otherwise, we could generate a succession of models with unreferenced
    "unknown" values, where the number of redundant unknown values could
    grow without bounds, and each such model would be treated as distinct.  */
 
-int
-region_model::purge_svalues ()
+void
+region_model::purge_svalues (purge_stats *stats, svalue_id *known_used_sid)
 {
+  //svalue_id_set used (*this);
   auto_sbitmap used (m_svalues.length ());
+  bitmap_clear (used); // FIXME: is this needed?
+
+  if (known_used_sid)
+    if (!known_used_sid->null_p ())
+      bitmap_set_bit (used, known_used_sid->as_int ());
 
   /* Walk the regions, marking sids that are used.  */
   unsigned i;
@@ -2756,7 +2926,7 @@ region_model::purge_svalues ()
 
   /* Now purge any constraints involving svalues we don't care about.  */
   restrict_to_used_svalues criterion (used);
-  m_constraints->purge (criterion);
+  m_constraints->purge (criterion, stats);
 
   /* Mark any sids that are in constraints that survived.  */
   {
@@ -2795,34 +2965,34 @@ region_model::purge_svalues ()
 
   /* Now walk the regions and the constraints, remapping sids,
      so that all the redundant svalues are at the end.  */
-  remap_svalues (map);
+  remap_svalue_ids (map);
 
   /* Drop the redundant svalues from the end of the vector.  */
-  int num_purged = 0;
   while (m_svalues.length () > next_used_new_sid)
     {
       delete m_svalues.pop ();
-      num_purged++;
+      stats->m_num_svalues++;
     }
 
-  validate ();
+  if (known_used_sid)
+    map.update (*known_used_sid);
 
-  return num_purged;
+  validate ();
 }
 
 /* Renumber the svalues within this model according to MAP.  */
 
 void
-region_model::remap_svalues (const svalue_id_map &map)
+region_model::remap_svalue_ids (const svalue_id_map &map)
 {
   /* Update IDs within regions.  */
   unsigned i;
   region *r;
   FOR_EACH_VEC_ELT (m_regions, i, r)
-    r->remap_svalues (map);
+    r->remap_svalue_ids (map);
 
   /* Update IDs within ECs within constraints.  */
-  m_constraints->remap_svalues (map);
+  m_constraints->remap_svalue_ids (map);
 
   /* Build a reordered svalues vector.  */
   auto_vec<svalue *> new_svalues;
@@ -2840,6 +3010,107 @@ region_model::remap_svalues (const svalue_id_map &map)
     m_svalues.safe_push (sval);
 }
 
+/* Renumber the regions within this model according to MAP.  */
+
+void
+region_model::remap_region_ids (const region_id_map &map)
+{
+  /* Update IDs within regions.  */
+  unsigned i;
+  region *r;
+  FOR_EACH_VEC_ELT (m_regions, i, r)
+    r->remap_region_ids (map);
+
+  /* Update IDs within svalues.  */
+  svalue *sval;
+  FOR_EACH_VEC_ELT (m_svalues, i, sval)
+    sval->remap_region_ids (map);
+
+  /* Build a reordered regions vector.  */
+  auto_vec<region *> new_regions;
+  for (int i = 0; i < m_regions.length (); i++)
+    {
+      region_id dst (region_id::from_int (i));
+      region_id src = map.get_src_for_dst (dst);
+      new_regions.safe_push (get_region (src));
+    }
+
+  /* Copy over the reordered vec to m_regions.  */
+  m_regions.truncate (0);
+  FOR_EACH_VEC_ELT (new_regions, i, r)
+    m_regions.safe_push (r);
+}
+
+/* Delete all regions within SET_TO_PURGE, remapping region IDs for
+   other regions.  It's required that there are no uses of the
+   regions within the set (or the region IDs will become invalid).
+
+   Accumulate stats to STATS.  */
+
+void
+region_model::purge_regions (const region_id_set &set_to_purge,
+			     purge_stats *stats)
+{
+  /* Build a mapping from old-rid to new-rid so that we can preserve
+     order of the used IDs and move all redundant ones to the end.
+     Iterate though region IDs, adding used ones to the front of
+     the new list, and unused ones to the back.  */
+  region_id_map map (m_regions.length ());
+  int next_used_new_rid = 0;
+  int after_next_unused_new_rid = m_regions.length ();
+  for (unsigned i = 0; i < m_regions.length (); i++)
+    {
+      region_id src (region_id::from_int (i));
+      if (set_to_purge.region_p (src))
+	map.put (src, region_id::from_int (--after_next_unused_new_rid));
+      else
+	map.put (src, region_id::from_int (next_used_new_rid++));
+    }
+  /* The two insertion points should have met.  */
+  gcc_assert (next_used_new_rid == after_next_unused_new_rid);
+
+  /* Now walk the regions and svalues, remapping rids,
+     so that all the redundant regions are at the end.  */
+  remap_region_ids (map);
+
+  /* Drop the redundant regions from the end of the vector.  */
+  while (m_regions.length () > next_used_new_rid)
+    {
+      delete m_regions.pop ();
+      stats->m_num_regions++;
+    }
+}
+
+/* Populate *OUT with RID and all of its descendents.  */
+
+void
+region_model::get_descendents (region_id rid, region_id_set *out) const
+{
+  out->add_region (rid);
+
+  bool changed = true;
+  while (changed)
+    {
+      changed = false;
+      unsigned i;
+      region *r;
+      FOR_EACH_VEC_ELT (m_regions, i, r)
+	{
+	  region_id iter_rid = region_id::from_int (i);
+	  if (!out->region_p (iter_rid))
+	    {
+	      region_id parent_rid = r->get_parent ();
+	      if (!parent_rid.null_p ())
+		if (out->region_p (parent_rid))
+		  {
+		    out->add_region (iter_rid);
+		    changed = true;
+		  }
+	    }
+	}
+    }
+}
+
 /* FIXME.  */
 
 void
@@ -3034,11 +3305,14 @@ test_purge_svalues ()
   model.add_constraint (y, GT_EXPR, int_0);
 
   /* The redundant unknown values should have been purged.  */
-  int num_purged = model.purge_svalues ();
-  ASSERT_EQ (num_purged, 3);
+  purge_stats purged;
+  model.purge_svalues (&purged);
+  ASSERT_EQ (purged.m_num_svalues, 3);
 
   /* and the redundant constraint on an old, unknown value for x should
      have been purged.  */
+  ASSERT_EQ (purged.m_num_equiv_classes, 1);
+  ASSERT_EQ (purged.m_num_constraints, 1);
   ASSERT_EQ (model.get_constraints ()->m_constraints.length (), 2);
 
   /* ...but we should still have x == 42.  */
@@ -3072,38 +3346,136 @@ test_assignment ()
   ASSERT_CONDITION_TRUE (model, y, EQ_EXPR, x);
 }
 
+#endif
+
 /* FIXME.  */
 
 static void
 test_stack_frames ()
 {
-  path_var int_42 (build_int_cst (integer_type_node, 42), 0);
-  path_var int_0 (build_int_cst (integer_type_node, 0), 0);
+  tree int_42 = build_int_cst (integer_type_node, 42);
+  tree int_10 = build_int_cst (integer_type_node, 10);
+  tree int_5 = build_int_cst (integer_type_node, 5);
+  tree int_0 = build_int_cst (integer_type_node, 0);
 
-  /* "a" in the parent frame.  */
-  path_var a (build_decl (UNKNOWN_LOCATION, VAR_DECL,
-			  get_identifier ("a"),
-			  integer_type_node);
-  /* "x" in a child frame.  */
-  path_var x (build_decl (UNKNOWN_LOCATION, VAR_DECL,
-			  get_identifier ("x"),
-			  integer_type_node), 1);
+  auto_vec <tree> param_types;
+  tree parent_fndecl = make_fndecl (integer_type_node,
+				    "parent_fn",
+				    param_types);
+  allocate_struct_function (parent_fndecl, true);
 
-  {
-    region_model model;
-    model.m_stack_depth = 1;
+  tree child_fndecl = make_fndecl (integer_type_node,
+				   "child_fn",
+				   param_types);
+  allocate_struct_function (child_fndecl, true);
+
+  /* "a" and "b" in the parent frame.  */
+  tree a = build_decl (UNKNOWN_LOCATION, PARM_DECL,
+		       get_identifier ("a"),
+		       integer_type_node);
+  tree b = build_decl (UNKNOWN_LOCATION, PARM_DECL,
+		       get_identifier ("b"),
+		       integer_type_node);
+  /* "x" and "y" in a child frame.  */
+  tree x = build_decl (UNKNOWN_LOCATION, PARM_DECL,
+		       get_identifier ("x"),
+		       integer_type_node);
+  tree y = build_decl (UNKNOWN_LOCATION, PARM_DECL,
+		       get_identifier ("y"),
+		       integer_type_node);
 
-    /* x == a.  */
-    ADD_SAT_CONSTRAINT (model, x, EQ_EXPR, a);
-    ASSERT_CONDITION_TRUE (model, x, EQ_EXPR, a);
+  /* "p" global.  */
+  tree p = build_decl (UNKNOWN_LOCATION, VAR_DECL,
+		       get_identifier ("p"),
+		       ptr_type_node);
 
-    /* Pop the stack.  */
-    model.pop_stack_frame ();
-    ASSERT_CONDITION_UNKNOWN (model, x, EQ_EXPR, a);
-  }
-}
+  /* "q" global.  */
+  tree q = build_decl (UNKNOWN_LOCATION, VAR_DECL,
+		       get_identifier ("q"),
+		       ptr_type_node);
 
-#endif
+  region_model model;
+
+  /* Push stack frame for "parent_fn".  */
+  region_id parent_frame_rid
+    = model.push_frame (DECL_STRUCT_FUNCTION (parent_fndecl), NULL);
+  ASSERT_EQ (model.get_current_frame_id (), parent_frame_rid);
+  region_id a_in_parent_rid = model.get_lvalue (a);
+  model.set_value (a_in_parent_rid, model.get_rvalue (int_42));
+  model.set_to_new_unknown_value (model.get_lvalue (b), integer_type_node);
+  model.add_constraint (b, LT_EXPR, int_10);
+  ASSERT_EQ (model.eval_condition (b, LT_EXPR, int_10),
+	     tristate (tristate::TS_TRUE));
+
+  /* Push stack frame for "child_fn".  */
+  region_id child_frame_rid
+    = model.push_frame (DECL_STRUCT_FUNCTION (child_fndecl), NULL);
+  ASSERT_EQ (model.get_current_frame_id (), child_frame_rid);
+  region_id x_in_child_rid = model.get_lvalue (x);
+  model.set_value (x_in_child_rid, model.get_rvalue (int_0));
+  model.set_to_new_unknown_value (model.get_lvalue (y), integer_type_node);
+  model.add_constraint (y, NE_EXPR, int_5);
+  ASSERT_EQ (model.eval_condition (y, NE_EXPR, int_5),
+	     tristate (tristate::TS_TRUE));
+
+  /* Point a global pointer at a local in the child frame:  p = &x.  */
+  region_id p_in_globals_rid = model.get_lvalue (p);
+  model.set_value (p_in_globals_rid,
+		   model.get_svalue_for_region (x_in_child_rid));
+
+  /* Point another global pointer at p: q = &p.  */
+  region_id q_in_globals_rid = model.get_lvalue (q);
+  model.set_value (q_in_globals_rid,
+		   model.get_svalue_for_region (p_in_globals_rid));
+
+  /* Test get_descendents.  */
+  region_id_set descendents (&model);
+  model.get_descendents (child_frame_rid, &descendents);
+  ASSERT_TRUE (descendents.region_p (child_frame_rid));
+  ASSERT_TRUE (descendents.region_p (x_in_child_rid));
+  ASSERT_FALSE (descendents.region_p (a_in_parent_rid));
+  ASSERT_EQ (descendents.num_regions (), 3);
+
+  /* Pop the "child_fn" frame from the stack.  */
+  purge_stats purged;
+  model.pop_frame (&purged);
+
+  /* We should have purged the unknown values for x and y. */
+  ASSERT_EQ (purged.m_num_svalues, 2);
+
+  /* We should have purged the frame region and the regions for x and y. */
+  ASSERT_EQ (purged.m_num_regions, 3);
+
+  /* We should have purged the constraint on y.  */
+  ASSERT_EQ (purged.m_num_equiv_classes, 1);
+  ASSERT_EQ (purged.m_num_constraints, 1);
+
+  /* Verify that p has been poisoned.  */
+  svalue *new_p_sval = model.get_svalue (model.get_rvalue (p));
+  ASSERT_EQ (new_p_sval->get_kind (), SK_POISONED);
+  ASSERT_EQ (new_p_sval->dyn_cast_poisoned_svalue ()->get_poison_kind (),
+	     POISON_KIND_POPPED_STACK);
+
+  /* Verify that q still points to p, in spite of the region
+     renumbering.  */
+  svalue *new_q_sval = model.get_svalue (model.get_rvalue (q));
+  ASSERT_EQ (new_q_sval->get_kind (), SK_REGION);
+  ASSERT_EQ (new_q_sval->dyn_cast_region_svalue ()->get_pointee (),
+	     model.get_lvalue (p));
+
+  /* Verify that top of stack has been updated.  */
+  ASSERT_EQ (model.get_current_frame_id (), parent_frame_rid);
+
+  /* Verify locals in parent frame.  */
+  /* Verify "a" still has its value.  */
+  svalue *new_a_sval = model.get_svalue (model.get_rvalue (a));
+  ASSERT_EQ (new_a_sval->get_kind (), SK_CONSTANT);
+  ASSERT_EQ (new_a_sval->dyn_cast_constant_svalue ()->get_constant (),
+	     int_42);
+  /* Verify "b" still has its constraint.  */
+  ASSERT_EQ (model.eval_condition (b, LT_EXPR, int_10),
+	     tristate (tristate::TS_TRUE));
+}
 
 #if 0
 /* A subclass of purge_criteria for selftests: purge all path_var instances.  */
@@ -3219,7 +3591,9 @@ analyzer_region_model_cc_tests ()
   test_purge_svalues ();
 #if 0
   test_assignment ();
+#endif
   test_stack_frames ();
+#if 0
   test_purging ();
 #endif
 }
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index 9fb355f..8957532 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -77,23 +77,6 @@ private:
   int m_idx;
 };
 
-/* FIXME.  */
-
-class svalue_id_map
-{
- public:
-  svalue_id_map (int num_svalues);
-  void put (svalue_id src, svalue_id dst);
-  svalue_id get_dst_for_src (svalue_id src) const;
-  svalue_id get_src_for_dst (svalue_id dst) const;
-  void dump_to_pp (pretty_printer *pp) const;
-  void dump () const;
-
- private:
-  auto_vec<svalue_id> m_src_to_dst;
-  auto_vec<svalue_id> m_dst_to_src;
-};
-
 /* An ID for a region within a region_model.  Internally this is an index
    into a vector.  */
 
@@ -128,6 +111,83 @@ private:
   int m_idx;
 };
 
+/* A mapping of IDs to IDs (e.g. when removing one or more elements,
+   thus needing to renumber).  */
+
+template <typename T>
+class id_map
+{
+ public:
+  id_map (int num_ids);
+  void put (T src, T dst);
+  T get_dst_for_src (T src) const;
+  T get_src_for_dst (T dst) const;
+  void dump_to_pp (pretty_printer *pp) const;
+  void dump () const;
+  void update (T&) const;
+
+ private:
+  auto_vec<T> m_src_to_dst;
+  auto_vec<T> m_dst_to_src;
+};
+
+typedef id_map<svalue_id> svalue_id_map;
+typedef id_map<region_id> region_id_map;
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+template <typename T>
+class id_set
+{
+public:
+  id_set (const region_model *model);
+
+  void add_region (T id)
+  {
+    if (!id.null_p ())
+      bitmap_set_bit (m_bitmap, id.as_int ());
+  }
+
+  bool region_p (T id) const
+  {
+    gcc_assert (!id.null_p ());
+    return bitmap_bit_p (const_cast <auto_sbitmap &> (m_bitmap),
+			 id.as_int ());
+  }
+
+  unsigned int num_regions ()
+  {
+    return bitmap_count_bits (m_bitmap);
+  }
+
+private:
+  auto_sbitmap m_bitmap;
+};
+
+typedef id_set<region_id> region_id_set;
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Stats on how much was purged, for various operations that delete
+   information.  */
+
+struct purge_stats
+{
+  purge_stats ()
+  : m_num_svalues (0),
+    m_num_regions (0),
+    m_num_equiv_classes (0),
+    m_num_constraints (0)
+  {}
+
+  int m_num_svalues;
+  int m_num_regions;
+  int m_num_equiv_classes;
+  int m_num_constraints;
+};
+
 ////////////////////////////////////////////////////////////////////////////
 
 /* FIXME.  */
@@ -173,6 +233,8 @@ public:
   virtual poisoned_svalue *dyn_cast_poisoned_svalue () { return NULL; }
   virtual unknown_svalue *dyn_cast_unknown_svalue () { return NULL; }
 
+  virtual void remap_region_ids (const region_id_map &map);
+
  protected:
   svalue (tree type) : m_type (type) {}
 
@@ -211,6 +273,8 @@ public:
 
   region_id get_pointee () const { return m_rid; }
 
+  void remap_region_ids (const region_id_map &map) FINAL OVERRIDE;
+
  private:
   void print_details (const region_model &model,
 		      svalue_id this_sid,
@@ -286,7 +350,10 @@ public:
 enum poison_kind
 {
   POISON_KIND_UNINIT,
-  POISON_KIND_FREED
+  POISON_KIND_FREED,
+
+  /* For use on pointers to regions within popped stack frames.  */
+  POISON_KIND_POPPED_STACK
 };
 
 extern const char *poison_kind_to_str (enum poison_kind);
@@ -362,6 +429,8 @@ public:
 
   virtual enum region_kind get_kind () const = 0;
 
+  region_id get_parent () const { return m_parent_rid; }
+
   void set_value (const region_model &model, svalue_id rhs_sid);
   svalue_id get_value (region_model &model, bool non_null);
   svalue_id get_value_direct () const { return m_sval_id; }
@@ -390,7 +459,8 @@ public:
 
   std::string to_str () const;
 
-  void remap_svalues (const svalue_id_map &map);
+  void remap_svalue_ids (const svalue_id_map &map);
+  virtual void remap_region_ids (const region_id_map &map);
 
  protected:
   region (region_id parent_rid, svalue_id sval_id, tree type)
@@ -445,6 +515,9 @@ public:
   region_id get_or_create (region_model *model,
 			   region_id this_rid,
 			   tree expr, tree type);
+  void unbind (tree expr);
+
+  void remap_region_ids (const region_id_map &map) FINAL OVERRIDE;
 
  protected:
   void add_to_hash (inchash::hash &hstate) const OVERRIDE;
@@ -454,6 +527,7 @@ public:
     OVERRIDE;
 
  private:
+ public: // FIXME
   // FIXME: mapping from tree to child region
   typedef hash_map<tree, region_id> map_t;
   map_t m_map;
@@ -518,7 +592,9 @@ public:
 
   void push_frame (region_id frame_rid);
   region_id get_current_frame_id () const;
-  void pop_frame (region_model *model);
+  svalue_id pop_frame (region_model *model, purge_stats *stats);
+
+  void remap_region_ids (const region_id_map &map) FINAL OVERRIDE;
 
  private:
   void add_to_hash (inchash::hash &hstate) const FINAL OVERRIDE;
@@ -569,15 +645,17 @@ public:
 			 pretty_printer *pp) const
     FINAL OVERRIDE;
 
-  void push_frame (region_model *model, function *fun,
-		   vec<svalue_id> *arg_sids);
+  region_id push_frame (region_model *model, function *fun,
+			vec<svalue_id> *arg_sids);
   region_id get_current_frame_id (const region_model &model) const;
-  void pop_frame (region_model *model);
+  svalue_id pop_frame (region_model *model, purge_stats *stats);
 
   region_id ensure_stack_region (region_model *model);
   region_id ensure_globals_region (region_model *model);
   region_id ensure_heap_region (region_model *model);
 
+  void remap_region_ids (const region_id_map &map) FINAL OVERRIDE;
+
 private:
   void add_to_hash (inchash::hash &hstate) const FINAL OVERRIDE;
   void print_fields (const region_model &model,
@@ -653,10 +731,10 @@ class region_model
 
   root_region *get_root_region () const;
 
-  void push_frame (function *fun, vec<svalue_id> *arg_sids);
+  region_id push_frame (function *fun, vec<svalue_id> *arg_sids);
   region_id get_current_frame_id () const;
   function * get_current_function () const;
-  void pop_frame ();
+  svalue_id pop_frame (purge_stats *stats);
 
   svalue_id add_svalue (svalue *sval);
   void replace_svalue (svalue_id sid, svalue *new_sval);
@@ -698,8 +776,12 @@ class region_model
 
   tree maybe_get_constant (svalue_id sid);
 
-  int purge_svalues ();
-  void remap_svalues (const svalue_id_map &map);
+  void purge_svalues (purge_stats *out,
+		      svalue_id *known_used_sid = NULL);
+  void remap_svalue_ids (const svalue_id_map &map);
+  void remap_region_ids (const region_id_map &map);
+
+  void purge_regions (const region_id_set &set, purge_stats *stats);
 
   int get_num_svalues () const { return m_svalues.length (); }
 
@@ -709,6 +791,8 @@ class region_model
     return m_constraints;
   }
 
+  void get_descendents (region_id rid, region_id_set *out) const;
+
  private:
   void update_for_call_superedge (const call_superedge &call_edge);
   void update_for_return_superedge (const return_superedge &return_edge);
@@ -717,8 +801,8 @@ class region_model
   bool apply_constraints_for_gswitch (const superedge &edge,
 				      const gswitch *switch_stmt);
 
-  auto_delete_vec<svalue> m_svalues;
  public: // FIXME:
+  auto_delete_vec<svalue> m_svalues;
   auto_delete_vec<region> m_regions;
   region_id m_root_rid;
  private:
diff --git a/gcc/function-tests.c b/gcc/function-tests.c
index f1e29e4..394a5ef 100644
--- a/gcc/function-tests.c
+++ b/gcc/function-tests.c
@@ -82,11 +82,11 @@ namespace selftest {
 
 /* Helper function for selftests of function-creation.  */
 
-static tree
+tree
 make_fndecl (tree return_type,
 	     const char *name,
 	     vec <tree> &param_types,
-	     bool is_variadic = false)
+	     bool is_variadic)
 {
   tree fn_type;
   if (is_variadic)
diff --git a/gcc/selftest.h b/gcc/selftest.h
index 88dc951..8d08843 100644
--- a/gcc/selftest.h
+++ b/gcc/selftest.h
@@ -170,6 +170,13 @@ class line_table_test
   ~line_table_test ();
 };
 
+/* FIXME.  */
+
+extern tree make_fndecl (tree return_type,
+			 const char *name,
+			 vec <tree> &param_types,
+			 bool is_variadic = false);
+
 /* Run TESTCASE multiple times, once for each case in our test matrix.  */
 
 extern void
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-1.c b/gcc/testsuite/gcc.dg/analyzer/malloc-1.c
index cb95f68..1f139ae 100644
--- a/gcc/testsuite/gcc.dg/analyzer/malloc-1.c
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-1.c
@@ -1,3 +1,5 @@
+/* { dg-additional-options "-fanalyzer-region-model" } */
+
 #include <stdlib.h>
 #include <string.h>
 
@@ -361,6 +363,19 @@ void test_33 (void)
   cant_free (ptr);
 } /* { dg-warning "leak of 'ptr'" } */ 
 
+void test_34 (void)
+{
+  float *q;
+  struct coord *p = malloc (sizeof (struct coord));
+  if (!p)
+    return;
+  p->x = 0.0f;
+  q = &p->x;
+  free (p);
+  *q = 1.0f; /* { dg-warning "use after free of '*q'" "" { xfail *-*-* } } */
+  // TODO(xfail): requires use of region model
+};
+
 #if 0
 void test_31 (void *p)
 {
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-dedupe-1.c b/gcc/testsuite/gcc.dg/analyzer/malloc-dedupe-1.c
index ba66e77..7eecadf 100644
--- a/gcc/testsuite/gcc.dg/analyzer/malloc-dedupe-1.c
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-dedupe-1.c
@@ -11,7 +11,8 @@ void caller_1_of_test_1 (int n)
   test_1 (0, n); /* { dg-bogus "test_1" } */
 }
 
-void test_1 (int flag, int n)
+void __attribute__((noinline))
+test_1 (int flag, int n)
 {
   int *ptr = (int *)malloc (sizeof (int));
 
diff --git a/notes.txt b/notes.txt
index bd8ed2c..4f4674f 100644
--- a/notes.txt
+++ b/notes.txt
@@ -15,3 +15,8 @@ Add:
 to the source being analyzed to trigger a breakpoint in the analyzer when
 that source is reached.  By putting a series of these in the source, it's
 much easier to effectively step through the program state as it's analyzed.
+
+__analyzer_dump ();
+
+will dump the copious information about the analyzer's state each time it
+reaches the call in its traversal of the source.
-- 
1.8.5.3

