From 46cab2c4f78dd84aeae7b8b3f60854ee433098dc Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Thu, 3 Jan 2019 19:30:15 -0500
Subject: [PATCH 017/138] FIXME: implementation of checker based on state
 machines

---
 gcc/analyzer/Make-lang.in                          |  160 ++
 gcc/analyzer/analyzer-logging.cc                   |  201 +++
 gcc/analyzer/analyzer-logging.h                    |  228 +++
 gcc/analyzer/analyzer-pass.cc                      |  127 ++
 gcc/analyzer/analyzer-plugin.cc                    |   65 +
 gcc/analyzer/analyzer.cc                           |  121 ++
 gcc/analyzer/analyzer.h                            |   48 +
 gcc/analyzer/checker-script.cc                     | 1677 ++++++++++++++++++++
 gcc/analyzer/config-lang.in                        |   34 +
 gcc/analyzer/constraints.cc                        |  989 ++++++++++++
 gcc/analyzer/constraints.h                         |  167 ++
 gcc/analyzer/engine.cc                             | 1315 +++++++++++++++
 gcc/analyzer/engine.h                              |    1 +
 gcc/analyzer/graphviz.cc                           |   62 +
 gcc/analyzer/graphviz.h                            |   19 +
 gcc/analyzer/hardcoded-state-machine.cc            |  319 ++++
 gcc/analyzer/hardcoded-state-machine.h             |  141 ++
 gcc/analyzer/supergraph.cc                         |  713 +++++++++
 gcc/analyzer/supergraph.h                          |  301 ++++
 gcc/analyzer/tristate.cc                           |   67 +
 gcc/analyzer/tristate.h                            |   74 +
 gcc/builtins.def                                   |    8 +
 gcc/common.opt                                     |    6 +
 gcc/gcc.c                                          |   10 +
 gcc/gimple.h                                       |    8 +
 gcc/plugin.c                                       |    2 +
 gcc/plugin.def                                     |    3 +
 gcc/selftest-run-tests.c                           |    4 +
 gcc/testsuite/gcc.dg/analyzer/abort.c              |   29 +
 gcc/testsuite/gcc.dg/analyzer/analyzer.exp         |   36 +
 gcc/testsuite/gcc.dg/analyzer/conditionals.c       |   51 +
 gcc/testsuite/gcc.dg/analyzer/equivalence.c        |   29 +
 gcc/testsuite/gcc.dg/analyzer/file-1.c             |   36 +
 gcc/testsuite/gcc.dg/analyzer/infinite-recursion.c |   20 +
 gcc/testsuite/gcc.dg/analyzer/malloc-1.c           |  190 +++
 gcc/testsuite/gcc.dg/analyzer/malloc-dce.c         |   28 +
 gcc/testsuite/gcc.dg/analyzer/malloc-paths-1.c     |   15 +
 gcc/testsuite/gcc.dg/analyzer/malloc-paths-2.c     |   13 +
 gcc/testsuite/gcc.dg/analyzer/malloc-paths-3.c     |   14 +
 gcc/testsuite/gcc.dg/analyzer/malloc-paths-4.c     |   18 +
 gcc/testsuite/gcc.dg/analyzer/switch.c             |   30 +
 41 files changed, 7379 insertions(+)
 create mode 100644 gcc/analyzer/Make-lang.in
 create mode 100644 gcc/analyzer/analyzer-logging.cc
 create mode 100644 gcc/analyzer/analyzer-logging.h
 create mode 100644 gcc/analyzer/analyzer-pass.cc
 create mode 100644 gcc/analyzer/analyzer-plugin.cc
 create mode 100644 gcc/analyzer/analyzer.cc
 create mode 100644 gcc/analyzer/analyzer.h
 create mode 100644 gcc/analyzer/checker-script.cc
 create mode 100644 gcc/analyzer/config-lang.in
 create mode 100644 gcc/analyzer/constraints.cc
 create mode 100644 gcc/analyzer/constraints.h
 create mode 100644 gcc/analyzer/engine.cc
 create mode 100644 gcc/analyzer/engine.h
 create mode 100644 gcc/analyzer/graphviz.cc
 create mode 100644 gcc/analyzer/graphviz.h
 create mode 100644 gcc/analyzer/hardcoded-state-machine.cc
 create mode 100644 gcc/analyzer/hardcoded-state-machine.h
 create mode 100644 gcc/analyzer/supergraph.cc
 create mode 100644 gcc/analyzer/supergraph.h
 create mode 100644 gcc/analyzer/tristate.cc
 create mode 100644 gcc/analyzer/tristate.h
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/abort.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/analyzer.exp
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/conditionals.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/equivalence.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/file-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/infinite-recursion.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-dce.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-paths-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-paths-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-paths-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-paths-4.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/switch.c

diff --git a/gcc/analyzer/Make-lang.in b/gcc/analyzer/Make-lang.in
new file mode 100644
index 0000000..7a37503
--- /dev/null
+++ b/gcc/analyzer/Make-lang.in
@@ -0,0 +1,160 @@
+# Top level -*- makefile -*- fragment for analyzer
+#   Copyright (C) 2013-2015 Free Software Foundation, Inc.
+
+#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/>.
+
+# This file provides the language dependent support in the main Makefile.
+# Each language makefile fragment must provide the following targets:
+#
+# foo.all.cross, foo.start.encap, foo.rest.encap,
+# foo.install-common, foo.install-man, foo.install-info, foo.install-pdf,
+# foo.install-html, foo.info, foo.dvi, foo.pdf, foo.html, foo.uninstall,
+# foo.mostlyclean, foo.clean, foo.distclean,
+# foo.maintainer-clean, foo.stage1, foo.stage2, foo.stage3, foo.stage4
+#
+# where `foo' is the name of the language.
+#
+# It should also provide rules for:
+#
+# - making any compiler driver (eg: g++)
+# - the compiler proper (eg: cc1plus)
+# - define the names for selecting the language in LANGUAGES.
+
+#
+# Define the names for selecting analyzer in LANGUAGES.
+# Note that it would be nice to move the dependency on g++
+# into the analyzer rule, but that needs a little bit of work
+# to do the right thing within all.cross.
+
+ANALYZER_PLUGIN_SO = analyzer_plugin.so
+
+analyzer: \
+	$(FULL_DRIVER_NAME) \
+	$(ANALYZER_PLUGIN_SO)
+
+# Tell GNU make to ignore these if they exist.
+.PHONY: analyzer
+
+# Files that are linked into analyzer plugin
+
+ANALYZER_PLUGIN_SO_OBJS = \
+	analyzer/analyzer.o \
+	analyzer/analyzer-logging.o \
+	analyzer/analyzer-pass.o \
+	analyzer/analyzer-plugin.o \
+	analyzer/checker-script.o \
+	analyzer/constraints.o \
+	analyzer/engine.o \
+	analyzer/graphviz.o \
+	analyzer/hardcoded-state-machine.o \
+	analyzer/supergraph.o \
+	analyzer/tristate.o \
+
+# Use strict warnings for this front end.
+analyzer-warn = $(STRICT_WARN)
+
+#
+# Build hooks:
+
+analyzer.all.cross:
+analyzer.start.encap:
+analyzer.rest.encap:
+
+$(ANALYZER_PLUGIN_SO_OBJS): %.o: $(srcdir)/%.cc
+	$(COMPILER) \
+	  $(INCLUDES) \
+	  $(ALL_COMPILERFLAGS) \
+	  -c -o $@ $< \
+	-fPIC
+
+$(ANALYZER_PLUGIN_SO): $(ANALYZER_PLUGIN_SO_OBJS)
+	$(LINKER) \
+	  -o $@ \
+	  -shared \
+	  $(ANALYZER_PLUGIN_SO_OBJS) \
+	-lpthread
+
+# If the analyzer is enabled, require the selftests to be run for it
+# at each stage of the build:
+selftest-analyzer: s-selftest-analyzer
+
+ANALYZER_SELFTEST_FLAGS = -xc $(SELFTEST_FLAGS) --analyzer
+ANALYZER_SELFTEST_DEPS = cc1$(exeext) $(SELFTEST_DEPS) $(ANALYZER_PLUGIN_SO)
+
+# Run the analyzer selftests:
+s-selftest-analyzer: $(ANALYZER_SELFTEST_DEPS)
+	$(GCC_FOR_TARGET) $(ANALYZER_SELFTEST_FLAGS)
+	$(STAMP) $@
+
+# Convenience methods for running analyzer selftests under gdb:
+.PHONY: selftest-analyzer-gdb
+selftest-analyzer-gdb: $(ANALYZER_SELFTEST_DEPS)
+	$(GCC_FOR_TARGET) $(ANALYZER_SELFTEST_FLAGS) \
+	  -wrapper gdb,--args
+
+# Convenience methods for running analyzer selftests under valgrind:
+.PHONY: selftest-analyzer-valgrind
+selftest-analyzer-valgrind: $(ANALYZER_SELFTEST_DEPS)
+	$(GCC_FOR_TARGET) $(ANALYZER_SELFTEST_FLAGS) \
+	  -wrapper valgrind,--leak-check=full
+
+# Documentation build hooks.
+analyzer.info:
+analyzer.man:
+
+# Testing hooks:
+lang_checks += check-analyzer
+
+#
+# Install hooks:
+analyzer.install-common:
+
+analyzer.install-man:
+
+analyzer.install-plugin:
+
+analyzer.uninstall:
+
+#
+# Clean hooks:
+# A lot of the ancillary files are deleted by the main makefile.
+# We just have to delete files specific to us.
+
+analyzer.mostlyclean:
+
+analyzer.clean:
+
+analyzer.distclean:
+
+analyzer.maintainer-clean:
+
+#
+# Stage hooks:
+# The main makefile has already created stage?/analyzer.
+
+analyzer.stage1: stage1-start
+	-mv analyzer/*$(objext) stage1/analyzer
+analyzer.stage2: stage2-start
+	-mv analyzer/*$(objext) stage2/analyzer
+analyzer.stage3: stage3-start
+	-mv analyzer/*$(objext) stage3/analyzer
+analyzer.stage4: stage4-start
+	-mv analyzer/*$(objext) stage4/analyzer
+analyzer.stageprofile: stageprofile-start
+	-mv analyzer/*$(objext) stageprofile/analyzer
+analyzer.stagefeedback: stagefeedback-start
+	-mv analyzer/*$(objext) stagefeedback/analyzer
diff --git a/gcc/analyzer/analyzer-logging.cc b/gcc/analyzer/analyzer-logging.cc
new file mode 100644
index 0000000..15cce36
--- /dev/null
+++ b/gcc/analyzer/analyzer-logging.cc
@@ -0,0 +1,201 @@
+/* Internals of libgccjit: logging
+   Copyright (C) 2014-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/>.  */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "toplev.h" /* for print_version */
+
+#include "analyzer-logging.h"
+
+/* Implementation of class gcc::jit::logger.  */
+
+/* The constructor for gcc::jit::logger, used by
+   gcc_jit_context_set_logfile.  */
+
+logger::logger (FILE *f_out,
+		int, /* flags */
+		int /* verbosity */) :
+  m_refcount (0),
+  m_f_out (f_out),
+  m_indent_level (0),
+  m_log_refcount_changes (false)
+{
+  /* Begin the log by writing the GCC version.  */
+  print_version (f_out, "", false);
+}
+
+/* The destructor for gcc::jit::logger, invoked via
+   the decref method when the refcount hits zero.
+   Note that we do not close the underlying FILE * (m_f_out).  */
+
+logger::~logger ()
+{
+  /* This should be the last message emitted.  */
+  log ("%s", __PRETTY_FUNCTION__);
+  gcc_assert (m_refcount == 0);
+}
+
+/* Increment the reference count of the gcc::jit::logger.  */
+
+void
+logger::incref (const char *reason)
+{
+  m_refcount++;
+  if (m_log_refcount_changes)
+    log ("%s: reason: %s refcount now %i ",
+	 __PRETTY_FUNCTION__, reason, m_refcount);
+}
+
+/* Decrement the reference count of the gcc::jit::logger,
+   deleting it if nothing is referring to it.  */
+
+void
+logger::decref (const char *reason)
+{
+  gcc_assert (m_refcount > 0);
+  --m_refcount;
+  if (m_log_refcount_changes)
+    log ("%s: reason: %s refcount now %i",
+	 __PRETTY_FUNCTION__, reason, m_refcount);
+  if (m_refcount == 0)
+    delete this;
+}
+
+/* Write a formatted message to the log, by calling the log_va method.  */
+
+void
+logger::log (const char *fmt, ...)
+{
+  va_list ap;
+  va_start (ap, fmt);
+  log_va (fmt, ap);
+  va_end (ap);
+}
+
+/* Write an indented line to the log file.
+
+   We explicitly flush after each line: if something crashes the process,
+   we want the logfile/stream to contain the most up-to-date hint about the
+   last thing that was happening, without it being hidden in an in-process
+   buffer.  */
+
+void
+logger::log_va (const char *fmt, va_list ap)
+{
+  start_log_line ();
+  log_va_partial (fmt, ap);
+  end_log_line ();
+}
+
+void
+logger::start_log_line ()
+{
+  for (int i = 0; i < m_indent_level; i++)
+    fputc (' ', m_f_out);
+}
+
+void
+logger::log_partial (const char *fmt, ...)
+{
+  va_list ap;
+  va_start (ap, fmt);
+  log_va_partial (fmt, ap);
+  va_end (ap);
+}
+
+void
+logger::log_va_partial (const char *fmt, va_list ap)
+{
+  vfprintf (m_f_out, fmt, ap);
+}
+
+void
+logger::end_log_line ()
+{
+  fprintf (m_f_out, "\n");
+  fflush (m_f_out);
+}
+
+/* Record the entry within a particular scope, indenting subsequent
+   log lines accordingly.  */
+
+void
+logger::enter_scope (const char *scope_name)
+{
+  log ("entering: %s", scope_name);
+  m_indent_level += 1;
+}
+
+void
+logger::enter_scope (const char *scope_name, const char *fmt, va_list ap)
+{
+  start_log_line ();
+  log_partial ("entering: %s: ", scope_name);
+  log_va_partial (fmt, ap);
+  end_log_line ();
+
+  m_indent_level += 1;
+}
+
+
+/* Record the exit from a particular scope, restoring the indent level to
+   before the scope was entered.  */
+
+void
+logger::exit_scope (const char *scope_name)
+{
+  if (m_indent_level)
+    m_indent_level -= 1;
+  else
+    log ("(mismatching indentation)");
+  log ("exiting: %s", scope_name);
+}
+
+/* Implementation of class gcc::jit::log_user.  */
+
+/* The constructor for gcc::jit::log_user.  */
+
+log_user::log_user (logger *logger) : m_logger (logger)
+{
+  if (m_logger)
+    m_logger->incref("log_user ctor");
+}
+
+/* The destructor for gcc::jit::log_user.  */
+
+log_user::~log_user ()
+{
+  if (m_logger)
+    m_logger->decref("log_user dtor");
+}
+
+/* Set the logger for a gcc::jit::log_user, managing the reference counts
+   of the old and new logger (either of which might be NULL).  */
+
+void
+log_user::set_logger (logger *logger)
+{
+  if (logger)
+    logger->incref ("log_user::set_logger");
+  if (m_logger)
+    m_logger->decref ("log_user::set_logger");
+  m_logger = logger;
+}
diff --git a/gcc/analyzer/analyzer-logging.h b/gcc/analyzer/analyzer-logging.h
new file mode 100644
index 0000000..c073475
--- /dev/null
+++ b/gcc/analyzer/analyzer-logging.h
@@ -0,0 +1,228 @@
+/* Internals of libgccjit: logging
+   Copyright (C) 2014-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/>.  */
+
+// FIXME:
+
+#ifndef JIT_LOGGING_H
+#define JIT_LOGGING_H
+
+#ifdef GCC_VERSION
+#if GCC_VERSION >= 4001
+#define GNU_PRINTF(M, N) __attribute__ ((format (gnu_printf, (M), (N))))
+#else
+#define GNU_PRINTF(M, N)
+#endif
+#endif
+
+// FIXME:
+
+/* A gcc::jit::logger encapsulates a logging stream: a way to send
+   lines of pertinent information to a FILE *.  */
+
+class logger
+{
+ public:
+  logger (FILE *f_out, int flags, int verbosity);
+  ~logger ();
+
+  void incref (const char *reason);
+  void decref (const char *reason);
+
+  void log (const char *fmt, ...)
+    GNU_PRINTF(2, 3);
+  void log_va (const char *fmt, va_list ap)
+    GNU_PRINTF(2, 0);
+  void start_log_line ();
+  void log_partial (const char *fmt, ...)
+    GNU_PRINTF(2, 3);
+  void log_va_partial (const char *fmt, va_list ap)
+    GNU_PRINTF(2, 0);
+  void end_log_line ();
+
+  void enter_scope (const char *scope_name);
+  void enter_scope (const char *scope_name, const char *fmt, va_list ap)
+    GNU_PRINTF(3, 0);
+  void exit_scope (const char *scope_name);
+
+private:
+  int m_refcount;
+  FILE *m_f_out;
+  int m_indent_level;
+  bool m_log_refcount_changes;
+};
+
+/* The class gcc::jit::log_scope is an RAII-style class intended to make
+   it easy to notify a logger about entering and exiting the body of a
+   given function.  */
+
+class log_scope
+{
+public:
+  log_scope (logger *logger, const char *name);
+  log_scope (logger *logger, const char *name, const char *fmt, ...)
+    GNU_PRINTF(4, 5);
+  ~log_scope ();
+
+ private:
+  logger *m_logger;
+  const char *m_name;
+};
+
+/* The constructor for gcc::jit::log_scope.
+
+   The normal case is that the logger is NULL, in which case this should
+   be largely a no-op.
+
+   If we do have a logger, notify it that we're entering the given scope.
+   We also need to hold a reference on it, to avoid a use-after-free
+   when logging the cleanup of the owner of the logger.  */
+
+inline
+log_scope::log_scope (logger *logger, const char *name) :
+ m_logger (logger),
+ m_name (name)
+{
+  if (m_logger)
+    {
+      m_logger->incref ("log_scope ctor");
+      m_logger->enter_scope (m_name);
+    }
+}
+
+inline
+log_scope::log_scope (logger *logger, const char *name, const char *fmt, ...):
+ m_logger (logger),
+ m_name (name)
+{
+  if (m_logger)
+    {
+      m_logger->incref ("log_scope ctor");
+      va_list ap;
+      va_start (ap, fmt);
+      m_logger->enter_scope (m_name, fmt, ap);
+      va_end (ap);
+    }
+}
+
+
+/* The destructor for gcc::jit::log_scope; essentially the opposite of
+   the constructor.  */
+
+inline
+log_scope::~log_scope ()
+{
+  if (m_logger)
+    {
+      m_logger->exit_scope (m_name);
+      m_logger->decref ("log_scope dtor");
+    }
+}
+
+/* A gcc::jit::log_user is something that potentially uses a
+   gcc::jit::logger (which could be NULL).
+
+   It is the base class for each of:
+
+      - class gcc::jit::recording::context
+
+      - class gcc::jit::playback::context
+
+      - class gcc::jit::tempdir
+
+      - class gcc::jit::result
+
+   The log_user class keeps the reference-count of a logger up-to-date.  */
+
+class log_user
+{
+ public:
+  log_user (logger *logger);
+  ~log_user ();
+
+  logger * get_logger () const { return m_logger; }
+  void set_logger (logger * logger);
+
+  void log (const char *fmt, ...) const
+    GNU_PRINTF(2, 3);
+
+  void enter_scope (const char *scope_name);
+  void exit_scope (const char *scope_name);
+
+ private:
+  logger *m_logger;
+};
+
+/* A shortcut for calling log from a context/result, handling the common
+   case where the underlying logger is NULL via a no-op.  */
+
+inline void
+log_user::log (const char *fmt, ...) const
+{
+  if (m_logger)
+    {
+      va_list ap;
+      va_start (ap, fmt);
+      m_logger->log_va (fmt, ap);
+      va_end (ap);
+    }
+}
+
+/* A shortcut for recording entry into a scope from a context/result,
+   handling the common case where the underlying logger is NULL via
+   a no-op.  */
+
+inline void
+log_user::enter_scope (const char *scope_name)
+{
+  if (m_logger)
+    m_logger->enter_scope (scope_name);
+}
+
+/* A shortcut for recording exit from a scope from a context/result,
+   handling the common case where the underlying logger is NULL via
+   a no-op.  */
+
+inline void
+log_user::exit_scope (const char *scope_name)
+{
+  if (m_logger)
+    m_logger->exit_scope (scope_name);
+}
+
+/* If the given logger is non-NULL, log entry/exit of this scope to
+   it, identifying it using __PRETTY_FUNCTION__.  */
+
+#define JIT_LOG_SCOPE(LOGGER)		\
+  log_scope s (LOGGER, __PRETTY_FUNCTION__)
+
+/* If the given logger is non-NULL, log entry/exit of this scope to
+   it, identifying it using __func__.  */
+
+#define JIT_LOG_FUNC(LOGGER) \
+  log_scope s (LOGGER, __func__)
+
+#define JIT_LOG_FUNC_1(LOGGER, FMT, A0)	\
+  log_scope s (LOGGER, __func__, FMT, A0)
+
+#define JIT_LOG_FUNC_2(LOGGER, FMT, A0, A1)		\
+  log_scope s (LOGGER, __func__, FMT, A0, A1)
+
+
+#endif /* JIT_LOGGING_H */
diff --git a/gcc/analyzer/analyzer-pass.cc b/gcc/analyzer/analyzer-pass.cc
new file mode 100644
index 0000000..cc60e5f
--- /dev/null
+++ b/gcc/analyzer/analyzer-pass.cc
@@ -0,0 +1,127 @@
+/* FIXME.  */
+#include "config.h"
+#include "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "tm.h"
+#include "toplev.h"
+#include "hash-table.h"
+#include "vec.h"
+#include "ggc.h"
+#include "basic-block.h"
+#include "tree-ssa-alias.h"
+#include "internal-fn.h"
+#include "gimple-fold.h"
+#include "tree-eh.h"
+#include "gimple-expr.h"
+#include "is-a.h"
+#include "gimple.h"
+#include "tree-pass.h"
+#include "intl.h"
+#include "context.h"
+#include "diagnostic.h"
+#include "bitmap.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "gimple-pretty-print.h"
+
+#include "analyzer.h"
+#include "engine.h"
+
+namespace {
+
+const pass_data pass_data_analyzer =
+{
+#if 1
+  IPA_PASS, /* type */
+#else
+  GIMPLE_PASS, /* type */
+#endif
+  "analyzer", /* name */
+  OPTGROUP_NONE, /* optinfo_flags */
+  TV_NONE, /* tv_id */
+  PROP_ssa, /* properties_required */
+  0, /* properties_provided */
+  0, /* properties_destroyed */
+  0, /* todo_flags_start */
+  0, /* todo_flags_finish */
+};
+
+class pass_analyzer 
+#if 1
+: public ipa_opt_pass_d
+#else
+: public gimple_opt_pass
+#endif
+{
+public:
+   pass_analyzer(gcc::context *ctxt)
+#if 1     
+    : ipa_opt_pass_d (pass_data_analyzer, ctxt,
+		      NULL, /* generate_summary */
+		      NULL, /* write_summary */
+		      NULL, /* read_summary */
+		      NULL, /* write_optimization_summary */
+		      NULL, /* read_optimization_summary */
+		      NULL, /* stmt_fixup */
+		      0, /* function_transform_todo_flags_start */
+		      NULL, /* function_transform */
+		      NULL) /* variable_transform */
+#else
+     : gimple_opt_pass(pass_data_analyzer, ctxt)
+#endif
+  {}
+
+  /* opt_pass methods: */
+  virtual unsigned int execute (function *);
+
+ private:
+}; // class pass_analyzer
+
+unsigned int
+pass_analyzer::execute (function *)
+{
+  run_checkers ();
+  return 0;
+}
+
+} // anon namespace
+
+#if 1
+static ipa_opt_pass_d *
+#else
+static gimple_opt_pass *
+#endif
+make_pass_analyzer (gcc::context *ctxt)
+{
+  return new pass_analyzer (ctxt);
+}
+
+/* Called by plugin_init.  */
+
+void
+register_analyzer_pass ()
+{
+  static struct register_pass_info pass_info;
+
+#if 1
+  /* IPA-LTO pass.  */
+  pass_info.pass = make_pass_analyzer (g);
+  pass_info.reference_pass_name = "whole-program";
+  pass_info.ref_pass_instance_number = 1;
+  pass_info.pos_op = PASS_POS_INSERT_BEFORE;
+
+  register_callback ("analyzer", PLUGIN_PASS_MANAGER_SETUP, NULL,
+		     &pass_info);
+#else
+  /* Self-assign detection should happen after SSA is constructed.  */
+  pass_info.pass = make_pass_analyzer (g);
+  pass_info.reference_pass_name = "ssa";
+  pass_info.ref_pass_instance_number = 1;
+  pass_info.pos_op = PASS_POS_INSERT_AFTER;
+
+  register_callback ("analyzer", PLUGIN_PASS_MANAGER_SETUP, NULL,
+		     &pass_info);
+#endif
+}
diff --git a/gcc/analyzer/analyzer-plugin.cc b/gcc/analyzer/analyzer-plugin.cc
new file mode 100644
index 0000000..2620bc9
--- /dev/null
+++ b/gcc/analyzer/analyzer-plugin.cc
@@ -0,0 +1,65 @@
+/* FIXME.  */
+#include "config.h"
+#include "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "tm.h"
+#include "toplev.h"
+#include "hash-table.h"
+#include "vec.h"
+#include "ggc.h"
+#include "basic-block.h"
+#include "tree-ssa-alias.h"
+#include "internal-fn.h"
+#include "gimple-fold.h"
+#include "tree-eh.h"
+#include "gimple-expr.h"
+#include "is-a.h"
+#include "gimple.h"
+#include "tree-pass.h"
+#include "intl.h"
+#include "context.h"
+#include "diagnostic.h"
+#include "bitmap.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "gimple-pretty-print.h"
+
+#include "analyzer.h"
+
+int plugin_is_GPL_compatible;
+
+#if CHECKING_P
+
+namespace selftest {
+
+static void
+analyzer_selftests (void */*gcc_data*/, void */*user_data*/)
+{
+  analyzer_checker_script_cc_tests ();
+  analyzer_constraints_cc_tests ();
+}
+
+} /* end of namespace selftest.  */
+
+#endif /* #if CHECKING_P */
+
+int
+plugin_init (struct plugin_name_args *plugin_info,
+	     struct plugin_gcc_version */*version*/)
+{
+  if (0)
+    fprintf (stderr, "got here\n");
+
+  /* Register our selftests.  */
+#if CHECKING_P
+  register_callback (plugin_info->base_name,
+		     PLUGIN_RUN_SELFTESTS,
+		     selftest::analyzer_selftests, NULL);
+#endif /* #if CHECKING_P */
+
+  register_analyzer_pass ();
+
+  return 0;
+}
diff --git a/gcc/analyzer/analyzer.cc b/gcc/analyzer/analyzer.cc
new file mode 100644
index 0000000..e9c3329
--- /dev/null
+++ b/gcc/analyzer/analyzer.cc
@@ -0,0 +1,121 @@
+/* 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>
+
+#if 0
+ana::analyzer::analyzer ()
+{
+      int len = strlen (dump_base_name);
+      const char *suffix = ".analyzer.txt";
+      char *dumpname = XNEWVEC (char, len + strlen (suffix) + 1);
+      memcpy (dumpname, dump_base_name, len);
+      memcpy (dumpname + len, suffix, strlen (suffix) + 1);
+      m_dumpfile = fopen (dumpname, "w");
+      fprintf (m_dumpfile, "got here\n");
+      free (dumpname);
+}
+#endif
+
+
+/* Helper function for checkers.  Is the CALL to the given function name,
+   and with the given number of arguments?  */
+
+bool
+is_named_call_p (const gcall *call, const char *funcname,
+		 unsigned int num_args)
+{
+  gcc_assert (funcname);
+
+  tree fndecl = gimple_call_fndecl (call);
+  if (!fndecl)
+    return false;
+
+  if (gimple_call_num_args (call) != num_args)
+    return false;
+
+  return 0 == strcmp (IDENTIFIER_POINTER (DECL_NAME (fndecl)), funcname);
+}
+
+// FIXME: horrible hack, so that we can use %qE, which needs subclassing of
+// pretty_printer.  Do we need a cloning op?
+
+static char *
+global_dc_print (const char *fmt, ...)
+{
+  pretty_printer *pp = global_dc->printer;
+  pp_clear_output_area (pp);
+
+  text_info ti;
+  rich_location rich_loc (line_table, UNKNOWN_LOCATION);
+
+  va_list ap;
+
+  va_start (ap, fmt);
+
+  ti.format_spec = _(fmt);
+  ti.args_ptr = &ap;
+  ti.err_no = 0;
+  ti.x_data = NULL;
+  ti.m_richloc = &rich_loc;
+
+#if 1
+#else
+  pp_show_color (pp) = pp_show_color (global_dc->printer);
+  pp_format_decoder (pp) = pp_format_decoder (global_dc->printer);
+#endif
+  pp_format (pp, &ti);
+  pp_output_formatted_text (pp);
+
+  va_end (ap);
+
+  char *result = xstrdup (pp_formatted_text (pp));
+
+  pp_clear_output_area (pp);
+
+  return result;
+}
+
+std::string
+expr_to_string (tree t)
+{
+  char *tmp;
+  if (TREE_CODE (t) == SSA_NAME)
+    // FIXME: is there a non-hacky way to distinguish these from the underlying var?
+    tmp = global_dc_print ("%<%E_%i%>", t, SSA_NAME_VERSION (t));
+  else
+    tmp = global_dc_print ("%qE", t);
+  std::string result = tmp;
+  free (tmp);
+  return result;
+}
diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h
new file mode 100644
index 0000000..1c82ee4
--- /dev/null
+++ b/gcc/analyzer/analyzer.h
@@ -0,0 +1,48 @@
+/* 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_ANALYZER_H
+#define GCC_ANALYZER_ANALYZER_H
+
+/* FIXME: this is all from an old experiment; what should be saved?  */
+
+class graphviz_out;
+
+extern bool is_named_call_p (const gcall *call, const char *funcname,
+			     unsigned int num_args);
+
+extern void register_analyzer_pass ();
+
+extern std::string expr_to_string (tree t);
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Declarations for specific families of tests (by source file), in
+   alphabetical order, for the checker plugin.  */
+extern void analyzer_checker_script_cc_tests ();
+extern void analyzer_constraints_cc_tests ();
+
+} /* end of namespace selftest.  */
+
+#endif /* #if CHECKING_P */
+
+#endif /* GCC_ANALYZER_ANALYZER_H */
diff --git a/gcc/analyzer/checker-script.cc b/gcc/analyzer/checker-script.cc
new file mode 100644
index 0000000..d68f14e
--- /dev/null
+++ b/gcc/analyzer/checker-script.cc
@@ -0,0 +1,1677 @@
+/*
+  FIXME: compare with:
+  ~/coding/gcc-python/gcc-python/sm-more-checkers/sm/parser.py
+  ~/coding/gcc-python/gcc-python/sm-more-checkers/docs/sm.rst
+  Changes:
+    "=>" became "->"
+    "$id$" became "@id".  */
+
+/* FIXME.
+   Copyright (C) 2019 Free Software Foundation, Inc.
+
+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/>.  */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "toplev.h"
+#include "cpplib.h"
+#include "diagnostic-core.h"
+#include "pretty-print.h"
+#include "selftest.h"
+
+namespace checker
+{
+class state_machine;
+
+class state_t;
+class state_name_t;
+
+class state_t
+{
+public:
+  state_t (int idx) : m_idx (idx) {}
+
+  const char *get_name (const state_machine *sm) const;
+
+  void print (pretty_printer *pp, const state_machine *sm) const;
+
+  //private:
+  int m_idx;
+};
+
+/* Either a list of states, or a wildcard.
+   States might be seen after a wildcard, so keep the "wildcard-ness"
+   around.  */
+
+class state_list_t
+{
+public:
+  state_list_t () : m_states (), m_wildcard (false) {}
+
+  void print (pretty_printer *pp, const state_machine *sm) const;
+
+  void add (state_name_t state_name);
+
+  /* Iterating.  */
+  unsigned get_length (const state_machine *sm) const;
+  state_t get_state_at (int idx) const;
+
+  auto_vec<state_t> m_states;
+  bool m_wildcard;
+};
+
+class type
+{
+public:
+  type (const char *fixme) {}
+
+  void print (pretty_printer *pp) const;
+};
+
+class decl
+{
+public:
+  decl (const char *name, type *t, bool stateful);
+
+  void print (pretty_printer *pp) const;
+
+private:
+  char *m_name;
+  type *m_type;
+  bool m_stateful;
+};
+
+/* state_name:
+     id.id
+     | id
+     | id ".*"
+     | "*"  */
+
+class state_name_t
+{
+public:
+  static state_name_t from_state (state_t state)
+  {
+    return state_name_t (state);
+  }
+  static state_name_t from_wildcard ()
+  {
+    return state_name_t (state_t (WILDCARD));
+  }
+
+  void print (pretty_printer *pp, const state_machine *sm) const;
+
+  bool wildcard_p () const
+  {
+    return m_state_or_wildcard.m_idx == WILDCARD;
+  }
+  state_t get_state () const
+  {
+    gcc_assert (!wildcard_p ());
+    return m_state_or_wildcard;
+  }
+
+private:
+  static const int WILDCARD = -1;
+
+  state_name_t (state_t state_or_wildcard)
+  : m_state_or_wildcard (state_or_wildcard)
+  {}
+
+  state_t m_state_or_wildcard;
+};
+
+class pattern
+{
+public:
+  virtual void print (pretty_printer *pp) const = 0;
+};
+
+class cpattern : public pattern
+{
+public:
+  void print (pretty_printer *pp) const FINAL OVERRIDE;
+
+private:
+  virtual void print_body (pretty_printer *pp) const;
+};
+
+class result_of_fncall : public cpattern
+{
+public:
+  result_of_fncall (const char *lhs, const char *fnname);
+
+private:
+  void print_body (pretty_printer *pp) const FINAL OVERRIDE;
+
+  char *m_lhs;
+  char *m_fnname;
+};
+
+class dereference : public cpattern
+{
+public:
+  dereference (const char *rhs);
+
+private:
+  void print_body (pretty_printer *pp) const FINAL OVERRIDE;
+
+  char *m_rhs;
+};
+
+class at_pattern : public pattern
+{
+public:
+  at_pattern (const char *id);
+  ~at_pattern ();
+
+  void print (pretty_printer *pp) const FINAL OVERRIDE;
+
+private:
+  char *m_id;
+};
+
+class use_named_pattern : public pattern
+{
+public:
+  use_named_pattern (const char *id);
+  ~use_named_pattern ();
+
+  void print (pretty_printer *pp) const FINAL OVERRIDE;
+
+private:
+  char *m_id;
+};
+
+class pattern_list
+{
+public:
+  void print (pretty_printer *pp) const;
+
+  void add_pattern (pattern *p);
+
+private:
+  auto_vec <pattern *> m_patterns;
+};
+
+class named_pattern_list
+{
+public:
+  named_pattern_list (const char *name, pattern_list *pl);
+
+  void print (pretty_printer *pp) const;
+
+private:
+  char *m_name;
+  pattern_list *m_pattern_list;
+};
+
+class state_outcome;
+class boolean_outcome;
+
+class outcome
+{
+public:
+  virtual void print (pretty_printer *pp) const = 0;
+
+  state_machine *get_sm () const { return m_sm; }
+
+  virtual state_outcome *dyncast_state_outcome () { return NULL; }
+  virtual boolean_outcome *dyncast_boolean_outcome () { return NULL; }
+
+protected:
+  outcome (state_machine *sm) : m_sm (sm) {}
+
+private:
+  state_machine *m_sm;
+};
+
+class script_outcome : public outcome
+{
+public:
+  script_outcome (state_machine *sm) : outcome (sm) {}
+
+  void print (pretty_printer *pp) const FINAL OVERRIDE;
+};
+
+class state_outcome : public outcome
+{
+public:
+  state_outcome (state_machine *sm, state_t state);
+  void print (pretty_printer *pp) const FINAL OVERRIDE;
+
+  state_outcome *dyncast_state_outcome () FINAL OVERRIDE { return this; }
+
+  //private:
+  state_t m_state;
+};
+
+class boolean_outcome : public outcome
+{
+public:
+  boolean_outcome (state_machine *sm, outcome *wrapped, bool guard);
+  void print (pretty_printer *pp) const FINAL OVERRIDE;
+
+  boolean_outcome *dyncast_boolean_outcome () FINAL OVERRIDE { return this; }
+
+private:
+  outcome *m_wrapped;
+  bool m_guard;
+};
+
+class pattern_rule
+{
+public:
+  pattern_rule (pattern_list *pl) : m_pattern_list (pl) {}
+
+  void add_outcome (outcome *o);
+
+  void print (pretty_printer *pp) const;
+
+  //private:
+  pattern_list *m_pattern_list;
+  auto_vec <outcome *> m_outcomes;
+};
+
+class state_clause
+{
+public:
+  void print (pretty_printer *pp, const state_machine *sm) const;
+
+  void dump_dot_to_pp (pretty_printer *pp, const state_machine *sm) const;
+
+  void add_pattern_rule (pattern_rule *pr);
+
+  //private:
+  state_list_t *m_states;
+  auto_vec <pattern_rule *> m_prs;
+};
+
+class state_machine
+{
+public:
+  /* Parser methods.  */
+  state_machine (const char *id);
+  ~state_machine ();
+  void add_decl (const char *name, type *t, bool stateful);
+  void add_named_pattern_list (const char *name, pattern_list *p);
+  void add_state_clause (state_clause *sc);
+
+  void print (pretty_printer *pp) const;
+  void dump_dot_to_pp (pretty_printer *pp) const;
+
+  const char *get_state_name (state_t state) const;
+  state_t get_state_by_name (const char *name);
+  int get_num_states () const { return m_state_names.length (); }
+
+private:
+  char *m_id;
+  auto_vec <decl *> m_decls;
+  auto_vec <named_pattern_list *> m_named_pattern_lists;
+  auto_vec <state_clause *> m_scs;
+
+  auto_vec<char *> m_state_names;
+};
+
+class script
+{
+public:
+  script (const char *path);
+  ~script ();
+
+  void add_state_machine (state_machine *sm);
+
+  void dump (FILE *f) const;
+  void print (pretty_printer *pp) const;
+
+  void dump_to_dot (const char *filename) const;
+  void dump_dot_to_pp (pretty_printer *pp) const;
+
+private:
+  char *m_path;
+  auto_vec <state_machine *> m_sms;
+};
+
+} // namespace checker
+
+using namespace checker;
+
+/* class checker::script.  */
+
+// FIXME
+
+script::script (const char *path)
+: m_path (xstrdup (path))
+{
+}
+
+script::~script ()
+{
+  free (m_path);
+
+  unsigned i;
+  state_machine *sm;
+  FOR_EACH_VEC_ELT (m_sms, i, sm)
+    delete sm;
+}
+
+void
+script::add_state_machine (state_machine *sm)
+{
+  m_sms.safe_push (sm);
+}
+
+void
+script::dump (FILE *outf) const
+{
+  pretty_printer pp;
+  pp_buffer (&pp)->stream = outf;
+  print (&pp);
+  pp_flush (&pp);
+}
+
+void
+script::print (pretty_printer *pp) const
+{
+  pp_printf (pp, "/* path: %s */\n", m_path);
+
+  unsigned i;
+  state_machine *sm;
+  FOR_EACH_VEC_ELT (m_sms, i, sm)
+    sm->print (pp);
+}
+
+void
+script::dump_to_dot (const char *filename) const
+{
+  FILE *fp = fopen (filename, "w");
+  pretty_printer pp;
+  pp.buffer->stream = fp;
+  dump_dot_to_pp (&pp);
+  pp_flush (&pp);
+  fclose (fp);
+}
+
+void
+script::dump_dot_to_pp (pretty_printer *pp) const
+{
+  pp_string (pp, "digraph \"");
+  pp_write_text_to_stream (pp);
+  pp_string (pp, m_path);
+  pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/false);
+  pp_string (pp, "\" {\n");
+
+  unsigned i;
+  state_machine *sm;
+  FOR_EACH_VEC_ELT (m_sms, i, sm)
+    sm->dump_dot_to_pp (pp);
+
+  /* Terminate "digraph" */
+  pp_string (pp, "}");
+}
+
+/* FIXME.  */
+
+const char *
+state_t::get_name (const state_machine *sm) const
+{
+  return sm->get_state_name (m_idx);
+}
+
+/* FIXME.  */
+
+void
+state_t::print (pretty_printer *pp, const state_machine *sm) const
+{
+  pp_string (pp, get_name (sm));
+}
+
+/* FIXME.  */
+
+void
+state_list_t::print (pretty_printer *pp, const state_machine *sm) const
+{
+  if (m_wildcard)
+    {
+      pp_string (pp, "FIXME.*");
+    }
+  else
+    {
+      unsigned i;
+      state_t *s;
+      FOR_EACH_VEC_ELT (m_states, i, s)
+	{
+	  if (i > 0)
+	    pp_string (pp, ", ");
+          pp_string (pp, "FIXME.");
+	  s->print (pp, sm);
+	}
+    }
+}
+
+/* FIXME.  */
+
+void
+state_list_t::add (state_name_t state_name)
+{
+  if (state_name.wildcard_p ())
+    m_wildcard = true;
+  else
+    m_states.safe_push (state_name.get_state ());
+}
+
+/* FIXME.  */
+
+unsigned
+state_list_t::get_length (const state_machine *sm) const
+{
+  if (m_wildcard)
+    return sm->get_num_states ();
+  else
+    return m_states.length ();
+}
+
+/* FIXME.  */
+
+state_t
+state_list_t::get_state_at (int idx) const
+{
+  if (m_wildcard)
+    return state_t (idx);
+  else
+    return m_states[idx];
+}
+
+/* class checker::type.  */
+
+void
+type::print (pretty_printer *pp) const
+{
+  // FIXME
+}
+
+/* class checker::decl.  */
+
+decl::decl (const char *name, type *t, bool stateful)
+: m_name (xstrdup (name)), m_type (t), m_stateful (stateful)
+{
+}
+
+void
+decl::print (pretty_printer *pp) const
+{
+  pp_string (pp, "  ");
+  if (m_stateful)
+    pp_printf (pp, "stateful ");
+  pp_printf (pp, "decl ");
+  m_type->print (pp);
+  pp_printf (pp, "%s;\n", m_name);
+}
+
+/* class checker::pattern.  */
+
+/* class checker::cpattern.  */
+
+void
+cpattern::print (pretty_printer *pp) const
+{
+  pp_printf (pp, "{{ ");
+  print_body (pp);
+  pp_printf (pp, " }}");
+}
+
+void
+cpattern::print_body (pretty_printer *pp) const
+{
+  pp_printf (pp, "FIXME");
+}
+
+/* class checker::result_of_fncall.  */
+
+result_of_fncall::result_of_fncall (const char *lhs, const char *fnname)
+: m_lhs (xstrdup (lhs)), m_fnname (xstrdup (fnname))
+{
+}
+
+void
+result_of_fncall::print_body (pretty_printer *pp) const
+{
+  pp_printf (pp, "%s = %s()", m_lhs, m_fnname);
+}
+
+/* class checker::dereference.  */
+
+dereference::dereference (const char *rhs)
+: m_rhs (xstrdup (rhs))
+{
+}
+
+void
+dereference::print_body (pretty_printer *pp) const
+{
+  pp_printf (pp, "*%s", m_rhs);
+}
+
+/* class checker::at_pattern.  */
+
+at_pattern::at_pattern (const char *id)
+: m_id (xstrdup (id))
+{
+}
+
+at_pattern::~at_pattern ()
+{
+  free (m_id);
+}
+
+void
+at_pattern::print (pretty_printer *pp) const
+{
+  pp_printf (pp, "@%s", m_id);
+}
+
+/* class checker::use_named_pattern.  */
+use_named_pattern::use_named_pattern (const char *id)
+: m_id (xstrdup (id))
+{
+}
+
+use_named_pattern::~use_named_pattern ()
+{
+  free (m_id);
+}
+
+void
+use_named_pattern::print (pretty_printer *pp) const
+{
+  pp_printf (pp, "%s", m_id);
+}
+
+/* class checker::pattern_list.  */
+void
+pattern_list::print (pretty_printer *pp) const
+{
+  unsigned i;
+  pattern *p;
+  FOR_EACH_VEC_ELT (m_patterns, i, p)
+    {
+      if (i > 0)
+	pp_string (pp, "| ");
+      p->print (pp);
+    }
+}
+
+void
+pattern_list::add_pattern (pattern *p)
+{
+  m_patterns.safe_push (p);
+}
+
+/* class checker::named_pattern_list.  */
+
+named_pattern_list::named_pattern_list (const char *name, pattern_list *pl)
+: m_name (xstrdup (name)),
+  m_pattern_list (pl)
+{
+}
+
+void
+named_pattern_list::print (pretty_printer *pp) const
+{
+  pp_printf (pp, "  pat %s ", m_name);
+  m_pattern_list->print (pp);
+  pp_string (pp, ";\n");
+}
+
+/* class checker::outcome.  */
+
+/* class checker::script_outcome.  */
+
+void
+script_outcome::print (pretty_printer *pp) const
+{
+  pp_string (pp, "{{ FIXME }}");
+}
+
+/* class checker::state_outcome.  */
+
+state_outcome::state_outcome (state_machine *sm, state_t state)
+: outcome (sm), m_state (state)
+{
+}
+
+void
+state_outcome::print (pretty_printer *pp) const
+{
+  m_state.print (pp, get_sm ());
+}
+
+/* class checker::boolean_outcome.  */
+
+boolean_outcome::boolean_outcome (state_machine *sm, outcome *wrapped, bool guard)
+: outcome (sm), m_wrapped (wrapped), m_guard (guard)
+{
+}
+
+void
+boolean_outcome::print (pretty_printer *pp) const
+{
+  pp_printf (pp, "%s=", m_guard ? "true" : "false");
+  m_wrapped->print (pp);
+}
+
+/* class checker::pattern_rule.  */
+
+void
+pattern_rule::add_outcome (outcome *o)
+{
+  m_outcomes.safe_push (o);
+}
+
+void
+pattern_rule::print (pretty_printer *pp) const
+{
+  m_pattern_list->print (pp);
+  pp_printf (pp, " -> ");
+
+  unsigned i;
+  outcome *o;
+  FOR_EACH_VEC_ELT (m_outcomes, i, o)
+    {
+      if (i > 0)
+	pp_string (pp, ", ");
+      o->print (pp);
+    }
+}
+
+/* class checker::state_clause.  */
+
+/* statelist ":" patternrulelist ";".  */
+
+void
+state_clause::print (pretty_printer *pp, const state_machine *sm) const
+{
+  pp_string (pp, "  ");
+  m_states->print (pp, sm);
+  pp_string (pp, ":\n");
+
+  unsigned i;
+  pattern_rule *pr;
+  FOR_EACH_VEC_ELT (m_prs, i, pr)
+    {
+      if (i > 0)
+	pp_string (pp, "    | ");
+      else
+	pp_string (pp, "      ");
+      pr->print (pp);
+      pp_string (pp, "\n");
+    }
+
+  pp_string (pp, "    ;\n");
+}
+
+/* FIXME.  */
+void
+state_clause::dump_dot_to_pp (pretty_printer *pp, const state_machine *sm) const
+{
+  /* Add edges.  */
+  for (unsigned i = 0; i < m_states->get_length (sm); i++)
+    {
+      state_t state = m_states->get_state_at (i);
+
+      unsigned j;
+      pattern_rule *pr;
+      FOR_EACH_VEC_ELT (m_prs, j, pr)
+	{
+	  unsigned k;
+	  outcome *o;
+	  FOR_EACH_VEC_ELT (pr->m_outcomes, k, o)
+	    {
+	      if (state_outcome *so = o->dyncast_state_outcome ())
+		{
+		  // FIXME: annotate the edge
+		  // maybe put the patterns as child nodes within the
+		  // original state?
+		  pp_printf (pp, "state_%s -> state_%s;\n",
+			     state.get_name (sm),
+			     so->m_state.get_name (sm));
+		}
+	    }
+	}
+    }
+}
+
+void
+state_clause::add_pattern_rule (pattern_rule *pr)
+{
+  m_prs.safe_push (pr);
+}
+
+/* class checker::state_machine.  */
+
+// FIXME
+
+state_machine::state_machine (const char *id)
+: m_id (xstrdup (id))
+{
+}
+
+// FIXME
+
+state_machine::~state_machine ()
+{
+  free (m_id);
+}
+
+void
+state_machine::add_decl (const char *name, type *t, bool stateful)
+{
+  decl *d = new decl (name, t, stateful);
+  m_decls.safe_push (d);
+}
+
+void
+state_machine::add_named_pattern_list (const char *name, pattern_list *pl)
+{
+  m_named_pattern_lists.safe_push (new named_pattern_list (name, pl));
+}
+
+void
+state_machine::add_state_clause (state_clause *sc)
+{
+  m_scs.safe_push (sc);
+}
+
+void
+state_machine::print (pretty_printer *pp) const
+{
+  pp_printf (pp, "sm %s {\n", m_id);
+
+  unsigned i;
+
+  decl *d;
+  FOR_EACH_VEC_ELT (m_decls, i, d)
+    d->print (pp);
+
+  named_pattern_list *npl;
+  FOR_EACH_VEC_ELT (m_named_pattern_lists, i, npl)
+    npl->print (pp);
+
+  state_clause *sc;
+  FOR_EACH_VEC_ELT (m_scs, i, sc)
+    sc->print (pp, this);
+
+  pp_string (pp, "}\n");
+}
+
+void
+state_machine::dump_dot_to_pp (pretty_printer *pp) const
+{
+  pp_printf (pp, "subgraph \"state_machine_%s\" {", m_id);
+  pp_printf (pp,
+	     ("style=\"dashed\";"
+	      " color=\"black\";"
+	      " label=\"%s\";\n"),
+	     m_id);
+
+  /* Add nodes for states.  */
+  {
+    unsigned i;
+    char *name;
+    FOR_EACH_VEC_ELT (m_state_names, i, name)
+      {
+	pp_printf (pp, "state_%s", name);
+	pp_printf (pp, " [shape=%s,style=filled,fillcolor=%s,label=\"",
+		   "record", "lightgrey");
+	pp_write_text_to_stream (pp);
+	pp_string (pp, name);
+	pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
+	pp_string (pp, "\"];\n\n");
+      }
+  }
+
+  /* Add edges for state clauses.  */
+  {
+    unsigned i;
+    state_clause *sc;
+    FOR_EACH_VEC_ELT (m_scs, i, sc)
+      sc->dump_dot_to_pp (pp, this);
+  }
+
+  /* Terminate per-bb "subgraph" */
+  pp_string (pp, "}\n");
+}
+
+/* FIXME.  */
+
+const char *
+state_machine::get_state_name (state_t state) const
+{
+  gcc_assert (state.m_idx >= 0);
+  gcc_assert ((unsigned)state.m_idx < m_state_names.length ());
+  return m_state_names[state.m_idx];
+}
+
+/* FIXME.  */
+
+state_t
+state_machine::get_state_by_name (const char *name)
+{
+  unsigned i;
+  char *existing_name;
+  FOR_EACH_VEC_ELT (m_state_names, i, existing_name)
+    {
+      if (0 == strcmp (name, existing_name))
+	return state_t (i);
+    }
+
+  /* Not found: add a new state.  */
+  m_state_names.safe_push (xstrdup (name));
+  return m_state_names.length () - 1;
+}
+
+static void
+#if GCC_VERSION >= 4001
+  ATTRIBUTE_GCC_DIAG (2,3)
+#endif
+fatal_at (const cpp_token *tk, const char *msg, ...)
+{
+  va_list ap;
+  va_start (ap, msg);
+  emit_diagnostic_valist (DK_FATAL, tk->src_loc, 0, msg, &ap);
+  va_end (ap);
+}
+
+namespace checker
+{
+
+class parser
+{
+public:
+  parser ();
+  ~parser ();
+
+  script *parse (const char *path);
+
+private:
+  script *parse_toplevel (const char *path);
+  state_machine *parse_sm (script *s);
+  void parse_sm_clauses (state_machine *sm);
+  void parse_sm_clause (state_machine *sm);
+  void parse_decl (state_machine *sm, bool stateful);
+  void parse_pat (state_machine *sm);
+  void parse_statelist_smclause (state_machine *sm);
+  state_list_t *parse_statelist (state_machine *sm);
+  state_name_t parse_statename (state_machine *sm);
+  void parse_patternrulelist (state_machine *sm, state_clause *sc);
+  pattern_rule *parse_patternrule (state_machine *sm);
+  pattern_list *parse_pattern_list (state_machine *sm);
+  pattern *parse_pattern (state_machine *sm);
+  pattern *parse_cpattern (state_machine *sm);
+  void parse_outcomes (state_machine *sm, pattern_rule *pr);
+  outcome *parse_outcome (state_machine *sm);
+  outcome *parse_boolean_outcome (state_machine *sm, bool guard);
+
+  const cpp_token *next ();
+  const cpp_token *peek (unsigned = 1);
+  const cpp_token *peek_ident (const char * = NULL, unsigned = 1);
+  const cpp_token *expect (enum cpp_ttype);
+  const char *get_ident (location_t *outloc = NULL);
+  const cpp_token *eat_ident (const char *);
+
+  cpp_reader *r;
+};
+
+} // namespace checker
+
+/* class checker::parser.  */
+
+/* FIXME.  */
+
+parser::parser ()
+{
+  r = cpp_create_reader (CLK_GNUCXX, /*c_dialect_cxx () ? CLK_GNUCXX: CLK_GNUC89 */
+			 ident_hash, line_table);
+
+  cpp_options *cpp_opts = cpp_get_options (r);
+  cpp_opts->warn_dollars = 0;
+
+#if 0
+  // FIXME: wire up to c-common and reuse c_cpp_diagnostic
+  cpp_callbacks *cb = cpp_get_callbacks (r);
+  cb->diagnostic = c_cpp_diagnostic;
+#endif
+}
+
+/* FIXME.  */
+
+parser::~parser ()
+{
+  // TODO: m_lexer
+}
+
+/* FIXME.  */
+
+script *
+parser::parse (const char *path)
+{
+  /* FIXME: should we reuse c-lex.c? */
+
+  if (!cpp_read_main_file (r, path))
+    return NULL;
+
+  return parse_toplevel (path);
+}
+
+#if 0
+/* FIXME: maybe build a tree of these during parsing, then optimize it down
+   to something else.  This way we can emit good error messages (as we have
+   source locations for everything.  */
+
+enum kind {
+  NODE_SCRIPT,
+  NODE_SM
+};
+
+/* FIXME.  */
+
+class ast_node
+{
+public:
+  ast_node (enum kind kind, location_t loc)
+  : m_children (), m_kind (kind), m_loc (loc) {}
+
+  void add_child (ast_node *child) { m_children.safe_push (child); }
+
+  auto_vec <ast_node *> m_children;
+  enum kind m_kind;
+  location_t m_loc;
+};
+#endif
+
+/* One or more "sm":
+     checker:
+       sm
+       | sm checker  */
+
+script *
+parser::parse_toplevel (const char *path)
+{
+  script *s = new script (path);
+
+#if 0
+  ast_node *node_s = new ast_node (NODE_SCRIPT, UNKNOWN_LOCATION); // FIXME
+#endif
+
+  const cpp_token *token = next ();
+  while (token->type != CPP_EOF)
+    {
+      _cpp_backup_tokens (r, 1);
+      state_machine *sm = parse_sm (s);
+      if (sm)
+	s->add_state_machine (sm);
+      token = next ();
+    }
+  /* TODO: warn about unreachable states.  */
+
+#if 0
+  delete node_s;
+#endif
+
+  return s;
+}
+
+/* Parse an "sm".
+
+   sm:
+     "sm" id "{" smclauses "}"  */
+
+state_machine *
+parser::parse_sm (script *s)
+{
+  if (!peek_ident ("sm"))
+    {
+      fatal_error (peek ()->src_loc, "expected %qs", "sm");
+      return NULL;
+    }
+
+  eat_ident ("sm");
+  const char *id = get_ident ();
+  expect (CPP_OPEN_BRACE);
+
+  state_machine *sm = new state_machine (id);
+
+  parse_sm_clauses (sm);
+
+  expect (CPP_CLOSE_BRACE);
+
+  return sm;
+}
+
+/* Parse smclauses: one or more smclause.
+
+   smclauses:
+     smclause
+     | smclauses smclause  */
+
+void
+parser::parse_sm_clauses (state_machine *sm)
+{
+  const cpp_token *token = peek ();
+  while (token->type != CPP_CLOSE_BRACE)
+    {
+      parse_sm_clause (sm);
+      token = peek ();
+    }
+}
+
+/* Parse smclause.
+
+   smclause:
+     "stateful"? "decl" declkind id ";"
+     | "pat" id pattern ";"
+     | statelist ":" patternrulelist ";"  */
+
+void
+parser::parse_sm_clause (state_machine *sm)
+{
+  const cpp_token *id = peek_ident ();
+  if (!id)
+    fatal_at (peek (),
+	      "expected %<stateful%>, %<decl%>, %<pat%>, or identifier");
+  //inform (input_location, "got id: %qs", id);
+
+  /* Handle decls.  */
+  if (peek_ident ("stateful"))
+    {
+      eat_ident ("stateful");
+      parse_decl (sm, true);
+      return;
+    }
+  if (peek_ident ("decl"))
+    {
+      parse_decl (sm, false);
+      return;
+    }
+  if (peek_ident ("pat"))
+    {
+      parse_pat (sm);
+      return;
+    }
+
+  parse_statelist_smclause (sm);
+}
+
+// FIXME
+/* Any "stateful" has already been consumed.  */
+
+void
+parser::parse_decl (state_machine *sm, bool stateful)
+{
+  eat_ident ("decl");
+
+  // TODO: parse declkind;
+  const char *id_type = get_ident ();
+  type *t = new type (id_type);
+
+  const char *id = get_ident ();
+
+  expect (CPP_SEMICOLON);
+
+  sm->add_decl (id, t, stateful);
+}
+
+void
+parser::parse_pat (state_machine *sm)
+{
+  eat_ident ("pat");
+
+  const char *id = get_ident ();
+
+  pattern_list *pl = parse_pattern_list (sm);
+
+  expect (CPP_SEMICOLON);
+
+  sm->add_named_pattern_list (id, pl);
+}
+
+// FIXME
+/*
+    smclause:
+      statelist ":" patternrulelist ";"  */
+
+void
+parser::parse_statelist_smclause (state_machine *sm)
+{
+  state_clause *sc = new state_clause ();
+
+  sc->m_states = parse_statelist (sm/*, sc*/);
+  expect (CPP_COLON);
+  parse_patternrulelist (sm, sc);
+  expect (CPP_SEMICOLON);
+
+  sm->add_state_clause (sc);
+}
+
+/* Parse one or more comma-separated statename.
+
+   statelist:
+     statename
+     | statename "," statelist  */
+
+state_list_t *
+parser::parse_statelist (state_machine *sm)
+{
+  state_list_t *result = new state_list_t ();
+  while (1)
+    {
+      state_name_t sn = parse_statename (sm);
+      result->add (sn);
+      if (peek ()->type != CPP_COMMA)
+	break;
+      expect (CPP_COMMA);
+    }
+  return result;
+}
+
+/* FIXME:
+   statename:
+     id.id
+     | id
+     | id ".*"
+     | "*"  */
+
+state_name_t
+parser::parse_statename (state_machine *sm)
+{
+  location_t loc;
+  const char *id = get_ident (&loc);
+  inform (loc, "got id: %qs", id);
+
+  // TODO: look up the decl
+
+  const cpp_token *tok = peek ();
+  if (tok->type == CPP_DOT_STAR)
+    {
+      location_t loc2 = tok->src_loc;
+      next ();
+      return state_name_t::from_wildcard ();
+    }
+  if (tok->type == CPP_DOT)
+    {
+      next ();
+      location_t loc2;
+      const char *id2 = get_ident (&loc2);
+      inform (loc2, "got id2: %qs", id2);
+      return state_name_t::from_state (sm->get_state_by_name (id2));
+    }
+
+  // FIXME
+  return state_name_t::from_state (sm->get_state_by_name ("FIXME"));
+}
+
+/* Parse one or more "|"-separated patternrule.
+
+   patternrulelist:
+     patternrule
+     | patternrule "|" patternrulelist  */
+
+void
+parser::parse_patternrulelist (state_machine *sm, state_clause *sc)
+{
+  while (1)
+    {
+      pattern_rule *pr = parse_patternrule (sm);
+      if (pr)
+	sc->add_pattern_rule (pr);
+      if (peek ()->type != CPP_OR)
+	break;
+      expect (CPP_OR);
+    }
+}
+
+/* FIXME.
+   patternrule:
+     pattern-list "->" outcomes  */
+
+pattern_rule *
+parser::parse_patternrule (state_machine *sm)
+{
+  pattern_list *pl = parse_pattern_list (sm);
+  pattern_rule *pr = new pattern_rule (pl);
+  expect (CPP_DEREF);
+  parse_outcomes (sm, pr);
+  return pr;
+}
+
+/* pattern-list:
+      pattern
+      pattern-list "|" pattern  */
+
+pattern_list *
+parser::parse_pattern_list (state_machine *sm)
+{
+  pattern_list *pl = new pattern_list ();
+
+  while (1)
+    {
+      pattern *p = parse_pattern (sm);
+      if (p)
+	pl->add_pattern (p);
+      if (peek ()->type != CPP_OR)
+	break;
+      expect (CPP_OR);
+    }
+
+  return pl;
+}
+
+/* FIXME:
+    pattern:
+      "{" cpattern "}"
+      | id
+      | "@" id  */
+
+pattern *
+parser::parse_pattern (state_machine *sm)
+{
+  const cpp_token *tok = peek ();
+  if (tok->type == CPP_OPEN_BRACE)
+    {
+      expect (CPP_OPEN_BRACE);
+      pattern *p = parse_cpattern (sm);
+      expect (CPP_CLOSE_BRACE);
+      return p;
+    }
+  if (tok->type == CPP_ATSIGN)
+    {
+      expect (CPP_ATSIGN);
+      const char *id = get_ident ();
+      return new at_pattern (id);
+    }
+  const char *id = get_ident ();
+  return new use_named_pattern (id);
+}
+
+/* FIXME.
+    cpattern
+    : assignment-cpattern
+    | addressof-cpattern
+    | return-cpattern
+    | result-of-fncall-cpattern
+    | comparison-cpattern
+
+   assignment-cpattern:
+     id "=" literal
+     | id "=" literal
+     | id "=" id
+   e.g. "q = 0"
+
+   addressof-cpattern:
+     id "=" "&" id
+   e.g. "&var"
+
+   return-cpattern:
+     "return" id
+     | "return" literal
+     | "return"
+   e.g. "return var"
+
+   result-of-fncall-cpattern:
+     id "=" id "(" fncall_args ")"
+   e.g. "ptr = malloc()".
+
+   dereference-cpattern:
+     "*" id
+   e.g. "*ptr"
+
+   comparison-cpattern:
+     id comparison literal
+     id comparison id
+   e.g. "ptr == 0".  */
+
+/*
+def p_fncall_arg(p):
+    '''
+    fncall_arg : ID
+               | LITERAL_STRING
+               | LITERAL_NUMBER
+    '''
+    p[0] = p[1]
+
+def p_nonempty_fncall_args(p):
+    '''
+    nonempty_fncall_args : fncall_arg
+                         | fncall_args COMMA fncall_arg
+    '''
+    if len(p) == 2:
+        p[0] = [p[1]]
+    else:
+        p[0] = p[1] + [p[3]]
+
+def p_fncall_args_from_nonempty(p):
+    '''
+    fncall_args : nonempty_fncall_args
+    '''
+    p[0] = p[1]
+
+def p_fncall_args_from_empty(p):
+    '''
+    fncall_args : empty
+    '''
+    p[0] = []
+
+def p_cpattern_arg_of_fn_call(p):
+    'cpattern : ID LPAREN fncall_args RPAREN'
+    # e.g. "free(ptr)"
+    p[0] = ArgsOfFnCall(fnname=p[1], args=p[3])
+
+def p_cpattern_arraylookup(p):
+    'cpattern : ID LSQUARE ID RSQUARE'
+    # e.g. "arr[x]"
+    p[0] = ArrayLookup(array=p[1], index=p[3])
+
+def p_cpattern_usage(p):
+    'cpattern : ID'
+*/
+
+pattern *
+parser::parse_cpattern (state_machine *sm)
+{
+  const cpp_token *tok1 = peek ();
+  const cpp_token *tok2 = peek (2);
+  if (tok1->type == CPP_NAME && tok2 && tok2->type == CPP_EQ)
+    {
+      const char *lhs = get_ident ();
+      expect (CPP_EQ);
+      /* id "=" ...  */
+      const cpp_token *rhs = peek ();
+      inform (rhs->src_loc, "FIXME");
+      if (rhs->type == CPP_NAME)
+	{
+	  const char *rhs_name = get_ident ();
+	  if (peek ()->type == CPP_OPEN_PAREN)
+	    {
+	      /* result-of-fncall-cpattern.  */
+
+	      /* FIXME: for now, consume until close paren.  */
+	      while (1)
+		{
+		  const cpp_token *tok = peek ();
+		  if (tok->type == CPP_CLOSE_PAREN || tok->type == CPP_EOF)
+		    break;
+		  next ();
+		}
+	      next ();
+	      return new result_of_fncall (lhs, rhs_name);
+	    }
+	  else
+	    inform (rhs->src_loc, "assignment");
+	}
+    }
+  if (tok1->type == CPP_MULT)
+    {
+      /* dereference-cpattern.  */
+      expect (CPP_MULT);
+      const char *rhs = get_ident ();
+      return new dereference (rhs);
+    }
+
+  /* FIXME: for now, consume until close brace.  */
+  while (1)
+    {
+      const cpp_token *tok = peek ();
+      if (tok->type == CPP_CLOSE_BRACE || tok->type == CPP_EOF)
+	break;
+      next ();
+    }
+
+  // FIXME:
+  return new cpattern ();
+}
+
+/* FIXME.
+   outcomes:
+     outcome
+     | outcome "," outcomes */
+
+void
+parser::parse_outcomes (state_machine *sm, pattern_rule *pr)
+{
+  while (1)
+    {
+      outcome *o = parse_outcome (sm);
+      if (o)
+	pr->add_outcome (o);
+      if (peek ()->type != CPP_COMMA)
+	break;
+      expect (CPP_COMMA);
+    }
+}
+
+/* FIXME.
+
+   outcome:
+     statename     e.g. "ptr.unknown"
+     | "true" "=" outcome    e.g. "true=ptr.null"
+     | "false" "=" outcome
+     | PYTHON  FIXME
+       e.g. "{ error('use of possibly-NULL pointer %s', ptr) }"  */
+
+outcome *
+parser::parse_outcome (state_machine *sm)
+{
+  const cpp_token *tok = peek_ident ();
+
+  if (peek_ident ("true"))
+    return parse_boolean_outcome (sm, true);
+  else if (peek_ident ("false"))
+    return parse_boolean_outcome (sm, false);
+  else if (peek ()->type == CPP_OPEN_BRACE)
+    {
+      expect (CPP_OPEN_BRACE);
+      expect (CPP_OPEN_BRACE);
+
+      /* FIXME: for now, consume until close brace.  */
+      while (1)
+	{
+	  const cpp_token *tok = peek ();
+	  if (tok->type == CPP_CLOSE_BRACE || tok->type == CPP_EOF)
+	    break;
+	  next ();
+	}
+
+      expect (CPP_CLOSE_BRACE);
+      expect (CPP_CLOSE_BRACE);
+      return new script_outcome (sm);
+    }
+
+  // FIXME:
+  state_name_t sn = parse_statename (sm);
+
+#if 0
+  if (sn.wildcard_p ())
+    {
+      error ("cannot transition to a wildcard state");
+      return NULL;
+    }
+#endif
+  return new state_outcome (sm, sn.get_state ());
+}
+
+/* Parse a guarded outcome:
+
+   outcome:
+     "true" "=" outcome
+     | "false" "=" outcome  */
+
+outcome *
+parser::parse_boolean_outcome (state_machine *sm, bool guard)
+{
+  next ();
+  expect (CPP_EQ);
+  return new boolean_outcome (sm, parse_outcome (sm), guard);
+}
+
+/* Lexing helpers.  */
+
+/* Read the next non-whitespace token from R.  */
+
+const cpp_token *
+parser::next ()
+{
+  const cpp_token *token;
+  do
+    {
+      token = cpp_get_token (r);
+    }
+  while (token->type == CPP_PADDING);
+  return token;
+}
+
+/* Peek at the next non-whitespace token from R.  */
+
+const cpp_token *
+parser::peek (unsigned num)
+{
+  const cpp_token *token;
+  unsigned i = 0;
+  do
+    {
+      token = cpp_peek_token (r, i++);
+    }
+  while (token->type == CPP_PADDING
+	 || (--num > 0));
+  /* If we peek at EOF this is a fatal error as it leaves the
+     cpp_reader in unusable state.  Assume we really wanted a
+     token and thus this EOF is unexpected.  */
+  if (token->type == CPP_EOF)
+    ; // fatal_at (token, "unexpected end of file");
+  return token;
+}
+
+/* Peek at the next identifier token (or return NULL if the next
+   token is not an identifier or equal to ID if supplied).  */
+
+const cpp_token *
+parser::peek_ident (const char *id, unsigned num)
+{
+  const cpp_token *token = peek (num);
+  if (token->type != CPP_NAME)
+    return 0;
+
+  if (id == 0)
+    return token;
+
+  const char *t = (const char *) CPP_HASHNODE (token->val.node.node)->ident.str;
+  if (strcmp (id, t) == 0)
+    return token;
+
+  return 0;
+}
+
+/* Read the next token from R and assert it is of type TK.  */
+
+const cpp_token *
+parser::expect (enum cpp_ttype tk)
+{
+  const cpp_token *token = next ();
+  if (token->type != tk)
+    fatal_at (token, "expected %s, got %s",
+	      cpp_type2name (tk, 0), cpp_type2name (token->type, 0));
+
+  return token;
+}
+
+/* Read the next token from R and assert it is of type CPP_NAME and
+   return its value.  */
+
+const char *
+parser::get_ident (location_t *outloc)
+{
+  const cpp_token *token = expect (CPP_NAME);
+  if (outloc)
+    *outloc = token->src_loc;
+  return (const char *)CPP_HASHNODE (token->val.node.node)->ident.str;
+}
+
+/* Eat an identifier token with value S from R.  */
+
+const cpp_token *
+parser::eat_ident (const char *s)
+{
+  const cpp_token *token = peek ();
+  const char *t = get_ident ();
+  if (strcmp (s, t) != 0)
+    fatal_at (token, "expected '%s' got '%s'\n", s, t);
+  return token;
+}
+
+static script *
+parse_script (const char *path)
+{
+  parser p;
+  return p.parse (path);
+}
+
+void
+debug (script *s)
+{
+  s->dump (stderr);
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Selftests for checker scripts.  */
+
+/* FIXME.  */
+
+static void
+test_parsing_malloc_checker ()
+{
+  // FIXME:
+  script *s = parse_script ("../../src/checkers/malloc_checker.sm");
+  debug (s);
+  s->dump_to_dot ("malloc_checker.dot");
+}
+
+/* FIXME.  */
+
+static void
+test_parsing_taint ()
+{
+  // FIXME:
+  script *s = parse_script ("../../src/checkers/taint.sm");
+  debug (s);
+  s->dump_to_dot ("taint.dot");
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+analyzer_checker_script_cc_tests ()
+{
+  test_parsing_malloc_checker ();
+  test_parsing_taint ();
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
diff --git a/gcc/analyzer/config-lang.in b/gcc/analyzer/config-lang.in
new file mode 100644
index 0000000..df2ca21
--- /dev/null
+++ b/gcc/analyzer/config-lang.in
@@ -0,0 +1,34 @@
+# Top level configure fragment for static analyzer
+#   Copyright (C) 2013-2015 Free Software Foundation, Inc.
+
+#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/>.
+
+# Configure looks for the existence of this file to auto-config each language.
+# We define several parameters used by configure:
+#
+# language	- name of language as it would appear in $(LANGUAGES)
+# compilers	- value to add to $(COMPILERS)
+
+language="analyzer"
+
+compilers=""
+
+target_libs=""
+
+gtfiles=""
+
+build_by_default="no"
diff --git a/gcc/analyzer/constraints.cc b/gcc/analyzer/constraints.cc
new file mode 100644
index 0000000..4cbec64
--- /dev/null
+++ b/gcc/analyzer/constraints.cc
@@ -0,0 +1,989 @@
+/* FIXME.  */
+#include "config.h"
+#include "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "tm.h"
+#include "toplev.h"
+#include "hash-table.h"
+#include "vec.h"
+#include "ggc.h"
+#include "basic-block.h"
+#include "tree-ssa-alias.h"
+#include "internal-fn.h"
+#include "gimple-fold.h"
+#include "tree-eh.h"
+#include "gimple-expr.h"
+#include "is-a.h"
+#include "gimple.h"
+#include "tree-pass.h"
+#include "intl.h"
+#include "context.h"
+#include "diagnostic.h"
+#include "bitmap.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "gimple-pretty-print.h"
+#include "tree-pretty-print.h"
+#include "analyzer.h"
+#include "graphviz.h"
+#include "cgraph.h"
+#include "diagnostic-path.h"
+#include "gcc-rich-location.h"
+#include "tree-dfa.h"
+#include <string>
+#include "analyzer-logging.h"
+#include "stringpool.h"
+#include "selftest.h"
+#include "fold-const.h"
+#include "tristate.h"
+#include "constraints.h"
+#include "supergraph.h"
+
+/* FIXME.  */
+
+std::string
+path_var::to_str () const
+{
+  if (CONSTANT_CLASS_P (m_tree))
+    return expr_to_string (m_tree);
+  else
+    return (std::string ("(")
+	    + expr_to_string (m_tree)
+	    + " @ "
+	    + std::to_string (m_stack_depth)
+	    + ")");
+}
+
+/* FIXME.  */
+
+equiv_class::equiv_class () : m_constant (NULL_TREE), m_vars ()
+{
+}
+
+/* FIXME.  */
+
+equiv_class::equiv_class (const equiv_class &other)
+: m_constant (other.m_constant)
+{
+  // FIXME: copy-ctor for auto_vec!
+  int i;
+  path_var *v;
+  FOR_EACH_VEC_ELT (other.m_vars, i, v)
+    m_vars.safe_push (*v);
+}
+
+/* FIXME.  */
+
+std::string
+equiv_class::to_str () const
+{
+  std::string result ("{");
+  int i;
+  path_var *v;
+  FOR_EACH_VEC_ELT (m_vars, i, v)
+    {
+      gcc_assert (!CONSTANT_CLASS_P (v->m_tree));
+      if (i > 0)
+	result += " == ";
+      result += v->to_str ();
+    }
+  if (m_constant)
+    {
+      if (i > 0)
+	result += " == ";
+      result += expr_to_string (m_constant);
+    }
+  return result + "}";
+}
+
+/* FIXME.  */
+
+void
+equiv_class::add (path_var v)
+{
+  if (CONSTANT_CLASS_P (v.m_tree))
+    m_constant = v.m_tree;
+  else
+    m_vars.safe_push (v);
+}
+
+/* Remove V from this equivalence class.
+   Return true if V was the last var in the equivalence class (suggesting
+   a possible leak).  */
+
+bool
+equiv_class::del (path_var v)
+{
+  gcc_assert (!CONSTANT_CLASS_P (v.m_tree));
+
+  int i;
+  path_var *iv;
+  FOR_EACH_VEC_ELT (m_vars, i, iv)
+    {
+      if (*iv == v)
+	{
+	  m_vars[i] = m_vars[m_vars.length () - 1];
+	  m_vars.pop ();
+	  return m_vars.length () == 0;
+	}
+    }
+
+  /* V must be in the class.  */
+  gcc_unreachable ();
+  return false;
+}
+
+/* FIXME.  */
+
+const char *
+constraint_op_code (enum constraint_op c_op)
+{
+  switch (c_op)
+    {
+    default:
+      gcc_unreachable ();
+    case CONSTRAINT_NE: return "!=";
+    case CONSTRAINT_LT: return "<";
+    case CONSTRAINT_LE: return "<=";
+    }
+}
+
+/* FIXME.  */
+
+static tristate
+eval_constraint_op_for_op (enum constraint_op c_op, enum tree_code t_op)
+{
+  switch (c_op)
+    {
+    default:
+      gcc_unreachable ();
+    case CONSTRAINT_NE:
+      if (t_op == EQ_EXPR)
+	return tristate (tristate::TS_FALSE);
+      if (t_op == NE_EXPR)
+	return tristate (tristate::TS_TRUE);
+      break;
+    case CONSTRAINT_LT:
+      if (t_op == LT_EXPR || t_op == LE_EXPR || t_op == NE_EXPR)
+	return tristate (tristate::TS_TRUE);
+      if (t_op == EQ_EXPR || t_op == GT_EXPR || t_op == GE_EXPR)
+	return tristate (tristate::TS_FALSE);
+      break;
+    case CONSTRAINT_LE:
+      if (t_op == LE_EXPR)
+	return tristate (tristate::TS_TRUE);
+      if (t_op == GT_EXPR)
+	return tristate (tristate::TS_FALSE);
+      break;
+    }
+  return tristate (tristate::TS_UNKNOWN);
+}
+
+/* FIXME.  */
+
+std::string
+constraint::to_str (const path_state &ps) const
+{
+  return (m_lhs.to_str () + ": "
+	  + m_lhs.get_obj (ps).to_str ()
+	  + constraint_op_code (m_op)
+	  + m_rhs.to_str () + ": "
+	  + m_rhs.get_obj (ps).to_str ());
+}
+
+/* FIXME.  */
+
+const equiv_class &
+equiv_class_id::get_obj (const path_state &ps) const
+{
+  return ps.get_equiv_class_by_index (m_idx);
+}
+
+/* FIXME.  */
+
+equiv_class &
+equiv_class_id::get_obj (path_state &ps) const
+{
+  return ps.get_equiv_class_by_index (m_idx);
+}
+
+/* FIXME.  */
+
+path_state::path_state (const path_state &other)
+{
+  // FIXME: auto_vec's copy_ctor...
+  int i;
+  equiv_class *ec;
+  FOR_EACH_VEC_ELT (other.m_equiv_classes, i, ec)
+    m_equiv_classes.safe_push (new equiv_class (*ec));
+  constraint *c;
+  FOR_EACH_VEC_ELT (other.m_constraints, i, c)
+    m_constraints.safe_push (*c);
+}
+
+/* FIXME.  */
+
+path_state::~path_state ()
+{
+  int i;
+  equiv_class *ec;
+  FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+    delete ec;
+}
+
+/* FIXME.  */
+
+std::string
+path_state::to_str () const
+{
+  std::string result ("{");
+  int i;
+  equiv_class *ec;
+  FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+    {
+      if (i > 0)
+	result += ", ";
+      result += equiv_class_id (i).to_str () + ": ";
+      result += ec->to_str ();
+    }
+  result += "|";
+  constraint *c;
+  FOR_EACH_VEC_ELT (m_constraints, i, c)
+    {
+      if (i > 0)
+	result += " && ";
+      result += c->to_str (*this);
+    }
+  result += "}";
+  return result;
+}
+
+/* FIXME.  */
+
+void
+path_state::on_assignment (path_var lhs, path_var rhs)
+{
+  /* Clear LHS.  */
+  {
+    equiv_class_id lhs_ec_id (-1);
+    if (get_equiv_class (lhs, &lhs_ec_id))
+      {
+	lhs_ec_id.get_obj (*this).del (lhs); // TODO
+      }
+  }
+
+  add_constraint (lhs, EQ_EXPR, rhs);
+}
+
+/* FIXME.
+   Attempt to extend this path's state with the constraints required by
+   EDGE (where LAST_STMT, if non-NULL, was the stmt guarding the edge).
+   Return true if the edge can be followed, false if it can't be.  */
+
+bool
+path_state::apply_constraints_for_edge (const superedge &edge,
+					const gimple *last_stmt)
+{
+  if (last_stmt == NULL)
+    return true;
+
+  if (const gcond *cond_stmt = dyn_cast <const gcond *> (last_stmt))
+    return apply_constraints_for_gcond (edge, cond_stmt);
+
+  // FIXME: some linker issues with is_a_helper const
+  if (const gswitch *switch_stmt = dyn_cast <gswitch *> (const_cast <gimple *> (last_stmt)))
+    return apply_constraints_for_gswitch (edge, switch_stmt);
+
+  return true;
+}
+
+/* FIXME.  */
+
+bool
+path_state::apply_constraints_for_gcond (const superedge &edge,
+					 const gcond *cond_stmt)
+{
+  ::edge cfg_edge = edge.get_any_cfg_edge ();
+  gcc_assert (cfg_edge != NULL);
+  gcc_assert (cfg_edge->flags & (EDGE_TRUE_VALUE | EDGE_FALSE_VALUE));
+
+  enum tree_code op = gimple_cond_code (cond_stmt);
+  tree lhs = gimple_cond_lhs (cond_stmt);
+  tree rhs = gimple_cond_rhs (cond_stmt);
+  if (cfg_edge->flags & EDGE_FALSE_VALUE)
+    op = invert_tree_comparison (op, false /* honor_nans */);
+  // FIXME: stack depth:
+  return add_constraint (path_var (lhs, 0), op, path_var (rhs, 0));
+}
+
+/* FIXME.  */
+
+bool
+path_state::apply_constraints_for_gswitch (const superedge &edge,
+					   const gswitch *switch_stmt)
+{
+  // FIXME: use an as_a for this cast:
+  const switch_cfg_superedge &switch_edge = (const switch_cfg_superedge &)edge;
+  path_var index (gimple_switch_index (switch_stmt), 0); // FIXME: depth
+  tree case_label = switch_edge.get_case_label ();
+  gcc_assert (TREE_CODE (case_label) == CASE_LABEL_EXPR);
+  path_var lower_bound (CASE_LOW (case_label), 0); // FIXME: depth
+  path_var upper_bound (CASE_HIGH (case_label), 0); // FIXME: depth
+  if (lower_bound.m_tree)
+    {
+      if (upper_bound.m_tree)
+	{
+	  /* Range.  */
+	  if (!add_constraint (index, GE_EXPR, lower_bound))
+	    return false;
+	  return add_constraint (index, LE_EXPR, upper_bound);
+	}
+      else
+	/* Single-value.  */
+	return add_constraint (index, EQ_EXPR, lower_bound);
+    }
+  else
+    {
+      /* The default case.
+	 Add exclusions based on the other cases.  */
+      for (unsigned other_idx = 1;
+	   other_idx < gimple_switch_num_labels (switch_stmt);
+	   other_idx++)
+	{
+	  tree other_label = gimple_switch_label (switch_stmt,
+						  other_idx);
+	  path_var other_lower_bound (CASE_LOW (other_label), 0); // FIXME: depth
+	  path_var other_upper_bound (CASE_HIGH (other_label), 0); // FIXME: depth
+	  gcc_assert (other_lower_bound.m_tree);
+	  if (other_upper_bound.m_tree)
+	    {
+	      /* Exclude this range-valued case.
+		 For now, we just exclude the boundary values.
+		 TODO: exclude the values within the region.  */
+	      if (!add_constraint (index, NE_EXPR, other_lower_bound))
+		return false;
+	      if (!add_constraint (index, NE_EXPR, other_upper_bound))
+		return false;
+	    }
+	  else
+	    /* Exclude this single-valued case.  */
+	    if (!add_constraint (index, NE_EXPR, other_lower_bound))
+	      return false;
+	}
+      return true;
+    }
+}
+
+/* FIXME.  */
+
+bool
+path_state::add_constraint (path_var lhs, enum tree_code op, path_var rhs)
+{
+  equiv_class_id lhs_ec_id = get_or_add_equiv_class (lhs);
+  equiv_class_id rhs_ec_id = get_or_add_equiv_class (rhs);
+
+  tristate t = eval_condition (lhs_ec_id, op, rhs_ec_id);
+
+  /* Discard constraints that are already known.  */
+  if (t.is_true ())
+    return true;
+
+  /* Reject unsatisfiable constraints.  */
+  if (t.is_false ())
+    return false;
+
+  gcc_assert (lhs_ec_id != rhs_ec_id);
+
+  /* For now, simply accumulate constraints, without attempting any further
+     optimization.  */
+  switch (op)
+    {
+    case EQ_EXPR:
+      {
+	/* Merge rhs_ec into lhs_ec.  */
+	equiv_class &lhs_ec_obj = lhs_ec_id.get_obj (*this);
+	const equiv_class &rhs_ec_obj = rhs_ec_id.get_obj (*this);
+
+	int i;
+	path_var *v;
+	FOR_EACH_VEC_ELT (rhs_ec_obj.m_vars, i, v)
+	  lhs_ec_obj.add (*v);
+
+	if (rhs_ec_obj.m_constant)
+	  lhs_ec_obj.m_constant = rhs_ec_obj.m_constant;
+
+	/* Drop rhs equivalence class, overwriting it with the
+	   final ec (which might be the same one).  */
+	equiv_class_id final_ec_id = m_equiv_classes.length () - 1;
+	equiv_class *old_ec = m_equiv_classes[rhs_ec_id.m_idx];
+	equiv_class *final_ec = m_equiv_classes.pop ();
+	if (final_ec != old_ec)
+	  m_equiv_classes[rhs_ec_id.m_idx] = final_ec;
+	delete old_ec;
+
+	/* Update the constraints.  */
+	constraint *c;
+	FOR_EACH_VEC_ELT (m_constraints, i, c)
+	  {
+	    /* Update references to the rhs_ec so that
+	       they refer to the lhs_ec.  */
+	    if (c->m_lhs == rhs_ec_id)
+	      c->m_lhs = lhs_ec_id;
+	    if (c->m_rhs == rhs_ec_id)
+	      c->m_rhs = lhs_ec_id;
+
+	    /* Renumber all constraints that refer to the final rhs_ec
+	       to the old rhs_ec, where the old final_ec now lives.  */
+	    if (c->m_lhs == final_ec_id)
+	      c->m_lhs = rhs_ec_id;
+	    if (c->m_rhs == final_ec_id)
+	      c->m_rhs = rhs_ec_id;
+	  }
+      }
+      break;
+    case GE_EXPR:
+      m_constraints.safe_push (constraint (rhs_ec_id, CONSTRAINT_LE, lhs_ec_id));
+      break;
+    case LE_EXPR:
+      m_constraints.safe_push (constraint (lhs_ec_id, CONSTRAINT_LE, rhs_ec_id));
+      break;
+    case NE_EXPR:
+      m_constraints.safe_push (constraint (lhs_ec_id, CONSTRAINT_NE, rhs_ec_id));
+      break;
+    case GT_EXPR:
+      m_constraints.safe_push (constraint (rhs_ec_id, CONSTRAINT_LT, lhs_ec_id));
+      break;
+    case LT_EXPR:
+      m_constraints.safe_push (constraint (lhs_ec_id, CONSTRAINT_LT, rhs_ec_id));
+      break;
+    default:
+      /* do nothing.  */
+      break;
+    }
+  return true;
+}
+
+/* FIXME.  */
+
+bool
+path_state::get_equiv_class (path_var pv, equiv_class_id *out) const
+{
+  gcc_assert (pv.m_tree);
+
+  /* FIXME: probably should be a map, rather than these lookups.  */
+  if (CONSTANT_CLASS_P (pv.m_tree))
+    {
+      // FIXME: we want to ensure that constants that are equal are treated as equal here
+      int i;
+      equiv_class *ec;
+      FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+	if (pv.m_tree == ec->m_constant)
+	  {
+	    *out = equiv_class_id (i);
+	    return true;
+	  }
+    }
+  else
+    {
+      int i;
+      equiv_class *ec;
+      FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+	{
+	  int j;
+	  path_var *v;
+	  FOR_EACH_VEC_ELT (ec->m_vars, j, v)
+	    if (*v == pv)
+	      {
+		*out = equiv_class_id (i);
+		return true;
+	      }
+	}
+    }
+  return false;
+}
+
+/* FIXME.  */
+
+equiv_class_id
+path_state::get_or_add_equiv_class (path_var pv)
+{
+  gcc_assert (pv.m_tree);
+
+  equiv_class_id result (-1);
+  if (get_equiv_class (pv, &result))
+    return result;
+
+  /* Not found.  */
+  equiv_class *new_ec = new equiv_class ();
+  new_ec->add (pv);
+  m_equiv_classes.safe_push (new_ec);
+  return equiv_class_id (m_equiv_classes.length () - 1);
+}
+
+/* FIXME.  */
+/* For implementing "__analyzer_eval".  */
+
+tristate
+path_state::dump_condition (tree lhs, enum tree_code op, tree rhs)
+{
+  // FIXME: stack depths?
+  equiv_class_id lhs_ec_id = get_or_add_equiv_class (path_var (lhs, 0));
+  equiv_class_id rhs_ec_id = get_or_add_equiv_class (path_var (rhs, 0));
+
+  return eval_condition (lhs_ec_id, op, rhs_ec_id);
+}
+
+/* FIXME.  */
+
+tristate
+path_state::eval_condition (equiv_class_id lhs_ec,
+			    enum tree_code op,
+			    equiv_class_id rhs_ec)
+{
+  if (lhs_ec == rhs_ec)
+    {
+      switch (op)
+	{
+	case EQ_EXPR:
+	case GE_EXPR:
+	case LE_EXPR:
+	  return tristate (tristate::TS_TRUE);
+
+	case NE_EXPR:
+	case GT_EXPR:
+	case LT_EXPR:
+	  return tristate (tristate::TS_FALSE);
+	default:
+	  break;
+	}
+    }
+
+  tree lhs_const = lhs_ec.get_obj (*this).get_any_constant ();
+  tree rhs_const = rhs_ec.get_obj (*this).get_any_constant ();
+  if (lhs_const && rhs_const)
+    {
+      tree comparison
+	= fold_build2 (op, boolean_type_node, lhs_const, rhs_const);
+      if (comparison == boolean_true_node)
+	return tristate (tristate::TS_TRUE);
+      if (comparison == boolean_false_node)
+	return tristate (tristate::TS_FALSE);
+    }
+
+  enum tree_code swapped_op = swap_tree_comparison (op);
+
+  int i;
+  constraint *c;
+  FOR_EACH_VEC_ELT (m_constraints, i, c)
+    {
+      if (c->m_lhs == lhs_ec
+	  && c->m_rhs == rhs_ec)
+	{
+	  tristate result_for_constraint
+	    = eval_constraint_op_for_op (c->m_op, op);
+	  if (result_for_constraint.is_known ())
+	    return result_for_constraint;
+	}
+      /* Swapped operands.  */
+      if (c->m_lhs == rhs_ec
+	  && c->m_rhs == lhs_ec)
+	{
+	  tristate result_for_constraint
+	    = eval_constraint_op_for_op (c->m_op, swapped_op);
+	  if (result_for_constraint.is_known ())
+	    return result_for_constraint;
+	}
+    }
+
+  // TODO: what about implied orderings?
+
+  return tristate (tristate::TS_UNKNOWN);
+}
+
+// FIXME:
+void
+debug (const path_state &ps)
+{
+  fprintf (stderr, "%s\n", ps.to_str ().c_str ());
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+static void
+assert_condition (const location &loc,
+		  path_state &ps,
+		  path_var lhs, tree_code op, path_var rhs,
+		  tristate expected)
+{
+  equiv_class_id lhs_ec = ps.get_or_add_equiv_class (lhs);
+  equiv_class_id rhs_ec = ps.get_or_add_equiv_class (rhs);
+  tristate actual = ps.eval_condition (lhs_ec, op, rhs_ec);
+  ASSERT_EQ_AT (loc, actual, expected);
+}
+
+#define ASSERT_CONDITION_TRUE(PATH_STATE, LHS, OP, RHS) \
+  SELFTEST_BEGIN_STMT							\
+  assert_condition (SELFTEST_LOCATION, PATH_STATE, LHS, OP, RHS,	\
+		    tristate (tristate::TS_TRUE));		\
+  SELFTEST_END_STMT
+
+#define ASSERT_CONDITION_FALSE(PATH_STATE, LHS, OP, RHS) \
+  SELFTEST_BEGIN_STMT							\
+  assert_condition (SELFTEST_LOCATION, PATH_STATE, LHS, OP, RHS,	\
+		    tristate (tristate::TS_FALSE));		\
+  SELFTEST_END_STMT
+
+#define ASSERT_CONDITION_UNKNOWN(PATH_STATE, LHS, OP, RHS) \
+  SELFTEST_BEGIN_STMT							\
+  assert_condition (SELFTEST_LOCATION, PATH_STATE, LHS, OP, RHS,	\
+		    tristate (tristate::TS_UNKNOWN));		\
+  SELFTEST_END_STMT
+
+/* FIXME.  */
+
+static void
+test_constraint_conditions ()
+{
+  path_var int_42 (build_int_cst (integer_type_node, 42), 0);
+  path_var int_0 (build_int_cst (integer_type_node, 0), 0);
+
+  path_var x (build_decl (UNKNOWN_LOCATION, PARM_DECL,
+			  get_identifier ("x"),
+			  integer_type_node), 0);
+  path_var y (build_decl (UNKNOWN_LOCATION, PARM_DECL,
+			  get_identifier ("y"),
+			  integer_type_node), 0);
+  path_var z (build_decl (UNKNOWN_LOCATION, PARM_DECL,
+			  get_identifier ("z"),
+			  integer_type_node), 0);
+
+  /* Self-comparisons.  */
+  {
+    path_state ps;
+    ASSERT_CONDITION_TRUE (ps, x, EQ_EXPR, x);
+    ASSERT_CONDITION_TRUE (ps, x, LE_EXPR, x);
+    ASSERT_CONDITION_TRUE (ps, x, GE_EXPR, x);
+    ASSERT_CONDITION_FALSE (ps, x, NE_EXPR, x);
+    ASSERT_CONDITION_FALSE (ps, x, LT_EXPR, x);
+    ASSERT_CONDITION_FALSE (ps, x, GT_EXPR, x);
+  }
+
+  /* x == y.  */
+  {
+    path_state ps;
+    ASSERT_CONDITION_UNKNOWN (ps, x, EQ_EXPR, y);
+
+    bool sat = ps.add_constraint (x, EQ_EXPR, y);
+    ASSERT_TRUE (sat);
+
+    ASSERT_CONDITION_TRUE (ps, x, EQ_EXPR, y);
+    ASSERT_CONDITION_TRUE (ps, x, LE_EXPR, y);
+    ASSERT_CONDITION_TRUE (ps, x, GE_EXPR, y);
+    ASSERT_CONDITION_FALSE (ps, x, NE_EXPR, y);
+    ASSERT_CONDITION_FALSE (ps, x, LT_EXPR, y);
+    ASSERT_CONDITION_FALSE (ps, x, GT_EXPR, y);
+
+    /* Swapped operands.  */
+    ASSERT_CONDITION_TRUE (ps, y, EQ_EXPR, x);
+    ASSERT_CONDITION_TRUE (ps, y, LE_EXPR, x);
+    ASSERT_CONDITION_TRUE (ps, y, GE_EXPR, x);
+    ASSERT_CONDITION_FALSE (ps, y, NE_EXPR, x);
+    ASSERT_CONDITION_FALSE (ps, y, LT_EXPR, x);
+    ASSERT_CONDITION_FALSE (ps, y, GT_EXPR, x);
+
+    /* Comparison with other var.  */
+    ASSERT_CONDITION_UNKNOWN (ps, x, EQ_EXPR, z);
+    ASSERT_CONDITION_UNKNOWN (ps, x, LE_EXPR, z);
+    ASSERT_CONDITION_UNKNOWN (ps, x, GE_EXPR, z);
+    ASSERT_CONDITION_UNKNOWN (ps, x, NE_EXPR, z);
+    ASSERT_CONDITION_UNKNOWN (ps, x, LT_EXPR, z);
+    ASSERT_CONDITION_UNKNOWN (ps, x, GT_EXPR, z);
+  }
+
+  /* x != y.  */
+  {
+    path_state ps;
+
+    bool sat = ps.add_constraint (x, NE_EXPR, y);
+    ASSERT_TRUE (sat);
+
+    ASSERT_CONDITION_TRUE (ps, x, NE_EXPR, y);
+    ASSERT_CONDITION_FALSE (ps, x, EQ_EXPR, y);
+    ASSERT_CONDITION_UNKNOWN (ps, x, LE_EXPR, y);
+    ASSERT_CONDITION_UNKNOWN (ps, x, GE_EXPR, y);
+    ASSERT_CONDITION_UNKNOWN (ps, x, LT_EXPR, y);
+    ASSERT_CONDITION_UNKNOWN (ps, x, GT_EXPR, y);
+
+    /* Swapped operands.  */
+    ASSERT_CONDITION_TRUE (ps, y, NE_EXPR, x);
+    ASSERT_CONDITION_FALSE (ps, y, EQ_EXPR, x);
+    ASSERT_CONDITION_UNKNOWN (ps, y, LE_EXPR, x);
+    ASSERT_CONDITION_UNKNOWN (ps, y, GE_EXPR, x);
+    ASSERT_CONDITION_UNKNOWN (ps, y, LT_EXPR, x);
+    ASSERT_CONDITION_UNKNOWN (ps, y, GT_EXPR, x);
+
+    /* Comparison with other var.  */
+    ASSERT_CONDITION_UNKNOWN (ps, x, EQ_EXPR, z);
+    ASSERT_CONDITION_UNKNOWN (ps, x, LE_EXPR, z);
+    ASSERT_CONDITION_UNKNOWN (ps, x, GE_EXPR, z);
+    ASSERT_CONDITION_UNKNOWN (ps, x, NE_EXPR, z);
+    ASSERT_CONDITION_UNKNOWN (ps, x, LT_EXPR, z);
+    ASSERT_CONDITION_UNKNOWN (ps, x, GT_EXPR, z);
+  }
+
+  /* x < y.  */
+  {
+    path_state ps;
+
+    bool sat = ps.add_constraint (x, LT_EXPR, y);
+    ASSERT_TRUE (sat);
+
+    ASSERT_CONDITION_TRUE (ps, x, LT_EXPR, y);
+    ASSERT_CONDITION_TRUE (ps, x, LE_EXPR, y);
+    ASSERT_CONDITION_TRUE (ps, x, NE_EXPR, y);
+    ASSERT_CONDITION_FALSE (ps, x, EQ_EXPR, y);
+    ASSERT_CONDITION_FALSE (ps, x, GT_EXPR, y);
+    ASSERT_CONDITION_FALSE (ps, x, GE_EXPR, y);
+
+    /* Swapped operands.  */
+    ASSERT_CONDITION_FALSE (ps, y, LT_EXPR, x);
+    ASSERT_CONDITION_FALSE (ps, y, LE_EXPR, x);
+    ASSERT_CONDITION_TRUE (ps, y, NE_EXPR, x);
+    ASSERT_CONDITION_FALSE (ps, y, EQ_EXPR, x);
+    ASSERT_CONDITION_TRUE (ps, y, GT_EXPR, x);
+    ASSERT_CONDITION_TRUE (ps, y, GE_EXPR, x);
+  }
+
+  /* x <= y.  */
+  {
+    path_state ps;
+
+    bool sat = ps.add_constraint (x, LE_EXPR, y);
+    ASSERT_TRUE (sat);
+
+    ASSERT_CONDITION_UNKNOWN (ps, x, LT_EXPR, y);
+    ASSERT_CONDITION_TRUE (ps, x, LE_EXPR, y);
+    ASSERT_CONDITION_UNKNOWN (ps, x, NE_EXPR, y);
+    ASSERT_CONDITION_UNKNOWN (ps, x, EQ_EXPR, y);
+    ASSERT_CONDITION_FALSE (ps, x, GT_EXPR, y);
+    ASSERT_CONDITION_UNKNOWN (ps, x, GE_EXPR, y);
+
+    /* Swapped operands.  */
+    ASSERT_CONDITION_FALSE (ps, y, LT_EXPR, x);
+    ASSERT_CONDITION_UNKNOWN (ps, y, LE_EXPR, x);
+    ASSERT_CONDITION_UNKNOWN (ps, y, NE_EXPR, x);
+    ASSERT_CONDITION_UNKNOWN (ps, y, EQ_EXPR, x);
+    ASSERT_CONDITION_UNKNOWN (ps, y, GT_EXPR, x);
+    ASSERT_CONDITION_TRUE (ps, y, GE_EXPR, x);
+  }
+
+  /* x > y.  */
+  {
+    path_state ps;
+
+    bool sat = ps.add_constraint (x, GT_EXPR, y);
+    ASSERT_TRUE (sat);
+
+    ASSERT_CONDITION_TRUE (ps, x, GT_EXPR, y);
+    ASSERT_CONDITION_TRUE (ps, x, GE_EXPR, y);
+    ASSERT_CONDITION_TRUE (ps, x, NE_EXPR, y);
+    ASSERT_CONDITION_FALSE (ps, x, EQ_EXPR, y);
+    ASSERT_CONDITION_FALSE (ps, x, LT_EXPR, y);
+    ASSERT_CONDITION_FALSE (ps, x, LE_EXPR, y);
+
+    /* Swapped operands.  */
+    ASSERT_CONDITION_FALSE (ps, y, GT_EXPR, x);
+    ASSERT_CONDITION_FALSE (ps, y, GE_EXPR, x);
+    ASSERT_CONDITION_TRUE (ps, y, NE_EXPR, x);
+    ASSERT_CONDITION_FALSE (ps, y, EQ_EXPR, x);
+    ASSERT_CONDITION_TRUE (ps, y, LT_EXPR, x);
+    ASSERT_CONDITION_TRUE (ps, y, LE_EXPR, x);
+  }
+
+  /* x >= y.  */
+  {
+    path_state ps;
+
+    bool sat = ps.add_constraint (x, GE_EXPR, y);
+    ASSERT_TRUE (sat);
+
+    ASSERT_CONDITION_UNKNOWN (ps, x, GT_EXPR, y);
+    ASSERT_CONDITION_TRUE (ps, x, GE_EXPR, y);
+    ASSERT_CONDITION_UNKNOWN (ps, x, NE_EXPR, y);
+    ASSERT_CONDITION_UNKNOWN (ps, x, EQ_EXPR, y);
+    ASSERT_CONDITION_FALSE (ps, x, LT_EXPR, y);
+    ASSERT_CONDITION_UNKNOWN (ps, x, LE_EXPR, y);
+
+    /* Swapped operands.  */
+    ASSERT_CONDITION_FALSE (ps, y, GT_EXPR, x);
+    ASSERT_CONDITION_UNKNOWN (ps, y, GE_EXPR, x);
+    ASSERT_CONDITION_UNKNOWN (ps, y, NE_EXPR, x);
+    ASSERT_CONDITION_UNKNOWN (ps, y, EQ_EXPR, x);
+    ASSERT_CONDITION_UNKNOWN (ps, y, LT_EXPR, x);
+    ASSERT_CONDITION_TRUE (ps, y, LE_EXPR, x);
+  }
+
+  // TODO: implied orderings
+
+  /* Constants.  */
+  {
+    path_state ps;
+    ASSERT_CONDITION_FALSE (ps, int_0, EQ_EXPR, int_42);
+    ASSERT_CONDITION_TRUE (ps, int_0, NE_EXPR, int_42);
+    ASSERT_CONDITION_TRUE (ps, int_0, LT_EXPR, int_42);
+    ASSERT_CONDITION_TRUE (ps, int_0, LE_EXPR, int_42);
+    ASSERT_CONDITION_FALSE (ps, int_0, GT_EXPR, int_42);
+    ASSERT_CONDITION_FALSE (ps, int_0, GE_EXPR, int_42);
+  }
+
+  /* x == 0, y == 42.  */
+  {
+    path_state ps;
+    bool sat_a = ps.add_constraint (x, EQ_EXPR, int_0);
+    ASSERT_TRUE (sat_a);
+    bool sat_b = ps.add_constraint (y, EQ_EXPR, int_42);
+    ASSERT_TRUE (sat_b);
+
+    ASSERT_CONDITION_TRUE (ps, x, NE_EXPR, y);
+    ASSERT_CONDITION_FALSE (ps, x, EQ_EXPR, y);
+    ASSERT_CONDITION_TRUE (ps, x, LE_EXPR, y);
+    ASSERT_CONDITION_FALSE (ps, x, GE_EXPR, y);
+    ASSERT_CONDITION_TRUE (ps, x, LT_EXPR, y);
+    ASSERT_CONDITION_FALSE (ps, x, GT_EXPR, y);
+  }
+
+  /* Unsatisfiable combinations.  */
+
+  /* x == y && x != y.  */
+  {
+    path_state ps;
+    bool sat_a = ps.add_constraint (x, EQ_EXPR, y);
+    ASSERT_TRUE (sat_a);
+    bool sat_b = ps.add_constraint (x, NE_EXPR, y);
+    ASSERT_FALSE (sat_b);
+  }
+
+  /* x != y && x == y.  */
+  {
+    path_state ps;
+    bool sat_a = ps.add_constraint (x, NE_EXPR, y);
+    ASSERT_TRUE (sat_a);
+    bool sat_b = ps.add_constraint (x, EQ_EXPR, y);
+    ASSERT_FALSE (sat_b);
+  }
+
+  /* x <= y && x > y.  */
+  {
+    path_state ps;
+    bool sat_a = ps.add_constraint (x, LE_EXPR, y);
+    ASSERT_TRUE (sat_a);
+    bool sat_b = ps.add_constraint (x, GT_EXPR, y);
+    ASSERT_FALSE (sat_b);
+  }
+
+  // etc
+}
+
+/* FIXME.  */
+
+static void
+test_constraint_impl ()
+{
+  path_var int_42 (build_int_cst (integer_type_node, 42), 0);
+  path_var int_0 (build_int_cst (integer_type_node, 0), 0);
+
+  path_var x (build_decl (UNKNOWN_LOCATION, PARM_DECL,
+			  get_identifier ("x"),
+			  integer_type_node), 0);
+  path_var y (build_decl (UNKNOWN_LOCATION, PARM_DECL,
+			  get_identifier ("y"),
+			  integer_type_node), 0);
+  path_var z (build_decl (UNKNOWN_LOCATION, PARM_DECL,
+			  get_identifier ("z"),
+			  integer_type_node), 0);
+
+  /* x == y.  */
+  {
+    path_state ps;
+
+    bool sat = ps.add_constraint (x, EQ_EXPR, y);
+    ASSERT_TRUE (sat);
+
+    /* Assert various things about the insides of ps.  */
+    ASSERT_EQ (ps.m_constraints.length (), 0);
+    ASSERT_EQ (ps.m_equiv_classes.length (), 1);
+  }
+
+  /* y <= z; x == y.  */
+  {
+    path_state ps;
+    ASSERT_CONDITION_UNKNOWN (ps, x, EQ_EXPR, y);
+    ASSERT_CONDITION_UNKNOWN (ps, x, GE_EXPR, z);
+
+    bool sat_a = ps.add_constraint (y, GE_EXPR, z);
+    ASSERT_TRUE (sat_a);
+    ASSERT_CONDITION_TRUE (ps, y, GE_EXPR, z);
+    ASSERT_CONDITION_UNKNOWN (ps, x, GE_EXPR, z);
+
+    bool sat_b = ps.add_constraint (x, EQ_EXPR, y);
+    ASSERT_TRUE (sat_b);
+
+    /* Assert various things about the insides of ps.  */
+    ASSERT_EQ (ps.m_constraints.length (), 1);
+    ASSERT_EQ (ps.m_equiv_classes.length (), 2);
+
+    /* Ensure that we merged the constraints.  */
+    ASSERT_CONDITION_TRUE (ps, x, GE_EXPR, z);
+  }
+
+  /* y <= z; y == x.  */
+  {
+    path_state ps;
+    ASSERT_CONDITION_UNKNOWN (ps, x, EQ_EXPR, y);
+    ASSERT_CONDITION_UNKNOWN (ps, x, GE_EXPR, z);
+
+    bool sat_a = ps.add_constraint (y, GE_EXPR, z);
+    ASSERT_TRUE (sat_a);
+    ASSERT_CONDITION_TRUE (ps, y, GE_EXPR, z);
+    ASSERT_CONDITION_UNKNOWN (ps, x, GE_EXPR, z);
+
+    bool sat_b = ps.add_constraint (y, EQ_EXPR, x);
+    ASSERT_TRUE (sat_b);
+
+    /* Assert various things about the insides of ps.  */
+    ASSERT_EQ (ps.m_constraints.length (), 1);
+    ASSERT_EQ (ps.m_equiv_classes.length (), 2);
+
+    /* Ensure that we merged the constraints.  */
+    ASSERT_CONDITION_TRUE (ps, x, GE_EXPR, z);
+  }
+
+  // TODO: selftest for merging ecs "in the middle"
+  // where a non-final one gets overwritten
+
+  // TODO: selftest where there are pre-existing constraints
+}
+
+static void
+test_assignment ()
+{
+  // TODO
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+analyzer_constraints_cc_tests ()
+{
+  test_constraint_conditions ();
+  test_constraint_impl ();
+  test_assignment ();
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
diff --git a/gcc/analyzer/constraints.h b/gcc/analyzer/constraints.h
new file mode 100644
index 0000000..b82ea88
--- /dev/null
+++ b/gcc/analyzer/constraints.h
@@ -0,0 +1,167 @@
+/* Tracking equivalence classes and constraints at a point on an execution path.
+   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_CONSTRAINTS_H
+#define GCC_ANALYZER_CONSTRAINTS_H
+
+class superedge;
+
+/* A tree, extended with stack frame information for locals, so that
+   we can distinguish between different values of locals within a potentially
+   recursive callstack.  */
+
+class path_var
+{
+public:
+  path_var (tree t, int stack_depth)
+  : m_tree (t), m_stack_depth (stack_depth)
+  {
+    // TODO: ignore stack depth for globals and constants
+  }
+
+  bool operator== (const path_var &other) const
+  {
+    return (m_tree == other.m_tree
+	    && m_stack_depth == other.m_stack_depth);
+  }
+
+  std::string to_str () const;
+
+  tree m_tree;
+  int m_stack_depth; // or -1 for globals?
+};
+
+/* FIXME.  */
+
+class equiv_class
+{
+public:
+  equiv_class ();
+  equiv_class (const equiv_class &other);
+
+  std::string to_str () const;
+
+  void add (path_var v);
+  bool del (path_var v);
+
+  tree get_any_constant () const { return m_constant; }
+
+  /* An equivalence class can contain at most one constant.  */
+  tree m_constant;
+
+  // FIXME: this is a bad representation, probably should be a set:
+  auto_vec<path_var> m_vars;
+};
+
+enum constraint_op
+{
+  CONSTRAINT_NE,
+  CONSTRAINT_LT,
+  CONSTRAINT_LE
+};
+
+const char *constraint_op_code (enum constraint_op c_op);
+
+class path_state;
+
+class equiv_class_id
+{
+public:
+  equiv_class_id (unsigned idx) : m_idx (idx) {}
+  const equiv_class &get_obj (const path_state &ps) const;
+  equiv_class &get_obj (path_state &ps) const;
+
+  bool operator== (const equiv_class_id &other) const
+  {
+    return m_idx == other.m_idx;
+  }
+  bool operator!= (const equiv_class_id &other) const
+  {
+    return m_idx != other.m_idx;
+  }
+
+  std::string to_str () const
+  {
+    return std::string ("ec") + std::to_string (m_idx);
+  }
+
+  unsigned m_idx;
+};
+
+class constraint {
+ public:
+  constraint (equiv_class_id lhs, enum constraint_op c_op, equiv_class_id rhs)
+    : m_lhs (lhs), m_op (c_op), m_rhs (rhs) {}
+
+  std::string to_str (const path_state &ps) const;
+
+  equiv_class_id m_lhs;
+  enum constraint_op m_op;
+  equiv_class_id m_rhs;
+};
+
+/* The state of a particular point at one point in the traversal (not including
+   state-machine information).  */
+
+class path_state
+{
+public:
+  path_state () {}
+  path_state (const path_state &other);
+  ~path_state ();
+
+  std::string to_str () const;
+
+  const equiv_class &get_equiv_class_by_index (unsigned idx) const
+  {
+    return *m_equiv_classes[idx];
+  }
+  equiv_class &get_equiv_class_by_index (unsigned idx)
+  {
+    return *m_equiv_classes[idx];
+  }
+
+  void on_assignment (path_var lhs, path_var rhs);
+
+  bool apply_constraints_for_edge (const superedge &edge,
+				   const gimple *last_stmt);
+
+  bool add_constraint (path_var lhs, enum tree_code op, path_var rhs);
+
+  //private:
+  bool apply_constraints_for_gcond (const superedge &edge,
+				    const gcond *cond_stmt);
+  bool apply_constraints_for_gswitch (const superedge &edge,
+				      const gswitch *switch_stmt);
+  bool get_equiv_class (path_var pv, equiv_class_id *out) const;
+  equiv_class_id get_or_add_equiv_class (path_var vr);
+  tristate eval_condition (equiv_class_id lhs,
+			   enum tree_code op,
+			   equiv_class_id rhs);
+
+  /* For implementing "__analyzer_eval".  */
+  tristate dump_condition (tree lhs, enum tree_code op, tree rhs);
+
+  auto_vec<equiv_class *> m_equiv_classes;
+  auto_vec<constraint> m_constraints;
+};
+
+
+#endif /* GCC_ANALYZER_CONSTRAINTS_H */
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
new file mode 100644
index 0000000..09bc1a9
--- /dev/null
+++ b/gcc/analyzer/engine.cc
@@ -0,0 +1,1315 @@
+/* FIXME.  */
+#include "config.h"
+#include "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "tm.h"
+#include "toplev.h"
+#include "hash-table.h"
+#include "vec.h"
+#include "ggc.h"
+#include "basic-block.h"
+#include "tree-ssa-alias.h"
+#include "internal-fn.h"
+#include "gimple-fold.h"
+#include "tree-eh.h"
+#include "gimple-expr.h"
+#include "is-a.h"
+#include "gimple.h"
+#include "tree-pass.h"
+#include "intl.h"
+#include "context.h"
+#include "diagnostic.h"
+#include "bitmap.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "gimple-pretty-print.h"
+#include "tree-pretty-print.h"
+#include "analyzer.h"
+#include "graphviz.h"
+#include "cgraph.h"
+#include "diagnostic-path.h"
+#include "gcc-rich-location.h"
+#include "tree-dfa.h"
+#include <string>
+#include "supergraph.h"
+#include "hardcoded-state-machine.h"
+#include "analyzer-logging.h"
+#include "stringpool.h"
+#include "selftest.h"
+#include "fold-const.h"
+#include "tristate.h"
+
+static const bool DEBUG = false;
+//static const bool DEBUG = true;
+
+static int verbosity = 2;
+
+////////////////////////////////////////////////////////////////////////////
+
+class sm_instance;
+
+////////////////////////////////////////////////////////////////////////////
+
+struct var_state_t
+{
+  //typedef var_state_t value_type;
+  static tree get_placeholder () { return error_mark_node; }
+
+  var_state_t (tree var, state_machine::state_t state)
+  : m_var (var), m_state (state)
+  {}
+
+  std::string to_str (const state_machine &sm) const
+  {
+    if (m_var == get_placeholder ())
+      return "<>";
+    std::string result = expr_to_string (m_var);
+    result += std::string (" ") + sm.get_state_name (m_state);
+    return result;
+  }
+
+  hashval_t hash () const
+  {
+    // FIXME:
+    return /*reinterpret_cast <hashval_t> (m_var) ^ */((hashval_t)m_state);
+  }
+
+  bool operator== (const var_state_t &other) const
+  {
+    return m_var == other.m_var && m_state == other.m_state;
+  }
+
+  tree m_var;
+  state_machine::state_t m_state;
+};
+struct summary_point
+{
+  summary_point (state_machine::state_t gstate, var_state_t varstate)
+  : m_gstate (gstate), m_varstate (varstate)
+  {}
+
+  std::string to_str (const state_machine &sm) const
+  {
+    return std::string ("(") + sm.get_state_name (m_gstate) + ", " + m_varstate.to_str (sm) + ")";
+  }
+
+  hashval_t hash () const
+  {
+    return ((hashval_t)m_gstate) ^ m_varstate.hash ();
+  }
+
+  bool operator== (const summary_point &other) const
+  {
+    return m_gstate == other.m_gstate && m_varstate == other.m_varstate;
+  }
+
+  state_machine::state_t m_gstate;
+  var_state_t m_varstate;
+};
+struct summary_edge
+{
+  summary_edge (const summary_point &start,
+		const summary_point &end)
+  : m_start (start), m_end (end) {}
+
+  std::string to_str (const state_machine &sm) const
+  {
+    return m_start.to_str (sm) + "->" + m_end.to_str (sm);
+  }
+
+  hashval_t hash () const
+  {
+    return m_start.hash () ^ m_end.hash ();
+  }
+
+  summary_point m_start;
+  summary_point m_end;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+template <typename Type>
+struct pod_hash_traits : typed_noop_remove<Type>
+{
+  typedef Type value_type;
+  typedef Type compare_type;
+  static inline hashval_t hash (value_type);
+  static inline bool equal (const value_type &existing,
+			    const value_type &candidate);
+  static inline void mark_deleted (Type &);
+  static inline void mark_empty (Type &);
+  static inline bool is_deleted (Type);
+  static inline bool is_empty (Type);
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+template <> struct default_hash_traits<var_state_t>
+: public pod_hash_traits<var_state_t>
+{
+};
+
+template <>
+inline hashval_t
+pod_hash_traits<var_state_t>::hash (value_type v)
+{
+  // FIXME:
+  return ((hashval_t)v.m_state);
+}
+
+template <>
+inline bool
+pod_hash_traits<var_state_t>::equal (const value_type &existing,
+				     const value_type &candidate)
+{
+  return (existing.m_var == candidate.m_var
+	  && existing.m_state == candidate.m_state);
+}
+template <>
+inline void
+pod_hash_traits<var_state_t>::mark_deleted (value_type &v)
+{
+  v = var_state_t (NULL_TREE, 0);
+}
+template <>
+inline void
+pod_hash_traits<var_state_t>::mark_empty (value_type &v)
+{
+  v = var_state_t (NULL_TREE, 0);
+}
+template <>
+inline bool
+pod_hash_traits<var_state_t>::is_deleted (value_type v)
+{
+  return (v.m_var == NULL_TREE && v.m_state == 0);
+}
+template <>
+inline bool
+pod_hash_traits<var_state_t>::is_empty (value_type v)
+{
+  return (v.m_var == NULL_TREE && v.m_state == 0);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+template <> struct default_hash_traits<summary_edge>
+: public pod_hash_traits<summary_edge>
+{
+};
+
+template <>
+inline hashval_t
+pod_hash_traits<summary_edge>::hash (value_type v)
+{
+  return v.hash ();
+}
+
+template <>
+inline bool
+pod_hash_traits<summary_edge>::equal (const value_type &existing,
+				     const value_type &candidate)
+{
+  return (existing.m_start == candidate.m_start
+	  && existing.m_end == candidate.m_end);
+}
+template <>
+inline void
+pod_hash_traits<summary_edge>::mark_deleted (value_type &v)
+{
+  v = summary_edge (summary_point (0, var_state_t (NULL_TREE, 0)),
+		    summary_point (0, var_state_t (NULL_TREE, 0)));
+}
+template <>
+inline void
+pod_hash_traits<summary_edge>::mark_empty (value_type &v)
+{
+  v = summary_edge (summary_point (0, var_state_t (NULL_TREE, 0)),
+		    summary_point (0, var_state_t (NULL_TREE, 0)));
+}
+template <>
+inline bool
+pod_hash_traits<summary_edge>::is_deleted (value_type v)
+{
+  return v.m_start.m_varstate.m_var == NULL_TREE;
+}
+template <>
+inline bool
+pod_hash_traits<summary_edge>::is_empty (value_type v)
+{
+  return v.m_start.m_varstate.m_var == NULL_TREE;
+}
+
+
+std::string
+to_str (const var_state_t &vs, const state_machine &sm)
+{
+  return vs.to_str (sm);
+}
+
+std::string
+to_str (const summary_edge &e, const state_machine &sm)
+{
+  return e.to_str (sm);
+}
+
+template <typename T>
+std::string
+to_str (hash_set<T> set, const state_machine &sm)
+{
+  std::string result ("{");
+  // FIXME: this is probably not optimal
+
+  for (typename hash_set<T>::iterator i = set.begin ();
+       i != set.end (); ++i)
+    {
+      if (i != set.begin ())
+	result += ", ";
+      result += to_str (*i, sm);
+    }
+
+  result += "}";
+
+  return result;
+}
+
+/* FIXME: does hash_set's copy-ctor work?
+   In case it doesn't, this class does.  */
+
+class set_of_summary_edges
+{
+public:
+  typedef hash_set<summary_edge>::iterator iterator;
+
+  set_of_summary_edges () {}
+
+  set_of_summary_edges (const set_of_summary_edges &other)
+  {
+    /* Deep copy, by union_with into empty set.  */
+    union_with (other);
+  }
+
+  void union_with (const set_of_summary_edges &other)
+  {
+    for (iterator i = other.begin (); i != other.end (); ++i)
+      {
+	m_hashset.add (*i);
+      }
+  }
+
+  std::string to_str (const state_machine &sm) const
+  {
+    return ::to_str (m_hashset, sm);
+  }
+
+  void add (const summary_edge &e)
+  {
+    m_hashset.add (e);
+  }
+
+  iterator begin () const { return m_hashset.begin (); }
+  iterator end () const { return m_hashset.end (); }
+
+ hash_set<summary_edge> m_hashset;
+};
+
+struct per_node_t
+{
+  set_of_summary_edges m_blk_add;
+  set_of_summary_edges m_blk_transition;
+  set_of_summary_edges m_sfx_add;
+  set_of_summary_edges m_sfx_transition;
+};
+
+class backtrace
+{
+public:
+  backtrace () {}
+
+  backtrace (const backtrace &other)
+  : m_path (other.m_path),
+    m_nodes ()
+  {
+    // FIXME: auto_vec's copy_ctor...
+    int i;
+    const supernode *node;
+    FOR_EACH_VEC_ELT (other.m_nodes, i, node)
+      m_nodes.safe_push (node);
+  }
+
+  std::string to_str () const
+  {
+    std::string result ("[");
+    // FIXME: this is probably not optimal
+
+    int i;
+    const supernode *node;
+    FOR_EACH_VEC_ELT (m_nodes, i, node)
+      {
+	if (i > 0)
+	  result += ", ";
+	result += std::to_string (node->m_index);
+      }
+    result += "]";
+    return result;
+  }
+
+  diagnostic_path m_path;
+  auto_vec <const supernode *> m_nodes; // FIXME: copy-ctor etc
+};
+
+#include "constraints.h" // FIXME
+
+class engine : public log_user
+{
+public:
+  engine (const supergraph &sg, const state_machine &sm,
+	  logger *logger)
+  : log_user (logger), m_sg (sg), m_sm (sm), m_per_node_data (),
+    m_num_traverse_calls (0),
+    m_num_cache_hits (0),
+    m_on_stmt_calls (0)
+  {
+  }
+
+  void prune_active_vars_to_cache_misses (sm_instance *sm_inst,
+					  const supernode *node);
+
+  void dfs_traverse (sm_instance sm_inst, backtrace b,
+		     path_state ps, const supernode *node);
+  void on_stmt (const supernode *node, const gimple *stmt,
+		sm_instance *sm_inst, const sm_instance *old_sm_inst,
+		const backtrace &b, diagnostic_path *path, path_state *ps);
+  void on_assignment (const supernode *node, const gimple *stmt,
+		      sm_instance *sm_inst, const sm_instance *old_sm_inst,
+		      tree lhs, tree rhs, path_state *ps);
+
+  void relax (backtrace b);
+
+  sm_instance refine_for_call (sm_instance sm_inst, cgraph_edge *edge,
+			       const supernode *caller_node);
+  sm_instance restore_after_call (sm_instance sm_inst, cgraph_edge *edge,
+				  const supernode *rest_of_caller_node);
+  void follow_call (sm_instance sm_inst, backtrace b, path_state ps,
+		    const supernode *caller_node, cgraph_edge *edge);
+
+  per_node_t &get_data_for_node (const supernode *node)
+  {
+    if (per_node_t **ptr_ptr = m_per_node_data.get (node))
+      return **ptr_ptr;
+    per_node_t *data = new per_node_t (); // FIXME: can we make it not be indirect?
+    m_per_node_data.put (node, data);
+    return *data;
+  }
+
+  void log_stats () const;
+
+  const supergraph &m_sg;
+
+  // FIXME: this implies one traversal per state machine; should we instead
+  // do multiple state machines in one traversal?
+  const state_machine &m_sm;
+
+  // FIXME: should this be just a vec, instead (using the node indices)?
+  hash_map <const supernode *, per_node_t *> m_per_node_data;
+
+  int m_num_traverse_calls;
+  int m_num_cache_hits;
+  int m_on_stmt_calls;
+};
+
+class set_of_vars
+{
+public:
+  typedef hash_set<var_state_t>::iterator iterator;
+
+  var_state_t *has_expr (tree expr) const
+  {
+    for (hash_set<var_state_t>::iterator iter = m_set.begin ();
+	 iter != m_set.end ();
+	 ++iter)
+      {
+	if ((*iter).m_var == expr)
+	  {
+	    // FIXME: get rid of this dynamic allocation!
+	    // FIXME: ideally we'd just return a pointer to the slot
+	    // but I'm not sure there's a way to do that.
+	    return new var_state_t (*iter);
+	    // FIXME: also: what if there's more than one?
+	  }
+      }
+    return NULL;
+  }
+
+  bool empty_p () const
+  {
+    return m_set.elements () == 0;
+  }
+
+  std::string to_str (const state_machine &sm) const
+  {
+    return ::to_str (m_set, sm);
+  }
+
+  iterator begin () const { return m_set.begin (); }
+  iterator end () const { return m_set.end (); }
+
+  hash_set<var_state_t> m_set;
+};
+
+/* FIXME: This changes as we do the traversal: */
+
+struct sm_instance
+{
+  sm_instance () : m_gstate (0), m_active_vars () {}
+
+  std::string to_str (const state_machine &sm) const
+  {
+    // FIXME: obstack?
+    std::string result = "<";
+    result += sm.get_state_name (m_gstate);
+    result += ", ";
+    result += m_active_vars.to_str (sm);
+    result += ">";
+    return result;
+  }
+
+  void add_placeholder ()
+  {
+    m_active_vars.m_set.add (var_state_t (var_state_t::get_placeholder (), 0));
+  }
+
+  state_machine::state_t m_gstate;
+  set_of_vars m_active_vars;
+};
+
+/* Remove any active vars that are on the "start" of a blk_transition
+   within NODE.  */
+
+void
+engine::prune_active_vars_to_cache_misses (sm_instance *sm_inst,
+					   const supernode *node)
+{
+  const set_of_summary_edges &edges = get_data_for_node (node).m_blk_transition;
+  for (set_of_summary_edges::iterator iter = edges.begin ();
+       iter != edges.end ();
+       ++iter)
+    {
+      // FIXME: I think we need a match on gstate, too
+      if (sm_inst->m_active_vars.m_set.contains ((*iter).m_start.m_varstate))
+	{
+	  if (get_logger ())
+	    log ("cache hit: %s",
+		 (*iter).m_start.m_varstate.to_str (m_sm).c_str ());
+	  sm_inst->m_active_vars.m_set.remove ((*iter).m_start.m_varstate);
+	  m_num_cache_hits++;
+	}
+    }
+}
+
+class engine_sm_context : public sm_context
+{
+public:
+  engine_sm_context (engine *e, sm_instance *sm_inst,
+		     const sm_instance *old_sm_inst, diagnostic_path *path)
+    : m_engine (e), m_sm_inst (sm_inst), m_old_sm_inst (old_sm_inst),
+      m_path (path) {}
+
+  void on_transition (const supernode *node, const gimple *stmt,
+		      tree var,
+		      state_machine::state_t from,
+		      state_machine::state_t to) FINAL OVERRIDE
+  {
+    JIT_LOG_FUNC_2 (m_engine->get_logger (),
+		    "from: %s to: %s",
+		    m_engine->m_sm.get_state_name (from),
+		    m_engine->m_sm.get_state_name (to));
+    if (m_engine->get_logger ())
+      m_engine->log ("active vars was: %s",
+		     m_old_sm_inst->m_active_vars.to_str (m_engine->m_sm).c_str ());
+
+    // TODO
+    var_state_t vs_from (var, from);
+    if (const_cast <sm_instance *> (m_old_sm_inst)->m_active_vars.m_set.contains (vs_from))
+      {
+        m_engine->log ("from state found");
+        m_sm_inst->m_active_vars.m_set.remove (vs_from);
+      }
+    else
+      {
+	m_engine->log ("from state %s not found",
+		       m_engine->m_sm.get_state_name (from));
+         /* If it's the start state and we're non-empty, then start,
+	    otherwise ignore.  */
+         if (from == 0)
+	   {
+             /* Start state.  */
+	     if (m_old_sm_inst->m_active_vars.has_expr (var))
+	       return;
+	   }
+	 else
+	   return;
+      }
+
+    m_sm_inst->m_active_vars.m_set.add (var_state_t (var, to));
+
+    if (m_path)
+      m_path->add_event (stmt->location,
+			 node->m_fun->decl, 0,/*state.get_stack_depth ()*/
+			 "state of %qE: %qs -> %qs", var,
+			 m_engine->m_sm.get_state_name (from),
+			 m_engine->m_sm.get_state_name (to));
+
+  if (m_engine->get_logger ())
+    m_engine->log ("active vars is now: %s",
+		   m_sm_inst->m_active_vars.to_str (m_engine->m_sm).c_str ());
+  }
+
+  void error (const supernode *node, const gimple *stmt,
+	      tree var, state_machine::state_t state,
+	      const char *fmt, ...) FINAL OVERRIDE
+  {
+    JIT_LOG_FUNC (m_engine->get_logger ());
+
+    var_state_t vs_from (var, state);
+    if (const_cast <sm_instance *> (m_old_sm_inst)->m_active_vars.m_set.contains (vs_from))
+      {
+	gcc_rich_location rich_loc (stmt->location);
+	diagnostic_path path (*m_path);
+	path.add_event (stmt->location,
+			node->m_fun->decl, 0,/*state.get_stack_depth ()*/
+			"here (%qE is in state %qs)", var,
+			m_engine->m_sm.get_state_name (state));
+	// FIXME: ideally would show the message here, too?
+
+	rich_loc.set_path (&path);
+	va_list ap;
+	va_start (ap, fmt);
+	emit_diagnostic_valist (DK_ERROR, &rich_loc, 0, fmt,
+				&ap);
+	va_end (ap);
+      }
+    else
+      if (m_engine->get_logger ())
+	m_engine->log ("not in state %s; no error",
+		       m_engine->m_sm.get_state_name (state));
+ }
+
+private:
+  engine *m_engine;
+  sm_instance *m_sm_inst;
+  const sm_instance *m_old_sm_inst;
+  diagnostic_path *m_path;
+};
+
+void
+engine::dfs_traverse (sm_instance sm_inst, backtrace b,
+		      path_state ps, const supernode *node)
+{
+  JIT_LOG_FUNC_1 (get_logger (), "SN: %i", node->m_index);
+  m_num_traverse_calls++;
+
+  b.m_nodes.safe_push (node);
+  if (get_logger ())
+    {
+      log ("sm_inst: %s", sm_inst.to_str (m_sm).c_str ());
+      log ("backtrace: %s", b.to_str ().c_str ());
+      log ("path_state: %s", ps.to_str ().c_str ());
+    }
+
+#if 0
+  path.add_event (node->get_start_location (),
+		  node->m_fun->decl, 0/*state.get_stack_depth ()*/
+		    "SN: %i", node->m_index);
+		    "SN"
+		    );
+#endif
+
+  /* Look up to see if we've visited this node in these states before.  */
+  prune_active_vars_to_cache_misses (&sm_inst, node);
+  if (get_logger ())
+    log ("after pruning to cache, sm_inst: %s",
+	 sm_inst.to_str (m_sm).c_str ());
+  if (sm_inst.m_active_vars.empty_p ())
+    {
+      log ("no active vars; pruning path");
+      relax (b);
+      return;
+    }
+
+  sm_instance per_block_old_sm_inst = sm_inst;
+  // FIXME: ^^^ does copy-ctor for hash_set do a deep copy?
+  // Looks like it might be a shallow copy
+
+  int i;
+  gimple *stmt;
+  FOR_EACH_VEC_ELT (node->m_stmts, i, stmt)
+    {
+      sm_instance per_stmt_old_sm_inst = sm_inst;
+      on_stmt (node, stmt, &sm_inst, &per_stmt_old_sm_inst,
+	       b, &b.m_path, &ps);
+    }
+
+  if (gcall *call = node->get_final_call ())
+    {
+      if (cgraph_edge *edge = supergraph_call_edge (node->m_fun, call))
+	{
+	  follow_call (sm_inst, b, ps, node, edge); // TODO
+	  return;
+	}
+    }
+
+  /* Compute add and transition edges.  */
+  {
+    log ("new block summary edges for SN %i", node->m_index);
+    for (hash_set<var_state_t>::iterator iter = sm_inst.m_active_vars.m_set.begin ();
+	 iter != sm_inst.m_active_vars.m_set.end ();
+	 ++iter)
+      {
+	summary_point e (sm_inst.m_gstate, *iter);
+	log ("summary_point: %s", e.to_str (m_sm).c_str ());
+	// TODO
+	/* Was the var_state *iter active at block entry?  */
+	if (var_state_t *old_vs
+	    = per_block_old_sm_inst.m_active_vars.has_expr ((*iter).m_var))
+	  {
+	    get_data_for_node (node).m_blk_transition.add
+	      (summary_edge (summary_point (per_block_old_sm_inst.m_gstate,
+					    *old_vs),
+			     e));
+	    delete old_vs; // FIXME
+	  }
+	else
+	  {
+	    var_state_t old_state ((*iter).m_var, 0 /* FIXME: "unknown" */);
+	    get_data_for_node (node).m_blk_add.add
+	      (summary_edge (summary_point (per_block_old_sm_inst.m_gstate,
+					    old_state),
+			     e));
+	  }
+      }
+  }
+
+  if (node->return_p ())
+    {
+      // TODO: leak detection
+      relax (b);
+    }
+  else
+    {
+      log ("considering successors of SN: %i", node->m_index);
+
+      /* Traverse into successors of NODE.  */
+       superedge *succ;
+       FOR_EACH_VEC_ELT (node->m_succs, i, succ)
+	 {
+	   log ("considering SN: %i -> SN: %i",
+		node->m_index, succ->m_dest->m_index);
+
+	   /* Don't traverse interprocedural edges here; FIXME: do this above.  */
+	   if (succ->m_kind == SUPEREDGE_CALL)
+	     {
+	       log ("rejecting interprocedural edge");
+	       continue;
+	     }
+
+	   /* For conditionals and switch statements, add the
+	      relevant conditions (for the specific edge) to new_ps;
+	      skip edges for which the resulting constraints
+	      are impossible.  */
+	   path_state new_ps (ps);
+	   const gimple *last_stmt = node->get_last_stmt ();
+	   if (!new_ps.apply_constraints_for_edge (*succ, last_stmt))
+	     {
+	       log ("skipping impossible edge to SN: %i",
+		    succ->m_dest->m_index);
+	       continue;
+	     }
+
+	   /* TODO: is the above correct?  What should the interaction
+	      be between the state-caching and the path constraints?
+	      Do we skip up-front, or do we prune later?  */
+
+	   /* Allow for pattern-matching based on edge constraints.  */
+	   log ("considering edge-based patterns");
+
+	   /* We need to clone the backtrace here so if the transition
+	      writes into the path each edge has its own copy.  */
+	   backtrace new_b (b);
+	   sm_instance per_edge_new_sm_inst = sm_inst;
+	   engine_sm_context ctxt (this,
+				   &per_edge_new_sm_inst,
+				   &sm_inst,
+				   &new_b.m_path);
+	   m_sm.on_edge (&ctxt, last_stmt, succ);
+
+	   dfs_traverse (per_edge_new_sm_inst, new_b, new_ps, succ->m_dest);
+	}
+    }
+}
+
+void
+engine::on_stmt (const supernode *node, const gimple *stmt,
+		 sm_instance *sm_inst, const sm_instance *old_sm_inst,
+		 const backtrace &b, /* FIXME: <<< this is purely so that we can generate full paths for debugging */
+		 diagnostic_path *path, path_state *ps)
+{
+  JIT_LOG_FUNC (get_logger ());
+  if (get_logger ())
+    {
+      pretty_printer pp;
+      pp_gimple_stmt_1 (&pp, const_cast <gimple *> (stmt) , 0, (dump_flags_t)0);
+      // FIXME: const_cast!
+      log ("stmt: %s", pp_formatted_text (&pp));
+      log ("sm_inst: %s", sm_inst->to_str (m_sm).c_str ());
+    }
+  engine_sm_context ctxt (this, sm_inst, old_sm_inst, path);
+  m_sm.on_stmt (&ctxt, node, stmt);
+  m_on_stmt_calls++;
+
+  if (const gassign *assign = dyn_cast <const gassign *> (stmt))
+    {
+      tree lhs = gimple_assign_lhs (assign);
+      tree rhs1 = gimple_assign_rhs1 (assign);
+      enum tree_code op = gimple_assign_rhs_code (assign);
+      switch (op)
+	{
+	default:
+	  break;
+
+	case NOP_EXPR:
+	  {
+	    /* Direct assignment of the form LHS = RHS;  */
+	    on_assignment (node, assign, sm_inst, old_sm_inst, lhs, rhs1, ps);
+	  }
+	  break;
+
+	case EQ_EXPR:
+	case NE_EXPR:
+	case GE_EXPR:
+	case LE_EXPR:
+	case GT_EXPR:
+	case LT_EXPR:
+	  {
+	    /* If we see a comparison, evaluate it based on what we know along
+	       this path, and record the result, if known.  */
+	    tree rhs2 = gimple_assign_rhs2 (assign);
+	    // FIXME: stack depths:
+	    equiv_class_id rhs1_ec_id = ps->get_or_add_equiv_class (path_var (rhs1, 0));
+	    equiv_class_id rhs2_ec_id = ps->get_or_add_equiv_class (path_var (rhs2, 0));
+	    tristate t = ps->eval_condition (rhs1_ec_id, op, rhs2_ec_id);
+	    if (get_logger ())
+	      log ("eval condition: %s", t.as_string ());
+	    if (t.is_known ())
+	      // FIXME: stack depth:
+	      ps->add_constraint (path_var (lhs, 0),
+				  t.is_true () ? NE_EXPR : EQ_EXPR,
+				  path_var (integer_zero_node, 0));
+	  }
+	}
+    }
+  if (const greturn *return_ = dyn_cast <const greturn *> (stmt))
+    {
+      tree callee = node->m_fun->decl;
+      tree lhs = DECL_RESULT (callee);
+      tree rhs = gimple_return_retval (return_);
+      if (rhs)
+	on_assignment (node, return_, sm_inst, old_sm_inst, lhs, rhs, ps);
+    }
+
+  if (const gcall *call = dyn_cast <const gcall *> (stmt))
+    {
+      /* Debugging/test support.  */
+      if (is_named_call_p (call, "__dump_constraints", 0))
+	{
+	  gcc_rich_location rich_loc (stmt->location);
+#if 0
+	  diagnostic_path full_path;
+	  int i;
+	  const supernode *iter;
+	  FOR_EACH_VEC_ELT (b.m_nodes, i, iter)
+	    full_path.add_event (node->get_end_location (),
+				 node->m_fun->decl,
+				 0, "");
+	  full_path.add_event (stmt->location,
+			       node->m_fun->decl,
+			       0, "%s", ps->to_str ().c_str ());
+	  rich_loc.set_path (&full_path);
+	  // FIXME: want a more detailed path, so use the backtrace
+#endif
+	  inform (&rich_loc, "__dump_constraints: %s", ps->to_str ().c_str ());
+	}
+
+      /* Handle the builtin "__analyzer_eval" by evaluating the input
+	 and dumping as a dummy warning, so that test cases can use dg-warning
+	 to validate the result (and so unexpected warnings will lead to DejaGnu
+	 failures.  */
+      if (is_named_call_p (call, "__analyzer_eval", 1))
+	{
+	  /* FIXME: crude hack: only do it for the malloc checker, to avoid
+	     getting every message more than once.
+	     Ultimately we want a command-line option for this, or
+	     something (this is really only for testcases though).  */
+	  if (0 == strcmp (m_sm.get_name (), "malloc"))
+	    {
+	      tree t_arg = gimple_call_arg (call, 0);
+	      tristate t
+		= ps->dump_condition (t_arg, NE_EXPR, integer_zero_node);
+	      warning_at (call->location, 0, "%s", t.as_string ());
+	    }
+	}
+    }
+
+  if (get_logger ())
+    log ("sm_inst is now: %s", sm_inst->to_str (m_sm).c_str ());
+}
+
+void
+engine::on_assignment (const supernode *node, const gimple *stmt,
+		       sm_instance *sm_inst, const sm_instance *old_sm_inst,
+		       tree lhs, tree rhs, path_state *ps)
+{
+  JIT_LOG_SCOPE (get_logger ());
+  if (get_logger ())
+    {
+      log ("sm_inst: %s", sm_inst->to_str (m_sm).c_str ());
+      log ("old_sm_inst: %s", old_sm_inst->to_str (m_sm).c_str ());
+      log ("lhs: %s", expr_to_string (lhs).c_str ());
+      log ("rhs: %s", expr_to_string (rhs).c_str ());
+      if (ps)
+	log ("old ps: %s", ps->to_str ().c_str ());
+    }
+
+  // FIXME: should ps always be non-NULL?
+  // FIXME: this isn't right; this assumes LHS = RHS; what if there are ops???
+  if (ps)
+    ps->on_assignment (path_var (lhs, 0), path_var (rhs, 0)); // FIXME stack depths
+
+  for (set_of_vars::iterator iter = old_sm_inst->m_active_vars.begin ();
+       iter != old_sm_inst->m_active_vars.end ();
+       ++iter)
+    {
+      if ((*iter).m_var == rhs)
+	{
+	  log ("got match");
+	  engine_sm_context ctxt (this, sm_inst, old_sm_inst, NULL /* FIXME */);
+	  // FIXME: we want to overwrite any old states
+	  // (but respect the gstate, I think):
+	  ctxt.on_transition (node, stmt, lhs, 0/* FIXME */, (*iter).m_state);
+	}
+    }
+
+  if (get_logger ())
+    {
+      log ("sm_inst is now: %s", sm_inst->to_str (m_sm).c_str ());
+      if (ps)
+	log ("ps: %s", ps->to_str ().c_str ());
+    }
+}
+
+void
+engine::relax (backtrace b)
+{
+  JIT_LOG_SCOPE (get_logger ());
+  if (get_logger ())
+    log ("backtrace: %s", b.to_str ().c_str ());
+
+  const supernode *node = b.m_nodes.pop ();
+  if (node->return_p ())
+    {
+      per_node_t &node_data = get_data_for_node (node);
+      node_data.m_sfx_add.union_with (node_data.m_blk_add);
+      node_data.m_sfx_transition.union_with (node_data.m_blk_transition);
+    }
+
+  while (b.m_nodes.length () > 0)
+    {
+      per_node_t &node_data = get_data_for_node (node);
+      const supernode *prev = b.m_nodes.pop ();
+      per_node_t &prev_data = get_data_for_node (prev);
+
+      /* Propagate add-edges backwards.  */
+      for (set_of_summary_edges::iterator i
+	     = node_data.m_sfx_add.begin ();
+	   i != node_data.m_sfx_add.end (); ++i)
+	{
+	  const summary_edge &e = (*i);
+	  for (set_of_summary_edges::iterator j
+		 = prev_data.m_blk_transition.begin ();
+	       j != prev_data.m_blk_transition.end (); ++j)
+	    {
+	      const summary_edge &s = (*j);
+	      if (s.m_end.m_gstate == e.m_start.m_gstate)
+		{
+		  summary_edge e2 (e);
+		  /* Relabel gstate.  */
+		  e2.m_start.m_gstate = s.m_start.m_gstate;
+		  prev_data.m_sfx_add.add (e2);
+		}
+	    }
+	}
+
+      /* Propagate transition-edges backwards.  */
+      for (set_of_summary_edges::iterator i
+	     = node_data.m_sfx_transition.begin ();
+	   i != node_data.m_sfx_transition.end (); ++i)
+	{
+	  const summary_edge &e = (*i);
+	  /* Handle blk_transition.  */
+	  for (set_of_summary_edges::iterator j
+		 = prev_data.m_blk_transition.begin ();
+	       j != prev_data.m_blk_transition.end (); ++j)
+	    {
+	      const summary_edge &s = (*j);
+	      if (s.m_end == e.m_start)
+		prev_data.m_sfx_transition.add
+		  (summary_edge (s.m_start, e.m_end));
+	    }
+	  /* Handle blk_add.  */
+	  for (set_of_summary_edges::iterator j
+		 = prev_data.m_blk_add.begin ();
+	       j != prev_data.m_blk_add.end (); ++j)
+	    {
+	      const summary_edge &s = (*j);
+	      if (s.m_end == e.m_start)
+		prev_data.m_sfx_add.add
+		  (summary_edge (s.m_start, e.m_end));
+	    }
+	}
+
+      node = prev;
+    }
+}
+
+sm_instance
+engine::refine_for_call (sm_instance sm_inst, cgraph_edge *edge,
+			 const supernode *caller_node)
+{
+  JIT_LOG_SCOPE (get_logger ());
+  if (get_logger ())
+    {
+      log ("from %s to %s",
+	   function_name (edge->caller->get_fun ()),
+	   function_name (edge->callee->get_fun ()));
+      log ("sm_inst was: %s", sm_inst.to_str (m_sm).c_str ());
+    }
+
+  sm_instance sm_inst_for_call;
+  sm_inst_for_call.add_placeholder ();
+
+  // TODO: map actual params to formal params:
+  tree callee = edge->callee->get_fun ()->decl;
+  gcall *call = edge->call_stmt;
+
+  int i = 0;
+  for (tree parm = DECL_ARGUMENTS (callee); parm;
+       parm = DECL_CHAIN (parm), ++i)
+    {
+      tree arg = gimple_call_arg (call, i);
+#if 0
+      on_assignment (caller_node, call,
+		     &sm_inst_for_call, &sm_inst /* FIXME.  */,
+		     parm, arg);
+#endif
+      tree parm_default_ssa = ssa_default_def (edge->callee->get_fun (), parm);
+      on_assignment (caller_node, call,
+		     &sm_inst_for_call, &sm_inst /* FIXME.  */,
+		     parm_default_ssa, arg, NULL);
+#if 0
+      /* Use: parm, arg, next_sm_inst, to map from args in caller
+	 to parms in callee */
+      for (hash_set<var_state_1_t>::iterator iter
+	     = next_sm_inst.m_active_vars.begin ();
+	   iter != next_sm_inst.m_active_vars.end ();
+	   ++iter)
+	{
+	  if ((*iter).m_var == arg)
+	    {
+	      call_sm_inst.m_active_vars.add
+		(var_state_1_t (parm, (*iter).m_state));
+	    }
+	}
+#endif
+    }
+
+  if (get_logger ())
+    log ("sm_inst_for_call: %s", sm_inst_for_call.to_str (m_sm).c_str ());
+
+  return sm_inst_for_call; // FIXME
+}
+
+sm_instance
+engine::restore_after_call (sm_instance sm_inst, cgraph_edge *edge,
+			    const supernode *rest_of_caller_node)
+{
+  JIT_LOG_SCOPE (get_logger ());
+  if (get_logger ())
+    {
+      log ("returning to %s from %s",
+	   function_name (edge->caller->get_fun ()),
+	   function_name (edge->callee->get_fun ()));
+      log ("sm_inst was: %s", sm_inst.to_str (m_sm).c_str ());
+    }
+
+  sm_instance sm_inst_in_caller;// (sm_inst);
+
+  tree callee = edge->callee->get_fun ()->decl;
+  tree result = DECL_RESULT (callee);
+  tree lhs = gimple_call_lhs (rest_of_caller_node->m_returning_call);
+  if (lhs)
+    {
+      on_assignment (rest_of_caller_node, rest_of_caller_node->m_returning_call,
+		     &sm_inst_in_caller, &sm_inst /* FIXME.  */,
+		     lhs, result, NULL);
+    }
+
+  // TODO: leak detection
+
+  if (get_logger ())
+    log ("sm_inst_in_caller: %s", sm_inst_in_caller.to_str (m_sm).c_str ());
+
+  return sm_inst_in_caller; // FIXME
+}
+
+void
+engine::follow_call (sm_instance sm_inst, backtrace b, path_state ps,
+		     const supernode *caller_node,
+		     cgraph_edge *edge)
+{
+  JIT_LOG_FUNC_2 (get_logger (), "from %s to %s",
+		  function_name (edge->caller->get_fun ()),
+		  function_name (edge->callee->get_fun ()));
+  // TODO!
+
+  /* TODO: refine extension state to the callee's scope.  */
+
+  sm_instance callee_sm_inst = refine_for_call (sm_inst, edge, caller_node);
+
+  const supernode *callee_node
+    = m_sg.get_node_for_function_entry (edge->callee->get_fun ());
+
+  /* Traverse into the callee.  */
+  dfs_traverse (callee_sm_inst, backtrace (), ps, callee_node);
+
+  /* Build set S of transition/add edges based on callee
+     function summary.  */
+  set_of_summary_edges callee_summary_edges;
+  {
+    // FIXME:
+    per_node_t &callee_data = get_data_for_node (callee_node);
+    // FIXME filter on gstate?
+    for (set_of_summary_edges::iterator i
+	   = callee_data.m_sfx_add.begin ();
+	 i != callee_data.m_sfx_add.end (); ++i)
+      {
+	callee_summary_edges.add (*i);
+      }
+    for (set_of_summary_edges::iterator i
+	   = callee_data.m_sfx_transition.begin ();
+	 i != callee_data.m_sfx_transition.end (); ++i)
+      {
+	callee_summary_edges.add (*i);
+      }
+    if (get_logger ())
+      log ("callee_summary_edges: %s", callee_summary_edges.to_str (m_sm).c_str ());
+  }
+#if 0
+    // TODO
+    var_state_t vs_from (var, from);
+    if (const_cast <sm_instance *> (m_old_sm_inst)->m_active_vars.m_set.contains (vs_from))
+      {
+        m_engine->log ("from state found");
+        m_sm_inst->m_active_vars.m_set.remove (vs_from);
+      }
+    else
+      {
+	m_engine->log ("from state %s not found",
+		       m_engine->m_sm.get_state_name (from));
+         /* If it's the start state and we're non-empty, then start,
+	    otherwise ignore.  */
+         if (from == 0)
+	   {
+             /* Start state.  */
+	     if (m_old_sm_inst->m_active_vars.has_expr (var))
+	       return;
+	   }
+	 else
+	   return;
+      }
+
+    m_sm_inst->m_active_vars.m_set.add (var_state_t (var, to));
+#endif
+
+  /* TODO: restore the edges in S to the caller's context.  */
+
+  /* TODO: create sm_instances for each disjoint exit state,
+     partitioning S.  */
+
+  sm_instance callee_summary_sm_inst; // FIXME
+
+    for (set_of_summary_edges::iterator i
+	   = callee_summary_edges.begin ();
+	 i != callee_summary_edges.end (); ++i)
+      {
+	callee_summary_sm_inst.m_active_vars.m_set.add
+	  ((*i).m_end.m_varstate); // FIXME
+      }
+
+  /* TODO traverse into intraprocedural successor for each
+     sm_instance.  */
+  const supernode *rest_of_caller_node = m_sg.get_caller_next_node (edge);
+  {
+    // TODO: make this a loop:
+    sm_instance restored_sm
+      = restore_after_call (callee_summary_sm_inst, edge, rest_of_caller_node);
+    log ("rest of call is in SN: %i", rest_of_caller_node->m_index);
+    dfs_traverse (restored_sm, b, ps, rest_of_caller_node);
+  }
+}
+
+void
+engine::log_stats () const
+{
+  log ("#supernodes: %i", m_sg.num_nodes ());
+  log ("#superedges: %i", m_sg.num_edges ());
+  log ("m_num_traverse_calls: %i", m_num_traverse_calls);
+  log ("m_num_cache_hits: %i", m_num_cache_hits);
+  log ("m_on_stmt_calls: %i", m_on_stmt_calls);
+}
+
+class engine_annotator : public dot_annotator
+{
+public:
+  engine_annotator (const engine &e) : m_engine (e) {}
+
+  void add_node_annotations (pretty_printer *pp,
+			     const supernode &n) const FINAL OVERRIDE
+  {
+    pp_printf (pp, "annotation_for_node_%i", n.m_index);
+    pp_printf (pp, " [shape=%s,style=filled,fillcolor=%s,label=\"",
+	       "record", "lightblue");
+    pp_left_brace (pp);
+    pp_write_text_to_stream (pp);
+
+    // TODO: write per-node state from engine here
+    //pp_string (pp, "FIXME: per-node state here!");
+    //pp_newline (pp);
+
+    per_node_t &data = const_cast <engine &> (m_engine).get_data_for_node (&n);
+
+    pp_printf (pp, "blk_add: %s",
+	       data.m_blk_add.to_str (m_engine.m_sm).c_str ());
+    pp_newline (pp);
+    pp_printf (pp, "blk_transition: %s",
+	       data.m_blk_transition.to_str (m_engine.m_sm).c_str ());
+    pp_newline (pp);
+    pp_printf (pp, "sfx_add: %s",
+	       data.m_sfx_add.to_str (m_engine.m_sm).c_str ());
+    pp_newline (pp);
+    pp_printf (pp, "sfx_transition: %s",
+	       data.m_sfx_transition.to_str (m_engine.m_sm).c_str ());
+    pp_newline (pp);
+
+    pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
+
+    pp_right_brace (pp);
+    pp_string (pp, "\"];\n\n");
+    pp_flush (pp);
+  }
+
+private:
+  const engine &m_engine;
+};
+
+/* FIXME.  */
+
+static int
+function_comparator (const void *pn1, const void *pn2)
+{
+  const cgraph_node *node1 = *(const cgraph_node * const *)pn1;
+  const cgraph_node *node2 = *(const cgraph_node * const *)pn2;
+
+  location_t loc1 = node1->get_fun ()->function_start_locus;
+  location_t loc2 = node2->get_fun ()->function_start_locus;
+
+  return linemap_compare_locations (line_table, loc2, loc1);
+}
+
+/* FIXME.  */
+/* TODO: should we run them all at once, or serially?
+   For now, this does them serially.  */
+
+void
+run_one_checker (const supergraph &sg, const state_machine &sm, logger *logger)
+{
+  engine e (sg, sm, logger);
+
+  cgraph_node *node;
+  auto_vec<cgraph_node *> root_functions;
+  FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
+    root_functions.safe_push (node);
+
+  /* Sort root_functions so that diagnostics are emitted in a
+     sane order.
+     TODO: perhaps we should buffer the diagnostics and sort them later,
+     instead.  */
+  root_functions.qsort (function_comparator);
+
+  int i;
+  FOR_EACH_VEC_ELT (root_functions, i, node)
+    {
+      function *fun = node->get_fun ();
+
+      backtrace b;
+      //	diagnostic_path path;
+      path_state ps;
+
+      sm_instance sm_inst;
+      sm_inst.add_placeholder ();
+
+      e.log ("function: %s", function_name (fun));
+      e.dfs_traverse (sm_inst, b, ps,
+		      sg.get_node_for_function_entry (fun));
+    }
+
+  engine_annotator a (e);
+  sg.dump_dot ("test-2.dot", &a); // TODO: different filename
+
+  e.log_stats ();
+}
+
+
+/* FIXME.  */
+
+void
+run_checkers ()
+{
+  //FILE *dump = fopen ("test.out", "w"); // FIXME
+  {
+    log_user the_logger (NULL);
+    if (flag_dump_analyzer)
+      {
+	FILE *dump_file = /* 0 ? dump : */stderr; // FIXME
+	the_logger.set_logger (new logger (dump_file, 0, 0));
+      }
+
+    supergraph sg;
+    sg.dump_dot ("test.dot");
+
+    {
+      malloc_state_machine sm (the_logger.get_logger ());
+      run_one_checker (sg, sm, the_logger.get_logger ());
+    }
+
+    {
+      fileptr_state_machine sm (the_logger.get_logger ());
+      run_one_checker (sg, sm, the_logger.get_logger ());
+    }
+
+    /* end of lifetime of the_logger (so that dump file is closed after the
+       various dtors run).  */
+  }
+
+  //fclose (dump);
+}
diff --git a/gcc/analyzer/engine.h b/gcc/analyzer/engine.h
new file mode 100644
index 0000000..6e7d60b
--- /dev/null
+++ b/gcc/analyzer/engine.h
@@ -0,0 +1 @@
+extern void run_checkers ();
diff --git a/gcc/analyzer/graphviz.cc b/gcc/analyzer/graphviz.cc
new file mode 100644
index 0000000..6121ca4
--- /dev/null
+++ b/gcc/analyzer/graphviz.cc
@@ -0,0 +1,62 @@
+/* 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 "graphviz.h"
+
+graphviz_out::graphviz_out (pretty_printer *pp)
+: m_pp (pp),
+  m_indent (0)
+{
+}
+
+void
+graphviz_out::println (const char *fmt, ...)
+{
+  text_info text;
+  va_list ap;
+
+  write_indent ();
+
+  va_start (ap, fmt);
+  text.err_no = errno;
+  text.args_ptr = &ap;
+  text.format_spec = fmt;
+  pp_format (m_pp, &text);
+  pp_output_formatted_text (m_pp);
+  va_end (ap);
+
+  pp_newline (m_pp);
+}
+
+void
+graphviz_out::write_indent ()
+{
+  for (int i = 0; i < m_indent * 2; ++i)
+    pp_space (m_pp);
+}
diff --git a/gcc/analyzer/graphviz.h b/gcc/analyzer/graphviz.h
new file mode 100644
index 0000000..a69626d
--- /dev/null
+++ b/gcc/analyzer/graphviz.h
@@ -0,0 +1,19 @@
+/* FIXME.  */
+
+class graphviz_out {
+ public:
+  graphviz_out (pretty_printer *pp);
+
+  void println (const char *fmt, ...)
+    ATTRIBUTE_GCC_PPDIAG(2,3);
+
+  void indent () { m_indent++; }
+  void outdent () { m_indent--; }
+
+  void write_indent ();
+
+  //private:
+  pretty_printer *m_pp;
+ private:
+  int m_indent;
+};
diff --git a/gcc/analyzer/hardcoded-state-machine.cc b/gcc/analyzer/hardcoded-state-machine.cc
new file mode 100644
index 0000000..7c11e05
--- /dev/null
+++ b/gcc/analyzer/hardcoded-state-machine.cc
@@ -0,0 +1,319 @@
+/* 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;
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+static 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;
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+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];
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+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))
+	{
+	  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");
+	  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_null, m_free);
+	  sm_ctxt->on_transition (node, stmt, arg, m_nonnull, m_free);
+
+          sm_ctxt->error (node, stmt, arg, m_free, "double-free of %qE", arg);
+	  sm_ctxt->on_transition (node, stmt, arg, m_free, m_stop);
+	}
+    }
+
+  // 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->error (node, stmt, arg, m_unchecked, "use of possibly-NULL %qE", arg);
+	  sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_nonnull);
+
+	  sm_ctxt->error (node, stmt, arg, m_null, "use of NULL %qE", arg);
+	  sm_ctxt->on_transition (node, stmt, arg, m_null, m_stop);
+
+	  sm_ctxt->error (node, stmt, arg, m_free, "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))
+	{
+	  ::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
+{
+  // TODO
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+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", 1))
+	{
+	  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->error (node, stmt, arg, m_closed, "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))
+	{
+	  ::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
+{
+  // TODO
+}
diff --git a/gcc/analyzer/hardcoded-state-machine.h b/gcc/analyzer/hardcoded-state-machine.h
new file mode 100644
index 0000000..b9a7bec
--- /dev/null
+++ b/gcc/analyzer/hardcoded-state-machine.h
@@ -0,0 +1,141 @@
+#include "analyzer-logging.h"
+
+////////////////////////////////////////////////////////////////////////////
+
+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) {}
+
+  const char *get_name () const { return m_name; }
+
+  const char *get_state_name (state_t s) const;
+
+  virtual void 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;
+
+protected:
+  state_t add_state (const char *name);
+
+private:
+  const char *m_name;
+  auto_vec<const char *> m_state_names;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* 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) = 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 error (const supernode *node, const gimple *stmt,
+		      tree var, state_machine::state_t state,
+		      const char *fmt, ...) = 0;
+
+  // etc, what else?
+
+protected:
+  //const state_machine &m_sm;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* A particular state machine: the malloc/free API, for
+   prototyping purposes.  */
+
+class malloc_state_machine : public state_machine
+{
+public:
+  malloc_state_machine (logger *logger);
+
+  void 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;
+
+private:
+  state_t m_start;
+  state_t m_unchecked;
+  state_t m_null;
+  state_t m_nonnull;
+  state_t m_free;
+  state_t m_stop;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* A particular state machine: the fopen API, for
+   prototyping purposes.  */
+
+class fileptr_state_machine : public state_machine
+{
+public:
+  fileptr_state_machine (logger *logger);
+
+  void 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;
+
+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;
+};
diff --git a/gcc/analyzer/supergraph.cc b/gcc/analyzer/supergraph.cc
new file mode 100644
index 0000000..019645c
--- /dev/null
+++ b/gcc/analyzer/supergraph.cc
@@ -0,0 +1,713 @@
+/* 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/>.  */
+
+#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"
+
+/* Get the cgraph_edge, but only if there's an underlying function body.  */
+
+cgraph_edge *
+supergraph_call_edge (function *fun, gimple *stmt)
+{
+  gcall *call = dyn_cast<gcall *> (stmt);
+  if (!call)
+    return NULL;
+  cgraph_edge *edge = cgraph_node::get (fun->decl)->get_edge (stmt);
+  if (!edge)
+    return NULL;
+  if (!edge->callee->get_fun ())
+    return NULL;
+  return edge;
+}
+
+// FIXME
+
+supergraph::supergraph ()
+{
+  /* First pass: make supernodes.  */
+  {
+    /* Sort the cgraph_nodes?  */
+    cgraph_node *node;
+    FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
+    {
+      function *fun = node->get_fun ();
+#if 0
+      inform (UNKNOWN_LOCATION, "make_supergraph: make nodes for function: %qD",
+	      fun->decl);
+#endif
+
+      basic_block bb;
+      FOR_ALL_BB_FN (bb, fun)
+	{
+	  /* The initial supernode for the BB gets the phi nodes (if any).  */
+	  supernode *node_for_stmts = add_node (fun, bb, NULL, phi_nodes (bb));
+	  m_bb_to_initial_node.put (bb, node_for_stmts);
+
+	  /* Append statements from BB to the current supernode, splitting
+	     them into a new supernode at each call site; such call statements
+	     appear in both supernodes (representing call and return).  */
+	  gimple_stmt_iterator gsi;
+	  for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
+	    {
+	      gimple *stmt = gsi_stmt (gsi);
+	      node_for_stmts->m_stmts.safe_push (stmt);
+	      if (cgraph_edge *edge = supergraph_call_edge (fun, stmt))
+		{
+		  m_cgraph_edge_to_caller_prev_node.put(edge, node_for_stmts);
+		  node_for_stmts = add_node (fun, bb, as_a <gcall *> (stmt), NULL);
+		  m_cgraph_edge_to_caller_next_node.put (edge, node_for_stmts);
+		}
+	    }
+
+	  m_bb_to_final_node.put (bb, node_for_stmts);
+	}
+    }
+  }
+
+  /* Second pass: make superedges.  */
+  {
+    /* Make superedges for CFG edges.  */
+    for (bb_to_node_t::iterator iter = m_bb_to_final_node.begin ();
+	 iter != m_bb_to_final_node.end ();
+	 ++iter)
+      {
+	basic_block bb = (*iter).first;
+	supernode *src_supernode = (*iter).second;
+
+	::edge cfg_edge;
+	int idx;
+	if (bb->succs)
+	  FOR_EACH_VEC_ELT (*bb->succs, idx, cfg_edge)
+	    {
+	      basic_block dest_cfg_block = cfg_edge->dest;
+	      supernode *dest_supernode
+		= *m_bb_to_initial_node.get (dest_cfg_block);
+	      add_cfg_edge (src_supernode, dest_supernode, cfg_edge, idx);
+	    }
+      }
+
+    /* Make interprocedural superedges for calls.  */
+    {
+      for (cgraph_edge_to_node_t::iterator iter
+	     = m_cgraph_edge_to_caller_prev_node.begin ();
+	   iter != m_cgraph_edge_to_caller_prev_node.end ();
+	   ++iter)
+	{
+	  cgraph_edge *edge = (*iter).first;
+	  supernode *caller_prev_supernode = (*iter).second;
+	  basic_block callee_cfg_block
+	    = ENTRY_BLOCK_PTR_FOR_FN (edge->callee->get_fun ());
+	  supernode *callee_supernode
+	    = *m_bb_to_initial_node.get (callee_cfg_block);
+	  add_callgraph_edge (caller_prev_supernode, callee_supernode,
+			      SUPEREDGE_CALL, edge);
+	}
+    }
+
+    /* Make interprocedural superedges for returns.  */
+    {
+      for (cgraph_edge_to_node_t::iterator iter
+	     = m_cgraph_edge_to_caller_next_node.begin ();
+	   iter != m_cgraph_edge_to_caller_next_node.end ();
+	   ++iter)
+	{
+	  cgraph_edge *edge = (*iter).first;
+	  supernode *caller_next_supernode = (*iter).second;
+	  basic_block callee_cfg_block
+	    = EXIT_BLOCK_PTR_FOR_FN (edge->callee->get_fun ());
+	  supernode *callee_supernode
+	    = *m_bb_to_initial_node.get (callee_cfg_block);
+	  add_callgraph_edge (callee_supernode, caller_next_supernode,
+			      SUPEREDGE_RETURN, edge);
+	}
+    }
+
+    /* Make intraprocedural superedges linking the two halves of a call.  */
+    {
+      for (cgraph_edge_to_node_t::iterator iter
+	     = m_cgraph_edge_to_caller_prev_node.begin ();
+	   iter != m_cgraph_edge_to_caller_prev_node.end ();
+	   ++iter)
+	{
+	  cgraph_edge *edge = (*iter).first;
+	  supernode *caller_prev_supernode = (*iter).second;
+	  supernode *caller_next_supernode
+	    = *m_cgraph_edge_to_caller_next_node.get (edge);
+	  add_callgraph_edge (caller_prev_supernode,
+			      caller_next_supernode,
+			      SUPEREDGE_INTRAPROCEDURAL_CALL, edge);
+	}
+
+    }
+  }
+}
+
+/* FIXME.  */
+
+void
+supergraph::dump_dot_to_pp (pretty_printer *pp, const dot_annotator *a) const
+{
+  graphviz_out gv (pp);
+
+  pp_string (pp, "digraph \"");
+  pp_write_text_to_stream (pp);
+  pp_string (pp, "FIXME: base");
+  pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/false);
+  pp_string (pp, "\" {\n");
+  gv.indent ();
+
+  gv.println ("overlap=false;");
+  gv.println ("compound=true;");
+
+  // TODO: maybe (optionally) sub-subdivide by TU, for LTO
+
+  /* TODO: see also:
+     https://gcc-python-plugin.readthedocs.io/en/latest/_images/sample-supergraph.png
+  */
+
+  /* Break out the supernodes into clusters by function.  */
+  {
+    cgraph_node *node;
+    FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
+    {
+      function *fun = node->get_fun ();
+      const char *funcname = function_name (fun);
+      gv.println ("subgraph \"cluster_%s\" {",
+		  funcname);
+      gv.indent ();
+      pp_printf (pp,
+		 ("style=\"dashed\";"
+		  " color=\"black\";"
+		  " label=\"%s\";\n"),
+		 funcname);
+
+      /* Break out the nodes into clusters by BB from original CFG.  */
+      {
+	basic_block bb;
+	FOR_ALL_BB_FN (bb, fun)
+	  {
+
+	    gv.println ("subgraph \"cluster_%s_bb_%i\" {",
+			funcname, bb->index);
+	    gv.indent ();
+	    pp_printf (pp,
+		       ("style=\"dashed\";"
+			" color=\"black\";"
+			" label=\"bb: %i\";\n"),
+		       bb->index);
+
+	    // TODO: maybe keep an index per-function/per-bb to speed this up???
+	    int i;
+	    supernode *n;
+	    FOR_EACH_VEC_ELT (m_nodes, i, n)
+	      if (n->m_fun == fun && n->m_bb == bb)
+		n->dump_dot (&gv, a);
+
+	    /* Terminate per-bb "subgraph" */
+	    gv.outdent ();
+	    gv.println ("}");
+	  }
+      }
+
+#if 1
+      /* Add an invisible edge from ENTRY to EXIT, to improve the graph layout.  */
+      pp_string (pp, "\t");
+      get_node_for_function_entry (fun)->dump_dot_id (pp);
+      pp_string (pp, ":s -> ");
+      get_node_for_function_exit (fun)->dump_dot_id (pp);
+      pp_string (pp, ":n [style=\"invis\",constraint=true];\n");
+#endif
+
+      /* Terminate per-function "subgraph" */
+      gv.outdent ();
+      gv.println ("}");
+    }
+  }
+
+  /* Superedges.  */
+  int i;
+  superedge *e;
+  FOR_EACH_VEC_ELT (m_edges, i, e)
+    e->dump_dot (&gv);
+
+  /* Terminate "digraph" */
+  gv.outdent ();
+  gv.println ("}");
+}
+
+void
+supergraph::dump_dot_to_file (FILE *fp, const dot_annotator *a) const
+{
+  pretty_printer pp;
+  pp.buffer->stream = fp;
+  dump_dot_to_pp (&pp, a);
+  pp_flush (&pp);
+}
+
+void
+supergraph::dump_dot (const char *path, const dot_annotator *a) const
+{
+  FILE *fp = fopen (path, "w");
+  dump_dot_to_file (fp, a);
+  fclose (fp);
+}
+
+supernode *
+supergraph::add_node (function *fun, basic_block bb, gcall *returning_call,
+		      gimple_seq phi_nodes)
+{
+  supernode *n = new supernode (fun, bb, returning_call, phi_nodes,
+				m_nodes.length ());
+  m_nodes.safe_push (n);
+  return n;
+}
+
+superedge *
+supergraph::add_cfg_edge (supernode *src, supernode *dest, ::edge e, int idx)
+{
+  /* Special-case switch edges.  */
+  // FIXME: see ana::at_stmt::add_unreached_successors_to_worklist
+  gimple *stmt = src->get_last_stmt ();
+  if (stmt && stmt->code == GIMPLE_SWITCH)
+    return add_edge (new switch_cfg_superedge (src, dest, e, idx));
+  else
+    return add_edge (new cfg_superedge (src, dest, e));
+}
+
+superedge *
+supergraph::add_callgraph_edge (supernode *src, supernode *dest,
+				enum edge_kind kind, cgraph_edge *cedge)
+{
+  return add_edge (new call_superedge (src, dest, kind, cedge));
+}
+
+superedge *
+supergraph::add_edge (superedge *e)
+{
+  m_edges.safe_push (e);
+  e->m_src->m_succs.safe_push (e);
+  return e;
+}
+
+std::string
+supernode::to_str () const
+{
+  return std::to_string (m_index);
+}
+
+void
+supernode::dump_dot (graphviz_out *gv, const dot_annotator *a) const
+{
+  gv->println ("subgraph cluster_node_%i {",
+	       m_index);
+  gv->indent ();
+
+  gv->println("style=\"solid\";");
+  gv->println("color=\"black\";");
+  gv->println("fillcolor=\"lightgrey\";");
+  gv->println("label=\"sn: %i\";", m_index);
+
+  pretty_printer *pp = gv->m_pp;
+
+  if (a)
+    a->add_node_annotations (pp, *this);
+
+  gv->write_indent ();
+  dump_dot_id (pp);
+  pp_printf (pp, " [shape=%s,style=filled,fillcolor=%s,label=\"",
+	     "record", "lightgrey");
+  pp_left_brace (pp);
+  pp_write_text_to_stream (pp);
+  //TODO: dump_bb_for_graph (pp, bb);
+
+  if (m_returning_call)
+    {
+      pp_string (pp, "returning call: ");
+      pp_gimple_stmt_1 (pp, m_returning_call, 0, (dump_flags_t)0);
+      pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
+      pp_newline (pp);
+    }
+
+  if (entry_p ())
+    {
+      pp_string (pp, "ENTRY");
+      pp_newline (pp);
+    }
+
+  if (return_p ())
+    {
+      pp_string (pp, "EXIT");
+      pp_newline (pp);
+    }
+
+  /* Phi nodes.  */
+  for (gphi_iterator gpi = const_cast<supernode *> (this)->start_phis ();
+       !gsi_end_p (gpi); gsi_next (&gpi))
+    {
+      gimple *stmt = gsi_stmt (gpi);
+      pp_gimple_stmt_1 (pp, stmt, 0, (dump_flags_t)0);
+      pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
+      pp_newline (pp);
+    }
+
+  /* Statements.  */
+  int i;
+  gimple *stmt;
+  FOR_EACH_VEC_ELT (m_stmts, i, stmt)
+    {
+      pp_gimple_stmt_1 (pp, stmt, 0, (dump_flags_t)0);
+      pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
+      pp_newline (pp);
+    }
+
+  pp_right_brace (pp);
+  pp_string (pp, "\"];\n\n");
+  pp_flush (pp);
+
+  /* Potentially visualize additional per-node data.  */
+#if 0
+  // compare with:
+  m_state->add_extra_nodes_and_edges (this, gv);
+#endif
+  if (0)
+    {
+      /* Begin a subgraph.  */
+      gv->println ("subgraph \"cluster_state_%p\" {", (const void *)this);
+      gv->indent ();
+      gv->println ("style=\"dashed\";");
+      gv->println ("color=\"green\";");
+      gv->println ("label=\"state\";");
+      gv->println ("clusterrank=\"local\";");
+
+      pretty_printer *pp = gv->m_pp;
+
+      /* Create dummy node.  */
+      gv->write_indent ();
+      pp_printf (pp,
+		 "state_%p_foo "
+		 "[shape=%s, style=\"filled\" fillcolor=\"yellow\" label=\"",
+		 (const void *)this, "record");
+      pp_left_brace (pp);
+      pp_write_text_to_stream (pp);
+      //dump_generic_node (pp, TREE_TYPE (expr), 0, (dump_flags_t)0, false);
+      pp_string (pp, " ");
+      //dump_generic_node (pp, expr, 0, (dump_flags_t)0, false);
+      pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
+      pp_right_brace (pp);
+      pp_string (pp, "\"];");
+      pp_flush (pp);
+      pp_newline (pp);
+
+      /* Terminate subgraph.  */
+      gv->outdent ();
+      gv->println ("}");
+    }
+
+
+  /* Terminate "subgraph" */
+  gv->outdent ();
+  gv->println ("}");
+}
+
+/* FIXME.  */
+
+void
+supernode::dump_dot_id (pretty_printer *pp) const
+{
+  pp_printf (pp, "node_%i", m_index);
+}
+
+// FIXME
+location_t
+supernode::get_start_location () const
+{
+  if (m_returning_call && m_returning_call->location != UNKNOWN_LOCATION)
+    return m_returning_call->location;
+
+  int i;
+  gimple *stmt;
+  FOR_EACH_VEC_ELT (m_stmts, i, stmt)
+    if (stmt->location != UNKNOWN_LOCATION)
+      return stmt->location;
+
+  if (entry_p ())
+    {
+      // TWEAK: show the decl instead; this leads to more readable output:
+      return DECL_SOURCE_LOCATION (m_fun->decl);
+
+      return m_fun->function_start_locus;
+    }
+  if (return_p ())
+    return m_fun->function_end_locus;
+
+  return UNKNOWN_LOCATION;
+}
+
+// FIXME
+
+location_t
+supernode::get_end_location () const
+{
+  int i;
+  gimple *stmt;
+  FOR_EACH_VEC_ELT_REVERSE (m_stmts, i, stmt)
+    if (stmt->location != UNKNOWN_LOCATION)
+      return stmt->location;
+
+  if (m_returning_call && m_returning_call->location != UNKNOWN_LOCATION)
+    return m_returning_call->location;
+
+  if (entry_p ())
+    return m_fun->function_start_locus;
+  if (return_p ())
+    return m_fun->function_end_locus;
+
+  return UNKNOWN_LOCATION;
+}
+
+void
+superedge::dump_dot (graphviz_out *gv) const
+{
+  const char *style = "\"solid,bold\"";
+  const char *color = "black";
+  int weight = 10;
+  const char *constraint = "true";
+
+  switch (m_kind)
+    {
+    default:
+      gcc_unreachable ();
+    case SUPEREDGE_CFG_EDGE:
+      break;
+    case SUPEREDGE_CALL:
+      color = "red";
+      //constraint = "false";
+      break;
+    case SUPEREDGE_RETURN:
+      color = "green";
+      //constraint = "false";
+      break;
+    case SUPEREDGE_INTRAPROCEDURAL_CALL:
+      style = "\"dotted\"";
+      break;
+    }
+
+  /* Adapted from graph.c:draw_cfg_node_succ_edges.  */
+  if (::edge cfg_edge = get_any_cfg_edge ())
+    {
+      if (cfg_edge->flags & EDGE_FAKE)
+	{
+	  style = "dotted";
+	  color = "green";
+	  weight = 0;
+	}
+      else if (cfg_edge->flags & EDGE_DFS_BACK)
+	{
+	  style = "\"dotted,bold\"";
+	  color = "blue";
+	  weight = 10;
+	}
+      else if (cfg_edge->flags & EDGE_FALLTHRU)
+	{
+	  color = "blue";
+	  weight = 100;
+	}
+
+      if (cfg_edge->flags & EDGE_ABNORMAL)
+	color = "red";
+    }
+
+  gv->write_indent ();
+
+  m_src->dump_dot_id (gv->m_pp);
+  if (m_kind == SUPEREDGE_CFG_EDGE)
+    pp_string (gv->m_pp, ":s");
+  /* FIXME: was seeing:
+       dot: compound.c:461: makeCompoundEdge: Assertion `bez->sflag' failed.
+     when experimenting with these.  */
+#if 0
+  else if (m_kind == SUPEREDGE_CALL)
+    pp_string (gv->m_pp, ":e");
+  else if (m_kind == SUPEREDGE_RETURN)
+    pp_string (gv->m_pp, ":w");
+#endif
+  pp_string (gv->m_pp, " -> ");
+  m_dest->dump_dot_id (gv->m_pp);
+  if (m_kind == SUPEREDGE_CFG_EDGE)
+    pp_string (gv->m_pp, ":n");
+#if 0
+  else if (m_kind == SUPEREDGE_CALL)
+    pp_string (gv->m_pp, ":w");
+  else if (m_kind == SUPEREDGE_RETURN)
+    pp_string (gv->m_pp, ":e");
+#endif
+  pp_printf (gv->m_pp,
+	     (" [style=%s, color=%s, weight=%d, constraint=%s,"
+	      " ltail=\"cluster_node_%i\", lhead=\"cluster_node_%i\""
+	      " headlabel=\""),
+	     style, color, weight, constraint,
+	     m_src->m_index, m_dest->m_index);
+
+  dump_label_to_pp (gv->m_pp);
+
+  pp_printf (gv->m_pp, "\"];\n");
+}
+
+/* FIXME.  */
+
+::edge
+superedge::get_any_cfg_edge () const
+{
+  if (const cfg_superedge *sub = dyn_cast_cfg_superedge ())
+    return sub->m_cfg_edge;
+  return NULL;
+}
+
+/* FIXME.  */
+
+cgraph_edge *
+superedge::get_any_callgraph_edge () const
+{
+  if (const call_superedge *sub = dyn_cast_call_superedge ())
+    return sub->m_cedge;
+  return NULL;
+}
+
+/* FIXME.  */
+
+cgraph_edge *
+superedge::get_callgraph_edge () const
+{
+  const call_superedge *sub = dyn_cast_call_superedge ();
+  gcc_assert (sub);
+  return sub->m_cedge;
+}
+
+/* FIXME.  */
+
+char *
+superedge::get_description () const
+{
+  pretty_printer pp;
+  dump_label_to_pp (&pp);
+  return xstrdup (pp_formatted_text (&pp));
+}
+
+/* FIXME.  */
+
+void
+cfg_superedge::dump_label_to_pp (pretty_printer *pp) const
+{
+  if (true_value_p ())
+    pp_printf (pp, "true");
+  else if (false_value_p ())
+    pp_printf (pp, "false");
+  /* Otherwise, no label.  */
+}
+
+/* FIXME.  */
+
+void
+switch_cfg_superedge::dump_label_to_pp (pretty_printer *pp) const
+{
+  tree case_label = get_case_label ();
+  gcc_assert (TREE_CODE (case_label) == CASE_LABEL_EXPR);
+  tree lower_bound = CASE_LOW (case_label);
+  tree upper_bound = CASE_HIGH (case_label);
+  if (lower_bound)
+    {
+      pp_printf (pp, "case ");
+      dump_generic_node (pp, lower_bound, 0, (dump_flags_t)0, false);
+      if (upper_bound)
+	{
+	  pp_printf (pp, " ... ");
+	  dump_generic_node (pp, upper_bound, 0, (dump_flags_t)0, false);
+	}
+      pp_printf (pp, ":");
+    }
+  else
+    pp_printf (pp, "default:");
+}
+
+/* FIXME.  */
+
+tree
+switch_cfg_superedge::get_case_label () const
+{
+  return gimple_switch_label (get_switch_stmt (), m_idx);
+}
+
+/* FIXME.  */
+
+void
+call_superedge::dump_label_to_pp (pretty_printer *pp) const
+{
+  /* FIXME.  */
+  switch (m_kind)
+    {
+    default:
+    case SUPEREDGE_CFG_EDGE:
+      gcc_unreachable ();
+
+    case SUPEREDGE_CALL:
+      pp_printf (pp, "call");
+      break;
+
+    case SUPEREDGE_RETURN:
+      pp_printf (pp, "return");
+      break;
+
+    case SUPEREDGE_INTRAPROCEDURAL_CALL:
+      pp_printf (pp, "intraproc link");
+      break;
+    }
+}
diff --git a/gcc/analyzer/supergraph.h b/gcc/analyzer/supergraph.h
new file mode 100644
index 0000000..a88326d
--- /dev/null
+++ b/gcc/analyzer/supergraph.h
@@ -0,0 +1,301 @@
+/* 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_SUPERGRAPH_H
+#define GCC_ANALYZER_SUPERGRAPH_H
+
+/* Forward decls, using indentation to show inheritance.  */
+
+class supergraph;
+class supernode;
+class superedge;
+  class call_superedge;
+  class cfg_superedge;
+    class switch_cfg_superedge;
+class dot_annotator;
+
+/* FIXME.  */
+
+enum edge_kind
+{
+  SUPEREDGE_CFG_EDGE,
+  SUPEREDGE_CALL,
+  SUPEREDGE_RETURN,
+  SUPEREDGE_INTRAPROCEDURAL_CALL,
+};
+
+#if 0
+// Do we really need vfuncs, or can we get away with m_kind ?
+  template <>
+  template <>
+  inline bool
+  is_a_helper <const cfg_superedge *>::test (const superedge *e)
+  {
+    return e->m_kind == SUPEREDGE_CFG_EDGE;
+  }
+
+  template <>
+  template <>
+  inline bool
+  is_a_helper <const call_superedge *>::test (const superedge *e)
+  {
+    return e->m_kind != SUPEREDGE_CFG_EDGE;
+  }
+#endif
+
+/* FIXME.  */
+
+class supergraph
+{
+public:
+  supergraph ();
+
+  supernode *get_node_for_function_entry (function *fun) const
+  {
+    return get_node_for_block (ENTRY_BLOCK_PTR_FOR_FN (fun));
+  }
+
+  supernode *get_node_for_function_exit (function *fun) const
+  {
+    return get_node_for_block (EXIT_BLOCK_PTR_FOR_FN (fun));
+  }
+
+  supernode *get_node_for_block (basic_block bb) const
+  {
+    return *const_cast <bb_to_node_t &> (m_bb_to_initial_node).get (bb);
+  }
+
+  /* Get the supernode containing the second half of the gcall *
+     at an interprocedural call, within the caller.  */
+  supernode *get_caller_next_node (cgraph_edge *edge) const
+  {
+    return (*const_cast <cgraph_edge_to_node_t &>
+	      (m_cgraph_edge_to_caller_next_node).get (edge));
+  }
+
+  void dump_dot_to_pp (pretty_printer *pp, const dot_annotator *a = NULL) const;
+  void dump_dot_to_file (FILE *fp, const dot_annotator *a = NULL) const;
+  void dump_dot (const char *path, const dot_annotator *a = NULL) const;
+
+  int num_nodes () const { return m_nodes.length (); }
+  int num_edges () const { return m_edges.length (); }
+
+private:
+  supernode *add_node (function *fun, basic_block bb, gcall *returning_call,
+		       gimple_seq phi_nodes);
+  superedge *add_cfg_edge (supernode *src, supernode *dest, ::edge e, int idx);
+  superedge *add_callgraph_edge (supernode *src, supernode *dest,
+				 enum edge_kind kind, cgraph_edge *cedge);
+  superedge *add_edge (superedge *e);
+
+  /* Data.  */
+
+  auto_vec<supernode *> m_nodes;
+  auto_vec<superedge *> m_edges;
+
+  typedef hash_map<basic_block, supernode *> bb_to_node_t;
+  bb_to_node_t m_bb_to_initial_node;
+  bb_to_node_t m_bb_to_final_node;
+
+  typedef hash_map<cgraph_edge *, supernode *> cgraph_edge_to_node_t;
+  cgraph_edge_to_node_t m_cgraph_edge_to_caller_prev_node;
+  cgraph_edge_to_node_t m_cgraph_edge_to_caller_next_node;
+};
+
+/* FIXME.  */
+
+class supernode
+{
+ public:
+  supernode (function *fun, basic_block bb, gcall *returning_call,
+	     gimple_seq phi_nodes, int index)
+  : m_fun (fun), m_bb (bb), m_returning_call (returning_call),
+    m_phi_nodes (phi_nodes), m_index (index)
+  {}
+
+  bool entry_p () const
+  {
+    return m_bb == ENTRY_BLOCK_PTR_FOR_FN (m_fun);
+  }
+
+  bool return_p () const
+  {
+    return m_bb == EXIT_BLOCK_PTR_FOR_FN (m_fun);
+  }
+
+  std::string to_str () const;
+
+  void dump_dot (graphviz_out *gv, const dot_annotator *a) const;
+  void dump_dot_id (pretty_printer *pp) const;
+
+  location_t get_start_location () const;
+  location_t get_end_location () const;
+
+  /* Returns iterator at the start of the list of phi nodes, if any.  */
+  // FIXME: adapted from gsi_start_phis.
+  gphi_iterator start_phis ()
+  {
+    gimple_seq *pseq = &m_phi_nodes;
+
+    /* Adapted from gsi_start_1. */
+    gphi_iterator i;
+
+    i.ptr = gimple_seq_first (*pseq);
+    i.seq = pseq;
+    i.bb = i.ptr ? gimple_bb (i.ptr) : NULL;
+
+    return i;
+  }
+
+  gimple *get_last_stmt () const
+  {
+    if (m_stmts.length () == 0)
+      return NULL;
+    return m_stmts[m_stmts.length () - 1];
+  }
+
+  gcall *get_final_call () const
+  {
+    gimple *stmt = get_last_stmt ();
+    if (stmt == NULL)
+      return NULL;
+    return dyn_cast<gcall *> (stmt);
+  }
+
+  function *m_fun; // alternatively could be stored as runs of indices within the supergraph
+  basic_block m_bb;
+  gcall *m_returning_call; // for handling the result of a returned call
+  gimple_seq m_phi_nodes; // ptr to that of the underlying BB, for the first supernode for the BB
+  auto_vec<gimple *> m_stmts;
+  auto_vec<superedge *> m_succs;
+  int m_index; /* unique within the supergraph as a whole.  */
+};
+
+/* FIXME.  */
+
+class superedge
+{
+ public:
+  void dump_dot (graphviz_out *gv) const;
+
+  virtual void dump_label_to_pp (pretty_printer *pp) const = 0;
+  virtual cfg_superedge *dyn_cast_cfg_superedge () { return NULL; }
+  virtual const cfg_superedge *dyn_cast_cfg_superedge () const { return NULL; }
+  virtual call_superedge *dyn_cast_call_superedge () { return NULL; }
+  virtual const call_superedge *dyn_cast_call_superedge () const { return NULL; }
+
+  ::edge get_any_cfg_edge () const;
+  cgraph_edge *get_any_callgraph_edge () const;
+  cgraph_edge *get_callgraph_edge () const;
+
+  char *get_description () const;
+
+ protected:
+  superedge (supernode *src, supernode *dest, enum edge_kind kind)
+    : m_src (src), m_dest (dest), m_kind (kind)
+  {}
+
+ public:
+  supernode *m_src;
+  supernode *m_dest;
+  enum edge_kind m_kind;
+};
+
+/* A subclass of superedge corresponding to a callgraph edge.  */
+
+class call_superedge : public superedge
+{
+ public:
+  call_superedge (supernode *src, supernode *dst, enum edge_kind kind,
+		  cgraph_edge *cedge)
+  : superedge (src, dst, kind),
+    m_cedge (cedge)
+  {}
+
+  void dump_label_to_pp (pretty_printer *pp) const OVERRIDE;
+  call_superedge *dyn_cast_call_superedge () FINAL OVERRIDE { return this; }
+  const call_superedge *dyn_cast_call_superedge () const FINAL OVERRIDE
+  {
+    return this;
+  }
+
+  cgraph_edge *m_cedge;
+};
+
+/* A subclass of superedge that corresponds to a CFG edge.  */
+
+class cfg_superedge : public superedge
+{
+ public:
+  cfg_superedge (supernode *src, supernode *dst, ::edge e)
+  : superedge (src, dst, SUPEREDGE_CFG_EDGE),
+    m_cfg_edge (e)
+  {}
+
+  void dump_label_to_pp (pretty_printer *pp) const OVERRIDE;
+  cfg_superedge *dyn_cast_cfg_superedge () FINAL OVERRIDE { return this; }
+  const cfg_superedge *dyn_cast_cfg_superedge () const FINAL OVERRIDE { return this; }
+
+  int get_flags () const { return m_cfg_edge->flags; }
+  int true_value_p () const { return get_flags () & EDGE_TRUE_VALUE; }
+  int false_value_p () const { return get_flags () & EDGE_FALSE_VALUE; }
+
+  ::edge m_cfg_edge;
+};
+
+/* A subclass for edges from switch statements, retaining enough
+   information to identify the pertinent case, and for adding labels
+   when rendering via graphviz.  */
+
+class switch_cfg_superedge : public cfg_superedge {
+ public:
+  switch_cfg_superedge (supernode *src, supernode *dst, ::edge e, int idx)
+  : cfg_superedge (src, dst, e),
+    m_idx (idx)
+  {}
+
+  void dump_label_to_pp (pretty_printer *pp) const FINAL OVERRIDE;
+
+  gswitch *get_switch_stmt () const
+  {
+    return as_a <gswitch *> (m_src->get_last_stmt ());
+  }
+
+  tree get_case_label () const;
+
+ private:
+  int m_idx;
+};
+
+/* Base class for adding additional content to the .dot output
+   for a supergraph.  */
+
+class dot_annotator
+{
+ public:
+  virtual ~dot_annotator () {}
+  virtual void add_node_annotations (pretty_printer *pp ATTRIBUTE_UNUSED,
+				     const supernode &n ATTRIBUTE_UNUSED)
+    const {}
+};
+
+extern cgraph_edge *supergraph_call_edge (function *fun, gimple *stmt);
+
+#endif /* GCC_ANALYZER_SUPERGRAPH_H */
diff --git a/gcc/analyzer/tristate.cc b/gcc/analyzer/tristate.cc
new file mode 100644
index 0000000..37b200c
--- /dev/null
+++ b/gcc/analyzer/tristate.cc
@@ -0,0 +1,67 @@
+#include "config.h"
+#include "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tristate.h"
+
+const char *
+tristate::as_string () const
+{
+  switch (m_value)
+    {
+    default:
+      gcc_unreachable ();
+    case TS_UNKNOWN:
+      return "UNKNOWN";
+    case TS_TRUE:
+      return "TRUE";
+    case TS_FALSE:
+      return "FALSE";
+    }
+}
+
+tristate
+tristate::not_ () const
+{
+  switch (m_value)
+    {
+    default:
+      gcc_unreachable ();
+    case TS_UNKNOWN:
+      return tristate (TS_UNKNOWN);
+    case TS_TRUE:
+      return tristate (TS_FALSE);
+    case TS_FALSE:
+      return tristate (TS_TRUE);
+    }
+}
+
+tristate
+tristate::or_ (tristate other) const
+{
+  switch (m_value)
+    {
+    default:
+      gcc_unreachable ();
+    case TS_UNKNOWN:
+    case TS_FALSE:
+      return other;
+    case TS_TRUE:
+      return tristate (TS_TRUE);
+    }
+}
+
+tristate
+tristate::and_ (tristate other) const
+{
+  switch (m_value)
+    {
+    default:
+      gcc_unreachable ();
+    case TS_UNKNOWN:
+    case TS_TRUE:
+      return other;
+    case TS_FALSE:
+      return tristate (TS_FALSE);
+    }
+}
diff --git a/gcc/analyzer/tristate.h b/gcc/analyzer/tristate.h
new file mode 100644
index 0000000..3e91734
--- /dev/null
+++ b/gcc/analyzer/tristate.h
@@ -0,0 +1,74 @@
+/* "True" vs "False" vs "Unknown".
+   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_TRISTATE_H
+#define GCC_ANALYZER_TRISTATE_H
+
+class tristate {
+ public:
+  enum value {
+    TS_UNKNOWN,
+    TS_TRUE,
+    TS_FALSE
+  };
+
+  tristate (enum value val) : m_value (val) {}
+  tristate (bool val) : m_value (val ? TS_TRUE : TS_FALSE) {}
+
+  const char *as_string () const;
+
+  bool is_known () const { return m_value != TS_UNKNOWN; }
+  bool is_true () const { return m_value == TS_TRUE; }
+  bool is_false () const { return m_value == TS_FALSE; }
+
+  tristate not_ () const;
+  tristate or_ (tristate other) const;
+  tristate and_ (tristate other) const;
+
+  bool operator== (const tristate &other) const
+  {
+    return m_value == other.m_value;
+  }
+
+ private:
+  enum value m_value;
+};
+
+/* Overloaded boolean operators on tristates.  */
+
+inline tristate
+operator ! (tristate t)
+{
+  return t.not_ ();
+}
+
+inline tristate
+operator || (tristate a, tristate b)
+{
+  return a.or_ (b);
+}
+
+inline tristate
+operator && (tristate a, tristate b)
+{
+  return a.and_ (b);
+}
+
+#endif /* GCC_ANALYZER_TRISTATE_H */
diff --git a/gcc/builtins.def b/gcc/builtins.def
index c92292a..1def29d 100644
--- a/gcc/builtins.def
+++ b/gcc/builtins.def
@@ -1096,4 +1096,12 @@ DEF_GCC_BUILTIN (BUILT_IN_LINE, "LINE", BT_FN_INT, ATTR_NOTHROW_LEAF_LIST)
 /* HSAIL/BRIG frontend builtins.  */
 #include "brig-builtins.def"
 
+
+/* Analyzer builtins.  */
+DEF_BUILTIN (BUILT_IN_ANALYZER_DUMP, "__analyzer_dump", BUILT_IN_NORMAL, BT_FN_VOID, BT_LAST,
+	     false, false, false, ATTR_NULL, true, true)
+
+DEF_BUILTIN (BUILT_IN_ANALYZER_EVAL, "__analyzer_eval", BUILT_IN_NORMAL, BT_FN_VOID_INT, BT_LAST,
+	     false, false, false, ATTR_NULL, true, true)
+
 #undef DEF_BUILTIN
diff --git a/gcc/common.opt b/gcc/common.opt
index dc038c9..44773e9 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -269,6 +269,9 @@ Driver Joined Alias(e)
 -extra-warnings
 Common Warning Alias(Wextra)
 
+-analyzer
+Driver Var(analyzer_flag)
+
 -for-assembler
 Driver Separate Alias(Xassembler)
 
@@ -1330,6 +1333,9 @@ fdump-
 Common Joined RejectNegative Var(common_deferred_options) Defer
 -fdump-<type>	Dump various compiler internals to a file.
 
+fdump-analyzer
+Common RejectNegative Var(flag_dump_analyzer)
+
 fdump-final-insns
 Driver RejectNegative
 
diff --git a/gcc/gcc.c b/gcc/gcc.c
index cc294d7..6b5a6b1 100644
--- a/gcc/gcc.c
+++ b/gcc/gcc.c
@@ -4252,6 +4252,16 @@ driver_handle_option (struct gcc_options *opts,
       handle_foffload_option (arg);
       break;
 
+    case OPT__analyzer:
+      {
+	validated = true;
+	// FIXME:
+	save_switch ("-iplugindir=/home/david/more-coding/gcc-git-analysis-output/build/gcc",
+		     0, NULL, validated, true);
+	save_switch ("-fplugin=analyzer_plugin", 0, NULL, validated, true);
+      }
+      break;
+
     default:
       /* Various driver options need no special processing at this
 	 point, having been handled in a prescan above or being
diff --git a/gcc/gimple.h b/gcc/gimple.h
index 8b5c9e2..91d3172 100644
--- a/gcc/gimple.h
+++ b/gcc/gimple.h
@@ -1407,6 +1407,14 @@ is_a_helper <const gphi *>::test (const gimple *gs)
 template <>
 template <>
 inline bool
+is_a_helper <const greturn *>::test (const gimple *gs)
+{
+  return gs->code == GIMPLE_RETURN;
+}
+
+template <>
+template <>
+inline bool
 is_a_helper <const gtransaction *>::test (const gimple *gs)
 {
   return gs->code == GIMPLE_TRANSACTION;
diff --git a/gcc/plugin.c b/gcc/plugin.c
index f6c61d2..fa7185b 100644
--- a/gcc/plugin.c
+++ b/gcc/plugin.c
@@ -496,6 +496,7 @@ register_callback (const char *plugin_name,
       case PLUGIN_EARLY_GIMPLE_PASSES_END:
       case PLUGIN_NEW_PASS:
       case PLUGIN_INCLUDE_FILE:
+      case PLUGIN_RUN_SELFTESTS:
         {
           struct callback_info *new_callback;
           if (!callback)
@@ -576,6 +577,7 @@ invoke_plugin_callbacks_full (int event, void *gcc_data)
       case PLUGIN_EARLY_GIMPLE_PASSES_END:
       case PLUGIN_NEW_PASS:
       case PLUGIN_INCLUDE_FILE:
+      case PLUGIN_RUN_SELFTESTS:
         {
           /* Iterate over every callback registered with this event and
              call it.  */
diff --git a/gcc/plugin.def b/gcc/plugin.def
index eb31f1a..e200728 100644
--- a/gcc/plugin.def
+++ b/gcc/plugin.def
@@ -99,6 +99,9 @@ DEFEVENT (PLUGIN_NEW_PASS)
    as a const char* pointer.  */
 DEFEVENT (PLUGIN_INCLUDE_FILE)
 
+/* Called when running selftests.  */
+DEFEVENT (PLUGIN_RUN_SELFTESTS)
+
 /* When adding a new hard-coded plugin event, don't forget to edit in
    file plugin.c the functions register_callback and
    invoke_plugin_callbacks_full accordingly!  */
diff --git a/gcc/selftest-run-tests.c b/gcc/selftest-run-tests.c
index c2e0ee2..184dfa6 100644
--- a/gcc/selftest-run-tests.c
+++ b/gcc/selftest-run-tests.c
@@ -27,6 +27,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "options.h"
 #include "stringpool.h"
 #include "attribs.h"
+#include "plugin.h"
 
 /* This function needed to be split out from selftest.c as it references
    tests from the whole source tree, and so is within
@@ -111,6 +112,9 @@ selftest::run_tests ()
   /* Run any lang-specific selftests.  */
   lang_hooks.run_lang_selftests ();
 
+  /* Run any plugin-provided selftests.  */
+  invoke_plugin_callbacks (PLUGIN_RUN_SELFTESTS, NULL);
+
   /* Force a GC at the end of the selftests, to shake out GC-related
      issues.  For example, if any GC-managed items have buggy (or missing)
      finalizers, this last collection will ensure that things that were
diff --git a/gcc/testsuite/gcc.dg/analyzer/abort.c b/gcc/testsuite/gcc.dg/analyzer/abort.c
new file mode 100644
index 0000000..ef8e501
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/abort.c
@@ -0,0 +1,29 @@
+#include <stdlib.h>
+
+extern void foo ();
+extern void bar ();
+
+void test_1 (int i)
+{
+  if (i == 42)
+    abort ();
+
+  __analyzer_eval (i != 42); /* { dg-warning "TRUE" } */
+}
+
+void test_2 (int i)
+{
+  if (i)
+    foo ();
+  else
+    bar ();
+
+  foo ();
+
+  if (i)
+    foo ();
+  else
+    abort ();
+
+  __analyzer_eval (i != 0); /* { dg-warning "TRUE" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/analyzer.exp b/gcc/testsuite/gcc.dg/analyzer/analyzer.exp
new file mode 100644
index 0000000..a11251f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/analyzer.exp
@@ -0,0 +1,36 @@
+#   Copyright (C) 2019 Free Software Foundation, Inc.
+
+# This program 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 of the License, or
+# (at your option) any later version.
+# 
+# This program 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/>.
+
+# GCC testsuite that uses the `dg.exp' driver.
+
+# Load support procs.
+load_lib gcc-dg.exp
+
+# If a testcase doesn't have special options, use these.
+global DEFAULT_CFLAGS
+if ![info exists DEFAULT_CFLAGS] then {
+    set DEFAULT_CFLAGS " --analyzer"
+}
+
+# Initialize `dg'.
+dg-init
+
+# Main loop.
+dg-runtest [lsort [glob -nocomplain $srcdir/$subdir/*.\[cS\]]] \
+	"" $DEFAULT_CFLAGS
+
+# All done.
+dg-finish
diff --git a/gcc/testsuite/gcc.dg/analyzer/conditionals.c b/gcc/testsuite/gcc.dg/analyzer/conditionals.c
new file mode 100644
index 0000000..ffaf9bc
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/conditionals.c
@@ -0,0 +1,51 @@
+void test (int i)
+{
+  if (i > 4)
+    {
+      __analyzer_eval (i > 4); /* { dg-warning "TRUE" } */
+      __analyzer_eval (i <= 4); /* { dg-warning "FALSE" } */
+      __analyzer_eval (i > 3); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+      /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+
+      __analyzer_eval (i > 5); /* { dg-warning "UNKNOWN" } */
+      __analyzer_eval (i != 3); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+      /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+
+      __analyzer_eval (i == 3); /* { dg-warning "FALSE" "" { xfail *-*-* } } */
+      /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+
+      __analyzer_eval (i != 4); /* { dg-warning "TRUE" } */
+      __analyzer_eval (i == 4); /* { dg-warning "FALSE" } */
+      __analyzer_eval (i == 5); /* { dg-warning "UNKNOWN" } */
+      __analyzer_eval (i != 5); /* { dg-warning "UNKNOWN" } */
+      __analyzer_eval (i < 5); /* { dg-warning "FALSE" } */
+      __analyzer_eval (i <= 5); /* { dg-warning "UNKNOWN" } */
+    }
+  else
+    {
+      __analyzer_eval (i > 4); /* { dg-warning "FALSE" } */
+      __analyzer_eval (i <= 4); /* { dg-warning "TRUE" } */
+      __analyzer_eval (i > 3); /* { dg-warning "FALSE" "" { xfail *-*-* } } */
+      /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+
+      __analyzer_eval (i > 5); /* { dg-warning "UNKNOWN" } */
+      __analyzer_eval (i != 3); /* { dg-warning "FALSE" "" { xfail *-*-* } } */
+      /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+
+      __analyzer_eval (i == 3); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+      /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+
+      __analyzer_eval (i != 4); /* { dg-warning "FALSE" "" { xfail *-*-* } } */
+      /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+
+      __analyzer_eval (i == 4); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+      /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+
+      __analyzer_eval (i == 5); /* { dg-warning "UNKNOWN" } */
+      __analyzer_eval (i != 5); /* { dg-warning "UNKNOWN" } */
+      __analyzer_eval (i < 5); /* { dg-warning "TRUE" } */
+      __analyzer_eval (i <= 5); /* { dg-warning "UNKNOWN" } */
+    }
+}
+
+/* TODO: The various xfails here involve implications of conditionals.  */
diff --git a/gcc/testsuite/gcc.dg/analyzer/equivalence.c b/gcc/testsuite/gcc.dg/analyzer/equivalence.c
new file mode 100644
index 0000000..fb28660
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/equivalence.c
@@ -0,0 +1,29 @@
+void test (int p, int q, int r)
+{
+  if (p == 42)
+    {
+      __analyzer_eval (p == 42);  /* { dg-warning "TRUE" } */
+      __analyzer_eval (p != 42);  /* { dg-warning "FALSE" } */
+      if (q == 42)
+	{
+	  __analyzer_eval (p == q);  /* { dg-warning "TRUE" } */
+	}
+      else
+	{
+	  __analyzer_eval (p != q);  /* { dg-warning "TRUE" } */
+	}
+    }
+  else
+    {
+      __analyzer_eval (p == 42);  /* { dg-warning "FALSE" } */
+      __analyzer_eval (p != 42);  /* { dg-warning "TRUE" } */
+      if (q == 42)
+	{
+	  __analyzer_eval (p == q);  /* { dg-warning "FALSE" } */
+	}
+      else
+	{
+	  __analyzer_eval (p == q);  /* { dg-warning "UNKNOWN" } */
+	}
+    }
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/file-1.c b/gcc/testsuite/gcc.dg/analyzer/file-1.c
new file mode 100644
index 0000000..fadd36d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/file-1.c
@@ -0,0 +1,36 @@
+#include <stdio.h>
+
+void
+test_1 (const char *path)
+{
+  FILE *f = fopen (path, "r");
+  if (!f)
+    return;
+
+  fclose (f);
+  fclose (f); /* { dg-error "double 'fclose' of FILE 'f'" } */ 
+}
+
+void
+test_2 (const char *src, const char *dst)
+{
+  FILE *f_in = fopen (src, "r");
+  if (!f_in)
+    return;
+
+  FILE *f_out = fopen (src, "w");
+  if (f_out)
+    return; /* { dg-error "leak of open FILE 'f_in'" "" { xfail *-*-* } } */ 
+  /* TODO(xfail): leak detection not yet implemented.  */
+
+  fclose (f_out);
+  fclose (f_in);
+}
+
+void
+test_3 (const char *path)
+{
+  FILE *f = fopen (path, "r");
+  return; /* { dg-error "leak of open FILE 'f'" "" { xfail *-*-* } } */ 
+  /* TODO(xfail): leak detection not yet implemented.  */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/infinite-recursion.c b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion.c
new file mode 100644
index 0000000..5b79fec
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion.c
@@ -0,0 +1,20 @@
+extern void marker_A(void);
+extern void marker_B(void);
+extern void marker_C(void);
+extern void marker_D(void);
+
+void test(int flag)
+{
+  marker_A();
+
+  if (flag) {
+    marker_B();
+
+    /* Recurse, infinitely, as it happens: */
+    test(flag);
+
+    marker_C();
+  }
+
+  marker_D();
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-1.c b/gcc/testsuite/gcc.dg/analyzer/malloc-1.c
new file mode 100644
index 0000000..80974c5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-1.c
@@ -0,0 +1,190 @@
+#include <stdlib.h>
+#include <string.h>
+
+extern int foo (void);
+extern int bar (void);
+
+void test_1 (void)
+{
+  void *ptr = malloc (1024);
+  free (ptr);
+  free (ptr); /* { dg-error "double-free of 'ptr'" } */
+}
+
+void test_2 (void *ptr)
+{
+  free (ptr);
+  free (ptr); /* { dg-error "double-free of 'ptr'" } */
+}
+
+int *test_3 (void)
+{
+  int *ptr = (int *)malloc (sizeof (int));
+  *ptr = 42; /* { dg-error "use of possibly-NULL 'ptr'" } */
+  return ptr;
+}
+
+int *test_4 (void)
+{
+  int *ptr = (int *)malloc (sizeof (int));
+  if (ptr)
+    *ptr = 42;
+  else
+    *ptr = 43; /* { dg-error "use of NULL 'ptr'" } */
+  return ptr;
+}
+
+int test_5 (int *ptr)
+{
+  free (ptr);
+  return *ptr; /* { dg-error "use after free of 'ptr'" } */
+}
+
+void test_6 (void *ptr)
+{
+  void *q;
+  q = ptr;
+  free (ptr);
+  free (q); /* { dg-error "double-free of 'q'" "" { xfail *-*-* } } */ 
+  // TODO: need to handle equivalence classes in state transitions
+}
+
+void test_7 (void)
+{
+  void *ptr = malloc(4096);
+  if (!ptr)
+    return;
+  memset(ptr, 0, 4096);
+  free(ptr);
+}
+
+void *test_8 (void)
+{
+  void *ptr = malloc(4096);
+  if (!ptr)
+    return NULL;
+  memset(ptr, 0, 4096);
+  return ptr;
+}
+
+void test_9 (void)
+{
+  void *ptr = malloc (1024);
+
+  int i;
+  for (i = 0; i < 1024; i++)
+    free (ptr); /* { dg-error "double-free of 'ptr'" } */
+}
+
+void test_10 (void)
+{
+  void *ptr = malloc (1024);
+
+  int i;
+  for (i = 0; i < 1024; i++)
+    foo ();
+
+  free (ptr);
+  free (ptr); /* { dg-error "double-free of 'ptr'" } */
+}
+
+void test_11 (void)
+{
+  void *ptr = malloc (1024);
+
+  while (foo ())
+    bar ();
+
+  free (ptr);
+  free (ptr); /* { dg-error "double-free of 'ptr'" } */
+}
+
+void test_12 (void)
+{
+  void *ptr = malloc (1024);
+
+  while (1)
+    {
+      free (ptr);
+      free (ptr); /* { dg-error "double-free of 'ptr'" } */
+    }
+}
+
+void test_13 (void)
+{
+  void *p = malloc (1024);
+  void *q = malloc (1024);
+
+  foo ();
+  if (!q)
+    {
+      free (q);
+      return; /* { dg-error "leak of 'p'" "" { xfail *-*-* } } */ 
+      /* TODO(xfail): implement leak detection.  */
+    }
+  bar ();
+  free (q);
+  free (p);
+}
+
+void test_14 (void)
+{
+  void *p, *q;
+  p = malloc (1024);
+  if (!p)
+    return;
+
+  q = malloc (1024);
+  if (!q)
+    {
+      free (p);
+      free (q);
+      /* oops: missing "return".  */
+    }
+  bar ();
+  free (q); /* { dg-error "double-free of 'q'" } */
+  free (p); /* { dg-error "double-free of 'p'" } */
+}
+
+void test_15 (void)
+{
+  void *p = NULL, *q = NULL;
+
+  p = malloc (1024);
+  if (!p)
+    goto fail;
+
+  foo ();
+
+  q = malloc (1024);
+  if (!q)
+    goto fail;
+
+  bar ();
+
+ fail:
+  free (q);
+  free (p);
+}
+
+void test_16 (void)
+{
+  void *p, *q;
+
+  p = malloc (1024);
+  if (!p)
+    goto fail;
+
+  foo ();
+
+  q = malloc (1024);
+  if (!q)
+    goto fail;
+
+  bar ();
+
+ fail:
+  free (q); /* { dg-error "free of uninitialized 'q'" "" { xfail *-*-* } } */ 
+  /* TODO(xfail): implement uninitialized detection.  */
+  free (p);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-dce.c b/gcc/testsuite/gcc.dg/analyzer/malloc-dce.c
new file mode 100644
index 0000000..ed871c3
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-dce.c
@@ -0,0 +1,28 @@
+/* { dg-options "--analyzer -O2" } */
+
+#include <stdlib.h>
+
+void test(void)
+{
+    void *ptr = malloc(512);
+    free(ptr);
+    free(ptr); /* { dg-error "double-free" "" { xfail *-*-* } } */
+}
+/* FIXME: is this old analysis still correct?
+
+   With optimization, the whole of test() goes away, and hence we get no error
+   message
+
+   Specifically, what's happening is dead-code elimination (the "cddce" pass
+   in tree-ssa-dce.c)
+   Invoking gcc with -fdump-tree-all-details shows this within input.c.031t.cddce1:
+   Eliminating unnecessary statements:
+   Deleting : free (ptr_1);
+
+   Deleting : free (ptr_1);
+
+   Deleting : ptr_1 = malloc (512);
+
+   and the whole of the function has been optimized away before the analysis
+   pass sees it
+*/
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-paths-1.c b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-1.c
new file mode 100644
index 0000000..8c746bf
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-1.c
@@ -0,0 +1,15 @@
+/* Verify that we emit sane paths for state machine errors.  */
+
+#include <stdlib.h>
+
+void test_1 (void)
+{
+  void *ptr = malloc (1024); /* { dg-line malloc } */
+  free (ptr); /* { dg-line first_free } */
+  free (ptr); /* { dg-line second_free } */
+
+  /* { dg-error "double-free of 'ptr'"  "" { target *-*-* } second_free } */
+  /* { dg-message "\\(1\\) state of 'ptr': 'start' -> 'unchecked'" "" { target *-*-* } malloc } */
+  /* { dg-message "\\(2\\) state of 'ptr': 'unchecked' -> 'free'" "" { target *-*-* } first_free } */
+  /* { dg-message "\\(3\\) here \\('ptr' is in state 'free'\\)" "" { target *-*-* } second_free } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-paths-2.c b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-2.c
new file mode 100644
index 0000000..71bada6
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-2.c
@@ -0,0 +1,13 @@
+/* Verify that we emit sane paths for state machine errors.  */
+
+#include <stdlib.h>
+
+void test_2 (void *ptr)
+{
+  free (ptr); /* { dg-line first_free } */
+  free (ptr); /* { dg-line second_free } */
+
+  /* { dg-error "double-free of 'ptr'"  "" { target *-*-* } second_free } */
+  /* { dg-message "\\(1\\) state of 'ptr': 'start' -> 'free'" "" { target *-*-* } first_free } */
+  /* { dg-message "\\(2\\) here \\('ptr' is in state 'free'\\)" "" { target *-*-* } second_free } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-paths-3.c b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-3.c
new file mode 100644
index 0000000..03e6cb7
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-3.c
@@ -0,0 +1,14 @@
+/* Verify that we emit sane paths for state machine errors.  */
+
+#include <stdlib.h>
+
+int *test_3 (void)
+{
+  int *ptr = (int *)malloc (sizeof (int)); /* { dg-line malloc } */
+  *ptr = 42; /* { dg-line unchecked_deref } */
+  return ptr;
+
+  /* { dg-error "use of possibly-NULL 'ptr'" "" { target *-*-* } unchecked_deref } */
+  /* { dg-message "\\(1\\) state of 'ptr': 'start' -> 'unchecked'" "" { target *-*-* } malloc } */
+  /* { dg-message "\\(2\\) here \\('ptr' is in state 'unchecked'\\)" "" { target *-*-* } unchecked_deref } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-paths-4.c b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-4.c
new file mode 100644
index 0000000..6e83d8a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-4.c
@@ -0,0 +1,18 @@
+/* Verify that we emit sane paths for state machine errors.  */
+
+#include <stdlib.h>
+
+int *test_4 (void)
+{
+  int *ptr = (int *)malloc (sizeof (int)); /* { dg-line malloc } */
+  if (ptr) /* { dg-line cond } */
+    *ptr = 42;
+  else
+    *ptr = 43; /* { dg-line on_null_ptr } */
+  return ptr;
+
+  /* { dg-error "use of NULL 'ptr'" "" { target *-*-* } on_null_ptr } */
+  /* { dg-message "\\(1\\) state of 'ptr': 'start' -> 'unchecked'" "" { target *-*-* } malloc } */
+  /* { dg-message "\\(2\\) state of 'ptr': 'unchecked' -> 'null'" "" { target *-*-* } cond } */
+  /* { dg-message "\\(3\\) here \\('ptr' is in state 'null'\\)" "" { target *-*-* } on_null_ptr } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/switch.c b/gcc/testsuite/gcc.dg/analyzer/switch.c
new file mode 100644
index 0000000..06d1398
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/switch.c
@@ -0,0 +1,30 @@
+void test (int i)
+{
+  switch (i)
+    {
+    case 0:
+      __analyzer_eval (i == 0); /* { dg-warning "TRUE" } */
+      break;
+
+    case 3 ... 5:
+      __analyzer_eval (i >= 3); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+      /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+      /* TODO(xfail^^^): The above comes in as "i_6(D) > 2" and hence
+	 doesn't match the constraint i_6 >= 3 we already have
+	 (actually 3 <= i_6).  */
+      __analyzer_eval (i <= 5); /* { dg-warning "TRUE" } */
+      break;
+
+    default:
+      __analyzer_eval (i == 0); /* { dg-warning "FALSE" } */
+      __analyzer_eval (i == 2); /* { dg-warning "UNKNOWN" } */
+      __analyzer_eval (i == 3); /* { dg-warning "FALSE" } */
+      __analyzer_eval (i == 4); /* { dg-warning "FALSE" "" { xfail *-*-* } } */
+      /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+      /* TODO(xfail^^^): we're only checking against endpoints of case
+	 ranges, not the insides.  */
+      __analyzer_eval (i == 5); /* { dg-warning "FALSE" } */
+      __analyzer_eval (i == 6); /* { dg-warning "UNKNOWN" } */
+      break;
+    }
+}
-- 
1.8.5.3

