From 8ec9f414dee0db37c831b591a17f6e363fedde1e Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Tue, 23 Apr 2019 12:25:03 -0400
Subject: [PATCH 161/161] FIXME: impl-4: initial impl

FIXME: impl-4: implement hash_map of path/state

FIXME: impl-4: run all checkers at once

FIXME: impl-4: work on sm state

FIXME: impl-4: capture exploded_node at warnings

FIXME: impl-4: capture superedges; improve ..dot output

FIXME: impl-4: implement shortest_paths

FIXME: impl-4: start generating diagnostic_paths; fix state transitions

FIXME: impl-4: more on diagnostic_paths, taking material from impl-3

FIXME: impl-4: move diagnostic emission to a subroutine

FIXME: impl-4: state-change events and final events

FIXME: impl-4: add path filtering

FIXME: impl-4: delete some unused checker4_path stuff

FIXME: start adding path_state to program_state

FIXME: impl-4: state-copying in on_assignment; start adding selftests

FIXME: impl-4: update states based on equivalence classes

FIXME: impl-4: add clustering to dot output

FIXME: impl-4: print phi nodes

FIXME: impl-4: call on_assignment for phi nodes

FIXME: impl-4: minor cleanups

FIXME: impl-4: introduce ordered_hash_map and use it for function clusters in .dot dumps

FIXME: impl-4: prune state machines based on flag_analyzer_checker

FIXME: impl-4: print source code in .dot dump

FIXME: impl-4: capture origins of states

FIXME: impl-4: add some stats to graph

FIXME: impl-4: implement hashing/equality for program_state and use it to consolidate nodes

FIXME: impl-4: WIP on pruning state

FIXME: impl-4: add overview

FIXME: impl-4: start adding call strings to program_point

FIXME: impl-4: more on adding call strings to program_point

FIXME: impl-4: hashing fixes

FIXME: impl-4: fix various hacked stack depths

FIXME: impl-4: show stack depth in path_state::to_str

FIXME: differentiate between call vs return superedges in class hierarchy

FIXME: impl-4: copy call string when making program_points

FIXME: impl-4: handle return statements

FIXME: impl-4: more logging

FIXME: impl-4: WIP on interprocedural checking

FIXME: impl-4: reduce the termination cutoff

FIXME: impl-4: update state from caller to callee

FIXME impl-4: fix stack depths in events

FIXME: impl-4: doc fixes

FIXME: impl-4: cluster .dot output by (function, call_string) pairs

FIXME: impl-4: fix call_string::print

FIXME: impl-4: handle recursion more gracefully

FIXME: impl-4: initial impl of leak detection

FIXME: impl-4: introduce class exploded_path

FIXME: impl-4: fix missing decl

FIXME: impl-4: make fields private/const; add m_node_reuse_count; comment fixes; rename m_entrypoint to m_origin

FIXME: impl-4: fixing testsuite failures

FIXME: impl-4: fix location of SUPEREDGE_RETURN events

FIXME: impl-4: fix stmt for state_change_event, to avoid missing events on CFG edges

FIXME: impl-4: exploded_graph::prune_path: prune redundant interprocedural events

FIXME: impl-4: implement COMPONENT_REF in exploded_node::on_stmt

FIXME: change default impl from 2 to 4

FIXME: impl-4: tweaks
---
 gcc/analyzer/Make-lang.in                          |    2 +-
 gcc/analyzer/analyzer-pass.cc                      |    5 +-
 gcc/analyzer/analyzer-plugin.cc                    |    1 +
 gcc/analyzer/analyzer.h                            |    1 +
 gcc/analyzer/constraints.cc                        |  191 +-
 gcc/analyzer/constraints.h                         |   28 +-
 gcc/analyzer/digraph.cc                            |   52 +-
 gcc/analyzer/digraph.h                             |   50 +-
 gcc/analyzer/engine.cc                             |   30 +-
 gcc/analyzer/engine.h                              |    1 +
 gcc/analyzer/exploded-graph.cc                     |   45 -
 gcc/analyzer/exploded-graph.h                      |   59 -
 gcc/analyzer/hardcoded-state-machine.cc            |   19 +-
 gcc/analyzer/hardcoded-state-machine.h             |    2 +
 gcc/analyzer/impl-3.cc                             |   29 +-
 gcc/analyzer/impl-4.cc                             | 4194 ++++++++++++++++++++
 gcc/analyzer/supergraph.cc                         |   54 +-
 gcc/analyzer/supergraph.h                          |   96 +-
 gcc/testsuite/gcc.dg/analyzer/factorial.c          |    7 +
 gcc/testsuite/gcc.dg/analyzer/fibonacci.c          |    7 +
 gcc/testsuite/gcc.dg/analyzer/infinite-recursion.c |   35 +
 gcc/testsuite/gcc.dg/analyzer/loop.c               |    5 +-
 gcc/testsuite/gcc.dg/analyzer/malloc-1.c           |    8 +-
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-12.c      |    2 +-
 .../gcc.dg/analyzer/malloc-ipa-8-double-free.c     |  119 +-
 .../gcc.dg/analyzer/malloc-ipa-8-unchecked.c       |   21 +-
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-9.c       |    5 +-
 gcc/testsuite/gcc.dg/analyzer/malloc-paths-5.c     |    2 +-
 gcc/testsuite/gcc.dg/analyzer/params-2.c           |    6 +-
 gcc/testsuite/gcc.dg/analyzer/params.c             |    4 +-
 30 files changed, 4783 insertions(+), 297 deletions(-)
 delete mode 100644 gcc/analyzer/exploded-graph.cc
 delete mode 100644 gcc/analyzer/exploded-graph.h
 create mode 100644 gcc/analyzer/impl-4.cc
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/factorial.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fibonacci.c

diff --git a/gcc/analyzer/Make-lang.in b/gcc/analyzer/Make-lang.in
index 95c14d8..f742b9d 100644
--- a/gcc/analyzer/Make-lang.in
+++ b/gcc/analyzer/Make-lang.in
@@ -60,7 +60,6 @@ ANALYZER_PLUGIN_SO_OBJS = \
 	analyzer/constraints.o \
 	analyzer/digraph.o \
 	analyzer/engine.o \
-	analyzer/exploded-graph.o \
 	analyzer/graphviz.o \
 	analyzer/hardcoded-state-machine.o \
 	analyzer/hardcoded-state-machine-malloc.o \
@@ -69,6 +68,7 @@ ANALYZER_PLUGIN_SO_OBJS = \
 	analyzer/hardcoded-state-machine-sensitive.o \
 	analyzer/hardcoded-state-machine-taint.o \
 	analyzer/impl-3.o \
+	analyzer/impl-4.o \
 	analyzer/supergraph.o \
 	analyzer/tristate.o \
 
diff --git a/gcc/analyzer/analyzer-pass.cc b/gcc/analyzer/analyzer-pass.cc
index 8417ef1..be676d4 100644
--- a/gcc/analyzer/analyzer-pass.cc
+++ b/gcc/analyzer/analyzer-pass.cc
@@ -84,13 +84,16 @@ pass_analyzer::execute (function *)
 {
   switch (flag_analyzer_impl)
     {
-    default:
     case 2:
       run_checkers_impl_2 ();
       break;
     case 3:
       run_checkers_impl_3 ();
       break;
+    default:
+    case 4:
+      run_checkers_impl_4 ();
+      break;
     }
   return 0;
 }
diff --git a/gcc/analyzer/analyzer-plugin.cc b/gcc/analyzer/analyzer-plugin.cc
index b7b505c..17b69bd 100644
--- a/gcc/analyzer/analyzer-plugin.cc
+++ b/gcc/analyzer/analyzer-plugin.cc
@@ -40,6 +40,7 @@ analyzer_selftests (void */*gcc_data*/, void */*user_data*/)
   analyzer_checker_script_cc_tests ();
   analyzer_constraints_cc_tests ();
   analyzer_engine_cc_tests ();
+  analyzer_impl_4_cc_tests ();
 }
 
 } /* end of namespace selftest.  */
diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h
index 72c55df..32a6ab9 100644
--- a/gcc/analyzer/analyzer.h
+++ b/gcc/analyzer/analyzer.h
@@ -53,6 +53,7 @@ namespace selftest {
 extern void analyzer_checker_script_cc_tests ();
 extern void analyzer_constraints_cc_tests ();
 extern void analyzer_engine_cc_tests ();
+extern void analyzer_impl_4_cc_tests ();
 
 } /* end of namespace selftest.  */
 
diff --git a/gcc/analyzer/constraints.cc b/gcc/analyzer/constraints.cc
index 5750c17..249e9de 100644
--- a/gcc/analyzer/constraints.cc
+++ b/gcc/analyzer/constraints.cc
@@ -46,6 +46,8 @@
 std::string
 path_var::to_str () const
 {
+  if (m_tree == NULL_TREE)
+    return "NULL";
   if (CONSTANT_CLASS_P (m_tree))
     return expr_to_string (m_tree);
   else
@@ -115,6 +117,51 @@ equiv_class::to_str () const
 
 /* FIXME.  */
 
+hashval_t
+equiv_class::hash () const
+{
+  inchash::hash hstate;
+  int i;
+  path_var *pv;
+
+  inchash::add_expr (m_constant, hstate);
+  FOR_EACH_VEC_ELT (m_vars, i, pv)
+    inchash::add_path_var (*pv, hstate);
+  hstate.add_flag (m_escaped);
+  hstate.commit_flag ();
+  if (m_extra_state)
+    hstate.merge_hash (m_extra_state->hash ());
+  return hstate.end ();
+}
+
+/* FIXME.  */
+
+bool
+equiv_class::operator== (const equiv_class &other)
+{
+  if (m_constant != other.m_constant)
+    return false; // TODO: use tree equality here
+
+  int i;
+  path_var *pv;
+  FOR_EACH_VEC_ELT (m_vars, i, pv)
+    if (! (*pv == other.m_vars[i]))
+      return false;
+  if (m_escaped != other.m_escaped)
+    return false;
+
+  if ((m_extra_state != NULL) != (other.m_extra_state != NULL))
+    return false;
+
+  if (m_extra_state != NULL)
+    if (!(*m_extra_state == *other.m_extra_state))
+      return false;
+
+  return true;
+}
+
+/* FIXME.  */
+
 void
 equiv_class::add (path_var v)
 {
@@ -264,6 +311,32 @@ constraint::to_str (const path_state &ps) const
 
 /* FIXME.  */
 
+hashval_t
+constraint::hash () const
+{
+  inchash::hash hstate;
+  hstate.add_int (m_lhs.m_idx);
+  hstate.add_int (m_op);
+  hstate.add_int (m_rhs.m_idx);
+  return hstate.end ();
+}
+
+/* FIXME.  */
+
+bool
+constraint::operator== (const constraint &other) const
+{
+  if (m_lhs != other.m_lhs)
+    return false;
+  if (m_op != other.m_op)
+    return false;
+  if (m_rhs != other.m_rhs)
+    return false;
+  return true;
+}
+
+/* FIXME.  */
+
 const equiv_class &
 equiv_class_id::get_obj (const path_state &ps) const
 {
@@ -305,6 +378,71 @@ path_state::~path_state ()
 
 /* FIXME.  */
 
+path_state&
+path_state::operator= (const path_state &other)
+{
+  m_stack_depth = other.m_stack_depth;
+
+  gcc_assert (m_equiv_classes.length () == 0);
+  gcc_assert (m_constraints.length () == 0);
+
+  int i;
+  equiv_class *ec;
+  FOR_EACH_VEC_ELT (other.m_equiv_classes, i, ec)
+    m_equiv_classes.safe_push (new equiv_class (*ec));
+  constraint *c;
+  FOR_EACH_VEC_ELT (other.m_constraints, i, c)
+    m_constraints.safe_push (*c);
+
+  return *this;
+}
+
+/* FIXME.  */
+
+hashval_t
+path_state::hash () const
+{
+  inchash::hash hstate;
+  int i;
+  equiv_class *ec;
+  constraint *c;
+
+  hstate.add_int (m_stack_depth);
+  FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+    hstate.merge_hash (ec->hash ());
+  FOR_EACH_VEC_ELT (m_constraints, i, c)
+    hstate.merge_hash (c->hash ());
+  return hstate.end ();
+}
+
+/* FIXME.  */
+
+bool
+path_state::operator== (const path_state &other) const
+{
+  if (m_stack_depth != other.m_stack_depth)
+    return false;
+  if (m_equiv_classes.length () != other.m_equiv_classes.length ())
+    return false;
+
+  int i;
+  equiv_class *ec;
+
+  FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+    if (!(*ec == *other.m_equiv_classes[i]))
+      return false;
+
+  constraint *c;
+
+  FOR_EACH_VEC_ELT (m_constraints, i, c)
+    if (!(*c == other.m_constraints[i]))
+      return false;
+
+  return true;
+}
+
+/* FIXME.  */
+
 std::string
 path_state::to_str () const
 {
@@ -326,7 +464,8 @@ path_state::to_str () const
 	result += " && ";
       result += c->to_str (*this);
     }
-  result += "}";
+  result += "}@";
+  result += std::to_string (m_stack_depth);
   return result;
 }
 
@@ -362,7 +501,7 @@ path_state::apply_constraints_for_edge (const superedge &edge,
 
   if (edge.m_kind == SUPEREDGE_RETURN)
     // FIXME: use as_a<>
-    return apply_constraints_for_return (*edge.dyn_cast_call_superedge ());
+    return apply_constraints_for_return (*edge.dyn_cast_return_superedge ());
 
   if (last_stmt == NULL)
     return true;
@@ -406,7 +545,7 @@ path_state::apply_constraints_for_call (const call_superedge& edge)
 /* FIXME.  */
 
 bool
-path_state::apply_constraints_for_return (const call_superedge&)
+path_state::apply_constraints_for_return (const return_superedge&)
 {
   pop_stack_frame ();
   return true;
@@ -1071,6 +1210,13 @@ debug (const path_state &ps)
   fprintf (stderr, "%s\n", ps.to_str ().c_str ());
 }
 
+void
+inchash::add_path_var (path_var pv, hash &hstate)
+{
+  inchash::add_expr (pv.m_tree, hstate);
+  hstate.add_int (pv.m_stack_depth);
+}
+
 #if CHECKING_P
 
 namespace selftest {
@@ -1784,6 +1930,44 @@ test_stack_frames ()
   }
 }
 
+/* Check that operator== and hashing works as expected for the
+   various types.  */
+
+static void
+test_equality ()
+{
+  path_var int_42 (build_int_cst (integer_type_node, 42), 0);
+  path_var int_0 (build_int_cst (integer_type_node, 0), 0);
+
+  path_var x (build_decl (UNKNOWN_LOCATION, PARM_DECL,
+			  get_identifier ("x"),
+			  integer_type_node), 0);
+  path_var y (build_decl (UNKNOWN_LOCATION, PARM_DECL,
+			  get_identifier ("y"),
+			  integer_type_node), 0);
+  path_var z (build_decl (UNKNOWN_LOCATION, PARM_DECL,
+			  get_identifier ("z"),
+			  integer_type_node), 0);
+
+  {
+    path_state ps0;
+    path_state ps1;
+
+    ASSERT_EQ (ps0.hash (), ps1.hash ());
+    ASSERT_EQ (ps0, ps1);
+
+    ADD_SAT_CONSTRAINT (ps1, x, EQ_EXPR, y);
+    ASSERT_NE (ps0.hash (), ps1.hash ());
+    ASSERT_NE (ps0, ps1);
+
+    path_state ps2;
+    /* Make the same change to ps2.  */
+    ADD_SAT_CONSTRAINT (ps2, x, EQ_EXPR, y);
+    ASSERT_EQ (ps1.hash (), ps2.hash ());
+    ASSERT_EQ (ps1, ps2);
+  }
+}
+
 /* Run all of the selftests within this file.  */
 
 void
@@ -1795,6 +1979,7 @@ analyzer_constraints_cc_tests ()
   test_constraint_impl ();
   test_assignment ();
   test_stack_frames ();
+  test_equality ();
 }
 
 } // namespace selftest
diff --git a/gcc/analyzer/constraints.h b/gcc/analyzer/constraints.h
index b8ef4ed..40243d0 100644
--- a/gcc/analyzer/constraints.h
+++ b/gcc/analyzer/constraints.h
@@ -23,6 +23,7 @@ along with GCC; see the file COPYING3.  If not see
 
 class superedge;
 class call_superedge;
+class return_superedge;
 
 /* One of the end-points of a range.  */
 
@@ -88,6 +89,8 @@ class extra_ec_state
   virtual ~extra_ec_state () {}
   virtual extra_ec_state *clone () const = 0;
   virtual std::string to_str () const = 0;
+  virtual hashval_t hash () const = 0;
+  virtual bool operator== (const extra_ec_state &other) const = 0;
 };
 
 /* FIXME.  */
@@ -100,6 +103,8 @@ public:
   ~equiv_class ();
 
   std::string to_str () const;
+  hashval_t hash () const;
+  bool operator== (const equiv_class &other);
 
   void add (path_var v);
   bool del (path_var v);
@@ -167,6 +172,8 @@ class constraint {
     : m_lhs (lhs), m_op (c_op), m_rhs (rhs) {}
 
   std::string to_str (const path_state &ps) const;
+  hashval_t hash () const;
+  bool operator== (const constraint &other) const;
 
   /* Is this an ordering, rather than a "!=".  */
   bool is_ordering_p () const
@@ -189,6 +196,15 @@ public:
   path_state (const path_state &other);
   ~path_state ();
 
+  path_state& operator= (const path_state &other);
+
+  hashval_t hash () const;
+  bool operator== (const path_state &other) const;
+  bool operator!= (const path_state &other) const
+  {
+    return !(*this == other);
+  }
+
   std::string to_str () const;
 
   const equiv_class &get_equiv_class_by_index (unsigned idx) const
@@ -200,6 +216,12 @@ public:
     return *m_equiv_classes[idx];
   }
 
+  equiv_class &get_equiv_class (path_var pv)
+  {
+    equiv_class_id ec_id = get_or_add_equiv_class (pv);
+    return ec_id.get_obj (*this);
+  }
+
   path_var make_path_var (tree expr) const
   {
     return path_var (expr, m_stack_depth);
@@ -219,7 +241,7 @@ public:
   //private:
   bool apply_constraints_for_call (const call_superedge& edge);
 
-  bool apply_constraints_for_return (const call_superedge& edge);
+  bool apply_constraints_for_return (const return_superedge& edge);
 
   void pop_stack_frame ();
 
@@ -249,5 +271,9 @@ public:
 				equiv_class_id rhs_id);
 };
 
+namespace inchash
+{
+  extern void add_path_var (path_var pv, hash &hstate);
+} // namespace inchash
 
 #endif /* GCC_ANALYZER_CONSTRAINTS_H */
diff --git a/gcc/analyzer/digraph.cc b/gcc/analyzer/digraph.cc
index 09218f3..d03fd6b 100644
--- a/gcc/analyzer/digraph.cc
+++ b/gcc/analyzer/digraph.cc
@@ -51,14 +51,18 @@ along with GCC; see the file COPYING3.  If not see
 #include "digraph.h"
 
 void
-digraph::dump_dot_to_pp (pretty_printer *pp) const
+digraph::dump_dot_to_pp (pretty_printer *pp, cluster *root_cluster) const
 {
+  graphviz_out gv (pp);
+
   pp_string (pp, "digraph \"");
   pp_write_text_to_stream (pp);
   pp_string (pp, "FIXME: base");
   pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/false);
   pp_string (pp, "\" {\n");
 
+  gv.indent ();
+
   pp_string (pp, "overlap=false;\n");
   pp_string (pp, "compound=true;\n");
 
@@ -68,35 +72,61 @@ digraph::dump_dot_to_pp (pretty_printer *pp) const
      https://gcc-python-plugin.readthedocs.io/en/latest/_images/sample-supergraph.png
   */
 
-  /* Nodes.  */
-  int i;
-  dnode *n;
-  FOR_EACH_VEC_ELT (m_nodes, i, n)
-    n->dump_dot ();
+  /* If using clustering, emit all nodes via clusters.  */
+  if (root_cluster)
+    {
+      int i;
+      dnode *n;
+      FOR_EACH_VEC_ELT (m_nodes, i, n)
+	root_cluster->add_node (n);
+      root_cluster->dump_dot (&gv);
+    }
+  else
+    {
+      /* Otherwise, display all nodes at top level.  */
+      int i;
+      dnode *n;
+      FOR_EACH_VEC_ELT (m_nodes, i, n)
+	n->dump_dot (&gv);
+    }
 
   /* Edges.  */
+  int i;
   dedge *e;
   FOR_EACH_VEC_ELT (m_edges, i, e)
-    e->dump_dot ();
+    e->dump_dot (&gv);
 
   /* Terminate "digraph" */
+  gv.outdent ();
   pp_string (pp, "}");
   pp_newline (pp);
 }
 
 void
-digraph::dump_dot_to_file (FILE *fp) const
+digraph::dump_dot_to_file (FILE *fp, cluster *root_cluster) const
 {
   pretty_printer pp;
   pp.buffer->stream = fp;
-  dump_dot_to_pp (&pp);
+  dump_dot_to_pp (&pp, root_cluster);
   pp_flush (&pp);
 }
 
 void
-digraph::dump_dot (const char *path) const
+digraph::dump_dot (const char *path, cluster *root_cluster) const
 {
   FILE *fp = fopen (path, "w");
-  dump_dot_to_file (fp);
+  dump_dot_to_file (fp, root_cluster);
   fclose (fp);
 }
+
+void
+digraph::add_node (dnode *node)
+{
+  m_nodes.safe_push (node);
+}
+
+void
+digraph::add_edge (dedge *edge)
+{
+  m_edges.safe_push (edge);
+}
diff --git a/gcc/analyzer/digraph.h b/gcc/analyzer/digraph.h
index 50f5390..54275d3 100644
--- a/gcc/analyzer/digraph.h
+++ b/gcc/analyzer/digraph.h
@@ -24,36 +24,74 @@ along with GCC; see the file COPYING3.  If not see
 /* Forwards decls.  */
 class dnode;
 class dedge;
+class cluster;
+
+/* Abstract base class for a node in a directed graph.  */
 
 class dnode
 {
  public:
   virtual ~dnode () {}
 
-  virtual void dump_dot () const = 0;
+  virtual void dump_dot (graphviz_out *gv) const = 0;
 };
 
+/* Abstract base class for an edge in a directed graph.  */
+
 class dedge
 {
  public:
   virtual ~dedge () {}
 
-  virtual void dump_dot () const = 0;
+  virtual void dump_dot (graphviz_out *gv) const = 0;
 };
 
+/* Abstract base class for a directed graph.  */
+
 class digraph
 {
  public:
   digraph () {}
   virtual ~digraph () {}
 
-  void dump_dot_to_pp (pretty_printer *pp) const;
-  void dump_dot_to_file (FILE *fp) const;
-  void dump_dot (const char *path) const;
+  void dump_dot_to_pp (pretty_printer *pp, cluster *root_cluster) const;
+  void dump_dot_to_file (FILE *fp, cluster *root_cluster) const;
+  void dump_dot (const char *path, cluster *root_cluster) const;
+
+  void add_node (dnode *node);
+  void add_edge (dedge *edge);
 
- private:
+  //protected:
   auto_delete_vec<dnode> m_nodes;
   auto_delete_vec<dedge> m_edges;
 };
 
+/* Abstract base class for splitting dnodes into hierarchical clusters
+   in the generated .dot file.
+
+   See "Subgraphs and Clusters" within
+     https://www.graphviz.org/doc/info/lang.html
+   and e.g.
+     https://graphviz.gitlab.io/_pages/Gallery/directed/cluster.html
+
+   If a root_cluster is passed to dump_dot*, then all nodes will be
+   added to it at the start of dumping, via calls to add_node.
+
+   The root cluster can organize the nodes into a hierarchy of
+   child clusters.
+
+   After all nodes are added to the root cluster, dump_dot will then
+   be called on it (and not on the nodes themselves).  */
+
+class cluster
+{
+ public:
+  virtual ~cluster () {}
+
+  virtual void add_node (dnode *node) = 0;
+
+  /* Recursively dump the cluster, all nodes, and child clusters.  */
+  virtual void dump_dot (graphviz_out *gv) const = 0;
+};
+
 #endif /* GCC_ANALYZER_DIGRAPH_H */
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index b549fc8..5281c16 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -41,7 +41,6 @@
 #include "fold-const.h"
 #include "tristate.h"
 #include "selftest.h"
-#include "exploded-graph.h"
 
 ////////////////////////////////////////////////////////////////////////////
 
@@ -811,10 +810,10 @@ public:
     return *m_edge->dyn_cast_cfg_superedge ();
   }
 
-  const call_superedge& get_call_superedge () const
+  const callgraph_superedge& get_callgraph_superedge () const
   {
     gcc_assert (m_edge->m_kind != SUPEREDGE_CFG_EDGE);
-    return *m_edge->dyn_cast_call_superedge ();
+    return *m_edge->dyn_cast_callgraph_superedge ();
   }
 
   checker_event *clone () const FINAL OVERRIDE
@@ -860,7 +859,7 @@ public:
   {
     return sg->get_edge_for_call (m_edge);
   }
-  call_superedge *get_edge_for_return (const supergraph *sg) const
+  return_superedge *get_edge_for_return (const supergraph *sg) const
   {
     return sg->get_edge_for_return (m_edge);
   }
@@ -2398,7 +2397,7 @@ engine::follow_call (sm_instance sm_inst, backtrace b, path_state ps,
 
     if (get_logger ())
       log ("ps before applying return edge: %s", ps.to_str ().c_str ());
-    const call_superedge &return_sedge = *m_sg.get_edge_for_return (edge);
+    const return_superedge &return_sedge = *m_sg.get_edge_for_return (edge);
     ps.apply_constraints_for_return (return_sedge);
     if (get_logger ())
       log ("ps after applying return edge: %s", ps.to_str ().c_str ());
@@ -3271,10 +3270,10 @@ engine::prune_path (checker_path *path,
 
 	      case SUPEREDGE_CALL:
 		{
-		  const call_superedge& call_superedge
-		    = event->get_call_superedge ();
+		  const callgraph_superedge& cg_superedge
+		    = event->get_callgraph_superedge ();
 		  tree caller_var
-		    = call_superedge.map_expr_from_callee_to_caller (var);
+		    = cg_superedge.map_expr_from_callee_to_caller (var);
 		  if (caller_var)
 		    var = caller_var;
 		}
@@ -3283,10 +3282,10 @@ engine::prune_path (checker_path *path,
 		// TODO: potentially update var/state based on return value,
 		// args etc
 		{
-		  const call_superedge& call_superedge
-		    = event->get_call_superedge ();
+		  const callgraph_superedge& cg_superedge
+		    = event->get_callgraph_superedge ();
 		  tree callee_var
-		    = call_superedge.map_expr_from_caller_to_callee (var);
+		    = cg_superedge.map_expr_from_caller_to_callee (var);
 		  if (callee_var)
 		    var = callee_var;
 		}
@@ -3763,15 +3762,6 @@ run_one_checker (const supergraph &sg, const state_machine &sm, logger *logger)
       free (filename);
     }
 
-  if (1)
-    {
-      exploded_graph eg;
-      char *filename
-	= concat (dump_base_name, ".", sm.get_name (), ".eg.dot", NULL);
-      eg.dump_dot (filename);
-      free (filename);
-    }
-
   if (logger)
     {
       log_scope s (logger, "per-node state");
diff --git a/gcc/analyzer/engine.h b/gcc/analyzer/engine.h
index 8f0b1f4..853f1c1 100644
--- a/gcc/analyzer/engine.h
+++ b/gcc/analyzer/engine.h
@@ -1,2 +1,3 @@
 extern void run_checkers_impl_2 ();
 extern void run_checkers_impl_3 ();
+extern void run_checkers_impl_4 ();
diff --git a/gcc/analyzer/exploded-graph.cc b/gcc/analyzer/exploded-graph.cc
deleted file mode 100644
index d4771dd..0000000
--- a/gcc/analyzer/exploded-graph.cc
+++ /dev/null
@@ -1,45 +0,0 @@
-#include "config.h"
-#include "gcc-plugin.h"
-#include "system.h"
-#include "coretypes.h"
-#include "tree.h"
-#include "tm.h"
-#include "toplev.h"
-#include "hash-table.h"
-#include "vec.h"
-#include "ggc.h"
-#include "basic-block.h"
-#include "tree-ssa-alias.h"
-#include "internal-fn.h"
-#include "gimple-fold.h"
-#include "tree-eh.h"
-#include "gimple-expr.h"
-#include "is-a.h"
-#include "gimple.h"
-#include "tree-pass.h"
-#include "intl.h"
-#include "context.h"
-#include "diagnostic.h"
-#include "bitmap.h"
-#include "gimple.h"
-#include "gimple-iterator.h"
-#include "gimple-pretty-print.h"
-#include "tree-pretty-print.h"
-#include "analyzer.h"
-#include "graphviz.h"
-#include "cgraph.h"
-#include "exploded-graph.h"
-
-void
-exploded_node::dump_dot () const
-{
-}
-
-void
-exploded_edge::dump_dot () const
-{
-}
-
-exploded_graph::exploded_graph ()
-{
-}
diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h
deleted file mode 100644
index 1f71dcf..0000000
--- a/gcc/analyzer/exploded-graph.h
+++ /dev/null
@@ -1,59 +0,0 @@
-/* FIXME.
-   Copyright (C) 2019 Free Software Foundation, Inc.
-   Contributed by David Malcolm <dmalcolm@redhat.com>.
-
-This file is part of GCC.
-
-GCC is free software; you can redistribute it and/or modify it
-under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 3, or (at your option)
-any later version.
-
-GCC is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with GCC; see the file COPYING3.  If not see
-<http://www.gnu.org/licenses/>.  */
-
-#ifndef GCC_ANALYZER_EXPLODED_GRAPH_H
-#define GCC_ANALYZER_EXPLODED_GRAPH_H
-
-#include "digraph.h"
-#include "supergraph.h"
-#include "hardcoded-state-machine.h"
-
-class exploded_node : public dnode
-{
- public:
-  exploded_node (const supernode *supernode,
-		 tree var,
-		 state_machine::state_t state)
-  : m_supernode (supernode),
-    m_var (var),
-    m_state (state)
-  {
-  }
-
-  void dump_dot () const FINAL OVERRIDE;
-
-  const supernode *m_supernode;
-  tree m_var;
-  state_machine::state_t m_state;
-};
-
-class exploded_edge : public dedge
-{
- public:
-  void dump_dot () const FINAL OVERRIDE;
-};
-
-class exploded_graph : public digraph
-{
- public:
-  exploded_graph ();
-};
-
-#endif /* GCC_ANALYZER_EXPLODED_GRAPH_H */
diff --git a/gcc/analyzer/hardcoded-state-machine.cc b/gcc/analyzer/hardcoded-state-machine.cc
index fa69b6c..0713d3b 100644
--- a/gcc/analyzer/hardcoded-state-machine.cc
+++ b/gcc/analyzer/hardcoded-state-machine.cc
@@ -142,5 +142,22 @@ make_checkers (auto_delete_vec <state_machine> &out, logger *logger)
   out.safe_push (new fileptr_state_machine (logger));
   out.safe_push (new taint_state_machine (logger));
   out.safe_push (new sensitive_state_machine (logger));
-  // we don't add pattern_test_state_machine by default
+
+  /* We only attempt to run the pattern tests if it might have been manually
+     enabled (for DejaGnu purposes).  */
+  if (flag_analyzer_checker)
+    out.safe_push (new pattern_test_state_machine (logger));
+
+  if (flag_analyzer_checker)
+    {
+      int read_index, write_index;
+      state_machine **sm;
+
+      // FIXME: this leaks the machines
+      // FIXME: would be nice to log the things that were removed
+      VEC_ORDERED_REMOVE_IF (out, read_index, write_index, sm,
+			     0 != strcmp (flag_analyzer_checker,
+					  (*sm)->get_name ()));
+    }
+
 }
diff --git a/gcc/analyzer/hardcoded-state-machine.h b/gcc/analyzer/hardcoded-state-machine.h
index bdc79e0..274c968 100644
--- a/gcc/analyzer/hardcoded-state-machine.h
+++ b/gcc/analyzer/hardcoded-state-machine.h
@@ -48,6 +48,8 @@ public:
   state_machine (const char *name, logger *logger)
   : log_user (logger), m_name (name) {}
 
+  virtual ~state_machine () {}
+
   const char *get_name () const { return m_name; }
 
   const char *get_state_name (state_t s) const;
diff --git a/gcc/analyzer/impl-3.cc b/gcc/analyzer/impl-3.cc
index bfdacb5..30b3240 100644
--- a/gcc/analyzer/impl-3.cc
+++ b/gcc/analyzer/impl-3.cc
@@ -41,7 +41,6 @@
 #include "fold-const.h"
 #include "tristate.h"
 #include "selftest.h"
-#include "exploded-graph.h"
 #include "constraints.h"
 
 namespace impl_3 {
@@ -111,6 +110,18 @@ class ec3_state : public extra_ec_state
 	    + m_sm.get_state_name (m_current_state)
 	    + "')");
   }
+  hashval_t hash () const
+  {
+    // FIXME
+    return (hashval_t)m_baseline_state ^ m_current_state;
+  }
+
+  bool operator== (const extra_ec_state &other) const FINAL OVERRIDE
+  {
+    const ec3_state &other_ec3 = (const ec3_state &)other;
+    return (m_baseline_state == other_ec3.m_baseline_state
+	    && m_current_state == other_ec3.m_current_state);
+  }
 
   const state_machine &m_sm;
 
@@ -445,10 +456,10 @@ public:
     return *m_edge->dyn_cast_cfg_superedge ();
   }
 
-  const call_superedge& get_call_superedge () const
+  const callgraph_superedge& get_callgraph_superedge () const
   {
     gcc_assert (m_edge->m_kind != SUPEREDGE_CFG_EDGE);
-    return *m_edge->dyn_cast_call_superedge ();
+    return *m_edge->dyn_cast_callgraph_superedge ();
   }
 
   checker3_event *clone () const FINAL OVERRIDE
@@ -1405,10 +1416,10 @@ engine::prune_path (checker3_path *path,
 
 	      case SUPEREDGE_CALL:
 		{
-		  const call_superedge& call_superedge
-		    = event->get_call_superedge ();
+		  const callgraph_superedge& cg_superedge
+		    = event->get_callgraph_superedge ();
 		  tree caller_var
-		    = call_superedge.map_expr_from_callee_to_caller (var);
+		    = cg_superedge.map_expr_from_callee_to_caller (var);
 		  if (caller_var)
 		    var = caller_var;
 		}
@@ -1417,10 +1428,10 @@ engine::prune_path (checker3_path *path,
 		// TODO: potentially update var/state based on return value,
 		// args etc
 		{
-		  const call_superedge& call_superedge
-		    = event->get_call_superedge ();
+		  const callgraph_superedge& cg_superedge
+		    = event->get_callgraph_superedge ();
 		  tree callee_var
-		    = call_superedge.map_expr_from_caller_to_callee (var);
+		    = cg_superedge.map_expr_from_caller_to_callee (var);
 		  if (callee_var)
 		    var = callee_var;
 		}
diff --git a/gcc/analyzer/impl-4.cc b/gcc/analyzer/impl-4.cc
new file mode 100644
index 0000000..b12102c
--- /dev/null
+++ b/gcc/analyzer/impl-4.cc
@@ -0,0 +1,4194 @@
+/* FIXME.  */
+#include "config.h"
+#include "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "tm.h"
+#include "toplev.h"
+#include "hash-table.h"
+#include "vec.h"
+#include "ggc.h"
+#include "basic-block.h"
+#include "tree-ssa-alias.h"
+#include "internal-fn.h"
+#include "gimple-fold.h"
+#include "tree-eh.h"
+#include "gimple-expr.h"
+#include "is-a.h"
+#include "gimple.h"
+#include "tree-pass.h"
+#include "intl.h"
+#include "context.h"
+#include "diagnostic.h"
+#include "bitmap.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "gimple-pretty-print.h"
+#include "tree-pretty-print.h"
+#include "analyzer.h"
+#include "graphviz.h"
+#include "cgraph.h"
+#include "diagnostic-path.h"
+#include "gcc-rich-location.h"
+#include "tree-dfa.h"
+#include <string>
+#include "supergraph.h"
+#include "hardcoded-state-machine.h"
+#include "analyzer-logging.h"
+#include "stringpool.h"
+#include "selftest.h"
+#include "fold-const.h"
+#include "tristate.h"
+#include "selftest.h"
+#include "digraph.h"
+#include "constraints.h"
+//#include "tree-ssa-operands.h"
+
+/* Overview:
+
+   The implementation works on the gimple-SSA representation.
+   (I chose this in the hopes of making it easy to work with LTO to
+   do whole-program analysis).
+
+   The implementation is read-only: it doesn't attempt to change anything,
+   just emit warnings.
+
+   First, we build a "supergraph" which combines the callgraph and all of
+   the CFGs into a single directed graph, with both interprocedural and
+   intraprocedural edges.
+
+   We then use a worklist to explore state within the supergraph,
+   building an "exploded graph".  Nodes in the exploded graph correspond
+   to <point, state> pairs, as in
+     "Precise Interprocedural Dataflow Analysis via Graph Reachability"
+     (Thomas Reps, Susan Horwitz and Mooly Sagiv)
+
+   We reuse nodes for <point, state> pairs we've already seen, and avoid
+   tracking state too closely, so that (hopefully) we rapidly converge
+   on a final exploded graph, and terminate the analysis.  We also bail
+   out if the number of exploded <end-of-basic-block, state> nodes gets
+   larger than a particular multiple of the total number of basic blocks
+   (to ensure termination in the face of pathological state-explosion
+   cases, or bugs).
+
+   We can identify problems directly when processing a <point, state>
+   instance.  For example, if we're finding the successors of
+     <point: before-stmt: "free (ptr);",
+      state: {"ptr": freed}>
+   then we can detect a double-free of "ptr".  We can then emit a path
+   to reach the problem by finding the simplest route through the graph.
+
+   Points in the exploded graph are much more fine-grained than in the
+   CFG and supergraph, with points (and thus nodes) for various events,
+   including both before and after points for individual statements.
+   Although this bloats the graph, this fine-grained approach
+   seems to make things simpler and more debuggable that other approaches
+   I tried, in that each point is responsible for one thing.  Hopefully it
+   will also better support unit-testing, by having program_point be
+   testable without building gimple stmts.
+
+   Points in the exploded graph also have a "call string" identifying the
+   stack of callsites below them, so that paths in the exploded graph
+   correspond to interprocedurally valid paths: we always return to the
+   correct call site, propagating state information accordingly.
+
+   TODO: What about infinite recursion?  */
+
+namespace impl_4 {
+
+class exploded_graph;
+class exploded_node;
+class exploded_edge;
+class checker4_path;
+class stmt_finder;
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+char *print (const char *fmt, ...)
+{
+  /* FIXME: all this cloning is likely to be expensive.  */
+  pretty_printer *pp = global_dc->printer->clone ();
+  pp_clear_output_area (pp);
+
+  text_info ti;
+  rich_location rich_loc (line_table, UNKNOWN_LOCATION);
+
+  va_list ap;
+
+  va_start (ap, fmt);
+
+  ti.format_spec = _(fmt);
+  ti.args_ptr = &ap;
+  ti.err_no = 0;
+  ti.x_data = NULL;
+  ti.m_richloc = &rich_loc;
+
+  pp_format (pp, &ti);
+  pp_output_formatted_text (pp);
+
+  va_end (ap);
+  char *result = xstrdup (pp_formatted_text (pp));
+
+  pp_clear_output_area (pp);
+
+  delete pp;
+
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+template <typename Type>
+struct pod_hash_traits : typed_noop_remove<Type>
+{
+  typedef Type value_type;
+  typedef Type compare_type;
+  static inline hashval_t hash (value_type);
+  static inline bool equal (const value_type &existing,
+			    const value_type &candidate);
+  static inline void mark_deleted (Type &);
+  static inline void mark_empty (Type &);
+  static inline bool is_deleted (Type);
+  static inline bool is_empty (Type);
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* A string of return_superedge pointers, representing a call stack
+   at a program point.
+
+   This is used to ensure that we generate interprocedurally valid paths
+   i.e. that we return to the same callsite that called us.
+
+   The class actually stores the return edges, rather than the call edges,
+   since that's what we need to compare against.  */
+
+class call_string
+{
+public:
+  call_string () : m_return_edges () {}
+  call_string (const call_string &other);
+  call_string& operator= (const call_string &other);
+
+  bool operator== (const call_string &other) const;
+
+  void print (pretty_printer *pp) const;
+  std::string to_str () const;
+
+  hashval_t hash () const;
+
+  bool empty_p () const { return m_return_edges.is_empty (); }
+
+  void push_call (const supergraph &sg,
+		  const call_superedge *sedge);
+  const return_superedge *pop () { return m_return_edges.pop (); }
+
+  int get_stack_depth () const { return m_return_edges.length (); }
+
+  path_var make_path_var (tree expr) const
+  {
+    return path_var (expr, get_stack_depth ());
+  }
+
+  int calc_recursion_depth () const;
+
+private:
+  auto_vec<const return_superedge *> m_return_edges;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* An enum for distinguishing the various kinds of program_point.  */
+
+enum point_kind {
+  /* A "fake" node which has edges to all entrypoints.  */
+  PK_ORIGIN,
+
+  PK_BEFORE_SUPERNODE,
+  PK_BEFORE_STMT,
+  PK_AFTER_STMT,
+  PK_AFTER_SUPERNODE,
+
+  /* Special values used for hash_map:  */
+  PK_EMPTY,
+  PK_DELETED,
+
+  NUM_POINT_KINDS
+};
+
+/* FIXME.  */
+
+static const char *
+point_kind_to_string (enum point_kind pk)
+{
+  switch (pk)
+    {
+    default:
+      gcc_unreachable ();
+    case PK_ORIGIN:
+      return "PK_ORIGIN";
+    case PK_BEFORE_SUPERNODE:
+      return "PK_BEFORE_SUPERNODE";
+    case PK_BEFORE_STMT:
+      return "PK_BEFORE_STMT";
+    case PK_AFTER_STMT:
+      return "PK_AFTER_STMT";
+    case PK_AFTER_SUPERNODE:
+      return "PK_AFTER_SUPERNODE";
+    case PK_EMPTY:
+      return "PK_EMPTY";
+    case PK_DELETED:
+      return "PK_DELETED";
+    }
+}
+
+/* A class for representing the location within the program.
+
+   This represents a fine-grained location within the supergraph (or
+   within one of its nodes), along with a call string giving the
+   interprocedual context.  */
+
+class program_point
+{
+public:
+  program_point (const supernode *supernode,
+		 const superedge *from_edge,
+		 unsigned stmt_idx,
+		 enum point_kind kind,
+		 const call_string &call_string)
+  : m_supernode (supernode), m_from_edge (from_edge),
+    m_stmt_idx (stmt_idx), m_kind (kind),
+    m_call_string (call_string)
+  {
+  }
+
+  void print (pretty_printer *pp) const;
+  void print_source_line (pretty_printer *pp) const;
+  std::string to_str () const;
+
+  hashval_t hash () const;
+  bool operator== (const program_point &other) const
+  {
+    return (m_supernode == other.m_supernode
+	    && m_from_edge == other.m_from_edge
+	    && m_stmt_idx == other.m_stmt_idx
+	    && m_kind == other.m_kind
+	    && m_call_string == other.m_call_string);
+  }
+
+  const supernode *get_supernode () const { return m_supernode; }
+  function *get_function () const
+  {
+    if (m_supernode)
+      return m_supernode->m_fun;
+    else
+      return NULL;
+  }
+
+  int get_stack_depth () const
+  {
+    return m_call_string.get_stack_depth ();
+  }
+
+  const gimple *get_stmt () const;
+
+  path_var make_path_var (tree expr) const
+  {
+    return m_call_string.make_path_var (expr);
+  }
+
+  static program_point from_function_entry (const supergraph &sg,
+					    function *fun)
+  {
+    return before_supernode (sg.get_node_for_function_entry (fun),
+			     NULL, call_string ());
+  }
+
+  static program_point before_supernode (const supernode *supernode,
+					 const superedge *from_edge,
+					 const call_string &call_string)
+  {
+    return program_point (supernode, from_edge, 0, PK_BEFORE_SUPERNODE,
+			  call_string);
+  }
+
+  static program_point before_stmt (const supernode *supernode,
+				    unsigned stmt_idx,
+				    const call_string &call_string)
+  {
+    return program_point (supernode, NULL, stmt_idx, PK_BEFORE_STMT,
+			  call_string);
+  }
+
+  static program_point after_stmt (const supernode *supernode,
+				   unsigned stmt_idx,
+				   const call_string &call_string)
+  {
+    return program_point (supernode, NULL, stmt_idx, PK_AFTER_STMT,
+			  call_string);
+  }
+
+  static program_point after_supernode (const supernode *supernode,
+					const call_string &call_string)
+  {
+    return program_point (supernode, NULL, 0, PK_AFTER_SUPERNODE,
+			  call_string);
+  }
+
+  static program_point empty ()
+  {
+    return program_point (NULL, NULL, 0, PK_EMPTY, call_string ());
+  }
+  static program_point deleted ()
+  {
+    return program_point (NULL, NULL, 0, PK_DELETED, call_string ());
+  }
+
+  bool on_edge (const exploded_graph &eg,
+		const superedge *succ);
+
+//private:
+  const supernode *m_supernode;
+  const superedge *m_from_edge; /* for PK_BEFORE_SUPERNODE.  */
+  unsigned m_stmt_idx;
+  enum point_kind m_kind;
+  call_string m_call_string;
+};
+
+/* Data shared by all program_state instances.  */
+
+class extrinsic_state
+{
+public:
+  extrinsic_state (auto_delete_vec <state_machine> &checkers)
+  : m_checkers (checkers)
+  {
+  }
+
+  const state_machine &get_sm (int idx) const
+  {
+    return *m_checkers[idx];
+  }
+
+  const char *get_name (int idx) const
+  {
+    return m_checkers[idx]->get_name ();
+  }
+
+  /* The state machines.  */
+  auto_delete_vec <state_machine> &m_checkers;
+};
+
+} // namespace impl_4
+
+using impl_4::pod_hash_traits;
+
+template <> struct default_hash_traits<path_var>
+: public pod_hash_traits<path_var>
+{
+};
+
+template <>
+inline hashval_t
+pod_hash_traits<path_var>::hash (value_type v)
+{
+  return (hashval_t)((intptr_t)v.m_tree >> 3) ^ v.m_stack_depth;
+}
+
+template <>
+inline bool
+pod_hash_traits<path_var>::equal (const value_type &existing,
+				  const value_type &candidate)
+{
+  return existing == candidate;
+}
+template <>
+inline void
+pod_hash_traits<path_var>::mark_deleted (value_type &v)
+{
+  v = path_var (reinterpret_cast<tree> (1), 0);
+}
+template <>
+inline void
+pod_hash_traits<path_var>::mark_empty (value_type &v)
+{
+  v = path_var (NULL_TREE, 0);
+}
+template <>
+inline bool
+pod_hash_traits<path_var>::is_deleted (value_type v)
+{
+  return v.m_tree == reinterpret_cast<tree> (1);
+}
+template <>
+inline bool
+pod_hash_traits<path_var>::is_empty (value_type v)
+{
+  return v.m_tree == NULL_TREE;
+}
+
+namespace impl_4 {
+
+/* Map from var to state machine state, also capturing the origin of
+   each state.  */
+
+class sm_state_map
+{
+public:
+  struct entry_t
+  {
+    entry_t (state_machine::state_t state,
+	     path_var origin)
+    : m_state (state), m_origin (origin)
+    {}
+
+    bool operator== (const entry_t &other) const
+    {
+      return (m_state == other.m_state
+	      && m_origin == other.m_origin);
+    }
+    bool operator!= (const entry_t &other) const
+    {
+      return !(*this == other);
+    }
+
+    state_machine::state_t m_state;
+    path_var m_origin;
+  };
+  typedef hash_map <path_var, entry_t> map_t;
+  typedef map_t::iterator iterator_t;
+
+  sm_state_map *clone () const;
+
+  void print (const state_machine &sm, pretty_printer *pp) const;
+  std::string to_str (const state_machine &sm) const;
+
+  bool is_empty_p () const;
+
+  hashval_t hash () const;
+
+  bool operator== (const sm_state_map &other) const;
+  bool operator!= (const sm_state_map &other) const
+  {
+    return !(*this == other);
+  }
+
+  state_machine::state_t get_state (path_var pv) const;
+  path_var get_origin (path_var pv) const;
+
+  void set_state (path_state *ps, path_var pv, state_machine::state_t state,
+		  path_var origin);
+  void set_state (const equiv_class &ec,
+		  state_machine::state_t state,
+		  path_var origin);
+  void impl_set_state (path_var pv,
+		       state_machine::state_t state,
+		       path_var origin);
+
+  void purge_stack_frame (int callee_stack_depth);
+
+  iterator_t begin () const { return m_map.begin (); }
+  iterator_t end () const { return m_map.end (); }
+
+private:
+  map_t m_map;
+};
+
+/* A class for representing the state of interest at a given path of
+   analysis.
+
+   Currently this is a combination of:
+   - a path_state giving a set of equivalence classes of path_vars and
+     inequalities between them
+   - sm_state_maps per state machine, giving a sparse mapping of
+     path_vars to states.  */
+
+class program_state
+{
+public:
+  program_state (int num_checkers);
+  program_state (const extrinsic_state &ext_state);
+  program_state (const program_state &other);
+  program_state& operator= (const program_state &other);
+
+  hashval_t hash () const;
+  bool operator== (const program_state &other) const;
+
+  void print (const extrinsic_state &ext_state,
+	      pretty_printer *pp) const;
+  std::string to_str (const extrinsic_state &ext_state) const;
+
+  equiv_class &get_ec (path_var pv)
+  {
+    equiv_class_id ec_id = m_path_state.get_or_add_equiv_class (pv);
+    return ec_id.get_obj (m_path_state);
+  }
+
+  program_state prune_for_point (const exploded_graph &eg,
+				 const program_point &point) const;
+
+  bool on_edge (const exploded_graph &eg,
+		const exploded_node &enode,
+		const superedge *succ);
+
+  //private:
+  path_state m_path_state;
+
+  auto_vec<sm_state_map *> m_checker_states;
+};
+
+/* A <program_point, program_state> pair, used internally by
+   exploded_node as its immutable data, and as a key for identifying
+   exploded_nodes we've already seen in the graph.  */
+
+struct point_and_state
+{
+  point_and_state (const program_point &point,
+		   const program_state &state)
+  : m_point (point),
+    m_state (state)
+  {
+  }
+
+  hashval_t hash () const
+  {
+    return m_point.hash () ^ m_state.hash ();
+  }
+
+  program_point m_point;
+  program_state m_state;
+};
+
+} // namespace impl_4
+
+using impl_4::point_and_state;
+using impl_4::pod_hash_traits;
+
+template <> struct default_hash_traits<point_and_state>
+: public pod_hash_traits<point_and_state>
+{
+};
+
+template <>
+inline hashval_t
+pod_hash_traits<point_and_state>::hash (value_type v)
+{
+  return v.hash ();
+}
+
+template <>
+inline bool
+pod_hash_traits<point_and_state>::equal (const value_type &existing,
+					 const value_type &candidate)
+{
+  return (existing.m_point == candidate.m_point
+	  && existing.m_state == candidate.m_state);
+}
+template <>
+inline void
+pod_hash_traits<point_and_state>::mark_deleted (value_type &v)
+{
+  v = point_and_state (program_point::deleted (), program_state (0));
+}
+template <>
+inline void
+pod_hash_traits<point_and_state>::mark_empty (value_type &v)
+{
+  v = point_and_state (program_point::empty (), program_state (0));
+}
+template <>
+inline bool
+pod_hash_traits<point_and_state>::is_deleted (value_type v)
+{
+  return (v.m_point.m_kind == PK_DELETED);
+}
+template <>
+inline bool
+pod_hash_traits<point_and_state>::is_empty (value_type v)
+{
+  return (v.m_point.m_kind == PK_EMPTY);
+}
+
+namespace impl_4 {
+
+/* An exploded_node is a unique, immutable <point, state> pair within the
+   exploded_graph.
+   Each exploded_node has a unique index within the graph
+   (for ease of debugging).  */
+
+class exploded_node : public dnode
+{
+ public:
+  exploded_node (point_and_state ps,
+		 const extrinsic_state &ext_state,
+		 int index)
+  : m_ps (ps), m_ext_state (ext_state), m_index (index)
+  {
+  }
+
+  void dump_dot (graphviz_out *gv) const FINAL OVERRIDE;
+  void dump_dot_id (pretty_printer *pp) const;
+
+  program_state on_stmt (const exploded_graph &eg) const;
+  bool on_edge (const exploded_graph &eg,
+		const superedge *succ,
+		program_point *next_point,
+		program_state *next_state) const;
+
+  void on_assignment (const exploded_graph &eg,
+		      path_var lhs,
+		      path_var rhs,
+		      const program_state &old_state,
+		      program_state *new_state) const;
+
+  void copy_sm_state (const exploded_graph &eg,
+		      path_var lhs,
+		      path_var rhs,
+		      const program_state &old_state,
+		      program_state *new_state) const;
+
+  void detect_leaks (const exploded_graph &eg) const;
+
+  const program_point &get_point () const { return m_ps.m_point; }
+  const supernode *get_supernode () const
+  {
+    return get_point ().get_supernode ();
+  }
+  function *get_function () const
+  {
+    return get_point ().get_function ();
+  }
+  int get_stack_depth () const
+  {
+    return get_point ().get_stack_depth ();
+  }
+
+  const program_state &get_state () const { return m_ps.m_state; }
+
+private:
+  /* The <program_point, program_state> pair.  This is const, as it
+     is immutable once the exploded_node has been created.  */
+  const point_and_state m_ps;
+
+  /* FIXME: this is within the node since dump_dot is a vfunc
+     In theory we could just pass this around instead.  */
+  const extrinsic_state &m_ext_state;
+
+public: // FIXME
+
+  /* The index of this exploded_node.  */
+  const int m_index;
+
+  auto_vec<exploded_edge *> m_succs;
+};
+
+/* An edge within the exploded graph.
+   Some exploded_edges have an underlying superedge; others don't.  */
+
+class exploded_edge : public dedge
+{
+ public:
+  exploded_edge (exploded_node *src, exploded_node *dest,
+		 const superedge *sedge);
+  void dump_dot (graphviz_out *gv) const FINAL OVERRIDE;
+
+  //private:
+  exploded_node *m_src;
+  exploded_node *m_dest;
+  const superedge *m_sedge;
+};
+
+/* An exploded_graph is a directed graph of unique <point, state> pairs.
+   It also has a worklist of nodes that are waiting for their successors
+   to be added to the graph.  */
+
+class exploded_graph : public digraph, public log_user
+{
+public:
+  exploded_graph (const supergraph &sg, logger *logger,
+		  const extrinsic_state &ext_state,
+		  int verbosity);
+
+  const supergraph &get_supergraph () const { return m_sg; }
+  const extrinsic_state &get_ext_state () const { return m_ext_state; }
+
+  exploded_node *get_origin () const { return m_origin; }
+
+  void add_function_entry (function *fun);
+
+  void process_worklist ();
+  void process_node (exploded_node *node);
+
+  exploded_node *get_or_create_node (const program_point &point,
+				     const program_state &state);
+  void add_edge (exploded_node *src, exploded_node *dest,
+		 const superedge *sedge);
+
+  void emit_diagnostic (const state_machine &sm,
+			const exploded_node *enode,
+			const supernode *node, const gimple *stmt,
+			stmt_finder *finder,
+			tree var, state_machine::state_t state,
+			pending_diagnostic *d) const;
+  void prune_path (checker4_path *path,
+		   const state_machine &sm,
+		   tree var, state_machine::state_t state) const;
+
+  void log_stats () const;
+
+private:
+  const supergraph &m_sg;
+
+  /* Map from point/state to exploded node.  */
+  typedef hash_map <point_and_state, exploded_node *> map_t;
+  map_t m_point_and_state_to_node;
+
+  auto_vec<exploded_node *> m_worklist;
+
+  exploded_node *m_origin;
+
+  const extrinsic_state &m_ext_state;
+
+  int m_verbosity;
+
+  /* Stats.  */
+  int m_num_nodes[NUM_POINT_KINDS];
+  int m_node_reuse_count;
+};
+
+/* A path within an exploded_graph: a sequence of edges.  */
+
+class exploded_path
+{
+public:
+  exploded_path () : m_edges () {}
+  exploded_path (const exploded_path &other);
+  exploded_path & operator= (const exploded_path &other);
+
+  bool find_stmt_backwards (const gimple *search_stmt,
+			    int *out_idx) const;
+
+  auto_vec<const exploded_edge *> m_edges;
+};
+
+/* A record of the shortest path to each node in an exploded_graph
+   from the origin node.
+   The constructor runs Dijkstra's algorithm, and the results are
+   stored in this class.  */
+
+class shortest_paths
+{
+public:
+  shortest_paths (const exploded_graph &eg);
+
+  exploded_path get_shortest_path (const exploded_node *to) const;
+
+private:
+  const exploded_graph &m_eg;
+
+  /* For each exploded_node (by index), the minimal distance to that
+     node from the origin.  */
+  auto_vec<int> m_dist;
+
+  /* For each exploded_node (by index), the previous exploded_edge in
+     the shortest path from the origin.  */
+  auto_vec<const exploded_edge *> m_prev;
+};
+
+/* Abstract base class for use when passing NULL as the stmt for
+   a possible warning, allowing the choice of stmt to be deferred
+   until after we have an emission path (and know we're emitting a
+   warning).  */
+
+class stmt_finder
+{
+public:
+  virtual const gimple *find_stmt (const exploded_path &epath) = 0;
+};
+
+#if 0
+/* FIXME.  */
+
+class engine : public log_user
+{
+public:
+  engine (const supergraph &sg, const state_machine &sm,
+	  logger *logger, int verbosity)
+  : log_user (logger), m_sg (sg), m_sm (sm),
+    m_verbosity (verbosity),
+    m_num_traverse_calls (0),
+    m_on_stmt_calls (0),
+    m_num_path_copies (0),
+    m_num_event_copies (0)
+  {
+  }
+
+  const supergraph &m_sg;
+
+  // FIXME: this implies one traversal per state machine; should we instead
+  // do multiple state machines in one traversal?
+  const state_machine &m_sm;
+
+  int m_verbosity;
+
+  int m_num_traverse_calls;
+  int m_on_stmt_calls;
+  int m_num_path_copies;
+  int m_num_event_copies;
+};
+#endif
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+enum event_kind
+{
+  EK_DEBUG,
+  EK_STMT,
+  EK_FUNCTION_ENTRY,
+  EK_STATE_CHANGE,
+  EK_EDGE,
+  EK_WARNING
+};
+
+static const char *
+event_kind_to_string (enum event_kind ek)
+{
+  switch (ek)
+    {
+    default:
+      gcc_unreachable ();
+    case EK_DEBUG:
+      return "EK_DEBUG";
+    case EK_STMT:
+      return "EK_STMT";
+    case EK_FUNCTION_ENTRY:
+      return "EK_FUNCTION_ENTRY";
+    case EK_STATE_CHANGE:
+      return "EK_STATE_CHANGE";
+    case EK_EDGE:
+      return "EK_EDGE";
+    case EK_WARNING:
+      return "EK_WARNING";
+    }
+}
+
+/* FIXME.  */
+
+class checker4_event : public diagnostic_event
+{
+public:
+  checker4_event (enum event_kind kind,
+		  location_t loc, tree fndecl, int depth)
+    : m_kind (kind), m_loc (loc), m_fndecl (fndecl), m_depth (depth),
+      m_pending_diagnostic (NULL), m_emission_id ()
+  {
+  }
+
+  location_t get_location () const FINAL OVERRIDE { return m_loc; }
+  tree get_fndecl () const FINAL OVERRIDE { return m_fndecl; }
+  int get_stack_depth () const FINAL OVERRIDE { return m_depth; }
+
+  virtual checker4_event *clone () const = 0;
+
+  std::string to_str () const
+  {
+    std::string result ("\"");
+    label_text event_desc (get_desc ());
+    result += event_desc.m_buffer;
+    event_desc.maybe_free ();
+    result += "\" (depth ";
+    result += std::to_string (get_stack_depth ());
+    result += ", m_loc=";
+    result += std::to_string (get_location ());
+    result += ")";
+    return result;
+  }
+
+  void prepare_for_emission (pending_diagnostic *pd, event_id_t emission_id)
+  {
+    m_pending_diagnostic = pd;
+    m_emission_id = emission_id;
+
+    /* Call get_desc now, to ensure that any side-effects take place
+       before pending_diagnostic::emit is called.
+       In particular, state_change_event::get_desc can call
+       pending_diagnostic::describe_critical_state_change;
+       free_of_non_heap can use this to tweak the message.
+       (Would be neater to simply capture this within the state).  */
+    label_text desc = get_desc ();
+    desc.maybe_free ();
+  }
+
+  virtual bool is_call_p () const { return false; }
+  virtual bool is_function_entry_p () const  { return false; }
+  virtual bool is_return_p () const  { return false; }
+
+  enum event_kind m_kind;
+  location_t m_loc;
+  tree m_fndecl;
+  int m_depth;
+  pending_diagnostic *m_pending_diagnostic;
+  event_id_t m_emission_id; // only set once all pruning has occurred
+};
+
+class debug_event : public checker4_event
+{
+public:
+  debug_event (location_t loc, tree fndecl, int depth,
+	      const char *desc)
+  : checker4_event (EK_DEBUG, loc, fndecl, depth),
+    m_desc (xstrdup (desc))
+  {
+  }
+  ~debug_event ()
+  {
+    free (m_desc);
+  }
+
+  label_text get_desc () const FINAL OVERRIDE
+  {
+    return label_text::borrow (m_desc);
+  }
+
+  checker4_event *clone () const FINAL OVERRIDE
+  {
+    return new debug_event (m_loc, m_fndecl, m_depth, m_desc);
+  }
+
+private:
+  char *m_desc;
+};
+
+class statement_event : public checker4_event
+{
+public:
+  statement_event (const gimple *stmt, tree fndecl, int depth)
+  : checker4_event (EK_STMT, gimple_location (stmt), fndecl, depth),
+    m_stmt (stmt)
+  {
+  }
+
+  label_text get_desc () const FINAL OVERRIDE
+  {
+    pretty_printer pp;
+    pp_string (&pp, "stmt: ");
+    // FIXME: const_cast!
+    pp_gimple_stmt_1 (&pp, const_cast <gimple *> (m_stmt) , 0, (dump_flags_t)0);
+    return label_text::take (xstrdup (pp_formatted_text (&pp)));
+  }
+
+  checker4_event *clone () const FINAL OVERRIDE
+  {
+    return new statement_event (m_stmt, m_fndecl, m_depth);
+  }
+
+  const gimple *m_stmt;
+};
+
+class function_entry_event : public checker4_event
+{
+public:
+  function_entry_event (location_t loc, tree fndecl, int depth)
+  : checker4_event (EK_FUNCTION_ENTRY, loc, fndecl, depth)
+  {
+  }
+
+  label_text get_desc () const FINAL OVERRIDE
+  {
+    return label_text::take (print ("entry to %qE", m_fndecl));
+  }
+
+  checker4_event *clone () const FINAL OVERRIDE
+  {
+    return new function_entry_event (m_loc, m_fndecl, m_depth);
+  }
+
+  bool is_function_entry_p () const FINAL OVERRIDE { return true; }
+};
+
+class state_change_event : public checker4_event
+{
+public:
+  state_change_event (const supernode *node, const gimple *stmt,
+		      int stack_depth,
+		      const state_machine &sm,
+		      tree var,
+		      state_machine::state_t from,
+		      state_machine::state_t to,
+		      tree origin)
+  : checker4_event (EK_STATE_CHANGE,
+		   stmt->location, node->m_fun->decl,
+		   stack_depth),
+    m_node (node), m_stmt (stmt), m_sm (sm),
+    m_var (var), m_from (from), m_to (to),
+    m_origin (origin)
+  {
+  }
+
+  label_text get_desc () const FINAL OVERRIDE
+  {
+    if (m_origin)
+      return label_text::take
+	(print ("state of %qE: %qs -> %qs (origin: %qE)",
+		m_var,
+		m_sm.get_state_name (m_from),
+		m_sm.get_state_name (m_to),
+		m_origin));
+    else
+      {
+	/* If there is no origin, then presumably this
+	   state change came directly from a pattern match.
+	   Attempt to generate a nicer human-readable description.
+	   For greatest precision-of-wording, give the pending diagnostic
+	   a chance to describe this state change (in terms of the
+	   diagnostic).
+	   Note that we only have a pending_diagnostic set on the event once
+	   the diagnostic is about to being emitted, so the description for
+	   an event can change.  */
+	if (m_pending_diagnostic)
+	  {
+	    label_text custom_desc
+	      = m_pending_diagnostic->describe_critical_state_change
+	      (//diagnostic_context *ctxt,
+	       m_var, m_from, m_to, m_emission_id);
+	    if (custom_desc.m_buffer)
+	      {
+		if (flag_analyzer_verbose_state_changes)
+		  {
+		    /* Append debug version.  */
+		    label_text result = label_text::take
+		      (print ("%s (state of %qE: %qs -> %qs, origin: NULL)",
+			      custom_desc.m_buffer,
+			      m_var,
+			      m_sm.get_state_name (m_from),
+			      m_sm.get_state_name (m_to)));
+		    custom_desc.maybe_free ();
+		    return result;
+		  }
+		else
+		  return custom_desc;
+	      }
+	  }
+
+	/* Fallback description.  */
+	return label_text::take
+	  (print ("state of %qE: %qs -> %qs (origin: NULL)",
+		  m_var,
+		  m_sm.get_state_name (m_from),
+		  m_sm.get_state_name (m_to)));
+      }
+  }
+
+  checker4_event *clone () const FINAL OVERRIDE
+  {
+    return new state_change_event (m_node, m_stmt, m_depth,
+				   m_sm, m_var, m_from, m_to, m_origin);
+  }
+
+  const supernode *m_node;
+  const gimple *m_stmt;
+  const state_machine &m_sm;
+  tree m_var;
+  state_machine::state_t m_from;
+  state_machine::state_t m_to;
+  tree m_origin;
+};
+
+class edge_event : public checker4_event
+{
+public:
+  edge_event (const superedge *edge,
+	      location_t loc, tree fndecl, int depth)
+  : checker4_event (EK_EDGE,
+		   loc, fndecl, depth),
+    m_edge (edge)
+  {
+  }
+
+  label_text get_desc () const FINAL OVERRIDE
+  {
+    switch (m_edge->m_kind)
+      {
+      default:
+	gcc_unreachable ();
+      case SUPEREDGE_CFG_EDGE:
+	{
+	  char *edge_desc = m_edge->get_description ();
+	  if (strlen (edge_desc) > 0)
+	    {
+	      label_text result
+		= label_text::take (print ("taking %qs edge SN:%i -> SN:%i",
+					   edge_desc,
+					   m_edge->m_src->m_index,
+					   m_edge->m_dest->m_index));
+	      free (edge_desc);
+	      return result;
+	    }
+	  else
+	    {
+	      free (edge_desc);
+	      return label_text::take (print ("taking edge SN:%i -> SN:%i",
+					      m_edge->m_src->m_index,
+					      m_edge->m_dest->m_index));
+	    }
+	}
+      case SUPEREDGE_CALL:
+	{
+	  return label_text::take (print ("calling %qE from %qE",
+					  m_edge->m_dest->m_fun->decl,
+					  m_edge->m_src->m_fun->decl));
+	}
+
+      case SUPEREDGE_RETURN:
+	{
+	  return label_text::take (print ("returning to %qE from %qE",
+					  m_edge->m_dest->m_fun->decl,
+					  m_edge->m_src->m_fun->decl));
+	}
+
+      case SUPEREDGE_INTRAPROCEDURAL_CALL:
+	return label_text::take (// TODO: possibly a higher-level description?
+				 print ("taking edge SN:%i -> SN:%i",
+					m_edge->m_src->m_index,
+					m_edge->m_dest->m_index));
+      }
+  }
+
+  const cfg_superedge& get_cfg_superedge () const
+  {
+    gcc_assert (m_edge->m_kind == SUPEREDGE_CFG_EDGE);
+    return *m_edge->dyn_cast_cfg_superedge ();
+  }
+
+  const callgraph_superedge& get_callgraph_superedge () const
+  {
+    gcc_assert (m_edge->m_kind != SUPEREDGE_CFG_EDGE);
+    return *m_edge->dyn_cast_callgraph_superedge ();
+  }
+
+  checker4_event *clone () const FINAL OVERRIDE
+  {
+    return new edge_event (m_edge, m_loc, m_fndecl, m_depth);
+  }
+
+  bool is_call_p () const FINAL OVERRIDE
+  {
+    return m_edge->m_kind == SUPEREDGE_CALL;
+  }
+  bool is_return_p () const FINAL OVERRIDE
+  {
+    return m_edge->m_kind == SUPEREDGE_RETURN;
+  }
+
+  const superedge *m_edge;
+};
+
+/* FIXME: a repeat of the warning message at the end of the path.  */
+
+class warning_event : public checker4_event
+{
+public:
+  warning_event (location_t loc, tree fndecl, int depth,
+		 const char *msg)
+  : checker4_event (EK_WARNING, loc, fndecl, depth),
+    m_msg (xstrdup (msg))
+  {
+  }
+  ~warning_event () { free (m_msg); }
+
+  label_text get_desc () const FINAL OVERRIDE
+  {
+    return label_text::borrow (m_msg);
+  }
+
+  warning_event *clone () const FINAL OVERRIDE
+  {
+    return new warning_event (m_loc, m_fndecl, m_depth, m_msg);
+  }
+
+  char *m_msg;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+class checker4_path : public diagnostic_path
+{
+public:
+  checker4_path () : diagnostic_path () {}
+  checker4_path (const checker4_path &other);
+  checker4_path& operator= (const checker4_path &other);
+
+  std::string to_str () const;
+
+  void maybe_log (logger *logger, const char *desc) const
+  {
+    if (!logger)
+      return;
+    logger->log ("%s: %s", desc, to_str ().c_str ());
+    for (unsigned i = 0; i < m_events.length (); i++)
+      logger->log ("%s[%i]: %s",
+		   desc, i, m_events[i]->to_str ().c_str ());
+  }
+
+  unsigned num_events () const FINAL OVERRIDE
+  {
+    return m_events.length ();
+  }
+
+  const diagnostic_event & get_event (int idx) const FINAL OVERRIDE
+  {
+    return *m_events[idx];
+  }
+
+  void add_event (checker4_event *event)
+  {
+    m_events.safe_push (event);
+  }
+
+  void delete_event (int idx)
+  {
+    checker4_event *event = m_events[idx];
+    m_events.ordered_remove (idx);
+    delete event;
+  }
+
+#if 0
+  /* Get the SUPEREDGE_RETURN corresponding to the most recent
+     unreturned SUPEREDGE_CALL.  Return NULL if there isn't one.  */
+  const superedge *get_next_return_edge () const
+  {
+    if (m_active_calls.length () == 0)
+      return NULL;
+    cgraph_edge *cedge = m_active_calls[m_active_calls.length () - 1];
+    return get_sg ().get_edge_for_return (cedge);
+  }
+
+  void add_link (const superedge *edge, const supernode *node);
+
+  int get_stack_depth () const { return m_ps.m_stack_depth; }
+
+  state_machine::state_t get_baseline_state (tree expr);
+  void set_state (tree expr,
+		  state_machine::state_t baseline_state,
+		  state_machine::state_t new_state);
+
+  equiv_class &get_ec (tree expr)
+  {
+    path_var pv (expr, get_stack_depth ());
+    equiv_class_id ec_id = m_ps.get_or_add_equiv_class (pv);
+    return ec_id.get_obj (m_ps);
+  }
+
+  const supergraph &get_sg () const;
+  const state_machine &get_sm () const;
+
+  int get_verbosity () const;
+#endif
+
+  void add_final_event (const state_machine &sm,
+			const exploded_node *enode, const gimple *stmt,
+			tree var, state_machine::state_t state,
+			pending_diagnostic *pd)
+  {
+    char *msg = NULL;
+    label_text ev_desc = pd->describe_final_event (var, state);
+    if (ev_desc.m_buffer)
+      {
+	if (flag_analyzer_verbose_state_changes)
+	  {
+	    msg = print ("%s (%qE is in state %qs)", ev_desc.m_buffer,
+			 var, sm.get_state_name (state));
+	    ev_desc.maybe_free ();
+	  }
+	else
+	  msg = ev_desc.take_or_copy ();
+      }
+    else
+      {
+	msg = print ("here (%qE is in state %qs)", var,
+		     sm.get_state_name (state));
+	// FIXME: ideally would show the full message here, too?
+      }
+    checker4_event *end_of_path
+      = new warning_event (stmt->location,
+			   enode->get_function ()->decl,
+			   enode->get_stack_depth (),
+			   msg);
+    add_event (end_of_path);
+  }
+
+  void prepare_for_emission (pending_diagnostic *pd)
+  {
+    checker4_event *e;
+    int i;
+    FOR_EACH_VEC_ELT (m_events, i, e)
+      e->prepare_for_emission (pd, event_id_t (i));
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+
+  /* The events that have occurred along this path.  */
+  auto_delete_vec<checker4_event> m_events;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class call_string.  */
+
+/* FIXME.  */
+
+call_string::call_string (const call_string &other)
+{
+  const return_superedge *e;
+  int i;
+  FOR_EACH_VEC_ELT (other.m_return_edges, i, e)
+    m_return_edges.safe_push (e);
+}
+
+/* FIXME.  */
+
+call_string&
+call_string::operator= (const call_string &other)
+{
+  // would be much simpler if we could rely on vec<> assignment op
+  m_return_edges.truncate (0);
+  const return_superedge *e;
+  int i;
+  FOR_EACH_VEC_ELT (other.m_return_edges, i, e)
+    m_return_edges.safe_push (e);
+  return *this;
+}
+
+/* FIXME.  */
+
+bool
+call_string::operator== (const call_string &other) const
+{
+  if (m_return_edges.length () != other.m_return_edges.length ())
+    return false;
+  const return_superedge *e;
+  int i;
+  FOR_EACH_VEC_ELT (m_return_edges, i, e)
+    if (e != other.m_return_edges[i])
+      return false;
+  return true;
+}
+
+/* FIXME.  */
+
+void
+call_string::print (pretty_printer *pp) const
+{
+  pp_string (pp, "[");
+
+  const return_superedge *e;
+  int i;
+  FOR_EACH_VEC_ELT (m_return_edges, i, e)
+    {
+      if (i > 0)
+	pp_string (pp, ", ");
+      pp_printf (pp, "(SN: %i -> SN: %i in %s)",
+		 e->m_src->m_index, e->m_dest->m_index,
+		 function_name (e->m_dest->m_fun));
+    }
+
+  pp_string (pp, "]");
+}
+
+/* FIXME.  */
+
+std::string
+call_string::to_str () const
+{
+  pretty_printer pp;
+  print (&pp);
+  return std::string (pp_formatted_text (&pp));
+}
+
+/* FIXME.  */
+
+hashval_t
+call_string::hash () const
+{
+  inchash::hash hstate;
+  int i;
+  const return_superedge *e;
+  FOR_EACH_VEC_ELT (m_return_edges, i, e)
+    hstate.add_ptr (e);
+  return hstate.end ();
+}
+
+/* FIXME.  */
+
+void
+call_string::push_call (const supergraph &sg,
+			const call_superedge *call_sedge)
+{
+  gcc_assert (call_sedge);
+  const return_superedge *return_sedge = call_sedge->get_edge_for_return (sg);
+  gcc_assert (return_sedge);
+  m_return_edges.safe_push (return_sedge);
+}
+
+/* Count the number of times the top-most call site appears in the
+   stack.  */
+
+int
+call_string::calc_recursion_depth () const
+{
+  if (m_return_edges.is_empty ())
+    return 0;
+  const return_superedge *top_return_sedge
+    = m_return_edges[m_return_edges.length () - 1];
+
+  int result = 0;
+  const return_superedge *e;
+  int i;
+  FOR_EACH_VEC_ELT (m_return_edges, i, e)
+    if (e == top_return_sedge)
+      ++result;
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+void
+program_point::print (pretty_printer *pp) const
+{
+  pp_string (pp, "callstring: ");
+  m_call_string.print (pp);
+  pp_newline (pp);
+
+  switch (m_kind)
+    {
+    default:
+      gcc_unreachable ();
+
+    case PK_ORIGIN:
+      pp_printf (pp, "origin");
+      pp_newline (pp);
+      break;
+
+    case PK_BEFORE_SUPERNODE:
+      {
+	if (m_from_edge)
+	  pp_printf (pp, "before SN: %i (from SN: %i)",
+		     m_supernode->m_index, m_from_edge->m_src->m_index);
+	else
+	  pp_printf (pp, "before SN: %i (NULL from-edge)",
+		     m_supernode->m_index);
+	pp_newline (pp);
+	for (gphi_iterator gpi
+	       = const_cast<supernode *>(get_supernode ())->start_phis ();
+	     !gsi_end_p (gpi); gsi_next (&gpi))
+	  {
+	    gphi *phi = gpi.phi ();
+	    pp_gimple_stmt_1 (pp, const_cast <gphi *> (phi),
+			      0, (dump_flags_t)0);
+	    pp_newline (pp);
+	  }
+      }
+      break;
+
+    case PK_BEFORE_STMT:
+      pp_printf (pp, "before (SN: %i stmt: %i): ", m_supernode->m_index,
+		 m_stmt_idx);
+      pp_newline (pp);
+      pp_gimple_stmt_1 (pp, const_cast <gimple *> (get_stmt ()),
+			0, (dump_flags_t)0);
+      pp_newline (pp);
+      print_source_line (pp);
+      break;
+
+    case PK_AFTER_STMT:
+      pp_printf (pp, "after (SN: %i stmt: %i): ", m_supernode->m_index,
+		 m_stmt_idx);
+      pp_newline (pp);
+      pp_gimple_stmt_1 (pp, const_cast <gimple *> (get_stmt ()),
+			0, (dump_flags_t)0);
+      pp_newline (pp);
+      print_source_line (pp);
+      break;
+
+    case PK_AFTER_SUPERNODE:
+      pp_printf (pp, "after SN: %i", m_supernode->m_index);
+      pp_newline (pp);
+      break;
+    }
+}
+
+/* A subclass of diagnostic_context for use by
+   program_point::print_source_line.  */
+
+class debug_diagnostic_context : public diagnostic_context
+{
+public:
+  debug_diagnostic_context ()
+  {
+    diagnostic_initialize (this, 0);
+    show_line_numbers_p = true;
+    show_caret = true;
+  }
+};
+
+/* FIXME.  */
+
+void
+program_point::print_source_line (pretty_printer *pp) const
+{
+  const gimple *stmt = get_stmt ();
+  if (!stmt)
+    return;
+  // TODO: monospace font
+  debug_diagnostic_context tmp_dc;
+  gcc_rich_location richloc (stmt->location);
+  diagnostic_show_locus (&tmp_dc, &richloc, DK_ERROR);
+  pp_string (pp, pp_formatted_text (tmp_dc.printer));
+}
+
+/* FIXME.  */
+
+std::string
+program_point::to_str () const
+{
+  pretty_printer pp;
+  print (&pp);
+  return std::string (pp_formatted_text (&pp));
+}
+
+/* FIXME.  */
+
+hashval_t
+program_point::hash () const
+{
+  inchash::hash hstate;
+  if (m_supernode)
+    hstate.add_int (m_supernode->m_index);
+  hstate.add_ptr (m_from_edge);
+  hstate.add_int (m_stmt_idx);
+  hstate.add_int (m_kind);
+  hstate.merge_hash (m_call_string.hash ());
+  return hstate.end ();
+}
+
+/* FIXME.  */
+
+const gimple *
+program_point::get_stmt () const
+{
+  if (m_kind == PK_BEFORE_STMT || m_kind == PK_AFTER_STMT)
+    return m_supernode->m_stmts[m_stmt_idx];
+  else
+    return NULL;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+sm_state_map *
+sm_state_map::clone () const
+{
+  return new sm_state_map (*this);
+}
+
+/* FIXME.  */
+
+void
+sm_state_map::print (const state_machine &sm, pretty_printer *pp) const
+{
+  pp_string (pp, "{");
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      if (iter != m_map.begin ())
+	pp_string (pp, ", ");
+      entry_t e = (*iter).second;
+      pp_printf (pp, "%s: %s (origin: %s)",
+		 (*iter).first.to_str ().c_str (),
+		 sm.get_state_name (e.m_state),
+		 e.m_origin.to_str ().c_str ());
+    }
+
+  // TODO
+  pp_string (pp, "}");
+}
+
+/* FIXME.  */
+
+std::string
+sm_state_map::to_str (const state_machine &sm) const
+{
+  pretty_printer pp;
+  print (sm, &pp);
+  return std::string (pp_formatted_text (&pp));
+}
+
+/* FIXME.  */
+
+bool
+sm_state_map::is_empty_p () const
+{
+  return m_map.elements () == 0;
+}
+
+/* FIXME.  */
+
+hashval_t
+sm_state_map::hash () const
+{
+  inchash::hash hstate;
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      inchash::add_path_var ((*iter).first, hstate);
+      entry_t e = (*iter).second;
+      hstate.add_int (e.m_state);
+      inchash::add_path_var (e.m_origin, hstate);
+    }
+
+  return hstate.end ();
+}
+
+/* FIXME.  */
+
+bool
+sm_state_map::operator== (const sm_state_map &other) const
+{
+  if (m_map.elements () != other.m_map.elements ())
+    return false;
+
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      path_var pv = (*iter).first;
+      entry_t e = (*iter).second;
+      entry_t *other_slot = const_cast <map_t &> (other.m_map).get (pv);
+      if (other_slot == NULL)
+	return false;
+      if (e != *other_slot)
+	return false;
+    }
+  return true;
+}
+
+/* FIXME.  */
+
+state_machine::state_t
+sm_state_map::get_state (path_var pv) const
+{
+  if (entry_t *slot
+      = const_cast <map_t &> (m_map).get (pv))
+    return slot->m_state;
+  else
+    return 0;
+}
+
+/* FIXME.  */
+
+path_var
+sm_state_map::get_origin (path_var pv) const
+{
+  entry_t *slot
+    = const_cast <map_t &> (m_map).get (pv);
+  gcc_assert (slot);
+  return slot->m_origin;
+}
+
+/* FIXME.  */
+
+void
+sm_state_map::set_state (path_state *ps,
+			 path_var pv,
+			 state_machine::state_t state,
+			 path_var origin)
+{
+  equiv_class &ec = ps->get_equiv_class (pv);
+  set_state (ec, state, origin);
+}
+
+/* FIXME.  */
+
+void
+sm_state_map::set_state (const equiv_class &ec, state_machine::state_t state,
+			 path_var origin)
+{
+  int i;
+  path_var *pv;
+  FOR_EACH_VEC_ELT (ec.m_vars, i, pv)
+    impl_set_state (*pv, state, origin);
+}
+
+/* Set state of PV to STATE, bypassing equivalence classes.  */
+
+void
+sm_state_map::impl_set_state (path_var pv, state_machine::state_t state,
+			      path_var origin)
+{
+  /* Special-case state 0 as the default value.  */
+  if (state == 0)
+    {
+      if (m_map.get (pv))
+	m_map.remove (pv);
+      return;
+    }
+  m_map.put (pv, entry_t (state, origin));
+}
+
+/* FIXME.  */
+
+void
+sm_state_map::purge_stack_frame (int callee_stack_depth)
+{
+  /* FIXME: not sure how to safely remove during a traversal, so for now
+     do it in two stages.  */
+  auto_vec <path_var> keys;
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    keys.safe_push ((*iter).first);
+
+  int i;
+  path_var *pv;
+  FOR_EACH_VEC_ELT (keys, i, pv)
+    if (pv->m_stack_depth >= callee_stack_depth)
+      m_map.remove (*pv);
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+class impl_sm_context : public sm_context, public log_user
+{
+public:
+  impl_sm_context (const exploded_graph &eg,
+		   const state_machine &sm,
+		   const exploded_node *node,
+
+		   /* FIXME: should we be getting the ECs from the
+		      old state, rather than the new?  */
+		   program_state *new_state,
+
+		   const sm_state_map &old_smap,
+		   sm_state_map *new_smap,
+
+		   stmt_finder *stmt_finder = NULL)
+  : log_user (eg.get_logger ()), m_eg (eg), m_sm (sm), m_node (node),
+    m_new_state (new_state),
+    m_old_smap (old_smap), m_new_smap (new_smap),
+    m_stmt_finder (stmt_finder)
+  {
+  }
+
+  void on_transition (const supernode *node  ATTRIBUTE_UNUSED,
+		      const gimple *stmt  ATTRIBUTE_UNUSED,
+		      tree var,
+		      state_machine::state_t from,
+		      state_machine::state_t to,
+		      tree origin) FINAL OVERRIDE
+  {
+    JIT_LOG_FUNC (get_logger ());
+    int stack_depth = m_node->get_stack_depth ();
+    path_var pv (var, stack_depth);
+    state_machine::state_t current = m_old_smap.get_state (pv);
+    if (current == from)
+      {
+	if (get_logger ())
+	  log ("%s: state transition of %qE: %s -> %s",
+	       m_sm.get_name (),
+	       var,
+	       m_sm.get_state_name (from),
+	       m_sm.get_state_name (to));
+	m_new_smap->set_state (&m_new_state->m_path_state, pv, to,
+			       path_var (origin, stack_depth));
+      }
+  }
+
+  void warn_for_state (const supernode *node, const gimple *stmt,
+		       tree var, state_machine::state_t state,
+		       pending_diagnostic *d) FINAL OVERRIDE
+  {
+    JIT_LOG_FUNC (get_logger ());
+    int stack_depth = m_node->get_stack_depth ();
+    gcc_assert (d); // take ownership
+    path_var pv (var, stack_depth);
+    state_machine::state_t current = m_old_smap.get_state (pv);
+    if (state == current)
+      {
+	m_eg.emit_diagnostic (m_sm, m_node, node, stmt, m_stmt_finder,
+			      var, state, d);
+      }
+    delete d;
+  }
+
+  const exploded_graph &m_eg;
+  const state_machine &m_sm;
+  const exploded_node *m_node;
+  program_state *m_new_state;
+  const sm_state_map &m_old_smap;
+  sm_state_map *m_new_smap;
+  impl_4::stmt_finder *m_stmt_finder;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+program_state::program_state (const extrinsic_state &ext_state)
+: m_path_state ()
+{
+  int num_states = ext_state.m_checkers.length ();
+  for (int i = 0; i < num_states; i++)
+    m_checker_states.safe_push (new sm_state_map ());
+}
+
+/* FIXME.  */
+
+program_state::program_state (int num_states)
+: m_path_state ()
+{
+  for (int i = 0; i < num_states; i++)
+    m_checker_states.safe_push (new sm_state_map ());
+}
+
+/* FIXME.  */
+
+program_state::program_state (const program_state &other)
+: m_path_state (other.m_path_state)
+{
+  int i;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (other.m_checker_states, i, smap)
+    m_checker_states.safe_push (smap->clone ());
+}
+
+/* FIXME.  */
+
+program_state&
+program_state::operator= (const program_state &other)
+{
+  m_path_state = other.m_path_state;
+
+  // TODO: eliminate existing things
+  gcc_assert (m_checker_states.length () == 0);
+
+  int i;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (other.m_checker_states, i, smap)
+    m_checker_states.safe_push (smap->clone ());
+
+  return *this;
+}
+
+/* FIXME.  */
+
+hashval_t
+program_state::hash () const
+{
+  hashval_t result = m_path_state.hash ();
+  int i;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (m_checker_states, i, smap)
+    result ^= smap->hash ();
+  return result;
+}
+
+/* FIXME.  */
+
+bool
+program_state::operator== (const program_state &other) const
+{
+  if (!(m_path_state == other.m_path_state))
+    return false;
+  int i;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (m_checker_states, i, smap)
+    if (!(*smap == *other.m_checker_states[i]))
+      return false;
+  return true;
+}
+
+/* FIXME.  */
+
+void
+program_state::print (const extrinsic_state &ext_state,
+		      pretty_printer *pp) const
+{
+  pp_printf (pp, "ps: %s", m_path_state.to_str ().c_str ());
+  pp_newline (pp);
+
+  int i;
+  sm_state_map *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);
+	}
+    }
+}
+
+/* FIXME.  */
+
+std::string
+program_state::to_str (const extrinsic_state &ext_state) const
+{
+  pretty_printer pp;
+  print (ext_state, &pp);
+  return std::string (pp_formatted_text (&pp));
+}
+
+/* FIXME.
+   Generate a simpler version of THIS, discarding state that's no longer
+   relevant at POINT.
+   The idea is that we're more likely to be able to consolidate
+   multiple (point, state) into single exploded_nodes if we discard
+   irrelevant state (e.g. at the end of functions).  */
+
+program_state
+program_state::prune_for_point (const exploded_graph &eg,
+				const program_point &point) const
+{
+  /* FIXME: for now a hack: purge all states at exit nodes (which won't work
+     interprocedurally).  */
+#if 0
+  if (const supernode *supernode = point.get_supernode ())
+    if (supernode->return_p ())
+      return program_state (eg.m_ext_state);
+#endif
+
+  // FIXME: what about leak detection, though?
+
+#if 0
+  {
+    function *fun = point.get_function ();
+    if (fun)
+      {
+	auto_cfun sentinel (fun);
+	debug_immediate_uses ();
+	/* FIXME: maybe do the pruning here: we only know what state will be
+	   important when in conjunction with a program_point.  */
+      }
+  }
+#endif
+
+
+  return *this;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+void
+exploded_node::dump_dot (graphviz_out *gv) const
+{
+  pretty_printer *pp = gv->m_pp;
+
+  dump_dot_id (pp);
+  pp_printf (pp, " [shape=%s,style=filled,fillcolor=%s,label=\"",
+	     "record", "lightgrey");
+  pp_left_brace (pp);
+  pp_write_text_to_stream (pp);
+
+  pp_printf (pp, "EN: %i", m_index);
+  pp_newline (pp);
+
+  m_ps.m_point.print (pp);
+  pp_newline (pp);
+
+  m_ps.m_state.print (m_ext_state, pp);
+  pp_newline (pp);
+
+  pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
+
+  //TODO: dump_bb_for_graph (pp, bb);
+#if 0
+  if (m_returning_call)
+    {
+      pp_string (pp, "returning call: ");
+      pp_gimple_stmt_1 (pp, m_returning_call, 0, (dump_flags_t)0);
+      if (a)
+	a->add_stmt_annotations (pp, m_returning_call);
+      pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
+      pp_newline (pp);
+    }
+
+  if (entry_p ())
+    {
+      pp_string (pp, "ENTRY");
+      pp_newline (pp);
+    }
+
+  if (return_p ())
+    {
+      pp_string (pp, "EXIT");
+      pp_newline (pp);
+    }
+
+  /* Phi nodes.  */
+  for (gphi_iterator gpi = const_cast<supernode *> (this)->start_phis ();
+       !gsi_end_p (gpi); gsi_next (&gpi))
+    {
+      gimple *stmt = gsi_stmt (gpi);
+      pp_gimple_stmt_1 (pp, stmt, 0, (dump_flags_t)0);
+      pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
+      pp_newline (pp);
+    }
+
+  /* Statements.  */
+  int i;
+  gimple *stmt;
+  FOR_EACH_VEC_ELT (m_stmts, i, stmt)
+    {
+      pp_gimple_stmt_1 (pp, stmt, 0, (dump_flags_t)0);
+      if (a)
+	a->add_stmt_annotations (pp, stmt);
+      pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
+      pp_newline (pp);
+    }
+#endif
+
+  pp_right_brace (pp);
+  pp_string (pp, "\"];\n\n");
+  pp_flush (pp);
+
+  /* Potentially visualize additional per-node data.  */
+#if 0
+  // compare with:
+  m_state->add_extra_nodes_and_edges (this, gv);
+#endif
+
+#if 0
+  if (0)
+    {
+      /* Begin a subgraph.  */
+      gv->println ("subgraph \"cluster_state_%p\" {", (const void *)this);
+      gv->indent ();
+      gv->println ("style=\"dashed\";");
+      gv->println ("color=\"green\";");
+      gv->println ("label=\"state\";");
+      gv->println ("clusterrank=\"local\";");
+
+      pretty_printer *pp = gv->m_pp;
+
+      /* Create dummy node.  */
+      gv->write_indent ();
+      pp_printf (pp,
+		 "state_%p_foo "
+		 "[shape=%s, style=\"filled\" fillcolor=\"yellow\" label=\"",
+		 (const void *)this, "record");
+      pp_left_brace (pp);
+      pp_write_text_to_stream (pp);
+      //dump_generic_node (pp, TREE_TYPE (expr), 0, (dump_flags_t)0, false);
+      pp_string (pp, " ");
+      //dump_generic_node (pp, expr, 0, (dump_flags_t)0, false);
+      pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
+      pp_right_brace (pp);
+      pp_string (pp, "\"];");
+      pp_flush (pp);
+      pp_newline (pp);
+
+      /* Terminate subgraph.  */
+      gv->outdent ();
+      gv->println ("}");
+    }
+#endif
+
+}
+
+/* FIXME.  */
+
+void
+exploded_node::dump_dot_id (pretty_printer *pp) const
+{
+  pp_printf (pp, "exploded_node_%i", m_index);
+}
+
+/* FIXME.  */
+
+program_state
+exploded_node::on_stmt (const exploded_graph &eg) const
+{
+  const program_point &point = m_ps.m_point;
+  const program_state &old_state = m_ps.m_state;
+  //const path_state &old_ps = old_state.m_path_state;
+
+  program_state new_state (old_state);
+  path_state *new_ps = &new_state.m_path_state;
+
+  const gimple *stmt = point.get_stmt ();
+  int stack_depth = point.get_stack_depth ();
+
+  int i;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (old_state.m_checker_states, i, smap)
+    {
+      const state_machine &sm = m_ext_state.get_sm (i);
+      impl_sm_context ctxt (eg, sm, this,
+			    &new_state,
+			    *old_state.m_checker_states[i],
+			    new_state.m_checker_states[i]);
+      sm.on_stmt (&ctxt, point.m_supernode, stmt);
+    }
+
+  if (const gassign *assign = dyn_cast <const gassign *> (stmt))
+    {
+      tree lhs = gimple_assign_lhs (assign);
+      tree rhs1 = gimple_assign_rhs1 (assign);
+      enum tree_code op = gimple_assign_rhs_code (assign);
+      switch (op)
+	{
+	default:
+	  break;
+
+	case NOP_EXPR:
+	case SSA_NAME:
+	case INTEGER_CST:
+	  {
+	    /* Direct assignment of the form LHS = RHS;  */
+	    on_assignment (eg,
+			   new_ps->make_path_var (lhs),
+			   new_ps->make_path_var (rhs1),
+			   old_state,
+			   &new_state);
+	  }
+	  break;
+
+	case EQ_EXPR:
+	case NE_EXPR:
+	case GE_EXPR:
+	case LE_EXPR:
+	case GT_EXPR:
+	case LT_EXPR:
+	  {
+	    /* If we see a comparison, evaluate it based on what we know along
+	       this path, and record the result, if known.  */
+	    tree rhs2 = gimple_assign_rhs2 (assign);
+	    equiv_class_id rhs1_ec_id
+	      = new_ps->get_or_add_equiv_class (path_var (rhs1, stack_depth));
+	    equiv_class_id rhs2_ec_id
+	      = new_ps->get_or_add_equiv_class (path_var (rhs2, stack_depth));
+	    tristate t = new_ps->eval_condition (rhs1_ec_id, op, rhs2_ec_id);
+	    if (eg.get_logger ())
+	      eg.log ("eval condition: %s", t.as_string ());
+	    if (t.is_known ())
+	      new_ps->add_constraint (path_var (lhs, stack_depth),
+				      t.is_true () ? NE_EXPR : EQ_EXPR,
+				      path_var (integer_zero_node,
+						stack_depth));
+	  }
+	  break;
+
+	case COMPONENT_REF:
+	  {
+	    /* Propagate any state from RHS's operand 0 to LHS
+	       (e.g. for use by the taint checker).  */
+	    copy_sm_state (eg,
+			   new_ps->make_path_var (lhs),
+			   new_ps->make_path_var (TREE_OPERAND (rhs1, 0)),
+			   old_state,
+			   &new_state);
+	  }
+	  break;
+	}
+    }
+
+  if (const greturn *return_ = dyn_cast <const greturn *> (stmt))
+    {
+      tree callee = point.get_function ()->decl;
+      tree lhs = DECL_RESULT (callee);
+      tree rhs = gimple_return_retval (return_);
+      if (rhs)
+	on_assignment (eg,
+		       new_ps->make_path_var (lhs),
+		       new_ps->make_path_var (rhs),
+		       old_state,
+		       &new_state);
+    }
+
+  if (const gcall *call = dyn_cast <const gcall *> (stmt))
+    {
+      /* Debugging/test support.  */
+      if (is_named_call_p (call, "__dump_constraints", 0))
+	inform (stmt->location, "__dump_constraints: %s",
+		new_ps->to_str ().c_str ());
+
+      /* Handle the builtin "__analyzer_eval" by evaluating the input
+	 and dumping as a dummy warning, so that test cases can use dg-warning
+	 to validate the result (and so unexpected warnings will lead to DejaGnu
+	 failures.  */
+      if (is_named_call_p (call, "__analyzer_eval", 1))
+	{
+	  tree t_arg = gimple_call_arg (call, 0);
+	  tristate t
+	    = new_ps->dump_condition (t_arg, NE_EXPR, integer_zero_node);
+	  warning_at (call->location, 0, "%s", t.as_string ());
+	}
+    }
+
+
+  return new_state;
+}
+
+void
+exploded_node::on_assignment (const exploded_graph &eg,
+			      path_var lhs,
+			      path_var rhs,
+			      const program_state &old_state,
+			      program_state *new_state) const
+{
+  JIT_LOG_FUNC (eg.get_logger ());
+  if (eg.get_logger ())
+    {
+      eg.log ("lhs: %s", lhs.to_str ().c_str ());
+      eg.log ("rhs: %s", rhs.to_str ().c_str ());
+    }
+
+  new_state->m_path_state.on_assignment (lhs, rhs);
+
+  copy_sm_state (eg, lhs, rhs, old_state, new_state);
+}
+
+/* Copy SM state (if any) from RHS to LHS.  */
+
+void
+exploded_node::copy_sm_state (const exploded_graph &eg,
+			      path_var lhs,
+			      path_var rhs,
+			      const program_state &old_state,
+			      program_state *new_state) const
+{
+  /* Don't attempt to copy SM state from constants, since otherwise
+     for "ptr = NULL;" it would overwrite the SM-matched "null" state
+     for "ptr" with the implicit "start" state of the NULL constant.  */
+  if (CONSTANT_CLASS_P (rhs.m_tree))
+    return;
+
+  const extrinsic_state &ext_state = eg.get_ext_state ();
+  gcc_assert (old_state.m_checker_states.length ()
+	      == ext_state.m_checkers.length ());
+  gcc_assert (new_state->m_checker_states.length ()
+	      == ext_state.m_checkers.length ());
+  for (unsigned i = 0; i < ext_state.m_checkers.length (); i++)
+    {
+      const state_machine &sm = ext_state.get_sm (i);
+      const sm_state_map *src_smap = old_state.m_checker_states[i];
+      sm_state_map *dst_smap = new_state->m_checker_states[i];
+      state_machine::state_t rhs_state = src_smap->get_state (rhs);
+      dst_smap->set_state (&new_state->m_path_state, lhs, rhs_state, rhs);
+      if (eg.get_logger ())
+	eg.log ("%s: copying state %qs to %s from %s",
+		sm.get_name (),
+		sm.get_state_name (rhs_state),
+		lhs.to_str ().c_str (),
+		rhs.to_str ().c_str ());
+    }
+}
+
+/* FIXME.
+   Consider the effect of following superedge SUCC from this node.
+
+   Return true if it's feasible to follow the edge, or false
+   if it's infeasible.
+
+   Examples: if it's the "true" branch within
+   a CFG and we know the conditional is false, we know it's infeasible.
+   If it's one of multiple interprocedual "return" edge, then only
+   the edge back to the most recent callsite is feasible.
+
+   Update NEXT_STATE accordingly (e.g. to record that a condition was
+   true or false, or that the NULL-ness of a pointer has been checked).
+
+   Update NEXT_POINT accordingly (updating the call string).  */
+
+bool
+exploded_node::on_edge (const exploded_graph &eg,
+			const superedge *succ,
+			program_point *next_point,
+			program_state *next_state) const
+{
+  JIT_LOG_FUNC (eg.get_logger ());
+
+  if (!next_point->on_edge (eg, succ))
+    return false;
+
+  if (!next_state->on_edge (eg, *this, succ))
+    return false;
+
+  return true;
+}
+
+/* A measurement of how good EXPR is for presenting to the user, so
+   that e.g. we can say prefer printing
+     "leak of 'tmp.m_ptr'"
+   over:
+     leak of '<unknown>'.  */
+
+static int
+readability (tree expr)
+{
+  switch (TREE_CODE (expr))
+    {
+    case COMPONENT_REF:
+    case MEM_REF:
+      return readability (TREE_OPERAND (expr, 0)) - 1;
+
+    case SSA_NAME:
+      {
+	if (tree var = SSA_NAME_VAR (expr))
+	  return readability (var);
+	return -1;
+      }
+      break;
+
+    case VAR_DECL:
+      return 256;
+
+    default:
+      return 0;
+    }
+
+  return 0;
+}
+
+/* A qsort comparator for path_var for use by exploded_node::detect_leaks,
+   to sort them into most user-readable to least user-readable.  */
+
+static int
+leak_comparator (const void *p1, const void *p2)
+{
+  const path_var *vs1 = (const path_var *)p1;
+  const path_var *vs2 = (const path_var *)p2;
+
+  int r1 = readability (vs1->m_tree);
+  int r2 = readability (vs2->m_tree);
+
+  return r2 - r1;
+}
+
+/* Subclass of stmt_finder for finding the best stmt to report the leak at,
+   given the emission path.  */
+
+class leak_stmt_finder : public stmt_finder
+{
+public:
+  leak_stmt_finder (const exploded_graph &eg, path_var pv)
+  : m_eg (eg), m_pv (pv) {}
+
+  const gimple *find_stmt (const exploded_path &epath)
+    FINAL OVERRIDE
+  {
+    JIT_LOG_FUNC (m_eg.get_logger ());
+
+    tree var = m_pv.m_tree;
+    if (TREE_CODE (var) == SSA_NAME)
+      {
+	/* Locate the final write to this SSA name in the path.  */
+	const gimple *def_stmt = SSA_NAME_DEF_STMT (var);
+
+	int idx_of_def_stmt;
+	bool found = epath.find_stmt_backwards (def_stmt, &idx_of_def_stmt);
+	gcc_assert (found);
+
+	/* What was the next write to the underlying var
+	   after the SSA name was set? (if any).  */
+
+	for (int idx = idx_of_def_stmt + 1;
+	     idx < epath.m_edges.length ();
+	     ++idx)
+	  {
+	    const exploded_edge *eedge = epath.m_edges[idx];
+	    if (m_eg.get_logger ())
+	      m_eg.log ("eedge[%i]: EN %i -> EN %i",
+			idx,
+			eedge->m_src->m_index,
+			eedge->m_dest->m_index);
+	    const exploded_node *dst_node = eedge->m_dest;
+	    const program_point &dst_point = dst_node->get_point ();
+	    const gimple *stmt = dst_point.get_stmt ();
+	    if (!stmt)
+	      continue;
+	    if (const gassign *assign = dyn_cast <const gassign *> (stmt))
+	      {
+		tree lhs = gimple_assign_lhs (assign);
+		if (TREE_CODE (lhs) == SSA_NAME
+		    && SSA_NAME_VAR (lhs) == SSA_NAME_VAR (var))
+		  return assign;
+	      }
+	  }
+      }
+
+    /* Look backwards for the first statement with a location.  */
+    int i;
+    const exploded_edge *eedge;
+    FOR_EACH_VEC_ELT_REVERSE (epath.m_edges, i, eedge)
+      {
+	if (m_eg.get_logger ())
+	  m_eg.log ("eedge[%i]: EN %i -> EN %i",
+		    i,
+		    eedge->m_src->m_index,
+		    eedge->m_dest->m_index);
+	const exploded_node *dst_node = eedge->m_dest;
+	const program_point &dst_point = dst_node->get_point ();
+	const gimple *stmt = dst_point.get_stmt ();
+	if (stmt)
+	  if (stmt->location != UNKNOWN_LOCATION)
+	    return stmt;
+      }
+
+    gcc_unreachable ();
+    return NULL;
+  }
+
+private:
+  const exploded_graph &m_eg;
+  path_var m_pv;
+};
+
+
+/* Called when finding the successors of the supernode for a function exit
+   basic block.
+
+   Detect equivalence classes that are about to go out of scope, and call
+   on_leak on any that have non-default state (on the most readable
+   path_var within each equivalence class), so that the state machine can
+   potentially issue a warning.
+
+   In theory we'd do this on the return superedge, but there isn't one
+   for top-level functions.  */
+
+void
+exploded_node::detect_leaks (const exploded_graph &eg) const
+{
+  JIT_LOG_FUNC_1 (eg.get_logger (), "EN: %i", m_index);
+
+  const program_state &old_state = get_state ();
+
+  /* Work with a temporary copy of the state.  */
+  program_state new_state (old_state);
+  path_state *new_ps = &new_state.m_path_state;
+
+  const supernode *snode = get_supernode ();
+  const int stack_depth = get_stack_depth ();
+
+  /* Mark EC of return value (if any) as escaping.  */
+  {
+    tree result = DECL_RESULT (snode->m_fun->decl);
+    if (result)
+      {
+	if (eg.get_logger ())
+	  eg.log ("new_ps before escapes: %s", new_ps->to_str ().c_str ());
+	equiv_class_id ec_id
+	  = new_ps->get_or_add_equiv_class (path_var (result, stack_depth));
+	new_ps->recursively_mark_as_escaping (ec_id);
+	if (eg.get_logger ())
+	  eg.log ("new_ps after escapes: %s", new_ps->to_str ().c_str ());
+      }
+  }
+
+  int i;
+  equiv_class *ec;
+  FOR_EACH_VEC_ELT (new_ps->m_equiv_classes, i, ec)
+    {
+      if (eg.get_logger ())
+	eg.log ("considering %s", ec->to_str ().c_str ());
+
+      if (ec->m_escaped)
+	{
+	  eg.log ("no leak: ec escaped");
+	  continue;
+	}
+
+      /* If anything in this EC is still alive in a parent stack frame,
+	 then this isn't a leak.  */
+      if (ec->get_min_stack_depth () < stack_depth)
+	{
+	  eg.log ("no leak: ec member alive within ancestor stack frame");
+	  continue;
+	}
+
+      /* Sort the leaks into most human-readable first, through
+	 to least user-readable.  Given that we only emit one
+	 leak per EC, this ought to ensure that we pick the most
+	 user-readable description of each leaking EC.
+	 This assumes that all vars in the EC have the same state.  */
+      ec->m_vars.qsort (leak_comparator);
+
+      if (ec->m_vars.length () < 1)
+	continue;
+
+      path_var best_pv (ec->m_vars[0]);
+      if (eg.get_logger ())
+	eg.log ("best_pv: %s", best_pv.to_str ().c_str ());
+
+      /* Determining the best statement for the leak needs to happen
+	 after we have an emission path, so pass in a stmt_finder.  */
+      leak_stmt_finder stmt_finder (eg, best_pv);
+
+      int i;
+      sm_state_map *smap;
+      FOR_EACH_VEC_ELT (old_state.m_checker_states, i, smap)
+	{
+	  const state_machine &sm = m_ext_state.get_sm (i);
+	  impl_sm_context ctxt (eg, sm, this,
+				&new_state,
+				*old_state.m_checker_states[i],
+				new_state.m_checker_states[i],
+				&stmt_finder);
+	  sm.on_leak (&ctxt, snode, NULL, best_pv.m_tree,
+		      smap->get_state (best_pv));
+	}
+    }
+}
+
+// TODO: move this fn
+/* Update the call string based on interprocedural
+   edges, ensuring that we have interprocedurally valid paths
+   in the exploded graph.  */
+
+bool
+program_point::on_edge (const exploded_graph &eg,
+			const superedge *succ)
+{
+  JIT_LOG_FUNC (eg.get_logger ());
+  switch (succ->m_kind)
+    {
+    case SUPEREDGE_CFG_EDGE:
+      break;
+
+    case SUPEREDGE_CALL:
+      {
+	/* Add the callsite to the call string.  */
+	// FIXME: this should be an as_a<>
+	m_call_string.push_call (eg.get_supergraph (),
+				 (const call_superedge *)succ);
+
+	/* Impose a maximum recursion depth and don't analyze paths
+	   that exceed it further.
+	   This is something of a blunt workaround, but it only
+	   applies to recursion (and mutual recursion), not to
+	   general call stacks.  */
+	// FIXME: make this a param:
+	const int MAX_RECURSION_DEPTH = 2;
+	if (m_call_string.calc_recursion_depth () > MAX_RECURSION_DEPTH)
+	  {
+	    eg.log ("rejecting call edge: recursion limit exceeded");
+	    // TODO: issue a sorry for this?
+	    return false;
+	  }
+      }
+      break;
+
+    case SUPEREDGE_RETURN:
+      {
+	/* Require that we return to the call site in the call string.  */
+	if (m_call_string.empty_p ())
+	  {
+	    eg.log ("rejecting return edge: empty call string");
+	    return false;
+	  }
+	const return_superedge *top_of_stack = m_call_string.pop ();
+	if (top_of_stack != succ)
+	  {
+	    eg.log ("rejecting return edge: return to wrong callsite");
+	    return false;
+	  }
+      }
+      break;
+
+    case SUPEREDGE_INTRAPROCEDURAL_CALL:
+      /* We ignore these edges.  FIXME: why have them?  */
+      eg.log ("rejecting interprocedural edge");
+      return false;
+    }
+
+  return true;
+}
+
+// TODO: move this fn
+/* FIXME.  */
+
+bool
+program_state::on_edge (const exploded_graph &eg,
+			const exploded_node &enode,
+			const superedge *succ)
+{
+  /* Update state.  */
+
+  const program_point &point = enode.get_point ();
+  const program_state &old_state = enode.get_state ();
+  const gimple *last_stmt = point.m_supernode->get_last_stmt ();
+
+  /* For conditionals and switch statements, add the
+     relevant conditions (for the specific edge) to new_state;
+     skip edges for which the resulting constraints
+     are impossible.  */
+  if (!m_path_state.apply_constraints_for_edge (*succ,
+						last_stmt))
+    {
+      eg.log ("edge to SN: %i is impossible due to path_state constraints",
+	      succ->m_dest->m_index);
+      return false;
+    }
+
+  /* FIXME: the above is setting ECs for args/params on calls, but not
+     updating states.  should we call on_assignment instead.
+     Also, where should it happen for return values at returns?
+     Similarly, the above is pruning state in path_state, but not within
+     the sm_state_map.
+
+     Should we be passing on_assignment to it as a callback?
+     Similarly for frame purging?  */
+
+  /* Hack to do it for interprocedural calls/returns.  */
+  switch (succ->m_kind)
+    {
+    case SUPEREDGE_CFG_EDGE:
+      break;
+
+    case SUPEREDGE_CALL:
+      /* Copy state from args to params.  */
+      {
+	const call_superedge *call_edge
+	  = succ->dyn_cast_call_superedge ();
+	// FIXME: should be an as_a
+
+	/* Map args to parms at next stack_depth, incrementing stack depth.  */
+	int caller_stack_depth = enode.get_stack_depth ();
+	int callee_stack_depth = caller_stack_depth + 1;
+
+	tree callee = call_edge->get_callee_decl ();
+	gcall *call_stmt = call_edge->get_call_stmt ();
+
+	int i = 0;
+	for (tree parm = DECL_ARGUMENTS (callee); parm;
+	     parm = DECL_CHAIN (parm), ++i)
+	  {
+	    tree parm_default_ssa
+	      = ssa_default_def (call_edge->m_dest->m_fun, parm);
+	    if (parm_default_ssa)
+	      enode.on_assignment (eg,
+				   path_var (parm_default_ssa,
+					     callee_stack_depth),
+				   path_var (gimple_call_arg (call_stmt, i),
+					     caller_stack_depth),
+				   old_state,
+				   this);
+	  }
+      }
+      break;
+
+    case SUPEREDGE_RETURN:
+      {
+	const return_superedge *return_edge
+	  = succ->dyn_cast_return_superedge ();
+	// FIXME: should be an as_a
+	int callee_stack_depth = enode.get_stack_depth ();
+	int caller_stack_depth = callee_stack_depth - 1;
+	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);
+	if (lhs)
+	  {
+	    enode.on_assignment (eg,
+				 path_var (lhs, caller_stack_depth),
+				 path_var (result, callee_stack_depth),
+				 old_state,
+				 this);
+	  }
+      }
+      break;
+
+    case SUPEREDGE_INTRAPROCEDURAL_CALL:
+      /* We ignore these edges.  FIXME: why have them?  */
+      eg.log ("rejecting interprocedural edge");
+      return false;
+    }
+
+  /* Pass the edge to the various state machines to see if any transitions
+     or warnings occur.  */
+  int i;
+  sm_state_map *old_smap;
+  FOR_EACH_VEC_ELT (old_state.m_checker_states, i, old_smap)
+    {
+      const state_machine &sm = eg.get_ext_state ().get_sm (i);
+      impl_sm_context ctxt (eg, sm, &enode,
+			    this,
+			    *old_state.m_checker_states[i],
+			    m_checker_states[i]);
+      sm.on_edge (&ctxt, last_stmt, succ);
+
+      switch (succ->m_kind)
+	{
+	case SUPEREDGE_CFG_EDGE:
+	  break;
+
+	case SUPEREDGE_CALL:
+	  /* Copy state from params to args.  */
+	  // TODO
+	  break;
+
+	case SUPEREDGE_RETURN:
+	  {
+	    int callee_stack_depth = enode.get_stack_depth ();
+	    m_checker_states[i]->purge_stack_frame (callee_stack_depth);
+	  }
+	  break;
+
+	case SUPEREDGE_INTRAPROCEDURAL_CALL:
+	  break;
+	}
+    }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+exploded_edge::exploded_edge (exploded_node *src, exploded_node *dest,
+			      const superedge *sedge)
+: m_src (src), m_dest (dest), m_sedge (sedge)
+{
+}
+
+/* FIXME.  */
+
+void
+exploded_edge::dump_dot (graphviz_out *gv) const
+{
+  pretty_printer *pp = gv->m_pp;
+
+  const char *style = "\"solid,bold\"";
+  const char *color = "black";
+  int weight = 10;
+  const char *constraint = "true";
+
+  if (m_sedge)
+    switch (m_sedge->m_kind)
+      {
+      default:
+	gcc_unreachable ();
+      case SUPEREDGE_CFG_EDGE:
+	break;
+      case SUPEREDGE_CALL:
+	color = "red";
+	//constraint = "false";
+	break;
+      case SUPEREDGE_RETURN:
+	color = "green";
+	//constraint = "false";
+	break;
+      case SUPEREDGE_INTRAPROCEDURAL_CALL:
+	style = "\"dotted\"";
+	break;
+      }
+
+  m_src->dump_dot_id (pp);
+  pp_string (pp, " -> ");
+  m_dest->dump_dot_id (pp);
+  pp_printf (pp,
+	     (" [style=%s, color=%s, weight=%d, constraint=%s,"
+	      " headlabel=\""),
+	     style, color, weight, constraint);
+
+  if (m_sedge)
+    m_sedge->dump_label_to_pp (pp);
+
+  pp_printf (pp, "\"];\n");
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+
+/* exploded_graph's ctor.  */
+
+exploded_graph::exploded_graph (const supergraph &sg, logger *logger,
+				const extrinsic_state &ext_state,
+				int verbosity)
+: log_user (logger), m_sg (sg), m_ext_state (ext_state),
+  m_verbosity (verbosity),
+  m_node_reuse_count (0)
+{
+  for (int i = 0; i < NUM_POINT_KINDS; i++)
+    m_num_nodes[i] = 0;
+
+  m_origin = get_or_create_node (program_point (NULL, NULL, 0, PK_ORIGIN,
+						call_string ()),
+				 program_state (ext_state));
+}
+
+/* Ensure that there is an exploded_node representing an external call to
+   FUN, adding it to the worklist if creating it.
+
+   Add an edge from the origin exploded_node to the function entrypoint
+   exploded_node.  */
+
+void
+exploded_graph::add_function_entry (function *fun)
+{
+  program_point point = program_point::from_function_entry (m_sg, fun);
+  program_state state (m_ext_state);
+
+  add_edge (m_origin, get_or_create_node (point, state), NULL);
+}
+
+/* Get or create an exploded_node for (POINT, STATE).
+   If a new node is created, it is added to the worklist.  */
+
+exploded_node *
+exploded_graph::get_or_create_node (const program_point &point,
+				    const program_state &state)
+{
+  JIT_LOG_FUNC (get_logger ());
+
+  /* Prune state to try to improve the chances of a cache hit,
+     avoiding generating redundant nodes.  */
+  program_state pruned_state = state.prune_for_point (*this, point);
+
+  point_and_state ps (point, pruned_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_node_reuse_count++;
+      return *slot;
+    }
+
+  /* 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 (ps, node);
+  ++m_num_nodes[node->get_point ().m_kind];
+
+  if (get_logger ())
+    {
+      log ("created EN: %i", node->m_index);
+      log ("point: %s",
+	   point.to_str ().c_str ());
+      log ("pruned_state: %s",
+	   pruned_state.to_str (m_ext_state).c_str ());
+    }
+
+  /* Add the new node to the worlist.  */
+  m_worklist.safe_push (node);
+  return node;
+}
+
+/* Add an exploded_edge from SRC to DEST, recording its association
+   with SEDGE (which may be NULL).  */
+
+void
+exploded_graph::add_edge (exploded_node *src, exploded_node *dest,
+			  const superedge *sedge)
+{
+  exploded_edge *e = new exploded_edge (src, dest, sedge);
+  digraph::add_edge (e);
+  src->m_succs.safe_push (e);
+}
+
+/* The main loop of the analysis.
+   Pop freshly-created exploded_nodes from the worklist, calling
+   process_node on them to explore the <point, state> graph.
+   Add edges to their successors, potentially creating new successors
+   (which are also added to the worklist).  */
+
+void
+exploded_graph::process_worklist ()
+{
+  while (m_worklist.length () > 0)
+    {
+      exploded_node *node = m_worklist.pop ();
+      process_node (node);
+
+      /* FIXME: make this less ad-hoc?  e.g. a param */
+      /* we use exit rather than entry since there can be multiple
+	 entry ENs, one per phi; the number of PK_AFTER_SUPERNODE ought
+	 to be equivalent to the number of supernodes multiplied by the
+	 number of states.  */
+      if (m_num_nodes[PK_AFTER_SUPERNODE]> m_sg.num_nodes () * 5)
+	{
+	  log ("bailing out; too many nodes");
+	  sorry ("analysis bailed out early");
+	  return;
+	}
+    }
+}
+
+/* The core of exploded_graph::process_worklist (the main analysis loop),
+   handling one node in the worklist.
+
+   Get successor <point, state> pairs for NODE, calling get_or_create on
+   them, and adding an exploded_edge to each successors.
+
+   Freshly-created nodes will be added to the worklist.  */
+
+void
+exploded_graph::process_node (exploded_node *node)
+{
+  JIT_LOG_FUNC_1 (get_logger (), "EN: %i", node->m_index);
+
+  const program_point &point = node->get_point ();
+  const program_state &state = node->get_state ();
+  if (get_logger ())
+    {
+      log ("point: %s, state: %s",
+	   point.to_str ().c_str (),
+	   state.to_str (m_ext_state).c_str ());
+    }
+
+  switch (point.m_kind)
+    {
+    default:
+      gcc_unreachable ();
+    case PK_ORIGIN:
+      /* This node exists to simplify finding the shortest path
+	 to an exploded_node.  */
+      break;
+
+    case PK_BEFORE_SUPERNODE:
+      {
+	program_state next_state (state);
+
+	for (gphi_iterator gpi
+	       = const_cast<supernode *>(node->get_supernode ())->start_phis ();
+	     !gsi_end_p (gpi); gsi_next (&gpi))
+	  {
+	    gphi *phi = gpi.phi ();
+
+	    const cfg_superedge *last_cfg_superedge
+	      = point.m_from_edge->dyn_cast_cfg_superedge ();
+	    gcc_assert (last_cfg_superedge);
+	    // FIXME: this ought to be an as_a<>
+
+	    tree src = last_cfg_superedge->get_phi_arg (phi);
+	    tree lhs = gimple_phi_result (phi);
+
+	    /* Update next_state based on phi.  */
+	    node->on_assignment (*this,
+				 point.make_path_var (lhs),
+				 point.make_path_var (src),
+				 state,
+				 &next_state);
+	  }
+
+	if (point.m_supernode->m_stmts.length () > 0)
+	  {
+	    program_point next_point
+	      = program_point::before_stmt (point.m_supernode, 0,
+					    point.m_call_string);
+	    exploded_node *next
+	      = get_or_create_node (next_point, next_state);
+	    add_edge (node, next, NULL);
+	  }
+	else
+	  {
+	    program_point next_point
+	      = program_point::after_supernode (point.m_supernode,
+						point.m_call_string);
+	    exploded_node *next = get_or_create_node (next_point, next_state);
+	    add_edge (node, next, NULL);
+	  }
+      }
+      break;
+    case PK_BEFORE_STMT:
+      {
+	// TODO: update state based on stmt!
+	program_point next_point
+	  = program_point::after_stmt (point.m_supernode, point.m_stmt_idx,
+				       point.m_call_string);
+	program_state next_state = node->on_stmt (*this);
+	exploded_node *next = get_or_create_node (next_point, next_state);
+	add_edge (node, next, NULL);
+      }
+      break;
+    case PK_AFTER_STMT:
+      {
+	unsigned next_idx = point.m_stmt_idx + 1;
+	if (next_idx < point.m_supernode->m_stmts.length ())
+	  {
+	    program_point next_point
+	      = program_point::before_stmt (point.m_supernode, next_idx,
+					    point.m_call_string);
+	    exploded_node *next
+	      = get_or_create_node (next_point, state);
+	    add_edge (node, next, NULL);
+	  }
+	else
+	  {
+	    program_point next_point
+	      = program_point::after_supernode (point.m_supernode,
+						point.m_call_string);
+	    exploded_node *next = get_or_create_node (next_point, state);
+	    add_edge (node, next, NULL);
+	  }
+      }
+      break;
+    case PK_AFTER_SUPERNODE:
+      {
+	/* If this is an EXIT BB, detect leaks.  */
+	if (point.m_supernode->return_p ())
+	  node->detect_leaks (*this);
+
+	/* Traverse into successors of the supernode.  */
+	int i;
+	superedge *succ;
+	FOR_EACH_VEC_ELT (point.m_supernode->m_succs, i, succ)
+	  {
+	    log ("considering SN: %i -> SN: %i",
+		 succ->m_src->m_index, succ->m_dest->m_index);
+
+	    program_point next_point
+	      = program_point::before_supernode (succ->m_dest, succ,
+						 point.m_call_string);
+	    program_state next_state (state);
+
+	    if (!node->on_edge (*this, succ, &next_point, &next_state))
+	      {
+		log ("skipping impossible edge to SN: %i",
+		     succ->m_dest->m_index);
+		continue;
+	      }
+
+	    exploded_node *next = get_or_create_node (next_point, next_state);
+	    add_edge (node, next, succ);
+	  }
+      }
+      break;
+    }
+}
+
+/* FIXME.  */
+
+void
+exploded_graph::emit_diagnostic (const state_machine &sm,
+				 const exploded_node *enode,
+				 const supernode *node, const gimple *stmt,
+				 stmt_finder *stmt_finder,
+				 tree var, state_machine::state_t state,
+				 pending_diagnostic *d) const
+{
+  /* TODO: generate a path.  */
+  /* TODO: cache the diagnostics, and flush them all at the
+     end, with one shortest_paths instance, and so finding
+     the shortest path, rather than just the first path.  */
+  shortest_paths sp (*this);
+  exploded_path epath (sp.get_shortest_path (enode));
+  pretty_printer *pp = global_dc->printer->clone ();
+
+  checker4_path emission_path;
+  // TODO: build path
+  for (unsigned i = 0; i < epath.m_edges.length (); i++)
+    {
+      const exploded_edge *eedge = epath.m_edges[i];
+      if (get_logger ())
+	log ("EN %i -> EN %i",
+	     eedge->m_src->m_index,
+	     eedge->m_dest->m_index);
+      const exploded_node *src_node = eedge->m_src;
+      const program_point &src_point = src_node->get_point ();
+      const exploded_node *dst_node = eedge->m_dest;
+      const program_point &dst_point = dst_node->get_point ();
+      const int src_stack_depth = src_point.get_stack_depth ();
+      const int dst_stack_depth = dst_point.get_stack_depth ();
+      switch (dst_point.m_kind)
+	{
+	default:
+	  break;
+	case PK_BEFORE_SUPERNODE:
+	  if (src_point.m_kind == PK_AFTER_SUPERNODE)
+	    {
+	      if (eedge->m_sedge)
+		{
+		  switch (eedge->m_sedge->m_kind)
+		    {
+		    case SUPEREDGE_CFG_EDGE:
+		    case SUPEREDGE_CALL:
+		    case SUPEREDGE_INTRAPROCEDURAL_CALL:
+		      {
+			const gimple *last_stmt
+			  = src_point.m_supernode->get_last_stmt ();
+			emission_path.add_event
+			  (new edge_event (eedge->m_sedge,
+					   (last_stmt
+					    ? last_stmt->location
+					    : UNKNOWN_LOCATION),
+					   src_point.m_supernode->m_fun->decl,
+					   src_stack_depth));
+		      }
+		      break;
+		    case SUPEREDGE_RETURN:
+		      {
+			const return_superedge *return_edge
+			  = eedge->m_sedge->dyn_cast_return_superedge ();
+			gcc_assert (return_edge);
+			// FIXME: use as_a<>
+
+			const gcall *call_stmt = return_edge->get_call_stmt ();
+			emission_path.add_event
+			  (new edge_event (eedge->m_sedge,
+					   (call_stmt
+					    ? call_stmt->location
+					    : UNKNOWN_LOCATION),
+					   dst_point.m_supernode->m_fun->decl,
+					   dst_stack_depth));
+		      }
+		      break;
+		    }
+		}
+	    }
+	  // TODO: add function entry events
+	  if (dst_point.m_supernode->entry_p ())
+	    {
+	      emission_path.add_event
+		(new function_entry_event
+		 (dst_point.m_supernode->get_start_location (),
+		  dst_point.m_supernode->m_fun->decl,
+		  dst_stack_depth));
+	    }
+	  break;
+	case PK_BEFORE_STMT:
+	  emission_path.add_event
+	    (new statement_event (dst_point.get_stmt (),
+				  dst_point.m_supernode->m_fun->decl,
+				  dst_stack_depth));
+	  break;
+	case PK_AFTER_STMT:
+	  break;
+	}
+
+      /* Add state change events for the states that have changed.  */
+      const program_state &src_state = src_node->get_state ();
+      const program_state &dst_state = dst_node->get_state ();
+      const extrinsic_state &ext_state = m_ext_state;
+      gcc_assert (src_state.m_checker_states.length ()
+		  == ext_state.m_checkers.length ());
+      gcc_assert (dst_state.m_checker_states.length ()
+		  == ext_state.m_checkers.length ());
+      for (unsigned i = 0; i < ext_state.m_checkers.length (); i++)
+	{
+	  const state_machine &sm = ext_state.get_sm (i);
+	  const sm_state_map &src_smap = *src_state.m_checker_states[i];
+	  const sm_state_map &dst_smap = *dst_state.m_checker_states[i];
+	  for (sm_state_map::iterator_t iter = dst_smap.begin ();
+	       iter != dst_smap.end ();
+	       ++iter)
+	    {
+	      path_var pv = (*iter).first;
+	      state_machine::state_t dst_sm_val = (*iter).second.m_state;
+	      state_machine::state_t src_sm_val = src_smap.get_state (pv);
+	      if (dst_sm_val != src_sm_val)
+		{
+		  const gimple *stmt = dst_point.get_stmt ();
+		  const supernode *supernode = dst_point.m_supernode;
+		  int stack_depth = dst_stack_depth;
+
+		  if (eedge->m_sedge
+		      && eedge->m_sedge->m_kind == SUPEREDGE_CFG_EDGE)
+		    {
+		      supernode = src_point.m_supernode;
+		      stmt = supernode->get_last_stmt ();
+		      stack_depth = src_stack_depth;
+		    }
+
+		  /* FIXME: bulletproofing for state changes at calls/returns;
+		     is there a better way? */
+		  if (!stmt)
+		    continue;
+
+		  emission_path.add_event
+		    (new state_change_event (supernode,
+					     stmt,
+					     stack_depth,
+					     sm,
+					     pv.m_tree,
+					     src_sm_val,
+					     dst_sm_val,
+					     (*iter).second.m_origin.m_tree));
+		}
+	    }
+	}
+    }
+
+  /* Support deferring the choice of stmt until after an emission path has
+     been built, using an optional stmt_finder.  */
+  if (stmt == NULL)
+    {
+      gcc_assert (stmt_finder);
+      stmt = stmt_finder->find_stmt (epath);
+      gcc_assert (stmt);
+    }
+
+  prune_path (&emission_path, sm, var, state);
+  emission_path.add_final_event (sm, enode, stmt, var, state, d);
+  emission_path.prepare_for_emission (d);
+
+  gcc_rich_location rich_loc (stmt->location);
+  rich_loc.set_path (&emission_path);
+
+  auto_cfun sentinel (node->m_fun);
+  d->emit (&rich_loc);
+  delete pp;
+}
+
+/* FIXME.  */
+
+void
+exploded_graph::prune_path (checker4_path *path,
+			    const state_machine &sm,
+			    tree var,
+			    state_machine::state_t state ATTRIBUTE_UNUSED) const
+{
+  JIT_LOG_FUNC (get_logger ());
+  path->maybe_log (get_logger (), "path");
+
+  // TODO
+  /* TODO: go backwards, skipping state change events that aren't var
+     but update the pertinent var when state-copying occurs.  */
+  int idx = path->m_events.length () - 1;
+  while (idx >= 0 && idx < path->m_events.length ())
+    {
+      checker4_event *base_event = path->m_events[idx];
+      if (get_logger ())
+	log  ("considering event %i, with var: %qE, state: %qs",
+	      idx, var, sm.get_state_name (state));
+      switch (base_event->m_kind)
+	{
+	default:
+	  gcc_unreachable ();
+
+	case EK_DEBUG:
+	  if (m_verbosity < 2)
+	    {
+	      log ("filtering event %i: debug event", idx);
+	      path->delete_event (idx);
+	    }
+	  break;
+
+	case EK_STMT:
+	  if (m_verbosity < 2)
+	    {
+	      log ("filtering event %i: statement event", idx);
+	      path->delete_event (idx);
+	    }
+	  break;
+
+	case EK_FUNCTION_ENTRY:
+	  if (m_verbosity < 1)
+	    {
+	      log ("filtering event %i: function entry", idx);
+	      path->delete_event (idx);
+	    }
+	  break;
+
+	case EK_STATE_CHANGE:
+	  {
+	    state_change_event *state_change = (state_change_event *)base_event;
+	    if (state_change->m_var == var)
+	      {
+		if (state_change->m_origin)
+		  {
+		    log ("event %i: switching var of interest from %qE to %qE",
+			 idx, var, state_change->m_origin);
+		    var = state_change->m_origin;
+		  }
+	      }
+	    else if (m_verbosity < 2)
+	      {
+		log ("filtering event %i: state change to %qE unrelated to %qE",
+		     idx, state_change->m_var, var);
+		path->delete_event (idx);
+	      }
+	  }
+	  break;
+
+	case EK_EDGE:
+	  {
+	    edge_event *event = (edge_event *)base_event;
+#if 0
+	    inform (base_event->m_loc, "considering edge_event %i: %s",
+		    idx, base_event->m_desc);
+#endif
+	    switch (event->m_edge->m_kind)
+	      {
+	      case SUPEREDGE_CFG_EDGE:
+		{
+		  const cfg_superedge& cfg_superedge
+		    = event->get_cfg_superedge ();
+		  const supernode *dest = event->m_edge->m_dest;
+		  /* Do we have an SSA_NAME defined via a phi node in
+		     the dest CFG node?  */
+		  if (TREE_CODE (var) == SSA_NAME)
+		    if (SSA_NAME_DEF_STMT (var)->bb == dest->m_bb)
+		      {
+			if (const gphi *phi
+			    = dyn_cast <gphi *> (SSA_NAME_DEF_STMT (var)))
+			  {
+			    /* Update var based on its phi node.  */
+			    tree old_var = var;
+			    var = cfg_superedge.get_phi_arg (phi);
+			    log ("updating from %qE to %qE based on phi node",
+				 old_var, var);
+			    if (get_logger ())
+			      {
+				pretty_printer pp;
+				pp_gimple_stmt_1 (&pp, (gimple *)phi, 0, (dump_flags_t)0);
+				// FIXME: const_cast!
+				log ("  phi: %s", pp_formatted_text (&pp));
+			      }
+			  }
+		      }
+
+		  /* TODO: is this edge significant to var?
+		     See if var can be in other states in the dest, but not
+		     in other states in the src?
+		     Must have multiple sibling edges.  */
+		  // TODO: for now, filter all intraprocedural edges:
+		  if (m_verbosity < 2)
+		    {
+		      log ("filtering event %i: CFG edge", idx);
+		      path->delete_event (idx);
+		    }
+		}
+		break;
+
+	      case SUPEREDGE_CALL:
+		{
+		  const callgraph_superedge& cg_superedge
+		    = event->get_callgraph_superedge ();
+		  tree caller_var
+		    = cg_superedge.map_expr_from_callee_to_caller (var);
+		  if (caller_var)
+		    var = caller_var;
+		}
+		break;
+	      case SUPEREDGE_RETURN:
+		// TODO: potentially update var/state based on return value,
+		// args etc
+		{
+		  const callgraph_superedge& cg_superedge
+		    = event->get_callgraph_superedge ();
+		  tree callee_var
+		    = cg_superedge.map_expr_from_caller_to_callee (var);
+		  if (callee_var)
+		    var = callee_var;
+		}
+		break;
+	      case SUPEREDGE_INTRAPROCEDURAL_CALL:
+		break;
+	      }
+	  }
+	  break;
+
+	case EK_WARNING:
+	  break;
+	}
+      idx--;
+    }
+
+  /* Second pass to remove redundant interprocedural information.  For
+     example, given:
+     - calling "f2" from "f1"
+     --- entry to "f2"
+     --- calling "f3" from "f2"
+     ----- entry to "f3"
+     --- returning to "f2" to "f3"
+     - returning to "f1" to "f2"
+     with no other intervening events, then none of these events are
+     likely to be interesting to the user.
+
+     Prune [..., call, function-entry, return, ...] triples repeatedly
+     until nothing has changed.  */
+  bool changed = false;
+  do
+    {
+      changed = false;
+      int idx = path->m_events.length () - 3;
+      while (idx >= 0 && idx < path->m_events.length ())
+	{
+	  if (path->m_events[idx]->is_call_p ()
+	      && path->m_events[idx + 1]->is_function_entry_p ()
+	      && path->m_events[idx + 2]->is_return_p ())
+	    {
+	      if (get_logger ())
+		{
+		  label_text desc (path->m_events[idx]->get_desc ());
+		  log ("filtering events %i-%i:"
+		       " irrelevant call/entry/return: %s",
+		       idx, idx + 2, desc.m_buffer);
+		  desc.maybe_free ();
+		}
+	      path->delete_event (idx + 2);
+	      path->delete_event (idx + 1);
+	      path->delete_event (idx);
+	      changed = true;
+	    }
+	  idx--;
+	}
+
+    }
+  while (changed);
+
+  /* Third pass.  If all we're left with is in one function, then
+     filter function entry events.  */
+  if (!path->interprocedural_p ())
+    {
+      // FIXME
+      int idx = path->m_events.length () - 1;
+      while (idx >= 0 && idx < path->m_events.length ())
+	{
+	  checker4_event *base_event = path->m_events[idx];
+	  if (base_event->m_kind == EK_FUNCTION_ENTRY)
+	    {
+	      log ("filtering event %i:"
+		   " function entry for purely intraprocedural path", idx);
+	      path->delete_event (idx);
+	    }
+	  idx--;
+	}
+    }
+}
+
+/* FIXME.  */
+
+void
+exploded_graph::log_stats () const
+{
+  if (!get_logger ())
+    return;
+
+  log ("m_sg.num_nodes (): %i", m_sg.num_nodes ());
+  log ("m_nodes.length (): %i", m_nodes.length ());
+  for (int i = 0; i < NUM_POINT_KINDS; i++)
+    log ("m_num_nodes[%s]: %i",
+	 point_kind_to_string (static_cast <enum point_kind> (i)),
+	 m_num_nodes[i]);
+  log ("m_node_reuse_count: %i", m_node_reuse_count);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Look for the last use of SEARCH_STMT within this path.
+   If found write the edge's index to *OUT_IDX and return true, otherwise
+   return false.  */
+
+bool
+exploded_path::find_stmt_backwards (const gimple *search_stmt,
+				    int *out_idx) const
+{
+  int i;
+  const exploded_edge *eedge;
+  FOR_EACH_VEC_ELT_REVERSE (m_edges, i, eedge)
+    {
+      const exploded_node *dst_node = eedge->m_dest;
+      const program_point &dst_point = dst_node->get_point ();
+      const gimple *stmt = dst_point.get_stmt ();
+      if (stmt == search_stmt)
+	{
+	  *out_idx = i;
+	  return true;
+	}
+    }
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* shortest_paths's constructor.
+
+   Use Dijkstra's algorithm relative to the origin of EG to populate
+   m_dist and m_prev with enough information to be able to generate
+   exploded_path instances to give the shortest path to any node in EG
+   from the origin node.  */
+
+shortest_paths::shortest_paths (const exploded_graph &eg)
+: m_eg (eg),
+  m_dist (eg.m_nodes.length ()),
+  m_prev (eg.m_nodes.length ())
+{
+  auto_vec<int> queue (eg.m_nodes.length ());
+
+  for (unsigned i = 0; i < eg.m_nodes.length (); i++)
+    {
+      m_dist.quick_push (INT_MAX);
+      m_prev.quick_push (NULL);
+      queue.quick_push (i);
+    }
+  m_dist[eg.get_origin ()->m_index] = 0;
+
+  while (queue.length () > 0)
+    {
+      /* Get minimal distance in queue.
+	 FIXME: this is O(N^2); replace with a priority queue.  */
+      int idx_with_min_dist = -1;
+      int idx_in_queue_with_min_dist = -1;
+      int min_dist = INT_MAX;
+      for (unsigned i = 0; i < queue.length (); i++)
+	{
+	  int idx = queue[i];
+	  if (m_dist[queue[i]] < min_dist)
+	    {
+	      min_dist = m_dist[idx];
+	      idx_with_min_dist = idx;
+	      idx_in_queue_with_min_dist = i;
+	    }
+	}
+      gcc_assert (idx_with_min_dist != -1);
+      gcc_assert (idx_in_queue_with_min_dist != -1);
+
+      // FIXME: this is confusing: there are two indices here
+
+      queue.unordered_remove (idx_in_queue_with_min_dist);
+
+      exploded_node *n
+	= static_cast <exploded_node *> (m_eg.m_nodes[idx_with_min_dist]);
+
+      int i;
+      exploded_edge *succ;
+      FOR_EACH_VEC_ELT (n->m_succs, i, succ)
+	{
+	  // TODO: only for dest still in queue
+	  exploded_node *dest = succ->m_dest;
+	  int alt = m_dist[n->m_index] + 1;
+	  if (alt < m_dist[dest->m_index])
+	    {
+	      m_dist[dest->m_index] = alt;
+	      m_prev[dest->m_index] = succ;
+	    }
+	}
+   }
+}
+
+/* Gnerate an exploded_path instance giving the shortest path to the node
+   TO from the origin node.  */
+
+exploded_path
+shortest_paths::get_shortest_path (const exploded_node *to) const
+{
+  exploded_path result;
+
+  while (m_prev[to->m_index])
+    {
+      result.m_edges.safe_push (m_prev[to->m_index]);
+      to = m_prev[to->m_index]->m_src;
+    }
+
+  result.m_edges.reverse ();
+
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+#if 0
+checker4_path::checker4_path (const checker4_path &other)
+: m_engine (other.m_engine),
+  m_links (),
+  m_ps (other.m_ps),
+  m_terminator (other.m_terminator)
+{
+  m_engine->m_num_path_copies++;
+  m_engine->m_num_event_copies += other.m_events.length ();
+
+  unsigned i;
+  {
+    link *l;
+    FOR_EACH_VEC_ELT (other.m_links, i, l)
+      m_links.safe_push (*l);
+  }
+  {
+    cgraph_edge *edge;
+    FOR_EACH_VEC_ELT (other.m_active_calls, i, edge)
+      m_active_calls.safe_push (edge);
+  }
+  {
+    checker4_event *e;
+    FOR_EACH_VEC_ELT (other.m_events, i, e)
+      m_events.safe_push (e->clone ());
+  }
+  // FIXME: quick push for these?
+}
+
+checker4_path&
+checker4_path::operator= (const checker4_path &other)
+{
+  m_engine = other.m_engine;
+  m_ps = other.m_ps;
+  m_terminator = other.m_terminator;
+
+  m_engine->m_num_path_copies++;
+  m_engine->m_num_event_copies += other.m_events.length ();
+
+  unsigned i;
+  {
+    link *l;
+    FOR_EACH_VEC_ELT (other.m_links, i, l)
+      m_links.safe_push (*l);
+  }
+  {
+    cgraph_edge *edge;
+    FOR_EACH_VEC_ELT (other.m_active_calls, i, edge)
+      m_active_calls.safe_push (edge);
+  }
+  {
+    checker4_event *e;
+    FOR_EACH_VEC_ELT (other.m_events, i, e)
+      m_events.safe_push (e->clone ());
+  }
+  return *this;
+}
+#endif
+
+/* FIXME.  */
+std::string
+checker4_path::to_str () const
+{
+  std::string result ("[");
+
+  checker4_event *e;
+  int i;
+  FOR_EACH_VEC_ELT (m_events, i, e)
+    {
+      if (i > 0)
+	result += ", ";
+      label_text event_desc (e->get_desc ());
+      result += std::string ("\"") + event_desc.m_buffer + std::string ("\"");
+      event_desc.maybe_free ();
+    }
+  result += "]";
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* A wrapper around hash_map which preserves insertion order.
+   The keys must be PODs, since vec<> uses assignment to populate slots
+   without properly initializing them.  */
+
+template<typename KeyId, typename Value,
+	 typename Traits = simple_hashmap_traits<default_hash_traits<KeyId>,
+						 Value> >
+class ordered_hash_map
+{
+  typedef hash_map <KeyId, Value> map_t;
+  typedef auto_vec <KeyId> vec_t;
+
+public:
+  typedef typename Traits::key_type Key;
+
+  Value *get (const KeyId &k)
+  {
+    return m_map.get (k);
+  }
+
+  bool put (const Key &k, const Value &v)
+  {
+    bool existed = m_map.put (k, v);
+    if (!existed)
+      m_vector.safe_push (k);
+    return existed;
+  }
+
+  class iterator
+  {
+  public:
+    typedef typename map_t::iterator::reference_pair reference_pair;
+
+    iterator (const ordered_hash_map &ordmap, int idx)
+    : m_ordmap (ordmap), m_idx (idx) {}
+
+    iterator &operator++ ()
+    {
+      ++m_idx;
+      return *this;
+    }
+
+    reference_pair operator* ()
+    {
+      KeyId k = m_ordmap.m_vector[m_idx];
+      Value *slot = const_cast <ordered_hash_map &> (m_ordmap).m_map.get (k);
+      return reference_pair (k, *slot);
+    }
+
+    bool
+    operator != (const iterator &other) const
+    {
+      return m_idx != other.m_idx;
+    }
+
+  private:
+    const ordered_hash_map &m_ordmap;
+    int m_idx;
+  };
+
+  iterator begin () const { return iterator (*this, 0); }
+  iterator end () const { return iterator (*this, m_vector.length ()); }
+
+private:
+  map_t m_map;
+  vec_t m_vector;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Subclass of cluster which casts the dnode instances to exploded_node.  */
+
+class en_cluster : public cluster
+{
+public:
+  void add_node (dnode *node) FINAL OVERRIDE
+  {
+    exploded_node *en = static_cast <exploded_node *> (node);
+    add_exploded_node (en);
+  }
+  virtual void add_exploded_node (exploded_node *en) = 0;
+};
+
+/* Cluster containing all exploded_node instances for one supernode.  */
+
+class supernode_cluster : public en_cluster
+{
+public:
+  supernode_cluster (const supernode *supernode) : m_supernode (supernode) {}
+
+  // TODO: dtor?
+
+  void dump_dot (graphviz_out *gv) const FINAL OVERRIDE
+  {
+    gv->println ("subgraph \"cluster_supernode_%p\" {",
+		 (const void *)this);
+    gv->indent ();
+    gv->println ("style=\"dashed\";");
+    gv->println ("label=\"SN: %i\";", m_supernode->m_index);
+
+    int i;
+    exploded_node *enode;
+    FOR_EACH_VEC_ELT (m_enodes, i, enode)
+      enode->dump_dot (gv);
+
+    /* Terminate subgraph.  */
+    gv->outdent ();
+    gv->println ("}");
+  }
+
+  void add_exploded_node (exploded_node *en) FINAL OVERRIDE
+  {
+    m_enodes.safe_push (en);
+  }
+
+private:
+  const supernode *m_supernode;
+  auto_vec <exploded_node *> m_enodes;
+};
+
+/* Cluster containing all supernode_cluster instances for one
+   (function, call_string) pair.  */
+
+class function_call_string_cluster : public en_cluster
+{
+public:
+  function_call_string_cluster (function *fun, call_string cs)
+  : m_fun (fun), m_cs (cs) {}
+
+  // TODO: dtor
+
+  void dump_dot (graphviz_out *gv) const FINAL OVERRIDE
+  {
+    const char *funcname = function_name (m_fun);
+
+    gv->println ("subgraph \"cluster_function_%p\" {", (const void *)this);
+    gv->indent ();
+    gv->println ("label=\"call string: %s function: %s \";",
+		 m_cs.to_str ().c_str (), funcname);
+
+    for (map_t::iterator iter = m_map.begin ();
+	 iter != m_map.end ();
+	 ++iter)
+      (*iter).second->dump_dot (gv);
+
+    /* Terminate subgraph.  */
+    gv->outdent ();
+    gv->println ("}");
+  }
+
+  void add_exploded_node (exploded_node *en) FINAL OVERRIDE
+  {
+    const supernode *supernode = en->get_supernode ();
+    gcc_assert (supernode);
+    supernode_cluster **slot = m_map.get (supernode);
+    if (slot)
+      (*slot)->add_exploded_node (en);
+    else
+      {
+	supernode_cluster *child = new supernode_cluster (supernode);
+	m_map.put (supernode, child);
+	child->add_exploded_node (en);
+      }
+  }
+
+private:
+  function *m_fun;
+  call_string m_cs;
+  typedef ordered_hash_map<const supernode *, supernode_cluster *> map_t;
+  map_t m_map;
+};
+
+/* Keys for root_cluster.  */
+
+struct function_call_string
+{
+  function_call_string (function *fun, call_string cs)
+  : m_fun (fun), m_cs (cs)
+  {
+    gcc_assert (fun);
+  }
+
+  function *m_fun;
+  call_string m_cs;
+};
+
+} // namespace impl_4
+
+using impl_4::pod_hash_traits;
+using impl_4::function_call_string;
+
+template <> struct default_hash_traits<function_call_string>
+: public pod_hash_traits<function_call_string>
+{
+};
+
+template <>
+inline hashval_t
+pod_hash_traits<function_call_string>::hash (value_type v)
+{
+  return pointer_hash <function>::hash (v.m_fun) ^ v.m_cs.hash ();
+}
+
+template <>
+inline bool
+pod_hash_traits<function_call_string>::equal (const value_type &existing,
+					      const value_type &candidate)
+{
+  return existing.m_fun == candidate.m_fun && existing.m_cs == candidate.m_cs;
+}
+template <>
+inline void
+pod_hash_traits<function_call_string>::mark_deleted (value_type &v)
+{
+  v.m_fun = reinterpret_cast<function *> (1);
+}
+template <>
+inline void
+pod_hash_traits<function_call_string>::mark_empty (value_type &v)
+{
+  v.m_fun = reinterpret_cast<function *> (NULL);
+}
+template <>
+inline bool
+pod_hash_traits<function_call_string>::is_deleted (value_type v)
+{
+  return v.m_fun == reinterpret_cast<function *> (1);
+}
+template <>
+inline bool
+pod_hash_traits<function_call_string>::is_empty (value_type v)
+{
+  return v.m_fun == reinterpret_cast<function *> (NULL);
+}
+
+namespace impl_4 {
+
+/* Top-level cluster for printing exploded graphs, handling the functionless
+   nodes, and grouping the remaining nodes by callstring.  */
+
+class root_cluster : public en_cluster
+{
+public:
+  ~root_cluster ()
+  {
+    // TODO: delete the function_cluster instances in the map
+  }
+
+  void dump_dot (graphviz_out *gv) const FINAL OVERRIDE
+  {
+    int i;
+    exploded_node *enode;
+    FOR_EACH_VEC_ELT (m_functionless_enodes, i, enode)
+      enode->dump_dot (gv);
+
+    for (map_t::iterator iter = m_map.begin ();
+	 iter != m_map.end ();
+	 ++iter)
+      (*iter).second->dump_dot (gv);
+  }
+
+  void add_exploded_node (exploded_node *en) FINAL OVERRIDE
+  {
+    function *fun = en->get_function ();
+    if (!fun)
+      {
+	m_functionless_enodes.safe_push (en);
+	return;
+      }
+
+    const call_string &cs = en->get_point ().m_call_string;
+    function_call_string key (fun, cs);
+    function_call_string_cluster **slot = m_map.get (key);
+    if (slot)
+      (*slot)->add_exploded_node (en);
+    else
+      {
+	function_call_string_cluster *child
+	  = new function_call_string_cluster (fun, cs);
+	m_map.put (key, child);
+	child->add_exploded_node (en);
+      }
+  }
+
+private:
+  /* This can't be an ordered_map, as we can't store vec<call_string>,
+     since it's not a POD; vec<>::quick_push has:
+       *slot = obj;
+     and the slot isn't initialized, so the assignment op dies when cleaning up
+     un-inited *slot (within the truncate call).  */
+  typedef hash_map<function_call_string, function_call_string_cluster *> map_t;
+  map_t m_map;
+
+  /* This should just be the origin exploded_node.  */
+  auto_vec <exploded_node *> m_functionless_enodes;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+#if 0
+/* FIXME.  */
+
+static int
+function_comparator (const void *pn1, const void *pn2)
+{
+  const cgraph_node *node1 = *(const cgraph_node * const *)pn1;
+  const cgraph_node *node2 = *(const cgraph_node * const *)pn2;
+
+  location_t loc1 = node1->get_fun ()->function_start_locus;
+  location_t loc2 = node2->get_fun ()->function_start_locus;
+
+  return linemap_compare_locations (line_table, loc2, loc1);
+}
+#endif
+
+/* FIXME.  */
+
+void
+impl_run_checkers (logger *logger)
+{
+  JIT_LOG_SCOPE (logger);
+
+  /* Create the supergraph.  */
+  supergraph sg (logger);
+
+  if (1)
+    {
+      char *filename = concat (dump_base_name, ".supergraph.dot", NULL);
+      sg.dump_dot (filename, (enum supergraph_dot_flags)0);
+      free (filename);
+    }
+
+  // TODO: selftest for auto_delete_vec
+  auto_delete_vec <state_machine> checkers;
+  make_checkers (checkers, logger);
+
+  if (logger)
+    {
+      int i;
+      state_machine *sm;
+      FOR_EACH_VEC_ELT (checkers, i, sm)
+	logger->log ("checkers[%i]: %s", i, sm->get_name ());
+    }
+  //engine e (sg, sm, logger, analyzer_verbosity);
+
+  // TODO: add initial nodes
+  impl_4::exploded_graph eg (sg, logger, checkers, analyzer_verbosity);
+
+  cgraph_node *node;
+  FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
+  {
+    function *fun = node->get_fun ();
+    if (!TREE_PUBLIC (fun->decl))
+      {
+	if (logger)
+	  logger->log ("not traversing %qE (static)", fun->decl);
+	continue;
+      }
+    eg.add_function_entry (fun);
+  }
+
+  eg.process_worklist ();
+
+  if (1)
+    {
+      char *filename
+	= concat (dump_base_name, ".eg.dot", NULL);
+      root_cluster c;
+      eg.dump_dot (filename, &c);
+      free (filename);
+    }
+
+  eg.log_stats ();
+}
+
+} // namespace impl_4
+
+/* FIXME.  */
+
+void
+run_checkers_impl_4 ()
+{
+  //FILE *dump = fopen ("test.out", "w"); // FIXME
+  auto_client_timevar tv ("run_checkers");
+  {
+    log_user the_logger (NULL);
+    if (flag_dump_analyzer)
+      {
+	FILE *dump_file = /* 0 ? dump : */stderr; // FIXME
+	the_logger.set_logger (new logger (dump_file, 0, 0,
+					   *global_dc->printer));
+      }
+    JIT_LOG_SCOPE (the_logger.get_logger ());
+
+    impl_4::impl_run_checkers (the_logger.get_logger ());
+
+    /* end of lifetime of the_logger (so that dump file is closed after the
+       various dtors run).  */
+  }
+
+  //fclose (dump);
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Tests for impl_4::sm_state_map.  */
+
+static void
+test_sm_state_map ()
+{
+  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);
+  tree z = build_decl (UNKNOWN_LOCATION, PARM_DECL,
+		       get_identifier ("z"),
+		       integer_type_node);
+  path_var x0 (x, 0);
+  path_var y0 (y, 0);
+  path_var z0 (z, 0);
+
+  /* Test setting states on path_var instances directly.  */
+  {
+    impl_4::sm_state_map map;
+    ASSERT_TRUE (map.is_empty_p ());
+    ASSERT_EQ (map.get_state (x0), 0);
+
+    map.impl_set_state (x0, 42, z0);
+    ASSERT_EQ (map.get_state (x0), 42);
+    ASSERT_EQ (map.get_origin (x0), z0);
+    ASSERT_EQ (map.get_state (y0), 0);
+    ASSERT_FALSE (map.is_empty_p ());
+
+    map.impl_set_state (y0, 0, z0);
+    ASSERT_EQ (map.get_state (y0), 0);
+
+    map.impl_set_state (x0, 0, z0);
+    ASSERT_EQ (map.get_state (x0), 0);
+    ASSERT_TRUE (map.is_empty_p ());
+  }
+
+  /* Test setting states via equivalence classes.  */
+  {
+    impl_4::sm_state_map map;
+    ASSERT_TRUE (map.is_empty_p ());
+    ASSERT_EQ (map.get_state (x0), 0);
+    ASSERT_EQ (map.get_state (y0), 0);
+
+    path_state ps;
+    ps.add_constraint (x0, EQ_EXPR, y0);
+
+    /* Setting x to a state should also update y, as they
+       are in the same equivalence class.  */
+    map.set_state (&ps, x0, 5, z0);
+    ASSERT_EQ (map.get_state (x0), 5);
+    ASSERT_EQ (map.get_state (y0), 5);
+    ASSERT_EQ (map.get_origin (x0), z0);
+    ASSERT_EQ (map.get_origin (y0), z0);
+  }
+
+  /* Test equality and hashing.  */
+  {
+    impl_4::sm_state_map map0;
+    impl_4::sm_state_map map1;
+    impl_4::sm_state_map map2;
+
+    ASSERT_EQ (map0.hash (), map1.hash ());
+    ASSERT_EQ (map0, map1);
+
+    map1.impl_set_state (y0, 5, z0);
+    ASSERT_NE (map0.hash (), map1.hash ());
+    ASSERT_NE (map0, map1);
+
+    /* Make the same change to map2.  */
+    map2.impl_set_state (y0, 5, z0);
+    ASSERT_EQ (map1.hash (), map2.hash ());
+    ASSERT_EQ (map1, map2);
+  }
+}
+
+/* FIXME.  */
+
+static void
+test_assignments ()
+{
+  /* Verify X = Y, then Y = Z.
+     Should propagate states accordingly.  */
+#if 0
+exploded_node::on_assignment (const exploded_graph &eg,
+			      path_var lhs,
+			      path_var rhs,
+			      const program_state &old_state,
+			      program_state *new_state) const
+#endif
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+analyzer_impl_4_cc_tests ()
+{
+  test_sm_state_map ();
+  test_assignments ();
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
diff --git a/gcc/analyzer/supergraph.cc b/gcc/analyzer/supergraph.cc
index 01f1a9d..31609e2 100644
--- a/gcc/analyzer/supergraph.cc
+++ b/gcc/analyzer/supergraph.cc
@@ -161,8 +161,9 @@ supergraph::supergraph (logger *logger)
 	  supernode *callee_supernode
 	    = *m_bb_to_initial_node.get (callee_cfg_block);
 	  call_superedge *sedge
-	    = add_callgraph_edge (caller_prev_supernode, callee_supernode,
-				  SUPEREDGE_CALL, edge);
+	    = add_call_superedge (caller_prev_supernode,
+				  callee_supernode,
+				  edge);
 	  m_cgraph_edge_to_call_superedge.put (edge, sedge);
 	}
     }
@@ -180,9 +181,10 @@ supergraph::supergraph (logger *logger)
 	    = EXIT_BLOCK_PTR_FOR_FN (edge->callee->get_fun ());
 	  supernode *callee_supernode
 	    = *m_bb_to_initial_node.get (callee_cfg_block);
-	  call_superedge *sedge
-	    = add_callgraph_edge (callee_supernode, caller_next_supernode,
-				  SUPEREDGE_RETURN, edge);
+	  return_superedge *sedge
+	    = add_return_superedge (callee_supernode,
+				    caller_next_supernode,
+				    edge);
 	  m_cgraph_edge_to_return_superedge.put (edge, sedge);
 	}
     }
@@ -198,9 +200,12 @@ supergraph::supergraph (logger *logger)
 	  supernode *caller_prev_supernode = (*iter).second;
 	  supernode *caller_next_supernode
 	    = *m_cgraph_edge_to_caller_next_node.get (edge);
-	  add_callgraph_edge (caller_prev_supernode,
-			      caller_next_supernode,
-			      SUPEREDGE_INTRAPROCEDURAL_CALL, edge);
+	  superedge *sedge
+	    = new callgraph_superedge (caller_prev_supernode,
+				       caller_next_supernode,
+				       SUPEREDGE_INTRAPROCEDURAL_CALL,
+				       edge);
+	  add_edge (sedge);
 	}
 
     }
@@ -349,10 +354,19 @@ supergraph::add_cfg_edge (supernode *src, supernode *dest, ::edge e, int idx)
 }
 
 call_superedge *
-supergraph::add_callgraph_edge (supernode *src, supernode *dest,
-				enum edge_kind kind, cgraph_edge *cedge)
+supergraph::add_call_superedge (supernode *src, supernode *dest,
+				cgraph_edge *cedge)
+{
+  call_superedge *new_edge = new call_superedge (src, dest, cedge);
+  add_edge (new_edge);
+  return new_edge;
+}
+
+return_superedge *
+supergraph::add_return_superedge (supernode *src, supernode *dest,
+				  cgraph_edge *cedge)
 {
-  call_superedge *new_edge = new call_superedge (src, dest, kind, cedge);
+  return_superedge *new_edge = new return_superedge (src, dest, cedge);
   add_edge (new_edge);
   return new_edge;
 }
@@ -655,7 +669,7 @@ superedge::get_any_cfg_edge () const
 cgraph_edge *
 superedge::get_any_callgraph_edge () const
 {
-  if (const call_superedge *sub = dyn_cast_call_superedge ())
+  if (const callgraph_superedge *sub = dyn_cast_callgraph_superedge ())
     return sub->m_cedge;
   return NULL;
 }
@@ -665,7 +679,7 @@ superedge::get_any_callgraph_edge () const
 cgraph_edge *
 superedge::get_callgraph_edge () const
 {
-  const call_superedge *sub = dyn_cast_call_superedge ();
+  const callgraph_superedge *sub = dyn_cast_callgraph_superedge ();
   gcc_assert (sub);
   return sub->m_cedge;
 }
@@ -757,7 +771,7 @@ switch_cfg_superedge::get_case_label () const
 /* FIXME.  */
 
 void
-call_superedge::dump_label_to_pp (pretty_printer *pp) const
+callgraph_superedge::dump_label_to_pp (pretty_printer *pp) const
 {
   /* FIXME.  */
   switch (m_kind)
@@ -783,7 +797,7 @@ call_superedge::dump_label_to_pp (pretty_printer *pp) const
 /* FIXME.  */
 
 tree
-call_superedge::get_callee_decl () const
+callgraph_superedge::get_callee_decl () const
 {
   return m_cedge->callee->get_fun ()->decl;
 }
@@ -791,7 +805,7 @@ call_superedge::get_callee_decl () const
 /* FIXME.  */
 
 tree
-call_superedge::get_caller_decl () const
+callgraph_superedge::get_caller_decl () const
 {
   return m_cedge->caller->get_fun ()->decl;
 }
@@ -799,7 +813,7 @@ call_superedge::get_caller_decl () const
 /* FIXME.  */
 
 tree
-call_superedge::get_arg_for_parm (tree parm_to_find) const
+callgraph_superedge::get_arg_for_parm (tree parm_to_find) const
 {
   gcc_assert  (TREE_CODE (parm_to_find) == PARM_DECL);
 
@@ -820,7 +834,7 @@ call_superedge::get_arg_for_parm (tree parm_to_find) const
 /* FIXME.  */
 
 tree
-call_superedge::get_parm_for_arg (tree arg_to_find) const
+callgraph_superedge::get_parm_for_arg (tree arg_to_find) const
 {
   tree callee = get_callee_decl ();
 
@@ -841,7 +855,7 @@ call_superedge::get_parm_for_arg (tree arg_to_find) const
 /* Map caller_expr back to an expr within the callee, or return NULL_TREE.  */
 
 tree
-call_superedge::map_expr_from_caller_to_callee (tree caller_expr) const
+callgraph_superedge::map_expr_from_caller_to_callee (tree caller_expr) const
 {
   /* Is it an argument (actual param)?  If so, convert to
      parameter (formal param).  */
@@ -858,7 +872,7 @@ call_superedge::map_expr_from_caller_to_callee (tree caller_expr) const
 /* Map callee_expr back to an expr within the caller, or return NULL_TREE.  */
 
 tree
-call_superedge::map_expr_from_callee_to_caller (tree callee_expr) const
+callgraph_superedge::map_expr_from_callee_to_caller (tree callee_expr) const
 {
   /* If it's a parameter (formal param), get the argument (actual param).  */
   if (TREE_CODE (callee_expr) == PARM_DECL)
diff --git a/gcc/analyzer/supergraph.h b/gcc/analyzer/supergraph.h
index 85b762e..f887998 100644
--- a/gcc/analyzer/supergraph.h
+++ b/gcc/analyzer/supergraph.h
@@ -26,7 +26,9 @@ along with GCC; see the file COPYING3.  If not see
 class supergraph;
 class supernode;
 class superedge;
-  class call_superedge;
+  class callgraph_superedge;
+    class call_superedge;
+    class return_superedge;
   class cfg_superedge;
     class switch_cfg_superedge;
 class dot_annotator;
@@ -106,9 +108,9 @@ public:
 	      (m_cgraph_edge_to_call_superedge).get (edge));
   }
 
-  call_superedge *get_edge_for_return (cgraph_edge *edge) const
+  return_superedge *get_edge_for_return (cgraph_edge *edge) const
   {
-    return (*const_cast <cgraph_edge_to_call_superedge_t &>
+    return (*const_cast <cgraph_edge_to_return_superedge_t &>
 	      (m_cgraph_edge_to_return_superedge).get (edge));
   }
 
@@ -131,8 +133,10 @@ private:
   supernode *add_node (function *fun, basic_block bb, gcall *returning_call,
 		       gimple_seq phi_nodes);
   superedge *add_cfg_edge (supernode *src, supernode *dest, ::edge e, int idx);
-  call_superedge *add_callgraph_edge (supernode *src, supernode *dest,
-				      enum edge_kind kind, cgraph_edge *cedge);
+  call_superedge *add_call_superedge (supernode *src, supernode *dest,
+				      cgraph_edge *cedge);
+  return_superedge *add_return_superedge (supernode *src, supernode *dest,
+					  cgraph_edge *cedge);
   superedge *add_edge (superedge *e);
 
   /* Data.  */
@@ -148,9 +152,13 @@ private:
   cgraph_edge_to_node_t m_cgraph_edge_to_caller_prev_node;
   cgraph_edge_to_node_t m_cgraph_edge_to_caller_next_node;
 
-  typedef hash_map<cgraph_edge *, call_superedge *> cgraph_edge_to_call_superedge_t;
+  typedef hash_map<cgraph_edge *, call_superedge *>
+    cgraph_edge_to_call_superedge_t;
   cgraph_edge_to_call_superedge_t m_cgraph_edge_to_call_superedge;
-  cgraph_edge_to_call_superedge_t m_cgraph_edge_to_return_superedge;
+
+  typedef hash_map<cgraph_edge *, return_superedge *>
+    cgraph_edge_to_return_superedge_t;
+  cgraph_edge_to_return_superedge_t m_cgraph_edge_to_return_superedge;
 };
 
 /* FIXME.  */
@@ -233,8 +241,12 @@ class superedge
   virtual void dump_label_to_pp (pretty_printer *pp) const = 0;
   virtual cfg_superedge *dyn_cast_cfg_superedge () { return NULL; }
   virtual const cfg_superedge *dyn_cast_cfg_superedge () const { return NULL; }
+  virtual callgraph_superedge *dyn_cast_callgraph_superedge () { return NULL; }
+  virtual const callgraph_superedge *dyn_cast_callgraph_superedge () const { return NULL; }
   virtual call_superedge *dyn_cast_call_superedge () { return NULL; }
   virtual const call_superedge *dyn_cast_call_superedge () const { return NULL; }
+  virtual return_superedge *dyn_cast_return_superedge () { return NULL; }
+  virtual const return_superedge *dyn_cast_return_superedge () const { return NULL; }
 
   ::edge get_any_cfg_edge () const;
   cgraph_edge *get_any_callgraph_edge () const;
@@ -257,21 +269,16 @@ class superedge
 
 /* A subclass of superedge corresponding to a callgraph edge.  */
 
-class call_superedge : public superedge
+class callgraph_superedge : public superedge
 {
  public:
-  call_superedge (supernode *src, supernode *dst, enum edge_kind kind,
-		  cgraph_edge *cedge)
+  callgraph_superedge (supernode *src, supernode *dst, enum edge_kind kind,
+		       cgraph_edge *cedge)
   : superedge (src, dst, kind),
     m_cedge (cedge)
   {}
 
   void dump_label_to_pp (pretty_printer *pp) const OVERRIDE;
-  call_superedge *dyn_cast_call_superedge () FINAL OVERRIDE { return this; }
-  const call_superedge *dyn_cast_call_superedge () const FINAL OVERRIDE
-  {
-    return this;
-  }
 
   tree get_callee_decl () const;
   tree get_caller_decl () const;
@@ -284,6 +291,65 @@ class call_superedge : public superedge
   cgraph_edge *m_cedge;
 };
 
+class call_superedge : public callgraph_superedge
+{
+ public:
+  call_superedge (supernode *src, supernode *dst, cgraph_edge *cedge)
+  : callgraph_superedge (src, dst, SUPEREDGE_CALL, cedge)
+  {}
+
+  callgraph_superedge *dyn_cast_callgraph_superedge () FINAL OVERRIDE
+  {
+    return this;
+  }
+  const callgraph_superedge *dyn_cast_callgraph_superedge () const
+    FINAL OVERRIDE
+  {
+    return this;
+  }
+
+  call_superedge *dyn_cast_call_superedge () FINAL OVERRIDE
+  {
+    return this;
+  }
+  const call_superedge *dyn_cast_call_superedge () const FINAL OVERRIDE
+  {
+    return this;
+  }
+
+  return_superedge *get_edge_for_return (const supergraph &sg) const
+  {
+    return sg.get_edge_for_return (m_cedge);
+  }
+};
+
+class return_superedge : public callgraph_superedge
+{
+ public:
+  return_superedge (supernode *src, supernode *dst, cgraph_edge *cedge)
+  : callgraph_superedge (src, dst, SUPEREDGE_RETURN, cedge)
+  {}
+
+  callgraph_superedge *dyn_cast_callgraph_superedge () FINAL OVERRIDE
+  {
+    return this;
+  }
+  const callgraph_superedge *dyn_cast_callgraph_superedge () const
+    FINAL OVERRIDE
+  { return this; }
+
+  return_superedge *dyn_cast_return_superedge () FINAL OVERRIDE { return this; }
+  const return_superedge *dyn_cast_return_superedge () const FINAL OVERRIDE
+  {
+    return this;
+  }
+
+  call_superedge *get_edge_for_call (const supergraph &sg) const
+  {
+    return sg.get_edge_for_call (m_cedge);
+  }
+};
+
 /* A subclass of superedge that corresponds to a CFG edge.  */
 
 class cfg_superedge : public superedge
diff --git a/gcc/testsuite/gcc.dg/analyzer/factorial.c b/gcc/testsuite/gcc.dg/analyzer/factorial.c
new file mode 100644
index 0000000..384713a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/factorial.c
@@ -0,0 +1,7 @@
+int factorial (int n)
+{
+  if (n > 1)
+    return n * factorial (n - 1);
+  else
+    return 1;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/fibonacci.c b/gcc/testsuite/gcc.dg/analyzer/fibonacci.c
new file mode 100644
index 0000000..1ca3726
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/fibonacci.c
@@ -0,0 +1,7 @@
+int fib (int n)
+{
+  if (n > 1)
+    return fib (n - 1) + fib (n - 2);
+  else
+    return n;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/infinite-recursion.c b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion.c
index 5b79fec..b770e12 100644
--- a/gcc/testsuite/gcc.dg/analyzer/infinite-recursion.c
+++ b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion.c
@@ -18,3 +18,38 @@ void test(int flag)
 
   marker_D();
 }
+
+/* A cycle of 4 mutually-recursive functions (but only for certain inputs).  */
+
+extern void mutual_test_1 (int flag);
+extern void mutual_test_2 (int flag);
+extern void mutual_test_3 (int flag);
+extern void mutual_test_4 (int flag);
+
+void mutual_test_1 (int flag)
+{
+  marker_A ();
+  if (flag)
+    mutual_test_2 (flag);
+}
+
+void mutual_test_2 (int flag)
+{
+  marker_B ();
+  if (flag)
+    mutual_test_3 (flag);
+}
+
+void mutual_test_3 (int flag)
+{
+  marker_C ();
+  if (flag)
+    mutual_test_4 (flag);
+}
+
+void mutual_test_4 (int flag)
+{
+  marker_D ();
+  if (flag)
+    mutual_test_1 (flag);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/loop.c b/gcc/testsuite/gcc.dg/analyzer/loop.c
index 7d1d95d..23294f2 100644
--- a/gcc/testsuite/gcc.dg/analyzer/loop.c
+++ b/gcc/testsuite/gcc.dg/analyzer/loop.c
@@ -13,7 +13,8 @@ void test(void)
       __analyzer_eval (i >= 0); /* { dg-warning "TRUE" } */
   }
 
-  __analyzer_eval (i >= 256); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  __analyzer_eval (i >= 256); /* { dg-warning "TRUE" } */
   __analyzer_eval (i == 256); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
-  /* TODO(xfail): we're not reaching the end of the loop.  */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  /* TODO(xfail^^^): it only figures out i >= 256, rather than i == 256.  */
 }
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-1.c b/gcc/testsuite/gcc.dg/analyzer/malloc-1.c
index 128dfe9..e30ff3c 100644
--- a/gcc/testsuite/gcc.dg/analyzer/malloc-1.c
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-1.c
@@ -88,9 +88,7 @@ void test_10 (void)
     foo ();
 
   free (ptr);
-  free (ptr); /* { dg-warning "double-free of 'ptr'" "" { xfail *-*-* } } */ 
-  /* TODO(xfail): it's stopping after the 1st time through the loop,
-     with a cache hit, and thus not seeing the end of the function.  */
+  free (ptr); /* { dg-warning "double-free of 'ptr'" } */ 
 }
 
 void test_11 (void)
@@ -239,9 +237,7 @@ void test_22 (void)
     foo ();
 
   free (ptr);
-  free (ptr); /* { dg-warning "double-free of 'ptr'" "" { xfail *-*-* } } */ 
-  /* TODO(xfail): it's stopping after the 1st time through the loop,
-     with a cache hit, and thus not seeing the end of the function.  */
+  free (ptr); /* { dg-warning "double-free of 'ptr'" } */ 
 }
 
 int *test_23 (int n)
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-12.c b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-12.c
index 3a2e2bb..c8361d5 100644
--- a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-12.c
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-12.c
@@ -2,7 +2,7 @@
 
 void recursive_free (void *ptr)
 {
-  free (ptr);
+  free (ptr); /* { dg-warning "double-free of 'ptr'" } */
   recursive_free (ptr);
   /* TODO: should we warn for this?  */
 }
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-double-free.c b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-double-free.c
index 62e7302..7c27998 100644
--- a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-double-free.c
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-double-free.c
@@ -1,21 +1,17 @@
 /* Example of a multilevel wrapper around malloc/free, with a double-free.  */
 
-/* { dg-options "--analyzer -fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fdump-analyzer-summaries -fanalyzer-checker=malloc -fanalyzer-verbose-state-changes" } */
+/* { dg-options "--analyzer -fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fanalyzer-checker=malloc -fanalyzer-verbose-state-changes" } */
 
 #include <stdlib.h>
 
-void *wrapped_malloc (size_t size) /* { dg-warning "summary: add: '<return-value>': 'start' -> 'unchecked'" } */
+void *wrapped_malloc (size_t size)
 {
   return malloc (size);
 }
 
-void wrapped_free (void *ptr) /* { dg-warning "summary: add: 'ptr': 'start' -> 'free'" } */
-/* { dg-warning "summary: transition: 'ptr': 'nonnull' -> 'free'" "" { target *-*-* } .-1 } */
-/* { dg-warning "summary: transition: 'ptr': 'free' -> 'stop'" "" { target *-*-* } .-2 } */
+void wrapped_free (void *ptr)
 {
   free (ptr); /* { dg-warning "double-free of 'ptr' \\\[CWE-415\\\]" } */
-  /* FIXME: unchecked -> free is correct, but start -> free should be pruned
-     from the path.  */
 }
 
 typedef struct boxed_int
@@ -24,7 +20,7 @@ typedef struct boxed_int
 } boxed_int;
 
 boxed_int *
-make_boxed_int (int i) /* { dg-warning "summary: add: '<return-value>': 'start' -> 'nonnull'" } */
+make_boxed_int (int i)
 {
   boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
   if (!result)
@@ -34,13 +30,9 @@ make_boxed_int (int i) /* { dg-warning "summary: add: '<return-value>': 'start'
 }
 
 void
-free_boxed_int (boxed_int *bi) /* { dg-warning "summary: add: 'bi': 'start' -> 'free'" } */
-/* { dg-warning "summary: add: 'bi': 'start' -> 'stop'" "" { target *-*-* } .-1 } */
+free_boxed_int (boxed_int *bi)
 {
   wrapped_free (bi);
-  /* FIXME: events here are wrong; they refer to ptr, not bi.
-     (was "state of 'ptr': 'start' -> 'free'")
-     Also, they should be just start->free, or be skipped.  */
 }
 
 void test (int i)
@@ -52,8 +44,6 @@ void test (int i)
   free_boxed_int (obj);
 }
 
-/* TODO: various strange entries in the path.  */
-
 /* Double-free.  */
 /* { dg-begin-multiline-output "" }
   'test': events 1-2
@@ -62,102 +52,81 @@ void test (int i)
      |      |      ^~~~
      |      |      |
      |      |      (1) entry to 'test'
-     |   NN | {
-     |   NN |   boxed_int *obj = make_boxed_int (i);
-     |      |                    ~~~~~~~~~~~~~~~~~~
-     |      |                    |
-     |      |                    (2) calling 'make_boxed_int' from 'test'
-     |
-     +--> 'make_boxed_int': event 3
-                  |
-                  |   NN | make_boxed_int (int i)
-                  |      | ^~~~~~~~~~~~~~
-                  |      | |
-                  |      | (3) entry to 'make_boxed_int'
-                  |
-          'wrapped_malloc': event 4
-                  |
-                  |   NN | void *wrapped_malloc (size_t size)
-                  |      |       ^~~~~~~~~~~~~~
-                  |      |       |
-                  |      |       (4) entry to 'wrapped_malloc'
-                  |
-     <------------+
-     |
-  'test': events 5-6
-     |
-     |   NN |   boxed_int *obj = make_boxed_int (i);
-     |      |                    ^~~~~~~~~~~~~~~~~~
-     |      |                    |
-     |      |                    (5) returning to 'test' from 'make_boxed_int'
-     |   NN | 
+     |......
      |   NN |   free_boxed_int (obj);
      |      |   ~~~~~~~~~~~~~~~~~~~~
      |      |   |
-     |      |   (6) calling 'free_boxed_int' from 'test'
+     |      |   (2) calling 'free_boxed_int' from 'test'
      |
-     +--> 'free_boxed_int': events 7-9
+     +--> 'free_boxed_int': events 3-4
                   |
                   |   NN | free_boxed_int (boxed_int *bi)
                   |      | ^~~~~~~~~~~~~~
                   |      | |
-                  |      | (7) entry to 'free_boxed_int'
-                  |......
+                  |      | (3) entry to 'free_boxed_int'
+                  |   NN | {
                   |   NN |   wrapped_free (bi);
                   |      |   ~~~~~~~~~~~~~~~~~
                   |      |   |
-                  |      |   (8) state of 'ptr': 'start' -> 'nonnull' (origin: NULL)
-                  |      |   (9) first 'free' here (state of 'ptr': 'start' -> 'free', origin: NULL)
+                  |      |   (4) calling 'wrapped_free' from 'free_boxed_int'
+                  |
+                  +--> 'wrapped_free': events 5-6
+                              |
+                              |   NN | void wrapped_free (void *ptr)
+                              |      |      ^~~~~~~~~~~~
+                              |      |      |
+                              |      |      (5) entry to 'wrapped_free'
+                              |   NN | {
+                              |   NN |   free (ptr);
+                              |      |   ~~~~~~~~~~
+                              |      |   |
+                              |      |   (6) first 'free' here (state of 'ptr': 'nonnull' -> 'free', origin: NULL)
+                              |
+                  <-----------+
+                  |
+          'free_boxed_int': event 7
+                  |
+                  |   NN |   wrapped_free (bi);
+                  |      |   ^~~~~~~~~~~~~~~~~
+                  |      |   |
+                  |      |   (7) returning to 'free_boxed_int' from 'wrapped_free'
                   |
-          'wrapped_free': events 10-12
-                 |
-                 |   NN | void wrapped_free (void *ptr)
-                 |      |      ^~~~~~~~~~~~
-                 |      |      |
-                 |      |      (10) entry to 'wrapped_free'
-                 |......
-                 |   NN |   free (ptr);
-                 |      |   ~~~~~~~~~~
-                 |      |   |
-                 |      |   (11) first 'free' here (state of 'ptr': 'start' -> 'free', origin: NULL)
-                 |      |   (12) first 'free' here (state of 'ptr': 'nonnull' -> 'free', origin: NULL)
-                 |
-     <-----------+
+     <------------+
      |
-  'test': events 13-14
+  'test': events 8-9
      |
      |   NN |   free_boxed_int (obj);
      |      |   ^~~~~~~~~~~~~~~~~~~~
      |      |   |
-     |      |   (13) returning to 'test' from 'free_boxed_int'
+     |      |   (8) returning to 'test' from 'free_boxed_int'
      |   NN | 
      |   NN |   free_boxed_int (obj);
      |      |   ~~~~~~~~~~~~~~~~~~~~
      |      |   |
-     |      |   (14) calling 'free_boxed_int' from 'test'
+     |      |   (9) calling 'free_boxed_int' from 'test'
      |
-     +--> 'free_boxed_int': events 15-16
+     +--> 'free_boxed_int': events 10-11
                   |
                   |   NN | free_boxed_int (boxed_int *bi)
                   |      | ^~~~~~~~~~~~~~
                   |      | |
-                  |      | (15) entry to 'free_boxed_int'
-                  |......
+                  |      | (10) entry to 'free_boxed_int'
+                  |   NN | {
                   |   NN |   wrapped_free (bi);
                   |      |   ~~~~~~~~~~~~~~~~~
                   |      |   |
-                  |      |   (16) calling 'wrapped_free' from 'free_boxed_int'
+                  |      |   (11) calling 'wrapped_free' from 'free_boxed_int'
                   |
-                  +--> 'wrapped_free': events 17-18
+                  +--> 'wrapped_free': events 12-13
                               |
                               |   NN | void wrapped_free (void *ptr)
                               |      |      ^~~~~~~~~~~~
                               |      |      |
-                              |      |      (17) entry to 'wrapped_free'
-                              |......
+                              |      |      (12) entry to 'wrapped_free'
+                              |   NN | {
                               |   NN |   free (ptr);
                               |      |   ~~~~~~~~~~
                               |      |   |
-                              |      |   (18) second 'free' here ('ptr' is in state 'free')
+                              |      |   (13) second 'free' here ('ptr' is in state 'free')
                               |
    { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-unchecked.c b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-unchecked.c
index 48680e0..f19370c 100644
--- a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-unchecked.c
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-unchecked.c
@@ -1,10 +1,10 @@
-/* Example of an  multilevel wrapper around malloc, with an unchecked write.  */
+/* Example of a multilevel wrapper around malloc, with an unchecked write.  */
 
-/* { dg-options "--analyzer -fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fdump-analyzer-summaries -fanalyzer-checker=malloc -fanalyzer-verbose-state-changes" } */
+/* { dg-options "--analyzer -fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fanalyzer-checker=malloc -fanalyzer-verbose-state-changes" } */
 
 #include <stdlib.h>
 
-void *wrapped_malloc (size_t size) /* { dg-warning "summary: add: '<return-value>': 'start' -> 'unchecked'" } */
+void *wrapped_malloc (size_t size)
 {
   return malloc (size); /* { dg-message "this call could return NULL" } */
 }
@@ -15,7 +15,7 @@ typedef struct boxed_int
 } boxed_int;
 
 boxed_int *
-make_boxed_int (int i) /* { dg-warning "summary: add: '<return-value>': 'start' -> 'nonnull'" } */
+make_boxed_int (int i)
 {
   boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
   result->i = i; /* { dg-warning "use of possibly-NULL 'result'" } */
@@ -24,6 +24,8 @@ make_boxed_int (int i) /* { dg-warning "summary: add: '<return-value>': 'start'
 }
 
 /* "use of possibly-NULL 'result' [CWE-690]".  */
+/* TODO: the state of '<return-value>': 'start' -> 'unchecked' message
+   is somewhat redundant.  */
 /* { dg-begin-multiline-output "" }
   'make_boxed_int': events 1-2
           |
@@ -37,7 +39,7 @@ make_boxed_int (int i) /* { dg-warning "summary: add: '<return-value>': 'start'
           |      |                                    |
           |      |                                    (2) calling 'wrapped_malloc' from 'make_boxed_int'
           |
-          +--> 'wrapped_malloc': events 3-4
+          +--> 'wrapped_malloc': events 3-5
                        |
                        |   NN | void *wrapped_malloc (size_t size)
                        |      |       ^~~~~~~~~~~~~~
@@ -47,19 +49,20 @@ make_boxed_int (int i) /* { dg-warning "summary: add: '<return-value>': 'start'
                        |   NN |   return malloc (size);
                        |      |          ~~~~~~~~~~~~~
                        |      |          |
-                       |      |          (4) this call could return NULL (state of '<return-value>': 'start' -> 'unchecked', origin: NULL)
+                       |      |          (4) this call could return NULL (state of '<unknown>': 'start' -> 'unchecked', origin: NULL)
+                       |      |          (5) state of '<return-value>': 'start' -> 'unchecked' (origin: '<unknown>')
                        |
           <------------+
           |
-  'make_boxed_int': events 5-6
+  'make_boxed_int': events 6-7
           |
           |   NN |   boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
           |      |                                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
           |      |                                    |
-          |      |                                    (5) returning to 'make_boxed_int' from 'wrapped_malloc'
+          |      |                                    (6) returning to 'make_boxed_int' from 'wrapped_malloc'
           |   NN |   result->i = i;
           |      |   ~~~~~~~~~~~~~                     
           |      |             |
-          |      |             (6) 'result' could be NULL ('result' is in state 'unchecked')
+          |      |             (7) 'result' could be NULL ('result' is in state 'unchecked')
           |
   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-9.c b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-9.c
index e998e22..325fa44 100644
--- a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-9.c
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-9.c
@@ -6,7 +6,8 @@ void
 two_frees (void *p, void *q)
 {
   free (p);
-  free (q);
+  free (q); /* { dg-warning "double-free of 'q'" } */
+  /* TODO: could be useful to identify that p == q when called from 'test'.  */
 }
 
 extern void do_stuff (void);
@@ -15,5 +16,3 @@ void test (void *ptr)
 {
   two_frees (ptr, ptr);
 }
-
-/* FIXME: shouldn't we warn for this?  */
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-paths-5.c b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-5.c
index 081c83c..7fdc8a1 100644
--- a/gcc/testsuite/gcc.dg/analyzer/malloc-paths-5.c
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-5.c
@@ -35,6 +35,6 @@ int test (const char *filename, int flag)
   /* { dg-message "\\(3\\) first 'free' here" "" { target *-*-* } first_free_of_p } */
   /* { dg-message "\\(4\\) second 'free' here" "" { target *-*-* } second_free_of_p } */
 
-  /* We dont' care about the state changes to q.  */
+  /* We don't care about the state changes to q.  */
   /* TODO: we do care about the test of q.  */
 }
diff --git a/gcc/testsuite/gcc.dg/analyzer/params-2.c b/gcc/testsuite/gcc.dg/analyzer/params-2.c
index a670283..1584ebd 100644
--- a/gcc/testsuite/gcc.dg/analyzer/params-2.c
+++ b/gcc/testsuite/gcc.dg/analyzer/params-2.c
@@ -12,9 +12,5 @@ void test(int i, int j)
 
   ensure_equal (i, j);
 
-  __analyzer_eval (i == j); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
-  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
-  /* TODO(xfail): follow_call doesn't capture state changes within the
-     function into the resulting path_state, and so we don't retain the
-     "a@1 == i@0" etc info.  */
+  __analyzer_eval (i == j); /* { dg-warning "TRUE" } */
 }
diff --git a/gcc/testsuite/gcc.dg/analyzer/params.c b/gcc/testsuite/gcc.dg/analyzer/params.c
index 2ef0bac..ff405f2 100644
--- a/gcc/testsuite/gcc.dg/analyzer/params.c
+++ b/gcc/testsuite/gcc.dg/analyzer/params.c
@@ -2,9 +2,7 @@ static int called_function(int j)
 {
   int k;
 
-  __analyzer_eval (j > 4); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
-  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
-  /* TODO(xfail): we're not passing on the knowledge from the callsite in test.  */
+  __analyzer_eval (j > 4); /* { dg-warning "TRUE" } */
 
   k = j - 1;
 
-- 
1.8.5.3

