From 357e191a5573a7f78f2d733a6be8a947f4ab8def Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Mon, 8 Apr 2019 17:23:39 -0400
Subject: [PATCH 160/161] FIXME: initial WIP on impl 3

FIXME: impl-3: start on_stmt

FIXME: impl-3: start wiring up state transitions

FIXME: impl-3: enough state transitions for malloc-ipa-8-double-free.c to work (kind-of)

FIXME: impl-3: emit correct wording, albeit without path for now
FIXME: impl-3: initial impl of adding events to path

FIXME: impl-3: initial impl of path-pruning

FIXME: impl-3: initial work on termination

FIXME: impl-3: more stats
---
 gcc/analyzer/Make-lang.in     |    1 +
 gcc/analyzer/analyzer-pass.cc |   11 +-
 gcc/analyzer/constraints.cc   |   25 +-
 gcc/analyzer/constraints.h    |   16 +
 gcc/analyzer/engine.cc        |    2 +-
 gcc/analyzer/engine.h         |    3 +-
 gcc/analyzer/impl-3.cc        | 1637 +++++++++++++++++++++++++++++++++++++++++
 gcc/analyzer/supergraph.cc    |   10 +
 gcc/analyzer/supergraph.h     |    2 +
 gcc/common.opt                |    4 +
 10 files changed, 1706 insertions(+), 5 deletions(-)
 create mode 100644 gcc/analyzer/impl-3.cc

diff --git a/gcc/analyzer/Make-lang.in b/gcc/analyzer/Make-lang.in
index b7c738d..95c14d8 100644
--- a/gcc/analyzer/Make-lang.in
+++ b/gcc/analyzer/Make-lang.in
@@ -68,6 +68,7 @@ ANALYZER_PLUGIN_SO_OBJS = \
 	analyzer/hardcoded-state-machine-pattern-test.o \
 	analyzer/hardcoded-state-machine-sensitive.o \
 	analyzer/hardcoded-state-machine-taint.o \
+	analyzer/impl-3.o \
 	analyzer/supergraph.o \
 	analyzer/tristate.o \
 
diff --git a/gcc/analyzer/analyzer-pass.cc b/gcc/analyzer/analyzer-pass.cc
index cc60e5f..8417ef1 100644
--- a/gcc/analyzer/analyzer-pass.cc
+++ b/gcc/analyzer/analyzer-pass.cc
@@ -82,7 +82,16 @@ public:
 unsigned int
 pass_analyzer::execute (function *)
 {
-  run_checkers ();
+  switch (flag_analyzer_impl)
+    {
+    default:
+    case 2:
+      run_checkers_impl_2 ();
+      break;
+    case 3:
+      run_checkers_impl_3 ();
+      break;
+    }
   return 0;
 }
 
diff --git a/gcc/analyzer/constraints.cc b/gcc/analyzer/constraints.cc
index 1fd3792..5750c17 100644
--- a/gcc/analyzer/constraints.cc
+++ b/gcc/analyzer/constraints.cc
@@ -58,7 +58,8 @@ path_var::to_str () const
 
 /* FIXME.  */
 
-equiv_class::equiv_class () : m_constant (NULL_TREE), m_vars (), m_escaped (false)
+equiv_class::equiv_class ()
+: m_constant (NULL_TREE), m_vars (), m_escaped (false), m_extra_state (NULL)
 {
 }
 
@@ -67,7 +68,8 @@ equiv_class::equiv_class () : m_constant (NULL_TREE), m_vars (), m_escaped (fals
 equiv_class::equiv_class (const equiv_class &other)
 : m_constant (other.m_constant),
   m_vars (),
-  m_escaped (other.m_escaped)
+  m_escaped (other.m_escaped),
+  m_extra_state (other.m_extra_state ? other.m_extra_state->clone () : NULL)
 {
   // FIXME: copy-ctor for auto_vec!
   int i;
@@ -78,6 +80,13 @@ equiv_class::equiv_class (const equiv_class &other)
 
 /* FIXME.  */
 
+equiv_class::~equiv_class ()
+{
+  delete m_extra_state;
+}
+
+/* FIXME.  */
+
 std::string
 equiv_class::to_str () const
 {
@@ -99,6 +108,8 @@ equiv_class::to_str () const
     }
   if (m_escaped)
     result += " (escaped)";
+  if (m_extra_state)
+    result += m_extra_state->to_str ();
   return result + "}";
 }
 
@@ -548,6 +559,16 @@ path_state::add_constraint (equiv_class_id lhs_ec_id,
 	    lhs_ec_obj.m_constant = rhs_ec_obj.m_constant;
 	  }
 
+	/* Merge any extra state.  */
+	extra_ec_state *lhs_extra_state = lhs_ec_obj.m_extra_state;
+	extra_ec_state *rhs_extra_state = rhs_ec_obj.m_extra_state;
+
+	/* How to merge two state objects?  */
+	gcc_assert (!(lhs_extra_state && rhs_extra_state));
+
+	if (lhs_extra_state == NULL && rhs_extra_state)
+	  lhs_ec_obj.m_extra_state = rhs_extra_state->clone ();
+
 	/* Drop rhs equivalence class, overwriting it with the
 	   final ec (which might be the same one).  */
 	equiv_class_id final_ec_id = m_equiv_classes.length () - 1;
diff --git a/gcc/analyzer/constraints.h b/gcc/analyzer/constraints.h
index 0c2e032..b8ef4ed 100644
--- a/gcc/analyzer/constraints.h
+++ b/gcc/analyzer/constraints.h
@@ -79,6 +79,17 @@ public:
   int m_stack_depth; // or -1 for globals?
 };
 
+/* Optional per-engine extra state (to avoid making path_state a
+   template).  */
+
+class extra_ec_state
+{
+ public:
+  virtual ~extra_ec_state () {}
+  virtual extra_ec_state *clone () const = 0;
+  virtual std::string to_str () const = 0;
+};
+
 /* FIXME.  */
 
 class equiv_class
@@ -86,6 +97,7 @@ class equiv_class
 public:
   equiv_class ();
   equiv_class (const equiv_class &other);
+  ~equiv_class ();
 
   std::string to_str () const;
 
@@ -108,6 +120,10 @@ public:
 
   /* Has this value escaped from this function?  */
   bool m_escaped;
+
+  /* Optional per-engine extra state (to avoid making path_state a
+     template).  */
+  extra_ec_state *m_extra_state;
 };
 
 enum constraint_op
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index c6418df..b549fc8 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -3798,7 +3798,7 @@ run_one_checker (const supergraph &sg, const state_machine &sm, logger *logger)
 /* FIXME.  */
 
 void
-run_checkers ()
+run_checkers_impl_2 ()
 {
   //FILE *dump = fopen ("test.out", "w"); // FIXME
   auto_client_timevar tv ("run_checkers");
diff --git a/gcc/analyzer/engine.h b/gcc/analyzer/engine.h
index 6e7d60b..8f0b1f4 100644
--- a/gcc/analyzer/engine.h
+++ b/gcc/analyzer/engine.h
@@ -1 +1,2 @@
-extern void run_checkers ();
+extern void run_checkers_impl_2 ();
+extern void run_checkers_impl_3 ();
diff --git a/gcc/analyzer/impl-3.cc b/gcc/analyzer/impl-3.cc
new file mode 100644
index 0000000..bfdacb5
--- /dev/null
+++ b/gcc/analyzer/impl-3.cc
@@ -0,0 +1,1637 @@
+/* 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 "exploded-graph.h"
+#include "constraints.h"
+
+namespace impl_3 {
+
+////////////////////////////////////////////////////////////////////////////
+
+/* 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;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+class engine;
+
+/* FIXME.  */
+
+class ec3_state : public extra_ec_state
+{
+ public:
+  ec3_state (const state_machine &sm,
+	     state_machine::state_t baseline_state,
+	     state_machine::state_t current_state)
+  : m_sm (sm),
+    m_baseline_state (baseline_state),
+    m_current_state (current_state)
+  {
+  }
+
+  extra_ec_state *clone () const FINAL OVERRIDE
+  {
+    return new ec3_state (m_sm, m_baseline_state, m_current_state);
+  }
+
+  std::string to_str () const FINAL OVERRIDE
+  {
+    return (std::string (" (state='")
+	    + m_sm.get_state_name (m_current_state)
+	    + "')");
+  }
+
+  const state_machine &m_sm;
+
+  /* For use in engine_sm_context::on_transition so that we only apply
+     changes relative to the state of the EC at the start of a set of
+     transitions.  */
+  state_machine::state_t m_baseline_state;
+
+  state_machine::state_t m_current_state;
+
+  // FIXME: alternatively, do these needs to be sets of states, to cope with
+  // merging of ECs???
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* 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 checker3_event : public diagnostic_event
+{
+public:
+  checker3_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 checker3_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 ();
+  }
+
+  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 checker3_event
+{
+public:
+  debug_event (location_t loc, tree fndecl, int depth,
+	      const char *desc)
+  : checker3_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);
+  }
+
+  checker3_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 checker3_event
+{
+public:
+  statement_event (const gimple *stmt, tree fndecl, int depth)
+  : checker3_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)));
+  }
+
+  checker3_event *clone () const FINAL OVERRIDE
+  {
+    return new statement_event (m_stmt, m_fndecl, m_depth);
+  }
+
+  const gimple *m_stmt;
+};
+
+class function_entry_event : public checker3_event
+{
+public:
+  function_entry_event (location_t loc, tree fndecl, int depth)
+  : checker3_event (EK_FUNCTION_ENTRY, loc, fndecl, depth)
+  {
+  }
+
+  label_text get_desc () const FINAL OVERRIDE
+  {
+    return label_text::take (print ("entry to %qE", m_fndecl));
+  }
+
+  checker3_event *clone () const FINAL OVERRIDE
+  {
+    return new function_entry_event (m_loc, m_fndecl, m_depth);
+  }
+
+  // FIXME
+};
+
+class state_change_event : public checker3_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)
+  : checker3_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)));
+      }
+  }
+
+  checker3_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 checker3_event
+{
+public:
+  edge_event (const superedge *edge,
+	      location_t loc, tree fndecl, int depth)
+  : checker3_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 call_superedge& get_call_superedge () const
+  {
+    gcc_assert (m_edge->m_kind != SUPEREDGE_CFG_EDGE);
+    return *m_edge->dyn_cast_call_superedge ();
+  }
+
+  checker3_event *clone () const FINAL OVERRIDE
+  {
+    return new edge_event (m_edge, m_loc, m_fndecl, m_depth);
+  }
+
+  const superedge *m_edge;
+};
+
+/* FIXME: a repeat of the warning message at the end of the path.  */
+
+class warning_event : public checker3_event
+{
+public:
+  warning_event (location_t loc, tree fndecl, int depth,
+		 const char *msg)
+  : checker3_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.
+
+   Information on where a path has been so far, both in terms of the
+   supergraph, but also on states of interest.
+
+   This is to ensure termination of the analysis.
+
+   The goal is to terminate early without attempting e.g. to fully unroll
+   loops, but also to maintain some precision if interesting state changes
+   are occurring.
+
+   TODO: what should this be?
+   I'm currently thinking of a map from superedge to records, recording
+   expr->state maps, so that we can see if an expr has been in the state
+   before (whilst allowing for ECs to vary on different iterations, but
+   without being too tied to the expr-to-EC mapping).  */
+
+class terminator
+{
+public:
+  struct per_edge_t
+  {
+    per_edge_t (const superedge *edge)
+    : m_edge (edge), m_count (0)
+    {}
+    per_edge_t (const per_edge_t &other)
+    : m_edge (other.m_edge), m_count (other.m_count)
+    {}
+
+    std::string to_str () const
+    {
+      return (std::string ("(")
+	      + m_edge->to_str ()
+	      + ": "
+	      + std::to_string (m_count)
+	      + ")");
+    }
+
+    const superedge *m_edge;
+    // map from expr to set of states we've seen that expr in
+    // or should it be path_var?
+    // FIXME: for now, just a count of how many times we've followed this edge
+    int m_count;
+  };
+
+  terminator ();
+  terminator (const terminator &other);
+  terminator & operator= (const terminator &other);
+
+  std::string to_str () const;
+
+  void add_edge (const superedge *edge);
+  bool terminate_edge_p (const superedge *edge);
+
+  per_edge_t &get_edge_data (const superedge *edge);
+
+  typedef hash_map <const superedge *, per_edge_t *> edge_map_t;
+  edge_map_t m_per_edge_data;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+class checker3_path : public diagnostic_path
+{
+public:
+  struct link
+  {
+    link (const superedge *edge, const supernode *node)
+    : m_edge (edge), m_node (node)
+    {}
+
+    const superedge *m_edge;
+    const supernode *m_node;
+  };
+
+  checker3_path (engine *engine)
+  : diagnostic_path (),
+    m_engine (engine),
+    m_links (),
+    m_active_calls (),
+    m_ps (),
+    m_terminator ()
+  {}
+  checker3_path (const checker3_path &other);
+  checker3_path& operator= (const checker3_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 (checker3_event *event)
+  {
+    m_events.safe_push (event);
+  }
+
+  void delete_event (int idx)
+  {
+    checker3_event *event = m_events[idx];
+    m_events.ordered_remove (idx);
+    delete event;
+  }
+
+  const supernode *get_end_node () const
+  {
+    gcc_assert (m_links.length () > 0);
+    return m_links[m_links.length () - 1].m_node;
+  }
+
+  /* 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;
+
+  void add_final_event (const supernode *node, 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, get_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,
+		     get_sm ().get_state_name (state));
+	// FIXME: ideally would show the full message here, too?
+      }
+    checker3_event *end_of_path
+      = new warning_event (stmt->location,
+			   node->m_fun->decl, get_stack_depth (),
+			   msg);
+    add_event (end_of_path);
+  }
+
+  void prepare_for_emission (pending_diagnostic *pd)
+  {
+    checker3_event *e;
+    int i;
+    FOR_EACH_VEC_ELT (m_events, i, e)
+      e->prepare_for_emission (pd, event_id_t (i));
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+
+  engine *m_engine;
+
+  /* The events that have occurred along this path.  */
+  auto_delete_vec<checker3_event> m_events;
+
+  /* A trace of nodes and edges that the path has followed.  */
+  auto_vec <link> m_links;
+
+  /* The outstanding calls, so that we can find where we should
+     return to.  */
+  auto_vec <cgraph_edge *> m_active_calls;
+
+  /* The current state of memory.  */
+  path_state m_ps;
+
+  terminator m_terminator;
+
+  /* FIXME: map from EC to state (or subclass path_state?)
+     No, store them in EC's m_extra_state.  */
+
+  /* TODO: add additional things, like edge counts, etc
+     (as an optimization; these could be derived by walking
+     the events).  */
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* 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)
+  {
+  }
+
+  void traverse (checker3_path path);
+  void on_stmt (checker3_path *path, const gimple *stmt);
+
+  void prune_path (checker3_path *path,
+		   tree var, state_machine::state_t state);
+
+  void log_stats () const;
+
+  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;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+class engine_sm_context : public sm_context
+{
+public:
+  engine_sm_context (engine *e, checker3_path *path)
+  : m_engine (e), m_path (path)
+  {
+    reset_baseline_states ();
+  }
+
+  ~engine_sm_context ()
+  {
+    reset_baseline_states ();
+  }
+
+  void reset_baseline_states ()
+  {
+    int i;
+    equiv_class *ec;
+    FOR_EACH_VEC_ELT (m_path->m_ps.m_equiv_classes, i, ec)
+      {
+	ec3_state *extra_state = (ec3_state *)ec->m_extra_state;
+	if (extra_state)
+	  extra_state->m_baseline_state = extra_state->m_current_state;
+      }
+  }
+
+  /* FIXME:  The SM can call this multiple times on one statement, e.g.
+     start -> free
+     free: warn_for_state
+     free -> stop.
+
+     We're interested in the states relative to when the sm_context
+     was created.  */
+
+  void on_transition (const supernode *node, const gimple *stmt,
+		      tree var,
+		      state_machine::state_t from,
+		      state_machine::state_t to,
+		      tree origin) FINAL OVERRIDE
+  {
+    JIT_LOG_FUNC_3 (m_engine->get_logger (),
+		    "var: %qE from: %s to: %s",
+		    var,
+		    m_engine->m_sm.get_state_name (from),
+		    m_engine->m_sm.get_state_name (to));
+    state_machine::state_t baseline = m_path->get_baseline_state (var);
+    if (from == baseline)
+      {
+	m_path->set_state (var, baseline, to);
+	checker3_event *event
+	  = new state_change_event (node, stmt, m_path->get_stack_depth (),
+				    m_engine->m_sm, var, from, to, origin);
+	m_path->add_event (event);
+      }
+  }
+
+  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_2 (m_engine->get_logger (),
+		    "if var: %qE in state: %s", var,
+		    m_engine->m_sm.get_state_name (state));
+
+    gcc_assert (d); // take ownership
+
+    state_machine::state_t baseline = m_path->get_baseline_state (var);
+    if (state == baseline)
+      {
+	checker3_path emission_path (*m_path);
+	m_engine->prune_path (&emission_path, var, state);
+	emission_path.add_final_event (node, 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 d;
+  }
+
+  engine *m_engine;
+  checker3_path *m_path;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+terminator::terminator ()
+{
+}
+
+terminator::terminator (const terminator &other)
+{
+  for (edge_map_t::iterator iter = other.m_per_edge_data.begin ();
+       iter != other.m_per_edge_data.end ();
+       ++iter)
+    {
+      m_per_edge_data.put ((*iter).first,
+			   new per_edge_t (*(*iter).second));
+    }
+}
+
+terminator &
+terminator::operator= (const terminator &other)
+{
+  for (edge_map_t::iterator iter = other.m_per_edge_data.begin ();
+       iter != other.m_per_edge_data.end ();
+       ++iter)
+    {
+      m_per_edge_data.put ((*iter).first,
+			   new per_edge_t (*(*iter).second));
+    }
+  return *this;
+}
+
+std::string
+terminator::to_str () const
+{
+  std::string result ("{");
+  for (edge_map_t::iterator iter = m_per_edge_data.begin ();
+       iter != m_per_edge_data.end ();
+       ++iter)
+    {
+      if (iter != m_per_edge_data.begin ())
+	result += ", ";
+      result += (*iter).second->to_str ();
+    }
+  result += "}";
+  return result;
+}
+
+void
+terminator::add_edge (const superedge *edge)
+{
+  per_edge_t &edge_data = get_edge_data (edge);
+  edge_data.m_count++;
+}
+
+bool
+terminator::terminate_edge_p (const superedge *edge)
+{
+  per_edge_t &edge_data = get_edge_data (edge);
+  const int MAX_USES_OF_EDGE_PER_PATH = 1;
+  if (edge_data.m_count >= MAX_USES_OF_EDGE_PER_PATH)
+    return true;
+  return false;
+}
+
+terminator::per_edge_t &
+terminator::get_edge_data (const superedge *edge)
+{
+  if (per_edge_t **ptr_ptr = m_per_edge_data.get (edge))
+    return **ptr_ptr;
+  per_edge_t *data = new per_edge_t (edge);
+  m_per_edge_data.put (edge, data);
+  return *data;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+checker3_path::checker3_path (const checker3_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);
+  }
+  {
+    checker3_event *e;
+    FOR_EACH_VEC_ELT (other.m_events, i, e)
+      m_events.safe_push (e->clone ());
+  }
+  // FIXME: quick push for these?
+}
+
+checker3_path&
+checker3_path::operator= (const checker3_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);
+  }
+  {
+    checker3_event *e;
+    FOR_EACH_VEC_ELT (other.m_events, i, e)
+      m_events.safe_push (e->clone ());
+  }
+  return *this;
+}
+
+/* FIXME.  */
+std::string
+checker3_path::to_str () const
+{
+  std::string result ("[");
+
+  checker3_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 += "]";
+  {
+    result += ", m_links=[";
+    link *link;
+    FOR_EACH_VEC_ELT (m_links, i, link)
+      {
+	if (i > 0)
+	  result += ", ";
+	result += std::to_string (link->m_node->m_index);
+      }
+    result += "]";
+  }
+  {
+    result += ", m_active_calls=[";
+    cgraph_edge *edge;
+    FOR_EACH_VEC_ELT (m_active_calls, i, edge)
+      {
+	if (i > 0)
+	  result += ", ";
+	result += m_engine->m_sg.get_edge_for_call (edge)->to_str ();
+      }
+    result += "]";
+  }
+  result += ", m_ps=" + m_ps.to_str ();
+  result += ", m_terminator=" + m_terminator.to_str ();
+  return result;
+}
+
+/* Record that we've reached NODE, via EDGE.  EDGE may be NULL,
+   to cover entrypoints to the analysis.
+   Update m_active_calls to reflect call and return edges.
+   Update m_terminator.  */
+
+void
+checker3_path::add_link (const superedge *edge, const supernode *node)
+{
+  m_links.safe_push (link (edge, node));
+  if (edge)
+    {
+      switch (edge->m_kind)
+	{
+	default:
+	  break;
+	case SUPEREDGE_CALL:
+	  {
+	    cgraph_edge *cedge = edge->get_callgraph_edge ();
+	    gcc_assert (cedge);
+	    m_active_calls.safe_push (cedge);
+	  }
+	  break;
+	case SUPEREDGE_RETURN:
+	  {
+	    cgraph_edge *top_cedge = m_active_calls.pop ();
+	    cgraph_edge *edge_cedge = edge->get_callgraph_edge ();
+	    gcc_assert (edge_cedge == top_cedge);
+	  }
+	  break;
+	}
+      m_terminator.add_edge (edge);
+    }
+  if (get_verbosity () > 1)
+    {
+      add_event (new debug_event (node->get_start_location (),
+				  node->m_fun->decl,
+				  get_stack_depth (),
+				  print ("SN: %i", node->m_index)));
+    }
+  if (get_verbosity () > 0 && node->entry_p ())
+    {
+      add_event
+	(new function_entry_event (node->get_start_location (),
+				   node->m_fun->decl,
+				   get_stack_depth ()));
+    }
+}
+
+state_machine::state_t
+checker3_path::get_baseline_state (tree expr)
+{
+  equiv_class &ec = get_ec (expr);
+  ec3_state *extra_state = (ec3_state *)ec.m_extra_state;
+  if (extra_state)
+    return extra_state->m_baseline_state;
+  else
+    return 0;  // FIXME "start"
+}
+
+void
+checker3_path::set_state (tree expr,
+			  state_machine::state_t baseline_state,
+			  state_machine::state_t new_state)
+{
+  equiv_class &ec = get_ec (expr);
+  delete ec.m_extra_state;
+  ec.m_extra_state = new ec3_state (get_sm (), baseline_state, new_state);
+  if (m_engine->get_logger ())
+    m_engine->log ("%qE state: %qs -> %qs",
+		   expr,
+		   get_sm ().get_state_name (baseline_state),
+		   get_sm ().get_state_name (new_state));
+}
+
+const supergraph &
+checker3_path::get_sg () const
+{
+  return m_engine->m_sg;
+}
+
+const state_machine &
+checker3_path::get_sm () const
+{
+  return m_engine->m_sm;
+}
+
+int
+checker3_path::get_verbosity () const
+{
+  return m_engine->m_verbosity;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+void
+engine::traverse (checker3_path path)
+{
+  const supernode *node = path.get_end_node ();
+  JIT_LOG_FUNC_1 (get_logger (), "SN: %i", node->m_index);
+  m_num_traverse_calls++;
+
+  if (get_logger ())
+    log ("path: %s", path.to_str ().c_str ());
+
+  // TODO
+
+  /* Phi nodes.  */
+  // FIXME: do these have to somehow happen simultaneously?
+  for (gphi_iterator gpi = const_cast<supernode *>(node)->start_phis ();
+       !gsi_end_p (gpi); gsi_next (&gpi))
+    {
+      gphi *phi = gpi.phi ();
+      // TODO
+    }
+
+  /* Stmts.  */
+  int i;
+  gimple *stmt;
+  FOR_EACH_VEC_ELT (node->m_stmts, i, stmt)
+    {
+      input_location = stmt->location;
+      on_stmt (&path, stmt);
+    }
+
+  /* Traverse into successors of NODE.  */
+  superedge *succ;
+  FOR_EACH_VEC_ELT (node->m_succs, i, succ)
+    {
+#if 1
+      log ("considering SN: %i -> SN: %i",
+	   node->m_index, succ->m_dest->m_index);
+#endif
+
+     switch (succ->m_kind)
+	{
+	default:
+	  gcc_unreachable ();
+	case SUPEREDGE_CFG_EDGE:
+	case SUPEREDGE_CALL:
+	  break;
+	case SUPEREDGE_RETURN:
+	  /* Only follow the correct return edge.  */
+	  if (succ != path.get_next_return_edge ())
+	    {
+	      log ("rejecting return edge");
+	      continue;
+	    }
+	  break;
+	case SUPEREDGE_INTRAPROCEDURAL_CALL:
+	  {
+	    /* Don't traverse intraprocedural edges here.  */
+	    log ("rejecting intraprocedural call edge");
+	    continue;
+	  }
+	}
+
+      if (path.m_terminator.terminate_edge_p (succ))
+	{
+	  log ("rejecting edge; seen too many times");
+	  // FIXME: but presumably we should do this upfront,
+	  // and figure out if we need to do an early-reject
+	  // of any edges; if so, we may need to relax constraints
+	  // to force an early loop exit
+	  continue;
+	}
+
+      checker3_path new_path (path);
+
+      /* For conditionals and switch statements, add the
+	 relevant conditions (for the specific edge) to new_ps;
+	 skip edges for which the resulting constraints
+	 are impossible.  */
+      const gimple *last_stmt = node->get_last_stmt ();
+      if (!new_path.m_ps.apply_constraints_for_edge (*succ, last_stmt))
+	{
+	  log ("skipping impossible edge to SN: %i",
+	       succ->m_dest->m_index);
+	  continue;
+	}
+      // TODO: but if it's a loop, consider skipping early
+
+      // TODO: reject edges that we've already followed
+
+      /* Allow for pattern-matching based on edge constraints.  */
+      //log ("considering edge-based patterns");
+
+      /* We need to clone the backtrace here so if the transition
+	 writes into the path each edge has its own copy.  */
+
+      checker3_event *event
+	= new edge_event (succ,
+			  (last_stmt
+			   ? last_stmt->location
+			   : UNKNOWN_LOCATION),
+			  node->m_fun->decl,
+			  path.get_stack_depth ()); // use old stack depth
+      new_path.add_event (event);
+
+      engine_sm_context ctxt (this, &new_path);
+      m_sm.on_edge (&ctxt, last_stmt, succ);
+
+      new_path.add_link (succ, succ->m_dest);
+      traverse (new_path);
+    }
+}
+
+/* FIXME.  */
+
+void
+engine::on_stmt (checker3_path *path, const gimple *stmt)
+{
+  JIT_LOG_FUNC (get_logger ());
+  if (get_logger ())
+    {
+      log ("path: %s", path->to_str ().c_str ());
+      pretty_printer pp;
+      pp_gimple_stmt_1 (&pp, const_cast <gimple *> (stmt) , 0, (dump_flags_t)0);
+      // FIXME: const_cast!
+      log ("stmt: %s", pp_formatted_text (&pp));
+    }
+
+  const supernode *node = path->get_end_node ();
+
+  checker3_event *event
+    = new statement_event (stmt,
+			   node->m_fun->decl,
+			   path->get_stack_depth ());
+  path->add_event (event);
+
+  engine_sm_context ctxt (this, path);
+  m_sm.on_stmt (&ctxt, node, stmt);
+  m_on_stmt_calls++;
+
+  // TODO
+
+  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",
+		path->m_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))
+	{
+	  /* FIXME: crude hack: only do it for the malloc checker, to avoid
+	     getting every message more than once.
+	     Ultimately we want a command-line option for this, or
+	     something (this is really only for testcases though).  */
+	  if (0 == strcmp (m_sm.get_name (), "malloc"))
+	    {
+	      tree t_arg = gimple_call_arg (call, 0);
+	      tristate t
+		= path->m_ps.dump_condition (t_arg, NE_EXPR, integer_zero_node);
+	      warning_at (call->location, 0, "%s", t.as_string ());
+	    }
+	}
+    }
+
+
+}
+
+/* FIXME.  */
+
+void
+engine::prune_path (checker3_path *path,
+		    tree var, state_machine::state_t state ATTRIBUTE_UNUSED)
+{
+  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 ())
+    {
+      checker3_event *base_event = path->m_events[idx];
+      if (get_logger ())
+	log  ("considering event %i, with var: %qE, state: %qs",
+	      idx, var, m_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 call_superedge& call_superedge
+		    = event->get_call_superedge ();
+		  tree caller_var
+		    = call_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 call_superedge& call_superedge
+		    = event->get_call_superedge ();
+		  tree callee_var
+		    = call_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.  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 ())
+	{
+	  checker3_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
+engine::log_stats () const
+{
+  log ("#supernodes: %i", m_sg.num_nodes ());
+  log ("#superedges: %i", m_sg.num_edges ());
+  log ("m_num_traverse_calls: %i", m_num_traverse_calls);
+  log ("m_on_stmt_calls: %i", m_on_stmt_calls);
+  log ("m_num_path_copies: %i", m_num_path_copies);
+  log ("m_num_event_copies: %i", m_num_event_copies);
+
+  if (0)
+    {
+      inform (UNKNOWN_LOCATION, "#supernodes: %i", m_sg.num_nodes ());
+      inform (UNKNOWN_LOCATION, "#superedges: %i", m_sg.num_edges ());
+      inform (UNKNOWN_LOCATION, "m_num_traverse_calls: %i",
+	      m_num_traverse_calls);
+      inform (UNKNOWN_LOCATION, "m_on_stmt_calls: %i", m_on_stmt_calls);
+      inform (UNKNOWN_LOCATION, "m_num_path_copies: %i", m_num_path_copies);
+      inform (UNKNOWN_LOCATION, "m_num_event_copies: %i", m_num_event_copies);
+    }
+}
+
+
+/* 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);
+}
+
+/* FIXME.  */
+/* TODO: should we run them all at once, or serially?
+   For now, this does them serially.  */
+
+void
+run_one_checker (const supergraph &sg, const state_machine &sm, logger *logger)
+{
+  JIT_LOG_FUNC_1 (logger, "sm: %s", sm.get_name ());
+
+  if (flag_analyzer_checker)
+    {
+      if (0 != strcmp (flag_analyzer_checker, sm.get_name ()))
+	{
+	  if (logger)
+	    logger->log ("skipping checker %s (does not match %s)",
+			 sm.get_name (), flag_analyzer_checker);
+	  return;
+	}
+    }
+
+  engine e (sg, sm, logger, analyzer_verbosity);
+
+  cgraph_node *node;
+  auto_vec<cgraph_node *> root_functions;
+  FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
+    root_functions.safe_push (node);
+
+  /* Sort root_functions so that diagnostics are emitted in a
+     sane order.
+     TODO: perhaps we should buffer the diagnostics and sort them later,
+     instead.  */
+  root_functions.qsort (function_comparator);
+
+  int i;
+  FOR_EACH_VEC_ELT (root_functions, i, node)
+    {
+      function *fun = node->get_fun ();
+
+      if (!TREE_PUBLIC (fun->decl))
+	{
+	  if (logger)
+	    logger->log ("not traversing %qE (static)", fun->decl);
+	  continue;
+	}
+
+      log_scope s (logger, "top-level traversal", "%s", function_name (fun));
+
+      checker3_path path (&e);
+      path.add_link (NULL, sg.get_node_for_function_entry (fun));
+      e.traverse (path);
+    }
+
+  e.log_stats ();
+}
+
+} // namespace impl_3
+
+/* FIXME.  */
+
+void
+run_checkers_impl_3 ()
+{
+  //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 ());
+
+    /* Create the supergraph.  */
+    supergraph sg (the_logger.get_logger ());
+
+    if (1)
+      {
+	char *filename = concat (dump_base_name, ".supergraph.dot", NULL);
+	sg.dump_dot (filename, (enum supergraph_dot_flags)0);
+	free (filename);
+      }
+
+#if 0
+    // TODO: selftest for auto_delete_vec
+    auto_delete_vec <state_machine> checkers;
+    make_checkers (checkers, the_logger.get_logger ());
+
+    int i;
+    state_machine *checker;
+    FOR_EACH_VEC_ELT (checkers, i, checker)
+      // FIXME: this requires better timevar ID management
+#endif
+
+    // TODO
+    // For now, just count paths through the supergraph
+
+#if 1
+    {
+      auto_client_timevar tv ("malloc_state_machine");
+      malloc_state_machine sm (the_logger.get_logger ());
+      impl_3::run_one_checker (sg, sm, the_logger.get_logger ());
+    }
+
+    {
+      auto_client_timevar tv ("fileptr_state_machine");
+      fileptr_state_machine sm (the_logger.get_logger ());
+      impl_3::run_one_checker (sg, sm, the_logger.get_logger ());
+    }
+
+    {
+      auto_client_timevar tv ("taint_state_machine");
+      taint_state_machine sm (the_logger.get_logger ());
+      impl_3::run_one_checker (sg, sm, the_logger.get_logger ());
+    }
+
+    {
+      auto_client_timevar tv ("sensitive_state_machine");
+      sensitive_state_machine sm (the_logger.get_logger ());
+      impl_3::run_one_checker (sg, sm, the_logger.get_logger ());
+    }
+
+    /* We only attempt to run the pattern tests if it might have been manually
+       enabled (for DejaGnu purposes).  */
+    if (flag_analyzer_checker)
+      {
+	auto_client_timevar tv ("pattern_test_state_machine");
+	pattern_test_state_machine sm (the_logger.get_logger ());
+	impl_3::run_one_checker (sg, sm, the_logger.get_logger ());
+      }
+#endif
+
+    /* end of lifetime of the_logger (so that dump file is closed after the
+       various dtors run).  */
+  }
+
+  //fclose (dump);
+}
diff --git a/gcc/analyzer/supergraph.cc b/gcc/analyzer/supergraph.cc
index 860d704..01f1a9d 100644
--- a/gcc/analyzer/supergraph.cc
+++ b/gcc/analyzer/supergraph.cc
@@ -682,6 +682,16 @@ superedge::get_description () const
 
 /* FIXME.  */
 
+std::string
+superedge::to_str () const
+{
+  return (std::to_string (m_src->m_index)
+	  + " -> "
+	  + std::to_string (m_dest->m_index));
+}
+
+/* FIXME.  */
+
 void
 cfg_superedge::dump_label_to_pp (pretty_printer *pp) const
 {
diff --git a/gcc/analyzer/supergraph.h b/gcc/analyzer/supergraph.h
index c68411f..85b762e 100644
--- a/gcc/analyzer/supergraph.h
+++ b/gcc/analyzer/supergraph.h
@@ -242,6 +242,8 @@ class superedge
 
   char *get_description () const;
 
+  std::string to_str () const;
+
  protected:
   superedge (supernode *src, supernode *dest, enum edge_kind kind)
     : m_src (src), m_dest (dest), m_kind (kind)
diff --git a/gcc/common.opt b/gcc/common.opt
index d5ccbe1..c59063d 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -996,6 +996,10 @@ fanalyzer-checker=
 Common Joined RejectNegative Var(flag_analyzer_checker)
 FIXME
 
+fanalyzer-impl=
+Common Joined RejectNegative UInteger Var(flag_analyzer_impl)
+FIXME
+
 fargument-alias
 Common Ignore
 Does nothing. Preserved for backward compatibility.
-- 
1.8.5.3

