From 9a61de7cfac7c57cc1e2e1079bfec7443e409f9d Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Wed, 6 Mar 2019 12:14:09 -0500
Subject: [PATCH 63/87] FIXME: split out state machines into
 hardcoded-state-machine-*.cc

---
 gcc/analyzer/Make-lang.in                      |   3 +
 gcc/analyzer/hardcoded-state-machine-file.cc   | 158 +++++++++
 gcc/analyzer/hardcoded-state-machine-malloc.cc | 211 ++++++++++++
 gcc/analyzer/hardcoded-state-machine-taint.cc  | 200 +++++++++++
 gcc/analyzer/hardcoded-state-machine.cc        | 456 +------------------------
 gcc/analyzer/hardcoded-state-machine.h         |   7 +
 6 files changed, 581 insertions(+), 454 deletions(-)
 create mode 100644 gcc/analyzer/hardcoded-state-machine-file.cc
 create mode 100644 gcc/analyzer/hardcoded-state-machine-malloc.cc
 create mode 100644 gcc/analyzer/hardcoded-state-machine-taint.cc

diff --git a/gcc/analyzer/Make-lang.in b/gcc/analyzer/Make-lang.in
index 7a37503..392edf6 100644
--- a/gcc/analyzer/Make-lang.in
+++ b/gcc/analyzer/Make-lang.in
@@ -61,6 +61,9 @@ ANALYZER_PLUGIN_SO_OBJS = \
 	analyzer/engine.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-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
new file mode 100644
index 0000000..5e0ae3b
--- /dev/null
+++ b/gcc/analyzer/hardcoded-state-machine-file.cc
@@ -0,0 +1,158 @@
+/* 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;
+
+////////////////////////////////////////////////////////////////////////////
+
+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");
+}
+
+void
+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)
+	    sm_ctxt->on_transition (node, stmt, lhs, m_start, m_unchecked);
+	  else
+	    {
+	      /* TODO: report leak.  */
+	    }
+	}
+
+      if (is_named_call_p (call, "fclose", 1))
+	{
+	  tree arg = gimple_call_arg (call, 0);
+	  if (DEBUG)
+	    inform (stmt->location, "DBG: got fclose");
+	  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, NULL,
+				   "double %<fclose%> of FILE %qE", arg);
+	  sm_ctxt->on_transition (node, stmt, arg, m_closed, m_stop);
+	}
+
+      // TODO: operations on closed file
+      // etc
+    }
+}
+
+/* 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
+{
+  metadata m;
+  m.add_cwe (775);
+  sm_ctxt->warn_for_state (node, stmt, var, m_unchecked, &m,
+			   "leak of FILE %qE", var);
+  sm_ctxt->warn_for_state (node, stmt, var, m_nonnull, &m,
+			   "leak of FILE %qE", var);
+}
diff --git a/gcc/analyzer/hardcoded-state-machine-malloc.cc b/gcc/analyzer/hardcoded-state-machine-malloc.cc
new file mode 100644
index 0000000..d49d7d4
--- /dev/null
+++ b/gcc/analyzer/hardcoded-state-machine-malloc.cc
@@ -0,0 +1,211 @@
+/* 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;
+
+////////////////////////////////////////////////////////////////////////////
+
+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_stop = add_state ("stop");
+}
+
+void
+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)
+	    sm_ctxt->on_transition (node, stmt, lhs, m_start, m_unchecked);
+	  else
+	    {
+	      /* TODO: report leak.  */
+	    }
+	}
+
+      if (is_named_call_p (call, "free", 1))
+	{
+	  tree arg = gimple_call_arg (call, 0);
+	  if (DEBUG)
+	    inform (stmt->location, "DBG: got free");
+
+	  /* 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.  */
+	  metadata m;
+	  m.add_cwe (415); /* CWE-415: Double Free.  */
+	  sm_ctxt->warn_for_state (node, stmt, arg, m_free, &m,
+				   "double-free of %qE", arg);
+	  sm_ctxt->on_transition (node, stmt, arg, m_free, m_stop);
+	}
+    }
+
+  /* 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);
+	}
+    }
+
+  // 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);
+
+	  {
+	    /* CWE-690: Unchecked Return Value to NULL Pointer Dereference.  */
+	    metadata m;
+	    m.add_cwe (690);
+	    sm_ctxt->warn_for_state (node, stmt, arg, m_unchecked, &m,
+				     "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, &m,
+				     "use of NULL %qE", arg);
+	    sm_ctxt->on_transition (node, stmt, arg, m_null, m_stop);
+	  }
+
+	  {
+	    metadata m;
+	    m.add_cwe (416); /* CWE-416: Use After Free.  */
+	    sm_ctxt->warn_for_state (node, stmt, arg, m_free, &m,
+				     "use after free of %qE", arg);
+	    sm_ctxt->on_transition (node, stmt, arg, m_free, m_stop);
+	  }
+	}
+    }
+}
+
+/* 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
+{
+  metadata m;
+  m.add_cwe (401);
+  sm_ctxt->warn_for_state (node, stmt, var, m_unchecked, &m,
+			   "leak of %qE", var);
+  sm_ctxt->warn_for_state (node, stmt, var, m_nonnull, &m,
+			   "leak of %qE", var);
+}
diff --git a/gcc/analyzer/hardcoded-state-machine-taint.cc b/gcc/analyzer/hardcoded-state-machine-taint.cc
new file mode 100644
index 0000000..6e0b1a1
--- /dev/null
+++ b/gcc/analyzer/hardcoded-state-machine-taint.cc
@@ -0,0 +1,200 @@
+/* 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;
+
+////////////////////////////////////////////////////////////////////////////
+
+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");
+}
+
+void
+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);
+	  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);
+	}
+    }
+
+#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);
+	  metadata m;
+	  m.add_cwe (129);
+	  sm_ctxt->warn_for_state
+	    (node, stmt, arg, m_tainted, &m,
+	     "use of tainted value %qE in array lookup without bounds checking",
+	     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
+}
+
+/* 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.  */
+}
diff --git a/gcc/analyzer/hardcoded-state-machine.cc b/gcc/analyzer/hardcoded-state-machine.cc
index 7938741..578998c 100644
--- a/gcc/analyzer/hardcoded-state-machine.cc
+++ b/gcc/analyzer/hardcoded-state-machine.cc
@@ -42,7 +42,7 @@ static const bool DEBUG = false;
 
 /* If STMT is an assignment to zero, return the LHS.  */
 
-static tree
+tree
 is_zero_assignment (const gimple *stmt)
 {
   const gassign *assign_stmt = dyn_cast <const gassign *> (stmt);
@@ -60,7 +60,7 @@ is_zero_assignment (const gimple *stmt)
 }
 
 /* FIXME.  */
-static bool
+bool
 is_comparison_against_zero (const gcond *cond_stmt,
 			    tree *out_lhs, enum tree_code *out_op)
 {
@@ -110,458 +110,6 @@ state_machine::get_state_name (state_t s) const
 
 ////////////////////////////////////////////////////////////////////////////
 
-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_stop = add_state ("stop");
-}
-
-void
-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)
-	    sm_ctxt->on_transition (node, stmt, lhs, m_start, m_unchecked);
-	  else
-	    {
-	      /* TODO: report leak.  */
-	    }
-	}
-
-      if (is_named_call_p (call, "free", 1))
-	{
-	  tree arg = gimple_call_arg (call, 0);
-	  if (DEBUG)
-	    inform (stmt->location, "DBG: got free");
-
-	  /* 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.  */
-	  metadata m;
-	  m.add_cwe (415); /* CWE-415: Double Free.  */
-	  sm_ctxt->warn_for_state (node, stmt, arg, m_free, &m,
-				   "double-free of %qE", arg);
-	  sm_ctxt->on_transition (node, stmt, arg, m_free, m_stop);
-	}
-    }
-
-  /* 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);
-	}
-    }
-
-  // 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);
-
-	  {
-	    /* CWE-690: Unchecked Return Value to NULL Pointer Dereference.  */
-	    metadata m;
-	    m.add_cwe (690);
-	    sm_ctxt->warn_for_state (node, stmt, arg, m_unchecked, &m,
-				     "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, &m,
-				     "use of NULL %qE", arg);
-	    sm_ctxt->on_transition (node, stmt, arg, m_null, m_stop);
-	  }
-
-	  {
-	    metadata m;
-	    m.add_cwe (416); /* CWE-416: Use After Free.  */
-	    sm_ctxt->warn_for_state (node, stmt, arg, m_free, &m,
-				     "use after free of %qE", arg);
-	    sm_ctxt->on_transition (node, stmt, arg, m_free, m_stop);
-	  }
-	}
-    }
-}
-
-/* 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
-{
-  metadata m;
-  m.add_cwe (401);
-  sm_ctxt->warn_for_state (node, stmt, var, m_unchecked, &m,
-			   "leak of %qE", var);
-  sm_ctxt->warn_for_state (node, stmt, var, m_nonnull, &m,
-			   "leak of %qE", var);
-}
-
-////////////////////////////////////////////////////////////////////////////
-
-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");
-}
-
-void
-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)
-	    sm_ctxt->on_transition (node, stmt, lhs, m_start, m_unchecked);
-	  else
-	    {
-	      /* TODO: report leak.  */
-	    }
-	}
-
-      if (is_named_call_p (call, "fclose", 1))
-	{
-	  tree arg = gimple_call_arg (call, 0);
-	  if (DEBUG)
-	    inform (stmt->location, "DBG: got fclose");
-	  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, NULL,
-				   "double %<fclose%> of FILE %qE", arg);
-	  sm_ctxt->on_transition (node, stmt, arg, m_closed, m_stop);
-	}
-
-      // TODO: operations on closed file
-      // etc
-    }
-}
-
-/* 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
-{
-  metadata m;
-  m.add_cwe (775);
-  sm_ctxt->warn_for_state (node, stmt, var, m_unchecked, &m,
-			   "leak of FILE %qE", var);
-  sm_ctxt->warn_for_state (node, stmt, var, m_nonnull, &m,
-			   "leak of FILE %qE", var);
-}
-
-////////////////////////////////////////////////////////////////////////////
-
-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");
-}
-
-void
-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);
-	  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);
-	}
-    }
-
-#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);
-	  metadata m;
-	  m.add_cwe (129);
-	  sm_ctxt->warn_for_state
-	    (node, stmt, arg, m_tainted, &m,
-	     "use of tainted value %qE in array lookup without bounds checking",
-	     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
-}
-
-/* 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.  */
-}
-
-////////////////////////////////////////////////////////////////////////////
-
 void
 make_checkers (auto_delete_vec <state_machine> &out, logger *logger)
 {
diff --git a/gcc/analyzer/hardcoded-state-machine.h b/gcc/analyzer/hardcoded-state-machine.h
index 39db59c..37e68e2 100644
--- a/gcc/analyzer/hardcoded-state-machine.h
+++ b/gcc/analyzer/hardcoded-state-machine.h
@@ -3,6 +3,13 @@
 
 ////////////////////////////////////////////////////////////////////////////
 
+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;
 
-- 
1.8.5.3

