From d287d67f28b50864b27c5c4ce4d2e40b53a475d4 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Thu, 28 Feb 2019 14:07:05 -0500
Subject: [PATCH 16/46] FIXME: impl-2, impl-3, impl-4, and some diagnostic_path
 stuff

---
 analyze-to-html.sh                                 |   13 +
 ansi2html.sh                                       |  520 +++
 checkers/address_of_function.sm                    |   14 +
 checkers/buffer_overflow.sm                        |   75 +
 checkers/fd_checker.sm                             |   26 +
 checkers/fileptr.sm                                |   17 +
 checkers/malloc_checker.sm                         |   74 +
 checkers/points_to_stack.sm                        |   60 +
 checkers/python.sm                                 |   61 +
 checkers/signal_handler.sm                         |   58 +
 checkers/sizeof_allocation.sm                      |   91 +
 checkers/taint.sm                                  |   71 +
 .../diagnostic-json-to-html-templates/index.html   |    5 +
 demo-2.html                                        |  216 +
 demo-control.c                                     |   44 +
 demo-direct-with-loop.c                            |   22 +
 demo-direct.c                                      |   31 +
 demo-indirect.c                                    |   21 +
 demo.c                                             |   38 +
 demo.html                                          |  277 ++
 foo.sh                                             |   13 +
 gcc/analyzer/Make-lang.in                          |  168 +
 gcc/analyzer/analyzer-logging.cc                   |  221 ++
 gcc/analyzer/analyzer-logging.h                    |  229 ++
 gcc/analyzer/analyzer-pass.cc                      |  139 +
 gcc/analyzer/analyzer-plugin.cc                    |   67 +
 gcc/analyzer/analyzer.cc                           |  129 +
 gcc/analyzer/analyzer.h                            |   62 +
 gcc/analyzer/checker-script.cc                     | 1677 ++++++++
 gcc/analyzer/config-lang.in                        |   34 +
 gcc/analyzer/constraints.cc                        | 1987 ++++++++++
 gcc/analyzer/constraints.h                         |  279 ++
 gcc/analyzer/dead-code/analyzer-config.cc          |   54 +
 gcc/analyzer/dead-code/constraints.cc              |  780 ++++
 gcc/analyzer/dead-code/core-checker.cc             |  904 +++++
 gcc/analyzer/dead-code/libc-file-checker.cc        |  226 ++
 gcc/analyzer/dead-code/libc-malloc-checker.cc      |  220 +
 gcc/analyzer/dead-code/toy-sm-impl-1.cc            | 1052 +++++
 gcc/analyzer/dead-code/toy-sm-impl-1.h             |    1 +
 gcc/analyzer/digraph.cc                            |  132 +
 gcc/analyzer/digraph.h                             |   97 +
 gcc/analyzer/engine.cc                             | 3910 ++++++++++++++++++
 gcc/analyzer/engine.h                              |    3 +
 gcc/analyzer/graphviz.cc                           |   62 +
 gcc/analyzer/graphviz.h                            |   19 +
 gcc/analyzer/hardcoded-state-machine-file.cc       |  192 +
 gcc/analyzer/hardcoded-state-machine-malloc.cc     |  496 +++
 .../hardcoded-state-machine-pattern-test.cc        |  131 +
 gcc/analyzer/hardcoded-state-machine-sensitive.cc  |  135 +
 gcc/analyzer/hardcoded-state-machine-taint.cc      |  216 +
 gcc/analyzer/hardcoded-state-machine.cc            |  163 +
 gcc/analyzer/hardcoded-state-machine.h             |  381 ++
 gcc/analyzer/impl-3.cc                             | 1648 ++++++++
 gcc/analyzer/impl-4.cc                             | 4194 ++++++++++++++++++++
 gcc/analyzer/supergraph.cc                         |  886 +++++
 gcc/analyzer/supergraph.h                          |  417 ++
 gcc/analyzer/tristate.cc                           |   67 +
 gcc/analyzer/tristate.h                            |   74 +
 gcc/builtins.def                                   |    8 +
 gcc/c-family/c-format.c                            |    2 +-
 gcc/c-family/c-opts.c                              |    4 +-
 gcc/c-family/c-pretty-print.c                      |    7 +
 gcc/c-family/c-pretty-print.h                      |    1 +
 gcc/c/c-objc-common.c                              |    4 +-
 gcc/common.opt                                     |   52 +
 gcc/cp/cxx-pretty-print.c                          |    8 +
 gcc/cp/cxx-pretty-print.h                          |    2 +
 gcc/cp/error.c                                     |    9 +-
 gcc/diagnostic-core.h                              |   12 +-
 gcc/diagnostic-format-json.cc                      |   24 +
 gcc/diagnostic-metadata.h                          |   44 +
 gcc/diagnostic-path.h                              |   90 +-
 gcc/diagnostic-show-locus.c                        |  100 +-
 gcc/diagnostic.c                                   |  207 +-
 gcc/diagnostic.h                                   |   28 +
 gcc/dumpfile.c                                     |    2 +-
 gcc/fortran/error.c                                |    1 +
 gcc/gcc-rich-location.c                            |    2 +-
 gcc/gcc-rich-location.h                            |    2 +-
 gcc/gcc.c                                          |   10 +
 gcc/gimple.h                                       |    8 +
 gcc/opts.c                                         |   12 +
 gcc/plugin.c                                       |    2 +
 gcc/plugin.def                                     |    3 +
 gcc/pretty-print.c                                 |   37 +-
 gcc/pretty-print.h                                 |    4 +
 gcc/selftest-run-tests.c                           |    5 +
 gcc/selftest.h                                     |    1 +
 gcc/testsuite/DEADCODE-analyzer.dg/analyzer.exp    |   49 +
 .../DEADCODE-analyzer.dg/constraints/path-count.c  |   16 +
 gcc/testsuite/DEADCODE-analyzer.dg/demo-2.c        |  100 +
 gcc/testsuite/DEADCODE-analyzer.dg/demo-3.c        |   31 +
 gcc/testsuite/DEADCODE-analyzer.dg/demo-4.c        |   61 +
 gcc/testsuite/DEADCODE-analyzer.dg/demo.c          |   30 +
 gcc/testsuite/DEADCODE-analyzer.dg/dg.exp          |   53 +
 .../DEADCODE-analyzer.dg/ipa-double-free/api.c     |   15 +
 .../DEADCODE-analyzer.dg/ipa-double-free/api.h     |    9 +
 .../DEADCODE-analyzer.dg/ipa-double-free/client.c  |    8 +
 .../DEADCODE-analyzer.dg/malloc/bad-size.c         |   20 +
 gcc/testsuite/DEADCODE-analyzer.dg/malloc/leak.c   |    6 +
 .../DEADCODE-analyzer.dg/malloc/unchecked.c        |   14 +
 .../DEADCODE-analyzer.dg/memory/example.c          |   16 +
 .../DEADCODE-analyzer.dg/memory/stack-address.c    |    8 +
 .../assignments/dereference-on-lhs/input.c         |   27 +
 .../assignments/dereference-on-lhs/script.py       |   37 +
 .../assignments/dereference-on-lhs/stderr.txt      |    4 +
 .../assignments/dereference-on-lhs/stdout.txt      |    0
 .../assignments/dereference-on-rhs/input.c         |   28 +
 .../assignments/dereference-on-rhs/script.py       |   23 +
 .../assignments/dereference-on-rhs/stderr.txt      |    4 +
 .../assignments/dereference-on-rhs/stdout.txt      |    0
 .../gcc.dg/TODO-analyzer/assignments/phi/input.c   |   38 +
 .../gcc.dg/TODO-analyzer/assignments/phi/script.py |   34 +
 .../TODO-analyzer/assignments/phi/stderr.txt       |    7 +
 .../TODO-analyzer/assignments/phi/stdout.txt       |    0
 .../malloc-checker/arg_must_not_be_null/input.c    |   30 +
 .../malloc-checker/arg_must_not_be_null/script.py  |   23 +
 .../malloc-checker/arg_must_not_be_null/stderr.txt |    4 +
 .../malloc-checker/arg_must_not_be_null/stdout.txt |    0
 .../checkers/malloc-checker/correct_usage/input.c  |   31 +
 .../correct_usage/input.c.cpychecker-log.txt       |    1 +
 .../malloc-checker/correct_usage/script.py         |   23 +
 .../malloc-checker/correct_usage/stdout.txt        |    0
 .../checkers/malloc-checker/double_free/input.c    |   29 +
 .../double_free/input.c.cpychecker-log.txt         |    1 +
 .../checkers/malloc-checker/double_free/script.py  |   23 +
 .../checkers/malloc-checker/double_free/stderr.txt |    7 +
 .../checkers/malloc-checker/double_free/stdout.txt |    0
 .../malloc-checker/fancy_control_flow/input.c      |   42 +
 .../fancy_control_flow/input.c.cpychecker-log.txt  |    1 +
 .../malloc-checker/fancy_control_flow/script.py    |   23 +
 .../malloc-checker/fancy_control_flow/stderr.txt   |    4 +
 .../malloc-checker/fancy_control_flow/stdout.txt   |    0
 .../input.c.cpychecker-log.txt                     |    1 +
 .../leaks/not-a-leak/input.c.cpychecker-log.txt    |    1 +
 .../leaks/simple-leak/input.c.cpychecker-log.txt   |    1 +
 .../unchecked-return/input.c.cpychecker-log.txt    |    1 +
 .../multiple-errors/unchecked_malloc/input.c       |   41 +
 .../multiple-errors/unchecked_malloc/script.py     |   46 +
 .../multiple-errors/unchecked_malloc/stderr.txt    |    4 +
 .../multiple-errors/unchecked_malloc/stdout.txt    |    0
 .../checkers/malloc-checker/two_ptrs/input.c       |   35 +
 .../two_ptrs/input.c.cpychecker-log.txt            |    1 +
 .../checkers/malloc-checker/two_ptrs/script.py     |   23 +
 .../checkers/malloc-checker/two_ptrs/stderr.txt    |    4 +
 .../checkers/malloc-checker/two_ptrs/stdout.txt    |    0
 .../malloc-checker/unchecked_malloc/input.c        |   28 +
 .../unchecked_malloc/input.c.cpychecker-log.txt    |    1 +
 .../malloc-checker/unchecked_malloc/script.py      |   23 +
 .../malloc-checker/unchecked_malloc/stderr.txt     |    4 +
 .../malloc-checker/unchecked_malloc/stdout.txt     |    0
 .../checkers/malloc-checker/use_after_free/input.c |   34 +
 .../use_after_free/input.c.cpychecker-log.txt      |    1 +
 .../malloc-checker/use_after_free/script.py        |   23 +
 .../malloc-checker/use_after_free/stderr.txt       |    4 +
 .../malloc-checker/use_after_free/stdout.txt       |    0
 .../checkers/points_to_stack/putenv/input.c        |   31 +
 .../checkers/points_to_stack/putenv/script.py      |   31 +
 .../checkers/points_to_stack/putenv/stderr.txt     |   22 +
 .../checkers/points_to_stack/putenv/stdout.txt     |    0
 .../points_to_stack/return_of_ptr_to_local/input.c |   69 +
 .../return_of_ptr_to_local/script.py               |   30 +
 .../return_of_ptr_to_local/stderr.txt              |    5 +
 .../return_of_ptr_to_local/stdout.txt              |    0
 .../checkers/signal_handler/example/input.c        |   52 +
 .../checkers/signal_handler/example/script.py      |   30 +
 .../checkers/signal_handler/example/stderr.txt     |    5 +
 .../checkers/signal_handler/example/stdout.txt     |    0
 .../sizeof_allocation/calloc_too_small/input.c     |   34 +
 .../sizeof_allocation/calloc_too_small/script.py   |   23 +
 .../sizeof_allocation/calloc_too_small/stderr.txt  |    2 +
 .../sizeof_allocation/calloc_too_small/stdout.txt  |    0
 .../sizeof_allocation/malloc_correct_size/input.c  |   31 +
 .../malloc_correct_size/script.py                  |   23 +
 .../malloc_correct_size/stdout.txt                 |    0
 .../sizeof_allocation/malloc_too_large/input.c     |   36 +
 .../sizeof_allocation/malloc_too_large/script.py   |   23 +
 .../sizeof_allocation/malloc_too_large/stdout.txt  |    0
 .../sizeof_allocation/malloc_too_small/input.c     |   34 +
 .../sizeof_allocation/malloc_too_small/script.py   |   23 +
 .../sizeof_allocation/malloc_too_small/stderr.txt  |    2 +
 .../sizeof_allocation/malloc_too_small/stdout.txt  |    0
 .../stateful_malloc_too_small/input.c              |   36 +
 .../stateful_malloc_too_small/script.py            |   40 +
 .../stateful_malloc_too_small/stderr.txt           |    4 +
 .../stateful_malloc_too_small/stdout.txt           |    0
 .../sizeof_allocation/unknown_size/input.c         |   32 +
 .../sizeof_allocation/unknown_size/script.py       |   38 +
 .../sizeof_allocation/unknown_size/stdout.txt      |    0
 .../TODO-analyzer/checkers/taint/example/script.py |   71 +
 .../checkers/taint/example/stderr.txt              |   22 +
 gcc/testsuite/gcc.dg/TODO-analyzer/dot/input.c     |    1 +
 .../TODO-analyzer/dot/input.c.cpychecker-log.txt   |    1 +
 gcc/testsuite/gcc.dg/TODO-analyzer/dot/script.py   |   55 +
 gcc/testsuite/gcc.dg/TODO-analyzer/dot/stdout.txt  |   83 +
 .../gcc.dg/TODO-analyzer/facts/meet/input.c        |   41 +
 .../gcc.dg/TODO-analyzer/facts/meet/script.py      |   46 +
 .../gcc.dg/TODO-analyzer/facts/meet/stdout.txt     |    0
 .../TODO-analyzer/interprocedural/complex/input.c  |   56 +
 .../interprocedural/complex/script.py              |   23 +
 .../interprocedural/complex/stderr.txt             |   13 +
 .../interprocedural/complex/stdout.txt             |    0
 .../param-inherits-from-arg/input.c                |   34 +
 .../param-inherits-from-arg/script.py              |   33 +
 .../param-inherits-from-arg/stderr.txt             |    5 +
 .../param-inherits-from-arg/stdout.txt             |    0
 gcc/testsuite/gcc.dg/TODO-analyzer/looping/input.c |   36 +
 .../gcc.dg/TODO-analyzer/looping/script.py         |   23 +
 .../gcc.dg/TODO-analyzer/looping/stderr.txt        |   13 +
 .../gcc.dg/TODO-analyzer/looping/stdout.txt        |    0
 gcc/testsuite/gcc.dg/TODO-analyzer/lto/input-f.c   |   34 +
 gcc/testsuite/gcc.dg/TODO-analyzer/lto/input-g.c   |   31 +
 gcc/testsuite/gcc.dg/TODO-analyzer/lto/input-h.c   |   31 +
 gcc/testsuite/gcc.dg/TODO-analyzer/lto/script.py   |   25 +
 gcc/testsuite/gcc.dg/TODO-analyzer/lto/stderr.txt  |   29 +
 gcc/testsuite/gcc.dg/TODO-analyzer/lto/stdout.txt  |    0
 gcc/testsuite/gcc.dg/TODO-analyzer/lto/test.h      |   22 +
 .../TODO-analyzer/parser/malloc-checker/input.c    |   91 +
 .../malloc-checker/input.c.cpychecker-log.txt      |    1 +
 .../parser/malloc-checker/metadata.ini             |    4 +
 .../TODO-analyzer/parser/malloc-checker/script.py  |  189 +
 .../TODO-analyzer/patterns/arg-of-fn-call/input.c  |   25 +
 .../arg-of-fn-call/input.c.cpychecker-log.txt      |    1 +
 .../patterns/arg-of-fn-call/script.py              |   32 +
 .../patterns/arg-of-fn-call/stderr.txt             |    2 +
 .../patterns/arg-of-fn-call/stdout.txt             |    0
 .../patterns/arg_must_not_be_null/input.c          |   44 +
 .../patterns/arg_must_not_be_null/script.py        |   39 +
 .../patterns/arg_must_not_be_null/stderr.txt       |   21 +
 .../patterns/arg_must_not_be_null/stdout.txt       |    0
 .../TODO-analyzer/patterns/args-of-fn-call/input.c |   25 +
 .../patterns/args-of-fn-call/script.py             |   34 +
 .../patterns/args-of-fn-call/stderr.txt            |    2 +
 .../patterns/args-of-fn-call/stdout.txt            |    0
 .../gcc.dg/TODO-analyzer/patterns/array/input.c    |   25 +
 .../gcc.dg/TODO-analyzer/patterns/array/script.py  |   33 +
 .../gcc.dg/TODO-analyzer/patterns/array/stderr.txt |    2 +
 .../gcc.dg/TODO-analyzer/patterns/array/stdout.txt |    0
 .../TODO-analyzer/patterns/named-pattern/input.c   |   30 +
 .../TODO-analyzer/patterns/named-pattern/script.py |   34 +
 .../patterns/named-pattern/stderr.txt              |    2 +
 .../patterns/named-pattern/stdout.txt              |    0
 .../gcc.dg/TODO-analyzer/patterns/or/input.c       |   39 +
 .../gcc.dg/TODO-analyzer/patterns/or/script.py     |   32 +
 .../gcc.dg/TODO-analyzer/patterns/or/stderr.txt    |    4 +
 .../gcc.dg/TODO-analyzer/patterns/or/stdout.txt    |    0
 .../patterns/result-of-fn-call/input.c             |   30 +
 .../result-of-fn-call/input.c.cpychecker-log.txt   |    1 +
 .../patterns/result-of-fn-call/script.py           |   32 +
 .../patterns/result-of-fn-call/stderr.txt          |    2 +
 .../patterns/result-of-fn-call/stdout.txt          |    0
 .../TODO-analyzer/patterns/var-dereference/input.c |   32 +
 .../var-dereference/input.c.cpychecker-log.txt     |    1 +
 .../patterns/var-dereference/script.py             |   32 +
 .../patterns/var-dereference/stderr.txt            |    4 +
 .../patterns/var-dereference/stdout.txt            |    0
 .../TODO-analyzer/patterns/var-usage/input.c       |   23 +
 .../patterns/var-usage/input.c.cpychecker-log.txt  |    1 +
 .../TODO-analyzer/patterns/var-usage/script.py     |   34 +
 .../TODO-analyzer/patterns/var-usage/stderr.txt    |    3 +
 .../TODO-analyzer/patterns/var-usage/stdout.txt    |    0
 .../configure-with-exceptions/getopts.py           |    1 +
 .../regression/configure-with-exceptions/input.c   |   14 +
 .../regression/configure-with-exceptions/script.py |   23 +
 .../configure-with-exceptions/stdout.txt           |    0
 .../configure-without-exceptions/getopts.py        |    1 +
 .../configure-without-exceptions/input.c           |   14 +
 .../configure-without-exceptions/script.py         |   23 +
 .../configure-without-exceptions/stdout.txt        |    0
 .../TODO-analyzer/regression/return-0/getopts.py   |   23 +
 .../TODO-analyzer/regression/return-0/input.c      |    4 +
 .../TODO-analyzer/regression/return-0/script.py    |   23 +
 .../TODO-analyzer/regression/return-0/stdout.txt   |    0
 .../regression/rhs-is-vardecl/input.c              |    8 +
 .../regression/rhs-is-vardecl/stdout.txt           |    0
 .../gcc.dg/analyzer/CVE-2005-1689-minimal.c        |   30 +
 gcc/testsuite/gcc.dg/analyzer/abort.c              |   71 +
 gcc/testsuite/gcc.dg/analyzer/analyzer.exp         |   36 +
 gcc/testsuite/gcc.dg/analyzer/conditionals.c       |  124 +
 gcc/testsuite/gcc.dg/analyzer/equivalence.c        |   29 +
 gcc/testsuite/gcc.dg/analyzer/factorial.c          |    7 +
 gcc/testsuite/gcc.dg/analyzer/fibonacci.c          |    7 +
 gcc/testsuite/gcc.dg/analyzer/file-1.c             |   34 +
 gcc/testsuite/gcc.dg/analyzer/function-ptr-1.c     |    8 +
 gcc/testsuite/gcc.dg/analyzer/function-ptr-2.c     |   17 +
 gcc/testsuite/gcc.dg/analyzer/function-ptr-3.c     |   17 +
 gcc/testsuite/gcc.dg/analyzer/gzio-2.c             |   11 +
 gcc/testsuite/gcc.dg/analyzer/gzio.c               |   15 +
 gcc/testsuite/gcc.dg/analyzer/infinite-recursion.c |   55 +
 gcc/testsuite/gcc.dg/analyzer/loop.c               |   20 +
 gcc/testsuite/gcc.dg/analyzer/malloc-1.c           |  364 ++
 gcc/testsuite/gcc.dg/analyzer/malloc-dce.c         |   28 +
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-1.c       |   24 +
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-10.c      |   32 +
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-11.c      |   29 +
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-12.c      |    8 +
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-2.c       |   34 +
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-3.c       |   23 +
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-4.c       |   13 +
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-5.c       |   13 +
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-6.c       |   23 +
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-7.c       |   29 +
 .../gcc.dg/analyzer/malloc-ipa-8-double-free.c     |  132 +
 .../gcc.dg/analyzer/malloc-ipa-8-unchecked.c       |   68 +
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-9.c       |   18 +
 gcc/testsuite/gcc.dg/analyzer/malloc-many-paths.c  |   83 +
 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/malloc-paths-5.c     |   40 +
 gcc/testsuite/gcc.dg/analyzer/malloc-paths-6.c     |   11 +
 gcc/testsuite/gcc.dg/analyzer/malloc-paths-7.c     |   21 +
 gcc/testsuite/gcc.dg/analyzer/malloc-paths-8.c     |   52 +
 gcc/testsuite/gcc.dg/analyzer/operations.c         |   42 +
 gcc/testsuite/gcc.dg/analyzer/params-2.c           |   16 +
 gcc/testsuite/gcc.dg/analyzer/params.c             |   32 +
 gcc/testsuite/gcc.dg/analyzer/pattern-test-1.c     |   14 +
 gcc/testsuite/gcc.dg/analyzer/sensitive-1.c        |   33 +
 gcc/testsuite/gcc.dg/analyzer/switch.c             |   26 +
 gcc/testsuite/gcc.dg/analyzer/taint-1.c            |  119 +
 .../diagnostic-path-summary-format-default.c       |  140 +
 ...iagnostic-path-summary-format-event-summaries.c |   74 +
 .../diagnostic-path-summary-format-inline-events.c |  140 +
 .../plugin/diagnostic-path-summary-format-none.c   |   43 +
 .../plugin/diagnostic-path-summary-format-simple.c |   66 +
 .../gcc.dg/plugin/diagnostic_plugin_test_paths.c   |  253 +-
 gcc/testsuite/gcc.dg/plugin/plugin.exp             |    7 +-
 gcc/toplev.c                                       |    6 +
 gcc/tree-diagnostic.c                              |  917 ++++-
 gcc/vec.h                                          |   24 +
 libcpp/include/line-map.h                          |   31 +-
 make-html.sh                                       |   32 +
 notes.txt                                          |    8 +
 334 files changed, 30873 insertions(+), 206 deletions(-)
 create mode 100755 analyze-to-html.sh
 create mode 100755 ansi2html.sh
 create mode 100644 checkers/address_of_function.sm
 create mode 100644 checkers/buffer_overflow.sm
 create mode 100644 checkers/fd_checker.sm
 create mode 100644 checkers/fileptr.sm
 create mode 100644 checkers/malloc_checker.sm
 create mode 100644 checkers/points_to_stack.sm
 create mode 100644 checkers/python.sm
 create mode 100644 checkers/signal_handler.sm
 create mode 100644 checkers/sizeof_allocation.sm
 create mode 100644 checkers/taint.sm
 create mode 100644 demo-2.html
 create mode 100644 demo-control.c
 create mode 100644 demo-direct-with-loop.c
 create mode 100644 demo-direct.c
 create mode 100644 demo-indirect.c
 create mode 100644 demo.c
 create mode 100644 demo.html
 create mode 100755 foo.sh
 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/dead-code/analyzer-config.cc
 create mode 100644 gcc/analyzer/dead-code/constraints.cc
 create mode 100644 gcc/analyzer/dead-code/core-checker.cc
 create mode 100644 gcc/analyzer/dead-code/libc-file-checker.cc
 create mode 100644 gcc/analyzer/dead-code/libc-malloc-checker.cc
 create mode 100644 gcc/analyzer/dead-code/toy-sm-impl-1.cc
 create mode 100644 gcc/analyzer/dead-code/toy-sm-impl-1.h
 create mode 100644 gcc/analyzer/digraph.cc
 create mode 100644 gcc/analyzer/digraph.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-file.cc
 create mode 100644 gcc/analyzer/hardcoded-state-machine-malloc.cc
 create mode 100644 gcc/analyzer/hardcoded-state-machine-pattern-test.cc
 create mode 100644 gcc/analyzer/hardcoded-state-machine-sensitive.cc
 create mode 100644 gcc/analyzer/hardcoded-state-machine-taint.cc
 create mode 100644 gcc/analyzer/hardcoded-state-machine.cc
 create mode 100644 gcc/analyzer/hardcoded-state-machine.h
 create mode 100644 gcc/analyzer/impl-3.cc
 create mode 100644 gcc/analyzer/impl-4.cc
 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/diagnostic-metadata.h
 create mode 100644 gcc/testsuite/DEADCODE-analyzer.dg/analyzer.exp
 create mode 100644 gcc/testsuite/DEADCODE-analyzer.dg/constraints/path-count.c
 create mode 100644 gcc/testsuite/DEADCODE-analyzer.dg/demo-2.c
 create mode 100644 gcc/testsuite/DEADCODE-analyzer.dg/demo-3.c
 create mode 100644 gcc/testsuite/DEADCODE-analyzer.dg/demo-4.c
 create mode 100644 gcc/testsuite/DEADCODE-analyzer.dg/demo.c
 create mode 100644 gcc/testsuite/DEADCODE-analyzer.dg/dg.exp
 create mode 100644 gcc/testsuite/DEADCODE-analyzer.dg/ipa-double-free/api.c
 create mode 100644 gcc/testsuite/DEADCODE-analyzer.dg/ipa-double-free/api.h
 create mode 100644 gcc/testsuite/DEADCODE-analyzer.dg/ipa-double-free/client.c
 create mode 100644 gcc/testsuite/DEADCODE-analyzer.dg/malloc/bad-size.c
 create mode 100644 gcc/testsuite/DEADCODE-analyzer.dg/malloc/leak.c
 create mode 100644 gcc/testsuite/DEADCODE-analyzer.dg/malloc/unchecked.c
 create mode 100644 gcc/testsuite/DEADCODE-analyzer.dg/memory/example.c
 create mode 100644 gcc/testsuite/DEADCODE-analyzer.dg/memory/stack-address.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/assignments/dereference-on-lhs/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/assignments/dereference-on-lhs/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/assignments/dereference-on-lhs/stderr.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/assignments/dereference-on-lhs/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/assignments/dereference-on-rhs/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/assignments/dereference-on-rhs/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/assignments/dereference-on-rhs/stderr.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/assignments/dereference-on-rhs/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/assignments/phi/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/assignments/phi/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/assignments/phi/stderr.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/assignments/phi/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/arg_must_not_be_null/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/arg_must_not_be_null/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/arg_must_not_be_null/stderr.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/arg_must_not_be_null/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/correct_usage/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/correct_usage/input.c.cpychecker-log.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/correct_usage/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/correct_usage/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/double_free/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/double_free/input.c.cpychecker-log.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/double_free/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/double_free/stderr.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/double_free/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/fancy_control_flow/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/fancy_control_flow/input.c.cpychecker-log.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/fancy_control_flow/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/fancy_control_flow/stderr.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/fancy_control_flow/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/leaks/leak-in-error-handling/input.c.cpychecker-log.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/leaks/not-a-leak/input.c.cpychecker-log.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/leaks/simple-leak/input.c.cpychecker-log.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/leaks/unchecked-return/input.c.cpychecker-log.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/multiple-errors/unchecked_malloc/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/multiple-errors/unchecked_malloc/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/multiple-errors/unchecked_malloc/stderr.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/multiple-errors/unchecked_malloc/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/two_ptrs/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/two_ptrs/input.c.cpychecker-log.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/two_ptrs/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/two_ptrs/stderr.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/two_ptrs/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/unchecked_malloc/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/unchecked_malloc/input.c.cpychecker-log.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/unchecked_malloc/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/unchecked_malloc/stderr.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/unchecked_malloc/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/use_after_free/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/use_after_free/input.c.cpychecker-log.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/use_after_free/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/use_after_free/stderr.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/use_after_free/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/points_to_stack/putenv/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/points_to_stack/putenv/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/points_to_stack/putenv/stderr.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/points_to_stack/putenv/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/points_to_stack/return_of_ptr_to_local/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/points_to_stack/return_of_ptr_to_local/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/points_to_stack/return_of_ptr_to_local/stderr.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/points_to_stack/return_of_ptr_to_local/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/signal_handler/example/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/signal_handler/example/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/signal_handler/example/stderr.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/signal_handler/example/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/calloc_too_small/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/calloc_too_small/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/calloc_too_small/stderr.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/calloc_too_small/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/malloc_correct_size/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/malloc_correct_size/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/malloc_correct_size/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/malloc_too_large/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/malloc_too_large/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/malloc_too_large/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/malloc_too_small/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/malloc_too_small/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/malloc_too_small/stderr.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/malloc_too_small/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/stateful_malloc_too_small/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/stateful_malloc_too_small/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/stateful_malloc_too_small/stderr.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/stateful_malloc_too_small/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/unknown_size/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/unknown_size/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/unknown_size/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/taint/example/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/checkers/taint/example/stderr.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/dot/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/dot/input.c.cpychecker-log.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/dot/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/dot/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/facts/meet/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/facts/meet/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/facts/meet/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/interprocedural/complex/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/interprocedural/complex/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/interprocedural/complex/stderr.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/interprocedural/complex/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/interprocedural/param-inherits-from-arg/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/interprocedural/param-inherits-from-arg/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/interprocedural/param-inherits-from-arg/stderr.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/interprocedural/param-inherits-from-arg/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/looping/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/looping/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/looping/stderr.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/looping/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/lto/input-f.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/lto/input-g.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/lto/input-h.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/lto/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/lto/stderr.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/lto/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/lto/test.h
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/parser/malloc-checker/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/parser/malloc-checker/input.c.cpychecker-log.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/parser/malloc-checker/metadata.ini
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/parser/malloc-checker/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/arg-of-fn-call/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/arg-of-fn-call/input.c.cpychecker-log.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/arg-of-fn-call/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/arg-of-fn-call/stderr.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/arg-of-fn-call/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/arg_must_not_be_null/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/arg_must_not_be_null/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/arg_must_not_be_null/stderr.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/arg_must_not_be_null/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/args-of-fn-call/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/args-of-fn-call/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/args-of-fn-call/stderr.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/args-of-fn-call/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/array/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/array/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/array/stderr.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/array/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/named-pattern/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/named-pattern/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/named-pattern/stderr.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/named-pattern/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/or/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/or/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/or/stderr.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/or/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/result-of-fn-call/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/result-of-fn-call/input.c.cpychecker-log.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/result-of-fn-call/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/result-of-fn-call/stderr.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/result-of-fn-call/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/var-dereference/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/var-dereference/input.c.cpychecker-log.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/var-dereference/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/var-dereference/stderr.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/var-dereference/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/var-usage/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/var-usage/input.c.cpychecker-log.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/var-usage/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/var-usage/stderr.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/patterns/var-usage/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/regression/configure-with-exceptions/getopts.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/regression/configure-with-exceptions/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/regression/configure-with-exceptions/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/regression/configure-with-exceptions/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/regression/configure-without-exceptions/getopts.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/regression/configure-without-exceptions/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/regression/configure-without-exceptions/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/regression/configure-without-exceptions/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/regression/return-0/getopts.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/regression/return-0/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/regression/return-0/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/regression/return-0/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/regression/rhs-is-vardecl/input.c
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/regression/rhs-is-vardecl/stdout.txt
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/CVE-2005-1689-minimal.c
 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/factorial.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/fibonacci.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/file-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/function-ptr-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/function-ptr-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/function-ptr-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/gzio-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/gzio.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/infinite-recursion.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/loop.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-ipa-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-10.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-11.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-12.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-4.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-5.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-6.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-7.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-double-free.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-unchecked.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-9.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-many-paths.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/malloc-paths-5.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-paths-6.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-paths-7.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-paths-8.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/operations.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/params-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/params.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/pattern-test-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/sensitive-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/switch.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/taint-1.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-path-summary-format-default.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-path-summary-format-event-summaries.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-path-summary-format-inline-events.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-path-summary-format-none.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-path-summary-format-simple.c
 create mode 100755 make-html.sh
 create mode 100644 notes.txt

diff --git a/analyze-to-html.sh b/analyze-to-html.sh
new file mode 100755
index 0000000..793bc00
--- /dev/null
+++ b/analyze-to-html.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+# Run this from BUILDDIR/gcc, e.g. via:
+# ../../src/analyze-to-html.sh ../../src/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-2.c
+
+export SRCFILE=$1
+
+SRCDIR=../../src
+(./xgcc -B. \
+    -fdiagnostics-format=json \
+    -c $SRCFILE \
+    --analyzer \
+    2>&1) | $SRCDIR/contrib/diagnostic-json-to-html.py > $(basename $SRCFILE).html
diff --git a/ansi2html.sh b/ansi2html.sh
new file mode 100755
index 0000000..ae83a78
--- /dev/null
+++ b/ansi2html.sh
@@ -0,0 +1,520 @@
+#!/bin/sh
+
+# Convert ANSI (terminal) colours and attributes to HTML
+
+# Licence: LGPLv2
+# Author:
+#    http://www.pixelbeat.org/docs/terminal_colours/
+# Examples:
+#    ls -l --color=always | ansi2html.sh > ls.html
+#    git show --color | ansi2html.sh > last_change.html
+#    Generally one can use the `script` util to capture full terminal output.
+# Changes:
+#    V0.1, 24 Apr 2008, Initial release
+#    V0.2, 01 Jan 2009, Phil Harnish <philharnish@gmail.com>
+#                         Support `git diff --color` output by
+#                         matching ANSI codes that specify only
+#                         bold or background colour.
+#                       P@draigBrady.com
+#                         Support `ls --color` output by stripping
+#                         redundant leading 0s from ANSI codes.
+#                         Support `grep --color=always` by stripping
+#                         unhandled ANSI codes (specifically ^[[K).
+#    V0.3, 20 Mar 2009, http://eexpress.blog.ubuntu.org.cn/
+#                         Remove cat -v usage which mangled non ascii input.
+#                         Cleanup regular expressions used.
+#                         Support other attributes like reverse, ...
+#                       P@draigBrady.com
+#                         Correctly nest <span> tags (even across lines).
+#                         Add a command line option to use a dark background.
+#                         Strip more terminal control codes.
+#    V0.4, 17 Sep 2009, P@draigBrady.com
+#                         Handle codes with combined attributes and color.
+#                         Handle isolated <bold> attributes with css.
+#                         Strip more terminal control codes.
+#    V0.24, 14 Sep 2017
+#      http://github.com/pixelb/scripts/commits/master/scripts/ansi2html.sh
+
+gawk --version >/dev/null || exit 1
+
+if [ "$1" = "--version" ]; then
+    printf '0.23\n' && exit
+fi
+
+usage()
+{
+printf '%s\n' \
+'This utility converts ANSI codes in data passed to stdin
+It has 4 optional parameters:
+--bg=dark --palette=linux|solarized|tango|xterm --css-only|--body-only
+E.g.: ls -l --color=always | ansi2html.sh --bg=dark > ls.html' >&2
+    exit
+}
+
+if [ "$1" = "--help" ]; then
+    usage
+fi
+
+processArg()
+{
+    [ "$1" = "--bg=dark" ] && { dark_bg=yes; return; }
+    [ "$1" = "--css-only" ] && { css_only=yes; return; }
+    [ "$1" = "--body-only" ] && { body_only=yes; return; }
+    if [ "$1" = "--palette=solarized" ]; then
+       # See http://ethanschoonover.com/solarized
+       P0=073642;  P1=D30102;  P2=859900;  P3=B58900;
+       P4=268BD2;  P5=D33682;  P6=2AA198;  P7=EEE8D5;
+       P8=002B36;  P9=CB4B16; P10=586E75; P11=657B83;
+      P12=839496; P13=6C71C4; P14=93A1A1; P15=FDF6E3;
+      return;
+    elif [ "$1" = "--palette=solarized-xterm" ]; then
+       # Above mapped onto the xterm 256 color palette
+       P0=262626;  P1=AF0000;  P2=5F8700;  P3=AF8700;
+       P4=0087FF;  P5=AF005F;  P6=00AFAF;  P7=E4E4E4;
+       P8=1C1C1C;  P9=D75F00; P10=585858; P11=626262;
+      P12=808080; P13=5F5FAF; P14=8A8A8A; P15=FFFFD7;
+      return;
+    elif [ "$1" = "--palette=tango" ]; then
+       # Gnome default
+       P0=000000;  P1=CC0000;  P2=4E9A06;  P3=C4A000;
+       P4=3465A4;  P5=75507B;  P6=06989A;  P7=D3D7CF;
+       P8=555753;  P9=EF2929; P10=8AE234; P11=FCE94F;
+      P12=729FCF; P13=AD7FA8; P14=34E2E2; P15=EEEEEC;
+      return;
+    elif [ "$1" = "--palette=xterm" ]; then
+       P0=000000;  P1=CD0000;  P2=00CD00;  P3=CDCD00;
+       P4=0000EE;  P5=CD00CD;  P6=00CDCD;  P7=E5E5E5;
+       P8=7F7F7F;  P9=FF0000; P10=00FF00; P11=FFFF00;
+      P12=5C5CFF; P13=FF00FF; P14=00FFFF; P15=FFFFFF;
+      return;
+    else # linux console
+       P0=000000;  P1=AA0000;  P2=00AA00;  P3=AA5500;
+       P4=0000AA;  P5=AA00AA;  P6=00AAAA;  P7=AAAAAA;
+       P8=555555;  P9=FF5555; P10=55FF55; P11=FFFF55;
+      P12=5555FF; P13=FF55FF; P14=55FFFF; P15=FFFFFF;
+      [ "$1" = "--palette=linux" ] && return;
+    fi
+}
+
+processArg #defaults
+for var in "$@"; do processArg $var; done
+[ "$css_only" ] && [ "$body_only" ] && usage
+
+# Mac OSX's GNU sed is installed as gsed
+# use e.g. homebrew 'gnu-sed' to get it
+if ! sed --version >/dev/null 2>&1; then
+  if gsed --version >/dev/null 2>&1; then
+    alias sed=gsed
+  else
+    echo "Error, can't find an acceptable GNU sed." >&2
+    exit 1
+  fi
+fi
+
+[ "$css_only" ] || [ "$body_only" ] || printf '%s' "<html>
+<head>
+<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
+<style type=\"text/css\">
+"
+[ "$body_only" ] || printf ".ef0,.f0 { color: #$P0; } .eb0,.b0 { background-color: #$P0; }
+.ef1,.f1 { color: #$P1; } .eb1,.b1 { background-color: #$P1; }
+.ef2,.f2 { color: #$P2; } .eb2,.b2 { background-color: #$P2; }
+.ef3,.f3 { color: #$P3; } .eb3,.b3 { background-color: #$P3; }
+.ef4,.f4 { color: #$P4; } .eb4,.b4 { background-color: #$P4; }
+.ef5,.f5 { color: #$P5; } .eb5,.b5 { background-color: #$P5; }
+.ef6,.f6 { color: #$P6; } .eb6,.b6 { background-color: #$P6; }
+.ef7,.f7 { color: #$P7; } .eb7,.b7 { background-color: #$P7; }
+.ef8, .f0 > .bold,.bold > .f0 { color: #$P8; font-weight: normal; }
+.ef9, .f1 > .bold,.bold > .f1 { color: #$P9; font-weight: normal; }
+.ef10,.f2 > .bold,.bold > .f2 { color: #$P10; font-weight: normal; }
+.ef11,.f3 > .bold,.bold > .f3 { color: #$P11; font-weight: normal; }
+.ef12,.f4 > .bold,.bold > .f4 { color: #$P12; font-weight: normal; }
+.ef13,.f5 > .bold,.bold > .f5 { color: #$P13; font-weight: normal; }
+.ef14,.f6 > .bold,.bold > .f6 { color: #$P14; font-weight: normal; }
+.ef15,.f7 > .bold,.bold > .f7 { color: #$P15; font-weight: normal; }
+.eb8  { background-color: #$P8; }
+.eb9  { background-color: #$P9; }
+.eb10 { background-color: #$P10; }
+.eb11 { background-color: #$P11; }
+.eb12 { background-color: #$P12; }
+.eb13 { background-color: #$P13; }
+.eb14 { background-color: #$P14; }
+.eb15 { background-color: #$P15; }
+"
+# The default xterm 256 colour palette
+for red in 0 1 2 3 4 5 ; do
+  for green in 0 1 2 3 4 5 ; do
+    for blue in 0 1 2 3 4 5 ; do
+      c=$((16 + ($red * 36) + ($green * 6) + $blue))
+      r=$((($red * 40 + 55) * ($red > 0)))
+      g=$((($green * 40 + 55) * ($green > 0)))
+      b=$((($blue * 40 + 55) * ($blue > 0)))
+      [ "$body_only" ] || printf ".ef%d { color: #%2.2x%2.2x%2.2x; } " $c $r $g $b
+      [ "$body_only" ] || printf ".eb%d { background-color: #%2.2x%2.2x%2.2x; }\n" $c $r $g $b
+    done
+  done
+done
+for gray in $(seq 0 23); do
+  c=$(($gray+232))
+  l=$(($gray*10 + 8))
+  [ "$body_only" ] || printf ".ef%d { color: #%2.2x%2.2x%2.2x; } " $c $l $l $l
+  [ "$body_only" ] || printf ".eb%d { background-color: #%2.2x%2.2x%2.2x; }\n" $c $l $l $l
+done
+
+[ "$body_only" ] || printf '%s' '
+.f9 { color: '`[ "$dark_bg" ] && printf "#$P7;" || printf "#$P0;"`' }
+.b9 { background-color: #'`[ "$dark_bg" ] && printf $P0 || printf $P15`'; }
+.f9 > .bold,.bold > .f9, body.f9 > pre > .bold {
+  /* Bold is heavy black on white, or bright white
+     depending on the default background */
+  color: '`[ "$dark_bg" ] && printf "#$P15;" || printf "#$P0;"`'
+  font-weight: '`[ "$dark_bg" ] && printf 'normal;' || printf 'bold;'`'
+}
+.reverse {
+  /* CSS does not support swapping fg and bg colours unfortunately,
+     so just hardcode something that will look OK on all backgrounds. */
+  '"color: #$P0; background-color: #$P7;"'
+}
+.underline { text-decoration: underline; }
+.line-through { text-decoration: line-through; }
+.blink { text-decoration: blink; }
+
+/* Avoid pixels between adjacent span elements.
+   Note this only works for lines less than 80 chars
+   where we close span elements on the same line.
+span { display: inline-block; }
+*/
+'
+[ "$body_only" ] || [ "$css_only" ] && printf '%s\n' \
+'To use the css generated from --css-only, do: '\
+'<head><link rel="stylesheet" type="text/css" href="style.css"></head>' >&2
+[ "$css_only" ] && exit
+[ "$body_only" ] || printf '%s' '</style>
+</head>
+
+<body class="f9 b9">
+<pre>
+'
+[ "$body_only" ] && printf '%s\n' 'Be sure to use <body class="f9 b9"> and <pre>' >&2
+
+p='\x1b\['        #shortcut to match escape codes
+
+# Handle various xterm control sequences.
+# See /usr/share/doc/xterm-*/ctlseqs.txt
+sed "
+# escape ampersand and quote
+s#&#\&amp;#g; s#\"#\&quot;#g;
+s#\x1b[^\x1b]*\x1b\\\##g  # strip anything between \e and ST
+s#\x1b][0-9]*;[^\a]*\a##g # strip any OSC (xterm title etc.)
+
+s#\r\$## # strip trailing \r
+
+# strip other non SGR escape sequences
+s#[\x07]##g
+s#\x1b[]>=\][0-9;]*##g
+s#\x1bP+.\{5\}##g
+# Mark cursor positioning codes \"Jr;c;
+s#${p}\([0-9]\{1,2\}\)G#\"J;\1;#g
+s#${p}\([0-9]\{1,2\}\);\([0-9]\{1,2\}\)H#\"J\1;\2;#g
+
+# Mark clear as \"Cn where n=1 is screen and n=0 is to end-of-line
+s#${p}H#\"C1;#g
+s#${p}K#\"C0;#g
+# Mark Cursor move columns as \"Mn where n is +ve for right, -ve for left
+s#${p}C#\"M1;#g
+s#${p}\([0-9]\{1,\}\)C#\"M\1;#g
+s#${p}\([0-9]\{1,\}\)D#\"M-\1;#g
+s#${p}\([0-9]\{1,\}\)P#\"X\1;#g
+
+s#${p}[0-9;?]*[^0-9;?m]##g
+
+" |
+
+# Normalize the input before transformation
+sed "
+# escape HTML (ampersand and quote done above)
+s#>#\&gt;#g; s#<#\&lt;#g;
+
+# handle truecolor
+s#${p}38;2;\([0-9]\{1,3\}\);\([0-9]\{1,3\}\);\([0-9]\{1,3\}\)m#\
+<span style=\"color:rgb(\1\,\2\,\3\)\">#g
+s#${p}48;2;\([0-9]\{1,3\}\);\([0-9]\{1,3\}\);\([0-9]\{1,3\}\)m#\
+<span style=\"background-color:rgb(\1\,\2\,\3\)\">#g
+
+# normalize SGR codes a little
+
+# split 256 colors out and mark so that they're not
+# recognised by the following 'split combined' line
+:e
+s#${p}\([0-9;]\{1,\}\);\([34]8;5;[0-9]\{1,3\}\)m#${p}\1m${p}¬\2m#g; t e
+s#${p}\([34]8;5;[0-9]\{1,3\}\)m#${p}¬\1m#g;
+
+:c
+s#${p}\([0-9]\{1,\}\);\([0-9;]\{1,\}\)m#${p}\1m${p}\2m#g; t c   # split combined
+s#${p}0\([0-7]\)#${p}\1#g                                 #strip leading 0
+s#${p}1m\(\(${p}[4579]m\)*\)#\1${p}1m#g                   #bold last (with clr)
+s#${p}m#${p}0m#g                                          #add leading 0 to norm
+
+# undo any 256 color marking
+s#${p}¬\([34]8;5;[0-9]\{1,3\}\)m#${p}\1m#g;
+
+# map 16 color codes to color + bold
+s#${p}9\([0-7]\)m#${p}3\1m${p}1m#g;
+s#${p}10\([0-7]\)m#${p}4\1m${p}1m#g;
+
+# change 'reset' code to \"R
+s#${p}0m#\"R;#g
+" |
+
+# Convert SGR sequences to HTML
+sed "
+# common combinations to minimise html (optional)
+:f
+s#${p}3[0-7]m${p}3\([0-7]\)m#${p}3\1m#g; t f
+:b
+s#${p}4[0-7]m${p}4\([0-7]\)m#${p}4\1m#g; t b
+s#${p}3\([0-7]\)m${p}4\([0-7]\)m#<span class=\"f\1 b\2\">#g
+s#${p}4\([0-7]\)m${p}3\([0-7]\)m#<span class=\"f\2 b\1\">#g
+
+s#${p}1m#<span class=\"bold\">#g
+s#${p}4m#<span class=\"underline\">#g
+s#${p}5m#<span class=\"blink\">#g
+s#${p}7m#<span class=\"reverse\">#g
+s#${p}9m#<span class=\"line-through\">#g
+s#${p}3\([0-9]\)m#<span class=\"f\1\">#g
+s#${p}4\([0-9]\)m#<span class=\"b\1\">#g
+
+s#${p}38;5;\([0-9]\{1,3\}\)m#<span class=\"ef\1\">#g
+s#${p}48;5;\([0-9]\{1,3\}\)m#<span class=\"eb\1\">#g
+
+s#${p}[0-9;]*m##g # strip unhandled codes
+" |
+
+# Convert alternative character set and handle cursor movement codes
+# Note we convert here, as if we do at start we have to worry about avoiding
+# conversion of SGR codes etc., whereas doing here we only have to
+# avoid conversions of stuff between &...; or <...>
+#
+# Note we could use sed to do this based around:
+#   sed 'y/abcdefghijklmnopqrstuvwxyz{}`~/▒␉␌␍␊°±␤␋┘┐┌└┼⎺⎻─⎼⎽├┤┴┬│≤≥π£◆·/'
+# However that would be very awkward as we need to only conv some input.
+# The basic scheme that we do in the awk script below is:
+#  1. enable transliterate once "T1; is seen
+#  2. disable once "T0; is seen (may be on diff line)
+#  3. never transliterate between &; or <> chars
+#  4. track x,y movements and active display mode at each position
+#  5. buffer line/screen and dump when required
+sed "
+# change 'smacs' and 'rmacs' to \"T1 and \"T0 to simplify matching.
+s#\x1b(0#\"T1;#g;
+s#\x0E#\"T1;#g;
+
+s#\x1b(B#\"T0;#g
+s#\x0F#\"T0;#g
+" |
+(
+gawk '
+function dump_line(l,del,c,blanks,ret) {
+  for(c=1;c<maxX;c++) {
+    if ((c SUBSEP l) in attr || length(cur)) {
+      ret = ret blanks fixas(cur,attr[c,l])
+      if(del) delete attr[c,l]
+      blanks=""
+    }
+    if ((c SUBSEP l) in dump) {
+      ret=ret blanks dump[c,l]
+      if(del) delete dump[c,l]
+      blanks=""
+    } else blanks=blanks " "
+  }
+  if(length(cur)) ret=ret blanks
+  return ret
+}
+
+function dump_screen(l,ret) {
+  for(l=1;l<=maxY;l++)
+    ret=ret dump_line(l,0) "\n"
+  return ret fixas(cur, "")
+}
+
+function atos(a,i,ret) {
+  for(i=1;i<=length(a);i++) if(i in a) ret=ret a[i]
+  return ret
+}
+
+function fixas(a,s,spc,i,attr,rm,ret) {
+  spc=length(a)
+  l=split(s,attr,">")
+  for(i=1;i<=spc;i++) {
+    rm=rm?rm:(a[i]!=attr[i]">")
+    if(rm) {
+      ret=ret "</span>"
+      delete a[i];
+    }
+  }
+  for(i=1;i<l;i++) {
+    attr[i]=attr[i]">"
+    if(a[i]!=attr[i]) {
+      a[i]=attr[i]
+      ret = ret attr[i]
+    }
+  }
+  return ret
+}
+
+function encode(string,start,end,i,ret,pos,sc,buf) {
+   if(!end) end=length(string);
+   if(!start) start=1;
+   state=3
+   for(i=1;i<=length(string);i++) {
+     c=substr(string,i,1)
+     if(state==2) {
+       sc=sc c
+       if(c==";") {
+          c=sc
+          state=last_mode
+       } else continue
+     } else {
+       if(c=="\r") { x=1; continue }
+       if(c=="<") {
+         # Change attributes - store current active
+         # attributes in span array
+         split(substr(string,i),cord,">");
+         i+=length(cord[1])
+         span[++spc]=cord[1] ">"
+         continue
+       }
+       else if(c=="&") {
+         # All goes to single position till we see a semicolon
+         sc=c
+         state=2
+         continue
+       }
+       else if(c=="\b") {
+          # backspace move insertion point back 1
+          if(spc) attr[x,y]=atos(span)
+          x=x>1?x-1:1
+          continue
+       }
+       else if(c=="\"") {
+          split(substr(string,i+2),cord,";")
+          cc=substr(string,i+1,1);
+          if(cc=="T") {
+              # Transliterate on/off
+              if(cord[1]==1&&state==3) last_mode=state=4
+              if(cord[1]==0&&state==4) last_mode=state=3
+          }
+          else if(cc=="C") {
+              # Clear
+              if(cord[1]+0) {
+                # Screen - if Recording dump screen
+                if(dumpStatus==dsActive) ret=ret dump_screen()
+                dumpStatus=dsActive
+                delete dump
+                delete attr
+                x=y=1
+              } else {
+                # To end of line
+                for(pos=x;pos<maxX;pos++) {
+                  dump[pos,y]=" "
+                  if (!spc) delete attr[pos,y]
+                  else attr[pos,y]=atos(span)
+                }
+              }
+          }
+          else if(cc=="J") {
+              # Jump to x,y
+              i+=length(cord[2])+1
+              # If line is higher - dump previous screen
+              if(dumpStatus==dsActive&&cord[1]<y) {
+                ret=ret dump_screen();
+                dumpStatus=dsNew;
+              }
+              x=cord[2]
+              if(length(cord[1]) && y!=cord[1]){
+                y=cord[1]
+                if(y>maxY) maxY=y
+                # Change y - start recording
+                dumpStatus=dumpStatus?dumpStatus:dsReset
+              }
+          }
+          else if(cc=="M") {
+              # Move left/right on current line
+              x+=cord[1]
+          }
+          else if(cc=="X") {
+              # delete on right
+              for(pos=x;pos<=maxX;pos++) {
+                nx=pos+cord[1]
+                if(nx<maxX) {
+                  if((nx SUBSEP y) in attr) attr[pos,y] = attr[nx,y]
+                  else delete attr[pos,y]
+                  if((nx SUBSEP y) in dump) dump[pos,y] = dump[nx,y]
+                  else delete dump[pos,y]
+                } else if(spc) {
+                  attr[pos,y]=atos(span)
+                  dump[pos,y]=" "
+                }
+              }
+          }
+          else if(cc=="R") {
+              # Reset attributes
+              while(spc) delete span[spc--]
+          }
+          i+=length(cord[1])+2
+          continue
+       }
+       else if(state==4&&i>=start&&i<=end&&c in Trans) c=Trans[c]
+     }
+     if(dumpStatus==dsReset) {
+       delete dump
+       delete attr
+       ret=ret"\n"
+       dumpStatus=dsActive
+     }
+     if(dumpStatus==dsNew) {
+       # After moving/clearing we are now ready to write
+       # somthing to the screen so start recording now
+       ret=ret"\n"
+       dumpStatus=dsActive
+     }
+     if(dumpStatus==dsActive||dumpStatus==dsOff) {
+       dump[x,y] = c
+       if(!spc) delete attr[x,y]
+       else attr[x,y] = atos(span)
+       if(++x>maxX) maxX=x;
+     }
+    }
+    # End of line if dumping increment y and set x back to first col
+    x=1
+    if(!dumpStatus) return ret dump_line(y,1);
+    else if(++y>maxY) maxY=y;
+    return ret
+}
+BEGIN{
+  OFS=FS
+  # dump screen status
+  dsOff=0    # Not dumping screen contents just write output direct
+  dsNew=1    # Just after move/clear waiting for activity to start recording
+  dsReset=2  # Screen cleared build new empty buffer and record
+  dsActive=3 # Currently recording
+  F="abcdefghijklmnopqrstuvwxyz{}`~"
+  T="▒␉␌␍␊°±␤␋┘┐┌└┼⎺⎻─⎼⎽├┤┴┬│≤≥π£◆·"
+  maxX=80
+  delete cur;
+  x=y=1
+  for(i=1;i<=length(F);i++)Trans[substr(F,i,1)]=substr(T,i,1);
+}
+
+{ $0=encode($0) }
+1
+END {
+  if(dumpStatus) {
+    print dump_screen();
+  }
+}'
+)
+
+[ "$body_only" ] || printf '</pre>
+</body>
+</html>\n'
diff --git a/checkers/address_of_function.sm b/checkers/address_of_function.sm
new file mode 100644
index 0000000..9917ab8
--- /dev/null
+++ b/checkers/address_of_function.sm
@@ -0,0 +1,14 @@
+/* function not invoked */
+sm address_of_function {
+   /* global state */
+   decl any_function_ptr fnptr;
+
+   fnptr.*:
+      { fnptr } ->
+          {{
+             # how to properly characterize?
+             if is_simple_reference(fnptr):
+                 error('usage of %s when you probably meant %s()')
+                 # does this have a CWE?
+           }};
+}
\ No newline at end of file
diff --git a/checkers/buffer_overflow.sm b/checkers/buffer_overflow.sm
new file mode 100644
index 0000000..47c2470
--- /dev/null
+++ b/checkers/buffer_overflow.sm
@@ -0,0 +1,75 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+
+   Checker to detect typos of the form:  malloc(sizeof(WRONG_TYPE))
+*/
+sm buffer_overflow {
+  state decl char * ptr;
+  decl any_pointer other_ptr;
+  decl any_expr nmemb;
+  decl any_expr sz;
+
+  /* We will use:
+       "size" for the size of the allocated buffer (when known),
+       "strlen" for the length of the string (not including NUL character),
+       when known
+     
+     None means "unknown" in both cases
+  */
+  {{
+      # Utility methods for getting at current state and size, given
+      # that the initial state has no key/value pairs set
+      def get_size(state):
+          if hasattr(state, 'size'):
+             return state.size
+          return None
+
+      def get_strlen(state):
+          if hasattr(state, 'strlen'):
+             return state.strlen
+          return None
+  }}
+
+  ptr.all:
+    /* Various allocators: */
+      /* libc: */
+      { ptr = malloc(sz) }
+    | { ptr = realloc(other_ptr, sz) }
+      /* libpython: */
+    | { ptr = PyMem_Malloc(sz) }
+    | { ptr = PyMem_Realloc(other_ptr, sz) }
+      /* glib: */
+    | { ptr = g_malloc(sz) }
+    | { ptr = g_malloc0(sz) }
+    | { ptr = g_realloc(other_ptr, sz) }
+    | { ptr = g_try_malloc(sz) }
+    | { ptr = g_try_malloc0(sz) }
+    | { ptr = g_try_realloc(other_ptr, sz) }
+      /* etc */
+      -> {{
+    set_state('ptr', size=sz, strlen=None)
+         }};
+
+    | {sz = strlen(ptr) }
+      -> {{
+          set_state('ptr',
+                    size=get_size(state),
+                    strlen=sz)
+        }}
+      /* etc */
+}
diff --git a/checkers/fd_checker.sm b/checkers/fd_checker.sm
new file mode 100644
index 0000000..cef881e
--- /dev/null
+++ b/checkers/fd_checker.sm
@@ -0,0 +1,26 @@
+/*
+    Work-in-progress checker for file-descriptor APIs
+ */
+        
+sm fd_checker {
+   state decl int fd;
+   decl any_expr buf, count;
+   
+   /* open() and creat return a new fd, or -1, setting errno */
+   fd.all:
+       { fd = open() } -> fd.unknown
+     | { fd = create() } -> fd.unknown
+     ;
+
+   fd.unknown:
+       { fd == -1 } -> true=fd.error, false=fd.open
+     | { fd != -1 } -> true=fd.open, false=fd.error
+     ;
+
+   fd.open:
+       { close(fd) } -> fd.maybeclosed;
+
+   fd.maybeclosed:
+       { close(fd) } -> {error('double close of %s' % fd)}
+
+}
diff --git a/checkers/fileptr.sm b/checkers/fileptr.sm
new file mode 100644
index 0000000..ae1a9e1
--- /dev/null
+++ b/checkers/fileptr.sm
@@ -0,0 +1,17 @@
+sm fileptr_checker {
+   state decl FILE* fp;
+
+   decl any_expr path, mode;
+   decl any_expr fd;
+   decl any_expr stream;
+   
+   fp.all:
+       { fp = fopen(path, mode) } -> fp.unchecked
+     | { fp = fdopen(fd, mode) } -> fp.unchecked
+     | { fp = freopen(path, mode, stream) } -> fp.unchecked
+     ;
+
+   fp.unchecked:
+       { fclose(fp) } -> { error('use of possibly NULL %s' % fp) };
+
+}
diff --git a/checkers/malloc_checker.sm b/checkers/malloc_checker.sm
new file mode 100644
index 0000000..39ab433
--- /dev/null
+++ b/checkers/malloc_checker.sm
@@ -0,0 +1,74 @@
+/*
+    Work-in-progress checker for malloc/free usage
+ */
+sm malloc_checker {
+  stateful decl any_pointer ptr;
+
+  ptr.*:
+    { ptr = malloc() } ->  ptr.unchecked;
+
+  ptr.*:
+    { ptr = 0 } ->  ptr.null;
+
+  ptr.start, ptr.unchecked:
+      { ptr == 0 } -> true=ptr.null, false=ptr.nonnull
+    | { ptr != 0 } -> true=ptr.nonnull, false=ptr.null
+    ;
+
+  ptr.free:
+      { ptr == 0 } -> true=ptr.null
+    | { ptr != 0 } -> false=ptr.null
+    ;
+
+  ptr.unchecked:
+    { *ptr }
+      -> {{
+            error('dereference of possibly-NULL pointer %s' % ptr,
+                  // "CWE-690: Unchecked Return Value to NULL Pointer Dereference"
+                  cwe='CWE-690')
+         }}, ptr.nonnull;
+
+  ptr.null:
+    { *ptr }
+      -> {{
+            error('dereference of NULL pointer %s' % ptr,
+                  // "CWE-690: Unchecked Return Value to NULL Pointer Dereference"
+                  cwe='CWE-690')
+         }}, ptr.stop;
+
+  ptr.start, ptr.unchecked, ptr.null, ptr.nonnull:
+    { free(ptr) } -> ptr.free;
+
+  ptr.free:
+      { free(ptr) }
+          -> {{
+                error('double-free of %s' % ptr,
+                      // "CWE-415: Double Free"
+                      cwe='CWE-415')
+             }}, ptr.stop
+    | { ptr }
+          -> {{
+                error('use-after-free of %s' % ptr,
+                      // "CWE-416: Use After Free"
+                      cwe='CWE-416')
+             }}, ptr.stop
+    ;
+
+  ptr.unchecked:
+    @arg_must_not_be_null
+      -> {{
+            error('possibly-NULL pointer %s passed as argument %i to %s'
+                  % (ptr, argnumber, function),
+                  // "CWE-690: Unchecked Return Value to NULL Pointer Dereference"
+                  cwe='CWE-690')
+          }}, ptr.nonnull;
+
+  ptr.null:
+    @arg_must_not_be_null
+      -> {{
+            error('NULL pointer %s passed as argument %i to %s'
+                  % (ptr, argnumber, function),
+                  // "CWE-690: Unchecked Return Value to NULL Pointer Dereference"
+                  cwe='CWE-690')
+         }}, ptr.stop;
+}
diff --git a/checkers/points_to_stack.sm b/checkers/points_to_stack.sm
new file mode 100644
index 0000000..63e0058
--- /dev/null
+++ b/checkers/points_to_stack.sm
@@ -0,0 +1,60 @@
+/*
+   Copyright 2013 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2013 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+/*
+   Checker for CWE-562: Return of Stack Variable Address
+
+   This will only detect simple cases: it doesn't attempt to track e.g.
+   pointers within a return struct, or within a reference graph.
+
+   Also, it potentially exhibits false positives for the case where
+   a pointer to a variable deeper in the stack is returned by a frame
+   higher up, where the usage of the area is within the lifetime of
+   the stack frame holding it.
+
+ */
+sm points_to_stack {
+   stateful decl any_pointer ptr;
+   decl any_variable var;
+
+   {{
+        def is_on_stack(var):
+           import gcc
+
+           if isinstance(var.context, gcc.FunctionDecl):
+               if not var.static:
+                  return True
+   }}
+
+   ptr.*:
+      { ptr = &var } ->
+        {{
+            if is_on_stack(var):
+                set_state('ptr.points_to_stack', var_pointed_to=var)
+        }};
+
+   ptr.points_to_stack:
+       { return ptr }
+         -> {{
+                error('return of "%s" returns address within stack of local variable "%s"'
+                      % (ptr, state.var_pointed_to),
+                      # "CWE-562: Return of Stack Variable Address":
+                      cwe='CWE-562')
+            }};
+}
\ No newline at end of file
diff --git a/checkers/python.sm b/checkers/python.sm
new file mode 100644
index 0000000..86b1b0a
--- /dev/null
+++ b/checkers/python.sm
@@ -0,0 +1,61 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+/* TODO: autogenerate this checker from the API description? */
+/* Is this actually implementable using the sm framework? */
+
+sm python {
+  state decl expr ptr;
+  decl any_pointer other_ptr;
+  decl any_expr sz;
+
+  ptr.all:
+      { ptr = PyList_New(sz) }
+        -> {{
+                set_state('ptr.unchecked_newref',
+                          ob_type='PyList_Type')
+                # How much can we link states?
+                set_field_state('ob_refcnt',
+                                'newref')
+                # Other idea:
+                set_field_fact(ptr, 'ob_refcnt', '>=', 1)
+                # (this can be manipulated Py_INCREF and Py_DECREF sanely, I think)
+                # what about the tp_dealloc call clearing things though?
+           }}
+
+  ptr.unchecked_newref:
+      /* FIXME: we'll want to inherit the kv pairs in the new states: */
+      { ptr == 0 } -> true=ptr.null, false=ptr.nonnull_newref
+    | { ptr != 0 } -> true=ptr.nonnull_rewref, false=ptr.null
+    | { *ptr } -> {{ warning('use of possibly-NULL pointer %s' % ptr) }}, ptr.nonnull_newref
+    ;
+
+  ptr.null:
+    { *ptr } -> {{ error('use of NULL pointer %s' % ptr) }}
+
+  ptr.unchecked_newref, ptr.nonnull_newref:
+      $leaked$ -> {{ error('leak of %s' % ptr) }};
+
+  /* how to handle Py_INCREF and Py_DECREF ? 
+       Idea: track the ob_refcnt as an entirely separate checker, with
+       states on ob_refcnt fields?
+       (potentially have linkage between checkers?)
+  */
+  ptr.nonnull_newref
+}
diff --git a/checkers/signal_handler.sm b/checkers/signal_handler.sm
new file mode 100644
index 0000000..8ef96de
--- /dev/null
+++ b/checkers/signal_handler.sm
@@ -0,0 +1,58 @@
+/* 
+  SIG30-C: https://www.securecoding.cert.org/confluence/display/seccode/SIG30-C.+Call+only+asynchronous-safe+functions+within+signal+handlers
+
+   I *think* this needs to be two-pass: find the signal handlers, then run
+   a normal state/vs handler state thing.
+
+*/
+
+sm find_signal_handlers {
+   stateful decl any_function_pointer fnptr;
+   decl any_expr signum;
+
+   {{
+        signal_handlers = set()
+   }}
+
+   fnptr.*:
+      { signal(signum, fnptr) }
+        -> {{
+              raise foo
+              # this isn't getting reached; I *think* it's because "handler" isn't in the smexprs
+              # scope for the Context
+
+              signal_handlers.add(fnptr)
+              print('signal_handlers: %r' % signal_handlers)
+           }};
+}
+
+sm async_unsafe {
+   /* (global state) */
+   stateful decl any_expr FIXME;
+
+    /* 
+      need to somehow wire in async calls to the functions in
+      signal_handlers into the supergraph
+      Or to just prime the states somehow:
+     */
+   {{
+       """
+       for fn in signal_handlers:
+           ep = add_entrypoint(fn,
+                               entrydesc='called as signal handler',
+                               exitdesc='returning from signal handler')
+           ep.set_state('within_signal_handler')
+           # instance of an EntryPoint class
+           """
+   }}
+
+/* FIXME:
+   within_signal_handler:
+      {{ fprintf(arg, ...) }}
+      -> {{
+            error('use of signal-unsafe function %s from within a signal-handler' % function,
+                  other_ids=[Id('SIG30-C',
+                                 url='https://www.securecoding.cert.org/confluence/display/seccode/SIG30-C.+Call+only+asynchronous-safe+functions+within+signal+handlers')])
+         }};
+*/
+}
\ No newline at end of file
diff --git a/checkers/sizeof_allocation.sm b/checkers/sizeof_allocation.sm
new file mode 100644
index 0000000..ca63e1d
--- /dev/null
+++ b/checkers/sizeof_allocation.sm
@@ -0,0 +1,91 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+
+   Checker to detect typos of the form:  malloc(sizeof(WRONG_TYPE))
+*/
+sm sizeof_allocation {
+  stateful decl any_pointer ptr;
+  decl any_pointer other_ptr;
+  decl any_expr nmemb;
+  decl any_expr sz;
+
+  {{
+    def is_known_int(var):
+        # We can only run the checker on constant integer expressions
+        import gcc
+        return isinstance(var.gccexpr, gcc.IntegerCst)
+
+    def check_size(ptr, allocated_size):
+        import gcc
+        type_pointed_to = ptr.type.dereference
+        if not isinstance(type_pointed_to, gcc.VoidType):
+            required_size = type_pointed_to.sizeof
+            if allocated_size < required_size:
+                error("allocation too small: pointer to %s (%i bytes)"
+                      " initialized with allocation of %i bytes"
+                      % (type_pointed_to, required_size, allocated_size),
+                      cwe="CWE-131") # "Incorrect Calculation of Buffer Size"
+
+        # Handle cases where the cast happens on another line:
+        set_state("ptr.sized", size=int(allocated_size))
+  }}
+
+  ptr.*:
+    /* Various allocators: */
+      /* libc: */
+      { ptr = malloc(sz) }
+    | { ptr = realloc(other_ptr, sz) }
+      /* libpython: */
+    | { ptr = PyMem_Malloc(sz) }
+    | { ptr = PyMem_Realloc(other_ptr, sz) }
+      /* glib: */
+    | { ptr = g_malloc(sz) }
+    | { ptr = g_malloc0(sz) }
+    | { ptr = g_realloc(other_ptr, sz) }
+    | { ptr = g_try_malloc(sz) }
+    | { ptr = g_try_malloc0(sz) }
+    | { ptr = g_try_realloc(other_ptr, sz) }
+      /* etc */
+      -> {{
+            if is_known_int(sz):
+                check_size(ptr, sz)
+         }};
+
+  ptr.*:
+      /* libc: */
+      { ptr = calloc(nmemb, sz) }
+      /* glib: */
+    | { ptr = g_malloc_n(nmemb, sz) }
+    | { ptr = g_malloc0_n(nmemb, sz) }
+    | { ptr = g_realloc_n(other_pointer, nmemb, sz) }
+    | { ptr = g_try_malloc_n(nmemb, sz) }
+    | { ptr = g_try_malloc0_n(nmemb, sz) }
+    | { ptr = g_try_realloc_n(other_pointer, nmemb, sz) }
+      -> {{
+            if is_known_int(nmemb):
+                if is_known_int(sz):
+                    check_size(ptr, int(nmemb) * int(sz))
+         }};
+
+  ptr.sized:
+     { other_ptr = ptr } ->
+       {{
+            check_size(other_ptr, allocated_size=state.size)
+       }};
+
+}
diff --git a/checkers/taint.sm b/checkers/taint.sm
new file mode 100644
index 0000000..7b305ab
--- /dev/null
+++ b/checkers/taint.sm
@@ -0,0 +1,71 @@
+sm taint {
+  stateful decl any_expr x;
+
+  decl any_expr y;
+  decl any_expr arr;
+  decl any_expr size;
+  decl any_expr nmemb;
+  decl any_expr stream;
+
+  /* Patterns that detect upper-bound and lower-bound checks: */
+  pat check_ub { x < y } | { x <= y };
+  pat check_lb { x > y } | { x >= y };
+  pat check_eq { x == y };
+  pat check_ne { x != y };
+
+  /* Sources of tainted data: */
+  x.*:
+    /* reading data from files: */
+      {fread(x, size, nmemb, stream)} -> x.tainted
+    | {read(fd, x, count)} -> x.tainted
+    ;
+
+  /*
+    When a tainted value is compared, mark it as now having either
+    an upper-bound, or a lower-bound:
+  */
+  x.tainted:
+      check_ub -> true=x.has_ub, false=x.has_lb
+    | check_lb -> true=x.has_lb, false=x.has_ub
+    ;
+
+  /*
+    If a value has only one bound, check to see if it gains the other bound:
+  */
+   x.has_lb:
+      check_ub -> true=x.ok
+    | check_lb -> false=x.ok
+    ;
+   x.has_ub:
+      check_ub -> false=x.ok
+    | check_lb -> true=x.ok
+    ;
+
+  /* If a value is successfully compared against a specific value, it's OK */
+  x.tainted, x.has_lb, x.has_ub:
+      check_eq -> true=x.ok
+    | check_ne -> false=x.ok
+    ;
+
+  /* Detect when a tainted value is used in a dangerous way: */
+  x.tainted:
+      { arr[x] } -> {{
+          error('use of tainted value %s in array lookup without bounds checking' % x,
+                cwe='CWE-129') # "Improper Validation of Array Index"
+        }};
+
+  x.has_lb:
+      { arr[x] } -> {{
+          error('use of tainted value %s in array lookup without upper-bound check' % x,
+                cwe='CWE-129') # "Improper Validation of Array Index"
+        }};
+
+  /* Unsigned types have an implicit lower bound: */
+  x.has_ub:
+      { arr[x] } -> {{
+          if not x.type.unsigned:
+             error('use of tainted value %s in array lookup without lower-bound check' % x,
+                   cwe='CWE-129') # "Improper Validation of Array Index"
+        }};
+
+}
diff --git a/contrib/diagnostic-json-to-html-templates/index.html b/contrib/diagnostic-json-to-html-templates/index.html
index e1d3cab..8f107b1 100644
--- a/contrib/diagnostic-json-to-html-templates/index.html
+++ b/contrib/diagnostic-json-to-html-templates/index.html
@@ -73,6 +73,11 @@ badge badge-pill badge-info
 	<span>: </span>
 	<span>{{ d['message'] }}</span>
       </a>
+      {% if d['metadata'] %}
+      {% if d['metadata']['cwe'] %}
+      <span>[<a href="https://cwe.mitre.org/data/definitions/{{d['metadata']['cwe']}}.html">CWE-{{d['metadata']['cwe']}}</a>]</span>
+      {% endif %}
+      {% endif %}
     </h5>
     <span><a href="{{d['option_url']}}">{{ d['option'] }}</a></span>
   </div>
diff --git a/demo-2.html b/demo-2.html
new file mode 100644
index 0000000..1afa046
--- /dev/null
+++ b/demo-2.html
@@ -0,0 +1,216 @@
+<!DOCTYPE html>
+<!-- see notebook p123 -->
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+
+  <!-- Bootstrap CSS -->
+  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" integrity="sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB" crossorigin="anonymous">
+
+    <style>
+    </style>
+
+    <title>Analyzer report</title>
+</head>
+<body>
+    <div id="content">
+    <ul class="list-group">
+
+    <li class="list-group-item">
+      <div>
+	<div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c:30:1: warning: double-free</div>
+	  <pre>
+   30 | }
+      | ^
+	  </pre>
+	<ul class="list-group">
+	  <li class="list-group-item">
+	    <div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c: In function ‘test’:</div>
+	  </li>
+	</ul>
+	<ul class="list-group">
+	  <div class="stack">stack: <code>test</code></div>
+
+	  <li class="list-group-item">
+	    <pre>
+   26 | {
+   27 |   boxed_int *bi = make_boxed_int (x);
+      |                   ~~~~~~~~~~~~~~~~~~
+      |                   |
+      |                   <span class="badge badge-pill badge-info">①: calling from ‘test’ to ‘make_boxed_int’</span>
+	    </pre>
+
+	    <ul class="list-group">
+	      <li class="list-group-item">
+		<div class="stack">stack: <code>test</code> / <code>make_boxed_int</code></div>
+		<div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c: In function ‘make_boxed_int’:</div>
+		<pre>
+   13 | {
+   14 |   boxed_int *new_ptr = (boxed_int *)malloc (sizeof (boxed_int));
+      |                                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+      |                                     |
+      |                                     <span class="badge badge-pill badge-info">②: ‘new_ptr’: ‘unchecked’</span>
+   15 |   new_ptr->val = val;
+   16 |   return new_ptr;
+      |          ~~~~~~~
+      |          |
+      |          <span class="badge badge-pill badge-info">③: returning ‘unchecked’</span>
+   17 | }
+		</pre>
+	      </li>
+	    </ul>
+
+	    <!-- returning to "test/" -->
+	    <div class="stack">stack: <code>test</code></div>
+
+      <div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c: In function ‘test’:</div>
+      <div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c:27:19: note: (19) returning from ‘make_boxed_int’ to ‘test’</div>
+      <pre>
+   27 |   boxed_int *bi = make_boxed_int (x);
+      |                   ^~~~~~~~~~~~~~~~~~
+      |                   |
+      |                   <span class="badge badge-pill badge-info">④: ‘bi’: ‘unchecked’</span>
+   28 |   free_boxed_int (bi);
+      |   ~~~~~~~~~~~~~~~~~~~
+      |   |
+      |   <span class="badge badge-pill badge-info">⑤: calling from ‘test’ to ‘free_boxed_int’ (‘bi’: ‘unchecked’)</span>
+      </pre>
+
+	    <ul class="list-group"> <!-- stack: "test/free_boxed_int/" -->
+	      <li class="list-group-item">
+		<div class="stack">stack: <code>test</code> / <code>free_boxed_int</code></div>
+      <div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c: In function ‘free_boxed_int’:</div>
+      <div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c:21:1: note: (NN) ‘bi’: ‘unchecked’</div>
+      <pre>
+   21 | {
+   22 |   free (victim);
+      |   ~~~~~~~~~~~~~
+      |   |
+      |   <span class="badge badge-pill badge-info">(NN): ‘unchecked’ -> ‘free’</span>
+   23 | }
+      |
+      </pre>
+	      </li>
+	    </ul>
+
+	    <!-- returning to "test/" -->
+	  </li>
+	</ul>
+
+	<div class="stack">stack: <code>test</code></div>
+      <div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c: In function ‘test’:</div>
+      <pre>
+   28 |   free_boxed_int (bi);
+      |   ^~~~~~~~~~~~~~~~~~~
+      |   |
+      |   <span class="badge badge-pill badge-info">(NN): returning from ‘free_boxed_int’ to ‘test’ (‘bi’: ‘freed’)</span>
+   29 |   free_boxed_int (bi); /* BUG: double-free.  */
+      |   ~~~~~~~~~~~~~~~~~~~
+      |   |
+      |   <span class="badge badge-pill badge-info">(NN) calling from ‘test’ to ‘free_boxed_int’ (‘bi’: ‘freed’)</span>
+      </pre>
+
+	    <ul class="list-group"> <!-- stack: "test/free_boxed_int/" -->
+	      <li class="list-group-item">
+		<div class="stack">stack: <code>test</code> / <code>free_boxed_int</code></div>
+		<div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c: In function ‘free_boxed_int’:</div>
+		<div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c:21:1: note: (35) ‘free_boxed_int’ supernode:6 sm_inst: <start, {}></div>
+		<pre>
+   21 | {
+   22 |   free (victim);
+      |   ~~~~~~~~~~~~~
+      |   |
+      |   <span class="badge badge-pill badge-danger">(N) double-free of ‘victim’</span>
+		</pre>
+	      </li>
+	    </ul>
+    </li>
+    </ul>
+
+    <li class="list-group-item">
+      <div>
+	<div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c: At top level:</div>
+	<div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c:23:1: note: END OF PATH (presumably started within ‘free_boxed_int’)</div>
+      <pre>
+   23 | }
+      | ^
+      </pre>
+      <pre>
+	<div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c: In function ‘free_boxed_int’:</div>
+	<div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c:21:1: note: (1) ‘free_boxed_int’ supernode:6 sm_inst: <start, {}></div>
+   21 | {
+      | ^
+      | |
+      | (2) taking CFG edge 0->2 within ‘free_boxed_int’ (supernode 6 -> 7)
+   22 |   free (victim);
+      |   ~~~~~~~~~~~~~
+      |   |
+      |   (5) <start, {}>; transition: ‘victim’: ‘start’ -> ‘free’
+      |   (4) on_stmt: ‘free (victim_2(D));’ <start, {}>
+      |   (3) ‘free_boxed_int’ supernode:7 sm_inst: <start, {}>
+   23 | }
+      | ~
+      | |
+      | (8) ‘free_boxed_int’ supernode:8 sm_inst: <start, {‘victim’: ‘free’}>
+      | (7) taking CFG edge 2->1 within ‘free_boxed_int’ (supernode 7 -> 8)
+      | (6) on_stmt: ‘return;’ <start, {‘victim’: ‘free’}>
+      </pre>
+    </li>
+
+
+    <li class="list-group-item">
+      <div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c: At top level:</div>
+      <div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c:17:1: note: END OF PATH (presumably started within ‘make_boxed_int’)</div>
+      <pre>
+   17 | }
+      | ^
+      </pre>
+      <div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c: In function ‘make_boxed_int’:</div>
+      <div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c:13:1: note: (1) ‘make_boxed_int’ supernode:9 sm_inst: <start, {}></div>
+      <pre>
+   13 | {
+      | ^
+      | |
+      | (2) taking CFG edge 0->2 within ‘make_boxed_int’ (supernode 9 -> 10)
+   14 |   boxed_int *new_ptr = (boxed_int *)malloc (sizeof (boxed_int));
+      |                                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+      |                                     |
+      |                                     (5) <start, {}>; transition: ‘new_ptr’: ‘start’ -> ‘unchecked’
+      |                                     (4) on_stmt: ‘new_ptr_3 = malloc (4);’ <start, {}>
+      |                                     (3) ‘make_boxed_int’ supernode:10 sm_inst: <start, {}>
+   15 |   new_ptr->val = val;
+      |   ~~~~~~~~~~~~~~~~~~
+      |                |
+      |                (6) on_stmt: ‘new_ptr_3->val = val_4(D);’ <start, {‘new_ptr’: ‘unchecked’}>
+   16 |   return new_ptr;
+      |          ~~~~~~~
+      |          |
+      |          (9) ‘make_boxed_int’ supernode:11 sm_inst: <start, {‘new_ptr’: ‘unchecked’, ‘<unknown>’: ‘unchecked’}>
+      |          (8) taking CFG edge 2->3 within ‘make_boxed_int’ (supernode 10 -> 11)
+      |          (7) on_stmt: ‘_6 = new_ptr_3;’ <start, {‘new_ptr’: ‘unchecked’}>
+      </pre>
+      <div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c:16:10: note: (11) on_stmt: ‘return _6;’ <start, {‘new_ptr’: ‘unchecked’, ‘<unknown>’: ‘unchecked’}></div>
+      <pre>
+   16 |   return new_ptr;
+      |          ^~~~~~~
+      |          |
+      |          (12) taking CFG edge 3->1 within ‘make_boxed_int’ (supernode 11 -> 12)
+   17 | }
+      | ~         
+      | |
+      | (13) ‘make_boxed_int’ supernode:12 sm_inst: <start, {‘new_ptr’: ‘unchecked’, ‘<unknown>’: ‘unchecked’, ‘<return-value>’: ‘unchecked’}>
+      </pre>
+      </div>
+    </ul>
+    </div>
+
+    <div id="footer">
+    </div>
+
+    <!-- Bootstrap JS -->
+    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
+    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js" integrity="sha384-smHYKdLADwkXOn1EmN1qk/HfnUcbVRZyYmZ4qpPea6sjB/pTJ0euyQp0Mk8ck+5T" crossorigin="anonymous"></script>
+
+</body>
+</html>
diff --git a/demo-control.c b/demo-control.c
new file mode 100644
index 0000000..68b8609
--- /dev/null
+++ b/demo-control.c
@@ -0,0 +1,44 @@
+extern void __dump_constraints (void);
+
+void test_cond (int flag)
+{
+  if (flag)
+    __dump_constraints ();
+  else
+    __dump_constraints ();
+}
+
+void test_switch (int x)
+{
+  switch (x)
+    {
+    case 5 ... 10:
+      __dump_constraints ();
+      break;
+
+    case 17:
+      __dump_constraints ();
+      break;
+
+    default:
+      __dump_constraints ();
+      break;
+    }
+}
+
+extern void do_something (int i);
+
+void test_double_diamond (int flag)
+{
+  if (flag)
+    do_something (0);
+  else
+    do_something (1);
+
+  do_something (2);
+
+  if (flag)
+    __dump_constraints ();
+  else
+    __dump_constraints ();
+}
diff --git a/demo-direct-with-loop.c b/demo-direct-with-loop.c
new file mode 100644
index 0000000..446cd7e
--- /dev/null
+++ b/demo-direct-with-loop.c
@@ -0,0 +1,22 @@
+#include <stdlib.h>
+
+void test (void)
+{
+  void *ptr = malloc (1024);
+
+  int i;
+  for (i = 0; i < 1024; i++)
+    free (ptr); /* BUG: double-free, 2nd time in loop.  */
+}
+
+void test_2 (void)
+{
+  void *ptr = malloc (1024);
+
+  int i;
+  for (i = 0; i < 1024; i++)
+    foo ();
+
+  free (ptr);
+  free (ptr);
+}
diff --git a/demo-direct.c b/demo-direct.c
new file mode 100644
index 0000000..f43de69
--- /dev/null
+++ b/demo-direct.c
@@ -0,0 +1,31 @@
+#include <stdlib.h>
+
+void test (void)
+{
+  void *ptr = malloc (1024);
+  free (ptr);
+  free (ptr); /* BUG: double-free.  */
+}
+
+void test_2 (int x, int y)
+{
+  void *ptr = malloc (1024);
+  if (x)
+    free (ptr);
+  if (y)
+    free (ptr); /* BUG: double-free.  */
+  /* BUG: leak.  */
+}
+
+int test_3 (int x, int y)
+{
+  int *ptr = (int *)malloc (sizeof (int));
+  *ptr = 42;
+  if (x)
+    free (ptr);
+  *ptr = 19;
+  if (y)
+    free (ptr); /* BUG: double-free.  */
+  return *ptr;
+  /* BUG: leak.  */
+}
diff --git a/demo-indirect.c b/demo-indirect.c
new file mode 100644
index 0000000..cf4dd50
--- /dev/null
+++ b/demo-indirect.c
@@ -0,0 +1,21 @@
+#include <stdlib.h>
+
+void *
+calls_malloc (void)
+{
+  void *result = malloc (1024);
+  return result;
+}
+
+void
+calls_free (void *victim)
+{
+  free (victim);
+}
+
+void test (void)
+{
+  void *ptr = calls_malloc ();
+  calls_free (ptr);
+  calls_free (ptr); /* BUG: double-free.  */
+}
diff --git a/demo.c b/demo.c
new file mode 100644
index 0000000..2572d49
--- /dev/null
+++ b/demo.c
@@ -0,0 +1,38 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+extern void do_stuff (void);
+
+int test (const char *filename, int flag)
+{
+  FILE *f;
+  int *p, *q;
+  int i;
+
+  f = fopen (filename, "w");
+  if (!f)
+    return -1;
+
+  p = (int *)malloc (sizeof (int));
+  if (!p)
+    {
+      free (p);
+      fclose (f);
+      return -1;
+    }
+
+  q = (int *)malloc (sizeof (int));
+  if (!q)
+    {
+      free (p);
+      fclose (f);
+      /* oops: forgot the "return" here, so it falls through.  */
+    }
+
+  do_stuff ();
+
+  free (p);
+  free (q);
+  fclose (f);
+  return 0;
+}
diff --git a/demo.html b/demo.html
new file mode 100644
index 0000000..981f9a0
--- /dev/null
+++ b/demo.html
@@ -0,0 +1,277 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+
+  <!-- Bootstrap CSS -->
+  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" integrity="sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB" crossorigin="anonymous">
+
+    <style>
+    </style>
+
+    <title>Analyzer report</title>
+</head>
+<body>
+    <div id="content">
+    <ul class="list-group">
+
+    <li class="list-group-item">
+      <div>
+	<div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c:30:1: warning: double-free</div>
+	  <pre>
+   30 | }
+      | ^
+	  </pre>
+	<ul class="list-group">
+	  <li class="list-group-item">
+	    <div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c: In function ‘test’:</div>
+	  </li>
+	</ul>
+	<ul class="list-group">
+	  <div class="stack">stack: <code>test</code></div>
+
+	  <li class="list-group-item">
+	    <div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c: In function ‘test’:</div>
+	    <div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c:26:1: note: (1) ‘test’ supernode:0 sm_inst: <start, {}></div>
+	    <pre>
+   26 | {
+      | ^
+      | |
+      | (2) taking CFG edge 0->2 within ‘test’ (supernode 0 -> 1)
+   27 |   boxed_int *bi = make_boxed_int (x);
+      |                   ~~~~~~~~~~~~~~~~~~
+      |                   |
+      |                   <span class="badge badge-pill badge-info">(5) calling from ‘test’ to ‘make_boxed_int’ (supernode 1 -> 9)</span>
+      |                   <span class="badge badge-pill badge-info">(4) on_stmt: ‘bi_4 = make_boxed_int (x_2(D));’ <start, {}></span>
+      |                   <span class="badge badge-pill badge-info">(3) ‘test’ supernode:1 sm_inst: <start, {}></span>
+	    </pre>
+
+	    <ul class="list-group">
+	      <li class="list-group-item">
+		<div class="stack">stack: <code>test</code> / <code>make_boxed_int</code></div>
+		<div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c: In function ‘make_boxed_int’:</div>
+		<div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c:13:1: note: (6) ‘make_boxed_int’ supernode:9 sm_inst: <start, {}></div>
+		<pre>
+   13 | {
+      | ^
+      | |
+      | (7) taking CFG edge 0->2 within ‘make_boxed_int’ (supernode 9 -> 10)
+   14 |   boxed_int *new_ptr = (boxed_int *)malloc (sizeof (boxed_int));
+      |                                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+      |                                     |
+      |                                     <span class="badge badge-pill badge-info">(10) <start, {}>; transition: ‘new_ptr’: ‘start’ -> ‘unchecked’</span>
+      |                                     <span class="badge badge-pill badge-info">(9) on_stmt: ‘new_ptr_3 = malloc (4);’ <start, {}></span>
+      |                                     <span class="badge badge-pill badge-info">(8) ‘make_boxed_int’ supernode:10 sm_inst: <start, {}></span>
+   15 |   new_ptr->val = val;
+      |   ~~~~~~~~~~~~~~~~~~
+      |                |
+      |                <span class="badge badge-pill badge-info">(11) on_stmt: ‘new_ptr_3->val = val_4(D);’ <start, {‘new_ptr’: ‘unchecked’}></span>
+   16 |   return new_ptr;
+      |          ~~~~~~~
+      |          |
+      |          <span class="badge badge-pill badge-info">(14) ‘make_boxed_int’ supernode:11 sm_inst: <start, {‘new_ptr’: ‘unchecked’, ‘<unknown>’: ‘unchecked’}></span>
+      |          <span class="badge badge-pill badge-info">(13) taking CFG edge 2->3 within ‘make_boxed_int’ (supernode 10 -> 11)</span>
+      |          <span class="badge badge-pill badge-info">(12) on_stmt: ‘_6 = new_ptr_3;’ <start, {‘new_ptr’: ‘unchecked’}></span>
+      |          <span class="badge badge-pill badge-info">(16) on_stmt: ‘return _6;’ <start, {‘new_ptr’: ‘unchecked’, ‘<unknown>’: ‘unchecked’}></span>
+      |          <span class="badge badge-pill badge-info">(17) taking CFG edge 3->1 within ‘make_boxed_int’ (supernode 11 -> 12)</span>
+   17 | }
+      | ~         
+      | |
+      | (18) ‘make_boxed_int’ supernode:12 sm_inst: <start, {‘new_ptr’: ‘unchecked’, ‘<unknown>’: ‘unchecked’, ‘<return-value>’: ‘unchecked’}>
+		</pre>
+	      </li>
+	    </ul>
+
+	    <!-- returning to "test/" -->
+	    <div class="stack">stack: <code>test</code></div>
+
+      <div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c: In function ‘test’:</div>
+      <div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c:27:19: note: (19) returning from ‘make_boxed_int’ to ‘test’ (supernode 12 -> 2)</div>
+      <pre>
+   27 |   boxed_int *bi = make_boxed_int (x);
+      |                   ^~~~~~~~~~~~~~~~~~
+      |                   |
+      |                   <span class="badge badge-pill badge-info">(20) ‘test’ supernode:2 sm_inst: <start, {‘bi’: ‘unchecked’}></span>
+   28 |   free_boxed_int (bi);
+      |   ~~~~~~~~~~~~~~~~~~~
+      |   |
+      |   <span class="badge badge-pill badge-info">(22) calling from ‘test’ to ‘free_boxed_int’ (supernode 2 -> 6)</span>
+      |   <span class="badge badge-pill badge-info">(21) on_stmt: ‘free_boxed_int (bi_4);’ <start, {‘bi’: ‘unchecked’}></span>
+      </pre>
+
+	    <ul class="list-group"> <!-- stack: "test/free_boxed_int/" -->
+	      <li class="list-group-item">
+		<div class="stack">stack: <code>test</code> / <code>free_boxed_int</code></div>
+      <div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c: In function ‘free_boxed_int’:</div>
+      <div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c:21:1: note: (23) ‘free_boxed_int’ supernode:6 sm_inst: <start, {‘bi’: ‘unchecked’}></div>
+      <pre>
+   21 | {
+      | ^
+      | |
+      | <span class="badge badge-pill badge-info">(24) taking CFG edge 0->2 within ‘free_boxed_int’ (supernode 6 -> 7)</span>
+   22 |   free (victim);
+      |   ~~~~~~~~~~~~~
+      |   |
+      |   <span class="badge badge-pill badge-info">(27) <start, {‘bi’: ‘unchecked’}>; transition: ‘victim’: ‘start’ -> ‘free’</span>
+      |   <div class="badge badge-pill badge-info">(26) on_stmt: ‘free (victim_2(D));’ <start, {‘bi’: ‘unchecked’}></div>
+      |   <span class="badge badge-pill badge-info">(25) ‘free_boxed_int’ supernode:7 sm_inst: <start, {‘bi’: ‘unchecked’}></span>
+   23 | }
+      | ~
+      | |
+      | <span class="badge badge-pill badge-info">(30) ‘free_boxed_int’ supernode:8 sm_inst: <start, {‘bi’: ‘unchecked’, ‘victim’: ‘free’}></span>
+      | <span class="badge badge-pill badge-info">(29) taking CFG edge 2->1 within ‘free_boxed_int’ (supernode 7 -> 8)</span>
+      | <span class="badge badge-pill badge-info">(28) on_stmt: ‘return;’ <start, {‘bi’: ‘unchecked’, ‘victim’: ‘free’}></span>
+      </pre>
+	      </li>
+	    </ul>
+
+	    <!-- returning to "test/" -->
+	  </li>
+	</ul>
+
+	<div class="stack">stack: <code>test</code></div>
+      <div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c: In function ‘test’:</div>
+      <div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c:28:3: note: (31) returning from ‘free_boxed_int’ to ‘test’ (supernode 8 -> 3)</div>
+      <pre>
+   28 |   free_boxed_int (bi);
+      |   ^~~~~~~~~~~~~~~~~~~
+      |   |
+      |   <span class="badge badge-pill badge-info">(32) ‘test’ supernode:3 sm_inst: <start, {}></span>
+   29 |   free_boxed_int (bi); /* BUG: double-free.  */
+      |   ~~~~~~~~~~~~~~~~~~~
+      |   |
+      |   <span class="badge badge-pill badge-info">(34) calling from ‘test’ to ‘free_boxed_int’ (supernode 3 -> 6)</span>
+      |   <span class="badge badge-pill badge-info">(33) on_stmt: ‘free_boxed_int (bi_4);’ <start, {}></span>
+      </pre>
+
+	    <ul class="list-group"> <!-- stack: "test/free_boxed_int/" -->
+	      <li class="list-group-item">
+		<div class="stack">stack: <code>test</code> / <code>free_boxed_int</code></div>
+		<div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c: In function ‘free_boxed_int’:</div>
+		<div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c:21:1: note: (35) ‘free_boxed_int’ supernode:6 sm_inst: <start, {}></div>
+		<pre>
+   21 | {
+      | ^
+      | |
+      | <span class="badge badge-pill badge-info">(36) taking CFG edge 0->2 within ‘free_boxed_int’ (supernode 6 -> 7)</span>
+   22 |   free (victim);
+      |   ~~~~~~~~~~~~~
+      |   |
+      |   <span class="badge badge-pill badge-info">(39) <start, {}>; transition: ‘victim’: ‘start’ -> ‘free’</span>
+      |   <span class="badge badge-pill badge-info">(38) on_stmt: ‘free (victim_2(D));’ <start, {}></span>
+      |   <span class="badge badge-pill badge-info">(37) ‘free_boxed_int’ supernode:7 sm_inst: <start, {}></span>
+   23 | }
+      | ~
+      | |
+      | <span class="badge badge-pill badge-info">(42) ‘free_boxed_int’ supernode:8 sm_inst: <start, {‘victim’: ‘free’}></span>
+      | <span class="badge badge-pill badge-info">(41) taking CFG edge 2->1 within ‘free_boxed_int’ (supernode 7 -> 8)</span>
+      | <span class="badge badge-pill badge-info">(40) on_stmt: ‘return;’ <start, {‘victim’: ‘free’}></span>
+		</pre>
+	      </li>
+	    </ul>
+
+	    <div class="stack">stack: <code>test</code></div>
+	    <div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c: In function ‘test’:</div>
+	    <div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c:29:3: note: (43) returning from ‘free_boxed_int’ to ‘test’ (supernode 8 -> 4)</div>
+	    <pre>
+   29 |   free_boxed_int (bi); /* BUG: double-free.  */
+      |   ^~~~~~~~~~~~~~~~~~~
+      |   |
+      |   <span class="badge badge-pill badge-info">(44) ‘test’ supernode:4 sm_inst: <start, {}></span>
+   30 | }
+      | ~  
+      | |
+      | <span class="badge badge-pill badge-info">(47) ‘test’ supernode:5 sm_inst: <start, {}></span>
+      | <span class="badge badge-pill badge-info">(46) taking CFG edge 2->1 within ‘test’ (supernode 4 -> 5)</span>
+      | <span class="badge badge-pill badge-info">(45) on_stmt: ‘return;’ <start, {}></span>
+	    </pre>
+    </li>
+    </ul>
+
+    <li class="list-group-item">
+      <div>
+	<div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c: At top level:</div>
+	<div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c:23:1: note: END OF PATH (presumably started within ‘free_boxed_int’)</div>
+      <pre>
+   23 | }
+      | ^
+      </pre>
+      <pre>
+	<div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c: In function ‘free_boxed_int’:</div>
+	<div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c:21:1: note: (1) ‘free_boxed_int’ supernode:6 sm_inst: <start, {}></div>
+   21 | {
+      | ^
+      | |
+      | (2) taking CFG edge 0->2 within ‘free_boxed_int’ (supernode 6 -> 7)
+   22 |   free (victim);
+      |   ~~~~~~~~~~~~~
+      |   |
+      |   (5) <start, {}>; transition: ‘victim’: ‘start’ -> ‘free’
+      |   (4) on_stmt: ‘free (victim_2(D));’ <start, {}>
+      |   (3) ‘free_boxed_int’ supernode:7 sm_inst: <start, {}>
+   23 | }
+      | ~
+      | |
+      | (8) ‘free_boxed_int’ supernode:8 sm_inst: <start, {‘victim’: ‘free’}>
+      | (7) taking CFG edge 2->1 within ‘free_boxed_int’ (supernode 7 -> 8)
+      | (6) on_stmt: ‘return;’ <start, {‘victim’: ‘free’}>
+      </pre>
+    </li>
+
+
+    <li class="list-group-item">
+      <div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c: At top level:</div>
+      <div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c:17:1: note: END OF PATH (presumably started within ‘make_boxed_int’)</div>
+      <pre>
+   17 | }
+      | ^
+      </pre>
+      <div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c: In function ‘make_boxed_int’:</div>
+      <div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c:13:1: note: (1) ‘make_boxed_int’ supernode:9 sm_inst: <start, {}></div>
+      <pre>
+   13 | {
+      | ^
+      | |
+      | (2) taking CFG edge 0->2 within ‘make_boxed_int’ (supernode 9 -> 10)
+   14 |   boxed_int *new_ptr = (boxed_int *)malloc (sizeof (boxed_int));
+      |                                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+      |                                     |
+      |                                     (5) <start, {}>; transition: ‘new_ptr’: ‘start’ -> ‘unchecked’
+      |                                     (4) on_stmt: ‘new_ptr_3 = malloc (4);’ <start, {}>
+      |                                     (3) ‘make_boxed_int’ supernode:10 sm_inst: <start, {}>
+   15 |   new_ptr->val = val;
+      |   ~~~~~~~~~~~~~~~~~~
+      |                |
+      |                (6) on_stmt: ‘new_ptr_3->val = val_4(D);’ <start, {‘new_ptr’: ‘unchecked’}>
+   16 |   return new_ptr;
+      |          ~~~~~~~
+      |          |
+      |          (9) ‘make_boxed_int’ supernode:11 sm_inst: <start, {‘new_ptr’: ‘unchecked’, ‘<unknown>’: ‘unchecked’}>
+      |          (8) taking CFG edge 2->3 within ‘make_boxed_int’ (supernode 10 -> 11)
+      |          (7) on_stmt: ‘_6 = new_ptr_3;’ <start, {‘new_ptr’: ‘unchecked’}>
+      </pre>
+      <div class="diagnostic>">../../src/gcc/testsuite/analyzer.dg/demo.c:16:10: note: (11) on_stmt: ‘return _6;’ <start, {‘new_ptr’: ‘unchecked’, ‘<unknown>’: ‘unchecked’}></div>
+      <pre>
+   16 |   return new_ptr;
+      |          ^~~~~~~
+      |          |
+      |          (12) taking CFG edge 3->1 within ‘make_boxed_int’ (supernode 11 -> 12)
+   17 | }
+      | ~         
+      | |
+      | (13) ‘make_boxed_int’ supernode:12 sm_inst: <start, {‘new_ptr’: ‘unchecked’, ‘<unknown>’: ‘unchecked’, ‘<return-value>’: ‘unchecked’}>
+      </pre>
+      </div>
+    </ul>
+    </div>
+
+    <div id="footer">
+    </div>
+
+    <!-- Bootstrap JS -->
+    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
+    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js" integrity="sha384-smHYKdLADwkXOn1EmN1qk/HfnUcbVRZyYmZ4qpPea6sjB/pTJ0euyQp0Mk8ck+5T" crossorigin="anonymous"></script>
+
+</body>
+</html>
diff --git a/foo.sh b/foo.sh
new file mode 100755
index 0000000..39a740a
--- /dev/null
+++ b/foo.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+TESTFILE=$1
+
+GCC_BUILD_DIR=~/more-coding/gcc-git-analysis-output/build/gcc
+
+#echo "TESTFILE: $TESTFILE"
+
+( $GCC_BUILD_DIR/xgcc \
+    -B $GCC_BUILD_DIR \
+    -S $TESTFILE \
+    --analyzer \
+  2>&1) | grep "internal compiler error: in operator\\[\\], at vec.h:845" || exit 1
diff --git a/gcc/analyzer/Make-lang.in b/gcc/analyzer/Make-lang.in
new file mode 100644
index 0000000..f742b9d
--- /dev/null
+++ b/gcc/analyzer/Make-lang.in
@@ -0,0 +1,168 @@
+# 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/digraph.o \
+	analyzer/engine.o \
+	analyzer/graphviz.o \
+	analyzer/hardcoded-state-machine.o \
+	analyzer/hardcoded-state-machine-malloc.o \
+	analyzer/hardcoded-state-machine-file.o \
+	analyzer/hardcoded-state-machine-pattern-test.o \
+	analyzer/hardcoded-state-machine-sensitive.o \
+	analyzer/hardcoded-state-machine-taint.o \
+	analyzer/impl-3.o \
+	analyzer/impl-4.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..179d3a3
--- /dev/null
+++ b/gcc/analyzer/analyzer-logging.cc
@@ -0,0 +1,221 @@
+/* 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 "pretty-print.h" /* for print_version */
+#include "diagnostic.h"
+#include "tree-diagnostic.h"
+
+#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 */,
+		const pretty_printer &reference_pp) :
+  m_refcount (0),
+  m_f_out (f_out),
+  m_indent_level (0),
+  m_log_refcount_changes (false),
+  m_pp (reference_pp.clone ())
+{
+  pp_show_color (m_pp) = 0;
+  pp_buffer (m_pp)->stream = f_out;
+
+  /* %qE in logs for SSA_NAMEs should show the ssa names, rather than
+     trying to prettify things by showing the underlying var.  */
+  pp_format_decoder (m_pp) = default_tree_printer;
+
+  /* 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);
+  delete m_pp;
+}
+
+/* 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)
+{
+  text_info text;
+  text.format_spec = fmt;
+  text.args_ptr = ap;
+  text.err_no = 0;
+  pp_format (m_pp, &text);
+  pp_output_formatted_text (m_pp);
+  pp_flush (m_pp);
+  pp_clear_output_area (m_pp);
+}
+
+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..e09ed8e8
--- /dev/null
+++ b/gcc/analyzer/analyzer-logging.h
@@ -0,0 +1,229 @@
+/* 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
+
+#include "diagnostic-core.h"
+
+// 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, const pretty_printer &reference_pp);
+  ~logger ();
+
+  void incref (const char *reason);
+  void decref (const char *reason);
+
+  void log (const char *fmt, ...)
+    ATTRIBUTE_GCC_DIAG(2, 3);
+  void log_va (const char *fmt, va_list *ap)
+    ATTRIBUTE_GCC_DIAG(2, 0);
+  void start_log_line ();
+  void log_partial (const char *fmt, ...)
+    ATTRIBUTE_GCC_DIAG(2, 3);
+  void log_va_partial (const char *fmt, va_list *ap)
+    ATTRIBUTE_GCC_DIAG(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)
+    ATTRIBUTE_GCC_DIAG(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;
+  pretty_printer *m_pp;
+};
+
+/* 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, ...)
+    ATTRIBUTE_GCC_DIAG(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
+    ATTRIBUTE_GCC_DIAG(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)
+
+#define JIT_LOG_FUNC_3(LOGGER, FMT, A0, A1, A2)	\
+  log_scope s (LOGGER, __func__, FMT, A0, A1, A2)
+
+#define JIT_LOG_FUNC_4(LOGGER, FMT, A0, A1, A2, A3) \
+  log_scope s (LOGGER, __func__, FMT, A0, A1, A2, A3)
+
+
+#endif /* JIT_LOGGING_H */
diff --git a/gcc/analyzer/analyzer-pass.cc b/gcc/analyzer/analyzer-pass.cc
new file mode 100644
index 0000000..be676d4
--- /dev/null
+++ b/gcc/analyzer/analyzer-pass.cc
@@ -0,0 +1,139 @@
+/* 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 *)
+{
+  switch (flag_analyzer_impl)
+    {
+    case 2:
+      run_checkers_impl_2 ();
+      break;
+    case 3:
+      run_checkers_impl_3 ();
+      break;
+    default:
+    case 4:
+      run_checkers_impl_4 ();
+      break;
+    }
+  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..17b69bd
--- /dev/null
+++ b/gcc/analyzer/analyzer-plugin.cc
@@ -0,0 +1,67 @@
+/* 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 ();
+  analyzer_engine_cc_tests ();
+  analyzer_impl_4_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..90963a4
--- /dev/null
+++ b/gcc/analyzer/analyzer.cc
@@ -0,0 +1,129 @@
+/* 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?  */
+
+bool
+is_named_call_p (const gcall *call, const char *funcname)
+{
+  gcc_assert (funcname);
+
+  tree fndecl = gimple_call_fndecl (call);
+  if (!fndecl)
+    return false;
+
+  return 0 == strcmp (IDENTIFIER_POINTER (DECL_NAME (fndecl)), funcname);
+}
+
+/* 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);
+
+  if (!is_named_call_p (call, funcname))
+    return false;
+
+  if (gimple_call_num_args (call) != num_args)
+    return false;
+
+  return true;
+}
+
+// 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->clone ();
+  pp_clear_output_area (pp);
+
+  text_info ti;
+  rich_location rich_loc (line_table, UNKNOWN_LOCATION);
+
+  va_list ap;
+
+  va_start (ap, fmt);
+
+  ti.format_spec = _(fmt);
+  ti.args_ptr = &ap;
+  ti.err_no = 0;
+  ti.x_data = NULL;
+  ti.m_richloc = &rich_loc;
+
+  pp_show_color (pp) = 0;
+  pp_format (pp, &ti);
+  pp_output_formatted_text (pp);
+
+  va_end (ap);
+
+  char *result = xstrdup (pp_formatted_text (pp));
+
+  delete 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..32a6ab9
--- /dev/null
+++ b/gcc/analyzer/analyzer.h
@@ -0,0 +1,62 @@
+/* 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);
+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);
+
+/* An RAII-style class for pushing/popping cfun within a scope.
+   Doing so ensures we get "In function " announcements
+   from the diagnostics subsystem.  */
+
+class auto_cfun
+{
+public:
+  auto_cfun (function *fun) { push_cfun (fun); }
+  ~auto_cfun () { pop_cfun (); }
+};
+
+#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 ();
+extern void analyzer_engine_cc_tests ();
+extern void analyzer_impl_4_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..249e9de
--- /dev/null
+++ b/gcc/analyzer/constraints.cc
@@ -0,0 +1,1987 @@
+/* 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 (m_tree == NULL_TREE)
+    return "NULL";
+  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 (), m_escaped (false), m_extra_state (NULL)
+{
+}
+
+/* FIXME.  */
+
+equiv_class::equiv_class (const equiv_class &other)
+: m_constant (other.m_constant),
+  m_vars (),
+  m_escaped (other.m_escaped),
+  m_extra_state (other.m_extra_state ? other.m_extra_state->clone () : NULL)
+{
+  // FIXME: copy-ctor for auto_vec!
+  int i;
+  path_var *v;
+  FOR_EACH_VEC_ELT (other.m_vars, i, v)
+    m_vars.safe_push (*v);
+}
+
+/* FIXME.  */
+
+equiv_class::~equiv_class ()
+{
+  delete m_extra_state;
+}
+
+/* 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);
+    }
+  if (m_escaped)
+    result += " (escaped)";
+  if (m_extra_state)
+    result += m_extra_state->to_str ();
+  return result + "}";
+}
+
+/* FIXME.  */
+
+hashval_t
+equiv_class::hash () const
+{
+  inchash::hash hstate;
+  int i;
+  path_var *pv;
+
+  inchash::add_expr (m_constant, hstate);
+  FOR_EACH_VEC_ELT (m_vars, i, pv)
+    inchash::add_path_var (*pv, hstate);
+  hstate.add_flag (m_escaped);
+  hstate.commit_flag ();
+  if (m_extra_state)
+    hstate.merge_hash (m_extra_state->hash ());
+  return hstate.end ();
+}
+
+/* FIXME.  */
+
+bool
+equiv_class::operator== (const equiv_class &other)
+{
+  if (m_constant != other.m_constant)
+    return false; // TODO: use tree equality here
+
+  int i;
+  path_var *pv;
+  FOR_EACH_VEC_ELT (m_vars, i, pv)
+    if (! (*pv == other.m_vars[i]))
+      return false;
+  if (m_escaped != other.m_escaped)
+    return false;
+
+  if ((m_extra_state != NULL) != (other.m_extra_state != NULL))
+    return false;
+
+  if (m_extra_state != NULL)
+    if (!(*m_extra_state == *other.m_extra_state))
+      return false;
+
+  return true;
+}
+
+/* FIXME.  */
+
+void
+equiv_class::add (path_var v)
+{
+  if (CONSTANT_CLASS_P (v.m_tree))
+    {
+      gcc_assert (m_constant == NULL_TREE);
+      m_constant = v.m_tree;
+    }
+  else
+    {
+      m_vars.safe_push (v);
+
+      if (DECL_P (v.m_tree)
+	  && is_global_var (v.m_tree))
+	m_escaped = true;
+    }
+}
+
+/* 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.  */
+
+int
+equiv_class::get_min_stack_depth () const
+{
+  int result = INT_MAX;
+
+  int i;
+  path_var *iv;
+  FOR_EACH_VEC_ELT (m_vars, i, iv)
+    result = std::min (result, iv->m_stack_depth);
+  return result;
+}
+
+/* FIXME.  */
+
+void
+equiv_class::purge_stack_frame (int callee_stack_depth)
+{
+  unsigned ix, ix2;
+  path_var *iv;
+  VEC_ORDERED_REMOVE_IF (m_vars, ix, ix2, iv,
+			 iv->m_stack_depth >= callee_stack_depth);
+  // FIXME: doesn't need to preserve order
+}
+
+/* Get a representative member of this class, for handling cases
+   where the IDs can change mid-traversal.  */
+
+path_var
+equiv_class::get_representative () const
+{
+  if (m_constant)
+    return path_var (m_constant, 0);
+  else
+    {
+      gcc_assert (m_vars.length () > 0);
+      return m_vars[0];
+    }
+}
+
+/* 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 "<=";
+    }
+}
+
+/* Given "lhs C_OP rhs", determine "lhs T_OP rhs".
+
+   For example, given "x < y", then "x > y" is false.  */
+
+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.  */
+
+hashval_t
+constraint::hash () const
+{
+  inchash::hash hstate;
+  hstate.add_int (m_lhs.m_idx);
+  hstate.add_int (m_op);
+  hstate.add_int (m_rhs.m_idx);
+  return hstate.end ();
+}
+
+/* FIXME.  */
+
+bool
+constraint::operator== (const constraint &other) const
+{
+  if (m_lhs != other.m_lhs)
+    return false;
+  if (m_op != other.m_op)
+    return false;
+  if (m_rhs != other.m_rhs)
+    return false;
+  return true;
+}
+
+/* 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)
+: m_stack_depth (other.m_stack_depth)
+{
+  // 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.  */
+
+path_state&
+path_state::operator= (const path_state &other)
+{
+  m_stack_depth = other.m_stack_depth;
+
+  gcc_assert (m_equiv_classes.length () == 0);
+  gcc_assert (m_constraints.length () == 0);
+
+  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);
+
+  return *this;
+}
+
+/* FIXME.  */
+
+hashval_t
+path_state::hash () const
+{
+  inchash::hash hstate;
+  int i;
+  equiv_class *ec;
+  constraint *c;
+
+  hstate.add_int (m_stack_depth);
+  FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+    hstate.merge_hash (ec->hash ());
+  FOR_EACH_VEC_ELT (m_constraints, i, c)
+    hstate.merge_hash (c->hash ());
+  return hstate.end ();
+}
+
+/* FIXME.  */
+
+bool
+path_state::operator== (const path_state &other) const
+{
+  if (m_stack_depth != other.m_stack_depth)
+    return false;
+  if (m_equiv_classes.length () != other.m_equiv_classes.length ())
+    return false;
+
+  int i;
+  equiv_class *ec;
+
+  FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+    if (!(*ec == *other.m_equiv_classes[i]))
+      return false;
+
+  constraint *c;
+
+  FOR_EACH_VEC_ELT (m_constraints, i, c)
+    if (!(*c == other.m_constraints[i]))
+      return false;
+
+  return true;
+}
+
+/* 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 += "}@";
+  result += std::to_string (m_stack_depth);
+  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 (edge.m_kind == SUPEREDGE_CALL)
+    // FIXME: use as_a<>
+    return apply_constraints_for_call (*edge.dyn_cast_call_superedge ());
+
+  if (edge.m_kind == SUPEREDGE_RETURN)
+    // FIXME: use as_a<>
+    return apply_constraints_for_return (*edge.dyn_cast_return_superedge ());
+
+  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_call (const call_superedge& edge)
+{
+  /* Map args to parms at next stack_depth, incrementing stack depth.  */
+  int caller_stack_depth = m_stack_depth;
+  int callee_stack_depth = ++m_stack_depth;
+
+  tree callee = edge.get_callee_decl ();
+  gcall *call_stmt = edge.get_call_stmt ();
+
+  int i = 0;
+  for (tree parm = DECL_ARGUMENTS (callee); parm;
+       parm = DECL_CHAIN (parm), ++i)
+    {
+      tree parm_default_ssa = ssa_default_def (edge.m_dest->m_fun, parm);
+      if (parm_default_ssa)
+	add_constraint (path_var (parm_default_ssa, callee_stack_depth),
+			EQ_EXPR,
+			path_var (gimple_call_arg (call_stmt, i),
+				  caller_stack_depth));
+    }
+  return true;
+}
+
+/* FIXME.  */
+
+bool
+path_state::apply_constraints_for_return (const return_superedge&)
+{
+  pop_stack_frame ();
+  return true;
+}
+
+/* Decrement the stack depth, purging vars at the old stack depth (and beyond).
+   This is broken out as a subroutine so it can be unit-tested.  */
+
+void
+path_state::pop_stack_frame ()
+{
+  int callee_stack_depth = m_stack_depth--;
+
+  int i;
+  equiv_class *ec;
+  FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+    {
+      ec->purge_stack_frame (callee_stack_depth);
+      /* This can lead to empty equiv classes (possibly without
+	 constants).  Perhaps we ought to purge them?  */
+    }
+}
+
+
+/* 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 */);
+  return add_constraint (path_var (lhs, m_stack_depth),
+			 op,
+			 path_var (rhs, m_stack_depth));
+}
+
+/* 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), m_stack_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), m_stack_depth);
+  path_var upper_bound (CASE_HIGH (case_label), m_stack_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), m_stack_depth);
+	  path_var other_upper_bound (CASE_HIGH (other_label), m_stack_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);
+  return add_constraint (lhs_ec_id, op,rhs_ec_id);
+}
+
+/* FIXME.  */
+
+bool
+path_state::add_constraint (equiv_class_id lhs_ec_id,
+			    enum tree_code op,
+			    equiv_class_id rhs_ec_id)
+{
+  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)
+	  {
+	    gcc_assert (lhs_ec_obj.m_constant == NULL);
+	    lhs_ec_obj.m_constant = rhs_ec_obj.m_constant;
+	  }
+
+	/* Merge any extra state.  */
+	extra_ec_state *lhs_extra_state = lhs_ec_obj.m_extra_state;
+	extra_ec_state *rhs_extra_state = rhs_ec_obj.m_extra_state;
+
+	/* How to merge two state objects?  */
+	gcc_assert (!(lhs_extra_state && rhs_extra_state));
+
+	if (lhs_extra_state == NULL && rhs_extra_state)
+	  lhs_ec_obj.m_extra_state = rhs_extra_state->clone ();
+
+	/* Drop rhs equivalence class, overwriting it with the
+	   final ec (which might be the same one).  */
+	equiv_class_id final_ec_id = m_equiv_classes.length () - 1;
+	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:
+      add_constraint_internal (rhs_ec_id, CONSTRAINT_LE, lhs_ec_id);
+      break;
+    case LE_EXPR:
+      add_constraint_internal (lhs_ec_id, CONSTRAINT_LE, rhs_ec_id);
+      break;
+    case NE_EXPR:
+      add_constraint_internal (lhs_ec_id, CONSTRAINT_NE, rhs_ec_id);
+      break;
+    case GT_EXPR:
+      add_constraint_internal (rhs_ec_id, CONSTRAINT_LT, lhs_ec_id);
+      break;
+    case LT_EXPR:
+      add_constraint_internal (lhs_ec_id, CONSTRAINT_LT, rhs_ec_id);
+      break;
+    default:
+      /* do nothing.  */
+      break;
+    }
+  return true;
+}
+
+/* FIXME.  */
+
+void
+bound::ensure_closed (bool is_upper)
+{
+  if (!m_closed)
+    {
+      /* Offset by 1 in the appropriate direction.
+	 For example, convert 3 < x into 4 <= x,
+	 and convert x < 5 into x <= 4.  */
+      gcc_assert (CONSTANT_CLASS_P (m_constant));
+      m_constant = fold_build2 (is_upper ? MINUS_EXPR : PLUS_EXPR,
+				TREE_TYPE (m_constant),
+				m_constant, integer_one_node);
+      gcc_assert (CONSTANT_CLASS_P (m_constant));
+      m_closed = true;
+    }
+}
+
+/* FIXME.  */
+
+std::string
+range::to_str () const
+{
+  std::string result = expr_to_string (m_lower_bound.m_constant);
+  result += " <";
+  if (m_lower_bound.m_closed)
+    result += "=";
+  result += " x <";
+  if (m_upper_bound.m_closed)
+    result += "=";
+  result += " ";
+  result += expr_to_string (m_upper_bound.m_constant);
+  return result;  // TODO
+}
+
+/* FIXME.  */
+
+bool
+range::constrained_to_single_element (tree *out)
+{
+  if (!INTEGRAL_TYPE_P (TREE_TYPE (m_lower_bound.m_constant)))
+    return false;
+  if (!INTEGRAL_TYPE_P (TREE_TYPE (m_upper_bound.m_constant)))
+    return false;
+  // FIXME: also check the endpoint
+
+  /* Convert any open bounds to closed bounds.  */
+  m_lower_bound.ensure_closed (false);
+  m_upper_bound.ensure_closed (true);
+
+  // Are they equal?
+  tree comparison
+    = fold_build2 (EQ_EXPR, boolean_type_node,
+		   m_lower_bound.m_constant,
+		   m_upper_bound.m_constant);
+  if (comparison == boolean_true_node)
+    {
+      *out = m_lower_bound.m_constant;
+      return true;
+    }
+  else
+    return false;
+}
+
+
+/* FIXME.  */
+
+void
+path_state::add_constraint_internal (equiv_class_id lhs_id,
+				     enum constraint_op c_op,
+				     equiv_class_id rhs_id)
+{
+  /* Add the constraint.  */
+  m_constraints.safe_push (constraint (lhs_id, c_op, rhs_id));
+
+  if (c_op != CONSTRAINT_NE)
+    {
+      /* The following can potentially add EQ_EXPR facts, which could lead
+	 to ECs being merged, which would change the meaning of the EC IDs.
+	 Hence we need to do this via path vars.  */
+      path_var lhs = lhs_id.get_obj (*this).get_representative ();
+      path_var rhs = rhs_id.get_obj (*this).get_representative ();
+
+      /* We have LHS </<= RHS */
+
+      /* Handle transitivity of ordering by adding additional constraints
+	 based on what we already knew.
+
+	 So if we have already have:
+	   (a < b)
+	   (c < d)
+	 Then adding:
+	   (b < c)
+	 will also add:
+	   (a < c)
+	   (b < d)
+	 We need to recurse to ensure we also add:
+	   (a < d).
+	 We call the checked add_constraint to avoid adding constraints
+	 that are already present.  Doing so also ensures termination
+	 in the case of cycles.
+
+	 We also check for single-element ranges, adding EQ_EXPR facts
+	 where we discover them.  For example 3 < x < 5 implies
+	 that x == 4 (if x is an integer).  */
+      for (unsigned i = 0; i < m_constraints.length (); i++)
+	{
+	  const constraint *other = &m_constraints[i];
+	  if (other->is_ordering_p ())
+	    {
+	      /* Refresh the EC IDs, in case any mergers have happened.  */
+	      lhs_id = get_or_add_equiv_class (lhs);
+	      rhs_id = get_or_add_equiv_class (rhs);
+
+	      tree lhs_const = lhs_id.get_obj (*this).m_constant;
+	      tree rhs_const = rhs_id.get_obj (*this).m_constant;
+	      tree other_lhs_const
+		= other->m_lhs.get_obj (*this).m_constant;
+	      tree other_rhs_const
+		= other->m_rhs.get_obj (*this).m_constant;
+
+	      /* We have "LHS </<= RHS" and "other.lhs </<= other.rhs".  */
+
+	      /* If we have LHS </<= RHS and RHS </<= LHS, then we have a
+		 cycle.  */
+	      if (rhs_id == other->m_lhs
+		  && other->m_rhs == lhs_id)
+		{
+		  /* We must have equality for this to be possible.  */
+		  gcc_assert (c_op == CONSTRAINT_LE
+			      && other->m_op == CONSTRAINT_LE);
+		  add_constraint (lhs_id, EQ_EXPR, rhs_id);
+		  /* Adding an equality will merge the two ECs and potentially
+		     reorganize the constraints.  Stop iterating.  */
+		  return;
+		}
+	      /* Otherwise, check for transitivity.  */
+	      if (rhs_id == other->m_lhs)
+		{
+		  /* With RHS == other.lhs, we have:
+		     "LHS </<= (RHS, other.lhs) </<= other.rhs"
+		     and thus this implies "LHS </<= other.rhs".  */
+
+		  /* Do we have a tightly-constrained range?  */
+		  if (lhs_const
+		      && !rhs_const
+		      && INTEGRAL_TYPE_P (TREE_TYPE (rhs.m_tree))
+		      && other_rhs_const)
+		    {
+		      range r (bound (lhs_const, c_op == CONSTRAINT_LE),
+			       bound (other_rhs_const,
+				      other->m_op == CONSTRAINT_LE));
+		      tree constant;
+		      if (r.constrained_to_single_element (&constant))
+			{
+			  add_constraint
+			    (rhs_id, EQ_EXPR,
+			     get_or_add_equiv_class (path_var (constant, 0)));
+			  return;
+			}
+		    }
+		  /* Otherwise, add the constraint implied by transitivity.  */
+		  enum tree_code new_op
+		    = ((c_op == CONSTRAINT_LE && other->m_op == CONSTRAINT_LE)
+		       ? LE_EXPR : LT_EXPR);
+		  add_constraint (lhs_id, new_op, other->m_rhs);
+		}
+	      else if (other->m_rhs == lhs_id)
+		{
+		  /* With other.rhs == LHS, we have:
+		     "other.lhs </<= (other.rhs, LHS) </<= RHS"
+		     and thus this implies "other.lhs </<= RHS".  */
+
+		  /* Do we have a tightly-constrained range?  */
+		  if (other_lhs_const
+		      && !lhs_const
+		      && INTEGRAL_TYPE_P (TREE_TYPE (lhs.m_tree))
+		      && rhs_const)
+		    {
+		      range r (bound (other_lhs_const,
+				      other->m_op == CONSTRAINT_LE),
+			       bound (rhs_const,
+				      c_op == CONSTRAINT_LE));
+		      tree constant;
+		      if (r.constrained_to_single_element (&constant))
+			{
+			  add_constraint
+			    (lhs_id, EQ_EXPR,
+			     get_or_add_equiv_class (path_var (constant, 0)));
+			  return;
+			}
+		    }
+		  /* Otherwise, add the constraint implied by transitivity.  */
+		  enum tree_code new_op
+		    = ((c_op == CONSTRAINT_LE && other->m_op == CONSTRAINT_LE)
+		       ? LE_EXPR : LT_EXPR);
+		  add_constraint (other->m_lhs, new_op, rhs_id);
+		}
+	    }
+	}
+    }
+}
+
+/* FIXME.  */
+
+bool
+path_state::get_equiv_class (path_var pv, equiv_class_id *out) const
+{
+  gcc_assert (pv.m_tree);
+
+  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
+    {
+      /* FIXME: probably should be a map, rather than these lookups.  */
+      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);
+
+  equiv_class_id new_id (m_equiv_classes.length () - 1);
+
+  if (CONSTANT_CLASS_P (pv.m_tree))
+    {
+      /* If we have a new EC for a constant, add constraints comparing this
+	 to other constants we may have (so that we accumulate the transitive
+	 closure of all constraints on constants as the constants are
+	 added).  */
+      for (equiv_class_id other_id (0); other_id.m_idx < new_id.m_idx;
+	   other_id.m_idx++)
+	{
+	  const equiv_class &other_ec = other_id.get_obj (*this);
+	  if (other_ec.m_constant)
+	    {
+	      /* If we have two ECs, both with constants, the constants must be
+		 non-equal (or they would be in the same EC).
+		 Determine the direction of the inequality, and record that
+		 fact.  */
+	      tree lt
+		= fold_build2 (LT_EXPR, boolean_type_node,
+			       new_ec->m_constant, other_ec.m_constant);
+	      //gcc_assert (lt == boolean_true_node || lt == boolean_false_node);
+	      // not true for int vs float comparisons
+	      if (lt == boolean_true_node)
+		add_constraint_internal (new_id, CONSTRAINT_LT, other_id);
+	      else if (lt == boolean_false_node)
+		add_constraint_internal (other_id, CONSTRAINT_LT, new_id);
+	      /* Refresh new_id, in case ECs were merged.  PV should always
+		 be present by now, so this should never lead to a
+		 recursion.  */
+	      new_id = get_or_add_equiv_class (pv);
+	    }
+	}
+    }
+
+  return new_id;
+}
+
+/* FIXME.  */
+/* For implementing "__analyzer_eval".  */
+
+tristate
+path_state::dump_condition (tree lhs, enum tree_code op, tree rhs)
+{
+  equiv_class_id lhs_ec_id
+    = get_or_add_equiv_class (path_var (lhs, m_stack_depth));
+  equiv_class_id rhs_ec_id
+    = get_or_add_equiv_class (path_var (rhs, m_stack_depth));
+
+  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;
+	}
+    }
+
+  return tristate (tristate::TS_UNKNOWN);
+}
+
+/* Mark EC_ID as having escaped; walk other ECs that haven't yet been marked
+   as escaped, and determine if they've now escaped, if so, mark them,
+   recursively.
+   e.g. if we know "ptr" escapes, then "ptr->foo" escapes.  */
+
+void
+path_state::recursively_mark_as_escaping (equiv_class_id ec_id)
+{
+  equiv_class &ec = ec_id.get_obj (*this);
+  if (ec.m_escaped)
+    {
+      /* Nothing to do.  */
+      return;
+    }
+
+  ec.m_escaped = true;
+
+  /* Look for other ECs that haven't yet been marked as escaped.  */
+  int other_ec_idx;
+  equiv_class *other_ec;
+  FOR_EACH_VEC_ELT (m_equiv_classes, other_ec_idx, other_ec)
+    {
+      if (other_ec->m_escaped)
+	continue;
+
+      int var_idx;
+      path_var *v;
+      FOR_EACH_VEC_ELT (other_ec->m_vars, var_idx, v)
+	{
+	  gcc_assert (!CONSTANT_CLASS_P (v->m_tree));
+	  if (calc_escaped_p (*v))
+	    {
+	      recursively_mark_as_escaping (other_ec_idx);
+	      break;
+	    }
+	}
+    }
+
+  // TODO: selftests
+  // TODO: selftest involving a cycle
+}
+
+/* Determine if PV (or something that points to it) has been marked as
+   having escaped.  */
+
+bool
+path_state::calc_escaped_p (path_var pv)
+{
+  switch (TREE_CODE (pv.m_tree))
+    {
+    case COMPONENT_REF:
+    case MEM_REF:
+      return calc_escaped_p (path_var (TREE_OPERAND (pv.m_tree, 0),
+				       pv.m_stack_depth));
+    case SSA_NAME:
+      {
+	equiv_class_id ec_id (-1);
+	if (get_equiv_class (pv, &ec_id))
+	  return ec_id.get_obj (*this).m_escaped;
+      }
+      break;
+    default:
+      return false;
+    }
+
+  return false;
+}
+
+// FIXME:
+void
+debug (const path_state &ps)
+{
+  fprintf (stderr, "%s\n", ps.to_str ().c_str ());
+}
+
+void
+inchash::add_path_var (path_var pv, hash &hstate)
+{
+  inchash::add_expr (pv.m_tree, hstate);
+  hstate.add_int (pv.m_stack_depth);
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Attempt to add the constraint (LHS OP RHS) to PS.
+   Verify that PS remains satisfiable.  */
+
+#define ADD_SAT_CONSTRAINT(PS, LHS, OP, RHS)				\
+  SELFTEST_BEGIN_STMT							\
+    bool sat = (PS).add_constraint (LHS, OP, RHS);			\
+    ASSERT_TRUE (sat);							\
+  SELFTEST_END_STMT
+
+/* Attempt to add the constraint (LHS OP RHS) to PS.
+   Verify that the result is not satisfiable.  */
+
+#define ADD_UNSAT_CONSTRAINT(PS, LHS, OP, RHS)				\
+  SELFTEST_BEGIN_STMT							\
+    bool sat = (PS).add_constraint (LHS, OP, RHS);			\
+    ASSERT_FALSE (sat);							\
+  SELFTEST_END_STMT
+
+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);
+
+    ADD_SAT_CONSTRAINT (ps, x, EQ_EXPR, y);
+
+    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, then y == z  */
+  {
+    path_state ps;
+    ASSERT_CONDITION_UNKNOWN (ps, x, EQ_EXPR, y);
+
+    ADD_SAT_CONSTRAINT (ps, x, EQ_EXPR, y);
+    ADD_SAT_CONSTRAINT (ps, y, EQ_EXPR, z);
+
+    ASSERT_CONDITION_TRUE (ps, x, EQ_EXPR, z);
+    ASSERT_CONDITION_TRUE (ps, x, LE_EXPR, z);
+    ASSERT_CONDITION_TRUE (ps, x, GE_EXPR, z);
+    ASSERT_CONDITION_FALSE (ps, x, NE_EXPR, z);
+    ASSERT_CONDITION_FALSE (ps, x, LT_EXPR, z);
+    ASSERT_CONDITION_FALSE (ps, x, GT_EXPR, z);
+  }
+
+  /* x != y.  */
+  {
+    path_state ps;
+
+    ADD_SAT_CONSTRAINT (ps, x, NE_EXPR, y);
+
+    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;
+
+    ADD_SAT_CONSTRAINT (ps, x, LT_EXPR, y);
+
+    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;
+
+    ADD_SAT_CONSTRAINT (ps, x, LE_EXPR, y);
+
+    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;
+
+    ADD_SAT_CONSTRAINT (ps, x, GT_EXPR, y);
+
+    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;
+
+    ADD_SAT_CONSTRAINT (ps, x, GE_EXPR, y);
+
+    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;
+    ADD_SAT_CONSTRAINT (ps, x, EQ_EXPR, int_0);
+    ADD_SAT_CONSTRAINT (ps, y, EQ_EXPR, int_42);
+
+    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;
+    ADD_SAT_CONSTRAINT (ps, x, EQ_EXPR, y);
+    ADD_UNSAT_CONSTRAINT (ps, x, NE_EXPR, y);
+  }
+
+  /* x == 0 then x == 42.  */
+  {
+    path_state ps;
+    ADD_SAT_CONSTRAINT (ps, x, EQ_EXPR, int_0);
+    ADD_UNSAT_CONSTRAINT (ps, x, EQ_EXPR, int_42);
+  }
+
+  /* x == 0 then x != 0.  */
+  {
+    path_state ps;
+    ADD_SAT_CONSTRAINT (ps, x, EQ_EXPR, int_0);
+    ADD_UNSAT_CONSTRAINT (ps, x, NE_EXPR, int_0);
+  }
+
+  /* x == 0 then x > 0.  */
+  {
+    path_state ps;
+    ADD_SAT_CONSTRAINT (ps, x, EQ_EXPR, int_0);
+    ADD_UNSAT_CONSTRAINT (ps, x, GT_EXPR, int_0);
+  }
+
+  /* x != y && x == y.  */
+  {
+    path_state ps;
+    ADD_SAT_CONSTRAINT (ps, x, NE_EXPR, y);
+    ADD_UNSAT_CONSTRAINT (ps, x, EQ_EXPR, y);
+  }
+
+  /* x <= y && x > y.  */
+  {
+    path_state ps;
+    ADD_SAT_CONSTRAINT (ps, x, LE_EXPR, y);
+    ADD_UNSAT_CONSTRAINT (ps, x, GT_EXPR, y);
+  }
+
+  // etc
+}
+
+/* FIXME.  */
+
+static void
+test_transitivity ()
+{
+  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 a (build_decl (UNKNOWN_LOCATION, PARM_DECL,
+			  get_identifier ("a"),
+			  integer_type_node), 0);
+  path_var b (build_decl (UNKNOWN_LOCATION, PARM_DECL,
+			  get_identifier ("b"),
+			  integer_type_node), 0);
+  path_var c (build_decl (UNKNOWN_LOCATION, PARM_DECL,
+			  get_identifier ("c"),
+			  integer_type_node), 0);
+  path_var d (build_decl (UNKNOWN_LOCATION, PARM_DECL,
+			  get_identifier ("d"),
+			  integer_type_node), 0);
+
+  /* a == b, then c == d, then c == b.  */
+  {
+    path_state ps;
+    ASSERT_CONDITION_UNKNOWN (ps, a, EQ_EXPR, b);
+    ASSERT_CONDITION_UNKNOWN (ps, b, EQ_EXPR, c);
+    ASSERT_CONDITION_UNKNOWN (ps, c, EQ_EXPR, d);
+    ASSERT_CONDITION_UNKNOWN (ps, a, EQ_EXPR, d);
+
+    ADD_SAT_CONSTRAINT (ps, a, EQ_EXPR, b);
+    ASSERT_CONDITION_TRUE (ps, a, EQ_EXPR, b);
+
+    ADD_SAT_CONSTRAINT (ps, c, EQ_EXPR, d);
+    ASSERT_CONDITION_TRUE (ps, c, EQ_EXPR, d);
+    ASSERT_CONDITION_UNKNOWN (ps, a, EQ_EXPR, d);
+
+    ADD_SAT_CONSTRAINT (ps, c, EQ_EXPR, b);
+    ASSERT_CONDITION_TRUE (ps, c, EQ_EXPR, b);
+    ASSERT_CONDITION_TRUE (ps, a, EQ_EXPR, d);
+  }
+
+  /* Transitivity: "a < b", "b < c" should imply "a < c".  */
+  {
+    path_state ps;
+    ADD_SAT_CONSTRAINT (ps, a, LT_EXPR, b);
+    ADD_SAT_CONSTRAINT (ps, b, LT_EXPR, c);
+
+    ASSERT_CONDITION_TRUE (ps, a, LT_EXPR, c);
+    ASSERT_CONDITION_FALSE (ps, a, EQ_EXPR, c);
+  }
+
+  /* Transitivity: "a <= b", "b < c" should imply "a < c".  */
+  {
+    path_state ps;
+    ADD_SAT_CONSTRAINT (ps, a, LE_EXPR, b);
+    ADD_SAT_CONSTRAINT (ps, b, LT_EXPR, c);
+
+    ASSERT_CONDITION_TRUE (ps, a, LT_EXPR, c);
+    ASSERT_CONDITION_FALSE (ps, a, EQ_EXPR, c);
+  }
+
+  /* Transitivity: "a <= b", "b <= c" should imply "a <= c".  */
+  {
+    path_state ps;
+    ADD_SAT_CONSTRAINT (ps, a, LE_EXPR, b);
+    ADD_SAT_CONSTRAINT (ps, b, LE_EXPR, c);
+
+    ASSERT_CONDITION_TRUE (ps, a, LE_EXPR, c);
+    ASSERT_CONDITION_UNKNOWN (ps, a, EQ_EXPR, c);
+  }
+
+  /* Transitivity: "a > b", "b > c" should imply "a > c".  */
+  {
+    path_state ps;
+    ADD_SAT_CONSTRAINT (ps, a, GT_EXPR, b);
+    ADD_SAT_CONSTRAINT (ps, b, GT_EXPR, c);
+
+    ASSERT_CONDITION_TRUE (ps, a, GT_EXPR, c);
+    ASSERT_CONDITION_FALSE (ps, a, EQ_EXPR, c);
+  }
+
+  /* Transitivity: "a >= b", "b > c" should imply " a > c".  */
+  {
+    path_state ps;
+    ADD_SAT_CONSTRAINT (ps, a, GE_EXPR, b);
+    ADD_SAT_CONSTRAINT (ps, b, GT_EXPR, c);
+
+    ASSERT_CONDITION_TRUE (ps, a, GT_EXPR, c);
+    ASSERT_CONDITION_FALSE (ps, a, EQ_EXPR, c);
+  }
+
+  /* Transitivity: "a >= b", "b >= c" should imply "a >= c".  */
+  {
+    path_state ps;
+    ADD_SAT_CONSTRAINT (ps, a, GE_EXPR, b);
+    ADD_SAT_CONSTRAINT (ps, b, GE_EXPR, c);
+
+    ASSERT_CONDITION_TRUE (ps, a, GE_EXPR, c);
+    ASSERT_CONDITION_UNKNOWN (ps, a, EQ_EXPR, c);
+  }
+
+  /* Transitivity: "(a < b)", "(c < d)", "(b < c)" should
+     imply the easy cases:
+       (a < c)
+       (b < d)
+     but also that:
+       (a < d).  */
+  {
+    path_state ps;
+    ADD_SAT_CONSTRAINT (ps, a, LT_EXPR, b);
+    ADD_SAT_CONSTRAINT (ps, c, LT_EXPR, d);
+    ADD_SAT_CONSTRAINT (ps, b, LT_EXPR, c);
+
+    ASSERT_CONDITION_TRUE (ps, a, LT_EXPR, c);
+    ASSERT_CONDITION_TRUE (ps, b, LT_EXPR, d);
+    ASSERT_CONDITION_TRUE (ps, a, LT_EXPR, d);
+  }
+
+  /* Transitivity: "a >= b", "b >= a" should imply that a == b.  */
+  {
+    path_state ps;
+    ADD_SAT_CONSTRAINT (ps, a, GE_EXPR, b);
+    ADD_SAT_CONSTRAINT (ps, b, GE_EXPR, a);
+
+    // TODO:
+    ASSERT_CONDITION_TRUE (ps, a, EQ_EXPR, b);
+  }
+
+  /* Transitivity: "a >= b", "b > a" should be impossible.  */
+  {
+    path_state ps;
+    ADD_SAT_CONSTRAINT (ps, a, GE_EXPR, b);
+    ADD_UNSAT_CONSTRAINT (ps, b, GT_EXPR, a);
+  }
+
+  /* Transitivity: "a >= b", "b >= c", "c >= a" should imply
+     that a == b == c.  */
+  {
+    path_state ps;
+    ADD_SAT_CONSTRAINT (ps, a, GE_EXPR, b);
+    ADD_SAT_CONSTRAINT (ps, b, GE_EXPR, c);
+    ADD_SAT_CONSTRAINT (ps, c, GE_EXPR, a);
+
+    ASSERT_CONDITION_TRUE (ps, a, EQ_EXPR, c);
+  }
+
+  /* Transitivity: "a > b", "b > c", "c > a"
+     should be impossible.  */
+  {
+    path_state ps;
+    ADD_SAT_CONSTRAINT (ps, a, GT_EXPR, b);
+    ADD_SAT_CONSTRAINT (ps, b, GT_EXPR, c);
+    ADD_UNSAT_CONSTRAINT (ps, c, GT_EXPR, a);
+  }
+
+}
+
+/* FIXME.  */
+
+static void
+test_constant_comparisons ()
+{
+  path_var int_3 (build_int_cst (integer_type_node, 3), 0);
+  path_var int_4 (build_int_cst (integer_type_node, 4), 0);
+  path_var int_5 (build_int_cst (integer_type_node, 5), 0);
+
+  path_var int_1023 (build_int_cst (integer_type_node, 1023), 0);
+  path_var int_1024 (build_int_cst (integer_type_node, 1024), 0);
+
+  path_var a (build_decl (UNKNOWN_LOCATION, PARM_DECL,
+			  get_identifier ("a"),
+			  integer_type_node), 0);
+  path_var b (build_decl (UNKNOWN_LOCATION, PARM_DECL,
+			  get_identifier ("b"),
+			  integer_type_node), 0);
+
+  /* Given a >= 1024, then a <= 1023 should be impossible.  */
+  {
+    path_state ps;
+    ADD_SAT_CONSTRAINT (ps, a, GE_EXPR, int_1024);
+    ADD_UNSAT_CONSTRAINT (ps, a, LE_EXPR, int_1023);
+  }
+
+  /* a > 4.  */
+  {
+    path_state ps;
+    ADD_SAT_CONSTRAINT (ps, a, GT_EXPR, int_4);
+    ASSERT_CONDITION_TRUE (ps, a, GT_EXPR, int_4);
+    ASSERT_CONDITION_TRUE (ps, a, NE_EXPR, int_3);
+    ASSERT_CONDITION_UNKNOWN (ps, a, NE_EXPR, int_5);
+  }
+
+  /* a <= 4.  */
+  {
+    path_state ps;
+    ADD_SAT_CONSTRAINT (ps, a, LE_EXPR, int_4);
+    ASSERT_CONDITION_FALSE (ps, a, GT_EXPR, int_4);
+    ASSERT_CONDITION_FALSE (ps, a, GT_EXPR, int_5);
+    ASSERT_CONDITION_UNKNOWN (ps, a, NE_EXPR, int_3);
+  }
+
+  /* If "a > b" and "a == 3", then "b == 4" ought to be unsatisfiable.  */
+  {
+    path_state ps;
+    ADD_SAT_CONSTRAINT (ps, a, GT_EXPR, b);
+    ADD_SAT_CONSTRAINT (ps, a, EQ_EXPR, int_3);
+    ADD_UNSAT_CONSTRAINT (ps, b, EQ_EXPR, int_4);
+  }
+
+  /* Various tests of int ranges where there is only one possible candidate.  */
+  {
+    /* If "a <= 4" && "a > 3", then "a == 4",
+       assuming a is of integral type.  */
+    {
+      path_state ps;
+      ADD_SAT_CONSTRAINT (ps, a, LE_EXPR, int_4);
+      ADD_SAT_CONSTRAINT (ps, a, GT_EXPR, int_3);
+      ASSERT_CONDITION_TRUE (ps, a, EQ_EXPR, int_4);
+    }
+
+    /* If "a > 3" && "a <= 4", then "a == 4",
+       assuming a is of integral type.  */
+    {
+      path_state ps;
+      ADD_SAT_CONSTRAINT (ps, a, GT_EXPR, int_3);
+      ADD_SAT_CONSTRAINT (ps, a, LE_EXPR, int_4);
+      ASSERT_CONDITION_TRUE (ps, a, EQ_EXPR, int_4);
+    }
+    /* If "a > 3" && "a < 5", then "a == 4",
+       assuming a is of integral type.  */
+    {
+      path_state ps;
+      ADD_SAT_CONSTRAINT (ps, a, GT_EXPR, int_3);
+      ADD_SAT_CONSTRAINT (ps, a, LT_EXPR, int_5);
+      ASSERT_CONDITION_TRUE (ps, a, EQ_EXPR, int_4);
+    }
+    /* If "a >= 4" && "a < 5", then "a == 4",
+       assuming a is of integral type.  */
+    {
+      path_state ps;
+      ADD_SAT_CONSTRAINT (ps, a, GE_EXPR, int_4);
+      ADD_SAT_CONSTRAINT (ps, a, LT_EXPR, int_5);
+      ASSERT_CONDITION_TRUE (ps, a, EQ_EXPR, int_4);
+    }
+    /* If "a >= 4" && "a <= 4", then "a == 4".  */
+    {
+      path_state ps;
+      ADD_SAT_CONSTRAINT (ps, a, GE_EXPR, int_4);
+      ADD_SAT_CONSTRAINT (ps, a, LE_EXPR, int_4);
+      ASSERT_CONDITION_TRUE (ps, a, EQ_EXPR, int_4);
+    }
+  }
+
+#if 0
+  /* As above, but for float.   */
+  {
+    path_state ps;
+    ADD_SAT_CONSTRAINT (ps, f, GT_EXPR, float_3);
+    ADD_SAT_CONSTRAINT (ps, f, LE_EXPR, float_4);
+    ASSERT_CONDITION_UNKNOWN (ps, f, EQ_EXPR, int_4);
+  }
+#endif
+}
+
+/* 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;
+
+    ADD_SAT_CONSTRAINT (ps, x, EQ_EXPR, y);
+
+    /* 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);
+
+    ADD_SAT_CONSTRAINT (ps, y, GE_EXPR, z);
+    ASSERT_CONDITION_TRUE (ps, y, GE_EXPR, z);
+    ASSERT_CONDITION_UNKNOWN (ps, x, GE_EXPR, z);
+
+    ADD_SAT_CONSTRAINT (ps, x, EQ_EXPR, y);
+
+    /* 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);
+
+    ADD_SAT_CONSTRAINT (ps, y, GE_EXPR, z);
+    ASSERT_CONDITION_TRUE (ps, y, GE_EXPR, z);
+    ASSERT_CONDITION_UNKNOWN (ps, x, GE_EXPR, z);
+
+    ADD_SAT_CONSTRAINT (ps, y, EQ_EXPR, x);
+
+    /* 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);
+  }
+
+  /* x == 0, then x != 42.  */
+  {
+    path_state ps;
+
+    ADD_SAT_CONSTRAINT (ps, x, EQ_EXPR, int_0);
+    ADD_SAT_CONSTRAINT (ps, x, NE_EXPR, int_42);
+
+    /* Assert various things about the insides of ps.  */
+    ASSERT_EQ (ps.m_constraints.length (), 1);
+    ASSERT_EQ (ps.m_equiv_classes.length (), 2);
+    ASSERT_EQ (ps.m_constraints[0].m_lhs, ps.get_or_add_equiv_class (int_0));
+    ASSERT_EQ (ps.m_constraints[0].m_rhs, ps.get_or_add_equiv_class (int_42));
+    ASSERT_EQ (ps.m_constraints[0].m_op, CONSTRAINT_LT);
+  }
+
+  // TODO: selftest for merging ecs "in the middle"
+  // where a non-final one gets overwritten
+
+  // TODO: selftest where there are pre-existing constraints
+}
+
+/* FIXME.  */
+
+static void
+test_assignment ()
+{
+  // TODO
+}
+
+/* FIXME.  */
+
+static void
+test_stack_frames ()
+{
+  path_var int_42 (build_int_cst (integer_type_node, 42), 0);
+  path_var int_0 (build_int_cst (integer_type_node, 0), 0);
+
+  /* "a" in the parent frame.  */
+  path_var a (build_decl (UNKNOWN_LOCATION, PARM_DECL,
+			  get_identifier ("a"),
+			  integer_type_node), 0);
+  /* "x" in a child frame.  */
+  path_var x (build_decl (UNKNOWN_LOCATION, PARM_DECL,
+			  get_identifier ("x"),
+			  integer_type_node), 1);
+
+  {
+    path_state ps;
+    ps.m_stack_depth = 1;
+
+    /* x == a.  */
+    ADD_SAT_CONSTRAINT (ps, x, EQ_EXPR, a);
+    ASSERT_CONDITION_TRUE (ps, x, EQ_EXPR, a);
+
+    /* Pop the stack.  */
+    ps.pop_stack_frame ();
+    ASSERT_CONDITION_UNKNOWN (ps, x, EQ_EXPR, a);
+  }
+}
+
+/* Check that operator== and hashing works as expected for the
+   various types.  */
+
+static void
+test_equality ()
+{
+  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);
+
+  {
+    path_state ps0;
+    path_state ps1;
+
+    ASSERT_EQ (ps0.hash (), ps1.hash ());
+    ASSERT_EQ (ps0, ps1);
+
+    ADD_SAT_CONSTRAINT (ps1, x, EQ_EXPR, y);
+    ASSERT_NE (ps0.hash (), ps1.hash ());
+    ASSERT_NE (ps0, ps1);
+
+    path_state ps2;
+    /* Make the same change to ps2.  */
+    ADD_SAT_CONSTRAINT (ps2, x, EQ_EXPR, y);
+    ASSERT_EQ (ps1.hash (), ps2.hash ());
+    ASSERT_EQ (ps1, ps2);
+  }
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+analyzer_constraints_cc_tests ()
+{
+  test_constraint_conditions ();
+  test_transitivity ();
+  test_constant_comparisons ();
+  test_constraint_impl ();
+  test_assignment ();
+  test_stack_frames ();
+  test_equality ();
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
diff --git a/gcc/analyzer/constraints.h b/gcc/analyzer/constraints.h
new file mode 100644
index 0000000..40243d0
--- /dev/null
+++ b/gcc/analyzer/constraints.h
@@ -0,0 +1,279 @@
+/* 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;
+class call_superedge;
+class return_superedge;
+
+/* One of the end-points of a range.  */
+
+struct bound
+{
+  bound () : m_constant (NULL_TREE), m_closed (false) {}
+  bound (tree constant, bool closed)
+  : m_constant (constant), m_closed (closed) {}
+
+  void ensure_closed (bool is_upper);
+
+  tree m_constant;
+  bool m_closed;
+};
+
+/* FIXME.  */
+
+struct range
+{
+  range () : m_lower_bound (), m_upper_bound () {}
+  range (const bound &lower, const bound &upper)
+  : m_lower_bound (lower), m_upper_bound (upper) {}
+
+  std::string to_str () const;
+
+  bool constrained_to_single_element (tree *out);
+
+  bound m_lower_bound;
+  bound m_upper_bound;
+};
+
+/* 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?
+};
+
+/* Optional per-engine extra state (to avoid making path_state a
+   template).  */
+
+class extra_ec_state
+{
+ public:
+  virtual ~extra_ec_state () {}
+  virtual extra_ec_state *clone () const = 0;
+  virtual std::string to_str () const = 0;
+  virtual hashval_t hash () const = 0;
+  virtual bool operator== (const extra_ec_state &other) const = 0;
+};
+
+/* FIXME.  */
+
+class equiv_class
+{
+public:
+  equiv_class ();
+  equiv_class (const equiv_class &other);
+  ~equiv_class ();
+
+  std::string to_str () const;
+  hashval_t hash () const;
+  bool operator== (const equiv_class &other);
+
+  void add (path_var v);
+  bool del (path_var v);
+
+  tree get_any_constant () const { return m_constant; }
+
+  int get_min_stack_depth () const;
+
+  void purge_stack_frame (int callee_stack_depth);
+
+  path_var get_representative () const;
+
+  /* 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;
+
+  /* Has this value escaped from this function?  */
+  bool m_escaped;
+
+  /* Optional per-engine extra state (to avoid making path_state a
+     template).  */
+  extra_ec_state *m_extra_state;
+};
+
+enum constraint_op
+{
+  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;
+  hashval_t hash () const;
+  bool operator== (const constraint &other) const;
+
+  /* Is this an ordering, rather than a "!=".  */
+  bool is_ordering_p () const
+  {
+    return m_op != CONSTRAINT_NE;
+  }
+
+  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 () : m_stack_depth (0) {}
+  path_state (const path_state &other);
+  ~path_state ();
+
+  path_state& operator= (const path_state &other);
+
+  hashval_t hash () const;
+  bool operator== (const path_state &other) const;
+  bool operator!= (const path_state &other) const
+  {
+    return !(*this == other);
+  }
+
+  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];
+  }
+
+  equiv_class &get_equiv_class (path_var pv)
+  {
+    equiv_class_id ec_id = get_or_add_equiv_class (pv);
+    return ec_id.get_obj (*this);
+  }
+
+  path_var make_path_var (tree expr) const
+  {
+    return path_var (expr, m_stack_depth);
+  }
+
+  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);
+
+  bool add_constraint (equiv_class_id lhs_ec_id,
+		       enum tree_code op,
+		       equiv_class_id rhs_ec_id);
+
+  //private:
+  bool apply_constraints_for_call (const call_superedge& edge);
+
+  bool apply_constraints_for_return (const return_superedge& edge);
+
+  void pop_stack_frame ();
+
+  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);
+
+  void recursively_mark_as_escaping (equiv_class_id ec_id);
+  bool calc_escaped_p (path_var pv);
+
+  auto_vec<equiv_class *> m_equiv_classes;
+  auto_vec<constraint> m_constraints;
+  int m_stack_depth;
+
+ private:
+  void add_constraint_internal (equiv_class_id lhs_id,
+				enum constraint_op c_op,
+				equiv_class_id rhs_id);
+};
+
+namespace inchash
+{
+  extern void add_path_var (path_var pv, hash &hstate);
+} // namespace inchash
+
+#endif /* GCC_ANALYZER_CONSTRAINTS_H */
diff --git a/gcc/analyzer/dead-code/analyzer-config.cc b/gcc/analyzer/dead-code/analyzer-config.cc
new file mode 100644
index 0000000..2a23dd1
--- /dev/null
+++ b/gcc/analyzer/dead-code/analyzer-config.cc
@@ -0,0 +1,54 @@
+/* FIXME.  */
+#include "config.h"
+#include "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "tm.h"
+#include "toplev.h"
+#include "hash-table.h"
+#include "vec.h"
+#include "ggc.h"
+#include "basic-block.h"
+#include "tree-ssa-alias.h"
+#include "internal-fn.h"
+#include "gimple-fold.h"
+#include "tree-eh.h"
+#include "gimple-expr.h"
+#include "is-a.h"
+#include "gimple.h"
+#include "tree-pass.h"
+#include "intl.h"
+#include "context.h"
+#include "diagnostic.h"
+#include "bitmap.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "gimple-pretty-print.h"
+
+#include "analyzer.h"
+
+namespace ana
+{
+
+/* Build an analyzer instance.  */
+
+analyzer *
+make_analyzer (void)
+{
+  analyzer *a = new ana::analyzer ();
+  a->add_checker (make_core_checker (a));
+  a->add_checker (make_constraints_checker (a));
+  a->add_checker (make_core_divide_by_zero_checker ());
+  a->add_checker (make_core_function_call_checker ());
+  a->add_checker (make_libc_file_checker ());
+  a->add_checker (make_libc_malloc_checker ());
+  /* etc */
+
+  /* Ultimately this should read in configuration
+     and act accordingly.  */
+
+  return a;
+}
+
+} // namespace ana
diff --git a/gcc/analyzer/dead-code/constraints.cc b/gcc/analyzer/dead-code/constraints.cc
new file mode 100644
index 0000000..93407c2
--- /dev/null
+++ b/gcc/analyzer/dead-code/constraints.cc
@@ -0,0 +1,780 @@
+/* FIXME.  */
+#include "config.h"
+#include "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "tm.h"
+#include "toplev.h"
+#include "hash-table.h"
+#include "vec.h"
+#include "ggc.h"
+#include "basic-block.h"
+#include "tree-ssa-alias.h"
+#include "internal-fn.h"
+#include "gimple-fold.h"
+#include "tree-eh.h"
+#include "gimple-expr.h"
+#include "is-a.h"
+#include "gimple.h"
+#include "tree-pass.h"
+#include "intl.h"
+#include "context.h"
+#include "diagnostic.h"
+#include "bitmap.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "gimple-pretty-print.h"
+#include "tree-pretty-print.h"
+#include "analyzer.h"
+#include "print-tree.h"
+#include "graphviz.h"
+
+namespace ana
+{
+
+class constraints_checker;
+
+enum constraint_op
+{
+  CONSTRAINT_EQ,
+  CONSTRAINT_NE,
+  CONSTRAINT_LT,
+  CONSTRAINT_LE
+};
+
+static const char *
+constraint_op_code (enum constraint_op c_op)
+{
+  switch (c_op)
+    {
+    default:
+      gcc_unreachable ();
+    case CONSTRAINT_EQ: return "==";
+    case CONSTRAINT_NE: return "!=";
+    case CONSTRAINT_LT: return "<";
+    case CONSTRAINT_LE: return "<=";
+    }
+}
+
+class constraint {
+ public:
+  constraint (absvalue *lhs, enum constraint_op c_op, absvalue *rhs)
+    : m_lhs (lhs), m_op (c_op), m_rhs (rhs) {}
+
+  absvalue *m_lhs;
+  enum constraint_op m_op;
+  absvalue *m_rhs;
+};
+
+class known_constraints : public custom_state {
+ public:
+  custom_state *clone () const
+  {
+    return new known_constraints (*this);
+  }
+
+  void dump_to_dot_record (pretty_printer *pp) const;
+  void add_extra_nodes_and_edges (const node *n,
+				  graphviz_out *gv) const;
+
+  known_constraints () : m_constraints () {}
+  known_constraints (const known_constraints &other);
+
+  bool add_constraint (absvalue *lhs, enum tree_code op, absvalue *rhs);
+  bool add_constraint (context *ctxt, node *n, absvalue *lhs, bool value);
+  bool add_constraint (context *ctxt, node *n, tree lhs, bool value);
+
+  tristate
+  eval_condition (absvalue *lhs, enum tree_code op, absvalue *rhs);
+
+  tristate
+  eval_condition (context *ctxt, node *n,
+		  tree lhs, enum tree_code op, tree rhs);
+
+ private:
+  tristate impl_eq (absvalue *lhs, absvalue *rhs);
+  tristate impl_lt (absvalue *lhs, absvalue *rhs);
+  tristate impl_le (absvalue *lhs, absvalue *rhs);
+  tristate impl_ne (absvalue *lhs, absvalue *rhs) { return not impl_eq (lhs, rhs); }
+
+ private:
+  /* TODO: optimize */
+  vec<constraint *> m_constraints;
+  friend class constraints_checker;
+
+};
+
+/* The checker itself.  */
+
+class constraints_checker : public stateful_checker <known_constraints>, public constraints
+{
+ public:
+  void on_node (node *n, context *ctxt);
+  void before_call (node *n, gcall *call, context *ctxt);
+  void after_call (node *n, gcall *call, context *ctxt);
+  void dead_value (node *n, context *ctxt);
+
+  /* Implementation of constraints.  */
+  tristate
+  eval_condition (node *n, absvalue *lhs, enum tree_code op, absvalue *rhs);
+
+  bool
+  add_constraint (state *s, absvalue *lhs, enum tree_code op, absvalue *rhs);
+
+ private:
+};
+
+} // namespace ana
+
+ana::checker *ana::make_constraints_checker (analyzer *a)
+{
+  constraints_checker *ch = new constraints_checker ();
+  a->set_constraints (ch);
+  return ch;
+}
+
+/* class known_constraints : public per_checker_state */
+
+ana::known_constraints::known_constraints (const known_constraints &other)
+: m_constraints ()
+{
+  /* FIXME: do we really need to copy explicitly?  */
+  int i;
+  constraint *c;
+  FOR_EACH_VEC_ELT (other.m_constraints, i, c)
+    m_constraints.safe_push (c);
+}
+
+void
+ana::known_constraints::dump_to_dot_record (pretty_printer *pp) const
+{
+  int i;
+  constraint *c;
+  FOR_EACH_VEC_ELT (m_constraints, i, c)
+    {
+      pp_printf (pp, "constraint %i: ", i);
+      c->m_lhs->dump_to_dot_record (pp);
+      pp_printf (pp, "  %s ", constraint_op_code (c->m_op));
+      c->m_rhs->dump_to_dot_record (pp);
+      pp_newline (pp);
+      pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
+    }
+}
+
+void
+ana::known_constraints::add_extra_nodes_and_edges (const node *n, graphviz_out *gv) const
+{
+  /* Begin a subgraph.  */
+  gv->println ("subgraph \"cluster_known_constraints_%p\" {",
+	       (void *)this);
+  gv->indent ();
+  gv->println ("style=\"dashed\";");
+  gv->println ("color=\"blue\";");
+  gv->println ("clusterrank=\"local\";");
+  gv->println ("label=\"known_constraints\";");
+
+  /* Add nodes and edges visualizing the known constraints.  */
+  pretty_printer *pp = gv->m_pp;
+  int i;
+  constraint *c;
+  FOR_EACH_VEC_ELT (m_constraints, i, c)
+    {
+      gv->write_indent ();
+      pp_printf (pp,
+		 "known_constraints_%p_node_%p [label=\"",
+		 (void *)this,
+		 c->m_lhs);
+      c->m_lhs->dump_to_dot_record (pp);
+      pp_string (pp, "\"];\n");
+
+      gv->write_indent ();
+      pp_printf (pp,
+		 "known_constraints_%p_node_%p [label=\"",
+		 (void *)this,
+		 c->m_rhs);
+      c->m_rhs->dump_to_dot_record (pp);
+      pp_string (pp, "\"];\n");
+
+      const char *style = "\"solid\"";
+      const char *color = "black";
+      int weight = 0;
+      /* FIXME: for some reason, setting constraint = "true" plays havoc
+	 with the layout, despite having clusterrank=local.
+	 Keep it false for now.  */
+      const char *constraint = "false";
+      const char *label = constraint_op_code (c->m_op);
+      gv->write_indent ();
+
+      /* Adding edge labels here leads to failures from graphviz of this form:
+	 libpath/shortest.c:324: triangulation failed
+	 libpath/shortest.c:192: source point not in any triangle
+	 Error: in routesplines, Pshortestpath failed
+	 which appears to be:
+	 http://www.graphviz.org/mantisbt/view.php?id=2382
+	 However, it appears to work if we set taillabel instead.  */
+      pp_printf (pp,
+		 "known_constraints_%p_node_%p -> known_constraints_%p_node_%p "
+		 "[style=%s,color=%s,weight=%d,constraint=%s, len=0.1, taillabel=\"%s\"];\n",
+		 (void *)this, (void *)c->m_lhs,
+		 (void *)this, (void *)c->m_rhs,
+		 style, color, weight,
+		 constraint, label);
+    }
+
+  /* Terminate "subgraph" */
+  gv->outdent ();
+  gv->println ("}");
+}
+
+/* Attempt to add the constraint, returning true if the result is
+   satisfiable, false if it is unsatisfiable.  */
+
+bool
+ana::known_constraints::add_constraint (absvalue *lhs, enum tree_code op, absvalue *rhs)
+{
+  gcc_assert (lhs);
+  gcc_assert (rhs);
+
+#if 0
+  fprintf (stdout, "add_constraint: ");
+
+  pretty_printer pp;
+  pp.buffer->stream = stdout;
+  dump_to_dot_record (&pp);
+  pp_flush (&pp);
+
+  debug (lhs);
+  fprintf (stdout, " %s ", op_symbol_code (op));
+  debug (rhs);
+  fprintf (stdout, "\n");
+#endif
+
+  tristate t = eval_condition (lhs, op, rhs);
+
+  /* Discard constraints that are already known.  */
+  if (t.is_true ())
+    return true;
+
+  /* Reject unsatisfiable constraints.  */
+  if (t.is_false ())
+    return false;
+
+  /* For now, simply accumulate constraints, without attempting any further
+     optimization.  */
+  constraint *c = NULL;
+  switch (op)
+    {
+    case EQ_EXPR: c = new constraint (lhs, CONSTRAINT_EQ, rhs); break;
+    case GE_EXPR: c = new constraint (rhs, CONSTRAINT_LT, lhs); break;
+    case LE_EXPR: c = new constraint (lhs, CONSTRAINT_LE, rhs); break;
+    case NE_EXPR: c = new constraint (lhs, CONSTRAINT_NE, rhs); break;
+    case GT_EXPR: c = new constraint (rhs, CONSTRAINT_LE, lhs); break;
+    case LT_EXPR: c = new constraint (lhs, CONSTRAINT_LT, rhs); break;
+    default:
+      /* do nothing.  */
+      break;
+    }
+  if (c)
+    m_constraints.safe_push (c);
+  return true;
+}
+
+bool
+ana::known_constraints::add_constraint (context *ctxt, node *n, absvalue *v_lhs, bool value)
+{
+  gcc_assert (v_lhs);
+
+  /* Make "true" and "false" as !=/== zero */
+  enum tree_code op = value ? NE_EXPR : EQ_EXPR;
+
+  absvalue *v_rhs = ctxt->eval_rvalue (n, integer_zero_node);
+
+  return add_constraint (v_lhs, op, v_rhs);
+}
+
+bool
+ana::known_constraints::add_constraint (context *ctxt, node *n, tree t_lhs, bool value)
+{
+  absvalue *v_lhs = ctxt->eval_rvalue (n, t_lhs);  
+  gcc_assert (v_lhs);
+  return add_constraint (ctxt, n, v_lhs, value);
+}
+
+ana::tristate
+ana::known_constraints::eval_condition (absvalue *lhs, enum tree_code op, absvalue *rhs)
+{
+#if 0
+  fprintf (stderr, "eval_condition: ");
+
+  pretty_printer pp;
+  pp.buffer->stream = stderr;
+  dump_to_dot_record (&pp);
+  pp_flush (&pp);
+
+  debug (lhs);
+  fprintf (stderr, " %s ", op_symbol_code (op));
+  debug (rhs);
+  fprintf (stderr, "\n");
+#endif
+
+  tristate t = tristate (tristate::TS_UNKNOWN);
+
+  switch (op)
+    {
+    case EQ_EXPR: t = impl_eq (lhs, rhs); break;
+    case GE_EXPR: t = impl_lt (rhs, lhs); break;
+    case LE_EXPR: t = impl_le (lhs, rhs); break;
+    case NE_EXPR: t = impl_ne (lhs, rhs); break;
+    case GT_EXPR: t = impl_le (rhs, lhs); break;
+    case LT_EXPR: t = impl_lt (lhs, rhs); break;
+    default:
+      break;
+    }
+
+#if 0
+  fprintf (stderr, "result: %s\n", t.as_string ());
+#endif
+  return t;
+}
+
+ana::tristate
+ana::known_constraints::eval_condition (context *ctxt,
+					node *n,
+					tree t_lhs,
+					enum tree_code op,
+					tree t_rhs)
+{
+  absvalue *v_lhs = ctxt->eval_rvalue (n, t_lhs);
+  absvalue *v_rhs = ctxt->eval_rvalue (n, t_rhs);
+  return eval_condition (v_lhs, op, v_rhs);
+}
+
+
+/* TODO: implement some logic for avoid infinite recursion
+   e.g. a bitset of edges that have been followed
+   (i.e. constraint indices).  */
+
+/* Does A == B ? */
+ana::tristate
+ana::known_constraints::impl_eq (absvalue *a, absvalue *b)
+{
+  if (a == b)
+    return tristate (tristate::TS_TRUE);
+
+  if (a && b)
+    {
+      tristate t = a->eq (b);
+      if (t.is_known ())
+	return t;
+    }
+
+#if 0
+  if (TREE_CODE (a) == INTEGER_CST
+      && TREE_CODE (b) == INTEGER_CST)
+    {
+      return tristate (tree_int_cst_compare (a, b) == 0);
+    }
+
+  int i;
+  constraint *c;
+  FOR_EACH_VEC_ELT (m_constraints, i, c)
+    {
+      if (a == c->m_lhs)
+	{
+	  switch (c->m_op)
+	    {
+	    default:
+	      gcc_unreachable ();
+
+	    case CONSTRAINT_EQ:
+	      /* We have a == R;  a == b iff b == R; recurse.  */
+	      {
+		tristate t = impl_eq (b, c->m_rhs);
+		if (t.is_known ())
+		  return t;
+	      }
+	      break;
+
+	    case CONSTRAINT_NE:
+	      /* We have a != R;  hence b == R would imply !(a == b).  Recurse. */
+	      {
+		tristate t = impl_eq (b, c->m_rhs);
+		if (t.is_true ())
+		  return tristate (tristate::TS_FALSE);
+	      }
+	      break;
+
+	    case CONSTRAINT_LT:
+	      /* We have (a < R);  hence R <= b would imply !(a == b).  Recurse. */
+	      {
+		tristate t = impl_le (c->m_rhs, b);
+		if (t.is_true ())
+		  return tristate (tristate::TS_FALSE);
+	      }
+	      break;
+
+	    case CONSTRAINT_LE:
+	      /* We have (a <= R);  hence R < b would imply !(a == b).  Recurse. */
+	      {
+		tristate t = impl_lt (c->m_rhs, b);
+		if (t.is_true ())
+		  return tristate (tristate::TS_FALSE);
+	      }
+	      break;
+	    }
+	}
+
+      if (b == c->m_lhs)
+	{
+	  switch (c->m_op)
+	    {
+	    default:
+	      gcc_unreachable ();
+
+	    case CONSTRAINT_EQ:
+	      /* We have b == R;  a == b iff a == R; recurse.  */
+	      {
+		tristate t = impl_eq (a, c->m_rhs);
+		if (t.is_known ())
+		  return t;
+	      }
+	      break;
+
+	    case CONSTRAINT_NE:
+	      /* We have b != R;  hence a == R would imply !(a == b).  Recurse. */
+	      {
+		tristate t = impl_eq (a, c->m_rhs);
+		if (t.is_true ())
+		  return tristate (tristate::TS_FALSE);
+	      }
+	      break;
+
+	    case CONSTRAINT_LT:
+	      /* We have (b < R);  hence R <= a would imply !(a == b).  Recurse. */
+	      {
+		tristate t = impl_le (c->m_rhs, a);
+		if (t.is_true ())
+		  return tristate (tristate::TS_FALSE);
+	      }
+	      break;
+
+	    case CONSTRAINT_LE:
+	      /* We have (b <= R);  hence R < a would imply !(a == b).  Recurse. */
+	      {
+		tristate t = impl_lt (c->m_rhs, a);
+		if (t.is_true ())
+		  return tristate (tristate::TS_FALSE);
+	      }
+	      break;
+	    }
+	}
+
+      /* TODO: need to do rhs also.  */
+    }
+#endif
+
+  return tristate (tristate::TS_UNKNOWN);
+}
+
+/* Is A < B ? */
+ana::tristate
+ana::known_constraints::impl_lt (absvalue *a, absvalue *b)
+{
+  if (a == b)
+    return tristate (tristate::TS_FALSE);
+
+#if 0
+  if (TREE_CODE (a) == INTEGER_CST
+      && TREE_CODE (b) == INTEGER_CST)
+    {
+      return tristate (tree_int_cst_lt (a, b));
+    }
+#endif
+
+  int i;
+  constraint *c;
+  FOR_EACH_VEC_ELT (m_constraints, i, c)
+    {
+      if (a == c->m_lhs)
+	{
+	  switch (c->m_op)
+	    {
+	    default:
+	      gcc_unreachable ();
+
+	    case CONSTRAINT_EQ:
+	      /* We have (a == R);  a < b iff R < b; recurse.  */
+	      {
+		tristate t = impl_lt (c->m_rhs, b);
+		if (t.is_known ())
+		  return t;
+	      }
+	      break;
+
+	    case CONSTRAINT_NE:
+	      /* We have (a != R). */
+	      break;
+
+	    case CONSTRAINT_LT:
+	      /* We have (a < R); (R <= b) implies (a < b).  */
+	      {
+		tristate t = impl_le (c->m_rhs, b);
+		if (t.is_true ())
+		  return t;
+	      }
+	      break;
+
+	    case CONSTRAINT_LE:
+	      /* We have (a <= R); (R < b) implies (a < b).  */
+	      {
+		tristate t = impl_lt (c->m_rhs, b);
+		if (t.is_true ())
+		  return t;
+	      }
+	      break;
+	    }
+	}
+
+      if (b == c->m_lhs)
+	{
+	  switch (c->m_op)
+	    {
+	    default:
+	      gcc_unreachable ();
+
+	    case CONSTRAINT_EQ:
+	      /* We have (b == R);  a < b iff a < R; recurse.  */
+	      {
+		tristate t = impl_lt (a, c->m_rhs);
+		if (t.is_known ())
+		  return t;
+	      }
+	      break;
+
+	    case CONSTRAINT_NE:
+	      /* We have (b != R). */
+	      break;
+
+	    case CONSTRAINT_LT:
+	      /* We have (b < R); (R < a) implies !(a < b).  */
+	      {
+		tristate t = impl_lt (c->m_rhs, a);
+		if (t.is_true ())
+		  return tristate (tristate::TS_FALSE);
+	      }
+	      break;
+
+	    case CONSTRAINT_LE:
+	      /* We have (b <= R); (R <= a) implies !(a < b).  */
+	      {
+		tristate t = impl_le (c->m_rhs, a);
+		if (t.is_true ())
+		  return tristate (tristate::TS_FALSE);
+	      }
+	      break;
+	    }
+	}
+
+      /* TODO: need to do rhs also.  */
+    }
+
+  return tristate (tristate::TS_UNKNOWN);
+}
+
+/* Is A <= B ? */
+ana::tristate
+ana::known_constraints::impl_le (absvalue *a, absvalue *b)
+{
+  if (a == b)
+    return tristate (tristate::TS_TRUE);
+
+#if 0
+  if (TREE_CODE (a) == INTEGER_CST
+      && TREE_CODE (b) == INTEGER_CST)
+    {
+      return tristate (tree_int_cst_le (a, b));
+    }
+#endif
+
+  int i;
+  constraint *c;
+  FOR_EACH_VEC_ELT (m_constraints, i, c)
+    {
+      if (a == c->m_lhs)
+	{
+	  switch (c->m_op)
+	    {
+	    default:
+	      gcc_unreachable ();
+
+	    case CONSTRAINT_EQ:
+	      /* We have (a == R);  a <= b iff R <= b; recurse.  */
+	      {
+		tristate t = impl_le (c->m_rhs, b);
+		if (t.is_known ())
+		  return t;
+	      }
+	      break;
+
+	    case CONSTRAINT_NE:
+	      break;
+
+	    case CONSTRAINT_LT:
+	      /* We have (a < R); (R <= b) implies (a <= b).  */
+	      {
+		tristate t = impl_le (c->m_rhs, b);
+		if (t.is_true ())
+		  return t;
+	      }
+	      break;
+
+	    case CONSTRAINT_LE:
+	      /* We have (a <= R); (R <= b) implies (a <= b).  */
+	      {
+		tristate t = impl_le (c->m_rhs, b);
+		if (t.is_true ())
+		  return t;
+	      }
+	      break;
+	    }
+	}
+
+      if (b == c->m_lhs)
+	{
+	  switch (c->m_op)
+	    {
+	    default:
+	      gcc_unreachable ();
+
+	    case CONSTRAINT_EQ:
+	      /* We have (b == R);  a <= b iff a <= R; recurse.  */
+	      {
+		tristate t = impl_le (a, c->m_rhs);
+		if (t.is_known ())
+		  return t;
+	      }
+	      break;
+
+	    case CONSTRAINT_NE:
+	      break;
+
+	    case CONSTRAINT_LT:
+	      /* We have (b < R); (R < a) implies !(a <= b).  */
+	      {
+		tristate t = impl_lt (c->m_rhs, b);
+		if (t.is_true ())
+		  return tristate (tristate::TS_FALSE);
+	      }
+	      break;
+
+	    case CONSTRAINT_LE:
+	      /* We have (b <= R); (R < a) implies !(a <= b).  */
+	      {
+		tristate t = impl_lt (c->m_rhs, a);
+		if (t.is_true ())
+		  return tristate (tristate::TS_FALSE);
+	      }
+	      break;
+	    }
+	}
+      /* TODO: need to do rhs also.  */
+    }
+
+  return tristate (tristate::TS_UNKNOWN);
+}
+
+/* class constraints_checker : public checker */
+
+void
+ana::constraints_checker::on_node (node *n, context *ctxt)
+{
+  // TODO
+  if (0)
+    {
+      printf ("node: %p\n", n);
+      n->dump (stderr);
+    }
+
+  /* Handle the effect of statements.  */
+  gimple *stmt = n->get_location ()->get_stmt ();
+  if (!stmt)
+    return;
+
+  gassign *assign = dyn_cast <gassign *> (stmt);
+  if (assign)
+    {
+      if (0)
+	{
+	  printf ("handle assignment: ");
+	  print_gimple_stmt (stderr, assign, 0, (dump_flags_t)0);
+	}
+      enum tree_code subcode = gimple_assign_rhs_code (assign);
+      tree lhs = gimple_assign_lhs (assign);
+      switch (get_gimple_rhs_class (subcode))
+	{
+	case GIMPLE_SINGLE_RHS:
+	    break;
+	case GIMPLE_UNARY_RHS:
+	  if (subcode == NOP_EXPR)
+	    {
+	      tree op1 = gimple_assign_rhs1 (assign);
+	      tristate t = checker_state (n)->eval_condition (ctxt, n,
+							      op1, NE_EXPR, integer_zero_node);
+	      if (t.is_known ())
+		checker_state (n)->add_constraint (ctxt, n, lhs, t.is_true ());
+	    }
+	  break;
+	case GIMPLE_BINARY_RHS:
+	  {
+	    /* Handle "a = b OP c" where b OP C is known.  */
+	    tree op1 = gimple_assign_rhs1 (assign);
+	    tree op2 = gimple_assign_rhs2 (assign);
+	    tristate t = checker_state (n)->eval_condition (ctxt,
+							    n, op1, subcode, op2);
+	    if (t.is_known ())
+	      checker_state (n)->add_constraint (ctxt, n, lhs, t.is_true ());
+	  }
+	  break;
+
+	case GIMPLE_TERNARY_RHS:
+	  break;
+
+	default:
+	case GIMPLE_INVALID_RHS:
+	  gcc_unreachable ();
+	  break;
+	}
+    }
+}
+
+void
+ana::constraints_checker::before_call (node *n, gcall *call, context *ctxt)
+{
+  // TODO
+}
+
+void
+ana::constraints_checker::after_call (node *n, gcall *call, context *ctxt)
+{
+  // TODO
+}
+
+void
+ana::constraints_checker::dead_value (node *n, context *ctxt)
+{
+  // TODO
+}
+
+ana::tristate
+ana::constraints_checker::eval_condition (node *n, absvalue *lhs, enum tree_code op, absvalue *rhs)
+{
+  return checker_state (n)->eval_condition (lhs, op, rhs);
+}
+
+bool
+ana::constraints_checker::add_constraint (state *s,
+					  absvalue *lhs, enum tree_code op, absvalue *rhs)
+{
+  return checker_state (s)->add_constraint (lhs, op, rhs);
+}
diff --git a/gcc/analyzer/dead-code/core-checker.cc b/gcc/analyzer/dead-code/core-checker.cc
new file mode 100644
index 0000000..47c98ff
--- /dev/null
+++ b/gcc/analyzer/dead-code/core-checker.cc
@@ -0,0 +1,904 @@
+/* FIXME.  */
+#include "config.h"
+#include "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "tm.h"
+#include "toplev.h"
+#include "hash-table.h"
+#include "vec.h"
+#include "ggc.h"
+#include "basic-block.h"
+#include "tree-ssa-alias.h"
+#include "internal-fn.h"
+#include "gimple-fold.h"
+#include "tree-eh.h"
+#include "gimple-expr.h"
+#include "is-a.h"
+#include "gimple.h"
+#include "tree-pass.h"
+#include "intl.h"
+#include "context.h"
+#include "diagnostic.h"
+#include "bitmap.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "gimple-pretty-print.h"
+#include "tree-pretty-print.h"
+#include "analyzer.h"
+#include "print-tree.h"
+#include "graphviz.h"
+
+namespace ana
+{
+
+/* Fundamental memory-modeling.  */
+
+class absvalue;
+class region;
+  class field_region;
+class core_state;
+
+enum memspace_kind {
+   MEMSPACE_KIND_STACK,
+   MEMSPACE_KIND_STATIC,
+   MEMSPACE_KIND_HEAP,
+   NUM_MEMSPACE_KINDS
+};
+
+static const char * const memspace_kind_str[NUM_MEMSPACE_KINDS] = {
+  "STACK",
+  "STATIC",
+  "HEAP"
+};
+
+class region : public absvalue
+{
+ public:
+  region *get_parent () const { return m_parent; }
+
+  region *dyn_cast_region () { return this; }
+
+  virtual enum memspace_kind get_memspace () const;
+
+  field_region *get_field (tree t_field, core_state *state);
+
+  virtual void write_label_for_parent_edge (pretty_printer *pp) {}
+
+ protected:
+  region (region *parent) : m_parent (parent), m_fields () {}
+
+ private:
+  region *m_parent;
+  hash_map <tree, field_region *> m_fields;
+};
+
+class memspace_region : public region
+{
+ public:
+  memspace_region (enum memspace_kind kind)
+    : region (NULL), m_kind (kind) {}
+
+  void dump_to_dot_record (pretty_printer *pp) const
+  {
+    pp_printf (pp, memspace_kind_str[m_kind]);
+  }
+
+  enum memspace_kind get_memspace () const { return m_kind; }
+
+ private:
+  enum memspace_kind m_kind;
+};
+
+class var_region : public region
+{
+ public:
+  var_region (region *parent, tree var_decl)
+    : region (parent), m_var_decl (var_decl) {}
+
+  void dump_to_dot_record (pretty_printer *pp) const
+  {
+    pp_printf (pp, "var_region: ");
+  }
+
+ private:
+  tree m_var_decl;
+};
+
+class field_region : public region
+{
+ public:
+  field_region (region *parent, tree field) : region (parent), m_field (field) {}
+
+  void dump_to_dot_record (pretty_printer *pp) const
+  {
+    pp_printf (pp, "field_region: ");
+  }
+
+  void write_label_for_parent_edge (pretty_printer *pp)
+  {
+    pp_printf (pp, ".");
+    dump_generic_node (pp, m_field, 0, (dump_flags_t)0, false);
+  }
+
+ private:
+  tree m_field;
+};
+
+class symbolic_region : public region
+{
+ public:
+  symbolic_region (region *parent)
+    : region (parent) {}
+
+  void dump_to_dot_record (pretty_printer *pp) const
+  {
+    pp_printf (pp, "symbolic_region: ");
+  }
+
+ private:
+};
+
+class core_state : public custom_state {
+ public:
+  custom_state *clone () const
+  {
+    return new core_state (*this);
+  }
+
+  void dump_to_dot_record (pretty_printer *pp) const;
+  void add_extra_nodes_and_edges (const node *n, graphviz_out *gv) const;
+
+  core_state ();
+  core_state (const core_state &other);
+
+  void on_assign (gassign *assign, context *ctxt, node *n);
+
+  region *eval_lvalue (tree expr);
+  region *eval_rvalue_loc (tree expr);
+  absvalue *eval_rvalue (tree expr);
+  void set_lvalue (region *lhs, absvalue *rhs);
+
+  region *get_stack () { return get_memspace_region (MEMSPACE_KIND_STACK); }
+
+  region *get_memspace_region (enum memspace_kind);
+
+  void add_region (region *r);
+
+ private:
+  region *m_memspace_regions[NUM_MEMSPACE_KINDS];
+  vec <region *> m_regions;
+
+  /* Mapping from variables to storage locations */
+  hash_map <tree, region *> m_env;
+  typedef hash_map <tree, region *>::iterator env_iterator;
+
+  /* Mapping from storage locations to values. */
+  hash_map <region *, absvalue *> m_store;
+  typedef hash_map <region *, absvalue *>::iterator store_iterator;
+};
+
+/* The checker itself.  */
+
+class core_checker : public stateful_checker <core_state>, public memory_model
+{
+ public:
+  void on_node (node *n, context *ctxt);
+  void before_call (node *n, gcall *call, context *ctxt);
+  void after_call (node *n, gcall *call, context *ctxt);
+  void dead_value (node */*n*/, context *ctxt) {}
+
+  /* Implementation of memory_model.  */
+  absvalue *eval_rvalue (node *n, tree expr);
+  absvalue *eval_rvalue (state *s, tree expr);
+};
+
+} // namespace ana
+
+ana::checker *ana::make_core_checker (analyzer *a)
+{
+  core_checker *ch = new core_checker ();
+  a->set_memory_model (ch);
+  return ch;
+}
+
+enum ana::memspace_kind
+ana::region::get_memspace () const
+{
+  gcc_assert (m_parent);
+  return m_parent->get_memspace ();
+}
+
+ana::field_region *
+ana::region::get_field (tree t_field, core_state *state)
+{
+  gcc_assert (t_field);
+  gcc_assert (state);
+
+  field_region **pfield = m_fields.get (t_field);
+  if (pfield)
+    return *pfield;
+  else
+    {
+      field_region *r = new field_region (this, t_field);
+      m_fields.put (t_field, r);
+      state->add_region (r);
+      return r;
+    }
+}
+
+/* class core_state : public per_checker_state */
+
+ana::core_state::core_state () : m_regions (), m_env (), m_store ()
+{
+  for (int i = 0; i < NUM_MEMSPACE_KINDS; i++)
+    m_memspace_regions[i] = NULL;
+}
+
+ana::core_state::core_state (const core_state &other)
+: m_regions (), m_env (), m_store ()
+{
+  int i;
+  region *r;
+
+  for (i = 0; i < NUM_MEMSPACE_KINDS; i++)
+    {
+      m_memspace_regions[i] = other.m_memspace_regions[i];
+    }
+
+  FOR_EACH_VEC_ELT (other.m_regions, i, r)
+    m_regions.safe_push (r);
+
+  /* Ideally we need a deep copy method for hash_map.  */
+  for (env_iterator iter = other.m_env.begin ();
+       iter != other.m_env.end ();
+       ++iter)
+    {
+      tree expr = (*iter).first;
+      region *r = (*iter).second;
+      m_env.get_or_insert (expr) = r;
+    }
+
+  for (store_iterator iter = other.m_store.begin ();
+       iter != other.m_store.end ();
+       ++iter)
+    {
+      region *r = (*iter).first;
+      absvalue *v = (*iter).second;
+      m_store.get_or_insert (r) = v;
+    }
+}
+
+void
+ana::core_state::dump_to_dot_record (pretty_printer *pp) const
+{
+  for (env_iterator iter = m_env.begin ();
+       iter != m_env.end ();
+       ++iter)
+    {
+      tree expr = (*iter).first;
+      pp_printf (pp, "expr: ");
+      dump_generic_node (pp, expr, 0, (dump_flags_t)0, false);
+
+      region *r = (*iter).second;
+      pp_printf (pp, " region: %p", r);
+      //r->dump_to_dot_record (pp);
+
+      absvalue **pvalue = const_cast <ana::core_state *> (this)->m_store.get (r);
+      pp_printf (pp, " value: ");
+      if (pvalue)
+	{
+	  absvalue *value = *pvalue;
+	  pp_printf (pp, " %p", value);
+	  //value->dump_to_dot_record (pp);
+	}
+      else
+	{
+	  pp_printf (pp, "(NULL)");
+	}
+      pp_newline (pp);
+      pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
+    }
+}
+
+void
+ana::core_state::add_extra_nodes_and_edges (const node *n, graphviz_out *gv) const
+{
+  /* Begin a subgraph.  */
+  gv->println ("subgraph \"cluster_core_state_%p\" {", (void *)this);
+  gv->indent ();
+  gv->println ("style=\"dashed\";");
+  gv->println ("color=\"green\";");
+  gv->println ("label=\"core_state\";");
+  gv->println ("clusterrank=\"local\";");
+
+  pretty_printer *pp = gv->m_pp;
+
+    for (env_iterator iter = m_env.begin ();
+	 iter != m_env.end ();
+	 ++iter)
+      {
+	tree expr = (*iter).first;
+	region *r = (*iter).second;
+
+	/* Create node for "expr".  */
+	gv->write_indent ();
+	pp_printf (pp,
+		   "core_state_%p_expr_%p "
+		   "[shape=%s, style=\"filled\" fillcolor=\"yellow\" label=\"",
+		   (const void *)this, (void *)expr, "record");
+	pp_left_brace (pp);
+	pp_write_text_to_stream (pp);
+	dump_generic_node (pp, TREE_TYPE (expr), 0, (dump_flags_t)0, false);
+	pp_string (pp, " ");
+	dump_generic_node (pp, expr, 0, (dump_flags_t)0, false);
+	pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
+	pp_right_brace (pp);
+	pp_string (pp, "\"];");
+	pp_flush (pp);
+	pp_newline (pp);
+      }
+
+    int i;
+    region *r;
+    FOR_EACH_VEC_ELT (m_regions, i, r)
+      {
+	pp_printf (pp,
+		   "core_state_%p_region_%p "
+		   "[shape=%s, style=\"filled\" fillcolor=\"cyan\" label=\"",
+		   (const void *)this, (void *)r, "record");
+	pp_left_brace (pp);
+	pp_write_text_to_stream (pp);
+	r->dump_to_dot_record (pp);
+	absvalue **pvalue = const_cast <ana::core_state *> (this)->m_store.get (r);
+	pp_printf (pp, " value: ");
+	if (pvalue)
+	  {
+	    absvalue *value = *pvalue;
+	    pp_printf (pp, " %p", value);
+	    if (value)
+	      value->dump_to_dot_record (pp);
+	  }
+	else
+	  {
+	    pp_printf (pp, "(NULL)");
+	  }
+	pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
+
+	pp_right_brace (pp);
+	pp_string (pp, "\"];");
+	pp_flush (pp);
+	pp_newline (pp);
+
+	/* Draw edge from child to parent.  */
+	if (r->get_parent ())
+	  {
+	    gv->write_indent ();
+	    pp_printf (pp,
+		       "core_state_%p_region_%p:s -> core_state_%p_region_%p:n [label=\"",
+		       (const void *)this, (void *)r,
+		       (const void *)this, (void *)r->get_parent ());
+	    pp_write_text_to_stream (pp);
+	    r->write_label_for_parent_edge (pp);
+	    pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
+	    pp_string (pp, "\"];");
+	    pp_flush (pp);
+	    pp_newline (pp);
+	  }
+
+	/* For pointers, draw edge to the region it points to.  */
+	if (pvalue)
+	  {
+	    if (*pvalue)
+	      {
+		region *pointee = (*pvalue)->dyn_cast_region ();
+		if (pointee)
+		  {
+		    gv->write_indent ();
+		    pp_printf (pp,
+			       "core_state_%p_region_%p:e -> core_state_%p_region_%p:w [style=dotted];\n",
+			       (const void *)this, (void *)r,
+			       (const void *)this, (void *)pointee);
+		  }
+	      }
+	  }
+      }
+
+  for (env_iterator iter = m_env.begin ();
+       iter != m_env.end ();
+       ++iter)
+    {
+      tree expr = (*iter).first;
+      region *r = (*iter).second;
+
+      /* Create edge from expr's node to that for its region (in
+	 the store subgraph).  */
+      gv->write_indent ();
+      pp_printf (pp,
+		 "core_state_%p_expr_%p -> core_state_%p_region_%p;\n",
+		 (const void *)this, (void *)expr,
+		 (const void *)this, (void *)r);
+    }
+
+  /* Terminate outermost subgraph.  */
+  gv->outdent ();
+  gv->println ("}");
+}
+
+void
+ana::core_state::on_assign (gassign *assign, context *ctxt, node *n)
+{
+  //printf ("on_assign\n");
+  tree lhs = gimple_assign_lhs (assign);
+  //debug_tree (lhs);
+  enum tree_code subcode = gimple_assign_rhs_code (assign);
+  switch (get_gimple_rhs_class (subcode))
+    {
+    case GIMPLE_SINGLE_RHS:
+      {
+	tree rhs = gimple_assign_rhs1 (assign);
+	//debug_tree (rhs);
+	switch (TREE_CODE (rhs))
+	  {
+	  case ADDR_EXPR:
+	    {
+	      printf ("got ADDR_EXPR\n");
+	      tree ref = TREE_OPERAND (rhs, 0);
+	      absvalue *a = eval_lvalue (ref);
+	      set_lvalue (eval_lvalue (lhs), a);
+	    }
+	    break;
+
+	  case INTEGER_CST:
+	    {
+	      absvalue *a = new int_constant (rhs);
+	      set_lvalue (eval_lvalue (lhs), a);
+	    }
+	    break;
+
+	  case CONSTRUCTOR:
+	    {
+	      // TODO
+	    }
+	    break;
+
+	  case COMPONENT_REF:
+	    {
+	      // TODO
+	      absvalue *a = eval_rvalue (rhs);
+	      set_lvalue (eval_lvalue (lhs), a);
+	    }
+	    break;
+
+	  case SSA_NAME:
+	    {
+	      absvalue *a = eval_rvalue (rhs);
+	      set_lvalue (eval_lvalue (lhs), a);
+	    }
+	    break;
+
+	  default:	    
+	    gcc_unreachable (); // TODO
+	    break;
+	  }
+      }
+      break;
+
+    case GIMPLE_UNARY_RHS:
+      {
+	tree rhs = gimple_assign_rhs1 (assign);
+	switch (subcode)
+	  {
+	  CASE_CONVERT:
+	    {
+	      /* FIXME: for now we don't properly handle conversions.  */
+	      set_lvalue (eval_lvalue (lhs), eval_rvalue (rhs));	      
+	    }
+	    break;
+
+	  default:
+	    gcc_unreachable (); // TODO
+	    break;
+	  }
+      }
+      break;
+
+    case GIMPLE_BINARY_RHS:
+      {
+	/* Handle conditionals.  */
+	// TODO: what about other kinds of binary op?
+	tree op1 = gimple_assign_rhs1 (assign);
+	tree op2 = gimple_assign_rhs2 (assign);
+	tristate t = ctxt->eval_condition (n, op1, subcode, op2);
+	absvalue *v_result;
+	if (t.is_known ())
+	  v_result = new int_constant (t.is_true ()
+				       ? integer_one_node : integer_zero_node);
+	else
+	  v_result = new unknown ();
+	set_lvalue(eval_lvalue(lhs), v_result);
+      }
+      break;
+
+    case GIMPLE_TERNARY_RHS:
+      gcc_unreachable (); // TODO
+      break;
+
+    default:
+    case GIMPLE_INVALID_RHS:
+      gcc_unreachable ();
+      break;
+    }
+}
+
+ana::region *
+ana::core_state::eval_lvalue (tree expr)
+{
+  switch (TREE_CODE (expr))
+    {
+    case PARM_DECL:
+    case VAR_DECL:
+      {
+	region **p_region = m_env.get (expr);
+	if (!p_region)
+	  {
+	    /* FIXME: is it really on the stack?  */
+	    region *r = new var_region (get_stack (), expr);
+	    m_regions.safe_push (r);
+	    m_env.put (expr, r);
+	    return r;
+	  }
+	return *p_region;
+      }
+
+    case SSA_NAME:
+      {
+	tree var = SSA_NAME_VAR (expr);
+	if (var)
+	  return eval_lvalue (var);
+	else
+	  {
+	    /* "expr" is a temporary.  */
+	    region **p_region = m_env.get (expr);
+	    if (!p_region)
+	      {
+		/* FIXME: is it really on the stack?  */
+		region *r = new var_region (get_stack (), expr);
+		m_regions.safe_push (r);
+		m_env.put (expr, r);
+		return r;
+	      }
+	    return *p_region;
+	  }
+      }
+
+    case COMPONENT_REF:
+      {
+	tree t_ref = TREE_OPERAND (expr, 0);
+	tree t_field = TREE_OPERAND (expr, 1);
+	region *r = eval_lvalue (t_ref);
+	if (r)
+	  return r->get_field (t_field, this);
+	else
+	  {
+	    gcc_unreachable ();
+	    return NULL; // FIXME
+	  }
+      }
+
+    case MEM_REF:
+      {
+	tree t_ptr = TREE_OPERAND (expr, 0);
+	tree t_offset = TREE_OPERAND (expr, 1);
+	gcc_assert (TREE_CODE (t_offset) == INTEGER_CST);
+	//gcc_assert (t_offset == zero);
+
+	region *r_ptr = eval_rvalue_loc (t_ptr);
+	if (r_ptr)
+	  return r_ptr;
+	else
+	  {
+	    region *r_star_ptr = new symbolic_region (NULL);
+	    m_regions.safe_push (r_star_ptr);
+	    set_lvalue (eval_lvalue (t_ptr), r_star_ptr);
+	    return r_star_ptr;
+          }
+      }
+
+    default:
+      gcc_unreachable ();
+      break;
+    }
+}
+
+ana::region *
+ana::core_state::eval_rvalue_loc (tree expr)
+{
+  switch (TREE_CODE (expr))
+    {
+    case SSA_NAME:
+      {
+	tree var = SSA_NAME_VAR (expr);
+	if (!var)
+	  /* Temporary.  */
+	  var = expr;
+	region *r = eval_lvalue (var);
+	absvalue **p_v = m_store.get (r);
+	if (p_v)
+	  {
+	    if (*p_v)
+	      {
+		region *pointee = (*p_v)->dyn_cast_region ();
+		if (pointee)
+		  return pointee;
+		else
+		  /* FIXME: what to do here?  */
+		  gcc_unreachable ();
+	      }
+	    else
+	      return NULL;
+	  }
+	else
+	  {
+	    /* Unknown value.  */
+	    return NULL;
+	  }
+      }
+      break;
+
+    default:
+      gcc_unreachable ();
+      break;
+    }
+}
+
+/* Attempt to eval the rvalue; return non-NULL.  */
+
+ana::absvalue *
+ana::core_state::eval_rvalue (tree expr)
+{
+  switch (TREE_CODE (expr))
+    {
+    case COMPONENT_REF:
+      {
+	region *r = eval_lvalue (expr);
+	absvalue **p_val = m_store.get (r);
+	if (p_val)
+	  return *p_val;
+	else
+	  {
+	    gcc_unreachable ();
+	    return NULL; /* FIXME */
+	  }
+      }
+      break;
+
+    case SSA_NAME:
+      {
+	region *r = eval_lvalue (expr);
+	absvalue **p_val = m_store.get (r);
+	if (p_val)
+	  return *p_val;
+	else
+	  {
+	    absvalue *v = new unknown ();
+	    m_store.put (r, v);
+	    return v;
+	  }
+      }
+      break;
+
+    case INTEGER_CST:
+      {
+	/* FIXME: cache/consolidate these?  */
+	return new int_constant (expr);
+      }
+
+    default:
+      gcc_unreachable ();
+      break;
+    }
+}
+
+void
+ana::core_state::set_lvalue (region *lhs, absvalue *rhs)
+{
+  m_store.get_or_insert (lhs) = rhs;
+}
+
+ana::region *
+ana::core_state::get_memspace_region (enum memspace_kind kind)
+{
+  /* Lazily create the base memspace regions.  */
+  if (!m_memspace_regions[kind])
+    {
+      m_memspace_regions[kind] = new memspace_region (kind);
+      m_regions.safe_push (m_memspace_regions[kind]);
+    }
+  return m_memspace_regions[kind];
+}
+
+void
+ana::core_state::add_region (region *r)
+{
+  m_regions.safe_push (r);
+}
+
+/* class core_checker : public checker */
+
+void
+ana::core_checker::on_node (node *n, context *ctxt)
+{
+  // TODO
+  gimple *stmt = n->get_location ()->get_stmt ();
+  if (!stmt)
+    return;
+
+  gassign *assign = dyn_cast <gassign *> (stmt);
+  if (assign)
+    {
+      core_state *state = checker_state (n);
+      state->on_assign (assign, ctxt, n);
+    }
+
+  greturn *return_stmt = dyn_cast <greturn *> (stmt);
+  if (return_stmt)
+    {
+      /* Complain about return of pointer to on-stack variable.  */
+      tree t_retval = gimple_return_retval (return_stmt);
+      if (t_retval)
+	{
+	  core_state *state = checker_state (n);
+	  absvalue *retval = state->eval_rvalue (t_retval);
+	  if (retval)
+	    {
+	      /* TODO: only complain if it's of pointer type.  */
+	      region *pointee = retval->dyn_cast_region ();
+	      if (pointee)
+		if (MEMSPACE_KIND_STACK == pointee->get_memspace ())
+		  {
+		      bug *b = new bug ("return of pointer to stack", n); // TODO: identify it
+		      // TODO: follow the symbols of interest
+		      emit_bug (b, ctxt);
+		  }
+	    }
+	}
+    }
+}
+
+void
+ana::core_checker::before_call (node *n, gcall *call, context *ctxt)
+{
+  /* Handle the builtin "__analyzer_dump" by dumping state to stderr.  */
+  if (is_named_call_p (call, "__analyzer_dump", 0))
+    {
+      n->dump (stdout);
+    }
+
+  /* Handle the builtin "__analyzer_eval" by evaluating the input
+     and dumping as a "warning", so that test cases can use dg-warning
+     to validate the result.  */
+  if (is_named_call_p (call, "__analyzer_eval", 1))
+    {
+      tree t_arg = gimple_call_arg (call, 0);
+      tristate t = ctxt->eval_condition (n,
+					 t_arg, NE_EXPR, integer_zero_node);
+      warning_at (n->get_source_location (), 0, t.as_string ());
+    }
+}
+
+void
+ana::core_checker::after_call (node */*n*/, gcall */*call*/, context *ctxt)
+{
+  // TODO
+}
+
+ana::absvalue *
+ana::core_checker::eval_rvalue (node *n, tree expr)
+{
+  core_state *state = checker_state (n);
+  return state->eval_rvalue (expr);
+}
+
+ana::absvalue *
+ana::core_checker::eval_rvalue (state *s, tree expr)
+{
+  core_state *state = checker_state (s);
+  return state->eval_rvalue (expr);
+}
+
+/************************************************************************/
+
+/* Additional checkers.  */
+
+namespace ana {
+class core_divide_by_zero_checker : public stateless_checker
+{
+ public:
+  void on_node (node *n, context *ctxt);
+  void before_call (node */*n*/, gcall */*call*/, context *ctxt) {}
+  void after_call (node */*n*/, gcall */*call*/, context *ctxt) {}
+  void dead_value (node */*n*/, context *ctxt) {}
+};
+
+class core_function_call_checker : public stateless_checker
+{
+ public:
+  void on_node (node */*n*/, context *ctxt) {}
+  void before_call (node *n, gcall *call, context *ctxt);
+  void after_call (node *n, gcall *call, context *ctxt);
+  void dead_value (node */*n*/, context *ctxt) {}
+};
+
+} // namespace ana
+
+/* class core_divide_by_zero_checker : public stateless_checker */
+void
+ana::core_divide_by_zero_checker::on_node (node *n, context *ctxt)
+{
+  // TODO
+  gimple *stmt = n->get_location ()->get_stmt ();
+  if (!stmt)
+    return;
+
+  gassign *assign = dyn_cast <gassign *> (stmt);
+  if (assign)
+    {
+      tree lhs = gimple_assign_lhs (assign);
+      //debug_tree (lhs);
+      enum tree_code subcode = gimple_assign_rhs_code (assign);
+      switch (subcode)
+	{
+	case TRUNC_DIV_EXPR:
+	case CEIL_DIV_EXPR:
+	case FLOOR_DIV_EXPR:
+	case ROUND_DIV_EXPR:
+	case TRUNC_MOD_EXPR:
+	case CEIL_MOD_EXPR:
+	case FLOOR_MOD_EXPR:
+	case ROUND_MOD_EXPR:
+	case RDIV_EXPR:
+	case EXACT_DIV_EXPR:
+	case FIX_TRUNC_EXPR:
+	  {
+	    tree op1 = gimple_assign_rhs1 (assign);
+	    tree op2 = gimple_assign_rhs2 (assign);
+	  }
+	  break;
+
+	default:
+	  break;
+	}
+    }
+}
+
+/* class core_function_call_checker : public stateless_checker */
+void
+ana::core_function_call_checker::before_call (node *n, gcall *call, context *ctxt)
+{
+  /* TODO:
+     warn about:
+     - uninitialized args
+     - passing NULL as a param that requires a non-NULL arg
+     - calling a function ptr that may be NULL
+     - etc.  */
+}
+
+void
+ana::core_function_call_checker::after_call (node *n, gcall *call, context *ctxt)
+{
+  // TODO
+}
+
+ana::checker *
+ana::make_core_divide_by_zero_checker ()
+{
+  return new core_divide_by_zero_checker ();
+}
+
+ana::checker *
+ana::make_core_function_call_checker ()
+{
+  return new core_function_call_checker ();
+}
diff --git a/gcc/analyzer/dead-code/libc-file-checker.cc b/gcc/analyzer/dead-code/libc-file-checker.cc
new file mode 100644
index 0000000..7854203
--- /dev/null
+++ b/gcc/analyzer/dead-code/libc-file-checker.cc
@@ -0,0 +1,226 @@
+/* FIXME.  */
+#include "config.h"
+#include "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "tm.h"
+#include "toplev.h"
+#include "hash-table.h"
+#include "vec.h"
+#include "ggc.h"
+#include "basic-block.h"
+#include "tree-ssa-alias.h"
+#include "internal-fn.h"
+#include "gimple-fold.h"
+#include "tree-eh.h"
+#include "gimple-expr.h"
+#include "is-a.h"
+#include "gimple.h"
+#include "tree-pass.h"
+#include "intl.h"
+#include "context.h"
+#include "diagnostic.h"
+#include "bitmap.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "gimple-pretty-print.h"
+#include "tree-pretty-print.h"
+#include "analyzer.h"
+
+namespace ana
+{
+
+class libc_file_checker;
+
+/* Checker for libc FILE * operations.  */
+
+/* State for the checker.  */
+enum file_state {FILE_MAYBE_OPEN, FILE_OPEN, FILE_CLOSED};
+
+class file_states : public custom_state {
+ public:
+  custom_state *clone () const
+  {
+    return new file_states (*this);
+  }
+
+  void dump_to_dot_record (pretty_printer *pp) const;
+
+  file_states () : m_states () {}
+  file_states (const file_states &other);
+
+  void set_tree_state (tree t, enum file_state value);
+  bool get_tree_state (tree t, enum file_state &out);
+
+ private:
+  /* TODO: this should be a map from symbolic values to states,
+     rather than from trees. */
+  hash_map <tree, enum file_state> m_states;
+  typedef hash_map <tree, enum file_state>::iterator state_iterator;
+  friend class libc_file_checker;
+};
+
+/* The checker itself.  */
+
+class libc_file_checker : public stateful_checker <file_states>
+{
+ public:
+  void on_node (node *n, context *ctxt);
+  void before_call (node *n, gcall *call, context *ctxt);
+  void after_call (node *n, gcall *call, context *ctxt);
+  void dead_value (node *n, context *ctxt);
+
+ private:
+  void report_double_close (node *n, tree fp, context *ctxt);
+  void report_leaked_file (node *n, tree fp, context *ctxt);
+
+};
+
+} // namespace ana
+
+ana::checker *ana::make_libc_file_checker ()
+{
+  return new libc_file_checker ();
+}
+
+/* class file_states : public per_checker_state */
+
+static const char * const state_names[] = {
+  "FILE_MAYBE_OPEN",
+  "FILE_OPEN",
+  "FILE_CLOSED"
+};
+
+ana::file_states::file_states (const file_states &other) : m_states ()
+{
+  /* We can't simple copy m_states, or the states will become linked;
+     ideally we need a deep copy method for hash_map.  */
+  for (state_iterator iter = other.m_states.begin ();
+       iter != other.m_states.end ();
+       ++iter)
+    {
+      tree t = (*iter).first;
+      enum file_state value = (*iter).second;
+      m_states.get_or_insert (t) = value;
+    }
+}
+
+void
+ana::file_states::dump_to_dot_record (pretty_printer *pp) const
+{
+  for (state_iterator iter = m_states.begin ();
+       iter != m_states.end ();
+       ++iter)
+    {
+      tree t = (*iter).first;
+      enum file_state value = (*iter).second;
+      pp_printf (pp, "file_state: ");
+      dump_generic_node (pp, t, 0, (dump_flags_t)0, false);
+      pp_printf (pp, ": %s", state_names[value]);
+      pp_newline (pp);
+      pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
+    }
+}
+
+void
+ana::file_states::set_tree_state (tree t, enum file_state value)
+{
+  if (TREE_CODE (t) != SSA_NAME)
+    return; // for now
+  m_states.get_or_insert (t) = value;
+}
+
+bool
+ana::file_states::get_tree_state (tree t, enum file_state &out)
+{
+  if (TREE_CODE (t) != SSA_NAME)
+    return false; // for now
+  enum file_state *state = m_states.get (t);
+  if (state)
+    {
+      out = *state;
+      return true;
+    }
+  else
+    return false;
+}
+
+/* class libc_file_checker : public checker */
+
+void
+ana::libc_file_checker::on_node (node */*n*/, context *ctxt)
+{
+  // TODO
+}
+
+void
+ana::libc_file_checker::before_call (node *n, gcall *call, context *ctxt)
+{
+  // TODO
+  if (is_named_call_p (call, "fclose", 1))
+    {
+      //printf ("before_call to: fclose\n");
+      tree fp = gimple_call_arg (call, 0);
+      if (fp)
+	{
+	  enum file_state val;
+	  if (checker_state (n)->get_tree_state (fp, val))
+	    if (val == FILE_CLOSED)
+	      report_double_close (n, fp, ctxt);
+	}
+    }
+}
+
+void
+ana::libc_file_checker::after_call (node *n, gcall *call, context *ctxt)
+{
+  // TODO
+  if (is_named_call_p (call, "fopen", 2))
+    {
+      //printf ("after_call to: fopen\n");
+      tree lhs = gimple_call_lhs (call);
+      if (lhs)
+	checker_state (n)->set_tree_state (lhs, FILE_MAYBE_OPEN);
+    }
+
+  if (is_named_call_p (call, "fclose", 1))
+    {
+      //printf ("after_call to: fclose\n");
+      tree fp = gimple_call_arg (call, 0);
+      if (fp)
+	checker_state (n)->set_tree_state (fp, FILE_CLOSED);
+    }
+}
+
+void
+ana::libc_file_checker::dead_value (node *n, context *ctxt)
+{
+  file_states *states = checker_state (n);
+  for (file_states::state_iterator iter = states->m_states.begin ();
+       iter != states->m_states.end ();
+       ++iter)
+    {
+      tree t = (*iter).first;
+      enum file_state value = (*iter).second;
+
+      if (value == FILE_MAYBE_OPEN)
+	report_leaked_file (n, t, ctxt);
+    }
+}
+
+void
+ana::libc_file_checker::report_double_close (node *n, tree /*fp*/, context *ctxt)
+{
+  bug *b = new bug ("double-close of FILE *", n);
+  // TODO: follow the symbol of interest (fp)
+  emit_bug (b, ctxt);
+}
+
+void
+ana::libc_file_checker::report_leaked_file (node *n, tree /*fp*/, context *ctxt)
+{
+  bug *b = new bug ("leak of FILE *", n); // TODO: name of fp?
+  // TODO: follow the symbol of interest (fp)
+  emit_bug (b, ctxt);
+}
diff --git a/gcc/analyzer/dead-code/libc-malloc-checker.cc b/gcc/analyzer/dead-code/libc-malloc-checker.cc
new file mode 100644
index 0000000..3565c85
--- /dev/null
+++ b/gcc/analyzer/dead-code/libc-malloc-checker.cc
@@ -0,0 +1,220 @@
+/* FIXME.  */
+#include "config.h"
+#include "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "tm.h"
+#include "toplev.h"
+#include "hash-table.h"
+#include "vec.h"
+#include "ggc.h"
+#include "basic-block.h"
+#include "tree-ssa-alias.h"
+#include "internal-fn.h"
+#include "gimple-fold.h"
+#include "tree-eh.h"
+#include "gimple-expr.h"
+#include "is-a.h"
+#include "gimple.h"
+#include "tree-pass.h"
+#include "intl.h"
+#include "context.h"
+#include "diagnostic.h"
+#include "bitmap.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "gimple-pretty-print.h"
+#include "tree-pretty-print.h"
+#include "analyzer.h"
+
+namespace ana
+{
+
+/* Checker for libc malloc/free/realloc.  */
+
+/* State for the checker.  */
+enum ptr_state {PTR_MALLOCED, /* or NULL */
+		PTR_FREED};
+
+class ptr_states : public custom_state {
+ public:
+  custom_state *clone () const
+  {
+    return new ptr_states (*this);
+  }
+
+  void dump_to_dot_record (pretty_printer *pp) const;
+
+  ptr_states () : m_states () {}
+  ptr_states (const ptr_states &other);
+
+  void set_tree_state (tree t, enum ptr_state value);
+  bool get_tree_state (tree t, enum ptr_state &out);
+
+ private:
+  /* TODO: this should be a map from symbolic values to states,
+     rather than from trees. */
+  hash_map <tree, enum ptr_state> m_states;
+  typedef hash_map <tree, enum ptr_state>::iterator state_iterator;
+  friend class libc_malloc_checker;
+};
+
+class libc_malloc_checker : public stateful_checker <ptr_states>
+{
+ public:
+  void on_node (node *n, context *ctxt);
+  void before_call (node *n, gcall *call, context *ctxt);
+  void after_call (node *n, gcall *call, context *ctxt);
+  void dead_value (node *n, context *ctxt);
+
+ private:
+  void report_double_free (node *n, tree ptr, context *ctxt);
+  void report_leak (node *n, tree ptr, context *ctxt);
+};
+
+} // namespace ana
+
+ana::checker *ana::make_libc_malloc_checker ()
+{
+  return new libc_malloc_checker ();
+}
+
+/* class ptr_states : public per_checker_state */
+
+static const char * const state_names[] = {
+  "PTR_MALLOCED",
+  "PTR_FREE"
+};
+
+ana::ptr_states::ptr_states (const ptr_states &other) : m_states ()
+{
+  /* We can't simple copy m_states, or the states will become linked;
+     ideally we need a deep copy method for hash_map.  */
+  for (state_iterator iter = other.m_states.begin ();
+       iter != other.m_states.end ();
+       ++iter)
+    {
+      tree t = (*iter).first;
+      enum ptr_state value = (*iter).second;
+      m_states.get_or_insert (t) = value;
+    }
+}
+
+void
+ana::ptr_states::dump_to_dot_record (pretty_printer *pp) const
+{
+  for (state_iterator iter = m_states.begin ();
+       iter != m_states.end ();
+       ++iter)
+    {
+      tree t = (*iter).first;
+      enum ptr_state value = (*iter).second;
+      pp_printf (pp, "ptr_state: ");
+      dump_generic_node (pp, t, 0, (dump_flags_t)0, false);
+      pp_printf (pp, ": %s", state_names[value]);
+      pp_newline (pp);
+      pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
+    }
+}
+
+void
+ana::ptr_states::set_tree_state (tree t, enum ptr_state value)
+{
+  if (TREE_CODE (t) != SSA_NAME)
+    return; // for now
+  m_states.get_or_insert (t) = value;
+}
+
+bool
+ana::ptr_states::get_tree_state (tree t, enum ptr_state &out)
+{
+  if (TREE_CODE (t) != SSA_NAME)
+    return false; // for now
+  enum ptr_state *state = m_states.get (t);
+  if (state)
+    {
+      out = *state;
+      return true;
+    }
+  else
+    return false;
+}
+
+/* class libc_malloc_checker : public checker */
+
+void
+ana::libc_malloc_checker::on_node (node * /*n*/, context *ctxt)
+{
+  // TODO
+}
+
+void
+ana::libc_malloc_checker::before_call (node *n, gcall *call, context *ctxt)
+{
+  // TODO
+  if (is_named_call_p (call, "free", 1))
+    {
+      tree ptr = gimple_call_arg (call, 0);
+      if (ptr)
+	{
+	  enum ptr_state val;
+	  if (checker_state (n)->get_tree_state (ptr, val))
+	    if (val == PTR_FREED)
+	      report_double_free (n, ptr, ctxt);
+	}
+    }
+}
+
+void
+ana::libc_malloc_checker::after_call (node *n, gcall *call, context *ctxt)
+{
+  // TODO
+  if (is_named_call_p (call, "malloc", 1))
+    {
+      tree lhs = gimple_call_lhs (call);
+      if (lhs)
+	checker_state (n)->set_tree_state (lhs, PTR_MALLOCED);
+      else
+	; /* TODO: report leak.  */
+    }
+
+  if (is_named_call_p (call, "free", 1))
+    {
+      tree ptr = gimple_call_arg (call, 0);
+      if (ptr)
+	checker_state (n)->set_tree_state (ptr, PTR_FREED);
+    }
+}
+
+void
+ana::libc_malloc_checker::dead_value (node *n, context *ctxt)
+{
+  ptr_states *states = checker_state (n);
+  for (ptr_states::state_iterator iter = states->m_states.begin ();
+       iter != states->m_states.end ();
+       ++iter)
+    {
+      tree t = (*iter).first;
+      enum ptr_state value = (*iter).second;
+
+      if (value == PTR_MALLOCED)
+	report_leak (n, t, ctxt);
+    }
+}
+
+void
+ana::libc_malloc_checker::report_double_free (node *n, tree ptr, context *ctxt)
+{
+  bug *b = new bug ("double-free", n);
+  // TODO: follow the symbol of interest (ptr)
+  emit_bug (b, ctxt);
+}
+
+void
+ana::libc_malloc_checker::report_leak (node *n, tree ptr, context *ctxt)
+{
+  bug *b = new bug ("leak", n);
+  // TODO: follow the symbol of interest (ptr)
+  emit_bug (b, ctxt);
+}
diff --git a/gcc/analyzer/dead-code/toy-sm-impl-1.cc b/gcc/analyzer/dead-code/toy-sm-impl-1.cc
new file mode 100644
index 0000000..beb2c20
--- /dev/null
+++ b/gcc/analyzer/dead-code/toy-sm-impl-1.cc
@@ -0,0 +1,1052 @@
+/* FIXME.  */
+#include "config.h"
+#include "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "tm.h"
+#include "toplev.h"
+#include "hash-table.h"
+#include "vec.h"
+#include "ggc.h"
+#include "basic-block.h"
+#include "tree-ssa-alias.h"
+#include "internal-fn.h"
+#include "gimple-fold.h"
+#include "tree-eh.h"
+#include "gimple-expr.h"
+#include "is-a.h"
+#include "gimple.h"
+#include "tree-pass.h"
+#include "intl.h"
+#include "context.h"
+#include "diagnostic.h"
+#include "bitmap.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "gimple-pretty-print.h"
+#include "tree-pretty-print.h"
+#include "analyzer.h"
+#include "graphviz.h"
+#include "cgraph.h"
+#include "diagnostic-path.h"
+#include "gcc-rich-location.h"
+#include <string>
+#include "supergraph.h"
+#include "toy-sm-state-machine.h"
+
+static const bool DEBUG = false;
+//static const bool DEBUG = true;
+
+static int verbosity = 2;
+
+////////////////////////////////////////////////////////////////////////////
+
+class context;
+class sm_instance;
+
+////////////////////////////////////////////////////////////////////////////
+
+// FIXME: horrible hack, so that we can use %qE, which needs subclassing of
+// pretty_printer.  Do we need a cloning op?
+
+static char *
+global_dc_print (const char *fmt, ...)
+{
+  pretty_printer *pp = global_dc->printer;
+  pp_clear_output_area (pp);
+
+  text_info ti;
+  rich_location rich_loc (line_table, UNKNOWN_LOCATION);
+
+  va_list ap;
+
+  va_start (ap, fmt);
+
+  ti.format_spec = _(fmt);
+  ti.args_ptr = &ap;
+  ti.err_no = 0;
+  ti.x_data = NULL;
+  ti.m_richloc = &rich_loc;
+
+#if 1
+#else
+  pp_show_color (pp) = pp_show_color (global_dc->printer);
+  pp_format_decoder (pp) = pp_format_decoder (global_dc->printer);
+#endif
+  pp_format (pp, &ti);
+  pp_output_formatted_text (pp);
+
+  va_end (ap);
+
+  char *result = xstrdup (pp_formatted_text (pp));
+
+  pp_clear_output_area (pp);
+
+  return result;
+}
+
+struct var_state_1_t
+{
+  //typedef var_state_1_t value_type;
+
+  var_state_1_t (tree var, state_machine::state_t state)
+  : m_var (var), m_state (state)
+  {}
+
+  std::string to_str (const state_machine &sm) const
+  {
+    char *tmp = global_dc_print ("%qE: %qs",
+				 m_var, sm.get_state_name (m_state));
+    std::string result = tmp;
+    free (tmp);
+    return result;
+  }
+
+  tree m_var;
+  state_machine::state_t m_state;
+};
+struct summary_point
+{
+  state_machine::state_t m_gstate;
+  var_state_1_t m_varstate;
+};
+struct summary_edge
+{
+  summary_point start;
+  summary_point end;
+};
+
+template <typename Type>
+struct pod_hash_traits : typed_noop_remove<Type>
+{
+  typedef Type value_type;
+  typedef Type compare_type;
+  static inline hashval_t hash (value_type);
+  static inline bool equal (const value_type &existing,
+			    const value_type &candidate);
+  static inline void mark_deleted (Type &);
+  static inline void mark_empty (Type &);
+  static inline bool is_deleted (Type);
+  static inline bool is_empty (Type);
+};
+
+template <> struct default_hash_traits<var_state_1_t>
+: public pod_hash_traits<var_state_1_t>
+{
+};
+
+template <>
+inline hashval_t
+pod_hash_traits<var_state_1_t>::hash (value_type v)
+{
+  // FIXME:
+  return ((hashval_t)v.m_state);
+}
+
+template <>
+inline bool
+pod_hash_traits<var_state_1_t>::equal (const value_type &existing,
+				     const value_type &candidate)
+{
+  return (existing.m_var == candidate.m_var
+	  && existing.m_state == candidate.m_state);
+}
+template <>
+inline void
+pod_hash_traits<var_state_1_t>::mark_deleted (value_type &v)
+{
+  v = var_state_1_t (NULL_TREE, 0);
+}
+template <>
+inline void
+pod_hash_traits<var_state_1_t>::mark_empty (value_type &v)
+{
+  v = var_state_1_t (NULL_TREE, 0);
+}
+template <>
+inline bool
+pod_hash_traits<var_state_1_t>::is_deleted (value_type v)
+{
+  return (v.m_var == NULL_TREE && v.m_state == 0);
+}
+template <>
+inline bool
+pod_hash_traits<var_state_1_t>::is_empty (value_type v)
+{
+  return (v.m_var == NULL_TREE && v.m_state == 0);
+}
+
+/* FIXME: This changes as we do the traversal: */
+
+struct sm_instance_1
+{
+  sm_instance_1 () : m_gstate (0), m_active_vars () {}
+
+  std::string to_str (const state_machine &sm) const
+  {
+    // FIXME: obstack?
+    std::string result = "<";
+    result += sm.get_state_name (m_gstate);
+    result += ", {";
+    bool first = true;
+    for (hash_set<var_state_1_t>::iterator iter = m_active_vars.begin ();
+	 iter != m_active_vars.end ();
+	 ++iter)
+      {
+	if (first)
+	  first = false;
+	else
+	  result += ", ";
+	result += (*iter).to_str (sm);
+      }
+    result += "}>";
+    return result;
+  }
+
+  state_machine::state_t m_gstate;
+  hash_set<var_state_1_t> m_active_vars;
+};
+
+class context
+{
+public:
+  // FIXME: for now, just global state
+  // FIXME: but should be a map from ndoes to state info
+  // e.g. a bitmap of states that are known to be reachable at entry
+  // to that node (possibly tagged with a minimal path that reaches it?)
+  //state_machine::state_t m_state;
+
+  struct per_node_t
+  {
+    hash_set<summary_edge *> m_add;
+    hash_set<summary_edge *> m_transition;
+  };
+  /* FIXME: This is shared by the traversal: */
+  hash_map <supernode *, per_node_t> m_per_node;
+
+  void on_transition (sm_instance_1 *sm_inst,
+		      const state_machine &sm,
+		      diagnostic_path *p,
+		      int stack_depth,
+		      const gimple *stmt, tree fndecl, tree dest,
+		      state_machine::state_t from,
+		      state_machine::state_t to);
+
+#if 0
+  // FIXME: map from tree to state_t (and where that state came from?)
+  hash_map <tree, state_t> m_states;
+  typedef hash_map <tree, state_t>::iterator state_iterator;
+#endif
+};
+
+void
+context::on_transition (sm_instance_1 *sm_inst,
+			const state_machine &sm,
+			diagnostic_path *p,
+			int stack_depth,
+			const gimple *stmt, tree fndecl,
+			tree dest,
+			state_machine::state_t from,
+			state_machine::state_t to)
+{
+  if (DEBUG)
+    inform (stmt->location, "DBG: %s; transition: %qE: %qs -> %qs",
+	    sm_inst->to_str (sm).c_str (),
+	    dest, sm.get_state_name (from), sm.get_state_name (to));
+
+  // TODO: filter: only the transitions for which the start state is reachable
+  // "cache_misses" ?
+
+  if (from == 0 || sm_inst->m_active_vars.contains (var_state_1_t (dest, from)))
+    {
+      p->add_event (stmt->location, fndecl, stack_depth,
+		    "%s; transition: %qE: %qs -> %qs",
+		    sm_inst->to_str (sm).c_str (), dest,
+		    sm.get_state_name (from), sm.get_state_name (to));
+      sm_inst->m_active_vars.add (var_state_1_t (dest, to));
+    }
+
+  // FIXME: also do the equivalence classes?
+}
+
+static std::string
+backtrace_to_str (const supergraph *sg,
+		  const auto_vec<cgraph_edge *> &backtrace)
+{
+  std::string result ("[");
+  // FIXME: this is probably not optimal
+
+  int i;
+  cgraph_edge *e;
+  FOR_EACH_VEC_ELT (backtrace, i, e)
+    {
+      if (i > 0)
+	result += ", ";
+      result += sg->get_caller_next_node (e)->to_str ();
+    }
+
+  result += "]";
+
+  return result;
+}
+
+/* A tree, extended with stack frame information for locals, so that
+   we can distinguish between different values of locals within a potentially
+   recursive callstack.  */
+
+
+class old_path_var
+{
+public:
+  old_path_var (tree t, int stack_depth)
+  : m_tree (t), m_stack_depth (stack_depth)
+  {
+    // TODO: ignore stack depth for globals and constants
+  }
+
+  std::string to_str () const;
+
+  tree m_tree;
+  int m_stack_depth; // or -1 for globals?
+};
+
+static std::string
+expr_to_string (tree t)
+{
+  char *tmp = global_dc_print ("%qE", t);
+  std::string result = tmp;
+  free (tmp);
+  return result;
+}
+
+std::string
+old_path_var::to_str () const
+{
+  return (std::string ("(")
+	  + expr_to_string (m_tree)
+	  + " @ "
+	  + std::to_string (m_stack_depth)
+	  + ")");
+}
+
+/* TODO: I *think* we need equiv classes in order to be able to cope with:
+
+     something_that_calls_free (ptr);
+
+   also transitioning "ptr" in this frame to "freed".  (but maybe summaries
+   help with this also?)  */
+
+#if 0
+/* FIXME.  */
+
+class equiv_class
+{
+public:
+  equiv_class ();
+  equiv_class (const equiv_class &other);
+
+  std::string to_str () const;
+
+  void add (old_path_var v) { m_vars.safe_push (v); }
+  //void del (old_path_var v);
+
+  // FIXME: this is a bad representation, probably should be a set.
+  auto_vec<old_path_var> m_vars;
+};
+
+equiv_class::equiv_class ()
+{
+}
+
+equiv_class::equiv_class (const equiv_class &other)
+{
+  // FIXME: copy-ctor for auto_vec!
+  int i;
+  old_path_var *v;
+  FOR_EACH_VEC_ELT (other.m_vars, i, v)
+    m_vars.safe_push (*v);
+}
+
+std::string
+equiv_class::to_str () const
+{
+  std::string result ("{");
+  int i;
+  old_path_var *v;
+  FOR_EACH_VEC_ELT (m_vars, i, v)
+    {
+      if (i > 0)
+	result += " == ";
+      result += v->to_str ();
+    }
+  return result + "}";
+}
+
+#endif
+
+// TODO: maybe reimplement in terms of constraints for now:
+
+enum constraint_op
+{
+  CONSTRAINT_EQ,
+  CONSTRAINT_NE,
+  CONSTRAINT_LT,
+  CONSTRAINT_LE
+};
+
+static const char *
+constraint_op_code (enum constraint_op c_op)
+{
+  switch (c_op)
+    {
+    default:
+      gcc_unreachable ();
+    case CONSTRAINT_EQ: return "==";
+    case CONSTRAINT_NE: return "!=";
+    case CONSTRAINT_LT: return "<";
+    case CONSTRAINT_LE: return "<=";
+    }
+}
+
+class old_constraint {
+ public:
+  old_constraint (old_path_var lhs, enum constraint_op c_op, old_path_var rhs)
+    : m_lhs (lhs), m_op (c_op), m_rhs (rhs) {}
+
+  std::string to_str () const;
+
+  int get_max_stack_depth () const
+  {
+    return std::max (m_lhs.m_stack_depth, m_rhs.m_stack_depth);
+  }
+
+  old_path_var m_lhs;
+  enum constraint_op m_op;
+  old_path_var m_rhs;
+};
+
+std::string
+old_constraint::to_str () const
+{
+  return (m_lhs.to_str ()
+	  + constraint_op_code (m_op)
+	  + m_rhs.to_str ());
+}
+
+/* Facts that we know, for the current path_state.  */
+
+class old_path_constraints
+{
+public:
+  old_path_constraints ();
+  old_path_constraints (const old_path_constraints &other);
+
+  std::string to_str () const;
+
+  void on_assignment (old_path_var dst, old_path_var src);
+  void on_stack_frame_popped (int new_max_depth);
+
+#if 0
+  auto_vec<equiv_class *> m_equiv_classes;
+#endif
+  auto_vec<old_constraint> m_constraints;
+};
+
+old_path_constraints::old_path_constraints ()
+{
+}
+
+old_path_constraints::old_path_constraints (const old_path_constraints &other)
+{
+  // FIXME: copy-ctor for auto_vec!
+  int i;
+#if 0
+  equiv_class *ec;
+  FOR_EACH_VEC_ELT (other.m_equiv_classes, i, ec)
+    m_equiv_classes.safe_push (new equiv_class (*ec));
+#endif
+
+  old_constraint *c;
+  FOR_EACH_VEC_ELT (other.m_constraints, i, c)
+    m_constraints.safe_push (*c);
+}
+
+std::string
+old_path_constraints::to_str () const
+{
+  std::string result ("{");
+
+  int i;
+#if 0
+  equiv_class *ec;
+  FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+    {
+      if (i > 0)
+	result += ", ";
+      result += ec->to_str ();
+    }
+#endif
+
+  old_constraint *c;
+  FOR_EACH_VEC_ELT (m_constraints, i, c)
+    {
+      if (i > 0)
+	result += ", ";
+      result += c->to_str ();
+    }
+
+  result += "}";
+  return result;
+}
+
+void
+old_path_constraints::on_assignment (old_path_var dst, old_path_var src)
+{
+  // FIXME: need a better implementation; for now
+#if 0
+  // just always add a new equivalence class:
+  equiv_class *ec = new equiv_class ();
+  m_equiv_classes.safe_push (ec);
+  ec->add (dst);
+  ec->add (src);
+#endif
+
+  m_constraints.safe_push (old_constraint (dst, CONSTRAINT_EQ, src));
+}
+
+void
+old_path_constraints::on_stack_frame_popped (int new_max_depth)
+{
+  // TODO: purge any ecs relating to the old frame:
+#if 0
+  int i;
+  equiv_class *ec;
+  FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+    {
+      for (unsigned j = 0; j < ec->m_vars.length (); j++)
+	if (ec->m_vars[j].m_stack_depth > new_max_depth)
+	  ec->m_vars.unordered_remove (j);
+    }
+#endif
+  // TODO: do it for constraints
+  for (unsigned i = 0; i < m_constraints.length (); i++)
+    if (m_constraints[i].get_max_stack_depth () > new_max_depth)
+      m_constraints.unordered_remove (i);
+}
+
+
+/* The state of a particular point at one point in the traversal (not including
+   state-machine information):
+   - the diagnostic_path that we're building (in case we need to
+     emit a diagnostic)
+   - the backtrace for unwinding calls,
+   - TODO: constraints (e.g. for diamond CFGs).  */
+
+class old_path_state
+{
+public:
+  old_path_state () : m_recursion_depth (0) {}
+  old_path_state (const old_path_state &other);
+
+  std::string to_str (const supergraph *sg) const;
+
+  int get_stack_depth () const { return m_backtrace.length (); }
+
+  diagnostic_path m_path;
+  auto_vec<cgraph_edge *> m_backtrace;
+  old_path_constraints m_constraints;
+  int m_recursion_depth;
+};
+
+/* FIXME.  */
+// TODO: maybe make this less inefficient (lots of copying)
+
+old_path_state::old_path_state (const old_path_state &other)
+: m_path (other.m_path),
+  m_backtrace (),
+  m_constraints (other.m_constraints),
+  m_recursion_depth (other.m_recursion_depth)
+{
+  // FIXME: copy-ctor for auto_vec!
+  int i;
+  cgraph_edge *edge;
+  FOR_EACH_VEC_ELT (other.m_backtrace, i, edge)
+    m_backtrace.safe_push (edge);
+}
+
+/* FIXME.  */
+
+std::string
+old_path_state::to_str (const supergraph *sg) const
+{
+  return (std::string ("<path length: ")
+	  + std::to_string (m_path.m_events.length())
+	  + std::string (" backtrace: ") + backtrace_to_str (sg, m_backtrace)
+	  + std::string (" constraints: ") + m_constraints.to_str ()
+	  + std::string (">"));
+}
+
+/* FIXME.  */
+
+class path_finder
+{
+public:
+  path_finder (const supergraph *supergraph,
+	       const state_machine &sm,
+	       context *ctxt)
+  : m_supergraph (supergraph), m_sm (sm), m_ctxt (ctxt),
+    m_traversals (0), m_max_recursion_depth (0)
+  {
+  }
+
+  void dfs_traversal (const supernode *node,
+		      old_path_state state,
+		      sm_instance_1 *sm_inst,
+		      auto_bitmap &visited);
+
+  void on_stmt (const supernode *node,
+		old_path_state &state,
+		sm_instance_1 *sm_inst,
+		const gimple *stmt);
+
+  //private:
+  /* Various things that are global to the whole traversal.  */
+  const supergraph *m_supergraph;
+  const state_machine &m_sm;
+  context *m_ctxt;
+
+  /* Stats.  */
+public:
+  int m_traversals;
+  int m_max_recursion_depth;
+};
+
+/* Interface to path_finder::dfs_traversal for a state_machine to call
+   into.  */
+
+class foo_sm_context : public sm_context
+{
+public:
+  foo_sm_context (const state_machine &sm,
+		  path_finder &pf,
+		  old_path_state &state,
+		  sm_instance_1 *sm_inst)
+  : sm_context (), m_sm (sm), m_pf (pf), m_state (state),
+    m_sm_inst (sm_inst)
+  {}
+
+#if 1
+  void on_transition (const supernode *node, const gimple *stmt,
+		      tree var,
+		      state_machine::state_t from,
+		      state_machine::state_t to) FINAL OVERRIDE
+  {
+    // FIXME
+    tree fndecl = node->m_fun->decl;
+    m_pf.m_ctxt->on_transition (m_sm_inst, m_sm, &m_state.m_path,
+				m_state.get_stack_depth (), stmt, fndecl,
+				var, from, to);
+  }
+#endif
+
+  //state_machine::state_t get_state (tree var) FINAL OVERRIDE {/* FIXME */}
+  //void transition (tree var, state_machine::state_t to) FINAL OVERRIDE {/* FIXME */}
+  void error (const supernode *node, const gimple *stmt,
+	      tree var, state_machine::state_t state, const char *fmt, ...) FINAL OVERRIDE {/* FIXME */}
+
+private:
+  const state_machine &m_sm;
+  path_finder &m_pf;
+  old_path_state &m_state;
+  sm_instance_1 *m_sm_inst;
+};
+
+void
+path_finder::dfs_traversal (const supernode *node,
+			    old_path_state state,
+			    sm_instance_1 *sm_inst,
+			    auto_bitmap &visited)
+{
+  m_traversals++;
+  state.m_recursion_depth++;
+  if (m_max_recursion_depth < state.m_recursion_depth)
+    m_max_recursion_depth = state.m_recursion_depth;
+
+  std::string sm_inst_str = sm_inst->to_str (m_sm);
+  if (DEBUG)
+    inform (node->get_start_location (),
+	    "DBG: dfs_traversal on %qD supernode=%i, sm_inst: %s, state: %s",
+	    node->m_fun->decl, node->m_index, sm_inst_str.c_str (),
+	    state.to_str (m_supergraph).c_str ());
+
+  bitmap_set_bit (visited, node->m_index);
+
+  if (verbosity >= 2)
+    state.m_path.add_event (node->get_start_location (),
+			    node->m_fun->decl, state.get_stack_depth (),
+			    "%qD supernode:%i sm_inst: %s state: %s",
+			    node->m_fun->decl, node->m_index,
+			    sm_inst_str.c_str (),
+			    state.to_str (m_supergraph).c_str ());
+
+  int i;
+  gimple *stmt;
+  FOR_EACH_VEC_ELT (node->m_stmts, i, stmt)
+    on_stmt (node, state, sm_inst, stmt);
+
+  if (node->return_p ())
+    {
+      tree callee = node->m_fun->decl;
+
+      // TODO: respect "visited"
+
+      /* Get IVP successor, if any, via backtrace.  */
+      if (state.m_backtrace.length () == 0)
+	{
+	  gcc_rich_location rich_loc (node->m_fun->function_end_locus);
+	  rich_loc.set_path (&state.m_path);
+	  // FIXME: set/reset current_function_decl so as to get "In function"
+	  // announcements
+	  //fprintf (stderr, "============================================================================\n");
+	  inform (&rich_loc, "END OF PATH: length: %i (presumably started within %qD)",
+		  state.m_path.m_events.length (),
+		  node->m_fun->decl);
+	  //fprintf (stderr, "----------------------------------------------------------------------------\n");
+	  return;
+	}
+
+      /* Recurse, taking a copy of the path.  */
+      old_path_state next (state);
+      cgraph_edge *edge = next.m_backtrace.pop ();
+
+      supernode *dest = m_supergraph->get_caller_next_node (edge);
+
+      // FIXME: need to purge state, and update return value based on *sm_inst:
+      sm_instance_1 next_sm_inst;
+      if (!VOID_TYPE_P (TREE_TYPE (DECL_RESULT (callee))))
+	{
+	  if (dest->m_returning_call
+	      && gimple_call_lhs (dest->m_returning_call))
+	    {
+	      for (hash_set<var_state_1_t>::iterator iter = sm_inst->m_active_vars.begin ();
+		   iter != sm_inst->m_active_vars.end ();
+		   ++iter)
+		{
+		  // purge to just the return value, and assign to lhs of m_returning_call:
+		  if ((*iter).m_var == DECL_RESULT (callee))
+		    {
+		      next_sm_inst.m_active_vars.add
+			(var_state_1_t (gimple_call_lhs (dest->m_returning_call),
+				      (*iter).m_state));
+		    }
+		  // FIXME: should this be done by the next node?
+		}
+	    }
+	}
+      // TODO: purge state from constraints
+      next.m_constraints.on_stack_frame_popped (next.get_stack_depth ());
+
+      if (DEBUG)
+	inform (UNKNOWN_LOCATION, "from %s to %s",
+		sm_inst->to_str (m_sm).c_str (),
+		next_sm_inst.to_str (m_sm).c_str ());
+
+      gcc_assert (dest->m_returning_call);
+      if (verbosity >= 1)
+	next.m_path.add_event
+	  (dest->m_returning_call->location,
+	   dest->m_fun->decl, // FIXME: or node?
+	   next.get_stack_depth (),
+	   "returning from %qD to %qD (supernode %i -> %i)",
+	   node->m_fun->decl,
+	   dest->m_fun->decl,
+	   node->m_index,
+	   dest->m_index);
+      dfs_traversal (dest, next, &next_sm_inst, visited);
+    }
+  else
+    {
+      superedge *succ;
+      FOR_EACH_VEC_ELT (node->m_succs, i, succ)
+	{
+	  // Don't revisit BBs we've already seen
+	  if (bitmap_bit_p (visited, succ->m_dest->m_index))
+	    {
+	      // FIXME: disabled for now
+	      // TODO: generalize this to take into account state traversals:
+	      // FIXME: we may want to do it based on edges, rather than nodes:
+	      // consider multiple edges in a switch statement, which only
+	      // differ via phi nodes (see demo-2.c)
+#if 0
+	      if (DEBUG)
+		inform (UNKNOWN_LOCATION,
+			"DBG: already visited supernode %i; skipping",
+			succ->m_dest->m_index);
+	      continue;
+#endif
+	    }
+
+	  /* Recurse, taking a copy of the path and backtrace.  */
+	  sm_instance_1 next_sm_inst (*sm_inst);
+	  old_path_state next (state);
+
+	  switch (succ->m_kind)
+	    {
+	    default:
+	      gcc_unreachable ();
+	    case SUPEREDGE_CFG_EDGE:
+	      if (verbosity >= 2)
+		{
+		  char *edge_desc = succ->get_description ();
+		  if (strlen (edge_desc) > 0)
+		    {
+		      char *wrapped = concat ("(", edge_desc, ") ", NULL);
+		      free (edge_desc);
+		      edge_desc = wrapped;
+		    }
+		  next.m_path.add_event
+		    (node->get_end_location (),
+		     node->m_fun->decl,
+		     state.get_stack_depth (),		     
+		     "taking CFG edge %s%i->%i within %qD (supernode %i -> %i)",
+		     edge_desc,
+		     node->m_bb->index,
+		     succ->m_dest->m_bb->index,
+		     node->m_fun->decl,
+		     node->m_index,
+		     succ->m_dest->m_index);
+		  free (edge_desc);
+		}
+	      break;
+	    case SUPEREDGE_CALL:
+	      {
+		// TODO: update next_sm_inst to reflect params
+		sm_instance_1 call_sm_inst;
+
+		/* Record where we're returning to (and update the
+		   stack depth of "next").  */
+		next.m_backtrace.safe_push (succ->get_callgraph_edge ());
+
+		tree callee = succ->m_dest->m_fun->decl;
+		gcall *call = node->get_final_call ();
+
+		int i = 0;
+		for (tree parm = DECL_ARGUMENTS (callee); parm;
+		     parm = DECL_CHAIN (parm), ++i)
+		  {
+		    tree arg = gimple_call_arg (call, i);
+		    if (DEBUG)
+		      inform (call->location, "arg[%i]: %qE parm[%i]: %qD",
+			      i, arg, i, parm);
+		    /* Use: parm, arg, next_sm_inst, to map from args in caller
+		       to parms in callee */
+		    for (hash_set<var_state_1_t>::iterator iter
+			   = next_sm_inst.m_active_vars.begin ();
+			 iter != next_sm_inst.m_active_vars.end ();
+			 ++iter)
+		      {
+			if ((*iter).m_var == arg)
+			  {
+			    call_sm_inst.m_active_vars.add
+			      (var_state_1_t (parm, (*iter).m_state));
+			  }
+		      }
+		    /* Associate the parm in the new stack frame with
+		       the arg in the old stack frame in the constraints
+		       of the next state.  */
+		    next.m_constraints.on_assignment
+		      (old_path_var (parm, next.get_stack_depth ()),
+		       old_path_var (arg, state.get_stack_depth ()));
+		  }
+		if (DEBUG)
+		  inform (UNKNOWN_LOCATION, "from %s to %s",
+			  next_sm_inst.to_str (m_sm).c_str (),
+			  call_sm_inst.to_str (m_sm).c_str ());
+#if 0
+		// FIXME: seeing crashes with this
+		// hash_set appears not to have a copy-ctor...
+		next_sm_inst = call_sm_inst;
+#endif
+
+		if (verbosity >= 0)
+		  next.m_path.add_event
+		    (node->get_end_location (),
+		     node->m_fun->decl, // FIXME: or dest?
+		     state.get_stack_depth (),
+		     "calling from %qD to %qD (supernode %i -> %i)",
+		     node->m_fun->decl,
+		     succ->m_dest->m_fun->decl,
+		     node->m_index,
+		     succ->m_dest->m_index);
+	      }
+	      break;
+	    case SUPEREDGE_RETURN:
+	      // This should be covered above:
+	      gcc_unreachable ();
+	      break;
+	    case SUPEREDGE_INTRAPROCEDURAL_CALL:
+	      /* Skip these.  */
+	      continue;
+#if 0
+	      next.m_path.add_event
+		(UNKNOWN_LOCATION,
+		 node->m_fun->decl,
+		 "intraprocedural edge at call within %qD from supernode %i to supernode %i",
+		 node->m_fun->decl,
+		 node->m_index,
+		 succ->m_dest->m_index);
+	      // FIXME: do we actually want to do this?
+	      break;
+#endif
+	    }
+
+	  dfs_traversal (succ->m_dest, next,
+			 &next_sm_inst, visited);
+	}
+    }
+}
+
+void
+path_finder::on_stmt (const supernode *node,
+		      old_path_state &state,
+		      sm_instance_1 *sm_inst,
+		      const gimple *stmt)
+{
+  if (DEBUG)
+    {
+      fprintf (stderr, "DBG: ");
+      print_gimple_stmt (stderr, const_cast <gimple *> (stmt), // FIXME<<<
+			 0, (dump_flags_t)0);
+    }
+
+  // FIXME: this is for debugging; which stmts do we want to log in the path?
+  {
+    pretty_printer pp;
+    pp_gimple_stmt_1 (&pp, const_cast <gimple *> (stmt), 0, (dump_flags_t)0);
+    if (verbosity >= 1)
+      state.m_path.add_event (stmt->location, node->m_fun->decl,
+			      state.get_stack_depth (),
+			      "on_stmt: %qs %s",
+			      pp_formatted_text (&pp),
+			      sm_inst->to_str (m_sm).c_str ());
+  }
+
+  foo_sm_context sm_ctxt (m_sm, *this, state, sm_inst);
+  m_sm.on_stmt (&sm_ctxt, node, stmt);
+
+  if (const gassign *assign = dyn_cast <const gassign *> (stmt))
+    {
+      tree lhs = gimple_assign_lhs (assign);
+      tree rhs = gimple_assign_rhs1 (assign);
+      // FIXME: what about other kinds of assignment?
+      if (DEBUG)
+	inform (stmt->location, "got assignment: %qE <- %qE", lhs, rhs);
+
+      for (hash_set<var_state_1_t>::iterator iter = sm_inst->m_active_vars.begin ();
+	   iter != sm_inst->m_active_vars.end ();
+	   ++iter)
+	{
+	  if ((*iter).m_var == rhs)
+	    sm_inst->m_active_vars.add (var_state_1_t (lhs, (*iter).m_state));
+	}
+    }
+
+  // FIXME: linker issues with helper for "const greturn *"
+  if (greturn *return_ = dyn_cast <greturn *> (const_cast <gimple *> (stmt)))
+    {
+      tree retval = gimple_return_retval (return_);
+      if (retval)
+	{
+	  if (DEBUG)
+	    inform (stmt->location, "got return: %qE", retval);
+	  tree callee = node->m_fun->decl;
+	  tree result = DECL_RESULT (callee);
+	  for (hash_set<var_state_1_t>::iterator iter = sm_inst->m_active_vars.begin ();
+	       iter != sm_inst->m_active_vars.end ();
+	       ++iter)
+	    {
+	      if ((*iter).m_var == retval)
+		sm_inst->m_active_vars.add (var_state_1_t (result, (*iter).m_state));
+	    }
+	}
+    }
+}
+
+class foo_annotator : public dot_annotator
+{
+public:
+  void add_node_annotations (pretty_printer *pp,
+			     const supernode &n) const FINAL OVERRIDE
+  {
+    pp_printf (pp, "annotation_for_node_%i", n.m_index);
+    pp_printf (pp, " [shape=%s,style=filled,fillcolor=%s,label=\"",
+	       "record", "lightblue");
+    pp_left_brace (pp);
+    pp_write_text_to_stream (pp);
+    pp_right_brace (pp);
+    pp_string (pp, "\"];\n\n");
+    pp_flush (pp);
+  }
+};
+
+/* FIXME: toy implementation */
+
+static void
+toy_impl (const state_machine &sm, function *)
+{
+#if 1
+  supergraph sg;
+  sg.dump_dot ("test.dot");
+
+  context ctxt;
+  path_finder pf (&sg, sm, &ctxt);
+
+  cgraph_node *node;
+  FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
+  {
+    function *fun = node->get_fun ();
+
+    old_path_state init_state;
+    auto_bitmap visited;
+    sm_instance_1 sm_inst;
+    pf.dfs_traversal (sg.get_node_for_function_entry (fun),
+		      init_state, &sm_inst, visited);
+  }
+
+  inform (UNKNOWN_LOCATION,
+	  "num traversal calls: %i; max recursion depth: %i",
+	  pf.m_traversals, pf.m_max_recursion_depth);
+
+  foo_annotator a;
+  sg.dump_dot ("test-2.dot", &a);
+#else
+  gimple_stmt_iterator gsi;
+  basic_block bb;
+
+  auto_vec<basic_block> worklist;
+  FOR_EACH_BB_FN (bb, fun)
+    worklist.safe_push (bb);
+
+  /* Process the worklist.  */
+  while (!worklist.is_empty ())
+    {
+      basic_block bb = worklist.pop ();
+
+      for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
+	{
+	  gimple *stmt = gsi_stmt (gsi);
+	  print_gimple_stmt (stdout, stmt, 0, (dump_flags_t)0);
+	  sm.on_stmt (stmt);
+	}
+    }
+#endif
+}
+
+void
+toy_impl_1 (function *fn)
+{
+  malloc_state_machine sm;
+
+  toy_impl (sm, fn);
+}
diff --git a/gcc/analyzer/dead-code/toy-sm-impl-1.h b/gcc/analyzer/dead-code/toy-sm-impl-1.h
new file mode 100644
index 0000000..f017b84
--- /dev/null
+++ b/gcc/analyzer/dead-code/toy-sm-impl-1.h
@@ -0,0 +1 @@
+extern void toy_impl_1 (function *fn);
diff --git a/gcc/analyzer/digraph.cc b/gcc/analyzer/digraph.cc
new file mode 100644
index 0000000..d03fd6b
--- /dev/null
+++ b/gcc/analyzer/digraph.cc
@@ -0,0 +1,132 @@
+/* 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 "digraph.h"
+
+void
+digraph::dump_dot_to_pp (pretty_printer *pp, cluster *root_cluster) 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 ();
+
+  pp_string (pp, "overlap=false;\n");
+  pp_string (pp, "compound=true;\n");
+
+  // TODO: maybe (optionally) sub-subdivide by TU, for LTO
+
+  /* TODO: see also:
+     https://gcc-python-plugin.readthedocs.io/en/latest/_images/sample-supergraph.png
+  */
+
+  /* If using clustering, emit all nodes via clusters.  */
+  if (root_cluster)
+    {
+      int i;
+      dnode *n;
+      FOR_EACH_VEC_ELT (m_nodes, i, n)
+	root_cluster->add_node (n);
+      root_cluster->dump_dot (&gv);
+    }
+  else
+    {
+      /* Otherwise, display all nodes at top level.  */
+      int i;
+      dnode *n;
+      FOR_EACH_VEC_ELT (m_nodes, i, n)
+	n->dump_dot (&gv);
+    }
+
+  /* Edges.  */
+  int i;
+  dedge *e;
+  FOR_EACH_VEC_ELT (m_edges, i, e)
+    e->dump_dot (&gv);
+
+  /* Terminate "digraph" */
+  gv.outdent ();
+  pp_string (pp, "}");
+  pp_newline (pp);
+}
+
+void
+digraph::dump_dot_to_file (FILE *fp, cluster *root_cluster) const
+{
+  pretty_printer pp;
+  pp.buffer->stream = fp;
+  dump_dot_to_pp (&pp, root_cluster);
+  pp_flush (&pp);
+}
+
+void
+digraph::dump_dot (const char *path, cluster *root_cluster) const
+{
+  FILE *fp = fopen (path, "w");
+  dump_dot_to_file (fp, root_cluster);
+  fclose (fp);
+}
+
+void
+digraph::add_node (dnode *node)
+{
+  m_nodes.safe_push (node);
+}
+
+void
+digraph::add_edge (dedge *edge)
+{
+  m_edges.safe_push (edge);
+}
diff --git a/gcc/analyzer/digraph.h b/gcc/analyzer/digraph.h
new file mode 100644
index 0000000..54275d3
--- /dev/null
+++ b/gcc/analyzer/digraph.h
@@ -0,0 +1,97 @@
+/* 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_DIGRAPH_H
+#define GCC_ANALYZER_DIGRAPH_H
+
+/* Forwards decls.  */
+class dnode;
+class dedge;
+class cluster;
+
+/* Abstract base class for a node in a directed graph.  */
+
+class dnode
+{
+ public:
+  virtual ~dnode () {}
+
+  virtual void dump_dot (graphviz_out *gv) const = 0;
+};
+
+/* Abstract base class for an edge in a directed graph.  */
+
+class dedge
+{
+ public:
+  virtual ~dedge () {}
+
+  virtual void dump_dot (graphviz_out *gv) const = 0;
+};
+
+/* Abstract base class for a directed graph.  */
+
+class digraph
+{
+ public:
+  digraph () {}
+  virtual ~digraph () {}
+
+  void dump_dot_to_pp (pretty_printer *pp, cluster *root_cluster) const;
+  void dump_dot_to_file (FILE *fp, cluster *root_cluster) const;
+  void dump_dot (const char *path, cluster *root_cluster) const;
+
+  void add_node (dnode *node);
+  void add_edge (dedge *edge);
+
+  //protected:
+  auto_delete_vec<dnode> m_nodes;
+  auto_delete_vec<dedge> m_edges;
+};
+
+/* Abstract base class for splitting dnodes into hierarchical clusters
+   in the generated .dot file.
+
+   See "Subgraphs and Clusters" within
+     https://www.graphviz.org/doc/info/lang.html
+   and e.g.
+     https://graphviz.gitlab.io/_pages/Gallery/directed/cluster.html
+
+   If a root_cluster is passed to dump_dot*, then all nodes will be
+   added to it at the start of dumping, via calls to add_node.
+
+   The root cluster can organize the nodes into a hierarchy of
+   child clusters.
+
+   After all nodes are added to the root cluster, dump_dot will then
+   be called on it (and not on the nodes themselves).  */
+
+class cluster
+{
+ public:
+  virtual ~cluster () {}
+
+  virtual void add_node (dnode *node) = 0;
+
+  /* Recursively dump the cluster, all nodes, and child clusters.  */
+  virtual void dump_dot (graphviz_out *gv) const = 0;
+};
+
+#endif /* GCC_ANALYZER_DIGRAPH_H */
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
new file mode 100644
index 0000000..5281c16
--- /dev/null
+++ b/gcc/analyzer/engine.cc
@@ -0,0 +1,3910 @@
+/* FIXME.  */
+#include "config.h"
+#include "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "tm.h"
+#include "toplev.h"
+#include "hash-table.h"
+#include "vec.h"
+#include "ggc.h"
+#include "basic-block.h"
+#include "tree-ssa-alias.h"
+#include "internal-fn.h"
+#include "gimple-fold.h"
+#include "tree-eh.h"
+#include "gimple-expr.h"
+#include "is-a.h"
+#include "gimple.h"
+#include "tree-pass.h"
+#include "intl.h"
+#include "context.h"
+#include "diagnostic.h"
+#include "bitmap.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "gimple-pretty-print.h"
+#include "tree-pretty-print.h"
+#include "analyzer.h"
+#include "graphviz.h"
+#include "cgraph.h"
+#include "diagnostic-path.h"
+#include "gcc-rich-location.h"
+#include "tree-dfa.h"
+#include <string>
+#include "supergraph.h"
+#include "hardcoded-state-machine.h"
+#include "analyzer-logging.h"
+#include "stringpool.h"
+#include "selftest.h"
+#include "fold-const.h"
+#include "tristate.h"
+#include "selftest.h"
+
+////////////////////////////////////////////////////////////////////////////
+
+class sm_instance;
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Gotcha: it's required that hashables have a different "deleted" value
+   from the "empty" value.
+   We use reinterpret_cast<Type *> (1) for pointer deletion, following
+   pointer_hash <Type>::mark_deleted.  */
+
+struct var_state_t
+{
+  //typedef var_state_t value_type;
+  static tree get_placeholder () { return error_mark_node; }
+
+  var_state_t ()
+  : m_var (NULL_TREE), m_state (0)
+  {
+  }
+
+  var_state_t (tree var, state_machine::state_t state)
+  : m_var (var), m_state (state)
+  {}
+
+  bool is_placeholder_p () const { return m_var == get_placeholder (); }
+
+  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;
+  }
+
+  bool operator!= (const summary_point &other) const
+  {
+    return !(*this == other);
+  }
+
+  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 ();
+  }
+
+  bool operator== (const summary_edge &other) const
+  {
+    return m_start == other.m_start && m_end == other.m_end;
+  }
+
+  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 (reinterpret_cast<tree> (1), 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 == reinterpret_cast<tree> (1) && 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 (reinterpret_cast<tree> (1), 0)),
+		    summary_point (0, var_state_t (reinterpret_cast<tree> (1), 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 == reinterpret_cast<tree> (1);
+}
+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);
+  }
+
+  bool is_empty () const
+  {
+    return m_hashset.elements () == 0;
+  }
+
+  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;
+};
+
+struct per_stmt_t
+{
+  void add_transition (tree var,
+		       state_machine::state_t from,
+		       state_machine::state_t to)
+  {
+    // FIXME: bogus global states here
+    summary_edge e (summary_point (0, var_state_t (var, from)),
+		    summary_point (0, var_state_t (var, to)));
+    m_transitions.add (e);
+  }
+
+  set_of_summary_edges m_transitions;
+};
+
+class set_of_vars
+{
+public:
+  typedef hash_set<var_state_t>::iterator iterator;
+
+  set_of_vars () : m_set () {}
+  set_of_vars (const set_of_vars &other) : m_set ()
+  {
+    for (iterator i = other.begin (); i != other.end (); ++i)
+      {
+	m_set.add (*i);
+      }
+  }
+
+  set_of_vars& operator= (const set_of_vars &other)
+  {
+    m_set.empty ();
+    for (iterator i = other.begin (); i != other.end (); ++i)
+      {
+	m_set.add (*i);
+      }
+    return *this;
+  }
+
+  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
+{
+  typedef set_of_vars::iterator iterator;
+
+  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));
+  }
+
+  iterator begin () const { return m_active_vars.begin (); }
+  iterator end () const { return m_active_vars.end (); }
+
+  state_machine::state_t m_gstate;
+  set_of_vars m_active_vars;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+char *print (const char *fmt, ...)
+{
+  /* FIXME: all this cloning is likely to be expensive.  */
+  pretty_printer *pp = global_dc->printer->clone ();
+  pp_clear_output_area (pp);
+
+  text_info ti;
+  rich_location rich_loc (line_table, UNKNOWN_LOCATION);
+
+  va_list ap;
+
+  va_start (ap, fmt);
+
+  ti.format_spec = _(fmt);
+  ti.args_ptr = &ap;
+  ti.err_no = 0;
+  ti.x_data = NULL;
+  ti.m_richloc = &rich_loc;
+
+  pp_format (pp, &ti);
+  pp_output_formatted_text (pp);
+
+  va_end (ap);
+  char *result = xstrdup (pp_formatted_text (pp));
+
+  pp_clear_output_area (pp);
+
+  delete pp;
+
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+enum event_kind
+{
+  EK_DEBUG,
+  EK_STMT,
+  EK_FUNCTION_ENTRY,
+  EK_STATE_CHANGE,
+  EK_EDGE,
+  EK_CALL_SUMMARY,
+  EK_WARNING
+};
+
+static const char *
+event_kind_to_string (enum event_kind ek)
+{
+  switch (ek)
+    {
+    default:
+      gcc_unreachable ();
+    case EK_DEBUG:
+      return "EK_DEBUG";
+    case EK_STMT:
+      return "EK_STMT";
+    case EK_FUNCTION_ENTRY:
+      return "EK_FUNCTION_ENTRY";
+    case EK_STATE_CHANGE:
+      return "EK_STATE_CHANGE";
+    case EK_EDGE:
+      return "EK_EDGE";
+    case EK_CALL_SUMMARY:
+      return "EK_CALL_SUMMARY";
+    case EK_WARNING:
+      return "EK_WARNING";
+    }
+}
+
+/* FIXME.  */
+
+class checker_event : public diagnostic_event
+{
+public:
+  checker_event (enum event_kind kind,
+		 location_t loc, tree fndecl, int depth)
+    : m_kind (kind), m_loc (loc), m_fndecl (fndecl), m_depth (depth),
+      m_pending_diagnostic (NULL), m_emission_id ()
+  {
+  }
+
+  location_t get_location () const FINAL OVERRIDE { return m_loc; }
+  tree get_fndecl () const FINAL OVERRIDE { return m_fndecl; }
+  int get_stack_depth () const FINAL OVERRIDE { return m_depth; }
+
+  virtual checker_event *clone () const = 0;
+
+  std::string to_str () const
+  {
+    std::string result ("\"");
+    label_text event_desc (get_desc ());
+    result += event_desc.m_buffer;
+    event_desc.maybe_free ();
+    result += "\" (";
+    result += event_kind_to_string (m_kind);
+    result += ", depth ";
+    result += std::to_string (get_stack_depth ());
+    result += ", m_loc=";
+    result += std::to_string (m_loc);
+    result += ")";
+    return result;
+  }
+
+  void prepare_for_emission (pending_diagnostic *pd, event_id_t emission_id)
+  {
+    m_pending_diagnostic = pd;
+    m_emission_id = emission_id;
+
+    /* Call get_desc now, to ensure that any side-effects take place
+       before pending_diagnostic::emit is called.
+       In particular, state_change_event::get_desc can call
+       pending_diagnostic::describe_critical_state_change;
+       free_of_non_heap can use this to tweak the message.
+       (Would be neater to simply capture this within the state).  */
+    label_text desc = get_desc ();
+    desc.maybe_free ();
+  }
+
+  enum event_kind m_kind;
+  location_t m_loc;
+  tree m_fndecl;
+  int m_depth;
+  pending_diagnostic *m_pending_diagnostic;
+  event_id_t m_emission_id; // only set once all pruning has occurred
+};
+
+class debug_event : public checker_event
+{
+public:
+  debug_event (location_t loc, tree fndecl, int depth,
+	      const char *desc)
+  : checker_event (EK_DEBUG, loc, fndecl, depth),
+    m_desc (xstrdup (desc))
+  {
+  }
+  ~debug_event ()
+  {
+    free (m_desc);
+  }
+
+  label_text get_desc () const FINAL OVERRIDE
+  {
+    return label_text::borrow (m_desc);
+  }
+
+  checker_event *clone () const FINAL OVERRIDE
+  {
+    return new debug_event (m_loc, m_fndecl, m_depth, m_desc);
+  }
+
+private:
+  char *m_desc;
+};
+
+class statement_event : public checker_event
+{
+public:
+  statement_event (const gimple *stmt, tree fndecl, int depth)
+  : checker_event (EK_STMT, gimple_location (stmt), fndecl, depth),
+    m_stmt (stmt)
+  {
+  }
+
+  label_text get_desc () const FINAL OVERRIDE
+  {
+    pretty_printer pp;
+    pp_string (&pp, "stmt: ");
+    // FIXME: const_cast!
+    pp_gimple_stmt_1 (&pp, const_cast <gimple *> (m_stmt) , 0, (dump_flags_t)0);
+    return label_text::take (xstrdup (pp_formatted_text (&pp)));
+  }
+
+  checker_event *clone () const FINAL OVERRIDE
+  {
+    return new statement_event (m_stmt, m_fndecl, m_depth);
+  }
+
+  const gimple *m_stmt;
+};
+
+class function_entry_event : public checker_event
+{
+public:
+  function_entry_event (location_t loc, tree fndecl, int depth)
+  : checker_event (EK_FUNCTION_ENTRY, loc, fndecl, depth)
+  {
+  }
+
+  label_text get_desc () const FINAL OVERRIDE
+  {
+    return label_text::take (print ("entry to %qE", m_fndecl));
+  }
+
+  checker_event *clone () const FINAL OVERRIDE
+  {
+    return new function_entry_event (m_loc, m_fndecl, m_depth);
+  }
+
+  // FIXME
+};
+
+class state_change_event : public checker_event
+{
+public:
+  state_change_event (const supernode *node, const gimple *stmt,
+		      int stack_depth,
+		      const state_machine &sm,
+		      tree var,
+		      state_machine::state_t from,
+		      state_machine::state_t to,
+		      tree origin)
+  : checker_event (EK_STATE_CHANGE,
+		   stmt->location, node->m_fun->decl,
+		   stack_depth),
+    m_node (node), m_stmt (stmt), m_sm (sm),
+    m_var (var), m_from (from), m_to (to),
+    m_origin (origin)
+  {
+  }
+
+  label_text get_desc () const FINAL OVERRIDE
+  {
+    if (m_origin)
+      return label_text::take
+	(print ("state of %qE: %qs -> %qs (origin: %qE)",
+		m_var,
+		m_sm.get_state_name (m_from),
+		m_sm.get_state_name (m_to),
+		m_origin));
+    else
+      {
+	/* If there is no origin, then presumably this
+	   state change came directly from a pattern match.
+	   Attempt to generate a nicer human-readable description.
+	   For greatest precision-of-wording, give the pending diagnostic
+	   a chance to describe this state change (in terms of the
+	   diagnostic).
+	   Note that we only have a pending_diagnostic set on the event once
+	   the diagnostic is about to being emitted, so the description for
+	   an event can change.  */
+	if (m_pending_diagnostic)
+	  {
+	    label_text custom_desc
+	      = m_pending_diagnostic->describe_critical_state_change
+	      (//diagnostic_context *ctxt,
+	       m_var, m_from, m_to, m_emission_id);
+	    if (custom_desc.m_buffer)
+	      {
+		if (flag_analyzer_verbose_state_changes)
+		  {
+		    /* Append debug version.  */
+		    label_text result = label_text::take
+		      (print ("%s (state of %qE: %qs -> %qs, origin: NULL)",
+			      custom_desc.m_buffer,
+			      m_var,
+			      m_sm.get_state_name (m_from),
+			      m_sm.get_state_name (m_to)));
+		    custom_desc.maybe_free ();
+		    return result;
+		  }
+		else
+		  return custom_desc;
+	      }
+	  }
+
+	/* Fallback description.  */
+	return label_text::take
+	  (print ("state of %qE: %qs -> %qs (origin: NULL)",
+		  m_var,
+		  m_sm.get_state_name (m_from),
+		  m_sm.get_state_name (m_to)));
+      }
+  }
+
+  checker_event *clone () const FINAL OVERRIDE
+  {
+    return new state_change_event (m_node, m_stmt, m_depth,
+				   m_sm, m_var, m_from, m_to, m_origin);
+  }
+
+  const supernode *m_node;
+  const gimple *m_stmt;
+  const state_machine &m_sm;
+  tree m_var;
+  state_machine::state_t m_from;
+  state_machine::state_t m_to;
+  tree m_origin;
+};
+
+class edge_event : public checker_event
+{
+public:
+  edge_event (const superedge *edge,
+	      location_t loc, tree fndecl, int depth)
+  : checker_event (EK_EDGE,
+		   loc, fndecl, depth),
+    m_edge (edge)
+  {
+  }
+
+  label_text get_desc () const FINAL OVERRIDE
+  {
+    switch (m_edge->m_kind)
+      {
+      default:
+	gcc_unreachable ();
+      case SUPEREDGE_CFG_EDGE:
+	{
+	  char *edge_desc = m_edge->get_description ();
+	  if (strlen (edge_desc) > 0)
+	    {
+	      label_text result
+		= label_text::take (print ("taking %qs edge SN:%i -> SN:%i",
+					   edge_desc,
+					   m_edge->m_src->m_index,
+					   m_edge->m_dest->m_index));
+	      free (edge_desc);
+	      return result;
+	    }
+	  else
+	    {
+	      free (edge_desc);
+	      return label_text::take (print ("taking edge SN:%i -> SN:%i",
+					      m_edge->m_src->m_index,
+					      m_edge->m_dest->m_index));
+	    }
+	}
+      case SUPEREDGE_CALL:
+	{
+	  return label_text::take (print ("calling %qE from %qE",
+					  m_edge->m_dest->m_fun->decl,
+					  m_edge->m_src->m_fun->decl));
+	}
+
+      case SUPEREDGE_RETURN:
+	{
+	  return label_text::take (print ("returning to %qE from %qE",
+					  m_edge->m_dest->m_fun->decl,
+					  m_edge->m_src->m_fun->decl));
+	}
+
+      case SUPEREDGE_INTRAPROCEDURAL_CALL:
+	return label_text::take (// TODO: possibly a higher-level description?
+				 print ("taking edge SN:%i -> SN:%i",
+					m_edge->m_src->m_index,
+					m_edge->m_dest->m_index));
+      }
+  }
+
+  const cfg_superedge& get_cfg_superedge () const
+  {
+    gcc_assert (m_edge->m_kind == SUPEREDGE_CFG_EDGE);
+    return *m_edge->dyn_cast_cfg_superedge ();
+  }
+
+  const callgraph_superedge& get_callgraph_superedge () const
+  {
+    gcc_assert (m_edge->m_kind != SUPEREDGE_CFG_EDGE);
+    return *m_edge->dyn_cast_callgraph_superedge ();
+  }
+
+  checker_event *clone () const FINAL OVERRIDE
+  {
+    return new edge_event (m_edge, m_loc, m_fndecl, m_depth);
+  }
+
+  const superedge *m_edge;
+};
+
+class call_summary : public checker_event
+{
+public:
+  call_summary (const supernode *caller_node,
+		cgraph_edge *edge, int depth,
+		sm_instance callee_sm_inst)
+  : checker_event (EK_CALL_SUMMARY,
+		   edge->call_stmt->location,
+		   caller_node->m_fun->decl,
+		   depth),
+    m_caller_node (caller_node),
+    m_edge (edge),
+    m_callee_sm_inst (callee_sm_inst)
+  {
+  }
+
+  label_text get_desc () const FINAL OVERRIDE
+  {
+    return label_text::take (print
+			     ("call summary: call to %qD from %qD",
+			      m_edge->callee->get_fun ()->decl,
+			      m_caller_node->m_fun->decl));
+  }
+
+  checker_event *clone () const FINAL OVERRIDE
+  {
+    return new call_summary (m_caller_node, m_edge, m_depth, m_callee_sm_inst);
+  }
+
+  // FIXME
+
+  call_superedge *get_edge_for_call (const supergraph *sg) const
+  {
+    return sg->get_edge_for_call (m_edge);
+  }
+  return_superedge *get_edge_for_return (const supergraph *sg) const
+  {
+    return sg->get_edge_for_return (m_edge);
+  }
+
+  const supernode *m_caller_node;
+  cgraph_edge *m_edge;
+  sm_instance m_callee_sm_inst;
+};
+
+/* FIXME: a repeat of the warning message at the end of the path.  */
+
+class warning_event : public checker_event
+{
+public:
+  warning_event (location_t loc, tree fndecl, int depth,
+		 const char *msg)
+  : checker_event (EK_WARNING, loc, fndecl, depth),
+    m_msg (xstrdup (msg))
+  {
+  }
+  ~warning_event () { free (m_msg); }
+
+  label_text get_desc () const FINAL OVERRIDE
+  {
+    return label_text::borrow (m_msg);
+  }
+
+  warning_event *clone () const FINAL OVERRIDE
+  {
+    return new warning_event (m_loc, m_fndecl, m_depth, m_msg);
+  }
+
+  char *m_msg;
+};
+
+/* FIXME.  */
+
+class checker_path : public diagnostic_path
+{
+public:
+  checker_path () : diagnostic_path (), m_stack_depth (0) {}
+  checker_path (const checker_path &other);
+  checker_path& operator= (const checker_path &other);
+
+  std::string to_str () const
+  {
+    std::string result ("[");
+
+    checker_event *e;
+    int i;
+    FOR_EACH_VEC_ELT (m_events, i, e)
+      {
+	if (i > 0)
+	  result += ", ";
+	label_text event_desc (e->get_desc ());
+	result += std::string ("\"") + event_desc.m_buffer + std::string ("\"");
+	event_desc.maybe_free ();
+      }
+
+    return result + "]";
+  }
+
+  void maybe_log (logger *logger, const char *desc) const
+  {
+    if (!logger)
+      return;
+    logger->log ("%s: %s", desc, to_str ().c_str ());
+    for (int i = 0; i < m_events.length (); i++)
+      logger->log ("%s[%i]: %s",
+		   desc, i, m_events[i]->to_str ().c_str ());
+  }
+
+  unsigned num_events () const FINAL OVERRIDE
+  {
+    return m_events.length ();
+  }
+
+  const diagnostic_event & get_event (int idx) const FINAL OVERRIDE
+  {
+    return *m_events[idx];
+  }
+
+  void add_event (checker_event *event)
+  {
+    m_events.safe_push (event);
+  }
+
+  void add_event_at (checker_event *event, int idx)
+  {
+    m_events.safe_insert (idx, event);
+  }
+
+  void delete_event (int idx)
+  {
+#if 1
+    checker_event *event = m_events[idx];
+    m_events.ordered_remove (idx);
+    delete event;
+#endif
+  }
+
+  void prepare_for_emission (pending_diagnostic *pd)
+  {
+    checker_event *e;
+    int i;
+    FOR_EACH_VEC_ELT (m_events, i, e)
+      e->prepare_for_emission (pd, event_id_t (i));
+  }
+
+  event_id_t get_final_event_id () const
+  {
+    unsigned num = num_events ();
+    if (num > 0)
+      return event_id_t (num - 1);
+    else
+      return event_id_t ();
+  }
+
+  auto_delete_vec<checker_event> m_events;
+  int m_stack_depth;
+};
+
+checker_path::checker_path (const checker_path &other)
+: m_events (), //other.m_events.length ()),
+  m_stack_depth (other.m_stack_depth)
+{
+  checker_event *e;
+  int i;
+  FOR_EACH_VEC_ELT (other.m_events, i, e)
+    m_events.safe_push (e->clone ());
+  // FIXME: quick push
+#if 0
+  fprintf (stderr, "DBG: diagnostic_path copy-ctor (length=%i)\n", m_events.length ());
+#endif
+}
+
+checker_path&
+checker_path::operator= (const checker_path &other)
+{
+  m_stack_depth = other.m_stack_depth;
+
+  checker_event *e;
+  int i;
+  FOR_EACH_VEC_ELT (other.m_events, i, e)
+    m_events.safe_push (e->clone ());
+  return *this;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+class backtrace
+{
+public:
+  struct link
+  {
+    link (const superedge *edge, const supernode *node)
+    : m_edge (edge), m_node (node)
+    {}
+
+    const superedge *m_edge;
+    const supernode *m_node;
+  };
+
+  backtrace ()
+  : m_path (), m_links () {}
+
+  backtrace (const backtrace &other)
+  : m_path (other.m_path),
+    m_links ()
+  {
+    // FIXME: auto_vec's copy_ctor...
+    int i;
+    link *link;
+    FOR_EACH_VEC_ELT (other.m_links, i, link)
+      m_links.safe_push (*link);
+  }
+
+  void push (const superedge *edge, const supernode *node)
+  {
+    m_links.safe_push (link (edge, node));
+  }
+
+  std::string to_str () const
+  {
+    std::string result ("[");
+    // FIXME: this is probably not optimal
+
+    int i;
+    link *link;
+    FOR_EACH_VEC_ELT (m_links, i, link)
+      {
+	if (i > 0)
+	  result += ", ";
+	result += std::to_string (link->m_node->m_index);
+      }
+    result += "]";
+    return result;
+  }
+
+  gimple *get_last_stmt () const
+  {
+    int i;
+    link *link;
+    FOR_EACH_VEC_ELT_REVERSE (m_links, i, link)
+      {
+	if (gimple *last_stmt = link->m_node->get_last_stmt ())
+	  return last_stmt;
+      }
+    return NULL;
+  }
+
+  checker_path m_path;
+  auto_vec <link> m_links; // FIXME: copy-ctor etc
+};
+
+#include "constraints.h" // FIXME
+
+class candidate_diagnostic
+{
+public:
+  candidate_diagnostic (const supernode *node, const gimple *stmt,
+			tree var, state_machine::state_t state,
+			pending_diagnostic *pd, const checker_path *path)
+  : m_node (node), m_stmt (stmt), m_var (var), m_state (state), m_pd (pd), 
+    m_path (*path)
+  {
+  }
+
+  ~candidate_diagnostic ()
+  {
+    delete m_pd;
+  }
+
+  const supernode *m_node;
+  const gimple *m_stmt;
+  tree m_var;
+  state_machine::state_t m_state;
+  pending_diagnostic *m_pd;
+  checker_path m_path;
+};
+
+class engine : public log_user
+{
+public:
+  engine (const supergraph &sg, const state_machine &sm,
+	  logger *logger, int verbosity)
+  : log_user (logger), m_sg (sg), m_sm (sm), m_per_node_data (),
+    m_per_stmt_data (),
+    m_verbosity (verbosity),
+    m_candidate_diagnostics (),
+    m_num_traverse_calls (0),
+    m_num_cache_hits (0),
+    m_on_stmt_calls (0)
+  {
+  }
+
+  void validate_sm_instance (const sm_instance &sm_inst,
+			     const supernode *node);
+
+  void validate_tree (tree var, const supernode *node);
+
+  void validation_failure (const supernode *node, tree var, tree var_fndecl);
+
+  void add_events_for_node (checker_path *path, const supernode *node,
+			    int stack_depth);
+
+  void prune_active_vars_to_cache_misses (sm_instance *sm_inst,
+					  const supernode *node);
+
+  void add_blk_edges_to_cache (const supernode *node,
+			       const sm_instance &sm_inst,
+			       const sm_instance &old_sm_inst);
+
+  void dfs_traverse (sm_instance sm_inst,
+		     sm_instance sm_inst_from_returning_call,
+		     backtrace b,
+		     path_state ps, const supernode *node,
+		     const superedge *last_edge);
+  void on_phi (const supernode *node, const gphi *phi,
+	       sm_instance *sm_inst, const sm_instance *old_sm_inst,
+	       const backtrace &b, checker_path *path, path_state *ps,
+	       const superedge *last_edge);
+  void on_stmt (const supernode *node, const gimple *stmt,
+		sm_instance *sm_inst, const sm_instance *old_sm_inst,
+		const backtrace &b, checker_path *path, path_state *ps);
+  void on_assignment (const supernode *node, const gimple *stmt,
+		      sm_instance *sm_inst, const sm_instance *old_sm_inst,
+		      path_var lhs, path_var rhs, const backtrace &b,
+		      path_state *ps);
+
+  void relax (backtrace b);
+
+  sm_instance refine_for_call (sm_instance sm_inst, cgraph_edge *edge,
+			       const supernode *caller_node,
+			       const backtrace &b);
+  sm_instance restore_after_call (sm_instance sm_inst, sm_instance caller_sm_inst,
+				  cgraph_edge *edge,
+				  const supernode *rest_of_caller_node,
+				  const backtrace &b);
+  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;
+  }
+
+  per_stmt_t &get_data_for_stmt (const gimple *stmt)
+  {
+    if (per_stmt_t **ptr_ptr = m_per_stmt_data.get (stmt))
+      return **ptr_ptr;
+    per_stmt_t *data = new per_stmt_t (); // FIXME: can we make it not be indirect?
+    m_per_stmt_data.put (stmt, data);
+    return *data;
+  }
+
+  void log_stats () const;
+
+  void detect_leaks (const supernode *node, const sm_instance &sm_inst,
+		     const backtrace &b, const path_state &ps);
+
+  void emit_candidate_diagnostics ();
+
+  checker_path *expand_call_summary (const call_summary *summary_event,
+				     tree var, state_machine::state_t state);
+  const superedge *get_next_expansion_edge (const supernode *node,
+					    summary_edge desired_suffix);
+  checker_path *expand_path (const checker_path &path,
+			     tree var, state_machine::state_t state);
+  void prune_path (checker_path *path, tree var, state_machine::state_t state);
+  bool is_valid_path (const checker_path &path);
+  void maybe_emit (const candidate_diagnostic &cd);
+
+  void dump_analyzer_summary (const supernode *node) const;
+  void dump_analyzer_summary (const supernode *node, tree var) 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;
+
+  hash_map <const gimple *, per_stmt_t *> m_per_stmt_data;
+
+  int m_verbosity;
+
+  auto_delete_vec <candidate_diagnostic> m_candidate_diagnostics;
+
+  int m_num_traverse_calls;
+  int m_num_cache_hits;
+  int m_on_stmt_calls;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Verify that SM_INST is valid w.r.t. NODE.
+   For example, the only locals within it should be for NODE's function.  */
+
+void
+engine::validate_sm_instance (const sm_instance &sm_inst,
+			      const supernode *node)
+{
+  for (sm_instance::iterator iter = sm_inst.begin ();
+       iter != sm_inst.end ();
+       ++iter)
+    {
+      tree var = (*iter).m_var;
+      gcc_assert (var);
+      if (var == var_state_t::get_placeholder ())
+	continue;
+
+      validate_tree (var, node);
+    }
+}
+
+/* FIXME.  */
+
+void
+engine::validate_tree (tree var, const supernode *node)
+{
+  if (CONSTANT_CLASS_P (var))
+    return;
+
+  if (TREE_CODE (var) == MEM_REF)
+    {
+      validate_tree (TREE_OPERAND (var, 0), node);
+      validate_tree (TREE_OPERAND (var, 1), node);
+      return;
+    }
+  if (TREE_CODE (var) == COMPONENT_REF
+      || TREE_CODE (var) == ADDR_EXPR)
+    {
+      validate_tree (TREE_OPERAND (var, 0), node);
+      return;
+    }
+
+  /* If we have an SSA_NAME for a temporary, verify that the
+     def_stmt is in the same function as NODE.  */
+  if (TREE_CODE (var) == SSA_NAME
+      && SSA_NAME_VAR (var) == NULL)
+    {
+      gimple *def_stmt = SSA_NAME_DEF_STMT (var);
+      basic_block bb = def_stmt->bb;
+      supernode *def_stmt_node = m_sg.get_node_for_block (bb);
+      if (node->m_fun != def_stmt_node->m_fun)
+	validation_failure (node, var, def_stmt_node->m_fun->decl);
+      return;
+    }
+
+  if (TREE_CODE (var) == SSA_NAME)
+    var = SSA_NAME_VAR (var);
+
+  tree context = DECL_CONTEXT (var);
+  if (context && TREE_CODE (context) != TRANSLATION_UNIT_DECL)
+    if (context != node->m_fun->decl)
+      validation_failure (node, var, context);
+}
+
+/* FIXME.  */
+
+void
+engine::validation_failure (const supernode *node, tree var, tree var_fndecl)
+{
+  log ("sm_instance validation failure"
+       " for SN: %i (in %qE): %qE is from %qE",
+       node->m_index, node->m_fun->decl,
+       var, var_fndecl);
+  if (0)
+    internal_error ("sm_instance validation failure"
+		    " for SN: %i (in %qE): %qE is from %qE",
+		    node->m_index, node->m_fun->decl,
+		    var, var_fndecl);
+}
+
+/* FIXME.  */
+
+void
+engine::add_events_for_node (checker_path *path, const supernode *node,
+			     int stack_depth)
+{
+  if (m_verbosity > 1)
+    {
+      path->add_event (new debug_event (node->get_start_location (),
+					node->m_fun->decl,
+					stack_depth,
+					print ("SN: %i", node->m_index)));
+    }
+  if (m_verbosity > 0 && node->entry_p ())
+    {
+      path->add_event
+	(new function_entry_event (node->get_start_location (),
+				   node->m_fun->decl,
+				   stack_depth));
+    }
+}
+
+
+/* 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)
+{
+  JIT_LOG_FUNC_1 (get_logger (), "SN: %i", node->m_index);
+  if (get_logger ())
+    {
+      log ("sm_inst: %s", sm_inst->to_str (m_sm).c_str ());
+      log ("blk_add: %s",
+	   get_data_for_node (node).m_blk_add.to_str (m_sm).c_str ());
+      log ("blk_transition: %s",
+	   get_data_for_node (node).m_blk_transition.to_str (m_sm).c_str ());
+    }
+
+  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)
+    {
+      var_state_t cached_start_vs = (*iter).m_start.m_varstate;
+      // FIXME: I think we need a match on gstate, too
+      if (sm_inst->m_active_vars.m_set.contains (cached_start_vs))
+	{
+	  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 (cached_start_vs);
+	  m_num_cache_hits++;
+	}
+    }
+}
+
+#if 0
+static void
+make_path_from_backtrace (checker_path *out_path,
+			  engine *e, const backtrace &b,
+			  const gimple *final_stmt)
+{
+  int i;
+  backtrace::link *link;
+  FOR_EACH_VEC_ELT (b.m_links, i, link)
+    {
+      const supernode *node = link->m_node;
+      int j;
+      gimple *stmt;
+      FOR_EACH_VEC_ELT (node->m_stmts, j, stmt)
+	{
+	  out_path->add_event (stmt->location, node->m_fun->decl,
+			       0, /* FIXME.  */
+			       "FIXME");
+	  if (stmt == final_stmt)
+	    return;
+	}
+    }
+}
+#endif
+
+class engine_sm_context : public sm_context
+{
+public:
+  engine_sm_context (engine *e, const backtrace &b, sm_instance *sm_inst,
+		     const sm_instance *old_sm_inst, checker_path *path,
+		     path_state *ps)
+    : m_engine (e), m_backtrace (b),
+      m_sm_inst (sm_inst), m_old_sm_inst (old_sm_inst),
+      m_path (path), m_ps (ps) {}
+
+  void on_transition (const supernode *node, const gimple *stmt,
+		      tree var,
+		      state_machine::state_t from,
+		      state_machine::state_t to,
+		      tree origin) FINAL OVERRIDE
+  {
+    JIT_LOG_FUNC_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 ());
+
+    // FIXME: do it for everything in the equivalence class
+    // FIXME: stack depth:
+    equiv_class_id ec_id = m_ps->get_or_add_equiv_class (path_var (var, 0));
+    equiv_class &ec = ec_id.get_obj (*m_ps);
+
+    if (m_engine->get_logger ())
+      m_engine->log ("considering equivalence class: %s",
+		     ec.to_str ().c_str ());
+    unsigned i;
+    path_var *ec_var;
+    FOR_EACH_VEC_ELT (ec.m_vars, i, ec_var)
+      {
+	if (m_engine->get_logger ())
+	  m_engine->log ("considering %s",
+			 ec_var->to_str ().c_str ());
+	// TODO
+	var_state_t vs_from (ec_var->m_tree, from);
+	if (const_cast <sm_instance *> (m_old_sm_inst)->m_active_vars.m_set.contains (vs_from))
+	  {
+	    m_engine->log ("from state found");
+	    // FIXME: why is the check on the old sm_inst vs the new sm_inst?
+	    if (m_sm_inst->m_active_vars.m_set.contains (vs_from))
+	      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 (ec_var->m_tree))
+		  continue;
+	      }
+	    else
+	      continue;
+	  }
+
+	var_state_t new_vs (ec_var->m_tree, to);
+	m_sm_inst->m_active_vars.m_set.add (new_vs);
+	if (m_engine->get_logger ())
+	  m_engine->log ("adding active var: %s",
+			 new_vs.to_str (m_engine->m_sm).c_str ());
+
+	if (m_path)
+	  {
+	    checker_event *event
+	      = new state_change_event (node, stmt, m_path->m_stack_depth,
+					m_engine->m_sm,
+					ec_var->m_tree, from, to, origin);
+	    m_path->add_event (event);
+	    if (m_engine->get_logger ())
+	      m_engine->log ("adding event: %s", event->to_str ().c_str ());
+	  }
+
+	per_stmt_t &stmt_data = m_engine->get_data_for_stmt (stmt);
+	stmt_data.add_transition (var, from, 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 warn_for_state (const supernode *node, const gimple *stmt,
+		       tree var, state_machine::state_t state,
+		       pending_diagnostic *d) FINAL OVERRIDE
+  {
+    JIT_LOG_FUNC (m_engine->get_logger ());
+    gcc_assert (d); // take ownership
+
+    var_state_t vs_from (var, state);
+    if (!start_start_p (state) &&
+	!const_cast <sm_instance *> (m_old_sm_inst)->m_active_vars.m_set.contains (vs_from))
+      {
+	if (m_engine->get_logger ())
+	  m_engine->log ("not in state %s; no error",
+			 m_engine->m_sm.get_state_name (state));
+	delete d;
+	return;
+      }
+
+    candidate_diagnostic *cd
+      = new candidate_diagnostic (node, stmt, var, state, d, m_path);
+    if (0)
+      {
+	m_engine->log ("recording candidate diagnostic %i",
+		       m_engine->m_candidate_diagnostics.length ());
+	m_engine->m_candidate_diagnostics.safe_push (cd);
+      }
+    else
+      {
+	// Immediate emission (easier to debug):
+	m_engine->maybe_emit (*cd);
+	delete cd;
+      }
+  }
+
+private:
+  bool make_full_backtrace (backtrace *out_backtrace)
+  {
+    // TODO: find a full, feasible way to get the issue to fire
+    int i;
+    backtrace::link *link;
+    FOR_EACH_VEC_ELT (m_backtrace.m_links, i, link)
+      {
+	out_backtrace->m_links.safe_push (*link);
+	// TODO: expand intraprocedural edges, somehow:
+	// maybe recursively try to build a viable subpath
+	// connecting the endstates, based on what we already know
+	/* But we don't e.g. want to fully unroll loops etc.  */
+	/* Maybe we can get it from the suffix summaries.  */
+	/* I *think* that any input m_backtrace we have is at one
+	   level, with a possible suffix that descends into another
+	   function.  */
+	/* There are two things here:
+	   - is the path feasible?
+	   - are the nodes interesting to the user?
+	   We only want the output to drill down into a call if there's
+	   a pertinent state change within the call.
+	   We can probably determine that via the blk_add/blk_transition
+	   (though it doesn't seem to show up for calls, currently).  */
+      }
+
+    return true;
+  }
+
+  engine *m_engine;
+  const backtrace &m_backtrace;
+  sm_instance *m_sm_inst;
+  const sm_instance *m_old_sm_inst;
+  checker_path *m_path;
+  path_state *m_ps;
+};
+
+/* FIXME.  */
+
+void
+engine::add_blk_edges_to_cache (const supernode *node,
+				const sm_instance &sm_inst,
+				const sm_instance &old_sm_inst)
+{
+  JIT_LOG_FUNC_1 (get_logger (), "SN: %i", node->m_index);
+  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 ("blk_add[%i] was: %s",
+	   node->m_index,
+	   get_data_for_node (node).m_blk_add.to_str (m_sm).c_str ());
+      log ("blk_transition[%i] was: %s",
+	   node->m_index,
+	   get_data_for_node (node).m_blk_transition.to_str (m_sm).c_str ());
+    }
+
+  /* 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
+	  = old_sm_inst.m_active_vars.has_expr ((*iter).m_var))
+	{
+	  get_data_for_node (node).m_blk_transition.add
+	    (summary_edge (summary_point (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 (old_sm_inst.m_gstate,
+					  old_state),
+			   e));
+	}
+    }
+
+  if (get_logger ())
+    {
+      log ("blk_add[%i] now: %s",
+	   node->m_index,
+	   get_data_for_node (node).m_blk_add.to_str (m_sm).c_str ());
+      log ("blk_transition[%i] now: %s",
+	   node->m_index,
+	   get_data_for_node (node).m_blk_transition.to_str (m_sm).c_str ());
+    }
+}
+
+/* FIXME.  */
+
+void
+engine::dfs_traverse (sm_instance sm_inst,
+		      sm_instance sm_inst_from_returning_call,
+		      backtrace b,
+		      path_state ps, const supernode *node,
+		      const superedge *last_edge)
+{
+  JIT_LOG_FUNC_1 (get_logger (), "SN: %i", node->m_index);
+  m_num_traverse_calls++;
+
+  b.push (last_edge, node);
+  if (get_logger ())
+    {
+      log ("sm_inst: %s", sm_inst.to_str (m_sm).c_str ());
+      log ("sm_inst_from_returning_call: %s",
+	   sm_inst_from_returning_call.to_str (m_sm).c_str ());
+      log ("backtrace: %s", b.to_str ().c_str ());
+      log ("backtrace.m_path: %s", b.m_path.to_str ().c_str ());
+      log ("path_state: %s", ps.to_str ().c_str ());
+    }
+
+  auto_cfun sentinel (node->m_fun);
+  input_location = node->get_start_location ();
+
+  validate_sm_instance (sm_inst, node);
+  validate_sm_instance (sm_inst_from_returning_call, node);
+
+  add_events_for_node (&b.m_path, node, b.m_path.m_stack_depth);
+
+  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
+
+  /* FIXME: returning call: the 2nd half.  */
+  if (node->m_returning_call)
+    {
+      // FIXME: assert no phi nodes
+      /* This ensures that transitions for the effect of the call
+	 show up, in this node.  */
+      sm_inst = sm_inst_from_returning_call;
+      // TODO: prune to cache!
+    }
+
+  /* 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;
+    }
+
+  /* Phi nodes.  */
+  // FIXME: do these have to somehow happen simultaneously?
+  for (gphi_iterator gpi = const_cast<supernode *>(node)->start_phis ();
+       !gsi_end_p (gpi); gsi_next (&gpi))
+    {
+      sm_instance per_stmt_old_sm_inst = sm_inst;
+      gphi *phi = gpi.phi ();
+      on_phi (node, phi, &sm_inst, &per_stmt_old_sm_inst,
+	      b, &b.m_path, &ps, last_edge);
+    }
+
+  /* Stmts.  */
+  int i;
+  gimple *stmt;
+  FOR_EACH_VEC_ELT (node->m_stmts, i, stmt)
+    {
+      sm_instance per_stmt_old_sm_inst = sm_inst;
+      input_location = stmt->location;
+      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))
+	{
+#if 1
+	  add_blk_edges_to_cache (node, sm_inst, per_block_old_sm_inst);
+#endif
+	  follow_call (sm_inst, b, ps, node, edge); // TODO
+	  return;
+	}
+    }
+
+  add_blk_edges_to_cache (node, sm_inst, per_block_old_sm_inst);
+
+  if (node->return_p ())
+    {
+      detect_leaks (node, sm_inst, b, ps);
+      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);
+
+	   checker_event *event
+	     = new edge_event (succ,
+			       (last_stmt
+				? last_stmt->location
+				: UNKNOWN_LOCATION),
+			       node->m_fun->decl,
+			       new_b.m_path.m_stack_depth);
+	   new_b.m_path.add_event (event);
+	   
+	   sm_instance per_edge_new_sm_inst = sm_inst;
+	   engine_sm_context ctxt (this, new_b,
+				   &per_edge_new_sm_inst,
+				   &sm_inst,
+				   &new_b.m_path, &new_ps);
+	   m_sm.on_edge (&ctxt, last_stmt, succ);
+
+#if 0
+	   /* This could have updated the state.  Make sure this effect
+	      is recorded in the cache.  */
+	   add_blk_edges_to_cache (node, per_edge_new_sm_inst,
+				   per_block_old_sm_inst);
+#endif
+
+	   dfs_traverse (per_edge_new_sm_inst, per_edge_new_sm_inst,
+			 new_b, new_ps, succ->m_dest,
+			 succ);
+	}
+    }
+}
+
+void
+engine::on_phi (const supernode *node, const gphi *phi,
+		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 */
+		checker_path *path ATTRIBUTE_UNUSED,
+		path_state *ps,
+		const superedge *last_edge)
+{
+  JIT_LOG_FUNC (get_logger ());
+  if (get_logger ())
+    {
+      pretty_printer pp;
+      pp_gimple_stmt_1 (&pp, const_cast <gphi *> (phi) , 0, (dump_flags_t)0);
+      // FIXME: const_cast!
+      log ("phi: %s", pp_formatted_text (&pp));
+      log ("sm_inst: %s", sm_inst->to_str (m_sm).c_str ());
+    }
+
+  gcc_assert (last_edge);
+  const cfg_superedge *last_cfg_superedge = last_edge->dyn_cast_cfg_superedge ();
+  gcc_assert (last_cfg_superedge);
+  // FIXME: this ought to be an as_a<>
+
+  tree src = last_cfg_superedge->get_phi_arg (phi);
+  tree lhs = gimple_phi_result (phi);
+  on_assignment (node, phi, sm_inst, old_sm_inst, ps->make_path_var (lhs),
+		 ps->make_path_var (src), b, ps);
+}
+
+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 */
+		 checker_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 ());
+    }
+
+  checker_event *event
+    = new statement_event (stmt,
+			   node->m_fun->decl,
+			   path->m_stack_depth);
+  path->add_event (event);
+
+  engine_sm_context ctxt (this, b, sm_inst, old_sm_inst, path, ps);
+  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:
+	case SSA_NAME:
+	case INTEGER_CST:
+	  {
+	    /* Direct assignment of the form LHS = RHS;  */
+	    on_assignment (node, assign, sm_inst, old_sm_inst,
+			   ps->make_path_var (lhs),
+			   ps->make_path_var (rhs1),
+			   b, 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));
+	  }
+	  break;
+
+	case COMPONENT_REF:
+	  // TODO: propagate state
+	  {
+	    /* Copy the state of RHS to LHS.  */
+	    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 == TREE_OPERAND (rhs1, 0))
+		  {
+		    log ("got match for COMPONENT_REF");
+		    engine_sm_context ctxt (this, b, sm_inst, old_sm_inst,
+					    path, ps);
+		    // 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, (*iter).m_var);
+		  }
+	      }
+	  }
+	  break;
+	}
+    }
+  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,
+		       ps->make_path_var (lhs),
+		       ps->make_path_var (rhs), b, 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
+	  checker_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,
+		       path_var lhs, path_var rhs, const backtrace &b,
+		       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", lhs.to_str ().c_str ());
+      log ("rhs: %s", rhs.to_str ().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 (lhs, rhs);
+
+  /* Copy the state of RHS to LHS.  */
+  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.m_tree)
+	{
+	  log ("got match");
+	  engine_sm_context ctxt (this, b, sm_inst, old_sm_inst,
+				  NULL /* FIXME */, ps);
+	  // FIXME: we want to overwrite any old states
+	  // (but respect the gstate, I think):
+	  ctxt.on_transition (node, stmt, lhs.m_tree, 0/* FIXME */,
+			      (*iter).m_state, rhs.m_tree);
+	}
+    }
+
+  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 ());
+      log ("backtrace.m_path: %s", b.m_path.to_str ().c_str ());
+    }
+
+  const supernode *node = b.m_links.pop ().m_node;
+  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_links.length () > 0)
+    {
+      per_node_t &node_data = get_data_for_node (node);
+      const supernode *prev = b.m_links.pop ().m_node;
+      per_node_t &prev_data = get_data_for_node (prev);
+
+      log ("propagating from SN: %i to SN: %i", node->m_index, prev->m_index);
+
+      /* 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);
+	  if (get_logger ())
+	    {
+	      log ("considering sfx_add[%i]: %s",
+		   node->m_index, e.to_str (m_sm).c_str ());
+	      log ("  with m_blk_transition[%i]: %s",
+		   prev->m_index,
+		   prev_data.m_blk_transition.to_str (m_sm).c_str ());
+	    }
+
+	  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);
+		  if (get_logger ())
+		    log ("adding to sfx_add[%i]: %s",
+			 prev->m_index, e2.to_str (m_sm).c_str ());
+		}
+	    }
+	}
+
+      /* 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)
+		{
+		  summary_edge new_e (s.m_start, e.m_end);
+		  prev_data.m_sfx_transition.add (new_e);
+		  if (get_logger ())
+		    log ("adding to sfx_transition[%i]: %s",
+			 prev->m_index, new_e.to_str (m_sm).c_str ());
+		}
+	    }
+	  /* 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)
+		{
+		  summary_edge new_e (s.m_start, e.m_end);
+		  prev_data.m_sfx_add.add (new_e);
+		  if (get_logger ())
+		    log ("adding to sfx_add[%i]: %s",
+			 prev->m_index, new_e.to_str (m_sm).c_str ());
+		}
+	    }
+	}
+
+      node = prev;
+    }
+}
+
+sm_instance
+engine::refine_for_call (sm_instance sm_inst, cgraph_edge *edge,
+			 const supernode *caller_node, const backtrace &b)
+{
+  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 caller_stack_depth = b.m_path.m_stack_depth;
+  int callee_stack_depth = caller_stack_depth + 1;
+
+  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
+      path_state ps; // FIXME
+      tree parm_default_ssa = ssa_default_def (edge->callee->get_fun (), parm);
+      if (parm_default_ssa)
+	on_assignment (caller_node, call,
+		       &sm_inst_for_call, &sm_inst /* FIXME.  */,
+		       path_var (parm_default_ssa, callee_stack_depth),
+		       path_var (arg, caller_stack_depth),
+		       b, &ps);
+#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_in_callee,
+			    sm_instance orig_sm_inst_in_caller,
+			    cgraph_edge *edge,
+			    const supernode *rest_of_caller_node,
+			    const backtrace &b)
+{
+  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_in_callee: %s", sm_inst_in_callee.to_str (m_sm).c_str ());
+      log ("orig_sm_inst_in_caller: %s",
+	   orig_sm_inst_in_caller.to_str (m_sm).c_str ());
+    }
+
+  sm_instance sm_inst_in_caller;
+
+  tree callee = edge->callee->get_fun ()->decl;
+  tree result = DECL_RESULT (callee);
+  gcall *call_stmt = rest_of_caller_node->m_returning_call;
+  tree lhs = gimple_call_lhs (call_stmt);
+  if (lhs)
+    {
+      path_state ps; // FIXME
+      int callee_stack_depth = b.m_path.m_stack_depth;
+      int caller_stack_depth = callee_stack_depth - 1;
+      on_assignment (rest_of_caller_node, call_stmt,
+		     &sm_inst_in_caller, &sm_inst_in_callee /* FIXME.  */,
+		     path_var (lhs, caller_stack_depth),
+		     path_var (result, callee_stack_depth), b, &ps);
+    }
+
+  // TODO: map formal params back to actual args:
+  int i = 0;
+  for (tree parm = DECL_ARGUMENTS (callee); parm;
+       parm = DECL_CHAIN (parm), ++i)
+    {
+      tree arg = gimple_call_arg (call_stmt, i);
+#if 0
+      on_assignment (caller_node, call,
+		     &sm_inst_for_call, &sm_inst /* FIXME.  */,
+		     parm, arg);
+#endif
+      path_state ps; // FIXME
+#if 0
+      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, &ps);
+#endif
+
+      /* Use: parm, arg, next_sm_inst, to map from parms in callee
+	 back to args in caller */
+      for (set_of_vars::iterator iter
+	     = sm_inst_in_callee.m_active_vars.begin ();
+	   iter != sm_inst_in_callee.m_active_vars.end ();
+	   ++iter)
+	{
+	  // FIXME: may want ECs for this
+	  tree var = (*iter).m_var;
+	  if (TREE_CODE (var) == SSA_NAME)
+	    var = SSA_NAME_VAR (var);
+	  if (var == parm)
+	    {
+	      log ("got active var for parm %i", i);
+	      engine_sm_context ctxt (this, b, &sm_inst_in_caller,
+				      &sm_inst_in_callee,
+				      NULL /* FIXME */, &ps);
+	      // FIXME: we want to overwrite any old states
+	      // (but respect the gstate, I think):
+	      ctxt.on_transition (rest_of_caller_node, call_stmt, arg,
+				  0/* FIXME */,
+				  (*iter).m_state, parm);
+	    }
+	}
+    }
+
+  /* Restore locals from orig_sm_inst_in_caller.  */
+  for (set_of_vars::iterator iter
+	 = orig_sm_inst_in_caller.m_active_vars.begin ();
+       iter != orig_sm_inst_in_caller.m_active_vars.end ();
+       ++iter)
+    {
+      if (!sm_inst_in_caller.m_active_vars.has_expr ((*iter).m_var)) // FIXME: leak
+	sm_inst_in_caller.m_active_vars.m_set.add (*iter);
+    }
+
+  // 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, b);
+
+  const supernode *callee_node
+    = m_sg.get_node_for_function_entry (edge->callee->get_fun ());
+
+  /* Traverse into the callee.  */
+  backtrace b_in_callee;
+  b_in_callee.m_path = b.m_path;
+
+  if (m_verbosity > 0)
+    {
+      const superedge *sedge = m_sg.get_edge_for_call (edge);
+      b_in_callee.m_path.add_event
+	(new edge_event (sedge,
+			 edge->call_stmt->location,
+			 caller_node->m_fun->decl,
+			 b_in_callee.m_path.m_stack_depth));
+    }
+  b_in_callee.m_path.m_stack_depth++;
+
+  const call_superedge &call_sedge = *m_sg.get_edge_for_call (edge);
+  ps.apply_constraints_for_call (call_sedge);
+
+  dfs_traverse (callee_sm_inst, callee_sm_inst, b_in_callee, ps, callee_node,
+		NULL /* FIXME */);
+
+  log ("building summary edges from callee function summary");
+
+  /* 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
+
+  if (callee_summary_edges.is_empty ())
+    {
+      log ("function did not return; stopping traversal");
+      return;
+    }
+
+  /* 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
+      }
+
+  if (get_logger ())
+    log ("callee_summary_sm_inst: %s",
+	 callee_summary_sm_inst.to_str (m_sm).c_str ());
+
+  /* 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:
+
+    backtrace new_b (b);
+    checker_event *event
+      = new call_summary (caller_node, edge, 0/*int depth,*/,
+			  callee_sm_inst);
+    new_b.m_path.add_event (event);
+
+    if (get_logger ())
+      log ("ps before applying return edge: %s", ps.to_str ().c_str ());
+    const return_superedge &return_sedge = *m_sg.get_edge_for_return (edge);
+    ps.apply_constraints_for_return (return_sedge);
+    if (get_logger ())
+      log ("ps after applying return edge: %s", ps.to_str ().c_str ());
+
+    sm_instance restored_sm
+      = restore_after_call (callee_summary_sm_inst, sm_inst, edge,
+			    rest_of_caller_node, new_b);
+    log ("rest of call is in SN: %i", rest_of_caller_node->m_index);
+    dfs_traverse (
+#if 0
+		  restored_sm,
+#else
+		  callee_sm_inst,
+#endif
+		  restored_sm, new_b, ps, rest_of_caller_node,
+		  NULL /* FIXME */);
+  }
+}
+
+/* FIXME.  */
+
+void
+engine::log_stats () const
+{
+  log ("#supernodes: %i", m_sg.num_nodes ());
+  log ("#superedges: %i", m_sg.num_edges ());
+  log ("m_num_traverse_calls: %i", m_num_traverse_calls);
+  log ("m_num_cache_hits: %i", m_num_cache_hits);
+  log ("m_on_stmt_calls: %i", m_on_stmt_calls);
+}
+
+/* FIXME.  */
+
+class backtrace_stmt_iterator
+{
+public:
+  backtrace_stmt_iterator (const backtrace &b)
+  : m_b (b), m_node_idx (0), m_stmt_idx (0), m_phis (false)
+  {
+    ensure_current_node_has_stmt ();
+  }
+  const gimple * operator* () const
+  {
+    gcc_assert (!done_p ());
+    gcc_assert (m_stmt_idx < current_node ()->m_stmts.length ());
+    return current_node ()->m_stmts[m_stmt_idx];
+  }
+  void next ()
+  {
+    m_stmt_idx++;
+    if (m_stmt_idx >= current_node ()->m_stmts.length ())
+      {
+	m_node_idx++;
+	m_stmt_idx = 0;
+	ensure_current_node_has_stmt ();
+      }
+  }
+  bool done_p () const
+  {
+    return m_node_idx >= m_b.m_links.length ();
+  }
+
+  const supernode *current_node () const
+  {
+    gcc_assert (!done_p ());
+    return m_b.m_links[m_node_idx].m_node;
+  }
+
+private:
+  /* Skip nodes without stmts.  */
+  void ensure_current_node_has_stmt ()
+  {
+    while (!done_p () && current_node ()->m_stmts.length () == 0)
+      m_node_idx++;
+  }
+
+  const backtrace &m_b;
+  int m_node_idx;
+  int m_stmt_idx;
+  bool m_phis; // TODO: use this
+};
+
+/* Attempt to get the best stmt within B for a leak of VAR detected at the
+   end of B.  */
+
+static const gimple *
+get_stmt_for_leak (tree var, const backtrace &b)
+{
+  if (TREE_CODE (var) == SSA_NAME)
+    {
+      /* What was the next write to the underlying var
+	 after var was set?.  */
+      const gimple *def_stmt = SSA_NAME_DEF_STMT (var);
+
+      backtrace_stmt_iterator iter (b);
+      /* Locate def_stmt.  */
+      while (!iter.done_p ())
+	{
+	  if (*iter == def_stmt)
+	    break;
+	  iter.next ();
+	}
+      if (!iter.done_p () && *iter == def_stmt)
+	{
+	  /* Locate next write to underlying var.  */
+	  iter.next ();
+	  while (!iter.done_p ())
+	    {
+	      if (const gassign *assign = dyn_cast <const gassign *> (*iter))
+		{
+		  tree lhs = gimple_assign_lhs (assign);
+		  if (TREE_CODE (lhs) == SSA_NAME
+		      && SSA_NAME_VAR (lhs) == SSA_NAME_VAR (var))
+		    return assign;
+		}
+	      iter.next ();
+	    }
+	}
+    }
+
+  /* Otherwise, look backwards for the first statement with a location.  */
+
+  int i;
+  backtrace::link *link;
+  FOR_EACH_VEC_ELT_REVERSE (b.m_links, i, link)
+    {
+      int j;
+      gimple *stmt;
+      FOR_EACH_VEC_ELT_REVERSE (link->m_node->m_stmts, j, stmt)
+	if (stmt->location != UNKNOWN_LOCATION)
+	  return stmt;
+    }
+  return NULL;
+}
+
+/* A measurement of how good EXPR is for presenting to the user, so
+   that e.g. we can say prefer printing
+     "leak of 'tmp.m_ptr'"
+   over:
+     leak of '<unknown>'.  */
+
+static int
+readability (tree expr)
+{
+  switch (TREE_CODE (expr))
+    {
+    case COMPONENT_REF:
+    case MEM_REF:
+      return readability (TREE_OPERAND (expr, 0)) - 1;
+
+    case SSA_NAME:
+      {
+	if (tree var = SSA_NAME_VAR (expr))
+	  return readability (var);
+	return -1;
+      }
+      break;
+
+    case VAR_DECL:
+      return 256;
+
+    default:
+      return 0;
+    }
+
+  return 0;
+}
+
+/* A qsort comparator for var_state_t for use by engine::detect_leaks,
+   to sort them into most user-readable to least user-readable.  */
+
+static int
+leak_comparator (const void *p1, const void *p2)
+{
+  const var_state_t *vs1 = (const var_state_t *)p1;
+  const var_state_t *vs2 = (const var_state_t *)p2;
+
+  int r1 = readability (vs1->m_var);
+  int r2 = readability (vs2->m_var);
+
+  return r2 - r1;
+}
+
+/* FIXME.  */
+
+void
+engine::detect_leaks (const supernode *node, const sm_instance &sm_inst,
+		      const backtrace &b, const path_state &old_ps)
+{
+  JIT_LOG_FUNC_2 (get_logger (), "SN: %i function: %s",
+		  node->m_index, function_name (node->m_fun));
+  if (get_logger ())
+    {
+      log ("sm_inst: %s", sm_inst.to_str (m_sm).c_str ());
+      log ("ps: %s", old_ps.to_str ().c_str ());
+    }
+
+  path_state new_ps (old_ps);
+
+  // FIXME: mark ec of return value (if any) as escaping
+  {
+    tree result = DECL_RESULT (node->m_fun->decl);
+    if (result)
+      {
+	log ("new_ps before escapes: %s", new_ps.to_str ().c_str ());
+	// FIXME: stack depth:
+	equiv_class_id ec_id
+	  = new_ps.get_or_add_equiv_class (path_var (result, 0));
+	new_ps.recursively_mark_as_escaping (ec_id);
+	log ("new_ps after escapes: %s", new_ps.to_str ().c_str ());
+      }
+  }
+
+  /* Gather all leaks into a vec so we can sort them.   */
+  auto_vec<var_state_t> leaks;
+
+  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)
+    {
+      if ((*iter).is_placeholder_p ())
+	continue;
+      if (get_logger ())
+	log ("considering %s", (*iter).to_str (m_sm).c_str ());
+
+     leaks.safe_push (*iter);
+    }
+
+  /* Sort the leaks into most human-readable first, through
+     to least user-readable.  Given that we only emit one
+     leak per EC, this ought to ensure that we pick the most
+     user-readable description of each leaking EC.  */
+  leaks.qsort (leak_comparator);
+
+  /* Iterate through the sorted leaks, potentially emitting them.  */
+  int i;
+  var_state_t *vs;
+  FOR_EACH_VEC_ELT (leaks, i, vs)
+    {
+       /* Did this var's equivalence class escape?  If so, don't complain
+	 about it leaking.  */
+      // FIXME: stack depth:
+      equiv_class_id ec_id
+	= new_ps.get_or_add_equiv_class (path_var (vs->m_var, 0));
+      equiv_class &ec = ec_id.get_obj (new_ps);
+      if (get_logger ())
+	log ("within %s", ec.to_str ().c_str ());
+      if (ec.m_escaped)
+	{
+	  log ("escaped");
+	  continue;
+	}
+
+      // FIXME: is anything in this ec still alive in parent stack frames?
+      if (ec.get_min_stack_depth () < old_ps.m_stack_depth)
+	{
+	  log ("alive within ancestor stack frame");
+	  continue;
+	}
+
+      // TODO: determine most appropriate stmt
+      const gimple *stmt = get_stmt_for_leak (vs->m_var, b);
+
+      // FIXME: various redundant copies here:
+      checker_path p (b.m_path);
+      sm_instance new_sm_inst (sm_inst);
+      engine_sm_context ctxt (this, b,
+			      &new_sm_inst,
+			      &sm_inst,
+			      &p, &new_ps);
+      m_sm.on_leak (&ctxt, node, stmt, vs->m_var, vs->m_state);
+
+      /* Mark this EC as having escaped, to suppress further leak warnings
+	 from it.  */
+      ec.m_escaped = true;
+    }
+}
+
+/* FIXME.  */
+
+void
+engine::emit_candidate_diagnostics ()
+{
+  // TODO: maybe sort them first
+  int i;
+  candidate_diagnostic *cd;
+  FOR_EACH_VEC_ELT (m_candidate_diagnostics, i, cd)
+    maybe_emit (*cd);
+}
+
+#if 0
+class expand_call_sm_context : public sm_context
+{
+};
+#endif
+
+#if 0
+checker_path *
+engine::dfs_reconstruct (const supernode *node,
+			 tree var, state_machine::state_t state)
+{
+}
+#endif
+
+checker_path *
+engine::expand_call_summary (const call_summary *summary_event,
+			     tree var, state_machine::state_t state)
+{
+  function *callee_fun = summary_event->m_edge->callee->get_fun ();
+  const supernode *callee_node
+    = m_sg.get_node_for_function_entry (callee_fun);
+  const supernode *caller_node
+    = summary_event->m_caller_node;
+
+  JIT_LOG_FUNC_4 (get_logger (), "%qD called from %qD: var: %qE state: %qs",
+		  callee_fun->decl, caller_node->m_fun->decl,
+		  var, m_sm.get_state_name (state));
+  if (get_logger ())
+    log ("  m_callee_sm_inst: %s",
+	 summary_event->m_callee_sm_inst.to_str (m_sm).c_str ());
+
+  const int caller_stack_depth = summary_event->m_depth;
+
+  if (TREE_CODE (var) == PARM_DECL)
+    var = ssa_default_def (callee_node->m_fun, var);
+
+  summary_point start
+    = summary_point (0 /*FIXME */,
+		     var_state_t (var, 0)); /* FIXME.  */
+#if 0
+  /* Potentially use any state for VAR within
+     summary_event->m_callee_sm_inst.  */
+  if (var_state_t *vs
+      = summary_event->m_callee_sm_inst.m_active_vars.has_expr (var))
+    {
+      log ("  using state from m_callee_sm_inst");
+      start.m_varstate.m_state = vs->m_state;
+      delete vs;
+    }
+#endif
+  summary_edge state_edge (start,
+			   summary_point (0 /* FIXME */,
+					  var_state_t (var, state)));
+  if (get_logger ())
+    log (" state_edge: %s", state_edge.to_str (m_sm).c_str ());
+
+  checker_path *result = new checker_path ();
+
+  // TODO:
+  const superedge *sedge_call = m_sg.get_edge_for_call (summary_event->m_edge);
+  result->add_event (new edge_event (sedge_call,
+				     summary_event->m_loc,
+				     caller_node->m_fun->decl,
+				     caller_stack_depth));
+
+  // TODO: body of function
+
+  /* Reconstruct a path (ideally feasible) within the function that makes
+     the state change happen.  We can probably figure that out from
+     the suffix summaries.  */
+  {
+    const int callee_stack_depth = caller_stack_depth + 1;
+    const supernode *node = callee_node;
+    while (node)
+      {
+	/* FIXME: look in sfx_add/sfx_transition.  */
+
+	add_events_for_node (result, node, callee_stack_depth);
+
+	/* Stmts.  */
+	int i;
+	gimple *stmt;
+	FOR_EACH_VEC_ELT (node->m_stmts, i, stmt)
+	  {
+	    if (m_verbosity >= 1)
+	      {
+		pretty_printer pp;
+		pp_string (&pp, "stmt: ");
+		pp_gimple_stmt_1 (&pp, const_cast <gimple *> (stmt),
+				  0, (dump_flags_t)0);
+		checker_event *event
+		  = new debug_event (stmt->location,
+				     node->m_fun->decl,
+				     callee_stack_depth,
+				     pp_formatted_text (&pp));
+		result->add_event (event);
+
+		per_stmt_t &data = get_data_for_stmt (stmt);
+		// TODO: add events for transitions
+		event
+		  = new debug_event (stmt->location,
+				     node->m_fun->decl,
+				     callee_stack_depth,
+				     data.m_transitions.to_str (m_sm).c_str ());
+		result->add_event (event);
+
+		for (set_of_summary_edges::iterator iter
+		       = data.m_transitions.begin ();
+		     iter != data.m_transitions.end ();
+		     ++iter)
+		  {
+		    const summary_edge &e = (*iter);
+		    event
+		      = new state_change_event (node, stmt,
+						callee_stack_depth,
+						m_sm,
+						e.m_start.m_varstate.m_var,
+						e.m_start.m_varstate.m_state,
+						e.m_end.m_varstate.m_state,
+						NULL_TREE /* FIXME: origin */);
+		    result->add_event (event);
+		  }
+
+		// FIXME: update state_edge as needed
+
+#if 0
+		expand_call_sm_context ctxt (this, b, sm_inst,
+					     old_sm_inst, path, ps);
+		m_sm.on_stmt (&ctxt, node, stmt);
+		m_on_stmt_calls++;
+#endif
+
+		// FIXME: We only really care about the stmts and edges
+		// that are required to get into the target state
+	      }
+	  }
+
+	// TODO: find better successor; track state etc in case of loops:
+	// FIXME: termination!
+	// TODO: we may need to recurse to handle
+	// TODO: we probably want to access recorded transitions about stmts
+	// TODO: alternatively: have a different context, and replay
+	// transitions using it
+	/* Or would dfs_traverse with a different "mode" be a better way of
+	   doing things:
+	   mode 1:
+	     get an overapproximation of states, and gather
+	     candidate diagnostics
+	   mode 2:
+	     during a candidate diagnostic, reconstruct a full
+	     path to said diagnostic (maybe using the suffix summaries?).
+
+	   Possibly a backwards DFS traversal, starting at endpoint, and
+	   exploring to find a route to an entrypoint?
+
+	   Should we tag the candidate diagnostics with the input state
+	   to their supernode that triggered them?
+
+	   Do we want to tag the blk_ edges with a "how this can happen?"
+	   object?  What if several ways?  */
+
+	if (node->return_p ())
+	  break;
+
+	/* Update for transitions within node.  */
+	per_node_t &data
+	  = const_cast <engine &> (*this).get_data_for_node (node);
+	// FIXME: what if there's more than one?  Can there be?
+	const set_of_summary_edges &blk_transition = data.m_blk_transition;
+	for (set_of_summary_edges::iterator iter = blk_transition.begin ();
+	     iter != blk_transition.end ();
+	     ++iter)
+	  {
+	    if ((*iter).m_start == state_edge.m_start)
+	      {
+		if (get_logger ())
+		  log ("found state_edge start %s in blk_transition: %s",
+		       (*iter).m_start.to_str (m_sm).c_str (),
+		       blk_transition.to_str (m_sm).c_str ());
+		state_edge.m_start = (*iter).m_end;
+		if (get_logger ())
+		  log ("state_edge: %s",
+		       state_edge.to_str (m_sm).c_str ());
+		break;
+	      }
+	  }
+	const set_of_summary_edges &blk_add = data.m_blk_add;
+	for (set_of_summary_edges::iterator iter = blk_add.begin ();
+	     iter != blk_add.end ();
+	     ++iter)
+	  {
+	    if ((*iter).m_start == state_edge.m_start)
+	      {
+		if (get_logger ())
+		  log ("found state_edge start %s in blk_add: %s",
+		       (*iter).m_start.to_str (m_sm).c_str (),
+		       blk_transition.to_str (m_sm).c_str ());
+		state_edge.m_start = (*iter).m_end;
+		if (get_logger ())
+		  log ("state_edge: %s",
+		       state_edge.to_str (m_sm).c_str ());
+		break;
+	      }
+	  }
+
+	/* Try to pick a suitable outedge from node.  */
+	const superedge *succ = get_next_expansion_edge (node, state_edge);
+	if (succ)
+	  {
+	    // FIXME: must be in same function, etc etc
+	    if (succ->m_kind == SUPEREDGE_CFG_EDGE)
+	      {
+		checker_event *event
+		  = new edge_event (succ, node->get_end_location (),
+				    node->m_fun->decl,
+				    callee_stack_depth);
+		result->add_event (event);
+	      }
+	    node = succ->m_dest;
+	  }
+	else
+	  {
+	    log ("no way to complete the path; rejecting expansion");
+	    delete result;
+	    return NULL;
+	  }
+      }
+  }
+
+  const superedge *sedge_return
+    = m_sg.get_edge_for_return (summary_event->m_edge);
+  result->add_event (new edge_event (sedge_return,
+				     summary_event->m_loc,
+				     caller_node->m_fun->decl,
+				     caller_stack_depth));
+
+  result->maybe_log (get_logger (), "result");
+
+  return result;
+}
+
+/* FIXME.  Subroutine of expand_call_summary.  */
+
+const superedge *
+engine::get_next_expansion_edge (const supernode *node,
+				 summary_edge desired_suffix)
+{
+  JIT_LOG_FUNC_1 (get_logger (), "SN: %i", node->m_index);
+  if (get_logger ())
+    log ("desired_suffix: %s", desired_suffix.to_str (m_sm).c_str ());
+
+  /* Try to find a successor edge where the destination node contains
+     the DESIRED_SUFFIX.  */
+
+  int i;
+  superedge *succ;
+  FOR_EACH_VEC_ELT (node->m_succs, i, succ)
+    {
+#if 0
+      if (get_logger ())
+	{
+	  log ("considering edge: SN:%i -> SN:%i",
+	       succ->m_src->m_index, succ->m_dest->m_index);
+	}
+
+      per_node_t &data = const_cast <engine &> (*this).get_data_for_node (succ->m_dest);
+      const set_of_summary_edges &sfx_add = data.m_sfx_add;
+      const set_of_summary_edges &sfx_transition = data.m_sfx_transition;
+      if (get_logger ())
+	{
+	  log ("sfx_add[%i]: %s", succ->m_dest->m_index,
+	       sfx_add.to_str (m_sm).c_str ());
+	  log ("sfx_transition[%i]: %s", succ->m_dest->m_index,
+	       sfx_transition.to_str (m_sm).c_str ());
+	}
+      for (set_of_summary_edges::iterator iter = sfx_add.begin ();
+	   iter != sfx_add.end ();
+	   ++iter)
+	{
+	  if ((*iter) == desired_suffix)
+	    {
+	      log ("found desired_suffix in next's sfx_add");
+	      return succ;
+	    }
+	}
+      for (set_of_summary_edges::iterator iter = sfx_transition.begin ();
+	   iter != sfx_transition.end ();
+	   ++iter)
+	{
+	  if ((*iter) == desired_suffix)
+	    {
+	      log ("found desired_suffix in next's sfx_transition");
+	      return succ;
+	    }
+	}
+#else
+      return succ;
+#endif
+    }
+
+ if (get_logger ())
+   log ("did not find a edge with desired_suffix: %s",
+	desired_suffix.to_str (m_sm).c_str ());
+  return NULL;
+}
+
+
+/* Attempt to generate a path, based on IN_PATH, which should end with
+   VAR in STATE.
+
+   Expand relevant call summaries.  */
+
+checker_path *
+engine::expand_path (const checker_path &in_path,
+		     tree var, state_machine::state_t state)
+{
+  JIT_LOG_FUNC (get_logger ());
+  in_path.maybe_log (get_logger (), "in_path");
+
+  /* Build the result backwards.  */
+  checker_path *result = new checker_path ();
+
+  // TODO
+  /* TODO: go backwards, skipping state change events that aren't var
+     but update the pertinent var when state-copying occurs.  */
+  for (int idx = in_path.m_events.length () - 1; idx >= 0; idx--)
+    {
+      checker_event *base_event = in_path.m_events[idx];
+      switch (base_event->m_kind)
+	{
+	default:
+	  gcc_unreachable ();
+
+	case EK_DEBUG:
+	case EK_STMT:
+	case EK_FUNCTION_ENTRY:
+	case EK_WARNING:
+	  result->add_event (base_event->clone ());
+	  break;
+
+	case EK_STATE_CHANGE:
+	  {
+	    state_change_event *state_change = (state_change_event *)base_event;
+	    if (state_change->m_var == var)
+	      {
+		if (state_change->m_origin)
+		  {
+		    log ("event %i: switching var of interest from %qE to %qE",
+			 idx, var, state_change->m_origin);
+		    var = state_change->m_origin;
+		  }
+	      }
+	    result->add_event (base_event->clone ());
+	  }
+	  break;
+
+	case EK_EDGE:
+	  {
+	    edge_event *event = (edge_event *)base_event;
+	    switch (event->m_edge->m_kind)
+	      {
+	      case SUPEREDGE_CFG_EDGE:
+	      case SUPEREDGE_RETURN:
+	      case SUPEREDGE_INTRAPROCEDURAL_CALL:
+		result->add_event (base_event->clone ());
+		break;
+
+	      case SUPEREDGE_CALL:
+		{
+		  // FIXME: use as_a<> here:
+		  const call_superedge *call_edge
+		    = event->m_edge->dyn_cast_call_superedge ();
+		  log ("considering call edge %i (from %qE to %qE); var: %qE",
+		       idx, call_edge->get_caller_decl (),
+		       call_edge->get_callee_decl (), var);
+		  tree maybe_parm = var;
+		  if (TREE_CODE (maybe_parm) == SSA_NAME)
+		    maybe_parm = SSA_NAME_VAR (maybe_parm);
+		  if (maybe_parm && TREE_CODE (maybe_parm) == PARM_DECL)
+		    {
+		      // TODO: what about ecs?
+		      tree arg = call_edge->get_arg_for_parm (maybe_parm);
+		      if (arg)
+			{
+			  log ("event %i: switching var of interest from"
+			       " parm %qE to arg %qE",
+			       idx, var, arg);
+			  var = arg;
+			}
+		    }
+		  result->add_event (base_event->clone ());
+		}
+		break;
+	      }
+	  }
+	  break;
+
+	case EK_CALL_SUMMARY:
+	  {
+	    call_summary *summary_event = (call_summary *)base_event;
+#if 0
+	    inform (event->m_loc, "considering call_summary %i: %s",
+		    idx, base_event->m_desc);
+#endif
+
+	    /* TODO: are we interested in a state change within this function?
+	       If not, we can simply eliminate it.
+	       If we are interested, we want to find a path (ideally feasible)
+	       within it that makes the state change happen.  We can probably
+	       figure that out from the suffix summaries.  */
+
+	    /* Attempt to convert "var" to the callee scope.  */
+	    call_superedge *call_edge
+	      = summary_event->get_edge_for_call (&m_sg);
+	    tree callee_var = call_edge->map_expr_from_caller_to_callee (var);
+	    if (callee_var == NULL_TREE)
+	      {
+		log ("event: %i"
+		     ": var %qE is not affected by call from %qE to %qE",
+		     idx, var, call_edge->get_caller_decl (),
+		     call_edge->get_callee_decl ());
+		result->add_event (base_event->clone ());
+		break;
+	      }
+	    log ("event %i: var %qE in %qE refined to %qE in %qE",
+		 idx, var, call_edge->get_caller_decl (),
+		 callee_var, call_edge->get_callee_decl ());
+
+	    /* Build path representing the inside of the function.  */
+	    log ("expanding event %i (call summary)", idx);
+	    checker_path *child_path
+	      = expand_call_summary (summary_event, callee_var, state);
+	    // FIXME: but here we want the thing in the end state
+	    // FIXME: maybe we don't want to filter here???
+	    // Or maybe a 2-stage filtering process?
+	    if (child_path == NULL)
+	      {
+		log ("failed to expand call sumary");
+		delete result;
+		return NULL;
+	      }
+
+	    /* Add backwards, since we'll be reversing the result.  */
+	    for (int j = child_path->m_events.length () - 1; j >=0 ; j--)
+	      result->add_event (child_path->m_events[j]->clone ());
+	    delete child_path;
+	    // FIXME: how to update var/state???
+	  }
+	  break;
+	}
+    }
+
+  /* OK, we have a backwards result; reverse it.  */
+  result->m_events.reverse ();
+
+  result->maybe_log (get_logger (), "result");
+
+  return result;
+}
+
+/* FIXME.  */
+
+void
+engine::prune_path (checker_path *path,
+		    tree var, state_machine::state_t state ATTRIBUTE_UNUSED)
+{
+  JIT_LOG_FUNC (get_logger ());
+  path->maybe_log (get_logger (), "path");
+
+  // TODO
+  /* TODO: go backwards, skipping state change events that aren't var
+     but update the pertinent var when state-copying occurs.  */
+  int idx = path->m_events.length () - 1;
+  while (idx >= 0 && idx < path->m_events.length ())
+    {
+      checker_event *base_event = path->m_events[idx];
+      if (get_logger ())
+	log  ("considering event %i, with var: %qE, state: %qs",
+	      idx, var, m_sm.get_state_name (state));
+      switch (base_event->m_kind)
+	{
+	default:
+	  gcc_unreachable ();
+
+	case EK_DEBUG:
+	  if (m_verbosity < 2)
+	    {
+	      log ("filtering event %i: debug event", idx);
+	      path->delete_event (idx);
+	    }
+	  break;
+
+	case EK_STMT:
+	  if (m_verbosity < 2)
+	    {
+	      log ("filtering event %i: statement event", idx);
+	      path->delete_event (idx);
+	    }
+	  break;
+
+	case EK_FUNCTION_ENTRY:
+	  if (m_verbosity < 1)
+	    {
+	      log ("filtering event %i: function entry", idx);
+	      path->delete_event (idx);
+	    }
+	  break;
+
+	case EK_STATE_CHANGE:
+	  {
+	    state_change_event *state_change = (state_change_event *)base_event;
+	    if (state_change->m_var == var)
+	      {
+		if (state_change->m_origin)
+		  {
+		    log ("event %i: switching var of interest from %qE to %qE",
+			 idx, var, state_change->m_origin);
+		    var = state_change->m_origin;
+		  }
+	      }
+	    else if (m_verbosity < 2)
+	      {
+		log ("filtering event %i: state change to %qE unrelated to %qE",
+		     idx, state_change->m_var, var);
+		path->delete_event (idx);
+	      }
+	  }
+	  break;
+
+	case EK_EDGE:
+	  {
+	    edge_event *event = (edge_event *)base_event;
+#if 0
+	    inform (base_event->m_loc, "considering edge_event %i: %s",
+		    idx, base_event->m_desc);
+#endif
+	    switch (event->m_edge->m_kind)
+	      {
+	      case SUPEREDGE_CFG_EDGE:
+		{
+		  const cfg_superedge& cfg_superedge
+		    = event->get_cfg_superedge ();
+		  const supernode *dest = event->m_edge->m_dest;
+		  /* Do we have an SSA_NAME defined via a phi node in
+		     the dest CFG node?  */
+		  if (TREE_CODE (var) == SSA_NAME)
+		    if (SSA_NAME_DEF_STMT (var)->bb == dest->m_bb)
+		      {
+			if (const gphi *phi
+			    = dyn_cast <gphi *> (SSA_NAME_DEF_STMT (var)))
+			  {
+			    /* Update var based on its phi node.  */
+			    tree old_var = var;
+			    var = cfg_superedge.get_phi_arg (phi);
+			    log ("updating from %qE to %qE based on phi node",
+				 old_var, var);
+			    if (get_logger ())
+			      {
+				pretty_printer pp;
+				pp_gimple_stmt_1 (&pp, (gimple *)phi, 0, (dump_flags_t)0);
+				// FIXME: const_cast!
+				log ("  phi: %s", pp_formatted_text (&pp));
+			      }
+			  }
+		      }
+
+		  /* TODO: is this edge significant to var?
+		     See if var can be in other states in the dest, but not
+		     in other states in the src?
+		     Must have multiple sibling edges.  */
+		  // TODO: for now, filter all intraprocedural edges:
+		  if (m_verbosity < 2)
+		    {
+		      log ("filtering event %i: CFG edge", idx);
+		      path->delete_event (idx);
+		    }
+		}
+		break;
+
+	      case SUPEREDGE_CALL:
+		{
+		  const callgraph_superedge& cg_superedge
+		    = event->get_callgraph_superedge ();
+		  tree caller_var
+		    = cg_superedge.map_expr_from_callee_to_caller (var);
+		  if (caller_var)
+		    var = caller_var;
+		}
+		break;
+	      case SUPEREDGE_RETURN:
+		// TODO: potentially update var/state based on return value,
+		// args etc
+		{
+		  const callgraph_superedge& cg_superedge
+		    = event->get_callgraph_superedge ();
+		  tree callee_var
+		    = cg_superedge.map_expr_from_caller_to_callee (var);
+		  if (callee_var)
+		    var = callee_var;
+		}
+		break;
+	      case SUPEREDGE_INTRAPROCEDURAL_CALL:
+		break;
+	      }
+	  }
+	  break;
+
+	case EK_CALL_SUMMARY:
+	  {
+	    call_summary *summary_event = (call_summary *)base_event;
+#if 0
+	    inform (event->m_loc, "considering call_summary %i: %s",
+		    idx, base_event->m_desc);
+#endif
+	    /* Filter any that weren't expanded earlier.  */
+	    if (m_verbosity < 2)
+	      {
+		log ("filtering event %i: call summary", idx);
+		path->delete_event (idx);
+	      }
+	  }
+	  break;
+
+	case EK_WARNING:
+	  break;
+	}
+      idx--;
+    }
+
+  /* Second pass.  If all we're left with is in one function, then
+     filter function entry events.  */
+  if (!path->interprocedural_p ())
+    {
+      // FIXME
+      int idx = path->m_events.length () - 1;
+      while (idx >= 0 && idx < path->m_events.length ())
+	{
+	  checker_event *base_event = path->m_events[idx];
+	  if (base_event->m_kind == EK_FUNCTION_ENTRY)
+	    {
+	      log ("filtering event %i:"
+		   " function entry for purely intraprocedural path", idx);
+	      path->delete_event (idx);
+	    }
+	  idx--;
+	}
+    }
+}
+
+/* FIXME.  */
+// FIXME: needs to happen before pruning
+
+bool
+engine::is_valid_path (const checker_path &path)
+{
+  JIT_LOG_FUNC (get_logger ());
+  path.maybe_log (get_logger (), "path");
+
+  path_state ps;
+  for (int idx = 0; idx < path.m_events.length (); idx++)
+    {
+      checker_event *base_event = path.m_events[idx];
+      log ("event %i: %s",
+	   idx, base_event->to_str ().c_str ());
+      log ("  ps was: %s", ps.to_str ().c_str ());
+
+      //FIXME:
+      switch (base_event->m_kind)
+	{
+	case EK_STMT:
+	  {
+	    statement_event *stmt_ev = (statement_event *)base_event;
+	    const gimple *stmt = stmt_ev->m_stmt;
+	    // TODO: this will largely be a duplicate of on_stmt
+	  }
+	  break;
+
+	case EK_EDGE:
+	  {
+	    edge_event *edge_ev = (edge_event *)base_event;
+	    if (!ps.apply_constraints_for_edge (*edge_ev->m_edge,
+						edge_ev->m_edge->m_src->get_last_stmt ()))
+	      {
+		log ("  constraints for edge aren't satisfiable");
+		return false;
+	      }
+	  }
+	  break;
+
+	default:
+	  break;
+	}
+
+      log ("  ps after: %s", ps.to_str ().c_str ());
+
+      // FIXME: we're going to need all the statements in order to track things
+      // ...as a subclass, I think
+      // also, we might need the call summaries to be expanded,
+      // and then filtered away, to ensure that we respect their effects
+    }
+
+  return true; // TODO
+}
+
+/* FIXME.  */
+
+void
+engine::maybe_emit (const candidate_diagnostic &cd)
+{
+  JIT_LOG_FUNC (get_logger ());
+  cd.m_path.maybe_log (get_logger (), "path");
+
+#if 0
+  backtrace full_backtrace;
+  if (!make_full_backtrace (&full_backtrace))
+    {
+      if (m_engine->get_logger ())
+	m_engine->log ("unable to make full backtrace in state %s; rejecting",
+		       m_engine->m_sm.get_state_name (state));
+      return;
+    }
+  checker_path path;
+  make_path_from_backtrace (&path, m_engine, full_backtrace, stmt);
+#else
+  checker_path path (cd.m_path);
+#endif
+
+  checker_path *expanded_path = expand_path (path, cd.m_var, cd.m_state);
+  if (expanded_path == NULL)
+    {
+      log ("did not expand path; rejecting");
+      return;
+    }
+
+  if (!is_valid_path (*expanded_path))
+    {
+      log ("rejecting non-valid path");
+      delete expanded_path;
+      return;
+    }
+
+  prune_path (expanded_path, cd.m_var, cd.m_state);
+
+  char *msg = NULL;
+  label_text ev_desc = cd.m_pd->describe_final_event (cd.m_var, cd.m_state);
+  if (ev_desc.m_buffer)
+    {
+      if (flag_analyzer_verbose_state_changes)
+	{
+	  msg = print ("%s (%qE is in state %qs)", ev_desc.m_buffer,
+		       cd.m_var, m_sm.get_state_name (cd.m_state));
+	  ev_desc.maybe_free ();
+	}
+      else
+	msg = ev_desc.take_or_copy ();
+    }
+  else
+    {
+      msg = print ("here (%qE is in state %qs)", cd.m_var,
+		   m_sm.get_state_name (cd.m_state));
+      // FIXME: ideally would show the full message here, too?
+    }
+
+  checker_event *end_of_path
+   = new warning_event (cd.m_stmt->location,
+			cd.m_node->m_fun->decl, path.m_stack_depth,
+			msg);
+  expanded_path->add_event (end_of_path);
+
+  expanded_path->prepare_for_emission (cd.m_pd);
+
+  expanded_path->maybe_log (get_logger (), "path for emission");
+
+  gcc_rich_location rich_loc (cd.m_stmt->location);
+  rich_loc.set_path (expanded_path);
+
+  auto_cfun sentinel (cd.m_node->m_fun);
+  cd.m_pd->emit (&rich_loc);
+
+  delete expanded_path;
+}
+
+/* FIXME.  return-value, args.  */
+
+void
+engine::dump_analyzer_summary (const supernode *node) const
+{
+
+  tree fndecl = node->m_fun->decl;
+  tree result = DECL_RESULT (fndecl);
+  if (result)
+    dump_analyzer_summary (node, result);
+
+  int i = 0;
+  for (tree parm = DECL_ARGUMENTS (fndecl); parm;
+       parm = DECL_CHAIN (parm), ++i)
+    {
+      tree parm_default_ssa = ssa_default_def (node->m_fun, parm);
+      dump_analyzer_summary (node, parm_default_ssa);
+    }
+}
+
+/* FIXME.  */
+
+void
+engine::dump_analyzer_summary (const supernode *node, tree var) const
+{
+  per_node_t &data = const_cast <engine &> (*this).get_data_for_node (node);
+  const set_of_summary_edges &sfx_add = data.m_sfx_add;
+  const set_of_summary_edges &sfx_transition = data.m_sfx_transition;
+  for (set_of_summary_edges::iterator iter = sfx_add.begin ();
+       iter != sfx_add.end ();
+       ++iter)
+    {
+      if ((*iter).m_start.m_varstate.m_var == var)
+	{
+	  auto_cfun sentinel (node->m_fun);
+	  warning_at (node->get_start_location (), 0,
+		      "summary: add: %qE: %qs -> %qs",
+		      var,
+		      m_sm.get_state_name ((*iter).m_start.m_varstate.m_state),
+		      m_sm.get_state_name ((*iter).m_end.m_varstate.m_state));
+	}
+    }
+  for (set_of_summary_edges::iterator iter = sfx_transition.begin ();
+       iter != sfx_transition.end ();
+       ++iter)
+    {
+      if ((*iter).m_start.m_varstate.m_var == var)
+	{
+	  auto_cfun sentinel (node->m_fun);
+	  warning_at (node->get_start_location (), 0,
+		      "summary: transition: %qE: %qs -> %qs",
+		      var,
+		      m_sm.get_state_name ((*iter).m_start.m_varstate.m_state),
+		      m_sm.get_state_name ((*iter).m_end.m_varstate.m_state));
+	}
+    }
+}
+
+/* FIXME.  */
+
+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);
+  }
+
+  void add_stmt_annotations (pretty_printer *pp,
+			     const gimple *stmt) const FINAL OVERRIDE
+  {
+    per_stmt_t &data
+      = const_cast <engine &> (m_engine).get_data_for_stmt (stmt);
+
+    pp_newline (pp);
+    pp_printf (pp, "  transitions: %s",
+	       data.m_transitions.to_str (m_engine.m_sm).c_str ());
+  }
+
+private:
+  const engine &m_engine;
+};
+
+/* FIXME.  */
+
+class engine_annotator_2 : public dot_annotator
+{
+public:
+  engine_annotator_2 (const engine &e) : m_engine (e) {}
+
+  void add_node_annotations (pretty_printer *pp,
+			     const supernode &n) const FINAL OVERRIDE
+  {
+    per_node_t &data = const_cast <engine &> (m_engine).get_data_for_node (&n);
+
+    /* Counterintuitively, show a pair of nodes per blk_add.  */
+    {
+      int idx = 0;
+      for (set_of_summary_edges::iterator iter = data.m_blk_add.begin ();
+	   iter != data.m_blk_add.end ();
+	   ++iter, ++idx)
+	{
+	  add_summary_point (pp, n, idx, (*iter).m_start,
+			     "blk_add", "start");
+	  add_summary_point (pp, n, idx, (*iter).m_end,
+			     "blk_add", "end");
+	  pp_printf (pp, "blk_add_%i_%i_start:s -> blk_add_%i_%i_end:n;\n",
+		     n.m_index, idx, n.m_index, idx);
+	}
+    }
+
+    /* Counterintuitively, only show a pair of nodes per blk_transition if it
+       actually *is* a transition.  */
+    {
+      int idx = 0;
+      for (set_of_summary_edges::iterator iter = data.m_blk_transition.begin ();
+	   iter != data.m_blk_transition.end ();
+	   ++iter, ++idx)
+	{
+	  add_summary_point (pp, n, idx, (*iter).m_start,
+			     "blk_transition", "start");
+	  if ((*iter).m_start != (*iter).m_end)
+	    {
+	      add_summary_point (pp, n, idx, (*iter).m_end,
+				 "blk_transition", "end");
+	      pp_printf (pp, "blk_transition_%i_%i_start:s -> blk_transition_%i_%i_end:n;\n",
+			 n.m_index, idx, n.m_index, idx);
+	    }
+	}
+    }
+
+  }
+
+private:
+  void add_summary_point (pretty_printer *pp, const supernode &n, int idx,
+			  summary_point p,
+			  const char *kind, const char *endpoint) const
+  {
+    pp_printf (pp, "%s_%i_%i_%s",
+	       kind, n.m_index, idx, endpoint);
+    pp_printf (pp, " [shape=%s,style=filled,fillcolor=%s,label=\"",
+	       "record", "lightblue");
+    pp_left_brace (pp);
+    pp_write_text_to_stream (pp);
+
+    pp_string (pp, p.to_str (m_engine.m_sm).c_str ());
+    pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
+
+    pp_right_brace (pp);
+    pp_string (pp, "\"];\n\n");
+    pp_flush (pp);
+  }
+
+  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)
+{
+  JIT_LOG_FUNC_1 (logger, "sm: %s", sm.get_name ());
+
+  if (flag_analyzer_checker)
+    {
+      if (0 != strcmp (flag_analyzer_checker, sm.get_name ()))
+	{
+	  if (logger)
+	    logger->log ("skipping checker %s (does not match %s)",
+			 sm.get_name (), flag_analyzer_checker);
+	  return;
+	}
+    }
+
+  engine e (sg, sm, logger, analyzer_verbosity);
+
+  cgraph_node *node;
+  auto_vec<cgraph_node *> root_functions;
+  FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
+    root_functions.safe_push (node);
+
+  /* Sort root_functions so that diagnostics are emitted in a
+     sane order.
+     TODO: perhaps we should buffer the diagnostics and sort them later,
+     instead.  */
+  root_functions.qsort (function_comparator);
+
+  int i;
+  FOR_EACH_VEC_ELT (root_functions, i, node)
+    {
+      function *fun = node->get_fun ();
+
+      if (!TREE_PUBLIC (fun->decl))
+	{
+	  if (logger)
+	    logger->log ("not traversing %qE (static)", fun->decl);
+	  continue;
+	}
+
+      log_scope s (logger, "top-level traversal", "%s", function_name (fun));
+
+      backtrace b;
+      //	checker_path path;
+      path_state ps;
+
+      sm_instance sm_inst;
+      sm_inst.add_placeholder ();
+
+      e.dfs_traverse (sm_inst, sm_inst, b, ps,
+		      sg.get_node_for_function_entry (fun),
+		      NULL);
+      e.emit_candidate_diagnostics ();
+    }
+
+  if (flag_dump_analyzer_summaries)
+    {
+      int i;
+      FOR_EACH_VEC_ELT (root_functions, i, node)
+	{
+	  function *fun = node->get_fun ();
+	  e.dump_analyzer_summary (sg.get_node_for_function_entry (fun));
+	}
+    }
+
+  if (1)
+    {
+      engine_annotator a (e);
+      char *filename
+	= concat (dump_base_name, ".", sm.get_name (), ".1.dot", NULL);
+      sg.dump_dot (filename, (enum supergraph_dot_flags)0, &a);
+      free (filename);
+    }
+
+  if (1)
+    {
+      engine_annotator_2 a (e);
+      char *filename
+	= concat (dump_base_name, ".", sm.get_name (), ".2.dot", NULL);
+      sg.dump_dot (filename, (enum supergraph_dot_flags)0, &a);
+      free (filename);
+    }
+
+  if (logger)
+    {
+      log_scope s (logger, "per-node state");
+      for (int idx = 0; idx < sg.num_nodes (); idx++)
+	{
+	  const supernode *node = sg.get_node_by_index (idx);
+	  if (node->entry_p ())
+	    logger->log ("%qD", node->m_fun->decl);
+	  const per_node_t &node_data = e.get_data_for_node (node);
+	  logger->log ("  blk_add[%i]: %s",
+		       idx, node_data.m_blk_add.to_str (sm).c_str ());
+	  logger->log ("  blk_transition[%i]: %s",
+		       idx, node_data.m_blk_transition.to_str (sm).c_str ());
+	  logger->log ("  sfx_add[%i]: %s",
+		       idx, node_data.m_sfx_add.to_str (sm).c_str ());
+	  logger->log ("  sfx_transition[%i]: %s",
+		       idx, node_data.m_sfx_transition.to_str (sm).c_str ());
+	}
+    }
+
+  e.log_stats ();
+}
+
+/* FIXME.  */
+
+void
+run_checkers_impl_2 ()
+{
+  //FILE *dump = fopen ("test.out", "w"); // FIXME
+  auto_client_timevar tv ("run_checkers");
+  {
+    log_user the_logger (NULL);
+    if (flag_dump_analyzer)
+      {
+	FILE *dump_file = /* 0 ? dump : */stderr; // FIXME
+	the_logger.set_logger (new logger (dump_file, 0, 0,
+					   *global_dc->printer));
+      }
+    JIT_LOG_SCOPE (the_logger.get_logger ());
+
+    /* Create the supergraph.  */
+    supergraph sg (the_logger.get_logger ());
+
+    if (1)
+      {
+	char *filename = concat (dump_base_name, ".supergraph.dot", NULL);
+	sg.dump_dot (filename, (enum supergraph_dot_flags)0);
+	free (filename);
+      }
+
+#if 0
+    // TODO: selftest for auto_delete_vec
+    auto_delete_vec <state_machine> checkers;
+    make_checkers (checkers, the_logger.get_logger ());
+
+    int i;
+    state_machine *checker;
+    FOR_EACH_VEC_ELT (checkers, i, checker)
+      // FIXME: this requires better timevar ID management
+#endif
+
+    {
+      auto_client_timevar tv ("malloc_state_machine");
+      malloc_state_machine sm (the_logger.get_logger ());
+      run_one_checker (sg, sm, the_logger.get_logger ());
+    }
+
+    {
+      auto_client_timevar tv ("fileptr_state_machine");
+      fileptr_state_machine sm (the_logger.get_logger ());
+      run_one_checker (sg, sm, the_logger.get_logger ());
+    }
+
+    {
+      auto_client_timevar tv ("taint_state_machine");
+      taint_state_machine sm (the_logger.get_logger ());
+      run_one_checker (sg, sm, the_logger.get_logger ());
+    }
+
+    {
+      auto_client_timevar tv ("sensitive_state_machine");
+      sensitive_state_machine sm (the_logger.get_logger ());
+      run_one_checker (sg, sm, the_logger.get_logger ());
+    }
+
+    /* We only attempt to run the pattern tests if it might have been manually
+       enabled (for DejaGnu purposes).  */
+    if (flag_analyzer_checker)
+      {
+	auto_client_timevar tv ("pattern_test_state_machine");
+	pattern_test_state_machine sm (the_logger.get_logger ());
+	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);
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+static void
+test_set_of_vars ()
+{
+  tree x = build_decl (UNKNOWN_LOCATION, PARM_DECL,
+		       get_identifier ("x"),
+		       integer_type_node);
+  tree y = build_decl (UNKNOWN_LOCATION, PARM_DECL,
+		       get_identifier ("y"),
+		       integer_type_node);
+  tree z = build_decl (UNKNOWN_LOCATION, PARM_DECL,
+		       get_identifier ("z"),
+		       integer_type_node);
+  var_state_t vs_x (x, 0);
+  var_state_t vs_y (y, 0);
+
+  set_of_vars sv;
+  sv.m_set.add (vs_x);
+  sv.m_set.add (vs_y);
+
+  ASSERT_TRUE (sv.has_expr (x));
+  ASSERT_TRUE (sv.has_expr (y));
+  ASSERT_FALSE (sv.has_expr (z));
+  // FIXME: these leak
+
+  sv.m_set.remove (vs_x);
+  ASSERT_FALSE (sv.has_expr (x));
+  ASSERT_TRUE (sv.has_expr (y));
+  ASSERT_FALSE (sv.has_expr (z));
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+analyzer_engine_cc_tests ()
+{
+  test_set_of_vars ();
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
diff --git a/gcc/analyzer/engine.h b/gcc/analyzer/engine.h
new file mode 100644
index 0000000..853f1c1
--- /dev/null
+++ b/gcc/analyzer/engine.h
@@ -0,0 +1,3 @@
+extern void run_checkers_impl_2 ();
+extern void run_checkers_impl_3 ();
+extern void run_checkers_impl_4 ();
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-file.cc b/gcc/analyzer/hardcoded-state-machine-file.cc
new file mode 100644
index 0000000..8d74298
--- /dev/null
+++ b/gcc/analyzer/hardcoded-state-machine-file.cc
@@ -0,0 +1,192 @@
+/* FIXME.  */
+#include "config.h"
+#include "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "tm.h"
+#include "toplev.h"
+#include "hash-table.h"
+#include "vec.h"
+#include "ggc.h"
+#include "basic-block.h"
+#include "tree-ssa-alias.h"
+#include "internal-fn.h"
+#include "gimple-fold.h"
+#include "tree-eh.h"
+#include "gimple-expr.h"
+#include "is-a.h"
+#include "gimple.h"
+#include "tree-pass.h"
+#include "intl.h"
+#include "context.h"
+#include "diagnostic.h"
+#include "bitmap.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "gimple-pretty-print.h"
+#include "tree-pretty-print.h"
+#include "analyzer.h"
+#include "graphviz.h"
+#include "cgraph.h"
+#include "diagnostic-path.h"
+#include "gcc-rich-location.h"
+#include <string>
+#include "supergraph.h"
+#include "hardcoded-state-machine.h"
+
+static const bool DEBUG = false;
+//static const bool DEBUG = true;
+
+////////////////////////////////////////////////////////////////////////////
+
+class double_fclose : public pending_diagnostic
+{
+public:
+  double_fclose (tree arg) : m_arg (arg) {}
+
+  void emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    warning_at (rich_loc, 0,
+		"double %<fclose%> of FILE %qE",
+		m_arg);
+  }
+
+private:
+  tree m_arg;
+};
+
+class file_leak : public pending_diagnostic
+{
+public:
+  file_leak (tree arg) : m_arg (arg) {}
+
+  void emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    metadata m;
+    m.add_cwe (775);
+    warning_at (rich_loc, 0,
+		"leak of FILE %qE",
+		m_arg);
+  }
+
+private:
+  tree m_arg;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+fileptr_state_machine::fileptr_state_machine (logger *logger)
+: state_machine ("file", logger)
+{
+  // TODO: parse all of this from a file:
+  m_start = add_state ("start");
+  m_unchecked = add_state ("unchecked");
+  m_null = add_state ("null");
+  m_nonnull = add_state ("nonnull");
+  m_closed = add_state ("closed");
+  m_stop = add_state ("stop");
+}
+
+void
+fileptr_state_machine::on_stmt (sm_context *sm_ctxt,
+				const supernode *node,
+				const gimple *stmt) const
+{
+  //tree fndecl = node->m_fun->decl;
+
+  // FIXME: do all of this from data
+  if (const gcall *call = dyn_cast <const gcall *> (stmt))
+    {
+      if (is_named_call_p (call, "fopen", 2))
+	{
+	  if (DEBUG)
+	    inform (stmt->location, "DBG: got fopen");
+	  tree lhs = gimple_call_lhs (call);
+	  if (lhs)
+	    sm_ctxt->on_transition (node, stmt, lhs, m_start, m_unchecked);
+	  else
+	    {
+	      /* TODO: report leak.  */
+	    }
+	}
+
+      if (is_named_call_p (call, "fclose", 1))
+	{
+	  tree arg = gimple_call_arg (call, 0);
+	  if (DEBUG)
+	    inform (stmt->location, "DBG: got fclose");
+	  sm_ctxt->on_transition (node, stmt, arg, m_start, m_closed);
+
+	  // TODO: is it safe to call fclose (NULL) ?
+	  sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_closed);
+	  sm_ctxt->on_transition (node, stmt, arg, m_null, m_closed);
+
+	  sm_ctxt->on_transition (node, stmt , arg, m_nonnull, m_closed);
+
+	  sm_ctxt->warn_for_state (node, stmt, arg, m_closed,
+				   new double_fclose (arg));
+	  sm_ctxt->on_transition (node, stmt, arg, m_closed, m_stop);
+	}
+
+      // TODO: operations on closed file
+      // etc
+    }
+}
+
+/* FIXME.  */
+
+void
+fileptr_state_machine::on_edge (sm_context *sm_ctxt,
+				const gimple *last_stmt,
+				const superedge *edge) const
+{
+  if (last_stmt == NULL)
+    return;
+
+  if (const gcond *cond_stmt = dyn_cast <const gcond *> (last_stmt))
+    {
+      tree arg;
+      enum tree_code op;
+      if (is_comparison_against_zero (cond_stmt, &arg, &op))
+	{
+	  // FIXME: has to be a FILE *, specifically
+	  if (TREE_CODE (TREE_TYPE (arg)) != POINTER_TYPE)
+	    return;
+
+	  ::edge cfg_edge = edge->get_any_cfg_edge ();
+	  gcc_assert (cfg_edge != NULL);
+	  gcc_assert (cfg_edge->flags & (EDGE_TRUE_VALUE | EDGE_FALSE_VALUE));
+	  
+	  if (op == NE_EXPR)
+	    {
+	      log ("got 'ARG != 0' match");
+	      sm_ctxt->on_transition (edge->m_src, last_stmt, arg, m_unchecked,
+				      (cfg_edge->flags & EDGE_TRUE_VALUE
+				       ? m_nonnull : m_null));
+	    }
+	  else if (op == EQ_EXPR)
+	    {
+	      log ("got 'ARG == 0' match");
+	      sm_ctxt->on_transition (edge->m_src, last_stmt, arg, m_unchecked,
+				      (cfg_edge->flags & EDGE_TRUE_VALUE
+				       ? m_null : m_nonnull));
+	    }
+	}
+    }
+
+  // TODO: something similar for switch statements?
+}
+
+void
+fileptr_state_machine::on_leak (sm_context *sm_ctxt,
+				const supernode *node,
+				const gimple *stmt,
+				tree var,
+				state_machine::state_t state) const
+{
+  sm_ctxt->warn_for_state (node, stmt, var, m_unchecked,
+			   new file_leak (var));
+  sm_ctxt->warn_for_state (node, stmt, var, m_nonnull,
+			   new file_leak (var));
+}
diff --git a/gcc/analyzer/hardcoded-state-machine-malloc.cc b/gcc/analyzer/hardcoded-state-machine-malloc.cc
new file mode 100644
index 0000000..cf61b0d
--- /dev/null
+++ b/gcc/analyzer/hardcoded-state-machine-malloc.cc
@@ -0,0 +1,496 @@
+/* FIXME.  */
+#include "config.h"
+#include "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "tm.h"
+#include "toplev.h"
+#include "hash-table.h"
+#include "vec.h"
+#include "ggc.h"
+#include "basic-block.h"
+#include "tree-ssa-alias.h"
+#include "internal-fn.h"
+#include "gimple-fold.h"
+#include "tree-eh.h"
+#include "gimple-expr.h"
+#include "is-a.h"
+#include "gimple.h"
+#include "tree-pass.h"
+#include "intl.h"
+#include "context.h"
+#include "diagnostic.h"
+#include "bitmap.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "gimple-pretty-print.h"
+#include "tree-pretty-print.h"
+#include "analyzer.h"
+#include "graphviz.h"
+#include "cgraph.h"
+#include "diagnostic-path.h"
+#include "gcc-rich-location.h"
+#include <string>
+#include "supergraph.h"
+#include "hardcoded-state-machine.h"
+
+static const bool DEBUG = false;
+//static const bool DEBUG = true;
+
+////////////////////////////////////////////////////////////////////////////
+
+class malloc_diagnostic : public pending_diagnostic
+{
+public:
+  malloc_diagnostic (const malloc_state_machine &sm, tree arg)
+  : m_sm (sm), m_arg (arg)
+  {}
+
+protected:
+  const malloc_state_machine &m_sm;
+  tree m_arg;
+};
+
+class double_free : public malloc_diagnostic
+{
+public:
+  double_free (const malloc_state_machine &sm, tree arg)
+  : malloc_diagnostic (sm, arg)
+  {}
+
+  void emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    auto_diagnostic_group d;
+    metadata m;
+    m.add_cwe (415); /* CWE-415: Double Free.  */
+    warning_at (rich_loc, m, "double-free of %qE", m_arg);
+
+#if 0
+    if (!rich_loc->get_path ())
+      return;
+
+    event_id_t second_free_event
+      = rich_loc->get_path ()->get_final_event_id ();
+
+    if (m_first_free_event.known_p ()
+	&& second_free_event.known_p ())
+      inform (rich_loc->get_loc (),
+	      "pointer freed at (%i) was already freed at (%i)",
+	      second_free_event.one_based (),
+	      m_first_free_event.one_based ());
+#endif
+  }
+
+  label_text describe_critical_state_change
+    (tree expr,
+     state_machine::state_t old_state ATTRIBUTE_UNUSED,
+     state_machine::state_t new_state,
+     event_id_t event_id)
+    FINAL OVERRIDE
+  {
+    if (new_state == m_sm.m_free)
+      {
+	m_first_free_event = event_id;
+	return formatted_print ("first %qs here", "free");
+      }
+    return label_text ();
+  }
+
+  /* FIXME.  */
+  label_text describe_final_event
+    (/*diagnostic_context *ctxt, */
+     tree expr ATTRIBUTE_UNUSED,
+     state_machine::state_t state ATTRIBUTE_UNUSED)
+  {
+    return formatted_print ("second %qs here", "free");
+  }
+
+  /* FIXME: maybe also something like this.  */
+#if 0
+  label_test label_critical_state (int event_idx)
+  //FINAL OVERRIDE
+  {
+    return label_text::take (xvsprintf ("freed at (%i)"));
+  }
+#endif
+
+private:
+  event_id_t m_first_free_event;
+  //event_id_t m_second_free_event;
+};
+
+class possible_null_deref : public malloc_diagnostic
+{
+public:
+  possible_null_deref (const malloc_state_machine &sm, tree arg)
+  : malloc_diagnostic (sm, arg) {}
+
+  void emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    /* CWE-690: Unchecked Return Value to NULL Pointer Dereference.  */
+    metadata m;
+    m.add_cwe (690);
+    warning_at (rich_loc, m, "use of possibly-NULL %qE", m_arg);
+  }
+
+  label_text describe_critical_state_change (tree expr,
+					     state_machine::state_t old_state,
+					     state_machine::state_t new_state,
+					     event_id_t event_id)
+    FINAL OVERRIDE
+  {
+    if (old_state == m_sm.m_start && new_state == m_sm.m_unchecked)
+      return label_text::borrow ("this call could return NULL");
+    return label_text ();
+  }
+
+  /* FIXME.  */
+  label_text describe_final_event
+    (/*diagnostic_context *ctxt, */
+     tree expr,
+     state_machine::state_t state ATTRIBUTE_UNUSED)
+  {
+    return formatted_print ("%qE could be NULL", expr);
+  }
+
+};
+
+class null_deref : public malloc_diagnostic
+{
+public:
+  null_deref (const malloc_state_machine &sm, tree arg)
+  : malloc_diagnostic (sm, arg) {}
+
+  void emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    /* CWE-690: Unchecked Return Value to NULL Pointer Dereference.  */
+    metadata m;
+    m.add_cwe (690);
+    warning_at (rich_loc, m, "use of NULL %qE", m_arg);
+  }
+};
+
+class use_after_free : public malloc_diagnostic
+{
+public:
+  use_after_free (const malloc_state_machine &sm, tree arg)
+  : malloc_diagnostic (sm, arg)
+  {}
+
+  void emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    /* CWE-416: Use After Free.  */
+    metadata m;
+    m.add_cwe (416);
+    warning_at (rich_loc, m, "use after free of %qE", m_arg);
+  }
+
+  label_text describe_critical_state_change (tree expr,
+					     state_machine::state_t old_state,
+					     state_machine::state_t new_state,
+					     event_id_t event_id)
+    FINAL OVERRIDE
+  {
+    if (new_state == m_sm.m_free)
+      return label_text::borrow ("freed here");
+    return label_text ();
+  }
+};
+
+class malloc_leak : public malloc_diagnostic
+{
+public:
+  malloc_leak (const malloc_state_machine &sm, tree arg)
+  : malloc_diagnostic (sm, arg) {}
+
+  void emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    metadata m;
+    m.add_cwe (401);
+    warning_at (rich_loc, m, "leak of %qE", m_arg);
+  }
+
+  label_text describe_critical_state_change (tree expr,
+					     state_machine::state_t old_state,
+					     state_machine::state_t new_state,
+					     event_id_t event_id ATTRIBUTE_UNUSED)
+    FINAL OVERRIDE
+  {
+    if (new_state == m_sm.m_unchecked)
+      return label_text::borrow ("allocated here");
+    return label_text ();
+  }
+
+  /* FIXME.  */
+  label_text describe_final_event
+    (/*diagnostic_context *ctxt, */
+     tree expr ATTRIBUTE_UNUSED,
+     state_machine::state_t state ATTRIBUTE_UNUSED)
+  {
+    return formatted_print ("%qE leaks here", expr);
+  }
+
+};
+
+class free_of_non_heap : public malloc_diagnostic
+{
+public:
+  free_of_non_heap (const malloc_state_machine &sm, tree arg)
+  : malloc_diagnostic (sm, arg), m_kind (KIND_UNKNOWN)
+  {
+  }
+
+  void emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    auto_diagnostic_group d;
+    metadata m;
+    m.add_cwe (590); /* CWE-590: Free of Memory not on the Heap.  */
+    switch (m_kind)
+      {
+      default:
+	gcc_unreachable ();
+      case KIND_UNKNOWN:
+	warning_at (rich_loc, m,
+		    "free of %qE which points to memory not on the heap",
+		    m_arg);
+	break;
+      case KIND_ALLOCA:
+	warning_at (rich_loc, m,
+		    "free of memory allocated on the stack by %qs (%qE)"
+		    " will corrupt the heap",
+		    "alloca", m_arg);
+	break;
+      }
+  }
+
+  label_text describe_critical_state_change
+    (tree expr,
+     state_machine::state_t old_state ATTRIBUTE_UNUSED,
+     state_machine::state_t new_state,
+     event_id_t event_id)
+    FINAL OVERRIDE
+  {
+    /* Attempt to reconstruct what kind of pointer it is.
+       (It seems neater for this to be a part of the state, though).  */
+    if (TREE_CODE (expr) == SSA_NAME)
+      {
+	gimple *def_stmt = SSA_NAME_DEF_STMT (expr);
+	if (gcall *call = dyn_cast <gcall *> (def_stmt))
+	  {
+	    if (is_named_call_p (call, "alloca", 1)
+		|| is_named_call_p (call, "__builtin_alloca", 1))
+	      {
+		m_kind = KIND_ALLOCA;
+		return label_text::borrow
+		  ("memory is allocated on the stack here");
+	      }
+	  }
+      }
+    return label_text::borrow ("pointer is from here");
+  }
+
+  /* FIXME.  */
+  label_text describe_final_event
+    (/*diagnostic_context *ctxt, */
+     tree expr ATTRIBUTE_UNUSED,
+     state_machine::state_t state ATTRIBUTE_UNUSED)
+  {
+    return formatted_print ("call to %qs here", "free");
+  }
+
+private:
+  enum kind
+  {
+    KIND_UNKNOWN,
+    KIND_ALLOCA
+  };
+  enum kind m_kind;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+malloc_state_machine::malloc_state_machine (logger *logger)
+: state_machine ("malloc", logger)
+{
+  // TODO: parse all of this from a file:
+  m_start = add_state ("start");
+  m_unchecked = add_state ("unchecked");
+  m_null = add_state ("null");
+  m_nonnull = add_state ("nonnull");
+  m_free = add_state ("free");
+  m_non_heap = add_state ("non-heap");
+  m_stop = add_state ("stop");
+}
+
+void
+malloc_state_machine::on_stmt (sm_context *sm_ctxt,
+			       const supernode *node,
+			       const gimple *stmt) const
+{
+  //tree fndecl = node->m_fun->decl;
+
+  // FIXME: do all of this from data
+  if (const gcall *call = dyn_cast <const gcall *> (stmt))
+    {
+      if (is_named_call_p (call, "malloc", 1)
+	  || is_named_call_p (call, "calloc", 2))
+	{
+	  if (DEBUG)
+	    inform (stmt->location, "DBG: got malloc");
+	  tree lhs = gimple_call_lhs (call);
+	  if (lhs)
+	    sm_ctxt->on_transition (node, stmt, lhs, m_start, m_unchecked);
+	  else
+	    {
+	      /* TODO: report leak.  */
+	    }
+	}
+
+      if (is_named_call_p (call, "alloca", 1)
+	  || is_named_call_p (call, "__builtin_alloca", 1))
+	{
+	  tree lhs = gimple_call_lhs (call);
+	  if (lhs)
+	    sm_ctxt->on_transition (node, stmt, lhs, m_start, m_non_heap);
+	}
+
+      if (is_named_call_p (call, "free", 1))
+	{
+	  tree arg = gimple_call_arg (call, 0);
+	  if (DEBUG)
+	    inform (stmt->location, "DBG: got free");
+
+	  /* start/unchecked/nonnull -> free.  */
+	  sm_ctxt->on_transition (node, stmt, arg, m_start, m_free);
+	  sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_free);
+	  sm_ctxt->on_transition (node, stmt, arg, m_nonnull, m_free);
+
+	  /* Keep state "null" as-is, rather than transitioning to "free";
+	     we don't want want to complain about double-free of NULL.  */
+
+	  /* free -> stop, with warning.  */
+	  sm_ctxt->warn_for_state (node, stmt, arg, m_free,
+				   new double_free (*this, arg));
+	  sm_ctxt->on_transition (node, stmt, arg, m_free, m_stop);
+
+	  /* non-heap -> stop, with warning.  */
+	  sm_ctxt->warn_for_state (node, stmt, arg, m_non_heap,
+				   new free_of_non_heap (*this, arg));
+	  sm_ctxt->on_transition (node, stmt, arg, m_non_heap, m_stop);
+	}
+    }
+
+  /* FIXME: It's not clear if we need this, due to the SSA representation:
+     each assignment creates a new SSA_NAME.  */
+  if (tree lhs = is_zero_assignment (stmt))
+    {
+      if (any_pointer_p (lhs))
+	{
+	  sm_ctxt->on_transition (node, stmt, lhs, m_start, m_null);
+	  sm_ctxt->on_transition (node, stmt, lhs, m_unchecked, m_null);
+	  sm_ctxt->on_transition (node, stmt, lhs, m_nonnull, m_null);
+	  sm_ctxt->on_transition (node, stmt, lhs, m_free, m_null);
+	}
+    }
+
+  if (const gassign *assign_stmt = dyn_cast <const gassign *> (stmt))
+    {
+      enum tree_code op = gimple_assign_rhs_code (assign_stmt);
+      if (op == ADDR_EXPR)
+	{
+	  tree lhs = gimple_assign_lhs (assign_stmt);
+	  if (lhs)
+	    sm_ctxt->on_transition (node, stmt, lhs, m_start, m_non_heap);
+	}
+    }
+
+  // TODO: dereferences
+  for (unsigned i = 0; i < gimple_num_ops (stmt); i++)
+    {
+      tree op = gimple_op (stmt, i);
+#if 0
+      if (op)
+	inform (stmt->location, "op %i: %qE", i, op);
+      else
+	inform (stmt->location, "op %i: NULL_TREE", i);
+#endif
+      if (!op)
+	continue;
+      if (TREE_CODE (op) == COMPONENT_REF)
+	op = TREE_OPERAND (op, 0);
+
+      if (TREE_CODE (op) == MEM_REF)
+	{
+	  tree arg = TREE_OPERAND (op, 0);
+
+	  sm_ctxt->warn_for_state (node, stmt, arg, m_unchecked,
+				   new possible_null_deref (*this, arg));
+	  sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_nonnull);
+
+	  sm_ctxt->warn_for_state (node, stmt, arg, m_null,
+				   new null_deref (*this, arg));
+	  sm_ctxt->on_transition (node, stmt, arg, m_null, m_stop);
+
+	  sm_ctxt->warn_for_state (node, stmt, arg, m_free,
+				   new use_after_free (*this, arg));
+	  sm_ctxt->on_transition (node, stmt, arg, m_free, m_stop);
+	}
+    }
+}
+
+/* FIXME.  */
+
+void
+malloc_state_machine::on_edge (sm_context *sm_ctxt,
+			       const gimple *last_stmt,
+			       const superedge *edge) const
+{
+  if (last_stmt == NULL)
+    return;
+
+  if (const gcond *cond_stmt = dyn_cast <const gcond *> (last_stmt))
+    {
+      tree arg;
+      enum tree_code op;
+      if (is_comparison_against_zero (cond_stmt, &arg, &op))
+	{
+	  if (!any_pointer_p (arg))
+	    return;
+
+	  ::edge cfg_edge = edge->get_any_cfg_edge ();
+	  gcc_assert (cfg_edge != NULL);
+	  gcc_assert (cfg_edge->flags & (EDGE_TRUE_VALUE | EDGE_FALSE_VALUE));
+	  
+	  if (op == NE_EXPR)
+	    {
+	      log ("got 'ARG != 0' match");
+	      sm_ctxt->on_transition (edge->m_src, last_stmt, arg, m_unchecked,
+				      (cfg_edge->flags & EDGE_TRUE_VALUE
+				       ? m_nonnull : m_null));
+	    }
+	  else if (op == EQ_EXPR)
+	    {
+	      log ("got 'ARG == 0' match");
+	      sm_ctxt->on_transition (edge->m_src, last_stmt, arg, m_unchecked,
+				      (cfg_edge->flags & EDGE_TRUE_VALUE
+				       ? m_null : m_nonnull));
+	    }
+	}
+    }
+
+  // TODO: something similar for switch statements?
+}
+
+void
+malloc_state_machine::on_leak (sm_context *sm_ctxt,
+			       const supernode *node,
+			       const gimple *stmt,
+			       tree var,
+			       state_machine::state_t state) const
+{
+  sm_ctxt->warn_for_state (node, stmt, var, m_unchecked,
+			   new malloc_leak (*this, var));
+  sm_ctxt->warn_for_state (node, stmt, var, m_nonnull,
+			   new malloc_leak (*this, var));
+}
diff --git a/gcc/analyzer/hardcoded-state-machine-pattern-test.cc b/gcc/analyzer/hardcoded-state-machine-pattern-test.cc
new file mode 100644
index 0000000..7f1460c
--- /dev/null
+++ b/gcc/analyzer/hardcoded-state-machine-pattern-test.cc
@@ -0,0 +1,131 @@
+/* FIXME.  */
+#include "config.h"
+#include "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "tm.h"
+#include "toplev.h"
+#include "hash-table.h"
+#include "vec.h"
+#include "ggc.h"
+#include "basic-block.h"
+#include "tree-ssa-alias.h"
+#include "internal-fn.h"
+#include "gimple-fold.h"
+#include "tree-eh.h"
+#include "gimple-expr.h"
+#include "is-a.h"
+#include "gimple.h"
+#include "tree-pass.h"
+#include "intl.h"
+#include "context.h"
+#include "diagnostic.h"
+#include "bitmap.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "gimple-pretty-print.h"
+#include "tree-pretty-print.h"
+#include "analyzer.h"
+#include "graphviz.h"
+#include "cgraph.h"
+#include "diagnostic-path.h"
+#include "gcc-rich-location.h"
+#include <string>
+#include "supergraph.h"
+#include "hardcoded-state-machine.h"
+
+static const bool DEBUG = false;
+//static const bool DEBUG = true;
+
+////////////////////////////////////////////////////////////////////////////
+
+class pattern_match : public pending_diagnostic
+{
+public:
+  pattern_match (tree arg, const char *msg) : m_arg (arg), m_msg (msg) {}
+
+  void emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    // TODO:
+    warning_at (rich_loc, 0, "pattern match on %qE: %s", m_arg, m_msg);
+  }
+
+private:
+  tree m_arg;
+  const char *m_msg;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+pattern_test_state_machine::pattern_test_state_machine (logger *logger)
+: state_machine ("pattern-test", logger)
+{
+  // TODO: parse all of this from a file:
+  m_start = add_state ("start");
+}
+
+void
+pattern_test_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))
+    {
+    }
+}
+
+/* FIXME.  */
+
+void
+pattern_test_state_machine::on_edge (sm_context *sm_ctxt,
+				     const gimple *last_stmt,
+				     const superedge *edge) const
+{
+  if (last_stmt == NULL)
+    return;
+
+  const supernode *node = edge->m_src;
+
+  if (const gcond *cond_stmt = dyn_cast <const gcond *> (last_stmt))
+    {
+      tree arg;
+      enum tree_code op;
+      if (is_comparison_against_zero (cond_stmt, &arg, &op))
+	{
+	  ::edge cfg_edge = edge->get_any_cfg_edge ();
+	  gcc_assert (cfg_edge != NULL);
+	  gcc_assert (cfg_edge->flags & (EDGE_TRUE_VALUE | EDGE_FALSE_VALUE));
+
+	  bool is_true = cfg_edge->flags & EDGE_TRUE_VALUE;
+
+	  if (op == NE_EXPR)
+	    {
+	      pending_diagnostic *diag
+		= new pattern_match (arg,
+				     is_true ? "!= 0" : "== 0");
+	      sm_ctxt->warn_for_state (node, last_stmt, arg, m_start, diag);
+	    }
+	  else if (op == EQ_EXPR)
+	    {
+	      pending_diagnostic *diag
+		= new pattern_match (arg,
+				     is_true ? "== 0" : "!= 0");
+	      sm_ctxt->warn_for_state (node, last_stmt, arg, m_start, diag);
+	    }
+	}
+    }
+}
+
+void
+pattern_test_state_machine::on_leak (sm_context *sm_ctxt,
+				     const supernode *node,
+				     const gimple *stmt,
+				     tree var,
+				     state_machine::state_t state) const
+{
+  /* Empty.  */
+}
diff --git a/gcc/analyzer/hardcoded-state-machine-sensitive.cc b/gcc/analyzer/hardcoded-state-machine-sensitive.cc
new file mode 100644
index 0000000..59e5cb7
--- /dev/null
+++ b/gcc/analyzer/hardcoded-state-machine-sensitive.cc
@@ -0,0 +1,135 @@
+/* FIXME.  */
+#include "config.h"
+#include "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "tm.h"
+#include "toplev.h"
+#include "hash-table.h"
+#include "vec.h"
+#include "ggc.h"
+#include "basic-block.h"
+#include "tree-ssa-alias.h"
+#include "internal-fn.h"
+#include "gimple-fold.h"
+#include "tree-eh.h"
+#include "gimple-expr.h"
+#include "is-a.h"
+#include "gimple.h"
+#include "tree-pass.h"
+#include "intl.h"
+#include "context.h"
+#include "diagnostic.h"
+#include "bitmap.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "gimple-pretty-print.h"
+#include "tree-pretty-print.h"
+#include "analyzer.h"
+#include "graphviz.h"
+#include "cgraph.h"
+#include "diagnostic-path.h"
+#include "gcc-rich-location.h"
+#include <string>
+#include "supergraph.h"
+#include "hardcoded-state-machine.h"
+
+static const bool DEBUG = false;
+//static const bool DEBUG = true;
+
+////////////////////////////////////////////////////////////////////////////
+
+class leakage_through_output_file : public pending_diagnostic
+{
+public:
+  leakage_through_output_file (tree arg) : m_arg (arg) {}
+
+  void emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    metadata m;
+    /* CWE-532: Information Exposure Through Log Files */
+    m.add_cwe (532);
+    warning_at (rich_loc, m,
+		"sensitive value %qE written to output file",
+		m_arg);
+  }
+
+private:
+  tree m_arg;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+sensitive_state_machine::sensitive_state_machine (logger *logger)
+: state_machine ("sensitive", logger)
+{
+  // TODO: parse all of this from a file:
+  m_start = add_state ("start");
+  m_sensitive = add_state ("sensitive");
+  m_stop = add_state ("stop");
+}
+
+void
+sensitive_state_machine::warn_for_any_leakage (sm_context *sm_ctxt,
+					       const supernode *node,
+					       const gimple *stmt,
+					       tree arg) const
+{
+  sm_ctxt->warn_for_state (node, stmt, arg, m_sensitive,
+			   new leakage_through_output_file (arg));
+}
+
+void
+sensitive_state_machine::on_stmt (sm_context *sm_ctxt,
+				  const supernode *node,
+				  const gimple *stmt) const
+{
+  //tree fndecl = node->m_fun->decl;
+
+  // FIXME: do all of this from data
+  if (const gcall *call = dyn_cast <const gcall *> (stmt))
+    {
+      if (is_named_call_p (call, "getpass", 1))
+	{
+	  tree lhs = gimple_call_lhs (call);
+	  if (lhs)
+	    sm_ctxt->on_transition (node, stmt, lhs, m_start, m_sensitive);
+	}
+      else if (is_named_call_p (call, "fprintf")
+	       || is_named_call_p (call, "printf"))
+	{
+	  /* Handle a match at any position in varargs.  */
+	  for (int idx = 1; idx < gimple_call_num_args (call); idx++)
+	    {
+	      tree arg = gimple_call_arg (call, idx);
+	      warn_for_any_leakage (sm_ctxt, node, stmt, arg);
+	    }
+	}
+      else if (is_named_call_p (call, "fwrite", 4))
+	{
+	  tree arg = gimple_call_arg (call, 0);
+	  warn_for_any_leakage (sm_ctxt, node, stmt, arg);
+	}
+    }
+}
+
+/* FIXME.  */
+
+void
+sensitive_state_machine::on_edge (sm_context *sm_ctxt,
+				  const gimple *last_stmt,
+				  const superedge *edge) const
+{
+  /* Empty.  */
+}
+
+void
+sensitive_state_machine::on_leak (sm_context *sm_ctxt,
+				  const supernode *node,
+				  const gimple *stmt,
+				  tree var,
+				  state_machine::state_t state) const
+{
+  /* Empty.  */
+}
diff --git a/gcc/analyzer/hardcoded-state-machine-taint.cc b/gcc/analyzer/hardcoded-state-machine-taint.cc
new file mode 100644
index 0000000..682726c
--- /dev/null
+++ b/gcc/analyzer/hardcoded-state-machine-taint.cc
@@ -0,0 +1,216 @@
+/* FIXME.  */
+#include "config.h"
+#include "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "tm.h"
+#include "toplev.h"
+#include "hash-table.h"
+#include "vec.h"
+#include "ggc.h"
+#include "basic-block.h"
+#include "tree-ssa-alias.h"
+#include "internal-fn.h"
+#include "gimple-fold.h"
+#include "tree-eh.h"
+#include "gimple-expr.h"
+#include "is-a.h"
+#include "gimple.h"
+#include "tree-pass.h"
+#include "intl.h"
+#include "context.h"
+#include "diagnostic.h"
+#include "bitmap.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "gimple-pretty-print.h"
+#include "tree-pretty-print.h"
+#include "analyzer.h"
+#include "graphviz.h"
+#include "cgraph.h"
+#include "diagnostic-path.h"
+#include "gcc-rich-location.h"
+#include <string>
+#include "supergraph.h"
+#include "hardcoded-state-machine.h"
+
+static const bool DEBUG = false;
+//static const bool DEBUG = true;
+
+////////////////////////////////////////////////////////////////////////////
+
+class tainted_array_index : public pending_diagnostic
+{
+public:
+  tainted_array_index (tree arg) : m_arg (arg) {}
+
+  void emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    metadata m;
+    m.add_cwe (129);
+    warning_at (rich_loc, m,
+		"use of tainted value %qE in array lookup without bounds checking",
+		m_arg);
+  }
+
+private:
+  tree m_arg;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+taint_state_machine::taint_state_machine (logger *logger)
+: state_machine ("taint", logger)
+{
+  // TODO: parse all of this from a file:
+  m_start = add_state ("start");
+  m_tainted = add_state ("tainted");
+  m_has_lb = add_state ("has_lb");
+  m_has_ub = add_state ("has_ub");
+  m_stop = add_state ("stop");
+}
+
+void
+taint_state_machine::on_stmt (sm_context *sm_ctxt,
+			       const supernode *node,
+			       const gimple *stmt) const
+{
+  //tree fndecl = node->m_fun->decl;
+
+  // FIXME: do all of this from data
+  if (const gcall *call = dyn_cast <const gcall *> (stmt))
+    {
+      if (is_named_call_p (call, "fread", 4))
+	{
+	  if (DEBUG)
+	    inform (stmt->location, "DBG: got taint");
+	  tree arg = gimple_call_arg (call, 0);
+	  sm_ctxt->on_transition (node, stmt, arg, m_start, m_tainted);
+
+	  // FIXME: dereference an ADDR_EXPR:
+	  // FIXME: should the engine do this?
+	  if (TREE_CODE (arg) == ADDR_EXPR)
+	    sm_ctxt->on_transition (node, stmt, TREE_OPERAND (arg, 0),
+				    m_start, m_tainted);
+	}
+    }
+
+#if 0
+  /* FIXME: It's not clear if we need this, due to the SSA representation:
+     each assignment creates a new SSA_NAME.  */
+  if (tree lhs = is_zero_assignment (stmt))
+    {
+      if (any_pointer_p (lhs))
+	{
+	  sm_ctxt->on_transition (node, stmt, lhs, m_start, m_null);
+	  sm_ctxt->on_transition (node, stmt, lhs, m_unchecked, m_null);
+	  sm_ctxt->on_transition (node, stmt, lhs, m_nonnull, m_null);
+	  sm_ctxt->on_transition (node, stmt, lhs, m_free, m_null);
+	}
+    }
+#endif
+
+  if (const gassign *assign = dyn_cast <const gassign *> (stmt))
+    {
+      tree lhs = gimple_assign_lhs (assign);
+      tree rhs1 = gimple_assign_rhs1 (assign);
+      enum tree_code op = gimple_assign_rhs_code (assign);
+      if (op == ARRAY_REF)
+	{
+	  tree arg = TREE_OPERAND (rhs1, 1);
+	  sm_ctxt->warn_for_state  (node, stmt, arg, m_tainted,
+				    new tainted_array_index (arg));
+	  sm_ctxt->on_transition (node, stmt, arg, m_tainted, m_stop);
+	}
+    }
+
+#if 0
+  // TODO: dereferences
+  for (unsigned i = 0; i < gimple_num_ops (stmt); i++)
+    {
+      tree op = gimple_op (stmt, i);
+#if 0
+      if (op)
+	inform (stmt->location, "op %i: %qE", i, op);
+      else
+	inform (stmt->location, "op %i: NULL_TREE", i);
+#endif
+      if (!op)
+	continue;
+      if (TREE_CODE (op) == MEM_REF)
+	{
+	  tree arg = TREE_OPERAND (op, 0);
+	  sm_ctxt->warn_for_state (node, stmt, arg, m_unchecked,
+				   "use of possibly-NULL %qE", arg);
+	  sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_nonnull);
+
+	  sm_ctxt->warn_for_state (node, stmt, arg, m_null,
+				   "use of NULL %qE", arg);
+	  sm_ctxt->on_transition (node, stmt, arg, m_null, m_stop);
+
+	  sm_ctxt->warn_for_state (node, stmt, arg, m_free,
+				   "use after free of %qE", arg);
+	  sm_ctxt->on_transition (node, stmt, arg, m_free, m_stop);
+	}
+    }
+#endif
+}
+
+/* FIXME.  */
+
+void
+taint_state_machine::on_edge (sm_context *sm_ctxt,
+			       const gimple *last_stmt,
+			       const superedge *edge) const
+{
+  if (last_stmt == NULL)
+    return;
+
+  if (const gcond *cond_stmt = dyn_cast <const gcond *> (last_stmt))
+    {
+      enum tree_code op = gimple_cond_code (cond_stmt);
+      tree lhs = gimple_cond_lhs (cond_stmt);
+      tree rhs = gimple_cond_rhs (cond_stmt);
+      // TODO: make it symmetric?
+
+      switch (op)
+	{
+	  //case NE_EXPR:
+	  //case EQ_EXPR:
+	case GE_EXPR:
+	case GT_EXPR:
+	  {
+	    sm_ctxt->on_transition (edge->m_src, last_stmt, lhs, m_tainted,
+				    m_has_lb);
+	    sm_ctxt->on_transition (edge->m_src, last_stmt, lhs, m_has_ub,
+				    m_stop);
+	  }
+	  break;
+	case LE_EXPR:
+	case LT_EXPR:
+	  {
+	    sm_ctxt->on_transition (edge->m_src, last_stmt, lhs, m_tainted,
+				    m_has_ub);
+	    sm_ctxt->on_transition (edge->m_src, last_stmt, lhs, m_has_lb,
+				    m_stop);
+	  }
+	  break;
+
+	default:
+	  break;
+	}
+    }
+
+  // TODO: something similar for switch statements?
+}
+
+void
+taint_state_machine::on_leak (sm_context *sm_ctxt,
+			       const supernode *node,
+			       const gimple *stmt,
+			       tree var,
+			       state_machine::state_t state) const
+{
+  /* Empty.  */
+}
diff --git a/gcc/analyzer/hardcoded-state-machine.cc b/gcc/analyzer/hardcoded-state-machine.cc
new file mode 100644
index 0000000..0713d3b
--- /dev/null
+++ b/gcc/analyzer/hardcoded-state-machine.cc
@@ -0,0 +1,163 @@
+/* FIXME.  */
+#include "config.h"
+#include "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "tm.h"
+#include "toplev.h"
+#include "hash-table.h"
+#include "vec.h"
+#include "ggc.h"
+#include "basic-block.h"
+#include "tree-ssa-alias.h"
+#include "internal-fn.h"
+#include "gimple-fold.h"
+#include "tree-eh.h"
+#include "gimple-expr.h"
+#include "is-a.h"
+#include "gimple.h"
+#include "tree-pass.h"
+#include "intl.h"
+#include "context.h"
+#include "diagnostic.h"
+#include "bitmap.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "gimple-pretty-print.h"
+#include "tree-pretty-print.h"
+#include "analyzer.h"
+#include "graphviz.h"
+#include "cgraph.h"
+#include "diagnostic-path.h"
+#include "gcc-rich-location.h"
+#include <string>
+#include "supergraph.h"
+#include "hardcoded-state-machine.h"
+
+static const bool DEBUG = false;
+//static const bool DEBUG = true;
+
+////////////////////////////////////////////////////////////////////////////
+
+/* If STMT is an assignment to zero, return the LHS.  */
+
+tree
+is_zero_assignment (const gimple *stmt)
+{
+  const gassign *assign_stmt = dyn_cast <const gassign *> (stmt);
+  if (!assign_stmt)
+    return NULL_TREE;
+
+  enum tree_code op = gimple_assign_rhs_code (assign_stmt);
+  if (op != INTEGER_CST)
+    return NULL_TREE;
+
+  if (!zerop (gimple_assign_rhs1 (assign_stmt)))
+    return NULL_TREE;
+
+  return gimple_assign_lhs (assign_stmt);
+}
+
+/* FIXME.  */
+bool
+is_comparison_against_zero (const gcond *cond_stmt,
+			    tree *out_lhs, enum tree_code *out_op)
+{
+  enum tree_code op = gimple_cond_code (cond_stmt);
+  tree lhs = gimple_cond_lhs (cond_stmt);
+  tree rhs = gimple_cond_rhs (cond_stmt);
+  if (!zerop (rhs))
+    return false;
+  // TODO: make it symmetric?
+
+  switch (op)
+    {
+    case NE_EXPR:
+    case EQ_EXPR:
+      *out_lhs = lhs;
+      *out_op = op;
+      return true;
+
+    default:
+      return false;
+    }
+}
+
+bool
+any_pointer_p (tree var)
+{
+  if (TREE_CODE (TREE_TYPE (var)) != POINTER_TYPE)
+    return false;
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+state_machine::state_t
+state_machine::add_state (const char *name)
+{
+  m_state_names.safe_push (name);
+  return m_state_names.length () - 1;
+}
+
+const char *
+state_machine::get_state_name (state_t s) const
+{
+  return m_state_names[s];
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+label_text
+pending_diagnostic::formatted_print (const char *fmt, ...)
+{
+  pretty_printer *pp = global_dc->printer->clone ();
+
+  text_info ti;
+  rich_location rich_loc (line_table, UNKNOWN_LOCATION);
+  va_list ap;
+  va_start (ap, fmt);
+  ti.format_spec = _(fmt);
+  ti.args_ptr = &ap;
+  ti.err_no = 0;
+  ti.x_data = NULL;
+  ti.m_richloc = &rich_loc;
+  pp_format (pp, &ti);
+  pp_output_formatted_text (pp);
+  va_end (ap);
+
+  label_text result = label_text::take (xstrdup (pp_formatted_text (pp)));
+  delete pp;
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+void
+make_checkers (auto_delete_vec <state_machine> &out, logger *logger)
+{
+  out.safe_push (new malloc_state_machine (logger));
+  out.safe_push (new fileptr_state_machine (logger));
+  out.safe_push (new taint_state_machine (logger));
+  out.safe_push (new sensitive_state_machine (logger));
+
+  /* We only attempt to run the pattern tests if it might have been manually
+     enabled (for DejaGnu purposes).  */
+  if (flag_analyzer_checker)
+    out.safe_push (new pattern_test_state_machine (logger));
+
+  if (flag_analyzer_checker)
+    {
+      int read_index, write_index;
+      state_machine **sm;
+
+      // FIXME: this leaks the machines
+      // FIXME: would be nice to log the things that were removed
+      VEC_ORDERED_REMOVE_IF (out, read_index, write_index, sm,
+			     0 != strcmp (flag_analyzer_checker,
+					  (*sm)->get_name ()));
+    }
+
+}
diff --git a/gcc/analyzer/hardcoded-state-machine.h b/gcc/analyzer/hardcoded-state-machine.h
new file mode 100644
index 0000000..274c968
--- /dev/null
+++ b/gcc/analyzer/hardcoded-state-machine.h
@@ -0,0 +1,381 @@
+/* FIXME.
+   Copyright (C) 2019 Free Software Foundation, Inc.
+   Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#ifndef GCC_ANALYZER_HARDCODED_STATE_MACHINE_H
+#define GCC_ANALYZER_HARDCODED_STATE_MACHINE_H
+
+#include "analyzer-logging.h"
+#include "diagnostic-metadata.h"
+
+////////////////////////////////////////////////////////////////////////////
+
+extern tree is_zero_assignment (const gimple *stmt);
+extern bool is_comparison_against_zero (const gcond *cond_stmt,
+					tree *out_lhs, enum tree_code *out_op);
+extern bool any_pointer_p (tree var);
+
+////////////////////////////////////////////////////////////////////////////
+
+class state_machine;
+class sm_context;
+
+/* A state machine describing an API.
+   A mapping from state IDs to names, and (for now) a vfunc implementation
+   for pattern-matching on statements.  */
+
+class state_machine : public log_user
+{
+public:
+  typedef int state_t;
+
+  state_machine (const char *name, logger *logger)
+  : log_user (logger), m_name (name) {}
+
+  virtual ~state_machine () {}
+
+  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;
+};
+
+/* FIXME.  */
+
+static inline bool
+start_start_p (state_machine::state_t state)
+{
+  return state == 0;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+// TODO: maybe capture which sm emitted the error
+
+class metadata : public diagnostic_metadata
+{
+ public:
+
+ private:
+};
+
+/* FIXME.  */
+
+class event_id_t
+{
+ public:
+  event_id_t () : m_index (UNKNOWN_EVENT_IDX) {}
+  event_id_t (int zero_based_idx) : m_index (zero_based_idx) {}
+
+  bool known_p () const { return m_index != UNKNOWN_EVENT_IDX; }
+
+  int one_based () const
+  {
+    gcc_assert (known_p ());
+    return m_index + 1;
+  }
+
+ private:
+  static const int UNKNOWN_EVENT_IDX = -1;
+  int m_index; // zero-based
+};
+
+/* FIXME:
+   Diagnostics are emitted using va_list, which can't be portably stored
+   for later use, so we have to make it be a vfunc.  */
+
+class pending_diagnostic
+{
+ public:
+  virtual ~pending_diagnostic () {}
+  virtual void emit (rich_location *) = 0;
+
+  /* For greatest precision-of-wording, give the pending diagnostic
+     a chance to describe critical state changes in terms of the
+     diagnostic.
+
+     For example, a double-free diagnostic might use the descriptions:
+     - "first 'free' happens here"
+     - "second 'free' happens here"
+     for the pertinent events, whereas a use-after-free might use the
+     descriptions:
+     - "freed here"
+     - "use after free here"
+     Note how in both cases the first event is a "free": the best
+     description to use depends on the diagnostic.
+
+     Return a non-NULL label_text to give the state change a custom
+     description; NULL otherwise.  */
+
+  virtual label_text describe_critical_state_change
+    (/*diagnostic_context *ctxt, */
+     tree expr ATTRIBUTE_UNUSED,
+     state_machine::state_t old_state ATTRIBUTE_UNUSED,
+     state_machine::state_t new_state ATTRIBUTE_UNUSED,
+     event_id_t event_id ATTRIBUTE_UNUSED)
+  {
+    /* Default no-op implementation.  */
+    return label_text ();
+  }
+
+  /* FIXME.  */
+  virtual label_text describe_final_event
+    (/*diagnostic_context *ctxt, */
+     tree expr ATTRIBUTE_UNUSED,
+     state_machine::state_t state ATTRIBUTE_UNUSED)
+  {
+    /* Default no-op implementation.  */
+    return label_text ();
+  }
+
+  static label_text formatted_print (const char *fmt, ...)
+    ATTRIBUTE_GCC_DIAG(1,2);
+};
+
+#if 0
+/* A thing for state machines to pass to the engine to create
+   pending_diagnostic instances (so that we can filter without having 
+   to create pending_diagnostic instances first).  */
+
+class diagnostic_emitter
+{
+  virtual pending_diagnostic *make_diagnostic () = 0;
+};
+#endif
+
+/* Interface for the state machine to call the checker engine, at a
+   particular stmt.  */
+
+class sm_context
+{
+public:
+  //  sm_context (const state_machine &sm) : m_sm (sm) {}
+  virtual ~sm_context () {}
+
+  // Stuff used by impl 1:
+  virtual void on_transition (const supernode *node, const gimple *stmt,
+			      tree var,
+			      state_machine::state_t from,
+			      state_machine::state_t to,
+			      tree origin = NULL_TREE) = 0;
+
+  // Stuff used by impl 2:
+
+  //virtual state_machine::state_t get_state (tree var) = 0;
+  //virtual void transition (tree var, state_machine::state_t to) = 0;
+  virtual void warn_for_state (const supernode *node, const gimple *stmt,
+			       tree var, state_machine::state_t state,
+			       pending_diagnostic *d) = 0;
+
+  // etc, what else?
+
+protected:
+  //const state_machine &m_sm;
+};
+
+
+////////////////////////////////////////////////////////////////////////////
+
+extern void make_checkers (auto_delete_vec <state_machine> &out, logger *logger);
+
+////////////////////////////////////////////////////////////////////////////
+
+/* A particular state machine, for prototyping purposes:
+   the malloc/free API.  */
+
+class malloc_state_machine : public state_machine
+{
+public:
+  malloc_state_machine (logger *logger);
+
+  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;
+
+  state_t m_start;
+  state_t m_unchecked;
+  state_t m_null;
+  state_t m_nonnull;
+  state_t m_free;
+  state_t m_non_heap; // FIXME: or should this be a different state machine?
+  // or do we need child values etc?
+  state_t m_stop;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* A particular state machine, for prototyping purposes:
+   the fopen API.  */
+
+class fileptr_state_machine : public state_machine
+{
+public:
+  fileptr_state_machine (logger *logger);
+
+  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;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* A particular state machine, for prototyping purposes:
+   tainted data.  */
+
+class taint_state_machine : public state_machine
+{
+public:
+  taint_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_tainted;
+  state_t m_has_lb;
+  state_t m_has_ub;
+  state_t m_stop;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* A particular state machine, for prototyping purposes:
+   sensitive data.  */
+
+class sensitive_state_machine : public state_machine
+{
+public:
+  sensitive_state_machine (logger *logger);
+
+  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:
+  void warn_for_any_leakage (sm_context *sm_ctxt,
+			     const supernode *node,
+			     const gimple *stmt,
+			     tree arg) const;
+
+  state_t m_start;
+  state_t m_sensitive;
+  state_t m_stop;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* A particular state machine, for prototyping purposes:
+   a pattern-matching test, for DejaGnu tests.  */
+
+class pattern_test_state_machine : public state_machine
+{
+public:
+  pattern_test_state_machine (logger *logger);
+
+  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;
+};
+
+#endif /* GCC_ANALYZER_HARDCODED_STATE_MACHINE_H */
diff --git a/gcc/analyzer/impl-3.cc b/gcc/analyzer/impl-3.cc
new file mode 100644
index 0000000..30b3240
--- /dev/null
+++ b/gcc/analyzer/impl-3.cc
@@ -0,0 +1,1648 @@
+/* FIXME.  */
+#include "config.h"
+#include "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "tm.h"
+#include "toplev.h"
+#include "hash-table.h"
+#include "vec.h"
+#include "ggc.h"
+#include "basic-block.h"
+#include "tree-ssa-alias.h"
+#include "internal-fn.h"
+#include "gimple-fold.h"
+#include "tree-eh.h"
+#include "gimple-expr.h"
+#include "is-a.h"
+#include "gimple.h"
+#include "tree-pass.h"
+#include "intl.h"
+#include "context.h"
+#include "diagnostic.h"
+#include "bitmap.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "gimple-pretty-print.h"
+#include "tree-pretty-print.h"
+#include "analyzer.h"
+#include "graphviz.h"
+#include "cgraph.h"
+#include "diagnostic-path.h"
+#include "gcc-rich-location.h"
+#include "tree-dfa.h"
+#include <string>
+#include "supergraph.h"
+#include "hardcoded-state-machine.h"
+#include "analyzer-logging.h"
+#include "stringpool.h"
+#include "selftest.h"
+#include "fold-const.h"
+#include "tristate.h"
+#include "selftest.h"
+#include "constraints.h"
+
+namespace impl_3 {
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+char *print (const char *fmt, ...)
+{
+  /* FIXME: all this cloning is likely to be expensive.  */
+  pretty_printer *pp = global_dc->printer->clone ();
+  pp_clear_output_area (pp);
+
+  text_info ti;
+  rich_location rich_loc (line_table, UNKNOWN_LOCATION);
+
+  va_list ap;
+
+  va_start (ap, fmt);
+
+  ti.format_spec = _(fmt);
+  ti.args_ptr = &ap;
+  ti.err_no = 0;
+  ti.x_data = NULL;
+  ti.m_richloc = &rich_loc;
+
+  pp_format (pp, &ti);
+  pp_output_formatted_text (pp);
+
+  va_end (ap);
+  char *result = xstrdup (pp_formatted_text (pp));
+
+  pp_clear_output_area (pp);
+
+  delete pp;
+
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+class engine;
+
+/* FIXME.  */
+
+class ec3_state : public extra_ec_state
+{
+ public:
+  ec3_state (const state_machine &sm,
+	     state_machine::state_t baseline_state,
+	     state_machine::state_t current_state)
+  : m_sm (sm),
+    m_baseline_state (baseline_state),
+    m_current_state (current_state)
+  {
+  }
+
+  extra_ec_state *clone () const FINAL OVERRIDE
+  {
+    return new ec3_state (m_sm, m_baseline_state, m_current_state);
+  }
+
+  std::string to_str () const FINAL OVERRIDE
+  {
+    return (std::string (" (state='")
+	    + m_sm.get_state_name (m_current_state)
+	    + "')");
+  }
+  hashval_t hash () const
+  {
+    // FIXME
+    return (hashval_t)m_baseline_state ^ m_current_state;
+  }
+
+  bool operator== (const extra_ec_state &other) const FINAL OVERRIDE
+  {
+    const ec3_state &other_ec3 = (const ec3_state &)other;
+    return (m_baseline_state == other_ec3.m_baseline_state
+	    && m_current_state == other_ec3.m_current_state);
+  }
+
+  const state_machine &m_sm;
+
+  /* For use in engine_sm_context::on_transition so that we only apply
+     changes relative to the state of the EC at the start of a set of
+     transitions.  */
+  state_machine::state_t m_baseline_state;
+
+  state_machine::state_t m_current_state;
+
+  // FIXME: alternatively, do these needs to be sets of states, to cope with
+  // merging of ECs???
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+enum event_kind
+{
+  EK_DEBUG,
+  EK_STMT,
+  EK_FUNCTION_ENTRY,
+  EK_STATE_CHANGE,
+  EK_EDGE,
+  EK_WARNING
+};
+
+static const char *
+event_kind_to_string (enum event_kind ek)
+{
+  switch (ek)
+    {
+    default:
+      gcc_unreachable ();
+    case EK_DEBUG:
+      return "EK_DEBUG";
+    case EK_STMT:
+      return "EK_STMT";
+    case EK_FUNCTION_ENTRY:
+      return "EK_FUNCTION_ENTRY";
+    case EK_STATE_CHANGE:
+      return "EK_STATE_CHANGE";
+    case EK_EDGE:
+      return "EK_EDGE";
+    case EK_WARNING:
+      return "EK_WARNING";
+    }
+}
+
+/* FIXME.  */
+
+class checker3_event : public diagnostic_event
+{
+public:
+  checker3_event (enum event_kind kind,
+		  location_t loc, tree fndecl, int depth)
+    : m_kind (kind), m_loc (loc), m_fndecl (fndecl), m_depth (depth),
+      m_pending_diagnostic (NULL), m_emission_id ()
+  {
+  }
+
+  location_t get_location () const FINAL OVERRIDE { return m_loc; }
+  tree get_fndecl () const FINAL OVERRIDE { return m_fndecl; }
+  int get_stack_depth () const FINAL OVERRIDE { return m_depth; }
+
+  virtual checker3_event *clone () const = 0;
+
+  std::string to_str () const
+  {
+    std::string result ("\"");
+    label_text event_desc (get_desc ());
+    result += event_desc.m_buffer;
+    event_desc.maybe_free ();
+    result += "\" (depth ";
+    result += std::to_string (get_stack_depth ());
+    result += ", m_loc=";
+    result += std::to_string (get_location ());
+    result += ")";
+    return result;
+  }
+
+  void prepare_for_emission (pending_diagnostic *pd, event_id_t emission_id)
+  {
+    m_pending_diagnostic = pd;
+    m_emission_id = emission_id;
+
+    /* Call get_desc now, to ensure that any side-effects take place
+       before pending_diagnostic::emit is called.
+       In particular, state_change_event::get_desc can call
+       pending_diagnostic::describe_critical_state_change;
+       free_of_non_heap can use this to tweak the message.
+       (Would be neater to simply capture this within the state).  */
+    label_text desc = get_desc ();
+    desc.maybe_free ();
+  }
+
+  enum event_kind m_kind;
+  location_t m_loc;
+  tree m_fndecl;
+  int m_depth;
+  pending_diagnostic *m_pending_diagnostic;
+  event_id_t m_emission_id; // only set once all pruning has occurred
+};
+
+class debug_event : public checker3_event
+{
+public:
+  debug_event (location_t loc, tree fndecl, int depth,
+	      const char *desc)
+  : checker3_event (EK_DEBUG, loc, fndecl, depth),
+    m_desc (xstrdup (desc))
+  {
+  }
+  ~debug_event ()
+  {
+    free (m_desc);
+  }
+
+  label_text get_desc () const FINAL OVERRIDE
+  {
+    return label_text::borrow (m_desc);
+  }
+
+  checker3_event *clone () const FINAL OVERRIDE
+  {
+    return new debug_event (m_loc, m_fndecl, m_depth, m_desc);
+  }
+
+private:
+  char *m_desc;
+};
+
+class statement_event : public checker3_event
+{
+public:
+  statement_event (const gimple *stmt, tree fndecl, int depth)
+  : checker3_event (EK_STMT, gimple_location (stmt), fndecl, depth),
+    m_stmt (stmt)
+  {
+  }
+
+  label_text get_desc () const FINAL OVERRIDE
+  {
+    pretty_printer pp;
+    pp_string (&pp, "stmt: ");
+    // FIXME: const_cast!
+    pp_gimple_stmt_1 (&pp, const_cast <gimple *> (m_stmt) , 0, (dump_flags_t)0);
+    return label_text::take (xstrdup (pp_formatted_text (&pp)));
+  }
+
+  checker3_event *clone () const FINAL OVERRIDE
+  {
+    return new statement_event (m_stmt, m_fndecl, m_depth);
+  }
+
+  const gimple *m_stmt;
+};
+
+class function_entry_event : public checker3_event
+{
+public:
+  function_entry_event (location_t loc, tree fndecl, int depth)
+  : checker3_event (EK_FUNCTION_ENTRY, loc, fndecl, depth)
+  {
+  }
+
+  label_text get_desc () const FINAL OVERRIDE
+  {
+    return label_text::take (print ("entry to %qE", m_fndecl));
+  }
+
+  checker3_event *clone () const FINAL OVERRIDE
+  {
+    return new function_entry_event (m_loc, m_fndecl, m_depth);
+  }
+
+  // FIXME
+};
+
+class state_change_event : public checker3_event
+{
+public:
+  state_change_event (const supernode *node, const gimple *stmt,
+		      int stack_depth,
+		      const state_machine &sm,
+		      tree var,
+		      state_machine::state_t from,
+		      state_machine::state_t to,
+		      tree origin)
+  : checker3_event (EK_STATE_CHANGE,
+		   stmt->location, node->m_fun->decl,
+		   stack_depth),
+    m_node (node), m_stmt (stmt), m_sm (sm),
+    m_var (var), m_from (from), m_to (to),
+    m_origin (origin)
+  {
+  }
+
+  label_text get_desc () const FINAL OVERRIDE
+  {
+    if (m_origin)
+      return label_text::take
+	(print ("state of %qE: %qs -> %qs (origin: %qE)",
+		m_var,
+		m_sm.get_state_name (m_from),
+		m_sm.get_state_name (m_to),
+		m_origin));
+    else
+      {
+	/* If there is no origin, then presumably this
+	   state change came directly from a pattern match.
+	   Attempt to generate a nicer human-readable description.
+	   For greatest precision-of-wording, give the pending diagnostic
+	   a chance to describe this state change (in terms of the
+	   diagnostic).
+	   Note that we only have a pending_diagnostic set on the event once
+	   the diagnostic is about to being emitted, so the description for
+	   an event can change.  */
+	if (m_pending_diagnostic)
+	  {
+	    label_text custom_desc
+	      = m_pending_diagnostic->describe_critical_state_change
+	      (//diagnostic_context *ctxt,
+	       m_var, m_from, m_to, m_emission_id);
+	    if (custom_desc.m_buffer)
+	      {
+		if (flag_analyzer_verbose_state_changes)
+		  {
+		    /* Append debug version.  */
+		    label_text result = label_text::take
+		      (print ("%s (state of %qE: %qs -> %qs, origin: NULL)",
+			      custom_desc.m_buffer,
+			      m_var,
+			      m_sm.get_state_name (m_from),
+			      m_sm.get_state_name (m_to)));
+		    custom_desc.maybe_free ();
+		    return result;
+		  }
+		else
+		  return custom_desc;
+	      }
+	  }
+
+	/* Fallback description.  */
+	return label_text::take
+	  (print ("state of %qE: %qs -> %qs (origin: NULL)",
+		  m_var,
+		  m_sm.get_state_name (m_from),
+		  m_sm.get_state_name (m_to)));
+      }
+  }
+
+  checker3_event *clone () const FINAL OVERRIDE
+  {
+    return new state_change_event (m_node, m_stmt, m_depth,
+				   m_sm, m_var, m_from, m_to, m_origin);
+  }
+
+  const supernode *m_node;
+  const gimple *m_stmt;
+  const state_machine &m_sm;
+  tree m_var;
+  state_machine::state_t m_from;
+  state_machine::state_t m_to;
+  tree m_origin;
+};
+
+class edge_event : public checker3_event
+{
+public:
+  edge_event (const superedge *edge,
+	      location_t loc, tree fndecl, int depth)
+  : checker3_event (EK_EDGE,
+		   loc, fndecl, depth),
+    m_edge (edge)
+  {
+  }
+
+  label_text get_desc () const FINAL OVERRIDE
+  {
+    switch (m_edge->m_kind)
+      {
+      default:
+	gcc_unreachable ();
+      case SUPEREDGE_CFG_EDGE:
+	{
+	  char *edge_desc = m_edge->get_description ();
+	  if (strlen (edge_desc) > 0)
+	    {
+	      label_text result
+		= label_text::take (print ("taking %qs edge SN:%i -> SN:%i",
+					   edge_desc,
+					   m_edge->m_src->m_index,
+					   m_edge->m_dest->m_index));
+	      free (edge_desc);
+	      return result;
+	    }
+	  else
+	    {
+	      free (edge_desc);
+	      return label_text::take (print ("taking edge SN:%i -> SN:%i",
+					      m_edge->m_src->m_index,
+					      m_edge->m_dest->m_index));
+	    }
+	}
+      case SUPEREDGE_CALL:
+	{
+	  return label_text::take (print ("calling %qE from %qE",
+					  m_edge->m_dest->m_fun->decl,
+					  m_edge->m_src->m_fun->decl));
+	}
+
+      case SUPEREDGE_RETURN:
+	{
+	  return label_text::take (print ("returning to %qE from %qE",
+					  m_edge->m_dest->m_fun->decl,
+					  m_edge->m_src->m_fun->decl));
+	}
+
+      case SUPEREDGE_INTRAPROCEDURAL_CALL:
+	return label_text::take (// TODO: possibly a higher-level description?
+				 print ("taking edge SN:%i -> SN:%i",
+					m_edge->m_src->m_index,
+					m_edge->m_dest->m_index));
+      }
+  }
+
+  const cfg_superedge& get_cfg_superedge () const
+  {
+    gcc_assert (m_edge->m_kind == SUPEREDGE_CFG_EDGE);
+    return *m_edge->dyn_cast_cfg_superedge ();
+  }
+
+  const callgraph_superedge& get_callgraph_superedge () const
+  {
+    gcc_assert (m_edge->m_kind != SUPEREDGE_CFG_EDGE);
+    return *m_edge->dyn_cast_callgraph_superedge ();
+  }
+
+  checker3_event *clone () const FINAL OVERRIDE
+  {
+    return new edge_event (m_edge, m_loc, m_fndecl, m_depth);
+  }
+
+  const superedge *m_edge;
+};
+
+/* FIXME: a repeat of the warning message at the end of the path.  */
+
+class warning_event : public checker3_event
+{
+public:
+  warning_event (location_t loc, tree fndecl, int depth,
+		 const char *msg)
+  : checker3_event (EK_WARNING, loc, fndecl, depth),
+    m_msg (xstrdup (msg))
+  {
+  }
+  ~warning_event () { free (m_msg); }
+
+  label_text get_desc () const FINAL OVERRIDE
+  {
+    return label_text::borrow (m_msg);
+  }
+
+  warning_event *clone () const FINAL OVERRIDE
+  {
+    return new warning_event (m_loc, m_fndecl, m_depth, m_msg);
+  }
+
+  char *m_msg;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.
+
+   Information on where a path has been so far, both in terms of the
+   supergraph, but also on states of interest.
+
+   This is to ensure termination of the analysis.
+
+   The goal is to terminate early without attempting e.g. to fully unroll
+   loops, but also to maintain some precision if interesting state changes
+   are occurring.
+
+   TODO: what should this be?
+   I'm currently thinking of a map from superedge to records, recording
+   expr->state maps, so that we can see if an expr has been in the state
+   before (whilst allowing for ECs to vary on different iterations, but
+   without being too tied to the expr-to-EC mapping).  */
+
+class terminator
+{
+public:
+  struct per_edge_t
+  {
+    per_edge_t (const superedge *edge)
+    : m_edge (edge), m_count (0)
+    {}
+    per_edge_t (const per_edge_t &other)
+    : m_edge (other.m_edge), m_count (other.m_count)
+    {}
+
+    std::string to_str () const
+    {
+      return (std::string ("(")
+	      + m_edge->to_str ()
+	      + ": "
+	      + std::to_string (m_count)
+	      + ")");
+    }
+
+    const superedge *m_edge;
+    // map from expr to set of states we've seen that expr in
+    // or should it be path_var?
+    // FIXME: for now, just a count of how many times we've followed this edge
+    int m_count;
+  };
+
+  terminator ();
+  terminator (const terminator &other);
+  terminator & operator= (const terminator &other);
+
+  std::string to_str () const;
+
+  void add_edge (const superedge *edge);
+  bool terminate_edge_p (const superedge *edge);
+
+  per_edge_t &get_edge_data (const superedge *edge);
+
+  typedef hash_map <const superedge *, per_edge_t *> edge_map_t;
+  edge_map_t m_per_edge_data;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+class checker3_path : public diagnostic_path
+{
+public:
+  struct link
+  {
+    link (const superedge *edge, const supernode *node)
+    : m_edge (edge), m_node (node)
+    {}
+
+    const superedge *m_edge;
+    const supernode *m_node;
+  };
+
+  checker3_path (engine *engine)
+  : diagnostic_path (),
+    m_engine (engine),
+    m_links (),
+    m_active_calls (),
+    m_ps (),
+    m_terminator ()
+  {}
+  checker3_path (const checker3_path &other);
+  checker3_path& operator= (const checker3_path &other);
+
+  std::string to_str () const;
+
+  void maybe_log (logger *logger, const char *desc) const
+  {
+    if (!logger)
+      return;
+    logger->log ("%s: %s", desc, to_str ().c_str ());
+    for (unsigned i = 0; i < m_events.length (); i++)
+      logger->log ("%s[%i]: %s",
+		   desc, i, m_events[i]->to_str ().c_str ());
+  }
+
+  unsigned num_events () const FINAL OVERRIDE
+  {
+    return m_events.length ();
+  }
+
+  const diagnostic_event & get_event (int idx) const FINAL OVERRIDE
+  {
+    return *m_events[idx];
+  }
+
+  void add_event (checker3_event *event)
+  {
+    m_events.safe_push (event);
+  }
+
+  void delete_event (int idx)
+  {
+    checker3_event *event = m_events[idx];
+    m_events.ordered_remove (idx);
+    delete event;
+  }
+
+  const supernode *get_end_node () const
+  {
+    gcc_assert (m_links.length () > 0);
+    return m_links[m_links.length () - 1].m_node;
+  }
+
+  /* Get the SUPEREDGE_RETURN corresponding to the most recent
+     unreturned SUPEREDGE_CALL.  Return NULL if there isn't one.  */
+  const superedge *get_next_return_edge () const
+  {
+    if (m_active_calls.length () == 0)
+      return NULL;
+    cgraph_edge *cedge = m_active_calls[m_active_calls.length () - 1];
+    return get_sg ().get_edge_for_return (cedge);
+  }
+
+  void add_link (const superedge *edge, const supernode *node);
+
+  int get_stack_depth () const { return m_ps.m_stack_depth; }
+
+  state_machine::state_t get_baseline_state (tree expr);
+  void set_state (tree expr,
+		  state_machine::state_t baseline_state,
+		  state_machine::state_t new_state);
+
+  equiv_class &get_ec (tree expr)
+  {
+    path_var pv (expr, get_stack_depth ());
+    equiv_class_id ec_id = m_ps.get_or_add_equiv_class (pv);
+    return ec_id.get_obj (m_ps);
+  }
+
+  const supergraph &get_sg () const;
+  const state_machine &get_sm () const;
+
+  int get_verbosity () const;
+
+  void add_final_event (const supernode *node, const gimple *stmt,
+			tree var, state_machine::state_t state,
+			pending_diagnostic *pd)
+  {
+    char *msg = NULL;
+    label_text ev_desc = pd->describe_final_event (var, state);
+    if (ev_desc.m_buffer)
+      {
+	if (flag_analyzer_verbose_state_changes)
+	  {
+	    msg = print ("%s (%qE is in state %qs)", ev_desc.m_buffer,
+			 var, get_sm ().get_state_name (state));
+	    ev_desc.maybe_free ();
+	  }
+	else
+	  msg = ev_desc.take_or_copy ();
+      }
+    else
+      {
+	msg = print ("here (%qE is in state %qs)", var,
+		     get_sm ().get_state_name (state));
+	// FIXME: ideally would show the full message here, too?
+      }
+    checker3_event *end_of_path
+      = new warning_event (stmt->location,
+			   node->m_fun->decl, get_stack_depth (),
+			   msg);
+    add_event (end_of_path);
+  }
+
+  void prepare_for_emission (pending_diagnostic *pd)
+  {
+    checker3_event *e;
+    int i;
+    FOR_EACH_VEC_ELT (m_events, i, e)
+      e->prepare_for_emission (pd, event_id_t (i));
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+
+  engine *m_engine;
+
+  /* The events that have occurred along this path.  */
+  auto_delete_vec<checker3_event> m_events;
+
+  /* A trace of nodes and edges that the path has followed.  */
+  auto_vec <link> m_links;
+
+  /* The outstanding calls, so that we can find where we should
+     return to.  */
+  auto_vec <cgraph_edge *> m_active_calls;
+
+  /* The current state of memory.  */
+  path_state m_ps;
+
+  terminator m_terminator;
+
+  /* FIXME: map from EC to state (or subclass path_state?)
+     No, store them in EC's m_extra_state.  */
+
+  /* TODO: add additional things, like edge counts, etc
+     (as an optimization; these could be derived by walking
+     the events).  */
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+class engine : public log_user
+{
+public:
+  engine (const supergraph &sg, const state_machine &sm,
+	  logger *logger, int verbosity)
+  : log_user (logger), m_sg (sg), m_sm (sm),
+    m_verbosity (verbosity),
+    m_num_traverse_calls (0),
+    m_on_stmt_calls (0),
+    m_num_path_copies (0),
+    m_num_event_copies (0)
+  {
+  }
+
+  void traverse (checker3_path path);
+  void on_stmt (checker3_path *path, const gimple *stmt);
+
+  void prune_path (checker3_path *path,
+		   tree var, state_machine::state_t state);
+
+  void log_stats () const;
+
+  const supergraph &m_sg;
+
+  // FIXME: this implies one traversal per state machine; should we instead
+  // do multiple state machines in one traversal?
+  const state_machine &m_sm;
+
+  int m_verbosity;
+
+  int m_num_traverse_calls;
+  int m_on_stmt_calls;
+  int m_num_path_copies;
+  int m_num_event_copies;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+class engine_sm_context : public sm_context
+{
+public:
+  engine_sm_context (engine *e, checker3_path *path)
+  : m_engine (e), m_path (path)
+  {
+    reset_baseline_states ();
+  }
+
+  ~engine_sm_context ()
+  {
+    reset_baseline_states ();
+  }
+
+  void reset_baseline_states ()
+  {
+    int i;
+    equiv_class *ec;
+    FOR_EACH_VEC_ELT (m_path->m_ps.m_equiv_classes, i, ec)
+      {
+	ec3_state *extra_state = (ec3_state *)ec->m_extra_state;
+	if (extra_state)
+	  extra_state->m_baseline_state = extra_state->m_current_state;
+      }
+  }
+
+  /* FIXME:  The SM can call this multiple times on one statement, e.g.
+     start -> free
+     free: warn_for_state
+     free -> stop.
+
+     We're interested in the states relative to when the sm_context
+     was created.  */
+
+  void on_transition (const supernode *node, const gimple *stmt,
+		      tree var,
+		      state_machine::state_t from,
+		      state_machine::state_t to,
+		      tree origin) FINAL OVERRIDE
+  {
+    JIT_LOG_FUNC_3 (m_engine->get_logger (),
+		    "var: %qE from: %s to: %s",
+		    var,
+		    m_engine->m_sm.get_state_name (from),
+		    m_engine->m_sm.get_state_name (to));
+    state_machine::state_t baseline = m_path->get_baseline_state (var);
+    if (from == baseline)
+      {
+	m_path->set_state (var, baseline, to);
+	checker3_event *event
+	  = new state_change_event (node, stmt, m_path->get_stack_depth (),
+				    m_engine->m_sm, var, from, to, origin);
+	m_path->add_event (event);
+      }
+  }
+
+  void warn_for_state (const supernode *node, const gimple *stmt,
+		       tree var, state_machine::state_t state,
+		       pending_diagnostic *d) FINAL OVERRIDE
+  {
+    JIT_LOG_FUNC_2 (m_engine->get_logger (),
+		    "if var: %qE in state: %s", var,
+		    m_engine->m_sm.get_state_name (state));
+
+    gcc_assert (d); // take ownership
+
+    state_machine::state_t baseline = m_path->get_baseline_state (var);
+    if (state == baseline)
+      {
+	checker3_path emission_path (*m_path);
+	m_engine->prune_path (&emission_path, var, state);
+	emission_path.add_final_event (node, stmt, var, state, d);
+	emission_path.prepare_for_emission (d);
+
+	gcc_rich_location rich_loc (stmt->location);
+	rich_loc.set_path (&emission_path);
+
+	auto_cfun sentinel (node->m_fun);
+	d->emit (&rich_loc);
+      }
+
+    delete d;
+  }
+
+  engine *m_engine;
+  checker3_path *m_path;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+terminator::terminator ()
+{
+}
+
+terminator::terminator (const terminator &other)
+{
+  for (edge_map_t::iterator iter = other.m_per_edge_data.begin ();
+       iter != other.m_per_edge_data.end ();
+       ++iter)
+    {
+      m_per_edge_data.put ((*iter).first,
+			   new per_edge_t (*(*iter).second));
+    }
+}
+
+terminator &
+terminator::operator= (const terminator &other)
+{
+  for (edge_map_t::iterator iter = other.m_per_edge_data.begin ();
+       iter != other.m_per_edge_data.end ();
+       ++iter)
+    {
+      m_per_edge_data.put ((*iter).first,
+			   new per_edge_t (*(*iter).second));
+    }
+  return *this;
+}
+
+std::string
+terminator::to_str () const
+{
+  std::string result ("{");
+  for (edge_map_t::iterator iter = m_per_edge_data.begin ();
+       iter != m_per_edge_data.end ();
+       ++iter)
+    {
+      if (iter != m_per_edge_data.begin ())
+	result += ", ";
+      result += (*iter).second->to_str ();
+    }
+  result += "}";
+  return result;
+}
+
+void
+terminator::add_edge (const superedge *edge)
+{
+  per_edge_t &edge_data = get_edge_data (edge);
+  edge_data.m_count++;
+}
+
+bool
+terminator::terminate_edge_p (const superedge *edge)
+{
+  per_edge_t &edge_data = get_edge_data (edge);
+  const int MAX_USES_OF_EDGE_PER_PATH = 1;
+  if (edge_data.m_count >= MAX_USES_OF_EDGE_PER_PATH)
+    return true;
+  return false;
+}
+
+terminator::per_edge_t &
+terminator::get_edge_data (const superedge *edge)
+{
+  if (per_edge_t **ptr_ptr = m_per_edge_data.get (edge))
+    return **ptr_ptr;
+  per_edge_t *data = new per_edge_t (edge);
+  m_per_edge_data.put (edge, data);
+  return *data;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+checker3_path::checker3_path (const checker3_path &other)
+: m_engine (other.m_engine),
+  m_links (),
+  m_ps (other.m_ps),
+  m_terminator (other.m_terminator)
+{
+  m_engine->m_num_path_copies++;
+  m_engine->m_num_event_copies += other.m_events.length ();
+
+  unsigned i;
+  {
+    link *l;
+    FOR_EACH_VEC_ELT (other.m_links, i, l)
+      m_links.safe_push (*l);
+  }
+  {
+    cgraph_edge *edge;
+    FOR_EACH_VEC_ELT (other.m_active_calls, i, edge)
+      m_active_calls.safe_push (edge);
+  }
+  {
+    checker3_event *e;
+    FOR_EACH_VEC_ELT (other.m_events, i, e)
+      m_events.safe_push (e->clone ());
+  }
+  // FIXME: quick push for these?
+}
+
+checker3_path&
+checker3_path::operator= (const checker3_path &other)
+{
+  m_engine = other.m_engine;
+  m_ps = other.m_ps;
+  m_terminator = other.m_terminator;
+
+  m_engine->m_num_path_copies++;
+  m_engine->m_num_event_copies += other.m_events.length ();
+
+  unsigned i;
+  {
+    link *l;
+    FOR_EACH_VEC_ELT (other.m_links, i, l)
+      m_links.safe_push (*l);
+  }
+  {
+    cgraph_edge *edge;
+    FOR_EACH_VEC_ELT (other.m_active_calls, i, edge)
+      m_active_calls.safe_push (edge);
+  }
+  {
+    checker3_event *e;
+    FOR_EACH_VEC_ELT (other.m_events, i, e)
+      m_events.safe_push (e->clone ());
+  }
+  return *this;
+}
+
+/* FIXME.  */
+std::string
+checker3_path::to_str () const
+{
+  std::string result ("[");
+
+  checker3_event *e;
+  int i;
+  FOR_EACH_VEC_ELT (m_events, i, e)
+    {
+      if (i > 0)
+	result += ", ";
+      label_text event_desc (e->get_desc ());
+      result += std::string ("\"") + event_desc.m_buffer + std::string ("\"");
+      event_desc.maybe_free ();
+    }
+  result += "]";
+  {
+    result += ", m_links=[";
+    link *link;
+    FOR_EACH_VEC_ELT (m_links, i, link)
+      {
+	if (i > 0)
+	  result += ", ";
+	result += std::to_string (link->m_node->m_index);
+      }
+    result += "]";
+  }
+  {
+    result += ", m_active_calls=[";
+    cgraph_edge *edge;
+    FOR_EACH_VEC_ELT (m_active_calls, i, edge)
+      {
+	if (i > 0)
+	  result += ", ";
+	result += m_engine->m_sg.get_edge_for_call (edge)->to_str ();
+      }
+    result += "]";
+  }
+  result += ", m_ps=" + m_ps.to_str ();
+  result += ", m_terminator=" + m_terminator.to_str ();
+  return result;
+}
+
+/* Record that we've reached NODE, via EDGE.  EDGE may be NULL,
+   to cover entrypoints to the analysis.
+   Update m_active_calls to reflect call and return edges.
+   Update m_terminator.  */
+
+void
+checker3_path::add_link (const superedge *edge, const supernode *node)
+{
+  m_links.safe_push (link (edge, node));
+  if (edge)
+    {
+      switch (edge->m_kind)
+	{
+	default:
+	  break;
+	case SUPEREDGE_CALL:
+	  {
+	    cgraph_edge *cedge = edge->get_callgraph_edge ();
+	    gcc_assert (cedge);
+	    m_active_calls.safe_push (cedge);
+	  }
+	  break;
+	case SUPEREDGE_RETURN:
+	  {
+	    cgraph_edge *top_cedge = m_active_calls.pop ();
+	    cgraph_edge *edge_cedge = edge->get_callgraph_edge ();
+	    gcc_assert (edge_cedge == top_cedge);
+	  }
+	  break;
+	}
+      m_terminator.add_edge (edge);
+    }
+  if (get_verbosity () > 1)
+    {
+      add_event (new debug_event (node->get_start_location (),
+				  node->m_fun->decl,
+				  get_stack_depth (),
+				  print ("SN: %i", node->m_index)));
+    }
+  if (get_verbosity () > 0 && node->entry_p ())
+    {
+      add_event
+	(new function_entry_event (node->get_start_location (),
+				   node->m_fun->decl,
+				   get_stack_depth ()));
+    }
+}
+
+state_machine::state_t
+checker3_path::get_baseline_state (tree expr)
+{
+  equiv_class &ec = get_ec (expr);
+  ec3_state *extra_state = (ec3_state *)ec.m_extra_state;
+  if (extra_state)
+    return extra_state->m_baseline_state;
+  else
+    return 0;  // FIXME "start"
+}
+
+void
+checker3_path::set_state (tree expr,
+			  state_machine::state_t baseline_state,
+			  state_machine::state_t new_state)
+{
+  equiv_class &ec = get_ec (expr);
+  delete ec.m_extra_state;
+  ec.m_extra_state = new ec3_state (get_sm (), baseline_state, new_state);
+  if (m_engine->get_logger ())
+    m_engine->log ("%qE state: %qs -> %qs",
+		   expr,
+		   get_sm ().get_state_name (baseline_state),
+		   get_sm ().get_state_name (new_state));
+}
+
+const supergraph &
+checker3_path::get_sg () const
+{
+  return m_engine->m_sg;
+}
+
+const state_machine &
+checker3_path::get_sm () const
+{
+  return m_engine->m_sm;
+}
+
+int
+checker3_path::get_verbosity () const
+{
+  return m_engine->m_verbosity;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+void
+engine::traverse (checker3_path path)
+{
+  const supernode *node = path.get_end_node ();
+  JIT_LOG_FUNC_1 (get_logger (), "SN: %i", node->m_index);
+  m_num_traverse_calls++;
+
+  if (get_logger ())
+    log ("path: %s", path.to_str ().c_str ());
+
+  // TODO
+
+  /* Phi nodes.  */
+  // FIXME: do these have to somehow happen simultaneously?
+  for (gphi_iterator gpi = const_cast<supernode *>(node)->start_phis ();
+       !gsi_end_p (gpi); gsi_next (&gpi))
+    {
+      gphi *phi = gpi.phi ();
+      // TODO
+    }
+
+  /* Stmts.  */
+  int i;
+  gimple *stmt;
+  FOR_EACH_VEC_ELT (node->m_stmts, i, stmt)
+    {
+      input_location = stmt->location;
+      on_stmt (&path, stmt);
+    }
+
+  /* Traverse into successors of NODE.  */
+  superedge *succ;
+  FOR_EACH_VEC_ELT (node->m_succs, i, succ)
+    {
+#if 1
+      log ("considering SN: %i -> SN: %i",
+	   node->m_index, succ->m_dest->m_index);
+#endif
+
+     switch (succ->m_kind)
+	{
+	default:
+	  gcc_unreachable ();
+	case SUPEREDGE_CFG_EDGE:
+	case SUPEREDGE_CALL:
+	  break;
+	case SUPEREDGE_RETURN:
+	  /* Only follow the correct return edge.  */
+	  if (succ != path.get_next_return_edge ())
+	    {
+	      log ("rejecting return edge");
+	      continue;
+	    }
+	  break;
+	case SUPEREDGE_INTRAPROCEDURAL_CALL:
+	  {
+	    /* Don't traverse intraprocedural edges here.  */
+	    log ("rejecting intraprocedural call edge");
+	    continue;
+	  }
+	}
+
+      if (path.m_terminator.terminate_edge_p (succ))
+	{
+	  log ("rejecting edge; seen too many times");
+	  // FIXME: but presumably we should do this upfront,
+	  // and figure out if we need to do an early-reject
+	  // of any edges; if so, we may need to relax constraints
+	  // to force an early loop exit
+	  continue;
+	}
+
+      checker3_path new_path (path);
+
+      /* For conditionals and switch statements, add the
+	 relevant conditions (for the specific edge) to new_ps;
+	 skip edges for which the resulting constraints
+	 are impossible.  */
+      const gimple *last_stmt = node->get_last_stmt ();
+      if (!new_path.m_ps.apply_constraints_for_edge (*succ, last_stmt))
+	{
+	  log ("skipping impossible edge to SN: %i",
+	       succ->m_dest->m_index);
+	  continue;
+	}
+      // TODO: but if it's a loop, consider skipping early
+
+      // TODO: reject edges that we've already followed
+
+      /* Allow for pattern-matching based on edge constraints.  */
+      //log ("considering edge-based patterns");
+
+      /* We need to clone the backtrace here so if the transition
+	 writes into the path each edge has its own copy.  */
+
+      checker3_event *event
+	= new edge_event (succ,
+			  (last_stmt
+			   ? last_stmt->location
+			   : UNKNOWN_LOCATION),
+			  node->m_fun->decl,
+			  path.get_stack_depth ()); // use old stack depth
+      new_path.add_event (event);
+
+      engine_sm_context ctxt (this, &new_path);
+      m_sm.on_edge (&ctxt, last_stmt, succ);
+
+      new_path.add_link (succ, succ->m_dest);
+      traverse (new_path);
+    }
+}
+
+/* FIXME.  */
+
+void
+engine::on_stmt (checker3_path *path, const gimple *stmt)
+{
+  JIT_LOG_FUNC (get_logger ());
+  if (get_logger ())
+    {
+      log ("path: %s", path->to_str ().c_str ());
+      pretty_printer pp;
+      pp_gimple_stmt_1 (&pp, const_cast <gimple *> (stmt) , 0, (dump_flags_t)0);
+      // FIXME: const_cast!
+      log ("stmt: %s", pp_formatted_text (&pp));
+    }
+
+  const supernode *node = path->get_end_node ();
+
+  checker3_event *event
+    = new statement_event (stmt,
+			   node->m_fun->decl,
+			   path->get_stack_depth ());
+  path->add_event (event);
+
+  engine_sm_context ctxt (this, path);
+  m_sm.on_stmt (&ctxt, node, stmt);
+  m_on_stmt_calls++;
+
+  // TODO
+
+  if (const gcall *call = dyn_cast <const gcall *> (stmt))
+    {
+      /* Debugging/test support.  */
+      if (is_named_call_p (call, "__dump_constraints", 0))
+	inform (stmt->location, "__dump_constraints: %s",
+		path->m_ps.to_str ().c_str ());
+
+      /* Handle the builtin "__analyzer_eval" by evaluating the input
+	 and dumping as a dummy warning, so that test cases can use dg-warning
+	 to validate the result (and so unexpected warnings will lead to DejaGnu
+	 failures.  */
+      if (is_named_call_p (call, "__analyzer_eval", 1))
+	{
+	  /* FIXME: crude hack: only do it for the malloc checker, to avoid
+	     getting every message more than once.
+	     Ultimately we want a command-line option for this, or
+	     something (this is really only for testcases though).  */
+	  if (0 == strcmp (m_sm.get_name (), "malloc"))
+	    {
+	      tree t_arg = gimple_call_arg (call, 0);
+	      tristate t
+		= path->m_ps.dump_condition (t_arg, NE_EXPR, integer_zero_node);
+	      warning_at (call->location, 0, "%s", t.as_string ());
+	    }
+	}
+    }
+
+
+}
+
+/* FIXME.  */
+
+void
+engine::prune_path (checker3_path *path,
+		    tree var, state_machine::state_t state ATTRIBUTE_UNUSED)
+{
+  JIT_LOG_FUNC (get_logger ());
+  path->maybe_log (get_logger (), "path");
+
+  // TODO
+  /* TODO: go backwards, skipping state change events that aren't var
+     but update the pertinent var when state-copying occurs.  */
+  int idx = path->m_events.length () - 1;
+  while (idx >= 0 && idx < path->m_events.length ())
+    {
+      checker3_event *base_event = path->m_events[idx];
+      if (get_logger ())
+	log  ("considering event %i, with var: %qE, state: %qs",
+	      idx, var, m_sm.get_state_name (state));
+      switch (base_event->m_kind)
+	{
+	default:
+	  gcc_unreachable ();
+
+	case EK_DEBUG:
+	  if (m_verbosity < 2)
+	    {
+	      log ("filtering event %i: debug event", idx);
+	      path->delete_event (idx);
+	    }
+	  break;
+
+	case EK_STMT:
+	  if (m_verbosity < 2)
+	    {
+	      log ("filtering event %i: statement event", idx);
+	      path->delete_event (idx);
+	    }
+	  break;
+
+	case EK_FUNCTION_ENTRY:
+	  if (m_verbosity < 1)
+	    {
+	      log ("filtering event %i: function entry", idx);
+	      path->delete_event (idx);
+	    }
+	  break;
+
+	case EK_STATE_CHANGE:
+	  {
+	    state_change_event *state_change = (state_change_event *)base_event;
+	    if (state_change->m_var == var)
+	      {
+		if (state_change->m_origin)
+		  {
+		    log ("event %i: switching var of interest from %qE to %qE",
+			 idx, var, state_change->m_origin);
+		    var = state_change->m_origin;
+		  }
+	      }
+	    else if (m_verbosity < 2)
+	      {
+		log ("filtering event %i: state change to %qE unrelated to %qE",
+		     idx, state_change->m_var, var);
+		path->delete_event (idx);
+	      }
+	  }
+	  break;
+
+	case EK_EDGE:
+	  {
+	    edge_event *event = (edge_event *)base_event;
+#if 0
+	    inform (base_event->m_loc, "considering edge_event %i: %s",
+		    idx, base_event->m_desc);
+#endif
+	    switch (event->m_edge->m_kind)
+	      {
+	      case SUPEREDGE_CFG_EDGE:
+		{
+		  const cfg_superedge& cfg_superedge
+		    = event->get_cfg_superedge ();
+		  const supernode *dest = event->m_edge->m_dest;
+		  /* Do we have an SSA_NAME defined via a phi node in
+		     the dest CFG node?  */
+		  if (TREE_CODE (var) == SSA_NAME)
+		    if (SSA_NAME_DEF_STMT (var)->bb == dest->m_bb)
+		      {
+			if (const gphi *phi
+			    = dyn_cast <gphi *> (SSA_NAME_DEF_STMT (var)))
+			  {
+			    /* Update var based on its phi node.  */
+			    tree old_var = var;
+			    var = cfg_superedge.get_phi_arg (phi);
+			    log ("updating from %qE to %qE based on phi node",
+				 old_var, var);
+			    if (get_logger ())
+			      {
+				pretty_printer pp;
+				pp_gimple_stmt_1 (&pp, (gimple *)phi, 0, (dump_flags_t)0);
+				// FIXME: const_cast!
+				log ("  phi: %s", pp_formatted_text (&pp));
+			      }
+			  }
+		      }
+
+		  /* TODO: is this edge significant to var?
+		     See if var can be in other states in the dest, but not
+		     in other states in the src?
+		     Must have multiple sibling edges.  */
+		  // TODO: for now, filter all intraprocedural edges:
+		  if (m_verbosity < 2)
+		    {
+		      log ("filtering event %i: CFG edge", idx);
+		      path->delete_event (idx);
+		    }
+		}
+		break;
+
+	      case SUPEREDGE_CALL:
+		{
+		  const callgraph_superedge& cg_superedge
+		    = event->get_callgraph_superedge ();
+		  tree caller_var
+		    = cg_superedge.map_expr_from_callee_to_caller (var);
+		  if (caller_var)
+		    var = caller_var;
+		}
+		break;
+	      case SUPEREDGE_RETURN:
+		// TODO: potentially update var/state based on return value,
+		// args etc
+		{
+		  const callgraph_superedge& cg_superedge
+		    = event->get_callgraph_superedge ();
+		  tree callee_var
+		    = cg_superedge.map_expr_from_caller_to_callee (var);
+		  if (callee_var)
+		    var = callee_var;
+		}
+		break;
+	      case SUPEREDGE_INTRAPROCEDURAL_CALL:
+		break;
+	      }
+	  }
+	  break;
+
+	case EK_WARNING:
+	  break;
+	}
+      idx--;
+    }
+
+  /* Second pass.  If all we're left with is in one function, then
+     filter function entry events.  */
+  if (!path->interprocedural_p ())
+    {
+      // FIXME
+      int idx = path->m_events.length () - 1;
+      while (idx >= 0 && idx < path->m_events.length ())
+	{
+	  checker3_event *base_event = path->m_events[idx];
+	  if (base_event->m_kind == EK_FUNCTION_ENTRY)
+	    {
+	      log ("filtering event %i:"
+		   " function entry for purely intraprocedural path", idx);
+	      path->delete_event (idx);
+	    }
+	  idx--;
+	}
+    }
+}
+
+/* FIXME.  */
+
+void
+engine::log_stats () const
+{
+  log ("#supernodes: %i", m_sg.num_nodes ());
+  log ("#superedges: %i", m_sg.num_edges ());
+  log ("m_num_traverse_calls: %i", m_num_traverse_calls);
+  log ("m_on_stmt_calls: %i", m_on_stmt_calls);
+  log ("m_num_path_copies: %i", m_num_path_copies);
+  log ("m_num_event_copies: %i", m_num_event_copies);
+
+  if (0)
+    {
+      inform (UNKNOWN_LOCATION, "#supernodes: %i", m_sg.num_nodes ());
+      inform (UNKNOWN_LOCATION, "#superedges: %i", m_sg.num_edges ());
+      inform (UNKNOWN_LOCATION, "m_num_traverse_calls: %i",
+	      m_num_traverse_calls);
+      inform (UNKNOWN_LOCATION, "m_on_stmt_calls: %i", m_on_stmt_calls);
+      inform (UNKNOWN_LOCATION, "m_num_path_copies: %i", m_num_path_copies);
+      inform (UNKNOWN_LOCATION, "m_num_event_copies: %i", m_num_event_copies);
+    }
+}
+
+
+/* FIXME.  */
+
+static int
+function_comparator (const void *pn1, const void *pn2)
+{
+  const cgraph_node *node1 = *(const cgraph_node * const *)pn1;
+  const cgraph_node *node2 = *(const cgraph_node * const *)pn2;
+
+  location_t loc1 = node1->get_fun ()->function_start_locus;
+  location_t loc2 = node2->get_fun ()->function_start_locus;
+
+  return linemap_compare_locations (line_table, loc2, loc1);
+}
+
+/* FIXME.  */
+/* TODO: should we run them all at once, or serially?
+   For now, this does them serially.  */
+
+void
+run_one_checker (const supergraph &sg, const state_machine &sm, logger *logger)
+{
+  JIT_LOG_FUNC_1 (logger, "sm: %s", sm.get_name ());
+
+  if (flag_analyzer_checker)
+    {
+      if (0 != strcmp (flag_analyzer_checker, sm.get_name ()))
+	{
+	  if (logger)
+	    logger->log ("skipping checker %s (does not match %s)",
+			 sm.get_name (), flag_analyzer_checker);
+	  return;
+	}
+    }
+
+  engine e (sg, sm, logger, analyzer_verbosity);
+
+  cgraph_node *node;
+  auto_vec<cgraph_node *> root_functions;
+  FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
+    root_functions.safe_push (node);
+
+  /* Sort root_functions so that diagnostics are emitted in a
+     sane order.
+     TODO: perhaps we should buffer the diagnostics and sort them later,
+     instead.  */
+  root_functions.qsort (function_comparator);
+
+  int i;
+  FOR_EACH_VEC_ELT (root_functions, i, node)
+    {
+      function *fun = node->get_fun ();
+
+      if (!TREE_PUBLIC (fun->decl))
+	{
+	  if (logger)
+	    logger->log ("not traversing %qE (static)", fun->decl);
+	  continue;
+	}
+
+      log_scope s (logger, "top-level traversal", "%s", function_name (fun));
+
+      checker3_path path (&e);
+      path.add_link (NULL, sg.get_node_for_function_entry (fun));
+      e.traverse (path);
+    }
+
+  e.log_stats ();
+}
+
+} // namespace impl_3
+
+/* FIXME.  */
+
+void
+run_checkers_impl_3 ()
+{
+  //FILE *dump = fopen ("test.out", "w"); // FIXME
+  auto_client_timevar tv ("run_checkers");
+  {
+    log_user the_logger (NULL);
+    if (flag_dump_analyzer)
+      {
+	FILE *dump_file = /* 0 ? dump : */stderr; // FIXME
+	the_logger.set_logger (new logger (dump_file, 0, 0,
+					   *global_dc->printer));
+      }
+    JIT_LOG_SCOPE (the_logger.get_logger ());
+
+    /* Create the supergraph.  */
+    supergraph sg (the_logger.get_logger ());
+
+    if (1)
+      {
+	char *filename = concat (dump_base_name, ".supergraph.dot", NULL);
+	sg.dump_dot (filename, (enum supergraph_dot_flags)0);
+	free (filename);
+      }
+
+#if 0
+    // TODO: selftest for auto_delete_vec
+    auto_delete_vec <state_machine> checkers;
+    make_checkers (checkers, the_logger.get_logger ());
+
+    int i;
+    state_machine *checker;
+    FOR_EACH_VEC_ELT (checkers, i, checker)
+      // FIXME: this requires better timevar ID management
+#endif
+
+    // TODO
+    // For now, just count paths through the supergraph
+
+#if 1
+    {
+      auto_client_timevar tv ("malloc_state_machine");
+      malloc_state_machine sm (the_logger.get_logger ());
+      impl_3::run_one_checker (sg, sm, the_logger.get_logger ());
+    }
+
+    {
+      auto_client_timevar tv ("fileptr_state_machine");
+      fileptr_state_machine sm (the_logger.get_logger ());
+      impl_3::run_one_checker (sg, sm, the_logger.get_logger ());
+    }
+
+    {
+      auto_client_timevar tv ("taint_state_machine");
+      taint_state_machine sm (the_logger.get_logger ());
+      impl_3::run_one_checker (sg, sm, the_logger.get_logger ());
+    }
+
+    {
+      auto_client_timevar tv ("sensitive_state_machine");
+      sensitive_state_machine sm (the_logger.get_logger ());
+      impl_3::run_one_checker (sg, sm, the_logger.get_logger ());
+    }
+
+    /* We only attempt to run the pattern tests if it might have been manually
+       enabled (for DejaGnu purposes).  */
+    if (flag_analyzer_checker)
+      {
+	auto_client_timevar tv ("pattern_test_state_machine");
+	pattern_test_state_machine sm (the_logger.get_logger ());
+	impl_3::run_one_checker (sg, sm, the_logger.get_logger ());
+      }
+#endif
+
+    /* end of lifetime of the_logger (so that dump file is closed after the
+       various dtors run).  */
+  }
+
+  //fclose (dump);
+}
diff --git a/gcc/analyzer/impl-4.cc b/gcc/analyzer/impl-4.cc
new file mode 100644
index 0000000..b12102c
--- /dev/null
+++ b/gcc/analyzer/impl-4.cc
@@ -0,0 +1,4194 @@
+/* FIXME.  */
+#include "config.h"
+#include "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "tm.h"
+#include "toplev.h"
+#include "hash-table.h"
+#include "vec.h"
+#include "ggc.h"
+#include "basic-block.h"
+#include "tree-ssa-alias.h"
+#include "internal-fn.h"
+#include "gimple-fold.h"
+#include "tree-eh.h"
+#include "gimple-expr.h"
+#include "is-a.h"
+#include "gimple.h"
+#include "tree-pass.h"
+#include "intl.h"
+#include "context.h"
+#include "diagnostic.h"
+#include "bitmap.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "gimple-pretty-print.h"
+#include "tree-pretty-print.h"
+#include "analyzer.h"
+#include "graphviz.h"
+#include "cgraph.h"
+#include "diagnostic-path.h"
+#include "gcc-rich-location.h"
+#include "tree-dfa.h"
+#include <string>
+#include "supergraph.h"
+#include "hardcoded-state-machine.h"
+#include "analyzer-logging.h"
+#include "stringpool.h"
+#include "selftest.h"
+#include "fold-const.h"
+#include "tristate.h"
+#include "selftest.h"
+#include "digraph.h"
+#include "constraints.h"
+//#include "tree-ssa-operands.h"
+
+/* Overview:
+
+   The implementation works on the gimple-SSA representation.
+   (I chose this in the hopes of making it easy to work with LTO to
+   do whole-program analysis).
+
+   The implementation is read-only: it doesn't attempt to change anything,
+   just emit warnings.
+
+   First, we build a "supergraph" which combines the callgraph and all of
+   the CFGs into a single directed graph, with both interprocedural and
+   intraprocedural edges.
+
+   We then use a worklist to explore state within the supergraph,
+   building an "exploded graph".  Nodes in the exploded graph correspond
+   to <point, state> pairs, as in
+     "Precise Interprocedural Dataflow Analysis via Graph Reachability"
+     (Thomas Reps, Susan Horwitz and Mooly Sagiv)
+
+   We reuse nodes for <point, state> pairs we've already seen, and avoid
+   tracking state too closely, so that (hopefully) we rapidly converge
+   on a final exploded graph, and terminate the analysis.  We also bail
+   out if the number of exploded <end-of-basic-block, state> nodes gets
+   larger than a particular multiple of the total number of basic blocks
+   (to ensure termination in the face of pathological state-explosion
+   cases, or bugs).
+
+   We can identify problems directly when processing a <point, state>
+   instance.  For example, if we're finding the successors of
+     <point: before-stmt: "free (ptr);",
+      state: {"ptr": freed}>
+   then we can detect a double-free of "ptr".  We can then emit a path
+   to reach the problem by finding the simplest route through the graph.
+
+   Points in the exploded graph are much more fine-grained than in the
+   CFG and supergraph, with points (and thus nodes) for various events,
+   including both before and after points for individual statements.
+   Although this bloats the graph, this fine-grained approach
+   seems to make things simpler and more debuggable that other approaches
+   I tried, in that each point is responsible for one thing.  Hopefully it
+   will also better support unit-testing, by having program_point be
+   testable without building gimple stmts.
+
+   Points in the exploded graph also have a "call string" identifying the
+   stack of callsites below them, so that paths in the exploded graph
+   correspond to interprocedurally valid paths: we always return to the
+   correct call site, propagating state information accordingly.
+
+   TODO: What about infinite recursion?  */
+
+namespace impl_4 {
+
+class exploded_graph;
+class exploded_node;
+class exploded_edge;
+class checker4_path;
+class stmt_finder;
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+char *print (const char *fmt, ...)
+{
+  /* FIXME: all this cloning is likely to be expensive.  */
+  pretty_printer *pp = global_dc->printer->clone ();
+  pp_clear_output_area (pp);
+
+  text_info ti;
+  rich_location rich_loc (line_table, UNKNOWN_LOCATION);
+
+  va_list ap;
+
+  va_start (ap, fmt);
+
+  ti.format_spec = _(fmt);
+  ti.args_ptr = &ap;
+  ti.err_no = 0;
+  ti.x_data = NULL;
+  ti.m_richloc = &rich_loc;
+
+  pp_format (pp, &ti);
+  pp_output_formatted_text (pp);
+
+  va_end (ap);
+  char *result = xstrdup (pp_formatted_text (pp));
+
+  pp_clear_output_area (pp);
+
+  delete pp;
+
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+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);
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* A string of return_superedge pointers, representing a call stack
+   at a program point.
+
+   This is used to ensure that we generate interprocedurally valid paths
+   i.e. that we return to the same callsite that called us.
+
+   The class actually stores the return edges, rather than the call edges,
+   since that's what we need to compare against.  */
+
+class call_string
+{
+public:
+  call_string () : m_return_edges () {}
+  call_string (const call_string &other);
+  call_string& operator= (const call_string &other);
+
+  bool operator== (const call_string &other) const;
+
+  void print (pretty_printer *pp) const;
+  std::string to_str () const;
+
+  hashval_t hash () const;
+
+  bool empty_p () const { return m_return_edges.is_empty (); }
+
+  void push_call (const supergraph &sg,
+		  const call_superedge *sedge);
+  const return_superedge *pop () { return m_return_edges.pop (); }
+
+  int get_stack_depth () const { return m_return_edges.length (); }
+
+  path_var make_path_var (tree expr) const
+  {
+    return path_var (expr, get_stack_depth ());
+  }
+
+  int calc_recursion_depth () const;
+
+private:
+  auto_vec<const return_superedge *> m_return_edges;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* An enum for distinguishing the various kinds of program_point.  */
+
+enum point_kind {
+  /* A "fake" node which has edges to all entrypoints.  */
+  PK_ORIGIN,
+
+  PK_BEFORE_SUPERNODE,
+  PK_BEFORE_STMT,
+  PK_AFTER_STMT,
+  PK_AFTER_SUPERNODE,
+
+  /* Special values used for hash_map:  */
+  PK_EMPTY,
+  PK_DELETED,
+
+  NUM_POINT_KINDS
+};
+
+/* FIXME.  */
+
+static const char *
+point_kind_to_string (enum point_kind pk)
+{
+  switch (pk)
+    {
+    default:
+      gcc_unreachable ();
+    case PK_ORIGIN:
+      return "PK_ORIGIN";
+    case PK_BEFORE_SUPERNODE:
+      return "PK_BEFORE_SUPERNODE";
+    case PK_BEFORE_STMT:
+      return "PK_BEFORE_STMT";
+    case PK_AFTER_STMT:
+      return "PK_AFTER_STMT";
+    case PK_AFTER_SUPERNODE:
+      return "PK_AFTER_SUPERNODE";
+    case PK_EMPTY:
+      return "PK_EMPTY";
+    case PK_DELETED:
+      return "PK_DELETED";
+    }
+}
+
+/* A class for representing the location within the program.
+
+   This represents a fine-grained location within the supergraph (or
+   within one of its nodes), along with a call string giving the
+   interprocedual context.  */
+
+class program_point
+{
+public:
+  program_point (const supernode *supernode,
+		 const superedge *from_edge,
+		 unsigned stmt_idx,
+		 enum point_kind kind,
+		 const call_string &call_string)
+  : m_supernode (supernode), m_from_edge (from_edge),
+    m_stmt_idx (stmt_idx), m_kind (kind),
+    m_call_string (call_string)
+  {
+  }
+
+  void print (pretty_printer *pp) const;
+  void print_source_line (pretty_printer *pp) const;
+  std::string to_str () const;
+
+  hashval_t hash () const;
+  bool operator== (const program_point &other) const
+  {
+    return (m_supernode == other.m_supernode
+	    && m_from_edge == other.m_from_edge
+	    && m_stmt_idx == other.m_stmt_idx
+	    && m_kind == other.m_kind
+	    && m_call_string == other.m_call_string);
+  }
+
+  const supernode *get_supernode () const { return m_supernode; }
+  function *get_function () const
+  {
+    if (m_supernode)
+      return m_supernode->m_fun;
+    else
+      return NULL;
+  }
+
+  int get_stack_depth () const
+  {
+    return m_call_string.get_stack_depth ();
+  }
+
+  const gimple *get_stmt () const;
+
+  path_var make_path_var (tree expr) const
+  {
+    return m_call_string.make_path_var (expr);
+  }
+
+  static program_point from_function_entry (const supergraph &sg,
+					    function *fun)
+  {
+    return before_supernode (sg.get_node_for_function_entry (fun),
+			     NULL, call_string ());
+  }
+
+  static program_point before_supernode (const supernode *supernode,
+					 const superedge *from_edge,
+					 const call_string &call_string)
+  {
+    return program_point (supernode, from_edge, 0, PK_BEFORE_SUPERNODE,
+			  call_string);
+  }
+
+  static program_point before_stmt (const supernode *supernode,
+				    unsigned stmt_idx,
+				    const call_string &call_string)
+  {
+    return program_point (supernode, NULL, stmt_idx, PK_BEFORE_STMT,
+			  call_string);
+  }
+
+  static program_point after_stmt (const supernode *supernode,
+				   unsigned stmt_idx,
+				   const call_string &call_string)
+  {
+    return program_point (supernode, NULL, stmt_idx, PK_AFTER_STMT,
+			  call_string);
+  }
+
+  static program_point after_supernode (const supernode *supernode,
+					const call_string &call_string)
+  {
+    return program_point (supernode, NULL, 0, PK_AFTER_SUPERNODE,
+			  call_string);
+  }
+
+  static program_point empty ()
+  {
+    return program_point (NULL, NULL, 0, PK_EMPTY, call_string ());
+  }
+  static program_point deleted ()
+  {
+    return program_point (NULL, NULL, 0, PK_DELETED, call_string ());
+  }
+
+  bool on_edge (const exploded_graph &eg,
+		const superedge *succ);
+
+//private:
+  const supernode *m_supernode;
+  const superedge *m_from_edge; /* for PK_BEFORE_SUPERNODE.  */
+  unsigned m_stmt_idx;
+  enum point_kind m_kind;
+  call_string m_call_string;
+};
+
+/* Data shared by all program_state instances.  */
+
+class extrinsic_state
+{
+public:
+  extrinsic_state (auto_delete_vec <state_machine> &checkers)
+  : m_checkers (checkers)
+  {
+  }
+
+  const state_machine &get_sm (int idx) const
+  {
+    return *m_checkers[idx];
+  }
+
+  const char *get_name (int idx) const
+  {
+    return m_checkers[idx]->get_name ();
+  }
+
+  /* The state machines.  */
+  auto_delete_vec <state_machine> &m_checkers;
+};
+
+} // namespace impl_4
+
+using impl_4::pod_hash_traits;
+
+template <> struct default_hash_traits<path_var>
+: public pod_hash_traits<path_var>
+{
+};
+
+template <>
+inline hashval_t
+pod_hash_traits<path_var>::hash (value_type v)
+{
+  return (hashval_t)((intptr_t)v.m_tree >> 3) ^ v.m_stack_depth;
+}
+
+template <>
+inline bool
+pod_hash_traits<path_var>::equal (const value_type &existing,
+				  const value_type &candidate)
+{
+  return existing == candidate;
+}
+template <>
+inline void
+pod_hash_traits<path_var>::mark_deleted (value_type &v)
+{
+  v = path_var (reinterpret_cast<tree> (1), 0);
+}
+template <>
+inline void
+pod_hash_traits<path_var>::mark_empty (value_type &v)
+{
+  v = path_var (NULL_TREE, 0);
+}
+template <>
+inline bool
+pod_hash_traits<path_var>::is_deleted (value_type v)
+{
+  return v.m_tree == reinterpret_cast<tree> (1);
+}
+template <>
+inline bool
+pod_hash_traits<path_var>::is_empty (value_type v)
+{
+  return v.m_tree == NULL_TREE;
+}
+
+namespace impl_4 {
+
+/* Map from var to state machine state, also capturing the origin of
+   each state.  */
+
+class sm_state_map
+{
+public:
+  struct entry_t
+  {
+    entry_t (state_machine::state_t state,
+	     path_var origin)
+    : m_state (state), m_origin (origin)
+    {}
+
+    bool operator== (const entry_t &other) const
+    {
+      return (m_state == other.m_state
+	      && m_origin == other.m_origin);
+    }
+    bool operator!= (const entry_t &other) const
+    {
+      return !(*this == other);
+    }
+
+    state_machine::state_t m_state;
+    path_var m_origin;
+  };
+  typedef hash_map <path_var, entry_t> map_t;
+  typedef map_t::iterator iterator_t;
+
+  sm_state_map *clone () const;
+
+  void print (const state_machine &sm, pretty_printer *pp) const;
+  std::string to_str (const state_machine &sm) const;
+
+  bool is_empty_p () const;
+
+  hashval_t hash () const;
+
+  bool operator== (const sm_state_map &other) const;
+  bool operator!= (const sm_state_map &other) const
+  {
+    return !(*this == other);
+  }
+
+  state_machine::state_t get_state (path_var pv) const;
+  path_var get_origin (path_var pv) const;
+
+  void set_state (path_state *ps, path_var pv, state_machine::state_t state,
+		  path_var origin);
+  void set_state (const equiv_class &ec,
+		  state_machine::state_t state,
+		  path_var origin);
+  void impl_set_state (path_var pv,
+		       state_machine::state_t state,
+		       path_var origin);
+
+  void purge_stack_frame (int callee_stack_depth);
+
+  iterator_t begin () const { return m_map.begin (); }
+  iterator_t end () const { return m_map.end (); }
+
+private:
+  map_t m_map;
+};
+
+/* A class for representing the state of interest at a given path of
+   analysis.
+
+   Currently this is a combination of:
+   - a path_state giving a set of equivalence classes of path_vars and
+     inequalities between them
+   - sm_state_maps per state machine, giving a sparse mapping of
+     path_vars to states.  */
+
+class program_state
+{
+public:
+  program_state (int num_checkers);
+  program_state (const extrinsic_state &ext_state);
+  program_state (const program_state &other);
+  program_state& operator= (const program_state &other);
+
+  hashval_t hash () const;
+  bool operator== (const program_state &other) const;
+
+  void print (const extrinsic_state &ext_state,
+	      pretty_printer *pp) const;
+  std::string to_str (const extrinsic_state &ext_state) const;
+
+  equiv_class &get_ec (path_var pv)
+  {
+    equiv_class_id ec_id = m_path_state.get_or_add_equiv_class (pv);
+    return ec_id.get_obj (m_path_state);
+  }
+
+  program_state prune_for_point (const exploded_graph &eg,
+				 const program_point &point) const;
+
+  bool on_edge (const exploded_graph &eg,
+		const exploded_node &enode,
+		const superedge *succ);
+
+  //private:
+  path_state m_path_state;
+
+  auto_vec<sm_state_map *> m_checker_states;
+};
+
+/* A <program_point, program_state> pair, used internally by
+   exploded_node as its immutable data, and as a key for identifying
+   exploded_nodes we've already seen in the graph.  */
+
+struct point_and_state
+{
+  point_and_state (const program_point &point,
+		   const program_state &state)
+  : m_point (point),
+    m_state (state)
+  {
+  }
+
+  hashval_t hash () const
+  {
+    return m_point.hash () ^ m_state.hash ();
+  }
+
+  program_point m_point;
+  program_state m_state;
+};
+
+} // namespace impl_4
+
+using impl_4::point_and_state;
+using impl_4::pod_hash_traits;
+
+template <> struct default_hash_traits<point_and_state>
+: public pod_hash_traits<point_and_state>
+{
+};
+
+template <>
+inline hashval_t
+pod_hash_traits<point_and_state>::hash (value_type v)
+{
+  return v.hash ();
+}
+
+template <>
+inline bool
+pod_hash_traits<point_and_state>::equal (const value_type &existing,
+					 const value_type &candidate)
+{
+  return (existing.m_point == candidate.m_point
+	  && existing.m_state == candidate.m_state);
+}
+template <>
+inline void
+pod_hash_traits<point_and_state>::mark_deleted (value_type &v)
+{
+  v = point_and_state (program_point::deleted (), program_state (0));
+}
+template <>
+inline void
+pod_hash_traits<point_and_state>::mark_empty (value_type &v)
+{
+  v = point_and_state (program_point::empty (), program_state (0));
+}
+template <>
+inline bool
+pod_hash_traits<point_and_state>::is_deleted (value_type v)
+{
+  return (v.m_point.m_kind == PK_DELETED);
+}
+template <>
+inline bool
+pod_hash_traits<point_and_state>::is_empty (value_type v)
+{
+  return (v.m_point.m_kind == PK_EMPTY);
+}
+
+namespace impl_4 {
+
+/* An exploded_node is a unique, immutable <point, state> pair within the
+   exploded_graph.
+   Each exploded_node has a unique index within the graph
+   (for ease of debugging).  */
+
+class exploded_node : public dnode
+{
+ public:
+  exploded_node (point_and_state ps,
+		 const extrinsic_state &ext_state,
+		 int index)
+  : m_ps (ps), m_ext_state (ext_state), m_index (index)
+  {
+  }
+
+  void dump_dot (graphviz_out *gv) const FINAL OVERRIDE;
+  void dump_dot_id (pretty_printer *pp) const;
+
+  program_state on_stmt (const exploded_graph &eg) const;
+  bool on_edge (const exploded_graph &eg,
+		const superedge *succ,
+		program_point *next_point,
+		program_state *next_state) const;
+
+  void on_assignment (const exploded_graph &eg,
+		      path_var lhs,
+		      path_var rhs,
+		      const program_state &old_state,
+		      program_state *new_state) const;
+
+  void copy_sm_state (const exploded_graph &eg,
+		      path_var lhs,
+		      path_var rhs,
+		      const program_state &old_state,
+		      program_state *new_state) const;
+
+  void detect_leaks (const exploded_graph &eg) const;
+
+  const program_point &get_point () const { return m_ps.m_point; }
+  const supernode *get_supernode () const
+  {
+    return get_point ().get_supernode ();
+  }
+  function *get_function () const
+  {
+    return get_point ().get_function ();
+  }
+  int get_stack_depth () const
+  {
+    return get_point ().get_stack_depth ();
+  }
+
+  const program_state &get_state () const { return m_ps.m_state; }
+
+private:
+  /* The <program_point, program_state> pair.  This is const, as it
+     is immutable once the exploded_node has been created.  */
+  const point_and_state m_ps;
+
+  /* FIXME: this is within the node since dump_dot is a vfunc
+     In theory we could just pass this around instead.  */
+  const extrinsic_state &m_ext_state;
+
+public: // FIXME
+
+  /* The index of this exploded_node.  */
+  const int m_index;
+
+  auto_vec<exploded_edge *> m_succs;
+};
+
+/* An edge within the exploded graph.
+   Some exploded_edges have an underlying superedge; others don't.  */
+
+class exploded_edge : public dedge
+{
+ public:
+  exploded_edge (exploded_node *src, exploded_node *dest,
+		 const superedge *sedge);
+  void dump_dot (graphviz_out *gv) const FINAL OVERRIDE;
+
+  //private:
+  exploded_node *m_src;
+  exploded_node *m_dest;
+  const superedge *m_sedge;
+};
+
+/* An exploded_graph is a directed graph of unique <point, state> pairs.
+   It also has a worklist of nodes that are waiting for their successors
+   to be added to the graph.  */
+
+class exploded_graph : public digraph, public log_user
+{
+public:
+  exploded_graph (const supergraph &sg, logger *logger,
+		  const extrinsic_state &ext_state,
+		  int verbosity);
+
+  const supergraph &get_supergraph () const { return m_sg; }
+  const extrinsic_state &get_ext_state () const { return m_ext_state; }
+
+  exploded_node *get_origin () const { return m_origin; }
+
+  void add_function_entry (function *fun);
+
+  void process_worklist ();
+  void process_node (exploded_node *node);
+
+  exploded_node *get_or_create_node (const program_point &point,
+				     const program_state &state);
+  void add_edge (exploded_node *src, exploded_node *dest,
+		 const superedge *sedge);
+
+  void emit_diagnostic (const state_machine &sm,
+			const exploded_node *enode,
+			const supernode *node, const gimple *stmt,
+			stmt_finder *finder,
+			tree var, state_machine::state_t state,
+			pending_diagnostic *d) const;
+  void prune_path (checker4_path *path,
+		   const state_machine &sm,
+		   tree var, state_machine::state_t state) const;
+
+  void log_stats () const;
+
+private:
+  const supergraph &m_sg;
+
+  /* Map from point/state to exploded node.  */
+  typedef hash_map <point_and_state, exploded_node *> map_t;
+  map_t m_point_and_state_to_node;
+
+  auto_vec<exploded_node *> m_worklist;
+
+  exploded_node *m_origin;
+
+  const extrinsic_state &m_ext_state;
+
+  int m_verbosity;
+
+  /* Stats.  */
+  int m_num_nodes[NUM_POINT_KINDS];
+  int m_node_reuse_count;
+};
+
+/* A path within an exploded_graph: a sequence of edges.  */
+
+class exploded_path
+{
+public:
+  exploded_path () : m_edges () {}
+  exploded_path (const exploded_path &other);
+  exploded_path & operator= (const exploded_path &other);
+
+  bool find_stmt_backwards (const gimple *search_stmt,
+			    int *out_idx) const;
+
+  auto_vec<const exploded_edge *> m_edges;
+};
+
+/* A record of the shortest path to each node in an exploded_graph
+   from the origin node.
+   The constructor runs Dijkstra's algorithm, and the results are
+   stored in this class.  */
+
+class shortest_paths
+{
+public:
+  shortest_paths (const exploded_graph &eg);
+
+  exploded_path get_shortest_path (const exploded_node *to) const;
+
+private:
+  const exploded_graph &m_eg;
+
+  /* For each exploded_node (by index), the minimal distance to that
+     node from the origin.  */
+  auto_vec<int> m_dist;
+
+  /* For each exploded_node (by index), the previous exploded_edge in
+     the shortest path from the origin.  */
+  auto_vec<const exploded_edge *> m_prev;
+};
+
+/* Abstract base class for use when passing NULL as the stmt for
+   a possible warning, allowing the choice of stmt to be deferred
+   until after we have an emission path (and know we're emitting a
+   warning).  */
+
+class stmt_finder
+{
+public:
+  virtual const gimple *find_stmt (const exploded_path &epath) = 0;
+};
+
+#if 0
+/* FIXME.  */
+
+class engine : public log_user
+{
+public:
+  engine (const supergraph &sg, const state_machine &sm,
+	  logger *logger, int verbosity)
+  : log_user (logger), m_sg (sg), m_sm (sm),
+    m_verbosity (verbosity),
+    m_num_traverse_calls (0),
+    m_on_stmt_calls (0),
+    m_num_path_copies (0),
+    m_num_event_copies (0)
+  {
+  }
+
+  const supergraph &m_sg;
+
+  // FIXME: this implies one traversal per state machine; should we instead
+  // do multiple state machines in one traversal?
+  const state_machine &m_sm;
+
+  int m_verbosity;
+
+  int m_num_traverse_calls;
+  int m_on_stmt_calls;
+  int m_num_path_copies;
+  int m_num_event_copies;
+};
+#endif
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+enum event_kind
+{
+  EK_DEBUG,
+  EK_STMT,
+  EK_FUNCTION_ENTRY,
+  EK_STATE_CHANGE,
+  EK_EDGE,
+  EK_WARNING
+};
+
+static const char *
+event_kind_to_string (enum event_kind ek)
+{
+  switch (ek)
+    {
+    default:
+      gcc_unreachable ();
+    case EK_DEBUG:
+      return "EK_DEBUG";
+    case EK_STMT:
+      return "EK_STMT";
+    case EK_FUNCTION_ENTRY:
+      return "EK_FUNCTION_ENTRY";
+    case EK_STATE_CHANGE:
+      return "EK_STATE_CHANGE";
+    case EK_EDGE:
+      return "EK_EDGE";
+    case EK_WARNING:
+      return "EK_WARNING";
+    }
+}
+
+/* FIXME.  */
+
+class checker4_event : public diagnostic_event
+{
+public:
+  checker4_event (enum event_kind kind,
+		  location_t loc, tree fndecl, int depth)
+    : m_kind (kind), m_loc (loc), m_fndecl (fndecl), m_depth (depth),
+      m_pending_diagnostic (NULL), m_emission_id ()
+  {
+  }
+
+  location_t get_location () const FINAL OVERRIDE { return m_loc; }
+  tree get_fndecl () const FINAL OVERRIDE { return m_fndecl; }
+  int get_stack_depth () const FINAL OVERRIDE { return m_depth; }
+
+  virtual checker4_event *clone () const = 0;
+
+  std::string to_str () const
+  {
+    std::string result ("\"");
+    label_text event_desc (get_desc ());
+    result += event_desc.m_buffer;
+    event_desc.maybe_free ();
+    result += "\" (depth ";
+    result += std::to_string (get_stack_depth ());
+    result += ", m_loc=";
+    result += std::to_string (get_location ());
+    result += ")";
+    return result;
+  }
+
+  void prepare_for_emission (pending_diagnostic *pd, event_id_t emission_id)
+  {
+    m_pending_diagnostic = pd;
+    m_emission_id = emission_id;
+
+    /* Call get_desc now, to ensure that any side-effects take place
+       before pending_diagnostic::emit is called.
+       In particular, state_change_event::get_desc can call
+       pending_diagnostic::describe_critical_state_change;
+       free_of_non_heap can use this to tweak the message.
+       (Would be neater to simply capture this within the state).  */
+    label_text desc = get_desc ();
+    desc.maybe_free ();
+  }
+
+  virtual bool is_call_p () const { return false; }
+  virtual bool is_function_entry_p () const  { return false; }
+  virtual bool is_return_p () const  { return false; }
+
+  enum event_kind m_kind;
+  location_t m_loc;
+  tree m_fndecl;
+  int m_depth;
+  pending_diagnostic *m_pending_diagnostic;
+  event_id_t m_emission_id; // only set once all pruning has occurred
+};
+
+class debug_event : public checker4_event
+{
+public:
+  debug_event (location_t loc, tree fndecl, int depth,
+	      const char *desc)
+  : checker4_event (EK_DEBUG, loc, fndecl, depth),
+    m_desc (xstrdup (desc))
+  {
+  }
+  ~debug_event ()
+  {
+    free (m_desc);
+  }
+
+  label_text get_desc () const FINAL OVERRIDE
+  {
+    return label_text::borrow (m_desc);
+  }
+
+  checker4_event *clone () const FINAL OVERRIDE
+  {
+    return new debug_event (m_loc, m_fndecl, m_depth, m_desc);
+  }
+
+private:
+  char *m_desc;
+};
+
+class statement_event : public checker4_event
+{
+public:
+  statement_event (const gimple *stmt, tree fndecl, int depth)
+  : checker4_event (EK_STMT, gimple_location (stmt), fndecl, depth),
+    m_stmt (stmt)
+  {
+  }
+
+  label_text get_desc () const FINAL OVERRIDE
+  {
+    pretty_printer pp;
+    pp_string (&pp, "stmt: ");
+    // FIXME: const_cast!
+    pp_gimple_stmt_1 (&pp, const_cast <gimple *> (m_stmt) , 0, (dump_flags_t)0);
+    return label_text::take (xstrdup (pp_formatted_text (&pp)));
+  }
+
+  checker4_event *clone () const FINAL OVERRIDE
+  {
+    return new statement_event (m_stmt, m_fndecl, m_depth);
+  }
+
+  const gimple *m_stmt;
+};
+
+class function_entry_event : public checker4_event
+{
+public:
+  function_entry_event (location_t loc, tree fndecl, int depth)
+  : checker4_event (EK_FUNCTION_ENTRY, loc, fndecl, depth)
+  {
+  }
+
+  label_text get_desc () const FINAL OVERRIDE
+  {
+    return label_text::take (print ("entry to %qE", m_fndecl));
+  }
+
+  checker4_event *clone () const FINAL OVERRIDE
+  {
+    return new function_entry_event (m_loc, m_fndecl, m_depth);
+  }
+
+  bool is_function_entry_p () const FINAL OVERRIDE { return true; }
+};
+
+class state_change_event : public checker4_event
+{
+public:
+  state_change_event (const supernode *node, const gimple *stmt,
+		      int stack_depth,
+		      const state_machine &sm,
+		      tree var,
+		      state_machine::state_t from,
+		      state_machine::state_t to,
+		      tree origin)
+  : checker4_event (EK_STATE_CHANGE,
+		   stmt->location, node->m_fun->decl,
+		   stack_depth),
+    m_node (node), m_stmt (stmt), m_sm (sm),
+    m_var (var), m_from (from), m_to (to),
+    m_origin (origin)
+  {
+  }
+
+  label_text get_desc () const FINAL OVERRIDE
+  {
+    if (m_origin)
+      return label_text::take
+	(print ("state of %qE: %qs -> %qs (origin: %qE)",
+		m_var,
+		m_sm.get_state_name (m_from),
+		m_sm.get_state_name (m_to),
+		m_origin));
+    else
+      {
+	/* If there is no origin, then presumably this
+	   state change came directly from a pattern match.
+	   Attempt to generate a nicer human-readable description.
+	   For greatest precision-of-wording, give the pending diagnostic
+	   a chance to describe this state change (in terms of the
+	   diagnostic).
+	   Note that we only have a pending_diagnostic set on the event once
+	   the diagnostic is about to being emitted, so the description for
+	   an event can change.  */
+	if (m_pending_diagnostic)
+	  {
+	    label_text custom_desc
+	      = m_pending_diagnostic->describe_critical_state_change
+	      (//diagnostic_context *ctxt,
+	       m_var, m_from, m_to, m_emission_id);
+	    if (custom_desc.m_buffer)
+	      {
+		if (flag_analyzer_verbose_state_changes)
+		  {
+		    /* Append debug version.  */
+		    label_text result = label_text::take
+		      (print ("%s (state of %qE: %qs -> %qs, origin: NULL)",
+			      custom_desc.m_buffer,
+			      m_var,
+			      m_sm.get_state_name (m_from),
+			      m_sm.get_state_name (m_to)));
+		    custom_desc.maybe_free ();
+		    return result;
+		  }
+		else
+		  return custom_desc;
+	      }
+	  }
+
+	/* Fallback description.  */
+	return label_text::take
+	  (print ("state of %qE: %qs -> %qs (origin: NULL)",
+		  m_var,
+		  m_sm.get_state_name (m_from),
+		  m_sm.get_state_name (m_to)));
+      }
+  }
+
+  checker4_event *clone () const FINAL OVERRIDE
+  {
+    return new state_change_event (m_node, m_stmt, m_depth,
+				   m_sm, m_var, m_from, m_to, m_origin);
+  }
+
+  const supernode *m_node;
+  const gimple *m_stmt;
+  const state_machine &m_sm;
+  tree m_var;
+  state_machine::state_t m_from;
+  state_machine::state_t m_to;
+  tree m_origin;
+};
+
+class edge_event : public checker4_event
+{
+public:
+  edge_event (const superedge *edge,
+	      location_t loc, tree fndecl, int depth)
+  : checker4_event (EK_EDGE,
+		   loc, fndecl, depth),
+    m_edge (edge)
+  {
+  }
+
+  label_text get_desc () const FINAL OVERRIDE
+  {
+    switch (m_edge->m_kind)
+      {
+      default:
+	gcc_unreachable ();
+      case SUPEREDGE_CFG_EDGE:
+	{
+	  char *edge_desc = m_edge->get_description ();
+	  if (strlen (edge_desc) > 0)
+	    {
+	      label_text result
+		= label_text::take (print ("taking %qs edge SN:%i -> SN:%i",
+					   edge_desc,
+					   m_edge->m_src->m_index,
+					   m_edge->m_dest->m_index));
+	      free (edge_desc);
+	      return result;
+	    }
+	  else
+	    {
+	      free (edge_desc);
+	      return label_text::take (print ("taking edge SN:%i -> SN:%i",
+					      m_edge->m_src->m_index,
+					      m_edge->m_dest->m_index));
+	    }
+	}
+      case SUPEREDGE_CALL:
+	{
+	  return label_text::take (print ("calling %qE from %qE",
+					  m_edge->m_dest->m_fun->decl,
+					  m_edge->m_src->m_fun->decl));
+	}
+
+      case SUPEREDGE_RETURN:
+	{
+	  return label_text::take (print ("returning to %qE from %qE",
+					  m_edge->m_dest->m_fun->decl,
+					  m_edge->m_src->m_fun->decl));
+	}
+
+      case SUPEREDGE_INTRAPROCEDURAL_CALL:
+	return label_text::take (// TODO: possibly a higher-level description?
+				 print ("taking edge SN:%i -> SN:%i",
+					m_edge->m_src->m_index,
+					m_edge->m_dest->m_index));
+      }
+  }
+
+  const cfg_superedge& get_cfg_superedge () const
+  {
+    gcc_assert (m_edge->m_kind == SUPEREDGE_CFG_EDGE);
+    return *m_edge->dyn_cast_cfg_superedge ();
+  }
+
+  const callgraph_superedge& get_callgraph_superedge () const
+  {
+    gcc_assert (m_edge->m_kind != SUPEREDGE_CFG_EDGE);
+    return *m_edge->dyn_cast_callgraph_superedge ();
+  }
+
+  checker4_event *clone () const FINAL OVERRIDE
+  {
+    return new edge_event (m_edge, m_loc, m_fndecl, m_depth);
+  }
+
+  bool is_call_p () const FINAL OVERRIDE
+  {
+    return m_edge->m_kind == SUPEREDGE_CALL;
+  }
+  bool is_return_p () const FINAL OVERRIDE
+  {
+    return m_edge->m_kind == SUPEREDGE_RETURN;
+  }
+
+  const superedge *m_edge;
+};
+
+/* FIXME: a repeat of the warning message at the end of the path.  */
+
+class warning_event : public checker4_event
+{
+public:
+  warning_event (location_t loc, tree fndecl, int depth,
+		 const char *msg)
+  : checker4_event (EK_WARNING, loc, fndecl, depth),
+    m_msg (xstrdup (msg))
+  {
+  }
+  ~warning_event () { free (m_msg); }
+
+  label_text get_desc () const FINAL OVERRIDE
+  {
+    return label_text::borrow (m_msg);
+  }
+
+  warning_event *clone () const FINAL OVERRIDE
+  {
+    return new warning_event (m_loc, m_fndecl, m_depth, m_msg);
+  }
+
+  char *m_msg;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+class checker4_path : public diagnostic_path
+{
+public:
+  checker4_path () : diagnostic_path () {}
+  checker4_path (const checker4_path &other);
+  checker4_path& operator= (const checker4_path &other);
+
+  std::string to_str () const;
+
+  void maybe_log (logger *logger, const char *desc) const
+  {
+    if (!logger)
+      return;
+    logger->log ("%s: %s", desc, to_str ().c_str ());
+    for (unsigned i = 0; i < m_events.length (); i++)
+      logger->log ("%s[%i]: %s",
+		   desc, i, m_events[i]->to_str ().c_str ());
+  }
+
+  unsigned num_events () const FINAL OVERRIDE
+  {
+    return m_events.length ();
+  }
+
+  const diagnostic_event & get_event (int idx) const FINAL OVERRIDE
+  {
+    return *m_events[idx];
+  }
+
+  void add_event (checker4_event *event)
+  {
+    m_events.safe_push (event);
+  }
+
+  void delete_event (int idx)
+  {
+    checker4_event *event = m_events[idx];
+    m_events.ordered_remove (idx);
+    delete event;
+  }
+
+#if 0
+  /* Get the SUPEREDGE_RETURN corresponding to the most recent
+     unreturned SUPEREDGE_CALL.  Return NULL if there isn't one.  */
+  const superedge *get_next_return_edge () const
+  {
+    if (m_active_calls.length () == 0)
+      return NULL;
+    cgraph_edge *cedge = m_active_calls[m_active_calls.length () - 1];
+    return get_sg ().get_edge_for_return (cedge);
+  }
+
+  void add_link (const superedge *edge, const supernode *node);
+
+  int get_stack_depth () const { return m_ps.m_stack_depth; }
+
+  state_machine::state_t get_baseline_state (tree expr);
+  void set_state (tree expr,
+		  state_machine::state_t baseline_state,
+		  state_machine::state_t new_state);
+
+  equiv_class &get_ec (tree expr)
+  {
+    path_var pv (expr, get_stack_depth ());
+    equiv_class_id ec_id = m_ps.get_or_add_equiv_class (pv);
+    return ec_id.get_obj (m_ps);
+  }
+
+  const supergraph &get_sg () const;
+  const state_machine &get_sm () const;
+
+  int get_verbosity () const;
+#endif
+
+  void add_final_event (const state_machine &sm,
+			const exploded_node *enode, const gimple *stmt,
+			tree var, state_machine::state_t state,
+			pending_diagnostic *pd)
+  {
+    char *msg = NULL;
+    label_text ev_desc = pd->describe_final_event (var, state);
+    if (ev_desc.m_buffer)
+      {
+	if (flag_analyzer_verbose_state_changes)
+	  {
+	    msg = print ("%s (%qE is in state %qs)", ev_desc.m_buffer,
+			 var, sm.get_state_name (state));
+	    ev_desc.maybe_free ();
+	  }
+	else
+	  msg = ev_desc.take_or_copy ();
+      }
+    else
+      {
+	msg = print ("here (%qE is in state %qs)", var,
+		     sm.get_state_name (state));
+	// FIXME: ideally would show the full message here, too?
+      }
+    checker4_event *end_of_path
+      = new warning_event (stmt->location,
+			   enode->get_function ()->decl,
+			   enode->get_stack_depth (),
+			   msg);
+    add_event (end_of_path);
+  }
+
+  void prepare_for_emission (pending_diagnostic *pd)
+  {
+    checker4_event *e;
+    int i;
+    FOR_EACH_VEC_ELT (m_events, i, e)
+      e->prepare_for_emission (pd, event_id_t (i));
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+
+  /* The events that have occurred along this path.  */
+  auto_delete_vec<checker4_event> m_events;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class call_string.  */
+
+/* FIXME.  */
+
+call_string::call_string (const call_string &other)
+{
+  const return_superedge *e;
+  int i;
+  FOR_EACH_VEC_ELT (other.m_return_edges, i, e)
+    m_return_edges.safe_push (e);
+}
+
+/* FIXME.  */
+
+call_string&
+call_string::operator= (const call_string &other)
+{
+  // would be much simpler if we could rely on vec<> assignment op
+  m_return_edges.truncate (0);
+  const return_superedge *e;
+  int i;
+  FOR_EACH_VEC_ELT (other.m_return_edges, i, e)
+    m_return_edges.safe_push (e);
+  return *this;
+}
+
+/* FIXME.  */
+
+bool
+call_string::operator== (const call_string &other) const
+{
+  if (m_return_edges.length () != other.m_return_edges.length ())
+    return false;
+  const return_superedge *e;
+  int i;
+  FOR_EACH_VEC_ELT (m_return_edges, i, e)
+    if (e != other.m_return_edges[i])
+      return false;
+  return true;
+}
+
+/* FIXME.  */
+
+void
+call_string::print (pretty_printer *pp) const
+{
+  pp_string (pp, "[");
+
+  const return_superedge *e;
+  int i;
+  FOR_EACH_VEC_ELT (m_return_edges, i, e)
+    {
+      if (i > 0)
+	pp_string (pp, ", ");
+      pp_printf (pp, "(SN: %i -> SN: %i in %s)",
+		 e->m_src->m_index, e->m_dest->m_index,
+		 function_name (e->m_dest->m_fun));
+    }
+
+  pp_string (pp, "]");
+}
+
+/* FIXME.  */
+
+std::string
+call_string::to_str () const
+{
+  pretty_printer pp;
+  print (&pp);
+  return std::string (pp_formatted_text (&pp));
+}
+
+/* FIXME.  */
+
+hashval_t
+call_string::hash () const
+{
+  inchash::hash hstate;
+  int i;
+  const return_superedge *e;
+  FOR_EACH_VEC_ELT (m_return_edges, i, e)
+    hstate.add_ptr (e);
+  return hstate.end ();
+}
+
+/* FIXME.  */
+
+void
+call_string::push_call (const supergraph &sg,
+			const call_superedge *call_sedge)
+{
+  gcc_assert (call_sedge);
+  const return_superedge *return_sedge = call_sedge->get_edge_for_return (sg);
+  gcc_assert (return_sedge);
+  m_return_edges.safe_push (return_sedge);
+}
+
+/* Count the number of times the top-most call site appears in the
+   stack.  */
+
+int
+call_string::calc_recursion_depth () const
+{
+  if (m_return_edges.is_empty ())
+    return 0;
+  const return_superedge *top_return_sedge
+    = m_return_edges[m_return_edges.length () - 1];
+
+  int result = 0;
+  const return_superedge *e;
+  int i;
+  FOR_EACH_VEC_ELT (m_return_edges, i, e)
+    if (e == top_return_sedge)
+      ++result;
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+void
+program_point::print (pretty_printer *pp) const
+{
+  pp_string (pp, "callstring: ");
+  m_call_string.print (pp);
+  pp_newline (pp);
+
+  switch (m_kind)
+    {
+    default:
+      gcc_unreachable ();
+
+    case PK_ORIGIN:
+      pp_printf (pp, "origin");
+      pp_newline (pp);
+      break;
+
+    case PK_BEFORE_SUPERNODE:
+      {
+	if (m_from_edge)
+	  pp_printf (pp, "before SN: %i (from SN: %i)",
+		     m_supernode->m_index, m_from_edge->m_src->m_index);
+	else
+	  pp_printf (pp, "before SN: %i (NULL from-edge)",
+		     m_supernode->m_index);
+	pp_newline (pp);
+	for (gphi_iterator gpi
+	       = const_cast<supernode *>(get_supernode ())->start_phis ();
+	     !gsi_end_p (gpi); gsi_next (&gpi))
+	  {
+	    gphi *phi = gpi.phi ();
+	    pp_gimple_stmt_1 (pp, const_cast <gphi *> (phi),
+			      0, (dump_flags_t)0);
+	    pp_newline (pp);
+	  }
+      }
+      break;
+
+    case PK_BEFORE_STMT:
+      pp_printf (pp, "before (SN: %i stmt: %i): ", m_supernode->m_index,
+		 m_stmt_idx);
+      pp_newline (pp);
+      pp_gimple_stmt_1 (pp, const_cast <gimple *> (get_stmt ()),
+			0, (dump_flags_t)0);
+      pp_newline (pp);
+      print_source_line (pp);
+      break;
+
+    case PK_AFTER_STMT:
+      pp_printf (pp, "after (SN: %i stmt: %i): ", m_supernode->m_index,
+		 m_stmt_idx);
+      pp_newline (pp);
+      pp_gimple_stmt_1 (pp, const_cast <gimple *> (get_stmt ()),
+			0, (dump_flags_t)0);
+      pp_newline (pp);
+      print_source_line (pp);
+      break;
+
+    case PK_AFTER_SUPERNODE:
+      pp_printf (pp, "after SN: %i", m_supernode->m_index);
+      pp_newline (pp);
+      break;
+    }
+}
+
+/* A subclass of diagnostic_context for use by
+   program_point::print_source_line.  */
+
+class debug_diagnostic_context : public diagnostic_context
+{
+public:
+  debug_diagnostic_context ()
+  {
+    diagnostic_initialize (this, 0);
+    show_line_numbers_p = true;
+    show_caret = true;
+  }
+};
+
+/* FIXME.  */
+
+void
+program_point::print_source_line (pretty_printer *pp) const
+{
+  const gimple *stmt = get_stmt ();
+  if (!stmt)
+    return;
+  // TODO: monospace font
+  debug_diagnostic_context tmp_dc;
+  gcc_rich_location richloc (stmt->location);
+  diagnostic_show_locus (&tmp_dc, &richloc, DK_ERROR);
+  pp_string (pp, pp_formatted_text (tmp_dc.printer));
+}
+
+/* FIXME.  */
+
+std::string
+program_point::to_str () const
+{
+  pretty_printer pp;
+  print (&pp);
+  return std::string (pp_formatted_text (&pp));
+}
+
+/* FIXME.  */
+
+hashval_t
+program_point::hash () const
+{
+  inchash::hash hstate;
+  if (m_supernode)
+    hstate.add_int (m_supernode->m_index);
+  hstate.add_ptr (m_from_edge);
+  hstate.add_int (m_stmt_idx);
+  hstate.add_int (m_kind);
+  hstate.merge_hash (m_call_string.hash ());
+  return hstate.end ();
+}
+
+/* FIXME.  */
+
+const gimple *
+program_point::get_stmt () const
+{
+  if (m_kind == PK_BEFORE_STMT || m_kind == PK_AFTER_STMT)
+    return m_supernode->m_stmts[m_stmt_idx];
+  else
+    return NULL;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+sm_state_map *
+sm_state_map::clone () const
+{
+  return new sm_state_map (*this);
+}
+
+/* FIXME.  */
+
+void
+sm_state_map::print (const state_machine &sm, pretty_printer *pp) const
+{
+  pp_string (pp, "{");
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      if (iter != m_map.begin ())
+	pp_string (pp, ", ");
+      entry_t e = (*iter).second;
+      pp_printf (pp, "%s: %s (origin: %s)",
+		 (*iter).first.to_str ().c_str (),
+		 sm.get_state_name (e.m_state),
+		 e.m_origin.to_str ().c_str ());
+    }
+
+  // TODO
+  pp_string (pp, "}");
+}
+
+/* FIXME.  */
+
+std::string
+sm_state_map::to_str (const state_machine &sm) const
+{
+  pretty_printer pp;
+  print (sm, &pp);
+  return std::string (pp_formatted_text (&pp));
+}
+
+/* FIXME.  */
+
+bool
+sm_state_map::is_empty_p () const
+{
+  return m_map.elements () == 0;
+}
+
+/* FIXME.  */
+
+hashval_t
+sm_state_map::hash () const
+{
+  inchash::hash hstate;
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      inchash::add_path_var ((*iter).first, hstate);
+      entry_t e = (*iter).second;
+      hstate.add_int (e.m_state);
+      inchash::add_path_var (e.m_origin, hstate);
+    }
+
+  return hstate.end ();
+}
+
+/* FIXME.  */
+
+bool
+sm_state_map::operator== (const sm_state_map &other) const
+{
+  if (m_map.elements () != other.m_map.elements ())
+    return false;
+
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      path_var pv = (*iter).first;
+      entry_t e = (*iter).second;
+      entry_t *other_slot = const_cast <map_t &> (other.m_map).get (pv);
+      if (other_slot == NULL)
+	return false;
+      if (e != *other_slot)
+	return false;
+    }
+  return true;
+}
+
+/* FIXME.  */
+
+state_machine::state_t
+sm_state_map::get_state (path_var pv) const
+{
+  if (entry_t *slot
+      = const_cast <map_t &> (m_map).get (pv))
+    return slot->m_state;
+  else
+    return 0;
+}
+
+/* FIXME.  */
+
+path_var
+sm_state_map::get_origin (path_var pv) const
+{
+  entry_t *slot
+    = const_cast <map_t &> (m_map).get (pv);
+  gcc_assert (slot);
+  return slot->m_origin;
+}
+
+/* FIXME.  */
+
+void
+sm_state_map::set_state (path_state *ps,
+			 path_var pv,
+			 state_machine::state_t state,
+			 path_var origin)
+{
+  equiv_class &ec = ps->get_equiv_class (pv);
+  set_state (ec, state, origin);
+}
+
+/* FIXME.  */
+
+void
+sm_state_map::set_state (const equiv_class &ec, state_machine::state_t state,
+			 path_var origin)
+{
+  int i;
+  path_var *pv;
+  FOR_EACH_VEC_ELT (ec.m_vars, i, pv)
+    impl_set_state (*pv, state, origin);
+}
+
+/* Set state of PV to STATE, bypassing equivalence classes.  */
+
+void
+sm_state_map::impl_set_state (path_var pv, state_machine::state_t state,
+			      path_var origin)
+{
+  /* Special-case state 0 as the default value.  */
+  if (state == 0)
+    {
+      if (m_map.get (pv))
+	m_map.remove (pv);
+      return;
+    }
+  m_map.put (pv, entry_t (state, origin));
+}
+
+/* FIXME.  */
+
+void
+sm_state_map::purge_stack_frame (int callee_stack_depth)
+{
+  /* FIXME: not sure how to safely remove during a traversal, so for now
+     do it in two stages.  */
+  auto_vec <path_var> keys;
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    keys.safe_push ((*iter).first);
+
+  int i;
+  path_var *pv;
+  FOR_EACH_VEC_ELT (keys, i, pv)
+    if (pv->m_stack_depth >= callee_stack_depth)
+      m_map.remove (*pv);
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+class impl_sm_context : public sm_context, public log_user
+{
+public:
+  impl_sm_context (const exploded_graph &eg,
+		   const state_machine &sm,
+		   const exploded_node *node,
+
+		   /* FIXME: should we be getting the ECs from the
+		      old state, rather than the new?  */
+		   program_state *new_state,
+
+		   const sm_state_map &old_smap,
+		   sm_state_map *new_smap,
+
+		   stmt_finder *stmt_finder = NULL)
+  : log_user (eg.get_logger ()), m_eg (eg), m_sm (sm), m_node (node),
+    m_new_state (new_state),
+    m_old_smap (old_smap), m_new_smap (new_smap),
+    m_stmt_finder (stmt_finder)
+  {
+  }
+
+  void on_transition (const supernode *node  ATTRIBUTE_UNUSED,
+		      const gimple *stmt  ATTRIBUTE_UNUSED,
+		      tree var,
+		      state_machine::state_t from,
+		      state_machine::state_t to,
+		      tree origin) FINAL OVERRIDE
+  {
+    JIT_LOG_FUNC (get_logger ());
+    int stack_depth = m_node->get_stack_depth ();
+    path_var pv (var, stack_depth);
+    state_machine::state_t current = m_old_smap.get_state (pv);
+    if (current == from)
+      {
+	if (get_logger ())
+	  log ("%s: state transition of %qE: %s -> %s",
+	       m_sm.get_name (),
+	       var,
+	       m_sm.get_state_name (from),
+	       m_sm.get_state_name (to));
+	m_new_smap->set_state (&m_new_state->m_path_state, pv, to,
+			       path_var (origin, stack_depth));
+      }
+  }
+
+  void warn_for_state (const supernode *node, const gimple *stmt,
+		       tree var, state_machine::state_t state,
+		       pending_diagnostic *d) FINAL OVERRIDE
+  {
+    JIT_LOG_FUNC (get_logger ());
+    int stack_depth = m_node->get_stack_depth ();
+    gcc_assert (d); // take ownership
+    path_var pv (var, stack_depth);
+    state_machine::state_t current = m_old_smap.get_state (pv);
+    if (state == current)
+      {
+	m_eg.emit_diagnostic (m_sm, m_node, node, stmt, m_stmt_finder,
+			      var, state, d);
+      }
+    delete d;
+  }
+
+  const exploded_graph &m_eg;
+  const state_machine &m_sm;
+  const exploded_node *m_node;
+  program_state *m_new_state;
+  const sm_state_map &m_old_smap;
+  sm_state_map *m_new_smap;
+  impl_4::stmt_finder *m_stmt_finder;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+program_state::program_state (const extrinsic_state &ext_state)
+: m_path_state ()
+{
+  int num_states = ext_state.m_checkers.length ();
+  for (int i = 0; i < num_states; i++)
+    m_checker_states.safe_push (new sm_state_map ());
+}
+
+/* FIXME.  */
+
+program_state::program_state (int num_states)
+: m_path_state ()
+{
+  for (int i = 0; i < num_states; i++)
+    m_checker_states.safe_push (new sm_state_map ());
+}
+
+/* FIXME.  */
+
+program_state::program_state (const program_state &other)
+: m_path_state (other.m_path_state)
+{
+  int i;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (other.m_checker_states, i, smap)
+    m_checker_states.safe_push (smap->clone ());
+}
+
+/* FIXME.  */
+
+program_state&
+program_state::operator= (const program_state &other)
+{
+  m_path_state = other.m_path_state;
+
+  // TODO: eliminate existing things
+  gcc_assert (m_checker_states.length () == 0);
+
+  int i;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (other.m_checker_states, i, smap)
+    m_checker_states.safe_push (smap->clone ());
+
+  return *this;
+}
+
+/* FIXME.  */
+
+hashval_t
+program_state::hash () const
+{
+  hashval_t result = m_path_state.hash ();
+  int i;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (m_checker_states, i, smap)
+    result ^= smap->hash ();
+  return result;
+}
+
+/* FIXME.  */
+
+bool
+program_state::operator== (const program_state &other) const
+{
+  if (!(m_path_state == other.m_path_state))
+    return false;
+  int i;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (m_checker_states, i, smap)
+    if (!(*smap == *other.m_checker_states[i]))
+      return false;
+  return true;
+}
+
+/* FIXME.  */
+
+void
+program_state::print (const extrinsic_state &ext_state,
+		      pretty_printer *pp) const
+{
+  pp_printf (pp, "ps: %s", m_path_state.to_str ().c_str ());
+  pp_newline (pp);
+
+  int i;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (m_checker_states, i, smap)
+    {
+      if (!smap->is_empty_p ())
+	{
+	  pp_printf (pp, "%s: ", ext_state.get_name (i));
+	  smap->print (ext_state.get_sm (i), pp);
+	  pp_newline (pp);
+	}
+    }
+}
+
+/* FIXME.  */
+
+std::string
+program_state::to_str (const extrinsic_state &ext_state) const
+{
+  pretty_printer pp;
+  print (ext_state, &pp);
+  return std::string (pp_formatted_text (&pp));
+}
+
+/* FIXME.
+   Generate a simpler version of THIS, discarding state that's no longer
+   relevant at POINT.
+   The idea is that we're more likely to be able to consolidate
+   multiple (point, state) into single exploded_nodes if we discard
+   irrelevant state (e.g. at the end of functions).  */
+
+program_state
+program_state::prune_for_point (const exploded_graph &eg,
+				const program_point &point) const
+{
+  /* FIXME: for now a hack: purge all states at exit nodes (which won't work
+     interprocedurally).  */
+#if 0
+  if (const supernode *supernode = point.get_supernode ())
+    if (supernode->return_p ())
+      return program_state (eg.m_ext_state);
+#endif
+
+  // FIXME: what about leak detection, though?
+
+#if 0
+  {
+    function *fun = point.get_function ();
+    if (fun)
+      {
+	auto_cfun sentinel (fun);
+	debug_immediate_uses ();
+	/* FIXME: maybe do the pruning here: we only know what state will be
+	   important when in conjunction with a program_point.  */
+      }
+  }
+#endif
+
+
+  return *this;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+void
+exploded_node::dump_dot (graphviz_out *gv) const
+{
+  pretty_printer *pp = gv->m_pp;
+
+  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);
+
+  pp_printf (pp, "EN: %i", m_index);
+  pp_newline (pp);
+
+  m_ps.m_point.print (pp);
+  pp_newline (pp);
+
+  m_ps.m_state.print (m_ext_state, pp);
+  pp_newline (pp);
+
+  pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
+
+  //TODO: dump_bb_for_graph (pp, bb);
+#if 0
+  if (m_returning_call)
+    {
+      pp_string (pp, "returning call: ");
+      pp_gimple_stmt_1 (pp, m_returning_call, 0, (dump_flags_t)0);
+      if (a)
+	a->add_stmt_annotations (pp, m_returning_call);
+      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);
+      if (a)
+	a->add_stmt_annotations (pp, stmt);
+      pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
+      pp_newline (pp);
+    }
+#endif
+
+  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
+  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 ("}");
+    }
+#endif
+
+}
+
+/* FIXME.  */
+
+void
+exploded_node::dump_dot_id (pretty_printer *pp) const
+{
+  pp_printf (pp, "exploded_node_%i", m_index);
+}
+
+/* FIXME.  */
+
+program_state
+exploded_node::on_stmt (const exploded_graph &eg) const
+{
+  const program_point &point = m_ps.m_point;
+  const program_state &old_state = m_ps.m_state;
+  //const path_state &old_ps = old_state.m_path_state;
+
+  program_state new_state (old_state);
+  path_state *new_ps = &new_state.m_path_state;
+
+  const gimple *stmt = point.get_stmt ();
+  int stack_depth = point.get_stack_depth ();
+
+  int i;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (old_state.m_checker_states, i, smap)
+    {
+      const state_machine &sm = m_ext_state.get_sm (i);
+      impl_sm_context ctxt (eg, sm, this,
+			    &new_state,
+			    *old_state.m_checker_states[i],
+			    new_state.m_checker_states[i]);
+      sm.on_stmt (&ctxt, point.m_supernode, stmt);
+    }
+
+  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:
+	case SSA_NAME:
+	case INTEGER_CST:
+	  {
+	    /* Direct assignment of the form LHS = RHS;  */
+	    on_assignment (eg,
+			   new_ps->make_path_var (lhs),
+			   new_ps->make_path_var (rhs1),
+			   old_state,
+			   &new_state);
+	  }
+	  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);
+	    equiv_class_id rhs1_ec_id
+	      = new_ps->get_or_add_equiv_class (path_var (rhs1, stack_depth));
+	    equiv_class_id rhs2_ec_id
+	      = new_ps->get_or_add_equiv_class (path_var (rhs2, stack_depth));
+	    tristate t = new_ps->eval_condition (rhs1_ec_id, op, rhs2_ec_id);
+	    if (eg.get_logger ())
+	      eg.log ("eval condition: %s", t.as_string ());
+	    if (t.is_known ())
+	      new_ps->add_constraint (path_var (lhs, stack_depth),
+				      t.is_true () ? NE_EXPR : EQ_EXPR,
+				      path_var (integer_zero_node,
+						stack_depth));
+	  }
+	  break;
+
+	case COMPONENT_REF:
+	  {
+	    /* Propagate any state from RHS's operand 0 to LHS
+	       (e.g. for use by the taint checker).  */
+	    copy_sm_state (eg,
+			   new_ps->make_path_var (lhs),
+			   new_ps->make_path_var (TREE_OPERAND (rhs1, 0)),
+			   old_state,
+			   &new_state);
+	  }
+	  break;
+	}
+    }
+
+  if (const greturn *return_ = dyn_cast <const greturn *> (stmt))
+    {
+      tree callee = point.get_function ()->decl;
+      tree lhs = DECL_RESULT (callee);
+      tree rhs = gimple_return_retval (return_);
+      if (rhs)
+	on_assignment (eg,
+		       new_ps->make_path_var (lhs),
+		       new_ps->make_path_var (rhs),
+		       old_state,
+		       &new_state);
+    }
+
+  if (const gcall *call = dyn_cast <const gcall *> (stmt))
+    {
+      /* Debugging/test support.  */
+      if (is_named_call_p (call, "__dump_constraints", 0))
+	inform (stmt->location, "__dump_constraints: %s",
+		new_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))
+	{
+	  tree t_arg = gimple_call_arg (call, 0);
+	  tristate t
+	    = new_ps->dump_condition (t_arg, NE_EXPR, integer_zero_node);
+	  warning_at (call->location, 0, "%s", t.as_string ());
+	}
+    }
+
+
+  return new_state;
+}
+
+void
+exploded_node::on_assignment (const exploded_graph &eg,
+			      path_var lhs,
+			      path_var rhs,
+			      const program_state &old_state,
+			      program_state *new_state) const
+{
+  JIT_LOG_FUNC (eg.get_logger ());
+  if (eg.get_logger ())
+    {
+      eg.log ("lhs: %s", lhs.to_str ().c_str ());
+      eg.log ("rhs: %s", rhs.to_str ().c_str ());
+    }
+
+  new_state->m_path_state.on_assignment (lhs, rhs);
+
+  copy_sm_state (eg, lhs, rhs, old_state, new_state);
+}
+
+/* Copy SM state (if any) from RHS to LHS.  */
+
+void
+exploded_node::copy_sm_state (const exploded_graph &eg,
+			      path_var lhs,
+			      path_var rhs,
+			      const program_state &old_state,
+			      program_state *new_state) const
+{
+  /* Don't attempt to copy SM state from constants, since otherwise
+     for "ptr = NULL;" it would overwrite the SM-matched "null" state
+     for "ptr" with the implicit "start" state of the NULL constant.  */
+  if (CONSTANT_CLASS_P (rhs.m_tree))
+    return;
+
+  const extrinsic_state &ext_state = eg.get_ext_state ();
+  gcc_assert (old_state.m_checker_states.length ()
+	      == ext_state.m_checkers.length ());
+  gcc_assert (new_state->m_checker_states.length ()
+	      == ext_state.m_checkers.length ());
+  for (unsigned i = 0; i < ext_state.m_checkers.length (); i++)
+    {
+      const state_machine &sm = ext_state.get_sm (i);
+      const sm_state_map *src_smap = old_state.m_checker_states[i];
+      sm_state_map *dst_smap = new_state->m_checker_states[i];
+      state_machine::state_t rhs_state = src_smap->get_state (rhs);
+      dst_smap->set_state (&new_state->m_path_state, lhs, rhs_state, rhs);
+      if (eg.get_logger ())
+	eg.log ("%s: copying state %qs to %s from %s",
+		sm.get_name (),
+		sm.get_state_name (rhs_state),
+		lhs.to_str ().c_str (),
+		rhs.to_str ().c_str ());
+    }
+}
+
+/* FIXME.
+   Consider the effect of following superedge SUCC from this node.
+
+   Return true if it's feasible to follow the edge, or false
+   if it's infeasible.
+
+   Examples: if it's the "true" branch within
+   a CFG and we know the conditional is false, we know it's infeasible.
+   If it's one of multiple interprocedual "return" edge, then only
+   the edge back to the most recent callsite is feasible.
+
+   Update NEXT_STATE accordingly (e.g. to record that a condition was
+   true or false, or that the NULL-ness of a pointer has been checked).
+
+   Update NEXT_POINT accordingly (updating the call string).  */
+
+bool
+exploded_node::on_edge (const exploded_graph &eg,
+			const superedge *succ,
+			program_point *next_point,
+			program_state *next_state) const
+{
+  JIT_LOG_FUNC (eg.get_logger ());
+
+  if (!next_point->on_edge (eg, succ))
+    return false;
+
+  if (!next_state->on_edge (eg, *this, succ))
+    return false;
+
+  return true;
+}
+
+/* A measurement of how good EXPR is for presenting to the user, so
+   that e.g. we can say prefer printing
+     "leak of 'tmp.m_ptr'"
+   over:
+     leak of '<unknown>'.  */
+
+static int
+readability (tree expr)
+{
+  switch (TREE_CODE (expr))
+    {
+    case COMPONENT_REF:
+    case MEM_REF:
+      return readability (TREE_OPERAND (expr, 0)) - 1;
+
+    case SSA_NAME:
+      {
+	if (tree var = SSA_NAME_VAR (expr))
+	  return readability (var);
+	return -1;
+      }
+      break;
+
+    case VAR_DECL:
+      return 256;
+
+    default:
+      return 0;
+    }
+
+  return 0;
+}
+
+/* A qsort comparator for path_var for use by exploded_node::detect_leaks,
+   to sort them into most user-readable to least user-readable.  */
+
+static int
+leak_comparator (const void *p1, const void *p2)
+{
+  const path_var *vs1 = (const path_var *)p1;
+  const path_var *vs2 = (const path_var *)p2;
+
+  int r1 = readability (vs1->m_tree);
+  int r2 = readability (vs2->m_tree);
+
+  return r2 - r1;
+}
+
+/* Subclass of stmt_finder for finding the best stmt to report the leak at,
+   given the emission path.  */
+
+class leak_stmt_finder : public stmt_finder
+{
+public:
+  leak_stmt_finder (const exploded_graph &eg, path_var pv)
+  : m_eg (eg), m_pv (pv) {}
+
+  const gimple *find_stmt (const exploded_path &epath)
+    FINAL OVERRIDE
+  {
+    JIT_LOG_FUNC (m_eg.get_logger ());
+
+    tree var = m_pv.m_tree;
+    if (TREE_CODE (var) == SSA_NAME)
+      {
+	/* Locate the final write to this SSA name in the path.  */
+	const gimple *def_stmt = SSA_NAME_DEF_STMT (var);
+
+	int idx_of_def_stmt;
+	bool found = epath.find_stmt_backwards (def_stmt, &idx_of_def_stmt);
+	gcc_assert (found);
+
+	/* What was the next write to the underlying var
+	   after the SSA name was set? (if any).  */
+
+	for (int idx = idx_of_def_stmt + 1;
+	     idx < epath.m_edges.length ();
+	     ++idx)
+	  {
+	    const exploded_edge *eedge = epath.m_edges[idx];
+	    if (m_eg.get_logger ())
+	      m_eg.log ("eedge[%i]: EN %i -> EN %i",
+			idx,
+			eedge->m_src->m_index,
+			eedge->m_dest->m_index);
+	    const exploded_node *dst_node = eedge->m_dest;
+	    const program_point &dst_point = dst_node->get_point ();
+	    const gimple *stmt = dst_point.get_stmt ();
+	    if (!stmt)
+	      continue;
+	    if (const gassign *assign = dyn_cast <const gassign *> (stmt))
+	      {
+		tree lhs = gimple_assign_lhs (assign);
+		if (TREE_CODE (lhs) == SSA_NAME
+		    && SSA_NAME_VAR (lhs) == SSA_NAME_VAR (var))
+		  return assign;
+	      }
+	  }
+      }
+
+    /* Look backwards for the first statement with a location.  */
+    int i;
+    const exploded_edge *eedge;
+    FOR_EACH_VEC_ELT_REVERSE (epath.m_edges, i, eedge)
+      {
+	if (m_eg.get_logger ())
+	  m_eg.log ("eedge[%i]: EN %i -> EN %i",
+		    i,
+		    eedge->m_src->m_index,
+		    eedge->m_dest->m_index);
+	const exploded_node *dst_node = eedge->m_dest;
+	const program_point &dst_point = dst_node->get_point ();
+	const gimple *stmt = dst_point.get_stmt ();
+	if (stmt)
+	  if (stmt->location != UNKNOWN_LOCATION)
+	    return stmt;
+      }
+
+    gcc_unreachable ();
+    return NULL;
+  }
+
+private:
+  const exploded_graph &m_eg;
+  path_var m_pv;
+};
+
+
+/* Called when finding the successors of the supernode for a function exit
+   basic block.
+
+   Detect equivalence classes that are about to go out of scope, and call
+   on_leak on any that have non-default state (on the most readable
+   path_var within each equivalence class), so that the state machine can
+   potentially issue a warning.
+
+   In theory we'd do this on the return superedge, but there isn't one
+   for top-level functions.  */
+
+void
+exploded_node::detect_leaks (const exploded_graph &eg) const
+{
+  JIT_LOG_FUNC_1 (eg.get_logger (), "EN: %i", m_index);
+
+  const program_state &old_state = get_state ();
+
+  /* Work with a temporary copy of the state.  */
+  program_state new_state (old_state);
+  path_state *new_ps = &new_state.m_path_state;
+
+  const supernode *snode = get_supernode ();
+  const int stack_depth = get_stack_depth ();
+
+  /* Mark EC of return value (if any) as escaping.  */
+  {
+    tree result = DECL_RESULT (snode->m_fun->decl);
+    if (result)
+      {
+	if (eg.get_logger ())
+	  eg.log ("new_ps before escapes: %s", new_ps->to_str ().c_str ());
+	equiv_class_id ec_id
+	  = new_ps->get_or_add_equiv_class (path_var (result, stack_depth));
+	new_ps->recursively_mark_as_escaping (ec_id);
+	if (eg.get_logger ())
+	  eg.log ("new_ps after escapes: %s", new_ps->to_str ().c_str ());
+      }
+  }
+
+  int i;
+  equiv_class *ec;
+  FOR_EACH_VEC_ELT (new_ps->m_equiv_classes, i, ec)
+    {
+      if (eg.get_logger ())
+	eg.log ("considering %s", ec->to_str ().c_str ());
+
+      if (ec->m_escaped)
+	{
+	  eg.log ("no leak: ec escaped");
+	  continue;
+	}
+
+      /* If anything in this EC is still alive in a parent stack frame,
+	 then this isn't a leak.  */
+      if (ec->get_min_stack_depth () < stack_depth)
+	{
+	  eg.log ("no leak: ec member alive within ancestor stack frame");
+	  continue;
+	}
+
+      /* Sort the leaks into most human-readable first, through
+	 to least user-readable.  Given that we only emit one
+	 leak per EC, this ought to ensure that we pick the most
+	 user-readable description of each leaking EC.
+	 This assumes that all vars in the EC have the same state.  */
+      ec->m_vars.qsort (leak_comparator);
+
+      if (ec->m_vars.length () < 1)
+	continue;
+
+      path_var best_pv (ec->m_vars[0]);
+      if (eg.get_logger ())
+	eg.log ("best_pv: %s", best_pv.to_str ().c_str ());
+
+      /* Determining the best statement for the leak needs to happen
+	 after we have an emission path, so pass in a stmt_finder.  */
+      leak_stmt_finder stmt_finder (eg, best_pv);
+
+      int i;
+      sm_state_map *smap;
+      FOR_EACH_VEC_ELT (old_state.m_checker_states, i, smap)
+	{
+	  const state_machine &sm = m_ext_state.get_sm (i);
+	  impl_sm_context ctxt (eg, sm, this,
+				&new_state,
+				*old_state.m_checker_states[i],
+				new_state.m_checker_states[i],
+				&stmt_finder);
+	  sm.on_leak (&ctxt, snode, NULL, best_pv.m_tree,
+		      smap->get_state (best_pv));
+	}
+    }
+}
+
+// TODO: move this fn
+/* Update the call string based on interprocedural
+   edges, ensuring that we have interprocedurally valid paths
+   in the exploded graph.  */
+
+bool
+program_point::on_edge (const exploded_graph &eg,
+			const superedge *succ)
+{
+  JIT_LOG_FUNC (eg.get_logger ());
+  switch (succ->m_kind)
+    {
+    case SUPEREDGE_CFG_EDGE:
+      break;
+
+    case SUPEREDGE_CALL:
+      {
+	/* Add the callsite to the call string.  */
+	// FIXME: this should be an as_a<>
+	m_call_string.push_call (eg.get_supergraph (),
+				 (const call_superedge *)succ);
+
+	/* Impose a maximum recursion depth and don't analyze paths
+	   that exceed it further.
+	   This is something of a blunt workaround, but it only
+	   applies to recursion (and mutual recursion), not to
+	   general call stacks.  */
+	// FIXME: make this a param:
+	const int MAX_RECURSION_DEPTH = 2;
+	if (m_call_string.calc_recursion_depth () > MAX_RECURSION_DEPTH)
+	  {
+	    eg.log ("rejecting call edge: recursion limit exceeded");
+	    // TODO: issue a sorry for this?
+	    return false;
+	  }
+      }
+      break;
+
+    case SUPEREDGE_RETURN:
+      {
+	/* Require that we return to the call site in the call string.  */
+	if (m_call_string.empty_p ())
+	  {
+	    eg.log ("rejecting return edge: empty call string");
+	    return false;
+	  }
+	const return_superedge *top_of_stack = m_call_string.pop ();
+	if (top_of_stack != succ)
+	  {
+	    eg.log ("rejecting return edge: return to wrong callsite");
+	    return false;
+	  }
+      }
+      break;
+
+    case SUPEREDGE_INTRAPROCEDURAL_CALL:
+      /* We ignore these edges.  FIXME: why have them?  */
+      eg.log ("rejecting interprocedural edge");
+      return false;
+    }
+
+  return true;
+}
+
+// TODO: move this fn
+/* FIXME.  */
+
+bool
+program_state::on_edge (const exploded_graph &eg,
+			const exploded_node &enode,
+			const superedge *succ)
+{
+  /* Update state.  */
+
+  const program_point &point = enode.get_point ();
+  const program_state &old_state = enode.get_state ();
+  const gimple *last_stmt = point.m_supernode->get_last_stmt ();
+
+  /* For conditionals and switch statements, add the
+     relevant conditions (for the specific edge) to new_state;
+     skip edges for which the resulting constraints
+     are impossible.  */
+  if (!m_path_state.apply_constraints_for_edge (*succ,
+						last_stmt))
+    {
+      eg.log ("edge to SN: %i is impossible due to path_state constraints",
+	      succ->m_dest->m_index);
+      return false;
+    }
+
+  /* FIXME: the above is setting ECs for args/params on calls, but not
+     updating states.  should we call on_assignment instead.
+     Also, where should it happen for return values at returns?
+     Similarly, the above is pruning state in path_state, but not within
+     the sm_state_map.
+
+     Should we be passing on_assignment to it as a callback?
+     Similarly for frame purging?  */
+
+  /* Hack to do it for interprocedural calls/returns.  */
+  switch (succ->m_kind)
+    {
+    case SUPEREDGE_CFG_EDGE:
+      break;
+
+    case SUPEREDGE_CALL:
+      /* Copy state from args to params.  */
+      {
+	const call_superedge *call_edge
+	  = succ->dyn_cast_call_superedge ();
+	// FIXME: should be an as_a
+
+	/* Map args to parms at next stack_depth, incrementing stack depth.  */
+	int caller_stack_depth = enode.get_stack_depth ();
+	int callee_stack_depth = caller_stack_depth + 1;
+
+	tree callee = call_edge->get_callee_decl ();
+	gcall *call_stmt = call_edge->get_call_stmt ();
+
+	int i = 0;
+	for (tree parm = DECL_ARGUMENTS (callee); parm;
+	     parm = DECL_CHAIN (parm), ++i)
+	  {
+	    tree parm_default_ssa
+	      = ssa_default_def (call_edge->m_dest->m_fun, parm);
+	    if (parm_default_ssa)
+	      enode.on_assignment (eg,
+				   path_var (parm_default_ssa,
+					     callee_stack_depth),
+				   path_var (gimple_call_arg (call_stmt, i),
+					     caller_stack_depth),
+				   old_state,
+				   this);
+	  }
+      }
+      break;
+
+    case SUPEREDGE_RETURN:
+      {
+	const return_superedge *return_edge
+	  = succ->dyn_cast_return_superedge ();
+	// FIXME: should be an as_a
+	int callee_stack_depth = enode.get_stack_depth ();
+	int caller_stack_depth = callee_stack_depth - 1;
+	tree callee = return_edge->get_callee_decl ();
+	tree result = DECL_RESULT (callee);
+	gcall *call_stmt = return_edge->get_call_stmt ();
+	tree lhs = gimple_call_lhs (call_stmt);
+	if (lhs)
+	  {
+	    enode.on_assignment (eg,
+				 path_var (lhs, caller_stack_depth),
+				 path_var (result, callee_stack_depth),
+				 old_state,
+				 this);
+	  }
+      }
+      break;
+
+    case SUPEREDGE_INTRAPROCEDURAL_CALL:
+      /* We ignore these edges.  FIXME: why have them?  */
+      eg.log ("rejecting interprocedural edge");
+      return false;
+    }
+
+  /* Pass the edge to the various state machines to see if any transitions
+     or warnings occur.  */
+  int i;
+  sm_state_map *old_smap;
+  FOR_EACH_VEC_ELT (old_state.m_checker_states, i, old_smap)
+    {
+      const state_machine &sm = eg.get_ext_state ().get_sm (i);
+      impl_sm_context ctxt (eg, sm, &enode,
+			    this,
+			    *old_state.m_checker_states[i],
+			    m_checker_states[i]);
+      sm.on_edge (&ctxt, last_stmt, succ);
+
+      switch (succ->m_kind)
+	{
+	case SUPEREDGE_CFG_EDGE:
+	  break;
+
+	case SUPEREDGE_CALL:
+	  /* Copy state from params to args.  */
+	  // TODO
+	  break;
+
+	case SUPEREDGE_RETURN:
+	  {
+	    int callee_stack_depth = enode.get_stack_depth ();
+	    m_checker_states[i]->purge_stack_frame (callee_stack_depth);
+	  }
+	  break;
+
+	case SUPEREDGE_INTRAPROCEDURAL_CALL:
+	  break;
+	}
+    }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+exploded_edge::exploded_edge (exploded_node *src, exploded_node *dest,
+			      const superedge *sedge)
+: m_src (src), m_dest (dest), m_sedge (sedge)
+{
+}
+
+/* FIXME.  */
+
+void
+exploded_edge::dump_dot (graphviz_out *gv) const
+{
+  pretty_printer *pp = gv->m_pp;
+
+  const char *style = "\"solid,bold\"";
+  const char *color = "black";
+  int weight = 10;
+  const char *constraint = "true";
+
+  if (m_sedge)
+    switch (m_sedge->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;
+      }
+
+  m_src->dump_dot_id (pp);
+  pp_string (pp, " -> ");
+  m_dest->dump_dot_id (pp);
+  pp_printf (pp,
+	     (" [style=%s, color=%s, weight=%d, constraint=%s,"
+	      " headlabel=\""),
+	     style, color, weight, constraint);
+
+  if (m_sedge)
+    m_sedge->dump_label_to_pp (pp);
+
+  pp_printf (pp, "\"];\n");
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+
+/* exploded_graph's ctor.  */
+
+exploded_graph::exploded_graph (const supergraph &sg, logger *logger,
+				const extrinsic_state &ext_state,
+				int verbosity)
+: log_user (logger), m_sg (sg), m_ext_state (ext_state),
+  m_verbosity (verbosity),
+  m_node_reuse_count (0)
+{
+  for (int i = 0; i < NUM_POINT_KINDS; i++)
+    m_num_nodes[i] = 0;
+
+  m_origin = get_or_create_node (program_point (NULL, NULL, 0, PK_ORIGIN,
+						call_string ()),
+				 program_state (ext_state));
+}
+
+/* Ensure that there is an exploded_node representing an external call to
+   FUN, adding it to the worklist if creating it.
+
+   Add an edge from the origin exploded_node to the function entrypoint
+   exploded_node.  */
+
+void
+exploded_graph::add_function_entry (function *fun)
+{
+  program_point point = program_point::from_function_entry (m_sg, fun);
+  program_state state (m_ext_state);
+
+  add_edge (m_origin, get_or_create_node (point, state), NULL);
+}
+
+/* Get or create an exploded_node for (POINT, STATE).
+   If a new node is created, it is added to the worklist.  */
+
+exploded_node *
+exploded_graph::get_or_create_node (const program_point &point,
+				    const program_state &state)
+{
+  JIT_LOG_FUNC (get_logger ());
+
+  /* Prune state to try to improve the chances of a cache hit,
+     avoiding generating redundant nodes.  */
+  program_state pruned_state = state.prune_for_point (*this, point);
+
+  point_and_state ps (point, pruned_state);
+  if (exploded_node **slot = m_point_and_state_to_node.get (ps))
+    {
+      /* An exploded_node for PS already exists.  */
+      log ("reused EN: %i", (*slot)->m_index);
+      m_node_reuse_count++;
+      return *slot;
+    }
+
+  /* An exploded_node for PS doesn't already exist; create one.  */
+  exploded_node *node = new exploded_node (ps, m_ext_state, m_nodes.length ());
+  add_node (node);
+  m_point_and_state_to_node.put (ps, node);
+  ++m_num_nodes[node->get_point ().m_kind];
+
+  if (get_logger ())
+    {
+      log ("created EN: %i", node->m_index);
+      log ("point: %s",
+	   point.to_str ().c_str ());
+      log ("pruned_state: %s",
+	   pruned_state.to_str (m_ext_state).c_str ());
+    }
+
+  /* Add the new node to the worlist.  */
+  m_worklist.safe_push (node);
+  return node;
+}
+
+/* Add an exploded_edge from SRC to DEST, recording its association
+   with SEDGE (which may be NULL).  */
+
+void
+exploded_graph::add_edge (exploded_node *src, exploded_node *dest,
+			  const superedge *sedge)
+{
+  exploded_edge *e = new exploded_edge (src, dest, sedge);
+  digraph::add_edge (e);
+  src->m_succs.safe_push (e);
+}
+
+/* The main loop of the analysis.
+   Pop freshly-created exploded_nodes from the worklist, calling
+   process_node on them to explore the <point, state> graph.
+   Add edges to their successors, potentially creating new successors
+   (which are also added to the worklist).  */
+
+void
+exploded_graph::process_worklist ()
+{
+  while (m_worklist.length () > 0)
+    {
+      exploded_node *node = m_worklist.pop ();
+      process_node (node);
+
+      /* FIXME: make this less ad-hoc?  e.g. a param */
+      /* we use exit rather than entry since there can be multiple
+	 entry ENs, one per phi; the number of PK_AFTER_SUPERNODE ought
+	 to be equivalent to the number of supernodes multiplied by the
+	 number of states.  */
+      if (m_num_nodes[PK_AFTER_SUPERNODE]> m_sg.num_nodes () * 5)
+	{
+	  log ("bailing out; too many nodes");
+	  sorry ("analysis bailed out early");
+	  return;
+	}
+    }
+}
+
+/* The core of exploded_graph::process_worklist (the main analysis loop),
+   handling one node in the worklist.
+
+   Get successor <point, state> pairs for NODE, calling get_or_create on
+   them, and adding an exploded_edge to each successors.
+
+   Freshly-created nodes will be added to the worklist.  */
+
+void
+exploded_graph::process_node (exploded_node *node)
+{
+  JIT_LOG_FUNC_1 (get_logger (), "EN: %i", node->m_index);
+
+  const program_point &point = node->get_point ();
+  const program_state &state = node->get_state ();
+  if (get_logger ())
+    {
+      log ("point: %s, state: %s",
+	   point.to_str ().c_str (),
+	   state.to_str (m_ext_state).c_str ());
+    }
+
+  switch (point.m_kind)
+    {
+    default:
+      gcc_unreachable ();
+    case PK_ORIGIN:
+      /* This node exists to simplify finding the shortest path
+	 to an exploded_node.  */
+      break;
+
+    case PK_BEFORE_SUPERNODE:
+      {
+	program_state next_state (state);
+
+	for (gphi_iterator gpi
+	       = const_cast<supernode *>(node->get_supernode ())->start_phis ();
+	     !gsi_end_p (gpi); gsi_next (&gpi))
+	  {
+	    gphi *phi = gpi.phi ();
+
+	    const cfg_superedge *last_cfg_superedge
+	      = point.m_from_edge->dyn_cast_cfg_superedge ();
+	    gcc_assert (last_cfg_superedge);
+	    // FIXME: this ought to be an as_a<>
+
+	    tree src = last_cfg_superedge->get_phi_arg (phi);
+	    tree lhs = gimple_phi_result (phi);
+
+	    /* Update next_state based on phi.  */
+	    node->on_assignment (*this,
+				 point.make_path_var (lhs),
+				 point.make_path_var (src),
+				 state,
+				 &next_state);
+	  }
+
+	if (point.m_supernode->m_stmts.length () > 0)
+	  {
+	    program_point next_point
+	      = program_point::before_stmt (point.m_supernode, 0,
+					    point.m_call_string);
+	    exploded_node *next
+	      = get_or_create_node (next_point, next_state);
+	    add_edge (node, next, NULL);
+	  }
+	else
+	  {
+	    program_point next_point
+	      = program_point::after_supernode (point.m_supernode,
+						point.m_call_string);
+	    exploded_node *next = get_or_create_node (next_point, next_state);
+	    add_edge (node, next, NULL);
+	  }
+      }
+      break;
+    case PK_BEFORE_STMT:
+      {
+	// TODO: update state based on stmt!
+	program_point next_point
+	  = program_point::after_stmt (point.m_supernode, point.m_stmt_idx,
+				       point.m_call_string);
+	program_state next_state = node->on_stmt (*this);
+	exploded_node *next = get_or_create_node (next_point, next_state);
+	add_edge (node, next, NULL);
+      }
+      break;
+    case PK_AFTER_STMT:
+      {
+	unsigned next_idx = point.m_stmt_idx + 1;
+	if (next_idx < point.m_supernode->m_stmts.length ())
+	  {
+	    program_point next_point
+	      = program_point::before_stmt (point.m_supernode, next_idx,
+					    point.m_call_string);
+	    exploded_node *next
+	      = get_or_create_node (next_point, state);
+	    add_edge (node, next, NULL);
+	  }
+	else
+	  {
+	    program_point next_point
+	      = program_point::after_supernode (point.m_supernode,
+						point.m_call_string);
+	    exploded_node *next = get_or_create_node (next_point, state);
+	    add_edge (node, next, NULL);
+	  }
+      }
+      break;
+    case PK_AFTER_SUPERNODE:
+      {
+	/* If this is an EXIT BB, detect leaks.  */
+	if (point.m_supernode->return_p ())
+	  node->detect_leaks (*this);
+
+	/* Traverse into successors of the supernode.  */
+	int i;
+	superedge *succ;
+	FOR_EACH_VEC_ELT (point.m_supernode->m_succs, i, succ)
+	  {
+	    log ("considering SN: %i -> SN: %i",
+		 succ->m_src->m_index, succ->m_dest->m_index);
+
+	    program_point next_point
+	      = program_point::before_supernode (succ->m_dest, succ,
+						 point.m_call_string);
+	    program_state next_state (state);
+
+	    if (!node->on_edge (*this, succ, &next_point, &next_state))
+	      {
+		log ("skipping impossible edge to SN: %i",
+		     succ->m_dest->m_index);
+		continue;
+	      }
+
+	    exploded_node *next = get_or_create_node (next_point, next_state);
+	    add_edge (node, next, succ);
+	  }
+      }
+      break;
+    }
+}
+
+/* FIXME.  */
+
+void
+exploded_graph::emit_diagnostic (const state_machine &sm,
+				 const exploded_node *enode,
+				 const supernode *node, const gimple *stmt,
+				 stmt_finder *stmt_finder,
+				 tree var, state_machine::state_t state,
+				 pending_diagnostic *d) const
+{
+  /* TODO: generate a path.  */
+  /* TODO: cache the diagnostics, and flush them all at the
+     end, with one shortest_paths instance, and so finding
+     the shortest path, rather than just the first path.  */
+  shortest_paths sp (*this);
+  exploded_path epath (sp.get_shortest_path (enode));
+  pretty_printer *pp = global_dc->printer->clone ();
+
+  checker4_path emission_path;
+  // TODO: build path
+  for (unsigned i = 0; i < epath.m_edges.length (); i++)
+    {
+      const exploded_edge *eedge = epath.m_edges[i];
+      if (get_logger ())
+	log ("EN %i -> EN %i",
+	     eedge->m_src->m_index,
+	     eedge->m_dest->m_index);
+      const exploded_node *src_node = eedge->m_src;
+      const program_point &src_point = src_node->get_point ();
+      const exploded_node *dst_node = eedge->m_dest;
+      const program_point &dst_point = dst_node->get_point ();
+      const int src_stack_depth = src_point.get_stack_depth ();
+      const int dst_stack_depth = dst_point.get_stack_depth ();
+      switch (dst_point.m_kind)
+	{
+	default:
+	  break;
+	case PK_BEFORE_SUPERNODE:
+	  if (src_point.m_kind == PK_AFTER_SUPERNODE)
+	    {
+	      if (eedge->m_sedge)
+		{
+		  switch (eedge->m_sedge->m_kind)
+		    {
+		    case SUPEREDGE_CFG_EDGE:
+		    case SUPEREDGE_CALL:
+		    case SUPEREDGE_INTRAPROCEDURAL_CALL:
+		      {
+			const gimple *last_stmt
+			  = src_point.m_supernode->get_last_stmt ();
+			emission_path.add_event
+			  (new edge_event (eedge->m_sedge,
+					   (last_stmt
+					    ? last_stmt->location
+					    : UNKNOWN_LOCATION),
+					   src_point.m_supernode->m_fun->decl,
+					   src_stack_depth));
+		      }
+		      break;
+		    case SUPEREDGE_RETURN:
+		      {
+			const return_superedge *return_edge
+			  = eedge->m_sedge->dyn_cast_return_superedge ();
+			gcc_assert (return_edge);
+			// FIXME: use as_a<>
+
+			const gcall *call_stmt = return_edge->get_call_stmt ();
+			emission_path.add_event
+			  (new edge_event (eedge->m_sedge,
+					   (call_stmt
+					    ? call_stmt->location
+					    : UNKNOWN_LOCATION),
+					   dst_point.m_supernode->m_fun->decl,
+					   dst_stack_depth));
+		      }
+		      break;
+		    }
+		}
+	    }
+	  // TODO: add function entry events
+	  if (dst_point.m_supernode->entry_p ())
+	    {
+	      emission_path.add_event
+		(new function_entry_event
+		 (dst_point.m_supernode->get_start_location (),
+		  dst_point.m_supernode->m_fun->decl,
+		  dst_stack_depth));
+	    }
+	  break;
+	case PK_BEFORE_STMT:
+	  emission_path.add_event
+	    (new statement_event (dst_point.get_stmt (),
+				  dst_point.m_supernode->m_fun->decl,
+				  dst_stack_depth));
+	  break;
+	case PK_AFTER_STMT:
+	  break;
+	}
+
+      /* Add state change events for the states that have changed.  */
+      const program_state &src_state = src_node->get_state ();
+      const program_state &dst_state = dst_node->get_state ();
+      const extrinsic_state &ext_state = m_ext_state;
+      gcc_assert (src_state.m_checker_states.length ()
+		  == ext_state.m_checkers.length ());
+      gcc_assert (dst_state.m_checker_states.length ()
+		  == ext_state.m_checkers.length ());
+      for (unsigned i = 0; i < ext_state.m_checkers.length (); i++)
+	{
+	  const state_machine &sm = ext_state.get_sm (i);
+	  const sm_state_map &src_smap = *src_state.m_checker_states[i];
+	  const sm_state_map &dst_smap = *dst_state.m_checker_states[i];
+	  for (sm_state_map::iterator_t iter = dst_smap.begin ();
+	       iter != dst_smap.end ();
+	       ++iter)
+	    {
+	      path_var pv = (*iter).first;
+	      state_machine::state_t dst_sm_val = (*iter).second.m_state;
+	      state_machine::state_t src_sm_val = src_smap.get_state (pv);
+	      if (dst_sm_val != src_sm_val)
+		{
+		  const gimple *stmt = dst_point.get_stmt ();
+		  const supernode *supernode = dst_point.m_supernode;
+		  int stack_depth = dst_stack_depth;
+
+		  if (eedge->m_sedge
+		      && eedge->m_sedge->m_kind == SUPEREDGE_CFG_EDGE)
+		    {
+		      supernode = src_point.m_supernode;
+		      stmt = supernode->get_last_stmt ();
+		      stack_depth = src_stack_depth;
+		    }
+
+		  /* FIXME: bulletproofing for state changes at calls/returns;
+		     is there a better way? */
+		  if (!stmt)
+		    continue;
+
+		  emission_path.add_event
+		    (new state_change_event (supernode,
+					     stmt,
+					     stack_depth,
+					     sm,
+					     pv.m_tree,
+					     src_sm_val,
+					     dst_sm_val,
+					     (*iter).second.m_origin.m_tree));
+		}
+	    }
+	}
+    }
+
+  /* Support deferring the choice of stmt until after an emission path has
+     been built, using an optional stmt_finder.  */
+  if (stmt == NULL)
+    {
+      gcc_assert (stmt_finder);
+      stmt = stmt_finder->find_stmt (epath);
+      gcc_assert (stmt);
+    }
+
+  prune_path (&emission_path, sm, var, state);
+  emission_path.add_final_event (sm, enode, stmt, var, state, d);
+  emission_path.prepare_for_emission (d);
+
+  gcc_rich_location rich_loc (stmt->location);
+  rich_loc.set_path (&emission_path);
+
+  auto_cfun sentinel (node->m_fun);
+  d->emit (&rich_loc);
+  delete pp;
+}
+
+/* FIXME.  */
+
+void
+exploded_graph::prune_path (checker4_path *path,
+			    const state_machine &sm,
+			    tree var,
+			    state_machine::state_t state ATTRIBUTE_UNUSED) const
+{
+  JIT_LOG_FUNC (get_logger ());
+  path->maybe_log (get_logger (), "path");
+
+  // TODO
+  /* TODO: go backwards, skipping state change events that aren't var
+     but update the pertinent var when state-copying occurs.  */
+  int idx = path->m_events.length () - 1;
+  while (idx >= 0 && idx < path->m_events.length ())
+    {
+      checker4_event *base_event = path->m_events[idx];
+      if (get_logger ())
+	log  ("considering event %i, with var: %qE, state: %qs",
+	      idx, var, sm.get_state_name (state));
+      switch (base_event->m_kind)
+	{
+	default:
+	  gcc_unreachable ();
+
+	case EK_DEBUG:
+	  if (m_verbosity < 2)
+	    {
+	      log ("filtering event %i: debug event", idx);
+	      path->delete_event (idx);
+	    }
+	  break;
+
+	case EK_STMT:
+	  if (m_verbosity < 2)
+	    {
+	      log ("filtering event %i: statement event", idx);
+	      path->delete_event (idx);
+	    }
+	  break;
+
+	case EK_FUNCTION_ENTRY:
+	  if (m_verbosity < 1)
+	    {
+	      log ("filtering event %i: function entry", idx);
+	      path->delete_event (idx);
+	    }
+	  break;
+
+	case EK_STATE_CHANGE:
+	  {
+	    state_change_event *state_change = (state_change_event *)base_event;
+	    if (state_change->m_var == var)
+	      {
+		if (state_change->m_origin)
+		  {
+		    log ("event %i: switching var of interest from %qE to %qE",
+			 idx, var, state_change->m_origin);
+		    var = state_change->m_origin;
+		  }
+	      }
+	    else if (m_verbosity < 2)
+	      {
+		log ("filtering event %i: state change to %qE unrelated to %qE",
+		     idx, state_change->m_var, var);
+		path->delete_event (idx);
+	      }
+	  }
+	  break;
+
+	case EK_EDGE:
+	  {
+	    edge_event *event = (edge_event *)base_event;
+#if 0
+	    inform (base_event->m_loc, "considering edge_event %i: %s",
+		    idx, base_event->m_desc);
+#endif
+	    switch (event->m_edge->m_kind)
+	      {
+	      case SUPEREDGE_CFG_EDGE:
+		{
+		  const cfg_superedge& cfg_superedge
+		    = event->get_cfg_superedge ();
+		  const supernode *dest = event->m_edge->m_dest;
+		  /* Do we have an SSA_NAME defined via a phi node in
+		     the dest CFG node?  */
+		  if (TREE_CODE (var) == SSA_NAME)
+		    if (SSA_NAME_DEF_STMT (var)->bb == dest->m_bb)
+		      {
+			if (const gphi *phi
+			    = dyn_cast <gphi *> (SSA_NAME_DEF_STMT (var)))
+			  {
+			    /* Update var based on its phi node.  */
+			    tree old_var = var;
+			    var = cfg_superedge.get_phi_arg (phi);
+			    log ("updating from %qE to %qE based on phi node",
+				 old_var, var);
+			    if (get_logger ())
+			      {
+				pretty_printer pp;
+				pp_gimple_stmt_1 (&pp, (gimple *)phi, 0, (dump_flags_t)0);
+				// FIXME: const_cast!
+				log ("  phi: %s", pp_formatted_text (&pp));
+			      }
+			  }
+		      }
+
+		  /* TODO: is this edge significant to var?
+		     See if var can be in other states in the dest, but not
+		     in other states in the src?
+		     Must have multiple sibling edges.  */
+		  // TODO: for now, filter all intraprocedural edges:
+		  if (m_verbosity < 2)
+		    {
+		      log ("filtering event %i: CFG edge", idx);
+		      path->delete_event (idx);
+		    }
+		}
+		break;
+
+	      case SUPEREDGE_CALL:
+		{
+		  const callgraph_superedge& cg_superedge
+		    = event->get_callgraph_superedge ();
+		  tree caller_var
+		    = cg_superedge.map_expr_from_callee_to_caller (var);
+		  if (caller_var)
+		    var = caller_var;
+		}
+		break;
+	      case SUPEREDGE_RETURN:
+		// TODO: potentially update var/state based on return value,
+		// args etc
+		{
+		  const callgraph_superedge& cg_superedge
+		    = event->get_callgraph_superedge ();
+		  tree callee_var
+		    = cg_superedge.map_expr_from_caller_to_callee (var);
+		  if (callee_var)
+		    var = callee_var;
+		}
+		break;
+	      case SUPEREDGE_INTRAPROCEDURAL_CALL:
+		break;
+	      }
+	  }
+	  break;
+
+	case EK_WARNING:
+	  break;
+	}
+      idx--;
+    }
+
+  /* Second pass to remove redundant interprocedural information.  For
+     example, given:
+     - calling "f2" from "f1"
+     --- entry to "f2"
+     --- calling "f3" from "f2"
+     ----- entry to "f3"
+     --- returning to "f2" to "f3"
+     - returning to "f1" to "f2"
+     with no other intervening events, then none of these events are
+     likely to be interesting to the user.
+
+     Prune [..., call, function-entry, return, ...] triples repeatedly
+     until nothing has changed.  */
+  bool changed = false;
+  do
+    {
+      changed = false;
+      int idx = path->m_events.length () - 3;
+      while (idx >= 0 && idx < path->m_events.length ())
+	{
+	  if (path->m_events[idx]->is_call_p ()
+	      && path->m_events[idx + 1]->is_function_entry_p ()
+	      && path->m_events[idx + 2]->is_return_p ())
+	    {
+	      if (get_logger ())
+		{
+		  label_text desc (path->m_events[idx]->get_desc ());
+		  log ("filtering events %i-%i:"
+		       " irrelevant call/entry/return: %s",
+		       idx, idx + 2, desc.m_buffer);
+		  desc.maybe_free ();
+		}
+	      path->delete_event (idx + 2);
+	      path->delete_event (idx + 1);
+	      path->delete_event (idx);
+	      changed = true;
+	    }
+	  idx--;
+	}
+
+    }
+  while (changed);
+
+  /* Third pass.  If all we're left with is in one function, then
+     filter function entry events.  */
+  if (!path->interprocedural_p ())
+    {
+      // FIXME
+      int idx = path->m_events.length () - 1;
+      while (idx >= 0 && idx < path->m_events.length ())
+	{
+	  checker4_event *base_event = path->m_events[idx];
+	  if (base_event->m_kind == EK_FUNCTION_ENTRY)
+	    {
+	      log ("filtering event %i:"
+		   " function entry for purely intraprocedural path", idx);
+	      path->delete_event (idx);
+	    }
+	  idx--;
+	}
+    }
+}
+
+/* FIXME.  */
+
+void
+exploded_graph::log_stats () const
+{
+  if (!get_logger ())
+    return;
+
+  log ("m_sg.num_nodes (): %i", m_sg.num_nodes ());
+  log ("m_nodes.length (): %i", m_nodes.length ());
+  for (int i = 0; i < NUM_POINT_KINDS; i++)
+    log ("m_num_nodes[%s]: %i",
+	 point_kind_to_string (static_cast <enum point_kind> (i)),
+	 m_num_nodes[i]);
+  log ("m_node_reuse_count: %i", m_node_reuse_count);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Look for the last use of SEARCH_STMT within this path.
+   If found write the edge's index to *OUT_IDX and return true, otherwise
+   return false.  */
+
+bool
+exploded_path::find_stmt_backwards (const gimple *search_stmt,
+				    int *out_idx) const
+{
+  int i;
+  const exploded_edge *eedge;
+  FOR_EACH_VEC_ELT_REVERSE (m_edges, i, eedge)
+    {
+      const exploded_node *dst_node = eedge->m_dest;
+      const program_point &dst_point = dst_node->get_point ();
+      const gimple *stmt = dst_point.get_stmt ();
+      if (stmt == search_stmt)
+	{
+	  *out_idx = i;
+	  return true;
+	}
+    }
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* shortest_paths's constructor.
+
+   Use Dijkstra's algorithm relative to the origin of EG to populate
+   m_dist and m_prev with enough information to be able to generate
+   exploded_path instances to give the shortest path to any node in EG
+   from the origin node.  */
+
+shortest_paths::shortest_paths (const exploded_graph &eg)
+: m_eg (eg),
+  m_dist (eg.m_nodes.length ()),
+  m_prev (eg.m_nodes.length ())
+{
+  auto_vec<int> queue (eg.m_nodes.length ());
+
+  for (unsigned i = 0; i < eg.m_nodes.length (); i++)
+    {
+      m_dist.quick_push (INT_MAX);
+      m_prev.quick_push (NULL);
+      queue.quick_push (i);
+    }
+  m_dist[eg.get_origin ()->m_index] = 0;
+
+  while (queue.length () > 0)
+    {
+      /* Get minimal distance in queue.
+	 FIXME: this is O(N^2); replace with a priority queue.  */
+      int idx_with_min_dist = -1;
+      int idx_in_queue_with_min_dist = -1;
+      int min_dist = INT_MAX;
+      for (unsigned i = 0; i < queue.length (); i++)
+	{
+	  int idx = queue[i];
+	  if (m_dist[queue[i]] < min_dist)
+	    {
+	      min_dist = m_dist[idx];
+	      idx_with_min_dist = idx;
+	      idx_in_queue_with_min_dist = i;
+	    }
+	}
+      gcc_assert (idx_with_min_dist != -1);
+      gcc_assert (idx_in_queue_with_min_dist != -1);
+
+      // FIXME: this is confusing: there are two indices here
+
+      queue.unordered_remove (idx_in_queue_with_min_dist);
+
+      exploded_node *n
+	= static_cast <exploded_node *> (m_eg.m_nodes[idx_with_min_dist]);
+
+      int i;
+      exploded_edge *succ;
+      FOR_EACH_VEC_ELT (n->m_succs, i, succ)
+	{
+	  // TODO: only for dest still in queue
+	  exploded_node *dest = succ->m_dest;
+	  int alt = m_dist[n->m_index] + 1;
+	  if (alt < m_dist[dest->m_index])
+	    {
+	      m_dist[dest->m_index] = alt;
+	      m_prev[dest->m_index] = succ;
+	    }
+	}
+   }
+}
+
+/* Gnerate an exploded_path instance giving the shortest path to the node
+   TO from the origin node.  */
+
+exploded_path
+shortest_paths::get_shortest_path (const exploded_node *to) const
+{
+  exploded_path result;
+
+  while (m_prev[to->m_index])
+    {
+      result.m_edges.safe_push (m_prev[to->m_index]);
+      to = m_prev[to->m_index]->m_src;
+    }
+
+  result.m_edges.reverse ();
+
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+#if 0
+checker4_path::checker4_path (const checker4_path &other)
+: m_engine (other.m_engine),
+  m_links (),
+  m_ps (other.m_ps),
+  m_terminator (other.m_terminator)
+{
+  m_engine->m_num_path_copies++;
+  m_engine->m_num_event_copies += other.m_events.length ();
+
+  unsigned i;
+  {
+    link *l;
+    FOR_EACH_VEC_ELT (other.m_links, i, l)
+      m_links.safe_push (*l);
+  }
+  {
+    cgraph_edge *edge;
+    FOR_EACH_VEC_ELT (other.m_active_calls, i, edge)
+      m_active_calls.safe_push (edge);
+  }
+  {
+    checker4_event *e;
+    FOR_EACH_VEC_ELT (other.m_events, i, e)
+      m_events.safe_push (e->clone ());
+  }
+  // FIXME: quick push for these?
+}
+
+checker4_path&
+checker4_path::operator= (const checker4_path &other)
+{
+  m_engine = other.m_engine;
+  m_ps = other.m_ps;
+  m_terminator = other.m_terminator;
+
+  m_engine->m_num_path_copies++;
+  m_engine->m_num_event_copies += other.m_events.length ();
+
+  unsigned i;
+  {
+    link *l;
+    FOR_EACH_VEC_ELT (other.m_links, i, l)
+      m_links.safe_push (*l);
+  }
+  {
+    cgraph_edge *edge;
+    FOR_EACH_VEC_ELT (other.m_active_calls, i, edge)
+      m_active_calls.safe_push (edge);
+  }
+  {
+    checker4_event *e;
+    FOR_EACH_VEC_ELT (other.m_events, i, e)
+      m_events.safe_push (e->clone ());
+  }
+  return *this;
+}
+#endif
+
+/* FIXME.  */
+std::string
+checker4_path::to_str () const
+{
+  std::string result ("[");
+
+  checker4_event *e;
+  int i;
+  FOR_EACH_VEC_ELT (m_events, i, e)
+    {
+      if (i > 0)
+	result += ", ";
+      label_text event_desc (e->get_desc ());
+      result += std::string ("\"") + event_desc.m_buffer + std::string ("\"");
+      event_desc.maybe_free ();
+    }
+  result += "]";
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* A wrapper around hash_map which preserves insertion order.
+   The keys must be PODs, since vec<> uses assignment to populate slots
+   without properly initializing them.  */
+
+template<typename KeyId, typename Value,
+	 typename Traits = simple_hashmap_traits<default_hash_traits<KeyId>,
+						 Value> >
+class ordered_hash_map
+{
+  typedef hash_map <KeyId, Value> map_t;
+  typedef auto_vec <KeyId> vec_t;
+
+public:
+  typedef typename Traits::key_type Key;
+
+  Value *get (const KeyId &k)
+  {
+    return m_map.get (k);
+  }
+
+  bool put (const Key &k, const Value &v)
+  {
+    bool existed = m_map.put (k, v);
+    if (!existed)
+      m_vector.safe_push (k);
+    return existed;
+  }
+
+  class iterator
+  {
+  public:
+    typedef typename map_t::iterator::reference_pair reference_pair;
+
+    iterator (const ordered_hash_map &ordmap, int idx)
+    : m_ordmap (ordmap), m_idx (idx) {}
+
+    iterator &operator++ ()
+    {
+      ++m_idx;
+      return *this;
+    }
+
+    reference_pair operator* ()
+    {
+      KeyId k = m_ordmap.m_vector[m_idx];
+      Value *slot = const_cast <ordered_hash_map &> (m_ordmap).m_map.get (k);
+      return reference_pair (k, *slot);
+    }
+
+    bool
+    operator != (const iterator &other) const
+    {
+      return m_idx != other.m_idx;
+    }
+
+  private:
+    const ordered_hash_map &m_ordmap;
+    int m_idx;
+  };
+
+  iterator begin () const { return iterator (*this, 0); }
+  iterator end () const { return iterator (*this, m_vector.length ()); }
+
+private:
+  map_t m_map;
+  vec_t m_vector;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Subclass of cluster which casts the dnode instances to exploded_node.  */
+
+class en_cluster : public cluster
+{
+public:
+  void add_node (dnode *node) FINAL OVERRIDE
+  {
+    exploded_node *en = static_cast <exploded_node *> (node);
+    add_exploded_node (en);
+  }
+  virtual void add_exploded_node (exploded_node *en) = 0;
+};
+
+/* Cluster containing all exploded_node instances for one supernode.  */
+
+class supernode_cluster : public en_cluster
+{
+public:
+  supernode_cluster (const supernode *supernode) : m_supernode (supernode) {}
+
+  // TODO: dtor?
+
+  void dump_dot (graphviz_out *gv) const FINAL OVERRIDE
+  {
+    gv->println ("subgraph \"cluster_supernode_%p\" {",
+		 (const void *)this);
+    gv->indent ();
+    gv->println ("style=\"dashed\";");
+    gv->println ("label=\"SN: %i\";", m_supernode->m_index);
+
+    int i;
+    exploded_node *enode;
+    FOR_EACH_VEC_ELT (m_enodes, i, enode)
+      enode->dump_dot (gv);
+
+    /* Terminate subgraph.  */
+    gv->outdent ();
+    gv->println ("}");
+  }
+
+  void add_exploded_node (exploded_node *en) FINAL OVERRIDE
+  {
+    m_enodes.safe_push (en);
+  }
+
+private:
+  const supernode *m_supernode;
+  auto_vec <exploded_node *> m_enodes;
+};
+
+/* Cluster containing all supernode_cluster instances for one
+   (function, call_string) pair.  */
+
+class function_call_string_cluster : public en_cluster
+{
+public:
+  function_call_string_cluster (function *fun, call_string cs)
+  : m_fun (fun), m_cs (cs) {}
+
+  // TODO: dtor
+
+  void dump_dot (graphviz_out *gv) const FINAL OVERRIDE
+  {
+    const char *funcname = function_name (m_fun);
+
+    gv->println ("subgraph \"cluster_function_%p\" {", (const void *)this);
+    gv->indent ();
+    gv->println ("label=\"call string: %s function: %s \";",
+		 m_cs.to_str ().c_str (), funcname);
+
+    for (map_t::iterator iter = m_map.begin ();
+	 iter != m_map.end ();
+	 ++iter)
+      (*iter).second->dump_dot (gv);
+
+    /* Terminate subgraph.  */
+    gv->outdent ();
+    gv->println ("}");
+  }
+
+  void add_exploded_node (exploded_node *en) FINAL OVERRIDE
+  {
+    const supernode *supernode = en->get_supernode ();
+    gcc_assert (supernode);
+    supernode_cluster **slot = m_map.get (supernode);
+    if (slot)
+      (*slot)->add_exploded_node (en);
+    else
+      {
+	supernode_cluster *child = new supernode_cluster (supernode);
+	m_map.put (supernode, child);
+	child->add_exploded_node (en);
+      }
+  }
+
+private:
+  function *m_fun;
+  call_string m_cs;
+  typedef ordered_hash_map<const supernode *, supernode_cluster *> map_t;
+  map_t m_map;
+};
+
+/* Keys for root_cluster.  */
+
+struct function_call_string
+{
+  function_call_string (function *fun, call_string cs)
+  : m_fun (fun), m_cs (cs)
+  {
+    gcc_assert (fun);
+  }
+
+  function *m_fun;
+  call_string m_cs;
+};
+
+} // namespace impl_4
+
+using impl_4::pod_hash_traits;
+using impl_4::function_call_string;
+
+template <> struct default_hash_traits<function_call_string>
+: public pod_hash_traits<function_call_string>
+{
+};
+
+template <>
+inline hashval_t
+pod_hash_traits<function_call_string>::hash (value_type v)
+{
+  return pointer_hash <function>::hash (v.m_fun) ^ v.m_cs.hash ();
+}
+
+template <>
+inline bool
+pod_hash_traits<function_call_string>::equal (const value_type &existing,
+					      const value_type &candidate)
+{
+  return existing.m_fun == candidate.m_fun && existing.m_cs == candidate.m_cs;
+}
+template <>
+inline void
+pod_hash_traits<function_call_string>::mark_deleted (value_type &v)
+{
+  v.m_fun = reinterpret_cast<function *> (1);
+}
+template <>
+inline void
+pod_hash_traits<function_call_string>::mark_empty (value_type &v)
+{
+  v.m_fun = reinterpret_cast<function *> (NULL);
+}
+template <>
+inline bool
+pod_hash_traits<function_call_string>::is_deleted (value_type v)
+{
+  return v.m_fun == reinterpret_cast<function *> (1);
+}
+template <>
+inline bool
+pod_hash_traits<function_call_string>::is_empty (value_type v)
+{
+  return v.m_fun == reinterpret_cast<function *> (NULL);
+}
+
+namespace impl_4 {
+
+/* Top-level cluster for printing exploded graphs, handling the functionless
+   nodes, and grouping the remaining nodes by callstring.  */
+
+class root_cluster : public en_cluster
+{
+public:
+  ~root_cluster ()
+  {
+    // TODO: delete the function_cluster instances in the map
+  }
+
+  void dump_dot (graphviz_out *gv) const FINAL OVERRIDE
+  {
+    int i;
+    exploded_node *enode;
+    FOR_EACH_VEC_ELT (m_functionless_enodes, i, enode)
+      enode->dump_dot (gv);
+
+    for (map_t::iterator iter = m_map.begin ();
+	 iter != m_map.end ();
+	 ++iter)
+      (*iter).second->dump_dot (gv);
+  }
+
+  void add_exploded_node (exploded_node *en) FINAL OVERRIDE
+  {
+    function *fun = en->get_function ();
+    if (!fun)
+      {
+	m_functionless_enodes.safe_push (en);
+	return;
+      }
+
+    const call_string &cs = en->get_point ().m_call_string;
+    function_call_string key (fun, cs);
+    function_call_string_cluster **slot = m_map.get (key);
+    if (slot)
+      (*slot)->add_exploded_node (en);
+    else
+      {
+	function_call_string_cluster *child
+	  = new function_call_string_cluster (fun, cs);
+	m_map.put (key, child);
+	child->add_exploded_node (en);
+      }
+  }
+
+private:
+  /* This can't be an ordered_map, as we can't store vec<call_string>,
+     since it's not a POD; vec<>::quick_push has:
+       *slot = obj;
+     and the slot isn't initialized, so the assignment op dies when cleaning up
+     un-inited *slot (within the truncate call).  */
+  typedef hash_map<function_call_string, function_call_string_cluster *> map_t;
+  map_t m_map;
+
+  /* This should just be the origin exploded_node.  */
+  auto_vec <exploded_node *> m_functionless_enodes;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+#if 0
+/* 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);
+}
+#endif
+
+/* FIXME.  */
+
+void
+impl_run_checkers (logger *logger)
+{
+  JIT_LOG_SCOPE (logger);
+
+  /* Create the supergraph.  */
+  supergraph sg (logger);
+
+  if (1)
+    {
+      char *filename = concat (dump_base_name, ".supergraph.dot", NULL);
+      sg.dump_dot (filename, (enum supergraph_dot_flags)0);
+      free (filename);
+    }
+
+  // TODO: selftest for auto_delete_vec
+  auto_delete_vec <state_machine> checkers;
+  make_checkers (checkers, logger);
+
+  if (logger)
+    {
+      int i;
+      state_machine *sm;
+      FOR_EACH_VEC_ELT (checkers, i, sm)
+	logger->log ("checkers[%i]: %s", i, sm->get_name ());
+    }
+  //engine e (sg, sm, logger, analyzer_verbosity);
+
+  // TODO: add initial nodes
+  impl_4::exploded_graph eg (sg, logger, checkers, analyzer_verbosity);
+
+  cgraph_node *node;
+  FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
+  {
+    function *fun = node->get_fun ();
+    if (!TREE_PUBLIC (fun->decl))
+      {
+	if (logger)
+	  logger->log ("not traversing %qE (static)", fun->decl);
+	continue;
+      }
+    eg.add_function_entry (fun);
+  }
+
+  eg.process_worklist ();
+
+  if (1)
+    {
+      char *filename
+	= concat (dump_base_name, ".eg.dot", NULL);
+      root_cluster c;
+      eg.dump_dot (filename, &c);
+      free (filename);
+    }
+
+  eg.log_stats ();
+}
+
+} // namespace impl_4
+
+/* FIXME.  */
+
+void
+run_checkers_impl_4 ()
+{
+  //FILE *dump = fopen ("test.out", "w"); // FIXME
+  auto_client_timevar tv ("run_checkers");
+  {
+    log_user the_logger (NULL);
+    if (flag_dump_analyzer)
+      {
+	FILE *dump_file = /* 0 ? dump : */stderr; // FIXME
+	the_logger.set_logger (new logger (dump_file, 0, 0,
+					   *global_dc->printer));
+      }
+    JIT_LOG_SCOPE (the_logger.get_logger ());
+
+    impl_4::impl_run_checkers (the_logger.get_logger ());
+
+    /* end of lifetime of the_logger (so that dump file is closed after the
+       various dtors run).  */
+  }
+
+  //fclose (dump);
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Tests for impl_4::sm_state_map.  */
+
+static void
+test_sm_state_map ()
+{
+  tree x = build_decl (UNKNOWN_LOCATION, PARM_DECL,
+		       get_identifier ("x"),
+		       integer_type_node);
+  tree y = build_decl (UNKNOWN_LOCATION, PARM_DECL,
+		       get_identifier ("y"),
+		       integer_type_node);
+  tree z = build_decl (UNKNOWN_LOCATION, PARM_DECL,
+		       get_identifier ("z"),
+		       integer_type_node);
+  path_var x0 (x, 0);
+  path_var y0 (y, 0);
+  path_var z0 (z, 0);
+
+  /* Test setting states on path_var instances directly.  */
+  {
+    impl_4::sm_state_map map;
+    ASSERT_TRUE (map.is_empty_p ());
+    ASSERT_EQ (map.get_state (x0), 0);
+
+    map.impl_set_state (x0, 42, z0);
+    ASSERT_EQ (map.get_state (x0), 42);
+    ASSERT_EQ (map.get_origin (x0), z0);
+    ASSERT_EQ (map.get_state (y0), 0);
+    ASSERT_FALSE (map.is_empty_p ());
+
+    map.impl_set_state (y0, 0, z0);
+    ASSERT_EQ (map.get_state (y0), 0);
+
+    map.impl_set_state (x0, 0, z0);
+    ASSERT_EQ (map.get_state (x0), 0);
+    ASSERT_TRUE (map.is_empty_p ());
+  }
+
+  /* Test setting states via equivalence classes.  */
+  {
+    impl_4::sm_state_map map;
+    ASSERT_TRUE (map.is_empty_p ());
+    ASSERT_EQ (map.get_state (x0), 0);
+    ASSERT_EQ (map.get_state (y0), 0);
+
+    path_state ps;
+    ps.add_constraint (x0, EQ_EXPR, y0);
+
+    /* Setting x to a state should also update y, as they
+       are in the same equivalence class.  */
+    map.set_state (&ps, x0, 5, z0);
+    ASSERT_EQ (map.get_state (x0), 5);
+    ASSERT_EQ (map.get_state (y0), 5);
+    ASSERT_EQ (map.get_origin (x0), z0);
+    ASSERT_EQ (map.get_origin (y0), z0);
+  }
+
+  /* Test equality and hashing.  */
+  {
+    impl_4::sm_state_map map0;
+    impl_4::sm_state_map map1;
+    impl_4::sm_state_map map2;
+
+    ASSERT_EQ (map0.hash (), map1.hash ());
+    ASSERT_EQ (map0, map1);
+
+    map1.impl_set_state (y0, 5, z0);
+    ASSERT_NE (map0.hash (), map1.hash ());
+    ASSERT_NE (map0, map1);
+
+    /* Make the same change to map2.  */
+    map2.impl_set_state (y0, 5, z0);
+    ASSERT_EQ (map1.hash (), map2.hash ());
+    ASSERT_EQ (map1, map2);
+  }
+}
+
+/* FIXME.  */
+
+static void
+test_assignments ()
+{
+  /* Verify X = Y, then Y = Z.
+     Should propagate states accordingly.  */
+#if 0
+exploded_node::on_assignment (const exploded_graph &eg,
+			      path_var lhs,
+			      path_var rhs,
+			      const program_state &old_state,
+			      program_state *new_state) const
+#endif
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+analyzer_impl_4_cc_tests ()
+{
+  test_sm_state_map ();
+  test_assignments ();
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
diff --git a/gcc/analyzer/supergraph.cc b/gcc/analyzer/supergraph.cc
new file mode 100644
index 0000000..31609e2
--- /dev/null
+++ b/gcc/analyzer/supergraph.cc
@@ -0,0 +1,886 @@
+/* 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"
+#include "analyzer-logging.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)
+    return NULL; /* e.g. for a function pointer.  */
+  if (!edge->callee->get_fun ())
+    return NULL;
+  return edge;
+}
+
+// FIXME
+
+supergraph::supergraph (logger *logger)
+{
+  auto_client_timevar tv ("building supergraph");
+
+  JIT_LOG_FUNC (logger);
+
+  /* First pass: make supernodes.  */
+  {
+    /* Sort the cgraph_nodes?  */
+    cgraph_node *node;
+    FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
+    {
+      function *fun = node->get_fun ();
+
+      const int start_idx = m_nodes.length ();
+
+      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);
+	}
+
+      if (logger)
+	{
+	  const int end_idx = m_nodes.length () - 1;
+	  logger->log ("SN: %i...%i: function %qD",
+		       start_idx, end_idx, fun->decl);
+	}
+    }
+  }
+
+  /* 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);
+	  call_superedge *sedge
+	    = add_call_superedge (caller_prev_supernode,
+				  callee_supernode,
+				  edge);
+	  m_cgraph_edge_to_call_superedge.put (edge, sedge);
+	}
+    }
+
+    /* 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);
+	  return_superedge *sedge
+	    = add_return_superedge (callee_supernode,
+				    caller_next_supernode,
+				    edge);
+	  m_cgraph_edge_to_return_superedge.put (edge, sedge);
+	}
+    }
+
+    /* 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);
+	  superedge *sedge
+	    = new callgraph_superedge (caller_prev_supernode,
+				       caller_next_supernode,
+				       SUPEREDGE_INTRAPROCEDURAL_CALL,
+				       edge);
+	  add_edge (sedge);
+	}
+
+    }
+  }
+}
+
+/* FIXME.  */
+
+void
+supergraph::dump_dot_to_pp (pretty_printer *pp,
+			    enum supergraph_dot_flags flags,
+			    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)
+	  {
+	    if (flags & SUPERGRAPH_DOT_SHOW_BBS)
+	      {
+		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, flags, a);
+
+	    if (flags & SUPERGRAPH_DOT_SHOW_BBS)
+	      {
+		/* 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, flags);
+
+  /* Terminate "digraph" */
+  gv.outdent ();
+  gv.println ("}");
+}
+
+void
+supergraph::dump_dot_to_file (FILE *fp, enum supergraph_dot_flags flags,
+			      const dot_annotator *a) const
+{
+  pretty_printer pp;
+  pp.buffer->stream = fp;
+  dump_dot_to_pp (&pp, flags, a);
+  pp_flush (&pp);
+}
+
+void
+supergraph::dump_dot (const char *path, enum supergraph_dot_flags flags,
+		      const dot_annotator *a) const
+{
+  FILE *fp = fopen (path, "w");
+  dump_dot_to_file (fp, flags, 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));
+}
+
+call_superedge *
+supergraph::add_call_superedge (supernode *src, supernode *dest,
+				cgraph_edge *cedge)
+{
+  call_superedge *new_edge = new call_superedge (src, dest, cedge);
+  add_edge (new_edge);
+  return new_edge;
+}
+
+return_superedge *
+supergraph::add_return_superedge (supernode *src, supernode *dest,
+				  cgraph_edge *cedge)
+{
+  return_superedge *new_edge = new return_superedge (src, dest, cedge);
+  add_edge (new_edge);
+  return new_edge;
+}
+
+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,
+		     enum supergraph_dot_flags flags ATTRIBUTE_UNUSED,
+		     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);
+      if (a)
+	a->add_stmt_annotations (pp, m_returning_call);
+      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);
+      if (a)
+	a->add_stmt_annotations (pp, stmt);
+      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,
+		     enum supergraph_dot_flags flags ATTRIBUTE_UNUSED) 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 0
+  if (m_kind == SUPEREDGE_CFG_EDGE)
+    pp_string (gv->m_pp, ":s");
+#endif
+  /* 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 0
+  if (m_kind == SUPEREDGE_CFG_EDGE)
+    pp_string (gv->m_pp, ":n");
+#endif
+#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 callgraph_superedge *sub = dyn_cast_callgraph_superedge ())
+    return sub->m_cedge;
+  return NULL;
+}
+
+/* FIXME.  */
+
+cgraph_edge *
+superedge::get_callgraph_edge () const
+{
+  const callgraph_superedge *sub = dyn_cast_callgraph_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.  */
+
+std::string
+superedge::to_str () const
+{
+  return (std::to_string (m_src->m_index)
+	  + " -> "
+	  + std::to_string (m_dest->m_index));
+}
+
+/* FIXME.  */
+
+void
+cfg_superedge::dump_label_to_pp (pretty_printer *pp) const
+{
+  if (true_value_p ())
+    pp_printf (pp, "true");
+  else if (false_value_p ())
+    pp_printf (pp, "false");
+  /* Otherwise, no label.  */
+}
+
+/* FIXME.  */
+
+tree
+cfg_superedge::get_phi_arg (const gphi *phi) const
+{
+  /* How to get from edge to phi arg?
+     PP shows: gimple_phi_arg_edge (phi, i)->src;
+     which is EDGE_PRED (gimple_bb (phi), i);
+     i.e. a lookup by index within bb->preds based on index within
+     Aha:
+       gimple_phi_arg_location_from_edge (gphi *phi, edge e)
+       {
+         return gimple_phi_arg (phi, e->dest_idx)->locus;
+       }
+       which implies we can use the e->dest_idx to get at the phi arg.  */
+
+  size_t index = m_cfg_edge->dest_idx;
+  return gimple_phi_arg_def (const_cast <gphi *> (phi), index); // FIXME const_cast
+}
+
+/* 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
+callgraph_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;
+    }
+}
+
+/* FIXME.  */
+
+tree
+callgraph_superedge::get_callee_decl () const
+{
+  return m_cedge->callee->get_fun ()->decl;
+}
+
+/* FIXME.  */
+
+tree
+callgraph_superedge::get_caller_decl () const
+{
+  return m_cedge->caller->get_fun ()->decl;
+}
+
+/* FIXME.  */
+
+tree
+callgraph_superedge::get_arg_for_parm (tree parm_to_find) const
+{
+  gcc_assert  (TREE_CODE (parm_to_find) == PARM_DECL);
+
+  tree callee = get_callee_decl ();
+
+  int i = 0;
+  for (tree iter_parm = DECL_ARGUMENTS (callee); iter_parm;
+       iter_parm = DECL_CHAIN (iter_parm), ++i)
+    {
+      if (iter_parm == parm_to_find)
+	return gimple_call_arg (get_call_stmt (), i);
+    }
+
+  /* Not found.  */
+  return NULL_TREE;
+}
+
+/* FIXME.  */
+
+tree
+callgraph_superedge::get_parm_for_arg (tree arg_to_find) const
+{
+  tree callee = get_callee_decl ();
+
+  int i = 0;
+  for (tree iter_parm = DECL_ARGUMENTS (callee); iter_parm;
+       iter_parm = DECL_CHAIN (iter_parm), ++i)
+    {
+      if (arg_to_find == gimple_call_arg (get_call_stmt (), i))
+	return iter_parm;
+    }
+
+  // FIXME: what if more than one?
+
+  /* Not found.  */
+  return NULL_TREE;
+}
+
+/* Map caller_expr back to an expr within the callee, or return NULL_TREE.  */
+
+tree
+callgraph_superedge::map_expr_from_caller_to_callee (tree caller_expr) const
+{
+  /* Is it an argument (actual param)?  If so, convert to
+     parameter (formal param).  */
+  tree parm = get_parm_for_arg (caller_expr);
+  if (parm)
+    return parm;
+  /* Otherwise try return value.  */
+  if (caller_expr == gimple_call_lhs (get_call_stmt ()))
+    return DECL_RESULT (get_callee_decl ());
+
+  return NULL_TREE;
+}
+
+/* Map callee_expr back to an expr within the caller, or return NULL_TREE.  */
+
+tree
+callgraph_superedge::map_expr_from_callee_to_caller (tree callee_expr) const
+{
+  /* If it's a parameter (formal param), get the argument (actual param).  */
+  if (TREE_CODE (callee_expr) == PARM_DECL)
+    return get_arg_for_parm (callee_expr);
+
+  /* Otherwise try return value.  */
+  if (callee_expr == DECL_RESULT (get_callee_decl ()))
+    return gimple_call_lhs (get_call_stmt ());
+
+  return NULL_TREE;
+}
diff --git a/gcc/analyzer/supergraph.h b/gcc/analyzer/supergraph.h
new file mode 100644
index 0000000..f887998
--- /dev/null
+++ b/gcc/analyzer/supergraph.h
@@ -0,0 +1,417 @@
+/* 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 callgraph_superedge;
+    class call_superedge;
+    class return_superedge;
+  class cfg_superedge;
+    class switch_cfg_superedge;
+class dot_annotator;
+
+class logger;
+
+/* 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
+
+/* Flags for controlling the appearance of .dot dumps.  */
+
+enum supergraph_dot_flags
+{
+  SUPERGRAPH_DOT_SHOW_BBS = (1 << 0)
+};
+
+
+/* FIXME.  */
+
+class supergraph
+{
+public:
+  supergraph (logger *logger);
+
+  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));
+  }
+
+  call_superedge *get_edge_for_call (cgraph_edge *edge) const
+  {
+    return (*const_cast <cgraph_edge_to_call_superedge_t &>
+	      (m_cgraph_edge_to_call_superedge).get (edge));
+  }
+
+  return_superedge *get_edge_for_return (cgraph_edge *edge) const
+  {
+    return (*const_cast <cgraph_edge_to_return_superedge_t &>
+	      (m_cgraph_edge_to_return_superedge).get (edge));
+  }
+
+  void dump_dot_to_pp (pretty_printer *pp, enum supergraph_dot_flags flags,
+		       const dot_annotator *a = NULL) const;
+  void dump_dot_to_file (FILE *fp, enum supergraph_dot_flags flags,
+			 const dot_annotator *a = NULL) const;
+  void dump_dot (const char *path, enum supergraph_dot_flags flags,
+		 const dot_annotator *a = NULL) const;
+
+  int num_nodes () const { return m_nodes.length (); }
+  int num_edges () const { return m_edges.length (); }
+
+  supernode *get_node_by_index (int idx) const
+  {
+    return m_nodes[idx];
+  }
+
+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);
+  call_superedge *add_call_superedge (supernode *src, supernode *dest,
+				      cgraph_edge *cedge);
+  return_superedge *add_return_superedge (supernode *src, supernode *dest,
+					  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;
+
+  typedef hash_map<cgraph_edge *, call_superedge *>
+    cgraph_edge_to_call_superedge_t;
+  cgraph_edge_to_call_superedge_t m_cgraph_edge_to_call_superedge;
+
+  typedef hash_map<cgraph_edge *, return_superedge *>
+    cgraph_edge_to_return_superedge_t;
+  cgraph_edge_to_return_superedge_t m_cgraph_edge_to_return_superedge;
+};
+
+/* 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, enum supergraph_dot_flags flags,
+		 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, enum supergraph_dot_flags flags) 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 callgraph_superedge *dyn_cast_callgraph_superedge () { return NULL; }
+  virtual const callgraph_superedge *dyn_cast_callgraph_superedge () const { return NULL; }
+  virtual call_superedge *dyn_cast_call_superedge () { return NULL; }
+  virtual const call_superedge *dyn_cast_call_superedge () const { return NULL; }
+  virtual return_superedge *dyn_cast_return_superedge () { return NULL; }
+  virtual const return_superedge *dyn_cast_return_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;
+
+  std::string to_str () 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 callgraph_superedge : public superedge
+{
+ public:
+  callgraph_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;
+
+  tree get_callee_decl () const;
+  tree get_caller_decl () const;
+  gcall *get_call_stmt () const { return m_cedge->call_stmt; }
+  tree get_arg_for_parm (tree parm) const;
+  tree get_parm_for_arg (tree arg) const;
+  tree map_expr_from_caller_to_callee (tree caller_expr) const;
+  tree map_expr_from_callee_to_caller (tree callee_expr) const;
+
+  cgraph_edge *m_cedge;
+};
+
+class call_superedge : public callgraph_superedge
+{
+ public:
+  call_superedge (supernode *src, supernode *dst, cgraph_edge *cedge)
+  : callgraph_superedge (src, dst, SUPEREDGE_CALL, cedge)
+  {}
+
+  callgraph_superedge *dyn_cast_callgraph_superedge () FINAL OVERRIDE
+  {
+    return this;
+  }
+  const callgraph_superedge *dyn_cast_callgraph_superedge () const
+    FINAL OVERRIDE
+  {
+    return this;
+  }
+
+  call_superedge *dyn_cast_call_superedge () FINAL OVERRIDE
+  {
+    return this;
+  }
+  const call_superedge *dyn_cast_call_superedge () const FINAL OVERRIDE
+  {
+    return this;
+  }
+
+  return_superedge *get_edge_for_return (const supergraph &sg) const
+  {
+    return sg.get_edge_for_return (m_cedge);
+  }
+};
+
+class return_superedge : public callgraph_superedge
+{
+ public:
+  return_superedge (supernode *src, supernode *dst, cgraph_edge *cedge)
+  : callgraph_superedge (src, dst, SUPEREDGE_RETURN, cedge)
+  {}
+
+  callgraph_superedge *dyn_cast_callgraph_superedge () FINAL OVERRIDE
+  {
+    return this;
+  }
+  const callgraph_superedge *dyn_cast_callgraph_superedge () const
+    FINAL OVERRIDE
+  { return this; }
+
+  return_superedge *dyn_cast_return_superedge () FINAL OVERRIDE { return this; }
+  const return_superedge *dyn_cast_return_superedge () const FINAL OVERRIDE
+  {
+    return this;
+  }
+
+  call_superedge *get_edge_for_call (const supergraph &sg) const
+  {
+    return sg.get_edge_for_call (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; }
+
+  tree get_phi_arg (const gphi *phi) const;
+
+  ::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 {}
+  virtual void add_stmt_annotations (pretty_printer *pp ATTRIBUTE_UNUSED,
+				     const gimple *stmt 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/c-family/c-format.c b/gcc/c-family/c-format.c
index 327fa29..dd54f22 100644
--- a/gcc/c-family/c-format.c
+++ b/gcc/c-family/c-format.c
@@ -3631,7 +3631,7 @@ class range_label_for_format_type_mismatch
 
     char *result = concat (text.m_buffer, p, NULL);
     text.maybe_free ();
-    return label_text (result, true);
+    return label_text::take (result);
   }
 
  private:
diff --git a/gcc/c-family/c-opts.c b/gcc/c-family/c-opts.c
index 2c22574..3aaadeb 100644
--- a/gcc/c-family/c-opts.c
+++ b/gcc/c-family/c-opts.c
@@ -168,11 +168,13 @@ c_diagnostic_finalizer (diagnostic_context *context,
 			diagnostic_info *diagnostic,
 			diagnostic_t)
 {
+  char *saved_prefix = pp_take_prefix (context->printer);
+  pp_set_prefix (context->printer, NULL);
   diagnostic_show_locus (context, diagnostic->richloc, diagnostic->kind);
   /* By default print macro expansion contexts in the diagnostic
      finalizer -- for tokens resulting from macro expansion.  */
   virt_loc_aware_diagnostic_finalizer (context, diagnostic);
-  pp_destroy_prefix (context->printer);
+  pp_set_prefix (context->printer, saved_prefix);
   pp_flush (context->printer);
 }
 
diff --git a/gcc/c-family/c-pretty-print.c b/gcc/c-family/c-pretty-print.c
index 46db469..909562d 100644
--- a/gcc/c-family/c-pretty-print.c
+++ b/gcc/c-family/c-pretty-print.c
@@ -2358,6 +2358,13 @@ c_pretty_printer::c_pretty_printer ()
   parameter_list            = pp_c_parameter_type_list;
 }
 
+/* FIXME.  */
+
+pretty_printer *
+c_pretty_printer::clone () const
+{
+  return new c_pretty_printer (*this);
+}
 
 /* Print the tree T in full, on file FILE.  */
 
diff --git a/gcc/c-family/c-pretty-print.h b/gcc/c-family/c-pretty-print.h
index 6084dcf..1100b76 100644
--- a/gcc/c-family/c-pretty-print.h
+++ b/gcc/c-family/c-pretty-print.h
@@ -50,6 +50,7 @@ typedef void (*c_pretty_print_fn) (c_pretty_printer *, tree);
 struct c_pretty_printer : pretty_printer
 {
   c_pretty_printer ();
+  pretty_printer *clone () const OVERRIDE;
 
   // Format string, possibly translated.
   void translate_string (const char *);
diff --git a/gcc/c/c-objc-common.c b/gcc/c/c-objc-common.c
index 21ca7e6..1509902 100644
--- a/gcc/c/c-objc-common.c
+++ b/gcc/c/c-objc-common.c
@@ -221,12 +221,12 @@ label_text
 range_label_for_type_mismatch::get_text (unsigned /*range_idx*/) const
 {
   if (m_labelled_type == NULL_TREE)
-    return label_text (NULL, false);
+    return label_text::borrow (NULL);
 
   c_pretty_printer cpp;
   bool quoted = false;
   print_type (&cpp, m_labelled_type, &quoted);
-  return label_text (xstrdup (pp_formatted_text (&cpp)), true);
+  return label_text::take (xstrdup (pp_formatted_text (&cpp)));
 }
 
 
diff --git a/gcc/common.opt b/gcc/common.opt
index 42fe7ec..13c25a1 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)
 
@@ -989,6 +992,14 @@ Align the start of loops.
 falign-loops=
 Common RejectNegative Joined Var(str_align_loops) Optimization
 
+fanalyzer-checker=
+Common Joined RejectNegative Var(flag_analyzer_checker)
+FIXME
+
+fanalyzer-impl=
+Common Joined RejectNegative UInteger Var(flag_analyzer_impl)
+FIXME
+
 fargument-alias
 Common Ignore
 Does nothing. Preserved for backward compatibility.
@@ -1314,6 +1325,33 @@ fdiagnostics-show-option
 Common Var(flag_diagnostics_show_option) Init(1)
 Amend appropriate diagnostic messages with the command line option that controls them.
 
+fdiagnostics-show-metadata
+Common Var(flag_diagnostics_show_metadata) Init(1)
+FIXME; TODO: add to docs
+
+fdiagnostics-path-summary-format=
+Common Joined RejectNegative Var(flag_diagnostics_path_summary_format) Enum(diagnostic_path_summary_format) Init(DPSF_INLINE_EVENTS)
+FIXME; TODO: docs
+
+Enum
+Name(diagnostic_path_summary_format) Type(int)
+
+EnumValue
+Enum(diagnostic_path_summary_format) String(none) Value(DPSF_NONE)
+
+EnumValue
+Enum(diagnostic_path_summary_format) String(simple) Value(DPSF_SIMPLE)
+
+EnumValue
+Enum(diagnostic_path_summary_format) String(event-summaries) Value(DPSF_EVENT_SUMMARIES)
+
+EnumValue
+Enum(diagnostic_path_summary_format) String(inline-events) Value(DPSF_INLINE_EVENTS)
+
+fdiagnostics-show-path-depths
+Common Var(flag_diagnostics_show_path_depths) Init(0)
+Show stack depths of events in paths. (TODO: add to docs)
+
 fdiagnostics-minimum-margin-width=
 Common Joined UInteger Var(diagnostics_minimum_margin_width) Init(6)
 Set minimum width of left margin of source code when showing source
@@ -1330,6 +1368,20 @@ 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-analyzer-summaries
+Common RejectNegative Var(flag_dump_analyzer_summaries)
+
+fanalyzer-verbosity=
+Common Joined UInteger Var(analyzer_verbosity) Init(1)
+FIXME
+
+fanalyzer-verbose-state-changes
+Common Var(flag_analyzer_verbose_state_changes) Init(0)
+FIXME
+
 fdump-final-insns
 Driver RejectNegative
 
diff --git a/gcc/cp/cxx-pretty-print.c b/gcc/cp/cxx-pretty-print.c
index 47eebd1..26fa9e1 100644
--- a/gcc/cp/cxx-pretty-print.c
+++ b/gcc/cp/cxx-pretty-print.c
@@ -3030,3 +3030,11 @@ cxx_pretty_printer::cxx_pretty_printer ()
   type_specifier_seq = (pp_fun) pp_cxx_type_specifier_seq;
   parameter_list = (pp_fun) pp_cxx_parameter_declaration_clause;
 }
+
+/* FIXME.  */
+
+pretty_printer *
+cxx_pretty_printer::clone () const
+{
+  return new cxx_pretty_printer (*this);
+}
diff --git a/gcc/cp/cxx-pretty-print.h b/gcc/cp/cxx-pretty-print.h
index aba3e84..742ac4a 100644
--- a/gcc/cp/cxx-pretty-print.h
+++ b/gcc/cp/cxx-pretty-print.h
@@ -33,6 +33,8 @@ struct cxx_pretty_printer : c_pretty_printer
 {
   cxx_pretty_printer ();
 
+  pretty_printer *clone () const OVERRIDE;
+
   void constant (tree);
   void id_expression (tree);
   void primary_expression (tree);
diff --git a/gcc/cp/error.c b/gcc/cp/error.c
index 3677213..b868c99 100644
--- a/gcc/cp/error.c
+++ b/gcc/cp/error.c
@@ -141,6 +141,11 @@ class cxx_format_postprocessor : public format_postprocessor
   : m_type_a (), m_type_b ()
   {}
 
+  format_postprocessor *clone() const FINAL OVERRIDE
+  {
+    return new cxx_format_postprocessor ();
+  }
+
   void handle (pretty_printer *pp) FINAL OVERRIDE;
 
   deferred_printed_type m_type_a;
@@ -4335,7 +4340,7 @@ label_text
 range_label_for_type_mismatch::get_text (unsigned /*range_idx*/) const
 {
   if (m_labelled_type == NULL_TREE)
-    return label_text (NULL, false);
+    return label_text::borrow (NULL);
 
   const bool verbose = false;
   const bool show_color = false;
@@ -4350,5 +4355,5 @@ range_label_for_type_mismatch::get_text (unsigned /*range_idx*/) const
 
   /* Both of the above return GC-allocated buffers, so the caller mustn't
      free them.  */
-  return label_text (const_cast <char *> (result), false);
+  return label_text::borrow (result);
 }
diff --git a/gcc/diagnostic-core.h b/gcc/diagnostic-core.h
index aeb285f..0770ac7 100644
--- a/gcc/diagnostic-core.h
+++ b/gcc/diagnostic-core.h
@@ -45,6 +45,9 @@ class auto_diagnostic_group
   ~auto_diagnostic_group ();
 };
 
+/* Forward decl.  */
+class diagnostic_metadata; /* See diagnostic-metadata.h.  */
+
 extern const char *progname;
 
 extern const char *trim_filename (const char *);
@@ -78,6 +81,8 @@ extern bool warning_at (location_t, int, const char *, ...)
     ATTRIBUTE_GCC_DIAG(3,4);
 extern bool warning_at (rich_location *, int, const char *, ...)
     ATTRIBUTE_GCC_DIAG(3,4);
+extern bool warning_at (rich_location *, const diagnostic_metadata &, const char *, ...)
+    ATTRIBUTE_GCC_DIAG(3,4);
 extern void error (const char *, ...) ATTRIBUTE_GCC_DIAG(1,2);
 extern void error_n (location_t, unsigned HOST_WIDE_INT, const char *,
 		     const char *, ...)
@@ -109,9 +114,10 @@ extern bool emit_diagnostic (diagnostic_t, rich_location *, int,
 			     const char *, ...) ATTRIBUTE_GCC_DIAG(4,5);
 extern bool emit_diagnostic_valist (diagnostic_t, location_t, int, const char *,
 				    va_list *) ATTRIBUTE_GCC_DIAG (4,0);
-extern bool emit_diagnostic_valist (diagnostic_t, rich_location *, int,
-				    const char *, va_list *)
-  ATTRIBUTE_GCC_DIAG (4,0);
+extern bool emit_diagnostic_valist (diagnostic_t, rich_location *, 
+				    const diagnostic_metadata *metadata,
+				    int, const char *, va_list *)
+  ATTRIBUTE_GCC_DIAG (5,0);
 extern bool seen_error (void);
 
 #ifdef BUFSIZ
diff --git a/gcc/diagnostic-format-json.cc b/gcc/diagnostic-format-json.cc
index e42386f..a8d7ebf 100644
--- a/gcc/diagnostic-format-json.cc
+++ b/gcc/diagnostic-format-json.cc
@@ -23,6 +23,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "system.h"
 #include "coretypes.h"
 #include "diagnostic.h"
+#include "diagnostic-metadata.h"
 #include "json.h"
 
 /* The top-level JSON array of pending diagnostics.  */
@@ -102,6 +103,20 @@ json_from_fixit_hint (const fixit_hint *hint)
   return fixit_obj;
 }
 
+/* FIXME.  */
+
+static json::object *
+json_from_metadata (const diagnostic_metadata *metadata)
+{
+  json::object *metadata_obj = new json::object ();
+
+  // TODO:
+  if (metadata->get_cwe ())
+    metadata_obj->set ("cwe", new json::number (metadata->get_cwe ()));
+
+  return metadata_obj;
+}
+
 /* No-op implementation of "begin_diagnostic" for JSON output.  */
 
 static void
@@ -217,6 +232,12 @@ json_end_diagnostic (diagnostic_context *context, diagnostic_info *diagnostic,
       json::value *path_value = context->make_json_for_path (context, path);
       diag_obj->set ("path", path_value);
     }
+
+  if (diagnostic->metadata)
+    {
+      json::object *metadata_obj = json_from_metadata (diagnostic->metadata);
+      diag_obj->set ("metadata", metadata_obj);
+    }
 }
 
 /* No-op implementation of "begin_group_cb" for JSON output.  */
@@ -275,6 +296,9 @@ diagnostic_output_format_init (diagnostic_context *context,
 	context->final_cb =  json_final_cb;
 	context->print_path = NULL; /* handled in json_end_diagnostic.  */
 
+	/* The metadata is handled in JSON format, rather than as text.  */
+	context->show_metadata = false;
+
 	/* The option is handled in JSON format, rather than as text.  */
 	context->show_option_requested = false;
 
diff --git a/gcc/diagnostic-metadata.h b/gcc/diagnostic-metadata.h
new file mode 100644
index 0000000..2103971
--- /dev/null
+++ b/gcc/diagnostic-metadata.h
@@ -0,0 +1,44 @@
+/* Arbitrary key-value pairs for a diagnostic.
+   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_DIAGNOSTIC_METADATA_H
+#define GCC_DIAGNOSTIC_METADATA_H
+
+/* FIXME.  */
+
+class diagnostic_metadata
+{
+ public:
+  diagnostic_metadata () : m_cwe (0) {}
+
+  void add_cwe (int cwe) { m_cwe = cwe; }
+  int get_cwe () const { return m_cwe; }
+
+  // TODO: how to store?
+ private:
+  int m_cwe;
+#if 0  
+  typedef hash_map <char *, char *,
+    simple_hashmap_traits<nofree_string_hash, char *> > map_t;
+  map_t m_map;
+#endif
+};
+
+#endif /* ! GCC_DIAGNOSTIC_METADATA_H */
diff --git a/gcc/diagnostic-path.h b/gcc/diagnostic-path.h
index cde0ea5..b337feb 100644
--- a/gcc/diagnostic-path.h
+++ b/gcc/diagnostic-path.h
@@ -55,36 +55,86 @@ along with GCC; see the file COPYING3.  If not see
     function; more complicated examples (such as interprocedural paths)
     might be printed as multiple notes.  */
 
-struct diagnostic_path
+/* Abstract base classes, describing events within a path, and the paths
+   themselves.  */
+
+/* One event within a diagnostic_path.  */
+
+class diagnostic_event
 {
-  /* One event within the path.  */
-  struct event
-  {
-    event (location_t loc, tree fndecl, int depth, const char *desc);
-    ~event ();
+ public:
+  virtual ~diagnostic_event () {}
+
+  virtual location_t get_location () const = 0;
+
+  virtual tree get_fndecl () const = 0;
 
-    location_t m_loc;
-    /* TODO: generalize this to a "backtrace", so that consumers can
-       decipher interprocedural sequences of events:  */
-    tree m_fndecl;
+  /* Stack depth, so that consumers can visualizes the interprocedural
+     calls, returns, and frame nesting.  */
+  virtual int get_stack_depth () const = 0;
 
-    /* Stack depth, so that consumers can visualizes the interprocedural
-       calls, returns, and frame nesting.  */
-    int m_depth;
+  /* Get a localized (and possibly colorized) description of this event.  */
+  virtual label_text get_desc () const = 0;
+};
+
+/* Abstract base class for getting at a sequence of events.  */
 
-    char *m_desc; // has been i18n-ed and formatted
-  };
+class diagnostic_path
+{
+ public:
+  virtual ~diagnostic_path () {}
+  virtual unsigned num_events () const = 0;
+  virtual const diagnostic_event & get_event (int idx) const = 0;
+
+  bool interprocedural_p () const;
+};
 
-  diagnostic_path ();
-  diagnostic_path (const diagnostic_path &other);
-  diagnostic_path& operator= (const diagnostic_path &other);
+/* Concrete subclasses.  */
 
-  ~diagnostic_path ();
+/* A simple implementation of diagnostic_event.  */
+
+class simple_diagnostic_event : public diagnostic_event
+{
+ public:
+  simple_diagnostic_event (location_t loc, tree fndecl, int depth,
+			   const char *desc);
+  ~simple_diagnostic_event ();
+
+  location_t get_location () const FINAL OVERRIDE { return m_loc; }
+  tree get_fndecl () const FINAL OVERRIDE { return m_fndecl; }
+  int get_stack_depth () const FINAL OVERRIDE { return m_depth; }
+  label_text get_desc () const FINAL OVERRIDE
+  {
+    return label_text::borrow (m_desc);
+  }
+
+ private:
+  location_t m_loc;
+  tree m_fndecl;
+  int m_depth;
+  char *m_desc; // has been i18n-ed and formatted
+};
+
+/* A simple implementation of diagnostic_path, as a vector of
+   simple_diagnostic_event instances.  */
+
+class simple_diagnostic_path : public diagnostic_path
+{
+ public:
+  simple_diagnostic_path (pretty_printer *event_pp)
+  : m_event_pp (event_pp) {}
+
+  unsigned num_events () const FINAL OVERRIDE;
+  const diagnostic_event & get_event (int idx) const FINAL OVERRIDE;
 
   void add_event (location_t loc, tree fndecl, int depth, const char *fmt, ...)
     ATTRIBUTE_GCC_DIAG(5,6);
 
-  auto_vec<event *> m_events;
+ private:
+  auto_delete_vec<simple_diagnostic_event> m_events;
+
+  /* (for use by add_event).  */
+  pretty_printer *m_event_pp;
 };
 
 // FIXME:
diff --git a/gcc/diagnostic-show-locus.c b/gcc/diagnostic-show-locus.c
index 9690536..bd7f3af 100644
--- a/gcc/diagnostic-show-locus.c
+++ b/gcc/diagnostic-show-locus.c
@@ -44,7 +44,7 @@ along with GCC; see the file COPYING3.  If not see
    The work is done by "class layout", which embeds and uses
    "class colorizer" and "class layout_range" to get things done.  */
 
-namespace {
+//namespace {
 
 /* The state at a given point of the source code, assuming that we're
    in a range: which range are we in, and whether we should draw a caret at
@@ -1031,6 +1031,8 @@ layout::print_gap_in_line_numbering ()
 {
   gcc_assert (m_show_line_numbers_p);
 
+  pp_emit_prefix (m_pp);
+
   for (int i = 0; i < m_linenum_width + 1; i++)
     pp_character (m_pp, '.');
 
@@ -1257,6 +1259,8 @@ layout::print_source_line (linenum_type row, const char *line, int line_width,
 							   line_width);
   line += m_x_offset;
 
+  pp_emit_prefix (m_pp);
+
   if (m_show_line_numbers_p)
     {
       int width = num_digits (row);
@@ -1337,6 +1341,7 @@ layout::should_print_annotation_line_p (linenum_type row) const
 void
 layout::start_annotation_line (char margin_char) const
 {
+  pp_emit_prefix (m_pp);
   if (m_show_line_numbers_p)
     {
       /* Print the margin.  If MARGIN_CHAR != ' ', then print up to 3
@@ -2230,7 +2235,7 @@ layout::print_line (linenum_type row)
   print_trailing_fixits (row);
 }
 
-} /* End of anonymous namespace.  */
+//} /* End of anonymous namespace.  */
 
 /* If LOC is within the spans of lines that will already be printed for
    this gcc_rich_location, then add it as a secondary location and return true.
@@ -2290,9 +2295,6 @@ diagnostic_show_locus (diagnostic_context * context,
 
   context->last_location = loc;
 
-  char *saved_prefix = pp_take_prefix (context->printer);
-  pp_set_prefix (context->printer, NULL);
-
   layout layout (context, richloc, diagnostic_kind);
   for (int line_span_idx = 0; line_span_idx < layout.get_num_line_spans ();
        line_span_idx++)
@@ -2320,8 +2322,6 @@ diagnostic_show_locus (diagnostic_context * context,
 	   row <= last_line; row++)
 	layout.print_line (row);
     }
-
-  pp_set_prefix (context->printer, saved_prefix);
 }
 
 #if CHECKING_P
@@ -2454,23 +2454,93 @@ test_one_liner_fixit_insert_after ()
 		pp_formatted_text (dc.printer));
 }
 
-/* Removal fix-it hint: removal of the ".field". */
+/* Removal fix-it hint: removal of the ".field".
+   Also verify the interaction of pp_set_prefix with rulers and
+   fix-it hints.  */
 
 static void
 test_one_liner_fixit_remove ()
 {
-  test_diagnostic_context dc;
   location_t start = linemap_position_for_column (line_table, 10);
   location_t finish = linemap_position_for_column (line_table, 15);
   location_t dot = make_location (start, start, finish);
   rich_location richloc (line_table, dot);
   richloc.add_fixit_remove ();
-  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-  ASSERT_STREQ ("\n"
-		" foo = bar.field;\n"
-		"          ^~~~~~\n"
-		"          ------\n",
-		pp_formatted_text (dc.printer));
+
+  /* Normal.  */
+  {
+    test_diagnostic_context dc;
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ ("\n"
+		  " foo = bar.field;\n"
+		  "          ^~~~~~\n"
+		  "          ------\n",
+		  pp_formatted_text (dc.printer));
+  }
+
+  /* Test of adding a prefix.  */
+  {
+    test_diagnostic_context dc;
+    pp_prefixing_rule (dc.printer) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
+    pp_set_prefix (dc.printer, xstrdup ("TEST PREFIX:"));
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ ("\n"
+		  "TEST PREFIX: foo = bar.field;\n"
+		  "TEST PREFIX:          ^~~~~~\n"
+		  "TEST PREFIX:          ------\n",
+		  pp_formatted_text (dc.printer));
+  }
+
+  /* Normal, with ruler.  */
+  {
+    test_diagnostic_context dc;
+    dc.show_ruler_p = true;
+    dc.caret_max_width = 104;
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ ("\n"
+		  "          0         0         0         0         0         0         0         0         0         1    \n"
+		  "          1         2         3         4         5         6         7         8         9         0    \n"
+		  " 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234\n"
+		  " foo = bar.field;\n"
+		  "          ^~~~~~\n"
+		  "          ------\n",
+		  pp_formatted_text (dc.printer));
+  }
+
+  /* Test of adding a prefix, with ruler.  */
+  {
+    test_diagnostic_context dc;
+    dc.show_ruler_p = true;
+    dc.caret_max_width = 50;
+    pp_prefixing_rule (dc.printer) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
+    pp_set_prefix (dc.printer, xstrdup ("TEST PREFIX:"));
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ ("\n"
+		  "TEST PREFIX:          1         2         3         4         5\n"
+		  "TEST PREFIX: 12345678901234567890123456789012345678901234567890\n"
+		  "TEST PREFIX: foo = bar.field;\n"
+		  "TEST PREFIX:          ^~~~~~\n"
+		  "TEST PREFIX:          ------\n",
+		  pp_formatted_text (dc.printer));
+  }
+
+  /* Test of adding a prefix, with ruler and line numbers.  */
+  {
+    test_diagnostic_context dc;
+    dc.show_ruler_p = true;
+    dc.caret_max_width = 50;
+    dc.show_line_numbers_p = true;
+    pp_prefixing_rule (dc.printer) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
+    pp_set_prefix (dc.printer, xstrdup ("TEST PREFIX:"));
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ ("\n"
+		  "TEST PREFIX:      |          1         2         3         4         5\n"
+		  "TEST PREFIX:      | 12345678901234567890123456789012345678901234567890\n"
+		  "TEST PREFIX:    1 | foo = bar.field;\n"
+		  "TEST PREFIX:      |          ^~~~~~\n"
+		  "TEST PREFIX:      |          ------\n",
+		  pp_formatted_text (dc.printer));
+  }
 }
 
 /* Replace fix-it hint: replacing "field" with "m_field". */
diff --git a/gcc/diagnostic.c b/gcc/diagnostic.c
index dd4712d..10511f8 100644
--- a/gcc/diagnostic.c
+++ b/gcc/diagnostic.c
@@ -31,6 +31,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "backtrace.h"
 #include "diagnostic.h"
 #include "diagnostic-color.h"
+#include "diagnostic-metadata.h"
 #include "diagnostic-path.h"
 #include "edit-context.h"
 #include "selftest.h"
@@ -50,11 +51,13 @@ along with GCC; see the file COPYING3.  If not see
 #define permissive_error_option(DC) ((DC)->opt_permissive)
 
 /* Prototypes.  */
-static bool diagnostic_impl (rich_location *, int, const char *,
-			     va_list *, diagnostic_t) ATTRIBUTE_GCC_DIAG(3,0);
-static bool diagnostic_n_impl (rich_location *, int, unsigned HOST_WIDE_INT,
+static bool diagnostic_impl (rich_location *, const diagnostic_metadata *,
+			     int, const char *,
+			     va_list *, diagnostic_t) ATTRIBUTE_GCC_DIAG(4,0);
+static bool diagnostic_n_impl (rich_location *, const diagnostic_metadata *,
+			       int, unsigned HOST_WIDE_INT,
 			       const char *, const char *, va_list *,
-			       diagnostic_t) ATTRIBUTE_GCC_DIAG(5,0);
+			       diagnostic_t) ATTRIBUTE_GCC_DIAG(6,0);
 
 static void error_recursion (diagnostic_context *) ATTRIBUTE_NORETURN;
 static void real_abort (void) ATTRIBUTE_NORETURN;
@@ -175,6 +178,9 @@ diagnostic_initialize (diagnostic_context *context, int n_opts)
   diagnostic_set_caret_max_width (context, pp_line_cutoff (context->printer));
   for (i = 0; i < rich_location::STATICALLY_ALLOCATED_RANGES; i++)
     context->caret_chars[i] = '^';
+  context->show_metadata = false;
+  context->path_summary_format = DPSF_NONE;
+  context->show_path_depths = false;
   context->show_option_requested = false;
   context->abort_on_error = false;
   context->show_column = false;
@@ -291,6 +297,7 @@ diagnostic_set_info_translated (diagnostic_info *diagnostic, const char *msg,
   diagnostic->message.format_spec = msg;
   diagnostic->message.m_richloc = richloc;
   diagnostic->richloc = richloc;
+  diagnostic->metadata = NULL;
   diagnostic->kind = kind;
   diagnostic->option_index = 0;
 }
@@ -659,6 +666,22 @@ diagnostic_show_any_path (diagnostic_context *context,
     context->print_path (context, path);
 }
 
+/* FIXME.  */
+
+bool
+diagnostic_path::interprocedural_p () const
+{
+  const unsigned num = num_events ();
+  for (unsigned i = 0; i < num; i++)
+    {
+      if (get_event (i).get_fndecl () != get_event (0).get_fndecl ())
+	return true;
+      if (get_event (i).get_stack_depth () != get_event (0).get_stack_depth ())
+	return true;
+    }
+  return false;
+}
+
 void
 default_diagnostic_starter (diagnostic_context *context,
 			    diagnostic_info *diagnostic)
@@ -683,8 +706,10 @@ default_diagnostic_finalizer (diagnostic_context *context,
 			      diagnostic_info *diagnostic,
 			      diagnostic_t)
 {
+  char *saved_prefix = pp_take_prefix (context->printer);
+  pp_set_prefix (context->printer, NULL);
   diagnostic_show_locus (context, diagnostic->richloc, diagnostic->kind);
-  pp_destroy_prefix (context->printer);
+  pp_set_prefix (context->printer, saved_prefix);
   pp_flush (context->printer);
 }
 
@@ -895,6 +920,43 @@ update_effective_level_from_pragmas (diagnostic_context *context,
   return diag_class;
 }
 
+/* FIXME.  */
+
+static char *
+get_cwe_url (int cwe)
+{
+  return xasprintf ("https://cwe.mitre.org/data/definitions/%i.html", cwe);
+}
+
+/* FIXME.  */
+
+static void
+print_any_metadata (diagnostic_context *context,
+		    const diagnostic_info *diagnostic)
+{
+  if (diagnostic->metadata == NULL)
+    return;
+
+  // FIXME:
+  int cwe = diagnostic->metadata->get_cwe ();
+  if (cwe)
+    {
+      pretty_printer *pp = context->printer;
+      char *saved_prefix = pp_take_prefix (context->printer);
+      pp_string (pp, " [");
+      pp_string (pp, colorize_start (pp_show_color (pp),
+				     diagnostic_kind_color[diagnostic->kind]));
+      char *cwe_url = get_cwe_url (cwe);
+      pp_begin_url (pp, cwe_url);
+      free (cwe_url);
+      pp_printf (pp, "CWE-%i", cwe);
+      pp_set_prefix (context->printer, saved_prefix);
+      pp_end_url (pp);
+      pp_string (pp, colorize_stop (pp_show_color (pp)));
+      pp_character (pp, ']');
+    }
+}
+
 /* Print any metadata about the option used to control DIAGNOSTIC to CONTEXT's
    printer, e.g. " [-Werror=uninitialized]".
    Subroutine of diagnostic_report_diagnostic.  */
@@ -1054,6 +1116,8 @@ diagnostic_report_diagnostic (diagnostic_context *context,
   pp_format (context->printer, &diagnostic->message);
   (*diagnostic_starter (context)) (context, diagnostic);
   pp_output_formatted_text (context->printer);
+  if (context->show_metadata)
+    print_any_metadata (context, diagnostic);
   if (context->show_option_requested)
     print_option_information (context, diagnostic, orig_diag_kind);
   (*diagnostic_finalizer (context)) (context, diagnostic, orig_diag_kind);
@@ -1180,8 +1244,8 @@ diagnostic_append_note (diagnostic_context *context,
    permerror, error, error_at, error_at, sorry, fatal_error, internal_error,
    and internal_error_no_backtrace, as documented and defined below.  */
 static bool
-diagnostic_impl (rich_location *richloc, int opt,
-		 const char *gmsgid,
+diagnostic_impl (rich_location *richloc, const diagnostic_metadata *metadata,
+		 int opt, const char *gmsgid,
 		 va_list *ap, diagnostic_t kind)
 {
   diagnostic_info diagnostic;
@@ -1197,13 +1261,15 @@ diagnostic_impl (rich_location *richloc, int opt,
       if (kind == DK_WARNING || kind == DK_PEDWARN)
 	diagnostic.option_index = opt;
     }
+  diagnostic.metadata = metadata;
   return diagnostic_report_diagnostic (global_dc, &diagnostic);
 }
 
 /* Implement inform_n, warning_n, and error_n, as documented and
    defined below.  */
 static bool
-diagnostic_n_impl (rich_location *richloc, int opt, unsigned HOST_WIDE_INT n,
+diagnostic_n_impl (rich_location *richloc, const diagnostic_metadata *metadata,
+		   int opt, unsigned HOST_WIDE_INT n,
 		   const char *singular_gmsgid,
 		   const char *plural_gmsgid,
 		   va_list *ap, diagnostic_t kind)
@@ -1223,6 +1289,7 @@ diagnostic_n_impl (rich_location *richloc, int opt, unsigned HOST_WIDE_INT n,
   diagnostic_set_info_translated (&diagnostic, text, ap, richloc, kind);
   if (kind == DK_WARNING)
     diagnostic.option_index = opt;
+  diagnostic.metadata = metadata;
   return diagnostic_report_diagnostic (global_dc, &diagnostic);
 }
 
@@ -1236,7 +1303,7 @@ emit_diagnostic (diagnostic_t kind, location_t location, int opt,
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, location);
-  bool ret = diagnostic_impl (&richloc, opt, gmsgid, &ap, kind);
+  bool ret = diagnostic_impl (&richloc, NULL, opt, gmsgid, &ap, kind);
   va_end (ap);
   return ret;
 }
@@ -1250,7 +1317,7 @@ emit_diagnostic (diagnostic_t kind, rich_location *richloc, int opt,
   auto_diagnostic_group d;
   va_list ap;
   va_start (ap, gmsgid);
-  bool ret = diagnostic_impl (richloc, opt, gmsgid, &ap, kind);
+  bool ret = diagnostic_impl (richloc, NULL, opt, gmsgid, &ap, kind);
   va_end (ap);
   return ret;
 }
@@ -1262,16 +1329,18 @@ emit_diagnostic_valist (diagnostic_t kind, location_t location, int opt,
 			const char *gmsgid, va_list *ap)
 {
   rich_location richloc (line_table, location);
-  return diagnostic_impl (&richloc, opt, gmsgid, ap, kind);
+  return diagnostic_impl (&richloc, NULL, opt, gmsgid, ap, kind);
 }
 
 /* Wrapper around diagnostic_impl taking a va_list parameter.  */
 
 bool
-emit_diagnostic_valist (diagnostic_t kind, rich_location *richloc, int opt,
+emit_diagnostic_valist (diagnostic_t kind, rich_location *richloc,
+			const diagnostic_metadata *metadata,
+			int opt,
 			const char *gmsgid, va_list *ap)
 {
-  return diagnostic_impl (richloc, opt, gmsgid, ap, kind);
+  return diagnostic_impl (richloc, metadata, opt, gmsgid, ap, kind);
 }
 
 /* An informative note at LOCATION.  Use this for additional details on an error
@@ -1283,7 +1352,7 @@ inform (location_t location, const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, location);
-  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_NOTE);
+  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_NOTE);
   va_end (ap);
 }
 
@@ -1296,7 +1365,7 @@ inform (rich_location *richloc, const char *gmsgid, ...)
   auto_diagnostic_group d;
   va_list ap;
   va_start (ap, gmsgid);
-  diagnostic_impl (richloc, -1, gmsgid, &ap, DK_NOTE);
+  diagnostic_impl (richloc, NULL, -1, gmsgid, &ap, DK_NOTE);
   va_end (ap);
 }
 
@@ -1310,7 +1379,7 @@ inform_n (location_t location, unsigned HOST_WIDE_INT n,
   va_start (ap, plural_gmsgid);
   auto_diagnostic_group d;
   rich_location richloc (line_table, location);
-  diagnostic_n_impl (&richloc, -1, n, singular_gmsgid, plural_gmsgid,
+  diagnostic_n_impl (&richloc, NULL, -1, n, singular_gmsgid, plural_gmsgid,
 		     &ap, DK_NOTE);
   va_end (ap);
 }
@@ -1325,7 +1394,7 @@ warning (int opt, const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, input_location);
-  bool ret = diagnostic_impl (&richloc, opt, gmsgid, &ap, DK_WARNING);
+  bool ret = diagnostic_impl (&richloc, NULL, opt, gmsgid, &ap, DK_WARNING);
   va_end (ap);
   return ret;
 }
@@ -1341,7 +1410,7 @@ warning_at (location_t location, int opt, const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, location);
-  bool ret = diagnostic_impl (&richloc, opt, gmsgid, &ap, DK_WARNING);
+  bool ret = diagnostic_impl (&richloc, NULL, opt, gmsgid, &ap, DK_WARNING);
   va_end (ap);
   return ret;
 }
@@ -1356,7 +1425,25 @@ warning_at (rich_location *richloc, int opt, const char *gmsgid, ...)
   auto_diagnostic_group d;
   va_list ap;
   va_start (ap, gmsgid);
-  bool ret = diagnostic_impl (richloc, opt, gmsgid, &ap, DK_WARNING);
+  bool ret = diagnostic_impl (richloc, NULL, opt, gmsgid, &ap, DK_WARNING);
+  va_end (ap);
+  return ret;
+}
+
+/* FIXME.  */
+
+bool
+warning_at (rich_location *richloc, const diagnostic_metadata &metadata,
+	    const char *gmsgid, ...)
+{
+  gcc_assert (richloc);
+
+  auto_diagnostic_group d;
+  va_list ap;
+  va_start (ap, gmsgid);
+  bool ret
+    = diagnostic_impl (richloc, &metadata, 0, gmsgid, &ap,
+		       DK_WARNING);
   va_end (ap);
   return ret;
 }
@@ -1372,7 +1459,7 @@ warning_n (rich_location *richloc, int opt, unsigned HOST_WIDE_INT n,
   auto_diagnostic_group d;
   va_list ap;
   va_start (ap, plural_gmsgid);
-  bool ret = diagnostic_n_impl (richloc, opt, n,
+  bool ret = diagnostic_n_impl (richloc, NULL, opt, n,
 				singular_gmsgid, plural_gmsgid,
 				&ap, DK_WARNING);
   va_end (ap);
@@ -1391,7 +1478,7 @@ warning_n (location_t location, int opt, unsigned HOST_WIDE_INT n,
   va_list ap;
   va_start (ap, plural_gmsgid);
   rich_location richloc (line_table, location);
-  bool ret = diagnostic_n_impl (&richloc, opt, n,
+  bool ret = diagnostic_n_impl (&richloc, NULL, opt, n,
 				singular_gmsgid, plural_gmsgid,
 				&ap, DK_WARNING);
   va_end (ap);
@@ -1418,7 +1505,7 @@ pedwarn (location_t location, int opt, const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, location);
-  bool ret = diagnostic_impl (&richloc, opt, gmsgid, &ap, DK_PEDWARN);
+  bool ret = diagnostic_impl (&richloc, NULL, opt, gmsgid, &ap, DK_PEDWARN);
   va_end (ap);
   return ret;
 }
@@ -1433,7 +1520,7 @@ pedwarn (rich_location *richloc, int opt, const char *gmsgid, ...)
   auto_diagnostic_group d;
   va_list ap;
   va_start (ap, gmsgid);
-  bool ret = diagnostic_impl (richloc, opt, gmsgid, &ap, DK_PEDWARN);
+  bool ret = diagnostic_impl (richloc, NULL, opt, gmsgid, &ap, DK_PEDWARN);
   va_end (ap);
   return ret;
 }
@@ -1452,7 +1539,7 @@ permerror (location_t location, const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, location);
-  bool ret = diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_PERMERROR);
+  bool ret = diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_PERMERROR);
   va_end (ap);
   return ret;
 }
@@ -1467,7 +1554,7 @@ permerror (rich_location *richloc, const char *gmsgid, ...)
   auto_diagnostic_group d;
   va_list ap;
   va_start (ap, gmsgid);
-  bool ret = diagnostic_impl (richloc, -1, gmsgid, &ap, DK_PERMERROR);
+  bool ret = diagnostic_impl (richloc, NULL, -1, gmsgid, &ap, DK_PERMERROR);
   va_end (ap);
   return ret;
 }
@@ -1481,7 +1568,7 @@ error (const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, input_location);
-  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_ERROR);
+  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_ERROR);
   va_end (ap);
 }
 
@@ -1495,7 +1582,7 @@ error_n (location_t location, unsigned HOST_WIDE_INT n,
   va_list ap;
   va_start (ap, plural_gmsgid);
   rich_location richloc (line_table, location);
-  diagnostic_n_impl (&richloc, -1, n, singular_gmsgid, plural_gmsgid,
+  diagnostic_n_impl (&richloc, NULL, -1, n, singular_gmsgid, plural_gmsgid,
 		     &ap, DK_ERROR);
   va_end (ap);
 }
@@ -1508,7 +1595,7 @@ error_at (location_t loc, const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, loc);
-  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_ERROR);
+  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_ERROR);
   va_end (ap);
 }
 
@@ -1522,7 +1609,7 @@ error_at (rich_location *richloc, const char *gmsgid, ...)
   auto_diagnostic_group d;
   va_list ap;
   va_start (ap, gmsgid);
-  diagnostic_impl (richloc, -1, gmsgid, &ap, DK_ERROR);
+  diagnostic_impl (richloc, NULL, -1, gmsgid, &ap, DK_ERROR);
   va_end (ap);
 }
 
@@ -1536,7 +1623,7 @@ sorry (const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, input_location);
-  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_SORRY);
+  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_SORRY);
   va_end (ap);
 }
 
@@ -1548,7 +1635,7 @@ sorry_at (location_t loc, const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, loc);
-  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_SORRY);
+  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_SORRY);
   va_end (ap);
 }
 
@@ -1570,7 +1657,7 @@ fatal_error (location_t loc, const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, loc);
-  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_FATAL);
+  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_FATAL);
   va_end (ap);
 
   gcc_unreachable ();
@@ -1587,7 +1674,7 @@ internal_error (const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, input_location);
-  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_ICE);
+  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_ICE);
   va_end (ap);
 
   gcc_unreachable ();
@@ -1603,7 +1690,7 @@ internal_error_no_backtrace (const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, input_location);
-  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_ICE_NOBT);
+  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_ICE_NOBT);
   va_end (ap);
 
   gcc_unreachable ();
@@ -1684,6 +1771,7 @@ auto_diagnostic_group::~auto_diagnostic_group ()
     }
 }
 
+#if 0
 /* struct diagnostic_path */
 
 diagnostic_path::diagnostic_path () : m_events ()
@@ -1721,19 +1809,33 @@ diagnostic_path::~diagnostic_path ()
     delete n;
 }
 
+#endif
+
+/* FIXME.  */
+
+unsigned
+simple_diagnostic_path::num_events () const
+{
+  return m_events.length ();
+}
+
 /* FIXME.  */
 
+const diagnostic_event &
+simple_diagnostic_path::get_event (int idx) const
+{
+  return *m_events[idx];
+}
+
+/* FIXME.  */
+// Use m_context's printer to format the events.
+
 void
-diagnostic_path::add_event (location_t loc, tree fndecl, int depth, const char *fmt, ...)
+simple_diagnostic_path::add_event (location_t loc, tree fndecl, int depth,
+				   const char *fmt, ...)
 {
-  /* FIXME: use global_dc's pp.  */
-#if 1
-  pretty_printer *pp = global_dc->printer;
+  pretty_printer *pp = m_event_pp;
   pp_clear_output_area (pp);
-#else
-  pretty_printer tmp_pp;
-  pretty_printer *pp = &tmp_pp;
-#endif
 
   text_info ti;
   rich_location rich_loc (line_table, UNKNOWN_LOCATION);
@@ -1748,34 +1850,29 @@ diagnostic_path::add_event (location_t loc, tree fndecl, int depth, const char *
   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);
 
-  event *new_event = new event (loc, fndecl, depth, pp_formatted_text (pp));
+  simple_diagnostic_event *new_event
+    = new simple_diagnostic_event (loc, fndecl, depth, pp_formatted_text (pp));
   m_events.safe_push (new_event);
-#if 0  
-  fprintf (stderr, "DBG: added to path %p: %s\n", this, new_event->m_desc);
-#endif
 
   pp_clear_output_area (pp);
 }
 
-/* struct diagnostic_path::event.  */
+/* struct simple_diagnostic_event.  */
 
-diagnostic_path::event::event (location_t loc, tree fndecl, int depth,
-			       const char *desc)
+simple_diagnostic_event::simple_diagnostic_event (location_t loc,
+						  tree fndecl,
+						  int depth,
+						  const char *desc)
 : m_loc (loc), m_fndecl (fndecl), m_depth (depth), m_desc (xstrdup (desc))
 {
 }
 
-diagnostic_path::event::~event ()
+simple_diagnostic_event::~simple_diagnostic_event ()
 {
   free (m_desc);
 }
diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
index cb4a1cf..2951177 100644
--- a/gcc/diagnostic.h
+++ b/gcc/diagnostic.h
@@ -35,6 +35,22 @@ enum diagnostics_output_format
   DIAGNOSTICS_OUTPUT_FORMAT_JSON
 };
 
+/* FIXME.  */
+enum diagnostic_path_summary_format
+{
+  /* FIXME.  */
+  DPSF_NONE,
+
+  /* FIXME.  */
+  DPSF_SIMPLE,
+
+  /* FIXME.  */
+  DPSF_EVENT_SUMMARIES,
+
+  /* FIXME.  */
+  DPSF_INLINE_EVENTS
+};
+
 /* A diagnostic is described by the MESSAGE to send, the FILE and LINE of
    its context and its KIND (ice, error, warning, note, ...)  See complete
    list in diagnostic.def.  */
@@ -46,6 +62,9 @@ struct diagnostic_info
   /* The location at which the diagnostic is to be reported.  */
   rich_location *richloc;
 
+  /* FIXME.  */
+  const diagnostic_metadata *metadata;
+
   /* Auxiliary data for client.  */
   void *x_data;
   /* The kind of diagnostic it is about.  */
@@ -127,6 +146,15 @@ struct diagnostic_context
   /* Character used for caret diagnostics.  */
   char caret_chars[rich_location::STATICALLY_ALLOCATED_RANGES];
 
+  /* FIXME.  */
+  bool show_metadata;
+
+  /* FIXME.  */
+  enum diagnostic_path_summary_format path_summary_format;
+
+  /* FIXME.  */
+  bool show_path_depths;
+
   /* True if we should print the command line option which controls
      each diagnostic, if known.  */
   bool show_option_requested;
diff --git a/gcc/dumpfile.c b/gcc/dumpfile.c
index c92bba8..5e258e4 100644
--- a/gcc/dumpfile.c
+++ b/gcc/dumpfile.c
@@ -808,7 +808,7 @@ dump_pretty_printer::emit_items (optinfo *dest)
   const char **args = chunk_array->args;
 
   gcc_assert (buffer->obstack == &buffer->formatted_obstack);
-  gcc_assert (buffer->line_length == 0);
+  //gcc_assert (buffer->line_length == 0);
 
   unsigned stashed_item_idx = 0;
   for (unsigned chunk = 0; args[chunk]; chunk++)
diff --git a/gcc/fortran/error.c b/gcc/fortran/error.c
index 68a2791..6e8e668 100644
--- a/gcc/fortran/error.c
+++ b/gcc/fortran/error.c
@@ -1109,6 +1109,7 @@ gfc_diagnostic_starter (diagnostic_context *context,
       free (locus_prefix);
       /* Fortran uses an empty line between locus and caret line.  */
       pp_newline (context->printer);
+      pp_set_prefix (context->printer, NULL);
       diagnostic_show_locus (context, diagnostic->richloc, diagnostic->kind);
       /* If the caret line was shown, the prefix does not contain the
 	 locus.  */
diff --git a/gcc/gcc-rich-location.c b/gcc/gcc-rich-location.c
index 82d4f52..071463e 100644
--- a/gcc/gcc-rich-location.c
+++ b/gcc/gcc-rich-location.c
@@ -196,7 +196,7 @@ maybe_range_label_for_tree_type_mismatch::get_text (unsigned range_idx) const
 {
   if (m_expr == NULL_TREE
       || !EXPR_P (m_expr))
-    return label_text (NULL, false);
+    return label_text::borrow (NULL);
   tree expr_type = TREE_TYPE (m_expr);
 
   tree other_type = NULL_TREE;
diff --git a/gcc/gcc-rich-location.h b/gcc/gcc-rich-location.h
index fee4af7..b4d47d4 100644
--- a/gcc/gcc-rich-location.h
+++ b/gcc/gcc-rich-location.h
@@ -113,7 +113,7 @@ class text_range_label : public range_label
 
   label_text get_text (unsigned /*range_idx*/) const FINAL OVERRIDE
   {
-    return label_text (const_cast <char *> (m_text), false);
+    return label_text::borrow (m_text);
   }
 
  private:
diff --git a/gcc/gcc.c b/gcc/gcc.c
index 0a22d2a..18176af 100644
--- a/gcc/gcc.c
+++ b/gcc/gcc.c
@@ -4253,6 +4253,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/opts.c b/gcc/opts.c
index f0a96b9..cb962e4 100644
--- a/gcc/opts.c
+++ b/gcc/opts.c
@@ -2400,6 +2400,18 @@ common_handle_option (struct gcc_options *opts,
       dc->parseable_fixits_p = value;
       break;
 
+    case OPT_fdiagnostics_show_metadata:
+      dc->show_metadata = value;
+      break;
+
+    case OPT_fdiagnostics_path_summary_format_:
+      dc->path_summary_format = (enum diagnostic_path_summary_format)value;
+      break;
+
+    case OPT_fdiagnostics_show_path_depths:
+      dc->show_path_depths = value;
+      break;
+
     case OPT_fdiagnostics_show_option:
       dc->show_option_requested = value;
       break;
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/pretty-print.c b/gcc/pretty-print.c
index a2d6f04..950e263 100644
--- a/gcc/pretty-print.c
+++ b/gcc/pretty-print.c
@@ -1192,6 +1192,7 @@ pp_format (pretty_printer *pp, text_info *text)
   /* Set output to the argument obstack, and switch line-wrapping and
      prefixing off.  */
   buffer->obstack = &buffer->chunk_obstack;
+  const int old_line_length = buffer->line_length;
   old_wrapping_mode = pp_set_verbatim_wrapping (pp);
 
   /* Second phase.  Replace each formatter with the formatted text it
@@ -1412,7 +1413,7 @@ pp_format (pretty_printer *pp, text_info *text)
 
   /* Revert to normal obstack and wrapping mode.  */
   buffer->obstack = &buffer->formatted_obstack;
-  buffer->line_length = 0;
+  buffer->line_length = old_line_length;
   pp_wrapping_mode (pp) = old_wrapping_mode;
   pp_clear_state (pp);
 }
@@ -1427,7 +1428,7 @@ pp_output_formatted_text (pretty_printer *pp)
   const char **args = chunk_array->args;
 
   gcc_assert (buffer->obstack == &buffer->formatted_obstack);
-  gcc_assert (buffer->line_length == 0);
+  //gcc_assert (buffer->line_length == 0);
 
   /* This is a third phase, first 2 phases done in pp_format_args.
      Now we actually print it.  */
@@ -1588,6 +1589,30 @@ pretty_printer::pretty_printer (int maximum_length)
   pp_set_prefix (this, NULL);
 }
 
+pretty_printer::pretty_printer (const pretty_printer &other)
+: buffer (new (XCNEW (output_buffer)) output_buffer ()),
+  prefix (),
+  padding (other.padding),
+  maximum_length (other.maximum_length),
+  indent_skip (other.indent_skip),
+  wrapping (other.wrapping),
+  format_decoder (other.format_decoder),
+  m_format_postprocessor (NULL),
+  emitted_prefix (other.emitted_prefix),
+  need_newline (other.need_newline),
+  translate_identifiers (other.translate_identifiers),
+  show_color (other.show_color),
+  show_urls (other.show_urls)
+{
+  pp_line_cutoff (this) = maximum_length;
+  /* By default, we emit prefixes once per message.  */
+  pp_prefixing_rule (this) = pp_prefixing_rule (&other);
+  pp_set_prefix (this, NULL);
+
+  if (other.m_format_postprocessor)
+    m_format_postprocessor = other.m_format_postprocessor->clone ();
+}
+
 pretty_printer::~pretty_printer ()
 {
   if (m_format_postprocessor)
@@ -1597,6 +1622,14 @@ pretty_printer::~pretty_printer ()
   free (prefix);
 }
 
+/* FIXME.  */
+
+pretty_printer *
+pretty_printer::clone () const
+{
+  return new pretty_printer (*this);
+}
+
 /* Append a string delimited by START and END to the output area of
    PRETTY-PRINTER.  No line wrapping is done.  However, if beginning a
    new line then emit PRETTY-PRINTER's prefix and skip any leading
diff --git a/gcc/pretty-print.h b/gcc/pretty-print.h
index 07cd391..9a8b81a 100644
--- a/gcc/pretty-print.h
+++ b/gcc/pretty-print.h
@@ -191,6 +191,7 @@ class format_postprocessor
 {
  public:
   virtual ~format_postprocessor () {}
+  virtual format_postprocessor *clone() const = 0;
   virtual void handle (pretty_printer *) = 0;
 };
 
@@ -219,9 +220,12 @@ struct pretty_printer
   /* Default construct a pretty printer with specified
      maximum line length cut off limit.  */
   explicit pretty_printer (int = 0);
+  explicit pretty_printer (const pretty_printer &other);
 
   virtual ~pretty_printer ();
 
+  virtual pretty_printer *clone () const;
+
   /* Where we print external representation of ENTITY.  */
   output_buffer *buffer;
 
diff --git a/gcc/selftest-run-tests.c b/gcc/selftest-run-tests.c
index c2e0ee2..3fb31ab 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
@@ -95,6 +96,7 @@ selftest::run_tests ()
   spellcheck_c_tests ();
   spellcheck_tree_c_tests ();
   tree_cfg_c_tests ();
+  tree_diagnostic_c_tests ();
   attribute_c_tests ();
 
   /* This one relies on most of the above.  */
@@ -111,6 +113,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/selftest.h b/gcc/selftest.h
index 3e00e75..88dc951 100644
--- a/gcc/selftest.h
+++ b/gcc/selftest.h
@@ -246,6 +246,7 @@ extern void sreal_c_tests ();
 extern void store_merging_c_tests ();
 extern void tree_c_tests ();
 extern void tree_cfg_c_tests ();
+extern void tree_diagnostic_c_tests ();
 extern void typed_splay_tree_c_tests ();
 extern void unique_ptr_tests_cc_tests ();
 extern void vec_c_tests ();
diff --git a/gcc/testsuite/DEADCODE-analyzer.dg/analyzer.exp b/gcc/testsuite/DEADCODE-analyzer.dg/analyzer.exp
new file mode 100644
index 0000000..515a676
--- /dev/null
+++ b/gcc/testsuite/DEADCODE-analyzer.dg/analyzer.exp
@@ -0,0 +1,49 @@
+# Test code for analyzer_plugin.so
+load_lib dg.exp
+load_lib prune.exp
+load_lib target-supports.exp
+load_lib gcc-defs.exp
+load_lib timeout.exp
+load_lib target-libpath.exp
+load_lib gcc.exp
+load_lib dejagnu.exp
+
+proc analyzer_target_compile { source dest type options } {
+    gcc_target_compile $source $dest $type $options
+}
+
+load_lib gcc-dg.exp
+
+proc analyzer-dg-test { prog do_what extra_tool_flags } {
+    verbose "within analyzer-dg-test..."
+    verbose "  prog: $prog"
+    verbose "  do_what: $do_what"
+    verbose "  extra_tool_flags: $extra_tool_flags"
+
+    set result \
+	[gcc-dg-test-1 analyzer_target_compile $prog $do_what $extra_tool_flags]
+
+    set comp_output [lindex $result 0]
+    set output_file [lindex $result 1]
+
+    return [list $comp_output $output_file]
+}
+
+# Initialize dg.
+dg-init
+
+# Gather a list of all tests.
+
+set tests [find $srcdir/$subdir *.c]
+
+set tests [lsort $tests]
+
+verbose "tests: $tests"
+
+set DEFAULT_CFLAGS "--analyzer"
+
+# Main loop.  This will invoke analyzer-dg-test on each test-*.c file.
+dg-runtest $tests "" $DEFAULT_CFLAGS
+
+# All done.
+dg-finish
diff --git a/gcc/testsuite/DEADCODE-analyzer.dg/constraints/path-count.c b/gcc/testsuite/DEADCODE-analyzer.dg/constraints/path-count.c
new file mode 100644
index 0000000..f6f9bd8
--- /dev/null
+++ b/gcc/testsuite/DEADCODE-analyzer.dg/constraints/path-count.c
@@ -0,0 +1,16 @@
+extern void foo (void);
+extern void bar (void);
+extern void baz (void);
+
+void test (int flag)
+{
+  if (flag)
+    foo ();
+  
+  bar ();
+
+  if (flag)
+    baz ();
+}
+
+/* { dg-final { scan-dump "analysis" "num paths: 2" "analyzer.txt" } } */
diff --git a/gcc/testsuite/DEADCODE-analyzer.dg/demo-2.c b/gcc/testsuite/DEADCODE-analyzer.dg/demo-2.c
new file mode 100644
index 0000000..254a9b3
--- /dev/null
+++ b/gcc/testsuite/DEADCODE-analyzer.dg/demo-2.c
@@ -0,0 +1,100 @@
+/* Control flow tests.  */
+
+#include <stdlib.h>
+
+/* Ensure that we don't need to laboriously walk every path to get
+   to the end of the function.  */
+
+int test_1 (int n)
+{
+  int i, j, k;
+  k = 0;
+  for (int i = 0; i < n; i++)
+    for (int j = 0; j < 1000; j++)
+      k++;
+  return k;
+}
+
+/* Ensure that we don't get an exponential growth in paths due to
+   repeated diamonds in the CFG.  */
+
+typedef struct obj {
+  int ob_refcnt;
+} PyObject;
+
+extern void Py_Dealloc (PyObject *op);
+
+#define Py_DECREF(op)                                   \
+    do {                                                \
+      if (--((PyObject*)(op))->ob_refcnt == 0)		\
+	Py_Dealloc((PyObject *)(op));			\
+    } while (0)
+
+int test_2 (PyObject *obj_01, PyObject *obj_02, PyObject *obj_03,
+	    PyObject *obj_04, PyObject *obj_05, PyObject *obj_06,
+	    PyObject *obj_07, PyObject *obj_08, PyObject *obj_09,
+	    PyObject *obj_10, PyObject *obj_11, PyObject *obj_12,
+	    PyObject *obj_13, PyObject *obj_14, PyObject *obj_15)
+{
+    Py_DECREF (obj_01); Py_DECREF (obj_02); Py_DECREF (obj_03);
+    Py_DECREF (obj_04); Py_DECREF (obj_05); Py_DECREF (obj_06);
+    Py_DECREF (obj_07); Py_DECREF (obj_08); Py_DECREF (obj_09);
+    Py_DECREF (obj_10); Py_DECREF (obj_11); Py_DECREF (obj_12);
+    Py_DECREF (obj_13); Py_DECREF (obj_14); Py_DECREF (obj_15);
+}
+
+int test_3 (int i)
+{
+  switch (i)
+    {
+    case 0:
+      return 43;
+    case 1:
+    case 2:
+      return 72;
+    default:
+      return i * 5;
+    }
+}
+
+void __attribute__((noinline)) do_something (int *ptr)
+{
+  *ptr = 42; /* BUG: can be NULL if malloc fails */
+}
+
+int test_4 (int i, int flag)
+{
+  /* Double diamond CFG; either use &i, or a malloc-ed buffer.  */
+  int *ptr = &i;
+  if (flag)
+    ptr = (int *)malloc (sizeof (int));
+  do_something (ptr);
+  if (flag)
+    free (ptr);
+  return i;
+}
+
+/* What about:  */
+
+int test_5 (int flag)
+{
+  int i;
+
+  if (flag)
+    do_something (&i);
+
+  do_something (&i);
+
+  if (!flag)
+    {
+      /* need to ensure we reach here in the traversal, even though
+	 no states have changed, and we already reached the join-point
+	 earlier with flag == true before trying this.
+	 Does the depth-firstness help?
+	 Also, we've called do_something twice; do we need function
+	 summaries here?  */
+      void *ptr = malloc (16);
+      free (ptr);
+      free (ptr);
+    }
+}
diff --git a/gcc/testsuite/DEADCODE-analyzer.dg/demo-3.c b/gcc/testsuite/DEADCODE-analyzer.dg/demo-3.c
new file mode 100644
index 0000000..cab0f09
--- /dev/null
+++ b/gcc/testsuite/DEADCODE-analyzer.dg/demo-3.c
@@ -0,0 +1,31 @@
+#include <stdlib.h>
+
+extern void __dump_malloc_state ();
+
+typedef struct boxed_int
+{
+  int val;
+} boxed_int;
+
+extern boxed_int *make_boxed_int (int i);
+
+/* Can we use magic fnnames to capture internal state, and thus write
+   DejaGnu tests for this?  */
+
+boxed_int *
+make_boxed_int (int val)
+{
+  boxed_int *new_ptr;
+
+  __dump_malloc_state (); /* { dg-message "malloc state: <start, {}> } */
+
+  new_ptr = (boxed_int *)malloc (sizeof (boxed_int));
+
+  __dump_malloc_state (); /* { dg-message "malloc state: <start, {'new_ptr': 'unchecked'}> } */
+
+  new_ptr->val = val;
+
+  __dump_malloc_state ();
+
+  return new_ptr;
+}
diff --git a/gcc/testsuite/DEADCODE-analyzer.dg/demo-4.c b/gcc/testsuite/DEADCODE-analyzer.dg/demo-4.c
new file mode 100644
index 0000000..2ab0f5c
--- /dev/null
+++ b/gcc/testsuite/DEADCODE-analyzer.dg/demo-4.c
@@ -0,0 +1,61 @@
+/* Control flow constraint tests.  */
+
+extern void __dump_truthiness (int);
+
+static void only_called_when_flag_a_true (int val)
+{
+  __dump_truthiness (i == 42); /* { dg-message "true" } */
+}
+
+static void only_called_when_flag_b_true (int val)
+{
+  __dump_truthiness (i == 17); /* { dg-message "true" } */
+}
+
+int test_1 (int flag_a, int flag_b)
+{
+  int i = 17;
+
+  __dump_truthiness (flag_a); /* { dg-message "unknown" } */
+  __dump_truthiness (flag_b); /* { dg-message "unknown" } */
+
+  if (flag_a)
+    {
+      __dump_truthiness (flag_a); /* { dg-message "true" } */
+      __dump_truthiness (flag_b); /* { dg-message "unknown" } */
+      i = 42;
+    }
+
+  __dump_truthiness (flag_b); /* { dg-message "unknown" } */
+
+  if (flag_a)
+    {
+      __dump_truthiness (flag_a); /* { dg-message "true" } */
+      __dump_truthiness (flag_b); /* { dg-message "unknown" } */
+      __dump_truthiness (i == 42); /* { dg-message "true" } */
+      __dump_truthiness (i == 17); /* { dg-message "false" } */
+      only_called_when_flag_a_true (i);
+    }  
+  else
+    {
+      __dump_truthiness (flag_a); /* { dg-message "false" } */
+      __dump_truthiness (flag_b); /* { dg-message "unknown" } */
+      __dump_truthiness (i == 42); /* { dg-message "false" } */
+      __dump_truthiness (i == 17); /* { dg-message "true" } */
+      only_called_when_flag_b_true (i);
+    }
+}
+
+int test_3 (int i)
+{
+  switch (i)
+    {
+    case 0:
+      return 43;
+    case 1:
+    case 2:
+      return 72;
+    default:
+      return i * 5;
+    }
+}
diff --git a/gcc/testsuite/DEADCODE-analyzer.dg/demo.c b/gcc/testsuite/DEADCODE-analyzer.dg/demo.c
new file mode 100644
index 0000000..7e17781
--- /dev/null
+++ b/gcc/testsuite/DEADCODE-analyzer.dg/demo.c
@@ -0,0 +1,30 @@
+#include <stdlib.h>
+
+typedef struct boxed_int
+{
+  int val;
+} boxed_int;
+
+extern boxed_int *make_boxed_int (int i);
+extern void free_boxed_int (boxed_int *victim);
+
+boxed_int *
+make_boxed_int (int val)
+{
+  boxed_int *new_ptr = (boxed_int *)malloc (sizeof (boxed_int));
+  new_ptr->val = val;
+  return new_ptr;
+}
+
+void
+free_boxed_int (boxed_int *victim)
+{
+  free (victim);
+}
+
+void test (int x)
+{
+  boxed_int *bi = make_boxed_int (x);
+  free_boxed_int (bi);
+  free_boxed_int (bi); /* BUG: double-free.  */
+}
diff --git a/gcc/testsuite/DEADCODE-analyzer.dg/dg.exp b/gcc/testsuite/DEADCODE-analyzer.dg/dg.exp
new file mode 100644
index 0000000..7fda22c
--- /dev/null
+++ b/gcc/testsuite/DEADCODE-analyzer.dg/dg.exp
@@ -0,0 +1,53 @@
+# Test code for analyzer_plugin.so
+
+#global tool
+#set tool "gcc"
+load_lib gcc-dg.exp
+
+#load_lib dg.exp
+#load_lib prune.exp
+#load_lib target-supports.exp
+#load_lib gcc-defs.exp
+#load_lib timeout.exp
+#load_lib target-libpath.exp
+#load_lib gcc.exp
+#load_lib dejagnu.exp
+
+proc analyzer_target_compile { source dest type options } {
+    gcc_target_compile $source $dest $type $options
+}
+
+
+proc analyzer-dg-test { prog do_what extra_tool_flags } {
+    verbose "within analyzer-dg-test..."
+    verbose "  prog: $prog"
+    verbose "  do_what: $do_what"
+    verbose "  extra_tool_flags: $extra_tool_flags"
+
+    set result \
+	[gcc-dg-test-1 analyzer_target_compile $prog $do_what $extra_tool_flags]
+
+    set comp_output [lindex $result 0]
+    set output_file [lindex $result 1]
+
+    return [list $comp_output $output_file]
+}
+
+# Initialize dg.
+dg-init
+
+# Gather a list of all tests.
+
+set tests [find $srcdir/$subdir *.c]
+
+set tests [lsort $tests]
+
+verbose "tests: $tests"
+
+set DEFAULT_CFLAGS "--analyzer"
+
+# Main loop.  This will invoke analyzer-dg-test on each test-*.c file.
+dg-runtest $tests "" $DEFAULT_CFLAGS
+
+# All done.
+dg-finish
diff --git a/gcc/testsuite/DEADCODE-analyzer.dg/ipa-double-free/api.c b/gcc/testsuite/DEADCODE-analyzer.dg/ipa-double-free/api.c
new file mode 100644
index 0000000..a418ee4
--- /dev/null
+++ b/gcc/testsuite/DEADCODE-analyzer.dg/ipa-double-free/api.c
@@ -0,0 +1,15 @@
+#include "api.h"
+
+boxed_int *
+make_boxed_int (int i)
+{
+  boxed_int *bi = (boxed_int *)malloc (sizeof (boxed_int));
+  bi->i = i;
+  return bi;
+}
+
+void
+free_boxed_int (boxed_int *bi)
+{
+  free (bi);
+}
diff --git a/gcc/testsuite/DEADCODE-analyzer.dg/ipa-double-free/api.h b/gcc/testsuite/DEADCODE-analyzer.dg/ipa-double-free/api.h
new file mode 100644
index 0000000..ebdfa89
--- /dev/null
+++ b/gcc/testsuite/DEADCODE-analyzer.dg/ipa-double-free/api.h
@@ -0,0 +1,9 @@
+#include <stdlib.h>
+
+typedef struct boxed_int
+{
+  int i;
+} boxed_int;
+
+extern boxed_int *make_boxed_int (int i);
+extern void free_boxed_int (boxed_int *bi);
diff --git a/gcc/testsuite/DEADCODE-analyzer.dg/ipa-double-free/client.c b/gcc/testsuite/DEADCODE-analyzer.dg/ipa-double-free/client.c
new file mode 100644
index 0000000..4c7e642
--- /dev/null
+++ b/gcc/testsuite/DEADCODE-analyzer.dg/ipa-double-free/client.c
@@ -0,0 +1,8 @@
+#include "api.h"
+
+void test (int i)
+{
+  boxed_int *bi = make_boxed_int (i);
+  free_boxed_int (bi);
+  free_boxed_int (bi); /* BUG: double-free.  */
+}
diff --git a/gcc/testsuite/DEADCODE-analyzer.dg/malloc/bad-size.c b/gcc/testsuite/DEADCODE-analyzer.dg/malloc/bad-size.c
new file mode 100644
index 0000000..1456c90
--- /dev/null
+++ b/gcc/testsuite/DEADCODE-analyzer.dg/malloc/bad-size.c
@@ -0,0 +1,20 @@
+#include <stdlib.h>
+
+struct foo
+{
+  int i;
+};
+
+struct bar
+{
+  struct foo f;
+  int j;
+};
+
+struct bar *test (void)
+{
+  struct bar *p;
+  p = (struct bar *)malloc (sizeof (struct foo)); /* should warn here */
+  p->j = 42;
+  return p;
+}
diff --git a/gcc/testsuite/DEADCODE-analyzer.dg/malloc/leak.c b/gcc/testsuite/DEADCODE-analyzer.dg/malloc/leak.c
new file mode 100644
index 0000000..957f554
--- /dev/null
+++ b/gcc/testsuite/DEADCODE-analyzer.dg/malloc/leak.c
@@ -0,0 +1,6 @@
+#include <stdlib.h>
+
+void test (void)
+{
+  void *p = malloc (256); /* { dg-error "leak" } */
+}
diff --git a/gcc/testsuite/DEADCODE-analyzer.dg/malloc/unchecked.c b/gcc/testsuite/DEADCODE-analyzer.dg/malloc/unchecked.c
new file mode 100644
index 0000000..a30ff08
--- /dev/null
+++ b/gcc/testsuite/DEADCODE-analyzer.dg/malloc/unchecked.c
@@ -0,0 +1,14 @@
+#include <stdlib.h>
+
+struct foo
+{
+  int i;
+};
+
+struct foo *test (void)
+{
+  struct foo *p;
+  p = (struct foo *)malloc (sizeof (struct foo));
+  p->i = 42; /* unchecked deref */
+  return p;
+}
diff --git a/gcc/testsuite/DEADCODE-analyzer.dg/memory/example.c b/gcc/testsuite/DEADCODE-analyzer.dg/memory/example.c
new file mode 100644
index 0000000..d9af197
--- /dev/null
+++ b/gcc/testsuite/DEADCODE-analyzer.dg/memory/example.c
@@ -0,0 +1,16 @@
+struct s {
+  struct s *next;
+  int f;
+};
+
+void foo (struct s *in)
+{
+  struct s a;
+  struct s b;
+  struct s c;
+  a.next = &b;
+  b.next = &c;
+  in->next = &a;
+  c.f = 42;
+  __analyzer_eval (in->next->next->next->f == 42);  /* { dg-warning "TRUE" } */
+}
diff --git a/gcc/testsuite/DEADCODE-analyzer.dg/memory/stack-address.c b/gcc/testsuite/DEADCODE-analyzer.dg/memory/stack-address.c
new file mode 100644
index 0000000..602b26a
--- /dev/null
+++ b/gcc/testsuite/DEADCODE-analyzer.dg/memory/stack-address.c
@@ -0,0 +1,8 @@
+int *test(void)
+{
+  /* Enough logic to prevent -Wreturn-local-addr from seeing it.  */
+  int i;
+  int *p;
+  p = &i;
+  return p; /* { dg-error "return of pointer to stack" } */
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/assignments/dereference-on-lhs/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/assignments/dereference-on-lhs/input.c
new file mode 100644
index 0000000..8234afa
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/assignments/dereference-on-lhs/input.c
@@ -0,0 +1,27 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdlib.h>
+
+int *test(int i)
+{
+  int *foo = (int*)malloc(sizeof(int*));
+  *foo = i; /* BUG: result of malloc could be NULL */
+  return foo;
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/assignments/dereference-on-lhs/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/assignments/dereference-on-lhs/script.py
new file mode 100644
index 0000000..5c59776
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/assignments/dereference-on-lhs/script.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012, 2013 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012, 2013 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+from sm import main
+from sm.parser import parse_file
+
+def selftest(ctxt, solution):
+    if 0:
+        import sys
+        solution.dump(sys.stderr)
+
+    # Verify that the:
+    #    foo = malloc()
+    # transitions "foo" from "ptr.start" to "ptr.unchecked"
+    node = ctxt.find_call_of('malloc')
+    ctxt.assert_statenames_for_varname(node, 'foo', {'ptr.start'})
+
+    node = ctxt.get_successor(node)
+    ctxt.assert_statenames_for_varname(node, 'foo', {'ptr.unchecked'})
+
+checker = parse_file('sm/checkers/malloc_checker.sm')
+main([checker], selftest=selftest)
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/assignments/dereference-on-lhs/stderr.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/assignments/dereference-on-lhs/stderr.txt
new file mode 100644
index 0000000..10b4b24
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/assignments/dereference-on-lhs/stderr.txt
@@ -0,0 +1,4 @@
+tests/sm/assignments/dereference-on-lhs/input.c: In function 'test':
+tests/sm/assignments/dereference-on-lhs/input.c:25:nn: error: dereference of possibly-NULL pointer foo [CWE-690]
+tests/sm/assignments/dereference-on-lhs/input.c:24:nn: note: foo assigned to the result of malloc()
+tests/sm/assignments/dereference-on-lhs/input.c:25:nn: note: dereference of possibly-NULL pointer foo
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/assignments/dereference-on-lhs/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/assignments/dereference-on-lhs/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/assignments/dereference-on-rhs/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/assignments/dereference-on-rhs/input.c
new file mode 100644
index 0000000..6a1c472
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/assignments/dereference-on-rhs/input.c
@@ -0,0 +1,28 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdlib.h>
+
+int *test(int i)
+{
+  int *foo = (int*)malloc(sizeof(int*));
+  i = *foo; /* BUG: result of malloc could be NULL
+               (fwiw it's also uninitialized) */
+  return foo;
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/assignments/dereference-on-rhs/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/assignments/dereference-on-rhs/script.py
new file mode 100644
index 0000000..f5439a2
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/assignments/dereference-on-rhs/script.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012, 2013 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012, 2013 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+from sm import main
+from sm.parser import parse_file
+
+checker = parse_file('sm/checkers/malloc_checker.sm')
+main([checker])
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/assignments/dereference-on-rhs/stderr.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/assignments/dereference-on-rhs/stderr.txt
new file mode 100644
index 0000000..dd63384
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/assignments/dereference-on-rhs/stderr.txt
@@ -0,0 +1,4 @@
+tests/sm/assignments/dereference-on-rhs/input.c: In function 'test':
+tests/sm/assignments/dereference-on-rhs/input.c:25:nn: error: dereference of possibly-NULL pointer foo [CWE-690]
+tests/sm/assignments/dereference-on-rhs/input.c:24:nn: note: foo assigned to the result of malloc()
+tests/sm/assignments/dereference-on-rhs/input.c:25:nn: note: dereference of possibly-NULL pointer foo
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/assignments/dereference-on-rhs/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/assignments/dereference-on-rhs/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/assignments/phi/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/assignments/phi/input.c
new file mode 100644
index 0000000..b1c939a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/assignments/phi/input.c
@@ -0,0 +1,38 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdlib.h>
+
+extern void marker(char *buffer);
+
+char *test(int flag)
+{
+  char *buffer;
+
+  if (flag) {
+    buffer = (char*)malloc(4096);
+  } else {
+    buffer = NULL;
+  }
+
+  marker(buffer);
+  buffer[0] = 'a';
+
+  return buffer;
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/assignments/phi/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/assignments/phi/script.py
new file mode 100644
index 0000000..6de41ca
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/assignments/phi/script.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012, 2013 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012, 2013 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+from sm import main
+from sm.parser import parse_file
+
+def selftest(ctxt, solution):
+    if 0:
+        import sys
+        solution.dump(sys.stderr)
+
+    # Verify that at
+    #    marker(buffer)
+    # that we have the states from both paths:
+    node = ctxt.find_call_of('marker')
+    ctxt.assert_statenames_for_varname(node, 'buffer', {'ptr.unchecked', 'ptr.null'})
+
+checker = parse_file('sm/checkers/malloc_checker.sm')
+main([checker], selftest=selftest)
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/assignments/phi/stderr.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/assignments/phi/stderr.txt
new file mode 100644
index 0000000..13d76ca
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/assignments/phi/stderr.txt
@@ -0,0 +1,7 @@
+tests/sm/assignments/phi/input.c: In function 'test':
+tests/sm/assignments/phi/input.c:35:nn: error: dereference of NULL pointer buffer [CWE-690]
+tests/sm/assignments/phi/input.c:31:nn: note: buffer assigned to 0
+tests/sm/assignments/phi/input.c:35:nn: note: dereference of NULL pointer buffer
+tests/sm/assignments/phi/input.c:35:nn: error: dereference of possibly-NULL pointer buffer [CWE-690]
+tests/sm/assignments/phi/input.c:29:nn: note: buffer assigned to the result of malloc()
+tests/sm/assignments/phi/input.c:35:nn: note: dereference of possibly-NULL pointer buffer
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/assignments/phi/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/assignments/phi/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/arg_must_not_be_null/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/arg_must_not_be_null/input.c
new file mode 100644
index 0000000..141e55c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/arg_must_not_be_null/input.c
@@ -0,0 +1,30 @@
+/*
+   Copyright 2013 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2013 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdlib.h>
+
+extern void foo(void *ptrA, void *ptrB, void *ptrC)
+  __attribute__((nonnull (1, 3)));
+
+void test(void *p, void *q, void *r)
+{
+  p = NULL;
+
+  foo(p, q, r);
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/arg_must_not_be_null/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/arg_must_not_be_null/script.py
new file mode 100644
index 0000000..d6390c5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/arg_must_not_be_null/script.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+from sm import main
+from sm.parser import parse_file
+
+checker = parse_file('sm/checkers/malloc_checker.sm')
+main([checker])
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/arg_must_not_be_null/stderr.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/arg_must_not_be_null/stderr.txt
new file mode 100644
index 0000000..cc5466f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/arg_must_not_be_null/stderr.txt
@@ -0,0 +1,4 @@
+tests/sm/checkers/malloc-checker/arg_must_not_be_null/input.c: In function 'test':
+tests/sm/checkers/malloc-checker/arg_must_not_be_null/input.c:29:nn: error: NULL pointer p passed as argument 1 to foo [CWE-690]
+tests/sm/checkers/malloc-checker/arg_must_not_be_null/input.c:27:nn: note: p assigned to 0
+tests/sm/checkers/malloc-checker/arg_must_not_be_null/input.c:29:nn: note: NULL pointer p passed as argument 1 to foo
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/arg_must_not_be_null/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/arg_must_not_be_null/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/correct_usage/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/correct_usage/input.c
new file mode 100644
index 0000000..c357663
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/correct_usage/input.c
@@ -0,0 +1,31 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdlib.h>
+#include <string.h>
+
+int test(void)
+{
+  void *ptr = malloc(4096);
+  if (!ptr)
+    return -1; /* FIXME: with a plain return we have a BB with no gimple, and that breaks my checker */
+  memset(ptr, 0, 4096);
+  free(ptr);
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/correct_usage/input.c.cpychecker-log.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/correct_usage/input.c.cpychecker-log.txt
new file mode 100644
index 0000000..7499d84
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/correct_usage/input.c.cpychecker-log.txt
@@ -0,0 +1 @@
+gcc.Function('test')
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/correct_usage/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/correct_usage/script.py
new file mode 100644
index 0000000..d6390c5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/correct_usage/script.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+from sm import main
+from sm.parser import parse_file
+
+checker = parse_file('sm/checkers/malloc_checker.sm')
+main([checker])
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/correct_usage/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/correct_usage/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/double_free/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/double_free/input.c
new file mode 100644
index 0000000..56385c3
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/double_free/input.c
@@ -0,0 +1,29 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdlib.h>
+#include <string.h>
+
+void test(void *ptr)
+{
+  free(ptr);
+
+  /* BUG: double-free: */
+  free(ptr);
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/double_free/input.c.cpychecker-log.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/double_free/input.c.cpychecker-log.txt
new file mode 100644
index 0000000..7499d84
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/double_free/input.c.cpychecker-log.txt
@@ -0,0 +1 @@
+gcc.Function('test')
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/double_free/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/double_free/script.py
new file mode 100644
index 0000000..d6390c5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/double_free/script.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+from sm import main
+from sm.parser import parse_file
+
+checker = parse_file('sm/checkers/malloc_checker.sm')
+main([checker])
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/double_free/stderr.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/double_free/stderr.txt
new file mode 100644
index 0000000..6bdd7a2
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/double_free/stderr.txt
@@ -0,0 +1,7 @@
+tests/sm/checkers/malloc-checker/double_free/input.c: In function 'test':
+tests/sm/checkers/malloc-checker/double_free/input.c:28:nn: error: double-free of ptr [CWE-415]
+tests/sm/checkers/malloc-checker/double_free/input.c:25:nn: note: ptr passed to free()
+tests/sm/checkers/malloc-checker/double_free/input.c:28:nn: note: double-free of ptr
+tests/sm/checkers/malloc-checker/double_free/input.c:28:nn: error: use-after-free of ptr [CWE-416]
+tests/sm/checkers/malloc-checker/double_free/input.c:25:nn: note: ptr passed to free()
+tests/sm/checkers/malloc-checker/double_free/input.c:28:nn: note: use-after-free of ptr
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/double_free/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/double_free/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/fancy_control_flow/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/fancy_control_flow/input.c
new file mode 100644
index 0000000..01a2387
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/fancy_control_flow/input.c
@@ -0,0 +1,42 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdlib.h>
+#include <string.h>
+
+void test(int i, int j)
+{
+  int k;
+  void *ptr;
+  for (k = i; k < j; k++) {
+    switch(k) {
+    case 0:
+      ptr = malloc(1024);
+      break;
+    case 1:
+      break;
+    case 2:
+      break;
+    default:
+      break;
+    }
+  }
+  memset(ptr, 0, 4096);
+  free(ptr);
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/fancy_control_flow/input.c.cpychecker-log.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/fancy_control_flow/input.c.cpychecker-log.txt
new file mode 100644
index 0000000..7499d84
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/fancy_control_flow/input.c.cpychecker-log.txt
@@ -0,0 +1 @@
+gcc.Function('test')
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/fancy_control_flow/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/fancy_control_flow/script.py
new file mode 100644
index 0000000..d6390c5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/fancy_control_flow/script.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+from sm import main
+from sm.parser import parse_file
+
+checker = parse_file('sm/checkers/malloc_checker.sm')
+main([checker])
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/fancy_control_flow/stderr.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/fancy_control_flow/stderr.txt
new file mode 100644
index 0000000..2744fa4
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/fancy_control_flow/stderr.txt
@@ -0,0 +1,4 @@
+tests/sm/checkers/malloc-checker/fancy_control_flow/input.c: In function 'test':
+tests/sm/checkers/malloc-checker/fancy_control_flow/input.c:40:nn: error: possibly-NULL pointer ptr passed as argument 1 to memset [CWE-690]
+tests/sm/checkers/malloc-checker/fancy_control_flow/input.c:30:nn: note: ptr assigned to the result of malloc()
+tests/sm/checkers/malloc-checker/fancy_control_flow/input.c:40:nn: note: possibly-NULL pointer ptr passed as argument 1 to memset
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/fancy_control_flow/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/fancy_control_flow/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/leaks/leak-in-error-handling/input.c.cpychecker-log.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/leaks/leak-in-error-handling/input.c.cpychecker-log.txt
new file mode 100644
index 0000000..7499d84
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/leaks/leak-in-error-handling/input.c.cpychecker-log.txt
@@ -0,0 +1 @@
+gcc.Function('test')
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/leaks/not-a-leak/input.c.cpychecker-log.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/leaks/not-a-leak/input.c.cpychecker-log.txt
new file mode 100644
index 0000000..7499d84
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/leaks/not-a-leak/input.c.cpychecker-log.txt
@@ -0,0 +1 @@
+gcc.Function('test')
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/leaks/simple-leak/input.c.cpychecker-log.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/leaks/simple-leak/input.c.cpychecker-log.txt
new file mode 100644
index 0000000..7499d84
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/leaks/simple-leak/input.c.cpychecker-log.txt
@@ -0,0 +1 @@
+gcc.Function('test')
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/leaks/unchecked-return/input.c.cpychecker-log.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/leaks/unchecked-return/input.c.cpychecker-log.txt
new file mode 100644
index 0000000..7499d84
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/leaks/unchecked-return/input.c.cpychecker-log.txt
@@ -0,0 +1 @@
+gcc.Function('test')
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/multiple-errors/unchecked_malloc/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/multiple-errors/unchecked_malloc/input.c
new file mode 100644
index 0000000..e1b0e28
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/multiple-errors/unchecked_malloc/input.c
@@ -0,0 +1,41 @@
+/*
+   Copyright 2013 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2013 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdlib.h>
+#include <string.h>
+
+struct foo {
+  int i;
+  int j;
+  int k;
+};
+
+struct foo *test(void)
+{
+  struct foo *p = (struct foo*)malloc(4096);
+
+  /* BUG: usage of p without checking if malloc return NULL: */
+  p->i = 1;
+
+  /* Only the first such usage should be reported: */
+  p->j = 2;
+  p->k = 3;
+
+  return p;
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/multiple-errors/unchecked_malloc/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/multiple-errors/unchecked_malloc/script.py
new file mode 100644
index 0000000..263594c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/multiple-errors/unchecked_malloc/script.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2013 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2013 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+from sm import main
+from sm.parser import parse_file
+
+def selftest(ctxt, solution):
+    if 0:
+        import sys
+        solution.dump(sys.stderr)
+
+    node_call = ctxt.find_call_of('malloc')
+    ssaname = node_call.stmt.lhs
+
+    p = ctxt.find_var(node_call, 'p')
+    node_write_to_p_i = ctxt.get_nodes().assigning_constant(1).one()
+    ctxt.assert_statenames_for_expr(node_write_to_p_i, p,
+                                    frozenset(['ptr.unchecked']))
+
+    # It should now have given up, with the p in the "ptr.nonnull" state:
+    node_write_to_p_j = ctxt.get_nodes().assigning_constant(2).one()
+    ctxt.assert_statenames_for_expr(node_write_to_p_j, p,
+                                    frozenset(['ptr.nonnull']))
+
+    node_write_to_p_k = ctxt.get_nodes().assigning_constant(3).one()
+    ctxt.assert_statenames_for_expr(node_write_to_p_k, p,
+                                    frozenset(['ptr.nonnull']))
+
+
+checker = parse_file('sm/checkers/malloc_checker.sm')
+main([checker], selftest=selftest)
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/multiple-errors/unchecked_malloc/stderr.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/multiple-errors/unchecked_malloc/stderr.txt
new file mode 100644
index 0000000..7861bb4
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/multiple-errors/unchecked_malloc/stderr.txt
@@ -0,0 +1,4 @@
+tests/sm/checkers/malloc-checker/multiple-errors/unchecked_malloc/input.c: In function 'test':
+tests/sm/checkers/malloc-checker/multiple-errors/unchecked_malloc/input.c:34:nn: error: dereference of possibly-NULL pointer p [CWE-690]
+tests/sm/checkers/malloc-checker/multiple-errors/unchecked_malloc/input.c:31:nn: note: p assigned to the result of malloc()
+tests/sm/checkers/malloc-checker/multiple-errors/unchecked_malloc/input.c:34:nn: note: dereference of possibly-NULL pointer p
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/multiple-errors/unchecked_malloc/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/multiple-errors/unchecked_malloc/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/two_ptrs/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/two_ptrs/input.c
new file mode 100644
index 0000000..28a1cdd
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/two_ptrs/input.c
@@ -0,0 +1,35 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdlib.h>
+#include <string.h>
+
+int test(void)
+{
+  void *p = malloc(4096);
+  void *q = malloc(4096);
+  if (p) {
+    memset(p, 0, 4096); /* Not a bug: checked */
+  } else {
+    memset(q, 0, 4096); /* BUG: not checked */
+  }
+  free(p);
+  free(q);
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/two_ptrs/input.c.cpychecker-log.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/two_ptrs/input.c.cpychecker-log.txt
new file mode 100644
index 0000000..7499d84
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/two_ptrs/input.c.cpychecker-log.txt
@@ -0,0 +1 @@
+gcc.Function('test')
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/two_ptrs/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/two_ptrs/script.py
new file mode 100644
index 0000000..d6390c5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/two_ptrs/script.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+from sm import main
+from sm.parser import parse_file
+
+checker = parse_file('sm/checkers/malloc_checker.sm')
+main([checker])
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/two_ptrs/stderr.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/two_ptrs/stderr.txt
new file mode 100644
index 0000000..7f11a6a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/two_ptrs/stderr.txt
@@ -0,0 +1,4 @@
+tests/sm/checkers/malloc-checker/two_ptrs/input.c: In function 'test':
+tests/sm/checkers/malloc-checker/two_ptrs/input.c:30:nn: error: possibly-NULL pointer q passed as argument 1 to memset [CWE-690]
+tests/sm/checkers/malloc-checker/two_ptrs/input.c:26:nn: note: q assigned to the result of malloc()
+tests/sm/checkers/malloc-checker/two_ptrs/input.c:30:nn: note: possibly-NULL pointer q passed as argument 1 to memset
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/two_ptrs/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/two_ptrs/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/unchecked_malloc/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/unchecked_malloc/input.c
new file mode 100644
index 0000000..358f4c9
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/unchecked_malloc/input.c
@@ -0,0 +1,28 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdlib.h>
+#include <string.h>
+
+void *test(void)
+{
+  void *ptr = malloc(4096);
+  memset(ptr, 0, 4096);
+  return ptr;
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/unchecked_malloc/input.c.cpychecker-log.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/unchecked_malloc/input.c.cpychecker-log.txt
new file mode 100644
index 0000000..7499d84
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/unchecked_malloc/input.c.cpychecker-log.txt
@@ -0,0 +1 @@
+gcc.Function('test')
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/unchecked_malloc/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/unchecked_malloc/script.py
new file mode 100644
index 0000000..d6390c5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/unchecked_malloc/script.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+from sm import main
+from sm.parser import parse_file
+
+checker = parse_file('sm/checkers/malloc_checker.sm')
+main([checker])
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/unchecked_malloc/stderr.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/unchecked_malloc/stderr.txt
new file mode 100644
index 0000000..c08d698
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/unchecked_malloc/stderr.txt
@@ -0,0 +1,4 @@
+tests/sm/checkers/malloc-checker/unchecked_malloc/input.c: In function 'test':
+tests/sm/checkers/malloc-checker/unchecked_malloc/input.c:26:nn: error: possibly-NULL pointer ptr passed as argument 1 to memset [CWE-690]
+tests/sm/checkers/malloc-checker/unchecked_malloc/input.c:25:nn: note: ptr assigned to the result of malloc()
+tests/sm/checkers/malloc-checker/unchecked_malloc/input.c:26:nn: note: possibly-NULL pointer ptr passed as argument 1 to memset
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/unchecked_malloc/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/unchecked_malloc/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/use_after_free/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/use_after_free/input.c
new file mode 100644
index 0000000..ca01596
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/use_after_free/input.c
@@ -0,0 +1,34 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdlib.h>
+#include <string.h>
+
+void foo(void *ptr);
+
+void test(int i)
+{
+  void *p;
+
+  p = malloc(1024);
+  if (p) {
+    free(p);
+    foo(p);
+  }
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/use_after_free/input.c.cpychecker-log.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/use_after_free/input.c.cpychecker-log.txt
new file mode 100644
index 0000000..7499d84
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/use_after_free/input.c.cpychecker-log.txt
@@ -0,0 +1 @@
+gcc.Function('test')
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/use_after_free/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/use_after_free/script.py
new file mode 100644
index 0000000..d6390c5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/use_after_free/script.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+from sm import main
+from sm.parser import parse_file
+
+checker = parse_file('sm/checkers/malloc_checker.sm')
+main([checker])
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/use_after_free/stderr.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/use_after_free/stderr.txt
new file mode 100644
index 0000000..f0ce7d1
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/use_after_free/stderr.txt
@@ -0,0 +1,4 @@
+tests/sm/checkers/malloc-checker/use_after_free/input.c: In function 'test':
+tests/sm/checkers/malloc-checker/use_after_free/input.c:32:nn: error: use-after-free of p [CWE-416]
+tests/sm/checkers/malloc-checker/use_after_free/input.c:31:nn: note: p passed to free()
+tests/sm/checkers/malloc-checker/use_after_free/input.c:32:nn: note: use-after-free of p
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/use_after_free/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/malloc-checker/use_after_free/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/points_to_stack/putenv/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/points_to_stack/putenv/input.c
new file mode 100644
index 0000000..b400778
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/points_to_stack/putenv/input.c
@@ -0,0 +1,31 @@
+/*
+   Copyright 2013 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2013 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdio.h>
+
+void test_bad_putenv(const char *key, const char *value)
+{
+  char buffer[1024];
+  
+  snprintf(buffer, 1024, "%s=%s", key, value);
+
+  /* BUG: buffer is on the stack, and some implementations of putenv only
+     store a pointer to the string: */
+  (void)putenv(buffer);
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/points_to_stack/putenv/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/points_to_stack/putenv/script.py
new file mode 100644
index 0000000..ef77626
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/points_to_stack/putenv/script.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012, 2013 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012, 2013 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+import gcc
+
+from sm import main
+from sm.parser import parse_file
+
+def selftest(ctxt, solution):
+    if 1:
+        import sys
+        solution.dump(sys.stderr)
+
+checker = parse_file('sm/checkers/points_to_stack.sm')
+print(checker)
+main([checker], selftest=selftest)
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/points_to_stack/putenv/stderr.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/points_to_stack/putenv/stderr.txt
new file mode 100644
index 0000000..f0976f0
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/points_to_stack/putenv/stderr.txt
@@ -0,0 +1,22 @@
+tests/sm/checkers/taint/example/input.c: In function 'test':
+tests/sm/checkers/taint/example/input.c:37:nn: error: use of tainted value tmp.i in array lookup without bounds checking [CWE-129]
+tests/sm/checkers/taint/example/input.c:34:nn: note: tmp passed to fread()
+tests/sm/checkers/taint/example/input.c:37:nn: note: state of tmp ("x.tainted") propagated to tmp.i
+tests/sm/checkers/taint/example/input.c:37:nn: note: use of tainted value tmp.i in array lookup without bounds checking
+tests/sm/checkers/taint/example/input.c: In function 'test4':
+tests/sm/checkers/taint/example/input.c:68:nn: error: use of tainted value tmp.i in array lookup without upper-bound check [CWE-129]
+tests/sm/checkers/taint/example/input.c:65:nn: note: tmp passed to fread()
+tests/sm/checkers/taint/example/input.c:66:nn: note: state of tmp ("x.tainted") propagated to tmp.i
+tests/sm/checkers/taint/example/input.c:66:nn: note: tmp.i compared against 0
+tests/sm/checkers/taint/example/input.c:68:nn: note: use of tainted value tmp.i in array lookup without upper-bound check
+tests/sm/checkers/taint/example/input.c: In function 'test5':
+tests/sm/checkers/taint/example/input.c:81:nn: error: use of tainted value tmp.i in array lookup without lower-bound check [CWE-129]
+tests/sm/checkers/taint/example/input.c:78:nn: note: tmp passed to fread()
+tests/sm/checkers/taint/example/input.c:79:nn: note: state of tmp ("x.tainted") propagated to tmp.i
+tests/sm/checkers/taint/example/input.c:79:nn: note: tmp.i compared against 255
+tests/sm/checkers/taint/example/input.c:81:nn: note: use of tainted value tmp.i in array lookup without lower-bound check
+tests/sm/checkers/taint/example/input.c: In function 'test6':
+tests/sm/checkers/taint/example/input.c:101:nn: error: use of tainted value tmp.i in array lookup without bounds checking [CWE-129]
+tests/sm/checkers/taint/example/input.c:98:nn: note: tmp passed to fread()
+tests/sm/checkers/taint/example/input.c:101:nn: note: state of tmp ("x.tainted") propagated to tmp.i
+tests/sm/checkers/taint/example/input.c:101:nn: note: use of tainted value tmp.i in array lookup without bounds checking
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/points_to_stack/putenv/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/points_to_stack/putenv/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/points_to_stack/return_of_ptr_to_local/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/points_to_stack/return_of_ptr_to_local/input.c
new file mode 100644
index 0000000..730a400
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/points_to_stack/return_of_ptr_to_local/input.c
@@ -0,0 +1,69 @@
+/*
+   Copyright 2013 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2013 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdio.h>
+
+char *test(const char *name)
+{
+  char buffer[1024];
+  char *result = NULL;
+
+  snprintf(buffer, 1024, "hello %s", name);
+
+  /* Note that if we simply
+       "return buffer;"
+     gcc is smart enough by itself to issue
+        "warning: function returns address of local variable [enabled by default]"
+     from c-typeck.c:c_finish_return as it builds the return statement.
+
+     However, if we introduce some dataflow, this warning isn't issued by
+     that code - but our points_to_stack.sm script can it:
+  */
+  result = buffer;
+
+  /* BUG: returns pointer to variable on the stack: */
+  return result;
+}
+
+char *test2(const char *name)
+{
+  static char buffer[1024];
+  char *result = NULL;
+
+  snprintf(buffer, 1024, "hello %s", name);
+
+  result = buffer;
+
+  /* Not a bug: buffer is static */
+  return result;
+}
+
+char other_buffer[1024];
+
+char *test3(const char *name)
+{
+  char *result = NULL;
+
+  snprintf(other_buffer, 1024, "hello %s", name);
+
+  result = other_buffer;
+
+  /* Not a bug: other_buffer is global */
+  return result;
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/points_to_stack/return_of_ptr_to_local/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/points_to_stack/return_of_ptr_to_local/script.py
new file mode 100644
index 0000000..60feca7
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/points_to_stack/return_of_ptr_to_local/script.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012, 2013 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012, 2013 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+import gcc
+
+from sm import main
+from sm.parser import parse_file
+
+def selftest(ctxt, solution):
+    if 0:
+        import sys
+        solution.dump(sys.stderr)
+
+checker = parse_file('sm/checkers/points_to_stack.sm')
+main([checker], selftest=selftest)
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/points_to_stack/return_of_ptr_to_local/stderr.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/points_to_stack/return_of_ptr_to_local/stderr.txt
new file mode 100644
index 0000000..55b4ce0
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/points_to_stack/return_of_ptr_to_local/stderr.txt
@@ -0,0 +1,5 @@
+tests/sm/checkers/points_to_stack/return_of_ptr_to_local/input.c: In function 'test':
+tests/sm/checkers/points_to_stack/return_of_ptr_to_local/input.c:41:nn: error: return of "result" returns address within stack of local variable "buffer" [CWE-562]
+tests/sm/checkers/points_to_stack/return_of_ptr_to_local/input.c:38:nn: note: assignment of result to &buffer
+tests/sm/checkers/points_to_stack/return_of_ptr_to_local/input.c:41:nn: note: state of result ("State('ptr.points_to_stack', 'var_pointed_to'=gcc.VarDecl('buffer'))") propagated to result
+tests/sm/checkers/points_to_stack/return_of_ptr_to_local/input.c:41:nn: note: return of "result" returns address within stack of local variable "buffer"
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/points_to_stack/return_of_ptr_to_local/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/points_to_stack/return_of_ptr_to_local/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/signal_handler/example/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/signal_handler/example/input.c
new file mode 100644
index 0000000..282c9bc
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/signal_handler/example/input.c
@@ -0,0 +1,52 @@
+/*
+   Copyright 2013 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2013 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdio.h>
+#include <signal.h>
+
+extern void body_of_program(void);
+
+int logging = 1;
+
+void custom_logger(const char *msg)
+{
+  if (logging) {
+    (void)fprintf(stderr, "LOG: %s", msg);
+  }
+}
+
+void handler(int signum)
+{
+  /* BUG: log calls fprintf, and this is not allowed from a signal
+     handler: */
+  custom_logger("got signal");
+}
+
+int main(int argc, const char *argv)
+{
+  custom_logger("started");
+
+  signal(SIGINT, handler);
+
+  body_of_program();
+
+  custom_logger("stopped");
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/signal_handler/example/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/signal_handler/example/script.py
new file mode 100644
index 0000000..80fe4ad
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/signal_handler/example/script.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012, 2013 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012, 2013 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+import gcc
+
+from sm import main
+from sm.parser import parse_file
+
+def selftest(ctxt, solution):
+    if 1:
+        import sys
+        solution.dump(sys.stderr)
+
+checker = parse_file('sm/checkers/signal_handler.sm')
+main([checker], selftest=selftest)
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/signal_handler/example/stderr.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/signal_handler/example/stderr.txt
new file mode 100644
index 0000000..55b4ce0
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/signal_handler/example/stderr.txt
@@ -0,0 +1,5 @@
+tests/sm/checkers/points_to_stack/return_of_ptr_to_local/input.c: In function 'test':
+tests/sm/checkers/points_to_stack/return_of_ptr_to_local/input.c:41:nn: error: return of "result" returns address within stack of local variable "buffer" [CWE-562]
+tests/sm/checkers/points_to_stack/return_of_ptr_to_local/input.c:38:nn: note: assignment of result to &buffer
+tests/sm/checkers/points_to_stack/return_of_ptr_to_local/input.c:41:nn: note: state of result ("State('ptr.points_to_stack', 'var_pointed_to'=gcc.VarDecl('buffer'))") propagated to result
+tests/sm/checkers/points_to_stack/return_of_ptr_to_local/input.c:41:nn: note: return of "result" returns address within stack of local variable "buffer"
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/signal_handler/example/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/signal_handler/example/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/calloc_too_small/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/calloc_too_small/input.c
new file mode 100644
index 0000000..e5053c5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/calloc_too_small/input.c
@@ -0,0 +1,34 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdlib.h>
+
+struct foo {
+  char buffer[1024];
+};
+
+struct bar {
+  char buffer[128];
+};
+
+struct foo *test(void)
+{
+  return (struct foo*)calloc(4, sizeof(struct bar));
+  /* BUG: sizeof(bar), rather than sizeof(foo), hence not enough space */
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/calloc_too_small/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/calloc_too_small/script.py
new file mode 100644
index 0000000..cd01c16
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/calloc_too_small/script.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+from sm import main
+from sm.parser import parse_file
+
+checker = parse_file('sm/checkers/sizeof_allocation.sm')
+main([checker])
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/calloc_too_small/stderr.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/calloc_too_small/stderr.txt
new file mode 100644
index 0000000..f62e50e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/calloc_too_small/stderr.txt
@@ -0,0 +1,2 @@
+tests/sm/checkers/sizeof_allocation/calloc_too_small/input.c: In function 'test':
+tests/sm/checkers/sizeof_allocation/calloc_too_small/input.c:32:nn: error: allocation too small: pointer to struct foo (1024 bytes) initialized with allocation of 512 bytes [CWE-131]
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/calloc_too_small/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/calloc_too_small/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/malloc_correct_size/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/malloc_correct_size/input.c
new file mode 100644
index 0000000..e0a994e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/malloc_correct_size/input.c
@@ -0,0 +1,31 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdlib.h>
+
+struct foo {
+  char buffer[256];
+  int i, j, k;
+  float x, y, z;
+};
+
+struct foo *test(void)
+{
+  return (struct foo*)malloc(sizeof(struct foo));
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/malloc_correct_size/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/malloc_correct_size/script.py
new file mode 100644
index 0000000..cd01c16
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/malloc_correct_size/script.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+from sm import main
+from sm.parser import parse_file
+
+checker = parse_file('sm/checkers/sizeof_allocation.sm')
+main([checker])
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/malloc_correct_size/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/malloc_correct_size/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/malloc_too_large/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/malloc_too_large/input.c
new file mode 100644
index 0000000..71b619e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/malloc_too_large/input.c
@@ -0,0 +1,36 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdlib.h>
+
+struct foo {
+  char buffer[128];
+};
+
+struct bar {
+  char buffer[256];
+};
+
+struct foo *test(void)
+{
+  return (struct foo*)malloc(sizeof(struct bar));
+  /* not a reportable bug: although the sizeof() is wrong, the size is large
+     enough, and some code does runtime size calculation that we don't want to
+     issue a false positive over */
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/malloc_too_large/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/malloc_too_large/script.py
new file mode 100644
index 0000000..cd01c16
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/malloc_too_large/script.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+from sm import main
+from sm.parser import parse_file
+
+checker = parse_file('sm/checkers/sizeof_allocation.sm')
+main([checker])
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/malloc_too_large/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/malloc_too_large/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/malloc_too_small/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/malloc_too_small/input.c
new file mode 100644
index 0000000..948a2de
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/malloc_too_small/input.c
@@ -0,0 +1,34 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdlib.h>
+
+struct foo {
+  char buffer[256];
+};
+
+struct bar {
+  char buffer[128];
+};
+
+struct foo *test(void)
+{
+  return (struct foo*)malloc(sizeof(struct bar));
+  /* BUG: sizeof(bar), rather than sizeof(foo), hence not enough space */
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/malloc_too_small/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/malloc_too_small/script.py
new file mode 100644
index 0000000..cd01c16
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/malloc_too_small/script.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+from sm import main
+from sm.parser import parse_file
+
+checker = parse_file('sm/checkers/sizeof_allocation.sm')
+main([checker])
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/malloc_too_small/stderr.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/malloc_too_small/stderr.txt
new file mode 100644
index 0000000..fcb1fd0
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/malloc_too_small/stderr.txt
@@ -0,0 +1,2 @@
+tests/sm/checkers/sizeof_allocation/malloc_too_small/input.c: In function 'test':
+tests/sm/checkers/sizeof_allocation/malloc_too_small/input.c:32:nn: error: allocation too small: pointer to struct foo (256 bytes) initialized with allocation of 128 bytes [CWE-131]
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/malloc_too_small/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/malloc_too_small/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/stateful_malloc_too_small/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/stateful_malloc_too_small/input.c
new file mode 100644
index 0000000..a3b3f9a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/stateful_malloc_too_small/input.c
@@ -0,0 +1,36 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdlib.h>
+
+struct foo {
+  char buffer[256];
+};
+
+struct bar {
+  char buffer[128];
+};
+
+struct foo *test(void)
+{
+  void *ptr;
+  ptr = malloc(sizeof(struct bar));
+  /* BUG: sizeof(bar), rather than sizeof(foo), hence not enough space */
+  return ptr;
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/stateful_malloc_too_small/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/stateful_malloc_too_small/script.py
new file mode 100644
index 0000000..4f333a0
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/stateful_malloc_too_small/script.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+from sm import main
+from sm.parser import parse_file
+from sm.solver import State
+
+def selftest(ctxt, solution):
+    if 0:
+        import sys
+        solution.dump(sys.stderr)
+
+    # Verify that the set_state() due to the pattern match for:
+    #     ptr = malloc(sizeof(struct bar));
+    # transitions the state of ptr from "ptr.start" to a "ptr.sized" instance
+    # with the correct size
+    node = ctxt.find_call_of('malloc')
+    ctxt.assert_statenames_for_varname(node, 'ptr', {'ptr.start'})
+
+    node = ctxt.get_successor(node)
+    ctxt.assert_states_for_varname(node, 'ptr',
+                                   {State('ptr.sized', size=128)})
+
+checker = parse_file('sm/checkers/sizeof_allocation.sm')
+main([checker], selftest=selftest)
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/stateful_malloc_too_small/stderr.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/stateful_malloc_too_small/stderr.txt
new file mode 100644
index 0000000..59aeba1
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/stateful_malloc_too_small/stderr.txt
@@ -0,0 +1,4 @@
+tests/sm/checkers/sizeof_allocation/stateful_malloc_too_small/input.c: In function 'test':
+tests/sm/checkers/sizeof_allocation/stateful_malloc_too_small/input.c:35:nn: error: allocation too small: pointer to struct foo (256 bytes) initialized with allocation of 128 bytes [CWE-131]
+tests/sm/checkers/sizeof_allocation/stateful_malloc_too_small/input.c:33:nn: note: ptr assigned to the result of malloc(128)
+tests/sm/checkers/sizeof_allocation/stateful_malloc_too_small/input.c:35:nn: note: allocation too small: pointer to struct foo (256 bytes) initialized with allocation of 128 bytes
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/stateful_malloc_too_small/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/stateful_malloc_too_small/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/unknown_size/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/unknown_size/input.c
new file mode 100644
index 0000000..ffaab54
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/unknown_size/input.c
@@ -0,0 +1,32 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdlib.h>
+
+struct foo {
+  char buffer[256];
+};
+
+struct foo *test(int i)
+{
+  void *ptr;
+  ptr = malloc(i);
+  /* might or might not be a bug; we don't know, so don't issue a bug */
+  return ptr;
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/unknown_size/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/unknown_size/script.py
new file mode 100644
index 0000000..02f3146
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/unknown_size/script.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+from sm import main
+from sm.parser import parse_file
+from sm.solver import State
+
+def selftest(ctxt, solution):
+    if 0:
+        import sys
+        solution.dump(sys.stderr)
+
+    # Verify that ptr doesn't change state due to
+    #     ptr = malloc(i);
+    # since the checker only tracks constant integer values:
+    node = ctxt.find_call_of('malloc')
+    ctxt.assert_statenames_for_varname(node, 'ptr', {'ptr.start'})
+
+    node = ctxt.get_successor(node)
+    ctxt.assert_statenames_for_varname(node, 'ptr', {'ptr.start'})
+
+checker = parse_file('sm/checkers/sizeof_allocation.sm')
+main([checker], selftest=selftest)
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/unknown_size/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/sizeof_allocation/unknown_size/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/taint/example/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/taint/example/script.py
new file mode 100644
index 0000000..9287534
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/taint/example/script.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+import gcc
+
+from sm import main
+from sm.parser import parse_file
+
+def selftest(ctxt, solution):
+    if 0:
+        import sys
+        solution.dump(sys.stderr)
+
+    # Verify the various state transitions within "test3"
+
+    # Verify that the:
+    #    D.2837_3 = fread (&tmp, 260, 1, f_2(D));
+    # transitions "tmp" from "x.start" to "x.tainted"
+    node = ctxt.find_call_of('fread', within='test3')
+    ctxt.assert_statenames_for_varname(node, 'tmp', {'x.start'})
+
+    node = ctxt.get_successor(node)
+    ctxt.assert_statenames_for_varname(node, 'tmp', {'x.tainted'})
+
+    # Verify that the:
+    #    if (D.n >= 0)
+    # transitions "D.n" from "x.tainted" to "x.has_lb"
+    node = ctxt.find_comparison_against(gcc.GeExpr, 0, within='test3')
+    tempvar = node.stmt.lhs
+    ctxt.assert_statenames_for_expr(node, tempvar, {'x.tainted'})
+
+    node = ctxt.get_true_successor(node)
+    ctxt.assert_statenames_for_expr(node, tempvar, {'x.has_lb'})
+
+    # Verify that the:
+    #    if (D.n <= 255)
+    # transitions "D.n" from "x.has_lb" to "x.ok"
+    node = ctxt.find_comparison_against(gcc.LeExpr, 255, within='test3')
+    tempvar = node.stmt.lhs
+    ctxt.assert_statenames_for_expr(node, tempvar, {'x.has_lb'})
+
+    node = ctxt.get_true_successor(node)
+    ctxt.assert_statenames_for_expr(node, tempvar, {'x.ok'})
+
+    # Verify that it's within the same equivcls as "tmp.i"
+    tmp_i = ctxt.get_expr_by_str(node, 'tmp.i')
+    ctxt.assert_statenames_for_expr(node, tmp_i, {'x.ok'})
+
+checker = parse_file('sm/checkers/taint.sm')
+#print(checker)
+dot = checker.to_dot('test_script')
+#print(dot)
+if 0:
+    from gccutils import invoke_dot
+    invoke_dot(dot)
+main([checker], selftest=selftest)
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/taint/example/stderr.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/taint/example/stderr.txt
new file mode 100644
index 0000000..f0976f0
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/checkers/taint/example/stderr.txt
@@ -0,0 +1,22 @@
+tests/sm/checkers/taint/example/input.c: In function 'test':
+tests/sm/checkers/taint/example/input.c:37:nn: error: use of tainted value tmp.i in array lookup without bounds checking [CWE-129]
+tests/sm/checkers/taint/example/input.c:34:nn: note: tmp passed to fread()
+tests/sm/checkers/taint/example/input.c:37:nn: note: state of tmp ("x.tainted") propagated to tmp.i
+tests/sm/checkers/taint/example/input.c:37:nn: note: use of tainted value tmp.i in array lookup without bounds checking
+tests/sm/checkers/taint/example/input.c: In function 'test4':
+tests/sm/checkers/taint/example/input.c:68:nn: error: use of tainted value tmp.i in array lookup without upper-bound check [CWE-129]
+tests/sm/checkers/taint/example/input.c:65:nn: note: tmp passed to fread()
+tests/sm/checkers/taint/example/input.c:66:nn: note: state of tmp ("x.tainted") propagated to tmp.i
+tests/sm/checkers/taint/example/input.c:66:nn: note: tmp.i compared against 0
+tests/sm/checkers/taint/example/input.c:68:nn: note: use of tainted value tmp.i in array lookup without upper-bound check
+tests/sm/checkers/taint/example/input.c: In function 'test5':
+tests/sm/checkers/taint/example/input.c:81:nn: error: use of tainted value tmp.i in array lookup without lower-bound check [CWE-129]
+tests/sm/checkers/taint/example/input.c:78:nn: note: tmp passed to fread()
+tests/sm/checkers/taint/example/input.c:79:nn: note: state of tmp ("x.tainted") propagated to tmp.i
+tests/sm/checkers/taint/example/input.c:79:nn: note: tmp.i compared against 255
+tests/sm/checkers/taint/example/input.c:81:nn: note: use of tainted value tmp.i in array lookup without lower-bound check
+tests/sm/checkers/taint/example/input.c: In function 'test6':
+tests/sm/checkers/taint/example/input.c:101:nn: error: use of tainted value tmp.i in array lookup without bounds checking [CWE-129]
+tests/sm/checkers/taint/example/input.c:98:nn: note: tmp passed to fread()
+tests/sm/checkers/taint/example/input.c:101:nn: note: state of tmp ("x.tainted") propagated to tmp.i
+tests/sm/checkers/taint/example/input.c:101:nn: note: use of tainted value tmp.i in array lookup without bounds checking
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/dot/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/dot/input.c
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/dot/input.c
@@ -0,0 +1 @@
+
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/dot/input.c.cpychecker-log.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/dot/input.c.cpychecker-log.txt
new file mode 100644
index 0000000..7499d84
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/dot/input.c.cpychecker-log.txt
@@ -0,0 +1 @@
+gcc.Function('test')
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/dot/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/dot/script.py
new file mode 100644
index 0000000..450cdd6
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/dot/script.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+SCRIPT = '''
+sm malloc_checker {
+  state decl any_pointer ptr;
+
+  ptr.all:
+    { ptr = malloc() } =>  ptr.unknown;
+
+  ptr.unknown, ptr.null, ptr.nonnull:
+      { ptr == 0 } => true=ptr.null, false=ptr.nonnull
+    | { ptr != 0 } => true=ptr.nonnull, false=ptr.null
+    ;
+
+  ptr.unknown:
+    { *ptr } => { error('use of possibly-NULL pointer %s' % ptr)};
+
+  ptr.null:
+    { *ptr } => { error('use of NULL pointer %s' % ptr)};
+
+  ptr.all, ptr.unknown, ptr.null, ptr.nonnull:
+    { free(ptr) } => ptr.free;
+
+  ptr.free:
+      { free(ptr) } => { error('double-free of %s' % ptr)}
+    | { ptr } => {error('use-after-free of %s' % ptr)}
+    ;
+}
+'''
+
+from sm.parser import parse_file
+ch = parse_file('sm/checkers/malloc_checker.sm')
+if 0:
+    print(ch)
+dot = ch.to_dot('test_script')
+print(dot)
+if 0:
+    from gccutils import invoke_dot
+    invoke_dot(dot)
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/dot/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/dot/stdout.txt
new file mode 100644
index 0000000..15abf23
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/dot/stdout.txt
@@ -0,0 +1,83 @@
+digraph test_script {
+  subgraph malloc_checker {
+    ptr_unchecked [label=<ptr.unchecked>];
+    ptr_null [label=<ptr.null>];
+    ptr_start [label=<ptr.start>];
+    ptr_nonnull [label=<ptr.nonnull>];
+    ptr_free [label=<ptr.free>];
+
+    ptr_unchecked -> ptr_unchecked [label=<{ ptr = malloc() }>];
+    ptr_null -> ptr_unchecked [label=<{ ptr = malloc() }>];
+    ptr_start -> ptr_unchecked [label=<{ ptr = malloc() }>];
+    ptr_nonnull -> ptr_unchecked [label=<{ ptr = malloc() }>];
+    ptr_free -> ptr_unchecked [label=<{ ptr = malloc() }>];
+    ptr_unchecked -> ptr_null [label=<{ ptr = 0 }>];
+    ptr_null -> ptr_null [label=<{ ptr = 0 }>];
+    ptr_start -> ptr_null [label=<{ ptr = 0 }>];
+    ptr_nonnull -> ptr_null [label=<{ ptr = 0 }>];
+    ptr_free -> ptr_null [label=<{ ptr = 0 }>];
+    ptr_unchecked -> ptr_null [label=<{ ptr == 0 } is True>];
+    ptr_unchecked -> ptr_nonnull [label=<{ ptr == 0 } is False>];
+    ptr_unchecked -> ptr_nonnull [label=<{ ptr != 0 } is True>];
+    ptr_unchecked -> ptr_null [label=<{ ptr != 0 } is False>];
+    ptr_null -> ptr_null [label=<{ ptr == 0 } is True>];
+    ptr_null -> ptr_nonnull [label=<{ ptr == 0 } is False>];
+    ptr_null -> ptr_nonnull [label=<{ ptr != 0 } is True>];
+    ptr_null -> ptr_null [label=<{ ptr != 0 } is False>];
+    ptr_start -> ptr_null [label=<{ ptr == 0 } is True>];
+    ptr_start -> ptr_nonnull [label=<{ ptr == 0 } is False>];
+    ptr_start -> ptr_nonnull [label=<{ ptr != 0 } is True>];
+    ptr_start -> ptr_null [label=<{ ptr != 0 } is False>];
+    ptr_nonnull -> ptr_null [label=<{ ptr == 0 } is True>];
+    ptr_nonnull -> ptr_nonnull [label=<{ ptr == 0 } is False>];
+    ptr_nonnull -> ptr_nonnull [label=<{ ptr != 0 } is True>];
+    ptr_nonnull -> ptr_null [label=<{ ptr != 0 } is False>];
+    ptr_free -> ptr_null [label=<{ ptr == 0 } is True>];
+    ptr_free -> ptr_nonnull [label=<{ ptr == 0 } is False>];
+    ptr_free -> ptr_nonnull [label=<{ ptr != 0 } is True>];
+    ptr_free -> ptr_null [label=<{ ptr != 0 } is False>];
+    ptr_unchecked -> ptr_unchecked [label=<{ *ptr }: 
+            error(&apos;dereference of possibly-NULL pointer %s&apos; % ptr,
+                  # &quot;CWE-690: Unchecked Return Value to NULL Pointer Dereference&quot;
+                  cwe=&apos;CWE-690&apos;)
+         >];
+    ptr_unchecked -> ptr_nonnull [label=<{ *ptr }>];
+    ptr_null -> ptr_null [label=<{ *ptr }: 
+            error(&apos;dereference of NULL pointer %s&apos; % ptr,
+                  # &quot;CWE-690: Unchecked Return Value to NULL Pointer Dereference&quot;
+                  cwe=&apos;CWE-690&apos;)
+         >];
+    ptr_null -> ptr_stop [label=<{ *ptr }>];
+    ptr_start -> ptr_free [label=<{ free(ptr) } >];
+    ptr_unchecked -> ptr_free [label=<{ free(ptr) } >];
+    ptr_null -> ptr_free [label=<{ free(ptr) } >];
+    ptr_nonnull -> ptr_free [label=<{ free(ptr) } >];
+    ptr_free -> ptr_free [label=<{ free(ptr) } : 
+                error(&apos;double-free of %s&apos; % ptr,
+                      # &quot;CWE-415: Double Free&quot;
+                      cwe=&apos;CWE-415&apos;)
+             >];
+    ptr_free -> ptr_stop [label=<{ free(ptr) } >];
+    ptr_free -> ptr_free [label=<{ ptr }: 
+                error(&apos;use-after-free of %s&apos; % ptr,
+                      # &quot;CWE-416: Use After Free&quot;
+                      cwe=&apos;CWE-416&apos;)
+             >];
+    ptr_free -> ptr_stop [label=<{ ptr }>];
+    ptr_unchecked -> ptr_unchecked [label=<$arg_must_not_be_null$: 
+            error(&apos;possibly-NULL pointer %s passed as argument %i to %s&apos;
+                  % (ptr, argnumber, function),
+                  # &quot;CWE-690: Unchecked Return Value to NULL Pointer Dereference&quot;
+                  cwe=&apos;CWE-690&apos;)
+          >];
+    ptr_unchecked -> ptr_nonnull [label=<$arg_must_not_be_null$>];
+    ptr_null -> ptr_null [label=<$arg_must_not_be_null$: 
+            error(&apos;NULL pointer %s passed as argument %i to %s&apos;
+                  % (ptr, argnumber, function),
+                  # &quot;CWE-690: Unchecked Return Value to NULL Pointer Dereference&quot;
+                  cwe=&apos;CWE-690&apos;)
+         >];
+    ptr_null -> ptr_stop [label=<$arg_must_not_be_null$>];
+  }
+}
+
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/facts/meet/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/facts/meet/input.c
new file mode 100644
index 0000000..b222c95
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/facts/meet/input.c
@@ -0,0 +1,41 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+extern void marker_A(void);
+extern void marker_B(void);
+extern void marker_C(void);
+extern void marker_D(void);
+
+void test(int flag, unsigned int i)
+{
+  if (!flag) {
+    return;
+  }
+  
+  marker_A();
+
+  if (i>0) {
+    marker_B();
+  } else {
+    marker_C();
+  }
+
+  marker_D();
+
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/facts/meet/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/facts/meet/script.py
new file mode 100644
index 0000000..6574be7
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/facts/meet/script.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012, 2013 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012, 2013 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+from sm import main
+from sm.parser import parse_file
+
+def selftest(ctxt, solution):
+    if 0:
+        import sys
+        solution.dump(sys.stderr)
+
+    # Verify that we know flag != 0 at marker_A:
+    node = ctxt.find_call_of('marker_A')
+    ctxt.assert_fact(node, 'flag', '!=', 0)
+
+    # We should also know it after the conditionals:
+    node = ctxt.find_call_of('marker_B')
+    ctxt.assert_fact(node, 'flag', '!=', 0)
+    ctxt.assert_fact(node, 'i', '!=', 0)
+
+    # and that due to the restricted range of i that i == 0 when !(i>0):
+    node = ctxt.find_call_of('marker_C')
+    ctxt.assert_fact(node, 'flag', '!=', 0)
+    ctxt.assert_fact(node, 'i', '==', 0)
+
+    # We should also know (flag != 0) when control flow merges:
+    node = ctxt.find_call_of('marker_D')
+    ctxt.assert_fact(node, 'flag', '!=', 0)
+
+checker = parse_file('sm/checkers/malloc_checker.sm')
+main([checker], selftest=selftest)
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/facts/meet/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/facts/meet/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/interprocedural/complex/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/interprocedural/complex/input.c
new file mode 100644
index 0000000..5a01138
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/interprocedural/complex/input.c
@@ -0,0 +1,56 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdlib.h>
+#include <string.h>
+
+static void *f(int a)
+{
+  if (a) {
+    void *p = malloc(4096);
+    return p;
+  } else {
+    return NULL;
+  }
+}
+
+static void g(int b, void *q)
+{
+  if (b==2) {
+    g(b, q); /* contrived infinite recursion */
+  }
+  free(q);
+}
+
+void h(int c)
+{
+  int *r = (int*)f(c);
+  r[0] = 42; /* BUG: the malloc in f could have failed */
+  g(c, r);
+  free(r); /* BUG: doublefree here, given that g frees the ptr */
+}
+
+static int factorial(int n)
+{
+  if (n < 2) {
+    return 1;
+  } else {
+    return n * factorial(n-1);
+  }
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/interprocedural/complex/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/interprocedural/complex/script.py
new file mode 100644
index 0000000..d6390c5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/interprocedural/complex/script.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+from sm import main
+from sm.parser import parse_file
+
+checker = parse_file('sm/checkers/malloc_checker.sm')
+main([checker])
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/interprocedural/complex/stderr.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/interprocedural/complex/stderr.txt
new file mode 100644
index 0000000..c219105
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/interprocedural/complex/stderr.txt
@@ -0,0 +1,13 @@
+tests/sm/interprocedural/complex/input.c: In function 'h':
+tests/sm/interprocedural/complex/input.c:44:nn: error: dereference of NULL pointer r [CWE-690]
+tests/sm/interprocedural/complex/input.c:44:nn: note: dereference of NULL pointer r
+tests/sm/interprocedural/complex/input.c:44:nn: error: dereference of possibly-NULL pointer r [CWE-690]
+tests/sm/interprocedural/complex/input.c:44:nn: note: dereference of possibly-NULL pointer r
+tests/sm/interprocedural/complex/input.c:46:nn: error: double-free of r [CWE-415]
+tests/sm/interprocedural/complex/input.c:44:nn: note: dereference of r
+tests/sm/interprocedural/complex/input.c:38:nn: note: q passed to free()
+tests/sm/interprocedural/complex/input.c:46:nn: note: double-free of r
+tests/sm/interprocedural/complex/input.c:46:nn: error: use-after-free of r [CWE-416]
+tests/sm/interprocedural/complex/input.c:44:nn: note: dereference of r
+tests/sm/interprocedural/complex/input.c:38:nn: note: q passed to free()
+tests/sm/interprocedural/complex/input.c:46:nn: note: use-after-free of r
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/interprocedural/complex/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/interprocedural/complex/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/interprocedural/param-inherits-from-arg/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/interprocedural/param-inherits-from-arg/input.c
new file mode 100644
index 0000000..062cf99
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/interprocedural/param-inherits-from-arg/input.c
@@ -0,0 +1,34 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdlib.h>
+#include <string.h>
+
+static void uses_ptr(int *q)
+{
+  /* BUG: ptr came from unchecked malloc in test() */
+  *q = 42;
+}
+
+int *test(void)
+{
+  int *p = (int*)malloc(sizeof(int));
+  uses_ptr(p);
+  return p;
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/interprocedural/param-inherits-from-arg/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/interprocedural/param-inherits-from-arg/script.py
new file mode 100644
index 0000000..78a1740
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/interprocedural/param-inherits-from-arg/script.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+from sm import main
+from sm.parser import parse_file
+
+def selftest(ctxt, solution):
+    if 0:
+        import sys
+        solution.dump(sys.stderr)
+
+    # Verify that uses_ptr()'s "q" param  inherits "ptr.unchecked"
+    # from the argument "p"
+    node = ctxt.find_implementation_of('uses_ptr')
+    ctxt.assert_statenames_for_varname(node, 'q', {'ptr.unchecked'})
+
+checker = parse_file('sm/checkers/malloc_checker.sm')
+main([checker], selftest=selftest)
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/interprocedural/param-inherits-from-arg/stderr.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/interprocedural/param-inherits-from-arg/stderr.txt
new file mode 100644
index 0000000..4f59744
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/interprocedural/param-inherits-from-arg/stderr.txt
@@ -0,0 +1,5 @@
+tests/sm/interprocedural/param-inherits-from-arg/input.c: In function 'uses_ptr':
+tests/sm/interprocedural/param-inherits-from-arg/input.c:26:nn: error: dereference of possibly-NULL pointer q [CWE-690]
+tests/sm/interprocedural/param-inherits-from-arg/input.c:31:nn: note: p assigned to the result of malloc()
+tests/sm/interprocedural/param-inherits-from-arg/input.c:32:nn: note: call from test() to uses_ptr(): state of p ("ptr.unchecked") propagated to q
+tests/sm/interprocedural/param-inherits-from-arg/input.c:26:nn: note: dereference of possibly-NULL pointer q
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/interprocedural/param-inherits-from-arg/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/interprocedural/param-inherits-from-arg/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/looping/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/looping/input.c
new file mode 100644
index 0000000..1bf8f6f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/looping/input.c
@@ -0,0 +1,36 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdlib.h>
+
+void test(int c)
+{
+  int i;
+  char *buffer = (char*)malloc(256);
+
+  for (i=0; i<255; i++) {
+    buffer[i] = c; /* BUG: the malloc could have failed
+                    TODO: the checker doesn't yet pick up on this due to
+                    the pointer arithmetic not picking up on the state */
+                   /* BUG use-after-free the second time through the loop */
+
+    free(buffer); /* BUG: doublefree here on second time through the  loop */
+  }
+
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/looping/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/looping/script.py
new file mode 100644
index 0000000..f5439a2
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/looping/script.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012, 2013 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012, 2013 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+from sm import main
+from sm.parser import parse_file
+
+checker = parse_file('sm/checkers/malloc_checker.sm')
+main([checker])
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/looping/stderr.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/looping/stderr.txt
new file mode 100644
index 0000000..ce8212e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/looping/stderr.txt
@@ -0,0 +1,13 @@
+tests/sm/looping/input.c: In function 'test':
+tests/sm/looping/input.c:28:nn: error: use-after-free of buffer [CWE-416]
+tests/sm/looping/input.c:25:nn: note: buffer assigned to the result of malloc()
+tests/sm/looping/input.c:33:nn: note: buffer passed to free()
+tests/sm/looping/input.c:28:nn: note: use-after-free of buffer
+tests/sm/looping/input.c:33:nn: error: double-free of buffer [CWE-415]
+tests/sm/looping/input.c:25:nn: note: buffer assigned to the result of malloc()
+tests/sm/looping/input.c:33:nn: note: buffer passed to free()
+tests/sm/looping/input.c:33:nn: note: double-free of buffer
+tests/sm/looping/input.c:33:nn: error: use-after-free of buffer [CWE-416]
+tests/sm/looping/input.c:25:nn: note: buffer assigned to the result of malloc()
+tests/sm/looping/input.c:33:nn: note: buffer passed to free()
+tests/sm/looping/input.c:33:nn: note: use-after-free of buffer
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/looping/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/looping/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/lto/input-f.c b/gcc/testsuite/gcc.dg/TODO-analyzer/lto/input-f.c
new file mode 100644
index 0000000..3a2360c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/lto/input-f.c
@@ -0,0 +1,34 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "test.h"
+
+void *f(int a)
+{
+  if (a) {
+    void *p = malloc(4096);
+    return p;
+  } else {
+    return NULL;
+  }
+}
+
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/lto/input-g.c b/gcc/testsuite/gcc.dg/TODO-analyzer/lto/input-g.c
new file mode 100644
index 0000000..816527d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/lto/input-g.c
@@ -0,0 +1,31 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "test.h"
+
+void g(int b, void *q)
+{
+  if (b==2) {
+    g(b, q); /* contrived infinite recursion */
+  }
+  free(q);
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/lto/input-h.c b/gcc/testsuite/gcc.dg/TODO-analyzer/lto/input-h.c
new file mode 100644
index 0000000..0ae7fd3
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/lto/input-h.c
@@ -0,0 +1,31 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "test.h"
+
+void h(int c)
+{
+  int *r = (int*)f(c);
+  r[0] = 42; /* BUG: the malloc in f could have failed */
+  g(c, r);
+  free(r); /* BUG: doublefree here, given that g frees the ptr */
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/lto/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/lto/script.py
new file mode 100644
index 0000000..e0cf5db
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/lto/script.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012, 2013 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012, 2013 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+from sm import main
+from sm.options import Options
+from sm.parser import parse_file
+
+opt = Options(during_lto=True)
+checker = parse_file('sm/checkers/malloc_checker.sm')
+main([checker], options=opt)
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/lto/stderr.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/lto/stderr.txt
new file mode 100644
index 0000000..28afeb9
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/lto/stderr.txt
@@ -0,0 +1,29 @@
+tests/sm/lto/input-g.c: In function 'g':
+tests/sm/lto/input-g.c:30:7: error: double-free of q [CWE-415]
+tests/sm/lto/input-g.c:30:7: note: q passed to free()
+tests/sm/lto/input-g.c:28:6: note: return from g() to g()
+tests/sm/lto/input-g.c:30:7: note: double-free of q
+tests/sm/lto/input-g.c:30:7: error: use-after-free of q [CWE-416]
+tests/sm/lto/input-g.c:30:7: note: q passed to free()
+tests/sm/lto/input-g.c:28:6: note: return from g() to g()
+tests/sm/lto/input-g.c:30:7: note: use-after-free of q
+tests/sm/lto/input-h.c: In function 'h':
+tests/sm/lto/input-h.c:28:8: error: dereference of NULL pointer r [CWE-690]
+tests/sm/lto/input-f.c:31:5: note: return value assigned to 0
+tests/sm/lto/input-h.c:27:8: note: return from f() to h(): state of return value ("ptr.null") propagated to r
+tests/sm/lto/input-h.c:28:8: note: dereference of NULL pointer r
+tests/sm/lto/input-h.c:28:8: error: dereference of possibly-NULL pointer r [CWE-690]
+tests/sm/lto/input-f.c:28:11: note: p assigned to the result of malloc()
+tests/sm/lto/input-h.c:27:8: note: return from f() to h(): state of return value ("ptr.unchecked") propagated to r
+tests/sm/lto/input-h.c:28:8: note: dereference of possibly-NULL pointer r
+tests/sm/lto/input-h.c:30:7: error: double-free of r [CWE-415]
+tests/sm/lto/input-g.c:30:7: note: q passed to free()
+tests/sm/lto/input-h.c:29:4: note: return from g() to h(): state of q ("ptr.free") propagated to r
+tests/sm/lto/input-h.c:30:7: note: double-free of r
+tests/sm/lto/input-h.c:30:7: error: use-after-free of r [CWE-416]
+tests/sm/lto/input-g.c:30:7: note: q passed to free()
+tests/sm/lto/input-h.c:29:4: note: return from g() to h(): state of q ("ptr.free") propagated to r
+tests/sm/lto/input-h.c:30:7: note: use-after-free of r
+lto-wrapper: gcc returned 1 exit status
+/usr/bin/ld: lto-wrapper failed
+collect2: error: ld returned 1 exit status
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/lto/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/lto/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/lto/test.h b/gcc/testsuite/gcc.dg/TODO-analyzer/lto/test.h
new file mode 100644
index 0000000..f5825c8
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/lto/test.h
@@ -0,0 +1,22 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+extern void *f(int a);
+extern void g(int b, void *q);
+extern void h(int c);
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/parser/malloc-checker/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/parser/malloc-checker/input.c
new file mode 100644
index 0000000..aafcadd
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/parser/malloc-checker/input.c
@@ -0,0 +1,91 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdlib.h>
+#include <string.h>
+
+#if 1
+void double_free(void *ptr)
+{
+  free(ptr);
+
+  /* BUG: double-free: */
+  free(ptr);
+}
+#endif
+
+
+#if 1
+void unchecked_malloc(void)
+{
+  void *ptr = malloc(4096);
+  memset(ptr, 0, 4096);
+}
+#endif
+
+#if 1
+int correct_usage(void)
+{
+  void *ptr = malloc(4096);
+  if (!ptr)
+    return -1; /* FIXME: with a plain return we have a BB with no gimple, and that breaks my checker */
+  memset(ptr, 0, 4096);
+  free(ptr);
+  return 0;
+}
+#endif
+
+#if 1
+int two_ptrs(void)
+{
+  void *p = malloc(4096);
+  void *q = malloc(4096);
+  if (p) {
+    memset(p, 0, 4096); /* Not a bug: checked */
+  } else {
+    memset(q, 0, 4096); /* BUG: not checked */
+  }
+  free(p);
+  free(q);
+  return 0;
+}
+#endif
+
+#if 1
+void fancy_control_flow(int i, int j)
+{
+  int k;
+  void *ptr;
+  for (k = i; k < j; k++) {
+    switch(k) {
+    case 0:
+      ptr = malloc(1024);
+      break;
+    case 1:
+      break;
+    case 2:
+      break;
+    default:
+      break;
+    }
+  }
+  memset(ptr, 0, 4096);
+  free(ptr);
+}
+#endif
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/parser/malloc-checker/input.c.cpychecker-log.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/parser/malloc-checker/input.c.cpychecker-log.txt
new file mode 100644
index 0000000..7499d84
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/parser/malloc-checker/input.c.cpychecker-log.txt
@@ -0,0 +1 @@
+gcc.Function('test')
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/parser/malloc-checker/metadata.ini b/gcc/testsuite/gcc.dg/TODO-analyzer/parser/malloc-checker/metadata.ini
new file mode 100644
index 0000000..ba7428c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/parser/malloc-checker/metadata.ini
@@ -0,0 +1,4 @@
+[ExpectedBehavior]
+# This test case emits warnings on stderr;
+# don't treat the stderr output as leading to an expected failure:
+exitcode = 0
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/parser/malloc-checker/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/parser/malloc-checker/script.py
new file mode 100644
index 0000000..a75230a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/parser/malloc-checker/script.py
@@ -0,0 +1,189 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+import unittest
+
+from sm.checker import Checker, Sm, AnyPointer, StateClause, PatternRule, \
+    ResultOfFnCall, ArgsOfFnCall, Comparison, VarDereference, VarUsage, \
+    TransitionTo, BooleanOutcome, PythonOutcome
+from sm.parser import parse_string
+
+#parse('tests/sm/parser/malloc-checker.sm')
+
+class ParserTests(unittest.TestCase):
+    def test_complex_example(self):
+        ch = parse_string('''
+sm malloc_checker {
+  stateful decl any_pointer ptr;
+
+  ptr.all:
+    { ptr = malloc() } =>  ptr.unknown;
+
+  ptr.unknown, ptr.null, ptr.nonnull:
+      { ptr == 0 } => true=ptr.null, false=ptr.nonnull
+    | { ptr != 0 } => true=ptr.nonnull, false=ptr.null
+    ;
+
+  ptr.unknown:
+    { *ptr } => {{ error('use of possibly-NULL pointer %s' % ptr) }};
+
+  ptr.null:
+    { *ptr } => {{ error('use of NULL pointer %s' % ptr) }};
+
+  ptr.all, ptr.unknown, ptr.null, ptr.nonnull:
+    { free(ptr) } => ptr.free;
+
+  ptr.free:
+      { free(ptr) } => {{ error('double-free of %s' % ptr) }}
+    | { ptr } => {{ error('use-after-free of %s' % ptr) }}
+    ;
+}
+''')
+        self.assert_(isinstance(ch, Checker))
+
+        # Verify that the str() of the parsed Checker looks sane:
+        self.assertEqual(str(ch).splitlines(),
+'''sm malloc_checker {
+  stateful decl any_pointer ptr;
+
+  ptr.all:
+    { ptr = malloc() } => ptr.unknown;
+
+  ptr.unknown, ptr.null, ptr.nonnull:
+    { ptr == 0 } => true=ptr.null, false=ptr.nonnull;
+    | { ptr != 0 } => true=ptr.nonnull, false=ptr.null;
+
+  ptr.unknown:
+    { *ptr } => {{ error('use of possibly-NULL pointer %s' % ptr) }};
+
+  ptr.null:
+    { *ptr } => {{ error('use of NULL pointer %s' % ptr) }};
+
+  ptr.all, ptr.unknown, ptr.null, ptr.nonnull:
+    { free(ptr) }  => ptr.free;
+
+  ptr.free:
+    { free(ptr) }  => {{ error('double-free of %s' % ptr) }};
+    | { ptr } => {{ error('use-after-free of %s' % ptr) }};
+
+}'''.splitlines())
+
+        self.assertEqual(len(ch.sms), 1)
+        sm = ch.sms[0]
+        self.assertEqual(sm.name, 'malloc_checker')
+        self.assertEqual(len(sm.clauses), 7)
+
+        decl = sm.clauses[0]
+        self.assertEqual(decl, AnyPointer(has_state=True, name='ptr'))
+
+        # Verify parsing of:
+        #   ptr.all:
+        #     { ptr = malloc() } =>  ptr.unknown;
+        sc = sm.clauses[1]
+        self.assertEqual(sc.statelist, ['ptr.all'])
+        self.assertEqual(len(sc.patternrulelist), 1)
+        pr = sc.patternrulelist[0]
+        self.assertEqual(pr.pattern,
+                         ResultOfFnCall(lhs='ptr',
+                                        fnname='malloc',
+                                        args=[]))
+        self.assertEqual(pr.outcomes,
+                         [TransitionTo(statename='ptr.unknown')])
+
+        # Verify parsing of:
+        #   ptr.unknown, ptr.null, ptr.nonnull:
+        #       { ptr == 0 } => true=ptr.null, false=ptr.nonnull
+        #     | { ptr != 0 } => true=ptr.nonnull, false=ptr.null
+        #     ;
+        sc = sm.clauses[2]
+        self.assertEqual(sc.statelist,
+                         ['ptr.unknown', 'ptr.null', 'ptr.nonnull'])
+        self.assertEqual(len(sc.patternrulelist), 2)
+        pr = sc.patternrulelist[0]
+        self.assertEqual(pr.pattern, Comparison(lhs='ptr', op='==', rhs=0))
+        self.assertEqual(pr.outcomes,
+                         [BooleanOutcome(guard=True, outcome=TransitionTo(statename='ptr.null')),
+                          BooleanOutcome(guard=False, outcome=TransitionTo(statename='ptr.nonnull'))])
+        pr = sc.patternrulelist[1]
+        self.assertEqual(pr.pattern, Comparison(lhs='ptr', op='!=', rhs=0))
+        self.assertEqual(pr.outcomes,
+                         [BooleanOutcome(guard=True, outcome=TransitionTo(statename='ptr.nonnull')),
+                          BooleanOutcome(guard=False, outcome=TransitionTo(statename='ptr.null'))])
+
+
+        # Verify parsing of:
+        #   ptr.unknown:
+        #     { *ptr } => {{ error('use of possibly-NULL pointer %s' % ptr) }};
+        sc = sm.clauses[3]
+        self.assertEqual(sc.statelist,
+                         ['ptr.unknown'])
+        self.assertEqual(len(sc.patternrulelist), 1)
+        pr = sc.patternrulelist[0]
+        self.assertEqual(pr.pattern, VarDereference(var='ptr'))
+        self.assertEqual(pr.outcomes,
+                         [PythonOutcome(src=(" error('use of possibly-NULL pointer %s' % ptr) "),
+                                        lineoffset=13)])
+
+        # Verify parsing of:
+        #   ptr.null:
+        #     { *ptr } => {{ error('use of NULL pointer %s' % ptr) }};
+        sc = sm.clauses[4]
+        self.assertEqual(sc.statelist,
+                         ['ptr.null'])
+        self.assertEqual(len(sc.patternrulelist), 1)
+        pr = sc.patternrulelist[0]
+        self.assertEqual(pr.pattern, VarDereference(var='ptr'))
+        self.assertEqual(pr.outcomes,
+                         [PythonOutcome(src=(" error('use of NULL pointer %s' % ptr) "),
+                                        lineoffset=16)])
+
+        # Verify parsing of:
+        #   ptr.all, ptr.unknown, ptr.null, ptr.nonnull:
+        #     { free(ptr) } => ptr.free;
+        sc = sm.clauses[5]
+        self.assertEqual(sc.statelist,
+                         ['ptr.all', 'ptr.unknown', 'ptr.null', 'ptr.nonnull'])
+        self.assertEqual(len(sc.patternrulelist), 1)
+        pr = sc.patternrulelist[0]
+        self.assertEqual(pr.pattern, ArgsOfFnCall(fnname='free', args=['ptr']))
+        self.assertEqual(pr.outcomes,
+                         [TransitionTo(statename='ptr.free')])
+
+        # Verify parsing of:
+        #   ptr.free:
+        #     { free(ptr) } => {{ error('double-free of %s' % ptr) }}
+        #     | { ptr } => {{ error('use-after-free of %s' % ptr) }}
+        sc = sm.clauses[6]
+        self.assertEqual(sc.statelist,
+                         ['ptr.free'])
+        self.assertEqual(len(sc.patternrulelist), 2)
+        pr = sc.patternrulelist[0]
+        self.assertEqual(pr.pattern, ArgsOfFnCall(fnname='free', args=['ptr']))
+        self.assertEqual(pr.outcomes,
+                         [PythonOutcome(src=" error('double-free of %s' % ptr) ",
+                                        lineoffset=22)])
+        pr = sc.patternrulelist[1]
+        self.assertEqual(pr.pattern, VarUsage(var='ptr'))
+        self.assertEqual(pr.outcomes,
+                         [PythonOutcome(src=" error('use-after-free of %s' % ptr) ",
+                                        lineoffset=23)])
+
+import sys
+sys.argv = ['foo', '-v']
+
+unittest.main()
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/arg-of-fn-call/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/arg-of-fn-call/input.c
new file mode 100644
index 0000000..04a6880
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/arg-of-fn-call/input.c
@@ -0,0 +1,25 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+extern void test2(void *);
+
+void test1(void *ptr)
+{
+  test2(ptr);
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/arg-of-fn-call/input.c.cpychecker-log.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/arg-of-fn-call/input.c.cpychecker-log.txt
new file mode 100644
index 0000000..7499d84
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/arg-of-fn-call/input.c.cpychecker-log.txt
@@ -0,0 +1 @@
+gcc.Function('test')
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/arg-of-fn-call/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/arg-of-fn-call/script.py
new file mode 100644
index 0000000..b9f41c2
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/arg-of-fn-call/script.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+from sm import main
+from sm.parser import parse_string
+
+SCRIPT = '''
+sm arg_of_fn_call {
+  stateful decl any_pointer ptr;
+
+  ptr.*:
+    { test2(ptr) } => {{ error('test2() was called with %s' % ptr) }};
+}
+'''
+
+checker = parse_string(SCRIPT)
+main([checker])
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/arg-of-fn-call/stderr.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/arg-of-fn-call/stderr.txt
new file mode 100644
index 0000000..1c2cba2
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/arg-of-fn-call/stderr.txt
@@ -0,0 +1,2 @@
+tests/sm/patterns/arg-of-fn-call/input.c: In function 'test1':
+tests/sm/patterns/arg-of-fn-call/input.c:24:nn: error: test2() was called with ptr
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/arg-of-fn-call/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/arg-of-fn-call/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/arg_must_not_be_null/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/arg_must_not_be_null/input.c
new file mode 100644
index 0000000..fdef90c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/arg_must_not_be_null/input.c
@@ -0,0 +1,44 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdlib.h>
+
+extern void foo(void *ptrA, void *ptrB, void *ptrC)
+  __attribute__((nonnull (1, 3)));
+
+extern void bar(void *ptrA, void *ptrB, void *ptrC)
+  __attribute__((nonnull (1, 3)));
+
+void foo(void *ptrA, void *ptrB, void *ptrC)
+{
+}
+
+void test(void)
+{
+  void *p;
+  void *q;
+  void *r;
+
+  p = NULL;
+  q = NULL;
+  r = NULL;
+
+  foo(p, q, r);
+  bar(p, q, r);
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/arg_must_not_be_null/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/arg_must_not_be_null/script.py
new file mode 100644
index 0000000..a270eb0
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/arg_must_not_be_null/script.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+from sm import main
+from sm.parser import parse_string
+
+SCRIPT = '''
+sm example_usage_of_nonnull_arg {
+  stateful decl any_pointer ptr;
+
+  ptr.*:
+    { ptr = 0 } => ptr.null;
+
+  ptr.null:
+    $arg_must_not_be_null$
+      => {{
+            error('%s() was called with NULL %s as argument %i/index %i: %r'
+                  % (function, ptr, argnumber, argindex, parameter))
+         }};
+}
+'''
+
+checker = parse_string(SCRIPT)
+main([checker])
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/arg_must_not_be_null/stderr.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/arg_must_not_be_null/stderr.txt
new file mode 100644
index 0000000..554a1aa
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/arg_must_not_be_null/stderr.txt
@@ -0,0 +1,21 @@
+tests/sm/patterns/arg_must_not_be_null/input.c: In function 'test':
+tests/sm/patterns/arg_must_not_be_null/input.c:42:nn: error: foo() was called with NULL p as argument 1/index 0: gcc.ParmDecl('ptrA')
+tests/sm/patterns/arg_must_not_be_null/input.c:38:nn: note: p assigned to 0
+tests/sm/patterns/arg_must_not_be_null/input.c:42:nn: note: call from test() to foo(): state of p ("ptr.null") propagated to ptrA
+tests/sm/patterns/arg_must_not_be_null/input.c:42:nn: note: return from foo() to test(): state of ptrA ("ptr.null") propagated to p
+tests/sm/patterns/arg_must_not_be_null/input.c:42:nn: note: foo() was called with NULL p as argument 1/index 0: gcc.ParmDecl('ptrA')
+tests/sm/patterns/arg_must_not_be_null/input.c:42:nn: error: foo() was called with NULL r as argument 3/index 2: gcc.ParmDecl('ptrC')
+tests/sm/patterns/arg_must_not_be_null/input.c:40:nn: note: r assigned to 0
+tests/sm/patterns/arg_must_not_be_null/input.c:42:nn: note: call from test() to foo(): state of p ("ptr.null") propagated to ptrC
+tests/sm/patterns/arg_must_not_be_null/input.c:42:nn: note: return from foo() to test(): state of ptrC ("ptr.null") propagated to r
+tests/sm/patterns/arg_must_not_be_null/input.c:42:nn: note: foo() was called with NULL r as argument 3/index 2: gcc.ParmDecl('ptrC')
+tests/sm/patterns/arg_must_not_be_null/input.c:43:nn: error: bar() was called with NULL p as argument 1/index 0: None
+tests/sm/patterns/arg_must_not_be_null/input.c:40:nn: note: r assigned to 0
+tests/sm/patterns/arg_must_not_be_null/input.c:42:nn: note: call from test() to foo(): state of p ("ptr.null") propagated to ptrA
+tests/sm/patterns/arg_must_not_be_null/input.c:42:nn: note: return from foo() to test(): state of ptrA ("ptr.null") propagated to p
+tests/sm/patterns/arg_must_not_be_null/input.c:43:nn: note: bar() was called with NULL p as argument 1/index 0: None
+tests/sm/patterns/arg_must_not_be_null/input.c:43:nn: error: bar() was called with NULL r as argument 3/index 2: None
+tests/sm/patterns/arg_must_not_be_null/input.c:39:nn: note: q assigned to 0
+tests/sm/patterns/arg_must_not_be_null/input.c:42:nn: note: call from test() to foo(): state of p ("ptr.null") propagated to ptrC
+tests/sm/patterns/arg_must_not_be_null/input.c:42:nn: note: return from foo() to test(): state of ptrC ("ptr.null") propagated to r
+tests/sm/patterns/arg_must_not_be_null/input.c:43:nn: note: bar() was called with NULL r as argument 3/index 2: None
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/arg_must_not_be_null/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/arg_must_not_be_null/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/args-of-fn-call/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/args-of-fn-call/input.c
new file mode 100644
index 0000000..6c912b7
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/args-of-fn-call/input.c
@@ -0,0 +1,25 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+extern void test2(void *param0, int param1, char param2);
+
+void test1(void *ptr, int i)
+{
+  test2(ptr, 0, 'A');
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/args-of-fn-call/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/args-of-fn-call/script.py
new file mode 100644
index 0000000..691e9a4
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/args-of-fn-call/script.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+from sm import main
+from sm.parser import parse_string
+
+SCRIPT = '''
+sm arg_of_fn_call {
+  stateful decl any_pointer arg0;
+  decl any_expr arg1;
+  decl any_expr arg2;
+
+  arg0.*:
+    { test2(arg0, arg1, arg2) } => {{ error('test2() was called with arg0:%s arg1:%s arg2:%s' % (arg0, arg1, arg2)) }};
+}
+'''
+
+checker = parse_string(SCRIPT)
+main([checker])
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/args-of-fn-call/stderr.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/args-of-fn-call/stderr.txt
new file mode 100644
index 0000000..798cb97
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/args-of-fn-call/stderr.txt
@@ -0,0 +1,2 @@
+tests/sm/patterns/args-of-fn-call/input.c: In function 'test1':
+tests/sm/patterns/args-of-fn-call/input.c:24:nn: error: test2() was called with arg0:ptr arg1:0 arg2:65
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/args-of-fn-call/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/args-of-fn-call/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/array/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/array/input.c
new file mode 100644
index 0000000..6ce27ac
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/array/input.c
@@ -0,0 +1,25 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+void test1(void)
+{
+  char buf[256];
+
+  buf[0] = 42;
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/array/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/array/script.py
new file mode 100644
index 0000000..4f72951
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/array/script.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+from sm import main
+from sm.parser import parse_string
+
+SCRIPT = '''
+sm array_access {
+  decl any_expr idx;
+  stateful decl any_expr arr;
+
+  arr.*:
+    { arr[idx] } => {{ error('lookup within array "%s" at index %s' % (arr, idx)) }};
+}
+'''
+
+checker = parse_string(SCRIPT)
+main([checker])
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/array/stderr.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/array/stderr.txt
new file mode 100644
index 0000000..ff30854
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/array/stderr.txt
@@ -0,0 +1,2 @@
+tests/sm/patterns/array/input.c: In function 'test1':
+tests/sm/patterns/array/input.c:24:nn: error: lookup within array "buf" at index 0
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/array/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/array/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/named-pattern/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/named-pattern/input.c
new file mode 100644
index 0000000..a6f25ed
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/named-pattern/input.c
@@ -0,0 +1,30 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+extern void foo(void *);
+extern void bar(void *);
+
+void test1(void *ptr)
+{
+  if (ptr) {
+    foo(ptr);
+  } else {
+    bar(ptr);
+  }
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/named-pattern/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/named-pattern/script.py
new file mode 100644
index 0000000..63b19b7
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/named-pattern/script.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+from sm import main
+from sm.parser import parse_string
+
+SCRIPT = '''
+sm checked_against_null {
+  stateful decl any_pointer ptr;
+
+  pat checked_against_null { ptr != 0 };
+
+  ptr.*:
+    checked_against_null => {{ error('%s was checked against NULL' % ptr) }};
+}
+'''
+
+checker = parse_string(SCRIPT)
+main([checker])
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/named-pattern/stderr.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/named-pattern/stderr.txt
new file mode 100644
index 0000000..4802151
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/named-pattern/stderr.txt
@@ -0,0 +1,2 @@
+tests/sm/patterns/named-pattern/input.c: In function 'test1':
+tests/sm/patterns/named-pattern/input.c:25:nn: error: ptr was checked against NULL
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/named-pattern/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/named-pattern/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/or/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/or/input.c
new file mode 100644
index 0000000..bb9c38c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/or/input.c
@@ -0,0 +1,39 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+extern void foo(void *);
+extern void bar(void *);
+
+void test1(void *ptr)
+{
+  if (ptr) {
+    foo(ptr);
+  } else {
+    bar(ptr);
+  }
+}
+
+void test2(void *ptr)
+{
+  if (!ptr) {
+    foo(ptr);
+  } else {
+    bar(ptr);
+  }
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/or/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/or/script.py
new file mode 100644
index 0000000..833ef78
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/or/script.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+from sm import main
+from sm.parser import parse_string
+
+SCRIPT = '''
+sm checked_against_null {
+  stateful decl any_pointer ptr;
+
+  ptr.*:
+    { ptr != 0 } | {ptr == 0 } => {{ error('%s was checked against NULL' % ptr) }};
+}
+'''
+
+checker = parse_string(SCRIPT)
+main([checker])
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/or/stderr.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/or/stderr.txt
new file mode 100644
index 0000000..2eb5619
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/or/stderr.txt
@@ -0,0 +1,4 @@
+tests/sm/patterns/or/input.c: In function 'test1':
+tests/sm/patterns/or/input.c:25:nn: error: ptr was checked against NULL
+tests/sm/patterns/or/input.c: In function 'test2':
+tests/sm/patterns/or/input.c:34:nn: error: ptr was checked against NULL
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/or/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/or/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/result-of-fn-call/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/result-of-fn-call/input.c
new file mode 100644
index 0000000..b39c880
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/result-of-fn-call/input.c
@@ -0,0 +1,30 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+extern void *never_call_this(void);
+
+void test1(void)
+{
+  void *ptr = never_call_this();
+}
+
+void test2(void)
+{
+  /* empty */
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/result-of-fn-call/input.c.cpychecker-log.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/result-of-fn-call/input.c.cpychecker-log.txt
new file mode 100644
index 0000000..7499d84
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/result-of-fn-call/input.c.cpychecker-log.txt
@@ -0,0 +1 @@
+gcc.Function('test')
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/result-of-fn-call/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/result-of-fn-call/script.py
new file mode 100644
index 0000000..eea7ef8
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/result-of-fn-call/script.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+from sm import main
+from sm.parser import parse_string
+
+SCRIPT = '''
+sm result_of_fn_call {
+  stateful decl any_pointer ptr;
+
+  ptr.*:
+    { ptr = never_call_this() } => {{ error('never_call_this was called %s' % ptr) }};
+}
+'''
+
+checker = parse_string(SCRIPT)
+main([checker])
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/result-of-fn-call/stderr.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/result-of-fn-call/stderr.txt
new file mode 100644
index 0000000..ff49839
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/result-of-fn-call/stderr.txt
@@ -0,0 +1,2 @@
+tests/sm/patterns/result-of-fn-call/input.c: In function 'test1':
+tests/sm/patterns/result-of-fn-call/input.c:24:nn: error: never_call_this was called ptr
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/result-of-fn-call/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/result-of-fn-call/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/var-dereference/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/var-dereference/input.c
new file mode 100644
index 0000000..346f675
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/var-dereference/input.c
@@ -0,0 +1,32 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+int test1(int *p)
+{
+  return *p;
+}
+
+struct foo {
+  int i;
+};
+
+int test2(struct foo *f)
+{
+  return f->i;
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/var-dereference/input.c.cpychecker-log.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/var-dereference/input.c.cpychecker-log.txt
new file mode 100644
index 0000000..7499d84
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/var-dereference/input.c.cpychecker-log.txt
@@ -0,0 +1 @@
+gcc.Function('test')
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/var-dereference/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/var-dereference/script.py
new file mode 100644
index 0000000..4e29c27
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/var-dereference/script.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+from sm import main
+from sm.parser import parse_string
+
+SCRIPT = '''
+sm var_dereference {
+  stateful decl any_pointer ptr;
+
+  ptr.*:
+    { *ptr } => {{ error('%s was dereferenced' % ptr) }};
+}
+'''
+
+checker = parse_string(SCRIPT)
+main([checker])
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/var-dereference/stderr.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/var-dereference/stderr.txt
new file mode 100644
index 0000000..65da25e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/var-dereference/stderr.txt
@@ -0,0 +1,4 @@
+tests/sm/patterns/var-dereference/input.c: In function 'test1':
+tests/sm/patterns/var-dereference/input.c:22:nn: error: p was dereferenced
+tests/sm/patterns/var-dereference/input.c: In function 'test2':
+tests/sm/patterns/var-dereference/input.c:31:nn: error: f was dereferenced
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/var-dereference/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/var-dereference/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/var-usage/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/var-usage/input.c
new file mode 100644
index 0000000..3518530
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/var-usage/input.c
@@ -0,0 +1,23 @@
+/*
+   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+   Copyright 2012 Red Hat, Inc.
+
+   This 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 this program.  If not, see
+   <http://www.gnu.org/licenses/>.
+*/
+
+void *test1(void *ptr)
+{
+  return ptr;
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/var-usage/input.c.cpychecker-log.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/var-usage/input.c.cpychecker-log.txt
new file mode 100644
index 0000000..7499d84
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/var-usage/input.c.cpychecker-log.txt
@@ -0,0 +1 @@
+gcc.Function('test')
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/var-usage/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/var-usage/script.py
new file mode 100644
index 0000000..013cf5d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/var-usage/script.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+from sm import main
+from sm.parser import parse_string
+
+SCRIPT = '''
+sm result_of_fn_call {
+  stateful decl any_pointer ptr;
+
+  ptr.*:
+    { ptr } => {{ error('%s was used' % ptr) }};
+}
+'''
+
+# TODO: for now this picks up on uses of temporaries
+
+checker = parse_string(SCRIPT)
+main([checker])
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/var-usage/stderr.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/var-usage/stderr.txt
new file mode 100644
index 0000000..f253b50
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/var-usage/stderr.txt
@@ -0,0 +1,3 @@
+tests/sm/patterns/var-usage/input.c: In function 'test1':
+tests/sm/patterns/var-usage/input.c:22:nn: error: ptr was used
+tests/sm/patterns/var-usage/input.c:22:nn: error: return value was used
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/var-usage/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/patterns/var-usage/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/regression/configure-with-exceptions/getopts.py b/gcc/testsuite/gcc.dg/TODO-analyzer/regression/configure-with-exceptions/getopts.py
new file mode 100644
index 0000000..6803180
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/regression/configure-with-exceptions/getopts.py
@@ -0,0 +1 @@
+print('-fexceptions')
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/regression/configure-with-exceptions/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/regression/configure-with-exceptions/input.c
new file mode 100644
index 0000000..f5711bf
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/regression/configure-with-exceptions/input.c
@@ -0,0 +1,14 @@
+/*
+   Taken from autoconf test
+*/
+
+#include <stdio.h>
+int
+main ()
+{
+  FILE *f = fopen ("conftest.out", "w");
+  return ferror (f) || fclose (f) != 0;
+
+  ;
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/regression/configure-with-exceptions/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/regression/configure-with-exceptions/script.py
new file mode 100644
index 0000000..f5439a2
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/regression/configure-with-exceptions/script.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012, 2013 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012, 2013 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+from sm import main
+from sm.parser import parse_file
+
+checker = parse_file('sm/checkers/malloc_checker.sm')
+main([checker])
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/regression/configure-with-exceptions/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/regression/configure-with-exceptions/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/regression/configure-without-exceptions/getopts.py b/gcc/testsuite/gcc.dg/TODO-analyzer/regression/configure-without-exceptions/getopts.py
new file mode 100644
index 0000000..964cadd
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/regression/configure-without-exceptions/getopts.py
@@ -0,0 +1 @@
+print('-O2')
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/regression/configure-without-exceptions/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/regression/configure-without-exceptions/input.c
new file mode 100644
index 0000000..f5711bf
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/regression/configure-without-exceptions/input.c
@@ -0,0 +1,14 @@
+/*
+   Taken from autoconf test
+*/
+
+#include <stdio.h>
+int
+main ()
+{
+  FILE *f = fopen ("conftest.out", "w");
+  return ferror (f) || fclose (f) != 0;
+
+  ;
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/regression/configure-without-exceptions/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/regression/configure-without-exceptions/script.py
new file mode 100644
index 0000000..f5439a2
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/regression/configure-without-exceptions/script.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012, 2013 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012, 2013 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+from sm import main
+from sm.parser import parse_file
+
+checker = parse_file('sm/checkers/malloc_checker.sm')
+main([checker])
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/regression/configure-without-exceptions/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/regression/configure-without-exceptions/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/regression/return-0/getopts.py b/gcc/testsuite/gcc.dg/TODO-analyzer/regression/return-0/getopts.py
new file mode 100644
index 0000000..8dd3ec3
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/regression/return-0/getopts.py
@@ -0,0 +1,23 @@
+"""
+Seeing this error:
+
+    tests/sm/regression/return-0/input.c:4:3: error: Unhandled Python exception raised calling 'execute' method
+    Traceback (most recent call last):
+      File "sm/__init__.py", line 53, in execute
+        solve(ctxt, 'solution')
+      File "sm/solver.py", line 586, in solve
+        solution = ctxt.solve(name)
+      File "sm/solver.py", line 544, in solve
+        find_leaks(self)
+      File "sm/leaks.py", line 59, in find_leaks
+        retval_aliases = get_retval_aliases(ctxt, edge.dstnode)
+      File "sm/leaks.py", line 33, in get_retval_aliases
+        retval = retval.var
+    AttributeError: 'gcc.IntegerCst' object has no attribute 'var'
+with:
+  -O2
+
+With optimization, the return statement directly returns a constant, rather
+than a temporary.
+"""
+print('-O2')
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/regression/return-0/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/regression/return-0/input.c
new file mode 100644
index 0000000..115ae04
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/regression/return-0/input.c
@@ -0,0 +1,4 @@
+int test(void)
+{
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/regression/return-0/script.py b/gcc/testsuite/gcc.dg/TODO-analyzer/regression/return-0/script.py
new file mode 100644
index 0000000..f5439a2
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/regression/return-0/script.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+#   Copyright 2012, 2013 David Malcolm <dmalcolm@redhat.com>
+#   Copyright 2012, 2013 Red Hat, Inc.
+#
+#   This 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 this program.  If not, see
+#   <http://www.gnu.org/licenses/>.
+
+from sm import main
+from sm.parser import parse_file
+
+checker = parse_file('sm/checkers/malloc_checker.sm')
+main([checker])
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/regression/return-0/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/regression/return-0/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/regression/rhs-is-vardecl/input.c b/gcc/testsuite/gcc.dg/TODO-analyzer/regression/rhs-is-vardecl/input.c
new file mode 100644
index 0000000..37458de
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/TODO-analyzer/regression/rhs-is-vardecl/input.c
@@ -0,0 +1,8 @@
+extern const char *char_ptr;
+
+int test(void)
+{
+  if (strcmp("literal", char_ptr))
+    return 1;
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/TODO-analyzer/regression/rhs-is-vardecl/stdout.txt b/gcc/testsuite/gcc.dg/TODO-analyzer/regression/rhs-is-vardecl/stdout.txt
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/analyzer/CVE-2005-1689-minimal.c b/gcc/testsuite/gcc.dg/analyzer/CVE-2005-1689-minimal.c
new file mode 100644
index 0000000..f19f5a7
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/CVE-2005-1689-minimal.c
@@ -0,0 +1,30 @@
+#include <stdlib.h>
+
+typedef struct _krb5_data {
+  char *data;
+} krb5_data;
+
+void
+test_1 (krb5_data inbuf, int flag)
+{
+  free(inbuf.data);
+  free(inbuf.data); // FIXME: double-free here
+}
+
+void
+test_2 (krb5_data inbuf, int flag)
+{
+  if (flag) {
+    free(inbuf.data);
+  }
+  free(inbuf.data); // FIXME: double-free here
+}
+
+void
+test_3 (krb5_data inbuf, int flag)
+{
+  if (flag) {
+    free((char *)inbuf.data);
+  }
+  free((char *)inbuf.data); // FIXME: double-free here
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/abort.c b/gcc/testsuite/gcc.dg/analyzer/abort.c
new file mode 100644
index 0000000..cfbf74c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/abort.c
@@ -0,0 +1,71 @@
+#include <assert.h>
+#include <stdio.h>
+#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" } */
+}
+
+/**************************************************************************/
+
+void calls_abort (const char *msg)
+{
+  fprintf (stderr, "%s", msg);
+  abort ();
+}
+
+void test_3 (void *ptr)
+{
+  if (!ptr)
+    calls_abort ("ptr was NULL");
+
+  __analyzer_eval (ptr != 0); /* { dg-warning "TRUE" } */
+}
+
+/**************************************************************************/
+
+extern void marked_noreturn (const char *msg)
+  __attribute__ ((__noreturn__));
+
+void test_4 (void *ptr)
+{
+  if (!ptr)
+    marked_noreturn ("ptr was NULL");
+
+  __analyzer_eval (ptr != 0); /* { dg-warning "TRUE" } */
+}
+
+/**************************************************************************/
+
+void test_5 (int i)
+{
+  assert (i < 10);
+
+  /* We have not defined NDEBUG, so this will call __assert_fail if
+     i >= 10, which is labelled with __attribute__ ((__noreturn__)).  */
+  __analyzer_eval (i < 10); /* { 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..4680d32
--- /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 -fdiagnostics-path-summary-format=none"
+}
+
+# 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..578a9b8
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/conditionals.c
@@ -0,0 +1,124 @@
+void test (int i, int j)
+{
+  if (i > 4)
+    {
+      __analyzer_eval (i > 4); /* { dg-warning "TRUE" } */
+      __analyzer_eval (i <= 4); /* { dg-warning "FALSE" } */
+      __analyzer_eval (i > 3); /* { dg-warning "TRUE" } */
+
+      __analyzer_eval (i > 5); /* { dg-warning "UNKNOWN" } */
+      __analyzer_eval (i != 3); /* { dg-warning "TRUE" } */
+
+      __analyzer_eval (i == 3); /* { dg-warning "FALSE" } */
+
+      __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" } */
+
+      /* Tests of transitivity.  */
+      if (j < i)
+	{
+	  __analyzer_eval (j < i); /* { dg-warning "TRUE" } */
+	  __analyzer_eval (j <= 4); /* { dg-warning "UNKNOWN" } */
+	}
+      else
+	{
+	  __analyzer_eval (j >= i); /* { dg-warning "TRUE" } */
+	  __analyzer_eval (j > 4); /* { dg-warning "TRUE" } */
+	}
+    }
+  else
+    {
+      __analyzer_eval (i > 4); /* { dg-warning "FALSE" } */
+      __analyzer_eval (i <= 4); /* { dg-warning "TRUE" } */
+      __analyzer_eval (i > 3); /* { dg-warning "UNKNOWN" } */
+
+      __analyzer_eval (i > 5); /* { dg-warning "FALSE" } */
+      __analyzer_eval (i != 3); /* { dg-warning "UNKNOWN" } */
+
+      __analyzer_eval (i == 3); /* { dg-warning "UNKNOWN" } */
+
+      __analyzer_eval (i != 4); /* { dg-warning "UNKNOWN" } */
+      __analyzer_eval (i == 4); /* { dg-warning "UNKNOWN" } */
+      __analyzer_eval (i == 5); /* { dg-warning "FALSE" } */
+      __analyzer_eval (i != 5); /* { dg-warning "TRUE" } */
+      __analyzer_eval (i < 5); /* { dg-warning "TRUE" } */
+      __analyzer_eval (i <= 5); /* { dg-warning "TRUE" } */
+    }
+}
+
+void test_2 (int i, int j, int k)
+{
+  if (i >= j)
+    {
+      __analyzer_eval (i == k); /* { dg-warning "UNKNOWN" } */
+      if (j >= k)
+	{
+	  __analyzer_eval (i >= k); /* { dg-warning "TRUE" } */
+	  __analyzer_eval (i == k); /* { dg-warning "UNKNOWN" } */
+	  if (k >= i)
+	    __analyzer_eval (i == k); /* { dg-warning "TRUE" } */
+	}
+    }
+}
+
+void test_range_int_gt_lt (int i)
+{
+  if (i > 3)
+    if (i < 5)
+      __analyzer_eval (i == 4); /* { dg-warning "TRUE" } */
+}
+
+void test_range_float_gt_lt (float f)
+{
+  if (f > 3)
+    if (f < 5)
+      __analyzer_eval (f == 4); /* { dg-warning "UNKNOWN" } */
+}
+
+void test_range_int_ge_lt (int i)
+{
+  if (i >= 4)
+    if (i < 5)
+      __analyzer_eval (i == 4); /* { dg-warning "TRUE" } */
+}
+
+void test_range_float_ge_lt (float f)
+{
+  if (f >= 4)
+    if (f < 5)
+      __analyzer_eval (f == 4); /* { dg-warning "UNKNOWN" } */
+}
+
+void test_range_int_gt_le (int i)
+{
+  if (i > 3)
+    if (i <= 4)
+      __analyzer_eval (i == 4); /* { dg-warning "TRUE" } */
+}
+
+void test_range_float_gt_le (float f)
+{
+  if (f > 3)
+    if (f <= 4)
+      __analyzer_eval (f == 4); /* { dg-warning "UNKNOWN" "" { xfail *-*-* } } */
+      /* { dg-warning "FALSE" "" { target *-*-* } .-1 } */
+}
+
+void test_range_int_ge_le (int i)
+{
+  if (i >= 4)
+    if (i <= 4)
+      __analyzer_eval (i == 4); /* { dg-warning "TRUE" } */
+}
+
+void test_range_float_ge_le (float f)
+{
+  if (f >= 4)
+    if (f <= 4)
+      __analyzer_eval (f == 4); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+      /* { dg-warning "FALSE" "" { target *-*-* } .-1 } */
+}
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/factorial.c b/gcc/testsuite/gcc.dg/analyzer/factorial.c
new file mode 100644
index 0000000..384713a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/factorial.c
@@ -0,0 +1,7 @@
+int factorial (int n)
+{
+  if (n > 1)
+    return n * factorial (n - 1);
+  else
+    return 1;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/fibonacci.c b/gcc/testsuite/gcc.dg/analyzer/fibonacci.c
new file mode 100644
index 0000000..1ca3726
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/fibonacci.c
@@ -0,0 +1,7 @@
+int fib (int n)
+{
+  if (n > 1)
+    return fib (n - 1) + fib (n - 2);
+  else
+    return n;
+}
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..af5bcc7
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/file-1.c
@@ -0,0 +1,34 @@
+#include <stdio.h>
+
+void
+test_1 (const char *path)
+{
+  FILE *f = fopen (path, "r");
+  if (!f)
+    return;
+
+  fclose (f);
+  fclose (f); /* { dg-warning "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-warning "leak of FILE 'f_in'" } */ 
+
+  fclose (f_out);
+  fclose (f_in);
+}
+
+void
+test_3 (const char *path)
+{
+  FILE *f = fopen (path, "r");
+  return; /* { dg-warning "leak of FILE 'f'" } */ 
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/function-ptr-1.c b/gcc/testsuite/gcc.dg/analyzer/function-ptr-1.c
new file mode 100644
index 0000000..38c3e59
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/function-ptr-1.c
@@ -0,0 +1,8 @@
+#include <stdlib.h>
+
+typedef void *(*fn_ptr_t) (void *);
+
+void *test_1 (fn_ptr_t fn_ptr, void *data)
+{
+  return fn_ptr (data);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/function-ptr-2.c b/gcc/testsuite/gcc.dg/analyzer/function-ptr-2.c
new file mode 100644
index 0000000..8623a9f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/function-ptr-2.c
@@ -0,0 +1,17 @@
+#include <stdlib.h>
+
+typedef void (*fn_ptr_t) (void *);
+
+void
+calls_free (void *victim)
+{
+  free (victim);
+}
+
+void test_1 (void *ptr)
+{
+  fn_ptr_t fn_ptr = calls_free;
+  fn_ptr (ptr);
+  fn_ptr (ptr);
+}
+// TODO: issue a double-free warning at 2nd call to fn_ptr.
diff --git a/gcc/testsuite/gcc.dg/analyzer/function-ptr-3.c b/gcc/testsuite/gcc.dg/analyzer/function-ptr-3.c
new file mode 100644
index 0000000..348ee4a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/function-ptr-3.c
@@ -0,0 +1,17 @@
+#include <stdlib.h>
+
+typedef void *(*alloc_func_t) (size_t);
+typedef void (*free_func_t) (void *);
+
+typedef struct callbacks
+{
+  alloc_func_t alloc_cb;
+  free_func_t dealloc_cb;
+} callbacks_t;
+
+void test (void)
+{
+  callbacks_t cb;
+  cb.alloc_cb = (alloc_func_t)0;
+  cb.dealloc_cb = (free_func_t)0;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/gzio-2.c b/gcc/testsuite/gcc.dg/analyzer/gzio-2.c
new file mode 100644
index 0000000..855ecc8
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/gzio-2.c
@@ -0,0 +1,11 @@
+void gzseek (long offset, int whence)
+{
+  if (whence == 2)
+    return;
+  if (whence == 0)
+    offset -= 1;
+  if (offset < 0)
+    return;
+  while (offset > 0) {
+  }
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/gzio.c b/gcc/testsuite/gcc.dg/analyzer/gzio.c
new file mode 100644
index 0000000..3a036d1
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/gzio.c
@@ -0,0 +1,15 @@
+#include <stdlib.h>
+typedef struct z_stream_s {
+  unsigned char *next_out;
+} z_stream;
+typedef struct gz_stream {
+  z_stream stream;
+  unsigned char *outbuf;
+} gz_stream;
+gz_stream *s;
+static void gz_open(const char *path)
+{
+  s->stream.next_out = s->outbuf = (unsigned char *)malloc(16384);
+}
+void gzopen(const char *path)
+{ return gz_open(path); }
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..b770e12
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion.c
@@ -0,0 +1,55 @@
+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();
+}
+
+/* A cycle of 4 mutually-recursive functions (but only for certain inputs).  */
+
+extern void mutual_test_1 (int flag);
+extern void mutual_test_2 (int flag);
+extern void mutual_test_3 (int flag);
+extern void mutual_test_4 (int flag);
+
+void mutual_test_1 (int flag)
+{
+  marker_A ();
+  if (flag)
+    mutual_test_2 (flag);
+}
+
+void mutual_test_2 (int flag)
+{
+  marker_B ();
+  if (flag)
+    mutual_test_3 (flag);
+}
+
+void mutual_test_3 (int flag)
+{
+  marker_C ();
+  if (flag)
+    mutual_test_4 (flag);
+}
+
+void mutual_test_4 (int flag)
+{
+  marker_D ();
+  if (flag)
+    mutual_test_1 (flag);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/loop.c b/gcc/testsuite/gcc.dg/analyzer/loop.c
new file mode 100644
index 0000000..23294f2
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/loop.c
@@ -0,0 +1,20 @@
+void test(void)
+{
+  int i;
+
+  for (i=0; i<256; i++) {
+      __analyzer_eval (i < 256); /* { dg-warning "TRUE" } */
+
+      __analyzer_eval (i == 0); /* { dg-warning "UNKNOWN" "" { xfail *-*-* } } */
+      /* { dg-warning "TRUE" "" { target *-*-* } .-1 } */
+      /* TODO(xfail^^^): we're only capturing the first iteration, so
+	 we erroneously get i == 0.  */
+
+      __analyzer_eval (i >= 0); /* { dg-warning "TRUE" } */
+  }
+
+  __analyzer_eval (i >= 256); /* { dg-warning "TRUE" } */
+  __analyzer_eval (i == 256); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  /* TODO(xfail^^^): it only figures out i >= 256, rather than i == 256.  */
+}
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..e30ff3c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-1.c
@@ -0,0 +1,364 @@
+#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-warning "double-free of 'ptr'" } */
+}
+
+void test_2 (void *ptr)
+{
+  free (ptr);
+  free (ptr); /* { dg-warning "double-free of 'ptr'" } */
+}
+
+int *test_3 (void)
+{
+  int *ptr = (int *)malloc (sizeof (int));
+  *ptr = 42; /* { dg-warning "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-warning "use of NULL 'ptr'" } */
+  return ptr;
+}
+
+int test_5 (int *ptr)
+{
+  free (ptr);
+  return *ptr; /* { dg-warning "use after free of 'ptr'" } */
+}
+
+void test_6 (void *ptr)
+{
+  void *q;
+  q = ptr;
+  free (ptr);
+  free (q); /* { dg-warning "double-free of 'q'" } */
+  /* The above case requires us 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;
+  /* This needs phi nodes to affect equivalence classes, or we get a false report
+     of a leak.  */
+}
+
+void test_9 (void)
+{
+  void *ptr = malloc (1024);
+
+  int i;
+  for (i = 0; i < 1024; i++)
+    free (ptr); /* { dg-warning "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-warning "double-free of 'ptr'" } */ 
+}
+
+void test_11 (void)
+{
+  void *ptr = malloc (1024);
+
+  while (foo ())
+    bar ();
+
+  free (ptr);
+  free (ptr); /* { dg-warning "double-free of 'ptr'" } */
+}
+
+void test_12 (void)
+{
+  void *ptr = malloc (1024);
+
+  while (1)
+    {
+      free (ptr);
+      free (ptr); /* { dg-warning "double-free of 'ptr'" } */
+    }
+}
+
+void test_13 (void)
+{
+  void *p = malloc (1024);
+  void *q = malloc (1024);
+
+  foo ();
+  if (!q)
+    {
+      free (q);
+      return; /* { dg-warning "leak of 'p'" } */ 
+    }
+  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); /* Although this looks like a double-free of q,
+	       it's known to be NULL for the case where free is
+	       called twice on it.  */
+  free (p); /* { dg-warning "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-warning "free of uninitialized 'q'" "" { xfail *-*-* } } */ 
+  /* TODO(xfail): implement uninitialized detection.  */
+  free (p);
+}
+
+void test_17 (void)
+{
+  void *ptr = malloc (1024); 
+} /* { dg-warning "leak of 'ptr'" } */ 
+
+void test_18 (void)
+{
+  void *ptr = malloc (64);
+  ptr = NULL; /* { dg-warning "leak of 'ptr'" } */ 
+}
+
+void test_19 (void)
+{
+  void *ptr = malloc (64);
+  free (ptr);
+  ptr = NULL;
+  free (ptr);
+}
+
+void *global_ptr_20;
+
+void test_20 (void)
+{
+  global_ptr_20 = malloc (1024);
+}
+
+int *test_21 (int i)
+{
+  int *ptr = malloc (sizeof (int));
+  if (!ptr)
+    abort ();
+  *ptr = i;
+  return ptr;
+}
+
+void test_22 (void)
+{
+  void *ptr = malloc (1024);
+
+  int i;
+  for (i = 5; i < 10; i++)
+    foo ();
+
+  free (ptr);
+  free (ptr); /* { dg-warning "double-free of 'ptr'" } */ 
+}
+
+int *test_23 (int n)
+{
+  int *ptr = (int *)calloc (n, sizeof (int));
+  ptr[0] = 42; /* { dg-warning "use of possibly-NULL 'ptr'" } */
+  return ptr;
+}
+
+int test_24 (void)
+{
+  void *ptr = alloca (sizeof (int)); /* { dg-message "memory is allocated on the stack here" } */
+  free (ptr); /* { dg-warning "free of memory allocated on the stack by 'alloca' \\('ptr'\\) will corrupt the heap \\\[CWE-590\\\]" } */
+}
+
+int test_25 (void)
+{
+  char tmp[100];
+  void *p = tmp; /* { dg-message "pointer is from here" } */
+  free (p); /* { dg-warning "free of 'p' which points to memory not on the heap \\\[CWE-590\\\]" } */
+  /* TODO: more precise messages here.  */
+}
+
+char global_buffer[100];
+
+int test_26 (void)
+{
+  void *p = global_buffer; /* { dg-message "pointer is from here" } */
+  free (p); /* { dg-warning "free of 'p' which points to memory not on the heap \\\[CWE-590\\\]" } */
+  /* TODO: more precise messages here.  */
+}
+
+struct coord {
+  float x;
+  float y;
+};
+
+struct coord *test_27 (void)
+{
+  struct coord *p = (struct coord *) malloc (sizeof (struct coord)); /* { dg-message "this call could return NULL" } */
+  p->x = 0.f;  /* { dg-warning "use of possibly-NULL 'p'" } */
+
+  /* Only the first such usage should be reported: */
+  p->y = 0.f;
+
+  return p;
+}
+
+struct coord *test_28 (void)
+{
+  struct coord *p = NULL;
+  p->x = 0.f; /* { dg-warning "use of NULL 'p'" } */
+
+  /* Only the first such usage should be reported: */
+  p->y = 0.f;
+
+  return p;
+}
+
+struct link
+{
+  struct link *m_ptr;
+};
+
+struct link *test_29 (void)
+{
+  struct link *res = (struct link *)malloc (sizeof (struct link));
+  if (!res)
+    return NULL;
+  res->m_ptr = (struct link *)malloc (sizeof (struct link));
+  return res;
+}
+
+#if 0
+/* FIXME: this is showing a leak.  */
+struct link *test_30 (void)
+{
+  struct link *res = (struct link *)malloc (sizeof (struct link));
+  if (!res)
+    return NULL;
+  res->m_ptr = (struct link *)malloc (sizeof (struct link));
+  if (!res->m_ptr)
+    {
+      free (res);
+      return NULL;
+    }
+  res->m_ptr->m_ptr = (struct link *)malloc (sizeof (struct link));
+  return res;
+}
+#endif
+
+/* Without consolidation by EC, this one used to show two leaks:
+     warning: leak of '<unknown>'
+     warning: leak of 'tmp.m_ptr' 
+   We should only show the latter (favoring the most user-readable
+   expression in the equivalence class).  */
+void test_30 (void)
+{
+  struct link tmp;
+  tmp.m_ptr = (struct link *)malloc (sizeof (struct link));
+} /* { dg-warning "leak of 'tmp.m_ptr'" } */ 
+
+void test_31 (void)
+{
+  struct link tmp;
+  void *ptr = malloc (sizeof (struct link));
+  tmp.m_ptr = (struct link *)ptr;
+} /* { dg-warning "leak of 'ptr'" } */ 
+
+#if 0
+void test_31 (void *p)
+{
+  void *q = realloc (p, 1024);
+  free (p); /* FIXME: this is a double-free.  */
+  free (q);
+}
+
+void test_32 (void)
+{
+  void *p = malloc (64);
+  p = realloc (p, 1024); /* FIXME: this leaks if it fails.  */
+  free (p);
+}
+#endif
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..67eb515
--- /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-warning "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-ipa-1.c b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-1.c
new file mode 100644
index 0000000..a8009b9
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-1.c
@@ -0,0 +1,24 @@
+#include <stdlib.h>
+
+void *
+calls_malloc (void)
+{
+  void *result = malloc (1024);
+  return result;
+}
+
+int *test_1 (int i)
+{
+  int *ptr = (int *)calls_malloc ();
+  *ptr = i; /* { dg-warning "use of possibly-NULL 'ptr'" } */
+  return ptr;
+}
+
+/* Same as test_1, to exercise the caches.  */
+
+int *test_2 (int i)
+{
+  int *ptr = (int *)calls_malloc ();
+  *ptr = i; /* { dg-warning "use of possibly-NULL 'ptr'" } */
+  return ptr;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-10.c b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-10.c
new file mode 100644
index 0000000..a88263f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-10.c
@@ -0,0 +1,32 @@
+#include <stdlib.h>
+
+void
+calls_free (void *victim) /* { dg-message "\\(3\\) entry to 'calls_free'" } */
+/* { dg-message "\\(7\\) entry to 'calls_free'" "" { target *-*-* } .-1 } */
+{
+  free (victim); /* { dg-warning "double-free of 'victim'" } */
+  /* { dg-message "\\(4\\) first 'free' here" "" { target *-*-* } .-1 } */
+  /* { dg-message "\\(8\\) second 'free' here" "" { target *-*-* } .-2 } */
+
+  /* TODO: would this be better emitted at the callsite,
+     for such a simple wrapper?  */
+}
+
+void do_stuff (void)
+{
+  /* Empty.  Irrelevant, and thus should not be expanded into in paths.  */
+}
+
+void test (void *ptr) /* { dg-message "\\(1\\) entry to 'test'" } */
+{
+  do_stuff ();
+
+  calls_free (ptr); /* { dg-message "\\(2\\) calling 'calls_free' from 'test'" } */
+  /* { dg-message "\\(5\\) returning to 'test' from 'calls_free'" "" { target *-*-* } .-1 } */
+
+  do_stuff ();
+
+  calls_free (ptr); /* { dg-message "\\(6\\) calling 'calls_free' from 'test'" } */
+
+  do_stuff ();
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-11.c b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-11.c
new file mode 100644
index 0000000..f3cebf8
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-11.c
@@ -0,0 +1,29 @@
+#include <stdlib.h>
+
+extern int some_condition ();
+extern void do_stuff (int);
+
+void
+may_call_free (void *victim)
+{
+  if (some_condition ())
+    return;
+
+  free (victim); /* { dg-warning "double-free of 'victim'" } */ 
+  /* { dg-message "state of 'victim': 'start' -> 'free'" "" { xfail *-*-* } .-1 } */
+  /* TODO(xfail): better expansion of call summary, to reflect the target
+     state we're interested in.  */
+}
+
+void test (void *ptr)
+{
+  do_stuff (1);
+
+  may_call_free (ptr);
+
+  do_stuff (2);
+
+  may_call_free (ptr);
+
+  do_stuff (3);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-12.c b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-12.c
new file mode 100644
index 0000000..c8361d5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-12.c
@@ -0,0 +1,8 @@
+#include <stdlib.h>
+
+void recursive_free (void *ptr)
+{
+  free (ptr); /* { dg-warning "double-free of 'ptr'" } */
+  recursive_free (ptr);
+  /* TODO: should we warn for this?  */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-2.c b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-2.c
new file mode 100644
index 0000000..f42042d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-2.c
@@ -0,0 +1,34 @@
+/* { dg-options "--analyzer -fdiagnostics-path-summary-format=none -fanalyzer-verbosity=1" } */
+
+#include <stdlib.h>
+
+void
+calls_free (void *victim) /* { dg-message "\\(3\\) entry to 'calls_free'" } */
+/* { dg-message "\\(7\\) entry to 'calls_free'" "" { target *-*-* } .-1 } */
+{
+  free (victim); /* { dg-warning "double-free of 'victim'" } */
+  /* { dg-message "\\(4\\) first 'free' here" "" { target *-*-* } .-1 } */
+  /* { dg-message "\\(8\\) second 'free' here" "" { target *-*-* } .-2 } */
+
+  /* TODO: would this be better emitted at the callsite,
+     for such a simple wrapper?  */
+}
+
+extern void do_stuff (void);
+
+void test (void *ptr) /* { dg-message "\\(1\\) entry to 'test'" } */
+{
+  do_stuff ();
+
+  calls_free (ptr); /* { dg-message "\\(2\\) calling 'calls_free' from 'test'" } */
+  /* { dg-message "\\(5\\) returning to 'test' from 'calls_free'" "" { target *-*-* } .-1 } */
+
+  do_stuff ();
+
+  calls_free (ptr); /* { dg-message "\\(6\\) calling 'calls_free' from 'test'" } */
+
+  do_stuff ();
+}
+
+
+
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-3.c b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-3.c
new file mode 100644
index 0000000..7d2f9c1
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-3.c
@@ -0,0 +1,23 @@
+#include <stdlib.h>
+
+void *
+calls_malloc (void)
+{
+  void *result = malloc (1024);
+  return result;
+}
+
+void
+calls_free (void *victim)
+{
+  free (victim); /* { dg-warning "double-free of 'victim'" } */
+  /* TODO: this would be better emitted at the callsite,
+     for such a simple wrapper.  */
+}
+
+void test (void)
+{
+  void *ptr = calls_malloc ();
+  calls_free (ptr);
+  calls_free (ptr); /* BUG: double-free.  */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-4.c b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-4.c
new file mode 100644
index 0000000..41a62c1
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-4.c
@@ -0,0 +1,13 @@
+#include <stdlib.h>
+
+static void calls_free(int *q)
+{
+  free(q);
+}
+
+void test(void *p)
+{
+  calls_free(p);
+
+  free(p); /* { dg-warning "double-free of 'p'" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-5.c b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-5.c
new file mode 100644
index 0000000..b70e7cf
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-5.c
@@ -0,0 +1,13 @@
+#include <stdlib.h>
+
+static int *calls_malloc(void)
+{
+  return malloc(sizeof(int));
+}
+
+int *test(void)
+{
+    int *p = calls_malloc();
+    *p = 42; /* { dg-warning "use of possibly-NULL 'p'" } */
+    return p;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-6.c b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-6.c
new file mode 100644
index 0000000..ea41889
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-6.c
@@ -0,0 +1,23 @@
+#include <stdlib.h>
+
+void *
+calls_malloc (void)
+{
+  void *result = malloc (1024);
+  return result;
+}
+
+void test_1 ()
+{
+  calls_malloc ();
+  // FIXME: this ought to report a leak
+}
+
+static void callee (int i)
+{
+}
+
+void test_2 (int i)
+{
+  callee (i);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-7.c b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-7.c
new file mode 100644
index 0000000..dfbe382
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-7.c
@@ -0,0 +1,29 @@
+#include <stdlib.h>
+
+/**************************************************************************/
+
+static void maybe_calls_free_1(int *q, int flag)
+{
+  if (flag)
+    free(q); /* { dg-warning "double-free of 'q'" } */
+}
+
+void test_1(void *p)
+{
+  maybe_calls_free_1(p, 1);
+  maybe_calls_free_1(p, 1); 
+}
+
+/**************************************************************************/
+
+static void maybe_calls_free_2(int *q, int flag)
+{
+  if (flag)
+    free(q); /* { dg-bogus "double-free" } */
+}
+
+void test_2(void *p)
+{
+  maybe_calls_free_2(p, 0);
+  maybe_calls_free_2(p, 0);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-double-free.c b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-double-free.c
new file mode 100644
index 0000000..7c27998
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-double-free.c
@@ -0,0 +1,132 @@
+/* Example of a multilevel wrapper around malloc/free, with a double-free.  */
+
+/* { dg-options "--analyzer -fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fanalyzer-checker=malloc -fanalyzer-verbose-state-changes" } */
+
+#include <stdlib.h>
+
+void *wrapped_malloc (size_t size)
+{
+  return malloc (size);
+}
+
+void wrapped_free (void *ptr)
+{
+  free (ptr); /* { dg-warning "double-free of 'ptr' \\\[CWE-415\\\]" } */
+}
+
+typedef struct boxed_int
+{
+  int i;
+} boxed_int;
+
+boxed_int *
+make_boxed_int (int i)
+{
+  boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+  if (!result)
+    abort ();
+  result->i = i;
+  return result;
+}
+
+void
+free_boxed_int (boxed_int *bi)
+{
+  wrapped_free (bi);
+}
+
+void test (int i)
+{
+  boxed_int *obj = make_boxed_int (i);
+
+  free_boxed_int (obj);
+
+  free_boxed_int (obj);
+}
+
+/* Double-free.  */
+/* { dg-begin-multiline-output "" }
+  'test': events 1-2
+     |
+     |   NN | void test (int i)
+     |      |      ^~~~
+     |      |      |
+     |      |      (1) entry to 'test'
+     |......
+     |   NN |   free_boxed_int (obj);
+     |      |   ~~~~~~~~~~~~~~~~~~~~
+     |      |   |
+     |      |   (2) calling 'free_boxed_int' from 'test'
+     |
+     +--> 'free_boxed_int': events 3-4
+                  |
+                  |   NN | free_boxed_int (boxed_int *bi)
+                  |      | ^~~~~~~~~~~~~~
+                  |      | |
+                  |      | (3) entry to 'free_boxed_int'
+                  |   NN | {
+                  |   NN |   wrapped_free (bi);
+                  |      |   ~~~~~~~~~~~~~~~~~
+                  |      |   |
+                  |      |   (4) calling 'wrapped_free' from 'free_boxed_int'
+                  |
+                  +--> 'wrapped_free': events 5-6
+                              |
+                              |   NN | void wrapped_free (void *ptr)
+                              |      |      ^~~~~~~~~~~~
+                              |      |      |
+                              |      |      (5) entry to 'wrapped_free'
+                              |   NN | {
+                              |   NN |   free (ptr);
+                              |      |   ~~~~~~~~~~
+                              |      |   |
+                              |      |   (6) first 'free' here (state of 'ptr': 'nonnull' -> 'free', origin: NULL)
+                              |
+                  <-----------+
+                  |
+          'free_boxed_int': event 7
+                  |
+                  |   NN |   wrapped_free (bi);
+                  |      |   ^~~~~~~~~~~~~~~~~
+                  |      |   |
+                  |      |   (7) returning to 'free_boxed_int' from 'wrapped_free'
+                  |
+     <------------+
+     |
+  'test': events 8-9
+     |
+     |   NN |   free_boxed_int (obj);
+     |      |   ^~~~~~~~~~~~~~~~~~~~
+     |      |   |
+     |      |   (8) returning to 'test' from 'free_boxed_int'
+     |   NN | 
+     |   NN |   free_boxed_int (obj);
+     |      |   ~~~~~~~~~~~~~~~~~~~~
+     |      |   |
+     |      |   (9) calling 'free_boxed_int' from 'test'
+     |
+     +--> 'free_boxed_int': events 10-11
+                  |
+                  |   NN | free_boxed_int (boxed_int *bi)
+                  |      | ^~~~~~~~~~~~~~
+                  |      | |
+                  |      | (10) entry to 'free_boxed_int'
+                  |   NN | {
+                  |   NN |   wrapped_free (bi);
+                  |      |   ~~~~~~~~~~~~~~~~~
+                  |      |   |
+                  |      |   (11) calling 'wrapped_free' from 'free_boxed_int'
+                  |
+                  +--> 'wrapped_free': events 12-13
+                              |
+                              |   NN | void wrapped_free (void *ptr)
+                              |      |      ^~~~~~~~~~~~
+                              |      |      |
+                              |      |      (12) entry to 'wrapped_free'
+                              |   NN | {
+                              |   NN |   free (ptr);
+                              |      |   ~~~~~~~~~~
+                              |      |   |
+                              |      |   (13) second 'free' here ('ptr' is in state 'free')
+                              |
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-unchecked.c b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-unchecked.c
new file mode 100644
index 0000000..f19370c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-unchecked.c
@@ -0,0 +1,68 @@
+/* Example of a multilevel wrapper around malloc, with an unchecked write.  */
+
+/* { dg-options "--analyzer -fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fanalyzer-checker=malloc -fanalyzer-verbose-state-changes" } */
+
+#include <stdlib.h>
+
+void *wrapped_malloc (size_t size)
+{
+  return malloc (size); /* { dg-message "this call could return NULL" } */
+}
+
+typedef struct boxed_int
+{
+  int i;
+} boxed_int;
+
+boxed_int *
+make_boxed_int (int i)
+{
+  boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+  result->i = i; /* { dg-warning "use of possibly-NULL 'result'" } */
+  /* { dg-message "\\(.*\\) 'result' could be NULL" "" { target *-*-* } .-1 } */
+  return result;
+}
+
+/* "use of possibly-NULL 'result' [CWE-690]".  */
+/* TODO: the state of '<return-value>': 'start' -> 'unchecked' message
+   is somewhat redundant.  */
+/* { dg-begin-multiline-output "" }
+  'make_boxed_int': events 1-2
+          |
+          |   NN | make_boxed_int (int i)
+          |      | ^~~~~~~~~~~~~~
+          |      | |
+          |      | (1) entry to 'make_boxed_int'
+          |   NN | {
+          |   NN |   boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+          |      |                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+          |      |                                    |
+          |      |                                    (2) calling 'wrapped_malloc' from 'make_boxed_int'
+          |
+          +--> 'wrapped_malloc': events 3-5
+                       |
+                       |   NN | void *wrapped_malloc (size_t size)
+                       |      |       ^~~~~~~~~~~~~~
+                       |      |       |
+                       |      |       (3) entry to 'wrapped_malloc'
+                       |   NN | {
+                       |   NN |   return malloc (size);
+                       |      |          ~~~~~~~~~~~~~
+                       |      |          |
+                       |      |          (4) this call could return NULL (state of '<unknown>': 'start' -> 'unchecked', origin: NULL)
+                       |      |          (5) state of '<return-value>': 'start' -> 'unchecked' (origin: '<unknown>')
+                       |
+          <------------+
+          |
+  'make_boxed_int': events 6-7
+          |
+          |   NN |   boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+          |      |                                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+          |      |                                    |
+          |      |                                    (6) returning to 'make_boxed_int' from 'wrapped_malloc'
+          |   NN |   result->i = i;
+          |      |   ~~~~~~~~~~~~~                     
+          |      |             |
+          |      |             (7) 'result' could be NULL ('result' is in state 'unchecked')
+          |
+  { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-9.c b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-9.c
new file mode 100644
index 0000000..325fa44
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-9.c
@@ -0,0 +1,18 @@
+/* { dg-options "--analyzer -fdiagnostics-path-summary-format=none -fanalyzer-verbosity=1" } */
+
+#include <stdlib.h>
+
+void
+two_frees (void *p, void *q)
+{
+  free (p);
+  free (q); /* { dg-warning "double-free of 'q'" } */
+  /* TODO: could be useful to identify that p == q when called from 'test'.  */
+}
+
+extern void do_stuff (void);
+
+void test (void *ptr)
+{
+  two_frees (ptr, ptr);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-many-paths.c b/gcc/testsuite/gcc.dg/analyzer/malloc-many-paths.c
new file mode 100644
index 0000000..062273d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-many-paths.c
@@ -0,0 +1,83 @@
+#include <stdlib.h>
+
+extern int foo (void);
+
+/* Ensure that we don't need to laboriously walk every path to get
+   to the end of the function.  */
+
+#if 1
+int test_1 (int n)
+{
+  int i, j, k;
+  k = 0;
+  for (int i = 0; i < n; i++)
+    for (int j = 0; j < 1000; j++)
+      k++;
+  return k;
+}
+#endif
+
+#if 1
+/* Ensure that we don't get an exponential growth in paths due to
+   repeated diamonds in the CFG.  */
+
+typedef struct obj {
+  int ob_refcnt;
+} PyObject;
+
+extern void Py_Dealloc (PyObject *op);
+
+#define Py_DECREF(op)                                   \
+    do {                                                \
+      if (--((PyObject*)(op))->ob_refcnt == 0)		\
+	Py_Dealloc((PyObject *)(op));			\
+    } while (0)
+
+int test_2 (PyObject *obj_01, PyObject *obj_02, PyObject *obj_03,
+	    PyObject *obj_04, PyObject *obj_05, PyObject *obj_06,
+	    PyObject *obj_07, PyObject *obj_08, PyObject *obj_09,
+	    PyObject *obj_10, PyObject *obj_11, PyObject *obj_12,
+	    PyObject *obj_13, PyObject *obj_14, PyObject *obj_15)
+{
+    Py_DECREF (obj_01); Py_DECREF (obj_02); Py_DECREF (obj_03);
+    Py_DECREF (obj_04); Py_DECREF (obj_05); Py_DECREF (obj_06);
+    Py_DECREF (obj_07); Py_DECREF (obj_08); Py_DECREF (obj_09);
+    Py_DECREF (obj_10); Py_DECREF (obj_11); Py_DECREF (obj_12);
+    Py_DECREF (obj_13); Py_DECREF (obj_14); Py_DECREF (obj_15);
+}
+#endif
+
+#if 1
+int successes;
+int failures;
+
+#define ONE_DIAMOND \
+    do {                                                \
+      void *ptr = malloc (128);				\
+      if (foo ())					\
+	successes++;					\
+      else						\
+	failures++;					\
+      free (ptr);					\
+    } while (0)
+
+#define TEN_DIAMONDS \
+  do {								   \
+    ONE_DIAMOND; ONE_DIAMOND; ONE_DIAMOND; ONE_DIAMOND; ONE_DIAMOND;	\
+    ONE_DIAMOND; ONE_DIAMOND; ONE_DIAMOND; ONE_DIAMOND; ONE_DIAMOND; \
+ } while (0)
+
+void test_3 (void *ptr)
+{
+  free (ptr);
+#if 1
+  ONE_DIAMOND;
+#else
+  /* TODO: enabling this leads to numerous duplicated reports,
+     all of them detailing all the extraneous info about the malloc/free
+     within the diamonds.  */
+  TEN_DIAMONDS;
+#endif
+  free (ptr); /* { dg-warning "double-free of 'ptr'" } */
+}
+#endif
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..0e743b6
--- /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-warning "double-free of 'ptr'"  "" { target *-*-* } second_free } */
+  /* { dg-message "\\(1\\) state of 'ptr': 'start' -> 'unchecked'" "" { target *-*-* } malloc } */
+  /* { dg-message "\\(2\\) first 'free' here" "" { target *-*-* } first_free } */
+  /* { dg-message "\\(3\\) second 'free' here" "" { 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..eda86c2
--- /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-warning "double-free of 'ptr'"  "" { target *-*-* } second_free } */
+  /* { dg-message "\\(1\\) first 'free' here" "" { target *-*-* } first_free } */
+  /* { dg-message "\\(2\\) second 'free' here" "" { 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..1bd4535
--- /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-warning "use of possibly-NULL 'ptr'" "" { target *-*-* } unchecked_deref } */
+  /* { dg-message "\\(1\\) this call could return NULL" "" { target *-*-* } malloc } */
+  /* { dg-message "\\(2\\) 'ptr' could be NULL" "" { 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..02de853
--- /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-warning "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/malloc-paths-5.c b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-5.c
new file mode 100644
index 0000000..7fdc8a1
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-5.c
@@ -0,0 +1,40 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+extern void do_stuff (void);
+
+int test (const char *filename, int flag)
+{
+  FILE *f;
+  int *p, *q;
+  int i;
+
+  p = (int *)malloc (sizeof (int)); /* { dg-line malloc_of_p } */
+  if (!p) /* { dg-line test_of_p } */
+    {
+      free (p);
+      return -1;
+    }
+
+  q = (int *)malloc (sizeof (int));
+  if (!q) /* { dg-line test_of_q } */
+    {
+      free (p); /* { dg-line first_free_of_p } */
+      /* oops: forgot the "return" here, so it falls through.  */
+    }
+
+  do_stuff ();
+
+  free (p); /* { dg-line second_free_of_p } */
+  free (q);
+  return 0;
+
+  /* { dg-warning "double-free of 'p'" "" { target *-*-* } second_free_of_p } */
+  /* { dg-message "\\(1\\) state of 'p': 'start' -> 'unchecked'" "" { target *-*-* } malloc_of_p } */
+  /* { dg-message "\\(2\\) state of 'p': 'unchecked' -> 'nonnull'" "" { target *-*-* } test_of_p } */
+  /* { dg-message "\\(3\\) first 'free' here" "" { target *-*-* } first_free_of_p } */
+  /* { dg-message "\\(4\\) second 'free' here" "" { target *-*-* } second_free_of_p } */
+
+  /* We don't care about the state changes to q.  */
+  /* TODO: we do care about the test of q.  */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-paths-6.c b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-6.c
new file mode 100644
index 0000000..fad6fbc
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-6.c
@@ -0,0 +1,11 @@
+#include <stdlib.h>
+
+void test (void *ptr)
+{
+  void *q;
+  q = ptr;
+  free (ptr);
+  free (q); /* { dg-warning "double-free of 'q'" } */
+  /* The above case requires us to handle equivalence classes in
+     state transitions.  */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-paths-7.c b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-7.c
new file mode 100644
index 0000000..82f54c1
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-7.c
@@ -0,0 +1,21 @@
+#include <stdlib.h>
+
+extern int foo (void);
+extern int bar (void);
+
+void test (void)
+{
+  void *p = malloc (1024); /* { dg-message "\\(1\\) allocated here" } */ 
+  void *q = malloc (1024);
+
+  foo ();
+  if (!q)
+    {
+      free (q);
+      return; /* { dg-warning "leak of 'p'" } */ 
+      /* { dg-message "\\(2\\) 'p' leaks here" "" { target *-*-* } .-1 } */
+    }
+  bar ();
+  free (q);
+  free (p);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-paths-8.c b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-8.c
new file mode 100644
index 0000000..d033969
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-8.c
@@ -0,0 +1,52 @@
+#include <stddef.h>
+#include <stdlib.h>
+
+extern void do_stuff (void *);
+
+#define LIMIT 1024
+
+void test_1 (size_t sz)
+{
+  void *ptr;
+  if (sz >= LIMIT)
+    ptr = malloc (sz);
+  else
+    ptr = alloca (sz);
+
+  do_stuff (ptr);
+
+  if (sz >= LIMIT)
+    free (ptr);
+}
+
+void test_2 (size_t sz)
+{
+  void *ptr;
+  if (sz < LIMIT)
+    ptr = alloca (sz);
+  else
+    ptr = malloc (sz);
+
+  do_stuff (ptr);
+
+  if (sz >= LIMIT)
+    free (ptr);
+}
+
+void test_3 (size_t sz)
+{
+  void *ptr;
+  if (sz <= LIMIT)
+    ptr = alloca (sz); /* { dg-message "memory is allocated on the stack here" } */
+  else
+    ptr = malloc (sz);
+
+  do_stuff (ptr);
+
+  /* Bug: the "sz <= LIMIT" above should have been "sz < LIMIT",
+     so there's a free-of-alloca when sz == LIMIT.  */
+  if (sz >= LIMIT)
+    free (ptr); /* { dg-warning "free of memory allocated on the stack by 'alloca'" } */
+}
+/* { dg-bogus "leak of 'ptr'" } */
+/* This can't happen, as "sz > 1024" && "sz <= 1023" is impossible.  */
diff --git a/gcc/testsuite/gcc.dg/analyzer/operations.c b/gcc/testsuite/gcc.dg/analyzer/operations.c
new file mode 100644
index 0000000..9ea2327
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/operations.c
@@ -0,0 +1,42 @@
+void test (int i, int j)
+{
+  int k, m;
+
+  if (i > 42) {
+    __analyzer_eval (i > 42); /* { dg-warning "TRUE" } */
+
+    i += 3;
+
+    __analyzer_eval (i > 45); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+    /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+    /* TODO(xfail): do we really know this?  what about overflow?  */
+
+    i -= 1;
+
+    __analyzer_eval (i > 44); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+    /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+    /* TODO(xfail): do we really know this?  what about overflow?  */
+
+    i = 3 * i;
+
+    __analyzer_eval (i > 132); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+    /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+    /* TODO(xfail): do we really know this?  what about overflow?  */
+
+    i /= 2;
+
+    __analyzer_eval (i > 66); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+    /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+    /* TODO(xfail): do we really know this?  what about overflow?  */
+
+    /* We don't know anything about j, so we don't know anything about k: */
+    k = i + j;
+    __analyzer_eval (k == 0); /* { dg-warning "UNKNOWN" } */
+
+    /* However, we should now know that m > 67: */
+    m = i + 1;
+    __analyzer_eval (m > 67); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+    /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+    /* TODO(xfail): do we really know this?  what about overflow?  */
+  }
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/params-2.c b/gcc/testsuite/gcc.dg/analyzer/params-2.c
new file mode 100644
index 0000000..1584ebd
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/params-2.c
@@ -0,0 +1,16 @@
+#include <stdlib.h>
+
+static void ensure_equal (int a, int b)
+{
+  if (a != b)
+    abort ();
+}
+
+void test(int i, int j)
+{
+  __analyzer_eval (i == j); /* { dg-warning "UNKNOWN" } */
+
+  ensure_equal (i, j);
+
+  __analyzer_eval (i == j); /* { dg-warning "TRUE" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/params.c b/gcc/testsuite/gcc.dg/analyzer/params.c
new file mode 100644
index 0000000..ff405f2
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/params.c
@@ -0,0 +1,32 @@
+static int called_function(int j)
+{
+  int k;
+
+  __analyzer_eval (j > 4); /* { dg-warning "TRUE" } */
+
+  k = j - 1;
+
+  __analyzer_eval (k > 3); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  /* TODO(xfail): we're not then updating based on the assignment.  */
+
+  return k;
+}
+
+void test(int i)
+{
+  __analyzer_eval (i > 4); /* { dg-warning "UNKNOWN" } */
+
+  if (i > 4) {
+
+    __analyzer_eval (i > 4); /* { dg-warning "TRUE" } */
+
+    i = called_function(i);
+
+    __analyzer_eval (i > 3); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+    /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+    /* TODO(xfail): we're not updating from the returned value.  */
+  }
+
+  __analyzer_eval (i > 3); /* { dg-warning "UNKNOWN" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/pattern-test-1.c b/gcc/testsuite/gcc.dg/analyzer/pattern-test-1.c
new file mode 100644
index 0000000..c5e6f1d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/pattern-test-1.c
@@ -0,0 +1,14 @@
+/* { dg-options "--analyzer -fanalyzer-checker=pattern-test" } */
+
+extern void foo(void *);
+extern void bar(void *);
+
+void test1(void *ptr)
+{
+  if (ptr) { /* { dg-warning "pattern match on 'ptr': != 0" } */
+  /* { dg-warning "pattern match on 'ptr': == 0" "" { target *-*-* } .-1 } */
+    foo(ptr);
+  } else {
+    bar(ptr);
+  }
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/sensitive-1.c b/gcc/testsuite/gcc.dg/analyzer/sensitive-1.c
new file mode 100644
index 0000000..b884179
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/sensitive-1.c
@@ -0,0 +1,33 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+
+char test_1 (FILE *logfile)
+{
+  char *password = getpass (">"); /* { dg-message "\\(1\\) state of 'password': 'start' -> 'sensitive'" } */
+  fprintf (logfile, "got password %s\n", password); /* { dg-warning "sensitive value 'password' written to output file \\\[CWE-532\\\]" } */
+  /* { dg-message "\\(2\\) here \\('password' is in state 'sensitive'\\)" "" { target *-*-* } .-1 } */
+}
+
+char test_2 (FILE *logfile, int i)
+{
+  char *password = getpass (">"); /* { dg-message "\\(1\\) state of 'password': 'start' -> 'sensitive'" } */
+  fprintf (logfile, "got password[%i]: %s\n", i, password); /* { dg-warning "sensitive value 'password' written to output file \\\[CWE-532\\\]" } */
+  /* { dg-message "\\(2\\) here \\('password' is in state 'sensitive'\\)" "" { target *-*-* } .-1 } */
+}
+
+char test_3 (FILE *logfile)
+{
+  char *password = getpass (">"); /* { dg-message "\\(1\\) state of 'password': 'start' -> 'sensitive'" } */
+  printf ("got password %s\n", password); /* { dg-warning "sensitive value 'password' written to output file \\\[CWE-532\\\]" } */
+  /* { dg-message "\\(2\\) here \\('password' is in state 'sensitive'\\)" "" { target *-*-* } .-1 } */
+}
+
+char test_4 (FILE *logfile)
+{
+  char *password = getpass (">"); /* { dg-message "\\(1\\) state of 'password': 'start' -> 'sensitive'" } */
+  fwrite (password, strlen (password), 1, logfile); /* { dg-warning "sensitive value 'password' written to output file \\\[CWE-532\\\]" } */
+  /* { dg-message "\\(2\\) here \\('password' is in state 'sensitive'\\)" "" { target *-*-* } .-1 } */
+}
+
+/* TODO: strdup etc, strcpy, memcpy, etc.  */
diff --git a/gcc/testsuite/gcc.dg/analyzer/switch.c b/gcc/testsuite/gcc.dg/analyzer/switch.c
new file mode 100644
index 0000000..a55ba2b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/switch.c
@@ -0,0 +1,26 @@
+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" } */
+      __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;
+    }
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/taint-1.c b/gcc/testsuite/gcc.dg/analyzer/taint-1.c
new file mode 100644
index 0000000..08ff892
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/taint-1.c
@@ -0,0 +1,119 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct foo
+{
+  signed int i;
+  char buf[256];
+};
+
+char test_1(FILE *f)
+{
+  struct foo tmp;
+
+  if (1 == fread(&tmp, sizeof(tmp), 1, f)) { /* { dg-message "\\(1\\) state of 'tmp': 'start' -> 'tainted'" } */
+    /* BUG: the following array lookup trusts that the input data's index is
+       in the range 0 <= i < 256; otherwise it's accessing the stack */
+    return tmp.buf[tmp.i]; // { dg-warning "use of tainted value '<unknown>' in array lookup without bounds checking" } */
+    /* { dg-message "23: \\(2\\) state of '<unknown>': 'start' -> 'tainted'" "" { target *-*-* } .-1 } */
+    /* { dg-message "\\(3\\) here \\('<unknown>' is in state 'tainted'\\)" "" { target *-*-* } .-2 } */
+    
+    // TODO: better description of value
+    // TOOD: better messages for state changes
+  }
+  return 0;
+}
+
+char test_2(struct foo *f, int i)
+{
+  /* not a bug: the data is not known to be tainted: */
+  return f->buf[f->i];
+}
+
+#if 0
+char test_3(FILE *f)
+{
+  struct foo tmp;
+
+  if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
+    if (tmp.i >= 0 && tmp.i < 256) {
+      /* not a bug: the access is guarded by upper and lower bounds: */
+      return tmp.buf[tmp.i];
+    }
+  }
+  return 0;
+}
+
+char test_4(FILE *f)
+{
+  struct foo tmp;
+
+  if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
+    if (tmp.i >= 0) {
+      /* BUG: has a lower bound, but not an upper bound: */
+      return tmp.buf[tmp.i];
+    }
+  }
+  return 0;
+}
+
+char test_5(FILE *f)
+{
+  struct foo tmp;
+
+  if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
+    if (tmp.i < 256) {
+      /* BUG: has an upper bound, but not a lower bound: */
+      return tmp.buf[tmp.i];
+    }
+  }
+  return 0;
+}
+
+/* unsigned types have a natural lower bound of 0 */
+struct bar
+{
+  unsigned int i;
+  char buf[256];
+};
+
+char test_6(FILE *f)
+{
+  struct bar tmp;
+
+  if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
+    if (tmp.i >= 0) {
+      /* BUG: has an implicit lower bound, but not an upper bound: */
+      return tmp.buf[tmp.i];
+    }
+  }
+  return 0;
+}
+
+char test_7(FILE *f)
+{
+  struct bar tmp;
+
+  if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
+    if (tmp.i < 256) {
+      /* not a bug: has an upper bound, and an implicit lower bound: */
+      return tmp.buf[tmp.i];
+    }
+  }
+  return 0;
+}
+
+char test_8(FILE *f)
+{
+  struct foo tmp;
+
+  if (1 == fread(&tmp, sizeof(tmp), 1, f)) {
+    if (tmp.i == 42) {
+      /* not a bug: tmp.i compared against a specific value: */
+      return tmp.buf[tmp.i];
+    }
+  }
+  return 0;
+}
+#endif
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-path-summary-format-default.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-summary-format-default.c
new file mode 100644
index 0000000..d68f856
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-summary-format-default.c
@@ -0,0 +1,140 @@
+/* { dg-do compile } */
+/* { dg-options "" } */
+
+#include <stdlib.h>
+
+void *wrapped_malloc (size_t size)
+{
+  return malloc (size);
+}
+
+void wrapped_free (void *ptr)
+{
+  free (ptr); /* { dg-warning "double-free of 'ptr' \\\[CWE-415\\]" } */
+  /* { dg-begin-multiline-output "" }
+  'test': events 1-2
+     |
+     | {
+     | ^
+     | |
+     | (1) entering 'test'
+     |   boxed_int *obj = make_boxed_int (i);
+     |                    ~~~~~~~~~~~~~~~~~~
+     |                    |
+     |                    (2) calling 'make_boxed_int'
+     |
+     +--> 'make_boxed_int': events 3-4
+                  |
+                  | {
+                  | ^
+                  | |
+                  | (3) entering 'make_boxed_int'
+                  |   boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+                  |                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                  |                                    |
+                  |                                    (4) calling 'wrapped_malloc'
+                  |
+                  +--> 'wrapped_malloc': events 5-6
+                               |
+                               | {
+                               | ^
+                               | |
+                               | (5) entering 'wrapped_malloc'
+                               |   return malloc (size);
+                               |          ~~~~~~~~~~~~~
+                               |          |
+                               |          (6) calling 'malloc'
+                               |
+     <-------------------------+
+     |
+  'test': event 7
+     |
+     |   free_boxed_int (obj);
+     |   ^~~~~~~~~~~~~~~~~~~~
+     |   |
+     |   (7) calling 'free_boxed_int'
+     |
+     +--> 'free_boxed_int': events 8-9
+                  |
+                  | {
+                  | ^
+                  | |
+                  | (8) entering 'free_boxed_int'
+                  |   wrapped_free (bi);
+                  |   ~~~~~~~~~~~~~~~~~
+                  |   |
+                  |   (9) calling 'wrapped_free'
+                  |
+                  +--> 'wrapped_free': events 10-11
+                              |
+                              | {
+                              | ^
+                              | |
+                              | (10) entering 'wrapped_free'
+                              |   free (ptr);
+                              |   ~~~~~~~~~~
+                              |   |
+                              |   (11) calling 'free'
+                              |
+     <------------------------+
+     |
+  'test': event 12
+     |
+     |   free_boxed_int (obj);
+     |   ^~~~~~~~~~~~~~~~~~~~
+     |   |
+     |   (12) calling 'free_boxed_int'
+     |
+     +--> 'free_boxed_int': events 13-14
+                  |
+                  | {
+                  | ^
+                  | |
+                  | (13) entering 'free_boxed_int'
+                  |   wrapped_free (bi);
+                  |   ~~~~~~~~~~~~~~~~~
+                  |   |
+                  |   (14) calling 'wrapped_free'
+                  |
+                  +--> 'wrapped_free': events 15-16
+                              |
+                              | {
+                              | ^
+                              | |
+                              | (15) entering 'wrapped_free'
+                              |   free (ptr);
+                              |   ~~~~~~~~~~
+                              |   |
+                              |   (16) calling 'free'
+                              |
+     { dg-end-multiline-output "" } */
+}
+
+typedef struct boxed_int
+{
+  int i;
+} boxed_int;
+
+boxed_int *
+make_boxed_int (int i)
+{
+  boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+  result->i = i;
+  return result;
+}
+
+void
+free_boxed_int (boxed_int *bi)
+{
+  wrapped_free (bi);
+}
+
+void test (int i)
+{
+  boxed_int *obj = make_boxed_int (i);
+
+  free_boxed_int (obj);
+
+  free_boxed_int (obj);
+}
+
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-path-summary-format-event-summaries.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-summary-format-event-summaries.c
new file mode 100644
index 0000000..903c405
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-summary-format-event-summaries.c
@@ -0,0 +1,74 @@
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-path-summary-format=event-summaries" } */
+
+#include <stdlib.h>
+
+void *wrapped_malloc (size_t size)
+{
+  return malloc (size);
+}
+
+void wrapped_free (void *ptr)
+{
+  free (ptr); /* { dg-warning "double-free of 'ptr' \\\[CWE-415\\]" } */
+  /* { dg-begin-multiline-output "" }
+  'test': events 1-2
+     | (1): entering 'test'
+     | (2): calling 'make_boxed_int'
+     +--> 'make_boxed_int': events 3-4
+                  | (3): entering 'make_boxed_int'
+                  | (4): calling 'wrapped_malloc'
+                  +--> 'wrapped_malloc': events 5-6
+                               | (5): entering 'wrapped_malloc'
+                               | (6): calling 'malloc'
+     <-------------------------+
+     |
+  'test': event 7
+     | (7): calling 'free_boxed_int'
+     +--> 'free_boxed_int': events 8-9
+                  | (8): entering 'free_boxed_int'
+                  | (9): calling 'wrapped_free'
+                  +--> 'wrapped_free': events 10-11
+                              | (10): entering 'wrapped_free'
+                              | (11): calling 'free'
+     <------------------------+
+     |
+  'test': event 12
+     | (12): calling 'free_boxed_int'
+     +--> 'free_boxed_int': events 13-14
+                  | (13): entering 'free_boxed_int'
+                  | (14): calling 'wrapped_free'
+                  +--> 'wrapped_free': events 15-16
+                              | (15): entering 'wrapped_free'
+                              | (16): calling 'free'
+     { dg-end-multiline-output "" } */
+}
+
+typedef struct boxed_int
+{
+  int i;
+} boxed_int;
+
+boxed_int *
+make_boxed_int (int i)
+{
+  boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+  result->i = i;
+  return result;
+}
+
+void
+free_boxed_int (boxed_int *bi)
+{
+  wrapped_free (bi);
+}
+
+void test (int i)
+{
+  boxed_int *obj = make_boxed_int (i);
+
+  free_boxed_int (obj);
+
+  free_boxed_int (obj);
+}
+
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-path-summary-format-inline-events.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-summary-format-inline-events.c
new file mode 100644
index 0000000..c111e11
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-summary-format-inline-events.c
@@ -0,0 +1,140 @@
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-path-summary-format=inline-events" } */
+
+#include <stdlib.h>
+
+void *wrapped_malloc (size_t size)
+{
+  return malloc (size);
+}
+
+void wrapped_free (void *ptr)
+{
+  free (ptr); /* { dg-warning "double-free of 'ptr' \\\[CWE-415\\]" } */
+  /* { dg-begin-multiline-output "" }
+  'test': events 1-2
+     |
+     | {
+     | ^
+     | |
+     | (1) entering 'test'
+     |   boxed_int *obj = make_boxed_int (i);
+     |                    ~~~~~~~~~~~~~~~~~~
+     |                    |
+     |                    (2) calling 'make_boxed_int'
+     |
+     +--> 'make_boxed_int': events 3-4
+                  |
+                  | {
+                  | ^
+                  | |
+                  | (3) entering 'make_boxed_int'
+                  |   boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+                  |                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                  |                                    |
+                  |                                    (4) calling 'wrapped_malloc'
+                  |
+                  +--> 'wrapped_malloc': events 5-6
+                               |
+                               | {
+                               | ^
+                               | |
+                               | (5) entering 'wrapped_malloc'
+                               |   return malloc (size);
+                               |          ~~~~~~~~~~~~~
+                               |          |
+                               |          (6) calling 'malloc'
+                               |
+     <-------------------------+
+     |
+  'test': event 7
+     |
+     |   free_boxed_int (obj);
+     |   ^~~~~~~~~~~~~~~~~~~~
+     |   |
+     |   (7) calling 'free_boxed_int'
+     |
+     +--> 'free_boxed_int': events 8-9
+                  |
+                  | {
+                  | ^
+                  | |
+                  | (8) entering 'free_boxed_int'
+                  |   wrapped_free (bi);
+                  |   ~~~~~~~~~~~~~~~~~
+                  |   |
+                  |   (9) calling 'wrapped_free'
+                  |
+                  +--> 'wrapped_free': events 10-11
+                              |
+                              | {
+                              | ^
+                              | |
+                              | (10) entering 'wrapped_free'
+                              |   free (ptr);
+                              |   ~~~~~~~~~~
+                              |   |
+                              |   (11) calling 'free'
+                              |
+     <------------------------+
+     |
+  'test': event 12
+     |
+     |   free_boxed_int (obj);
+     |   ^~~~~~~~~~~~~~~~~~~~
+     |   |
+     |   (12) calling 'free_boxed_int'
+     |
+     +--> 'free_boxed_int': events 13-14
+                  |
+                  | {
+                  | ^
+                  | |
+                  | (13) entering 'free_boxed_int'
+                  |   wrapped_free (bi);
+                  |   ~~~~~~~~~~~~~~~~~
+                  |   |
+                  |   (14) calling 'wrapped_free'
+                  |
+                  +--> 'wrapped_free': events 15-16
+                              |
+                              | {
+                              | ^
+                              | |
+                              | (15) entering 'wrapped_free'
+                              |   free (ptr);
+                              |   ~~~~~~~~~~
+                              |   |
+                              |   (16) calling 'free'
+                              |
+     { dg-end-multiline-output "" } */
+}
+
+typedef struct boxed_int
+{
+  int i;
+} boxed_int;
+
+boxed_int *
+make_boxed_int (int i)
+{
+  boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+  result->i = i;
+  return result;
+}
+
+void
+free_boxed_int (boxed_int *bi)
+{
+  wrapped_free (bi);
+}
+
+void test (int i)
+{
+  boxed_int *obj = make_boxed_int (i);
+
+  free_boxed_int (obj);
+
+  free_boxed_int (obj);
+}
+
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-path-summary-format-none.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-summary-format-none.c
new file mode 100644
index 0000000..2efdd74
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-summary-format-none.c
@@ -0,0 +1,43 @@
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-path-summary-format=none" } */
+
+#include <stdlib.h>
+
+void *wrapped_malloc (size_t size)
+{
+  return malloc (size);
+}
+
+void wrapped_free (void *ptr)
+{
+  free (ptr); /* { dg-warning "double-free of 'ptr' \\\[CWE-415\\]" } */
+}
+
+typedef struct boxed_int
+{
+  int i;
+} boxed_int;
+
+boxed_int *
+make_boxed_int (int i)
+{
+  boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+  result->i = i;
+  return result;
+}
+
+void
+free_boxed_int (boxed_int *bi)
+{
+  wrapped_free (bi);
+}
+
+void test (int i)
+{
+  boxed_int *obj = make_boxed_int (i);
+
+  free_boxed_int (obj);
+
+  free_boxed_int (obj);
+}
+
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-path-summary-format-simple.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-summary-format-simple.c
new file mode 100644
index 0000000..097eb55
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-summary-format-simple.c
@@ -0,0 +1,66 @@
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-show-path-depths -fdiagnostics-path-summary-format=simple" } */
+
+#include <stdlib.h>
+
+void *wrapped_malloc (size_t size)
+{
+  return malloc (size);
+}
+
+void wrapped_free (void *ptr)
+{
+  free (ptr); /* { dg-warning "double-free of 'ptr' \\\[CWE-415\\]" } */
+  /* { dg-begin-multiline-output "" }
+  'test': events 1-2 (depth 0)
+     |
+     +--> 'make_boxed_int': events 3-4 (depth 1)
+                  |
+                  +--> 'wrapped_malloc': events 5-6 (depth 2)
+                               |
+     <-------------------------+
+     |
+  'test': event 7 (depth 0)
+     |
+     +--> 'free_boxed_int': events 8-9 (depth 1)
+                  |
+                  +--> 'wrapped_free': events 10-11 (depth 2)
+                              |
+     <------------------------+
+     |
+  'test': event 12 (depth 0)
+     |
+     +--> 'free_boxed_int': events 13-14 (depth 1)
+                  |
+                  +--> 'wrapped_free': events 15-16 (depth 2)
+     { dg-end-multiline-output "" } */
+}
+
+typedef struct boxed_int
+{
+  int i;
+} boxed_int;
+
+boxed_int *
+make_boxed_int (int i)
+{
+  boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+  result->i = i;
+  return result;
+}
+
+void
+free_boxed_int (boxed_int *bi)
+{
+  wrapped_free (bi);
+}
+
+void test (int i)
+{
+  boxed_int *obj = make_boxed_int (i);
+
+  free_boxed_int (obj);
+
+  free_boxed_int (obj);
+}
+
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c
index d813348..82d7ee9 100644
--- a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c
@@ -33,15 +33,17 @@
 #include "plugin-version.h"
 #include "diagnostic.h"
 #include "diagnostic-path.h"
+#include "diagnostic-metadata.h"
 #include "context.h"
 #include "print-tree.h"
 #include "gcc-rich-location.h"
+#include "cgraph.h"
 
 int plugin_is_GPL_compatible;
 
 const pass_data pass_data_test_show_path =
 {
-  GIMPLE_PASS, /* type */
+  IPA_PASS, /* type */
   "test_show_path", /* name */
   OPTGROUP_NONE, /* optinfo_flags */
   TV_NONE, /* tv_id */
@@ -52,11 +54,20 @@ const pass_data pass_data_test_show_path =
   0, /* todo_flags_finish */
 };
 
-class pass_test_show_path : public gimple_opt_pass
+class pass_test_show_path : public ipa_opt_pass_d
 {
 public:
   pass_test_show_path(gcc::context *ctxt)
-    : gimple_opt_pass(pass_data_test_show_path, ctxt)
+    : ipa_opt_pass_d (pass_data_test_show_path, 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 */
   {}
 
   /* opt_pass methods: */
@@ -96,10 +107,10 @@ check_for_named_call (gimple *stmt,
   return call;
 }
 
-/* FIXME.  */
+/* Example 1: a purely intraprocedural path.  */
 
-unsigned int
-pass_test_show_path::execute (function *fun)
+static void
+example_1 ()
 {
   gimple_stmt_iterator gsi;
   basic_block bb;
@@ -107,47 +118,239 @@ pass_test_show_path::execute (function *fun)
   gcall *call_to_PyList_Append = NULL;
   gcall *call_to_PyList_New = NULL;
   gcond *for_cond = NULL;
+  function *example_a_fun = NULL;
 
-  FOR_EACH_BB_FN (bb, fun)
-    for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
-      {
-	gimple *stmt = gsi_stmt (gsi);
-	if (gcall *call = check_for_named_call (stmt, "PyList_New", 1))
-	  call_to_PyList_New = call;
-	if (gcall *call = check_for_named_call (stmt, "PyList_Append", 2))
-	  call_to_PyList_Append = call;
-	if (gcond *cond = dyn_cast <gcond *> (stmt))
-	  for_cond = cond;
-      }
+  cgraph_node *node;
+  FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
+    {
+      function *fun = node->get_fun ();
+      FOR_EACH_BB_FN (bb, fun)
+	{
+	  for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
+	    {
+	      gimple *stmt = gsi_stmt (gsi);
+	      if (gcall *call = check_for_named_call (stmt, "PyList_New", 1))
+		{
+		  call_to_PyList_New = call;
+		  example_a_fun = fun;
+		}
+	      if (gcall *call = check_for_named_call (stmt, "PyList_Append", 2))
+		call_to_PyList_Append = call;
+	      if (gcond *cond = dyn_cast <gcond *> (stmt))
+		for_cond = cond;
+	    }
+	}
+    }
 
   if (call_to_PyList_New && for_cond && call_to_PyList_Append)
     {
       auto_diagnostic_group d;
       gcc_rich_location richloc (gimple_location (call_to_PyList_Append));
-      diagnostic_path path;
+      simple_diagnostic_path path (global_dc->printer);
       path.add_event (gimple_location (call_to_PyList_New),
-		      fun->decl, 0,
+		      example_a_fun->decl, 0,
 		      "when %qs fails, returning NULL",
 		      "PyList_New");
       path.add_event (gimple_location (for_cond),
-		      fun->decl, 0,
+		      example_a_fun->decl, 0,
 		      "when %qs", "i < count");
       path.add_event (gimple_location (call_to_PyList_Append),
-		      fun->decl, 0,
+		      example_a_fun->decl, 0,
 		      "when calling %qs, passing NULL as argument %i",
 		      "PyList_Append", 1);
       richloc.set_path (&path);
       error_at (&richloc,
-		"passing NULL as argument %i to %qs which requires a non-NULL parameter",
+		"passing NULL as argument %i to %qs"
+		" which requires a non-NULL parameter",
 		1, "PyList_Append");
       // TODO: show param within decl
       //inform (
     }
+}
+
+/* FIXME.  */
+
+struct event_location_t
+{
+  event_location_t ()
+  : m_fun (NULL), m_loc (UNKNOWN_LOCATION)
+  {}
+
+  event_location_t (function *fun, location_t loc)
+  : m_fun (fun), m_loc (loc)
+  {}
+
+  void set (const gimple *stmt, function *fun)
+  {
+    m_fun = fun;
+    m_loc = gimple_location (stmt);
+  }
+
+  function *m_fun;
+  location_t m_loc;
+};
+
+/* FIXME.  */
+
+static void
+check_for_named_function (function *fun, const char *funcname,
+			  event_location_t *out_entry)
+{
+  gcc_assert (fun);
+  gcc_assert (funcname);
+
+  if (strcmp (IDENTIFIER_POINTER (DECL_NAME (fun->decl)), funcname))
+    return;
+
+  *out_entry = event_location_t (fun, fun->function_start_locus);
+}
+
+
+/* Example 2: an interprocedural path.  */
+
+class test_diagnostic_path : public simple_diagnostic_path
+{
+ public:
+  test_diagnostic_path (pretty_printer *event_pp)
+  : simple_diagnostic_path (event_pp)
+  {
+  }
+  void add_entry (event_location_t evloc, int stack_depth,
+		  const char *funcname)
+  {
+    gcc_assert (evloc.m_fun);
+    add_event (evloc.m_loc, evloc.m_fun->decl, stack_depth,
+	       "entering %qs", funcname);
+  }
+
+#if 0
+  void add_return (tree fndecl, int stack_depth)
+  {
+    add_event (UNKNOWN_LOCATION, fndecl, stack_depth,
+	       "returning to %qE", fndecl);
+  }
+#endif
+  void add_call (event_location_t call_evloc, int caller_stack_depth,
+		 event_location_t callee_entry_evloc, const char *callee)
+  {
+    gcc_assert (call_evloc.m_fun);
+    add_event (call_evloc.m_loc, call_evloc.m_fun->decl, caller_stack_depth,
+	       "calling %qs", callee);
+    add_entry (callee_entry_evloc, caller_stack_depth + 1, callee);
+  }
+
+  void add_leaf_call (event_location_t call_evloc, int caller_stack_depth,
+		      const char *callee)
+  {
+    gcc_assert (call_evloc.m_fun);
+    add_event (call_evloc.m_loc, call_evloc.m_fun->decl, caller_stack_depth,
+	       "calling %qs", callee);
+  }
+};
+
+static void
+example_2 ()
+{
+  gimple_stmt_iterator gsi;
+  basic_block bb;
+
+  event_location_t entry_to_wrapped_malloc;
+  event_location_t call_to_malloc;
+
+  event_location_t entry_to_wrapped_free;
+  event_location_t call_to_free;
+
+  event_location_t entry_to_make_boxed_int;
+  event_location_t call_to_wrapped_malloc;
+
+  event_location_t entry_to_free_boxed_int;
+  event_location_t call_to_wrapped_free;
+
+  event_location_t entry_to_test;
+  event_location_t call_to_make_boxed_int;
+  event_location_t call_to_free_boxed_int;
+
+  cgraph_node *node;
+  FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
+    {
+      function *fun = node->get_fun ();
+      FOR_EACH_BB_FN (bb, fun)
+	{
+	  check_for_named_function (fun, "wrapped_malloc",
+				    &entry_to_wrapped_malloc);
+	  check_for_named_function (fun, "wrapped_free",
+				    &entry_to_wrapped_free);
+	  check_for_named_function (fun, "make_boxed_int",
+				    &entry_to_make_boxed_int);
+	  check_for_named_function (fun, "free_boxed_int",
+				    &entry_to_free_boxed_int);
+	  check_for_named_function (fun, "test",
+				    &entry_to_test);
+
+	  for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
+	    {
+	      gimple *stmt = gsi_stmt (gsi);
+	      if (gcall *call = check_for_named_call (stmt, "malloc", 1))
+		call_to_malloc.set (call, fun);
+	      if (gcall *call = check_for_named_call (stmt, "free", 1))
+		call_to_free.set (call, fun);
+	      if (gcall *call = check_for_named_call (stmt, "wrapped_malloc", 1))
+		call_to_wrapped_malloc.set (call, fun);
+	      if (gcall *call = check_for_named_call (stmt, "wrapped_free", 1))
+		call_to_wrapped_free.set (call, fun);
+	      if (gcall *call = check_for_named_call (stmt, "make_boxed_int", 1))
+		call_to_make_boxed_int.set (call, fun);
+	      if (gcall *call = check_for_named_call (stmt, "free_boxed_int", 1))
+		call_to_free_boxed_int.set (call, fun);
+	    }
+	}
+    }
+
+  if (call_to_malloc.m_fun)
+    {
+      auto_diagnostic_group d;
+
+      gcc_rich_location richloc (call_to_free.m_loc);
+      test_diagnostic_path path (global_dc->printer);
+      path.add_entry (entry_to_test, 0, "test");
+      path.add_call (call_to_make_boxed_int, 0,
+		     entry_to_make_boxed_int, "make_boxed_int");
+      path.add_call (call_to_wrapped_malloc, 1,
+		     entry_to_wrapped_malloc, "wrapped_malloc");
+      path.add_leaf_call (call_to_malloc, 2, "malloc");
+
+      for (int i = 0; i < 2; i++)
+	{
+	  path.add_call (call_to_free_boxed_int, 0,
+			 entry_to_free_boxed_int, "free_boxed_int");
+	  path.add_call (call_to_wrapped_free, 1,
+			 entry_to_wrapped_free, "wrapped_free");
+	  path.add_leaf_call (call_to_free, 2, "free");
+	}
+
+      richloc.set_path (&path);
+
+      diagnostic_metadata m;
+      m.add_cwe (415); /* CWE-415: Double Free.  */
+
+      warning_at (&richloc, m,
+		  "double-free of %qs", "ptr");
+    }
+}
+
+
+/* FIXME.  */
+
+unsigned int
+pass_test_show_path::execute (function *)
+{
+  example_1 ();
+  example_2 ();
 
   return 0;
 }
 
-static gimple_opt_pass *
+static opt_pass *
 make_pass_test_show_path (gcc::context *ctxt)
 {
   return new pass_test_show_path (ctxt);
@@ -166,9 +369,9 @@ plugin_init (struct plugin_name_args *plugin_info,
     return 1;
 
   pass_info.pass = make_pass_test_show_path (g);
-  pass_info.reference_pass_name = "ssa";
+  pass_info.reference_pass_name = "whole-program";
   pass_info.ref_pass_instance_number = 1;
-  pass_info.pos_op = PASS_POS_INSERT_AFTER;
+  pass_info.pos_op = PASS_POS_INSERT_BEFORE;
   register_callback (plugin_name, PLUGIN_PASS_MANAGER_SETUP, NULL,
 		     &pass_info);
 
diff --git a/gcc/testsuite/gcc.dg/plugin/plugin.exp b/gcc/testsuite/gcc.dg/plugin/plugin.exp
index 635a387..60065e6 100644
--- a/gcc/testsuite/gcc.dg/plugin/plugin.exp
+++ b/gcc/testsuite/gcc.dg/plugin/plugin.exp
@@ -59,7 +59,12 @@ set plugin_test_list [list \
     { diagnostic_plugin_test_paths.c \
 	  diagnostic-test-paths-1.c \
 	  diagnostic-test-paths-2.c \
-	  diagnostic-test-paths-3.c } \
+	  diagnostic-test-paths-3.c \
+	  diagnostic-path-summary-format-default.c \
+	  diagnostic-path-summary-format-none.c \
+	  diagnostic-path-summary-format-simple.c \
+	  diagnostic-path-summary-format-event-summaries.c \
+	  diagnostic-path-summary-format-inline-events.c } \
 ]
 
 foreach plugin_test $plugin_test_list {
diff --git a/gcc/toplev.c b/gcc/toplev.c
index 6b3bbdc..55d4bdb 100644
--- a/gcc/toplev.c
+++ b/gcc/toplev.c
@@ -1119,6 +1119,12 @@ general_init (const char *argv0, bool init_signals)
     = global_options_init.x_flag_diagnostics_show_labels;
   global_dc->show_line_numbers_p
     = global_options_init.x_flag_diagnostics_show_line_numbers;
+  global_dc->show_metadata
+    = global_options_init.x_flag_diagnostics_show_metadata;
+  global_dc->path_summary_format
+    = (enum diagnostic_path_summary_format)global_options_init.x_flag_diagnostics_path_summary_format;
+  global_dc->show_path_depths
+    = global_options_init.x_flag_diagnostics_show_path_depths;
   global_dc->show_option_requested
     = global_options_init.x_flag_diagnostics_show_option;
   global_dc->min_margin_width
diff --git a/gcc/tree-diagnostic.c b/gcc/tree-diagnostic.c
index 162b31f..91a3881 100644
--- a/gcc/tree-diagnostic.c
+++ b/gcc/tree-diagnostic.c
@@ -32,6 +32,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "diagnostic-path.h"
 #include "json.h"
 #include "gcc-rich-location.h"
+#include "diagnostic-color.h"
+#include "selftest.h"
 
 /* Prints out, if necessary, the name of the current function
    that caused an error.  Called from all error and warning functions.  */
@@ -322,10 +324,14 @@ class path_label : public range_label
   label_text get_text (unsigned range_idx) const FINAL OVERRIDE
   {
     unsigned event_idx = m_start_idx + range_idx;
-    const diagnostic_path::event *event = m_path->m_events[event_idx];
-
-    return label_text (xasprintf ("(%i) %s", event_idx + 1, event->m_desc),
-		       true);
+    const diagnostic_event &event = m_path->get_event (event_idx);
+
+    label_text event_text (event.get_desc ());
+    label_text result
+      = label_text::take (xasprintf ("(%i) %s", event_idx + 1,
+				     event_text.m_buffer));
+    event_text.maybe_free ();
+    return result;
   }
 
  private:
@@ -333,27 +339,35 @@ class path_label : public range_label
   unsigned m_start_idx;
 };
 
+static void
+print_event_range (pretty_printer *pp,
+		   const diagnostic_path *path,
+		   unsigned start_idx, unsigned end_idx,
+		   bool use_diagnostic);
+
+/* FIXME.  */
+
 static bool
-can_consolidate_events (diagnostic_path::event *e1,
-			diagnostic_path::event *e2)
+can_consolidate_events (const diagnostic_event &e1,
+			const diagnostic_event &e2)
 {
   // FIXME: might want to simply group them by function, and also
   // split whenever there's a backward jump?
-  if (e1->m_fndecl != e2->m_fndecl)
+  if (e1.get_fndecl () != e2.get_fndecl ())
     return false;
 
 #if 0
   // FIXME: do we need this?
-  if (e1->m_depth != e2->m_depth)
+  if (e1.m_depth != e2.m_depth)
     return false;
 #endif
 
   /* Neither can be macro-based.  */
   if (linemap_location_from_macro_expansion_p (line_table,
-					       e1->m_loc))
+					       e1.get_location ()))
     return false;
   if (linemap_location_from_macro_expansion_p (line_table,
-					       e2->m_loc))
+					       e2.get_location ()))
     return false;
 
   // TODO:
@@ -363,81 +377,439 @@ can_consolidate_events (diagnostic_path::event *e1,
   return true;
 }
 
+/* A class for grouing together the events in a diagnostic_path into
+   ranges of events, partitioned by stack frame (i.e. by fndecl and
+   stack depth).
+
+   For use by print_path_summary.  */
+
+struct interprocedural_path_summary
+{
+  /* A range of consecutive events within a diagnostic_path,
+     all with the same fndecl and stack_depth.  */
+  struct event_range
+  {
+    event_range (tree fndecl, int stack_depth, int start_idx, int end_idx)
+    : m_fndecl (fndecl), m_stack_depth (stack_depth),
+      m_start_idx (start_idx), m_end_idx (end_idx)
+    {}
+    tree m_fndecl;
+    int m_stack_depth;
+    int m_start_idx;
+    int m_end_idx;
+  };
+  interprocedural_path_summary (const diagnostic_path &path);
+
+  auto_vec <event_range> m_ranges;
+};
+
+/* interprocedural_path_summary's ctor.  */
+
+interprocedural_path_summary::
+interprocedural_path_summary (const diagnostic_path &path)
+{
+  const unsigned num_events = path.num_events ();
+
+  tree cur_fndecl = NULL_TREE;
+  int cur_stack_depth = 0;
+  unsigned cur_start_idx = 0;
+  bool in_range = false;
+  for (unsigned idx = 0; idx < num_events; idx++)
+    {
+      tree ev_fndecl = path.get_event (idx).get_fndecl ();
+      int ev_stack_depth = path.get_event (idx).get_stack_depth ();
+      if (ev_fndecl != cur_fndecl
+	  || ev_stack_depth != cur_stack_depth)
+	{
+	  if (in_range)
+	    m_ranges.safe_push (event_range (cur_fndecl, cur_stack_depth,
+					     cur_start_idx, idx - 1));
+	  in_range = true;
+	  cur_start_idx = idx;
+	}
+      cur_fndecl = ev_fndecl;
+      cur_stack_depth = ev_stack_depth;
+    }
+  if (in_range)
+    {
+      m_ranges.safe_push (event_range (cur_fndecl, cur_stack_depth,
+				       cur_start_idx, num_events - 1));
+    }
+}
+
 /* FIXME.  */
-// This needs to be in tree-diagnostic.c so we can hack up current_function_decl,
-// which is used by lhd_print_error_function etc.
 
-void
-tree_print_diagnostic_path (diagnostic_context *context,
-			    const diagnostic_path *path)
+static void
+write_indent (pretty_printer *pp, int spaces)
 {
+  for (int i = 0; i < spaces; i++)
+    pp_space (pp);
+}
 
-  /* TODO: options for different printing approaches.  */
-  if (context->show_caret && context->show_line_numbers_p)
+/* FIXME.  */
+/* We can't use "%qE" here since we can't guarantee the
+   capabilities of PP; -Wformat will complain.  */
+
+static void
+print_fndecl (pretty_printer *pp, tree fndecl, bool quoted)
+{
+  const char *n = DECL_NAME (fndecl)
+    ? identifier_to_locale (lang_hooks.decl_printable_name (fndecl, 2))
+    : _("<anonymous>");
+  if (quoted)
+    pp_printf (pp, "%qs", n);
+  else
+    pp_string (pp, n);
+}
+
+/* Print SUMMARY to PP, giving an overview of the interprocedural
+   calls and returns.
+
+   For example:
+
+   'foo' (events 1-2)
+     |
+     +--> 'bar' (events 3-4)
+            |
+            +--> 'baz' (events 5-6)
+                   |
+     <------------ +
+     |
+   'foo' (events 7-8)
+     |
+     +--> 'bar' (events 9-10)
+            |
+            +--> 'baz' (events 11-12).  */
+
+static void
+print_path_summary (pretty_printer *pp,
+		    const interprocedural_path_summary& summary,
+		    const diagnostic_path *path, bool show_depths,
+		    enum diagnostic_path_summary_format summary_format)
+{
+  auto_vec<int> name_lengths;
+
+  /* Populate name_lengths.  */
+  {
+    pretty_printer *tmp_pp = pp->clone ();
+    pp_show_color (tmp_pp) = 0;
+    int i;
+    interprocedural_path_summary::event_range *range;
+    FOR_EACH_VEC_ELT (summary.m_ranges, i, range)
+      {
+	print_fndecl (tmp_pp, range->m_fndecl, false);
+	/* We want the length of the quoted decl in characters, not in bytes.
+	   For now, we get the length of the unquoted decl (in bytes), hope
+	   it's ASCII, and add 2 for the quotes.  */
+	name_lengths.safe_push (strlen (pp_formatted_text (tmp_pp)) + 2);
+	pp_clear_output_area (tmp_pp);
+      }
+    delete tmp_pp;
+  }
+
+  /* FIXME: do we want this to have its own name?  */
+  const char *const line_color = "note";
+  const char *start_line_color
+    = colorize_start (pp_show_color (pp), line_color);
+  const char *end_line_color = colorize_stop (pp_show_color (pp));
+
+  /* Keep track of column numbers of existing '|' characters for
+     stack depths we've already printed.  */
+  const int EMPTY = -1;
+  const int DELETED = -2;
+  typedef int_hash <int, EMPTY, DELETED> vbar_hash;
+  hash_map <vbar_hash, int> vbar_column_for_depth;
+
+  /* Print the ranges.  */
+  const int base_indent = 2;
+  int cur_indent = base_indent;
+  unsigned i;
+  interprocedural_path_summary::event_range *range;
+  FOR_EACH_VEC_ELT (summary.m_ranges, i, range)
     {
-      /* Try consolidating events into rich locations.  */
-      unsigned start_idx = 0;
-      while (start_idx < path->m_events.length ())
+      write_indent (pp, cur_indent);
+      if (i > 0)
+	{
+	  const interprocedural_path_summary::event_range *prev_range
+	    = &summary.m_ranges[i - 1];
+	  if (range->m_stack_depth > prev_range->m_stack_depth)
+	    {
+	      /* Show pushed stack frame(s).  */
+	      const char *push_prefix = "+--> ";
+	      pp_string (pp, start_line_color);
+	      pp_string (pp, push_prefix);
+	      pp_string (pp, end_line_color);
+	      cur_indent += strlen (push_prefix);
+	    }
+	}
+      print_fndecl (pp, range->m_fndecl, true);
+      if (range->m_start_idx == range->m_end_idx)
+	pp_printf (pp, ": event %i",
+		   range->m_start_idx + 1);
+      else
+	pp_printf (pp, ": events %i-%i",
+		   range->m_start_idx + 1, range->m_end_idx + 1);
+      if (show_depths)
+	pp_printf (pp, " (depth %i)", range->m_stack_depth);
+      pp_newline (pp);
+
+      int midpoint = name_lengths[i] / 2;
+
+      switch (summary_format)
 	{
-	  path_label label (path, start_idx);
-	  diagnostic_path::event *event = path->m_events[start_idx];
-	  gcc_rich_location richloc (event->m_loc);
-	  unsigned j;
-	  for (j = start_idx + 1; j < path->m_events.length (); j++)
+	default:
+	case DPSF_NONE:
+	  gcc_unreachable ();
+
+	case DPSF_SIMPLE:
+	  break;
+	case DPSF_EVENT_SUMMARIES:
+	  {
+	    for (int event_idx = range->m_start_idx;
+		 event_idx <= range->m_end_idx; event_idx++)
+	      {
+		write_indent (pp, cur_indent + midpoint);
+		label_text event_desc = path->get_event (event_idx).get_desc ();
+		pp_string (pp, start_line_color);
+		pp_string (pp, "|");
+		pp_string (pp, end_line_color);
+		pp_printf (pp, " (%i): %s",
+			   event_idx + 1, event_desc.m_buffer);
+		event_desc.maybe_free ();
+		pp_newline (pp);
+	      }
+	  }
+	  break;
+	case DPSF_INLINE_EVENTS:
+	  {
+	    write_indent (pp, cur_indent + midpoint);
+	    pp_string (pp, start_line_color);
+	    pp_string (pp, "|");
+	    pp_string (pp, end_line_color);
+	    /* No newline here, as diagnostic_show_locus adds a leading
+	       newline.  */
+
+	    char *saved_prefix = pp_take_prefix (pp);
+	    char *prefix;
+	    {
+	      pretty_printer tmp_pp;
+	      write_indent (&tmp_pp, cur_indent + midpoint);
+	      pp_string (&tmp_pp, start_line_color);
+	      pp_string (&tmp_pp, "|");
+	      pp_string (&tmp_pp, end_line_color);
+	      prefix = xstrdup (pp_formatted_text (&tmp_pp));
+	    }
+	    pp_set_prefix (pp, prefix);
+	    pp_prefixing_rule (pp) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
+	    print_event_range (pp, path,
+			       range->m_start_idx, range->m_end_idx,
+			       false);
+	    pp_set_prefix (pp, saved_prefix);
+
+	    write_indent (pp, cur_indent + midpoint);
+	    pp_string (pp, start_line_color);
+	    pp_string (pp, "|");
+	    pp_string (pp, end_line_color);
+	    pp_newline (pp);
+	  }
+	  break;
+	}
+
+      if (i < summary.m_ranges.length () - 1)
+	{
+	  const interprocedural_path_summary::event_range *next_range
+	    = &summary.m_ranges[i + 1];
+
+	  if (summary_format == DPSF_SIMPLE)
 	    {
-	      diagnostic_path::event *other = path->m_events[j];
-	      // TODO: label, and slacken the constraint
-	      if (can_consolidate_events (event, other))
-		if (richloc.add_location_if_nearby (other->m_loc, false,
-						    &label))
-		  continue;
-	      break;
+	      write_indent (pp, cur_indent + midpoint);
+	      pp_string (pp, start_line_color);
+	      pp_printf (pp, "|");
+	      pp_string (pp, end_line_color);
+	      pp_newline (pp);
 	    }
 
+	  if (range->m_stack_depth > next_range->m_stack_depth)
+	    {
+	      /* Show returning from stack frame(s), by printing
+		 something like:
+		 "                   |\n"
+		 "     <------------ +\n"
+		 "     |\n".  */
+
+	      int next_midpoint = name_lengths[i + 1] / 2;
+
+	      gcc_assert (vbar_column_for_depth.get
+			  (next_range->m_stack_depth));
+
+	      int vbar_for_next_frame
+		= *vbar_column_for_depth.get (next_range->m_stack_depth);
+
+	      int indent_for_next_frame = vbar_for_next_frame - next_midpoint;
+	      write_indent (pp, vbar_for_next_frame);
+	      pp_string (pp, start_line_color);
+	      pp_character (pp, '<');
+	      for (int i = indent_for_next_frame + next_midpoint;
+		   i < cur_indent + midpoint - 1; i++)
+		pp_character (pp, '-');
+	      pp_character (pp, '+');
+	      pp_string (pp, end_line_color);
+	      pp_newline (pp);
+	      cur_indent = indent_for_next_frame;
+
+	      write_indent (pp, vbar_for_next_frame);
+	      pp_string (pp, start_line_color);
+	      pp_printf (pp, "|");
+	      pp_string (pp, end_line_color);
+	      pp_newline (pp);
+	    }
+	  else if (range->m_stack_depth < next_range->m_stack_depth)
+	    {
+	      /* Prepare to show pushed stack frame.  */
+	      gcc_assert (range->m_stack_depth != EMPTY);
+	      gcc_assert (range->m_stack_depth != DELETED);
+	      vbar_column_for_depth.put (range->m_stack_depth,
+					 cur_indent + midpoint);
+	      cur_indent += midpoint;
+	    }
+
+	}
+    }
+  // TODO: maybe show state changes?
+  // TODO: maybe show all events
+  /* TODO: example of recursion in DejaGnu tests.  */
+}
+
+/* FIXME.
+   Print events, consolidating them into rich locations.  */
+
+static void
+print_event_range (pretty_printer *pp,
+		   const diagnostic_path *path,
+		   unsigned start_idx, unsigned end_idx,
+		   bool use_diagnostic)
+{
+  while (start_idx <= end_idx)
+    {
+      path_label label (path, start_idx);
+      const diagnostic_event &event = path->get_event (start_idx);
+      gcc_rich_location richloc (event.get_location (),
+				 use_diagnostic ? NULL : &label);
+      unsigned j;
+      for (j = start_idx + 1; j <= end_idx; j++)
+	{
+	  const diagnostic_event &other = path->get_event (j);
+	  // TODO: label, and slacken the constraint
+	  if (can_consolidate_events (event, other))
+	    if (richloc.add_location_if_nearby (other.get_location (),
+						false, &label))
+	      continue;
+	  break;
+	}
+
 #if 1
-	  /* Hack up state so that diagnostic_report_current_function will announce
-	     function name changes.  */
-	  tree saved_current_function_decl = current_function_decl;
-	  location_t saved_input_location = input_location;
-	  current_function_decl = event->m_fndecl;
-	  input_location = event->m_loc;
+      /* Hack up state so that diagnostic_report_current_function will announce
+	 function name changes.  */
+      tree saved_current_function_decl = current_function_decl;
+      location_t saved_input_location = input_location;
+      current_function_decl = event.get_fndecl ();
+      input_location = event.get_location ();
 #endif
 
 #if 0
-	  /* Alternatively, part of lhd_print_error_function.  */
-	  char *old_prefix = pp_take_prefix (context->printer);
-	  const char *file = LOCATION_FILE (event->m_loc);
-	  char *new_prefix = file ? file_name_as_prefix (context, file) : NULL;
-	  tree fndecl = event->m_fndecl;
-	  if (TREE_CODE (TREE_TYPE (fndecl)) == METHOD_TYPE)
-	    pp_printf
-	      (context->printer, _("In member function %qs"),
-	       identifier_to_locale (lang_hooks.decl_printable_name (fndecl, 2)));
-	  else
-	    pp_printf
-	      (context->printer, _("In function %qs"),
-	       identifier_to_locale (lang_hooks.decl_printable_name (fndecl, 2)));
-	  pp_newline_and_flush (context->printer);
-	  context->printer->prefix = old_prefix;
-	  free ((char*) new_prefix);
+      /* Alternatively, part of lhd_print_error_function.  */
+      char *old_prefix = pp_take_prefix (pp);
+      const char *file = LOCATION_FILE (event->m_loc);
+      char *new_prefix = file ? file_name_as_prefix (context, file) : NULL;
+      tree fndecl = event->m_fndecl;
+      if (TREE_CODE (TREE_TYPE (fndecl)) == METHOD_TYPE)
+	pp_printf
+	  (pp, _("In member function %qs"),
+	   identifier_to_locale (lang_hooks.decl_printable_name (fndecl, 2)));
+      else
+	pp_printf
+	  (pp, _("In function %qs"),
+	   identifier_to_locale (lang_hooks.decl_printable_name (fndecl, 2)));
+      pp_newline_and_flush (pp);
+      pp->prefix = old_prefix;
+      free ((char*) new_prefix);
 #endif
 
-	  inform (&richloc, "(%i) %s", start_idx + 1, event->m_desc);
-	  start_idx = j;
+      if (use_diagnostic)
+	{
+	  label_text event_text (event.get_desc ());
+	  inform (&richloc, "(%i) %s", start_idx + 1, event_text.m_buffer);
+	  event_text.maybe_free ();
+	}
+      else
+	{
+	  diagnostic_context dc;
+	  memset (&dc, 0, sizeof (dc));
+	  dc.printer = pp;
+	  dc.show_caret = true;
+	  dc.show_labels_p = global_dc->show_labels_p;
+	  dc.show_line_numbers_p = global_dc->show_line_numbers_p;
+	  dc.start_span = global_dc->start_span;
+	  dc.min_margin_width = global_dc->min_margin_width;
+	  dc.caret_chars[0] = '^';
+	  diagnostic_set_caret_max_width (&dc, pp_line_cutoff (pp));
+	  diagnostic_show_locus (&dc, &richloc, DK_NOTE); // FIXME
+	}
+
+      start_idx = j;
 
 #if 1
-	  current_function_decl = saved_current_function_decl;
-	  input_location = saved_input_location;
+      current_function_decl = saved_current_function_decl;
+      input_location = saved_input_location;
 #endif
-	}
+    }
+}
+
+/* FIXME.  */
+// This needs to be in tree-diagnostic.c so we can hack up current_function_decl,
+// which is used by lhd_print_error_function etc.
+
+void
+tree_print_diagnostic_path (diagnostic_context *context,
+			    const diagnostic_path *path)
+{
+  const unsigned num_events = path->num_events ();
+
+  /* Attempt to show intraprocedural summary.  */
+  if (path->interprocedural_p ()
+      && context->path_summary_format != DPSF_NONE)
+    {
+      inform (UNKNOWN_LOCATION, "summary of interprocedural path");
+
+      interprocedural_path_summary summary (*path);
+
+      char *saved_prefix = pp_take_prefix (context->printer);
+      pp_set_prefix (context->printer, NULL);
+      print_path_summary (context->printer,
+			  summary, path, context->show_path_depths,
+			  context->path_summary_format);
+      pp_flush (context->printer);
+      pp_set_prefix (context->printer, saved_prefix);
+    }
+
+  /* TODO: options for different printing approaches.  */
+  if (context->show_caret && context->show_line_numbers_p)
+    {
+      if (num_events > 0)
+	print_event_range (context->printer, path, 0, num_events - 1,
+			   true);
     }
   else
     {
       /* Simpler implementation: a note per event.  */
-      diagnostic_path::event *event;
-      int i;
-      FOR_EACH_VEC_ELT (path->m_events, i, event)
-	inform (event->m_loc, "(%i) %s", i + 1, event->m_desc);
+      for (unsigned i = 0; i < num_events; i++)
+	{
+	  const diagnostic_event &event = path->get_event (i);
+	  label_text event_text (event.get_desc ());
+	  inform (event.get_location (),
+		  "(%i) %s", i + 1, event_text.m_buffer);
+	  event_text.maybe_free ();
+	}
     }
 }
 
@@ -449,21 +821,24 @@ static json::value *
 tree_make_json_for_path (diagnostic_context *, const diagnostic_path *path)
 {
   json::array *path_array = new json::array ();
-  int i;
-  diagnostic_path::event *event;
-  FOR_EACH_VEC_ELT (path->m_events, i, event)
+  for (unsigned i = 0; i < path->num_events (); i++)
     {
+      const diagnostic_event &event = path->get_event (i);
+
       json::object *event_obj = new json::object ();
-      if (event->m_loc)
-	event_obj->set ("location", json_from_expanded_location (event->m_loc));
-      event_obj->set ("description", new json::string (event->m_desc));
-      if (event->m_fndecl)
+      if (event.get_location ())
+	event_obj->set ("location",
+			json_from_expanded_location (event.get_location ()));
+      label_text event_text (event.get_desc ());
+      event_obj->set ("description", new json::string (event_text.m_buffer));
+      event_text.maybe_free ();
+      if (tree fndecl = event.get_fndecl ())
 	{
 	  const char *function
-	    = identifier_to_locale (lang_hooks.decl_printable_name (event->m_fndecl, 2));
+	    = identifier_to_locale (lang_hooks.decl_printable_name (fndecl, 2));
 	  event_obj->set ("function", new json::string (function));
 	}
-      event_obj->set ("depth", new json::number (event->m_depth));
+      event_obj->set ("depth", new json::number (event.get_stack_depth ()));
       path_array->append (event_obj);
     }
   return path_array;
@@ -479,3 +854,401 @@ tree_diagnostics_defaults (diagnostic_context *context)
   context->print_path = tree_print_diagnostic_path;
   context->make_json_for_path = tree_make_json_for_path;
 }
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* FIXME.  */
+
+class test_diagnostic_path : public simple_diagnostic_path
+{
+ public:
+  test_diagnostic_path (pretty_printer *event_pp)
+  : simple_diagnostic_path (event_pp)
+  {
+  }
+
+  void add_entry (tree fndecl, int stack_depth)
+  {
+    add_event (UNKNOWN_LOCATION, fndecl, stack_depth,
+	       "entering %qE", fndecl);
+  }
+
+  void add_return (tree fndecl, int stack_depth)
+  {
+    add_event (UNKNOWN_LOCATION, fndecl, stack_depth,
+	       "returning to %qE", fndecl);
+  }
+
+  void add_call (tree caller, int caller_stack_depth, tree callee)
+  {
+    add_event (UNKNOWN_LOCATION, caller, caller_stack_depth,
+	       "calling %qE", callee);
+    add_entry (callee, caller_stack_depth + 1);
+  }
+};
+
+/* FIXME.  */
+
+static void
+test_empty_path (pretty_printer *event_pp)
+{
+  test_diagnostic_path path (event_pp);
+  ASSERT_FALSE (path.interprocedural_p ());
+
+  interprocedural_path_summary summary (path);
+  ASSERT_EQ (summary.m_ranges.length (), 0);
+
+  pretty_printer pp;
+  print_path_summary (&pp, summary, &path, true,
+		      DPSF_INLINE_EVENTS);
+  ASSERT_STREQ ("",
+		pp_formatted_text (&pp));
+}
+
+/* FIXME.  */
+
+static void
+test_intraprocedural_path (pretty_printer *event_pp)
+{
+  tree fntype_void_void
+    = build_function_type_array (void_type_node, 0, NULL);
+  tree fndecl_foo = build_fn_decl ("foo", fntype_void_void);
+
+  test_diagnostic_path path (event_pp);
+  path.add_event (UNKNOWN_LOCATION, fndecl_foo, 0, "first %qs", "free");
+  path.add_event (UNKNOWN_LOCATION, fndecl_foo, 0, "double %qs", "free");
+
+  ASSERT_FALSE (path.interprocedural_p ());
+
+  interprocedural_path_summary summary (path);
+  ASSERT_EQ (summary.m_ranges.length (), 1);
+
+  {
+    pretty_printer pp;
+    print_path_summary (&pp, summary, &path, true,
+			DPSF_SIMPLE);
+    ASSERT_STREQ ("  `foo': events 1-2 (depth 0)\n",
+		  pp_formatted_text (&pp));
+  }
+
+  {
+    pretty_printer pp;
+    print_path_summary (&pp, summary, &path, true,
+			DPSF_EVENT_SUMMARIES);
+    ASSERT_STREQ ("  `foo': events 1-2 (depth 0)\n"
+		  "    | (1): first `free'\n"
+		  "    | (2): double `free'\n",
+		  pp_formatted_text (&pp));
+  }
+
+  {
+    pretty_printer pp;
+    print_path_summary (&pp, summary, &path, true,
+			DPSF_INLINE_EVENTS);
+#if 0
+    ASSERT_STREQ ("  `foo': events 1-2 (depth 0)\n"
+		  "    | (1): first `free'\n"
+		  "    | (2): double `free'\n",
+		  pp_formatted_text (&pp));
+#endif
+  }
+}
+
+/* FIXME.  */
+
+static void
+test_interprocedural_path_1 (pretty_printer *event_pp)
+{
+  /* Build fndecls.  The types aren't quite right, but that
+     doesn't matter for the purposes of this test.  */
+  tree fntype_void_void
+    = build_function_type_array (void_type_node, 0, NULL);
+  tree fndecl_test = build_fn_decl ("test", fntype_void_void);
+  tree fndecl_make_boxed_int
+    = build_fn_decl ("make_boxed_int", fntype_void_void);
+  tree fndecl_wrapped_malloc
+    = build_fn_decl ("wrapped_malloc", fntype_void_void);
+  tree fndecl_free_boxed_int
+    = build_fn_decl ("free_boxed_int", fntype_void_void);
+  tree fndecl_wrapped_free
+    = build_fn_decl ("wrapped_free", fntype_void_void);
+
+  test_diagnostic_path path (event_pp);
+  path.add_entry (fndecl_test, 0);
+  path.add_call (fndecl_test, 0, fndecl_make_boxed_int);
+  path.add_call (fndecl_make_boxed_int, 1, fndecl_wrapped_malloc);
+  path.add_event (UNKNOWN_LOCATION, fndecl_wrapped_malloc, 2, "calling malloc");
+  path.add_return (fndecl_test, 0);
+  path.add_call (fndecl_test, 0, fndecl_free_boxed_int);
+  path.add_call (fndecl_free_boxed_int, 1, fndecl_wrapped_free);
+  path.add_event (UNKNOWN_LOCATION, fndecl_wrapped_free, 2, "calling free");
+  path.add_return (fndecl_test, 0);
+  path.add_call (fndecl_test, 0, fndecl_free_boxed_int);
+  path.add_call (fndecl_free_boxed_int, 1, fndecl_wrapped_free);
+  path.add_event (UNKNOWN_LOCATION, fndecl_wrapped_free, 2, "calling free");
+  ASSERT_EQ (path.num_events (), 18);
+
+  ASSERT_TRUE (path.interprocedural_p ());
+
+  interprocedural_path_summary summary (path);
+  ASSERT_EQ (summary.m_ranges.length (), 9);
+
+  {
+    pretty_printer pp;
+    print_path_summary (&pp, summary, &path, true,
+			DPSF_SIMPLE);
+    ASSERT_STREQ
+      ("  `test': events 1-2 (depth 0)\n"
+       "     |\n"
+       "     +--> `make_boxed_int': events 3-4 (depth 1)\n"
+       "                  |\n"
+       "                  +--> `wrapped_malloc': events 5-6 (depth 2)\n"
+       "                               |\n"
+       "     <-------------------------+\n"
+       "     |\n"
+       "  `test': events 7-8 (depth 0)\n"
+       "     |\n"
+       "     +--> `free_boxed_int': events 9-10 (depth 1)\n"
+       "                  |\n"
+       "                  +--> `wrapped_free': events 11-12 (depth 2)\n"
+       "                              |\n"
+       "     <------------------------+\n"
+       "     |\n"
+       "  `test': events 13-14 (depth 0)\n"
+       "     |\n"
+       "     +--> `free_boxed_int': events 15-16 (depth 1)\n"
+       "                  |\n"
+       "                  +--> `wrapped_free': events 17-18 (depth 2)\n",
+       pp_formatted_text (&pp));
+  }
+
+  /* FIXME: this is a little too verbose.  Would be nice to show
+     just the most pertinent events (here 6, 12, and 18), given that
+     the interprocedural stuff is much clearer, given the picture.  */
+  {
+    pretty_printer pp;
+    print_path_summary (&pp, summary, &path, true,
+			DPSF_EVENT_SUMMARIES);
+    ASSERT_STREQ
+      ("  `test': events 1-2 (depth 0)\n"
+       "     | (1): entering `test'\n"
+       "     | (2): calling `make_boxed_int'\n"
+       "     +--> `make_boxed_int': events 3-4 (depth 1)\n"
+       "                  | (3): entering `make_boxed_int'\n"
+       "                  | (4): calling `wrapped_malloc'\n"
+       "                  +--> `wrapped_malloc': events 5-6 (depth 2)\n"
+       "                               | (5): entering `wrapped_malloc'\n"
+       "                               | (6): calling malloc\n"
+       "     <-------------------------+\n"
+       "     |\n"
+       "  `test': events 7-8 (depth 0)\n"
+       "     | (7): returning to `test'\n"
+       "     | (8): calling `free_boxed_int'\n"
+       "     +--> `free_boxed_int': events 9-10 (depth 1)\n"
+       "                  | (9): entering `free_boxed_int'\n"
+       "                  | (10): calling `wrapped_free'\n"
+       "                  +--> `wrapped_free': events 11-12 (depth 2)\n"
+       "                              | (11): entering `wrapped_free'\n"
+       "                              | (12): calling free\n"
+       "     <------------------------+\n"
+       "     |\n"
+       "  `test': events 13-14 (depth 0)\n"
+       "     | (13): returning to `test'\n"
+       "     | (14): calling `free_boxed_int'\n"
+       "     +--> `free_boxed_int': events 15-16 (depth 1)\n"
+       "                  | (15): entering `free_boxed_int'\n"
+       "                  | (16): calling `wrapped_free'\n"
+       "                  +--> `wrapped_free': events 17-18 (depth 2)\n"
+       "                              | (17): entering `wrapped_free'\n"
+       "                              | (18): calling free\n",
+       pp_formatted_text (&pp));
+  }
+
+  {
+    pretty_printer pp;
+    print_path_summary (&pp, summary, &path, true,
+			DPSF_INLINE_EVENTS);
+#if 0
+    ASSERT_STREQ
+      ("  `test': events 1-2 (depth 0)\n"
+       "     | (1): entering `test'\n"
+       "     | (2): calling `make_boxed_int'\n"
+       "     +--> `make_boxed_int': events 3-4 (depth 1)\n"
+       "                  | (3): entering `make_boxed_int'\n"
+       "                  | (4): calling `wrapped_malloc'\n"
+       "                  +--> `wrapped_malloc': events 5-6 (depth 2)\n"
+       "                               | (5): entering `wrapped_malloc'\n"
+       "                               | (6): calling malloc\n"
+       "     <-------------------------+\n"
+       "     |\n"
+       "  `test': events 7-8 (depth 0)\n"
+       "     | (7): returning to `test'\n"
+       "     | (8): calling `free_boxed_int'\n"
+       "     +--> `free_boxed_int': events 9-10 (depth 1)\n"
+       "                  | (9): entering `free_boxed_int'\n"
+       "                  | (10): calling `wrapped_free'\n"
+       "                  +--> `wrapped_free': events 11-12 (depth 2)\n"
+       "                              | (11): entering `wrapped_free'\n"
+       "                              | (12): calling free\n"
+       "     <------------------------+\n"
+       "     |\n"
+       "  `test': events 13-14 (depth 0)\n"
+       "     | (13): returning to `test'\n"
+       "     | (14): calling `free_boxed_int'\n"
+       "     +--> `free_boxed_int': events 15-16 (depth 1)\n"
+       "                  | (15): entering `free_boxed_int'\n"
+       "                  | (16): calling `wrapped_free'\n"
+       "                  +--> `wrapped_free': events 17-18 (depth 2)\n"
+       "                              | (17): entering `wrapped_free'\n"
+       "                              | (18): calling free\n",
+       pp_formatted_text (&pp));
+#endif
+  }
+}
+
+/* Example where we pop the stack to an intermediate frame, rather than the
+   initial one.  */
+
+static void
+test_interprocedural_path_2 (pretty_printer *event_pp)
+{
+  /* Build fndecls.  The types aren't quite right, but that
+     doesn't matter for the purposes of this test.  */
+  tree fntype_void_void
+    = build_function_type_array (void_type_node, 0, NULL);
+  tree fndecl_foo = build_fn_decl ("foo", fntype_void_void);
+  tree fndecl_bar = build_fn_decl ("bar", fntype_void_void);
+  tree fndecl_baz = build_fn_decl ("baz", fntype_void_void);
+
+  test_diagnostic_path path (event_pp);
+  path.add_entry (fndecl_foo, 0);
+  path.add_call (fndecl_foo, 0, fndecl_bar);
+  path.add_call (fndecl_bar, 1, fndecl_baz);
+  path.add_return (fndecl_bar, 1);
+  path.add_call (fndecl_bar, 1, fndecl_baz);
+  ASSERT_EQ (path.num_events (), 8);
+
+  ASSERT_TRUE (path.interprocedural_p ());
+
+  interprocedural_path_summary summary (path);
+  ASSERT_EQ (summary.m_ranges.length (), 5);
+
+  {
+    pretty_printer pp;
+    print_path_summary (&pp, summary, &path, true,
+			DPSF_SIMPLE);
+    ASSERT_STREQ
+      ("  `foo': events 1-2 (depth 0)\n"
+       "    |\n"
+       "    +--> `bar': events 3-4 (depth 1)\n"
+       "           |\n"
+       "           +--> `baz': event 5 (depth 2)\n"
+       "                  |\n"
+       "           <------+\n"
+       "           |\n"
+       "         `bar': events 6-7 (depth 1)\n"
+       "           |\n"
+       "           +--> `baz': event 8 (depth 2)\n",
+       pp_formatted_text (&pp));
+  }
+
+  {
+    pretty_printer pp;
+    print_path_summary (&pp, summary, &path, true,
+			DPSF_EVENT_SUMMARIES);
+    ASSERT_STREQ
+      ("  `foo': events 1-2 (depth 0)\n"
+       "    | (1): entering `foo'\n"
+       "    | (2): calling `bar'\n"
+       "    +--> `bar': events 3-4 (depth 1)\n"
+       "           | (3): entering `bar'\n"
+       "           | (4): calling `baz'\n"
+       "           +--> `baz': event 5 (depth 2)\n"
+       "                  | (5): entering `baz'\n"
+       "           <------+\n"
+       "           |\n"
+       "         `bar': events 6-7 (depth 1)\n"
+       "           | (6): returning to `bar'\n"
+       "           | (7): calling `baz'\n"
+       "           +--> `baz': event 8 (depth 2)\n"
+       "                  | (8): entering `baz'\n",
+       pp_formatted_text (&pp));
+  }
+}
+
+/* FIXME.  */
+static void
+test_recursion (pretty_printer *event_pp)
+{
+  tree fntype_void_void
+    = build_function_type_array (void_type_node, 0, NULL);
+  tree fndecl_factorial = build_fn_decl ("factorial", fntype_void_void);
+
+ test_diagnostic_path path (event_pp);
+  path.add_entry (fndecl_factorial, 0);
+  for (int depth = 0; depth < 3; depth++)
+    path.add_call (fndecl_factorial, depth, fndecl_factorial);
+  ASSERT_EQ (path.num_events (), 7);
+
+  ASSERT_TRUE (path.interprocedural_p ());
+
+  interprocedural_path_summary summary (path);
+  ASSERT_EQ (summary.m_ranges.length (), 4);
+
+  {
+    pretty_printer pp;
+    print_path_summary (&pp, summary, &path, true,
+			DPSF_SIMPLE);
+    ASSERT_STREQ
+      ("  `factorial': events 1-2 (depth 0)\n"
+       "       |\n"
+       "       +--> `factorial': events 3-4 (depth 1)\n"
+       "                 |\n"
+       "                 +--> `factorial': events 5-6 (depth 2)\n"
+       "                           |\n"
+       "                           +--> `factorial': event 7 (depth 3)\n",
+       pp_formatted_text (&pp));
+  }
+
+  {
+    pretty_printer pp;
+    print_path_summary (&pp, summary, &path, true,
+			DPSF_EVENT_SUMMARIES);
+    ASSERT_STREQ
+      ("  `factorial': events 1-2 (depth 0)\n"
+       "       | (1): entering `factorial'\n"
+       "       | (2): calling `factorial'\n"
+       "       +--> `factorial': events 3-4 (depth 1)\n"
+       "                 | (3): entering `factorial'\n"
+       "                 | (4): calling `factorial'\n"
+       "                 +--> `factorial': events 5-6 (depth 2)\n"
+       "                           | (5): entering `factorial'\n"
+       "                           | (6): calling `factorial'\n"
+       "                           +--> `factorial': event 7 (depth 3)\n"
+       "                                     | (7): entering `factorial'\n",
+       pp_formatted_text (&pp));
+  }
+
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+tree_diagnostic_c_tests ()
+{
+  auto_fix_quotes fix_quotes;
+  pretty_printer *event_pp = global_dc->printer->clone ();
+  pp_show_color (event_pp) = 0;
+  test_empty_path (event_pp);
+  test_intraprocedural_path (event_pp);
+  test_interprocedural_path_1 (event_pp);
+  test_interprocedural_path_2 (event_pp);
+  test_recursion (event_pp);
+  delete event_pp;
+}
+
+} // namespace selftest
+
+#endif /* #if CHECKING_P */
diff --git a/gcc/vec.h b/gcc/vec.h
index 8c2a39b..d9fb738 100644
--- a/gcc/vec.h
+++ b/gcc/vec.h
@@ -1475,6 +1475,17 @@ class auto_string_vec : public auto_vec <char *>
   ~auto_string_vec ();
 };
 
+/* A subclass of auto_vec <T *> that deletes all of its elements on
+   destruction (we can't rely on gnu::unique_ptr within a container,
+   since we can't rely on move semantics in C++98).  */
+
+template <typename T>
+class auto_delete_vec : public auto_vec <T *>
+{
+ public:
+  ~auto_delete_vec ();
+};
+
 /* Conditionally allocate heap memory for VEC and its internal vector.  */
 
 template<typename T>
@@ -1579,6 +1590,19 @@ auto_string_vec::~auto_string_vec ()
     free (str);
 }
 
+/* auto_delete_vec's dtor, deleting all contained items, automatically
+   chaining up to ~auto_vec <T*>, which frees the internal buffer.  */
+
+template <typename T>
+inline
+auto_delete_vec<T>::~auto_delete_vec ()
+{
+  int i;
+  T *item;
+  FOR_EACH_VEC_ELT (*this, i, item)
+    delete item;
+}
+
 
 /* Return a copy of this vector.  */
 
diff --git a/libcpp/include/line-map.h b/libcpp/include/line-map.h
index 8ca4cf3..107fb46 100644
--- a/libcpp/include/line-map.h
+++ b/libcpp/include/line-map.h
@@ -1793,18 +1793,41 @@ struct label_text
   : m_buffer (NULL), m_caller_owned (false)
   {}
 
-  label_text (char *buffer, bool caller_owned)
-  : m_buffer (buffer), m_caller_owned (caller_owned)
-  {}
-
   void maybe_free ()
   {
     if (m_caller_owned)
       free (m_buffer);
   }
 
+  /* Create a label_text instance that borrows BUFFER from a
+     longer-lived owner.  */
+  static label_text borrow (const char *buffer)
+  {
+    return label_text (const_cast <char *> (buffer), false);
+  }
+
+  /* Create a label_text instance that takes ownership of BUFFER.  */
+  static label_text take (char *buffer)
+  {
+    return label_text (buffer, true);
+  }
+
+  /* Take ownership of the buffer, copying if necessary.  */
+  char *take_or_copy ()
+  {
+    if (m_caller_owned)
+      return m_buffer;
+    else
+      return xstrdup (m_buffer);
+  }
+
   char *m_buffer;
   bool m_caller_owned;
+
+private:
+  label_text (char *buffer, bool owned)
+  : m_buffer (buffer), m_caller_owned (owned)
+  {}
 };
 
 /* Abstract base class for labelling a range within a rich_location
diff --git a/make-html.sh b/make-html.sh
new file mode 100755
index 0000000..f9925c1
--- /dev/null
+++ b/make-html.sh
@@ -0,0 +1,32 @@
+#!/bin/bash
+
+# Run this from BUILDDIR/gcc, e.g. via:
+#   make -j64 && make check-gcc RUNTESTFLAGS="-v -v plugin.exp=*" && ../../src/make-html.sh
+
+SRCDIR=../../src
+
+for f in $SRCDIR/gcc/testsuite/analyzer.dg/demo.c \
+    $SRCDIR/gcc/testsuite/analyzer.dg/malloc-ipa-2.c \
+    $SRCDIR/demo-direct.c \
+    $SRCDIR/demo-indirect.c \
+    $SRCDIR/demo-direct-with-loop.c \
+    $SRCDIR/gcc/testsuite/gcc.dg/analyzer/*.c ; do
+    echo $f
+    (./xgcc -B. \
+	-fdiagnostics-format=json \
+	-c $f \
+	--analyzer \
+	2>&1) | $SRCDIR/contrib/diagnostic-json-to-html.py > $(basename $f).c.html
+done
+
+(./xgcc -B. \
+    -Wall \
+    -fdiagnostics-format=json \
+   -c $SRCDIR/gcc/testsuite/c-c++-common/Wmisleading-indentation.c \
+    2>&1) | $SRCDIR/contrib/diagnostic-json-to-html.py > Wmisleading-indentation.c.html
+
+(./xgcc -B. \
+   -fdiagnostics-format=json \
+    -c $SRCDIR/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-3.c \
+    -fplugin=testsuite/gcc/diagnostic_plugin_test_paths.so \
+    2>&1) | $SRCDIR/contrib/diagnostic-json-to-html.py > diagnostic-test-paths-3.c.html
diff --git a/notes.txt b/notes.txt
new file mode 100644
index 0000000..9f95f0f
--- /dev/null
+++ b/notes.txt
@@ -0,0 +1,8 @@
+We need a path: we need to explain to the user what the problem is, and to
+persuade them that there really is a problem.  Hence having a path isn't
+just an incidental detail of the analyzer; it's required.
+
+Paths ought to be:
+* interprocedurally-valid
+* feasible
+...but it's not clear to what extent we can enforce this.
-- 
1.8.5.3

