From af40c7503e6f839f798cc5d242710866752b79fc Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Fri, 21 Jun 2019 13:07:30 -0400
Subject: [PATCH 40/47] FIXME: rename 'hardcoded-state-machine' to 'sm'

---
 gcc/analyzer/Make-lang.in                          |  12 +-
 gcc/analyzer/hardcoded-state-machine-file.cc       | 220 -------
 gcc/analyzer/hardcoded-state-machine-malloc.cc     | 710 ---------------------
 .../hardcoded-state-machine-pattern-test.cc        | 151 -----
 gcc/analyzer/hardcoded-state-machine-sensitive.cc  | 153 -----
 gcc/analyzer/hardcoded-state-machine-taint.cc      | 234 -------
 gcc/analyzer/hardcoded-state-machine.cc            | 169 -----
 gcc/analyzer/hardcoded-state-machine.h             | 554 ----------------
 gcc/analyzer/impl-2.cc                             |   2 +-
 gcc/analyzer/impl-3.cc                             |   2 +-
 gcc/analyzer/impl-4.cc                             |   2 +-
 gcc/analyzer/region-model.cc                       |   2 +-
 gcc/analyzer/region-model.h                        |   2 +-
 gcc/analyzer/sm-file.cc                            | 220 +++++++
 gcc/analyzer/sm-malloc.cc                          | 710 +++++++++++++++++++++
 gcc/analyzer/sm-pattern-test.cc                    | 151 +++++
 gcc/analyzer/sm-sensitive.cc                       | 153 +++++
 gcc/analyzer/sm-taint.cc                           | 234 +++++++
 gcc/analyzer/sm.cc                                 | 169 +++++
 gcc/analyzer/sm.h                                  | 554 ++++++++++++++++
 20 files changed, 2202 insertions(+), 2202 deletions(-)
 delete mode 100644 gcc/analyzer/hardcoded-state-machine-file.cc
 delete mode 100644 gcc/analyzer/hardcoded-state-machine-malloc.cc
 delete mode 100644 gcc/analyzer/hardcoded-state-machine-pattern-test.cc
 delete mode 100644 gcc/analyzer/hardcoded-state-machine-sensitive.cc
 delete mode 100644 gcc/analyzer/hardcoded-state-machine-taint.cc
 delete mode 100644 gcc/analyzer/hardcoded-state-machine.cc
 delete mode 100644 gcc/analyzer/hardcoded-state-machine.h
 create mode 100644 gcc/analyzer/sm-file.cc
 create mode 100644 gcc/analyzer/sm-malloc.cc
 create mode 100644 gcc/analyzer/sm-pattern-test.cc
 create mode 100644 gcc/analyzer/sm-sensitive.cc
 create mode 100644 gcc/analyzer/sm-taint.cc
 create mode 100644 gcc/analyzer/sm.cc
 create mode 100644 gcc/analyzer/sm.h

diff --git a/gcc/analyzer/Make-lang.in b/gcc/analyzer/Make-lang.in
index 985993e..e1497c3 100644
--- a/gcc/analyzer/Make-lang.in
+++ b/gcc/analyzer/Make-lang.in
@@ -60,17 +60,17 @@ ANALYZER_PLUGIN_SO_OBJS = \
 	analyzer/constraint-manager.o \
 	analyzer/digraph.o \
 	analyzer/graphviz.o \
-	analyzer/hardcoded-state-machine.o \
-	analyzer/hardcoded-state-machine-malloc.o \
-	analyzer/hardcoded-state-machine-file.o \
-	analyzer/hardcoded-state-machine-pattern-test.o \
-	analyzer/hardcoded-state-machine-sensitive.o \
-	analyzer/hardcoded-state-machine-taint.o \
 	analyzer/impl-2.o \
 	analyzer/impl-3.o \
 	analyzer/impl-4.o \
 	analyzer/path-state.o \
 	analyzer/region-model.o \
+	analyzer/sm.o \
+	analyzer/sm-file.o \
+	analyzer/sm-malloc.o \
+	analyzer/sm-pattern-test.o \
+	analyzer/sm-sensitive.o \
+	analyzer/sm-taint.o \
 	analyzer/supergraph.o \
 	analyzer/tristate.o \
 
diff --git a/gcc/analyzer/hardcoded-state-machine-file.cc b/gcc/analyzer/hardcoded-state-machine-file.cc
deleted file mode 100644
index 17eaf5f..0000000
--- a/gcc/analyzer/hardcoded-state-machine-file.cc
+++ /dev/null
@@ -1,220 +0,0 @@
-/* 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 "hardcoded-state-machine.h"
-
-static const bool DEBUG = false;
-//static const bool DEBUG = true;
-
-////////////////////////////////////////////////////////////////////////////
-
-class double_fclose : public pending_diagnostic_subclass<double_fclose>
-{
-public:
-  double_fclose (tree arg) : m_arg (arg) {}
-
-  const char *get_kind () const FINAL OVERRIDE { return "double_fclose"; }
-  bool operator== (const double_fclose &other) const
-  {
-    return m_arg == other.m_arg;
-  }
-
-  void emit (rich_location *rich_loc) FINAL OVERRIDE
-  {
-    warning_at (rich_loc, OPT_Wanalyzer_double_fclose,
-		"double %<fclose%> of FILE %qE",
-		m_arg);
-  }
-
-private:
-  tree m_arg;
-};
-
-class file_leak : public pending_diagnostic_subclass<file_leak>
-{
-public:
-  file_leak (tree arg) : m_arg (arg) {}
-
-  const char *get_kind () const FINAL OVERRIDE { return "file_leak"; }
-  bool operator== (const file_leak &other) const
-  {
-    return m_arg == other.m_arg;
-  }
-
-  void emit (rich_location *rich_loc) FINAL OVERRIDE
-  {
-    metadata m;
-    /* CWE-775: "Missing Release of File Descriptor or Handle after
-       Effective Lifetime". */
-    m.add_cwe (775);
-    warning_at (rich_loc, m, OPT_Wanalyzer_file_leak,
-		"leak of FILE %qE",
-		m_arg);
-  }
-
-private:
-  tree m_arg;
-};
-
-////////////////////////////////////////////////////////////////////////////
-
-fileptr_state_machine::fileptr_state_machine (logger *logger)
-: state_machine ("file", logger)
-{
-  // TODO: parse all of this from a file:
-  m_start = add_state ("start");
-  m_unchecked = add_state ("unchecked");
-  m_null = add_state ("null");
-  m_nonnull = add_state ("nonnull");
-  m_closed = add_state ("closed");
-  m_stop = add_state ("stop");
-}
-
-bool
-fileptr_state_machine::on_stmt (sm_context *sm_ctxt,
-				const supernode *node,
-				const gimple *stmt) const
-{
-  //tree fndecl = node->m_fun->decl;
-
-  // FIXME: do all of this from data
-  if (const gcall *call = dyn_cast <const gcall *> (stmt))
-    {
-      if (is_named_call_p (call, "fopen", 2))
-	{
-	  if (DEBUG)
-	    inform (stmt->location, "DBG: got fopen");
-	  tree lhs = gimple_call_lhs (call);
-	  if (lhs)
-	    {
-	      lhs = sm_ctxt->get_readable_tree (lhs);
-	      sm_ctxt->on_transition (node, stmt, lhs, m_start, m_unchecked);
-	    }
-	  else
-	    {
-	      /* TODO: report leak.  */
-	    }
-	  return true;
-	}
-
-      if (is_named_call_p (call, "fclose", 1))
-	{
-	  tree arg = gimple_call_arg (call, 0);
-	  if (DEBUG)
-	    inform (stmt->location, "DBG: got fclose");
-	  arg = sm_ctxt->get_readable_tree (arg);
-
-	  sm_ctxt->on_transition (node, stmt, arg, m_start, m_closed);
-
-	  // TODO: is it safe to call fclose (NULL) ?
-	  sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_closed);
-	  sm_ctxt->on_transition (node, stmt, arg, m_null, m_closed);
-
-	  sm_ctxt->on_transition (node, stmt , arg, m_nonnull, m_closed);
-
-	  sm_ctxt->warn_for_state (node, stmt, arg, m_closed,
-				   new double_fclose (arg));
-	  sm_ctxt->on_transition (node, stmt, arg, m_closed, m_stop);
-	  return true;
-	}
-
-      // TODO: operations on closed file
-      // etc
-    }
-  return false;
-}
-
-/* FIXME.  */
-
-void
-fileptr_state_machine::on_edge (sm_context *sm_ctxt,
-				const gimple *last_stmt,
-				const superedge *edge) const
-{
-  if (last_stmt == NULL)
-    return;
-
-  if (const gcond *cond_stmt = dyn_cast <const gcond *> (last_stmt))
-    {
-      tree arg;
-      enum tree_code op;
-      if (is_comparison_against_zero (cond_stmt, &arg, &op))
-	{
-	  // FIXME: has to be a FILE *, specifically
-	  if (TREE_CODE (TREE_TYPE (arg)) != POINTER_TYPE)
-	    return;
-
-	  ::edge cfg_edge = edge->get_any_cfg_edge ();
-	  gcc_assert (cfg_edge != NULL);
-	  gcc_assert (cfg_edge->flags & (EDGE_TRUE_VALUE | EDGE_FALSE_VALUE));
-	  
-	  if (op == NE_EXPR)
-	    {
-	      log ("got 'ARG != 0' match");
-	      sm_ctxt->on_transition (edge->m_src, last_stmt, arg, m_unchecked,
-				      (cfg_edge->flags & EDGE_TRUE_VALUE
-				       ? m_nonnull : m_null));
-	    }
-	  else if (op == EQ_EXPR)
-	    {
-	      log ("got 'ARG == 0' match");
-	      sm_ctxt->on_transition (edge->m_src, last_stmt, arg, m_unchecked,
-				      (cfg_edge->flags & EDGE_TRUE_VALUE
-				       ? m_null : m_nonnull));
-	    }
-	}
-    }
-
-  // TODO: something similar for switch statements?
-}
-
-void
-fileptr_state_machine::on_leak (sm_context *sm_ctxt,
-				const supernode *node,
-				const gimple *stmt,
-				tree var,
-				state_machine::state_t state) const
-{
-  sm_ctxt->warn_for_state (node, stmt, var, m_unchecked,
-			   new file_leak (var));
-  sm_ctxt->warn_for_state (node, stmt, var, m_nonnull,
-			   new file_leak (var));
-}
-
-bool
-fileptr_state_machine::can_purge_p (state_t s) const
-{
-  return s != m_unchecked && s != m_nonnull;
-}
diff --git a/gcc/analyzer/hardcoded-state-machine-malloc.cc b/gcc/analyzer/hardcoded-state-machine-malloc.cc
deleted file mode 100644
index 27eb044..0000000
--- a/gcc/analyzer/hardcoded-state-machine-malloc.cc
+++ /dev/null
@@ -1,710 +0,0 @@
-/* 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 "hardcoded-state-machine.h"
-
-static const bool DEBUG = false;
-//static const bool DEBUG = true;
-
-////////////////////////////////////////////////////////////////////////////
-
-class malloc_diagnostic : public pending_diagnostic
-{
-public:
-  malloc_diagnostic (const malloc_state_machine &sm, tree arg)
-  : m_sm (sm), m_arg (arg)
-  {}
-
-  bool subclass_equal_p (const pending_diagnostic &base_other) const OVERRIDE
-  {
-    return m_arg == ((const malloc_diagnostic &)base_other).m_arg;
-  }
-
-protected:
-  const malloc_state_machine &m_sm;
-  tree m_arg;
-};
-
-class double_free : public malloc_diagnostic
-{
-public:
-  double_free (const malloc_state_machine &sm, tree arg)
-  : malloc_diagnostic (sm, arg)
-  {}
-
-  const char *get_kind () const FINAL OVERRIDE { return "double_free"; }
-
-  void emit (rich_location *rich_loc) FINAL OVERRIDE
-  {
-    auto_diagnostic_group d;
-    metadata m;
-    m.add_cwe (415); /* CWE-415: Double Free.  */
-    warning_at (rich_loc, m, OPT_Wanalyzer_double_free,
-		"double-free of %qE", m_arg);
-
-#if 0
-    if (!rich_loc->get_path ())
-      return;
-
-    event_id_t second_free_event
-      = rich_loc->get_path ()->get_final_event_id ();
-
-    if (m_first_free_event.known_p ()
-	&& second_free_event.known_p ())
-      inform (rich_loc->get_loc (),
-	      "pointer freed at (%i) was already freed at (%i)",
-	      second_free_event.one_based (),
-	      m_first_free_event.one_based ());
-#endif
-  }
-
-  label_text describe (const evdesc::state_change &change)
-    FINAL OVERRIDE
-  {
-    if (change.m_new_state == m_sm.m_free)
-      {
-	m_first_free_event = change.m_event_id;
-	return formatted_print ("first %qs here", "free");
-      }
-    return label_text ();
-  }
-
-  label_text describe (const evdesc::call_with_state &info)
-    FINAL OVERRIDE
-  {
-    if (info.m_state == m_sm.m_free)
-      return formatted_print
-	("passing freed pointer %qE in call to %qE from %qE",
-	 info.m_expr, info.m_callee_fndecl, info.m_caller_fndecl);
-    return label_text ();
-  }
-
-  label_text describe (const evdesc::final_event &) FINAL OVERRIDE
-  {
-    if (m_first_free_event.known_p ())
-      return formatted_print ("second %qs here; first %qs was at (%i)",
-			      "free", "free", m_first_free_event.one_based ());
-    return formatted_print ("second %qs here", "free");
-  }
-
-  /* FIXME: maybe also something like this.  */
-#if 0
-  label_test label_critical_state (int event_idx)
-  //FINAL OVERRIDE
-  {
-    return label_text::take (xvsprintf ("freed at (%i)"));
-  }
-#endif
-
-private:
-  event_id_t m_first_free_event;
-};
-
-/* Abstract subclass for describing possible bad uses of NULL.
-   Responsible for describing the call that could return NULL.  */
-
-class possible_null : public malloc_diagnostic
-{
-public:
-  possible_null (const malloc_state_machine &sm, tree arg)
-  : malloc_diagnostic (sm, arg)
-  {}
-
-  label_text describe (const evdesc::state_change &change)
-    FINAL OVERRIDE
-  {
-    if (change.m_old_state == m_sm.m_start
-	&& change.m_new_state == m_sm.m_unchecked)
-      return label_text::borrow ("this call could return NULL");
-    return label_text ();
-  }
-
-  label_text describe (const evdesc::return_of_state &info)
-    FINAL OVERRIDE
-  {
-    if (info.m_state == m_sm.m_unchecked)
-      return formatted_print ("possible return of NULL to %qE from %qE",
-			      info.m_caller_fndecl, info.m_callee_fndecl);
-    return label_text ();
-  }
-};
-
-/* Concrete subclass for describing dereference of a possible NULL
-   value.  */
-
-class possible_null_deref : public possible_null
-{
-public:
-  possible_null_deref (const malloc_state_machine &sm, tree arg)
-  : possible_null (sm, arg)
-  {}
-
-  const char *get_kind () const FINAL OVERRIDE { return "possible_null_deref"; }
-
-  void emit (rich_location *rich_loc) FINAL OVERRIDE
-  {
-    /* CWE-690: Unchecked Return Value to NULL Pointer Dereference.  */
-    metadata m;
-    m.add_cwe (690);
-    warning_at (rich_loc, m, OPT_Wanalyzer_possible_null_dereference,
-		"use of possibly-NULL %qE", m_arg);
-  }
-
-  /* FIXME.  */
-  label_text describe (const evdesc::final_event &ev) FINAL OVERRIDE
-  {
-    return formatted_print ("%qE could be NULL", ev.m_expr);
-  }
-
-};
-
-/* Subroutine for use by possible_null_arg::emit and null_arg::emit.
-   Issue a note informing that the pertinent argument must be non-NULL.  */
-
-static void
-inform_nonnull_attribute (tree fndecl, int arg_idx)
-{
-  inform (DECL_SOURCE_LOCATION (fndecl),
-	  "argument %u of %qD must be non-null",
-	  arg_idx + 1, fndecl);
-  /* FIXME: ideally, use the location of the parm
-     and underline the attribute also (but we probably don't have the
-     location_t...)  */
-
-  /* C and C++ FEs have get_fndecl_argument_location.  */
-}
-
-/* Concrete subclass for describing passing a possibly-NULL value to a
-   function marked with __attribute__((nonnull)).  */
-
-class possible_null_arg : public possible_null
-{
-public:
-  possible_null_arg (const malloc_state_machine &sm, tree arg,
-		     tree fndecl, int arg_idx)
-  : possible_null (sm, arg),
-    m_fndecl (fndecl), m_arg_idx (arg_idx)
-  {}
-
-  const char *get_kind () const FINAL OVERRIDE { return "possible_null_arg"; }
-
-  bool subclass_equal_p (const pending_diagnostic &base_other) const
-  {
-    const possible_null_arg &sub_other
-      = (const possible_null_arg &)base_other;
-    return (m_arg == sub_other.m_arg
-	    && m_fndecl == sub_other.m_fndecl
-	    && m_arg_idx == sub_other.m_arg_idx);
-  }
-
-
-  void emit (rich_location *rich_loc) FINAL OVERRIDE
-  {
-    /* CWE-690: Unchecked Return Value to NULL Pointer Dereference.  */
-    auto_diagnostic_group d;
-    metadata m;
-    m.add_cwe (690);
-    warning_at (rich_loc, m, OPT_Wanalyzer_possible_null_argument,
-		"use of possibly-NULL %qE where non-null expected",
-		m_arg);
-    inform_nonnull_attribute (m_fndecl, m_arg_idx);
-  }
-
-  /* FIXME.  */
-  label_text describe (const evdesc::final_event &ev) FINAL OVERRIDE
-  {
-    return formatted_print ("argument %u (%qE) could be NULL"
-			    " where non-null expected",
-			    m_arg_idx + 1, ev.m_expr);
-  }
-
-  // TODO
-
-private:
-  tree m_fndecl;
-  int m_arg_idx;
-};
-
-/* Concrete subclass for describing a dereference of a NULL value.  */
-
-class null_deref : public malloc_diagnostic
-{
-public:
-  null_deref (const malloc_state_machine &sm, tree arg)
-  : malloc_diagnostic (sm, arg) {}
-
-  const char *get_kind () const FINAL OVERRIDE { return "null_deref"; }
-
-  void emit (rich_location *rich_loc) FINAL OVERRIDE
-  {
-    /* CWE-690: Unchecked Return Value to NULL Pointer Dereference.  */
-    metadata m;
-    m.add_cwe (690);
-    warning_at (rich_loc, m, OPT_Wanalyzer_null_dereference,
-		"use of NULL %qE", m_arg);
-  }
-};
-
-/* Concrete subclass for describing passing a NULL value to a
-   function marked with __attribute__((nonnull)).  */
-
-class null_arg : public malloc_diagnostic
-{
-public:
-  null_arg (const malloc_state_machine &sm, tree arg,
-	    tree fndecl, int arg_idx)
-  : malloc_diagnostic (sm, arg),
-    m_fndecl (fndecl), m_arg_idx (arg_idx)
-  {}
-
-  const char *get_kind () const FINAL OVERRIDE { return "null_arg"; }
-
-  bool subclass_equal_p (const pending_diagnostic &base_other) const
-  {
-    const null_arg &sub_other
-      = (const null_arg &)base_other;
-    return (m_arg == sub_other.m_arg
-	    && m_fndecl == sub_other.m_fndecl
-	    && m_arg_idx == sub_other.m_arg_idx);
-  }
-
-  void emit (rich_location *rich_loc) FINAL OVERRIDE
-  {
-    /* CWE-690: Unchecked Return Value to NULL Pointer Dereference.  */
-    auto_diagnostic_group d;
-    metadata m;
-    m.add_cwe (690);
-    warning_at (rich_loc, m, OPT_Wanalyzer_null_argument,
-		"use of NULL %qE where non-null expected", m_arg);
-    inform_nonnull_attribute (m_fndecl, m_arg_idx);
-  }
-
-  /* FIXME.  */
-  label_text describe (const evdesc::final_event &ev) FINAL OVERRIDE
-  {
-    return formatted_print ("argument %u (%qE) NULL"
-			    " where non-null expected",
-			    m_arg_idx + 1, ev.m_expr);
-  }
-
-private:
-  tree m_fndecl;
-  int m_arg_idx;
-};
-
-class use_after_free : public malloc_diagnostic
-{
-public:
-  use_after_free (const malloc_state_machine &sm, tree arg)
-  : malloc_diagnostic (sm, arg)
-  {}
-
-  const char *get_kind () const FINAL OVERRIDE { return "use_after_free"; }
-
-  void emit (rich_location *rich_loc) FINAL OVERRIDE
-  {
-    /* CWE-416: Use After Free.  */
-    metadata m;
-    m.add_cwe (416);
-    warning_at (rich_loc, m, OPT_Wanalyzer_use_after_free,
-		"use after free of %qE", m_arg);
-  }
-
-  label_text describe (const evdesc::state_change &change)
-    FINAL OVERRIDE
-  {
-    if (change.m_new_state == m_sm.m_free)
-      return label_text::borrow ("freed here");
-    return label_text ();
-  }
-};
-
-class malloc_leak : public malloc_diagnostic
-{
-public:
-  malloc_leak (const malloc_state_machine &sm, tree arg)
-  : malloc_diagnostic (sm, arg) {}
-
-  const char *get_kind () const FINAL OVERRIDE { return "malloc_leak"; }
-
-  void emit (rich_location *rich_loc) FINAL OVERRIDE
-  {
-    metadata m;
-    m.add_cwe (401);
-    warning_at (rich_loc, m, OPT_Wanalyzer_malloc_leak,
-		"leak of %qE", m_arg);
-  }
-
-  label_text describe (const evdesc::state_change &change)
-    FINAL OVERRIDE
-  {
-    if (change.m_new_state == m_sm.m_unchecked)
-      {
-	m_malloc_event = change.m_event_id;
-	return label_text::borrow ("allocated here");
-      }
-    return label_text ();
-  }
-
-  label_text describe (const evdesc::final_event &ev) FINAL OVERRIDE
-  {
-    if (m_malloc_event.known_p ())
-      return formatted_print ("%qE leaks here; was allocated at (%i)",
-			      ev.m_expr, m_malloc_event.one_based ());
-    return formatted_print ("%qE leaks here", ev.m_expr);
-  }
-
-private:
-  event_id_t m_malloc_event;
-};
-
-class free_of_non_heap : public malloc_diagnostic
-{
-public:
-  free_of_non_heap (const malloc_state_machine &sm, tree arg)
-  : malloc_diagnostic (sm, arg), m_kind (KIND_UNKNOWN)
-  {
-  }
-
-  const char *get_kind () const FINAL OVERRIDE { return "free_of_non_heap"; }
-
-  bool subclass_equal_p (const pending_diagnostic &base_other) const
-    FINAL OVERRIDE
-  {
-    const free_of_non_heap &other = (const free_of_non_heap &)base_other;
-    return (m_arg == other.m_arg && m_kind == other.m_kind);
-  }
-
-  void emit (rich_location *rich_loc) FINAL OVERRIDE
-  {
-    auto_diagnostic_group d;
-    metadata m;
-    m.add_cwe (590); /* CWE-590: Free of Memory not on the Heap.  */
-    switch (m_kind)
-      {
-      default:
-	gcc_unreachable ();
-      case KIND_UNKNOWN:
-	warning_at (rich_loc, m, OPT_Wanalyzer_free_of_non_heap,
-		    "free of %qE which points to memory not on the heap",
-		    m_arg);
-	break;
-      case KIND_ALLOCA:
-	warning_at (rich_loc, m, OPT_Wanalyzer_free_of_non_heap,
-		    "free of memory allocated on the stack by %qs (%qE)"
-		    " will corrupt the heap",
-		    "alloca", m_arg);
-	break;
-      }
-  }
-
-  label_text describe (const evdesc::state_change &change)
-    FINAL OVERRIDE
-  {
-    /* Attempt to reconstruct what kind of pointer it is.
-       (It seems neater for this to be a part of the state, though).  */
-    if (TREE_CODE (change.m_expr) == SSA_NAME)
-      {
-	gimple *def_stmt = SSA_NAME_DEF_STMT (change.m_expr);
-	if (gcall *call = dyn_cast <gcall *> (def_stmt))
-	  {
-	    if (is_named_call_p (call, "alloca", 1)
-		|| is_named_call_p (call, "__builtin_alloca", 1))
-	      {
-		m_kind = KIND_ALLOCA;
-		return label_text::borrow
-		  ("memory is allocated on the stack here");
-	      }
-	  }
-      }
-    return label_text::borrow ("pointer is from here");
-  }
-
-  /* FIXME.  */
-  label_text describe (const evdesc::final_event &) FINAL OVERRIDE
-  {
-    return formatted_print ("call to %qs here", "free");
-  }
-
-private:
-  enum kind
-  {
-    KIND_UNKNOWN,
-    KIND_ALLOCA
-  };
-  enum kind m_kind;
-};
-
-////////////////////////////////////////////////////////////////////////////
-
-malloc_state_machine::malloc_state_machine (logger *logger)
-: state_machine ("malloc", logger)
-{
-  // TODO: parse all of this from a file:
-  m_start = add_state ("start");
-  m_unchecked = add_state ("unchecked");
-  m_null = add_state ("null");
-  m_nonnull = add_state ("nonnull");
-  m_free = add_state ("free");
-  m_non_heap = add_state ("non-heap");
-  m_stop = add_state ("stop");
-}
-
-bool
-malloc_state_machine::on_stmt (sm_context *sm_ctxt,
-			       const supernode *node,
-			       const gimple *stmt) const
-{
-  //tree fndecl = node->m_fun->decl;
-
-  // FIXME: do all of this from data
-  if (const gcall *call = dyn_cast <const gcall *> (stmt))
-    {
-      if (is_named_call_p (call, "malloc", 1)
-	  || is_named_call_p (call, "calloc", 2))
-	{
-	  if (DEBUG)
-	    inform (stmt->location, "DBG: got malloc");
-	  tree lhs = gimple_call_lhs (call);
-	  if (lhs)
-	    {
-	      lhs = sm_ctxt->get_readable_tree (lhs);
-	      sm_ctxt->on_transition (node, stmt, lhs, m_start, m_unchecked);
-	    }
-	  else
-	    {
-	      /* TODO: report leak.  */
-	    }
-	  return true;
-	}
-
-      if (is_named_call_p (call, "alloca", 1)
-	  || is_named_call_p (call, "__builtin_alloca", 1))
-	{
-	  tree lhs = gimple_call_lhs (call);
-	  if (lhs)
-	    {
-	      lhs = sm_ctxt->get_readable_tree (lhs);
-	      sm_ctxt->on_transition (node, stmt, lhs, m_start, m_non_heap);
-	    }
-	  return true;
-	}
-
-      if (is_named_call_p (call, "free", 1))
-	{
-	  tree arg = gimple_call_arg (call, 0);
-	  if (DEBUG)
-	    inform (stmt->location, "DBG: got free");
-
-	  arg = sm_ctxt->get_readable_tree (arg);
-
-	  /* start/unchecked/nonnull -> free.  */
-	  sm_ctxt->on_transition (node, stmt, arg, m_start, m_free);
-	  sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_free);
-	  sm_ctxt->on_transition (node, stmt, arg, m_nonnull, m_free);
-
-	  /* Keep state "null" as-is, rather than transitioning to "free";
-	     we don't want want to complain about double-free of NULL.  */
-
-	  /* free -> stop, with warning.  */
-	  sm_ctxt->warn_for_state (node, stmt, arg, m_free,
-				   new double_free (*this, arg));
-	  sm_ctxt->on_transition (node, stmt, arg, m_free, m_stop);
-
-	  /* non-heap -> stop, with warning.  */
-	  sm_ctxt->warn_for_state (node, stmt, arg, m_non_heap,
-				   new free_of_non_heap (*this, arg));
-	  sm_ctxt->on_transition (node, stmt, arg, m_non_heap, m_stop);
-	  return true;
-	}
-
-      /* TODO: handle "__attribute__((nonnull))".   */
-      if (tree callee_fndecl = gimple_call_fndecl (stmt))
-	{
-	  tree fntype = TREE_TYPE (callee_fndecl);
-	  bitmap nonnull_args = get_nonnull_args (fntype);
-	  if (nonnull_args)
-	    {
-	      for (unsigned i = 0; i < gimple_call_num_args (stmt); i++)
-		{
-		  tree arg = gimple_call_arg (stmt, i);
-		  if (TREE_CODE (TREE_TYPE (arg)) != POINTER_TYPE)
-		    continue;
-		  /* If we have a nonnull-args, and either all pointers, or just
-		     the specified pointers.  */
-		  if (bitmap_empty_p (nonnull_args)
-		      || bitmap_bit_p (nonnull_args, i))
-		    {
-		      sm_ctxt->warn_for_state
-			(node, stmt, arg, m_unchecked,
-			 new possible_null_arg (*this, arg, callee_fndecl, i));
-		      sm_ctxt->on_transition (node, stmt, arg, m_unchecked,
-					      m_nonnull);
-
-		      sm_ctxt->warn_for_state
-			(node, stmt, arg, m_null,
-			 new null_arg (*this, arg, callee_fndecl, i));
-		      sm_ctxt->on_transition (node, stmt, arg, m_null, m_stop);
-		    }
-		}
-	      BITMAP_FREE (nonnull_args);
-	    }
-	}
-    }
-
-  /* FIXME: It's not clear if we need this, due to the SSA representation:
-     each assignment creates a new SSA_NAME.  */
-  if (tree lhs = is_zero_assignment (stmt))
-    {
-      if (any_pointer_p (lhs))
-	{
-	  sm_ctxt->on_transition (node, stmt, lhs, m_start, m_null);
-	  sm_ctxt->on_transition (node, stmt, lhs, m_unchecked, m_null);
-	  sm_ctxt->on_transition (node, stmt, lhs, m_nonnull, m_null);
-	  sm_ctxt->on_transition (node, stmt, lhs, m_free, m_null);
-	}
-    }
-
-  if (const gassign *assign_stmt = dyn_cast <const gassign *> (stmt))
-    {
-      enum tree_code op = gimple_assign_rhs_code (assign_stmt);
-      if (op == ADDR_EXPR)
-	{
-	  tree lhs = gimple_assign_lhs (assign_stmt);
-	  if (lhs)
-	    {
-	      lhs = sm_ctxt->get_readable_tree (lhs);
-	      sm_ctxt->on_transition (node, stmt, lhs, m_start, m_non_heap);
-	    }
-	}
-    }
-
-  // TODO: dereferences
-  for (unsigned i = 0; i < gimple_num_ops (stmt); i++)
-    {
-      tree op = gimple_op (stmt, i);
-#if 0
-      if (op)
-	inform (stmt->location, "op %i: %qE", i, op);
-      else
-	inform (stmt->location, "op %i: NULL_TREE", i);
-#endif
-      if (!op)
-	continue;
-      if (TREE_CODE (op) == COMPONENT_REF)
-	op = TREE_OPERAND (op, 0);
-
-      if (TREE_CODE (op) == MEM_REF)
-	{
-	  tree arg = TREE_OPERAND (op, 0);
-	  arg = sm_ctxt->get_readable_tree (arg);
-
-	  sm_ctxt->warn_for_state (node, stmt, arg, m_unchecked,
-				   new possible_null_deref (*this, arg));
-	  sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_nonnull);
-
-	  sm_ctxt->warn_for_state (node, stmt, arg, m_null,
-				   new null_deref (*this, arg));
-	  sm_ctxt->on_transition (node, stmt, arg, m_null, m_stop);
-
-	  sm_ctxt->warn_for_state (node, stmt, arg, m_free,
-				   new use_after_free (*this, arg));
-	  sm_ctxt->on_transition (node, stmt, arg, m_free, m_stop);
-	}
-    }
-  return false;
-}
-
-/* FIXME.  */
-
-void
-malloc_state_machine::on_edge (sm_context *sm_ctxt,
-			       const gimple *last_stmt,
-			       const superedge *edge) const
-{
-  if (last_stmt == NULL)
-    return;
-
-  if (const gcond *cond_stmt = dyn_cast <const gcond *> (last_stmt))
-    {
-      tree arg;
-      enum tree_code op;
-      if (is_comparison_against_zero (cond_stmt, &arg, &op))
-	{
-	  if (!any_pointer_p (arg))
-	    return;
-
-	  ::edge cfg_edge = edge->get_any_cfg_edge ();
-	  gcc_assert (cfg_edge != NULL);
-	  gcc_assert (cfg_edge->flags & (EDGE_TRUE_VALUE | EDGE_FALSE_VALUE));
-	  
-	  if (op == NE_EXPR)
-	    {
-	      log ("got 'ARG != 0' match");
-	      sm_ctxt->on_transition (edge->m_src, last_stmt, arg, m_unchecked,
-				      (cfg_edge->flags & EDGE_TRUE_VALUE
-				       ? m_nonnull : m_null));
-	    }
-	  else if (op == EQ_EXPR)
-	    {
-	      log ("got 'ARG == 0' match");
-	      sm_ctxt->on_transition (edge->m_src, last_stmt, arg, m_unchecked,
-				      (cfg_edge->flags & EDGE_TRUE_VALUE
-				       ? m_null : m_nonnull));
-	    }
-	}
-    }
-
-  // TODO: something similar for switch statements?
-}
-
-void
-malloc_state_machine::on_leak (sm_context *sm_ctxt,
-			       const supernode *node,
-			       const gimple *stmt,
-			       tree var,
-			       state_machine::state_t state) const
-{
-  sm_ctxt->warn_for_state (node, stmt, var, m_unchecked,
-			   new malloc_leak (*this, var));
-  sm_ctxt->warn_for_state (node, stmt, var, m_nonnull,
-			   new malloc_leak (*this, var));
-}
-
-bool
-malloc_state_machine::can_purge_p (state_t s) const
-{
-  return s != m_unchecked && s != m_nonnull;
-}
diff --git a/gcc/analyzer/hardcoded-state-machine-pattern-test.cc b/gcc/analyzer/hardcoded-state-machine-pattern-test.cc
deleted file mode 100644
index 997a7ac..0000000
--- a/gcc/analyzer/hardcoded-state-machine-pattern-test.cc
+++ /dev/null
@@ -1,151 +0,0 @@
-/* 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 "hardcoded-state-machine.h"
-
-static const bool DEBUG = false;
-//static const bool DEBUG = true;
-
-////////////////////////////////////////////////////////////////////////////
-
-class pattern_match : public pending_diagnostic_subclass<pattern_match>
-{
-public:
-  pattern_match (tree arg, const char *msg) : m_arg (arg), m_msg (msg) {}
-
-  const char *get_kind () const FINAL OVERRIDE { return "pattern_match"; }
-
-  bool operator== (const pattern_match &other) const
-  {
-    return (m_arg == other.m_arg
-	    // FIXME: should this be string equality?
-	    && m_msg == other.m_msg);
-  }
-
-  void emit (rich_location *rich_loc) FINAL OVERRIDE
-  {
-    // TODO:
-    warning_at (rich_loc, 0, "pattern match on %qE: %s", m_arg, m_msg);
-  }
-
-private:
-  tree m_arg;
-  const char *m_msg;
-};
-
-////////////////////////////////////////////////////////////////////////////
-
-pattern_test_state_machine::pattern_test_state_machine (logger *logger)
-: state_machine ("pattern-test", logger)
-{
-  // TODO: parse all of this from a file:
-  m_start = add_state ("start");
-}
-
-bool
-pattern_test_state_machine::on_stmt (sm_context *sm_ctxt ATTRIBUTE_UNUSED,
-				     const supernode *node ATTRIBUTE_UNUSED,
-				     const gimple *stmt ATTRIBUTE_UNUSED) const
-{
-  //tree fndecl = node->m_fun->decl;
-
-#if 0
-  // FIXME: do all of this from data
-  if (const gcall *call = dyn_cast <const gcall *> (stmt))
-    {
-    }
-#endif
-  return false;
-}
-
-/* FIXME.  */
-
-void
-pattern_test_state_machine::on_edge (sm_context *sm_ctxt,
-				     const gimple *last_stmt,
-				     const superedge *edge) const
-{
-  if (last_stmt == NULL)
-    return;
-
-  const supernode *node = edge->m_src;
-
-  if (const gcond *cond_stmt = dyn_cast <const gcond *> (last_stmt))
-    {
-      tree arg;
-      enum tree_code op;
-      if (is_comparison_against_zero (cond_stmt, &arg, &op))
-	{
-	  ::edge cfg_edge = edge->get_any_cfg_edge ();
-	  gcc_assert (cfg_edge != NULL);
-	  gcc_assert (cfg_edge->flags & (EDGE_TRUE_VALUE | EDGE_FALSE_VALUE));
-
-	  bool is_true = cfg_edge->flags & EDGE_TRUE_VALUE;
-
-	  if (op == NE_EXPR)
-	    {
-	      pending_diagnostic *diag
-		= new pattern_match (arg,
-				     is_true ? "!= 0" : "== 0");
-	      sm_ctxt->warn_for_state (node, last_stmt, arg, m_start, diag);
-	    }
-	  else if (op == EQ_EXPR)
-	    {
-	      pending_diagnostic *diag
-		= new pattern_match (arg,
-				     is_true ? "== 0" : "!= 0");
-	      sm_ctxt->warn_for_state (node, last_stmt, arg, m_start, diag);
-	    }
-	}
-    }
-}
-
-void
-pattern_test_state_machine::on_leak (sm_context *sm_ctxt ATTRIBUTE_UNUSED,
-				     const supernode *node ATTRIBUTE_UNUSED,
-				     const gimple *stmt ATTRIBUTE_UNUSED,
-				     tree var ATTRIBUTE_UNUSED,
-				     state_machine::state_t state
-				       ATTRIBUTE_UNUSED)
-  const
-{
-  /* Empty.  */
-}
-
-bool
-pattern_test_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const
-{
-  return true;
-}
diff --git a/gcc/analyzer/hardcoded-state-machine-sensitive.cc b/gcc/analyzer/hardcoded-state-machine-sensitive.cc
deleted file mode 100644
index 5c52431..0000000
--- a/gcc/analyzer/hardcoded-state-machine-sensitive.cc
+++ /dev/null
@@ -1,153 +0,0 @@
-/* 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 "hardcoded-state-machine.h"
-
-static const bool DEBUG = false;
-//static const bool DEBUG = true;
-
-////////////////////////////////////////////////////////////////////////////
-
-class exposure_through_output_file
-  : public pending_diagnostic_subclass<exposure_through_output_file>
-{
-public:
-  exposure_through_output_file (tree arg) : m_arg (arg) {}
-
-  const char *get_kind () const FINAL OVERRIDE { return "exposure_through_output_file"; }
-
-  bool operator== (const exposure_through_output_file &other) const
-  {
-    return m_arg == other.m_arg;
-  }
-
-  void emit (rich_location *rich_loc) FINAL OVERRIDE
-  {
-    metadata m;
-    /* CWE-532: Information Exposure Through Log Files */
-    m.add_cwe (532);
-    warning_at (rich_loc, m, OPT_Wanalyzer_exposure_through_output_file,
-		"sensitive value %qE written to output file",
-		m_arg);
-  }
-
-private:
-  tree m_arg;
-};
-
-////////////////////////////////////////////////////////////////////////////
-
-sensitive_state_machine::sensitive_state_machine (logger *logger)
-: state_machine ("sensitive", logger)
-{
-  // TODO: parse all of this from a file:
-  m_start = add_state ("start");
-  m_sensitive = add_state ("sensitive");
-  m_stop = add_state ("stop");
-}
-
-void
-sensitive_state_machine::warn_for_any_exposure (sm_context *sm_ctxt,
-						const supernode *node,
-						const gimple *stmt,
-						tree arg) const
-{
-  sm_ctxt->warn_for_state (node, stmt, arg, m_sensitive,
-			   new exposure_through_output_file (arg));
-}
-
-bool
-sensitive_state_machine::on_stmt (sm_context *sm_ctxt,
-				  const supernode *node,
-				  const gimple *stmt) const
-{
-  //tree fndecl = node->m_fun->decl;
-
-  // FIXME: do all of this from data
-  if (const gcall *call = dyn_cast <const gcall *> (stmt))
-    {
-      if (is_named_call_p (call, "getpass", 1))
-	{
-	  tree lhs = gimple_call_lhs (call);
-	  if (lhs)
-	    sm_ctxt->on_transition (node, stmt, lhs, m_start, m_sensitive);
-	  return true;
-	}
-      else if (is_named_call_p (call, "fprintf")
-	       || is_named_call_p (call, "printf"))
-	{
-	  /* Handle a match at any position in varargs.  */
-	  for (int idx = 1; idx < gimple_call_num_args (call); idx++)
-	    {
-	      tree arg = gimple_call_arg (call, idx);
-	      warn_for_any_exposure (sm_ctxt, node, stmt, arg);
-	    }
-	  return true;
-	}
-      else if (is_named_call_p (call, "fwrite", 4))
-	{
-	  tree arg = gimple_call_arg (call, 0);
-	  warn_for_any_exposure (sm_ctxt, node, stmt, arg);
-	  return true;
-	}
-    }
-  return false;
-}
-
-/* FIXME.  */
-
-void
-sensitive_state_machine::on_edge (sm_context *sm_ctxt,
-				  const gimple *last_stmt,
-				  const superedge *edge) const
-{
-  /* Empty.  */
-}
-
-void
-sensitive_state_machine::on_leak (sm_context *sm_ctxt,
-				  const supernode *node,
-				  const gimple *stmt,
-				  tree var,
-				  state_machine::state_t state) const
-{
-  /* Empty.  */
-}
-
-bool
-sensitive_state_machine::can_purge_p (state_t s) const
-{
-  return true;
-}
diff --git a/gcc/analyzer/hardcoded-state-machine-taint.cc b/gcc/analyzer/hardcoded-state-machine-taint.cc
deleted file mode 100644
index a977674..0000000
--- a/gcc/analyzer/hardcoded-state-machine-taint.cc
+++ /dev/null
@@ -1,234 +0,0 @@
-/* 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 "hardcoded-state-machine.h"
-
-static const bool DEBUG = false;
-//static const bool DEBUG = true;
-
-////////////////////////////////////////////////////////////////////////////
-
-class tainted_array_index : public pending_diagnostic_subclass<tainted_array_index>
-{
-public:
-  tainted_array_index (tree arg) : m_arg (arg) {}
-
-  const char *get_kind () const FINAL OVERRIDE { return "tainted_array_index"; }
-
-  bool operator== (const tainted_array_index &other) const
-  {
-    return m_arg == other.m_arg;
-  }
-
-  void emit (rich_location *rich_loc) FINAL OVERRIDE
-  {
-    metadata m;
-    m.add_cwe (129);
-    warning_at (rich_loc, m, OPT_Wanalyzer_tainted_array_index,
-		"use of tainted value %qE in array lookup without bounds checking",
-		m_arg);
-  }
-
-private:
-  tree m_arg;
-};
-
-////////////////////////////////////////////////////////////////////////////
-
-taint_state_machine::taint_state_machine (logger *logger)
-: state_machine ("taint", logger)
-{
-  // TODO: parse all of this from a file:
-  m_start = add_state ("start");
-  m_tainted = add_state ("tainted");
-  m_has_lb = add_state ("has_lb");
-  m_has_ub = add_state ("has_ub");
-  m_stop = add_state ("stop");
-}
-
-bool
-taint_state_machine::on_stmt (sm_context *sm_ctxt,
-			       const supernode *node,
-			       const gimple *stmt) const
-{
-  //tree fndecl = node->m_fun->decl;
-
-  // FIXME: do all of this from data
-  if (const gcall *call = dyn_cast <const gcall *> (stmt))
-    {
-      if (is_named_call_p (call, "fread", 4))
-	{
-	  if (DEBUG)
-	    inform (stmt->location, "DBG: got taint");
-	  tree arg = gimple_call_arg (call, 0);
-	  arg = sm_ctxt->get_readable_tree (arg);
-
-	  sm_ctxt->on_transition (node, stmt, arg, m_start, m_tainted);
-
-	  // FIXME: dereference an ADDR_EXPR:
-	  // FIXME: should the engine do this?
-	  if (TREE_CODE (arg) == ADDR_EXPR)
-	    sm_ctxt->on_transition (node, stmt, TREE_OPERAND (arg, 0),
-				    m_start, m_tainted);
-	  return true;
-	}
-    }
-
-#if 0
-  /* FIXME: It's not clear if we need this, due to the SSA representation:
-     each assignment creates a new SSA_NAME.  */
-  if (tree lhs = is_zero_assignment (stmt))
-    {
-      if (any_pointer_p (lhs))
-	{
-	  sm_ctxt->on_transition (node, stmt, lhs, m_start, m_null);
-	  sm_ctxt->on_transition (node, stmt, lhs, m_unchecked, m_null);
-	  sm_ctxt->on_transition (node, stmt, lhs, m_nonnull, m_null);
-	  sm_ctxt->on_transition (node, stmt, lhs, m_free, m_null);
-	}
-    }
-#endif
-
-  if (const gassign *assign = dyn_cast <const gassign *> (stmt))
-    {
-      tree lhs = gimple_assign_lhs (assign);
-      tree rhs1 = gimple_assign_rhs1 (assign);
-      enum tree_code op = gimple_assign_rhs_code (assign);
-      if (op == ARRAY_REF)
-	{
-	  tree arg = TREE_OPERAND (rhs1, 1);
-	  arg = sm_ctxt->get_readable_tree (arg);
-	  sm_ctxt->warn_for_state  (node, stmt, arg, m_tainted,
-				    new tainted_array_index (arg));
-	  sm_ctxt->on_transition (node, stmt, arg, m_tainted, m_stop);
-	}
-    }
-
-#if 0
-  // TODO: dereferences
-  for (unsigned i = 0; i < gimple_num_ops (stmt); i++)
-    {
-      tree op = gimple_op (stmt, i);
-#if 0
-      if (op)
-	inform (stmt->location, "op %i: %qE", i, op);
-      else
-	inform (stmt->location, "op %i: NULL_TREE", i);
-#endif
-      if (!op)
-	continue;
-      if (TREE_CODE (op) == MEM_REF)
-	{
-	  tree arg = TREE_OPERAND (op, 0);
-	  sm_ctxt->warn_for_state (node, stmt, arg, m_unchecked,
-				   "use of possibly-NULL %qE", arg);
-	  sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_nonnull);
-
-	  sm_ctxt->warn_for_state (node, stmt, arg, m_null,
-				   "use of NULL %qE", arg);
-	  sm_ctxt->on_transition (node, stmt, arg, m_null, m_stop);
-
-	  sm_ctxt->warn_for_state (node, stmt, arg, m_free,
-				   "use after free of %qE", arg);
-	  sm_ctxt->on_transition (node, stmt, arg, m_free, m_stop);
-	}
-    }
-#endif
-  return false;
-}
-
-/* FIXME.  */
-
-void
-taint_state_machine::on_edge (sm_context *sm_ctxt,
-			       const gimple *last_stmt,
-			       const superedge *edge) const
-{
-  if (last_stmt == NULL)
-    return;
-
-  if (const gcond *cond_stmt = dyn_cast <const gcond *> (last_stmt))
-    {
-      enum tree_code op = gimple_cond_code (cond_stmt);
-      tree lhs = gimple_cond_lhs (cond_stmt);
-      tree rhs = gimple_cond_rhs (cond_stmt);
-      // TODO: make it symmetric?
-
-      switch (op)
-	{
-	  //case NE_EXPR:
-	  //case EQ_EXPR:
-	case GE_EXPR:
-	case GT_EXPR:
-	  {
-	    sm_ctxt->on_transition (edge->m_src, last_stmt, lhs, m_tainted,
-				    m_has_lb);
-	    sm_ctxt->on_transition (edge->m_src, last_stmt, lhs, m_has_ub,
-				    m_stop);
-	  }
-	  break;
-	case LE_EXPR:
-	case LT_EXPR:
-	  {
-	    sm_ctxt->on_transition (edge->m_src, last_stmt, lhs, m_tainted,
-				    m_has_ub);
-	    sm_ctxt->on_transition (edge->m_src, last_stmt, lhs, m_has_lb,
-				    m_stop);
-	  }
-	  break;
-
-	default:
-	  break;
-	}
-    }
-
-  // TODO: something similar for switch statements?
-}
-
-void
-taint_state_machine::on_leak (sm_context *sm_ctxt,
-			       const supernode *node,
-			       const gimple *stmt,
-			       tree var,
-			       state_machine::state_t state) const
-{
-  /* Empty.  */
-}
-
-bool
-taint_state_machine::can_purge_p (state_t s) const
-{
-  return true;
-}
diff --git a/gcc/analyzer/hardcoded-state-machine.cc b/gcc/analyzer/hardcoded-state-machine.cc
deleted file mode 100644
index 084d0e3..0000000
--- a/gcc/analyzer/hardcoded-state-machine.cc
+++ /dev/null
@@ -1,169 +0,0 @@
-/* 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 "hardcoded-state-machine.h"
-
-static const bool DEBUG = false;
-//static const bool DEBUG = true;
-
-////////////////////////////////////////////////////////////////////////////
-
-/* If STMT is an assignment to zero, return the LHS.  */
-
-tree
-is_zero_assignment (const gimple *stmt)
-{
-  const gassign *assign_stmt = dyn_cast <const gassign *> (stmt);
-  if (!assign_stmt)
-    return NULL_TREE;
-
-  enum tree_code op = gimple_assign_rhs_code (assign_stmt);
-  if (op != INTEGER_CST)
-    return NULL_TREE;
-
-  if (!zerop (gimple_assign_rhs1 (assign_stmt)))
-    return NULL_TREE;
-
-  return gimple_assign_lhs (assign_stmt);
-}
-
-/* FIXME.  */
-bool
-is_comparison_against_zero (const gcond *cond_stmt,
-			    tree *out_lhs, enum tree_code *out_op)
-{
-  enum tree_code op = gimple_cond_code (cond_stmt);
-  tree lhs = gimple_cond_lhs (cond_stmt);
-  tree rhs = gimple_cond_rhs (cond_stmt);
-  if (!zerop (rhs))
-    return false;
-  // TODO: make it symmetric?
-
-  switch (op)
-    {
-    case NE_EXPR:
-    case EQ_EXPR:
-      *out_lhs = lhs;
-      *out_op = op;
-      return true;
-
-    default:
-      return false;
-    }
-}
-
-bool
-any_pointer_p (tree var)
-{
-  if (TREE_CODE (TREE_TYPE (var)) != POINTER_TYPE)
-    return false;
-
-  return true;
-}
-
-////////////////////////////////////////////////////////////////////////////
-
-state_machine::state_t
-state_machine::add_state (const char *name)
-{
-  m_state_names.safe_push (name);
-  return m_state_names.length () - 1;
-}
-
-const char *
-state_machine::get_state_name (state_t s) const
-{
-  return m_state_names[s];
-}
-
-void
-state_machine::validate (state_t s) const
-{
-  gcc_assert (s < m_state_names.length ());
-}
-
-////////////////////////////////////////////////////////////////////////////
-
-label_text
-pending_diagnostic::formatted_print (const char *fmt, ...)
-{
-  pretty_printer *pp = global_dc->printer->clone ();
-
-  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);
-
-  label_text result = label_text::take (xstrdup (pp_formatted_text (pp)));
-  delete pp;
-  return result;
-}
-
-////////////////////////////////////////////////////////////////////////////
-
-void
-make_checkers (auto_delete_vec <state_machine> &out, logger *logger)
-{
-  out.safe_push (new malloc_state_machine (logger));
-  out.safe_push (new fileptr_state_machine (logger));
-  out.safe_push (new taint_state_machine (logger));
-  out.safe_push (new sensitive_state_machine (logger));
-
-  /* We only attempt to run the pattern tests if it might have been manually
-     enabled (for DejaGnu purposes).  */
-  if (flag_analyzer_checker)
-    out.safe_push (new pattern_test_state_machine (logger));
-
-  if (flag_analyzer_checker)
-    {
-      int read_index, write_index;
-      state_machine **sm;
-
-      // FIXME: this leaks the machines
-      // FIXME: would be nice to log the things that were removed
-      VEC_ORDERED_REMOVE_IF (out, read_index, write_index, sm,
-			     0 != strcmp (flag_analyzer_checker,
-					  (*sm)->get_name ()));
-    }
-
-}
diff --git a/gcc/analyzer/hardcoded-state-machine.h b/gcc/analyzer/hardcoded-state-machine.h
deleted file mode 100644
index 9577f09..0000000
--- a/gcc/analyzer/hardcoded-state-machine.h
+++ /dev/null
@@ -1,554 +0,0 @@
-/* FIXME.
-   Copyright (C) 2019 Free Software Foundation, Inc.
-   Contributed by David Malcolm <dmalcolm@redhat.com>.
-
-This file is part of GCC.
-
-GCC is free software; you can redistribute it and/or modify it
-under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 3, or (at your option)
-any later version.
-
-GCC is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with GCC; see the file COPYING3.  If not see
-<http://www.gnu.org/licenses/>.  */
-
-#ifndef GCC_ANALYZER_HARDCODED_STATE_MACHINE_H
-#define GCC_ANALYZER_HARDCODED_STATE_MACHINE_H
-
-#include "analyzer-logging.h"
-#include "diagnostic-metadata.h"
-
-////////////////////////////////////////////////////////////////////////////
-
-extern tree is_zero_assignment (const gimple *stmt);
-extern bool is_comparison_against_zero (const gcond *cond_stmt,
-					tree *out_lhs, enum tree_code *out_op);
-extern bool any_pointer_p (tree var);
-
-////////////////////////////////////////////////////////////////////////////
-
-class state_machine;
-class sm_context;
-
-/* A state machine describing an API.
-   A mapping from state IDs to names, and (for now) a vfunc implementation
-   for pattern-matching on statements.  */
-
-class state_machine : public log_user
-{
-public:
-  typedef int state_t;
-
-  state_machine (const char *name, logger *logger)
-  : log_user (logger), m_name (name) {}
-
-  virtual ~state_machine () {}
-
-  /* Should states be inherited from a parent region to a child region,
-     when first accessing a child region?
-     For example we should inherit the taintedness of a subregion,
-     but we should not inherit the "malloc:non-null" state of a field
-     within a heap-allocated struct.  */
-  virtual bool inherited_state_p () const = 0;
-
-  const char *get_name () const { return m_name; }
-
-  const char *get_state_name (state_t s) const;
-
-  /* Return true if STMT is a function call recognized by this sm.  */
-  virtual bool on_stmt (sm_context *sm_ctxt,
-			const supernode *node,
-			const gimple *stmt) const = 0;
-
-  virtual void on_edge (sm_context *sm_ctxt,
-			const gimple *last_stmt,
-			const superedge *edge) const = 0;
-
-  virtual void on_leak (sm_context *sm_ctxt,
-			const supernode *node,
-			const gimple *stmt,
-			tree var,
-			state_machine::state_t state) const = 0;
-
-  /* Return true if it safe to discard the given state (to help
-     when simplifying state objects).
-     States that need leak detection should return false.  */
-  virtual bool can_purge_p (state_t s) const = 0;
-
-  void validate (state_t s) const;
-
-protected:
-  state_t add_state (const char *name);
-
-private:
-  const char *m_name;
-  auto_vec<const char *> m_state_names;
-};
-
-/* FIXME.  */
-
-static inline bool
-start_start_p (state_machine::state_t state)
-{
-  return state == 0;
-}
-
-////////////////////////////////////////////////////////////////////////////
-
-/* FIXME.  */
-
-// TODO: maybe capture which sm emitted the error
-
-class metadata : public diagnostic_metadata
-{
- public:
-
- private:
-};
-
-/* FIXME.  */
-
-class event_id_t
-{
- public:
-  event_id_t () : m_index (UNKNOWN_EVENT_IDX) {}
-  event_id_t (int zero_based_idx) : m_index (zero_based_idx) {}
-
-  bool known_p () const { return m_index != UNKNOWN_EVENT_IDX; }
-
-  int one_based () const
-  {
-    gcc_assert (known_p ());
-    return m_index + 1;
-  }
-
- private:
-  static const int UNKNOWN_EVENT_IDX = -1;
-  int m_index; // zero-based
-};
-
-/* Various bundles of information used for generating more precise
-   messages for events within a diagnostic_path, for passing to
-   "describe" vfuncs of pending_diagnostic.  */
-
-namespace evdesc {
-
-/* Information for describing a critical state change within the
-   diagnostic_path.
-
-   For example, a double-free diagnostic might use the descriptions:
-   - "first 'free' happens here"
-   - "second 'free' happens here"
-   for the pertinent events, whereas a use-after-free might use the
-   descriptions:
-   - "freed here"
-   - "use after free here"
-   Note how in both cases the first event is a "free": the best
-   description to use depends on the diagnostic.  */
-
-struct state_change
-{
-  state_change (tree expr,
-		state_machine::state_t old_state,
-		state_machine::state_t new_state,
-		event_id_t event_id)
-  : m_expr (expr), m_old_state (old_state),
-    m_new_state (new_state), m_event_id (event_id)
-  {}
-
-  tree m_expr;
-  state_machine::state_t m_old_state;
-  state_machine::state_t m_new_state;
-  event_id_t m_event_id;
-};
-
-/* Information for describing an interprocedural call carrying
-   critial state for the diagnostic, from caller to callee.
-
-   For example a double-free diagnostic might use:
-     "passing freed pointer 'ptr' in call to 'deallocator' from 'test'"
-   to make it clearer how the unchecked value moves from caller
-   to callee.  */
-
-struct call_with_state
-{
-  call_with_state (tree caller_fndecl, tree callee_fndecl,
-		   tree expr, state_machine::state_t state)
-  : m_caller_fndecl (caller_fndecl),
-    m_callee_fndecl (callee_fndecl),
-    m_expr (expr),
-    m_state (state)
-  {
-  }
-
-  tree m_caller_fndecl;
-  tree m_callee_fndecl;
-  tree m_expr;
-  state_machine::state_t m_state;
-};
-
-/* Information for describing an interprocedural return within the
-   diagnostic_path that carries critial state for the diagnostic,
-   from callee back to caller.
-
-   For example, a deref-of-unchecked-malloc diagnostic might use:
-   - "returning possibly-NULL pointer to 'make_obj' from 'allocator'"
-   to make it clearer how the unchecked value moves from callee
-   back to caller.  */
-
-struct return_of_state
-{
-  return_of_state (tree caller_fndecl, tree callee_fndecl,
-		   state_machine::state_t state)
-  : m_caller_fndecl (caller_fndecl),
-    m_callee_fndecl (callee_fndecl),
-    m_state (state)
-  {
-  }
-
-  tree m_caller_fndecl;
-  tree m_callee_fndecl;
-  state_machine::state_t m_state;
-};
-
-/* Information for describing the final event within a diagnostic_path.
-
-   For example a double-free diagnostic might use:
-     "second 'free' here"
-   and a use-after-free might use
-     "use after 'free' here".  */
-
-struct final_event
-{
-  final_event (tree expr, state_machine::state_t state)
-  : m_expr (expr), m_state (state)
-  {}
-
-  tree m_expr;
-  state_machine::state_t m_state;
-};
-
-} /* end of namespace evdesc */
-
-/* FIXME:
-   Diagnostics are emitted using va_list, which can't be portably stored
-   for later use, so we have to make it be a vfunc.  */
-
-class pending_diagnostic
-{
- public:
-  virtual ~pending_diagnostic () {}
-
-  /* Hand-coded RTTI: get an ID for the subclass.  */
-  virtual const char *get_kind () const = 0;
-
-  virtual void emit (rich_location *) = 0;
-
-  /* Compare for equality with OTHER, which might be of a different
-     subclass.  */
-
-  bool equal_p (const pending_diagnostic &other)
-  {
-    /* Check for pointer equality on the IDs from get_kind.  */
-    if (get_kind () != other.get_kind ())
-      return false;
-    /* Call vfunc now we know they have the same ID: */
-    return subclass_equal_p (other);
-  }
-
-  /* A vfunc for testing for equality, where we've already
-     checked they have the same ID.  See pending_diagnostic_subclass
-     below for a convenience subclass for implementing this.  */
-  virtual bool subclass_equal_p (const pending_diagnostic &other) const = 0;
-
-  /* For greatest precision-of-wording, the various following overloads of
-     "describe" are virtual functions that give the pending diagnostic a
-     way to describe events in a diagnostic_path in terms that make sense
-     for that diagnostic.
-
-     In each case, return a non-NULL label_text to give the event a custom
-     description; NULL otherwise (falling back on a more generic
-     description).
-
-     Refer to the various "evdesc" struct definitions above for more
-     information on the meanings of each event kind.  */
-
-  virtual label_text describe (const evdesc::state_change &)
-  {
-    /* Default no-op implementation.  */
-    return label_text ();
-  }
-  virtual label_text describe (const evdesc::call_with_state &)
-  {
-    /* Default no-op implementation.  */
-    return label_text ();
-  }
-  virtual label_text describe (const evdesc::return_of_state &)
-  {
-    /* Default no-op implementation.  */
-    return label_text ();
-  }
-  virtual label_text describe (const evdesc::final_event &)
-  {
-    /* Default no-op implementation.  */
-    return label_text ();
-  }
-
-  /* End of precision-of-wording vfuncs.  */
-
-  static label_text formatted_print (const char *fmt, ...)
-    ATTRIBUTE_GCC_DIAG(1,2);
-};
-
-/* Use of the curiously-recurring template pattern, to implement
-   subclass_equal_p by casting and calling the operator==
-   This assumes that BASE_OTHER has already been checked to have
-   been of the same subclass.  */
-
-template <class Subclass>
-class pending_diagnostic_subclass : public pending_diagnostic
-{
- public:
-  bool subclass_equal_p (const pending_diagnostic &base_other) const
-    FINAL OVERRIDE
-  {
-    const Subclass &other = (const Subclass &)base_other;
-    return *(const Subclass*)this == other;
-  }
-};
-
-#if 0
-/* A thing for state machines to pass to the engine to create
-   pending_diagnostic instances (so that we can filter without having 
-   to create pending_diagnostic instances first).  */
-
-class diagnostic_emitter
-{
-  virtual pending_diagnostic *make_diagnostic () = 0;
-};
-#endif
-
-/* Interface for the state machine to call the checker engine, at a
-   particular stmt.  */
-
-class sm_context
-{
-public:
-  //  sm_context (const state_machine &sm) : m_sm (sm) {}
-  virtual ~sm_context () {}
-
-  // Stuff used by impl 1:
-  virtual void on_transition (const supernode *node, const gimple *stmt,
-			      tree var,
-			      state_machine::state_t from,
-			      state_machine::state_t to,
-			      tree origin = NULL_TREE) = 0;
-
-  // Stuff used by impl 2:
-
-  //virtual state_machine::state_t get_state (tree var) = 0;
-  //virtual void transition (tree var, state_machine::state_t to) = 0;
-  virtual void warn_for_state (const supernode *node, const gimple *stmt,
-			       tree var, state_machine::state_t state,
-			       pending_diagnostic *d) = 0;
-
-  // etc, what else?
-
-  virtual tree get_readable_tree (tree expr)
-  {
-    return expr;
-  }
-
-protected:
-  //const state_machine &m_sm;
-};
-
-
-////////////////////////////////////////////////////////////////////////////
-
-extern void make_checkers (auto_delete_vec <state_machine> &out, logger *logger);
-
-////////////////////////////////////////////////////////////////////////////
-
-/* A particular state machine, for prototyping purposes:
-   the malloc/free API.  */
-
-class malloc_state_machine : public state_machine
-{
-public:
-  malloc_state_machine (logger *logger);
-
-  bool inherited_state_p () const FINAL OVERRIDE { return false; }
-
-  bool on_stmt (sm_context *sm_ctxt,
-		const supernode *node,
-		const gimple *stmt) const FINAL OVERRIDE;
-
-  void on_edge (sm_context *sm_ctxt,
-		const gimple *last_stmt,
-		const superedge *edge) const FINAL OVERRIDE;
-
-  void on_leak (sm_context *sm_ctxt,
-		const supernode *node,
-		const gimple *stmt,
-		tree var,
-		state_machine::state_t state) const FINAL OVERRIDE;
-  bool can_purge_p (state_t s) const FINAL OVERRIDE;
-
-  state_t m_start;
-  state_t m_unchecked;
-  state_t m_null;
-  state_t m_nonnull;
-  state_t m_free;
-  state_t m_non_heap; // FIXME: or should this be a different state machine?
-  // or do we need child values etc?
-  state_t m_stop;
-};
-
-////////////////////////////////////////////////////////////////////////////
-
-/* A particular state machine, for prototyping purposes:
-   the fopen API.  */
-
-class fileptr_state_machine : public state_machine
-{
-public:
-  fileptr_state_machine (logger *logger);
-
-  bool inherited_state_p () const FINAL OVERRIDE { return false; }
-
-  bool on_stmt (sm_context *sm_ctxt,
-		const supernode *node,
-		const gimple *stmt) const FINAL OVERRIDE;
-
-  void on_edge (sm_context *sm_ctxt,
-		const gimple *last_stmt,
-		const superedge *edge) const FINAL OVERRIDE;
-
-  void on_leak (sm_context *sm_ctxt,
-		const supernode *node,
-		const gimple *stmt,
-		tree var,
-		state_machine::state_t state) const FINAL OVERRIDE;
-  bool can_purge_p (state_t s) const FINAL OVERRIDE;
-
-private:
-  state_t m_start;
-  state_t m_unchecked;
-  state_t m_null;
-  state_t m_nonnull;
-  state_t m_closed;
-  state_t m_stop;
-};
-
-////////////////////////////////////////////////////////////////////////////
-
-/* A particular state machine, for prototyping purposes:
-   tainted data.  */
-
-class taint_state_machine : public state_machine
-{
-public:
-  taint_state_machine (logger *logger);
-
-  bool inherited_state_p () const FINAL OVERRIDE { return true; }
-
-  bool on_stmt (sm_context *sm_ctxt,
-		const supernode *node,
-		const gimple *stmt) const FINAL OVERRIDE;
-
-  void on_edge (sm_context *sm_ctxt,
-		const gimple *last_stmt,
-		const superedge *edge) const FINAL OVERRIDE;
-
-  void on_leak (sm_context *sm_ctxt,
-		const supernode *node,
-		const gimple *stmt,
-		tree var,
-		state_machine::state_t state) const FINAL OVERRIDE;
-  bool can_purge_p (state_t s) const FINAL OVERRIDE;
-
-private:
-  state_t m_start;
-  state_t m_tainted;
-  state_t m_has_lb;
-  state_t m_has_ub;
-  state_t m_stop;
-};
-
-////////////////////////////////////////////////////////////////////////////
-
-/* A particular state machine, for prototyping purposes:
-   sensitive data.  */
-
-class sensitive_state_machine : public state_machine
-{
-public:
-  sensitive_state_machine (logger *logger);
-
-  bool inherited_state_p () const FINAL OVERRIDE { return true; }
-
-  bool on_stmt (sm_context *sm_ctxt,
-		const supernode *node,
-		const gimple *stmt) const FINAL OVERRIDE;
-
-  void on_edge (sm_context *sm_ctxt,
-		const gimple *last_stmt,
-		const superedge *edge) const FINAL OVERRIDE;
-
-  void on_leak (sm_context *sm_ctxt,
-		const supernode *node,
-		const gimple *stmt,
-		tree var,
-		state_machine::state_t state) const FINAL OVERRIDE;
-  bool can_purge_p (state_t s) const FINAL OVERRIDE;
-
-private:
-  void warn_for_any_exposure (sm_context *sm_ctxt,
-			      const supernode *node,
-			      const gimple *stmt,
-			      tree arg) const;
-
-  state_t m_start;
-  state_t m_sensitive;
-  state_t m_stop;
-};
-
-////////////////////////////////////////////////////////////////////////////
-
-/* A particular state machine, for prototyping purposes:
-   a pattern-matching test, for DejaGnu tests.  */
-
-class pattern_test_state_machine : public state_machine
-{
-public:
-  pattern_test_state_machine (logger *logger);
-
-  bool inherited_state_p () const FINAL OVERRIDE { return false; }
-
-  bool on_stmt (sm_context *sm_ctxt,
-		const supernode *node,
-		const gimple *stmt) const FINAL OVERRIDE;
-
-  void on_edge (sm_context *sm_ctxt,
-		const gimple *last_stmt,
-		const superedge *edge) const FINAL OVERRIDE;
-
-  void on_leak (sm_context *sm_ctxt,
-		const supernode *node,
-		const gimple *stmt,
-		tree var,
-		state_machine::state_t state) const FINAL OVERRIDE;
-  bool can_purge_p (state_t s) const FINAL OVERRIDE;
-
-private:
-  state_t m_start;
-};
-
-#endif /* GCC_ANALYZER_HARDCODED_STATE_MACHINE_H */
diff --git a/gcc/analyzer/impl-2.cc b/gcc/analyzer/impl-2.cc
index c62fa44..e0d8ec5 100644
--- a/gcc/analyzer/impl-2.cc
+++ b/gcc/analyzer/impl-2.cc
@@ -34,7 +34,7 @@
 #include "tree-dfa.h"
 #include <string>
 #include "supergraph.h"
-#include "hardcoded-state-machine.h"
+#include "sm.h"
 #include "analyzer-logging.h"
 #include "stringpool.h"
 #include "selftest.h"
diff --git a/gcc/analyzer/impl-3.cc b/gcc/analyzer/impl-3.cc
index 6453c42..3bfd44d 100644
--- a/gcc/analyzer/impl-3.cc
+++ b/gcc/analyzer/impl-3.cc
@@ -34,7 +34,7 @@
 #include "tree-dfa.h"
 #include <string>
 #include "supergraph.h"
-#include "hardcoded-state-machine.h"
+#include "sm.h"
 #include "analyzer-logging.h"
 #include "stringpool.h"
 #include "selftest.h"
diff --git a/gcc/analyzer/impl-4.cc b/gcc/analyzer/impl-4.cc
index 6e9aecc..411ecaa 100644
--- a/gcc/analyzer/impl-4.cc
+++ b/gcc/analyzer/impl-4.cc
@@ -34,7 +34,7 @@
 #include "tree-dfa.h"
 #include <string>
 #include "supergraph.h"
-#include "hardcoded-state-machine.h"
+#include "sm.h"
 #include "analyzer-logging.h"
 #include "stringpool.h"
 #include "selftest.h"
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index 3fbd1c5..53a58ff 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -44,7 +44,7 @@
 #include "tree-diagnostic.h"
 #include "diagnostic-color.h"
 #include "constraint-manager.h"
-#include "hardcoded-state-machine.h" // for pending_diagnostic
+#include "sm.h" // for pending_diagnostic
 
 ////////////////////////////////////////////////////////////////////////////
 
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index 961cf3b..a0bf2eb 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -21,7 +21,7 @@ along with GCC; see the file COPYING3.  If not see
 #ifndef GCC_ANALYZER_REGION_MODEL_H
 #define GCC_ANALYZER_REGION_MODEL_H
 
-#include "hardcoded-state-machine.h"
+#include "sm.h"
 
 /* Implemention of the region-based ternary model described in:
      "A Memory Model for Static Analysis of C Programs"
diff --git a/gcc/analyzer/sm-file.cc b/gcc/analyzer/sm-file.cc
new file mode 100644
index 0000000..b60312e
--- /dev/null
+++ b/gcc/analyzer/sm-file.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"
+#include "graphviz.h"
+#include "cgraph.h"
+#include "diagnostic-path.h"
+#include "gcc-rich-location.h"
+#include <string>
+#include "supergraph.h"
+#include "sm.h"
+
+static const bool DEBUG = false;
+//static const bool DEBUG = true;
+
+////////////////////////////////////////////////////////////////////////////
+
+class double_fclose : public pending_diagnostic_subclass<double_fclose>
+{
+public:
+  double_fclose (tree arg) : m_arg (arg) {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "double_fclose"; }
+  bool operator== (const double_fclose &other) const
+  {
+    return m_arg == other.m_arg;
+  }
+
+  void emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    warning_at (rich_loc, OPT_Wanalyzer_double_fclose,
+		"double %<fclose%> of FILE %qE",
+		m_arg);
+  }
+
+private:
+  tree m_arg;
+};
+
+class file_leak : public pending_diagnostic_subclass<file_leak>
+{
+public:
+  file_leak (tree arg) : m_arg (arg) {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "file_leak"; }
+  bool operator== (const file_leak &other) const
+  {
+    return m_arg == other.m_arg;
+  }
+
+  void emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    metadata m;
+    /* CWE-775: "Missing Release of File Descriptor or Handle after
+       Effective Lifetime". */
+    m.add_cwe (775);
+    warning_at (rich_loc, m, OPT_Wanalyzer_file_leak,
+		"leak of FILE %qE",
+		m_arg);
+  }
+
+private:
+  tree m_arg;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+fileptr_state_machine::fileptr_state_machine (logger *logger)
+: state_machine ("file", logger)
+{
+  // TODO: parse all of this from a file:
+  m_start = add_state ("start");
+  m_unchecked = add_state ("unchecked");
+  m_null = add_state ("null");
+  m_nonnull = add_state ("nonnull");
+  m_closed = add_state ("closed");
+  m_stop = add_state ("stop");
+}
+
+bool
+fileptr_state_machine::on_stmt (sm_context *sm_ctxt,
+				const supernode *node,
+				const gimple *stmt) const
+{
+  //tree fndecl = node->m_fun->decl;
+
+  // FIXME: do all of this from data
+  if (const gcall *call = dyn_cast <const gcall *> (stmt))
+    {
+      if (is_named_call_p (call, "fopen", 2))
+	{
+	  if (DEBUG)
+	    inform (stmt->location, "DBG: got fopen");
+	  tree lhs = gimple_call_lhs (call);
+	  if (lhs)
+	    {
+	      lhs = sm_ctxt->get_readable_tree (lhs);
+	      sm_ctxt->on_transition (node, stmt, lhs, m_start, m_unchecked);
+	    }
+	  else
+	    {
+	      /* TODO: report leak.  */
+	    }
+	  return true;
+	}
+
+      if (is_named_call_p (call, "fclose", 1))
+	{
+	  tree arg = gimple_call_arg (call, 0);
+	  if (DEBUG)
+	    inform (stmt->location, "DBG: got fclose");
+	  arg = sm_ctxt->get_readable_tree (arg);
+
+	  sm_ctxt->on_transition (node, stmt, arg, m_start, m_closed);
+
+	  // TODO: is it safe to call fclose (NULL) ?
+	  sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_closed);
+	  sm_ctxt->on_transition (node, stmt, arg, m_null, m_closed);
+
+	  sm_ctxt->on_transition (node, stmt , arg, m_nonnull, m_closed);
+
+	  sm_ctxt->warn_for_state (node, stmt, arg, m_closed,
+				   new double_fclose (arg));
+	  sm_ctxt->on_transition (node, stmt, arg, m_closed, m_stop);
+	  return true;
+	}
+
+      // TODO: operations on closed file
+      // etc
+    }
+  return false;
+}
+
+/* FIXME.  */
+
+void
+fileptr_state_machine::on_edge (sm_context *sm_ctxt,
+				const gimple *last_stmt,
+				const superedge *edge) const
+{
+  if (last_stmt == NULL)
+    return;
+
+  if (const gcond *cond_stmt = dyn_cast <const gcond *> (last_stmt))
+    {
+      tree arg;
+      enum tree_code op;
+      if (is_comparison_against_zero (cond_stmt, &arg, &op))
+	{
+	  // FIXME: has to be a FILE *, specifically
+	  if (TREE_CODE (TREE_TYPE (arg)) != POINTER_TYPE)
+	    return;
+
+	  ::edge cfg_edge = edge->get_any_cfg_edge ();
+	  gcc_assert (cfg_edge != NULL);
+	  gcc_assert (cfg_edge->flags & (EDGE_TRUE_VALUE | EDGE_FALSE_VALUE));
+	  
+	  if (op == NE_EXPR)
+	    {
+	      log ("got 'ARG != 0' match");
+	      sm_ctxt->on_transition (edge->m_src, last_stmt, arg, m_unchecked,
+				      (cfg_edge->flags & EDGE_TRUE_VALUE
+				       ? m_nonnull : m_null));
+	    }
+	  else if (op == EQ_EXPR)
+	    {
+	      log ("got 'ARG == 0' match");
+	      sm_ctxt->on_transition (edge->m_src, last_stmt, arg, m_unchecked,
+				      (cfg_edge->flags & EDGE_TRUE_VALUE
+				       ? m_null : m_nonnull));
+	    }
+	}
+    }
+
+  // TODO: something similar for switch statements?
+}
+
+void
+fileptr_state_machine::on_leak (sm_context *sm_ctxt,
+				const supernode *node,
+				const gimple *stmt,
+				tree var,
+				state_machine::state_t state) const
+{
+  sm_ctxt->warn_for_state (node, stmt, var, m_unchecked,
+			   new file_leak (var));
+  sm_ctxt->warn_for_state (node, stmt, var, m_nonnull,
+			   new file_leak (var));
+}
+
+bool
+fileptr_state_machine::can_purge_p (state_t s) const
+{
+  return s != m_unchecked && s != m_nonnull;
+}
diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc
new file mode 100644
index 0000000..fd83d3e
--- /dev/null
+++ b/gcc/analyzer/sm-malloc.cc
@@ -0,0 +1,710 @@
+/* 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 "sm.h"
+
+static const bool DEBUG = false;
+//static const bool DEBUG = true;
+
+////////////////////////////////////////////////////////////////////////////
+
+class malloc_diagnostic : public pending_diagnostic
+{
+public:
+  malloc_diagnostic (const malloc_state_machine &sm, tree arg)
+  : m_sm (sm), m_arg (arg)
+  {}
+
+  bool subclass_equal_p (const pending_diagnostic &base_other) const OVERRIDE
+  {
+    return m_arg == ((const malloc_diagnostic &)base_other).m_arg;
+  }
+
+protected:
+  const malloc_state_machine &m_sm;
+  tree m_arg;
+};
+
+class double_free : public malloc_diagnostic
+{
+public:
+  double_free (const malloc_state_machine &sm, tree arg)
+  : malloc_diagnostic (sm, arg)
+  {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "double_free"; }
+
+  void emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    auto_diagnostic_group d;
+    metadata m;
+    m.add_cwe (415); /* CWE-415: Double Free.  */
+    warning_at (rich_loc, m, OPT_Wanalyzer_double_free,
+		"double-free of %qE", m_arg);
+
+#if 0
+    if (!rich_loc->get_path ())
+      return;
+
+    event_id_t second_free_event
+      = rich_loc->get_path ()->get_final_event_id ();
+
+    if (m_first_free_event.known_p ()
+	&& second_free_event.known_p ())
+      inform (rich_loc->get_loc (),
+	      "pointer freed at (%i) was already freed at (%i)",
+	      second_free_event.one_based (),
+	      m_first_free_event.one_based ());
+#endif
+  }
+
+  label_text describe (const evdesc::state_change &change)
+    FINAL OVERRIDE
+  {
+    if (change.m_new_state == m_sm.m_free)
+      {
+	m_first_free_event = change.m_event_id;
+	return formatted_print ("first %qs here", "free");
+      }
+    return label_text ();
+  }
+
+  label_text describe (const evdesc::call_with_state &info)
+    FINAL OVERRIDE
+  {
+    if (info.m_state == m_sm.m_free)
+      return formatted_print
+	("passing freed pointer %qE in call to %qE from %qE",
+	 info.m_expr, info.m_callee_fndecl, info.m_caller_fndecl);
+    return label_text ();
+  }
+
+  label_text describe (const evdesc::final_event &) FINAL OVERRIDE
+  {
+    if (m_first_free_event.known_p ())
+      return formatted_print ("second %qs here; first %qs was at (%i)",
+			      "free", "free", m_first_free_event.one_based ());
+    return formatted_print ("second %qs here", "free");
+  }
+
+  /* FIXME: maybe also something like this.  */
+#if 0
+  label_test label_critical_state (int event_idx)
+  //FINAL OVERRIDE
+  {
+    return label_text::take (xvsprintf ("freed at (%i)"));
+  }
+#endif
+
+private:
+  event_id_t m_first_free_event;
+};
+
+/* Abstract subclass for describing possible bad uses of NULL.
+   Responsible for describing the call that could return NULL.  */
+
+class possible_null : public malloc_diagnostic
+{
+public:
+  possible_null (const malloc_state_machine &sm, tree arg)
+  : malloc_diagnostic (sm, arg)
+  {}
+
+  label_text describe (const evdesc::state_change &change)
+    FINAL OVERRIDE
+  {
+    if (change.m_old_state == m_sm.m_start
+	&& change.m_new_state == m_sm.m_unchecked)
+      return label_text::borrow ("this call could return NULL");
+    return label_text ();
+  }
+
+  label_text describe (const evdesc::return_of_state &info)
+    FINAL OVERRIDE
+  {
+    if (info.m_state == m_sm.m_unchecked)
+      return formatted_print ("possible return of NULL to %qE from %qE",
+			      info.m_caller_fndecl, info.m_callee_fndecl);
+    return label_text ();
+  }
+};
+
+/* Concrete subclass for describing dereference of a possible NULL
+   value.  */
+
+class possible_null_deref : public possible_null
+{
+public:
+  possible_null_deref (const malloc_state_machine &sm, tree arg)
+  : possible_null (sm, arg)
+  {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "possible_null_deref"; }
+
+  void emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    /* CWE-690: Unchecked Return Value to NULL Pointer Dereference.  */
+    metadata m;
+    m.add_cwe (690);
+    warning_at (rich_loc, m, OPT_Wanalyzer_possible_null_dereference,
+		"use of possibly-NULL %qE", m_arg);
+  }
+
+  /* FIXME.  */
+  label_text describe (const evdesc::final_event &ev) FINAL OVERRIDE
+  {
+    return formatted_print ("%qE could be NULL", ev.m_expr);
+  }
+
+};
+
+/* Subroutine for use by possible_null_arg::emit and null_arg::emit.
+   Issue a note informing that the pertinent argument must be non-NULL.  */
+
+static void
+inform_nonnull_attribute (tree fndecl, int arg_idx)
+{
+  inform (DECL_SOURCE_LOCATION (fndecl),
+	  "argument %u of %qD must be non-null",
+	  arg_idx + 1, fndecl);
+  /* FIXME: ideally, use the location of the parm
+     and underline the attribute also (but we probably don't have the
+     location_t...)  */
+
+  /* C and C++ FEs have get_fndecl_argument_location.  */
+}
+
+/* Concrete subclass for describing passing a possibly-NULL value to a
+   function marked with __attribute__((nonnull)).  */
+
+class possible_null_arg : public possible_null
+{
+public:
+  possible_null_arg (const malloc_state_machine &sm, tree arg,
+		     tree fndecl, int arg_idx)
+  : possible_null (sm, arg),
+    m_fndecl (fndecl), m_arg_idx (arg_idx)
+  {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "possible_null_arg"; }
+
+  bool subclass_equal_p (const pending_diagnostic &base_other) const
+  {
+    const possible_null_arg &sub_other
+      = (const possible_null_arg &)base_other;
+    return (m_arg == sub_other.m_arg
+	    && m_fndecl == sub_other.m_fndecl
+	    && m_arg_idx == sub_other.m_arg_idx);
+  }
+
+
+  void emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    /* CWE-690: Unchecked Return Value to NULL Pointer Dereference.  */
+    auto_diagnostic_group d;
+    metadata m;
+    m.add_cwe (690);
+    warning_at (rich_loc, m, OPT_Wanalyzer_possible_null_argument,
+		"use of possibly-NULL %qE where non-null expected",
+		m_arg);
+    inform_nonnull_attribute (m_fndecl, m_arg_idx);
+  }
+
+  /* FIXME.  */
+  label_text describe (const evdesc::final_event &ev) FINAL OVERRIDE
+  {
+    return formatted_print ("argument %u (%qE) could be NULL"
+			    " where non-null expected",
+			    m_arg_idx + 1, ev.m_expr);
+  }
+
+  // TODO
+
+private:
+  tree m_fndecl;
+  int m_arg_idx;
+};
+
+/* Concrete subclass for describing a dereference of a NULL value.  */
+
+class null_deref : public malloc_diagnostic
+{
+public:
+  null_deref (const malloc_state_machine &sm, tree arg)
+  : malloc_diagnostic (sm, arg) {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "null_deref"; }
+
+  void emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    /* CWE-690: Unchecked Return Value to NULL Pointer Dereference.  */
+    metadata m;
+    m.add_cwe (690);
+    warning_at (rich_loc, m, OPT_Wanalyzer_null_dereference,
+		"use of NULL %qE", m_arg);
+  }
+};
+
+/* Concrete subclass for describing passing a NULL value to a
+   function marked with __attribute__((nonnull)).  */
+
+class null_arg : public malloc_diagnostic
+{
+public:
+  null_arg (const malloc_state_machine &sm, tree arg,
+	    tree fndecl, int arg_idx)
+  : malloc_diagnostic (sm, arg),
+    m_fndecl (fndecl), m_arg_idx (arg_idx)
+  {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "null_arg"; }
+
+  bool subclass_equal_p (const pending_diagnostic &base_other) const
+  {
+    const null_arg &sub_other
+      = (const null_arg &)base_other;
+    return (m_arg == sub_other.m_arg
+	    && m_fndecl == sub_other.m_fndecl
+	    && m_arg_idx == sub_other.m_arg_idx);
+  }
+
+  void emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    /* CWE-690: Unchecked Return Value to NULL Pointer Dereference.  */
+    auto_diagnostic_group d;
+    metadata m;
+    m.add_cwe (690);
+    warning_at (rich_loc, m, OPT_Wanalyzer_null_argument,
+		"use of NULL %qE where non-null expected", m_arg);
+    inform_nonnull_attribute (m_fndecl, m_arg_idx);
+  }
+
+  /* FIXME.  */
+  label_text describe (const evdesc::final_event &ev) FINAL OVERRIDE
+  {
+    return formatted_print ("argument %u (%qE) NULL"
+			    " where non-null expected",
+			    m_arg_idx + 1, ev.m_expr);
+  }
+
+private:
+  tree m_fndecl;
+  int m_arg_idx;
+};
+
+class use_after_free : public malloc_diagnostic
+{
+public:
+  use_after_free (const malloc_state_machine &sm, tree arg)
+  : malloc_diagnostic (sm, arg)
+  {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "use_after_free"; }
+
+  void emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    /* CWE-416: Use After Free.  */
+    metadata m;
+    m.add_cwe (416);
+    warning_at (rich_loc, m, OPT_Wanalyzer_use_after_free,
+		"use after free of %qE", m_arg);
+  }
+
+  label_text describe (const evdesc::state_change &change)
+    FINAL OVERRIDE
+  {
+    if (change.m_new_state == m_sm.m_free)
+      return label_text::borrow ("freed here");
+    return label_text ();
+  }
+};
+
+class malloc_leak : public malloc_diagnostic
+{
+public:
+  malloc_leak (const malloc_state_machine &sm, tree arg)
+  : malloc_diagnostic (sm, arg) {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "malloc_leak"; }
+
+  void emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    metadata m;
+    m.add_cwe (401);
+    warning_at (rich_loc, m, OPT_Wanalyzer_malloc_leak,
+		"leak of %qE", m_arg);
+  }
+
+  label_text describe (const evdesc::state_change &change)
+    FINAL OVERRIDE
+  {
+    if (change.m_new_state == m_sm.m_unchecked)
+      {
+	m_malloc_event = change.m_event_id;
+	return label_text::borrow ("allocated here");
+      }
+    return label_text ();
+  }
+
+  label_text describe (const evdesc::final_event &ev) FINAL OVERRIDE
+  {
+    if (m_malloc_event.known_p ())
+      return formatted_print ("%qE leaks here; was allocated at (%i)",
+			      ev.m_expr, m_malloc_event.one_based ());
+    return formatted_print ("%qE leaks here", ev.m_expr);
+  }
+
+private:
+  event_id_t m_malloc_event;
+};
+
+class free_of_non_heap : public malloc_diagnostic
+{
+public:
+  free_of_non_heap (const malloc_state_machine &sm, tree arg)
+  : malloc_diagnostic (sm, arg), m_kind (KIND_UNKNOWN)
+  {
+  }
+
+  const char *get_kind () const FINAL OVERRIDE { return "free_of_non_heap"; }
+
+  bool subclass_equal_p (const pending_diagnostic &base_other) const
+    FINAL OVERRIDE
+  {
+    const free_of_non_heap &other = (const free_of_non_heap &)base_other;
+    return (m_arg == other.m_arg && m_kind == other.m_kind);
+  }
+
+  void emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    auto_diagnostic_group d;
+    metadata m;
+    m.add_cwe (590); /* CWE-590: Free of Memory not on the Heap.  */
+    switch (m_kind)
+      {
+      default:
+	gcc_unreachable ();
+      case KIND_UNKNOWN:
+	warning_at (rich_loc, m, OPT_Wanalyzer_free_of_non_heap,
+		    "free of %qE which points to memory not on the heap",
+		    m_arg);
+	break;
+      case KIND_ALLOCA:
+	warning_at (rich_loc, m, OPT_Wanalyzer_free_of_non_heap,
+		    "free of memory allocated on the stack by %qs (%qE)"
+		    " will corrupt the heap",
+		    "alloca", m_arg);
+	break;
+      }
+  }
+
+  label_text describe (const evdesc::state_change &change)
+    FINAL OVERRIDE
+  {
+    /* Attempt to reconstruct what kind of pointer it is.
+       (It seems neater for this to be a part of the state, though).  */
+    if (TREE_CODE (change.m_expr) == SSA_NAME)
+      {
+	gimple *def_stmt = SSA_NAME_DEF_STMT (change.m_expr);
+	if (gcall *call = dyn_cast <gcall *> (def_stmt))
+	  {
+	    if (is_named_call_p (call, "alloca", 1)
+		|| is_named_call_p (call, "__builtin_alloca", 1))
+	      {
+		m_kind = KIND_ALLOCA;
+		return label_text::borrow
+		  ("memory is allocated on the stack here");
+	      }
+	  }
+      }
+    return label_text::borrow ("pointer is from here");
+  }
+
+  /* FIXME.  */
+  label_text describe (const evdesc::final_event &) FINAL OVERRIDE
+  {
+    return formatted_print ("call to %qs here", "free");
+  }
+
+private:
+  enum kind
+  {
+    KIND_UNKNOWN,
+    KIND_ALLOCA
+  };
+  enum kind m_kind;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+malloc_state_machine::malloc_state_machine (logger *logger)
+: state_machine ("malloc", logger)
+{
+  // TODO: parse all of this from a file:
+  m_start = add_state ("start");
+  m_unchecked = add_state ("unchecked");
+  m_null = add_state ("null");
+  m_nonnull = add_state ("nonnull");
+  m_free = add_state ("free");
+  m_non_heap = add_state ("non-heap");
+  m_stop = add_state ("stop");
+}
+
+bool
+malloc_state_machine::on_stmt (sm_context *sm_ctxt,
+			       const supernode *node,
+			       const gimple *stmt) const
+{
+  //tree fndecl = node->m_fun->decl;
+
+  // FIXME: do all of this from data
+  if (const gcall *call = dyn_cast <const gcall *> (stmt))
+    {
+      if (is_named_call_p (call, "malloc", 1)
+	  || is_named_call_p (call, "calloc", 2))
+	{
+	  if (DEBUG)
+	    inform (stmt->location, "DBG: got malloc");
+	  tree lhs = gimple_call_lhs (call);
+	  if (lhs)
+	    {
+	      lhs = sm_ctxt->get_readable_tree (lhs);
+	      sm_ctxt->on_transition (node, stmt, lhs, m_start, m_unchecked);
+	    }
+	  else
+	    {
+	      /* TODO: report leak.  */
+	    }
+	  return true;
+	}
+
+      if (is_named_call_p (call, "alloca", 1)
+	  || is_named_call_p (call, "__builtin_alloca", 1))
+	{
+	  tree lhs = gimple_call_lhs (call);
+	  if (lhs)
+	    {
+	      lhs = sm_ctxt->get_readable_tree (lhs);
+	      sm_ctxt->on_transition (node, stmt, lhs, m_start, m_non_heap);
+	    }
+	  return true;
+	}
+
+      if (is_named_call_p (call, "free", 1))
+	{
+	  tree arg = gimple_call_arg (call, 0);
+	  if (DEBUG)
+	    inform (stmt->location, "DBG: got free");
+
+	  arg = sm_ctxt->get_readable_tree (arg);
+
+	  /* start/unchecked/nonnull -> free.  */
+	  sm_ctxt->on_transition (node, stmt, arg, m_start, m_free);
+	  sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_free);
+	  sm_ctxt->on_transition (node, stmt, arg, m_nonnull, m_free);
+
+	  /* Keep state "null" as-is, rather than transitioning to "free";
+	     we don't want want to complain about double-free of NULL.  */
+
+	  /* free -> stop, with warning.  */
+	  sm_ctxt->warn_for_state (node, stmt, arg, m_free,
+				   new double_free (*this, arg));
+	  sm_ctxt->on_transition (node, stmt, arg, m_free, m_stop);
+
+	  /* non-heap -> stop, with warning.  */
+	  sm_ctxt->warn_for_state (node, stmt, arg, m_non_heap,
+				   new free_of_non_heap (*this, arg));
+	  sm_ctxt->on_transition (node, stmt, arg, m_non_heap, m_stop);
+	  return true;
+	}
+
+      /* TODO: handle "__attribute__((nonnull))".   */
+      if (tree callee_fndecl = gimple_call_fndecl (stmt))
+	{
+	  tree fntype = TREE_TYPE (callee_fndecl);
+	  bitmap nonnull_args = get_nonnull_args (fntype);
+	  if (nonnull_args)
+	    {
+	      for (unsigned i = 0; i < gimple_call_num_args (stmt); i++)
+		{
+		  tree arg = gimple_call_arg (stmt, i);
+		  if (TREE_CODE (TREE_TYPE (arg)) != POINTER_TYPE)
+		    continue;
+		  /* If we have a nonnull-args, and either all pointers, or just
+		     the specified pointers.  */
+		  if (bitmap_empty_p (nonnull_args)
+		      || bitmap_bit_p (nonnull_args, i))
+		    {
+		      sm_ctxt->warn_for_state
+			(node, stmt, arg, m_unchecked,
+			 new possible_null_arg (*this, arg, callee_fndecl, i));
+		      sm_ctxt->on_transition (node, stmt, arg, m_unchecked,
+					      m_nonnull);
+
+		      sm_ctxt->warn_for_state
+			(node, stmt, arg, m_null,
+			 new null_arg (*this, arg, callee_fndecl, i));
+		      sm_ctxt->on_transition (node, stmt, arg, m_null, m_stop);
+		    }
+		}
+	      BITMAP_FREE (nonnull_args);
+	    }
+	}
+    }
+
+  /* FIXME: It's not clear if we need this, due to the SSA representation:
+     each assignment creates a new SSA_NAME.  */
+  if (tree lhs = is_zero_assignment (stmt))
+    {
+      if (any_pointer_p (lhs))
+	{
+	  sm_ctxt->on_transition (node, stmt, lhs, m_start, m_null);
+	  sm_ctxt->on_transition (node, stmt, lhs, m_unchecked, m_null);
+	  sm_ctxt->on_transition (node, stmt, lhs, m_nonnull, m_null);
+	  sm_ctxt->on_transition (node, stmt, lhs, m_free, m_null);
+	}
+    }
+
+  if (const gassign *assign_stmt = dyn_cast <const gassign *> (stmt))
+    {
+      enum tree_code op = gimple_assign_rhs_code (assign_stmt);
+      if (op == ADDR_EXPR)
+	{
+	  tree lhs = gimple_assign_lhs (assign_stmt);
+	  if (lhs)
+	    {
+	      lhs = sm_ctxt->get_readable_tree (lhs);
+	      sm_ctxt->on_transition (node, stmt, lhs, m_start, m_non_heap);
+	    }
+	}
+    }
+
+  // TODO: dereferences
+  for (unsigned i = 0; i < gimple_num_ops (stmt); i++)
+    {
+      tree op = gimple_op (stmt, i);
+#if 0
+      if (op)
+	inform (stmt->location, "op %i: %qE", i, op);
+      else
+	inform (stmt->location, "op %i: NULL_TREE", i);
+#endif
+      if (!op)
+	continue;
+      if (TREE_CODE (op) == COMPONENT_REF)
+	op = TREE_OPERAND (op, 0);
+
+      if (TREE_CODE (op) == MEM_REF)
+	{
+	  tree arg = TREE_OPERAND (op, 0);
+	  arg = sm_ctxt->get_readable_tree (arg);
+
+	  sm_ctxt->warn_for_state (node, stmt, arg, m_unchecked,
+				   new possible_null_deref (*this, arg));
+	  sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_nonnull);
+
+	  sm_ctxt->warn_for_state (node, stmt, arg, m_null,
+				   new null_deref (*this, arg));
+	  sm_ctxt->on_transition (node, stmt, arg, m_null, m_stop);
+
+	  sm_ctxt->warn_for_state (node, stmt, arg, m_free,
+				   new use_after_free (*this, arg));
+	  sm_ctxt->on_transition (node, stmt, arg, m_free, m_stop);
+	}
+    }
+  return false;
+}
+
+/* FIXME.  */
+
+void
+malloc_state_machine::on_edge (sm_context *sm_ctxt,
+			       const gimple *last_stmt,
+			       const superedge *edge) const
+{
+  if (last_stmt == NULL)
+    return;
+
+  if (const gcond *cond_stmt = dyn_cast <const gcond *> (last_stmt))
+    {
+      tree arg;
+      enum tree_code op;
+      if (is_comparison_against_zero (cond_stmt, &arg, &op))
+	{
+	  if (!any_pointer_p (arg))
+	    return;
+
+	  ::edge cfg_edge = edge->get_any_cfg_edge ();
+	  gcc_assert (cfg_edge != NULL);
+	  gcc_assert (cfg_edge->flags & (EDGE_TRUE_VALUE | EDGE_FALSE_VALUE));
+	  
+	  if (op == NE_EXPR)
+	    {
+	      log ("got 'ARG != 0' match");
+	      sm_ctxt->on_transition (edge->m_src, last_stmt, arg, m_unchecked,
+				      (cfg_edge->flags & EDGE_TRUE_VALUE
+				       ? m_nonnull : m_null));
+	    }
+	  else if (op == EQ_EXPR)
+	    {
+	      log ("got 'ARG == 0' match");
+	      sm_ctxt->on_transition (edge->m_src, last_stmt, arg, m_unchecked,
+				      (cfg_edge->flags & EDGE_TRUE_VALUE
+				       ? m_null : m_nonnull));
+	    }
+	}
+    }
+
+  // TODO: something similar for switch statements?
+}
+
+void
+malloc_state_machine::on_leak (sm_context *sm_ctxt,
+			       const supernode *node,
+			       const gimple *stmt,
+			       tree var,
+			       state_machine::state_t state) const
+{
+  sm_ctxt->warn_for_state (node, stmt, var, m_unchecked,
+			   new malloc_leak (*this, var));
+  sm_ctxt->warn_for_state (node, stmt, var, m_nonnull,
+			   new malloc_leak (*this, var));
+}
+
+bool
+malloc_state_machine::can_purge_p (state_t s) const
+{
+  return s != m_unchecked && s != m_nonnull;
+}
diff --git a/gcc/analyzer/sm-pattern-test.cc b/gcc/analyzer/sm-pattern-test.cc
new file mode 100644
index 0000000..5f9bfd3
--- /dev/null
+++ b/gcc/analyzer/sm-pattern-test.cc
@@ -0,0 +1,151 @@
+/* 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 "sm.h"
+
+static const bool DEBUG = false;
+//static const bool DEBUG = true;
+
+////////////////////////////////////////////////////////////////////////////
+
+class pattern_match : public pending_diagnostic_subclass<pattern_match>
+{
+public:
+  pattern_match (tree arg, const char *msg) : m_arg (arg), m_msg (msg) {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "pattern_match"; }
+
+  bool operator== (const pattern_match &other) const
+  {
+    return (m_arg == other.m_arg
+	    // FIXME: should this be string equality?
+	    && m_msg == other.m_msg);
+  }
+
+  void emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    // TODO:
+    warning_at (rich_loc, 0, "pattern match on %qE: %s", m_arg, m_msg);
+  }
+
+private:
+  tree m_arg;
+  const char *m_msg;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+pattern_test_state_machine::pattern_test_state_machine (logger *logger)
+: state_machine ("pattern-test", logger)
+{
+  // TODO: parse all of this from a file:
+  m_start = add_state ("start");
+}
+
+bool
+pattern_test_state_machine::on_stmt (sm_context *sm_ctxt ATTRIBUTE_UNUSED,
+				     const supernode *node ATTRIBUTE_UNUSED,
+				     const gimple *stmt ATTRIBUTE_UNUSED) const
+{
+  //tree fndecl = node->m_fun->decl;
+
+#if 0
+  // FIXME: do all of this from data
+  if (const gcall *call = dyn_cast <const gcall *> (stmt))
+    {
+    }
+#endif
+  return false;
+}
+
+/* FIXME.  */
+
+void
+pattern_test_state_machine::on_edge (sm_context *sm_ctxt,
+				     const gimple *last_stmt,
+				     const superedge *edge) const
+{
+  if (last_stmt == NULL)
+    return;
+
+  const supernode *node = edge->m_src;
+
+  if (const gcond *cond_stmt = dyn_cast <const gcond *> (last_stmt))
+    {
+      tree arg;
+      enum tree_code op;
+      if (is_comparison_against_zero (cond_stmt, &arg, &op))
+	{
+	  ::edge cfg_edge = edge->get_any_cfg_edge ();
+	  gcc_assert (cfg_edge != NULL);
+	  gcc_assert (cfg_edge->flags & (EDGE_TRUE_VALUE | EDGE_FALSE_VALUE));
+
+	  bool is_true = cfg_edge->flags & EDGE_TRUE_VALUE;
+
+	  if (op == NE_EXPR)
+	    {
+	      pending_diagnostic *diag
+		= new pattern_match (arg,
+				     is_true ? "!= 0" : "== 0");
+	      sm_ctxt->warn_for_state (node, last_stmt, arg, m_start, diag);
+	    }
+	  else if (op == EQ_EXPR)
+	    {
+	      pending_diagnostic *diag
+		= new pattern_match (arg,
+				     is_true ? "== 0" : "!= 0");
+	      sm_ctxt->warn_for_state (node, last_stmt, arg, m_start, diag);
+	    }
+	}
+    }
+}
+
+void
+pattern_test_state_machine::on_leak (sm_context *sm_ctxt ATTRIBUTE_UNUSED,
+				     const supernode *node ATTRIBUTE_UNUSED,
+				     const gimple *stmt ATTRIBUTE_UNUSED,
+				     tree var ATTRIBUTE_UNUSED,
+				     state_machine::state_t state
+				       ATTRIBUTE_UNUSED)
+  const
+{
+  /* Empty.  */
+}
+
+bool
+pattern_test_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const
+{
+  return true;
+}
diff --git a/gcc/analyzer/sm-sensitive.cc b/gcc/analyzer/sm-sensitive.cc
new file mode 100644
index 0000000..6330cca
--- /dev/null
+++ b/gcc/analyzer/sm-sensitive.cc
@@ -0,0 +1,153 @@
+/* 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 "sm.h"
+
+static const bool DEBUG = false;
+//static const bool DEBUG = true;
+
+////////////////////////////////////////////////////////////////////////////
+
+class exposure_through_output_file
+  : public pending_diagnostic_subclass<exposure_through_output_file>
+{
+public:
+  exposure_through_output_file (tree arg) : m_arg (arg) {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "exposure_through_output_file"; }
+
+  bool operator== (const exposure_through_output_file &other) const
+  {
+    return m_arg == other.m_arg;
+  }
+
+  void emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    metadata m;
+    /* CWE-532: Information Exposure Through Log Files */
+    m.add_cwe (532);
+    warning_at (rich_loc, m, OPT_Wanalyzer_exposure_through_output_file,
+		"sensitive value %qE written to output file",
+		m_arg);
+  }
+
+private:
+  tree m_arg;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+sensitive_state_machine::sensitive_state_machine (logger *logger)
+: state_machine ("sensitive", logger)
+{
+  // TODO: parse all of this from a file:
+  m_start = add_state ("start");
+  m_sensitive = add_state ("sensitive");
+  m_stop = add_state ("stop");
+}
+
+void
+sensitive_state_machine::warn_for_any_exposure (sm_context *sm_ctxt,
+						const supernode *node,
+						const gimple *stmt,
+						tree arg) const
+{
+  sm_ctxt->warn_for_state (node, stmt, arg, m_sensitive,
+			   new exposure_through_output_file (arg));
+}
+
+bool
+sensitive_state_machine::on_stmt (sm_context *sm_ctxt,
+				  const supernode *node,
+				  const gimple *stmt) const
+{
+  //tree fndecl = node->m_fun->decl;
+
+  // FIXME: do all of this from data
+  if (const gcall *call = dyn_cast <const gcall *> (stmt))
+    {
+      if (is_named_call_p (call, "getpass", 1))
+	{
+	  tree lhs = gimple_call_lhs (call);
+	  if (lhs)
+	    sm_ctxt->on_transition (node, stmt, lhs, m_start, m_sensitive);
+	  return true;
+	}
+      else if (is_named_call_p (call, "fprintf")
+	       || is_named_call_p (call, "printf"))
+	{
+	  /* Handle a match at any position in varargs.  */
+	  for (int idx = 1; idx < gimple_call_num_args (call); idx++)
+	    {
+	      tree arg = gimple_call_arg (call, idx);
+	      warn_for_any_exposure (sm_ctxt, node, stmt, arg);
+	    }
+	  return true;
+	}
+      else if (is_named_call_p (call, "fwrite", 4))
+	{
+	  tree arg = gimple_call_arg (call, 0);
+	  warn_for_any_exposure (sm_ctxt, node, stmt, arg);
+	  return true;
+	}
+    }
+  return false;
+}
+
+/* FIXME.  */
+
+void
+sensitive_state_machine::on_edge (sm_context *sm_ctxt,
+				  const gimple *last_stmt,
+				  const superedge *edge) const
+{
+  /* Empty.  */
+}
+
+void
+sensitive_state_machine::on_leak (sm_context *sm_ctxt,
+				  const supernode *node,
+				  const gimple *stmt,
+				  tree var,
+				  state_machine::state_t state) const
+{
+  /* Empty.  */
+}
+
+bool
+sensitive_state_machine::can_purge_p (state_t s) const
+{
+  return true;
+}
diff --git a/gcc/analyzer/sm-taint.cc b/gcc/analyzer/sm-taint.cc
new file mode 100644
index 0000000..4e3a46b
--- /dev/null
+++ b/gcc/analyzer/sm-taint.cc
@@ -0,0 +1,234 @@
+/* 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 "sm.h"
+
+static const bool DEBUG = false;
+//static const bool DEBUG = true;
+
+////////////////////////////////////////////////////////////////////////////
+
+class tainted_array_index : public pending_diagnostic_subclass<tainted_array_index>
+{
+public:
+  tainted_array_index (tree arg) : m_arg (arg) {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "tainted_array_index"; }
+
+  bool operator== (const tainted_array_index &other) const
+  {
+    return m_arg == other.m_arg;
+  }
+
+  void emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    metadata m;
+    m.add_cwe (129);
+    warning_at (rich_loc, m, OPT_Wanalyzer_tainted_array_index,
+		"use of tainted value %qE in array lookup without bounds checking",
+		m_arg);
+  }
+
+private:
+  tree m_arg;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+taint_state_machine::taint_state_machine (logger *logger)
+: state_machine ("taint", logger)
+{
+  // TODO: parse all of this from a file:
+  m_start = add_state ("start");
+  m_tainted = add_state ("tainted");
+  m_has_lb = add_state ("has_lb");
+  m_has_ub = add_state ("has_ub");
+  m_stop = add_state ("stop");
+}
+
+bool
+taint_state_machine::on_stmt (sm_context *sm_ctxt,
+			       const supernode *node,
+			       const gimple *stmt) const
+{
+  //tree fndecl = node->m_fun->decl;
+
+  // FIXME: do all of this from data
+  if (const gcall *call = dyn_cast <const gcall *> (stmt))
+    {
+      if (is_named_call_p (call, "fread", 4))
+	{
+	  if (DEBUG)
+	    inform (stmt->location, "DBG: got taint");
+	  tree arg = gimple_call_arg (call, 0);
+	  arg = sm_ctxt->get_readable_tree (arg);
+
+	  sm_ctxt->on_transition (node, stmt, arg, m_start, m_tainted);
+
+	  // FIXME: dereference an ADDR_EXPR:
+	  // FIXME: should the engine do this?
+	  if (TREE_CODE (arg) == ADDR_EXPR)
+	    sm_ctxt->on_transition (node, stmt, TREE_OPERAND (arg, 0),
+				    m_start, m_tainted);
+	  return true;
+	}
+    }
+
+#if 0
+  /* FIXME: It's not clear if we need this, due to the SSA representation:
+     each assignment creates a new SSA_NAME.  */
+  if (tree lhs = is_zero_assignment (stmt))
+    {
+      if (any_pointer_p (lhs))
+	{
+	  sm_ctxt->on_transition (node, stmt, lhs, m_start, m_null);
+	  sm_ctxt->on_transition (node, stmt, lhs, m_unchecked, m_null);
+	  sm_ctxt->on_transition (node, stmt, lhs, m_nonnull, m_null);
+	  sm_ctxt->on_transition (node, stmt, lhs, m_free, m_null);
+	}
+    }
+#endif
+
+  if (const gassign *assign = dyn_cast <const gassign *> (stmt))
+    {
+      tree lhs = gimple_assign_lhs (assign);
+      tree rhs1 = gimple_assign_rhs1 (assign);
+      enum tree_code op = gimple_assign_rhs_code (assign);
+      if (op == ARRAY_REF)
+	{
+	  tree arg = TREE_OPERAND (rhs1, 1);
+	  arg = sm_ctxt->get_readable_tree (arg);
+	  sm_ctxt->warn_for_state  (node, stmt, arg, m_tainted,
+				    new tainted_array_index (arg));
+	  sm_ctxt->on_transition (node, stmt, arg, m_tainted, m_stop);
+	}
+    }
+
+#if 0
+  // TODO: dereferences
+  for (unsigned i = 0; i < gimple_num_ops (stmt); i++)
+    {
+      tree op = gimple_op (stmt, i);
+#if 0
+      if (op)
+	inform (stmt->location, "op %i: %qE", i, op);
+      else
+	inform (stmt->location, "op %i: NULL_TREE", i);
+#endif
+      if (!op)
+	continue;
+      if (TREE_CODE (op) == MEM_REF)
+	{
+	  tree arg = TREE_OPERAND (op, 0);
+	  sm_ctxt->warn_for_state (node, stmt, arg, m_unchecked,
+				   "use of possibly-NULL %qE", arg);
+	  sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_nonnull);
+
+	  sm_ctxt->warn_for_state (node, stmt, arg, m_null,
+				   "use of NULL %qE", arg);
+	  sm_ctxt->on_transition (node, stmt, arg, m_null, m_stop);
+
+	  sm_ctxt->warn_for_state (node, stmt, arg, m_free,
+				   "use after free of %qE", arg);
+	  sm_ctxt->on_transition (node, stmt, arg, m_free, m_stop);
+	}
+    }
+#endif
+  return false;
+}
+
+/* FIXME.  */
+
+void
+taint_state_machine::on_edge (sm_context *sm_ctxt,
+			       const gimple *last_stmt,
+			       const superedge *edge) const
+{
+  if (last_stmt == NULL)
+    return;
+
+  if (const gcond *cond_stmt = dyn_cast <const gcond *> (last_stmt))
+    {
+      enum tree_code op = gimple_cond_code (cond_stmt);
+      tree lhs = gimple_cond_lhs (cond_stmt);
+      tree rhs = gimple_cond_rhs (cond_stmt);
+      // TODO: make it symmetric?
+
+      switch (op)
+	{
+	  //case NE_EXPR:
+	  //case EQ_EXPR:
+	case GE_EXPR:
+	case GT_EXPR:
+	  {
+	    sm_ctxt->on_transition (edge->m_src, last_stmt, lhs, m_tainted,
+				    m_has_lb);
+	    sm_ctxt->on_transition (edge->m_src, last_stmt, lhs, m_has_ub,
+				    m_stop);
+	  }
+	  break;
+	case LE_EXPR:
+	case LT_EXPR:
+	  {
+	    sm_ctxt->on_transition (edge->m_src, last_stmt, lhs, m_tainted,
+				    m_has_ub);
+	    sm_ctxt->on_transition (edge->m_src, last_stmt, lhs, m_has_lb,
+				    m_stop);
+	  }
+	  break;
+
+	default:
+	  break;
+	}
+    }
+
+  // TODO: something similar for switch statements?
+}
+
+void
+taint_state_machine::on_leak (sm_context *sm_ctxt,
+			       const supernode *node,
+			       const gimple *stmt,
+			       tree var,
+			       state_machine::state_t state) const
+{
+  /* Empty.  */
+}
+
+bool
+taint_state_machine::can_purge_p (state_t s) const
+{
+  return true;
+}
diff --git a/gcc/analyzer/sm.cc b/gcc/analyzer/sm.cc
new file mode 100644
index 0000000..bb98bb8
--- /dev/null
+++ b/gcc/analyzer/sm.cc
@@ -0,0 +1,169 @@
+/* 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 "sm.h"
+
+static const bool DEBUG = false;
+//static const bool DEBUG = true;
+
+////////////////////////////////////////////////////////////////////////////
+
+/* If STMT is an assignment to zero, return the LHS.  */
+
+tree
+is_zero_assignment (const gimple *stmt)
+{
+  const gassign *assign_stmt = dyn_cast <const gassign *> (stmt);
+  if (!assign_stmt)
+    return NULL_TREE;
+
+  enum tree_code op = gimple_assign_rhs_code (assign_stmt);
+  if (op != INTEGER_CST)
+    return NULL_TREE;
+
+  if (!zerop (gimple_assign_rhs1 (assign_stmt)))
+    return NULL_TREE;
+
+  return gimple_assign_lhs (assign_stmt);
+}
+
+/* FIXME.  */
+bool
+is_comparison_against_zero (const gcond *cond_stmt,
+			    tree *out_lhs, enum tree_code *out_op)
+{
+  enum tree_code op = gimple_cond_code (cond_stmt);
+  tree lhs = gimple_cond_lhs (cond_stmt);
+  tree rhs = gimple_cond_rhs (cond_stmt);
+  if (!zerop (rhs))
+    return false;
+  // TODO: make it symmetric?
+
+  switch (op)
+    {
+    case NE_EXPR:
+    case EQ_EXPR:
+      *out_lhs = lhs;
+      *out_op = op;
+      return true;
+
+    default:
+      return false;
+    }
+}
+
+bool
+any_pointer_p (tree var)
+{
+  if (TREE_CODE (TREE_TYPE (var)) != POINTER_TYPE)
+    return false;
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+state_machine::state_t
+state_machine::add_state (const char *name)
+{
+  m_state_names.safe_push (name);
+  return m_state_names.length () - 1;
+}
+
+const char *
+state_machine::get_state_name (state_t s) const
+{
+  return m_state_names[s];
+}
+
+void
+state_machine::validate (state_t s) const
+{
+  gcc_assert (s < m_state_names.length ());
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+label_text
+pending_diagnostic::formatted_print (const char *fmt, ...)
+{
+  pretty_printer *pp = global_dc->printer->clone ();
+
+  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);
+
+  label_text result = label_text::take (xstrdup (pp_formatted_text (pp)));
+  delete pp;
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+void
+make_checkers (auto_delete_vec <state_machine> &out, logger *logger)
+{
+  out.safe_push (new malloc_state_machine (logger));
+  out.safe_push (new fileptr_state_machine (logger));
+  out.safe_push (new taint_state_machine (logger));
+  out.safe_push (new sensitive_state_machine (logger));
+
+  /* We only attempt to run the pattern tests if it might have been manually
+     enabled (for DejaGnu purposes).  */
+  if (flag_analyzer_checker)
+    out.safe_push (new pattern_test_state_machine (logger));
+
+  if (flag_analyzer_checker)
+    {
+      int read_index, write_index;
+      state_machine **sm;
+
+      // FIXME: this leaks the machines
+      // FIXME: would be nice to log the things that were removed
+      VEC_ORDERED_REMOVE_IF (out, read_index, write_index, sm,
+			     0 != strcmp (flag_analyzer_checker,
+					  (*sm)->get_name ()));
+    }
+
+}
diff --git a/gcc/analyzer/sm.h b/gcc/analyzer/sm.h
new file mode 100644
index 0000000..9577f09
--- /dev/null
+++ b/gcc/analyzer/sm.h
@@ -0,0 +1,554 @@
+/* FIXME.
+   Copyright (C) 2019 Free Software Foundation, Inc.
+   Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#ifndef GCC_ANALYZER_HARDCODED_STATE_MACHINE_H
+#define GCC_ANALYZER_HARDCODED_STATE_MACHINE_H
+
+#include "analyzer-logging.h"
+#include "diagnostic-metadata.h"
+
+////////////////////////////////////////////////////////////////////////////
+
+extern tree is_zero_assignment (const gimple *stmt);
+extern bool is_comparison_against_zero (const gcond *cond_stmt,
+					tree *out_lhs, enum tree_code *out_op);
+extern bool any_pointer_p (tree var);
+
+////////////////////////////////////////////////////////////////////////////
+
+class state_machine;
+class sm_context;
+
+/* A state machine describing an API.
+   A mapping from state IDs to names, and (for now) a vfunc implementation
+   for pattern-matching on statements.  */
+
+class state_machine : public log_user
+{
+public:
+  typedef int state_t;
+
+  state_machine (const char *name, logger *logger)
+  : log_user (logger), m_name (name) {}
+
+  virtual ~state_machine () {}
+
+  /* Should states be inherited from a parent region to a child region,
+     when first accessing a child region?
+     For example we should inherit the taintedness of a subregion,
+     but we should not inherit the "malloc:non-null" state of a field
+     within a heap-allocated struct.  */
+  virtual bool inherited_state_p () const = 0;
+
+  const char *get_name () const { return m_name; }
+
+  const char *get_state_name (state_t s) const;
+
+  /* Return true if STMT is a function call recognized by this sm.  */
+  virtual bool on_stmt (sm_context *sm_ctxt,
+			const supernode *node,
+			const gimple *stmt) const = 0;
+
+  virtual void on_edge (sm_context *sm_ctxt,
+			const gimple *last_stmt,
+			const superedge *edge) const = 0;
+
+  virtual void on_leak (sm_context *sm_ctxt,
+			const supernode *node,
+			const gimple *stmt,
+			tree var,
+			state_machine::state_t state) const = 0;
+
+  /* Return true if it safe to discard the given state (to help
+     when simplifying state objects).
+     States that need leak detection should return false.  */
+  virtual bool can_purge_p (state_t s) const = 0;
+
+  void validate (state_t s) const;
+
+protected:
+  state_t add_state (const char *name);
+
+private:
+  const char *m_name;
+  auto_vec<const char *> m_state_names;
+};
+
+/* FIXME.  */
+
+static inline bool
+start_start_p (state_machine::state_t state)
+{
+  return state == 0;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+// TODO: maybe capture which sm emitted the error
+
+class metadata : public diagnostic_metadata
+{
+ public:
+
+ private:
+};
+
+/* FIXME.  */
+
+class event_id_t
+{
+ public:
+  event_id_t () : m_index (UNKNOWN_EVENT_IDX) {}
+  event_id_t (int zero_based_idx) : m_index (zero_based_idx) {}
+
+  bool known_p () const { return m_index != UNKNOWN_EVENT_IDX; }
+
+  int one_based () const
+  {
+    gcc_assert (known_p ());
+    return m_index + 1;
+  }
+
+ private:
+  static const int UNKNOWN_EVENT_IDX = -1;
+  int m_index; // zero-based
+};
+
+/* Various bundles of information used for generating more precise
+   messages for events within a diagnostic_path, for passing to
+   "describe" vfuncs of pending_diagnostic.  */
+
+namespace evdesc {
+
+/* Information for describing a critical state change within the
+   diagnostic_path.
+
+   For example, a double-free diagnostic might use the descriptions:
+   - "first 'free' happens here"
+   - "second 'free' happens here"
+   for the pertinent events, whereas a use-after-free might use the
+   descriptions:
+   - "freed here"
+   - "use after free here"
+   Note how in both cases the first event is a "free": the best
+   description to use depends on the diagnostic.  */
+
+struct state_change
+{
+  state_change (tree expr,
+		state_machine::state_t old_state,
+		state_machine::state_t new_state,
+		event_id_t event_id)
+  : m_expr (expr), m_old_state (old_state),
+    m_new_state (new_state), m_event_id (event_id)
+  {}
+
+  tree m_expr;
+  state_machine::state_t m_old_state;
+  state_machine::state_t m_new_state;
+  event_id_t m_event_id;
+};
+
+/* Information for describing an interprocedural call carrying
+   critial state for the diagnostic, from caller to callee.
+
+   For example a double-free diagnostic might use:
+     "passing freed pointer 'ptr' in call to 'deallocator' from 'test'"
+   to make it clearer how the unchecked value moves from caller
+   to callee.  */
+
+struct call_with_state
+{
+  call_with_state (tree caller_fndecl, tree callee_fndecl,
+		   tree expr, state_machine::state_t state)
+  : m_caller_fndecl (caller_fndecl),
+    m_callee_fndecl (callee_fndecl),
+    m_expr (expr),
+    m_state (state)
+  {
+  }
+
+  tree m_caller_fndecl;
+  tree m_callee_fndecl;
+  tree m_expr;
+  state_machine::state_t m_state;
+};
+
+/* Information for describing an interprocedural return within the
+   diagnostic_path that carries critial state for the diagnostic,
+   from callee back to caller.
+
+   For example, a deref-of-unchecked-malloc diagnostic might use:
+   - "returning possibly-NULL pointer to 'make_obj' from 'allocator'"
+   to make it clearer how the unchecked value moves from callee
+   back to caller.  */
+
+struct return_of_state
+{
+  return_of_state (tree caller_fndecl, tree callee_fndecl,
+		   state_machine::state_t state)
+  : m_caller_fndecl (caller_fndecl),
+    m_callee_fndecl (callee_fndecl),
+    m_state (state)
+  {
+  }
+
+  tree m_caller_fndecl;
+  tree m_callee_fndecl;
+  state_machine::state_t m_state;
+};
+
+/* Information for describing the final event within a diagnostic_path.
+
+   For example a double-free diagnostic might use:
+     "second 'free' here"
+   and a use-after-free might use
+     "use after 'free' here".  */
+
+struct final_event
+{
+  final_event (tree expr, state_machine::state_t state)
+  : m_expr (expr), m_state (state)
+  {}
+
+  tree m_expr;
+  state_machine::state_t m_state;
+};
+
+} /* end of namespace evdesc */
+
+/* FIXME:
+   Diagnostics are emitted using va_list, which can't be portably stored
+   for later use, so we have to make it be a vfunc.  */
+
+class pending_diagnostic
+{
+ public:
+  virtual ~pending_diagnostic () {}
+
+  /* Hand-coded RTTI: get an ID for the subclass.  */
+  virtual const char *get_kind () const = 0;
+
+  virtual void emit (rich_location *) = 0;
+
+  /* Compare for equality with OTHER, which might be of a different
+     subclass.  */
+
+  bool equal_p (const pending_diagnostic &other)
+  {
+    /* Check for pointer equality on the IDs from get_kind.  */
+    if (get_kind () != other.get_kind ())
+      return false;
+    /* Call vfunc now we know they have the same ID: */
+    return subclass_equal_p (other);
+  }
+
+  /* A vfunc for testing for equality, where we've already
+     checked they have the same ID.  See pending_diagnostic_subclass
+     below for a convenience subclass for implementing this.  */
+  virtual bool subclass_equal_p (const pending_diagnostic &other) const = 0;
+
+  /* For greatest precision-of-wording, the various following overloads of
+     "describe" are virtual functions that give the pending diagnostic a
+     way to describe events in a diagnostic_path in terms that make sense
+     for that diagnostic.
+
+     In each case, return a non-NULL label_text to give the event a custom
+     description; NULL otherwise (falling back on a more generic
+     description).
+
+     Refer to the various "evdesc" struct definitions above for more
+     information on the meanings of each event kind.  */
+
+  virtual label_text describe (const evdesc::state_change &)
+  {
+    /* Default no-op implementation.  */
+    return label_text ();
+  }
+  virtual label_text describe (const evdesc::call_with_state &)
+  {
+    /* Default no-op implementation.  */
+    return label_text ();
+  }
+  virtual label_text describe (const evdesc::return_of_state &)
+  {
+    /* Default no-op implementation.  */
+    return label_text ();
+  }
+  virtual label_text describe (const evdesc::final_event &)
+  {
+    /* Default no-op implementation.  */
+    return label_text ();
+  }
+
+  /* End of precision-of-wording vfuncs.  */
+
+  static label_text formatted_print (const char *fmt, ...)
+    ATTRIBUTE_GCC_DIAG(1,2);
+};
+
+/* Use of the curiously-recurring template pattern, to implement
+   subclass_equal_p by casting and calling the operator==
+   This assumes that BASE_OTHER has already been checked to have
+   been of the same subclass.  */
+
+template <class Subclass>
+class pending_diagnostic_subclass : public pending_diagnostic
+{
+ public:
+  bool subclass_equal_p (const pending_diagnostic &base_other) const
+    FINAL OVERRIDE
+  {
+    const Subclass &other = (const Subclass &)base_other;
+    return *(const Subclass*)this == other;
+  }
+};
+
+#if 0
+/* A thing for state machines to pass to the engine to create
+   pending_diagnostic instances (so that we can filter without having 
+   to create pending_diagnostic instances first).  */
+
+class diagnostic_emitter
+{
+  virtual pending_diagnostic *make_diagnostic () = 0;
+};
+#endif
+
+/* Interface for the state machine to call the checker engine, at a
+   particular stmt.  */
+
+class sm_context
+{
+public:
+  //  sm_context (const state_machine &sm) : m_sm (sm) {}
+  virtual ~sm_context () {}
+
+  // Stuff used by impl 1:
+  virtual void on_transition (const supernode *node, const gimple *stmt,
+			      tree var,
+			      state_machine::state_t from,
+			      state_machine::state_t to,
+			      tree origin = NULL_TREE) = 0;
+
+  // Stuff used by impl 2:
+
+  //virtual state_machine::state_t get_state (tree var) = 0;
+  //virtual void transition (tree var, state_machine::state_t to) = 0;
+  virtual void warn_for_state (const supernode *node, const gimple *stmt,
+			       tree var, state_machine::state_t state,
+			       pending_diagnostic *d) = 0;
+
+  // etc, what else?
+
+  virtual tree get_readable_tree (tree expr)
+  {
+    return expr;
+  }
+
+protected:
+  //const state_machine &m_sm;
+};
+
+
+////////////////////////////////////////////////////////////////////////////
+
+extern void make_checkers (auto_delete_vec <state_machine> &out, logger *logger);
+
+////////////////////////////////////////////////////////////////////////////
+
+/* A particular state machine, for prototyping purposes:
+   the malloc/free API.  */
+
+class malloc_state_machine : public state_machine
+{
+public:
+  malloc_state_machine (logger *logger);
+
+  bool inherited_state_p () const FINAL OVERRIDE { return false; }
+
+  bool on_stmt (sm_context *sm_ctxt,
+		const supernode *node,
+		const gimple *stmt) const FINAL OVERRIDE;
+
+  void on_edge (sm_context *sm_ctxt,
+		const gimple *last_stmt,
+		const superedge *edge) const FINAL OVERRIDE;
+
+  void on_leak (sm_context *sm_ctxt,
+		const supernode *node,
+		const gimple *stmt,
+		tree var,
+		state_machine::state_t state) const FINAL OVERRIDE;
+  bool can_purge_p (state_t s) const FINAL OVERRIDE;
+
+  state_t m_start;
+  state_t m_unchecked;
+  state_t m_null;
+  state_t m_nonnull;
+  state_t m_free;
+  state_t m_non_heap; // FIXME: or should this be a different state machine?
+  // or do we need child values etc?
+  state_t m_stop;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* A particular state machine, for prototyping purposes:
+   the fopen API.  */
+
+class fileptr_state_machine : public state_machine
+{
+public:
+  fileptr_state_machine (logger *logger);
+
+  bool inherited_state_p () const FINAL OVERRIDE { return false; }
+
+  bool on_stmt (sm_context *sm_ctxt,
+		const supernode *node,
+		const gimple *stmt) const FINAL OVERRIDE;
+
+  void on_edge (sm_context *sm_ctxt,
+		const gimple *last_stmt,
+		const superedge *edge) const FINAL OVERRIDE;
+
+  void on_leak (sm_context *sm_ctxt,
+		const supernode *node,
+		const gimple *stmt,
+		tree var,
+		state_machine::state_t state) const FINAL OVERRIDE;
+  bool can_purge_p (state_t s) const FINAL OVERRIDE;
+
+private:
+  state_t m_start;
+  state_t m_unchecked;
+  state_t m_null;
+  state_t m_nonnull;
+  state_t m_closed;
+  state_t m_stop;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* A particular state machine, for prototyping purposes:
+   tainted data.  */
+
+class taint_state_machine : public state_machine
+{
+public:
+  taint_state_machine (logger *logger);
+
+  bool inherited_state_p () const FINAL OVERRIDE { return true; }
+
+  bool on_stmt (sm_context *sm_ctxt,
+		const supernode *node,
+		const gimple *stmt) const FINAL OVERRIDE;
+
+  void on_edge (sm_context *sm_ctxt,
+		const gimple *last_stmt,
+		const superedge *edge) const FINAL OVERRIDE;
+
+  void on_leak (sm_context *sm_ctxt,
+		const supernode *node,
+		const gimple *stmt,
+		tree var,
+		state_machine::state_t state) const FINAL OVERRIDE;
+  bool can_purge_p (state_t s) const FINAL OVERRIDE;
+
+private:
+  state_t m_start;
+  state_t m_tainted;
+  state_t m_has_lb;
+  state_t m_has_ub;
+  state_t m_stop;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* A particular state machine, for prototyping purposes:
+   sensitive data.  */
+
+class sensitive_state_machine : public state_machine
+{
+public:
+  sensitive_state_machine (logger *logger);
+
+  bool inherited_state_p () const FINAL OVERRIDE { return true; }
+
+  bool on_stmt (sm_context *sm_ctxt,
+		const supernode *node,
+		const gimple *stmt) const FINAL OVERRIDE;
+
+  void on_edge (sm_context *sm_ctxt,
+		const gimple *last_stmt,
+		const superedge *edge) const FINAL OVERRIDE;
+
+  void on_leak (sm_context *sm_ctxt,
+		const supernode *node,
+		const gimple *stmt,
+		tree var,
+		state_machine::state_t state) const FINAL OVERRIDE;
+  bool can_purge_p (state_t s) const FINAL OVERRIDE;
+
+private:
+  void warn_for_any_exposure (sm_context *sm_ctxt,
+			      const supernode *node,
+			      const gimple *stmt,
+			      tree arg) const;
+
+  state_t m_start;
+  state_t m_sensitive;
+  state_t m_stop;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* A particular state machine, for prototyping purposes:
+   a pattern-matching test, for DejaGnu tests.  */
+
+class pattern_test_state_machine : public state_machine
+{
+public:
+  pattern_test_state_machine (logger *logger);
+
+  bool inherited_state_p () const FINAL OVERRIDE { return false; }
+
+  bool on_stmt (sm_context *sm_ctxt,
+		const supernode *node,
+		const gimple *stmt) const FINAL OVERRIDE;
+
+  void on_edge (sm_context *sm_ctxt,
+		const gimple *last_stmt,
+		const superedge *edge) const FINAL OVERRIDE;
+
+  void on_leak (sm_context *sm_ctxt,
+		const supernode *node,
+		const gimple *stmt,
+		tree var,
+		state_machine::state_t state) const FINAL OVERRIDE;
+  bool can_purge_p (state_t s) const FINAL OVERRIDE;
+
+private:
+  state_t m_start;
+};
+
+#endif /* GCC_ANALYZER_HARDCODED_STATE_MACHINE_H */
-- 
1.8.5.3

