From 7a1ce012ea9db0e9615a82cb3bd1068c27ecc03e Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Thu, 28 Feb 2019 14:22:48 -0500
Subject: [PATCH 14/38] FIXME: add gcc/analyzer/dead-code

---
 gcc/analyzer/dead-code/analyzer-config.cc     |   54 ++
 gcc/analyzer/dead-code/constraints.cc         |  780 ++++++++++++++++++
 gcc/analyzer/dead-code/core-checker.cc        |  904 +++++++++++++++++++++
 gcc/analyzer/dead-code/libc-file-checker.cc   |  226 ++++++
 gcc/analyzer/dead-code/libc-malloc-checker.cc |  220 ++++++
 gcc/analyzer/dead-code/toy-sm-impl-1.cc       | 1052 +++++++++++++++++++++++++
 gcc/analyzer/dead-code/toy-sm-impl-1.h        |    1 +
 7 files changed, 3237 insertions(+)
 create mode 100644 gcc/analyzer/dead-code/analyzer-config.cc
 create mode 100644 gcc/analyzer/dead-code/constraints.cc
 create mode 100644 gcc/analyzer/dead-code/core-checker.cc
 create mode 100644 gcc/analyzer/dead-code/libc-file-checker.cc
 create mode 100644 gcc/analyzer/dead-code/libc-malloc-checker.cc
 create mode 100644 gcc/analyzer/dead-code/toy-sm-impl-1.cc
 create mode 100644 gcc/analyzer/dead-code/toy-sm-impl-1.h

diff --git a/gcc/analyzer/dead-code/analyzer-config.cc b/gcc/analyzer/dead-code/analyzer-config.cc
new file mode 100644
index 0000000..2a23dd1
--- /dev/null
+++ b/gcc/analyzer/dead-code/analyzer-config.cc
@@ -0,0 +1,54 @@
+/* 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 "analyzer.h"
+
+namespace ana
+{
+
+/* Build an analyzer instance.  */
+
+analyzer *
+make_analyzer (void)
+{
+  analyzer *a = new ana::analyzer ();
+  a->add_checker (make_core_checker (a));
+  a->add_checker (make_constraints_checker (a));
+  a->add_checker (make_core_divide_by_zero_checker ());
+  a->add_checker (make_core_function_call_checker ());
+  a->add_checker (make_libc_file_checker ());
+  a->add_checker (make_libc_malloc_checker ());
+  /* etc */
+
+  /* Ultimately this should read in configuration
+     and act accordingly.  */
+
+  return a;
+}
+
+} // namespace ana
diff --git a/gcc/analyzer/dead-code/constraints.cc b/gcc/analyzer/dead-code/constraints.cc
new file mode 100644
index 0000000..93407c2
--- /dev/null
+++ b/gcc/analyzer/dead-code/constraints.cc
@@ -0,0 +1,780 @@
+/* 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 "print-tree.h"
+#include "graphviz.h"
+
+namespace ana
+{
+
+class constraints_checker;
+
+enum constraint_op
+{
+  CONSTRAINT_EQ,
+  CONSTRAINT_NE,
+  CONSTRAINT_LT,
+  CONSTRAINT_LE
+};
+
+static const char *
+constraint_op_code (enum constraint_op c_op)
+{
+  switch (c_op)
+    {
+    default:
+      gcc_unreachable ();
+    case CONSTRAINT_EQ: return "==";
+    case CONSTRAINT_NE: return "!=";
+    case CONSTRAINT_LT: return "<";
+    case CONSTRAINT_LE: return "<=";
+    }
+}
+
+class constraint {
+ public:
+  constraint (absvalue *lhs, enum constraint_op c_op, absvalue *rhs)
+    : m_lhs (lhs), m_op (c_op), m_rhs (rhs) {}
+
+  absvalue *m_lhs;
+  enum constraint_op m_op;
+  absvalue *m_rhs;
+};
+
+class known_constraints : public custom_state {
+ public:
+  custom_state *clone () const
+  {
+    return new known_constraints (*this);
+  }
+
+  void dump_to_dot_record (pretty_printer *pp) const;
+  void add_extra_nodes_and_edges (const node *n,
+				  graphviz_out *gv) const;
+
+  known_constraints () : m_constraints () {}
+  known_constraints (const known_constraints &other);
+
+  bool add_constraint (absvalue *lhs, enum tree_code op, absvalue *rhs);
+  bool add_constraint (context *ctxt, node *n, absvalue *lhs, bool value);
+  bool add_constraint (context *ctxt, node *n, tree lhs, bool value);
+
+  tristate
+  eval_condition (absvalue *lhs, enum tree_code op, absvalue *rhs);
+
+  tristate
+  eval_condition (context *ctxt, node *n,
+		  tree lhs, enum tree_code op, tree rhs);
+
+ private:
+  tristate impl_eq (absvalue *lhs, absvalue *rhs);
+  tristate impl_lt (absvalue *lhs, absvalue *rhs);
+  tristate impl_le (absvalue *lhs, absvalue *rhs);
+  tristate impl_ne (absvalue *lhs, absvalue *rhs) { return not impl_eq (lhs, rhs); }
+
+ private:
+  /* TODO: optimize */
+  vec<constraint *> m_constraints;
+  friend class constraints_checker;
+
+};
+
+/* The checker itself.  */
+
+class constraints_checker : public stateful_checker <known_constraints>, public constraints
+{
+ public:
+  void on_node (node *n, context *ctxt);
+  void before_call (node *n, gcall *call, context *ctxt);
+  void after_call (node *n, gcall *call, context *ctxt);
+  void dead_value (node *n, context *ctxt);
+
+  /* Implementation of constraints.  */
+  tristate
+  eval_condition (node *n, absvalue *lhs, enum tree_code op, absvalue *rhs);
+
+  bool
+  add_constraint (state *s, absvalue *lhs, enum tree_code op, absvalue *rhs);
+
+ private:
+};
+
+} // namespace ana
+
+ana::checker *ana::make_constraints_checker (analyzer *a)
+{
+  constraints_checker *ch = new constraints_checker ();
+  a->set_constraints (ch);
+  return ch;
+}
+
+/* class known_constraints : public per_checker_state */
+
+ana::known_constraints::known_constraints (const known_constraints &other)
+: m_constraints ()
+{
+  /* FIXME: do we really need to copy explicitly?  */
+  int i;
+  constraint *c;
+  FOR_EACH_VEC_ELT (other.m_constraints, i, c)
+    m_constraints.safe_push (c);
+}
+
+void
+ana::known_constraints::dump_to_dot_record (pretty_printer *pp) const
+{
+  int i;
+  constraint *c;
+  FOR_EACH_VEC_ELT (m_constraints, i, c)
+    {
+      pp_printf (pp, "constraint %i: ", i);
+      c->m_lhs->dump_to_dot_record (pp);
+      pp_printf (pp, "  %s ", constraint_op_code (c->m_op));
+      c->m_rhs->dump_to_dot_record (pp);
+      pp_newline (pp);
+      pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
+    }
+}
+
+void
+ana::known_constraints::add_extra_nodes_and_edges (const node *n, graphviz_out *gv) const
+{
+  /* Begin a subgraph.  */
+  gv->println ("subgraph \"cluster_known_constraints_%p\" {",
+	       (void *)this);
+  gv->indent ();
+  gv->println ("style=\"dashed\";");
+  gv->println ("color=\"blue\";");
+  gv->println ("clusterrank=\"local\";");
+  gv->println ("label=\"known_constraints\";");
+
+  /* Add nodes and edges visualizing the known constraints.  */
+  pretty_printer *pp = gv->m_pp;
+  int i;
+  constraint *c;
+  FOR_EACH_VEC_ELT (m_constraints, i, c)
+    {
+      gv->write_indent ();
+      pp_printf (pp,
+		 "known_constraints_%p_node_%p [label=\"",
+		 (void *)this,
+		 c->m_lhs);
+      c->m_lhs->dump_to_dot_record (pp);
+      pp_string (pp, "\"];\n");
+
+      gv->write_indent ();
+      pp_printf (pp,
+		 "known_constraints_%p_node_%p [label=\"",
+		 (void *)this,
+		 c->m_rhs);
+      c->m_rhs->dump_to_dot_record (pp);
+      pp_string (pp, "\"];\n");
+
+      const char *style = "\"solid\"";
+      const char *color = "black";
+      int weight = 0;
+      /* FIXME: for some reason, setting constraint = "true" plays havoc
+	 with the layout, despite having clusterrank=local.
+	 Keep it false for now.  */
+      const char *constraint = "false";
+      const char *label = constraint_op_code (c->m_op);
+      gv->write_indent ();
+
+      /* Adding edge labels here leads to failures from graphviz of this form:
+	 libpath/shortest.c:324: triangulation failed
+	 libpath/shortest.c:192: source point not in any triangle
+	 Error: in routesplines, Pshortestpath failed
+	 which appears to be:
+	 http://www.graphviz.org/mantisbt/view.php?id=2382
+	 However, it appears to work if we set taillabel instead.  */
+      pp_printf (pp,
+		 "known_constraints_%p_node_%p -> known_constraints_%p_node_%p "
+		 "[style=%s,color=%s,weight=%d,constraint=%s, len=0.1, taillabel=\"%s\"];\n",
+		 (void *)this, (void *)c->m_lhs,
+		 (void *)this, (void *)c->m_rhs,
+		 style, color, weight,
+		 constraint, label);
+    }
+
+  /* Terminate "subgraph" */
+  gv->outdent ();
+  gv->println ("}");
+}
+
+/* Attempt to add the constraint, returning true if the result is
+   satisfiable, false if it is unsatisfiable.  */
+
+bool
+ana::known_constraints::add_constraint (absvalue *lhs, enum tree_code op, absvalue *rhs)
+{
+  gcc_assert (lhs);
+  gcc_assert (rhs);
+
+#if 0
+  fprintf (stdout, "add_constraint: ");
+
+  pretty_printer pp;
+  pp.buffer->stream = stdout;
+  dump_to_dot_record (&pp);
+  pp_flush (&pp);
+
+  debug (lhs);
+  fprintf (stdout, " %s ", op_symbol_code (op));
+  debug (rhs);
+  fprintf (stdout, "\n");
+#endif
+
+  tristate t = eval_condition (lhs, op, rhs);
+
+  /* Discard constraints that are already known.  */
+  if (t.is_true ())
+    return true;
+
+  /* Reject unsatisfiable constraints.  */
+  if (t.is_false ())
+    return false;
+
+  /* For now, simply accumulate constraints, without attempting any further
+     optimization.  */
+  constraint *c = NULL;
+  switch (op)
+    {
+    case EQ_EXPR: c = new constraint (lhs, CONSTRAINT_EQ, rhs); break;
+    case GE_EXPR: c = new constraint (rhs, CONSTRAINT_LT, lhs); break;
+    case LE_EXPR: c = new constraint (lhs, CONSTRAINT_LE, rhs); break;
+    case NE_EXPR: c = new constraint (lhs, CONSTRAINT_NE, rhs); break;
+    case GT_EXPR: c = new constraint (rhs, CONSTRAINT_LE, lhs); break;
+    case LT_EXPR: c = new constraint (lhs, CONSTRAINT_LT, rhs); break;
+    default:
+      /* do nothing.  */
+      break;
+    }
+  if (c)
+    m_constraints.safe_push (c);
+  return true;
+}
+
+bool
+ana::known_constraints::add_constraint (context *ctxt, node *n, absvalue *v_lhs, bool value)
+{
+  gcc_assert (v_lhs);
+
+  /* Make "true" and "false" as !=/== zero */
+  enum tree_code op = value ? NE_EXPR : EQ_EXPR;
+
+  absvalue *v_rhs = ctxt->eval_rvalue (n, integer_zero_node);
+
+  return add_constraint (v_lhs, op, v_rhs);
+}
+
+bool
+ana::known_constraints::add_constraint (context *ctxt, node *n, tree t_lhs, bool value)
+{
+  absvalue *v_lhs = ctxt->eval_rvalue (n, t_lhs);  
+  gcc_assert (v_lhs);
+  return add_constraint (ctxt, n, v_lhs, value);
+}
+
+ana::tristate
+ana::known_constraints::eval_condition (absvalue *lhs, enum tree_code op, absvalue *rhs)
+{
+#if 0
+  fprintf (stderr, "eval_condition: ");
+
+  pretty_printer pp;
+  pp.buffer->stream = stderr;
+  dump_to_dot_record (&pp);
+  pp_flush (&pp);
+
+  debug (lhs);
+  fprintf (stderr, " %s ", op_symbol_code (op));
+  debug (rhs);
+  fprintf (stderr, "\n");
+#endif
+
+  tristate t = tristate (tristate::TS_UNKNOWN);
+
+  switch (op)
+    {
+    case EQ_EXPR: t = impl_eq (lhs, rhs); break;
+    case GE_EXPR: t = impl_lt (rhs, lhs); break;
+    case LE_EXPR: t = impl_le (lhs, rhs); break;
+    case NE_EXPR: t = impl_ne (lhs, rhs); break;
+    case GT_EXPR: t = impl_le (rhs, lhs); break;
+    case LT_EXPR: t = impl_lt (lhs, rhs); break;
+    default:
+      break;
+    }
+
+#if 0
+  fprintf (stderr, "result: %s\n", t.as_string ());
+#endif
+  return t;
+}
+
+ana::tristate
+ana::known_constraints::eval_condition (context *ctxt,
+					node *n,
+					tree t_lhs,
+					enum tree_code op,
+					tree t_rhs)
+{
+  absvalue *v_lhs = ctxt->eval_rvalue (n, t_lhs);
+  absvalue *v_rhs = ctxt->eval_rvalue (n, t_rhs);
+  return eval_condition (v_lhs, op, v_rhs);
+}
+
+
+/* TODO: implement some logic for avoid infinite recursion
+   e.g. a bitset of edges that have been followed
+   (i.e. constraint indices).  */
+
+/* Does A == B ? */
+ana::tristate
+ana::known_constraints::impl_eq (absvalue *a, absvalue *b)
+{
+  if (a == b)
+    return tristate (tristate::TS_TRUE);
+
+  if (a && b)
+    {
+      tristate t = a->eq (b);
+      if (t.is_known ())
+	return t;
+    }
+
+#if 0
+  if (TREE_CODE (a) == INTEGER_CST
+      && TREE_CODE (b) == INTEGER_CST)
+    {
+      return tristate (tree_int_cst_compare (a, b) == 0);
+    }
+
+  int i;
+  constraint *c;
+  FOR_EACH_VEC_ELT (m_constraints, i, c)
+    {
+      if (a == c->m_lhs)
+	{
+	  switch (c->m_op)
+	    {
+	    default:
+	      gcc_unreachable ();
+
+	    case CONSTRAINT_EQ:
+	      /* We have a == R;  a == b iff b == R; recurse.  */
+	      {
+		tristate t = impl_eq (b, c->m_rhs);
+		if (t.is_known ())
+		  return t;
+	      }
+	      break;
+
+	    case CONSTRAINT_NE:
+	      /* We have a != R;  hence b == R would imply !(a == b).  Recurse. */
+	      {
+		tristate t = impl_eq (b, c->m_rhs);
+		if (t.is_true ())
+		  return tristate (tristate::TS_FALSE);
+	      }
+	      break;
+
+	    case CONSTRAINT_LT:
+	      /* We have (a < R);  hence R <= b would imply !(a == b).  Recurse. */
+	      {
+		tristate t = impl_le (c->m_rhs, b);
+		if (t.is_true ())
+		  return tristate (tristate::TS_FALSE);
+	      }
+	      break;
+
+	    case CONSTRAINT_LE:
+	      /* We have (a <= R);  hence R < b would imply !(a == b).  Recurse. */
+	      {
+		tristate t = impl_lt (c->m_rhs, b);
+		if (t.is_true ())
+		  return tristate (tristate::TS_FALSE);
+	      }
+	      break;
+	    }
+	}
+
+      if (b == c->m_lhs)
+	{
+	  switch (c->m_op)
+	    {
+	    default:
+	      gcc_unreachable ();
+
+	    case CONSTRAINT_EQ:
+	      /* We have b == R;  a == b iff a == R; recurse.  */
+	      {
+		tristate t = impl_eq (a, c->m_rhs);
+		if (t.is_known ())
+		  return t;
+	      }
+	      break;
+
+	    case CONSTRAINT_NE:
+	      /* We have b != R;  hence a == R would imply !(a == b).  Recurse. */
+	      {
+		tristate t = impl_eq (a, c->m_rhs);
+		if (t.is_true ())
+		  return tristate (tristate::TS_FALSE);
+	      }
+	      break;
+
+	    case CONSTRAINT_LT:
+	      /* We have (b < R);  hence R <= a would imply !(a == b).  Recurse. */
+	      {
+		tristate t = impl_le (c->m_rhs, a);
+		if (t.is_true ())
+		  return tristate (tristate::TS_FALSE);
+	      }
+	      break;
+
+	    case CONSTRAINT_LE:
+	      /* We have (b <= R);  hence R < a would imply !(a == b).  Recurse. */
+	      {
+		tristate t = impl_lt (c->m_rhs, a);
+		if (t.is_true ())
+		  return tristate (tristate::TS_FALSE);
+	      }
+	      break;
+	    }
+	}
+
+      /* TODO: need to do rhs also.  */
+    }
+#endif
+
+  return tristate (tristate::TS_UNKNOWN);
+}
+
+/* Is A < B ? */
+ana::tristate
+ana::known_constraints::impl_lt (absvalue *a, absvalue *b)
+{
+  if (a == b)
+    return tristate (tristate::TS_FALSE);
+
+#if 0
+  if (TREE_CODE (a) == INTEGER_CST
+      && TREE_CODE (b) == INTEGER_CST)
+    {
+      return tristate (tree_int_cst_lt (a, b));
+    }
+#endif
+
+  int i;
+  constraint *c;
+  FOR_EACH_VEC_ELT (m_constraints, i, c)
+    {
+      if (a == c->m_lhs)
+	{
+	  switch (c->m_op)
+	    {
+	    default:
+	      gcc_unreachable ();
+
+	    case CONSTRAINT_EQ:
+	      /* We have (a == R);  a < b iff R < b; recurse.  */
+	      {
+		tristate t = impl_lt (c->m_rhs, b);
+		if (t.is_known ())
+		  return t;
+	      }
+	      break;
+
+	    case CONSTRAINT_NE:
+	      /* We have (a != R). */
+	      break;
+
+	    case CONSTRAINT_LT:
+	      /* We have (a < R); (R <= b) implies (a < b).  */
+	      {
+		tristate t = impl_le (c->m_rhs, b);
+		if (t.is_true ())
+		  return t;
+	      }
+	      break;
+
+	    case CONSTRAINT_LE:
+	      /* We have (a <= R); (R < b) implies (a < b).  */
+	      {
+		tristate t = impl_lt (c->m_rhs, b);
+		if (t.is_true ())
+		  return t;
+	      }
+	      break;
+	    }
+	}
+
+      if (b == c->m_lhs)
+	{
+	  switch (c->m_op)
+	    {
+	    default:
+	      gcc_unreachable ();
+
+	    case CONSTRAINT_EQ:
+	      /* We have (b == R);  a < b iff a < R; recurse.  */
+	      {
+		tristate t = impl_lt (a, c->m_rhs);
+		if (t.is_known ())
+		  return t;
+	      }
+	      break;
+
+	    case CONSTRAINT_NE:
+	      /* We have (b != R). */
+	      break;
+
+	    case CONSTRAINT_LT:
+	      /* We have (b < R); (R < a) implies !(a < b).  */
+	      {
+		tristate t = impl_lt (c->m_rhs, a);
+		if (t.is_true ())
+		  return tristate (tristate::TS_FALSE);
+	      }
+	      break;
+
+	    case CONSTRAINT_LE:
+	      /* We have (b <= R); (R <= a) implies !(a < b).  */
+	      {
+		tristate t = impl_le (c->m_rhs, a);
+		if (t.is_true ())
+		  return tristate (tristate::TS_FALSE);
+	      }
+	      break;
+	    }
+	}
+
+      /* TODO: need to do rhs also.  */
+    }
+
+  return tristate (tristate::TS_UNKNOWN);
+}
+
+/* Is A <= B ? */
+ana::tristate
+ana::known_constraints::impl_le (absvalue *a, absvalue *b)
+{
+  if (a == b)
+    return tristate (tristate::TS_TRUE);
+
+#if 0
+  if (TREE_CODE (a) == INTEGER_CST
+      && TREE_CODE (b) == INTEGER_CST)
+    {
+      return tristate (tree_int_cst_le (a, b));
+    }
+#endif
+
+  int i;
+  constraint *c;
+  FOR_EACH_VEC_ELT (m_constraints, i, c)
+    {
+      if (a == c->m_lhs)
+	{
+	  switch (c->m_op)
+	    {
+	    default:
+	      gcc_unreachable ();
+
+	    case CONSTRAINT_EQ:
+	      /* We have (a == R);  a <= b iff R <= b; recurse.  */
+	      {
+		tristate t = impl_le (c->m_rhs, b);
+		if (t.is_known ())
+		  return t;
+	      }
+	      break;
+
+	    case CONSTRAINT_NE:
+	      break;
+
+	    case CONSTRAINT_LT:
+	      /* We have (a < R); (R <= b) implies (a <= b).  */
+	      {
+		tristate t = impl_le (c->m_rhs, b);
+		if (t.is_true ())
+		  return t;
+	      }
+	      break;
+
+	    case CONSTRAINT_LE:
+	      /* We have (a <= R); (R <= b) implies (a <= b).  */
+	      {
+		tristate t = impl_le (c->m_rhs, b);
+		if (t.is_true ())
+		  return t;
+	      }
+	      break;
+	    }
+	}
+
+      if (b == c->m_lhs)
+	{
+	  switch (c->m_op)
+	    {
+	    default:
+	      gcc_unreachable ();
+
+	    case CONSTRAINT_EQ:
+	      /* We have (b == R);  a <= b iff a <= R; recurse.  */
+	      {
+		tristate t = impl_le (a, c->m_rhs);
+		if (t.is_known ())
+		  return t;
+	      }
+	      break;
+
+	    case CONSTRAINT_NE:
+	      break;
+
+	    case CONSTRAINT_LT:
+	      /* We have (b < R); (R < a) implies !(a <= b).  */
+	      {
+		tristate t = impl_lt (c->m_rhs, b);
+		if (t.is_true ())
+		  return tristate (tristate::TS_FALSE);
+	      }
+	      break;
+
+	    case CONSTRAINT_LE:
+	      /* We have (b <= R); (R < a) implies !(a <= b).  */
+	      {
+		tristate t = impl_lt (c->m_rhs, a);
+		if (t.is_true ())
+		  return tristate (tristate::TS_FALSE);
+	      }
+	      break;
+	    }
+	}
+      /* TODO: need to do rhs also.  */
+    }
+
+  return tristate (tristate::TS_UNKNOWN);
+}
+
+/* class constraints_checker : public checker */
+
+void
+ana::constraints_checker::on_node (node *n, context *ctxt)
+{
+  // TODO
+  if (0)
+    {
+      printf ("node: %p\n", n);
+      n->dump (stderr);
+    }
+
+  /* Handle the effect of statements.  */
+  gimple *stmt = n->get_location ()->get_stmt ();
+  if (!stmt)
+    return;
+
+  gassign *assign = dyn_cast <gassign *> (stmt);
+  if (assign)
+    {
+      if (0)
+	{
+	  printf ("handle assignment: ");
+	  print_gimple_stmt (stderr, assign, 0, (dump_flags_t)0);
+	}
+      enum tree_code subcode = gimple_assign_rhs_code (assign);
+      tree lhs = gimple_assign_lhs (assign);
+      switch (get_gimple_rhs_class (subcode))
+	{
+	case GIMPLE_SINGLE_RHS:
+	    break;
+	case GIMPLE_UNARY_RHS:
+	  if (subcode == NOP_EXPR)
+	    {
+	      tree op1 = gimple_assign_rhs1 (assign);
+	      tristate t = checker_state (n)->eval_condition (ctxt, n,
+							      op1, NE_EXPR, integer_zero_node);
+	      if (t.is_known ())
+		checker_state (n)->add_constraint (ctxt, n, lhs, t.is_true ());
+	    }
+	  break;
+	case GIMPLE_BINARY_RHS:
+	  {
+	    /* Handle "a = b OP c" where b OP C is known.  */
+	    tree op1 = gimple_assign_rhs1 (assign);
+	    tree op2 = gimple_assign_rhs2 (assign);
+	    tristate t = checker_state (n)->eval_condition (ctxt,
+							    n, op1, subcode, op2);
+	    if (t.is_known ())
+	      checker_state (n)->add_constraint (ctxt, n, lhs, t.is_true ());
+	  }
+	  break;
+
+	case GIMPLE_TERNARY_RHS:
+	  break;
+
+	default:
+	case GIMPLE_INVALID_RHS:
+	  gcc_unreachable ();
+	  break;
+	}
+    }
+}
+
+void
+ana::constraints_checker::before_call (node *n, gcall *call, context *ctxt)
+{
+  // TODO
+}
+
+void
+ana::constraints_checker::after_call (node *n, gcall *call, context *ctxt)
+{
+  // TODO
+}
+
+void
+ana::constraints_checker::dead_value (node *n, context *ctxt)
+{
+  // TODO
+}
+
+ana::tristate
+ana::constraints_checker::eval_condition (node *n, absvalue *lhs, enum tree_code op, absvalue *rhs)
+{
+  return checker_state (n)->eval_condition (lhs, op, rhs);
+}
+
+bool
+ana::constraints_checker::add_constraint (state *s,
+					  absvalue *lhs, enum tree_code op, absvalue *rhs)
+{
+  return checker_state (s)->add_constraint (lhs, op, rhs);
+}
diff --git a/gcc/analyzer/dead-code/core-checker.cc b/gcc/analyzer/dead-code/core-checker.cc
new file mode 100644
index 0000000..47c98ff
--- /dev/null
+++ b/gcc/analyzer/dead-code/core-checker.cc
@@ -0,0 +1,904 @@
+/* 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 "print-tree.h"
+#include "graphviz.h"
+
+namespace ana
+{
+
+/* Fundamental memory-modeling.  */
+
+class absvalue;
+class region;
+  class field_region;
+class core_state;
+
+enum memspace_kind {
+   MEMSPACE_KIND_STACK,
+   MEMSPACE_KIND_STATIC,
+   MEMSPACE_KIND_HEAP,
+   NUM_MEMSPACE_KINDS
+};
+
+static const char * const memspace_kind_str[NUM_MEMSPACE_KINDS] = {
+  "STACK",
+  "STATIC",
+  "HEAP"
+};
+
+class region : public absvalue
+{
+ public:
+  region *get_parent () const { return m_parent; }
+
+  region *dyn_cast_region () { return this; }
+
+  virtual enum memspace_kind get_memspace () const;
+
+  field_region *get_field (tree t_field, core_state *state);
+
+  virtual void write_label_for_parent_edge (pretty_printer *pp) {}
+
+ protected:
+  region (region *parent) : m_parent (parent), m_fields () {}
+
+ private:
+  region *m_parent;
+  hash_map <tree, field_region *> m_fields;
+};
+
+class memspace_region : public region
+{
+ public:
+  memspace_region (enum memspace_kind kind)
+    : region (NULL), m_kind (kind) {}
+
+  void dump_to_dot_record (pretty_printer *pp) const
+  {
+    pp_printf (pp, memspace_kind_str[m_kind]);
+  }
+
+  enum memspace_kind get_memspace () const { return m_kind; }
+
+ private:
+  enum memspace_kind m_kind;
+};
+
+class var_region : public region
+{
+ public:
+  var_region (region *parent, tree var_decl)
+    : region (parent), m_var_decl (var_decl) {}
+
+  void dump_to_dot_record (pretty_printer *pp) const
+  {
+    pp_printf (pp, "var_region: ");
+  }
+
+ private:
+  tree m_var_decl;
+};
+
+class field_region : public region
+{
+ public:
+  field_region (region *parent, tree field) : region (parent), m_field (field) {}
+
+  void dump_to_dot_record (pretty_printer *pp) const
+  {
+    pp_printf (pp, "field_region: ");
+  }
+
+  void write_label_for_parent_edge (pretty_printer *pp)
+  {
+    pp_printf (pp, ".");
+    dump_generic_node (pp, m_field, 0, (dump_flags_t)0, false);
+  }
+
+ private:
+  tree m_field;
+};
+
+class symbolic_region : public region
+{
+ public:
+  symbolic_region (region *parent)
+    : region (parent) {}
+
+  void dump_to_dot_record (pretty_printer *pp) const
+  {
+    pp_printf (pp, "symbolic_region: ");
+  }
+
+ private:
+};
+
+class core_state : public custom_state {
+ public:
+  custom_state *clone () const
+  {
+    return new core_state (*this);
+  }
+
+  void dump_to_dot_record (pretty_printer *pp) const;
+  void add_extra_nodes_and_edges (const node *n, graphviz_out *gv) const;
+
+  core_state ();
+  core_state (const core_state &other);
+
+  void on_assign (gassign *assign, context *ctxt, node *n);
+
+  region *eval_lvalue (tree expr);
+  region *eval_rvalue_loc (tree expr);
+  absvalue *eval_rvalue (tree expr);
+  void set_lvalue (region *lhs, absvalue *rhs);
+
+  region *get_stack () { return get_memspace_region (MEMSPACE_KIND_STACK); }
+
+  region *get_memspace_region (enum memspace_kind);
+
+  void add_region (region *r);
+
+ private:
+  region *m_memspace_regions[NUM_MEMSPACE_KINDS];
+  vec <region *> m_regions;
+
+  /* Mapping from variables to storage locations */
+  hash_map <tree, region *> m_env;
+  typedef hash_map <tree, region *>::iterator env_iterator;
+
+  /* Mapping from storage locations to values. */
+  hash_map <region *, absvalue *> m_store;
+  typedef hash_map <region *, absvalue *>::iterator store_iterator;
+};
+
+/* The checker itself.  */
+
+class core_checker : public stateful_checker <core_state>, public memory_model
+{
+ public:
+  void on_node (node *n, context *ctxt);
+  void before_call (node *n, gcall *call, context *ctxt);
+  void after_call (node *n, gcall *call, context *ctxt);
+  void dead_value (node */*n*/, context *ctxt) {}
+
+  /* Implementation of memory_model.  */
+  absvalue *eval_rvalue (node *n, tree expr);
+  absvalue *eval_rvalue (state *s, tree expr);
+};
+
+} // namespace ana
+
+ana::checker *ana::make_core_checker (analyzer *a)
+{
+  core_checker *ch = new core_checker ();
+  a->set_memory_model (ch);
+  return ch;
+}
+
+enum ana::memspace_kind
+ana::region::get_memspace () const
+{
+  gcc_assert (m_parent);
+  return m_parent->get_memspace ();
+}
+
+ana::field_region *
+ana::region::get_field (tree t_field, core_state *state)
+{
+  gcc_assert (t_field);
+  gcc_assert (state);
+
+  field_region **pfield = m_fields.get (t_field);
+  if (pfield)
+    return *pfield;
+  else
+    {
+      field_region *r = new field_region (this, t_field);
+      m_fields.put (t_field, r);
+      state->add_region (r);
+      return r;
+    }
+}
+
+/* class core_state : public per_checker_state */
+
+ana::core_state::core_state () : m_regions (), m_env (), m_store ()
+{
+  for (int i = 0; i < NUM_MEMSPACE_KINDS; i++)
+    m_memspace_regions[i] = NULL;
+}
+
+ana::core_state::core_state (const core_state &other)
+: m_regions (), m_env (), m_store ()
+{
+  int i;
+  region *r;
+
+  for (i = 0; i < NUM_MEMSPACE_KINDS; i++)
+    {
+      m_memspace_regions[i] = other.m_memspace_regions[i];
+    }
+
+  FOR_EACH_VEC_ELT (other.m_regions, i, r)
+    m_regions.safe_push (r);
+
+  /* Ideally we need a deep copy method for hash_map.  */
+  for (env_iterator iter = other.m_env.begin ();
+       iter != other.m_env.end ();
+       ++iter)
+    {
+      tree expr = (*iter).first;
+      region *r = (*iter).second;
+      m_env.get_or_insert (expr) = r;
+    }
+
+  for (store_iterator iter = other.m_store.begin ();
+       iter != other.m_store.end ();
+       ++iter)
+    {
+      region *r = (*iter).first;
+      absvalue *v = (*iter).second;
+      m_store.get_or_insert (r) = v;
+    }
+}
+
+void
+ana::core_state::dump_to_dot_record (pretty_printer *pp) const
+{
+  for (env_iterator iter = m_env.begin ();
+       iter != m_env.end ();
+       ++iter)
+    {
+      tree expr = (*iter).first;
+      pp_printf (pp, "expr: ");
+      dump_generic_node (pp, expr, 0, (dump_flags_t)0, false);
+
+      region *r = (*iter).second;
+      pp_printf (pp, " region: %p", r);
+      //r->dump_to_dot_record (pp);
+
+      absvalue **pvalue = const_cast <ana::core_state *> (this)->m_store.get (r);
+      pp_printf (pp, " value: ");
+      if (pvalue)
+	{
+	  absvalue *value = *pvalue;
+	  pp_printf (pp, " %p", value);
+	  //value->dump_to_dot_record (pp);
+	}
+      else
+	{
+	  pp_printf (pp, "(NULL)");
+	}
+      pp_newline (pp);
+      pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
+    }
+}
+
+void
+ana::core_state::add_extra_nodes_and_edges (const node *n, graphviz_out *gv) const
+{
+  /* Begin a subgraph.  */
+  gv->println ("subgraph \"cluster_core_state_%p\" {", (void *)this);
+  gv->indent ();
+  gv->println ("style=\"dashed\";");
+  gv->println ("color=\"green\";");
+  gv->println ("label=\"core_state\";");
+  gv->println ("clusterrank=\"local\";");
+
+  pretty_printer *pp = gv->m_pp;
+
+    for (env_iterator iter = m_env.begin ();
+	 iter != m_env.end ();
+	 ++iter)
+      {
+	tree expr = (*iter).first;
+	region *r = (*iter).second;
+
+	/* Create node for "expr".  */
+	gv->write_indent ();
+	pp_printf (pp,
+		   "core_state_%p_expr_%p "
+		   "[shape=%s, style=\"filled\" fillcolor=\"yellow\" label=\"",
+		   (const void *)this, (void *)expr, "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);
+      }
+
+    int i;
+    region *r;
+    FOR_EACH_VEC_ELT (m_regions, i, r)
+      {
+	pp_printf (pp,
+		   "core_state_%p_region_%p "
+		   "[shape=%s, style=\"filled\" fillcolor=\"cyan\" label=\"",
+		   (const void *)this, (void *)r, "record");
+	pp_left_brace (pp);
+	pp_write_text_to_stream (pp);
+	r->dump_to_dot_record (pp);
+	absvalue **pvalue = const_cast <ana::core_state *> (this)->m_store.get (r);
+	pp_printf (pp, " value: ");
+	if (pvalue)
+	  {
+	    absvalue *value = *pvalue;
+	    pp_printf (pp, " %p", value);
+	    if (value)
+	      value->dump_to_dot_record (pp);
+	  }
+	else
+	  {
+	    pp_printf (pp, "(NULL)");
+	  }
+	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);
+
+	/* Draw edge from child to parent.  */
+	if (r->get_parent ())
+	  {
+	    gv->write_indent ();
+	    pp_printf (pp,
+		       "core_state_%p_region_%p:s -> core_state_%p_region_%p:n [label=\"",
+		       (const void *)this, (void *)r,
+		       (const void *)this, (void *)r->get_parent ());
+	    pp_write_text_to_stream (pp);
+	    r->write_label_for_parent_edge (pp);
+	    pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
+	    pp_string (pp, "\"];");
+	    pp_flush (pp);
+	    pp_newline (pp);
+	  }
+
+	/* For pointers, draw edge to the region it points to.  */
+	if (pvalue)
+	  {
+	    if (*pvalue)
+	      {
+		region *pointee = (*pvalue)->dyn_cast_region ();
+		if (pointee)
+		  {
+		    gv->write_indent ();
+		    pp_printf (pp,
+			       "core_state_%p_region_%p:e -> core_state_%p_region_%p:w [style=dotted];\n",
+			       (const void *)this, (void *)r,
+			       (const void *)this, (void *)pointee);
+		  }
+	      }
+	  }
+      }
+
+  for (env_iterator iter = m_env.begin ();
+       iter != m_env.end ();
+       ++iter)
+    {
+      tree expr = (*iter).first;
+      region *r = (*iter).second;
+
+      /* Create edge from expr's node to that for its region (in
+	 the store subgraph).  */
+      gv->write_indent ();
+      pp_printf (pp,
+		 "core_state_%p_expr_%p -> core_state_%p_region_%p;\n",
+		 (const void *)this, (void *)expr,
+		 (const void *)this, (void *)r);
+    }
+
+  /* Terminate outermost subgraph.  */
+  gv->outdent ();
+  gv->println ("}");
+}
+
+void
+ana::core_state::on_assign (gassign *assign, context *ctxt, node *n)
+{
+  //printf ("on_assign\n");
+  tree lhs = gimple_assign_lhs (assign);
+  //debug_tree (lhs);
+  enum tree_code subcode = gimple_assign_rhs_code (assign);
+  switch (get_gimple_rhs_class (subcode))
+    {
+    case GIMPLE_SINGLE_RHS:
+      {
+	tree rhs = gimple_assign_rhs1 (assign);
+	//debug_tree (rhs);
+	switch (TREE_CODE (rhs))
+	  {
+	  case ADDR_EXPR:
+	    {
+	      printf ("got ADDR_EXPR\n");
+	      tree ref = TREE_OPERAND (rhs, 0);
+	      absvalue *a = eval_lvalue (ref);
+	      set_lvalue (eval_lvalue (lhs), a);
+	    }
+	    break;
+
+	  case INTEGER_CST:
+	    {
+	      absvalue *a = new int_constant (rhs);
+	      set_lvalue (eval_lvalue (lhs), a);
+	    }
+	    break;
+
+	  case CONSTRUCTOR:
+	    {
+	      // TODO
+	    }
+	    break;
+
+	  case COMPONENT_REF:
+	    {
+	      // TODO
+	      absvalue *a = eval_rvalue (rhs);
+	      set_lvalue (eval_lvalue (lhs), a);
+	    }
+	    break;
+
+	  case SSA_NAME:
+	    {
+	      absvalue *a = eval_rvalue (rhs);
+	      set_lvalue (eval_lvalue (lhs), a);
+	    }
+	    break;
+
+	  default:	    
+	    gcc_unreachable (); // TODO
+	    break;
+	  }
+      }
+      break;
+
+    case GIMPLE_UNARY_RHS:
+      {
+	tree rhs = gimple_assign_rhs1 (assign);
+	switch (subcode)
+	  {
+	  CASE_CONVERT:
+	    {
+	      /* FIXME: for now we don't properly handle conversions.  */
+	      set_lvalue (eval_lvalue (lhs), eval_rvalue (rhs));	      
+	    }
+	    break;
+
+	  default:
+	    gcc_unreachable (); // TODO
+	    break;
+	  }
+      }
+      break;
+
+    case GIMPLE_BINARY_RHS:
+      {
+	/* Handle conditionals.  */
+	// TODO: what about other kinds of binary op?
+	tree op1 = gimple_assign_rhs1 (assign);
+	tree op2 = gimple_assign_rhs2 (assign);
+	tristate t = ctxt->eval_condition (n, op1, subcode, op2);
+	absvalue *v_result;
+	if (t.is_known ())
+	  v_result = new int_constant (t.is_true ()
+				       ? integer_one_node : integer_zero_node);
+	else
+	  v_result = new unknown ();
+	set_lvalue(eval_lvalue(lhs), v_result);
+      }
+      break;
+
+    case GIMPLE_TERNARY_RHS:
+      gcc_unreachable (); // TODO
+      break;
+
+    default:
+    case GIMPLE_INVALID_RHS:
+      gcc_unreachable ();
+      break;
+    }
+}
+
+ana::region *
+ana::core_state::eval_lvalue (tree expr)
+{
+  switch (TREE_CODE (expr))
+    {
+    case PARM_DECL:
+    case VAR_DECL:
+      {
+	region **p_region = m_env.get (expr);
+	if (!p_region)
+	  {
+	    /* FIXME: is it really on the stack?  */
+	    region *r = new var_region (get_stack (), expr);
+	    m_regions.safe_push (r);
+	    m_env.put (expr, r);
+	    return r;
+	  }
+	return *p_region;
+      }
+
+    case SSA_NAME:
+      {
+	tree var = SSA_NAME_VAR (expr);
+	if (var)
+	  return eval_lvalue (var);
+	else
+	  {
+	    /* "expr" is a temporary.  */
+	    region **p_region = m_env.get (expr);
+	    if (!p_region)
+	      {
+		/* FIXME: is it really on the stack?  */
+		region *r = new var_region (get_stack (), expr);
+		m_regions.safe_push (r);
+		m_env.put (expr, r);
+		return r;
+	      }
+	    return *p_region;
+	  }
+      }
+
+    case COMPONENT_REF:
+      {
+	tree t_ref = TREE_OPERAND (expr, 0);
+	tree t_field = TREE_OPERAND (expr, 1);
+	region *r = eval_lvalue (t_ref);
+	if (r)
+	  return r->get_field (t_field, this);
+	else
+	  {
+	    gcc_unreachable ();
+	    return NULL; // FIXME
+	  }
+      }
+
+    case MEM_REF:
+      {
+	tree t_ptr = TREE_OPERAND (expr, 0);
+	tree t_offset = TREE_OPERAND (expr, 1);
+	gcc_assert (TREE_CODE (t_offset) == INTEGER_CST);
+	//gcc_assert (t_offset == zero);
+
+	region *r_ptr = eval_rvalue_loc (t_ptr);
+	if (r_ptr)
+	  return r_ptr;
+	else
+	  {
+	    region *r_star_ptr = new symbolic_region (NULL);
+	    m_regions.safe_push (r_star_ptr);
+	    set_lvalue (eval_lvalue (t_ptr), r_star_ptr);
+	    return r_star_ptr;
+          }
+      }
+
+    default:
+      gcc_unreachable ();
+      break;
+    }
+}
+
+ana::region *
+ana::core_state::eval_rvalue_loc (tree expr)
+{
+  switch (TREE_CODE (expr))
+    {
+    case SSA_NAME:
+      {
+	tree var = SSA_NAME_VAR (expr);
+	if (!var)
+	  /* Temporary.  */
+	  var = expr;
+	region *r = eval_lvalue (var);
+	absvalue **p_v = m_store.get (r);
+	if (p_v)
+	  {
+	    if (*p_v)
+	      {
+		region *pointee = (*p_v)->dyn_cast_region ();
+		if (pointee)
+		  return pointee;
+		else
+		  /* FIXME: what to do here?  */
+		  gcc_unreachable ();
+	      }
+	    else
+	      return NULL;
+	  }
+	else
+	  {
+	    /* Unknown value.  */
+	    return NULL;
+	  }
+      }
+      break;
+
+    default:
+      gcc_unreachable ();
+      break;
+    }
+}
+
+/* Attempt to eval the rvalue; return non-NULL.  */
+
+ana::absvalue *
+ana::core_state::eval_rvalue (tree expr)
+{
+  switch (TREE_CODE (expr))
+    {
+    case COMPONENT_REF:
+      {
+	region *r = eval_lvalue (expr);
+	absvalue **p_val = m_store.get (r);
+	if (p_val)
+	  return *p_val;
+	else
+	  {
+	    gcc_unreachable ();
+	    return NULL; /* FIXME */
+	  }
+      }
+      break;
+
+    case SSA_NAME:
+      {
+	region *r = eval_lvalue (expr);
+	absvalue **p_val = m_store.get (r);
+	if (p_val)
+	  return *p_val;
+	else
+	  {
+	    absvalue *v = new unknown ();
+	    m_store.put (r, v);
+	    return v;
+	  }
+      }
+      break;
+
+    case INTEGER_CST:
+      {
+	/* FIXME: cache/consolidate these?  */
+	return new int_constant (expr);
+      }
+
+    default:
+      gcc_unreachable ();
+      break;
+    }
+}
+
+void
+ana::core_state::set_lvalue (region *lhs, absvalue *rhs)
+{
+  m_store.get_or_insert (lhs) = rhs;
+}
+
+ana::region *
+ana::core_state::get_memspace_region (enum memspace_kind kind)
+{
+  /* Lazily create the base memspace regions.  */
+  if (!m_memspace_regions[kind])
+    {
+      m_memspace_regions[kind] = new memspace_region (kind);
+      m_regions.safe_push (m_memspace_regions[kind]);
+    }
+  return m_memspace_regions[kind];
+}
+
+void
+ana::core_state::add_region (region *r)
+{
+  m_regions.safe_push (r);
+}
+
+/* class core_checker : public checker */
+
+void
+ana::core_checker::on_node (node *n, context *ctxt)
+{
+  // TODO
+  gimple *stmt = n->get_location ()->get_stmt ();
+  if (!stmt)
+    return;
+
+  gassign *assign = dyn_cast <gassign *> (stmt);
+  if (assign)
+    {
+      core_state *state = checker_state (n);
+      state->on_assign (assign, ctxt, n);
+    }
+
+  greturn *return_stmt = dyn_cast <greturn *> (stmt);
+  if (return_stmt)
+    {
+      /* Complain about return of pointer to on-stack variable.  */
+      tree t_retval = gimple_return_retval (return_stmt);
+      if (t_retval)
+	{
+	  core_state *state = checker_state (n);
+	  absvalue *retval = state->eval_rvalue (t_retval);
+	  if (retval)
+	    {
+	      /* TODO: only complain if it's of pointer type.  */
+	      region *pointee = retval->dyn_cast_region ();
+	      if (pointee)
+		if (MEMSPACE_KIND_STACK == pointee->get_memspace ())
+		  {
+		      bug *b = new bug ("return of pointer to stack", n); // TODO: identify it
+		      // TODO: follow the symbols of interest
+		      emit_bug (b, ctxt);
+		  }
+	    }
+	}
+    }
+}
+
+void
+ana::core_checker::before_call (node *n, gcall *call, context *ctxt)
+{
+  /* Handle the builtin "__analyzer_dump" by dumping state to stderr.  */
+  if (is_named_call_p (call, "__analyzer_dump", 0))
+    {
+      n->dump (stdout);
+    }
+
+  /* Handle the builtin "__analyzer_eval" by evaluating the input
+     and dumping as a "warning", so that test cases can use dg-warning
+     to validate the result.  */
+  if (is_named_call_p (call, "__analyzer_eval", 1))
+    {
+      tree t_arg = gimple_call_arg (call, 0);
+      tristate t = ctxt->eval_condition (n,
+					 t_arg, NE_EXPR, integer_zero_node);
+      warning_at (n->get_source_location (), 0, t.as_string ());
+    }
+}
+
+void
+ana::core_checker::after_call (node */*n*/, gcall */*call*/, context *ctxt)
+{
+  // TODO
+}
+
+ana::absvalue *
+ana::core_checker::eval_rvalue (node *n, tree expr)
+{
+  core_state *state = checker_state (n);
+  return state->eval_rvalue (expr);
+}
+
+ana::absvalue *
+ana::core_checker::eval_rvalue (state *s, tree expr)
+{
+  core_state *state = checker_state (s);
+  return state->eval_rvalue (expr);
+}
+
+/************************************************************************/
+
+/* Additional checkers.  */
+
+namespace ana {
+class core_divide_by_zero_checker : public stateless_checker
+{
+ public:
+  void on_node (node *n, context *ctxt);
+  void before_call (node */*n*/, gcall */*call*/, context *ctxt) {}
+  void after_call (node */*n*/, gcall */*call*/, context *ctxt) {}
+  void dead_value (node */*n*/, context *ctxt) {}
+};
+
+class core_function_call_checker : public stateless_checker
+{
+ public:
+  void on_node (node */*n*/, context *ctxt) {}
+  void before_call (node *n, gcall *call, context *ctxt);
+  void after_call (node *n, gcall *call, context *ctxt);
+  void dead_value (node */*n*/, context *ctxt) {}
+};
+
+} // namespace ana
+
+/* class core_divide_by_zero_checker : public stateless_checker */
+void
+ana::core_divide_by_zero_checker::on_node (node *n, context *ctxt)
+{
+  // TODO
+  gimple *stmt = n->get_location ()->get_stmt ();
+  if (!stmt)
+    return;
+
+  gassign *assign = dyn_cast <gassign *> (stmt);
+  if (assign)
+    {
+      tree lhs = gimple_assign_lhs (assign);
+      //debug_tree (lhs);
+      enum tree_code subcode = gimple_assign_rhs_code (assign);
+      switch (subcode)
+	{
+	case TRUNC_DIV_EXPR:
+	case CEIL_DIV_EXPR:
+	case FLOOR_DIV_EXPR:
+	case ROUND_DIV_EXPR:
+	case TRUNC_MOD_EXPR:
+	case CEIL_MOD_EXPR:
+	case FLOOR_MOD_EXPR:
+	case ROUND_MOD_EXPR:
+	case RDIV_EXPR:
+	case EXACT_DIV_EXPR:
+	case FIX_TRUNC_EXPR:
+	  {
+	    tree op1 = gimple_assign_rhs1 (assign);
+	    tree op2 = gimple_assign_rhs2 (assign);
+	  }
+	  break;
+
+	default:
+	  break;
+	}
+    }
+}
+
+/* class core_function_call_checker : public stateless_checker */
+void
+ana::core_function_call_checker::before_call (node *n, gcall *call, context *ctxt)
+{
+  /* TODO:
+     warn about:
+     - uninitialized args
+     - passing NULL as a param that requires a non-NULL arg
+     - calling a function ptr that may be NULL
+     - etc.  */
+}
+
+void
+ana::core_function_call_checker::after_call (node *n, gcall *call, context *ctxt)
+{
+  // TODO
+}
+
+ana::checker *
+ana::make_core_divide_by_zero_checker ()
+{
+  return new core_divide_by_zero_checker ();
+}
+
+ana::checker *
+ana::make_core_function_call_checker ()
+{
+  return new core_function_call_checker ();
+}
diff --git a/gcc/analyzer/dead-code/libc-file-checker.cc b/gcc/analyzer/dead-code/libc-file-checker.cc
new file mode 100644
index 0000000..7854203
--- /dev/null
+++ b/gcc/analyzer/dead-code/libc-file-checker.cc
@@ -0,0 +1,226 @@
+/* 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"
+
+namespace ana
+{
+
+class libc_file_checker;
+
+/* Checker for libc FILE * operations.  */
+
+/* State for the checker.  */
+enum file_state {FILE_MAYBE_OPEN, FILE_OPEN, FILE_CLOSED};
+
+class file_states : public custom_state {
+ public:
+  custom_state *clone () const
+  {
+    return new file_states (*this);
+  }
+
+  void dump_to_dot_record (pretty_printer *pp) const;
+
+  file_states () : m_states () {}
+  file_states (const file_states &other);
+
+  void set_tree_state (tree t, enum file_state value);
+  bool get_tree_state (tree t, enum file_state &out);
+
+ private:
+  /* TODO: this should be a map from symbolic values to states,
+     rather than from trees. */
+  hash_map <tree, enum file_state> m_states;
+  typedef hash_map <tree, enum file_state>::iterator state_iterator;
+  friend class libc_file_checker;
+};
+
+/* The checker itself.  */
+
+class libc_file_checker : public stateful_checker <file_states>
+{
+ public:
+  void on_node (node *n, context *ctxt);
+  void before_call (node *n, gcall *call, context *ctxt);
+  void after_call (node *n, gcall *call, context *ctxt);
+  void dead_value (node *n, context *ctxt);
+
+ private:
+  void report_double_close (node *n, tree fp, context *ctxt);
+  void report_leaked_file (node *n, tree fp, context *ctxt);
+
+};
+
+} // namespace ana
+
+ana::checker *ana::make_libc_file_checker ()
+{
+  return new libc_file_checker ();
+}
+
+/* class file_states : public per_checker_state */
+
+static const char * const state_names[] = {
+  "FILE_MAYBE_OPEN",
+  "FILE_OPEN",
+  "FILE_CLOSED"
+};
+
+ana::file_states::file_states (const file_states &other) : m_states ()
+{
+  /* We can't simple copy m_states, or the states will become linked;
+     ideally we need a deep copy method for hash_map.  */
+  for (state_iterator iter = other.m_states.begin ();
+       iter != other.m_states.end ();
+       ++iter)
+    {
+      tree t = (*iter).first;
+      enum file_state value = (*iter).second;
+      m_states.get_or_insert (t) = value;
+    }
+}
+
+void
+ana::file_states::dump_to_dot_record (pretty_printer *pp) const
+{
+  for (state_iterator iter = m_states.begin ();
+       iter != m_states.end ();
+       ++iter)
+    {
+      tree t = (*iter).first;
+      enum file_state value = (*iter).second;
+      pp_printf (pp, "file_state: ");
+      dump_generic_node (pp, t, 0, (dump_flags_t)0, false);
+      pp_printf (pp, ": %s", state_names[value]);
+      pp_newline (pp);
+      pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
+    }
+}
+
+void
+ana::file_states::set_tree_state (tree t, enum file_state value)
+{
+  if (TREE_CODE (t) != SSA_NAME)
+    return; // for now
+  m_states.get_or_insert (t) = value;
+}
+
+bool
+ana::file_states::get_tree_state (tree t, enum file_state &out)
+{
+  if (TREE_CODE (t) != SSA_NAME)
+    return false; // for now
+  enum file_state *state = m_states.get (t);
+  if (state)
+    {
+      out = *state;
+      return true;
+    }
+  else
+    return false;
+}
+
+/* class libc_file_checker : public checker */
+
+void
+ana::libc_file_checker::on_node (node */*n*/, context *ctxt)
+{
+  // TODO
+}
+
+void
+ana::libc_file_checker::before_call (node *n, gcall *call, context *ctxt)
+{
+  // TODO
+  if (is_named_call_p (call, "fclose", 1))
+    {
+      //printf ("before_call to: fclose\n");
+      tree fp = gimple_call_arg (call, 0);
+      if (fp)
+	{
+	  enum file_state val;
+	  if (checker_state (n)->get_tree_state (fp, val))
+	    if (val == FILE_CLOSED)
+	      report_double_close (n, fp, ctxt);
+	}
+    }
+}
+
+void
+ana::libc_file_checker::after_call (node *n, gcall *call, context *ctxt)
+{
+  // TODO
+  if (is_named_call_p (call, "fopen", 2))
+    {
+      //printf ("after_call to: fopen\n");
+      tree lhs = gimple_call_lhs (call);
+      if (lhs)
+	checker_state (n)->set_tree_state (lhs, FILE_MAYBE_OPEN);
+    }
+
+  if (is_named_call_p (call, "fclose", 1))
+    {
+      //printf ("after_call to: fclose\n");
+      tree fp = gimple_call_arg (call, 0);
+      if (fp)
+	checker_state (n)->set_tree_state (fp, FILE_CLOSED);
+    }
+}
+
+void
+ana::libc_file_checker::dead_value (node *n, context *ctxt)
+{
+  file_states *states = checker_state (n);
+  for (file_states::state_iterator iter = states->m_states.begin ();
+       iter != states->m_states.end ();
+       ++iter)
+    {
+      tree t = (*iter).first;
+      enum file_state value = (*iter).second;
+
+      if (value == FILE_MAYBE_OPEN)
+	report_leaked_file (n, t, ctxt);
+    }
+}
+
+void
+ana::libc_file_checker::report_double_close (node *n, tree /*fp*/, context *ctxt)
+{
+  bug *b = new bug ("double-close of FILE *", n);
+  // TODO: follow the symbol of interest (fp)
+  emit_bug (b, ctxt);
+}
+
+void
+ana::libc_file_checker::report_leaked_file (node *n, tree /*fp*/, context *ctxt)
+{
+  bug *b = new bug ("leak of FILE *", n); // TODO: name of fp?
+  // TODO: follow the symbol of interest (fp)
+  emit_bug (b, ctxt);
+}
diff --git a/gcc/analyzer/dead-code/libc-malloc-checker.cc b/gcc/analyzer/dead-code/libc-malloc-checker.cc
new file mode 100644
index 0000000..3565c85
--- /dev/null
+++ b/gcc/analyzer/dead-code/libc-malloc-checker.cc
@@ -0,0 +1,220 @@
+/* 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"
+
+namespace ana
+{
+
+/* Checker for libc malloc/free/realloc.  */
+
+/* State for the checker.  */
+enum ptr_state {PTR_MALLOCED, /* or NULL */
+		PTR_FREED};
+
+class ptr_states : public custom_state {
+ public:
+  custom_state *clone () const
+  {
+    return new ptr_states (*this);
+  }
+
+  void dump_to_dot_record (pretty_printer *pp) const;
+
+  ptr_states () : m_states () {}
+  ptr_states (const ptr_states &other);
+
+  void set_tree_state (tree t, enum ptr_state value);
+  bool get_tree_state (tree t, enum ptr_state &out);
+
+ private:
+  /* TODO: this should be a map from symbolic values to states,
+     rather than from trees. */
+  hash_map <tree, enum ptr_state> m_states;
+  typedef hash_map <tree, enum ptr_state>::iterator state_iterator;
+  friend class libc_malloc_checker;
+};
+
+class libc_malloc_checker : public stateful_checker <ptr_states>
+{
+ public:
+  void on_node (node *n, context *ctxt);
+  void before_call (node *n, gcall *call, context *ctxt);
+  void after_call (node *n, gcall *call, context *ctxt);
+  void dead_value (node *n, context *ctxt);
+
+ private:
+  void report_double_free (node *n, tree ptr, context *ctxt);
+  void report_leak (node *n, tree ptr, context *ctxt);
+};
+
+} // namespace ana
+
+ana::checker *ana::make_libc_malloc_checker ()
+{
+  return new libc_malloc_checker ();
+}
+
+/* class ptr_states : public per_checker_state */
+
+static const char * const state_names[] = {
+  "PTR_MALLOCED",
+  "PTR_FREE"
+};
+
+ana::ptr_states::ptr_states (const ptr_states &other) : m_states ()
+{
+  /* We can't simple copy m_states, or the states will become linked;
+     ideally we need a deep copy method for hash_map.  */
+  for (state_iterator iter = other.m_states.begin ();
+       iter != other.m_states.end ();
+       ++iter)
+    {
+      tree t = (*iter).first;
+      enum ptr_state value = (*iter).second;
+      m_states.get_or_insert (t) = value;
+    }
+}
+
+void
+ana::ptr_states::dump_to_dot_record (pretty_printer *pp) const
+{
+  for (state_iterator iter = m_states.begin ();
+       iter != m_states.end ();
+       ++iter)
+    {
+      tree t = (*iter).first;
+      enum ptr_state value = (*iter).second;
+      pp_printf (pp, "ptr_state: ");
+      dump_generic_node (pp, t, 0, (dump_flags_t)0, false);
+      pp_printf (pp, ": %s", state_names[value]);
+      pp_newline (pp);
+      pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
+    }
+}
+
+void
+ana::ptr_states::set_tree_state (tree t, enum ptr_state value)
+{
+  if (TREE_CODE (t) != SSA_NAME)
+    return; // for now
+  m_states.get_or_insert (t) = value;
+}
+
+bool
+ana::ptr_states::get_tree_state (tree t, enum ptr_state &out)
+{
+  if (TREE_CODE (t) != SSA_NAME)
+    return false; // for now
+  enum ptr_state *state = m_states.get (t);
+  if (state)
+    {
+      out = *state;
+      return true;
+    }
+  else
+    return false;
+}
+
+/* class libc_malloc_checker : public checker */
+
+void
+ana::libc_malloc_checker::on_node (node * /*n*/, context *ctxt)
+{
+  // TODO
+}
+
+void
+ana::libc_malloc_checker::before_call (node *n, gcall *call, context *ctxt)
+{
+  // TODO
+  if (is_named_call_p (call, "free", 1))
+    {
+      tree ptr = gimple_call_arg (call, 0);
+      if (ptr)
+	{
+	  enum ptr_state val;
+	  if (checker_state (n)->get_tree_state (ptr, val))
+	    if (val == PTR_FREED)
+	      report_double_free (n, ptr, ctxt);
+	}
+    }
+}
+
+void
+ana::libc_malloc_checker::after_call (node *n, gcall *call, context *ctxt)
+{
+  // TODO
+  if (is_named_call_p (call, "malloc", 1))
+    {
+      tree lhs = gimple_call_lhs (call);
+      if (lhs)
+	checker_state (n)->set_tree_state (lhs, PTR_MALLOCED);
+      else
+	; /* TODO: report leak.  */
+    }
+
+  if (is_named_call_p (call, "free", 1))
+    {
+      tree ptr = gimple_call_arg (call, 0);
+      if (ptr)
+	checker_state (n)->set_tree_state (ptr, PTR_FREED);
+    }
+}
+
+void
+ana::libc_malloc_checker::dead_value (node *n, context *ctxt)
+{
+  ptr_states *states = checker_state (n);
+  for (ptr_states::state_iterator iter = states->m_states.begin ();
+       iter != states->m_states.end ();
+       ++iter)
+    {
+      tree t = (*iter).first;
+      enum ptr_state value = (*iter).second;
+
+      if (value == PTR_MALLOCED)
+	report_leak (n, t, ctxt);
+    }
+}
+
+void
+ana::libc_malloc_checker::report_double_free (node *n, tree ptr, context *ctxt)
+{
+  bug *b = new bug ("double-free", n);
+  // TODO: follow the symbol of interest (ptr)
+  emit_bug (b, ctxt);
+}
+
+void
+ana::libc_malloc_checker::report_leak (node *n, tree ptr, context *ctxt)
+{
+  bug *b = new bug ("leak", n);
+  // TODO: follow the symbol of interest (ptr)
+  emit_bug (b, ctxt);
+}
diff --git a/gcc/analyzer/dead-code/toy-sm-impl-1.cc b/gcc/analyzer/dead-code/toy-sm-impl-1.cc
new file mode 100644
index 0000000..beb2c20
--- /dev/null
+++ b/gcc/analyzer/dead-code/toy-sm-impl-1.cc
@@ -0,0 +1,1052 @@
+/* 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 <string>
+#include "supergraph.h"
+#include "toy-sm-state-machine.h"
+
+static const bool DEBUG = false;
+//static const bool DEBUG = true;
+
+static int verbosity = 2;
+
+////////////////////////////////////////////////////////////////////////////
+
+class context;
+class sm_instance;
+
+////////////////////////////////////////////////////////////////////////////
+
+// FIXME: horrible hack, so that we can use %qE, which needs subclassing of
+// pretty_printer.  Do we need a cloning op?
+
+static char *
+global_dc_print (const char *fmt, ...)
+{
+  pretty_printer *pp = global_dc->printer;
+  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;
+
+#if 1
+#else
+  pp_show_color (pp) = pp_show_color (global_dc->printer);
+  pp_format_decoder (pp) = pp_format_decoder (global_dc->printer);
+#endif
+  pp_format (pp, &ti);
+  pp_output_formatted_text (pp);
+
+  va_end (ap);
+
+  char *result = xstrdup (pp_formatted_text (pp));
+
+  pp_clear_output_area (pp);
+
+  return result;
+}
+
+struct var_state_1_t
+{
+  //typedef var_state_1_t value_type;
+
+  var_state_1_t (tree var, state_machine::state_t state)
+  : m_var (var), m_state (state)
+  {}
+
+  std::string to_str (const state_machine &sm) const
+  {
+    char *tmp = global_dc_print ("%qE: %qs",
+				 m_var, sm.get_state_name (m_state));
+    std::string result = tmp;
+    free (tmp);
+    return result;
+  }
+
+  tree m_var;
+  state_machine::state_t m_state;
+};
+struct summary_point
+{
+  state_machine::state_t m_gstate;
+  var_state_1_t m_varstate;
+};
+struct summary_edge
+{
+  summary_point start;
+  summary_point end;
+};
+
+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);
+};
+
+template <> struct default_hash_traits<var_state_1_t>
+: public pod_hash_traits<var_state_1_t>
+{
+};
+
+template <>
+inline hashval_t
+pod_hash_traits<var_state_1_t>::hash (value_type v)
+{
+  // FIXME:
+  return ((hashval_t)v.m_state);
+}
+
+template <>
+inline bool
+pod_hash_traits<var_state_1_t>::equal (const value_type &existing,
+				     const value_type &candidate)
+{
+  return (existing.m_var == candidate.m_var
+	  && existing.m_state == candidate.m_state);
+}
+template <>
+inline void
+pod_hash_traits<var_state_1_t>::mark_deleted (value_type &v)
+{
+  v = var_state_1_t (NULL_TREE, 0);
+}
+template <>
+inline void
+pod_hash_traits<var_state_1_t>::mark_empty (value_type &v)
+{
+  v = var_state_1_t (NULL_TREE, 0);
+}
+template <>
+inline bool
+pod_hash_traits<var_state_1_t>::is_deleted (value_type v)
+{
+  return (v.m_var == NULL_TREE && v.m_state == 0);
+}
+template <>
+inline bool
+pod_hash_traits<var_state_1_t>::is_empty (value_type v)
+{
+  return (v.m_var == NULL_TREE && v.m_state == 0);
+}
+
+/* FIXME: This changes as we do the traversal: */
+
+struct sm_instance_1
+{
+  sm_instance_1 () : m_gstate (0), m_active_vars () {}
+
+  std::string to_str (const state_machine &sm) const
+  {
+    // FIXME: obstack?
+    std::string result = "<";
+    result += sm.get_state_name (m_gstate);
+    result += ", {";
+    bool first = true;
+    for (hash_set<var_state_1_t>::iterator iter = m_active_vars.begin ();
+	 iter != m_active_vars.end ();
+	 ++iter)
+      {
+	if (first)
+	  first = false;
+	else
+	  result += ", ";
+	result += (*iter).to_str (sm);
+      }
+    result += "}>";
+    return result;
+  }
+
+  state_machine::state_t m_gstate;
+  hash_set<var_state_1_t> m_active_vars;
+};
+
+class context
+{
+public:
+  // FIXME: for now, just global state
+  // FIXME: but should be a map from ndoes to state info
+  // e.g. a bitmap of states that are known to be reachable at entry
+  // to that node (possibly tagged with a minimal path that reaches it?)
+  //state_machine::state_t m_state;
+
+  struct per_node_t
+  {
+    hash_set<summary_edge *> m_add;
+    hash_set<summary_edge *> m_transition;
+  };
+  /* FIXME: This is shared by the traversal: */
+  hash_map <supernode *, per_node_t> m_per_node;
+
+  void on_transition (sm_instance_1 *sm_inst,
+		      const state_machine &sm,
+		      diagnostic_path *p,
+		      int stack_depth,
+		      const gimple *stmt, tree fndecl, tree dest,
+		      state_machine::state_t from,
+		      state_machine::state_t to);
+
+#if 0
+  // FIXME: map from tree to state_t (and where that state came from?)
+  hash_map <tree, state_t> m_states;
+  typedef hash_map <tree, state_t>::iterator state_iterator;
+#endif
+};
+
+void
+context::on_transition (sm_instance_1 *sm_inst,
+			const state_machine &sm,
+			diagnostic_path *p,
+			int stack_depth,
+			const gimple *stmt, tree fndecl,
+			tree dest,
+			state_machine::state_t from,
+			state_machine::state_t to)
+{
+  if (DEBUG)
+    inform (stmt->location, "DBG: %s; transition: %qE: %qs -> %qs",
+	    sm_inst->to_str (sm).c_str (),
+	    dest, sm.get_state_name (from), sm.get_state_name (to));
+
+  // TODO: filter: only the transitions for which the start state is reachable
+  // "cache_misses" ?
+
+  if (from == 0 || sm_inst->m_active_vars.contains (var_state_1_t (dest, from)))
+    {
+      p->add_event (stmt->location, fndecl, stack_depth,
+		    "%s; transition: %qE: %qs -> %qs",
+		    sm_inst->to_str (sm).c_str (), dest,
+		    sm.get_state_name (from), sm.get_state_name (to));
+      sm_inst->m_active_vars.add (var_state_1_t (dest, to));
+    }
+
+  // FIXME: also do the equivalence classes?
+}
+
+static std::string
+backtrace_to_str (const supergraph *sg,
+		  const auto_vec<cgraph_edge *> &backtrace)
+{
+  std::string result ("[");
+  // FIXME: this is probably not optimal
+
+  int i;
+  cgraph_edge *e;
+  FOR_EACH_VEC_ELT (backtrace, i, e)
+    {
+      if (i > 0)
+	result += ", ";
+      result += sg->get_caller_next_node (e)->to_str ();
+    }
+
+  result += "]";
+
+  return result;
+}
+
+/* A tree, extended with stack frame information for locals, so that
+   we can distinguish between different values of locals within a potentially
+   recursive callstack.  */
+
+
+class old_path_var
+{
+public:
+  old_path_var (tree t, int stack_depth)
+  : m_tree (t), m_stack_depth (stack_depth)
+  {
+    // TODO: ignore stack depth for globals and constants
+  }
+
+  std::string to_str () const;
+
+  tree m_tree;
+  int m_stack_depth; // or -1 for globals?
+};
+
+static std::string
+expr_to_string (tree t)
+{
+  char *tmp = global_dc_print ("%qE", t);
+  std::string result = tmp;
+  free (tmp);
+  return result;
+}
+
+std::string
+old_path_var::to_str () const
+{
+  return (std::string ("(")
+	  + expr_to_string (m_tree)
+	  + " @ "
+	  + std::to_string (m_stack_depth)
+	  + ")");
+}
+
+/* TODO: I *think* we need equiv classes in order to be able to cope with:
+
+     something_that_calls_free (ptr);
+
+   also transitioning "ptr" in this frame to "freed".  (but maybe summaries
+   help with this also?)  */
+
+#if 0
+/* FIXME.  */
+
+class equiv_class
+{
+public:
+  equiv_class ();
+  equiv_class (const equiv_class &other);
+
+  std::string to_str () const;
+
+  void add (old_path_var v) { m_vars.safe_push (v); }
+  //void del (old_path_var v);
+
+  // FIXME: this is a bad representation, probably should be a set.
+  auto_vec<old_path_var> m_vars;
+};
+
+equiv_class::equiv_class ()
+{
+}
+
+equiv_class::equiv_class (const equiv_class &other)
+{
+  // FIXME: copy-ctor for auto_vec!
+  int i;
+  old_path_var *v;
+  FOR_EACH_VEC_ELT (other.m_vars, i, v)
+    m_vars.safe_push (*v);
+}
+
+std::string
+equiv_class::to_str () const
+{
+  std::string result ("{");
+  int i;
+  old_path_var *v;
+  FOR_EACH_VEC_ELT (m_vars, i, v)
+    {
+      if (i > 0)
+	result += " == ";
+      result += v->to_str ();
+    }
+  return result + "}";
+}
+
+#endif
+
+// TODO: maybe reimplement in terms of constraints for now:
+
+enum constraint_op
+{
+  CONSTRAINT_EQ,
+  CONSTRAINT_NE,
+  CONSTRAINT_LT,
+  CONSTRAINT_LE
+};
+
+static const char *
+constraint_op_code (enum constraint_op c_op)
+{
+  switch (c_op)
+    {
+    default:
+      gcc_unreachable ();
+    case CONSTRAINT_EQ: return "==";
+    case CONSTRAINT_NE: return "!=";
+    case CONSTRAINT_LT: return "<";
+    case CONSTRAINT_LE: return "<=";
+    }
+}
+
+class old_constraint {
+ public:
+  old_constraint (old_path_var lhs, enum constraint_op c_op, old_path_var rhs)
+    : m_lhs (lhs), m_op (c_op), m_rhs (rhs) {}
+
+  std::string to_str () const;
+
+  int get_max_stack_depth () const
+  {
+    return std::max (m_lhs.m_stack_depth, m_rhs.m_stack_depth);
+  }
+
+  old_path_var m_lhs;
+  enum constraint_op m_op;
+  old_path_var m_rhs;
+};
+
+std::string
+old_constraint::to_str () const
+{
+  return (m_lhs.to_str ()
+	  + constraint_op_code (m_op)
+	  + m_rhs.to_str ());
+}
+
+/* Facts that we know, for the current path_state.  */
+
+class old_path_constraints
+{
+public:
+  old_path_constraints ();
+  old_path_constraints (const old_path_constraints &other);
+
+  std::string to_str () const;
+
+  void on_assignment (old_path_var dst, old_path_var src);
+  void on_stack_frame_popped (int new_max_depth);
+
+#if 0
+  auto_vec<equiv_class *> m_equiv_classes;
+#endif
+  auto_vec<old_constraint> m_constraints;
+};
+
+old_path_constraints::old_path_constraints ()
+{
+}
+
+old_path_constraints::old_path_constraints (const old_path_constraints &other)
+{
+  // FIXME: copy-ctor for auto_vec!
+  int i;
+#if 0
+  equiv_class *ec;
+  FOR_EACH_VEC_ELT (other.m_equiv_classes, i, ec)
+    m_equiv_classes.safe_push (new equiv_class (*ec));
+#endif
+
+  old_constraint *c;
+  FOR_EACH_VEC_ELT (other.m_constraints, i, c)
+    m_constraints.safe_push (*c);
+}
+
+std::string
+old_path_constraints::to_str () const
+{
+  std::string result ("{");
+
+  int i;
+#if 0
+  equiv_class *ec;
+  FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+    {
+      if (i > 0)
+	result += ", ";
+      result += ec->to_str ();
+    }
+#endif
+
+  old_constraint *c;
+  FOR_EACH_VEC_ELT (m_constraints, i, c)
+    {
+      if (i > 0)
+	result += ", ";
+      result += c->to_str ();
+    }
+
+  result += "}";
+  return result;
+}
+
+void
+old_path_constraints::on_assignment (old_path_var dst, old_path_var src)
+{
+  // FIXME: need a better implementation; for now
+#if 0
+  // just always add a new equivalence class:
+  equiv_class *ec = new equiv_class ();
+  m_equiv_classes.safe_push (ec);
+  ec->add (dst);
+  ec->add (src);
+#endif
+
+  m_constraints.safe_push (old_constraint (dst, CONSTRAINT_EQ, src));
+}
+
+void
+old_path_constraints::on_stack_frame_popped (int new_max_depth)
+{
+  // TODO: purge any ecs relating to the old frame:
+#if 0
+  int i;
+  equiv_class *ec;
+  FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+    {
+      for (unsigned j = 0; j < ec->m_vars.length (); j++)
+	if (ec->m_vars[j].m_stack_depth > new_max_depth)
+	  ec->m_vars.unordered_remove (j);
+    }
+#endif
+  // TODO: do it for constraints
+  for (unsigned i = 0; i < m_constraints.length (); i++)
+    if (m_constraints[i].get_max_stack_depth () > new_max_depth)
+      m_constraints.unordered_remove (i);
+}
+
+
+/* The state of a particular point at one point in the traversal (not including
+   state-machine information):
+   - the diagnostic_path that we're building (in case we need to
+     emit a diagnostic)
+   - the backtrace for unwinding calls,
+   - TODO: constraints (e.g. for diamond CFGs).  */
+
+class old_path_state
+{
+public:
+  old_path_state () : m_recursion_depth (0) {}
+  old_path_state (const old_path_state &other);
+
+  std::string to_str (const supergraph *sg) const;
+
+  int get_stack_depth () const { return m_backtrace.length (); }
+
+  diagnostic_path m_path;
+  auto_vec<cgraph_edge *> m_backtrace;
+  old_path_constraints m_constraints;
+  int m_recursion_depth;
+};
+
+/* FIXME.  */
+// TODO: maybe make this less inefficient (lots of copying)
+
+old_path_state::old_path_state (const old_path_state &other)
+: m_path (other.m_path),
+  m_backtrace (),
+  m_constraints (other.m_constraints),
+  m_recursion_depth (other.m_recursion_depth)
+{
+  // FIXME: copy-ctor for auto_vec!
+  int i;
+  cgraph_edge *edge;
+  FOR_EACH_VEC_ELT (other.m_backtrace, i, edge)
+    m_backtrace.safe_push (edge);
+}
+
+/* FIXME.  */
+
+std::string
+old_path_state::to_str (const supergraph *sg) const
+{
+  return (std::string ("<path length: ")
+	  + std::to_string (m_path.m_events.length())
+	  + std::string (" backtrace: ") + backtrace_to_str (sg, m_backtrace)
+	  + std::string (" constraints: ") + m_constraints.to_str ()
+	  + std::string (">"));
+}
+
+/* FIXME.  */
+
+class path_finder
+{
+public:
+  path_finder (const supergraph *supergraph,
+	       const state_machine &sm,
+	       context *ctxt)
+  : m_supergraph (supergraph), m_sm (sm), m_ctxt (ctxt),
+    m_traversals (0), m_max_recursion_depth (0)
+  {
+  }
+
+  void dfs_traversal (const supernode *node,
+		      old_path_state state,
+		      sm_instance_1 *sm_inst,
+		      auto_bitmap &visited);
+
+  void on_stmt (const supernode *node,
+		old_path_state &state,
+		sm_instance_1 *sm_inst,
+		const gimple *stmt);
+
+  //private:
+  /* Various things that are global to the whole traversal.  */
+  const supergraph *m_supergraph;
+  const state_machine &m_sm;
+  context *m_ctxt;
+
+  /* Stats.  */
+public:
+  int m_traversals;
+  int m_max_recursion_depth;
+};
+
+/* Interface to path_finder::dfs_traversal for a state_machine to call
+   into.  */
+
+class foo_sm_context : public sm_context
+{
+public:
+  foo_sm_context (const state_machine &sm,
+		  path_finder &pf,
+		  old_path_state &state,
+		  sm_instance_1 *sm_inst)
+  : sm_context (), m_sm (sm), m_pf (pf), m_state (state),
+    m_sm_inst (sm_inst)
+  {}
+
+#if 1
+  void on_transition (const supernode *node, const gimple *stmt,
+		      tree var,
+		      state_machine::state_t from,
+		      state_machine::state_t to) FINAL OVERRIDE
+  {
+    // FIXME
+    tree fndecl = node->m_fun->decl;
+    m_pf.m_ctxt->on_transition (m_sm_inst, m_sm, &m_state.m_path,
+				m_state.get_stack_depth (), stmt, fndecl,
+				var, from, to);
+  }
+#endif
+
+  //state_machine::state_t get_state (tree var) FINAL OVERRIDE {/* FIXME */}
+  //void transition (tree var, state_machine::state_t to) FINAL OVERRIDE {/* FIXME */}
+  void error (const supernode *node, const gimple *stmt,
+	      tree var, state_machine::state_t state, const char *fmt, ...) FINAL OVERRIDE {/* FIXME */}
+
+private:
+  const state_machine &m_sm;
+  path_finder &m_pf;
+  old_path_state &m_state;
+  sm_instance_1 *m_sm_inst;
+};
+
+void
+path_finder::dfs_traversal (const supernode *node,
+			    old_path_state state,
+			    sm_instance_1 *sm_inst,
+			    auto_bitmap &visited)
+{
+  m_traversals++;
+  state.m_recursion_depth++;
+  if (m_max_recursion_depth < state.m_recursion_depth)
+    m_max_recursion_depth = state.m_recursion_depth;
+
+  std::string sm_inst_str = sm_inst->to_str (m_sm);
+  if (DEBUG)
+    inform (node->get_start_location (),
+	    "DBG: dfs_traversal on %qD supernode=%i, sm_inst: %s, state: %s",
+	    node->m_fun->decl, node->m_index, sm_inst_str.c_str (),
+	    state.to_str (m_supergraph).c_str ());
+
+  bitmap_set_bit (visited, node->m_index);
+
+  if (verbosity >= 2)
+    state.m_path.add_event (node->get_start_location (),
+			    node->m_fun->decl, state.get_stack_depth (),
+			    "%qD supernode:%i sm_inst: %s state: %s",
+			    node->m_fun->decl, node->m_index,
+			    sm_inst_str.c_str (),
+			    state.to_str (m_supergraph).c_str ());
+
+  int i;
+  gimple *stmt;
+  FOR_EACH_VEC_ELT (node->m_stmts, i, stmt)
+    on_stmt (node, state, sm_inst, stmt);
+
+  if (node->return_p ())
+    {
+      tree callee = node->m_fun->decl;
+
+      // TODO: respect "visited"
+
+      /* Get IVP successor, if any, via backtrace.  */
+      if (state.m_backtrace.length () == 0)
+	{
+	  gcc_rich_location rich_loc (node->m_fun->function_end_locus);
+	  rich_loc.set_path (&state.m_path);
+	  // FIXME: set/reset current_function_decl so as to get "In function"
+	  // announcements
+	  //fprintf (stderr, "============================================================================\n");
+	  inform (&rich_loc, "END OF PATH: length: %i (presumably started within %qD)",
+		  state.m_path.m_events.length (),
+		  node->m_fun->decl);
+	  //fprintf (stderr, "----------------------------------------------------------------------------\n");
+	  return;
+	}
+
+      /* Recurse, taking a copy of the path.  */
+      old_path_state next (state);
+      cgraph_edge *edge = next.m_backtrace.pop ();
+
+      supernode *dest = m_supergraph->get_caller_next_node (edge);
+
+      // FIXME: need to purge state, and update return value based on *sm_inst:
+      sm_instance_1 next_sm_inst;
+      if (!VOID_TYPE_P (TREE_TYPE (DECL_RESULT (callee))))
+	{
+	  if (dest->m_returning_call
+	      && gimple_call_lhs (dest->m_returning_call))
+	    {
+	      for (hash_set<var_state_1_t>::iterator iter = sm_inst->m_active_vars.begin ();
+		   iter != sm_inst->m_active_vars.end ();
+		   ++iter)
+		{
+		  // purge to just the return value, and assign to lhs of m_returning_call:
+		  if ((*iter).m_var == DECL_RESULT (callee))
+		    {
+		      next_sm_inst.m_active_vars.add
+			(var_state_1_t (gimple_call_lhs (dest->m_returning_call),
+				      (*iter).m_state));
+		    }
+		  // FIXME: should this be done by the next node?
+		}
+	    }
+	}
+      // TODO: purge state from constraints
+      next.m_constraints.on_stack_frame_popped (next.get_stack_depth ());
+
+      if (DEBUG)
+	inform (UNKNOWN_LOCATION, "from %s to %s",
+		sm_inst->to_str (m_sm).c_str (),
+		next_sm_inst.to_str (m_sm).c_str ());
+
+      gcc_assert (dest->m_returning_call);
+      if (verbosity >= 1)
+	next.m_path.add_event
+	  (dest->m_returning_call->location,
+	   dest->m_fun->decl, // FIXME: or node?
+	   next.get_stack_depth (),
+	   "returning from %qD to %qD (supernode %i -> %i)",
+	   node->m_fun->decl,
+	   dest->m_fun->decl,
+	   node->m_index,
+	   dest->m_index);
+      dfs_traversal (dest, next, &next_sm_inst, visited);
+    }
+  else
+    {
+      superedge *succ;
+      FOR_EACH_VEC_ELT (node->m_succs, i, succ)
+	{
+	  // Don't revisit BBs we've already seen
+	  if (bitmap_bit_p (visited, succ->m_dest->m_index))
+	    {
+	      // FIXME: disabled for now
+	      // TODO: generalize this to take into account state traversals:
+	      // FIXME: we may want to do it based on edges, rather than nodes:
+	      // consider multiple edges in a switch statement, which only
+	      // differ via phi nodes (see demo-2.c)
+#if 0
+	      if (DEBUG)
+		inform (UNKNOWN_LOCATION,
+			"DBG: already visited supernode %i; skipping",
+			succ->m_dest->m_index);
+	      continue;
+#endif
+	    }
+
+	  /* Recurse, taking a copy of the path and backtrace.  */
+	  sm_instance_1 next_sm_inst (*sm_inst);
+	  old_path_state next (state);
+
+	  switch (succ->m_kind)
+	    {
+	    default:
+	      gcc_unreachable ();
+	    case SUPEREDGE_CFG_EDGE:
+	      if (verbosity >= 2)
+		{
+		  char *edge_desc = succ->get_description ();
+		  if (strlen (edge_desc) > 0)
+		    {
+		      char *wrapped = concat ("(", edge_desc, ") ", NULL);
+		      free (edge_desc);
+		      edge_desc = wrapped;
+		    }
+		  next.m_path.add_event
+		    (node->get_end_location (),
+		     node->m_fun->decl,
+		     state.get_stack_depth (),		     
+		     "taking CFG edge %s%i->%i within %qD (supernode %i -> %i)",
+		     edge_desc,
+		     node->m_bb->index,
+		     succ->m_dest->m_bb->index,
+		     node->m_fun->decl,
+		     node->m_index,
+		     succ->m_dest->m_index);
+		  free (edge_desc);
+		}
+	      break;
+	    case SUPEREDGE_CALL:
+	      {
+		// TODO: update next_sm_inst to reflect params
+		sm_instance_1 call_sm_inst;
+
+		/* Record where we're returning to (and update the
+		   stack depth of "next").  */
+		next.m_backtrace.safe_push (succ->get_callgraph_edge ());
+
+		tree callee = succ->m_dest->m_fun->decl;
+		gcall *call = node->get_final_call ();
+
+		int i = 0;
+		for (tree parm = DECL_ARGUMENTS (callee); parm;
+		     parm = DECL_CHAIN (parm), ++i)
+		  {
+		    tree arg = gimple_call_arg (call, i);
+		    if (DEBUG)
+		      inform (call->location, "arg[%i]: %qE parm[%i]: %qD",
+			      i, arg, i, parm);
+		    /* Use: parm, arg, next_sm_inst, to map from args in caller
+		       to parms in callee */
+		    for (hash_set<var_state_1_t>::iterator iter
+			   = next_sm_inst.m_active_vars.begin ();
+			 iter != next_sm_inst.m_active_vars.end ();
+			 ++iter)
+		      {
+			if ((*iter).m_var == arg)
+			  {
+			    call_sm_inst.m_active_vars.add
+			      (var_state_1_t (parm, (*iter).m_state));
+			  }
+		      }
+		    /* Associate the parm in the new stack frame with
+		       the arg in the old stack frame in the constraints
+		       of the next state.  */
+		    next.m_constraints.on_assignment
+		      (old_path_var (parm, next.get_stack_depth ()),
+		       old_path_var (arg, state.get_stack_depth ()));
+		  }
+		if (DEBUG)
+		  inform (UNKNOWN_LOCATION, "from %s to %s",
+			  next_sm_inst.to_str (m_sm).c_str (),
+			  call_sm_inst.to_str (m_sm).c_str ());
+#if 0
+		// FIXME: seeing crashes with this
+		// hash_set appears not to have a copy-ctor...
+		next_sm_inst = call_sm_inst;
+#endif
+
+		if (verbosity >= 0)
+		  next.m_path.add_event
+		    (node->get_end_location (),
+		     node->m_fun->decl, // FIXME: or dest?
+		     state.get_stack_depth (),
+		     "calling from %qD to %qD (supernode %i -> %i)",
+		     node->m_fun->decl,
+		     succ->m_dest->m_fun->decl,
+		     node->m_index,
+		     succ->m_dest->m_index);
+	      }
+	      break;
+	    case SUPEREDGE_RETURN:
+	      // This should be covered above:
+	      gcc_unreachable ();
+	      break;
+	    case SUPEREDGE_INTRAPROCEDURAL_CALL:
+	      /* Skip these.  */
+	      continue;
+#if 0
+	      next.m_path.add_event
+		(UNKNOWN_LOCATION,
+		 node->m_fun->decl,
+		 "intraprocedural edge at call within %qD from supernode %i to supernode %i",
+		 node->m_fun->decl,
+		 node->m_index,
+		 succ->m_dest->m_index);
+	      // FIXME: do we actually want to do this?
+	      break;
+#endif
+	    }
+
+	  dfs_traversal (succ->m_dest, next,
+			 &next_sm_inst, visited);
+	}
+    }
+}
+
+void
+path_finder::on_stmt (const supernode *node,
+		      old_path_state &state,
+		      sm_instance_1 *sm_inst,
+		      const gimple *stmt)
+{
+  if (DEBUG)
+    {
+      fprintf (stderr, "DBG: ");
+      print_gimple_stmt (stderr, const_cast <gimple *> (stmt), // FIXME<<<
+			 0, (dump_flags_t)0);
+    }
+
+  // FIXME: this is for debugging; which stmts do we want to log in the path?
+  {
+    pretty_printer pp;
+    pp_gimple_stmt_1 (&pp, const_cast <gimple *> (stmt), 0, (dump_flags_t)0);
+    if (verbosity >= 1)
+      state.m_path.add_event (stmt->location, node->m_fun->decl,
+			      state.get_stack_depth (),
+			      "on_stmt: %qs %s",
+			      pp_formatted_text (&pp),
+			      sm_inst->to_str (m_sm).c_str ());
+  }
+
+  foo_sm_context sm_ctxt (m_sm, *this, state, sm_inst);
+  m_sm.on_stmt (&sm_ctxt, node, stmt);
+
+  if (const gassign *assign = dyn_cast <const gassign *> (stmt))
+    {
+      tree lhs = gimple_assign_lhs (assign);
+      tree rhs = gimple_assign_rhs1 (assign);
+      // FIXME: what about other kinds of assignment?
+      if (DEBUG)
+	inform (stmt->location, "got assignment: %qE <- %qE", lhs, rhs);
+
+      for (hash_set<var_state_1_t>::iterator iter = sm_inst->m_active_vars.begin ();
+	   iter != sm_inst->m_active_vars.end ();
+	   ++iter)
+	{
+	  if ((*iter).m_var == rhs)
+	    sm_inst->m_active_vars.add (var_state_1_t (lhs, (*iter).m_state));
+	}
+    }
+
+  // FIXME: linker issues with helper for "const greturn *"
+  if (greturn *return_ = dyn_cast <greturn *> (const_cast <gimple *> (stmt)))
+    {
+      tree retval = gimple_return_retval (return_);
+      if (retval)
+	{
+	  if (DEBUG)
+	    inform (stmt->location, "got return: %qE", retval);
+	  tree callee = node->m_fun->decl;
+	  tree result = DECL_RESULT (callee);
+	  for (hash_set<var_state_1_t>::iterator iter = sm_inst->m_active_vars.begin ();
+	       iter != sm_inst->m_active_vars.end ();
+	       ++iter)
+	    {
+	      if ((*iter).m_var == retval)
+		sm_inst->m_active_vars.add (var_state_1_t (result, (*iter).m_state));
+	    }
+	}
+    }
+}
+
+class foo_annotator : public dot_annotator
+{
+public:
+  void add_node_annotations (pretty_printer *pp,
+			     const supernode &n) const FINAL OVERRIDE
+  {
+    pp_printf (pp, "annotation_for_node_%i", n.m_index);
+    pp_printf (pp, " [shape=%s,style=filled,fillcolor=%s,label=\"",
+	       "record", "lightblue");
+    pp_left_brace (pp);
+    pp_write_text_to_stream (pp);
+    pp_right_brace (pp);
+    pp_string (pp, "\"];\n\n");
+    pp_flush (pp);
+  }
+};
+
+/* FIXME: toy implementation */
+
+static void
+toy_impl (const state_machine &sm, function *)
+{
+#if 1
+  supergraph sg;
+  sg.dump_dot ("test.dot");
+
+  context ctxt;
+  path_finder pf (&sg, sm, &ctxt);
+
+  cgraph_node *node;
+  FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
+  {
+    function *fun = node->get_fun ();
+
+    old_path_state init_state;
+    auto_bitmap visited;
+    sm_instance_1 sm_inst;
+    pf.dfs_traversal (sg.get_node_for_function_entry (fun),
+		      init_state, &sm_inst, visited);
+  }
+
+  inform (UNKNOWN_LOCATION,
+	  "num traversal calls: %i; max recursion depth: %i",
+	  pf.m_traversals, pf.m_max_recursion_depth);
+
+  foo_annotator a;
+  sg.dump_dot ("test-2.dot", &a);
+#else
+  gimple_stmt_iterator gsi;
+  basic_block bb;
+
+  auto_vec<basic_block> worklist;
+  FOR_EACH_BB_FN (bb, fun)
+    worklist.safe_push (bb);
+
+  /* Process the worklist.  */
+  while (!worklist.is_empty ())
+    {
+      basic_block bb = worklist.pop ();
+
+      for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
+	{
+	  gimple *stmt = gsi_stmt (gsi);
+	  print_gimple_stmt (stdout, stmt, 0, (dump_flags_t)0);
+	  sm.on_stmt (stmt);
+	}
+    }
+#endif
+}
+
+void
+toy_impl_1 (function *fn)
+{
+  malloc_state_machine sm;
+
+  toy_impl (sm, fn);
+}
diff --git a/gcc/analyzer/dead-code/toy-sm-impl-1.h b/gcc/analyzer/dead-code/toy-sm-impl-1.h
new file mode 100644
index 0000000..f017b84
--- /dev/null
+++ b/gcc/analyzer/dead-code/toy-sm-impl-1.h
@@ -0,0 +1 @@
+extern void toy_impl_1 (function *fn);
-- 
1.8.5.3

