From 0e48b46db98876ba4caf85ea357355418ffdd596 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Thu, 28 Feb 2019 14:07:05 -0500
Subject: [PATCH 29/33] FIXME: impl-2, impl-3, impl-4

FIXME: more work on impl-4
FIXME: make various graph fields const
FIXME: tweaks to comment
FIXME: add base_node/edge/graph idea
FIXME: logging tweaks
FIXME: initial implementation of state purging
FIXME: add path_state::validate
FIXME: add xfails; use warning rather than sorry, for the sake of DejaGnu
FIXME: add per-function stats
FIXME: ensure that we can't have empty ECs
FIXME: fix equiv_class::operator==
FIXME: track the number of pathvars purged
FIXME: purge sm state for unrecognized function calls
NOT_FOR_UPSTREAM-CVE-2005-1689-krb5-recvauth.c

FIXME: fix memory leaks; add timing info

FIXME: rework exploded_graph's hash_map

FIXME: more leak fixes

FIXME: purgable sm state

FIXME: add exploded_graph::dump_stats

FIXME: add #enode:#snode ratio

FIXME: add m_PK_AFTER_SUPERNODE_per_snode

FIXME: add exploded_graph::dump_states_for_supernode

FIXME: add timevars for writing .dot files

FIXME: undo accidental commit to path_state::purge

FIXME: save diagnostics until the end

FIXME: only compute shortest_paths once

FIXME: replace saved_diagnostic::emit with diagnostic_manager::emit_saved_diagnostic

FIXME: move exploded_graph::emit_diagnostic and prune_path to diagnostic_manager

FIXME: more tests

FIXME: deduplication of diagnostics

FIXME: add selftest for auto_delete_vec

FIXME: another test

FIXME: add -fdump-analyzer-supergraph and -fdump-analyzer-exploded-graph

FIXME: WIP on better memory model

FIXME: more on better memory model

FIXME: more on better memory model

FIXME: more work on new data model

FIXME: 2019-05-17 WIP on data model

FIXME: 2019-05-18: WIP on data model

FIXME: add options for each warning

FIXME: rename constraints.h/cc to path-state.h/cc

FIXME: put old constraint impl in a namespace

FIXME: constraint_manager class

FIXME: use constraint_manager class

FIXME: 2019-05-21 work on region model

FIXME: 2019-05-22 work on region model

FIXME: 2019-05-23 work on region model

FIXME: function pointers

FIXME: cleanups

FIXME: 2019-05-24 work on region model

FIXME: further region_model cleanups

FIXME: More test coverage

FIXME: various cleanups

FIXME: more tests

FIXME: more on data-model-5.c

FIXME: remove dead test files

FIXME: fix loop-3.c

FIXME: WIP on data-model-5.c

FIXME: 2019-06-04: paths for region_model warnings

FIXME: 2019-06-05: further work on region model

FIXME: WIP on porting state machines to region model

FIXME: port sm_state_map to region model

FIXME: avoid printing '<unknown>'

FIXME: more tests

FIXME: fix malloc-vs-local.c overzealous state-purging

FIXME: rename engine.cc to impl_2.cc

FIXME: impl-4: drop path_state, make region_model compulsory

FIXME: better paths (showing pertinent conditionals)

FIXME: fix bug in constraint_manager::operator==

FIXME: impl-4 fixup

FIXME: impl-4 fixup

FIXME: precision-of-wording for calls and returns

FIXME: double_free: add event cross-reference

FIXME: leaks: identify allocation event

FIXME: disable unfinished checker DSL code for now

FIXME: fixing warnings

FIXME: rename 'hardcoded-state-machine' to 'sm'

FIXME: fixing warnings

FIXME: rename pending_diagnostic description vfuncs

FIXME: warning fixes and cleanups

FIXME: add tristate.cc selftests

FIXME: fix broken tristate cases

FIXME: various cleanups

FIXME: fix memory leaks

FIXME: update gcc.c plugindir hack

More on analyzer

FIXME: add malloc-paths-9.c testcase

FIXME: more precision-of-wording

FIXME: fix false uninit message about reading function-static vars

FIXME: fix ICE on pointer subtraction

FIXME: add zlib testcases

FIXME: move pending_diagnostic from sm.h/cc to its own .h/.cc

FIXME: hide the state_machine subclasses in their respective source files

FIXME: cleanups to sm.h

FIXME: update analyzer for diagnostic_path formatting-option changes

More on analyzer

FIXME: expand malloc-paths-9.c

FIXME: precision-of-wording fixes

FIXME: disable .rmodel.dot dumps

More on analyzer

FIXME: add optional move constructors; cache point_and_state hash

FIXME: implement __analyzer_dump_exploded_nodes and other ways to measure enode growth

FIXME: WIP on enode merging

FIXME: WIP on state merging

FIXME: common graph subclasses; include paths

FIXME: fix merging of more than two nodes

FIXME: don't merge if there are sm differences

FIXME: more test coverage

FIXME: add -fdump-analyzer-exploded-nodes

FIXME: more test coverage

FIXME: fix worklist comment

FIXME: implement merging of states based on existing enodes for program_point

FIXME: fix worklist comment

FIXME: analyzer cleanups

FIXME: WIP on region_model::can_merge_with_p

FIXME: further WIP on region merging

FIXME: more on region-merging

FIXME: fixes and testing for program_point ordering

FIXME: more selftests for region_model merging

FIXME: paths-6.c

FIXME: stabilize the sort within the worklist

FIXME: disable state_purge_annotator in supergraph.dot for now

FIXME: add more selftest coverage to region_model merging

FIXME: malloc-vs-local-2.c

FIXME: paths-7.c

FIXME: WIP on region-merging

FIXME: more WIP on region-merging

FIXME: initial implementation of canonicalization (disabled for now)

FIXME: more on canonicalization, enabling for now

FIXME: fix sm-state merging

FIXME: fix issues seen by valgrind

FIXME: WIP on reimplementing tree_cmp

FIXME: More on analyzer

More on canonicalization

FIXME: use ordered_hash_map in a few places

FIXME: implement dump methods for canonicalization

FIXME: more non-determinism fixes

FIXME: more on nondeterminism etc

FIXME: cleanups

FIXME: WIP on constraint merging

FIXME: cleanups

FIXME: implement canonicalization of constraints

FIXME: rework canonicalization to handle constants only reached via constraints

FIXME: implement constraint_manager merging

FIXME: cleanups

FIXME: more tests

FIXME: retain svalue sharing when merging region_models

FIXME: always have a svalue_id_merger_mapping when merging region_models

FIXME: add selftest coverage for merger of pointers to a local

FIXME: skip validation in a release build

FIXME: add region_model::get_value_by_name

FIXME: use representative path_vars rather than trees when merging region pointers, fixing malloc-vs-local.c:test_pointer_comparison

FIXME: cleanups

FIXME: more cleanups

FIXME: dump preds/succs with __analyzer_dump_exploded_nodes (1); capture preds

FIXME: remove redundant add_edge code

FIXME: cleanups

FIXME: add -fdump-analyzer-exploded-nodes-2

FIXME: colorize .eg.dot output to show sm-state

FIXME: group worklist by sm-state, and canonicalize after merging

FIXME: simplify dump output

FIXME: fix merging of sm_state_map

FIXME: cleanups

FIXME: more detail in 'bailed out early' message

FIXME: add zlib-2.c

FIXME: add test_many_constants

FIXME: update to TODO in data-model-1.c

FIXME: add disabled assert to constraint_manager::add_constraint_internal

FIXME: test_purging_by_criteria

FIXME: add test_assignment

FIXME: WIP updates to constraint_manager

FIXME: warning fixes

FIXME: move id_map implementation to header

FIXME: support more than one constant per equivalence class

FIXME: eliminate program_point::after_stmt and PK_AFTER_STMT

FIXME: use quick_push rather than safe_push

FIXME: experiment with using pool allocator

FIXME: enable -fanalyzer-state-merge by default

FIXME: filter diagnostics by feasibility

FIXME: remove redundant -fanalyzer-state-merge dg options

FIXME: add transitivity hack

FIXME: add hack for detecting bogus states

FIXME: eliminate unnecessary copies in can_merge_with_p
---
 analyze-to-html.sh                                 |    13 +
 ansi2html.sh                                       |   520 +
 .../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                          |   172 +
 gcc/analyzer/analyzer-logging.cc                   |   221 +
 gcc/analyzer/analyzer-logging.h                    |   237 +
 gcc/analyzer/analyzer-pass.cc                      |   137 +
 gcc/analyzer/analyzer-plugin.cc                    |    88 +
 gcc/analyzer/analyzer-selftests.cc                 |    93 +
 gcc/analyzer/analyzer-selftests.h                  |    47 +
 gcc/analyzer/analyzer.cc                           |   149 +
 gcc/analyzer/analyzer.h                            |    80 +
 gcc/analyzer/checker-script.cc                     |  1680 +++
 gcc/analyzer/config-lang.in                        |    34 +
 gcc/analyzer/constraint-manager.cc                 |  2560 +++++
 gcc/analyzer/constraint-manager.h                  |   263 +
 gcc/analyzer/dead-code/address_of_function.sm      |    14 +
 gcc/analyzer/dead-code/analyzer-config.cc          |    54 +
 gcc/analyzer/dead-code/buffer_overflow.sm          |    75 +
 gcc/analyzer/dead-code/constraints.cc              |   780 ++
 gcc/analyzer/dead-code/core-checker.cc             |   904 ++
 gcc/analyzer/dead-code/fd_checker.sm               |    26 +
 gcc/analyzer/dead-code/fileptr.sm                  |    17 +
 gcc/analyzer/dead-code/libc-file-checker.cc        |   226 +
 gcc/analyzer/dead-code/libc-malloc-checker.cc      |   220 +
 gcc/analyzer/dead-code/malloc_checker.sm           |    74 +
 gcc/analyzer/dead-code/points_to_stack.sm          |    60 +
 gcc/analyzer/dead-code/python.sm                   |    61 +
 gcc/analyzer/dead-code/signal_handler.sm           |    58 +
 gcc/analyzer/dead-code/sizeof_allocation.sm        |    91 +
 gcc/analyzer/dead-code/taint.sm                    |    71 +
 gcc/analyzer/dead-code/toy-sm-impl-1.cc            |  1052 ++
 gcc/analyzer/dead-code/toy-sm-impl-1.h             |     1 +
 gcc/analyzer/digraph.cc                            |   140 +
 gcc/analyzer/digraph.h                             |   252 +
 gcc/analyzer/engine.h                              |     3 +
 gcc/analyzer/graphviz.cc                           |    62 +
 gcc/analyzer/graphviz.h                            |    19 +
 gcc/analyzer/impl-2.cc                             |  3931 +++++++
 gcc/analyzer/impl-3.cc                             |  1658 +++
 gcc/analyzer/impl-4.cc                             |  7893 ++++++++++++++
 gcc/analyzer/path-state.cc                         |  2272 ++++
 gcc/analyzer/path-state.h                          |   316 +
 gcc/analyzer/pending-diagnostic.cc                 |    79 +
 gcc/analyzer/pending-diagnostic.h                  |   285 +
 gcc/analyzer/region-model.cc                       |  5952 ++++++++++
 gcc/analyzer/region-model.h                        |  1492 +++
 gcc/analyzer/sm-file.cc                            |   349 +
 gcc/analyzer/sm-malloc.cc                          |   807 ++
 gcc/analyzer/sm-pattern-test.cc                    |   208 +
 gcc/analyzer/sm-sensitive.cc                       |   217 +
 gcc/analyzer/sm-taint.cc                           |   297 +
 gcc/analyzer/sm.cc                                 |   163 +
 gcc/analyzer/sm.h                                  |   160 +
 gcc/analyzer/supergraph.cc                         |  1017 ++
 gcc/analyzer/supergraph.h                          |   509 +
 gcc/analyzer/tristate.cc                           |   222 +
 gcc/analyzer/tristate.h                            |    80 +
 gcc/builtins.def                                   |    26 +
 gcc/common.opt                                     |   115 +
 gcc/gcc.c                                          |    15 +
 gcc/params.def                                     |     5 +
 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 +
 .../malloc-checker/fancy_control_flow/input.c      |    42 +
 .../malloc-checker/fancy_control_flow/script.py    |    23 +
 .../malloc-checker/fancy_control_flow/stderr.txt   |     4 +
 .../malloc-checker/fancy_control_flow/stdout.txt   |     0
 .../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/points_to_stack/putenv/input.c        |    31 +
 .../checkers/points_to_stack/putenv/script.py      |    31 +
 .../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 +
 .../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 +
 gcc/testsuite/gcc.dg/TODO-analyzer/dot/script.py   |    55 +
 gcc/testsuite/gcc.dg/TODO-analyzer/dot/stdout.txt  |    83 +
 .../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/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 +
 .../TODO-analyzer/parser/malloc-checker/script.py  |   189 +
 .../TODO-analyzer/patterns/arg-of-fn-call/input.c  |    25 +
 .../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 +
 .../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 +
 .../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 +
 .../TODO-analyzer/patterns/var-usage/script.py     |    34 +
 .../TODO-analyzer/patterns/var-usage/stderr.txt    |     3 +
 .../TODO-analyzer/patterns/var-usage/stdout.txt    |     0
 .../gcc.dg/analyzer/CVE-2005-1689-minimal.c        |    30 +
 .../NOT_FOR_UPSTREAM-CVE-2005-1689-krb5-recvauth.c | 10854 +++++++++++++++++++
 .../NOT_FOR_UPSTREAM-clang-paper-example-1.c       |    10 +
 .../NOT_FOR_UPSTREAM-clang-paper-example-2.c       |    17 +
 .../NOT_FOR_UPSTREAM-clang-paper-example-3.c       |    22 +
 .../NOT_FOR_UPSTREAM-clang-paper-example-4.c       |    29 +
 .../analyzer/NOT_FOR_UPSTREAM-zlib-1.1.3-deflate.c |  4047 +++++++
 .../NOT_FOR_UPSTREAM-zlib-1.1.3-infblock.c         |  3363 ++++++
 gcc/testsuite/gcc.dg/analyzer/abort.c              |    71 +
 gcc/testsuite/gcc.dg/analyzer/alloca-leak.c        |     8 +
 .../gcc.dg/analyzer/analyzer-verbosity-0.c         |   131 +
 .../gcc.dg/analyzer/analyzer-verbosity-1.c         |   158 +
 .../gcc.dg/analyzer/analyzer-verbosity-2.c         |   179 +
 gcc/testsuite/gcc.dg/analyzer/analyzer.exp         |    36 +
 gcc/testsuite/gcc.dg/analyzer/attribute-nonnull.c  |    57 +
 gcc/testsuite/gcc.dg/analyzer/conditionals.c       |   141 +
 gcc/testsuite/gcc.dg/analyzer/data-model-1.c       |   896 ++
 gcc/testsuite/gcc.dg/analyzer/data-model-2.c       |    13 +
 gcc/testsuite/gcc.dg/analyzer/data-model-3.c       |    15 +
 gcc/testsuite/gcc.dg/analyzer/data-model-4.c       |    16 +
 gcc/testsuite/gcc.dg/analyzer/data-model-5.c       |   124 +
 gcc/testsuite/gcc.dg/analyzer/data-model-5b.c      |    91 +
 gcc/testsuite/gcc.dg/analyzer/data-model-5c.c      |    84 +
 gcc/testsuite/gcc.dg/analyzer/data-model-5d.c      |    63 +
 gcc/testsuite/gcc.dg/analyzer/data-model-6.c       |    13 +
 gcc/testsuite/gcc.dg/analyzer/data-model-7.c       |    19 +
 gcc/testsuite/gcc.dg/analyzer/data-model-path-1.c  |    15 +
 gcc/testsuite/gcc.dg/analyzer/equivalence.c        |    29 +
 gcc/testsuite/gcc.dg/analyzer/factorial.c          |     7 +
 gcc/testsuite/gcc.dg/analyzer/fibonacci.c          |    10 +
 gcc/testsuite/gcc.dg/analyzer/fields.c             |    41 +
 gcc/testsuite/gcc.dg/analyzer/file-1.c             |    36 +
 gcc/testsuite/gcc.dg/analyzer/file-2.c             |    18 +
 gcc/testsuite/gcc.dg/analyzer/function-ptr-1.c     |     8 +
 gcc/testsuite/gcc.dg/analyzer/function-ptr-2.c     |    43 +
 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               |    17 +
 gcc/testsuite/gcc.dg/analyzer/infinite-recursion.c |    55 +
 gcc/testsuite/gcc.dg/analyzer/loop-2.c             |    45 +
 gcc/testsuite/gcc.dg/analyzer/loop-3.c             |    17 +
 gcc/testsuite/gcc.dg/analyzer/loop.c               |    34 +
 gcc/testsuite/gcc.dg/analyzer/malloc-1.c           |   525 +
 gcc/testsuite/gcc.dg/analyzer/malloc-dce.c         |    28 +
 gcc/testsuite/gcc.dg/analyzer/malloc-dedupe-1.c    |    46 +
 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      |    91 +
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-12.c      |     8 +
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-13.c      |    30 +
 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       |    22 +
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-7.c       |    29 +
 .../gcc.dg/analyzer/malloc-ipa-8-double-free.c     |   166 +
 .../gcc.dg/analyzer/malloc-ipa-8-unchecked.c       |    64 +
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-9.c       |    18 +
 .../gcc.dg/analyzer/malloc-many-paths-1.c          |    14 +
 .../gcc.dg/analyzer/malloc-many-paths-2.c          |    96 +
 .../gcc.dg/analyzer/malloc-many-paths-3.c          |    36 +
 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     |    19 +
 gcc/testsuite/gcc.dg/analyzer/malloc-paths-5.c     |    41 +
 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/malloc-paths-9.c     |   254 +
 gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-2.c  |   178 +
 gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-3.c  |    65 +
 gcc/testsuite/gcc.dg/analyzer/malloc-vs-local.c    |   178 +
 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/paths-1.c            |    16 +
 gcc/testsuite/gcc.dg/analyzer/paths-2.c            |    25 +
 gcc/testsuite/gcc.dg/analyzer/paths-3.c            |    46 +
 gcc/testsuite/gcc.dg/analyzer/paths-4.c            |    49 +
 gcc/testsuite/gcc.dg/analyzer/paths-5.c            |    10 +
 gcc/testsuite/gcc.dg/analyzer/paths-6.c            |   118 +
 gcc/testsuite/gcc.dg/analyzer/paths-7.c            |    58 +
 gcc/testsuite/gcc.dg/analyzer/pattern-test-1.c     |    14 +
 gcc/testsuite/gcc.dg/analyzer/pragma-1.c           |    26 +
 gcc/testsuite/gcc.dg/analyzer/scope-1.c            |    23 +
 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 +
 gcc/testsuite/gcc.dg/analyzer/zlib-1.c             |    71 +
 gcc/testsuite/gcc.dg/analyzer/zlib-2.c             |    49 +
 notes.txt                                          |    75 +
 273 files changed, 65890 insertions(+)
 create mode 100755 analyze-to-html.sh
 create mode 100755 ansi2html.sh
 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-selftests.cc
 create mode 100644 gcc/analyzer/analyzer-selftests.h
 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/constraint-manager.cc
 create mode 100644 gcc/analyzer/constraint-manager.h
 create mode 100644 gcc/analyzer/dead-code/address_of_function.sm
 create mode 100644 gcc/analyzer/dead-code/analyzer-config.cc
 create mode 100644 gcc/analyzer/dead-code/buffer_overflow.sm
 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/fd_checker.sm
 create mode 100644 gcc/analyzer/dead-code/fileptr.sm
 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/malloc_checker.sm
 create mode 100644 gcc/analyzer/dead-code/points_to_stack.sm
 create mode 100644 gcc/analyzer/dead-code/python.sm
 create mode 100644 gcc/analyzer/dead-code/signal_handler.sm
 create mode 100644 gcc/analyzer/dead-code/sizeof_allocation.sm
 create mode 100644 gcc/analyzer/dead-code/taint.sm
 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.h
 create mode 100644 gcc/analyzer/graphviz.cc
 create mode 100644 gcc/analyzer/graphviz.h
 create mode 100644 gcc/analyzer/impl-2.cc
 create mode 100644 gcc/analyzer/impl-3.cc
 create mode 100644 gcc/analyzer/impl-4.cc
 create mode 100644 gcc/analyzer/path-state.cc
 create mode 100644 gcc/analyzer/path-state.h
 create mode 100644 gcc/analyzer/pending-diagnostic.cc
 create mode 100644 gcc/analyzer/pending-diagnostic.h
 create mode 100644 gcc/analyzer/region-model.cc
 create mode 100644 gcc/analyzer/region-model.h
 create mode 100644 gcc/analyzer/sm-file.cc
 create mode 100644 gcc/analyzer/sm-malloc.cc
 create mode 100644 gcc/analyzer/sm-pattern-test.cc
 create mode 100644 gcc/analyzer/sm-sensitive.cc
 create mode 100644 gcc/analyzer/sm-taint.cc
 create mode 100644 gcc/analyzer/sm.cc
 create mode 100644 gcc/analyzer/sm.h
 create mode 100644 gcc/analyzer/supergraph.cc
 create mode 100644 gcc/analyzer/supergraph.h
 create mode 100644 gcc/analyzer/tristate.cc
 create mode 100644 gcc/analyzer/tristate.h
 create mode 100644 gcc/testsuite/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/checkers/malloc-checker/fancy_control_flow/input.c
 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/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/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/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/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/script.py
 create mode 100644 gcc/testsuite/gcc.dg/TODO-analyzer/dot/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/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/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/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/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/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/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/analyzer/CVE-2005-1689-minimal.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-CVE-2005-1689-krb5-recvauth.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-clang-paper-example-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-clang-paper-example-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-clang-paper-example-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-clang-paper-example-4.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-zlib-1.1.3-deflate.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-zlib-1.1.3-infblock.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/abort.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/alloca-leak.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-0.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/analyzer.exp
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/attribute-nonnull.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/conditionals.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-4.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-5.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-5b.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-5c.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-5d.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-6.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-7.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/data-model-path-1.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/fields.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/file-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/file-2.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-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/loop-3.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-dedupe-1.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-13.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-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-3.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/malloc-paths-9.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/malloc-vs-local.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/paths-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/paths-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/paths-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/paths-4.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/paths-5.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/paths-6.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/paths-7.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/pattern-test-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/pragma-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/scope-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/analyzer/zlib-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/zlib-2.c
 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/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..58bf520
--- /dev/null
+++ b/gcc/analyzer/Make-lang.in
@@ -0,0 +1,172 @@
+# 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/analyzer-selftests.o \
+	analyzer/checker-script.o \
+	analyzer/constraint-manager.o \
+	analyzer/digraph.o \
+	analyzer/graphviz.o \
+	analyzer/impl-2.o \
+	analyzer/impl-3.o \
+	analyzer/impl-4.o \
+	analyzer/path-state.o \
+	analyzer/pending-diagnostic.o \
+	analyzer/region-model.o \
+	analyzer/sm.o \
+	analyzer/sm-file.o \
+	analyzer/sm-malloc.o \
+	analyzer/sm-pattern-test.o \
+	analyzer/sm-sensitive.o \
+	analyzer/sm-taint.o \
+	analyzer/supergraph.o \
+	analyzer/tristate.o \
+
+# 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..6f2343e
--- /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/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..8f950d9
--- /dev/null
+++ b/gcc/analyzer/analyzer-logging.h
@@ -0,0 +1,237 @@
+/* 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);
+
+  pretty_printer *get_printer () const { return m_pp; }
+
+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);
+
+  pretty_printer *get_logger_pp () const
+  {
+    gcc_assert (m_logger);
+    return m_logger->get_printer ();
+  }
+
+ 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..8f7bcd6
--- /dev/null
+++ b/gcc/analyzer/analyzer-pass.cc
@@ -0,0 +1,137 @@
+/* Integration of the analyzer with GCC's pass manager.
+   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/>.  */
+/* 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/analyzer.h"
+#include "analyzer/engine.h"
+
+namespace {
+
+/* Data for the analyzer pass.  */
+
+const pass_data pass_data_analyzer =
+{
+  IPA_PASS, /* type */
+  "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 */
+};
+
+/* The analyzer pass.  */
+
+class pass_analyzer : public ipa_opt_pass_d
+{
+public:
+   pass_analyzer(gcc::context *ctxt)
+   : 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 */
+  {}
+
+  /* opt_pass methods: */
+  unsigned int execute (function *) FINAL OVERRIDE;
+}; // class pass_analyzer
+
+/* Entrypoint for the analyzer pass.  */
+
+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
+
+/* Make an instance of the analyzer pass.  */
+
+static ipa_opt_pass_d *
+make_pass_analyzer (gcc::context *ctxt)
+{
+  return new pass_analyzer (ctxt);
+}
+
+/* Register the analyzer pass with GCC's pass manager.
+   Called by plugin_init.  */
+
+void
+register_analyzer_pass ()
+{
+  static struct register_pass_info pass_info;
+
+  /* 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);
+}
diff --git a/gcc/analyzer/analyzer-plugin.cc b/gcc/analyzer/analyzer-plugin.cc
new file mode 100644
index 0000000..2c64b81
--- /dev/null
+++ b/gcc/analyzer/analyzer-plugin.cc
@@ -0,0 +1,88 @@
+/* Top-level interface to the analyzer, as a GCC plugin.
+   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/>.  */
+
+/* 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/analyzer.h"
+#include "analyzer/analyzer-selftests.h"
+
+int plugin_is_GPL_compatible;
+
+#if CHECKING_P
+
+namespace selftest {
+
+static void
+analyzer_selftests_cb (void */*gcc_data*/, void */*user_data*/)
+{
+  run_analyzer_selftests ();
+}
+
+} /* end of namespace selftest.  */
+
+#endif /* #if CHECKING_P */
+
+/* Entrypoint to the pass.  */
+
+int
+plugin_init (struct plugin_name_args *plugin_info ATTRIBUTE_UNUSED,
+	     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_cb, NULL);
+#endif /* #if CHECKING_P */
+
+  /* Register our pass.  */
+  register_analyzer_pass ();
+
+  return 0;
+}
diff --git a/gcc/analyzer/analyzer-selftests.cc b/gcc/analyzer/analyzer-selftests.cc
new file mode 100644
index 0000000..cbb9e3f
--- /dev/null
+++ b/gcc/analyzer/analyzer-selftests.cc
@@ -0,0 +1,93 @@
+/* Selftest support for the analyzer.
+   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/>.  */
+
+/* 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 "stringpool.h"
+#include "analyzer/analyzer-selftests.h"
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Build a VAR_DECL named NAME of type TYPE, simulating a file-level
+   static variable.  */
+
+tree
+build_global_decl (const char *name, tree type)
+{
+  tree decl = build_decl (UNKNOWN_LOCATION, VAR_DECL,
+			  get_identifier (name), type);
+  TREE_STATIC (decl) = 1;
+  return decl;
+}
+
+/* Run all analyzer-specific selftests.  */
+
+void
+run_analyzer_selftests ()
+{
+#if 0
+  analyzer_checker_script_cc_tests ();
+#endif
+  analyzer_constraint_manager_cc_tests ();
+  analyzer_digraph_cc_tests ();
+  analyzer_impl_2_cc_tests ();
+  analyzer_impl_4_cc_tests ();
+  analyzer_path_state_cc_tests ();
+  analyzer_region_model_cc_tests ();
+  analyzer_tristate_cc_tests ();
+}
+
+} /* end of namespace selftest.  */
+
+#endif /* #if CHECKING_P */
diff --git a/gcc/analyzer/analyzer-selftests.h b/gcc/analyzer/analyzer-selftests.h
new file mode 100644
index 0000000..f427411
--- /dev/null
+++ b/gcc/analyzer/analyzer-selftests.h
@@ -0,0 +1,47 @@
+/* Selftests for the analyzer.
+   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_SELFTESTS_H
+#define GCC_ANALYZER_SELFTESTS_H
+
+#if CHECKING_P
+
+namespace selftest {
+
+extern tree build_global_decl (const char *name, tree type);
+
+extern void run_analyzer_selftests ();
+
+/* 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_constraint_manager_cc_tests ();
+extern void analyzer_digraph_cc_tests ();
+extern void analyzer_impl_2_cc_tests ();
+extern void analyzer_impl_4_cc_tests ();
+extern void analyzer_path_state_cc_tests ();
+extern void analyzer_region_model_cc_tests ();
+extern void analyzer_tristate_cc_tests ();
+
+} /* end of namespace selftest.  */
+
+#endif /* #if CHECKING_P */
+
+#endif /* GCC_ANALYZER_SELFTESTS_H */
diff --git a/gcc/analyzer/analyzer.cc b/gcc/analyzer/analyzer.cc
new file mode 100644
index 0000000..3cac7f5
--- /dev/null
+++ b/gcc/analyzer/analyzer.cc
@@ -0,0 +1,149 @@
+/* Utility functions for the analyzer.
+   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/>.  */
+/* 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 "stringpool.h"
+
+#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..a1b0a8a
--- /dev/null
+++ b/gcc/analyzer/analyzer.h
@@ -0,0 +1,80 @@
+/* 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 (); }
+};
+
+/* FIXME.  */
+
+#define PUSH_IGNORE_WFORMAT \
+  _Pragma("GCC diagnostic push") \
+  _Pragma("GCC diagnostic ignored \"-Wformat\"") \
+  _Pragma("GCC diagnostic ignored \"-Wformat-extra-args\"")
+
+/* FIXME.  */
+
+#define POP_IGNORE_WFORMAT \
+  _Pragma("GCC diagnostic pop")
+
+#if CHECKING_P
+
+namespace selftest {
+
+extern tree build_global_decl (const char *name, tree type);
+
+/* 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_constraint_manager_cc_tests ();
+extern void analyzer_digraph_cc_tests ();
+extern void analyzer_impl_2_cc_tests ();
+extern void analyzer_impl_4_cc_tests ();
+extern void analyzer_path_state_cc_tests ();
+extern void analyzer_region_model_cc_tests ();
+extern void analyzer_tristate_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..7876fac
--- /dev/null
+++ b/gcc/analyzer/checker-script.cc
@@ -0,0 +1,1680 @@
+/*
+  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"
+
+#if 0
+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
+
+#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/constraint-manager.cc b/gcc/analyzer/constraint-manager.cc
new file mode 100644
index 0000000..66ad063
--- /dev/null
+++ b/gcc/analyzer/constraint-manager.cc
@@ -0,0 +1,2560 @@
+/* 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/>.  */
+/* 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/analyzer-logging.h"
+#include "stringpool.h"
+#include "selftest.h"
+#include "fold-const.h"
+#include "analyzer/tristate.h"
+#include "analyzer/path-state.h"
+#include "analyzer/supergraph.h"
+#include "analyzer/constraint-manager.h"
+#include "tree-diagnostic.h"
+#include "alloc-pool.h"
+
+/* 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;
+};
+
+/* A range of values, used for determining if a value has been
+   constrained to just one possible constant value.  */
+
+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 subclass of purge_criteria: purge a specific svalue_id.  */
+
+class purge_one_svalue_id : public purge_criteria
+{
+public:
+  purge_one_svalue_id (svalue_id victim) : m_victim (victim) {}
+
+  bool should_purge_p (svalue_id sid) const FINAL OVERRIDE
+  {
+    return sid == m_victim;
+  }
+
+private:
+  svalue_id m_victim;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* struct bound.  */
+
+/* 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;
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* struct range.  */
+
+/* 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
+}
+
+/* Determine if there is only one possible value for this range.
+   If so, return true and write the constant to *OUT.
+   Otherwise, return false.  */
+
+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;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class equiv_class.  */
+
+/* FIXME.  */
+static pool_allocator equiv_class_pool ("equiv_class", sizeof (equiv_class));
+
+/* FIXME.  */
+void *
+equiv_class::operator new (size_t sz)
+{
+  gcc_assert (sz == sizeof (equiv_class));
+  return equiv_class_pool.allocate ();
+}
+
+/* FIXME.  */
+void
+equiv_class::operator delete (void* ptr, size_t sz)
+{
+  gcc_assert (sz == sizeof (equiv_class));
+  equiv_class_pool.remove (ptr);
+}
+
+/* equiv_class's default ctor.  */
+
+equiv_class::equiv_class ()
+: m_constant (NULL_TREE), m_cst_sid (svalue_id::null ()),
+  m_vars (), m_escaped (false)
+{
+}
+
+/* equiv_class's copy ctor.  */
+
+equiv_class::equiv_class (const equiv_class &other)
+: m_constant (other.m_constant), m_cst_sid (other.m_cst_sid),
+  m_vars (other.m_vars.length ()),
+  m_escaped (other.m_escaped)
+{
+  int i;
+  svalue_id *sid;
+  FOR_EACH_VEC_ELT (other.m_vars, i, sid)
+    m_vars.quick_push (*sid);
+}
+
+/* Generate a std::string for this equiv_class.  */
+
+std::string
+equiv_class::to_str () const
+{
+  std::string result ("{");
+  int i;
+  svalue_id *sid;
+  FOR_EACH_VEC_ELT (m_vars, i, sid)
+    {
+      if (i > 0)
+	result += " == ";
+      result += sid->to_str ();
+    }
+  if (m_constant)
+    {
+      if (i > 0)
+	result += " == ";
+      result += expr_to_string (m_constant);
+    }
+  if (m_escaped)
+    result += " (escaped)";
+  return result + "}";
+}
+
+/* Generate a hash value for this equiv_class.  */
+
+hashval_t
+equiv_class::hash () const
+{
+  inchash::hash hstate;
+  int i;
+  svalue_id *sid;
+
+  inchash::add_expr (m_constant, hstate);
+  FOR_EACH_VEC_ELT (m_vars, i, sid)
+    inchash::add (*sid, hstate);
+  hstate.add_flag (m_escaped);
+  hstate.commit_flag ();
+  return hstate.end ();
+}
+
+/* Equality operator for equiv_class.  */
+
+bool
+equiv_class::operator== (const equiv_class &other)
+{
+  if (m_constant != other.m_constant)
+    return false; // TODO: use tree equality here
+
+  // FIXME: m_cst_sid
+
+  if (m_vars.length () != other.m_vars.length ())
+    return false;
+
+  int i;
+  svalue_id *sid;
+  FOR_EACH_VEC_ELT (m_vars, i, sid)
+    if (! (*sid == other.m_vars[i]))
+      return false;
+  if (m_escaped != other.m_escaped)
+    return false;
+
+  return true;
+}
+
+/* Add SID to this equiv_class, using CM to check if it's a constant.  */
+
+void
+equiv_class::add (svalue_id sid, const constraint_manager &cm)
+{
+  gcc_assert (!sid.null_p ());
+  if (tree cst = cm.maybe_get_constant (sid))
+    {
+      gcc_assert (CONSTANT_CLASS_P (cst));
+      /* FIXME: should we canonicalize which svalue is the constant
+	 when there are multiple equal constants?  */
+      m_constant = cst;
+      m_cst_sid = sid;
+    }
+  m_vars.safe_push (sid);
+
+#if 0
+      if (DECL_P (v.m_tree)
+	  && is_global_var (v.m_tree))
+	m_escaped = true;
+#endif
+}
+
+/* Remove SID from this equivalence class.
+   Return true if SID was the last var in the equivalence class (suggesting
+   a possible leak).  */
+
+bool
+equiv_class::del (svalue_id sid)
+{
+  gcc_assert (!sid.null_p ());
+  gcc_assert (sid != m_cst_sid);
+
+  int i;
+  svalue_id *iv;
+  FOR_EACH_VEC_ELT (m_vars, i, iv)
+    {
+      if (*iv == sid)
+	{
+	  m_vars[i] = m_vars[m_vars.length () - 1];
+	  m_vars.pop ();
+	  return m_vars.length () == 0;
+	}
+    }
+
+  /* SID must be in the class.  */
+  gcc_unreachable ();
+  return false;
+}
+
+/* Get a representative member of this class, for handling cases
+   where the IDs can change mid-traversal.  */
+
+svalue_id
+equiv_class::get_representative () const
+{
+#if 1
+  if (!m_cst_sid.null_p ())
+    return m_cst_sid;
+  else
+#endif
+    {
+      gcc_assert (m_vars.length () > 0);
+      return m_vars[0];
+    }
+}
+
+/* Remap all svalue_ids within this equiv_class using MAP.  */
+
+void
+equiv_class::remap_svalue_ids (const svalue_id_map &map)
+{
+  int i;
+  svalue_id *iv;
+  FOR_EACH_VEC_ELT (m_vars, i, iv)
+    map.update (iv);
+  map.update (&m_cst_sid);
+}
+
+/* Comparator for use by equiv_class::canonicalize.  */
+
+static int
+svalue_id_cmp_by_id (const void *p1, const void *p2)
+{
+  const svalue_id *sid1 = (const svalue_id *)p1;
+  const svalue_id *sid2 = (const svalue_id *)p2;
+  return sid1->as_int () - sid2->as_int ();
+}
+
+/* Sort the svalues_ids within this equiv_class.  */
+
+void
+equiv_class::canonicalize ()
+{
+  m_vars.qsort (svalue_id_cmp_by_id);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Get a debug string for C_OP.  */
+
+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 "<=";
+    }
+}
+
+/* Convert C_OP to an enum tree_code.  */
+
+enum tree_code
+constraint_tree_code (enum constraint_op c_op)
+{
+  switch (c_op)
+    {
+    default:
+      gcc_unreachable ();
+    case CONSTRAINT_NE: return NE_EXPR;
+    case CONSTRAINT_LT: return LT_EXPR;
+    case CONSTRAINT_LE: return LE_EXPR;
+    }
+}
+
+/* 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);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class constraint.  */
+
+/* Generate a std::string for this constraint, using CM to look up
+   equiv_class instances from ids.  */
+
+std::string
+constraint::to_str (const constraint_manager &cm) const
+{
+  return (m_lhs.to_str () + ": "
+	  + m_lhs.get_obj (cm).to_str ()
+	  + " "
+	  + constraint_op_code (m_op)
+	  + " "
+	  + m_rhs.to_str () + ": "
+	  + m_rhs.get_obj (cm).to_str ());
+}
+
+/* Generate a hash value for this constraint.  */
+
+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 ();
+}
+
+/* Equality operator for constraints.  */
+
+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;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class equiv_class_id.  */
+
+/* FIXME.  */
+
+const equiv_class &
+equiv_class_id::get_obj (const constraint_manager &cm) const
+{
+  return cm.get_equiv_class_by_index (m_idx);
+}
+
+/* FIXME.  */
+
+equiv_class &
+equiv_class_id::get_obj (constraint_manager &cm) const
+{
+  return cm.get_equiv_class_by_index (m_idx);
+}
+
+/* FIXME.  */
+
+void
+equiv_class_id::print (pretty_printer *pp) const
+{
+  if (null_p ())
+    pp_printf (pp, "null");
+  else
+    pp_printf (pp, "ec%i", m_idx);
+}
+
+/* FIXME.  */
+
+std::string
+equiv_class_id::to_str () const
+{
+  pretty_printer pp;
+  print (&pp);
+  return pp_formatted_text (&pp);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class constraint_manager.  */
+
+/* constraint_manager's copy ctor.  */
+
+constraint_manager::constraint_manager (const constraint_manager &other)
+: m_equiv_classes (other.m_equiv_classes.length ()),
+  m_constraints (other.m_constraints.length ())
+{
+  // FIXME: auto_vec's copy_ctor...
+  int i;
+  equiv_class *ec;
+  FOR_EACH_VEC_ELT (other.m_equiv_classes, i, ec)
+    m_equiv_classes.quick_push (new equiv_class (*ec));
+  constraint *c;
+  FOR_EACH_VEC_ELT (other.m_constraints, i, c)
+    m_constraints.quick_push (*c);
+}
+
+/* constraint_manager's assignment operator.  */
+
+constraint_manager&
+constraint_manager::operator= (const constraint_manager &other)
+{
+  gcc_assert (m_equiv_classes.length () == 0);
+  gcc_assert (m_constraints.length () == 0);
+
+  int i;
+  equiv_class *ec;
+  m_equiv_classes.reserve (other.m_equiv_classes.length ());
+  FOR_EACH_VEC_ELT (other.m_equiv_classes, i, ec)
+    m_equiv_classes.quick_push (new equiv_class (*ec));
+  constraint *c;
+  m_constraints.reserve (other.m_constraints.length ());
+  FOR_EACH_VEC_ELT (other.m_constraints, i, c)
+    m_constraints.quick_push (*c);
+
+  return *this;
+}
+
+/* Generate a hash value for this constraint_manager.  */
+
+hashval_t
+constraint_manager::hash () const
+{
+  inchash::hash hstate;
+  int i;
+  equiv_class *ec;
+  constraint *c;
+
+  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 ();
+}
+
+/* Equality operator for constraint_manager.  */
+
+bool
+constraint_manager::operator== (const constraint_manager &other) const
+{
+  if (m_equiv_classes.length () != other.m_equiv_classes.length ())
+    return false;
+  if (m_constraints.length () != other.m_constraints.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.  */
+
+void
+constraint_manager::print (pretty_printer *pp) const
+{
+  pp_string (pp, "{");
+  int i;
+  equiv_class *ec;
+  FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+    {
+      if (i > 0)
+	pp_string (pp, ", ");
+      pp_printf (pp, "%s: ", equiv_class_id (i).to_str ().c_str ());
+      pp_string (pp, ec->to_str ().c_str ());
+    }
+  pp_string (pp, "  |  ");
+  constraint *c;
+  FOR_EACH_VEC_ELT (m_constraints, i, c)
+    {
+      if (i > 0)
+	pp_string (pp, " && ");
+      pp_string (pp, c->to_str (*this).c_str ());
+    }
+  pp_printf (pp, "}");
+}
+
+/* FIXME.  */
+
+std::string
+constraint_manager::to_str () const
+{
+  pretty_printer pp;
+  print (&pp);
+  return pp_formatted_text (&pp);
+}
+
+/* Dump a multiline representation of this constraint_manager to PP.  */
+
+void
+constraint_manager::dump_to_pp (pretty_printer *pp) const
+{
+  // TODO
+  pp_string (pp, "  equiv classes:");
+  pp_newline (pp);
+  int i;
+  equiv_class *ec;
+  FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+    {
+      pp_printf (pp, "    %s: ", equiv_class_id (i).to_str ().c_str ());
+      pp_string (pp, ec->to_str ().c_str ());
+      pp_newline (pp);
+    }
+  pp_string (pp, "  constraints:");
+  pp_newline (pp);
+  constraint *c;
+  FOR_EACH_VEC_ELT (m_constraints, i, c)
+    {
+      pp_printf (pp, "    %i: %s", i, c->to_str (*this).c_str ());
+      pp_newline (pp);
+    }
+}
+
+/* Dump a multiline representation of this constraint_manager to FP.  */
+
+void
+constraint_manager::dump (FILE *fp) const
+{
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  pp_show_color (&pp) = pp_show_color (global_dc->printer);
+  pp.buffer->stream = fp;
+  dump_to_pp (&pp);
+  pp_flush (&pp);
+}
+
+/* Dump a multiline representation of this constraint_manager to stderr.  */
+
+DEBUG_FUNCTION void
+constraint_manager::dump () const
+{
+  dump (stderr);
+}
+
+/* Attempt to add the constraint LHS OP RHS to this constraint_manager.
+   Return true if the constraint could be added (or is already true).
+   Return false if the constraint contradicts existing knowledge.  */
+
+bool
+constraint_manager::add_constraint (svalue_id lhs,
+				    enum tree_code op,
+				    svalue_id 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);
+}
+
+/* Attempt to add the constraint LHS_EC_ID OP RHS_EC_ID to this
+   constraint_manager.
+   Return true if the constraint could be added (or is already true).
+   Return false if the constraint contradicts existing knowledge.  */
+
+bool
+constraint_manager::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;
+	svalue_id *sid;
+	FOR_EACH_VEC_ELT (rhs_ec_obj.m_vars, i, sid)
+	  lhs_ec_obj.add (*sid, *this);
+
+	if (rhs_ec_obj.m_constant)
+	  {
+	    //gcc_assert (lhs_ec_obj.m_constant == NULL);
+	    lhs_ec_obj.m_constant = rhs_ec_obj.m_constant;
+	  }
+
+	/* Drop rhs equivalence class, overwriting it with the
+	   final ec (which might be the same one).  */
+	equiv_class_id final_ec_id = m_equiv_classes.length () - 1;
+	equiv_class *old_ec = m_equiv_classes[rhs_ec_id.m_idx];
+	equiv_class *final_ec = m_equiv_classes.pop ();
+	if (final_ec != old_ec)
+	  m_equiv_classes[rhs_ec_id.m_idx] = final_ec;
+	delete old_ec;
+
+	/* Update the constraints.  */
+	constraint *c;
+	FOR_EACH_VEC_ELT (m_constraints, i, c)
+	  {
+	    /* Update references to the rhs_ec so that
+	       they refer to the lhs_ec.  */
+	    if (c->m_lhs == rhs_ec_id)
+	      c->m_lhs = lhs_ec_id;
+	    if (c->m_rhs == rhs_ec_id)
+	      c->m_rhs = lhs_ec_id;
+
+	    /* Renumber all constraints that refer to the final rhs_ec
+	       to the old rhs_ec, where the old final_ec now lives.  */
+	    if (c->m_lhs == final_ec_id)
+	      c->m_lhs = rhs_ec_id;
+	    if (c->m_rhs == final_ec_id)
+	      c->m_rhs = rhs_ec_id;
+	  }
+      }
+      break;
+    case GE_EXPR:
+      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;
+    }
+  validate ();
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME: The current implementation of transitivity is expensive.
+   For now, disable it on release builds (so that I can test for correctness
+   in a debug build, and benchmark on a release build - and hope that there
+   aren't any correctness issues in a release build).  */
+
+const bool ENABLE_TRANSITIVITY =
+#if CHECKING_P
+  false
+#else
+  true
+#endif
+  ;
+
+/* Subroutine of constraint_manager::add_constraint, for handling all
+   operations other than equality (for which equiv classes are merged).  */
+
+void
+constraint_manager::add_constraint_internal (equiv_class_id lhs_id,
+					     enum constraint_op c_op,
+					     equiv_class_id rhs_id)
+{
+  /* FIXME.  */
+#if 0
+  gcc_assert (m_constraints.length () < 150);
+#endif
+
+  /* Add the constraint.  */
+  m_constraints.safe_push (constraint (lhs_id, c_op, rhs_id));
+
+  /* FIXME: how much breaks if we disable this? */
+  if (ENABLE_TRANSITIVITY)
+    return;
+
+  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.  */
+      svalue_id lhs = lhs_id.get_obj (*this).get_representative ();
+      svalue_id 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
+		      // FIXME:
+		      //&& 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))
+			{
+			  svalue_id cst_sid = get_sid_for_constant (constant);
+			  add_constraint
+			    (rhs_id, EQ_EXPR,
+			     get_or_add_equiv_class (cst_sid));
+			  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?  */
+		  // TODO:
+		  if (other_lhs_const
+		      && !lhs_const
+		      // FIXME:
+		      //&& 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))
+			{
+			  svalue_id cst_sid = get_sid_for_constant (constant);
+			  add_constraint
+			    (lhs_id, EQ_EXPR,
+			     get_or_add_equiv_class (cst_sid));
+			  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
+constraint_manager::get_equiv_class_by_sid (svalue_id sid, equiv_class_id *out) const
+{
+  /* 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;
+      svalue_id *iv;
+      FOR_EACH_VEC_ELT (ec->m_vars, j, iv)
+	if (*iv == sid)
+	  {
+	    *out = equiv_class_id (i);
+	    return true;
+	  }
+    }
+  return false;
+}
+
+/* FIXME.  */
+
+equiv_class_id
+constraint_manager::get_or_add_equiv_class (svalue_id sid)
+{
+  equiv_class_id result (-1);
+
+  /* Try svalue_id match.  */
+  if (get_equiv_class_by_sid (sid, &result))
+    return result;
+
+  /* Try equality of constants.  */
+  if (tree cst = maybe_get_constant (sid))
+    {
+      int i;
+      equiv_class *ec;
+      FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+	if (ec->m_constant)
+	  {
+	    tree eq = fold_build2 (EQ_EXPR, boolean_type_node,
+				   cst, ec->m_constant);
+	    if (eq == boolean_true_node)
+	      {
+		ec->add (sid, *this);
+		return equiv_class_id (i);
+	      }
+	  }
+    }
+
+
+  /* Not found.  */
+  equiv_class *new_ec = new equiv_class ();
+  new_ec->add (sid, *this);
+  m_equiv_classes.safe_push (new_ec);
+
+  equiv_class_id new_id (m_equiv_classes.length () - 1);
+
+  if (maybe_get_constant (sid))
+    {
+      /* 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.  SID should always
+		 be present by now, so this should never lead to a
+		 recursion.  */
+	      new_id = get_or_add_equiv_class (sid);
+	    }
+	}
+    }
+
+  return new_id;
+}
+
+/* FIXME.  */
+/* For implementing "__analyzer_eval".  */
+
+#if 0
+tristate
+constraint_manager::dump_condition (tree lhs, enum tree_code op, tree rhs)
+{
+  equiv_class_id lhs_ec_id
+    = get_or_add_equiv_class (svalue_id (lhs, m_stack_depth));
+  equiv_class_id rhs_ec_id
+    = get_or_add_equiv_class (svalue_id (rhs, m_stack_depth));
+
+  return eval_condition (lhs_ec_id, op, rhs_ec_id);
+}
+#endif
+
+/* Evaluate the condition LHS_EC OP RHS_EC.  */
+
+tristate
+constraint_manager::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);
+}
+
+/* Evaluate the condition LHS OP RHS, creating equiv_class instances for
+   LHS and RHS if they aren't already in equiv_classes.  */
+
+tristate
+constraint_manager::eval_condition (svalue_id lhs,
+				    enum tree_code op,
+				    svalue_id rhs)
+{
+  return eval_condition (get_or_add_equiv_class (lhs),
+			 op,
+			 get_or_add_equiv_class (rhs));
+}
+
+/* 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.  */
+
+#if 0
+void
+constraint_manager::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;
+      svalue_id *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
+}
+#endif
+
+/* Determine if PV (or something that points to it) has been marked as
+   having escaped.  */
+
+#if 0
+bool
+constraint_manager::calc_escaped_p (svalue_id pv)
+{
+  switch (TREE_CODE (pv.m_tree))
+    {
+    case COMPONENT_REF:
+    case MEM_REF:
+      return calc_escaped_p (svalue_id (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;
+}
+#endif
+
+/* Delete any information about svalue_id instances identified by P.
+   Such instances are removed from equivalence classes, and any
+   redundant ECs and constraints are also removed.
+   Accumulate stats into STATS.  */
+
+void
+constraint_manager::purge (const purge_criteria &p, purge_stats *stats)
+{
+  /* Delete any svalue_ids identified by P within the various equivalence
+     classes.  */
+  for (unsigned ec_idx = 0; ec_idx < m_equiv_classes.length (); )
+    {
+      equiv_class *ec = m_equiv_classes[ec_idx];
+
+      int i;
+      svalue_id *pv;
+      bool delete_ec = false;
+      FOR_EACH_VEC_ELT (ec->m_vars, i, pv)
+	{
+	  if (*pv == ec->m_cst_sid)
+	    continue;
+	  if (p.should_purge_p (*pv))
+	    {
+	      if (ec->del (*pv))
+		if (!ec->m_constant)
+		  delete_ec = true;
+	    }
+	}
+
+      if (delete_ec)
+	{
+	  delete ec;
+	  m_equiv_classes.ordered_remove (ec_idx);
+	  stats->m_num_equiv_classes++;
+
+	  /* Update the constraints, potentially removing some.  */
+	  for (unsigned con_idx = 0; con_idx < m_constraints.length (); )
+	    {
+	      constraint *c = &m_constraints[con_idx];
+
+	      /* Remove constraints that refer to the deleted EC.  */
+	      if (c->m_lhs == ec_idx
+		  || c->m_rhs == ec_idx)
+		{
+		  m_constraints.ordered_remove (con_idx);
+		  stats->m_num_constraints++;
+		}
+	      else
+		{
+		  /* Renumber constraints that refer to ECs that have
+		     had their idx changed.  */
+		  c->m_lhs.update_for_removal (ec_idx);
+		  c->m_rhs.update_for_removal (ec_idx);
+
+		  con_idx++;
+		}
+	    }
+	}
+      else
+	ec_idx++;
+    }
+
+  /* Now delete any constraints that are purely between constants.  */
+  for (unsigned con_idx = 0; con_idx < m_constraints.length (); )
+    {
+      constraint *c = &m_constraints[con_idx];
+      if (m_equiv_classes[c->m_lhs.m_idx]->m_vars.length () == 0
+	  && m_equiv_classes[c->m_rhs.m_idx]->m_vars.length () == 0)
+	{
+	  m_constraints.ordered_remove (con_idx);
+	  stats->m_num_constraints++;
+	}
+      else
+	{
+	  con_idx++;
+	}
+    }
+
+  /* Finally, delete any ECs that purely contain constants and aren't
+     referenced by any constraints.  */
+  for (unsigned ec_idx = 0; ec_idx < m_equiv_classes.length (); )
+    {
+      equiv_class *ec = m_equiv_classes[ec_idx];
+      if (ec->m_vars.length () == 0)
+	{
+	  equiv_class_id ec_id (ec_idx);
+	  bool has_constraint = false;
+	  for (unsigned con_idx = 0; con_idx < m_constraints.length ();
+	       con_idx++)
+	    {
+	      constraint *c = &m_constraints[con_idx];
+	      if (c->m_lhs == ec_id
+		  || c->m_rhs == ec_id)
+		{
+		  has_constraint = true;
+		  break;
+		}
+	    }
+	  if (!has_constraint)
+	    {
+	      delete ec;
+	      m_equiv_classes.ordered_remove (ec_idx);
+	      stats->m_num_equiv_classes++;
+
+	      /* Renumber constraints that refer to ECs that have
+		 had their idx changed.  */
+	      for (unsigned con_idx = 0; con_idx < m_constraints.length ();
+		   con_idx++)
+		{
+		  constraint *c = &m_constraints[con_idx];
+		  c->m_lhs.update_for_removal (ec_idx);
+		  c->m_rhs.update_for_removal (ec_idx);
+		}
+	      continue;
+	    }
+	}
+      ec_idx++;
+    }
+
+  validate ();
+}
+
+/* Remap all svalue_ids within this constraint_manager using MAP.  */
+
+void
+constraint_manager::remap_svalue_ids (const svalue_id_map &map)
+{
+  int i;
+  equiv_class *ec;
+  FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+    ec->remap_svalue_ids (map);
+}
+
+/* Comparator for use by constraint_manager::canonicalize.
+   Sort a pair of equiv_class instances, using the representative
+   svalue_id as a sort key.  */
+
+static int
+equiv_class_cmp (const void *p1, const void *p2)
+{
+  const equiv_class *ec1 = *(const equiv_class * const *)p1;
+  const equiv_class *ec2 = *(const equiv_class * const *)p2;
+
+  svalue_id rep1 = ec1->get_representative ();
+  svalue_id rep2 = ec2->get_representative ();
+
+  return rep1.as_int () - rep2.as_int ();
+}
+
+/* Comparator for use by constraint_manager::canonicalize.
+   Sort a pair of constraint instances.  */
+
+static int
+constraint_cmp (const void *p1, const void *p2)
+{
+  const constraint *c1 = (const constraint *)p1;
+  const constraint *c2 = (const constraint *)p2;
+  int lhs_cmp = c1->m_lhs.as_int () - c2->m_lhs.as_int ();
+  if (lhs_cmp)
+    return lhs_cmp;
+  int rhs_cmp = c1->m_rhs.as_int () - c2->m_rhs.as_int ();
+  if (rhs_cmp)
+    return rhs_cmp;
+  return c1->m_op - c2->m_op;
+}
+
+/* Reorder the equivalence classes and constraints within this
+   constraint_manager into a canonical order, to increase the
+   chances of finding equality with another instance.  */
+
+void
+constraint_manager::canonicalize (unsigned num_svalue_ids)
+{
+  /* First, sort svalue_ids within the ECs.  */
+  unsigned i;
+  equiv_class *ec;
+  FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+    ec->canonicalize ();
+
+  /* Next, sort the ECs into a canonical order.  */
+
+  /* We will need to remap the equiv_class_ids in the constraints,
+     so we need to store the original index of each EC.
+     Build a lookup table, mapping from representative svalue_id
+     to the original equiv_class_id of that svalue_id.  */
+  auto_vec<equiv_class_id> original_ec_id (num_svalue_ids);
+  for (i = 0; i < num_svalue_ids; i++)
+    original_ec_id.quick_push (equiv_class_id::null ());
+  FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+    {
+      svalue_id rep = ec->get_representative ();
+      gcc_assert (!rep.null_p ());
+      original_ec_id[rep.as_int ()] = i;
+    }
+
+  /* Sort the equivalence classes.  */
+  m_equiv_classes.qsort (equiv_class_cmp);
+
+  /* Populate ec_id_map based on the old vs new EC ids.  */
+  one_way_id_map<equiv_class_id> ec_id_map (m_equiv_classes.length ());
+  FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+    {
+      svalue_id rep = ec->get_representative ();
+      ec_id_map.put (original_ec_id[rep.as_int ()], i);
+    }
+
+  /* Update the EC ids within the constraints.  */
+  constraint *c;
+  FOR_EACH_VEC_ELT (m_constraints, i, c)
+    {
+      ec_id_map.update (&c->m_lhs);
+      ec_id_map.update (&c->m_rhs);
+    }
+
+  /* Finally, sort the constraints. */
+  m_constraints.qsort (constraint_cmp);
+}
+
+/* A concrete subclass of constraint_manager for use when
+   merging two constraint_manager into a third constraint_manager,
+   each of which has its own region_model.
+   Calls are delegated to the constraint_manager for the merged model,
+   and thus affect its region_model.  */
+
+class cleaned_constraint_manager : public constraint_manager
+{
+public:
+  cleaned_constraint_manager (constraint_manager *merged) : m_merged (merged) {}
+
+  constraint_manager *clone (region_model *) const FINAL OVERRIDE
+  {
+    gcc_unreachable ();
+  }
+  tree maybe_get_constant (svalue_id sid) const FINAL OVERRIDE
+  {
+    return m_merged->maybe_get_constant (sid);
+  }
+  svalue_id get_sid_for_constant (tree cst) const FINAL OVERRIDE
+  {
+    return m_merged->get_sid_for_constant (cst);
+  }
+  virtual int get_num_svalues () const FINAL OVERRIDE
+  {
+    return m_merged->get_num_svalues ();
+  }
+private:
+  constraint_manager *m_merged;
+};
+
+/* Concrete subclass of fact_visitor for use by constraint_manager::merge.
+   For every fact in CM_A, see if it is also true in *CM_B.  Add such
+   facts to *OUT.  */
+
+class merger_fact_visitor : public fact_visitor
+{
+public:
+  merger_fact_visitor (constraint_manager *cm_b,
+		       constraint_manager *out)
+  : m_cm_b (cm_b), m_out (out)
+  {}
+
+  void on_fact (svalue_id lhs, enum tree_code code, svalue_id rhs)
+    FINAL OVERRIDE
+  {
+    if (m_cm_b->eval_condition (lhs, code, rhs).is_true ())
+      {
+	bool sat = m_out->add_constraint (lhs, code, rhs);
+	gcc_assert (sat);
+      }
+  }
+
+private:
+  constraint_manager *m_cm_b;
+  constraint_manager *m_out;
+};
+
+/* Use MERGER to merge CM_A and CM_B into *OUT.
+   If one thinks of a constraint_manager as a subset of N-dimensional
+   space, this takes the union of the points of CM_A and CM_B, and
+   expresses that into *OUT.  Alternatively, it can be thought of
+   as the intersection of the constraints.  */
+
+void
+constraint_manager::merge (const constraint_manager &cm_a,
+			   const constraint_manager &cm_b,
+			   constraint_manager *out,
+			   const model_merger &merger)
+{
+  gcc_assert (merger.m_sid_mapping);
+
+  /* Map svalue_ids in each equiv class from both sources
+     to the merged region_model, dropping ids that don't survive merger,
+     and potentially creating svalues in *OUT for constants.  */
+  cleaned_constraint_manager cleaned_cm_a (out);
+  const one_way_svalue_id_map &map_a_to_m
+    = merger.m_sid_mapping->m_map_from_a_to_m;
+  clean_merger_input (cm_a, map_a_to_m, &cleaned_cm_a);
+
+  cleaned_constraint_manager cleaned_cm_b (out);
+  const one_way_svalue_id_map &map_b_to_m
+    = merger.m_sid_mapping->m_map_from_b_to_m;
+  clean_merger_input (cm_b, map_b_to_m, &cleaned_cm_b);
+
+  /* At this point, the two cleaned CMs have ECs and constraints referring
+     to svalues in the merged region model, but both of them have separate
+     ECs.  */
+
+  /* Merge the equivalence classes and constraints.
+     The easiest way to do this seems to be to enumerate all of the facts
+     in cleaned_cm_a, see which are also true in cleaned_cm_b,
+     and add those to *OUT.  */
+  merger_fact_visitor v (&cleaned_cm_b, out);
+  cleaned_cm_a.for_each_fact (&v);
+}
+
+/* A subroutine of constraint_manager::merge.
+   Use MAP_SID_TO_M to map equivalence classes and constraints from
+   SM_IN to *OUT.  Purge any non-constant svalue_id that don't appear
+   in the result of MAP_SID_TO_M, purging any ECs and their constraints
+   that become empty as a result.  Potentially create svalues in
+   the merged region_model for constants that weren't already in use there.  */
+
+void
+constraint_manager::
+clean_merger_input (const constraint_manager &cm_in,
+		    const one_way_svalue_id_map &map_sid_to_m,
+		    constraint_manager *out)
+{
+  one_way_id_map<equiv_class_id> map_ec_to_m
+    (cm_in.m_equiv_classes.length ());
+  unsigned ec_idx;
+  equiv_class *ec;
+  FOR_EACH_VEC_ELT (cm_in.m_equiv_classes, ec_idx, ec)
+    {
+      equiv_class cleaned_ec;
+      if (tree cst = ec->get_any_constant ())
+	{
+	  cleaned_ec.m_constant = cst;
+	  /* Lazily create the constant in the out region_model.  */
+	  cleaned_ec.m_cst_sid = out->get_sid_for_constant (cst);
+	}
+      unsigned var_idx;
+      svalue_id *var_in_sid;
+      FOR_EACH_VEC_ELT (ec->m_vars, var_idx, var_in_sid)
+	{
+	  svalue_id var_m_sid = map_sid_to_m.get_dst_for_src (*var_in_sid);
+	  if (!var_m_sid.null_p ())
+	    cleaned_ec.m_vars.safe_push (var_m_sid);
+	}
+      if (cleaned_ec.get_any_constant () || !cleaned_ec.m_vars.is_empty ())
+	{
+	  map_ec_to_m.put (ec_idx, out->m_equiv_classes.length ());
+	  out->m_equiv_classes.safe_push (new equiv_class (cleaned_ec));
+	}
+    }
+
+  /* Write out to *OUT any constraints for which both sides survived
+     cleaning, using the new EC IDs.  */
+  unsigned con_idx;
+  constraint *c;
+  FOR_EACH_VEC_ELT (cm_in.m_constraints, con_idx, c)
+    {
+      equiv_class_id new_lhs = map_ec_to_m.get_dst_for_src (c->m_lhs);
+      if (new_lhs.null_p ())
+	continue;
+      equiv_class_id new_rhs = map_ec_to_m.get_dst_for_src (c->m_rhs);
+      if (new_rhs.null_p ())
+	continue;
+      out->m_constraints.safe_push (constraint (new_lhs,
+						c->m_op,
+						new_rhs));
+    }
+}
+
+/* FIXME.  */
+
+#if 0
+void
+constraint_manager::get_ec_map (auto_vec<equiv_class_id> *out_ec_for_sid) const
+{
+  const unsigned num_svalues = get_num_svalues ();
+  for (unsigned i = 0; i < num_svalues; i++)
+    out_ec_for_sid->safe_push (equiv_class_id::null ());
+  unsigned ec_idx;
+  equiv_class *ec;
+  FOR_EACH_VEC_ELT (m_equiv_classes, ec_idx, ec)
+    {
+      if (!ec->m_cst_sid.null_p ())
+	(*out_ec_for_sid)[ec->m_cst_sid.as_int ()] = ec_idx;
+      unsigned i;
+      svalue_id *sid;
+      FOR_EACH_VEC_ELT (ec->m_vars, i, sid)
+	(*out_ec_for_sid)[sid->as_int ()] = ec_idx;
+    }
+}
+#endif
+
+/* Call VISITOR's on_fact vfunc repeatedly to express the various
+   equivalence classes and constraints.
+   This is used by constraint_manager::merge to find the common
+   facts between two input constraint_managers.  */
+
+void
+constraint_manager::for_each_fact (fact_visitor *visitor) const
+{
+  /* First, call EQ_EXPR within the various equivalence classes.  */
+  unsigned ec_idx;
+  equiv_class *ec;
+  FOR_EACH_VEC_ELT (m_equiv_classes, ec_idx, ec)
+    {
+      if (!ec->m_cst_sid.null_p ())
+	{
+	  unsigned i;
+	  svalue_id *sid;
+	  FOR_EACH_VEC_ELT (ec->m_vars, i, sid)
+	    visitor->on_fact (ec->m_cst_sid, EQ_EXPR, *sid);
+	}
+      for (unsigned i = 0; i < ec->m_vars.length (); i++)
+	for (unsigned j = i + 1; j < ec->m_vars.length (); j++)
+	  visitor->on_fact (ec->m_vars[i], EQ_EXPR, ec->m_vars[j]);
+    }
+
+  /* Now, express the various constraints.  */
+  unsigned con_idx;
+  constraint *c;
+  FOR_EACH_VEC_ELT (m_constraints, con_idx, c)
+    {
+      const equiv_class &ec_lhs = c->m_lhs.get_obj (*this);
+      const equiv_class &ec_rhs = c->m_rhs.get_obj (*this);
+      enum tree_code code = constraint_tree_code (c->m_op);
+
+      if (!ec_lhs.m_cst_sid.null_p ())
+	{
+	  for (unsigned j = 0; j < ec_rhs.m_vars.length (); j++)
+	    {
+	      visitor->on_fact (ec_lhs.m_cst_sid, code, ec_rhs.m_vars[j]);
+	    }
+	}
+      for (unsigned i = 0; i < ec_lhs.m_vars.length (); i++)
+	{
+	  if (!ec_rhs.m_cst_sid.null_p ())
+	    visitor->on_fact (ec_lhs.m_vars[i], code, ec_rhs.m_cst_sid);
+	  for (unsigned j = 0; j < ec_rhs.m_vars.length (); j++)
+	    visitor->on_fact (ec_lhs.m_vars[i], code, ec_rhs.m_vars[j]);
+	}
+    }
+}
+
+/* Assert that this object is valid.  */
+
+void
+constraint_manager::validate () const
+{
+  /* Skip this in a release build.  */
+#if !CHECKING_P
+  return;
+#endif
+
+  int i;
+  equiv_class *ec;
+  FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+    {
+      gcc_assert (ec);
+
+      int j;
+      svalue_id *sid;
+      FOR_EACH_VEC_ELT (ec->m_vars, j, sid)
+	{
+	  gcc_assert (!sid->null_p ());
+	  gcc_assert (sid->as_int () < get_num_svalues ());
+	}
+      if (ec->m_constant)
+	gcc_assert (CONSTANT_CLASS_P (ec->m_constant));
+#if 0
+      else
+	gcc_assert (ec->m_vars.length () > 0);
+#endif
+    }
+
+  constraint *c;
+  FOR_EACH_VEC_ELT (m_constraints, i, c)
+    {
+      gcc_assert (!c->m_lhs.null_p ());
+      gcc_assert (c->m_lhs.as_int () <= (int)m_equiv_classes.length ());
+      gcc_assert (!c->m_rhs.null_p ());
+      gcc_assert (c->m_rhs.as_int () <= (int)m_equiv_classes.length ());
+    }
+}
+
+// FIXME:
+void
+debug (const constraint_manager &cm)
+{
+  fprintf (stderr, "%s\n", cm.to_str ().c_str ());
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Various constraint_manager selftests.
+   These have to be written in terms of a region_model, since
+   the latter is responsible for managing svalue and svalue_id
+   instances.  */
+
+/* Verify that setting and getting simple conditions within a region_model
+   work (thus exercising the underlying constraint_manager).  */
+
+static void
+test_constraint_conditions ()
+{
+  tree int_42 = build_int_cst (integer_type_node, 42);
+  tree int_0 = build_int_cst (integer_type_node, 0);
+
+  tree x = build_global_decl ("x", integer_type_node);
+  tree y = build_global_decl ("y", integer_type_node);
+  tree z = build_global_decl ("z", integer_type_node);
+
+  /* Self-comparisons.  */
+  {
+    region_model model;
+    ASSERT_CONDITION_TRUE (model, x, EQ_EXPR, x);
+    ASSERT_CONDITION_TRUE (model, x, LE_EXPR, x);
+    ASSERT_CONDITION_TRUE (model, x, GE_EXPR, x);
+    ASSERT_CONDITION_FALSE (model, x, NE_EXPR, x);
+    ASSERT_CONDITION_FALSE (model, x, LT_EXPR, x);
+    ASSERT_CONDITION_FALSE (model, x, GT_EXPR, x);
+  }
+
+  /* x == y.  */
+  {
+    region_model model;
+    ASSERT_CONDITION_UNKNOWN (model, x, EQ_EXPR, y);
+
+    ADD_SAT_CONSTRAINT (model, x, EQ_EXPR, y);
+
+    ASSERT_CONDITION_TRUE (model, x, EQ_EXPR, y);
+    ASSERT_CONDITION_TRUE (model, x, LE_EXPR, y);
+    ASSERT_CONDITION_TRUE (model, x, GE_EXPR, y);
+    ASSERT_CONDITION_FALSE (model, x, NE_EXPR, y);
+    ASSERT_CONDITION_FALSE (model, x, LT_EXPR, y);
+    ASSERT_CONDITION_FALSE (model, x, GT_EXPR, y);
+
+    /* Swapped operands.  */
+    ASSERT_CONDITION_TRUE (model, y, EQ_EXPR, x);
+    ASSERT_CONDITION_TRUE (model, y, LE_EXPR, x);
+    ASSERT_CONDITION_TRUE (model, y, GE_EXPR, x);
+    ASSERT_CONDITION_FALSE (model, y, NE_EXPR, x);
+    ASSERT_CONDITION_FALSE (model, y, LT_EXPR, x);
+    ASSERT_CONDITION_FALSE (model, y, GT_EXPR, x);
+
+    /* Comparison with other var.  */
+    ASSERT_CONDITION_UNKNOWN (model, x, EQ_EXPR, z);
+    ASSERT_CONDITION_UNKNOWN (model, x, LE_EXPR, z);
+    ASSERT_CONDITION_UNKNOWN (model, x, GE_EXPR, z);
+    ASSERT_CONDITION_UNKNOWN (model, x, NE_EXPR, z);
+    ASSERT_CONDITION_UNKNOWN (model, x, LT_EXPR, z);
+    ASSERT_CONDITION_UNKNOWN (model, x, GT_EXPR, z);
+  }
+
+  /* x == y, then y == z  */
+  {
+    region_model model;
+    ASSERT_CONDITION_UNKNOWN (model, x, EQ_EXPR, y);
+
+    ADD_SAT_CONSTRAINT (model, x, EQ_EXPR, y);
+    ADD_SAT_CONSTRAINT (model, y, EQ_EXPR, z);
+
+    ASSERT_CONDITION_TRUE (model, x, EQ_EXPR, z);
+    ASSERT_CONDITION_TRUE (model, x, LE_EXPR, z);
+    ASSERT_CONDITION_TRUE (model, x, GE_EXPR, z);
+    ASSERT_CONDITION_FALSE (model, x, NE_EXPR, z);
+    ASSERT_CONDITION_FALSE (model, x, LT_EXPR, z);
+    ASSERT_CONDITION_FALSE (model, x, GT_EXPR, z);
+  }
+
+  /* x != y.  */
+  {
+    region_model model;
+
+    ADD_SAT_CONSTRAINT (model, x, NE_EXPR, y);
+
+    ASSERT_CONDITION_TRUE (model, x, NE_EXPR, y);
+    ASSERT_CONDITION_FALSE (model, x, EQ_EXPR, y);
+    ASSERT_CONDITION_UNKNOWN (model, x, LE_EXPR, y);
+    ASSERT_CONDITION_UNKNOWN (model, x, GE_EXPR, y);
+    ASSERT_CONDITION_UNKNOWN (model, x, LT_EXPR, y);
+    ASSERT_CONDITION_UNKNOWN (model, x, GT_EXPR, y);
+
+    /* Swapped operands.  */
+    ASSERT_CONDITION_TRUE (model, y, NE_EXPR, x);
+    ASSERT_CONDITION_FALSE (model, y, EQ_EXPR, x);
+    ASSERT_CONDITION_UNKNOWN (model, y, LE_EXPR, x);
+    ASSERT_CONDITION_UNKNOWN (model, y, GE_EXPR, x);
+    ASSERT_CONDITION_UNKNOWN (model, y, LT_EXPR, x);
+    ASSERT_CONDITION_UNKNOWN (model, y, GT_EXPR, x);
+
+    /* Comparison with other var.  */
+    ASSERT_CONDITION_UNKNOWN (model, x, EQ_EXPR, z);
+    ASSERT_CONDITION_UNKNOWN (model, x, LE_EXPR, z);
+    ASSERT_CONDITION_UNKNOWN (model, x, GE_EXPR, z);
+    ASSERT_CONDITION_UNKNOWN (model, x, NE_EXPR, z);
+    ASSERT_CONDITION_UNKNOWN (model, x, LT_EXPR, z);
+    ASSERT_CONDITION_UNKNOWN (model, x, GT_EXPR, z);
+  }
+
+  /* x < y.  */
+  {
+    region_model model;
+
+    ADD_SAT_CONSTRAINT (model, x, LT_EXPR, y);
+
+    ASSERT_CONDITION_TRUE (model, x, LT_EXPR, y);
+    ASSERT_CONDITION_TRUE (model, x, LE_EXPR, y);
+    ASSERT_CONDITION_TRUE (model, x, NE_EXPR, y);
+    ASSERT_CONDITION_FALSE (model, x, EQ_EXPR, y);
+    ASSERT_CONDITION_FALSE (model, x, GT_EXPR, y);
+    ASSERT_CONDITION_FALSE (model, x, GE_EXPR, y);
+
+    /* Swapped operands.  */
+    ASSERT_CONDITION_FALSE (model, y, LT_EXPR, x);
+    ASSERT_CONDITION_FALSE (model, y, LE_EXPR, x);
+    ASSERT_CONDITION_TRUE (model, y, NE_EXPR, x);
+    ASSERT_CONDITION_FALSE (model, y, EQ_EXPR, x);
+    ASSERT_CONDITION_TRUE (model, y, GT_EXPR, x);
+    ASSERT_CONDITION_TRUE (model, y, GE_EXPR, x);
+  }
+
+  /* x <= y.  */
+  {
+    region_model model;
+
+    ADD_SAT_CONSTRAINT (model, x, LE_EXPR, y);
+
+    ASSERT_CONDITION_UNKNOWN (model, x, LT_EXPR, y);
+    ASSERT_CONDITION_TRUE (model, x, LE_EXPR, y);
+    ASSERT_CONDITION_UNKNOWN (model, x, NE_EXPR, y);
+    ASSERT_CONDITION_UNKNOWN (model, x, EQ_EXPR, y);
+    ASSERT_CONDITION_FALSE (model, x, GT_EXPR, y);
+    ASSERT_CONDITION_UNKNOWN (model, x, GE_EXPR, y);
+
+    /* Swapped operands.  */
+    ASSERT_CONDITION_FALSE (model, y, LT_EXPR, x);
+    ASSERT_CONDITION_UNKNOWN (model, y, LE_EXPR, x);
+    ASSERT_CONDITION_UNKNOWN (model, y, NE_EXPR, x);
+    ASSERT_CONDITION_UNKNOWN (model, y, EQ_EXPR, x);
+    ASSERT_CONDITION_UNKNOWN (model, y, GT_EXPR, x);
+    ASSERT_CONDITION_TRUE (model, y, GE_EXPR, x);
+  }
+
+  /* x > y.  */
+  {
+    region_model model;
+
+    ADD_SAT_CONSTRAINT (model, x, GT_EXPR, y);
+
+    ASSERT_CONDITION_TRUE (model, x, GT_EXPR, y);
+    ASSERT_CONDITION_TRUE (model, x, GE_EXPR, y);
+    ASSERT_CONDITION_TRUE (model, x, NE_EXPR, y);
+    ASSERT_CONDITION_FALSE (model, x, EQ_EXPR, y);
+    ASSERT_CONDITION_FALSE (model, x, LT_EXPR, y);
+    ASSERT_CONDITION_FALSE (model, x, LE_EXPR, y);
+
+    /* Swapped operands.  */
+    ASSERT_CONDITION_FALSE (model, y, GT_EXPR, x);
+    ASSERT_CONDITION_FALSE (model, y, GE_EXPR, x);
+    ASSERT_CONDITION_TRUE (model, y, NE_EXPR, x);
+    ASSERT_CONDITION_FALSE (model, y, EQ_EXPR, x);
+    ASSERT_CONDITION_TRUE (model, y, LT_EXPR, x);
+    ASSERT_CONDITION_TRUE (model, y, LE_EXPR, x);
+  }
+
+  /* x >= y.  */
+  {
+    region_model model;
+
+    ADD_SAT_CONSTRAINT (model, x, GE_EXPR, y);
+
+    ASSERT_CONDITION_UNKNOWN (model, x, GT_EXPR, y);
+    ASSERT_CONDITION_TRUE (model, x, GE_EXPR, y);
+    ASSERT_CONDITION_UNKNOWN (model, x, NE_EXPR, y);
+    ASSERT_CONDITION_UNKNOWN (model, x, EQ_EXPR, y);
+    ASSERT_CONDITION_FALSE (model, x, LT_EXPR, y);
+    ASSERT_CONDITION_UNKNOWN (model, x, LE_EXPR, y);
+
+    /* Swapped operands.  */
+    ASSERT_CONDITION_FALSE (model, y, GT_EXPR, x);
+    ASSERT_CONDITION_UNKNOWN (model, y, GE_EXPR, x);
+    ASSERT_CONDITION_UNKNOWN (model, y, NE_EXPR, x);
+    ASSERT_CONDITION_UNKNOWN (model, y, EQ_EXPR, x);
+    ASSERT_CONDITION_UNKNOWN (model, y, LT_EXPR, x);
+    ASSERT_CONDITION_TRUE (model, y, LE_EXPR, x);
+  }
+
+  // TODO: implied orderings
+
+  /* Constants.  */
+  {
+    region_model model;
+    ASSERT_CONDITION_FALSE (model, int_0, EQ_EXPR, int_42);
+    ASSERT_CONDITION_TRUE (model, int_0, NE_EXPR, int_42);
+    ASSERT_CONDITION_TRUE (model, int_0, LT_EXPR, int_42);
+    ASSERT_CONDITION_TRUE (model, int_0, LE_EXPR, int_42);
+    ASSERT_CONDITION_FALSE (model, int_0, GT_EXPR, int_42);
+    ASSERT_CONDITION_FALSE (model, int_0, GE_EXPR, int_42);
+  }
+
+  /* x == 0, y == 42.  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, x, EQ_EXPR, int_0);
+    ADD_SAT_CONSTRAINT (model, y, EQ_EXPR, int_42);
+
+    ASSERT_CONDITION_TRUE (model, x, NE_EXPR, y);
+    ASSERT_CONDITION_FALSE (model, x, EQ_EXPR, y);
+    ASSERT_CONDITION_TRUE (model, x, LE_EXPR, y);
+    ASSERT_CONDITION_FALSE (model, x, GE_EXPR, y);
+    ASSERT_CONDITION_TRUE (model, x, LT_EXPR, y);
+    ASSERT_CONDITION_FALSE (model, x, GT_EXPR, y);
+  }
+
+  /* Unsatisfiable combinations.  */
+
+  /* x == y && x != y.  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, x, EQ_EXPR, y);
+    ADD_UNSAT_CONSTRAINT (model, x, NE_EXPR, y);
+  }
+
+  /* x == 0 then x == 42.  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, x, EQ_EXPR, int_0);
+    ADD_UNSAT_CONSTRAINT (model, x, EQ_EXPR, int_42);
+  }
+
+  /* x == 0 then x != 0.  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, x, EQ_EXPR, int_0);
+    ADD_UNSAT_CONSTRAINT (model, x, NE_EXPR, int_0);
+  }
+
+  /* x == 0 then x > 0.  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, x, EQ_EXPR, int_0);
+    ADD_UNSAT_CONSTRAINT (model, x, GT_EXPR, int_0);
+  }
+
+  /* x != y && x == y.  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, x, NE_EXPR, y);
+    ADD_UNSAT_CONSTRAINT (model, x, EQ_EXPR, y);
+  }
+
+  /* x <= y && x > y.  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, x, LE_EXPR, y);
+    ADD_UNSAT_CONSTRAINT (model, x, GT_EXPR, y);
+  }
+
+  // etc
+}
+
+/* Test transitivity of conditions.  */
+
+static void
+test_transitivity ()
+{
+  tree a = build_global_decl ("a", integer_type_node);
+  tree b = build_global_decl ("b", integer_type_node);
+  tree c = build_global_decl ("c", integer_type_node);
+  tree d = build_global_decl ("d", integer_type_node);
+
+  /* a == b, then c == d, then c == b.  */
+  {
+    region_model model;
+    ASSERT_CONDITION_UNKNOWN (model, a, EQ_EXPR, b);
+    ASSERT_CONDITION_UNKNOWN (model, b, EQ_EXPR, c);
+    ASSERT_CONDITION_UNKNOWN (model, c, EQ_EXPR, d);
+    ASSERT_CONDITION_UNKNOWN (model, a, EQ_EXPR, d);
+
+    ADD_SAT_CONSTRAINT (model, a, EQ_EXPR, b);
+    ASSERT_CONDITION_TRUE (model, a, EQ_EXPR, b);
+
+    ADD_SAT_CONSTRAINT (model, c, EQ_EXPR, d);
+    ASSERT_CONDITION_TRUE (model, c, EQ_EXPR, d);
+    ASSERT_CONDITION_UNKNOWN (model, a, EQ_EXPR, d);
+
+    ADD_SAT_CONSTRAINT (model, c, EQ_EXPR, b);
+    ASSERT_CONDITION_TRUE (model, c, EQ_EXPR, b);
+    ASSERT_CONDITION_TRUE (model, a, EQ_EXPR, d);
+  }
+
+  /* Transitivity: "a < b", "b < c" should imply "a < c".  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, a, LT_EXPR, b);
+    ADD_SAT_CONSTRAINT (model, b, LT_EXPR, c);
+
+    ASSERT_CONDITION_TRUE (model, a, LT_EXPR, c);
+    ASSERT_CONDITION_FALSE (model, a, EQ_EXPR, c);
+  }
+
+  /* Transitivity: "a <= b", "b < c" should imply "a < c".  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, a, LE_EXPR, b);
+    ADD_SAT_CONSTRAINT (model, b, LT_EXPR, c);
+
+    ASSERT_CONDITION_TRUE (model, a, LT_EXPR, c);
+    ASSERT_CONDITION_FALSE (model, a, EQ_EXPR, c);
+  }
+
+  /* Transitivity: "a <= b", "b <= c" should imply "a <= c".  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, a, LE_EXPR, b);
+    ADD_SAT_CONSTRAINT (model, b, LE_EXPR, c);
+
+    ASSERT_CONDITION_TRUE (model, a, LE_EXPR, c);
+    ASSERT_CONDITION_UNKNOWN (model, a, EQ_EXPR, c);
+  }
+
+  /* Transitivity: "a > b", "b > c" should imply "a > c".  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, a, GT_EXPR, b);
+    ADD_SAT_CONSTRAINT (model, b, GT_EXPR, c);
+
+    ASSERT_CONDITION_TRUE (model, a, GT_EXPR, c);
+    ASSERT_CONDITION_FALSE (model, a, EQ_EXPR, c);
+  }
+
+  /* Transitivity: "a >= b", "b > c" should imply " a > c".  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, a, GE_EXPR, b);
+    ADD_SAT_CONSTRAINT (model, b, GT_EXPR, c);
+
+    ASSERT_CONDITION_TRUE (model, a, GT_EXPR, c);
+    ASSERT_CONDITION_FALSE (model, a, EQ_EXPR, c);
+  }
+
+  /* Transitivity: "a >= b", "b >= c" should imply "a >= c".  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, a, GE_EXPR, b);
+    ADD_SAT_CONSTRAINT (model, b, GE_EXPR, c);
+
+    ASSERT_CONDITION_TRUE (model, a, GE_EXPR, c);
+    ASSERT_CONDITION_UNKNOWN (model, 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).  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, a, LT_EXPR, b);
+    ADD_SAT_CONSTRAINT (model, c, LT_EXPR, d);
+    ADD_SAT_CONSTRAINT (model, b, LT_EXPR, c);
+
+    ASSERT_CONDITION_TRUE (model, a, LT_EXPR, c);
+    ASSERT_CONDITION_TRUE (model, b, LT_EXPR, d);
+    ASSERT_CONDITION_TRUE (model, a, LT_EXPR, d);
+  }
+
+  /* Transitivity: "a >= b", "b >= a" should imply that a == b.  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, a, GE_EXPR, b);
+    ADD_SAT_CONSTRAINT (model, b, GE_EXPR, a);
+
+    // TODO:
+    ASSERT_CONDITION_TRUE (model, a, EQ_EXPR, b);
+  }
+
+  /* Transitivity: "a >= b", "b > a" should be impossible.  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, a, GE_EXPR, b);
+    ADD_UNSAT_CONSTRAINT (model, b, GT_EXPR, a);
+  }
+
+  /* Transitivity: "a >= b", "b >= c", "c >= a" should imply
+     that a == b == c.  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, a, GE_EXPR, b);
+    ADD_SAT_CONSTRAINT (model, b, GE_EXPR, c);
+    ADD_SAT_CONSTRAINT (model, c, GE_EXPR, a);
+
+    ASSERT_CONDITION_TRUE (model, a, EQ_EXPR, c);
+  }
+
+  /* Transitivity: "a > b", "b > c", "c > a"
+     should be impossible.  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, a, GT_EXPR, b);
+    ADD_SAT_CONSTRAINT (model, b, GT_EXPR, c);
+    ADD_UNSAT_CONSTRAINT (model, c, GT_EXPR, a);
+  }
+
+}
+
+/* Test various conditionals involving constants where the results
+   ought to be implied based on the values of the constants.  */
+
+static void
+test_constant_comparisons ()
+{
+  tree int_3 = build_int_cst (integer_type_node, 3);
+  tree int_4 = build_int_cst (integer_type_node, 4);
+  tree int_5 = build_int_cst (integer_type_node, 5);
+
+  tree int_1023 = build_int_cst (integer_type_node, 1023);
+  tree int_1024 = build_int_cst (integer_type_node, 1024);
+
+  tree a = build_global_decl ("a", integer_type_node);
+  tree b = build_global_decl ("b", integer_type_node);
+
+  /* Given a >= 1024, then a <= 1023 should be impossible.  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, a, GE_EXPR, int_1024);
+    ADD_UNSAT_CONSTRAINT (model, a, LE_EXPR, int_1023);
+  }
+
+  /* a > 4.  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, a, GT_EXPR, int_4);
+    ASSERT_CONDITION_TRUE (model, a, GT_EXPR, int_4);
+    ASSERT_CONDITION_TRUE (model, a, NE_EXPR, int_3);
+    ASSERT_CONDITION_UNKNOWN (model, a, NE_EXPR, int_5);
+  }
+
+  /* a <= 4.  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, a, LE_EXPR, int_4);
+    ASSERT_CONDITION_FALSE (model, a, GT_EXPR, int_4);
+    ASSERT_CONDITION_FALSE (model, a, GT_EXPR, int_5);
+    ASSERT_CONDITION_UNKNOWN (model, a, NE_EXPR, int_3);
+  }
+
+  /* If "a > b" and "a == 3", then "b == 4" ought to be unsatisfiable.  */
+  {
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, a, GT_EXPR, b);
+    ADD_SAT_CONSTRAINT (model, a, EQ_EXPR, int_3);
+    ADD_UNSAT_CONSTRAINT (model, 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.  */
+    {
+      region_model model;
+      ADD_SAT_CONSTRAINT (model, a, LE_EXPR, int_4);
+      ADD_SAT_CONSTRAINT (model, a, GT_EXPR, int_3);
+      ASSERT_CONDITION_TRUE (model, a, EQ_EXPR, int_4);
+    }
+
+    /* If "a > 3" && "a <= 4", then "a == 4",
+       assuming a is of integral type.  */
+    {
+      region_model model;
+      ADD_SAT_CONSTRAINT (model, a, GT_EXPR, int_3);
+      ADD_SAT_CONSTRAINT (model, a, LE_EXPR, int_4);
+      ASSERT_CONDITION_TRUE (model, a, EQ_EXPR, int_4);
+    }
+    /* If "a > 3" && "a < 5", then "a == 4",
+       assuming a is of integral type.  */
+    {
+      region_model model;
+      ADD_SAT_CONSTRAINT (model, a, GT_EXPR, int_3);
+      ADD_SAT_CONSTRAINT (model, a, LT_EXPR, int_5);
+      ASSERT_CONDITION_TRUE (model, a, EQ_EXPR, int_4);
+    }
+    /* If "a >= 4" && "a < 5", then "a == 4",
+       assuming a is of integral type.  */
+    {
+      region_model model;
+      ADD_SAT_CONSTRAINT (model, a, GE_EXPR, int_4);
+      ADD_SAT_CONSTRAINT (model, a, LT_EXPR, int_5);
+      ASSERT_CONDITION_TRUE (model, a, EQ_EXPR, int_4);
+    }
+    /* If "a >= 4" && "a <= 4", then "a == 4".  */
+    {
+      region_model model;
+      ADD_SAT_CONSTRAINT (model, a, GE_EXPR, int_4);
+      ADD_SAT_CONSTRAINT (model, a, LE_EXPR, int_4);
+      ASSERT_CONDITION_TRUE (model, a, EQ_EXPR, int_4);
+    }
+  }
+
+  /* As above, but for floating-point:
+     if "f > 3" && "f <= 4" we don't know that f == 4.  */
+  {
+    tree f = build_global_decl ("f", double_type_node);
+    tree float_3 = build_real_from_int_cst (double_type_node, int_3);
+    tree float_4 = build_real_from_int_cst (double_type_node, int_4);
+
+    region_model model;
+    ADD_SAT_CONSTRAINT (model, f, GT_EXPR, float_3);
+    ADD_SAT_CONSTRAINT (model, f, LE_EXPR, float_4);
+    ASSERT_CONDITION_UNKNOWN (model, f, EQ_EXPR, float_4);
+    ASSERT_CONDITION_UNKNOWN (model, f, EQ_EXPR, int_4);
+  }
+}
+
+/* Verify various lower-level implementation details about
+   constraint_manager.  */
+
+static void
+test_constraint_impl ()
+{
+  tree int_42 = build_int_cst (integer_type_node, 42);
+  tree int_0 = build_int_cst (integer_type_node, 0);
+
+  tree x = build_global_decl ("x", integer_type_node);
+  tree y = build_global_decl ("y", integer_type_node);
+  tree z = build_global_decl ("z", integer_type_node);
+
+  /* x == y.  */
+  {
+    region_model model;
+
+    ADD_SAT_CONSTRAINT (model, x, EQ_EXPR, y);
+
+    /* Assert various things about the insides of model.  */
+    constraint_manager *cm = model.get_constraints ();
+    ASSERT_EQ (cm->m_constraints.length (), 0);
+    ASSERT_EQ (cm->m_equiv_classes.length (), 1);
+  }
+
+  /* y <= z; x == y.  */
+  {
+    region_model model;
+    ASSERT_CONDITION_UNKNOWN (model, x, EQ_EXPR, y);
+    ASSERT_CONDITION_UNKNOWN (model, x, GE_EXPR, z);
+
+    ADD_SAT_CONSTRAINT (model, y, GE_EXPR, z);
+    ASSERT_CONDITION_TRUE (model, y, GE_EXPR, z);
+    ASSERT_CONDITION_UNKNOWN (model, x, GE_EXPR, z);
+
+    ADD_SAT_CONSTRAINT (model, x, EQ_EXPR, y);
+
+    /* Assert various things about the insides of model.  */
+    constraint_manager *cm = model.get_constraints ();
+    ASSERT_EQ (cm->m_constraints.length (), 1);
+    ASSERT_EQ (cm->m_equiv_classes.length (), 2);
+
+    /* Ensure that we merged the constraints.  */
+    ASSERT_CONDITION_TRUE (model, x, GE_EXPR, z);
+  }
+
+  /* y <= z; y == x.  */
+  {
+    region_model model;
+    ASSERT_CONDITION_UNKNOWN (model, x, EQ_EXPR, y);
+    ASSERT_CONDITION_UNKNOWN (model, x, GE_EXPR, z);
+
+    ADD_SAT_CONSTRAINT (model, y, GE_EXPR, z);
+    ASSERT_CONDITION_TRUE (model, y, GE_EXPR, z);
+    ASSERT_CONDITION_UNKNOWN (model, x, GE_EXPR, z);
+
+    ADD_SAT_CONSTRAINT (model, y, EQ_EXPR, x);
+
+    /* Assert various things about the insides of model.  */
+    constraint_manager *cm = model.get_constraints ();
+    ASSERT_EQ (cm->m_constraints.length (), 1);
+    ASSERT_EQ (cm->m_equiv_classes.length (), 2);
+
+    /* Ensure that we merged the constraints.  */
+    ASSERT_CONDITION_TRUE (model, x, GE_EXPR, z);
+  }
+
+  /* x == 0, then x != 42.  */
+  {
+    region_model model;
+
+    ADD_SAT_CONSTRAINT (model, x, EQ_EXPR, int_0);
+    ADD_SAT_CONSTRAINT (model, x, NE_EXPR, int_42);
+
+    /* Assert various things about the insides of model.  */
+    constraint_manager *cm = model.get_constraints ();
+    ASSERT_EQ (cm->m_constraints.length (), 1);
+    ASSERT_EQ (cm->m_equiv_classes.length (), 2);
+    ASSERT_EQ (cm->m_constraints[0].m_lhs,
+	       cm->get_or_add_equiv_class (model.get_rvalue (int_0, NULL)));
+    ASSERT_EQ (cm->m_constraints[0].m_rhs,
+	       cm->get_or_add_equiv_class (model.get_rvalue (int_42, NULL)));
+    ASSERT_EQ (cm->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
+}
+
+/* Check that operator== and hashing works as expected for the
+   various types.  */
+
+static void
+test_equality ()
+{
+  tree x = build_global_decl ("x", integer_type_node);
+  tree y = build_global_decl ("y", integer_type_node);
+
+  {
+    region_model model0;
+    region_model model1;
+
+    constraint_manager *cm0 = model0.get_constraints ();
+    constraint_manager *cm1 = model1.get_constraints ();
+
+    ASSERT_EQ (cm0->hash (), cm1->hash ());
+    ASSERT_EQ (*cm0, *cm1);
+
+    ASSERT_EQ (model0.hash (), model1.hash ());
+    ASSERT_EQ (model0, model1);
+
+    ADD_SAT_CONSTRAINT (model1, x, EQ_EXPR, y);
+    ASSERT_NE (cm0->hash (), cm1->hash ());
+    ASSERT_NE (*cm0, *cm1);
+
+    ASSERT_NE (model0.hash (), model1.hash ());
+    ASSERT_NE (model0, model1);
+
+    region_model model2;
+    constraint_manager *cm2 = model2.get_constraints ();
+    /* Make the same change to cm2.  */
+    ADD_SAT_CONSTRAINT (model2, x, EQ_EXPR, y);
+    ASSERT_EQ (cm1->hash (), cm2->hash ());
+    ASSERT_EQ (*cm1, *cm2);
+
+    ASSERT_EQ (model1.hash (), model2.hash ());
+    ASSERT_EQ (model1, model2);
+  }
+}
+
+#if 0
+
+/* A subclass of purge_criteria for selftests: purge all svalue_id instances.  */
+
+class purge_all_svalue_ids : public purge_criteria
+{
+public:
+  bool should_purge_p (svalue_id) const FINAL OVERRIDE
+  {
+    return true;
+  }
+};
+
+/* Check that constraint_manager objects can "forget".  */
+
+static void
+test_purging ()
+{
+  svalue_id int_42 (build_int_cst (integer_type_node, 42), 0);
+  svalue_id int_0 (build_int_cst (integer_type_node, 0), 0);
+
+  svalue_id x (build_decl (UNKNOWN_LOCATION, PARM_DECL,
+			  get_identifier ("x"),
+			  integer_type_node), 0);
+  svalue_id y (build_decl (UNKNOWN_LOCATION, PARM_DECL,
+			  get_identifier ("y"),
+			  integer_type_node), 0);
+
+  {
+    constraint_manager cm0;
+    constraint_manager cm1;
+
+    ADD_SAT_CONSTRAINT (cm1, x, EQ_EXPR, y);
+    ASSERT_NE (cm0, cm1);
+
+    purge_one_svalue_id px (x);
+    cm1.purge (px);
+
+    purge_one_svalue_id py (y);
+    cm1.purge (py);
+
+    ASSERT_EQ (cm0, cm1);
+  }
+
+  {
+    constraint_manager cm0;
+    constraint_manager cm1;
+
+    ADD_SAT_CONSTRAINT (cm1, x, EQ_EXPR, int_42);
+    ASSERT_NE (cm0, cm1);
+
+    purge_one_svalue_id px (x);
+    cm1.purge (px);
+
+    ASSERT_EQ (cm0, cm1);
+  }
+
+  {
+    constraint_manager cm0;
+    constraint_manager cm1;
+
+    ADD_SAT_CONSTRAINT (cm1, x, GE_EXPR, int_0);
+    ADD_SAT_CONSTRAINT (cm1, x, LE_EXPR, int_42);
+    ASSERT_NE (cm0, cm1);
+
+    purge_one_svalue_id px (x);
+    cm1.purge (px);
+
+    ASSERT_EQ (cm0, cm1);
+  }
+
+  {
+    constraint_manager cm0;
+    constraint_manager cm1;
+
+    ADD_SAT_CONSTRAINT (cm1, x, NE_EXPR, int_42);
+    ADD_SAT_CONSTRAINT (cm1, y, NE_EXPR, int_0);
+    ASSERT_NE (cm0, cm1);
+
+    purge_one_svalue_id px (x);
+    cm1.purge (px);
+    ASSERT_NE (cm0, cm1);
+
+    ASSERT_CONDITION_TRUE (cm1, y, NE_EXPR, int_0);
+  }
+
+  {
+    constraint_manager cm0;
+    constraint_manager cm1;
+
+    ADD_SAT_CONSTRAINT (cm1, x, NE_EXPR, int_42);
+    ADD_SAT_CONSTRAINT (cm1, y, NE_EXPR, int_0);
+    ASSERT_NE (cm0, cm1);
+
+    purge_all_svalue_ids p;
+    cm1.purge (p);
+    ASSERT_EQ (cm0, cm1);
+  }
+
+}
+#endif
+
+/* FIXME.  */
+
+static void
+test_many_constants ()
+{
+  tree a = build_global_decl ("a", integer_type_node);
+
+  /* FIXME: increase the limit.  There currently appears to be an O(n^5)
+     issue in this code.  */
+  region_model model;
+  for (int i = 0; i < 20; i++)
+    {
+      ADD_SAT_CONSTRAINT (model, a, NE_EXPR,
+			  build_int_cst (integer_type_node, i));
+
+      /* Merge, and check the result.  */
+      region_model other (model);
+
+      region_model merged;
+      ASSERT_TRUE (model.can_merge_with_p (other, &merged));
+      model.canonicalize (NULL);
+      merged.canonicalize (NULL);
+      ASSERT_EQ (model, merged);
+    }
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+analyzer_constraint_manager_cc_tests ()
+{
+  test_constraint_conditions ();
+  if (ENABLE_TRANSITIVITY)
+    {
+      test_transitivity ();
+      test_constant_comparisons ();
+    }
+  test_constraint_impl ();
+  test_equality ();
+#if 0
+  test_purging ();
+#endif
+  test_many_constants ();
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
diff --git a/gcc/analyzer/constraint-manager.h b/gcc/analyzer/constraint-manager.h
new file mode 100644
index 0000000..e25fea9
--- /dev/null
+++ b/gcc/analyzer/constraint-manager.h
@@ -0,0 +1,263 @@
+/* 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_CONSTRAINT_MANAGER_H
+#define GCC_ANALYZER_CONSTRAINT_MANAGER_H
+
+#include "analyzer/region-model.h" // for svalue_id
+
+class constraint_manager;
+
+/* Abstract base class for specifying how state should be purged.  */
+
+class purge_criteria
+{
+public:
+  virtual ~purge_criteria () {}
+  virtual bool should_purge_p (svalue_id sid) const = 0;
+};
+
+/* An equivalence class within a constraint manager: a set of
+   svalue_ids that are known to all be equal to each other,
+   together with an optional tree constant that they are equal to.  */
+
+class equiv_class
+{
+public:
+  static void *operator new (size_t sz);
+  static void operator delete (void* ptr, size_t sz);
+
+  equiv_class ();
+  equiv_class (const equiv_class &other);
+
+  std::string to_str () const;
+  hashval_t hash () const;
+  bool operator== (const equiv_class &other);
+
+  void add (svalue_id sid, const constraint_manager &cm);
+  bool del (svalue_id sid);
+
+  tree get_any_constant () const { return m_constant; }
+
+  svalue_id get_representative () const;
+
+  void remap_svalue_ids (const svalue_id_map &map);
+
+  void canonicalize ();
+
+  /* An equivalence class can contain multiple constants (e.g. multiple
+     different zeroes, for different types); these are just for the last
+     constant added.  */
+  tree m_constant;
+  svalue_id m_cst_sid;
+
+  // FIXME: this is a bad representation, probably should be a set:
+  auto_vec<svalue_id> m_vars;
+
+  /* Has this value escaped from this function?  */
+  bool m_escaped;
+};
+
+/* The various kinds of constraint.  */
+
+enum constraint_op
+{
+  CONSTRAINT_NE,
+  CONSTRAINT_LT,
+  CONSTRAINT_LE
+};
+
+const char *constraint_op_code (enum constraint_op c_op);
+
+/* An ID for an equiv_class within a constraint_manager.  Internally, this
+   is an index into a vector of equiv_class * within the constraint_manager.  */
+
+class equiv_class_id
+{
+public:
+  static equiv_class_id null () { return equiv_class_id (-1); }
+
+  equiv_class_id (unsigned idx) : m_idx (idx) {}
+  const equiv_class &get_obj (const constraint_manager &cm) const;
+  equiv_class &get_obj (constraint_manager &cm) 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;
+  }
+
+  bool null_p () const { return m_idx == -1; }
+
+  static equiv_class_id from_int (int idx) { return equiv_class_id (idx); }
+  int as_int () const { return m_idx; }
+
+  void print (pretty_printer *pp) const;
+  std::string to_str () const;
+
+  void update_for_removal (equiv_class_id other)
+  {
+    if (m_idx > other.m_idx)
+      m_idx--;
+  }
+
+  int m_idx;
+};
+
+/* A relationship between two equivalence classes in a constraint_manager.  */
+
+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)
+  {
+    gcc_assert (!lhs.null_p ());
+    gcc_assert (!rhs.null_p ());
+  }
+
+  std::string to_str (const constraint_manager &cm) 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;
+};
+
+/* An abstract base class for use with constraint_manager::for_each_fact.  */
+
+class fact_visitor
+{
+ public:
+  virtual ~fact_visitor () {}
+  virtual void on_fact (svalue_id lhs, enum tree_code, svalue_id rhs) = 0;
+};
+
+/* A collection of equivalence classes and constraints on them.
+
+   Given N svalues, this can be thought of as representing a subset of
+   N-dimensional space.  When we call add_constraint,
+   we are effectively taking an intersection with that constraint.  */
+
+class constraint_manager
+{
+public:
+  constraint_manager () {}
+  constraint_manager (const constraint_manager &other);
+  virtual ~constraint_manager () {}
+
+  virtual constraint_manager *clone (region_model *) const = 0;
+  virtual tree maybe_get_constant (svalue_id sid) const = 0;
+  virtual svalue_id get_sid_for_constant (tree cst) const = 0;
+  virtual int get_num_svalues () const = 0;
+
+  constraint_manager& operator= (const constraint_manager &other);
+
+  hashval_t hash () const;
+  bool operator== (const constraint_manager &other) const;
+  bool operator!= (const constraint_manager &other) const
+  {
+    return !(*this == other);
+  }
+
+  std::string to_str () const;
+  void print (pretty_printer *pp) const;
+  void dump_to_pp (pretty_printer *pp) const;
+  void dump (FILE *fp) const;
+  void dump () 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 (svalue_id sid)
+  {
+    equiv_class_id ec_id = get_or_add_equiv_class (sid);
+    return ec_id.get_obj (*this);
+  }
+
+  bool add_constraint (svalue_id lhs, enum tree_code op, svalue_id rhs);
+
+  bool add_constraint (equiv_class_id lhs_ec_id,
+		       enum tree_code op,
+		       equiv_class_id rhs_ec_id);
+
+  bool get_equiv_class_by_sid (svalue_id sid, equiv_class_id *out) const;
+  equiv_class_id get_or_add_equiv_class (svalue_id sid);
+  tristate eval_condition (equiv_class_id lhs,
+			   enum tree_code op,
+			   equiv_class_id rhs);
+  tristate eval_condition (svalue_id lhs,
+			   enum tree_code op,
+			   svalue_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 (svalue_id sid);
+
+  void purge (const purge_criteria &p, purge_stats *stats);
+
+  void remap_svalue_ids (const svalue_id_map &map);
+
+  void canonicalize (unsigned num_svalue_ids);
+
+  static void merge (const constraint_manager &cm_a,
+		     const constraint_manager &cm_b,
+		     constraint_manager *out,
+		     const model_merger &merger);
+
+  void for_each_fact (fact_visitor *) const;
+
+  void validate () const;
+
+  auto_delete_vec<equiv_class> m_equiv_classes;
+  auto_vec<constraint> m_constraints;
+
+ private:
+  static void clean_merger_input (const constraint_manager &cm_in,
+				  const one_way_svalue_id_map &map_sid_to_m,
+				  constraint_manager *out);
+#if 0
+  void get_ec_map (auto_vec<equiv_class_id> *out_ec_for_sid) const;
+#endif
+
+  void add_constraint_internal (equiv_class_id lhs_id,
+				enum constraint_op c_op,
+				equiv_class_id rhs_id);
+};
+
+#endif /* GCC_ANALYZER_CONSTRAINT_MANAGER_H */
diff --git a/gcc/analyzer/dead-code/address_of_function.sm b/gcc/analyzer/dead-code/address_of_function.sm
new file mode 100644
index 0000000..9917ab8
--- /dev/null
+++ b/gcc/analyzer/dead-code/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/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/buffer_overflow.sm b/gcc/analyzer/dead-code/buffer_overflow.sm
new file mode 100644
index 0000000..47c2470
--- /dev/null
+++ b/gcc/analyzer/dead-code/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/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/fd_checker.sm b/gcc/analyzer/dead-code/fd_checker.sm
new file mode 100644
index 0000000..cef881e
--- /dev/null
+++ b/gcc/analyzer/dead-code/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/gcc/analyzer/dead-code/fileptr.sm b/gcc/analyzer/dead-code/fileptr.sm
new file mode 100644
index 0000000..ae1a9e1
--- /dev/null
+++ b/gcc/analyzer/dead-code/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/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/malloc_checker.sm b/gcc/analyzer/dead-code/malloc_checker.sm
new file mode 100644
index 0000000..39ab433
--- /dev/null
+++ b/gcc/analyzer/dead-code/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/gcc/analyzer/dead-code/points_to_stack.sm b/gcc/analyzer/dead-code/points_to_stack.sm
new file mode 100644
index 0000000..63e0058
--- /dev/null
+++ b/gcc/analyzer/dead-code/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/gcc/analyzer/dead-code/python.sm b/gcc/analyzer/dead-code/python.sm
new file mode 100644
index 0000000..86b1b0a
--- /dev/null
+++ b/gcc/analyzer/dead-code/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/gcc/analyzer/dead-code/signal_handler.sm b/gcc/analyzer/dead-code/signal_handler.sm
new file mode 100644
index 0000000..8ef96de
--- /dev/null
+++ b/gcc/analyzer/dead-code/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/gcc/analyzer/dead-code/sizeof_allocation.sm b/gcc/analyzer/dead-code/sizeof_allocation.sm
new file mode 100644
index 0000000..ca63e1d
--- /dev/null
+++ b/gcc/analyzer/dead-code/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/gcc/analyzer/dead-code/taint.sm b/gcc/analyzer/dead-code/taint.sm
new file mode 100644
index 0000000..7b305ab
--- /dev/null
+++ b/gcc/analyzer/dead-code/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/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..6ddb33a
--- /dev/null
+++ b/gcc/analyzer/digraph.cc
@@ -0,0 +1,140 @@
+/* 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/analyzer.h"
+#include "analyzer/graphviz.h"
+#include "cgraph.h"
+#include "analyzer/digraph.h"
+#include "selftest.h"
+
+/* TODO: selftests.  */
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* A family of digraph classes for writing selftests.  */
+
+struct test_node;
+struct test_edge;
+struct test_graph;
+struct test_dump_args_t {};
+struct test_cluster;
+
+struct test_graph_traits
+{
+  typedef test_node node_t;
+  typedef test_edge edge_t;
+  typedef test_graph graph_t;
+  typedef test_dump_args_t dump_args_t;
+  typedef test_cluster cluster_t;
+};
+
+struct test_node : public dnode<test_graph_traits>
+{
+  test_node (const char *name) : m_name (name) {}
+  void dump_dot (graphviz_out *, const dump_args_t &) const OVERRIDE
+  {
+  }
+
+  const char *m_name;
+};
+
+struct test_edge : public dedge<test_graph_traits>
+{
+  test_edge (node_t *src, node_t *dest)
+  : dedge (src, dest)
+  {}
+
+  void dump_dot (graphviz_out *gv, const dump_args_t &) const OVERRIDE
+  {
+    pp_printf (gv->m_pp, "%s -> %s;\n",
+	       m_src->m_name, m_dest->m_name);
+  }
+};
+
+struct test_graph : public digraph<test_graph_traits>
+{
+};
+
+struct test_cluster : public cluster<test_graph_traits>
+{
+};
+
+/* FIXME.  */
+
+static void
+test_dump_to_dot ()
+{
+  test_graph g;
+  test_node *a = new test_node ("a");
+  test_node *b = new test_node ("b");
+  test_edge *e = new test_edge (a, b);
+  g.add_node (a);
+  g.add_node (b);
+  g.add_edge (e);
+
+  pretty_printer pp;
+  pp.buffer->stream = NULL;
+  test_dump_args_t dump_args;
+  g.dump_dot_to_pp (&pp, NULL, dump_args);
+
+  ASSERT_STR_CONTAINS (pp_formatted_text (&pp),
+		       "a -> b;\n");
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+analyzer_digraph_cc_tests ()
+{
+  test_dump_to_dot ();
+}
+
+} // namespace selftest
+
+#endif /* #if CHECKING_P */
diff --git a/gcc/analyzer/digraph.h b/gcc/analyzer/digraph.h
new file mode 100644
index 0000000..7e7f2e5
--- /dev/null
+++ b/gcc/analyzer/digraph.h
@@ -0,0 +1,252 @@
+/* Template classes for directed graphs.
+   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
+
+#include "tree-diagnostic.h" /* for default_tree_printer.  */
+
+/* Templates for a family of classes: digraph, node, edge, and cluster.
+   This assumes a traits type with the following typedefs:
+   node_t: the node class
+   edge_t: the edge class
+   dump_args_t: additional args for dot-dumps
+   cluster_t: the cluster class (for use when generating .dot files).
+
+   Using a template allows for typesafe nodes and edges: a node's
+   predecessor and successor edges can be of a node-specific edge
+   subclass, without needing casting.  */
+
+/* Abstract base class for a node in a directed graph.  */
+
+template <typename GraphTraits>
+class dnode
+{
+ public:
+  typedef typename GraphTraits::edge_t edge_t;
+  typedef typename GraphTraits::dump_args_t dump_args_t;
+
+  virtual ~dnode () {}
+  virtual void dump_dot (graphviz_out *gv, const dump_args_t &args) const = 0;
+
+  auto_vec<edge_t *> m_preds;
+  auto_vec<edge_t *> m_succs;
+};
+
+/* Abstract base class for an edge in a directed graph.  */
+
+template <typename GraphTraits>
+class dedge
+{
+ public:
+  typedef typename GraphTraits::node_t node_t;
+  typedef typename GraphTraits::dump_args_t dump_args_t;
+
+  dedge (node_t *src, node_t *dest)
+  : m_src (src), m_dest (dest) {}
+
+  virtual ~dedge () {}
+
+  virtual void dump_dot (graphviz_out *gv, const dump_args_t &args) const = 0;
+
+  node_t *const m_src;
+  node_t *const m_dest;
+};
+
+/* Abstract base class for a directed graph.
+   This class maintains the vectors of nodes and edges,
+   and owns the nodes and edges.  */
+
+template <typename GraphTraits>
+class digraph
+{
+ public:
+  typedef typename GraphTraits::node_t node_t;
+  typedef typename GraphTraits::edge_t edge_t;
+  typedef typename GraphTraits::dump_args_t dump_args_t;
+  typedef typename GraphTraits::cluster_t cluster_t;
+
+  digraph () {}
+  virtual ~digraph () {}
+
+  void dump_dot_to_pp (pretty_printer *pp,
+		       cluster_t *root_cluster,
+		       const dump_args_t &args) const;
+  void dump_dot_to_file (FILE *fp,
+			 cluster_t *root_cluster,
+			 const dump_args_t &args) const;
+  void dump_dot (const char *path,
+		 cluster_t *root_cluster,
+		 const dump_args_t &args) const;
+
+  void add_node (node_t *node);
+  void add_edge (edge_t *edge);
+
+  auto_delete_vec<node_t> m_nodes;
+  auto_delete_vec<edge_t> 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).  */
+
+template <typename GraphTraits>
+class cluster
+{
+ public:
+  typedef typename GraphTraits::node_t node_t;
+  typedef typename GraphTraits::dump_args_t dump_args_t;
+
+  virtual ~cluster () {}
+
+  virtual void add_node (node_t *node) = 0;
+
+  /* Recursively dump the cluster, all nodes, and child clusters.  */
+  virtual void dump_dot (graphviz_out *gv, const dump_args_t &) const = 0;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Write .dot information for this graph to PP, passing ARGS to the nodes
+   and edges.
+   If ROOT_CLUSTER is non-NULL, use it to organize the nodes into clusters.  */
+
+template <typename GraphTraits>
+inline void
+digraph<GraphTraits>::dump_dot_to_pp (pretty_printer *pp,
+				      cluster_t *root_cluster,
+				      const dump_args_t &args) const
+{
+  graphviz_out gv (pp);
+
+  pp_string (pp, "digraph \"");
+  pp_string (pp, "FIXME: base");
+  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;
+      node_t *n;
+      FOR_EACH_VEC_ELT (m_nodes, i, n)
+	root_cluster->add_node (n);
+      root_cluster->dump_dot (&gv, args);
+    }
+  else
+    {
+      /* Otherwise, display all nodes at top level.  */
+      int i;
+      node_t *n;
+      FOR_EACH_VEC_ELT (m_nodes, i, n)
+	n->dump_dot (&gv, args);
+    }
+
+  /* Edges.  */
+  int i;
+  edge_t *e;
+  FOR_EACH_VEC_ELT (m_edges, i, e)
+    e->dump_dot (&gv, args);
+
+  /* Terminate "digraph" */
+  gv.outdent ();
+  pp_string (pp, "}");
+  pp_newline (pp);
+}
+
+/* Write .dot information for this graph to FP, passing ARGS to the nodes
+   and edges.
+   If ROOT_CLUSTER is non-NULL, use it to organize the nodes into clusters.  */
+
+template <typename GraphTraits>
+inline void
+digraph<GraphTraits>::dump_dot_to_file (FILE *fp,
+					cluster_t *root_cluster,
+					const dump_args_t &args) const
+{
+  pretty_printer pp;
+  // TODO:
+  pp_format_decoder (&pp) = default_tree_printer;
+  pp.buffer->stream = fp;
+  dump_dot_to_pp (&pp, root_cluster, args);
+  pp_flush (&pp);
+}
+
+/* Write .dot information for this graph to a file at PATH, passing ARGS
+   to the nodes and edges.
+   If ROOT_CLUSTER is non-NULL, use it to organize the nodes into clusters.  */
+
+template <typename GraphTraits>
+inline void
+digraph<GraphTraits>::dump_dot (const char *path,
+				cluster_t *root_cluster,
+				const dump_args_t &args) const
+{
+  FILE *fp = fopen (path, "w");
+  dump_dot_to_file (fp, root_cluster, args);
+  fclose (fp);
+}
+
+/* Add NODE to this DIGRAPH, taking ownership.  */
+
+template <typename GraphTraits>
+inline void
+digraph<GraphTraits>::add_node (node_t *node)
+{
+  m_nodes.safe_push (node);
+}
+
+/* Add EDGE to this digraph, and to the preds/succs of its endpoints.
+   Take ownership of EDGE.  */
+
+template <typename GraphTraits>
+inline void
+digraph<GraphTraits>::add_edge (edge_t *edge)
+{
+  m_edges.safe_push (edge);
+  edge->m_dest->m_preds.safe_push (edge);
+  edge->m_src->m_succs.safe_push (edge);
+
+}
+
+#endif /* GCC_ANALYZER_DIGRAPH_H */
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..c878d7b
--- /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 "analyzer/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/impl-2.cc b/gcc/analyzer/impl-2.cc
new file mode 100644
index 0000000..c6656ee
--- /dev/null
+++ b/gcc/analyzer/impl-2.cc
@@ -0,0 +1,3931 @@
+/* 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/supergraph.h"
+#include "analyzer/sm.h"
+#include "analyzer/analyzer-logging.h"
+#include "stringpool.h"
+#include "selftest.h"
+#include "fold-const.h"
+#include "analyzer/tristate.h"
+#include "analyzer/pending-diagnostic.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 (false));
+    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 (false);
+    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 (bool) 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 (bool) 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 (bool) 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 (bool) 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_state_change
+		  (evdesc::state_change (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 (bool) const FINAL OVERRIDE
+  {
+    switch (m_edge->m_kind)
+      {
+      default:
+	gcc_unreachable ();
+      case SUPEREDGE_CFG_EDGE:
+	{
+	  bool user_facing = !flag_analyzer_verbose_edges;
+	  char *edge_desc = m_edge->get_description (user_facing);
+	  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 (bool) 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 (bool) 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 (false));
+	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 (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 (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 "analyzer/path-state.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)
+    : sm_context (e->m_sm),
+      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:
+    psn::equiv_class_id ec_id = m_ps->get_or_add_equiv_class (path_var (var, 0));
+    psn::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:
+	    psn::equiv_class_id rhs1_ec_id = ps->get_or_add_equiv_class (path_var (rhs1, 0));
+	    psn::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;
+  unsigned m_node_idx;
+  unsigned 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:
+	psn::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:
+      psn::equiv_class_id ec_id
+	= new_ps.get_or_add_equiv_class (path_var (vs->m_var, 0));
+      psn::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, NULL);
+		      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,
+									 NULL);
+	    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 < (signed)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 (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, NULL);
+		  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, NULL);
+		  if (callee_var)
+		    var = callee_var;
+		}
+		break;
+	      case SUPEREDGE_INTRAPROCEDURAL_CALL:
+		break;
+	      }
+	  }
+	  break;
+
+	case EK_CALL_SUMMARY:
+	  {
+#if 0
+	    call_summary *summary_event = (call_summary *)base_event;
+#endif
+#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 < (signed)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 (unsigned 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:
+	  {
+#if 0
+	    statement_event *stmt_ev = (statement_event *)base_event;
+#endif
+	    //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 (evdesc::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);
+      supergraph::dump_args_t args ((enum supergraph_dot_flags)0, &a);
+      sg.dump_dot (filename, args);
+      free (filename);
+    }
+
+  if (1)
+    {
+      engine_annotator_2 a (e);
+      char *filename
+	= concat (dump_base_name, ".", sm.get_name (), ".2.dot", NULL);
+      supergraph::dump_args_t args ((enum supergraph_dot_flags)0, &a);
+      sg.dump_dot (filename, args);
+      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);
+	supergraph::dump_args_t args ((enum supergraph_dot_flags)0, NULL);
+	sg.dump_dot (filename, args);
+	free (filename);
+      }
+
+#if 0
+    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");
+      state_machine *sm = make_malloc_state_machine (the_logger.get_logger ());
+      run_one_checker (sg, *sm, the_logger.get_logger ());
+      delete sm;
+    }
+
+    {
+      auto_client_timevar tv ("fileptr_state_machine");
+      state_machine *sm
+	= make_fileptr_state_machine (the_logger.get_logger ());
+      run_one_checker (sg, *sm, the_logger.get_logger ());
+      delete sm;
+    }
+
+    {
+      auto_client_timevar tv ("taint_state_machine");
+      state_machine *sm
+	= make_taint_state_machine (the_logger.get_logger ());
+      run_one_checker (sg, *sm, the_logger.get_logger ());
+      delete sm;
+    }
+
+    {
+      auto_client_timevar tv ("sensitive_state_machine");
+      state_machine *sm
+	= make_sensitive_state_machine (the_logger.get_logger ());
+      run_one_checker (sg, *sm, the_logger.get_logger ());
+      delete sm;
+    }
+
+    /* 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");
+	state_machine *sm
+	  = make_pattern_test_state_machine (the_logger.get_logger ());
+	run_one_checker (sg, *sm, the_logger.get_logger ());
+	delete sm;
+      }
+
+    /* 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_impl_2_cc_tests ()
+{
+  test_set_of_vars ();
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
diff --git a/gcc/analyzer/impl-3.cc b/gcc/analyzer/impl-3.cc
new file mode 100644
index 0000000..fbd7ad5
--- /dev/null
+++ b/gcc/analyzer/impl-3.cc
@@ -0,0 +1,1658 @@
+/* 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 "cgraph.h"
+#include "diagnostic-path.h"
+#include "gcc-rich-location.h"
+#include "tree-dfa.h"
+#include <string>
+#include "stringpool.h"
+#include "fold-const.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/graphviz.h"
+#include "analyzer/supergraph.h"
+#include "analyzer/sm.h"
+#include "analyzer/analyzer-logging.h"
+#include "analyzer/tristate.h"
+#include "analyzer/pending-diagnostic.h"
+#include "analyzer/path-state.h"
+#include "selftest.h"
+
+using namespace psn;
+
+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 psn::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 DEBUG_FUNCTION 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 (false));
+    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 (false);
+    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 (bool) 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 (bool) 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 (bool) 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 (bool) 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_state_change
+		  (evdesc::state_change (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 (bool) const FINAL OVERRIDE
+  {
+    switch (m_edge->m_kind)
+      {
+      default:
+	gcc_unreachable ();
+      case SUPEREDGE_CFG_EDGE:
+	{
+	  bool user_facing = !flag_analyzer_verbose_edges;
+	  char *edge_desc = m_edge->get_description (user_facing);
+	  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 (bool) 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);
+
+  psn::equiv_class &get_ec (tree expr)
+  {
+    path_var pv (expr, get_stack_depth ());
+    psn::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 (evdesc::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)
+  : sm_context (e->m_sm),
+    m_engine (e), m_path (path)
+  {
+    reset_baseline_states ();
+  }
+
+  ~engine_sm_context ()
+  {
+    reset_baseline_states ();
+  }
+
+  void reset_baseline_states ()
+  {
+    int i;
+    psn::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 (false));
+      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)
+{
+  psn::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)
+{
+  psn::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 < (signed)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 (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, phi, 0, (dump_flags_t)0);
+				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, NULL);
+		  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, NULL);
+		  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 < (signed)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);
+	supergraph::dump_args_t args ((enum supergraph_dot_flags)0, NULL);
+	sg.dump_dot (filename, args);
+	free (filename);
+      }
+
+#if 0
+    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");
+      state_machine *sm = make_malloc_state_machine (the_logger.get_logger ());
+      impl_3::run_one_checker (sg, *sm, the_logger.get_logger ());
+      delete sm;
+    }
+
+    {
+      auto_client_timevar tv ("fileptr_state_machine");
+      state_machine *sm = make_fileptr_state_machine (the_logger.get_logger ());
+      impl_3::run_one_checker (sg, *sm, the_logger.get_logger ());
+      delete sm;
+    }
+
+    {
+      auto_client_timevar tv ("taint_state_machine");
+      state_machine *sm = make_taint_state_machine (the_logger.get_logger ());
+      impl_3::run_one_checker (sg, *sm, the_logger.get_logger ());
+      delete sm;
+    }
+
+    {
+      auto_client_timevar tv ("sensitive_state_machine");
+      state_machine *sm
+	= make_sensitive_state_machine (the_logger.get_logger ());
+      impl_3::run_one_checker (sg, *sm, the_logger.get_logger ());
+      delete sm;
+    }
+
+    /* 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");
+	state_machine *sm
+	  = make_pattern_test_state_machine (the_logger.get_logger ());
+	impl_3::run_one_checker (sg, *sm, the_logger.get_logger ());
+	delete sm;
+      }
+#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..6874d99
--- /dev/null
+++ b/gcc/analyzer/impl-4.cc
@@ -0,0 +1,7893 @@
+/* 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/>.  */
+/* 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/analyzer.h"
+#include "analyzer/graphviz.h"
+#include "cgraph.h"
+#include "diagnostic-path.h"
+#include "gcc-rich-location.h"
+#include "tree-dfa.h"
+#include <string>
+#include "analyzer/supergraph.h"
+#include "analyzer/sm.h"
+#include "analyzer/analyzer-logging.h"
+#include "stringpool.h"
+#include "selftest.h"
+#include "fold-const.h"
+#include "analyzer/tristate.h"
+#include "analyzer/digraph.h"
+#include "analyzer/path-state.h"
+#include "tree-ssa-operands.h"
+#include "tree-vrp.h"
+#include "gimple-ssa.h"
+#include "tree-ssanames.h"
+#include "tree-phinodes.h"
+#include "ssa-iterators.h"
+#include "analyzer/region-model.h"
+#include "tree-diagnostic.h"
+#include "analyzer/constraint-manager.h"
+#include "analyzer/pending-diagnostic.h"
+#include "fibonacci_heap.h"
+#include "params.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.
+
+   There's a tension between:
+   * precision of analysis in the straight-line case, vs
+   * exponential blow-up in the face of control flow.
+
+   For example, in general, given this CFG:
+
+      A
+     / \
+    B   C
+     \ /
+      D
+     / \
+    E   F
+     \ /
+      G
+
+   we want to avoid differences in state-tracking in B and C from
+   leading to blow-up.  If we don't prevent state blowup, we end up
+   with exponential growth of the exploded graph like this:
+
+           1:A
+          /   \
+         /     \
+        /       \
+      2:B       3:C
+       |         |
+      4:D       5:D        (2 exploded nodes for D)
+     /   \     /   \
+   6:E   7:F 8:E   9:F
+    |     |   |     |
+   10:G 11:G 12:G  13:G    (4 exploded nodes for G)
+
+   Similar issues arise with loops.
+
+   To prevent this, we follow various approaches:
+
+   (a) state pruning: which tries to discard state that won't be relevant
+   later on withing the function.
+
+   (b) state merging.  We can try to find the commonality between two
+   program_state instances to make a third, simpler program_state.
+   We have two strategies here:
+     (i) the worklist keeps new nodes for the same program_point together,
+     and tries to merge them before processing, and thus before they have
+     successors.  Hence, in the above, the two nodes for D (4 and 5) reach
+     the front of the worklist together, and we create a node for D with
+     the merger of the incoming states.
+     (ii) try merging with the state of existing enodes for the program_point
+     (which may have already been explored).  There will be duplication,
+     but only one set of duplication; subsequent duplicates are more likely
+     to hit the cache.  In particular, (hopefully) all merger chains are
+     finite, and so we guarantee termination.
+     This is intended to help with loops: we ought to explore the first
+     iteration, and then have a "subsequent iterations" exploration,
+     which uses a state merged from that of the first, to be more abstract.
+
+   We avoid merging pairs of states that have state-machine differences,
+   as these are the kinds of differences that are likely to be most
+   interesting.
+
+   TODO: State merging does lose us the "all paths in the EG are feasible"
+   guarantee.
+
+   TODO: What about infinite recursion?  */
+
+namespace impl_4 {
+
+class exploded_graph;
+class exploded_node;
+class exploded_edge;
+class exploded_cluster;
+class exploded_path;
+class checker4_path;
+class stmt_finder;
+class state_purge_per_ssa_name;
+class shortest_paths;
+
+static int readability_comparator (const void *p1, const void *p2);
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+char *print (bool can_colorize, const char *fmt, ...)
+{
+  /* FIXME: all this cloning is likely to be expensive.  */
+  pretty_printer *pp = global_dc->printer->clone ();
+  pp_clear_output_area (pp);
+
+  if (!can_colorize)
+    pp_show_color (pp) = false;
+
+  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);
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+class format
+{
+public:
+  format (bool newlines) : m_newlines (newlines) {}
+
+  void spacer (pretty_printer *pp) const
+  {
+    if (m_newlines)
+      pp_newline (pp);
+    else
+      pp_space (pp);
+  }
+
+  bool m_newlines;
+};
+
+///////////////////////////////////////////////////////////////////////////
+
+/* 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 (); }
+
+  int calc_recursion_depth () const;
+
+  static int cmp (const call_string &a,
+		  const call_string &b);
+
+  unsigned length () const { return m_return_edges.length (); }
+  const return_superedge *operator[] (unsigned idx) const
+  {
+    return m_return_edges[idx];
+  }
+
+private:
+  static int cmp_1 (const call_string &a,
+		    const call_string &b);
+
+  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_SUPERNODE,
+
+  /* Special values used for hash_map:  */
+  PK_EMPTY,
+  PK_DELETED,
+
+  NUM_POINT_KINDS
+};
+
+/* Get a string for PK.  */
+
+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_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 format &f) const;
+  void print_source_line (pretty_printer *pp) const;
+  std::string to_str (const format &f) 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;
+
+  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_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 (exploded_graph &eg,
+		const superedge *succ);
+
+  static int cmp_within_supernode_1 (const program_point &point_a,
+				     const program_point &point_b);
+  static int cmp_within_supernode (const program_point &point_a,
+				   const program_point &point_b);
+
+//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;
+};
+
+} // namespace impl_4
+
+using impl_4::pod_hash_traits;
+using impl_4::program_point;
+
+template <> struct default_hash_traits<program_point>
+: public pod_hash_traits<program_point>
+{
+};
+
+template <>
+inline hashval_t
+pod_hash_traits<program_point>::hash (value_type v)
+{
+  return v.hash ();
+}
+
+template <>
+inline bool
+pod_hash_traits<program_point>::equal (const value_type &existing,
+                                 const value_type &candidate)
+{
+  return existing == candidate;
+}
+template <>
+inline void
+pod_hash_traits<program_point>::mark_deleted (value_type &v)
+{
+  v = program_point::deleted ();
+}
+template <>
+inline void
+pod_hash_traits<program_point>::mark_empty (value_type &v)
+{
+  v = program_point::empty ();
+}
+template <>
+inline bool
+pod_hash_traits<program_point>::is_deleted (value_type v)
+{
+  return v.m_kind == PK_DELETED;
+}
+template <>
+inline bool
+pod_hash_traits<program_point>::is_empty (value_type v)
+{
+  return v.m_kind == PK_EMPTY;
+}
+
+namespace impl_4 {
+
+////////////////////////////////////////////////////////////////////////////
+
+/* The result of analyzing which SSA names can be purged from state at
+   different points in the program, so that we can simplify program_state
+   objects, in the hope of reducing state-blowup.  */
+
+class state_purge_map : public log_user
+{
+public:
+  typedef ordered_hash_map<tree, state_purge_per_ssa_name *> map_t;
+  typedef map_t::iterator iterator;
+
+  state_purge_map (const supergraph &sg, logger *logger);
+  ~state_purge_map ();
+
+  const state_purge_per_ssa_name &get_data_for_ssa_name (tree var) const
+  {
+    gcc_assert (TREE_CODE (var) == SSA_NAME);
+    state_purge_per_ssa_name **slot
+      = const_cast <map_t&> (m_map).get (var);
+    return **slot;
+  }
+
+  iterator begin () const { return m_map.begin (); }
+  iterator end () const { return m_map.end (); }
+
+  //private:
+  const supergraph &m_sg;
+  map_t m_map;
+};
+
+/* The part of a state_purge_map relating to a specific SSA name.
+
+   The result of analyzing a given SSA name, recording which
+   program_points need to retain state information about it to handle
+   their successor states, so that we can simplify program_state objects,
+   in the hope of reducing state-blowup.  */
+
+class state_purge_per_ssa_name
+{
+public:
+  state_purge_per_ssa_name (const state_purge_map &map,
+			    tree var,
+			    function *fun);
+
+  bool needed_at_point_p (const program_point &point) const;
+
+  /* FIXME: this stores points, but ignores interprocedural issues;
+     everything will be at depth 0.  */
+  /* Should we build a program_point graph first???  */
+
+private:
+  static program_point before_use_stmt (const state_purge_map &map,
+					const gimple *use_stmt);
+
+  void add_to_worklist (const program_point &point,
+			auto_vec<program_point *> *worklist,
+			logger *logger);
+
+  void process_point (const program_point &point,
+		      auto_vec<program_point *> *worklist,
+		      logger *logger);
+
+  typedef hash_set<program_point> point_set_t;
+  point_set_t m_points_needing_var;
+  tree m_var;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* 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 ();
+  }
+
+  unsigned get_num_checkers () const { return m_checkers.length (); }
+
+  /* The state machines.  */
+  auto_delete_vec <state_machine> &m_checkers;
+};
+
+} // namespace impl_4
+
+using impl_4::pod_hash_traits;
+
+template <> struct default_hash_traits<svalue_id>
+: public pod_hash_traits<svalue_id>
+{
+};
+
+template <>
+inline hashval_t
+pod_hash_traits<svalue_id>::hash (value_type v)
+{
+  return v.as_int ();
+}
+
+template <>
+inline bool
+pod_hash_traits<svalue_id>::equal (const value_type &existing,
+				   const value_type &candidate)
+{
+  return existing == candidate;
+}
+template <>
+inline void
+pod_hash_traits<svalue_id>::mark_deleted (value_type &v)
+{
+  v = svalue_id::from_int (-2);
+}
+template <>
+inline void
+pod_hash_traits<svalue_id>::mark_empty (value_type &v)
+{
+  v = svalue_id::null ();
+}
+template <>
+inline bool
+pod_hash_traits<svalue_id>::is_deleted (value_type v)
+{
+  return v.as_int () == -2;
+}
+template <>
+inline bool
+pod_hash_traits<svalue_id>::is_empty (value_type v)
+{
+  return v.null_p ();
+}
+
+namespace impl_4 {
+
+/* Map from svalue_id to state machine state, also capturing the origin of
+   each state.  */
+
+class sm_state_map
+{
+public:
+  /* An entry in the hash_map.  */
+  struct entry_t
+  {
+    /* Default ctor needed by hash_map::empty.  */
+    entry_t ()
+    : m_state (0), m_origin (svalue_id::null ())
+    {
+    }
+
+    entry_t (state_machine::state_t state,
+	     svalue_id 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;
+    svalue_id m_origin;
+  };
+  typedef hash_map <svalue_id, entry_t> map_t;
+  typedef typename map_t::iterator iterator_t;
+
+  sm_state_map *clone () const;
+
+  sm_state_map *
+  clone_with_remapping (const one_way_svalue_id_map &id_map) const;
+
+  void print (const state_machine &sm, pretty_printer *pp) const;
+  void dump (const state_machine &sm) 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 (svalue_id sid) const;
+  svalue_id get_origin (svalue_id sid) const;
+
+  void set_state (region_model *model,
+		  svalue_id sid,
+		  state_machine::state_t state,
+		  svalue_id origin);
+  void set_state (const equiv_class &ec,
+		  state_machine::state_t state,
+		  svalue_id origin);
+  void impl_set_state (svalue_id sid,
+		       state_machine::state_t state,
+		       svalue_id origin);
+
+  void purge_for_unknown_fncall (const exploded_graph &eg,
+				 const state_machine &sm,
+				 const gcall *call, tree fndecl,
+				 region_model *new_model);
+
+  void remap_svalue_ids (const svalue_id_map &map);
+
+  int on_svalue_purge (const state_machine &sm,
+		       int sm_idx,
+		       svalue_id first_unused_sid,
+		       const svalue_id_map &map,
+		       region_model_context *ctxt);
+
+  void on_inherited_svalue (svalue_id parent_sid,
+			    svalue_id child_sid);
+
+  void validate (const state_machine &sm, int num_svalues) const;
+
+  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) a region_model, giving:
+      (a.1) a hierarchy of memory regions
+      (a.2) values for the regions
+      (a.3) inequalities between values
+   (b) sm_state_maps per state machine, giving a sparse mapping of
+       values to states.  */
+
+class program_state
+{
+public:
+  program_state (const extrinsic_state &ext_state);
+  program_state (const program_state &other);
+  program_state& operator= (const program_state &other);
+
+#if __cplusplus >= 201103
+  program_state (program_state &&other);
+  program_state& operator= (program_state &&other); // doesn't seem to be used
+#endif
+
+  ~program_state ();
+
+  hashval_t hash () const;
+  bool operator== (const program_state &other) const;
+  bool operator!= (const program_state &other) const
+  {
+    return !(*this == other);
+  }
+
+  void print (const extrinsic_state &ext_state,
+	      pretty_printer *pp) const;
+  std::string to_str (const extrinsic_state &ext_state) const;
+
+  void dump_to_pp (const extrinsic_state &ext_state,
+		   pretty_printer *pp) const;
+  void dump_to_file (const extrinsic_state &ext_state, FILE *outf) const;
+  void dump (const extrinsic_state &ext_state) const;
+
+  bool on_edge (exploded_graph &eg,
+		const exploded_node &enode,
+		const superedge *succ);
+
+  program_state prune_for_point (exploded_graph &eg,
+				 const program_point &point) const;
+
+  void remap_svalue_ids (const svalue_id_map &map);
+
+#if 1
+  tree get_representative_tree (svalue_id sid) const;
+#endif
+
+  /* FIXME: does this ever get called?  */
+  bool can_purge_p (const extrinsic_state &ext_state,
+		    svalue_id sid)
+  {
+    /* Don't purge vars that have non-purgeable sm state, to avoid
+       generating false "leak" complaints.  */
+    int i;
+    sm_state_map *smap;
+    FOR_EACH_VEC_ELT (m_checker_states, i, smap)
+      {
+	const state_machine &sm = ext_state.get_sm (i);
+	if (!sm.can_purge_p (smap->get_state (sid)))
+	  return false;
+      }
+    return true;
+  }
+
+  bool can_merge_with_p (const program_state &other,
+			 const extrinsic_state &ext_state,
+			 program_state *out) const;
+
+  void validate (const extrinsic_state &ext_state) const;
+
+  //private:
+  // FIXME: lose the pointer here (but there are const-correctness issues):
+  region_model *m_region_model;
+
+  auto_delete_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),
+    m_hash (m_point.hash () ^ m_state.hash ())
+  {
+  }
+
+  hashval_t hash () const
+  {
+    return m_hash;
+  }
+  bool operator== (const point_and_state &other) const
+  {
+    return m_point == other.m_point && m_state == other.m_state;
+  }
+
+  void set_state (const program_state &state)
+  {
+    m_state = state;
+    m_hash = m_point.hash () ^ m_state.hash ();
+  }
+
+  program_point m_point;
+  program_state m_state;
+  hashval_t m_hash;
+};
+
+/* A traits class for exploded graphs and their nodes and edges.  */
+
+struct eg_traits
+{
+  typedef exploded_node node_t;
+  typedef exploded_edge edge_t;
+  typedef exploded_graph graph_t;
+  struct dump_args_t {};
+  typedef exploded_cluster cluster_t;
+};
+
+/* 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<eg_traits>
+{
+ 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)
+  {
+  }
+
+  hashval_t hash () const { return m_ps.hash (); }
+
+  void dump_dot (graphviz_out *gv, const dump_args_t &args)
+    const FINAL OVERRIDE;
+  void dump_dot_id (pretty_printer *pp) const;
+
+  void dump_to_pp (pretty_printer *pp) const;
+  void dump (FILE *fp) const;
+  void dump () const;
+
+  program_state on_stmt (exploded_graph &eg) const;
+  bool on_edge (exploded_graph &eg,
+		const superedge *succ,
+		program_point *next_point,
+		program_state *next_state) const;
+
+  void detect_leaks (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 gimple *get_stmt () const { return get_point ().get_stmt (); }
+
+  const program_state &get_state () const { return m_ps.m_state; }
+
+  const point_and_state *get_ps_key () const { return &m_ps; }
+  const program_point *get_point_key () const { return &m_ps.m_point; }
+
+  void dump_succs_and_preds (FILE *outf) const;
+
+private:
+  const char * get_dot_fillcolor () const;
+
+  /* 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;
+};
+
+/* An edge within the exploded graph.
+   Some exploded_edges have an underlying superedge; others don't.  */
+
+class exploded_edge : public dedge<eg_traits>
+{
+ public:
+  exploded_edge (exploded_node *src, exploded_node *dest,
+		 const superedge *sedge);
+  void dump_dot (graphviz_out *gv, const dump_args_t &args)
+    const FINAL OVERRIDE;
+
+  //private:
+  const superedge *const m_sedge;
+};
+
+/* Statistics about aspects of an exploded_graph.  */
+
+struct stats
+{
+  stats (int num_supernodes);
+  void log (logger *logger) const;
+  void dump (FILE *out) const;
+
+  int m_num_nodes[NUM_POINT_KINDS];
+  int m_node_reuse_count;
+  int m_node_reuse_after_merge_count;
+  int m_num_supernodes;
+};
+
+/* Traits class for ensuring uniqueness of point_and_state data within
+   an exploded_graph.  */
+
+struct eg_hash_map_traits
+{
+  typedef const point_and_state *key_type;
+  typedef exploded_node *value_type;
+  typedef exploded_node *compare_type;
+
+  static inline hashval_t hash (const key_type &k)
+  {
+    gcc_assert (k != NULL);
+    gcc_assert (k != reinterpret_cast<key_type> (1));
+    return k->hash ();
+  }
+  static inline bool equal_keys (const key_type &k1, const key_type &k2)
+  {
+    gcc_assert (k1 != NULL);
+    gcc_assert (k2 != NULL);
+    gcc_assert (k1 != reinterpret_cast<key_type> (1));
+    gcc_assert (k2 != reinterpret_cast<key_type> (1));
+    if (k1 && k2)
+      return *k1 == *k2;
+    else
+      /* Otherwise they must both be non-NULL.  */
+      return k1 == k2;
+  }
+  template <typename T>
+  static inline void remove (T &)
+  {
+    /* empty; the nodes are handled elsewhere.  */
+  }
+  template <typename T>
+  static inline void mark_deleted (T &entry)
+  {
+    entry.m_key = reinterpret_cast<key_type> (1);
+  }
+  template <typename T>
+  static inline void mark_empty (T &entry)
+  {
+    entry.m_key = NULL;
+  }
+  template <typename T>
+  static inline bool is_deleted (const T &entry)
+  {
+    return entry.m_key == reinterpret_cast<key_type> (1);
+  }
+  template <typename T>
+  static inline bool is_empty (const T &entry)
+  {
+    return entry.m_key == NULL;
+  }
+};
+
+/* FIXME.  */
+
+struct per_program_point_data
+{
+  auto_vec<exploded_node *> m_enodes;
+};
+
+/* Traits class for storing per-program_point data within
+   an exploded_graph.  */
+
+struct eg_point_hash_map_traits
+{
+  typedef const program_point *key_type;
+  typedef per_program_point_data *value_type;
+  typedef per_program_point_data *compare_type;
+
+  static inline hashval_t hash (const key_type &k)
+  {
+    gcc_assert (k != NULL);
+    gcc_assert (k != reinterpret_cast<key_type> (1));
+    return k->hash ();
+  }
+  static inline bool equal_keys (const key_type &k1, const key_type &k2)
+  {
+    gcc_assert (k1 != NULL);
+    gcc_assert (k2 != NULL);
+    gcc_assert (k1 != reinterpret_cast<key_type> (1));
+    gcc_assert (k2 != reinterpret_cast<key_type> (1));
+    if (k1 && k2)
+      return *k1 == *k2;
+    else
+      /* Otherwise they must both be non-NULL.  */
+      return k1 == k2;
+  }
+  template <typename T>
+  static inline void remove (T &)
+  {
+    /* empty; the nodes are handled elsewhere.  */
+  }
+  template <typename T>
+  static inline void mark_deleted (T &entry)
+  {
+    entry.m_key = reinterpret_cast<key_type> (1);
+  }
+  template <typename T>
+  static inline void mark_empty (T &entry)
+  {
+    entry.m_key = NULL;
+  }
+  template <typename T>
+  static inline bool is_deleted (const T &entry)
+  {
+    return entry.m_key == reinterpret_cast<key_type> (1);
+  }
+  template <typename T>
+  static inline bool is_empty (const T &entry)
+  {
+    return entry.m_key == NULL;
+  }
+};
+
+/* A to-be-emitted diagnostic stored within diagnostic_manager.  */
+
+class saved_diagnostic
+{
+public:
+  saved_diagnostic (const state_machine *sm,
+		    const exploded_node *enode,
+		    const supernode *snode, const gimple *stmt,
+		    stmt_finder *stmt_finder,
+		    tree var, state_machine::state_t state,
+		    pending_diagnostic *d);
+  ~saved_diagnostic ();
+
+  bool operator== (const saved_diagnostic &other) const
+  {
+    return (m_sm == other.m_sm
+	    /* We don't compare m_enode.  */
+	    && m_snode == other.m_snode
+	    && m_stmt == other.m_stmt
+	    /* We don't compare m_stmt_finder.  */
+	    && m_var == other.m_var
+	    && m_state == other.m_state
+	    && m_d->equal_p (*other.m_d));
+  }
+
+  //private:
+  const state_machine *m_sm;
+  const exploded_node *m_enode;
+  const supernode *m_snode;
+  const gimple *m_stmt;
+  stmt_finder *m_stmt_finder;
+  tree m_var;
+  state_machine::state_t m_state;
+  pending_diagnostic *m_d;
+};
+
+/* A class with responsibility for saving pending diagnostics, so that
+   they can be emitted after the exploded_graph is complete.
+   This lets us de-duplicate diagnostics, and find the shortest path
+   for each similar diagnostic, potentially using edges that might
+   not have been found when each diagnostic was first saved.
+
+   This also lets us compute shortest_paths once, rather than
+   per-diagnostic.  */
+
+class diagnostic_manager : public log_user
+{
+public:
+  diagnostic_manager (logger *logger, int verbosity);
+
+  void add_diagnostic (const state_machine *sm,
+		       const exploded_node *enode,
+		       const supernode *snode, const gimple *stmt,
+		       stmt_finder *finder,
+		       tree var, state_machine::state_t state,
+		       pending_diagnostic *d);
+
+  void add_diagnostic (const exploded_node *enode,
+		       const supernode *snode, const gimple *stmt,
+		       stmt_finder *finder,
+		       pending_diagnostic *d);
+
+  void emit_saved_diagnostics (const exploded_graph &eg);
+
+  void emit_saved_diagnostic (const exploded_graph &eg,
+			      const saved_diagnostic &sd,
+			      const exploded_path &epath,
+			      const gimple *stmt,
+			      int num_dupes);
+
+private:
+  void build_emission_path (const exploded_graph &eg,
+			    const exploded_path &epath,
+			    checker4_path *emission_path) const;
+
+  void add_events_for_eedge (const exploded_edge &eedge,
+			     const extrinsic_state &ext_state,
+			     checker4_path *emission_path) const;
+
+  void add_events_for_superedge (const exploded_edge &eedge,
+				 checker4_path *emission_path) const;
+
+  void add_state_change_events (const exploded_edge &eedge,
+				const state_machine &sm,
+				const sm_state_map &src_smap,
+				const sm_state_map &dst_smap,
+				checker4_path *emission_path) const;
+
+  void prune_path (checker4_path *path,
+		   const state_machine *sm,
+		   tree var, state_machine::state_t state) const;
+
+  auto_delete_vec<saved_diagnostic> m_saved_diagnostics;
+  const int m_verbosity;
+};
+
+/* The strongly connected components of a supergraph.
+   In particular, this allows us to compute a partial ordering
+   of supernodes.  */
+
+class strongly_connected_components
+{
+public:
+  strongly_connected_components (const supergraph &sg, logger *logger);
+
+  int get_scc_id (int node_index) const
+  {
+    return m_per_node[node_index].m_lowlink;
+  }
+
+  void dump () const;
+
+private:
+  struct per_node_data
+  {
+    per_node_data ()
+      : m_index (-1), m_lowlink (-1), m_on_stack (false)
+    {}
+
+    int m_index;
+    int m_lowlink;
+    bool m_on_stack;
+  };
+
+  void strong_connect (unsigned index);
+
+  const supergraph &m_sg;
+  auto_vec<unsigned> m_stack;
+  auto_vec<per_node_data> m_per_node;
+};
+
+/* The worklist of exploded_node instances that have been added to
+   an exploded_graph, but that haven't yet been processed to find
+   their successors (or warnings).
+
+   The enodes are stored in a priority queue, ordered by a topological
+   sort of the SCCs in the supergraph, so that enodes for the same
+   program_point should appear at the front of the queue together.
+   This allows for state-merging at CFG join-points, so that
+   sufficiently-similar enodes can be merged into one.  */
+
+class worklist
+{
+public:
+  worklist (const exploded_graph &eg);
+  unsigned length () const;
+  exploded_node *take_next ();
+  exploded_node *peek_next ();
+  void add_node (exploded_node *enode);
+
+private:
+  class key_t
+  {
+  public:
+    key_t (const worklist &w, exploded_node *enode)
+    : m_worklist (w), m_enode (enode)
+    {}
+
+    bool operator< (const key_t &other) const
+    {
+      return cmp (*this, other) < 0;
+    }
+
+    bool operator== (const key_t &other) const
+    {
+      return cmp (*this, other) == 0;
+    }
+
+    bool operator> (const key_t &other) const
+    {
+      return !(*this == other || *this < other);
+    }
+
+  private:
+    static int cmp_1 (const key_t &ka, const key_t &kb);
+    static int cmp (const key_t &ka, const key_t &kb);
+
+    int get_scc_id (const exploded_node *enode) const
+    {
+      const supernode *snode = enode->get_supernode ();
+      if (snode == NULL)
+	return 0;
+      return m_worklist.m_scc.get_scc_id (snode->m_index);
+    }
+
+    const worklist &m_worklist;
+    exploded_node *m_enode;
+  };
+
+  /* The order is important here: m_scc needs to stick around
+     until after m_queue has finished being cleaned up (the dtor
+     calls the ordering fns).  */
+  const exploded_graph &m_eg;
+  strongly_connected_components m_scc;
+
+  /* Priority queue, backed by a fibonacci_heap.  */
+  typedef fibonacci_heap<key_t, exploded_node> queue_t;
+  queue_t m_queue;
+};
+
+/* 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<eg_traits>, public log_user
+{
+public:
+  exploded_graph (const supergraph &sg, logger *logger,
+		  const extrinsic_state &ext_state,
+		  const state_purge_map *purge_map,
+		  int verbosity);
+  ~exploded_graph ();
+
+  const supergraph &get_supergraph () const { return m_sg; }
+  const extrinsic_state &get_ext_state () const { return m_ext_state; }
+  const state_purge_map *get_purge_map () const { return m_purge_map; }
+
+  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 save_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);
+
+  diagnostic_manager &get_diagnostic_manager ()
+  {
+    return m_diagnostic_manager;
+  }
+
+  stats *get_global_stats () { return &m_global_stats; }
+  stats *get_or_create_function_stats (function *fn);
+  void log_stats () const;
+  void dump_stats (FILE *) const;
+  void dump_states_for_supernode (FILE *, const supernode *snode) const;
+  void dump_exploded_nodes () const;
+
+private:
+  const supergraph &m_sg;
+
+  /* Map from point/state to exploded node.
+     To avoid duplication we store point_and_state
+     *pointers* as keys, rather than point_and_state, using the
+     instance from within the exploded_node, with a custom hasher.  */
+  typedef hash_map <const point_and_state *, exploded_node *,
+		    eg_hash_map_traits> map_t;
+  map_t m_point_and_state_to_node;
+
+  /* Map from program_point to the first exploded_node at
+     that point.
+     FIXME: generalize
+     FIXME: why?*/
+  typedef hash_map <const program_point *, per_program_point_data *,
+		    eg_point_hash_map_traits> point_map_t;
+  point_map_t m_per_point_data;
+
+  worklist m_worklist;
+
+  exploded_node *m_origin;
+
+  const extrinsic_state &m_ext_state;
+
+  const state_purge_map *const m_purge_map;
+
+  diagnostic_manager m_diagnostic_manager;
+
+  /* Stats.  */
+  stats m_global_stats;
+  typedef ordered_hash_map<function *, stats *> function_stat_map_t;
+  function_stat_map_t m_per_function_stats;
+  stats m_functionless_stats;
+  auto_vec<int> m_PK_AFTER_SUPERNODE_per_snode;
+};
+
+/* 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);
+
+  unsigned length () const { return m_edges.length (); }
+
+  bool find_stmt_backwards (const gimple *search_stmt,
+			    int *out_idx) const;
+
+  exploded_node *get_final_enode () const;
+
+  void dump_to_pp (pretty_printer *pp) const;
+  void dump (FILE *fp) const;
+  void dump () const;
+
+  bool feasible_p (logger *logger) 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 ~stmt_finder () {}
+  virtual stmt_finder *clone () const = 0;
+  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 DEBUG_FUNCTION 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 (false));
+    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 (false);
+    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 (bool) 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,
+		   const program_state &dst_state)
+  : checker4_event (EK_STMT, gimple_location (stmt), fndecl, depth),
+    m_stmt (stmt),
+    m_dst_state (dst_state)
+  {
+  }
+
+  label_text get_desc (bool) 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, m_dst_state);
+  }
+
+  const gimple *m_stmt;
+  program_state m_dst_state;
+};
+
+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 (bool can_colorize) const FINAL OVERRIDE
+  {
+    return label_text::take (print (can_colorize, "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,
+		      const program_state &dst_state)
+  : 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),
+    m_dst_state (dst_state)
+  {
+  }
+
+  label_text get_desc (bool can_colorize) const FINAL OVERRIDE
+  {
+    if (m_origin)
+      return label_text::take
+	(print (can_colorize,
+		"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_state_change
+		  (evdesc::state_change (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 (can_colorize,
+			      "%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 (can_colorize,
+		  "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,
+				   m_dst_state);
+  }
+
+  region_id get_lvalue (tree expr) const
+  {
+    return m_dst_state.m_region_model->get_lvalue (expr, NULL);
+  }
+
+  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;
+  program_state m_dst_state;
+};
+
+class edge_event : public checker4_event
+{
+public:
+  edge_event (const exploded_edge &eedge,
+	      location_t loc, tree fndecl, int depth)
+  : checker4_event (EK_EDGE, loc, fndecl, depth),
+    m_eedge (eedge), m_sedge (eedge.m_sedge),
+    m_var (NULL_TREE), m_critical_state (0)
+  {
+  }
+
+  /* Mark this edge event as being either an interprocedural call or
+     return in which VAR is in STATE, and that this is critical to the
+     diagnostic (so that get_desc can attempt to get a better description
+     from any pending_diagnostic).  */
+  void record_critical_state (tree var, state_machine::state_t state)
+  {
+    m_var = var;
+    m_critical_state = state;
+  }
+
+  label_text get_desc (bool can_colorize) const FINAL OVERRIDE
+  {
+    switch (m_sedge->m_kind)
+      {
+      default:
+	gcc_unreachable ();
+      case SUPEREDGE_CFG_EDGE:
+	{
+	  bool user_facing = !flag_analyzer_verbose_edges;
+	  char *edge_desc = m_sedge->get_description (user_facing);
+	  if (user_facing)
+	    {
+	      if (edge_desc && strlen (edge_desc) > 0)
+		{
+		  label_text result
+		    = label_text::take (print (can_colorize,
+					       "following %qs branch",
+					       edge_desc));
+		  free (edge_desc);
+		  return result;
+		}
+	      else
+		{
+		  free (edge_desc);
+		  return label_text::borrow ("");
+		}
+	    }
+	  else
+	    {
+	      if (strlen (edge_desc) > 0)
+		{
+		  label_text result
+		    = label_text::take (print (can_colorize,
+					       "taking %qs edge SN:%i -> SN:%i",
+					       edge_desc,
+					       m_sedge->m_src->m_index,
+					       m_sedge->m_dest->m_index));
+		  free (edge_desc);
+		  return result;
+		}
+	      else
+		{
+		  free (edge_desc);
+		  return label_text::take (print (can_colorize,
+						  "taking edge SN:%i -> SN:%i",
+						  m_sedge->m_src->m_index,
+						  m_sedge->m_dest->m_index));
+		}
+	    }
+	}
+      case SUPEREDGE_CALL:
+	{
+	  if (m_critical_state && m_pending_diagnostic)
+	    {
+	      gcc_assert (m_var);
+	      label_text custom_desc
+		= m_pending_diagnostic->describe_call_with_state
+		    (evdesc::call_with_state (m_sedge->m_src->m_fun->decl,
+					      m_sedge->m_dest->m_fun->decl,
+					      m_var,
+					      m_critical_state));
+	      if (custom_desc.m_buffer)
+		return custom_desc;
+	    }
+
+	  return label_text::take (print (can_colorize,
+					  "calling %qE from %qE",
+					  m_sedge->m_dest->m_fun->decl,
+					  m_sedge->m_src->m_fun->decl));
+	}
+
+      case SUPEREDGE_RETURN:
+	{
+	  /*  For greatest precision-of-wording, if this is returning the
+	      state involved in the pending diagnostic, give the pending
+	      diagnostic a chance to describe this return (in terms of
+	      itself).  */
+	  if (m_critical_state && m_pending_diagnostic)
+	    {
+	      label_text custom_desc
+		= m_pending_diagnostic->describe_return_of_state
+		    (evdesc::return_of_state (m_sedge->m_dest->m_fun->decl,
+					      m_sedge->m_src->m_fun->decl,
+					      m_critical_state));
+	      if (custom_desc.m_buffer)
+		return custom_desc;
+	    }
+	  return label_text::take (print (can_colorize,
+					  "returning to %qE from %qE",
+					  m_sedge->m_dest->m_fun->decl,
+					  m_sedge->m_src->m_fun->decl));
+	}
+
+      case SUPEREDGE_INTRAPROCEDURAL_CALL:
+	return label_text::take (// TODO: possibly a higher-level description?
+				 print (can_colorize,
+					"taking edge SN:%i -> SN:%i",
+					m_sedge->m_src->m_index,
+					m_sedge->m_dest->m_index));
+      }
+  }
+
+  const cfg_superedge& get_cfg_superedge () const
+  {
+    gcc_assert (m_sedge->m_kind == SUPEREDGE_CFG_EDGE);
+    return *m_sedge->dyn_cast_cfg_superedge ();
+  }
+
+  const callgraph_superedge& get_callgraph_superedge () const
+  {
+    gcc_assert (m_sedge->m_kind != SUPEREDGE_CFG_EDGE);
+    return *m_sedge->dyn_cast_callgraph_superedge ();
+  }
+
+  checker4_event *clone () const FINAL OVERRIDE
+  {
+    return new edge_event (m_eedge, m_loc, m_fndecl, m_depth);
+  }
+
+  bool is_call_p () const FINAL OVERRIDE
+  {
+    return m_sedge->m_kind == SUPEREDGE_CALL;
+  }
+  bool is_return_p () const FINAL OVERRIDE
+  {
+    return m_sedge->m_kind == SUPEREDGE_RETURN;
+  }
+
+  bool should_filter_p (int verbosity) const
+  {
+    switch (m_sedge->m_kind)
+      {
+      case SUPEREDGE_CFG_EDGE:
+	{
+	  if (verbosity < 2)
+	    return true;
+
+	  if (verbosity == 2)
+	    {
+	      /* Filter events with empty descriptions.  This ought to filter
+		 FALLTHRU, but retain true/false/switch edges.  */
+	      label_text desc = get_desc (false);
+	      gcc_assert (desc.m_buffer);
+	      if (desc.m_buffer[0] == '\0')
+		return true;
+	      desc.maybe_free ();
+	    }
+	}
+	break;
+
+	default:
+	  break;
+      }
+    return false;
+  }
+
+  const exploded_edge &m_eedge;
+  const superedge *m_sedge;
+  tree m_var;
+  state_machine::state_t m_critical_state;
+};
+
+/* 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 state_machine *sm,
+		 tree var, state_machine::state_t state)
+  : checker4_event (EK_WARNING, loc, fndecl, depth),
+    m_sm (sm), m_var (var), m_state (state)
+  {
+  }
+
+  label_text get_desc (bool can_colorize) const FINAL OVERRIDE
+  {
+    if (m_pending_diagnostic)
+      {
+	label_text ev_desc = m_pending_diagnostic->describe_final_event
+	  (evdesc::final_event (m_var, m_state));
+	if (ev_desc.m_buffer)
+	  {
+	    if (m_sm && flag_analyzer_verbose_state_changes)
+	      {
+		label_text result = label_text::take
+		  (print (can_colorize,
+			  "%s (%qE is in state %qs)",
+			  ev_desc.m_buffer,
+			  m_var, m_sm->get_state_name (m_state)));
+		ev_desc.maybe_free ();
+		return result;
+	      }
+	    else
+	      return ev_desc;
+	  }
+      }
+
+    if (m_sm)
+      return label_text::take
+	(print (can_colorize,
+		"here (%qE is in state %qs)",
+		m_var,
+		m_sm->get_state_name (m_state)));
+    else
+      return label_text::borrow ("here");
+  }
+
+  warning_event *clone () const FINAL OVERRIDE
+  {
+    return new warning_event (m_loc, m_fndecl, m_depth, m_sm, m_var, m_state);
+  }
+
+private:
+  const state_machine *m_sm;
+  tree m_var;
+  state_machine::state_t m_state;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* 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);
+
+  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)
+  {
+    checker4_event *end_of_path
+      = new warning_event (stmt->location,
+			   enode->get_function ()->decl,
+			   enode->get_stack_depth (),
+			   sm, var, state);
+    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)
+: m_return_edges (other.m_return_edges.length ())
+{
+  const return_superedge *e;
+  int i;
+  FOR_EACH_VEC_ELT (other.m_return_edges, i, e)
+    m_return_edges.quick_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);
+  m_return_edges.reserve (other.m_return_edges.length (), true);
+  const return_superedge *e;
+  int i;
+  FOR_EACH_VEC_ELT (other.m_return_edges, i, e)
+    m_return_edges.quick_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;
+}
+
+/* Comparator for call strings.
+   Return negative if A is before B.
+   Return positive if B is after A.
+   Return 0 if they are equal.  */
+
+int
+call_string::cmp (const call_string &a,
+		  const call_string &b)
+{
+  int result = cmp_1 (a, b);
+
+  /* Check that the ordering is symmetric  */
+#if CHECKING_P
+  int reversed = cmp_1 (b, a);
+  gcc_assert (reversed == -result);
+#endif
+
+  /* We should only have 0 for equal pairs.  */
+  gcc_assert (result != 0
+	      || a == b);
+
+  return result;
+}
+
+/* Implementation of call_string::cmp.
+   This implements a version of lexicographical order.  */
+
+int
+call_string::cmp_1 (const call_string &a,
+		    const call_string &b)
+{
+  unsigned len_a = a.length ();
+  unsigned len_b = b.length ();
+
+  unsigned i = 0;
+  while (1)
+    {
+      /* Consider index i; the strings have been equal up to it.  */
+
+      /* Have both strings run out?  */
+      if (i >= len_a && i >= len_b)
+	return 0;
+
+      /* Otherwise, has just one of the strings run out?  */
+      if (i >= len_a)
+	return 1;
+      if (i >= len_b)
+	return -1;
+
+      /* Otherwise, compare the edges.  */
+      const return_superedge *edge_a = a[i];
+      const return_superedge *edge_b = b[i];
+      int src_cmp = edge_a->m_src->m_index - edge_b->m_src->m_index;
+      if (src_cmp)
+	return src_cmp;
+      int dest_cmp = edge_a->m_dest->m_index - edge_b->m_dest->m_index;
+      if (dest_cmp)
+	return dest_cmp;
+      i++;
+      // TODO: test coverage for this
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+void
+program_point::print (pretty_printer *pp, const format &f) const
+{
+  pp_string (pp, "callstring: ");
+  m_call_string.print (pp);
+  f.spacer (pp);
+
+  switch (m_kind)
+    {
+    default:
+      gcc_unreachable ();
+
+    case PK_ORIGIN:
+      pp_printf (pp, "origin");
+      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);
+	f.spacer (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);
+	  }
+      }
+      break;
+
+    case PK_BEFORE_STMT:
+      pp_printf (pp, "before (SN: %i stmt: %i): ", m_supernode->m_index,
+		 m_stmt_idx);
+      f.spacer (pp);
+      pp_gimple_stmt_1 (pp, const_cast <gimple *> (get_stmt ()),
+			0, (dump_flags_t)0);
+      if (f.m_newlines)
+	print_source_line (pp);
+      break;
+
+    case PK_AFTER_SUPERNODE:
+      pp_printf (pp, "after SN: %i", m_supernode->m_index);
+      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;
+  }
+  ~debug_diagnostic_context ()
+  {
+    diagnostic_finish (this);
+  }
+};
+
+/* 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 format &f) const
+{
+  pretty_printer pp;
+  print (&pp, f);
+  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)
+    return m_supernode->m_stmts[m_stmt_idx];
+  else
+    return NULL;
+}
+
+/* Check to see if SUCC is a valid edge to take (ensuring that we have
+   interprocedurally valid paths in the exploded graph, and enforcing
+   recursion limits).
+
+   Return true if SUCC can be taken, or false otherwise.
+
+   This is the "point" half of exploded_node::on_edge.  */
+
+bool
+program_point::on_edge (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;
+}
+
+/* Comparator for program points within the same supernode,
+   for implementing worklist::key_t comparison operators.
+   Return negative if POINT_A is before POINT_B
+   Return positive if POINT_A is after POINT_B
+   Return 0 if they are equal.  */
+
+int
+program_point::cmp_within_supernode_1 (const program_point &point_a,
+				       const program_point &point_b)
+{
+  gcc_assert (point_a.m_supernode == point_b.m_supernode);
+
+  switch (point_a.m_kind)
+    {
+    default:
+      gcc_unreachable ();
+    case PK_BEFORE_SUPERNODE:
+      switch (point_b.m_kind)
+	{
+	default:
+	  gcc_unreachable ();
+	case PK_BEFORE_SUPERNODE:
+	  {
+	    int a_src_idx = -1;
+	    int b_src_idx = -1;
+	    if (point_a.m_from_edge)
+	      a_src_idx = point_a.m_from_edge->m_src->m_index;
+	    if (point_b.m_from_edge)
+	      b_src_idx = point_b.m_from_edge->m_src->m_index;
+	    return a_src_idx - b_src_idx;
+	  }
+	  break;
+
+	case PK_BEFORE_STMT:
+	case PK_AFTER_SUPERNODE:
+	  return -1;
+	}
+      break;
+    case PK_BEFORE_STMT:
+      switch (point_b.m_kind)
+	{
+	default:
+	  gcc_unreachable ();
+	case PK_BEFORE_SUPERNODE:
+	  return 1;
+
+	case PK_BEFORE_STMT:
+	  return point_a.m_stmt_idx - point_b.m_stmt_idx;
+
+	case PK_AFTER_SUPERNODE:
+	  return -1;
+	}
+      break;
+    case PK_AFTER_SUPERNODE:
+      switch (point_b.m_kind)
+	{
+	default:
+	  gcc_unreachable ();
+	case PK_BEFORE_SUPERNODE:
+	case PK_BEFORE_STMT:
+	  return 1;
+
+	case PK_AFTER_SUPERNODE:
+	  return 0;
+	}
+      break;
+    }
+}
+
+/* Comparator for program points within the same supernode,
+   for implementing worklist::key_t comparison operators.
+   Return negative if POINT_A is before POINT_B
+   Return positive if POINT_A is after POINT_B
+   Return 0 if they are equal.  */
+
+int
+program_point::cmp_within_supernode (const program_point &point_a,
+				     const program_point &point_b)
+{
+  int result = cmp_within_supernode_1 (point_a, point_b);
+
+  /* Check that the ordering is symmetric  */
+#if CHECKING_P
+  int reversed = cmp_within_supernode_1 (point_b, point_a);
+  gcc_assert (reversed == -result);
+#endif
+
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Clone the sm_state_map.  */
+
+sm_state_map *
+sm_state_map::clone () const
+{
+  return new sm_state_map (*this);
+}
+
+/* Clone this sm_state_map, remapping all svalue_ids within it with ID_MAP.  */
+
+sm_state_map *
+sm_state_map::clone_with_remapping (const one_way_svalue_id_map &id_map) const
+{
+  sm_state_map *result = new sm_state_map ();
+  for (typename map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      svalue_id sid = (*iter).first;
+      entry_t e = (*iter).second;
+      id_map.update (&e.m_origin);
+      result->m_map.put (id_map.get_dst_for_src (sid), e);
+    }
+  return result;
+}
+
+/* FIXME.  */
+
+void
+sm_state_map::print (const state_machine &sm, pretty_printer *pp) const
+{
+  pp_string (pp, "{");
+  for (typename map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      if (iter != m_map.begin ())
+	pp_string (pp, ", ");
+      svalue_id sid = (*iter).first;
+      sid.print (pp);
+
+      entry_t e = (*iter).second;
+      pp_printf (pp, ": %s (origin: %s)",
+		 sm.get_state_name (e.m_state),
+		 e.m_origin.to_str ().c_str ());
+    }
+  pp_string (pp, "}");
+}
+
+/* FIXME.  */
+
+DEBUG_FUNCTION void
+sm_state_map::dump (const state_machine &sm) const
+{
+  pretty_printer pp;
+  pp_show_color (&pp) = pp_show_color (global_dc->printer);
+  pp.buffer->stream = stderr;
+  print (sm, &pp);
+  pp_newline (&pp);
+  pp_flush (&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 (typename map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      inchash::add ((*iter).first, hstate);
+      entry_t e = (*iter).second;
+      hstate.add_int (e.m_state);
+      inchash::add (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 (typename map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      svalue_id sid = (*iter).first;
+      entry_t e = (*iter).second;
+      entry_t *other_slot = const_cast <map_t &> (other.m_map).get (sid);
+      if (other_slot == NULL)
+	return false;
+      if (e != *other_slot)
+	return false;
+    }
+
+  return true;
+}
+
+/* FIXME.  */
+state_machine::state_t
+sm_state_map::get_state (svalue_id sid) const
+{
+  if (entry_t *slot
+      = const_cast <map_t &> (m_map).get (sid))
+    return slot->m_state;
+  else
+    return 0;
+}
+
+/* FIXME.  */
+
+svalue_id
+sm_state_map::get_origin (svalue_id sid) const
+{
+  entry_t *slot
+    = const_cast <map_t &> (m_map).get (sid);
+  gcc_assert (slot);
+  return slot->m_origin;
+}
+
+/* FIXME.  */
+
+void
+sm_state_map::set_state (region_model *model,
+			 svalue_id sid,
+			 state_machine::state_t state,
+			 svalue_id origin)
+{
+  if (model == NULL)
+    return;
+  equiv_class &ec = model->get_constraints ()->get_equiv_class (sid);
+  set_state (ec, state, origin);
+}
+
+/* FIXME.  */
+
+void
+sm_state_map::set_state (const equiv_class &ec,
+			 state_machine::state_t state,
+			 svalue_id origin)
+{
+  int i;
+  svalue_id *sid;
+  FOR_EACH_VEC_ELT (ec.m_vars, i, sid)
+    impl_set_state (*sid, state, origin);
+}
+
+/* Set state of PV to STATE, bypassing equivalence classes.  */
+
+void
+sm_state_map::impl_set_state (svalue_id sid, state_machine::state_t state,
+			      svalue_id origin)
+{
+  /* Special-case state 0 as the default value.  */
+  if (state == 0)
+    {
+      if (m_map.get (sid))
+	m_map.remove (sid);
+      return;
+    }
+  m_map.put (sid, entry_t (state, origin));
+}
+
+/* Handle CALL to unknown FNDECL with an unknown function body, which
+   could do anything to the states passed to it.
+   Clear any state for SM for the params and any LHS.
+   Note that the function might be known to other state machines, but
+   not to this one.  */
+
+void
+sm_state_map::purge_for_unknown_fncall (const exploded_graph &eg,
+					const state_machine &sm,
+					const gcall *call,
+					tree fndecl,
+					region_model *new_model)
+{
+  if (fndecl)
+    eg.log ("function %qE is unknown to checker %qs",
+	    fndecl, sm.get_name ());
+  else
+    eg.log ("unknown function pointer for checker %qs",
+	    sm.get_name ());
+
+  /* Purge any state for parms.  */
+  tree iter_param_types = NULL_TREE;
+  if (fndecl)
+    iter_param_types = TYPE_ARG_TYPES (TREE_TYPE (fndecl));
+  for (unsigned arg_idx = 0; arg_idx < gimple_call_num_args (call); arg_idx++)
+    {
+      /* Track expected param type, where available.  */
+      if (iter_param_types)
+	{
+	  tree param_type = TREE_VALUE (iter_param_types);
+	  gcc_assert (param_type);
+	  iter_param_types = TREE_CHAIN (iter_param_types);
+
+	  /* Don't purge state if it was passed as a const pointer
+	     e.g. for things like strlen (PTR).  */
+	  if (TREE_CODE (param_type) == POINTER_TYPE)
+	    if (TYPE_READONLY (TREE_TYPE (param_type)))
+	      continue;
+	}
+      tree parm = gimple_call_arg (call, arg_idx);
+      svalue_id parm_sid = new_model->get_rvalue (parm, NULL); // FIXME: ctxt!
+      set_state (new_model, parm_sid, 0, svalue_id::null ());
+    }
+
+  /* Purge any state for any LHS.  */
+  if (tree lhs = gimple_call_lhs (call))
+    {
+      svalue_id lhs_sid = new_model->get_rvalue (lhs, NULL); // FIXME: ctxt!
+      set_state (new_model, lhs_sid, 0, svalue_id::null ());
+    }
+}
+
+/* FIXME.  */
+
+void
+sm_state_map::remap_svalue_ids (const svalue_id_map &map)
+{
+  map_t tmp_map;
+
+  /* Build an intermediate map, using the new sids.  */
+  for (typename map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      svalue_id sid = (*iter).first;
+      entry_t e = (*iter).second;
+
+      map.update (&sid);
+      map.update (&e.m_origin);
+      tmp_map.put (sid, e);
+    }
+
+  /* Clear the existing values.  */
+  m_map.empty ();
+
+  /* Copy over from intermediate map.  */
+  for (typename map_t::iterator iter = tmp_map.begin ();
+       iter != tmp_map.end ();
+       ++iter)
+    {
+      svalue_id sid = (*iter).first;
+      entry_t e = (*iter).second;
+
+      impl_set_state (sid, e.m_state, e.m_origin);
+    }
+}
+
+/* FIXME.  */
+
+int
+sm_state_map::on_svalue_purge (const state_machine &sm,
+			       int sm_idx,
+			       svalue_id first_unused_sid,
+			       const svalue_id_map &map,
+			       region_model_context *ctxt)
+{
+  /* FIXME: not sure how to safely remove during a traversal, so for now
+     do it in two stages.  */
+  auto_vec<svalue_id> to_remove;
+  for (typename map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      svalue_id sid ((*iter).first);
+      if (sid.as_int () >= first_unused_sid.as_int ())
+	{
+	  // TODO: complain about leaks here!
+	  entry_t e = (*iter).second;
+	  //gcc_unreachable ();
+
+	  if (!sm.can_purge_p (e.m_state))
+	    {
+	      //gcc_unreachable ();
+	      ctxt->on_state_leak (sm, sm_idx, sid, map, e.m_state);
+	    }
+
+#if 0
+	  // FIXME: we need a tree, an snode, a stmt, and an sm_ctxt
+	  sm.on_leak (sm_context *sm_ctxt,
+		      const supernode *node,
+		      const gimple *stmt,
+		      tree var,
+		      e.m_state);
+#endif
+
+	  to_remove.safe_push (sid);
+	}
+    }
+
+  int i;
+  svalue_id *sid;
+  FOR_EACH_VEC_ELT (to_remove, i, sid)
+    m_map.remove (*sid);
+
+  return to_remove.length ();
+}
+
+/* FIXME.  */
+
+void
+sm_state_map::on_inherited_svalue (svalue_id parent_sid,
+				   svalue_id child_sid)
+{
+  state_machine::state_t state = get_state (parent_sid);
+  impl_set_state (child_sid, state, parent_sid);
+}
+
+/* FIXME.  */
+
+void
+sm_state_map::validate (const state_machine &sm,
+			int num_svalues) const
+{
+  /* Skip this in a release build.  */
+#if !CHECKING_P
+  return;
+#endif
+
+  for (typename map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      svalue_id sid = (*iter).first;
+      entry_t e = (*iter).second;
+
+      gcc_assert (sid.as_int () < num_svalues);
+      sm.validate (e.m_state);
+      gcc_assert (e.m_origin.as_int () < num_svalues);
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+class impl_region_model_context : public region_model_context, public log_user
+{
+ public:
+  impl_region_model_context (exploded_graph &eg,
+			     const exploded_node *enode,
+			     /* FIXME: should we be getting the ECs from the
+				old state, rather than the new?  */
+			     program_state *new_state,
+
+			     stmt_finder *stmt_finder = NULL)
+  : log_user (eg.get_logger ()), m_eg (&eg), m_enode (enode),
+    m_new_state (new_state),
+    m_stmt_finder (stmt_finder),
+    m_ext_state (eg.get_ext_state ())
+  {
+  }
+
+  impl_region_model_context (program_state *state,
+			     const extrinsic_state &ext_state)
+  : log_user (NULL), m_eg (NULL), m_enode (NULL),
+    m_new_state (state),
+    m_stmt_finder (NULL),
+    m_ext_state (ext_state)
+  {
+  }
+
+  void warn (pending_diagnostic *d) FINAL OVERRIDE
+  {
+    JIT_LOG_FUNC (get_logger ());
+    if (m_eg)
+      m_eg->get_diagnostic_manager ().add_diagnostic
+	(m_enode, m_enode->get_supernode (),
+	 m_enode->get_stmt (),
+	 m_stmt_finder, d);
+  }
+
+  void remap_svalue_ids (const svalue_id_map &map) FINAL OVERRIDE
+  {
+    m_new_state->remap_svalue_ids (map);
+  }
+
+  /* FIXME: does this ever get called?  */
+  bool can_purge_p (svalue_id sid) FINAL OVERRIDE
+  {
+    /* Don't purge vars that have non-purgeable sm state, to avoid
+       generating false "leak" complaints.  */
+    int i;
+    sm_state_map *smap;
+    FOR_EACH_VEC_ELT (m_new_state->m_checker_states, i, smap)
+      {
+	const state_machine &sm = m_ext_state.get_sm (i);
+	if (!sm.can_purge_p (smap->get_state (sid)))
+	  return false;
+      }
+    return true;
+  }
+
+  int on_svalue_purge (svalue_id first_unused_sid,
+		       const svalue_id_map &map) FINAL OVERRIDE
+  {
+    int total = 0;
+    int sm_idx;
+    sm_state_map *smap;
+    FOR_EACH_VEC_ELT (m_new_state->m_checker_states, sm_idx, smap)
+      {
+	const state_machine &sm = m_ext_state.get_sm (sm_idx);
+	total += smap->on_svalue_purge (sm, sm_idx, first_unused_sid,
+					map, this);
+      }
+    return total;
+  }
+
+  logger *get_logger () FINAL OVERRIDE
+  {
+    return log_user::get_logger ();
+  }
+
+  void on_state_leak (const state_machine &sm,
+		      int sm_idx,
+		      svalue_id sid,
+		      const svalue_id_map &map,
+		      state_machine::state_t state) FINAL OVERRIDE;
+
+  void on_inherited_svalue (svalue_id parent_sid,
+			    svalue_id child_sid) FINAL OVERRIDE;
+
+  exploded_graph *m_eg;
+  const exploded_node *m_enode;
+  program_state *m_new_state;
+  impl_4::stmt_finder *m_stmt_finder;
+  const extrinsic_state &m_ext_state;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+class impl_sm_context : public sm_context, public log_user
+{
+public:
+  impl_sm_context (exploded_graph &eg,
+		   const state_machine &sm,
+		   const exploded_node *enode,
+
+		   /* 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)
+  : sm_context (sm),
+    log_user (eg.get_logger ()),
+    m_eg (eg), m_enode (enode),
+    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 ());
+    impl_region_model_context old_ctxt
+      (m_eg, m_enode, NULL/*m_enode->get_state ()*/, NULL);
+    svalue_id var_old_sid
+      = m_enode->get_state ().m_region_model->get_rvalue (var, &old_ctxt);
+
+    impl_region_model_context new_ctxt (m_eg, m_enode, m_new_state, NULL);
+    svalue_id var_new_sid
+      = m_new_state->m_region_model->get_rvalue (var, &new_ctxt);
+    svalue_id origin_new_sid
+      = m_new_state->m_region_model->get_rvalue (origin, &new_ctxt);
+
+    state_machine::state_t current = m_old_smap->get_state (var_old_sid);
+    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_region_model, var_new_sid,
+			       to, origin_new_sid);
+      }
+  }
+
+  void warn_for_state (const supernode *snode, const gimple *stmt,
+		       tree var, state_machine::state_t state,
+		       pending_diagnostic *d) FINAL OVERRIDE
+  {
+    JIT_LOG_FUNC (get_logger ());
+    gcc_assert (d); // take ownership
+
+    impl_region_model_context old_ctxt
+      (m_eg, m_enode, NULL/*m_enode->get_state ()*/, NULL);
+    svalue_id var_old_sid
+      = m_enode->get_state ().m_region_model->get_rvalue (var, &old_ctxt);
+    state_machine::state_t current = m_old_smap->get_state (var_old_sid);
+    if (state == current)
+      {
+	m_eg.get_diagnostic_manager ().add_diagnostic
+	  (&m_sm, m_enode, snode, stmt, m_stmt_finder,
+	   var, state, d);
+      }
+    else
+      delete d;
+  }
+
+  /* Hook for picking more readable trees for SSA names of temporaries,
+     so that rather than e.g.
+       "double-free of '<unknown>'"
+     we can print:
+       "double-free of 'inbuf.data'".  */
+
+  tree get_readable_tree (tree expr) FINAL OVERRIDE
+  {
+    /* Only for SSA_NAMEs of temporaries; otherwise, return EXPR, as it's
+       likely to be the least surprising tree to report.  */
+    if (TREE_CODE (expr) != SSA_NAME)
+      return expr;
+    if (SSA_NAME_VAR (expr) != NULL)
+      return expr;
+
+    gcc_assert (m_new_state);
+    svalue_id sid = m_new_state->m_region_model->get_rvalue (expr, NULL);
+    /* Find trees for all regions storing the value.  */
+    auto_vec<path_var> pvs;
+    m_new_state->m_region_model->get_path_vars_for_svalue (sid, &pvs);
+    if (pvs.length () < 1)
+      return expr;
+    /* Pick the "best" such tree.  */
+    // TODO: should we also consider (and consolidate) equiv classes?
+    pvs.qsort (readability_comparator);
+    return pvs[0].m_tree;
+  }
+
+  exploded_graph &m_eg;
+  const exploded_node *m_enode;
+  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;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* 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, tree var)
+  : m_eg (eg), m_var (var) {}
+
+  stmt_finder *clone () const FINAL OVERRIDE
+  {
+    return new leak_stmt_finder (m_eg, m_var);
+  }
+
+  const gimple *find_stmt (const exploded_path &epath)
+    FINAL OVERRIDE
+  {
+    JIT_LOG_FUNC (m_eg.get_logger ());
+
+    if (TREE_CODE (m_var) == SSA_NAME)
+      {
+	/* Locate the final write to this SSA name in the path.  */
+	const gimple *def_stmt = SSA_NAME_DEF_STMT (m_var);
+
+	int idx_of_def_stmt;
+	bool found = epath.find_stmt_backwards (def_stmt, &idx_of_def_stmt);
+#if 0
+	gcc_assert (found);
+#else
+	if (!found)
+	  goto not_found;
+#endif
+
+	/* What was the next write to the underlying var
+	   after the SSA name was set? (if any).  */
+
+	for (unsigned 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 (m_var))
+		  return assign;
+	      }
+	  }
+      }
+
+    /* FIXME: not sure why this is needed.  */
+  not_found:
+
+    /* 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;
+  tree m_var;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* 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 (const_tree expr)
+{
+  gcc_assert (expr);
+  switch (TREE_CODE (expr))
+    {
+    case COMPONENT_REF:
+    case MEM_REF:
+      /* Impose a slight readability penalty relative to that of
+	 operand 0.  */
+      return readability (TREE_OPERAND (expr, 0)) - 1;
+
+    case SSA_NAME:
+      {
+	if (tree var = SSA_NAME_VAR (expr))
+	  return readability (var);
+	/* Avoid printing '<unknown>' for SSA names for temporaries.  */
+	return -1;
+      }
+      break;
+
+    case VAR_DECL:
+      /* Arbitrarily-chosen "high readability" value.  */
+      return 256;
+
+    default:
+      return 0;
+    }
+
+  return 0;
+}
+
+/* A qsort comparator for trees to sort them into most user-readable to
+   least user-readable.  */
+
+static int
+readability_comparator (const void *p1, const void *p2)
+{
+  path_var pv1 = *(path_var const *)p1;
+  path_var pv2 = *(path_var const *)p2;
+
+  /* FIXME: should we consider stack depths?  */
+  int r1 = readability (pv1.m_tree);
+  int r2 = readability (pv2.m_tree);
+
+  return r2 - r1;
+}
+
+/* FIXME.  */
+
+void
+impl_region_model_context::on_state_leak (const state_machine &sm,
+					  int sm_idx,
+					  svalue_id sid,
+					  const svalue_id_map &map,
+					  state_machine::state_t state)
+{
+  if (!m_eg)
+    return;
+
+  /* FIXME: m_enode also needs to be non-NULL so warn_for_state can look up the
+     state of the sid.  */
+  gcc_assert (m_enode);
+
+  /* SID has leaked within the new state: no regions use it.
+     We need to convert it back to a tree, but since no regions use it, we
+     have to use MAP to convert it back to an svalue_id within the old state.
+     We can then look that svalue_id up to locate regions and thus tree(s)
+     that use it.  */
+
+  gcc_assert (m_enode);
+  const program_state &old_state = m_enode->get_state ();
+  svalue_id old_sid = map.get_src_for_dst (sid);
+
+  auto_vec<path_var> leaked_pvs;
+  old_state.m_region_model->get_path_vars_for_svalue (old_sid, &leaked_pvs);
+
+  // FIXME: find "best" leaked tree
+  // TODO: should we also consider (and consolidate) equiv classes?
+  // FIXME: rewrite this comment:
+  /* 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.  */
+  leaked_pvs.qsort (readability_comparator);
+
+  if (leaked_pvs.length () < 1)
+    return;
+
+  tree leaked_tree = leaked_pvs[0].m_tree;
+  if (get_logger ())
+    log ("best leaked_tree: %qE", leaked_tree);
+
+  leak_stmt_finder stmt_finder (*m_eg, leaked_tree);
+  impl_sm_context sm_ctxt (*m_eg, sm, m_enode, NULL,
+			   m_enode->get_state ().m_checker_states[sm_idx],
+			   m_new_state->m_checker_states[sm_idx],
+			   &stmt_finder);
+
+  sm.on_leak (&sm_ctxt, m_enode->get_supernode (), m_enode->get_stmt (),
+	      leaked_tree, state);
+}
+
+/* FIXME.  */
+
+void
+impl_region_model_context::on_inherited_svalue (svalue_id parent_sid,
+						svalue_id child_sid)
+{
+  /* FIXME: */
+  if (!m_new_state)
+    return;
+
+  int sm_idx;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (m_new_state->m_checker_states, sm_idx, smap)
+    {
+      const state_machine &sm = m_ext_state.get_sm (sm_idx);
+      if (sm.inherited_state_p ())
+	smap->on_inherited_svalue (parent_sid, child_sid);
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class program_state.  */
+
+/* program_state's ctor.  */
+
+program_state::program_state (const extrinsic_state &ext_state)
+: m_region_model (new region_model ()),
+  m_checker_states (ext_state.m_checkers.length ())
+{
+  int num_states = ext_state.m_checkers.length ();
+  for (int i = 0; i < num_states; i++)
+    m_checker_states.quick_push (new sm_state_map ());
+}
+
+/* program_state's copy ctor.  */
+
+program_state::program_state (const program_state &other)
+: m_region_model (new region_model (*other.m_region_model)),
+  m_checker_states (other.m_checker_states.length ())
+{
+  int i;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (other.m_checker_states, i, smap)
+    m_checker_states.quick_push (smap->clone ());
+}
+
+/* program_state's assignment operator.  */
+
+program_state&
+program_state::operator= (const program_state &other)
+{
+  delete m_region_model;
+  m_region_model = new region_model (*other.m_region_model);
+
+  int i;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (m_checker_states, i, smap)
+    delete smap;
+  m_checker_states.truncate (0);
+  gcc_assert (m_checker_states.space (other.m_checker_states.length ()));
+
+  FOR_EACH_VEC_ELT (other.m_checker_states, i, smap)
+    m_checker_states.quick_push (smap->clone ());
+
+  return *this;
+}
+
+#if __cplusplus >= 201103
+/* FIXME.  */
+program_state::program_state (program_state &&other)
+: m_region_model (other.m_region_model),
+  m_checker_states (other.m_checker_states.length ())
+{
+  other.m_region_model = NULL;
+
+  // FIXME:
+  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.quick_push (smap);
+  other.m_checker_states.truncate (0);
+}
+#endif
+
+/* program_state's dtor.  */
+
+program_state::~program_state ()
+{
+  delete m_region_model;
+}
+
+/* FIXME.  */
+
+hashval_t
+program_state::hash () const
+{
+  hashval_t result = m_region_model->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_region_model == *other.m_region_model))
+    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;
+}
+
+/* Print a compact representation of this state to PP.  */
+
+void
+program_state::print (const extrinsic_state &ext_state,
+		      pretty_printer *pp) const
+{
+  pp_printf (pp, "rmodel: ");
+  m_region_model->print (pp);
+  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;
+  pp_format_decoder (&pp) = default_tree_printer;
+  print (ext_state, &pp);
+  return std::string (pp_formatted_text (&pp));
+}
+
+/* Dump a multiline representation of this state to PP.  */
+
+void
+program_state::dump_to_pp (const extrinsic_state &ext_state,
+			   pretty_printer *pp) const
+{
+  pp_printf (pp, "rmodel: ");
+  m_region_model->dump_to_pp (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);
+	}
+    }
+}
+
+/* Dump a multiline representation of this state to OUTF.  */
+
+void
+program_state::dump_to_file (const extrinsic_state &ext_state,
+			     FILE *outf) const
+{
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  if (outf == stderr)
+    pp_show_color (&pp) = pp_show_color (global_dc->printer);
+  pp.buffer->stream = outf;
+  dump_to_pp (ext_state, &pp);
+  pp_flush (&pp);
+}
+
+/* Dump a multiline representation of this state to stderr.  */
+
+DEBUG_FUNCTION void
+program_state::dump (const extrinsic_state &ext_state) const
+{
+  dump_to_file (ext_state, stderr);
+}
+
+/* Determine if following edge SUCC from ENODE is valid within the graph EG
+   and update this state accordingly in-place.
+
+   Return true if the edge can be followed, or false otherwise.
+
+   Check for relevant conditionals and switch-values for conditionals
+   and switch statements, adding the relevant conditions to this state.
+   Push/pop frames for interprocedural edges and update params/returned
+   values.
+
+   This is the "state" half of exploded_node::on_edge.  */
+
+bool
+program_state::on_edge (exploded_graph &eg,
+			const exploded_node &enode,
+			const superedge *succ)
+{
+  /* Update state.  */
+
+  if (succ->m_kind == SUPEREDGE_INTRAPROCEDURAL_CALL)
+    {
+      /* We ignore these edges.  FIXME: why have them?  */
+      eg.log ("rejecting interprocedural edge");
+      return false;
+    }
+
+  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.
+     This also updates frame information for call/return superedges.  */
+
+  impl_region_model_context ctxt (eg, &enode, this, NULL);
+  if (!m_region_model->maybe_update_for_edge (*succ,
+					      last_stmt,
+					      &ctxt))
+    {
+      eg.log ("edge to SN: %i is impossible due to region_model constraints",
+	      succ->m_dest->m_index);
+      return false;
+    }
+
+  /* Pass the edge to the various state machines to see if any transitions
+     or warnings occur (e.g. transitions due to ptrs becoming known to be NULL
+     or non-NULL).  */
+  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);
+    }
+
+  return true;
+}
+
+/* Implementation of purge_criteria for using state_purge_map.
+
+   Purge state within a path_state using PURGE_MAP for POINT.
+   Use the sm-states within OLD_STATE to avoid purging path_vars
+   that have any sm-state.  */
+
+class purge_at_point_criteria : public psn::purge_criteria
+{
+public:
+  purge_at_point_criteria (const program_point &point,
+			   const state_purge_map *purge_map,
+			   const extrinsic_state &ext_state,
+			   const program_state &old_state)
+  : m_point (point),
+    m_flat_point (point),
+    m_purge_map (purge_map),
+    m_ext_state (ext_state),
+    m_old_state (old_state)
+  {
+    m_flat_point.m_call_string = call_string ();
+  }
+
+  bool should_purge_p (path_var pv) const FINAL OVERRIDE
+  {
+    if (TREE_CODE (pv.m_tree) != SSA_NAME)
+      return false;
+    if (pv.m_stack_depth != m_point.get_stack_depth ())
+      return false;
+
+    if (!m_purge_map)
+      return false;
+
+    const state_purge_per_ssa_name &per_var_data
+      = m_purge_map->get_data_for_ssa_name (pv.m_tree);
+    if (!per_var_data.needed_at_point_p (m_flat_point))
+      {
+	/* Don't purge vars that have sm state, to avoid
+	   losing leak complaints.  */
+	int i;
+	sm_state_map *smap;
+	FOR_EACH_VEC_ELT (m_old_state.m_checker_states, i, smap)
+	  {
+	    // FIXME:
+#if 0
+	    const state_machine &sm = m_ext_state.get_sm (i);
+	    if (!sm.can_purge_p (smap->get_state (pv)))
+	      return false;
+#endif
+	  }
+
+	return true;
+      }
+    return false;
+  }
+
+private:
+  const program_point &m_point;
+  program_point m_flat_point;
+  const state_purge_map *m_purge_map;
+  const extrinsic_state &m_ext_state;
+  const program_state &m_old_state;
+};
+
+/* 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 (exploded_graph &eg,
+				const program_point &point) const
+{
+  function *fun = point.get_function ();
+  if (!fun)
+    return *this;
+
+  /* The points in the state map don't have call strings, so flatten
+     accordingly.  */
+  program_point flat_point (point);
+  flat_point.m_call_string = call_string ();
+
+  program_state new_state (*this);
+
+  purge_at_point_criteria criteria (point, eg.get_purge_map (),
+				    eg.get_ext_state (),
+				    *this);
+
+  purge_stats stats;
+
+  const state_purge_map *pm = eg.get_purge_map ();
+  if (pm)
+    {
+      region_id_set purgeable_ssa_regions (new_state.m_region_model);
+      region_id frame_rid
+	= new_state.m_region_model->get_current_frame_id ();
+      frame_region *frame
+	= new_state.m_region_model->get_region <frame_region>(frame_rid);
+
+      /* FIXME: move to a member of region_model, once the state_purge_map
+	 etc decls are visible there.  */
+
+      auto_vec<tree> ssa_names_to_purge;
+      for (frame_region::map_t::iterator iter = frame->begin ();
+	   iter != frame->end ();
+	   ++iter)
+	{
+	  tree var = (*iter).first;
+	  region_id rid = (*iter).second;
+	  if (TREE_CODE (var) == SSA_NAME)
+	    {
+	      const state_purge_per_ssa_name &per_ssa
+		= pm->get_data_for_ssa_name (var);
+	      if (!per_ssa.needed_at_point_p (flat_point))
+		{
+		  region *region
+		    = new_state.m_region_model->get_region (rid);
+		  svalue_id sid = region->get_value_direct ();
+		  if (!sid.null_p ())
+		    if (!new_state.can_purge_p (eg.get_ext_state (), sid))
+		      {
+			/* (currently only state maps can keep things
+			   alive).  */
+			eg.log ("not purging RID: %i for %qE"
+				" (used by state map)",
+				rid.as_int (), var);
+			continue;
+		      }
+		  purgeable_ssa_regions.add_region (rid);
+		  ssa_names_to_purge.safe_push (var);
+		  eg.log ("purging RID: %i for %qE", rid.as_int (), var);
+		  // FIXME: we also need to remove the region from the map
+		  // note that we're mid-traversal: this is done in unbind
+		  // below
+		}
+	    }
+	}
+
+      /* Unbind the regions from the frame's map of vars-to-regions.  */
+      unsigned i;
+      tree var;
+      FOR_EACH_VEC_ELT (ssa_names_to_purge, i, var)
+	frame->unbind (var);
+
+      /* Purge the regions.  Nothing should point to them, and they
+	 should have no children, as they are for SSA names.  */
+      new_state.m_region_model->purge_regions (purgeable_ssa_regions,
+					       &stats,
+					       eg.get_logger ());
+    }
+
+  /* Purge unused svalues.  */
+  // FIXME: which enode to use, if any?
+  impl_region_model_context ctxt (eg, NULL /* FIXME: old_state*/,
+				  &new_state, NULL);
+  new_state.m_region_model->purge_unused_svalues (&stats, &ctxt);
+  eg.log ("num svalues purged: %i", stats.m_num_svalues);
+  eg.log ("num regions purged: %i", stats.m_num_regions);
+  eg.log ("num equiv_classes purged: %i", stats.m_num_equiv_classes);
+  eg.log ("num constraints purged: %i", stats.m_num_constraints);
+  eg.log ("num sm map items purged: %i", stats.m_num_client_items);
+
+  new_state.m_region_model->canonicalize (&ctxt);
+
+  return new_state;
+}
+
+/* FIXME.  */
+
+void
+program_state::remap_svalue_ids (const svalue_id_map &map)
+{
+  int i;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (m_checker_states, i, smap)
+    smap->remap_svalue_ids (map);
+}
+
+/* FIXME.  */
+
+tree
+program_state::get_representative_tree (svalue_id sid) const
+{
+  return m_region_model->get_representative_tree (sid);
+}
+
+/* Attempt to merge this state with OTHER, both using EXT_STATE.
+   Write the result to *OUT.
+   If the states were merged successfully, return true.  */
+
+bool
+program_state::can_merge_with_p (const program_state &other,
+				 const extrinsic_state &ext_state,
+				 program_state *out) const
+{
+  gcc_assert (out);
+
+  /* FIXME:  initially I had an early reject here if there
+     are sm-differences between the states.  However, this was
+     falsely rejecting merger opportunities for states where the
+     only difference was in svalue_id ordering.  */
+  int i;
+  sm_state_map *smap;
+#if 0
+  FOR_EACH_VEC_ELT (m_checker_states, i, smap)
+    if (!(*smap == *other.m_checker_states[i]))
+      return false;
+#endif
+
+  svalue_id_merger_mapping sid_mapping (*m_region_model,
+					*other.m_region_model);
+  if (!m_region_model->can_merge_with_p (*other.m_region_model,
+					 out->m_region_model,
+					 &sid_mapping))
+    return false;
+
+  /* Copy m_checker_states to result, remapping svalue_ids using
+     sid_mapping.  */
+  FOR_EACH_VEC_ELT (out->m_checker_states, i, smap)
+    delete smap;
+  out->m_checker_states.truncate (0);
+
+  /* Remap this and other's m_checker_states using sid_mapping.
+     Only merge states that have equality between the two end-results:
+     sm-state differences are likely to be interesting to end-users, and
+     hence are worth exploring as separate paths in the exploded graph.  */
+  FOR_EACH_VEC_ELT (m_checker_states, i, smap)
+    {
+      sm_state_map *other_smap = other.m_checker_states[i];
+      sm_state_map *remapped_a_smap
+	= smap->clone_with_remapping (sid_mapping.m_map_from_a_to_m);
+      sm_state_map *remapped_b_smap
+	= other_smap->clone_with_remapping (sid_mapping.m_map_from_b_to_m);
+      if (*remapped_a_smap == *remapped_b_smap)
+	{
+	  out->m_checker_states.safe_push (remapped_a_smap);
+	  delete remapped_b_smap;
+	}
+      else
+	{
+	  /* Don't merge if there are sm-state differences.  */
+	  delete remapped_a_smap;
+	  delete remapped_b_smap;
+	  return false;
+	}
+    }
+
+  impl_region_model_context ctxt (out, ext_state);
+  out->m_region_model->canonicalize (&ctxt);
+
+  return true;
+}
+
+/* Assert that this object is valid.  */
+
+void
+program_state::validate (const extrinsic_state &ext_state) const
+{
+  /* Skip this in a release build.  */
+#if !CHECKING_P
+  return;
+#endif
+
+  m_region_model->validate ();
+  gcc_assert (m_checker_states.length () == ext_state.get_num_checkers ());
+  int sm_idx;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (m_checker_states, sm_idx, smap)
+    {
+      const state_machine &sm = ext_state.get_sm (sm_idx);
+      smap->validate (sm, m_region_model->get_num_svalues ());
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+static void
+print_run (pretty_printer *pp, int start_idx, int end_idx,
+	   bool *first_run)
+{
+  if (!(*first_run))
+    pp_string (pp, ", ");
+  *first_run = false;
+  if (start_idx == end_idx)
+    pp_printf (pp, "EN: %i", start_idx);
+  else
+    pp_printf (pp, "EN: %i-%i", start_idx, end_idx);
+}
+
+/* Print the indices within ENODES to PP, collecting them as
+   runs/singletons e.g. "EN: 4-7, EN: 20-23, EN: 42".  */
+
+static void
+print_enode_indices (pretty_printer *pp,
+		     const auto_vec<exploded_node *> &enodes)
+{
+  int cur_start_idx = -1;
+  int cur_finish_idx = -1;
+  bool first_run = true;
+  unsigned i;
+  exploded_node *enode;
+  FOR_EACH_VEC_ELT (enodes, i, enode)
+    {
+      if (cur_start_idx == -1)
+	{
+	  gcc_assert (cur_finish_idx == -1);
+	  cur_start_idx = cur_finish_idx = enode->m_index;
+	}
+      else
+	{
+	  if (enode->m_index == cur_finish_idx + 1)
+	    /* Continuation of a run.  */
+	    cur_finish_idx = enode->m_index;
+	  else
+	    {
+	      /* Finish existing run, start a new one.  */
+	      gcc_assert (cur_start_idx >= 0);
+	      gcc_assert (cur_finish_idx >= 0);
+	      print_run (pp, cur_start_idx, cur_finish_idx,
+			 &first_run);
+	      cur_start_idx = cur_finish_idx = enode->m_index;
+	    }
+	}
+    }
+  /* Finish any existing run.  */
+  if (cur_start_idx >= 0)
+    {
+      gcc_assert (cur_finish_idx >= 0);
+      print_run (pp, cur_start_idx, cur_finish_idx,
+		 &first_run);
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* For use by dump_dot, get a value for the .dot "fillcolor" attribute.
+   Colorize by sm-state, to make it easier to see how sm-state propagates
+   through the exploded_graph.  */
+
+const char *
+exploded_node::get_dot_fillcolor () const
+{
+  const program_state &state = get_state ();
+
+  /* We want to be able to easily distinguish the no-sm-state case,
+     and to be able to distinguish cases where there's a single state
+     from each other.
+
+     Sum the sm_states, and use the result to choose from a table,
+     modulo table-size, special-casing the "no sm-state" case.   */
+  int total_sm_state = 0;
+  int i;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (state.m_checker_states, i, smap)
+    for (sm_state_map::iterator_t iter = smap->begin ();
+	 iter != smap->end ();
+	 ++iter)
+      total_sm_state += (*iter).second.m_state;
+
+  if (total_sm_state > 0)
+    {
+      /* An arbitrarily-picked collection of light colors.  */
+      const char * const colors[]
+	= {"azure", "coral", "cornsilk", "lightblue", "yellow"};
+      const int num_colors = sizeof (colors) / sizeof (colors[0]);
+      return colors[total_sm_state % num_colors];
+    }
+  else
+    /* No sm-state.   */
+    return "lightgrey";
+}
+
+/* FIXME.  */
+
+void
+exploded_node::dump_dot (graphviz_out *gv, const dump_args_t &) const
+{
+  pretty_printer *pp = gv->m_pp;
+
+  dump_dot_id (pp);
+  pp_printf (pp, " [shape=%s,style=filled,fillcolor=%s,label=\"",
+	     "record",
+	     get_dot_fillcolor ());
+  pp_left_brace (pp);
+  pp_write_text_to_stream (pp);
+
+  pp_printf (pp, "EN: %i", m_index);
+  pp_newline (pp);
+
+  format f (true);
+  m_ps.m_point.print (pp, f);
+  pp_newline (pp);
+
+#if 0
+  m_ps.m_state.dump_to_pp (m_ext_state, pp);
+  pp_newline (pp);
+#endif
+
+#if 1
+  // FIXME: copied-and-pasted from program_state::dump_dot
+  {
+  int i;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (m_ps.m_state.m_checker_states, i, smap)
+    {
+      if (!smap->is_empty_p ())
+	{
+	  pp_printf (pp, "%s: ", m_ext_state.get_name (i));
+	  smap->print (m_ext_state.get_sm (i), pp);
+	  pp_newline (pp);
+	}
+    }
+}
+#endif
+
+  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);
+}
+
+/* Dump a multiline representation of this node to PP.  */
+
+void
+exploded_node::dump_to_pp (pretty_printer *pp) const
+{
+  pp_printf (pp, "EN: %i", m_index);
+  pp_newline (pp);
+
+  format f (true);
+  m_ps.m_point.print (pp, f);
+  pp_newline (pp);
+
+  m_ps.m_state.dump_to_pp (m_ext_state, pp);
+  pp_newline (pp);
+}
+
+/* Dump a multiline representation of this node to FILE.  */
+
+void
+exploded_node::dump (FILE *fp) const
+{
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  pp_show_color (&pp) = pp_show_color (global_dc->printer);
+  pp.buffer->stream = fp;
+  dump_to_pp (&pp);
+  pp_flush (&pp);
+}
+
+/* Dump a multiline representation of this node to stderr.  */
+
+DEBUG_FUNCTION void
+exploded_node::dump () const
+{
+  dump (stderr);
+}
+
+/* FIXME.  */
+// is there a pre-canned way to do this?
+
+static bool
+fndecl_has_gimple_body_p (tree fndecl)
+{
+  if (fndecl == NULL_TREE)
+    return false;
+
+  cgraph_node *n = cgraph_node::get (fndecl);
+  if (!n)
+    return false;
+
+  return n->has_gimple_body_p ();
+}
+
+/* FIXME.  */
+
+program_state
+exploded_node::on_stmt (exploded_graph &eg) const
+{
+  const program_point &point = m_ps.m_point;
+  const program_state &old_state = m_ps.m_state;
+
+  program_state new_state (old_state);
+
+  const gimple *stmt = point.get_stmt ();
+
+  /* Update cfun and input_location in case of ICE: make it easier to
+     track down which source construct we're failing to handle.  */
+  auto_cfun sentinel (point.get_function ());
+  input_location = stmt->location;
+
+  impl_region_model_context ctxt (eg, this, &new_state, NULL);
+
+  if (const gassign *assign = dyn_cast <const gassign *> (stmt))
+    new_state.m_region_model->on_assignment (assign, &ctxt);
+
+  if (const greturn *return_ = dyn_cast <const greturn *> (stmt))
+    new_state.m_region_model->on_return (return_, &ctxt);
+
+  if (const gcall *call = dyn_cast <const gcall *> (stmt))
+    {
+      /* Debugging/test support.  */
+      if (is_named_call_p (call, "__dump_constraints", 0))
+	{
+	  // FIXME: port to region model?
+#if 0
+	  inform (stmt->location, "__dump_constraints: %s",
+		  new_ps->to_str ().c_str ());
+#endif
+	}
+      else if (is_named_call_p (call, "__analyzer_dump", 0))
+	{
+	  /* Handle the builtin "__analyzer_dump" by dumping state
+	     to stderr.  */
+	  dump ();
+	}
+      else if (is_named_call_p (call, "__analyzer_dump_region_model", 0))
+	{
+	  /* Handle the builtin "__analyzer_dump_region_model" by dumping
+	     the region model's state to stderr.  */
+	  new_state.m_region_model->dump ();
+	}
+      else if (is_named_call_p (call, "__analyzer_eval", 1))
+	{
+	  /* 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).  */
+	  tree t_arg = gimple_call_arg (call, 0);
+	  tristate t
+	    = new_state.m_region_model->eval_condition (t_arg,
+							NE_EXPR,
+							integer_zero_node,
+							&ctxt);
+	  warning_at (call->location, 0, "%s", t.as_string ());
+	}
+      else if (is_named_call_p (call, "__analyzer_break", 0))
+	{
+	  raise (SIGINT); // FIXME: cross-platform way to do this?
+	  //gcc_unreachable (); // FIXME: compare to a hard breakpoint
+	}
+      new_state.m_region_model->on_call (call, &ctxt);
+    }
+
+  int sm_idx;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (old_state.m_checker_states, sm_idx, smap)
+    {
+      const state_machine &sm = m_ext_state.get_sm (sm_idx);
+      const sm_state_map *old_smap
+	= old_state.m_checker_states[sm_idx];
+      sm_state_map *new_smap = new_state.m_checker_states[sm_idx];
+      impl_sm_context ctxt (eg, sm, this,
+			    &new_state,
+			    old_smap, new_smap);
+      /* Allow the state_machine to handle the stmt.  */
+      if (!sm.on_stmt (&ctxt, point.m_supernode, stmt))
+	{
+	  /* For those stmts that were not handled by the state machine.  */
+	  if (const gcall *call = dyn_cast <const gcall *> (stmt))
+	    {
+	      tree callee_fndecl = gimple_call_fndecl (call);
+	      // TODO: maybe we can be smarter about handling function pointers?
+
+	      if (!fndecl_has_gimple_body_p (callee_fndecl))
+		new_smap->purge_for_unknown_fncall (eg, sm, call, callee_fndecl,
+						    new_state.m_region_model);
+	    }
+	}
+    }
+
+  return new_state;
+}
+
+/* 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,
+   pushing/popping stack frames, etc).
+
+   Update NEXT_POINT accordingly (updating the call string).  */
+
+bool
+exploded_node::on_edge (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;
+}
+
+// FIXME: this comment needs rewriting:
+/* 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 (exploded_graph &eg) const
+{
+  JIT_LOG_FUNC_1 (eg.get_logger (), "EN: %i", m_index);
+
+  /* FIXME: with region model, should we only do this when the callstring
+     is empty?  when we're popping the stack, purge_unused_svalues ought
+     to detect leaks due to popping the frame.  */
+
+  const program_state &old_state = get_state ();
+
+  if (get_point ().get_stack_depth () > 0)
+    return;
+  gcc_assert (get_point ().get_stack_depth () == 0);
+
+  /* Work with a temporary copy of the state: pop the frame, and see
+     what leaks (via purge_unused_svalues).  */
+  program_state new_state (old_state);
+
+  gcc_assert (new_state.m_region_model);
+
+  purge_stats stats;
+  impl_region_model_context ctxt(eg, this, &new_state);
+  new_state.m_region_model->pop_frame (&stats, &ctxt);
+}
+
+/* FIXME.  */
+
+void
+exploded_node::dump_succs_and_preds (FILE *outf) const
+{
+  unsigned i;
+  exploded_edge *e;
+  {
+    auto_vec<exploded_node *> preds (m_preds.length ());
+    FOR_EACH_VEC_ELT (m_preds, i, e)
+      preds.quick_push (e->m_src);
+    pretty_printer pp;
+    print_enode_indices (&pp, preds);
+    fprintf (outf, "preds: %s\n",
+	     pp_formatted_text (&pp));
+  }
+  {
+    auto_vec<exploded_node *> succs (m_succs.length ());
+    FOR_EACH_VEC_ELT (m_succs, i, e)
+      succs.quick_push (e->m_dest);
+    pretty_printer pp;
+    print_enode_indices (&pp, succs);
+    fprintf (outf, "succs: %s\n",
+	     pp_formatted_text (&pp));
+  }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class exploded_edge : public dedge.  */
+
+/* exploded_edge's ctor.  */
+
+exploded_edge::exploded_edge (exploded_node *src, exploded_node *dest,
+			      const superedge *sedge)
+: dedge (src, dest), m_sedge (sedge)
+{
+}
+
+/* FIXME.  */
+
+void
+exploded_edge::dump_dot (graphviz_out *gv, const dump_args_t &) 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, false);
+
+  pp_printf (pp, "\"];\n");
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* struct stats.  */
+
+/* FIXME.  */
+
+stats::stats (int num_supernodes)
+: m_node_reuse_count (0),
+  m_node_reuse_after_merge_count (0),
+  m_num_supernodes (num_supernodes)
+{
+  for (int i = 0; i < NUM_POINT_KINDS; i++)
+    m_num_nodes[i] = 0;
+}
+
+/* FIXME.  */
+
+void
+stats::log (logger *logger) const
+{
+  gcc_assert (logger);
+  for (int i = 0; i < NUM_POINT_KINDS; i++)
+    logger->log ("m_num_nodes[%s]: %i",
+		 point_kind_to_string (static_cast <enum point_kind> (i)),
+		 m_num_nodes[i]);
+  logger->log ("m_node_reuse_count: %i", m_node_reuse_count);
+  logger->log ("m_node_reuse_after_merge_count: %i",
+	       m_node_reuse_after_merge_count);
+}
+
+/* FIXME.  */
+
+void
+stats::dump (FILE *out) const
+{
+  for (int i = 0; i < NUM_POINT_KINDS; i++)
+    fprintf (out, "m_num_nodes[%s]: %i\n",
+	     point_kind_to_string (static_cast <enum point_kind> (i)),
+	     m_num_nodes[i]);
+  fprintf (out, "m_node_reuse_count: %i\n", m_node_reuse_count);
+  fprintf (out, "m_node_reuse_after_merge_count: %i\n",
+	   m_node_reuse_after_merge_count);
+
+  if (m_num_supernodes > 0)
+    fprintf (out, "PK_AFTER_SUPERNODE nodes per supernode: %.2f\n",
+	     (float)m_num_nodes[PK_AFTER_SUPERNODE] / (float)m_num_supernodes);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class saved_diagnostic.  */
+
+/* saved_diagnostic's ctor.
+   Take ownership of D and STMT_FINDER.  */
+
+saved_diagnostic::saved_diagnostic (const state_machine *sm,
+				    const exploded_node *enode,
+				    const supernode *snode, const gimple *stmt,
+				    stmt_finder *stmt_finder,
+				    tree var, state_machine::state_t state,
+				    pending_diagnostic *d)
+: m_sm (sm), m_enode (enode), m_snode (snode), m_stmt (stmt),
+ /* stmt_finder could be on-stack; we want our own copy that can
+    outlive that.  */
+  m_stmt_finder (stmt_finder ? stmt_finder->clone () : NULL),
+  m_var (var), m_state (state),
+  m_d (d)
+{
+}
+
+/* saved_diagnostic's dtor.  */
+
+saved_diagnostic::~saved_diagnostic ()
+{
+  delete m_stmt_finder;
+  delete m_d;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class diagnostic_manager.  */
+
+/* diagnostic_manager's ctor.  */
+
+diagnostic_manager::diagnostic_manager (logger *logger, int verbosity)
+: log_user (logger), m_verbosity (verbosity)
+{
+}
+
+/* FIXME.  */
+
+void
+diagnostic_manager::add_diagnostic (const state_machine *sm,
+				    const exploded_node *enode,
+				    const supernode *snode, const gimple *stmt,
+				    stmt_finder *finder,
+				    tree var, state_machine::state_t state,
+				    pending_diagnostic *d)
+{
+  JIT_LOG_FUNC (get_logger ());
+  // TODO: potentially do them directly, vs saved
+  saved_diagnostic *sd
+    = new saved_diagnostic (sm, enode, snode, stmt, finder, var, state, d);
+  m_saved_diagnostics.safe_push (sd);
+  if (get_logger ())
+    log ("adding saved diagnostic %i at SN %i: %qs",
+	 m_saved_diagnostics.length () - 1,
+	 snode->m_index, d->get_kind ());
+}
+
+/* FIXME.  */
+
+void
+diagnostic_manager::add_diagnostic (const exploded_node *enode,
+				    const supernode *snode, const gimple *stmt,
+				    stmt_finder *finder,
+				    pending_diagnostic *d)
+{
+  add_diagnostic (NULL, enode, snode, stmt, finder, NULL_TREE, 0, d);
+}
+
+/* FIXME.  */
+/* we want to find the simplest dedupe_candidate amongst those that share a
+   dedupe_key.  */
+
+class dedupe_key
+{
+public:
+  dedupe_key (const saved_diagnostic &sd,
+	      const exploded_path &epath)
+  : m_sd (sd), m_stmt (sd.m_stmt)
+  {
+    /* Support deferring the choice of stmt until after an emission path has
+     been built, using an optional stmt_finder.  */
+    if (m_stmt == NULL)
+      {
+	gcc_assert (sd.m_stmt_finder);
+	m_stmt = sd.m_stmt_finder->find_stmt (epath);
+      }
+    gcc_assert (m_stmt);
+  }
+
+  hashval_t hash () const
+  {
+    inchash::hash hstate;
+    hstate.add_ptr (m_stmt);
+    // TODO: m_sd
+    return hstate.end ();
+  }
+  bool operator== (const dedupe_key &other) const
+  {
+    return (m_sd == other.m_sd
+	    && m_stmt == other.m_stmt);
+  }
+
+  location_t get_location () const
+  {
+    return m_stmt->location;
+  }
+
+  /* A qsort comparator for use by dedupe_winners::emit_best
+     to sort them into location_t order.  */
+
+  static int
+  comparator (const void *p1, const void *p2)
+  {
+    const dedupe_key *pk1 = *(const dedupe_key * const *)p1;
+    const dedupe_key *pk2 = *(const dedupe_key * const *)p2;
+
+    location_t loc1 = pk1->get_location ();
+    location_t loc2 = pk2->get_location ();
+
+    return linemap_compare_locations (line_table, loc2, loc1);
+  }
+
+  // the pending_diagnostic
+  // the (stmt (non-NULL), var, state)
+  const saved_diagnostic &m_sd;
+  const gimple *m_stmt;
+};
+
+/* FIXME.  */
+class dedupe_candidate
+{
+public:
+  // has the exploded_path
+  dedupe_candidate  (const shortest_paths &sp,
+		     const saved_diagnostic &sd)
+  : m_epath (sp.get_shortest_path (sd.m_enode)),
+    m_num_dupes (0)
+  {
+  }
+
+  unsigned length () const { return m_epath.length (); }
+  const exploded_path &get_path () const { return m_epath; }
+
+private:
+  exploded_path m_epath;
+public:
+  int m_num_dupes;
+};
+
+/* FIXME.  */
+
+class dedupe_hash_map_traits
+{
+public:
+  typedef const dedupe_key *key_type;
+  typedef dedupe_candidate *value_type;
+  typedef dedupe_candidate *compare_type;
+
+  static inline hashval_t hash (const key_type &v)
+  {
+    return v->hash ();
+  }
+  static inline bool equal_keys (const key_type &k1, const key_type &k2)
+  {
+    return *k1 == *k2;
+  }
+  template <typename T>
+  static inline void remove (T &)
+  {
+    // TODO
+  }
+  template <typename T>
+  static inline void mark_deleted (T &entry)
+  {
+    entry.m_key = reinterpret_cast<key_type> (1);
+  }
+  template <typename T>
+  static inline void mark_empty (T &entry)
+  {
+    entry.m_key = NULL;
+  }
+  template <typename T>
+  static inline bool is_deleted (const T &entry)
+  {
+    return entry.m_key == reinterpret_cast<key_type> (1);
+  }
+  template <typename T>
+  static inline bool is_empty (const T &entry)
+  {
+    return entry.m_key == NULL;
+  }
+
+};
+
+/* A map from dedupe_key to best current dedupe_candidate.  */
+
+class dedupe_winners
+{
+public:
+  ~dedupe_winners ()
+  {
+    /* Delete all keys and candidates.  */
+    for (map_t::iterator iter = m_map.begin ();
+	 iter != m_map.end ();
+	 ++iter)
+      {
+	delete (*iter).first;
+	delete (*iter).second;
+      }
+  }
+
+  void add (logger *logger,
+	    const shortest_paths &sp,
+	    const saved_diagnostic &sd)
+  {
+    dedupe_candidate *dc = new dedupe_candidate (sp, sd);
+    /* Verify that the epath is feasible.
+       State-merging means that not every path in the epath corresponds
+       to a feasible one w.r.t. states.
+       Here we simply check each duplicate saved_diagnostic's
+       shortest_path, and reject any that aren't feasible.
+       This could introduce false negatives, as there could be longer
+       feasible paths within the egraph.  */
+    if (logger)
+      logger->log ("considering %qs at SN: %i",
+		   sd.m_d->get_kind (), sd.m_snode->m_index);
+    if (!dc->get_path ().feasible_p (logger))
+      {
+	if (logger)
+	  logger->log ("rejecting %qs at SN: %i"
+		       " due to infeasible path",
+		       sd.m_d->get_kind (), sd.m_snode->m_index);
+	delete dc;
+	return;
+      }
+    else
+      if (logger)
+	logger->log ("accepting %qs at SN: %i with feasible path",
+		     sd.m_d->get_kind (), sd.m_snode->m_index);
+
+    dedupe_key *key = new dedupe_key (sd, dc->get_path ());
+    if (dedupe_candidate **slot = m_map.get (key))
+      {
+	(*slot)->m_num_dupes++;
+
+	if (dc->length () < (*slot)->length ())
+	  {
+	    /* We've got a shorter path for the key; replace
+	       the current candidate.  */
+	    dc->m_num_dupes = (*slot)->m_num_dupes;
+	    delete *slot;
+	    *slot = dc;
+	  }
+	else
+	  /* We haven't beaten the current best candidate;
+	     drop the new candidate.  */
+	  delete dc;
+	delete key;
+      }
+    else
+      /* This is the first candidate for this key.  */
+      m_map.put (key, dc);
+  }
+
+ /* Emit the simplest diagnostic within each set.  */
+
+  void emit_best (diagnostic_manager *dm,
+		  const exploded_graph &eg,
+		  const shortest_paths &/*sp*/)
+  {
+    JIT_LOG_SCOPE (dm->get_logger ());
+
+    /* Get keys into a vec for sorting.  */
+    auto_vec<const dedupe_key *> keys;
+    for (map_t::iterator iter = m_map.begin ();
+	 iter != m_map.end ();
+	 ++iter)
+      keys.safe_push ((*iter).first);
+
+    dm->log ("# keys after de-duplication: %i", keys.length ());
+
+    /* Sort into a good emission order.  */
+    keys.qsort (dedupe_key::comparator);
+
+    /* Emit the best candidate for each key.  */
+    int i;
+    const dedupe_key *key;
+    FOR_EACH_VEC_ELT (keys, i, key)
+      {
+	dedupe_candidate **slot = m_map.get (key);
+	gcc_assert (*slot);
+	const dedupe_candidate &dc = **slot;
+
+	dm->emit_saved_diagnostic (eg, key->m_sd,
+				   dc.get_path (), key->m_stmt,
+				   dc.m_num_dupes);
+      }
+  }
+
+private:
+
+  typedef hash_map<const dedupe_key *, dedupe_candidate *,
+		   dedupe_hash_map_traits> map_t;
+  map_t m_map;
+};
+
+/* FIXME.  */
+
+void
+diagnostic_manager::emit_saved_diagnostics (const exploded_graph &eg)
+{
+  JIT_LOG_SCOPE (get_logger ());
+  auto_client_timevar tv ("emit saved diagnostics");
+  log ("# saved diagnostics: %i", m_saved_diagnostics.length ());
+
+  if (m_saved_diagnostics.length () == 0)
+    return;
+
+  /* Compute the shortest_paths once, sharing it between all diagnostics.  */
+  shortest_paths sp (eg);
+
+  // FIXME: implement a way to disable de-duplication
+  /* FIXME: add stats on de-duplication, and a way to show the
+     number of duplicates (e.g. via an option that adds a message.
+     Add a DejaGnu test for this  */
+
+  // FIXME: this isn't true:
+  /* De-duplicate the diagnostics by generating a collection of diagnostic_set,
+     picking the diagnostic with the shortest path within each set.  */
+#if 1
+  dedupe_winners best_candidates;
+
+  int i;
+  saved_diagnostic *sd;
+  FOR_EACH_VEC_ELT (m_saved_diagnostics, i, sd)
+    best_candidates.add (get_logger (), sp, *sd);
+
+  best_candidates.emit_best (this, eg, sp);
+
+#else
+  // TODO: for now, just emit them all
+
+  int i;
+  saved_diagnostic *sd;
+  FOR_EACH_VEC_ELT (m_saved_diagnostics, i, sd)
+    emit_saved_diagnostic (eg, sp, *sd);
+#endif
+}
+
+/* FIXME.  */
+
+void
+diagnostic_manager::emit_saved_diagnostic (const exploded_graph &eg,
+					   const saved_diagnostic &sd,
+					   const exploded_path &epath,
+					   const gimple *stmt,
+					   int num_dupes)
+{
+  JIT_LOG_SCOPE (get_logger ());
+  log ("sd: %qs at SN: %i", sd.m_d->get_kind (), sd.m_snode->m_index);
+  log ("num dupes: %i", num_dupes);
+
+  pretty_printer *pp = global_dc->printer->clone ();
+
+  checker4_path emission_path;
+  /* Populate emission_path with a full description of EPATH.  */
+  build_emission_path (eg, epath, &emission_path);
+
+  /* Now prune it to just cover the most pertinent events.  */
+  prune_path (&emission_path, sd.m_sm, sd.m_var, sd.m_state);
+
+  /* Add a final event to the path, covering the diagnostic itself.
+     We use the final enode from the epath, which might be different from
+     the sd.m_enode, as the dedupe code doesn't care about enodes, just
+     snodes.  */
+  emission_path.add_final_event (sd.m_sm, epath.get_final_enode (), stmt,
+				 sd.m_var, sd.m_state);
+
+  emission_path.prepare_for_emission (sd.m_d);
+
+  gcc_rich_location rich_loc (stmt->location);
+  rich_loc.set_path (&emission_path);
+
+  auto_diagnostic_group d;
+  auto_cfun sentinel (sd.m_snode->m_fun);
+  sd.m_d->emit (&rich_loc);
+  if (num_dupes > 0)
+    inform_n (stmt->location, num_dupes,
+	      "%i duplicate", "%i duplicates",
+	      num_dupes);
+  delete pp;
+}
+
+/* Given a state change to DST_REP, determine a tree that gives the origin
+   of that state at STMT, using DST_STATE's region model, so that state
+   changes based on assignments can be tracked back to their origins.
+
+   For example, if we have
+
+     (S1) _1 = malloc (64);
+     (S2) EXPR = _1;
+
+   then at stmt S2 we can get the origin of EXPR's state as being _1,
+   and thus track the allocation back to S1.  */
+
+static tree
+get_any_origin (const gimple *stmt,
+		tree dst_rep,
+		const program_state &dst_state)
+{
+  if (!stmt)
+    return NULL_TREE;
+
+  if (const gassign *assign = dyn_cast <const gassign *> (stmt))
+    {
+      tree lhs = gimple_assign_lhs (assign);
+      /* Use region IDs to compare lhs with DST_REP.  */
+      if (dst_state.m_region_model->get_lvalue (lhs, NULL)
+	  == dst_state.m_region_model->get_lvalue (dst_rep, NULL))
+	{
+	  tree rhs1 = gimple_assign_rhs1 (assign);
+	  enum tree_code op = gimple_assign_rhs_code (assign);
+	  switch (op)
+	    {
+	    default:
+	      //gcc_unreachable ();  // TODO
+	      break;
+	    case COMPONENT_REF:
+	    case SSA_NAME:
+	      return rhs1;
+	    }
+	}
+    }
+  return NULL_TREE;
+}
+
+/* Emit a "path" of events to EMISSION_PATH describing the exploded path
+   EPATH within EG.  */
+
+void
+diagnostic_manager::build_emission_path (const exploded_graph &eg,
+					 const exploded_path &epath,
+					 checker4_path *emission_path) const
+{
+  JIT_LOG_SCOPE (get_logger ());
+  const extrinsic_state &ext_state = eg.get_ext_state ();
+  for (unsigned i = 0; i < epath.m_edges.length (); i++)
+    {
+      const exploded_edge *eedge = epath.m_edges[i];
+      add_events_for_eedge (*eedge, ext_state, emission_path);
+    }
+}
+
+/* Subroutine of diagnostic_manager::build_emission_path.
+   Add any events for EEDGE to EMISSION_PATH.  */
+
+void
+diagnostic_manager::add_events_for_eedge (const exploded_edge &eedge,
+					  const extrinsic_state &ext_state,
+					  checker4_path *emission_path) const
+{
+  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 dst_stack_depth = dst_point.get_stack_depth ();
+  const program_state &src_state = src_node->get_state ();
+  const program_state &dst_state = dst_node->get_state ();
+  switch (dst_point.m_kind)
+    {
+    default:
+      break;
+    case PK_BEFORE_SUPERNODE:
+      if (src_point.m_kind == PK_AFTER_SUPERNODE)
+	{
+	  if (eedge.m_sedge)
+	    add_events_for_superedge (eedge, emission_path);
+	}
+      /* 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, dst_state));
+      break;
+    }
+
+  /* Add state change events for the states that have changed.  */
+  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];
+      add_state_change_events (eedge, sm, src_smap, dst_smap, emission_path);
+    }
+}
+
+/* Subroutine of diagnostic_manager::add_events_for_eedge
+   where EEDGE has an underlying superedge i.e. a CFG edge,
+   or an interprocedural call/return.
+   Add any events for the superedge to EMISSION_PATH.  */
+
+void
+diagnostic_manager::add_events_for_superedge (const exploded_edge &eedge,
+					      checker4_path *emission_path)
+  const
+{
+  gcc_assert (eedge.m_sedge);
+
+  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 (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,
+			   (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,
+			   (call_stmt
+			    ? call_stmt->location
+			    : UNKNOWN_LOCATION),
+			   dst_point.m_supernode->m_fun->decl,
+			   dst_stack_depth));
+      }
+      break;
+    }
+}
+
+/* Subroutine of diagnostic_manager::add_events_for_edge, for handling
+   EEDGE.
+   Detect any state changes in DST_SMAP relative to SRC_SMAP for the
+   underlying state machine SM, adding them as events to *EMISSION_PATH.  */
+
+void
+diagnostic_manager::add_state_change_events (const exploded_edge &eedge,
+					     const state_machine &sm,
+					     const sm_state_map &src_smap,
+					     const sm_state_map &dst_smap,
+					     checker4_path *emission_path) const
+{
+  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 int src_stack_depth = src_point.get_stack_depth ();
+  const program_state &src_state = src_node->get_state ();
+  const program_state &dst_state = dst_node->get_state ();
+
+  for (sm_state_map::iterator_t iter = dst_smap.begin ();
+       iter != dst_smap.end ();
+       ++iter)
+    {
+      /* FIXME: we want to compare the SM state between src state
+	 and dst state, but there's no guarantee that the IDs can
+	 be meaningfully compared.  */
+      svalue_id dst_sid = (*iter).first;
+      state_machine::state_t dst_sm_val = (*iter).second.m_state;
+
+      auto_vec<path_var> dst_pvs;
+      dst_state.m_region_model->get_path_vars_for_svalue (dst_sid,
+							  &dst_pvs);
+
+#if 1
+      unsigned j;
+      path_var *dst_pv;
+      FOR_EACH_VEC_ELT (dst_pvs, j, dst_pv)
+	{
+	  tree dst_rep = dst_pv->m_tree;
+	  gcc_assert (dst_rep);
+#else
+	  tree dst_rep = dst_state.get_representative_tree (dst_sid);
+	  if (dst_rep == NULL_TREE)
+	    continue;
+#endif
+	  if (dst_pv->m_stack_depth >= src_state.m_region_model->get_stack_depth ())
+	    continue;
+	  svalue_id src_sid
+	    = src_state.m_region_model->get_rvalue (*dst_pv, NULL);
+	  if (src_sid.null_p ())
+	    continue;
+	  state_machine::state_t src_sm_val = src_smap.get_state (src_sid);
+	  if (dst_sm_val != src_sm_val)
+	    {
+	      const gimple *stmt = src_point.get_stmt ();
+	      const supernode *supernode = src_point.m_supernode;
+	      int stack_depth = src_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;
+
+	      svalue_id dst_origin_sid = (*iter).second.m_origin;
+#if 0
+	      if (dst_origin_sid.null_p ())
+		{
+		  emission_path->add_event
+		    (new state_change_event (supernode,
+					     stmt,
+					     stack_depth,
+					     sm,
+					     dst_rep,
+					     src_sm_val,
+					     dst_sm_val,
+					     NULL_TREE));
+		}
+	      else
+		{
+		  auto_vec<tree> origin_trees;
+		  dst_state.m_region_model->get_trees_for_svalue
+		    (dst_origin_sid, &origin_trees);
+		  unsigned k;
+		  tree origin_rep;
+		  FOR_EACH_VEC_ELT (origin_trees, j, origin_rep)
+		    {
+		      gcc_assert (origin_rep);
+#else
+		      tree origin_rep
+			= dst_state.get_representative_tree (dst_origin_sid);
+
+		      if (origin_rep == NULL_TREE)
+			origin_rep = get_any_origin (stmt, dst_rep, dst_state);
+#endif
+		      emission_path->add_event
+			(new state_change_event (supernode,
+						 stmt,
+						 stack_depth,
+						 sm,
+						 dst_rep,
+						 src_sm_val,
+						 dst_sm_val,
+					     origin_rep,
+						 dst_state));
+#if 0
+		    }
+		}
+#endif
+	    }
+#if 1
+	}
+#endif
+    }
+}
+
+/* Prune PATH, based on the verbosity level, to the most pertinent
+   events for a diagnostic that involves VAR ending in state STATE
+   (for state machine SM).  */
+
+void
+diagnostic_manager::prune_path (checker4_path *path,
+				const state_machine *sm,
+				tree var,
+				state_machine::state_t state) const
+{
+  JIT_LOG_FUNC (get_logger ());
+  path->maybe_log (get_logger (), "path");
+
+  /* First pass: apply verbosity level, pruning unrelated state
+     change events.
+     Iterate backwards through PATH, 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 < (signed)path->m_events.length ())
+    {
+      checker4_event *base_event = path->m_events[idx];
+      if (get_logger ())
+	{
+	  if (sm)
+	    log ("considering event %i, with var: %qE, state: %qs",
+		 idx, var, sm->get_state_name (state));
+	  else
+	    log ("considering event %i", idx);
+	}
+      switch (base_event->m_kind)
+	{
+	default:
+	  gcc_unreachable ();
+
+	case EK_DEBUG:
+	  if (m_verbosity < 3)
+	    {
+	      log ("filtering event %i: debug event", idx);
+	      path->delete_event (idx);
+	    }
+	  break;
+
+	case EK_STMT:
+	  {
+	    /* If this stmt is the origin of "var", update var.  */
+	    statement_event *stmt_event = (statement_event *)base_event;
+	    tree new_var = get_any_origin (stmt_event->m_stmt, var,
+					   stmt_event->m_dst_state);
+	    if (new_var)
+	      {
+		log ("event %i: switching var of interest from %qE to %qE",
+		     idx, var, new_var);
+		var = new_var;
+	      }
+	    if (m_verbosity < 3)
+	      {
+		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->get_lvalue (state_change->m_var)
+		== state_change->get_lvalue (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;
+		  }
+		log ("event %i: switching state of interest from %qs to %qs",
+		     idx, sm->get_state_name (state_change->m_to),
+		     sm->get_state_name (state_change->m_from));
+		state = state_change->m_from;
+	      }
+	    else if (m_verbosity < 3)
+	      {
+		if (var)
+		  log ("filtering event %i:"
+		       " state change to %qE unrelated to %qE",
+		       idx, state_change->m_var, var);
+		else
+		  log ("filtering event %i: state change to %qE",
+		       idx, state_change->m_var);
+		path->delete_event (idx);
+	      }
+	  }
+	  break;
+
+	case EK_EDGE:
+	  {
+	    edge_event *event = (edge_event *)base_event;
+
+#if 0
+	    const program_state &src_state
+	      = event->m_eedge->m_src->get_state ();
+#endif
+#if 0
+	      svalue_id src_sid
+		= src_state.m_region_model->get_rvalue (dst_rep, NULL);
+	      if (src_sid.null_p ())
+		continue;
+#endif
+
+#if 0
+	    inform (base_event->m_loc, "considering edge_event %i: %s",
+		    idx, base_event->m_desc);
+#endif
+	    switch (event->m_sedge->m_kind)
+	      {
+	      case SUPEREDGE_CFG_EDGE:
+		{
+		  const cfg_superedge& cfg_superedge
+		    = event->get_cfg_superedge ();
+		  const supernode *dest = event->m_sedge->m_dest;
+		  /* Do we have an SSA_NAME defined via a phi node in
+		     the dest CFG node?  */
+		  if (var && TREE_CODE (var) == SSA_NAME)
+		    if (SSA_NAME_DEF_STMT (var)->bb == dest->m_bb)
+		      {
+			if (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, phi, 0, (dump_flags_t)0);
+				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.  */
+
+		  if (event->should_filter_p (m_verbosity))
+		    {
+		      log ("filtering event %i: CFG edge", idx);
+		      path->delete_event (idx);
+		    }
+		}
+		break;
+
+	      case SUPEREDGE_CALL:
+		{
+		  const callgraph_superedge& cg_superedge
+		    = event->get_callgraph_superedge ();
+		  callsite_expr expr;
+		  tree caller_var
+		    = cg_superedge.map_expr_from_callee_to_caller (var, &expr);
+		  if (caller_var)
+		    {
+		      log ("event %i:"
+			   " switching var of interest"
+			   " from %qE in callee to %qE in caller",
+			   idx, var, caller_var);
+		      var = caller_var;
+		      if (expr.param_p ())
+			event->record_critical_state (var, state);
+		    }
+		  //#error
+		  /* potentially use this for precision-of-wording:
+		     identify passing a parm (and which parm idx)
+		     then update the edge_event to note that it's a critical
+		     state-related edge (with the detail, maybe as an optional
+		     object).
+		     Then update the get_desc to use the detail, passing
+		     it to the pending_diagnostic to get a precise message
+		     e.g. passing possibly-NULL pointer 'ptr' to 'foo' from 'bar' as param 1
+		     or somesuch
+		   */
+		}
+		break;
+	      case SUPEREDGE_RETURN:
+		// TODO: potentially update var/state based on return value,
+		// args etc
+		{
+		  const callgraph_superedge& cg_superedge
+		    = event->get_callgraph_superedge ();
+		  callsite_expr expr;
+		  tree callee_var
+		    = cg_superedge.map_expr_from_caller_to_callee (var, &expr);
+		  if (callee_var)
+		    {
+		      if (var)
+			log ("event %i:"
+			     " switching var of interest"
+			     " from %qE in caller to %qE in callee",
+			     idx, var, callee_var);
+		      else
+			// FIXME: how does this happen?
+			log ("event %i:"
+			     " switching var of interest"
+			     " from NULL in caller to %qE in callee",
+			     idx, callee_var);
+		      var = callee_var;
+		      if (expr.return_value_p ())
+			event->record_critical_state (var, state);
+		    }
+		  /* FIXME: similar precision-of-wording notes to above,
+		     but for return value.  */
+		}
+		break;
+	      case SUPEREDGE_INTRAPROCEDURAL_CALL:
+		break;
+	      }
+	  }
+	  break;
+
+	case EK_WARNING:
+	  break;
+	}
+      idx--;
+    }
+
+  /* Second pass: remove redundant interprocedural information.  For
+     example, given:
+     (1)- calling "f2" from "f1"
+     (2)--- entry to "f2"
+     (3)--- calling "f3" from "f2"
+     (4)----- entry to "f3"
+     (5)--- returning to "f2" to "f3"
+     (6)- 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.  For the example above, this would
+     remove events (3, 4, 5), and then remove events (1, 2, 6).  */
+  bool changed = false;
+  do
+    {
+      changed = false;
+      int idx = path->m_events.length () - 3;
+      while (idx >= 0 && idx < (signed)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 (false));
+		  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 < (signed)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--;
+	}
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* strongly_connected_components's ctor.  Tarjan's SCC algorithm.  */
+
+strongly_connected_components::
+strongly_connected_components (const supergraph &sg, logger *logger)
+: m_sg (sg), m_per_node (m_sg.num_nodes ())
+{
+  JIT_LOG_SCOPE (logger);
+  auto_client_timevar tv ("computing scc");
+
+  for (int i = 0; i < m_sg.num_nodes (); i++)
+    m_per_node.quick_push (per_node_data ());
+
+  for (int i = 0; i < m_sg.num_nodes (); i++)
+    if (m_per_node[i].m_index == -1)
+      strong_connect (i);
+
+  if (0)
+    dump ();
+}
+
+/* Dump this object to stderr.  */
+
+DEBUG_FUNCTION void
+strongly_connected_components::dump () const
+{
+  for (int i = 0; i < m_sg.num_nodes (); i++)
+    {
+      const per_node_data &v = m_per_node[i];
+      fprintf (stderr, "SN %i: index: %i lowlink: %i on_stack: %i\n",
+	       i, v.m_index, v.m_lowlink, v.m_on_stack);
+    }
+}
+
+/* Subroutine of strongly_connected_components's ctor, part of Tarjan's
+   SCC algorithm.  */
+
+void
+strongly_connected_components::strong_connect (unsigned index)
+{
+  supernode *v_snode = m_sg.get_node_by_index (index);
+
+  /* Set the depth index for v to the smallest unused index.  */
+  per_node_data *v = &m_per_node[index];
+  v->m_index = index;
+  v->m_lowlink = index;
+  m_stack.safe_push (index);
+  v->m_on_stack = true;
+  index++;
+
+  /* Consider successors of v.  */
+  unsigned i;
+  superedge *sedge;
+  FOR_EACH_VEC_ELT (v_snode->m_succs, i, sedge)
+    {
+      supernode *w_snode = sedge->m_dest;
+      per_node_data *w = &m_per_node[w_snode->m_index];
+      if (w->m_index == -1)
+	{
+	  /* Successor w has not yet been visited; recurse on it.  */
+	  strong_connect (w_snode->m_index);
+	  v->m_lowlink = std::min (v->m_lowlink, w->m_lowlink);
+	}
+      else if (w->m_on_stack)
+	{
+	  /* Successor w is in stack S and hence in the current SCC
+	     If w is not on stack, then (v, w) is a cross-edge in the DFS
+	     tree and must be ignored.  */
+	  v->m_lowlink = std::min (v->m_lowlink, w->m_index);
+	}
+    }
+
+  /* If v is a root node, pop the stack and generate an SCC.  */
+
+  if (v->m_lowlink == v->m_index)
+    {
+      /* FIXME: Start a new strongly connected component.  */
+      per_node_data *w;
+      do {
+	int idx = m_stack.pop ();
+	w = &m_per_node[idx];
+	w->m_on_stack = false;
+	/* FIXME: add w to current strongly connected component.  */
+      } while (w != v);
+      /* FIXME: output the current strongly connected component.  */
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* worklist's ctor.  */
+
+worklist::worklist (const exploded_graph &eg)
+: m_eg (eg),
+  m_scc (eg.get_supergraph (), eg.get_logger ()),
+  m_queue (key_t (*this, NULL))
+{
+}
+
+/* Return the number of nodes in the worklist.  */
+
+unsigned
+worklist::length () const
+{
+  return m_queue.nodes ();
+}
+
+/* Return the next node in the worklist, removing it.  */
+
+exploded_node *
+worklist::take_next ()
+{
+  return m_queue.extract_min ();
+}
+
+/* Return the next node in the worklist without removing it.  */
+
+exploded_node *
+worklist::peek_next ()
+{
+  return m_queue.min ();
+}
+
+/* Add ENODE to the worklist.  */
+
+void
+worklist::add_node (exploded_node *enode)
+{
+  m_queue.insert (key_t (*this, enode), enode);
+}
+
+/* Comparator for implementing worklist::key_t comparison operators.
+   Return negative if KA is before KB
+   Return positive if KA is after KB
+   Return 0 if they are equal.  */
+
+int
+worklist::key_t::cmp_1 (const worklist::key_t &ka, const worklist::key_t &kb)
+{
+  /* First, order by SCC.  */
+  int scc_id_a = ka.get_scc_id (ka.m_enode);
+  int scc_id_b = kb.get_scc_id (kb.m_enode);
+  if (scc_id_a != scc_id_b)
+    return scc_id_a - scc_id_b;
+
+  /* If in same SCC, order by supernode index (an arbitrary but stable
+     ordering).  */
+  const supernode *snode_a = ka.m_enode->get_supernode ();
+  const supernode *snode_b = kb.m_enode->get_supernode ();
+  if (snode_a == NULL)
+    {
+      if (snode_b != NULL)
+	/* One is NULL.  */
+	return -1;
+      else
+	/* Both are NULL.  */
+	return 0;
+    }
+  if (snode_b == NULL)
+    /* One is NULL.  */
+    return 1;
+  /* Neither are NULL.  */
+  gcc_assert (snode_a && snode_b);
+  if (snode_a->m_index != snode_b->m_index)
+    return snode_a->m_index - snode_b->m_index;
+
+  gcc_assert (snode_a == snode_b);
+
+  /* Order within supernode via program point.  */
+  const program_point &point_a = ka.m_enode->get_point ();
+  const program_point &point_b = kb.m_enode->get_point ();
+  int within_snode_cmp = program_point::cmp_within_supernode (point_a, point_b);
+  if (within_snode_cmp)
+    return within_snode_cmp;
+
+  /* The points might vary by callstring; try sorting by callstring.  */
+  const call_string &call_string_a = point_a.m_call_string;
+  const call_string &call_string_b = point_b.m_call_string;
+  int cs_cmp = call_string::cmp (call_string_a, call_string_b);
+  if (cs_cmp)
+    return cs_cmp;
+
+  /* Otherwise, we ought to have the same program_point.  */
+  gcc_assert (point_a == point_b);
+
+  const program_state &state_a = ka.m_enode->get_state ();
+  const program_state &state_b = kb.m_enode->get_state ();
+
+  /* FIXME: sort by sm-state, so that identical sm-states are grouped
+     together in the worklist.  */
+  // for now, try the hash value (might not be deterministic)
+  for (unsigned sm_idx = 0; sm_idx < state_a.m_checker_states.length ();
+       ++sm_idx)
+    {
+      sm_state_map *smap_a = state_a.m_checker_states[sm_idx];
+      sm_state_map *smap_b = state_b.m_checker_states[sm_idx];
+
+      int sm_cmp = smap_a->hash () - smap_b->hash ();
+      if (sm_cmp)
+	return sm_cmp;
+    }
+
+  /* Otherwise, we have two enodes at the same program point but with
+     different states.  We don't have a good total ordering on states,
+     so order them by enode index, so that we have at least have a
+     stable sort.  */
+  return ka.m_enode->m_index - kb.m_enode->m_index;
+}
+
+/* Comparator for implementing worklist::key_t comparison operators.
+   Return negative if KA is before KB
+   Return positive if KA is after KB
+   Return 0 if they are equal.  */
+
+int
+worklist::key_t::cmp (const worklist::key_t &ka, const worklist::key_t &kb)
+{
+  int result = cmp_1 (ka, kb);
+
+  /* Check that the ordering is symmetric  */
+#if CHECKING_P
+  int reversed = cmp_1 (kb, ka);
+  gcc_assert (reversed == -result);
+#endif
+
+  /* We should only have 0 for equal (point, state) pairs.  */
+  gcc_assert (result != 0
+	      || (*ka.m_enode->get_ps_key ()
+		  == *kb.m_enode->get_ps_key ()));
+
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* exploded_graph's ctor.  */
+
+exploded_graph::exploded_graph (const supergraph &sg, logger *logger,
+				const extrinsic_state &ext_state,
+				const state_purge_map *purge_map,
+				int verbosity)
+: log_user (logger), m_sg (sg),
+  m_worklist (*this),
+  m_ext_state (ext_state),
+  m_purge_map (purge_map),
+  m_diagnostic_manager (logger, verbosity),
+  m_global_stats (m_sg.num_nodes ()),
+  m_functionless_stats (m_sg.num_nodes ()),
+  m_PK_AFTER_SUPERNODE_per_snode (m_sg.num_nodes ())
+{
+  m_origin = get_or_create_node (program_point (NULL, NULL, 0, PK_ORIGIN,
+						call_string ()),
+				 program_state (ext_state));
+  for (int i = 0; i < m_sg.num_nodes (); i++)
+    m_PK_AFTER_SUPERNODE_per_snode.quick_push (i);
+}
+
+/* exploded_graph's dtor.  */
+
+exploded_graph::~exploded_graph ()
+{
+  for (function_stat_map_t::iterator iter = m_per_function_stats.begin ();
+       iter != m_per_function_stats.end ();
+       ++iter)
+    delete (*iter).second;
+
+  for (point_map_t::iterator iter = m_per_point_data.begin ();
+       iter != m_per_point_data.end ();
+       ++iter)
+    delete (*iter).second;
+}
+
+/* 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);
+  state.m_region_model->push_frame (fun, NULL);
+
+  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 ());
+  if (get_logger ())
+    {
+      format f (false);
+      log ("point: %s", point.to_str (f).c_str ());
+      log ("state: %s", state.to_str (m_ext_state).c_str ());
+    }
+
+  auto_cfun sentinel (point.get_function ());
+
+  state.validate (get_ext_state ());
+
+  //state.dump (get_ext_state ());
+
+  /* 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);
+
+  pruned_state.validate (get_ext_state ());
+
+  //pruned_state.dump (get_ext_state ());
+
+  if (get_logger ())
+    {
+      log ("pruned_state: %s", pruned_state.to_str (m_ext_state).c_str ());
+      pruned_state.m_region_model->dump_to_pp (get_logger_pp ());
+    }
+
+  stats *per_fn_stats = get_or_create_function_stats (point.get_function ());
+
+  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_global_stats.m_node_reuse_count++;
+      per_fn_stats->m_node_reuse_count++;
+      return *slot;
+    }
+
+  /* Consider merging state with another enode at this program_point.  */
+  if (flag_analyzer_state_merge)
+    if (per_program_point_data **slot = m_per_point_data.get (&point))
+      {
+	per_program_point_data *per_point_data = (*slot);
+	exploded_node *existing_enode;
+	unsigned i;
+	FOR_EACH_VEC_ELT (per_point_data->m_enodes, i, existing_enode)
+	  {
+	    log ("considering merging with existing EN: %i for point",
+		 existing_enode->m_index);
+	    gcc_assert (existing_enode->get_point () == point);
+	    const program_state &existing_state = existing_enode->get_state ();
+
+	    /* They shouldn't be equal, or we would have found and returned
+	       it above.  */
+	    gcc_assert (pruned_state != existing_state);
+
+	    // FIXME: this is successively merging within the loop
+
+	    program_state merged_state (m_ext_state);
+	    if (pruned_state.can_merge_with_p (existing_state, m_ext_state,
+					       &merged_state))
+	      {
+		log ("merging new state with that of EN: %i",
+		     existing_enode->m_index);
+
+		/* Try again for a cache hit.  */
+		ps.set_state (merged_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_global_stats.m_node_reuse_after_merge_count++;
+		    per_fn_stats->m_node_reuse_after_merge_count++;
+		    return *slot;
+		  }
+
+		/* Otherwise, continue, using the merged state in "ps".  */
+	      }
+	    else
+	      log ("not merging new state with that of EN: %i",
+		   existing_enode->m_index);
+	  }
+      }
+
+  /* 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 (node->get_ps_key (), node);
+
+  /* Update per-program_point data.  */
+  // FIXME: update this comment:
+  /* FIXME: doing this stores the last enode with the given program_point.
+     Should we store more than one enode (perhaps a vec, and bail
+     out if we have too many states per program_point?  */
+  if (per_program_point_data **slot = m_per_point_data.get (&point))
+    {
+      (*slot)->m_enodes.safe_push (node);
+    }
+  else
+    {
+      per_program_point_data *per_point_data = new per_program_point_data;
+      m_per_point_data.put (node->get_point_key (), per_point_data);
+      per_point_data->m_enodes.safe_push (node);
+    }
+
+  m_global_stats.m_num_nodes[node->get_point ().m_kind]++;
+  per_fn_stats->m_num_nodes[node->get_point ().m_kind]++;
+
+  if (node->get_point ().m_kind == PK_AFTER_SUPERNODE)
+    m_PK_AFTER_SUPERNODE_per_snode[point.get_supernode ()->m_index]++;
+
+  if (get_logger ())
+    {
+      format f (false);
+      log ("created EN: %i", node->m_index);
+      log ("point: %s", point.to_str (f).c_str ());
+      log ("pruned_state: %s", pruned_state.to_str (m_ext_state).c_str ());
+    }
+
+  if (0)
+    {
+      pretty_printer pp;
+      pp_printf (&pp, "%s.EN-%i.rmodel.dot",
+		 dump_base_name, node->m_index);
+      pruned_state.m_region_model->dump_dot (pp_formatted_text (&pp));
+    }
+
+  /* Add the new node to the worlist.  */
+  m_worklist.add_node (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);
+}
+
+/* The main loop of the analysis.
+   Take 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 ()
+{
+  JIT_LOG_SCOPE (get_logger ());
+  auto_client_timevar tv ("exploded_graph::process_worklist");
+
+  while (m_worklist.length () > 0)
+    {
+      exploded_node *node = m_worklist.take_next ();
+      gcc_assert (node->m_succs.length () == 0
+		  || node == m_origin);
+      // FIXME: avoid the "origin" specialcase?
+
+      log ("next to process: EN: %i", node->m_index);
+
+      /* Avoid exponential explosions of nodes by attempting to merge
+	 nodes that are at the same program point and which have
+	 sufficiently similar state.  */
+      if (flag_analyzer_state_merge && node != m_origin)
+	if (exploded_node *node_2 = m_worklist.peek_next ())
+	  {
+	    gcc_assert (node->m_succs.length () == 0);
+	    gcc_assert (node_2->m_succs.length () == 0);
+
+	    gcc_assert (node != node_2);
+
+	    log ("peek worklist: EN: %i", node_2->m_index);
+
+	    if (node->get_point () == node_2->get_point ())
+	      {
+		if (get_logger ())
+		  {
+		    format f (false);
+		    log ("got potential merge EN: %i and EN: %i at %s",
+			 node->m_index, node_2->m_index,
+			 node->get_point ().to_str (f).c_str ());
+		  }
+
+		const program_state &state = node->get_state ();
+		const program_state &state_2 = node_2->get_state ();
+
+		/* They shouldn't be equal, or we wouldn't have two
+		   separate nodes.  */
+		gcc_assert (state != state_2);
+
+		program_state merged_state (m_ext_state);
+		if (state.can_merge_with_p (state_2, m_ext_state,
+					    &merged_state))
+		  {
+		    log ("merging EN: %i and EN: %i",
+			 node->m_index, node_2->m_index);
+
+		    if (merged_state == state)
+		      {
+			/* Then merge node_2 into node by adding an edge.  */
+			add_edge (node_2, node, NULL);
+
+			/* Remove node_2 from the worklist.  */
+			m_worklist.take_next ();
+
+			/* Continue processing "node" below.  */
+		      }
+		    else if (merged_state == state_2)
+		      {
+			/* Then merge node into node_2, and leave node_2
+			   in the worklist, to be processed on the next
+			   iteration.  */
+			add_edge (node, node_2, NULL);
+			continue;
+		      }
+		    else
+		      {
+			/* We have a merged state that differs from
+			   both state and state_2.  */
+
+			/* Remove node_2 from the worklist.  */
+			m_worklist.take_next ();
+
+			/* Create (or get) an exploded node for the merged
+			   states, adding to the worklist.  */
+			exploded_node *merged_enode
+			  = get_or_create_node (node->get_point (),
+						merged_state);
+
+			log ("merged EN: %i and EN: %i into EN: %i",
+			     node->m_index, node_2->m_index,
+			     merged_enode->m_index);
+
+			/* "node" and "node_2" have both now been removed
+			   from the worklist; we should not process them.
+
+			   "merged_enode" may be a new node; if so it will be
+			   processed in a subsequent iteration.
+			   Alternatively, "merged_enode" could be an existing
+			   node; one way the latter can
+			   happen is if we end up merging a succession of
+			   similar nodes into one.  */
+
+			/* If merged_node is one of the two we were merging,
+			   add it back to the worklist to ensure it gets
+			   processed.
+
+			   Add edges from the merged nodes to it (but not a
+			   self-edge).  */
+			if (merged_enode == node)
+			  m_worklist.add_node (merged_enode);
+			else
+			  add_edge (node, merged_enode, NULL);
+
+			if (merged_enode == node_2)
+			  m_worklist.add_node (merged_enode);
+			else
+			  add_edge (node_2, merged_enode, NULL);
+
+			continue;
+		      }
+		  }
+
+		/* TODO: should we attempt more than two nodes,
+		   or just do pairs of nodes?  (and hope that we get
+		   a cascade of mergers).  */
+	      }
+	}
+
+      process_node (node);
+
+      /* Impose a hard limit on the number of exploded nodes, to ensure
+	 that the analysis terminates in the face of pathological state
+	 explosion (or bugs).
+
+	 Specifically, the limit is on the number of PK_AFTER_SUPERNODE
+	 exploded nodes, looking at supernode exit events.
+
+	 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.  */
+      // FIXME: do per function
+      const int limit
+	= m_sg.num_nodes () * PARAM_VALUE (PARAM_ANALYZER_BB_EXPLOSION_FACTOR);
+      if (m_global_stats.m_num_nodes[PK_AFTER_SUPERNODE] > limit)
+	{
+	  log ("bailing out; too many nodes");
+	  warning (0, "analysis bailed out early"
+		   " (%i 'after-snode' enodes; %i enodes)",
+		   m_global_stats.m_num_nodes[PK_AFTER_SUPERNODE],
+		   m_nodes.length ());
+	  // TODO: better location??
+	  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);
+
+  auto_cfun sentinel (node->get_function ());
+
+  const program_point &point = node->get_point ();
+  const program_state &state = node->get_state ();
+  if (get_logger ())
+    {
+      format f (false);
+      log ("point: %s, state: %s",
+	   point.to_str (f).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);
+
+	if (point.m_from_edge)
+	  {
+	    impl_region_model_context ctxt (*this, node, &next_state, NULL);
+	    const cfg_superedge *last_cfg_superedge
+	      = point.m_from_edge->dyn_cast_cfg_superedge ();
+	    if (last_cfg_superedge)
+	      next_state.m_region_model->update_for_phis
+		(node->get_supernode (),
+		 last_cfg_superedge,
+		 &ctxt);
+	  }
+
+	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:
+      {
+	unsigned next_idx = point.m_stmt_idx + 1;
+	program_point next_point
+	  = (next_idx < point.m_supernode->m_stmts.length ()
+	     ? program_point::before_stmt (point.m_supernode, next_idx,
+					   point.m_call_string)
+	     : program_point::after_supernode (point.m_supernode,
+					       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_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;
+    }
+}
+
+/* Ensure that this graph has a stats instance for FN, return it.
+   FN can be NULL, in which case a stats instances is returned covering
+   "functionless" parts of the graph (the origin node).  */
+
+stats *
+exploded_graph::get_or_create_function_stats (function *fn)
+{
+  if (!fn)
+    return &m_functionless_stats;
+
+  if (stats **slot = m_per_function_stats.get (fn))
+    return *slot;
+  else
+    {
+      int num_supernodes = fn ? n_basic_blocks_for_fn (fn) : 0;
+      // FIXME: ^^^ not quite the num supernodes, but nearly
+      stats *new_stats = new stats (num_supernodes);
+      m_per_function_stats.put (fn, new_stats);
+      return new_stats;
+    }
+}
+
+/* Write all stats information to this graph's logger, if any.  */
+
+void
+exploded_graph::log_stats () const
+{
+  if (!get_logger ())
+    return;
+
+  JIT_LOG_SCOPE (get_logger ());
+
+  log ("m_sg.num_nodes (): %i", m_sg.num_nodes ());
+  log ("m_nodes.length (): %i", m_nodes.length ());
+  log ("m_edges.length (): %i", m_edges.length ());
+
+  log ("global stats:");
+  m_global_stats.log (get_logger ());
+
+  for (function_stat_map_t::iterator iter = m_per_function_stats.begin ();
+       iter != m_per_function_stats.end ();
+       ++iter)
+    {
+      function *fn = (*iter).first;
+      log_scope s (get_logger (), function_name (fn));
+      (*iter).second->log (get_logger ());
+    }
+}
+
+/* Dump all stats information to OUT.  */
+
+void
+exploded_graph::dump_stats (FILE *out) const
+{
+  fprintf (out, "m_sg.num_nodes (): %i\n", m_sg.num_nodes ());
+  fprintf (out, "m_nodes.length (): %i\n", m_nodes.length ());
+  fprintf (out, "m_edges.length (): %i\n", m_edges.length ());
+
+  fprintf (out, "global stats:\n");
+  m_global_stats.dump (out);
+
+  for (function_stat_map_t::iterator iter = m_per_function_stats.begin ();
+       iter != m_per_function_stats.end ();
+       ++iter)
+    {
+      function *fn = (*iter).first;
+      fprintf (out, "function: %s\n", function_name (fn));
+      (*iter).second->dump (out);
+    }
+
+  fprintf (out, "PK_AFTER_SUPERNODE per supernode:\n");
+  for (unsigned i = 0; i < m_PK_AFTER_SUPERNODE_per_snode.length (); i++)
+    fprintf (out, "  SN %i: %3i\n", i, m_PK_AFTER_SUPERNODE_per_snode[i]);
+}
+
+void
+exploded_graph::dump_states_for_supernode (FILE *out,
+					   const supernode *snode) const
+{
+  fprintf (out, "PK_AFTER_SUPERNODE nodes for SN: %i\n", snode->m_index);
+  int i;
+  exploded_node *enode;
+  int state_idx = 0;
+  FOR_EACH_VEC_ELT (m_nodes, i, enode)
+    {
+      const supernode *iter_snode = enode->get_supernode ();
+      if (enode->get_point ().m_kind == PK_AFTER_SUPERNODE
+	  && iter_snode == snode)
+	fprintf (out, "state %i: EN: %i\n  %s\n",
+		 state_idx++, enode->m_index,
+		 enode->get_state ().to_str (m_ext_state).c_str ());
+    }
+  fprintf (out, "#exploded_node for PK_AFTER_SUPERNODE for SN: %i = %i\n",
+	   snode->m_index, state_idx);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* 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;
+}
+
+/* FIXME.  */
+
+exploded_node *
+exploded_path::get_final_enode () const
+{
+  gcc_assert (m_edges.length () > 0);
+  return m_edges[m_edges.length () - 1]->m_dest;
+}
+
+/* Check state along this path, returning true if it is feasible.  */
+
+bool
+exploded_path::feasible_p (logger *logger) const
+{
+  JIT_LOG_SCOPE (logger);
+
+  region_model model;
+  for (unsigned i = 0; i < m_edges.length (); i++)
+    {
+      const exploded_edge *eedge = m_edges[i];
+      if (logger)
+	logger->log ("considering edge %i: EN:%i -> EN:%i",
+		     i,
+		     eedge->m_src->m_index,
+		     eedge->m_dest->m_index);
+      const exploded_node &src_enode = *eedge->m_src;
+      const program_point &src_point = src_enode.get_point ();
+      if (logger)
+	src_point.print (logger->get_printer (), format (true));
+
+      if (const gimple *stmt = src_point.get_stmt ())
+	{
+	  /* Update cfun and input_location in case of ICE: make it easier to
+	     track down which source construct we're failing to handle.  */
+	  auto_cfun sentinel (src_point.get_function ());
+	  input_location = stmt->location;
+
+	  if (const gassign *assign = dyn_cast <const gassign *> (stmt))
+	    model.on_assignment (assign, NULL);
+	  else if (const greturn *return_ = dyn_cast <const greturn *> (stmt))
+	    model.on_return (return_, NULL);
+	}
+
+      const superedge *sedge = eedge->m_sedge;
+      if (sedge)
+	{
+	  if (logger)
+	    logger->log ("  sedge: SN:%i -> SN:%i %s",
+			 sedge->m_src->m_index,
+			 sedge->m_dest->m_index,
+			 sedge->get_description (false));
+
+	  const gimple *last_stmt = src_point.m_supernode->get_last_stmt ();
+	  if (!model.maybe_update_for_edge (*sedge, last_stmt, NULL))
+	    {
+	      if (logger)
+		logger->log ("rejecting due to region model");
+	      return false;
+	    }
+	}
+      else
+	{
+	  /* Special-case the initial eedge from the origin node to the
+	     initial function by pushing a frame for it.  */
+	  if (i == 0)
+	    {
+	      gcc_assert (eedge->m_src->m_index == 0);
+	      gcc_assert (src_point.m_kind == PK_ORIGIN);
+	      gcc_assert (eedge->m_dest->get_point ().m_kind
+			  == PK_BEFORE_SUPERNODE);
+	      function *fun = eedge->m_dest->get_function ();
+	      gcc_assert (fun);
+	      model.push_frame (fun, NULL);
+	      if (logger)
+		logger->log ("  pushing frame for %qD", fun->decl);
+	    }
+	}
+
+      /* Handle phi nodes on an edge leaving a PK_BEFORE_SUPERNODE (to
+	 a PK_BEFORE_STMT, or a PK_AFTER_SUPERNODE if no stmts).
+	 This will typically not be associated with a superedge.  */
+      if (src_point.m_from_edge)
+	{
+	  const cfg_superedge *last_cfg_superedge
+	    = src_point.m_from_edge->dyn_cast_cfg_superedge ();
+	  if (last_cfg_superedge)
+	    {
+	      if (logger)
+		logger->log ("  update for phis");
+	      model.update_for_phis (src_enode.get_supernode (),
+				     last_cfg_superedge,
+				     NULL);
+	    }
+	}
+
+      if (logger)
+	{
+	  pp_newline (logger->get_printer ());
+	  logger->log ("state after edge %i: EN:%i -> EN:%i",
+		       i,
+		       eedge->m_src->m_index,
+		       eedge->m_dest->m_index);
+	  model.dump_to_pp (logger->get_printer ());
+	  pp_newline (logger->get_printer ());
+	}
+    }
+
+  // TODO
+  return true;
+}
+
+/* FIXME.  */
+
+void
+exploded_path::dump_to_pp (pretty_printer *pp) const
+{
+  for (unsigned i = 0; i < m_edges.length (); i++)
+    {
+      const exploded_edge *eedge = m_edges[i];
+      pp_printf (pp, "m_edges[%i]: EN %i -> EN %i",
+		 i,
+		 eedge->m_src->m_index,
+		 eedge->m_dest->m_index);
+      pp_newline (pp);
+    }
+}
+
+/* FIXME.  */
+
+void
+exploded_path::dump (FILE *fp) const
+{
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  pp_show_color (&pp) = pp_show_color (global_dc->printer);
+  pp.buffer->stream = fp;
+  dump_to_pp (&pp);
+  pp_flush (&pp);
+}
+
+/* FIXME.  */
+
+DEBUG_FUNCTION void
+exploded_path::dump () const
+{
+  dump (stderr);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* 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_client_timevar tv ("shortest_paths");
+
+  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 (false));
+      result += std::string ("\"") + event_desc.m_buffer + std::string ("\"");
+      event_desc.maybe_free ();
+    }
+  result += "]";
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+// FIXME:
+/* Subclass of cluster which casts the dnode instances to exploded_node.  */
+
+class exploded_cluster : public cluster<eg_traits>
+{
+};
+
+/* Cluster containing all exploded_node instances for one supernode.  */
+
+class supernode_cluster : public exploded_cluster
+{
+public:
+  supernode_cluster (const supernode *supernode) : m_supernode (supernode) {}
+
+  // TODO: dtor?
+
+  void dump_dot (graphviz_out *gv, const dump_args_t &args) 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, args);
+
+    /* Terminate subgraph.  */
+    gv->outdent ();
+    gv->println ("}");
+  }
+
+  void add_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 exploded_cluster
+{
+public:
+  function_call_string_cluster (function *fun, call_string cs)
+  : m_fun (fun), m_cs (cs) {}
+
+  ~function_call_string_cluster ()
+  {
+    for (map_t::iterator iter = m_map.begin ();
+	 iter != m_map.end ();
+	 ++iter)
+      delete (*iter).second;
+  }
+
+  void dump_dot (graphviz_out *gv, const dump_args_t &args) 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, args);
+
+    /* Terminate subgraph.  */
+    gv->outdent ();
+    gv->println ("}");
+  }
+
+  void add_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_node (en);
+    else
+      {
+	supernode_cluster *child = new supernode_cluster (supernode);
+	m_map.put (supernode, child);
+	child->add_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 exploded_cluster
+{
+public:
+  ~root_cluster ()
+  {
+    for (map_t::iterator iter = m_map.begin ();
+	 iter != m_map.end ();
+	 ++iter)
+      delete (*iter).second;
+  }
+
+  void dump_dot (graphviz_out *gv, const dump_args_t &args) const FINAL OVERRIDE
+  {
+    int i;
+    exploded_node *enode;
+    FOR_EACH_VEC_ELT (m_functionless_enodes, i, enode)
+      enode->dump_dot (gv, args);
+
+    for (map_t::iterator iter = m_map.begin ();
+	 iter != m_map.end ();
+	 ++iter)
+      (*iter).second->dump_dot (gv, args);
+  }
+
+  void add_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_node (en);
+    else
+      {
+	function_call_string_cluster *child
+	  = new function_call_string_cluster (fun, cs);
+	m_map.put (key, child);
+	child->add_node (en);
+      }
+  }
+
+private:
+  /* This can't be an ordered_hash_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;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+state_purge_map::state_purge_map (const supergraph &sg,
+				  logger *logger)
+: log_user (logger), m_sg (sg)
+{
+  JIT_LOG_FUNC (logger);
+
+  auto_client_timevar tv ("state_purge_map ctor");
+
+  cgraph_node *node;
+  FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
+  {
+    function *fun = node->get_fun ();
+    if (logger)
+      log ("function: %s", function_name (fun));
+    tree var;
+    unsigned int i;;
+    FOR_EACH_SSA_NAME (i, var, fun)
+      m_map.put (var, new state_purge_per_ssa_name (*this, var, fun));
+  }
+}
+
+/* FIXME.  */
+
+state_purge_map::~state_purge_map ()
+{
+  for (iterator iter = m_map.begin (); iter != m_map.end (); ++iter)
+    delete (*iter).second;
+}
+
+/* state_purge_per_ssa_name's ctor.
+
+   Locate all uses of VAR within FUN.
+   Walk backwards from each use, marking program points, until
+   we reach the def stmt, populating m_points_needing_var.
+
+   We have to track program points rather than
+   just stmts since there could be empty basic blocks on the way.  */
+
+state_purge_per_ssa_name::state_purge_per_ssa_name (const state_purge_map &map,
+						    tree var,
+						    function *fun)
+: m_var (var)
+{
+  JIT_LOG_FUNC (map.get_logger ());
+
+  map.log ("SSA name: %qE within %qD", var, fun->decl);
+  if (map.get_logger ())
+    {
+      /* Show def stmt.  */
+      gimple *def_stmt = SSA_NAME_DEF_STMT (var);
+      pretty_printer pp;
+      pp_gimple_stmt_1 (&pp, def_stmt, 0, (dump_flags_t)0);
+      map.log ("def stmt: %s", pp_formatted_text (&pp));
+    }
+
+  /* The worklist for the traversal.
+     FIXME: worklist can't be a vec<program_point>; we can't have a
+     vec<program_point> because vec<T> require T to be a POD.
+     Hence (annoyingly) we dynamically allocate.  */
+  auto_vec<program_point *> worklist;
+
+  /* Add all immediate uses of var to the worklist.
+     Compare with debug_immediate_uses.  */
+  imm_use_iterator iter;
+  use_operand_p use_p;
+  FOR_EACH_IMM_USE_FAST (use_p, iter, var)
+    {
+      if (USE_STMT (use_p))
+	{
+	  gimple *use_stmt = USE_STMT (use_p);
+	  if (map.get_logger ())
+	    {
+	      pretty_printer pp;
+	      pp_gimple_stmt_1 (&pp, use_stmt, 0, (dump_flags_t)0);
+	      map.log ("used by stmt: %s", pp_formatted_text (&pp));
+	    }
+
+	  const supernode *snode = map.m_sg.get_supernode_for_stmt (use_stmt);
+
+	  /* If it's a use within a phi node, then we care about
+	     which in-edge we came from.  */
+	  if (use_stmt->code == GIMPLE_PHI)
+	    {
+	      for (gphi_iterator gpi
+		     = const_cast<supernode *> (snode)->start_phis ();
+		   !gsi_end_p (gpi); gsi_next (&gpi))
+		{
+		  gphi *phi = gpi.phi ();
+		  if (phi == use_stmt)
+		    {
+		      /* Find arguments (and thus in-edges) which use VAR.  */
+		      for (unsigned arg_idx = 0;
+			   arg_idx < gimple_phi_num_args (phi);
+			   ++arg_idx)
+			{
+			  if (var == gimple_phi_arg (phi, arg_idx)->def)
+			    {
+			      edge in_edge = gimple_phi_arg_edge (phi, arg_idx);
+			      const superedge *in_sedge
+				= map.m_sg.get_edge_for_cfg_edge (in_edge);
+			      program_point point
+				= program_point::before_supernode
+				(snode, in_sedge, call_string ());
+			      add_to_worklist (point, &worklist,
+					       map.get_logger ());
+			      m_points_needing_var.add (point);
+			    }
+			}
+		    }
+		}
+	    }
+	  else
+	    {
+	      program_point point = before_use_stmt (map, use_stmt);
+	      add_to_worklist (point, &worklist, map.get_logger ());
+	      m_points_needing_var.add (point);
+
+	      /* We also need to add uses for conditionals and switches,
+		 where the stmt "happens" at the after_supernode, for filtering
+		 the out-edges.  */
+	      if (use_stmt == snode->get_last_stmt ())
+		{
+		  if (map.get_logger ())
+		    map.log ("last stmt in BB");
+		  program_point point
+		    = program_point::after_supernode (snode, call_string ());
+		  add_to_worklist (point, &worklist, map.get_logger ());
+		  m_points_needing_var.add (point);
+		}
+	      else
+		if (map.get_logger ())
+		  map.log ("not last stmt in BB");
+	    }
+	}
+    }
+
+  /* Process worklist by walking backwards until we reach the def stmt.  */
+  {
+    log_scope s (map.get_logger (), "processing worklist");
+    while (worklist.length () > 0)
+      {
+	program_point *point_ptr = worklist.pop ();
+	program_point point (*point_ptr);
+	process_point (*point_ptr, &worklist, map.get_logger ());
+	delete point_ptr;
+    }
+  }
+
+  if (map.get_logger ())
+    {
+      map.log ("%qE in %qD is needed to process:", var, fun->decl);
+      for (point_set_t::iterator iter = m_points_needing_var.begin ();
+	   iter != m_points_needing_var.end ();
+	   ++iter)
+	{
+	  format f (false);
+	  map.log ("  point: %s", (*iter).to_str (f).c_str ());
+	}
+    }
+}
+
+/* FIXME.  */
+
+bool
+state_purge_per_ssa_name::needed_at_point_p (const program_point &point) const
+{
+  return const_cast <point_set_t &> (m_points_needing_var).contains (point);
+}
+
+/* FIXME.  */
+
+program_point
+state_purge_per_ssa_name::before_use_stmt (const state_purge_map &map,
+					   const gimple *use_stmt)
+{
+  gcc_assert (use_stmt->code != GIMPLE_PHI); // FIXME: for now
+#if 0
+  if (stmt->code == GIMPLE_PHI)
+    {
+      const supernode *supernode = sg.get_node_for_block (stmt->bb);
+      return before_supernode (supernode, from_edge, call_string);
+    }
+  else
+#endif
+    {
+      const supernode *supernode = map.m_sg.get_supernode_for_stmt (use_stmt);
+      unsigned int stmt_idx = supernode->get_stmt_index (use_stmt);
+      return program_point::before_stmt (supernode, stmt_idx, call_string ());
+    }
+}
+
+/* FIXME.  */
+
+void
+state_purge_per_ssa_name::add_to_worklist (const program_point &point,
+					   auto_vec<program_point *> *worklist,
+					   logger *logger)
+{
+  JIT_LOG_FUNC (logger);
+  if (logger)
+    {
+      format f (false);
+      logger->log ("point: %qs for worklist for %qE",
+		   point.to_str (f).c_str (), m_var);
+    }
+
+  if (m_points_needing_var.contains (point))
+    {
+      if (logger)
+	logger->log ("already seen for %qE", m_var);
+    }
+  else
+    {
+      if (logger)
+	logger->log ("not seen; adding to worklist for %qE", m_var);
+      worklist->safe_push (new program_point (point));
+    }
+}
+
+/* Process POINT, popped from WORKLIST.
+   Iterate over predecessors of POINT, adding to WORKLIST.  */
+
+void
+state_purge_per_ssa_name::process_point (const program_point &point,
+					 auto_vec<program_point *> *worklist,
+					 logger *logger)
+{
+  JIT_LOG_FUNC (logger);
+  if (logger)
+    {
+      format f (false);
+      logger->log ("considering point: %qs for %qE",
+		   point.to_str (f).c_str (), m_var);
+    }
+
+  gimple *def_stmt = SSA_NAME_DEF_STMT (m_var);
+
+  m_points_needing_var.add (point);
+
+  const supernode *snode = point.get_supernode ();
+
+  switch (point.m_kind)
+    {
+    default:
+      gcc_unreachable ();
+
+    case PK_ORIGIN:
+      break;
+
+    case PK_BEFORE_SUPERNODE:
+      {
+	for (gphi_iterator gpi
+	       = const_cast<supernode *> (snode)->start_phis ();
+	     !gsi_end_p (gpi); gsi_next (&gpi))
+	  {
+	    gphi *phi = gpi.phi ();
+	    if (phi == def_stmt)
+	      {
+		if (logger)
+		  logger->log ("def stmt within phis; terminating");
+		return;
+	      }
+	  }
+
+	/* Add given pred to worklist.  */
+	if (point.m_from_edge)
+	  {
+	    if (point.m_from_edge->m_src)
+	      add_to_worklist
+		(program_point::after_supernode (point.m_from_edge->m_src,
+						 call_string ()),
+		 worklist, logger);
+	  }
+	else
+	  gcc_assert (snode->entry_p ());
+      }
+      break;
+
+    case PK_BEFORE_STMT:
+      {
+	if (def_stmt == point.get_stmt ())
+	  {
+	    if (logger)
+	      logger->log ("def stmt; terminating");
+	    return;
+	  }
+	if (point.m_stmt_idx > 0)
+	  add_to_worklist (program_point::before_stmt (snode,
+						       point.m_stmt_idx - 1,
+						       call_string ()),
+			   worklist, logger);
+	else
+	{
+	  /* Add before_supernode to worklist.  This captures the in-edge,
+	     so we have to do it once per in-edge.  */
+	  unsigned i;
+	  superedge *pred;
+	  FOR_EACH_VEC_ELT (snode->m_preds, i, pred)
+	    add_to_worklist (program_point::before_supernode (snode,
+							      pred,
+							      call_string ()),
+			     worklist, logger);
+	}
+      }
+      break;
+
+    case PK_AFTER_SUPERNODE:
+      {
+	if (snode->m_stmts.length ())
+	  add_to_worklist
+	    (program_point::before_stmt (snode,
+					 snode->m_stmts.length () - 1,
+					 call_string ()),
+	     worklist, logger);
+	else
+	  {
+	    /* Add before_supernode to worklist.  This captures the in-edge,
+	       so we have to do it once per in-edge.  */
+	    unsigned i;
+	    superedge *pred;
+	    FOR_EACH_VEC_ELT (snode->m_preds, i, pred)
+	      add_to_worklist (program_point::before_supernode (snode,
+								pred,
+								call_string ()),
+			       worklist, logger);
+	    /* If it's the initial BB, add it, to ensure that we
+	       have "before supernode" for the initial ENTRY block, and don't
+	       erroneously purge SSA names for initial values of parameters.  */
+	    if (snode->entry_p ())
+	      {
+		add_to_worklist
+		  (program_point::before_supernode (snode,
+						    NULL,
+						    call_string ()),
+		   worklist, logger);
+	      }
+	  }
+      }
+      break;
+    }
+}
+
+/* FIXME.  */
+
+class state_purge_annotator  : public dot_annotator
+{
+public:
+  state_purge_annotator (const state_purge_map *map) : m_map (map) {}
+
+  void add_node_annotations (pretty_printer *pp ATTRIBUTE_UNUSED,
+			     const supernode &n ATTRIBUTE_UNUSED)
+    const FINAL OVERRIDE
+  {
+    if (m_map == NULL)
+      return;
+
+#if 0
+    //pp_printf (pp, "FIXME: node annotation");
+    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: which in-edge?
+    program_point before_supernode
+      (program_point::before_supernode (&n, call_string ()));
+
+    for (state_purge_map::iterator iter = m_map.begin ();
+	 iter != m_map.end ();
+	 ++iter)
+      {
+	tree var = (*iter).first;
+	state_purge_per_ssa_name *per_var_data = (*iter).second;
+	if (per_var_data->needed_at_point_p (before_supernode))
+	  pp_printf (pp, "%qE needed here", var);
+	else
+	  pp_printf (pp, "%qE not needed here", var);
+	pp_newline (pp);
+      }
+
+    pp_right_brace (pp);
+
+    pp_string (pp, "\"];\n\n");
+    pp_flush (pp);
+#endif
+  }
+
+  void add_stmt_annotations (pretty_printer *pp ATTRIBUTE_UNUSED,
+			     const gimple *stmt ATTRIBUTE_UNUSED)
+    const FINAL OVERRIDE
+  {
+    if (m_map == NULL)
+      return;
+
+    if (stmt->code == GIMPLE_PHI)
+      return;
+
+    pp_newline (pp);
+
+    const supernode *supernode = m_map->m_sg.get_supernode_for_stmt (stmt);
+    unsigned int stmt_idx = supernode->get_stmt_index (stmt);
+    program_point before_stmt
+      (program_point::before_stmt (supernode, stmt_idx, call_string ()));
+
+    auto_vec<tree> needed;
+    auto_vec<tree> not_needed;
+    for (state_purge_map::iterator iter = m_map->begin ();
+	 iter != m_map->end ();
+	 ++iter)
+      {
+	tree var = (*iter).first;
+	state_purge_per_ssa_name *per_var_data = (*iter).second;
+	if (per_var_data->needed_at_point_p (before_stmt))
+	  needed.safe_push (var);
+	else
+	  not_needed.safe_push (var);
+      }
+
+    tree var;
+    unsigned i;
+    pp_printf (pp, "needed here: {");
+    FOR_EACH_VEC_ELT (needed, i, var)
+      {
+	if (i > 0)
+	  pp_string (pp, ", ");
+PUSH_IGNORE_WFORMAT
+	pp_printf (pp, "%qE", var);
+POP_IGNORE_WFORMAT
+      }
+    pp_printf (pp, "}");
+    pp_newline (pp);
+    pp_printf (pp, "not needed here: {");
+    FOR_EACH_VEC_ELT (not_needed, i, var)
+      {
+	if (i > 0)
+	  pp_string (pp, ", ");
+PUSH_IGNORE_WFORMAT
+	pp_printf (pp, "%qE", var);
+POP_IGNORE_WFORMAT
+      }
+    pp_newline (pp);
+  }
+
+private:
+  const state_purge_map *m_map;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+#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.  */
+
+class enode_label : public range_label
+{
+ public:
+  enode_label (exploded_node *enode) : m_enode (enode) {}
+
+  label_text get_text (unsigned) const FINAL OVERRIDE
+  {
+    return label_text::take (print (false, "EN: %i", m_enode->m_index));
+  }
+
+private:
+  exploded_node *m_enode;
+};
+
+/* Postprocessing support for dumping the exploded nodes.
+   Handle -fdump-analyzer-exploded-nodes,
+   -fdump-analyzer-exploded-nodes-2, and the
+   "__analyzer_dump_exploded_nodes" builtin.  */
+
+void
+exploded_graph::dump_exploded_nodes () const
+{
+  // TODO
+  /* Locate calls to __analyzer_dump_exploded_nodes.  */
+  // Print how many egs there are for them?
+  /* Better: log them as we go, and record the exploded nodes
+     in question.  */
+
+  /* Show every enode.  */
+
+  /* Gather them by stmt, so that we can more clearly see the
+     "hotspots" requiring numerous exploded nodes.  */
+
+  /* Alternatively, simply throw them all into one big rich_location
+     and see if the label-printing will sort it out...
+     This requires them all to be in the same source file.  */
+
+  if (flag_dump_analyzer_exploded_nodes)
+    {
+      auto_client_timevar tv ("-fdump-analyzer-exploded-nodes");
+      gcc_rich_location richloc (UNKNOWN_LOCATION);
+      unsigned i;
+      exploded_node *enode;
+      FOR_EACH_VEC_ELT (m_nodes, i, enode)
+	{
+	  //const supernode *iter_snode = enode->get_supernode ();
+	  if (const gimple *stmt = enode->get_stmt ())
+	    {
+	      if (richloc.get_loc () == UNKNOWN_LOCATION)
+		richloc.set_range (0, stmt->location, SHOW_RANGE_WITH_CARET);
+	      else
+		richloc.add_range (stmt->location,
+				   SHOW_RANGE_WITHOUT_CARET,
+				   new enode_label (enode));
+	    }
+	}
+      warning_at (&richloc, 0, "%i exploded nodes", m_nodes.length ());
+
+      /* Repeat the warning without all the labels, so that message is visible
+	 (the other one may well have scrolled past the terminal limit).  */
+      warning_at (richloc.get_loc (), 0,
+		  "%i exploded nodes", m_nodes.length ());
+
+      if (m_worklist.length () > 0)
+	warning_at (richloc.get_loc (), 0,
+		    "worklist still contains %i nodes", m_worklist.length ());
+    }
+
+  /* Dump the egraph in textual form to a dump file.  */
+  if (flag_dump_analyzer_exploded_nodes_2)
+    {
+      auto_client_timevar tv ("-fdump-analyzer-exploded-nodes-2");
+      char *filename
+	= concat (dump_base_name, ".eg.txt", NULL);
+      FILE *outf = fopen (filename, "w");
+      if (!outf)
+	error_at (UNKNOWN_LOCATION, "unable to open %qs for writing", filename);
+      free (filename);
+
+      fprintf (outf, "exploded graph for %s\n", dump_base_name);
+      fprintf (outf, "  nodes: %i\n", m_nodes.length ());
+      fprintf (outf, "  edges: %i\n", m_edges.length ());
+
+      unsigned i;
+      exploded_node *enode;
+      FOR_EACH_VEC_ELT (m_nodes, i, enode)
+	{
+	  fprintf (outf, "\nEN %i:\n", enode->m_index);
+	  enode->dump_succs_and_preds (outf);
+	  pretty_printer pp;
+	  enode->get_point ().print (&pp, format (true));
+	  fprintf (outf, "%s\n", pp_formatted_text (&pp));
+	  enode->get_state ().dump_to_file (m_ext_state, outf);
+	}
+
+      fclose (outf);
+    }
+
+  /* Emit a warning at any call to "__analyzer_dump_exploded_nodes",
+     giving the number of exploded nodes for "before-stmt", and their
+     IDs.  */
+
+  unsigned i;
+  exploded_node *enode;
+  hash_set<const gimple *> seen;
+  FOR_EACH_VEC_ELT (m_nodes, i, enode)
+    {
+      if (enode->get_point ().m_kind != PK_BEFORE_STMT)
+	continue;
+
+      if (const gimple *stmt = enode->get_stmt ())
+	if (const gcall *call = dyn_cast <const gcall *> (stmt))
+	  if (is_named_call_p (call, "__analyzer_dump_exploded_nodes", 1))
+	    {
+	      if (seen.contains (stmt))
+		continue;
+
+	      /* This is O(N^2).  */
+	      unsigned j;
+	      auto_vec<exploded_node *> enodes;
+	      exploded_node *other_enode;
+	      FOR_EACH_VEC_ELT (m_nodes, j, other_enode)
+		{
+		  if (other_enode->get_point ().m_kind != PK_BEFORE_STMT)
+		    continue;
+		  if (other_enode->get_stmt () == stmt)
+		    enodes.safe_push (other_enode);
+		}
+
+	      pretty_printer pp;
+	      print_enode_indices (&pp, enodes);
+
+	      warning_n (stmt->location, 0, enodes.length (),
+			 "%i exploded node: %s",
+			 "%i exploded nodes: %s",
+			 enodes.length (), pp_formatted_text (&pp));
+	      seen.add (stmt);
+
+	      /* If the argument is non-zero, then print all of the states
+		 of the various enodes.  */
+	      tree t_arg = fold (gimple_call_arg (call, 0));
+	      if (TREE_CODE (t_arg) != INTEGER_CST)
+		{
+		  error_at (call->location,
+			    "integer constant required for arg 1");
+		  return;
+		}
+	      int i_arg = TREE_INT_CST_LOW (t_arg);
+	      if (i_arg)
+		{
+		  exploded_node *other_enode;
+		  FOR_EACH_VEC_ELT (enodes, j, other_enode)
+		    {
+		      fprintf (stderr, "%i of %i: EN %i:\n",
+			       j + 1, enodes.length (), other_enode->m_index);
+		      other_enode->dump_succs_and_preds (stderr);
+		      /* Dump state.  */
+		      other_enode->get_state ().dump (m_ext_state);
+		    }
+		}
+	    }
+    }
+}
+
+/* FIXME.  */
+
+void
+impl_run_checkers (logger *logger)
+{
+  JIT_LOG_SCOPE (logger);
+
+  /* Create the supergraph.  */
+  supergraph sg (logger);
+
+  state_purge_map *purge_map = NULL;
+
+  if (flag_analyzer_state_purge)
+      purge_map = new state_purge_map (sg, logger);
+
+  if (flag_dump_analyzer_supergraph)
+    {
+      auto_client_timevar tv ("writing .supergraph.dot");
+      //state_purge_annotator a (purge_map);
+      char *filename = concat (dump_base_name, ".supergraph.dot", NULL);
+      //supergraph::dump_args_t args ((enum supergraph_dot_flags)0, &a);
+      supergraph::dump_args_t args ((enum supergraph_dot_flags)0, NULL);
+      sg.dump_dot (filename, args);
+      free (filename);
+    }
+
+#if 0
+  // FIXME: early exit (while debugging state_purge_map)
+  return;
+#endif
+
+  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 ());
+    }
+
+  /* The exploded graph.  */
+  impl_4::exploded_graph eg (sg, logger, checkers, purge_map,
+			     analyzer_verbosity);
+
+  /* Add entrypoints to the graph for externally-callable functions.  */
+  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);
+  }
+
+  /* Now process the worklist, exploring the <point, state> graph.  */
+  eg.process_worklist ();
+
+  if (flag_dump_analyzer_exploded_graph)
+    {
+      auto_client_timevar tv ("writing .eg.dot");
+      char *filename
+	= concat (dump_base_name, ".eg.dot", NULL);
+      exploded_graph::dump_args_t args;
+      root_cluster c;
+      eg.dump_dot (filename, &c, args);
+      free (filename);
+    }
+
+  /* Now emit any saved diagnostics.  */
+  eg.get_diagnostic_manager ().emit_saved_diagnostics (eg);
+
+  eg.dump_exploded_nodes ();
+
+  eg.log_stats ();
+
+  delete purge_map;
+}
+
+} // 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_global_decl ("x", integer_type_node);
+  tree y = build_global_decl ("y", integer_type_node);
+  tree z = build_global_decl ("z", integer_type_node);
+
+  /* Test setting states on svalue_id instances directly.  */
+  {
+    region_model model;
+    svalue_id sid_x = model.get_rvalue (x, NULL);
+    svalue_id sid_y = model.get_rvalue (y, NULL);
+    svalue_id sid_z = model.get_rvalue (z, NULL);
+
+    impl_4::sm_state_map map;
+    ASSERT_TRUE (map.is_empty_p ());
+    ASSERT_EQ (map.get_state (sid_x), 0);
+
+    map.impl_set_state (sid_x, 42, sid_z);
+    ASSERT_EQ (map.get_state (sid_x), 42);
+    ASSERT_EQ (map.get_origin (sid_x), sid_z);
+    ASSERT_EQ (map.get_state (sid_y), 0);
+    ASSERT_FALSE (map.is_empty_p ());
+
+    map.impl_set_state (sid_y, 0, sid_z);
+    ASSERT_EQ (map.get_state (sid_y), 0);
+
+    map.impl_set_state (sid_x, 0, sid_z);
+    ASSERT_EQ (map.get_state (sid_x), 0);
+    ASSERT_TRUE (map.is_empty_p ());
+  }
+
+  /* Test setting states via equivalence classes.  */
+  {
+    region_model model;
+    svalue_id sid_x = model.get_rvalue (x, NULL);
+    svalue_id sid_y = model.get_rvalue (y, NULL);
+    svalue_id sid_z = model.get_rvalue (z, NULL);
+
+    impl_4::sm_state_map map;
+    ASSERT_TRUE (map.is_empty_p ());
+    ASSERT_EQ (map.get_state (sid_x), 0);
+    ASSERT_EQ (map.get_state (sid_y), 0);
+
+    model.add_constraint (x, EQ_EXPR, y, NULL);
+
+    /* Setting x to a state should also update y, as they
+       are in the same equivalence class.  */
+    map.set_state (&model, sid_x, 5, sid_z);
+    ASSERT_EQ (map.get_state (sid_x), 5);
+    ASSERT_EQ (map.get_state (sid_y), 5);
+    ASSERT_EQ (map.get_origin (sid_x), sid_z);
+    ASSERT_EQ (map.get_origin (sid_y), sid_z);
+  }
+
+  /* Test equality and hashing.  */
+  {
+    region_model model;
+    //svalue_id sid_x = model.get_rvalue (x, NULL);
+    svalue_id sid_y = model.get_rvalue (y, NULL);
+    svalue_id sid_z = model.get_rvalue (z, NULL);
+
+    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 (sid_y, 5, sid_z);
+    ASSERT_NE (map0.hash (), map1.hash ());
+    ASSERT_NE (map0, map1);
+
+    /* Make the same change to map2.  */
+    map2.impl_set_state (sid_y, 5, sid_z);
+    ASSERT_EQ (map1.hash (), map2.hash ());
+    ASSERT_EQ (map1, map2);
+  }
+
+  /* Test sm_state_map::remap_svalue_ids.  */
+  {
+    impl_4::sm_state_map map;
+    svalue_id sid_0 = svalue_id::from_int (0);
+    svalue_id sid_1 = svalue_id::from_int (1);
+    svalue_id sid_2 = svalue_id::from_int (2);
+
+    map.impl_set_state (sid_0, 42, sid_2);
+    ASSERT_EQ (map.get_state (sid_0), 42);
+    ASSERT_EQ (map.get_origin (sid_0), sid_2);
+    ASSERT_EQ (map.get_state (sid_1), 0);
+    ASSERT_EQ (map.get_state (sid_2), 0);
+
+    /* Apply a remapping to the IDs.  */
+    svalue_id_map remapping (3);
+    remapping.put (sid_0, sid_1);
+    remapping.put (sid_1, sid_2);
+    remapping.put (sid_2, sid_0);
+    map.remap_svalue_ids (remapping);
+
+    /* Verify that the IDs have been remapped.  */
+    ASSERT_EQ (map.get_state (sid_1), 42);
+    ASSERT_EQ (map.get_origin (sid_1), sid_0);
+    ASSERT_EQ (map.get_state (sid_2), 0);
+    ASSERT_EQ (map.get_state (sid_0), 0);
+  }
+
+  // TODO: coverage for purging
+}
+
+/* Verify that program_point::operator== works as expected.  */
+
+static void
+test_program_point_equality ()
+{
+  const supernode *snode = NULL;
+
+  const impl_4::call_string cs;
+
+  program_point a = program_point (snode, NULL, 0,
+				   impl_4::PK_BEFORE_SUPERNODE,
+				   cs);
+
+  program_point b = program_point (snode, NULL, 0,
+				   impl_4::PK_BEFORE_SUPERNODE,
+				   cs);
+  ASSERT_EQ (a, b);
+  // TODO: verify with non-empty callstrings, with different edges
+}
+
+/* Verify that program_point::cmp_within_supernode works as expected.  */
+
+static void
+test_program_point_ordering ()
+{
+  const supernode *snode = NULL;
+  const impl_4::call_string call_string;
+
+  /* Populate an array with various points within the same
+     snode, in order.  */
+  auto_delete_vec<program_point> points;
+  points.safe_push (new program_point (snode, NULL, 0,
+				       impl_4::PK_BEFORE_SUPERNODE,
+				       call_string));
+  points.safe_push (new program_point (snode, NULL, 0,
+				       impl_4::PK_BEFORE_STMT,
+				       call_string));
+  points.safe_push (new program_point (snode, NULL, 1,
+				       impl_4::PK_BEFORE_STMT,
+				       call_string));
+  points.safe_push (new program_point (snode, NULL, 0,
+				       impl_4::PK_AFTER_SUPERNODE,
+				       call_string));
+  /* Check all pairs.  */
+  unsigned i;
+  program_point *point_a;
+  FOR_EACH_VEC_ELT (points, i, point_a)
+    {
+      unsigned j;
+      program_point *point_b;
+      FOR_EACH_VEC_ELT (points, j, point_b)
+	{
+	  int cmp = program_point::cmp_within_supernode (*point_a, *point_b);
+	  if (i == j)
+	    ASSERT_EQ (cmp, 0);
+	  if (i < j)
+	    ASSERT_TRUE (cmp < 0);
+	  if (i > j)
+	    ASSERT_TRUE (cmp > 0);
+	}
+    }
+}
+
+/* Verify that program_states with identical sm-state can be merged,
+   and that the merged program_state preserves the sm-state.  */
+
+static void
+test_program_state_merging ()
+{
+  /* Create a program_state for a global ptr "p" that has
+     malloc sm-state, pointing to a region on the heap.  */
+  tree p = build_global_decl ("p", ptr_type_node);
+
+  using namespace impl_4;
+  auto_delete_vec <state_machine> checkers;
+  checkers.safe_push (make_malloc_state_machine (NULL));
+  extrinsic_state ext_state (checkers);
+
+  program_state s0 (ext_state);
+  impl_region_model_context ctxt (&s0, ext_state);
+
+  region_model *model0 = s0.m_region_model;
+  region_id new_rid = model0->add_new_malloc_region ();
+  svalue_id ptr_sid
+      = model0->get_or_create_ptr_svalue (ptr_type_node, new_rid);
+  model0->set_value (model0->get_lvalue (p, &ctxt),
+		     ptr_sid);
+  sm_state_map *smap = s0.m_checker_states[0];
+  const state_machine::state_t TEST_STATE = 3;
+  smap->impl_set_state (ptr_sid, TEST_STATE, svalue_id::null ());
+  ASSERT_EQ (smap->get_state (ptr_sid), TEST_STATE);
+
+  model0->canonicalize (&ctxt);
+
+  /* Verify that canonicalization preserves sm-state.  */
+  ASSERT_EQ (smap->get_state (model0->get_rvalue (p, NULL)), TEST_STATE);
+
+  /* Make a copy of the program_state.  */
+  program_state s1 (s0);
+  ASSERT_EQ (s0, s1);
+
+  /* We have two identical states with "p" pointing to a heap region
+     with the given sm-state.
+     They ought to be mergeable, preserving the sm-state.  */
+  program_state merged (ext_state);
+  ASSERT_TRUE (s0.can_merge_with_p (s1, ext_state, &merged));
+  merged.validate (ext_state);
+
+  /* Verify that the merged state has the sm-state for "p".  */
+  region_model *merged_model = merged.m_region_model;
+  sm_state_map *merged_smap = merged.m_checker_states[0];
+  ASSERT_EQ (merged_smap->get_state (merged_model->get_rvalue (p, NULL)),
+	     TEST_STATE);
+
+  /* Try canonicalizing.  */
+  impl_region_model_context merged_ctxt (&merged, ext_state);
+  merged.m_region_model->canonicalize (&merged_ctxt);
+  merged.validate (ext_state);
+
+  /* Verify that the merged state still has the sm-state for "p".  */
+  ASSERT_EQ (merged_smap->get_state (merged_model->get_rvalue (p, NULL)),
+	     TEST_STATE);
+
+  /* After canonicalization, we ought to have equality with the inputs.  */
+  ASSERT_EQ (s0, merged);
+}
+
+/* FIXME.  */
+
+static void
+test_assignments ()
+{
+  /* Verify X = Y, then Y = Z.
+     Should propagate states accordingly.  */
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+analyzer_impl_4_cc_tests ()
+{
+  test_sm_state_map ();
+  test_program_point_equality ();
+  test_program_point_ordering ();
+  test_program_state_merging ();
+  test_assignments ();
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
diff --git a/gcc/analyzer/path-state.cc b/gcc/analyzer/path-state.cc
new file mode 100644
index 0000000..d8ce6a6
--- /dev/null
+++ b/gcc/analyzer/path-state.cc
@@ -0,0 +1,2272 @@
+/* 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/analyzer-logging.h"
+#include "stringpool.h"
+#include "selftest.h"
+#include "fold-const.h"
+#include "analyzer/tristate.h"
+#include "analyzer/path-state.h"
+#include "analyzer/supergraph.h"
+
+using namespace psn;
+
+/* A subclass of purge_criteria: purge a specific path_var.  */
+
+class purge_one_path_var : public purge_criteria
+{
+public:
+  purge_one_path_var (path_var victim) : m_victim (victim) {}
+
+  bool should_purge_p (path_var pv) const FINAL OVERRIDE
+  {
+    return pv == m_victim;
+  }
+
+private:
+  path_var m_victim;
+};
+
+/* 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
+
+  if (m_vars.length () != other.m_vars.length ())
+    return false;
+
+  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;
+}
+
+/* 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 *
+psn::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;
+  if (m_constraints.length () != other.m_constraints.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.  */
+  purge_one_path_var p (lhs);
+  purge (p);
+
+  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;
+}
+
+/* A subclass of purge_criteria: purge anything above a given stack depth.  */
+
+class purge_above_stack_frame : public purge_criteria
+{
+public:
+  purge_above_stack_frame (int max_stack_depth)
+  : m_max_stack_depth (max_stack_depth) {}
+
+  bool should_purge_p (path_var pv) const FINAL OVERRIDE
+  {
+    return pv.m_stack_depth > m_max_stack_depth;
+  }
+
+private:
+  int m_max_stack_depth;
+};
+
+
+/* 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 caller_stack_depth = --m_stack_depth;
+
+  purge_above_stack_frame p (caller_stack_depth);
+  purge (p);
+}
+
+/* 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;
+    }
+  validate ();
+  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;
+}
+
+/* Delete any information about path_var instances identified by P.
+   Such instances are removed from equivalence classes, and any
+   redundant ECs and constraints are also removed.
+   Return the number of path_vars that are purged.  */
+
+int
+path_state::purge (const purge_criteria &p)
+{
+  int num_purged = 0;
+
+  /* Delete any path_vars identified by P within the various equivalence
+     classes.  */
+  for (unsigned ec_idx = 0; ec_idx < m_equiv_classes.length (); )
+    {
+      equiv_class *ec = m_equiv_classes[ec_idx];
+
+      int i;
+      path_var *pv;
+      bool delete_ec = false;
+      FOR_EACH_VEC_ELT (ec->m_vars, i, pv)
+	{
+	  if (p.should_purge_p (*pv))
+	    {
+	      num_purged++;
+	      if (ec->del (*pv))
+		if (!ec->m_constant)
+		  delete_ec = true;
+	    }
+	}
+
+      if (delete_ec)
+	{
+	  delete ec;
+	  m_equiv_classes.ordered_remove (ec_idx);
+
+	  /* Update the constraints, potentially removing some.  */
+	  for (unsigned con_idx = 0; con_idx < m_constraints.length (); )
+	    {
+	      constraint *c = &m_constraints[con_idx];
+
+	      /* Remove constraints that refer to the deleted EC.  */
+	      if (c->m_lhs == ec_idx
+		  || c->m_rhs == ec_idx)
+		{
+		  m_constraints.ordered_remove (con_idx);
+		}
+	      else
+		{
+		  /* Renumber constraints that refer to ECs that have
+		     had their idx changed.  */
+		  c->m_lhs.update_for_removal (ec_idx);
+		  c->m_rhs.update_for_removal (ec_idx);
+
+		  con_idx++;
+		}
+	    }
+	}
+      else
+	ec_idx++;
+    }
+
+  /* Now delete any constraints that are purely between constants.  */
+  for (unsigned con_idx = 0; con_idx < m_constraints.length (); )
+    {
+      constraint *c = &m_constraints[con_idx];
+      if (m_equiv_classes[c->m_lhs.m_idx]->m_vars.length () == 0
+	  && m_equiv_classes[c->m_rhs.m_idx]->m_vars.length () == 0)
+	m_constraints.ordered_remove (con_idx);
+      else
+	{
+	  con_idx++;
+	}
+    }
+
+  /* Finally, delete any ECs that purely contain constants and aren't
+     referenced by any constraints.  */
+  for (unsigned ec_idx = 0; ec_idx < m_equiv_classes.length (); )
+    {
+      equiv_class *ec = m_equiv_classes[ec_idx];
+      if (ec->m_vars.length () == 0)
+	{
+	  bool has_constraint = false;
+	  for (unsigned con_idx = 0; con_idx < m_constraints.length ();
+	       con_idx++)
+	    {
+	      constraint *c = &m_constraints[con_idx];
+	      if (c->m_lhs.m_idx == ec_idx
+		  || c->m_rhs.m_idx == ec_idx)
+		{
+		  has_constraint = true;
+		  break;
+		}
+	    }
+	  if (!has_constraint)
+	    {
+	      delete ec;
+	      m_equiv_classes.ordered_remove (ec_idx);
+
+	      /* Renumber constraints that refer to ECs that have
+		 had their idx changed.  */
+	      for (unsigned con_idx = 0; con_idx < m_constraints.length ();
+		   con_idx++)
+		{
+		  constraint *c = &m_constraints[con_idx];
+		  c->m_lhs.update_for_removal (ec_idx);
+		  c->m_rhs.update_for_removal (ec_idx);
+		}
+	      continue;
+	    }
+	}
+      ec_idx++;
+    }
+
+  validate ();
+
+  return num_purged;
+}
+
+/* FIXME.  */
+
+void
+path_state::validate () const
+{
+  // TODO: skip this in a release build
+
+  int i;
+  equiv_class *ec;
+  FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+    {
+      gcc_assert (ec);
+
+      int j;
+      path_var *pv;
+      FOR_EACH_VEC_ELT (ec->m_vars, j, pv)
+	{
+	  gcc_assert (!CONSTANT_CLASS_P (pv->m_tree));
+	  //gcc_assert (pv->m_stack_depth <= m_stack_depth);
+	}
+      if (ec->m_constant)
+	gcc_assert (CONSTANT_CLASS_P (ec->m_constant));
+      else
+	gcc_assert (ec->m_vars.length () > 0);
+    }
+
+  constraint *c;
+  FOR_EACH_VEC_ELT (m_constraints, i, c)
+    {
+      gcc_assert (c->m_lhs.m_idx <= m_equiv_classes.length ());
+      gcc_assert (c->m_rhs.m_idx <= m_equiv_classes.length ());
+    }
+}
+
+// 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 ()
+{
+  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);
+  /* "x == 0", then use of y, then "y = 0".  */
+  path_state ps;
+  ADD_SAT_CONSTRAINT (ps, x, EQ_EXPR, int_0);
+  ASSERT_CONDITION_UNKNOWN (ps, y, EQ_EXPR, int_0);
+  ps.on_assignment (y, int_0);
+  ASSERT_CONDITION_TRUE (ps, y, EQ_EXPR, int_0);
+  ASSERT_CONDITION_TRUE (ps, y, EQ_EXPR, x);
+}
+
+/* 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);
+  }
+}
+
+/* A subclass of purge_criteria for selftests: purge all path_var instances.  */
+
+class purge_all_path_vars : public purge_criteria
+{
+public:
+  bool should_purge_p (path_var) const FINAL OVERRIDE
+  {
+    return true;
+  }
+};
+
+/* Check that path_state objects can "forget".  */
+
+static void
+test_purging ()
+{
+  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_state ps0;
+    path_state ps1;
+
+    ADD_SAT_CONSTRAINT (ps1, x, EQ_EXPR, y);
+    ASSERT_NE (ps0, ps1);
+
+    purge_one_path_var px (x);
+    ps1.purge (px);
+
+    purge_one_path_var py (y);
+    ps1.purge (py);
+
+    ASSERT_EQ (ps0, ps1);
+  }
+
+  {
+    path_state ps0;
+    path_state ps1;
+
+    ADD_SAT_CONSTRAINT (ps1, x, EQ_EXPR, int_42);
+    ASSERT_NE (ps0, ps1);
+
+    purge_one_path_var px (x);
+    ps1.purge (px);
+
+    ASSERT_EQ (ps0, ps1);
+  }
+
+  {
+    path_state ps0;
+    path_state ps1;
+
+    ADD_SAT_CONSTRAINT (ps1, x, GE_EXPR, int_0);
+    ADD_SAT_CONSTRAINT (ps1, x, LE_EXPR, int_42);
+    ASSERT_NE (ps0, ps1);
+
+    purge_one_path_var px (x);
+    ps1.purge (px);
+
+    ASSERT_EQ (ps0, ps1);
+  }
+
+  {
+    path_state ps0;
+    path_state ps1;
+
+    ADD_SAT_CONSTRAINT (ps1, x, NE_EXPR, int_42);
+    ADD_SAT_CONSTRAINT (ps1, y, NE_EXPR, int_0);
+    ASSERT_NE (ps0, ps1);
+
+    purge_one_path_var px (x);
+    ps1.purge (px);
+    ASSERT_NE (ps0, ps1);
+
+    ASSERT_CONDITION_TRUE (ps1, y, NE_EXPR, int_0);
+  }
+
+  {
+    path_state ps0;
+    path_state ps1;
+
+    ADD_SAT_CONSTRAINT (ps1, x, NE_EXPR, int_42);
+    ADD_SAT_CONSTRAINT (ps1, y, NE_EXPR, int_0);
+    ASSERT_NE (ps0, ps1);
+
+    purge_all_path_vars p;
+    ps1.purge (p);
+    ASSERT_EQ (ps0, ps1);
+  }
+
+}
+
+
+/* Run all of the selftests within this file.  */
+
+void
+analyzer_path_state_cc_tests ()
+{
+  test_constraint_conditions ();
+  test_transitivity ();
+  test_constant_comparisons ();
+  test_constraint_impl ();
+  test_assignment ();
+  test_stack_frames ();
+  test_equality ();
+  test_purging ();
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
diff --git a/gcc/analyzer/path-state.h b/gcc/analyzer/path-state.h
new file mode 100644
index 0000000..b53b511
--- /dev/null
+++ b/gcc/analyzer/path-state.h
@@ -0,0 +1,316 @@
+/* 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_PATH_STATE_H
+#define GCC_ANALYZER_PATH_STATE_H
+
+class superedge;
+class call_superedge;
+class return_superedge;
+
+/* FIXME: put everything to do with path_state in its own namespace
+   while building up the alternative impl.  */
+
+namespace psn {
+
+/* 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;
+};
+
+} // namespace psn
+
+/* A tree, extended with stack frame information for locals, so that
+   we can distinguish between different values of locals within a potentially
+   recursive callstack.  */
+// FIXME: would this be better as a new tree code?
+
+class path_var
+{
+public:
+  path_var (tree t, int stack_depth)
+  : m_tree (t), m_stack_depth (stack_depth)
+  {
+    // TODO: ignore stack depth for globals and constants
+  }
+
+  bool operator== (const path_var &other) const
+  {
+    return (m_tree == other.m_tree
+	    && m_stack_depth == other.m_stack_depth);
+  }
+
+  std::string to_str () const;
+
+  tree m_tree;
+  int m_stack_depth; // or -1 for globals?
+};
+
+/* FIXME: put everything to do with path_state in its own namespace
+   while building up the alternative impl.  */
+
+namespace psn {
+
+/* Abstract base class for specifying how state should be purged.  */
+
+class purge_criteria
+{
+public:
+  virtual ~purge_criteria () {}
+  virtual bool should_purge_p (path_var pv) const = 0;
+};
+
+} // namespace psn
+
+
+class path_state;
+
+namespace psn {
+
+/* 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;
+
+  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 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);
+  }
+
+  void update_for_removal (equiv_class_id other)
+  {
+    if (m_idx > other.m_idx)
+      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;
+};
+
+} // namespace psn
+
+/* 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 psn::equiv_class &get_equiv_class_by_index (unsigned idx) const
+  {
+    return *m_equiv_classes[idx];
+  }
+  psn::equiv_class &get_equiv_class_by_index (unsigned idx)
+  {
+    return *m_equiv_classes[idx];
+  }
+
+  psn::equiv_class &get_equiv_class (path_var pv)
+  {
+    psn::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 (psn::equiv_class_id lhs_ec_id,
+		       enum tree_code op,
+		       psn::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, psn::equiv_class_id *out) const;
+  psn::equiv_class_id get_or_add_equiv_class (path_var vr);
+  tristate eval_condition (psn::equiv_class_id lhs,
+			   enum tree_code op,
+			   psn::equiv_class_id rhs);
+
+  /* For implementing "__analyzer_eval".  */
+  tristate dump_condition (tree lhs, enum tree_code op, tree rhs);
+
+  void recursively_mark_as_escaping (psn::equiv_class_id ec_id);
+  bool calc_escaped_p (path_var pv);
+
+  int purge (const psn::purge_criteria &p);
+
+  void validate () const;
+
+  auto_vec<psn::equiv_class *> m_equiv_classes;
+  auto_vec<psn::constraint> m_constraints;
+  int m_stack_depth;
+
+ private:
+  void add_constraint_internal (psn::equiv_class_id lhs_id,
+				enum psn::constraint_op c_op,
+				psn::equiv_class_id rhs_id);
+};
+
+namespace inchash
+{
+  extern void add_path_var (path_var pv, hash &hstate);
+} // namespace inchash
+
+#endif /* GCC_ANALYZER_PATH_STATE_H */
diff --git a/gcc/analyzer/pending-diagnostic.cc b/gcc/analyzer/pending-diagnostic.cc
new file mode 100644
index 0000000..a07339b
--- /dev/null
+++ b/gcc/analyzer/pending-diagnostic.cc
@@ -0,0 +1,79 @@
+/* Classes for analyzer diagnostics.
+   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/>.  */
+/* 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/analyzer.h"
+#include "analyzer/graphviz.h"
+#include "cgraph.h"
+#include "diagnostic-path.h"
+#include "gcc-rich-location.h"
+#include "analyzer/pending-diagnostic.h"
+
+label_text
+pending_diagnostic::formatted_print (const char *fmt, ...)
+{
+  pretty_printer *pp = global_dc->printer->clone ();
+
+  /* FIXME.  */
+  pp_show_color (pp) = false;
+
+  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;
+}
diff --git a/gcc/analyzer/pending-diagnostic.h b/gcc/analyzer/pending-diagnostic.h
new file mode 100644
index 0000000..90ac572
--- /dev/null
+++ b/gcc/analyzer/pending-diagnostic.h
@@ -0,0 +1,285 @@
+/* Classes for analyzer diagnostics.
+   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_PENDING_DIAGNOSTIC_H
+#define GCC_ANALYZER_PENDING_DIAGNOSTIC_H
+
+#include "analyzer/sm.h"
+#include "diagnostic-metadata.h"
+
+/* FIXME.  */
+
+// TODO: maybe capture which sm and pending_diagnostic emitted the error?
+// FIXME: is this needed?
+
+class metadata : public diagnostic_metadata
+{
+ public:
+
+ private:
+};
+
+/* A class for referring to events within a diagnostic_path.
+   They are stored as 0-based offsets into the events, but
+   printed as 1-based numbers.  For example, a 3-event path
+   has event offsets 0, 1, and 2, which would be shown to
+   the user as "(1)", "(2)" and "(3)".  */
+
+class event_id_t
+{
+ public:
+  event_id_t () : m_index (UNKNOWN_EVENT_IDX) {}
+  event_id_t (int zero_based_idx) : m_index (zero_based_idx) {}
+
+  bool known_p () const { return m_index != UNKNOWN_EVENT_IDX; }
+
+  int one_based () const
+  {
+    gcc_assert (known_p ());
+    return m_index + 1;
+  }
+
+ private:
+  static const int UNKNOWN_EVENT_IDX = -1;
+  int m_index; // zero-based
+};
+
+/* Various bundles of information used for generating more precise
+   messages for events within a diagnostic_path, for passing to the
+   various "describe_*" vfuncs of pending_diagnostic.  See those
+   for more information.  */
+
+namespace evdesc {
+
+/* For use by pending_diagnostic::describe_state_change.  */
+
+struct state_change
+{
+  state_change (tree expr,
+		state_machine::state_t old_state,
+		state_machine::state_t new_state,
+		event_id_t event_id)
+  : m_expr (expr), m_old_state (old_state),
+    m_new_state (new_state), m_event_id (event_id)
+  {}
+
+  tree m_expr;
+  state_machine::state_t m_old_state;
+  state_machine::state_t m_new_state;
+  event_id_t m_event_id;
+};
+
+/* For use by pending_diagnostic::describe_call_with_state.  */
+
+struct call_with_state
+{
+  call_with_state (tree caller_fndecl, tree callee_fndecl,
+		   tree expr, state_machine::state_t state)
+  : m_caller_fndecl (caller_fndecl),
+    m_callee_fndecl (callee_fndecl),
+    m_expr (expr),
+    m_state (state)
+  {
+  }
+
+  tree m_caller_fndecl;
+  tree m_callee_fndecl;
+  tree m_expr;
+  state_machine::state_t m_state;
+};
+
+/* For use by pending_diagnostic::describe_return_of_state.  */
+
+struct return_of_state
+{
+  return_of_state (tree caller_fndecl, tree callee_fndecl,
+		   state_machine::state_t state)
+  : m_caller_fndecl (caller_fndecl),
+    m_callee_fndecl (callee_fndecl),
+    m_state (state)
+  {
+  }
+
+  tree m_caller_fndecl;
+  tree m_callee_fndecl;
+  state_machine::state_t m_state;
+};
+
+/* For use by pending_diagnostic::describe_final_event.  */
+
+struct final_event
+{
+  final_event (tree expr, state_machine::state_t state)
+  : m_expr (expr), m_state (state)
+  {}
+
+  tree m_expr;
+  state_machine::state_t m_state;
+};
+
+} /* end of namespace evdesc */
+
+/* An abstract base class for capturing information about a diagnostic in
+   a form that is ready to emit at a later point (or be rejected).
+   Each kind of diagnostic will have a concrete subclass of
+   pending_diagnostic.
+
+   Normally, gcc diagnostics are emitted using va_list, which can't be
+   portably stored for later use, so we have to use an "emit" virtual
+   function.
+
+   This class also supports comparison, so that multiple pending_diagnostic
+   instances can be de-duplicated.
+
+   As well as emitting a diagnostic, the class has various "precision of
+   wording" virtual functions, for generating descriptions for events
+   within a diagnostic_path.  These are optional, but implementing these
+   allows for more precise wordings than the more generic
+   implementation.  */
+
+class pending_diagnostic
+{
+ public:
+  virtual ~pending_diagnostic () {}
+
+  /* Vfunc for emitting the diagnostic.  The rich_location will have been
+     populated with a diagnostic_path.  */
+  virtual void emit (rich_location *) = 0;
+
+  /* Hand-coded RTTI: get an ID for the subclass.  */
+  virtual const char *get_kind () const = 0;
+
+  /* Compare for equality with OTHER, which might be of a different
+     subclass.  */
+
+  bool equal_p (const pending_diagnostic &other)
+  {
+    /* Check for pointer equality on the IDs from get_kind.  */
+    if (get_kind () != other.get_kind ())
+      return false;
+    /* Call vfunc now we know they have the same ID: */
+    return subclass_equal_p (other);
+  }
+
+  /* A vfunc for testing for equality, where we've already
+     checked they have the same ID.  See pending_diagnostic_subclass
+     below for a convenience subclass for implementing this.  */
+  virtual bool subclass_equal_p (const pending_diagnostic &other) const = 0;
+
+  /* For greatest precision-of-wording, the various following "describe_*"
+     virtual functions give the pending diagnostic a way to describe events
+     in a diagnostic_path in terms that make sense for that diagnostic.
+
+     In each case, return a non-NULL label_text to give the event a custom
+     description; NULL otherwise (falling back on a more generic
+     description).  */
+
+  /* Precision-of-wording vfunc for describing a critical state change
+     within the diagnostic_path.
+
+     For example, a double-free diagnostic might use the descriptions:
+     - "first 'free' happens here"
+     - "second 'free' happens here"
+     for the pertinent events, whereas a use-after-free might use the
+     descriptions:
+     - "freed here"
+     - "use after free here"
+     Note how in both cases the first event is a "free": the best
+     description to use depends on the diagnostic.  */
+
+  virtual label_text describe_state_change (const evdesc::state_change &)
+  {
+    /* Default no-op implementation.  */
+    return label_text ();
+  }
+
+  /* Precision-of-wording vfunc for describing an interprocedural call
+     carrying critial state for the diagnostic, from caller to callee.
+
+     For example a double-free diagnostic might use:
+     - "passing freed pointer 'ptr' in call to 'deallocator' from 'test'"
+     to make it clearer how the unchecked value moves from caller
+     to callee.  */
+
+  virtual label_text describe_call_with_state (const evdesc::call_with_state &)
+  {
+    /* Default no-op implementation.  */
+    return label_text ();
+  }
+
+  /* Precision-of-wording vfunc for describing an interprocedural return
+     within the diagnostic_path that carries critial state for the
+     diagnostic, from callee back to caller.
+
+     For example, a deref-of-unchecked-malloc diagnostic might use:
+     - "returning possibly-NULL pointer to 'make_obj' from 'allocator'"
+     to make it clearer how the unchecked value moves from callee
+     back to caller.  */
+
+  virtual label_text describe_return_of_state (const evdesc::return_of_state &)
+  {
+    /* Default no-op implementation.  */
+    return label_text ();
+  }
+
+  /* Precision-of-wording vfunc for describing the final event within a
+     diagnostic_path.
+
+     For example a double-free diagnostic might use:
+      - "second 'free' here; first 'free' was at (3)"
+     and a use-after-free might use
+      - "use after 'free' here; memory was freed at (2)".  */
+
+  virtual label_text describe_final_event (const evdesc::final_event &)
+  {
+    /* Default no-op implementation.  */
+    return label_text ();
+  }
+
+  /* End of precision-of-wording vfuncs.  */
+
+  /* Utility function for implementing the above vfuncs.  */
+
+  static label_text formatted_print (const char *fmt, ...)
+    ATTRIBUTE_GCC_DIAG(1,2);
+};
+
+/* A template to make it easier to make subclasses of pending_diagnostic.
+
+   This uses the curiously-recurring template pattern, to implement
+   pending_diagnostic::subclass_equal_p by casting and calling
+   the operator==
+
+   This assumes that BASE_OTHER has already been checked to have
+   been of the same subclass (which pending_diagnostic::equal_p does).  */
+
+template <class Subclass>
+class pending_diagnostic_subclass : public pending_diagnostic
+{
+ public:
+  bool subclass_equal_p (const pending_diagnostic &base_other) const
+    FINAL OVERRIDE
+  {
+    const Subclass &other = (const Subclass &)base_other;
+    return *(const Subclass*)this == other;
+  }
+};
+
+#endif /* GCC_ANALYZER_PENDING_DIAGNOSTIC_H */
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
new file mode 100644
index 0000000..d147a15
--- /dev/null
+++ b/gcc/analyzer/region-model.cc
@@ -0,0 +1,5952 @@
+/* Classes for modeling the state of memory.
+   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/>.  */
+/* 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/analyzer-logging.h"
+#include "stringpool.h"
+#include "selftest.h"
+#include "fold-const.h"
+#include "analyzer/tristate.h"
+#include "analyzer/path-state.h"
+#include "analyzer/supergraph.h"
+#include "analyzer/region-model.h"
+#include "tree-diagnostic.h"
+#include "diagnostic-color.h"
+#include "analyzer/constraint-manager.h"
+#include "analyzer/pending-diagnostic.h"
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+class impl_constraint_manager : public constraint_manager
+{
+ public:
+  impl_constraint_manager (region_model *model)
+  : constraint_manager (),
+    m_model (model)
+  {}
+
+  impl_constraint_manager (const impl_constraint_manager &other,
+			   region_model *model)
+  : constraint_manager (other),
+    m_model (model)
+  {}
+
+  constraint_manager *clone (region_model *model) const
+  {
+    return new impl_constraint_manager (*this, model);
+  }
+
+  tree maybe_get_constant (svalue_id sid) const FINAL OVERRIDE
+  {
+    svalue *svalue = m_model->get_svalue (sid);
+    if (constant_svalue *cst_svalue = svalue->dyn_cast_constant_svalue ())
+      {
+	tree cst = cst_svalue->get_constant ();
+	/* This could be a constant or a decl; constraint_manager is only
+	   expecting actual constants.  */
+	if (CONSTANT_CLASS_P (cst))
+	  return cst;
+      }
+    return NULL_TREE;
+  }
+
+  svalue_id get_sid_for_constant (tree cst) const FINAL OVERRIDE
+  {
+    gcc_assert (CONSTANT_CLASS_P (cst));
+    return m_model->get_rvalue (cst, NULL);
+  }
+
+  int get_num_svalues () const FINAL OVERRIDE
+  {
+    return m_model->get_num_svalues ();
+  }
+
+ private:
+  region_model *m_model;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class svalue_id.  */
+
+/* Print this svalue_id to PP.  */
+
+void
+svalue_id::print (pretty_printer *pp) const
+{
+  if (null_p ())
+    pp_printf (pp, "null");
+  else
+    pp_printf (pp, "sv%i", m_idx);
+}
+
+/* FIXME.  */
+
+void
+svalue_id::dump_node_name_to_pp (pretty_printer *pp) const
+{
+  gcc_assert (!null_p ());
+  pp_printf (pp, "svalue_%i", m_idx);
+}
+
+/* FIXME.  */
+
+std::string
+svalue_id::to_str () const
+{
+  pretty_printer pp;
+  print (&pp);
+  return pp_formatted_text (&pp);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class region_id.  */
+
+/* Print this region_id to PP.  */
+
+void
+region_id::print (pretty_printer *pp) const
+{
+  if (null_p ())
+    pp_printf (pp, "null");
+  else
+    pp_printf (pp, "r%i", m_idx);
+}
+
+/* FIXME.  */
+
+void
+region_id::dump_node_name_to_pp (pretty_printer *pp) const
+{
+  gcc_assert (!null_p ());
+  pp_printf (pp, "region_%i", m_idx);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class id_set.  */
+
+/* id_set<region_id>'s ctor.  */
+
+template<>
+id_set<region_id>::id_set (const region_model *model)
+: m_bitmap (model->get_num_regions ())
+{
+  bitmap_clear (m_bitmap);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class svalue and its various subclasses.  */
+
+/* class svalue.  */
+
+/* svalue's equality operator.  Most of the work is done by the
+   a "compare_fields" implementation on each subclass.  */
+
+bool
+svalue::operator== (const svalue &other) const
+{
+  enum svalue_kind this_kind = get_kind ();
+  enum svalue_kind other_kind = other.get_kind ();
+  if (this_kind != other_kind)
+    return false;
+
+  if (m_type != other.m_type)
+    return false;
+
+  switch (this_kind)
+    {
+    default:
+      gcc_unreachable ();
+    case SK_REGION:
+      {
+	const region_svalue &this_sub
+	  = (const region_svalue &)*this;
+	const region_svalue &other_sub
+	  = (const region_svalue &)other;
+	return this_sub.compare_fields (other_sub);
+      }
+      break;
+    case SK_CONSTANT:
+      {
+	const constant_svalue &this_sub
+	  = (const constant_svalue &)*this;
+	const constant_svalue &other_sub
+	  = (const constant_svalue &)other;
+	return this_sub.compare_fields (other_sub);
+      }
+      break;
+    case SK_POISONED:
+      {
+	const poisoned_svalue &this_sub
+	  = (const poisoned_svalue &)*this;
+	const poisoned_svalue &other_sub
+	  = (const poisoned_svalue &)other;
+	return this_sub.compare_fields (other_sub);
+      }
+      break;
+    case SK_UNKNOWN:
+      {
+	const unknown_svalue &this_sub
+	  = (const unknown_svalue &)*this;
+	const unknown_svalue &other_sub
+	  = (const unknown_svalue &)other;
+	return this_sub.compare_fields (other_sub);
+      }
+      break;
+    }
+}
+
+/* FIXME.  */
+
+void
+svalue::print (const region_model &model,
+	       svalue_id this_sid,
+	       pretty_printer *pp) const
+{
+  this_sid.print (pp);
+  pp_string (pp, ": {");
+
+PUSH_IGNORE_WFORMAT
+  if (m_type)
+    pp_printf (pp, "type: %qT, ", m_type);
+POP_IGNORE_WFORMAT
+
+  /* vfunc.  */
+  print_details (model, this_sid, pp);
+
+  pp_string (pp, "}");
+}
+
+/* FIXME.  */
+
+void
+svalue::dump_dot_to_pp (const region_model &model,
+			svalue_id this_sid,
+			pretty_printer *pp) const
+{
+  this_sid.dump_node_name_to_pp (pp);
+  pp_printf (pp, " [label=\"");
+  pp_write_text_to_stream (pp);
+  this_sid.print (pp);
+  pp_string (pp, ": {");
+  print (model, this_sid, pp);
+  pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/false);
+  pp_string (pp, "}\"];");
+  pp_newline (pp);
+}
+
+#if 0
+/* FIXME.  */
+
+std::string
+svalue::to_str () const
+{
+  pretty_printer pp;
+  print (&pp);
+  return pp_formatted_text (&pp);
+}
+#endif
+
+/* Base implementation of svalue::remap_region_ids vfunc.  */
+
+void
+svalue::remap_region_ids (const region_id_map &)
+{
+  /* Empty.  */
+}
+
+/* Base implementation of svalue::walk_for_canonicalization vfunc.  */
+
+void
+svalue::walk_for_canonicalization (canonicalization *) const
+{
+  /* Empty.  */
+}
+
+/* class region_svalue : public svalue.  */
+
+/* Compare the fields of this region_svalue with OTHER, returning true
+   if they are equal.
+   For use by svalue::operator==.  */
+
+bool
+region_svalue::compare_fields (const region_svalue &other) const
+{
+  return m_rid == other.m_rid;
+}
+
+/* Implementation of svalue::hash vfunc for region_svalue.  */
+
+hashval_t
+region_svalue::hash () const
+{
+  return m_rid.as_int ();
+}
+
+/* FIXME.  */
+
+void
+region_svalue::print_details (const region_model &model ATTRIBUTE_UNUSED,
+			      svalue_id this_sid ATTRIBUTE_UNUSED,
+			      pretty_printer *pp) const
+{
+  if (m_rid.null_p ())
+    pp_string (pp, "NULL");
+  else
+    {
+      pp_string (pp, "&");
+      m_rid.print (pp);
+    }
+}
+
+/* FIXME.  */
+
+void
+region_svalue::dump_dot_to_pp (const region_model &model,
+			       svalue_id this_sid,
+			       pretty_printer *pp) const
+{
+  svalue::dump_dot_to_pp (model, this_sid, pp);
+
+  /* If non-NULL, add an edge to the pointed-to region.  */
+  if (!m_rid.null_p ())
+    {
+      this_sid.dump_node_name_to_pp (pp);
+      pp_string (pp, " -> ");
+      m_rid.dump_node_name_to_pp (pp);
+      pp_string (pp, ";");
+      pp_newline (pp);
+  }
+}
+
+/* Implementation of svalue::remap_region_ids vfunc for region_svalue.  */
+
+void
+region_svalue::remap_region_ids (const region_id_map &map)
+{
+  map.update (&m_rid);
+}
+
+/* FIXME.  */
+
+bool
+region_svalue::can_merge_values_p (const region_svalue &region_sval_a,
+				   const region_svalue &region_sval_b,
+				   svalue_id *merged_sid,
+				   tree type,
+				   model_merger *merger)
+{
+  /* FIXME: they must be non-NULL; how could we test the kind otherwise?
+     We should have been testing the pointee value.  */
+  region_id a_rid = region_sval_a.get_pointee ();
+  region_id b_rid = region_sval_b.get_pointee ();
+
+  /* FIXME: currently, I don't think we can ever have null regions.  */
+  gcc_assert (!a_rid.null_p () && !b_rid.null_p ());
+
+  if (a_rid.null_p () && b_rid.null_p ())
+    {
+      /* Both are NULL.  */
+      svalue *merged_sval = new region_svalue (type, region_id::null ());
+      *merged_sid = merger->m_merged_model->add_svalue (merged_sval);
+      return true;
+    }
+
+  if (a_rid.null_p () || b_rid.null_p ())
+    {
+      /* If we have NULL vs non-NULL, then merge to unknown.  */
+      svalue *merged_sval = new unknown_svalue (type);
+      *merged_sid = merger->m_merged_model->add_svalue (merged_sval);
+      return true;
+    }
+
+  /* Both are non-NULL. */
+  gcc_assert (!a_rid.null_p () && !b_rid.null_p ());
+
+  /* Have these ptr-values already been merged?  */
+
+  region_id a_rid_in_m
+    = merger->m_map_regions_from_a_to_m.get_dst_for_src (a_rid);
+  region_id b_rid_in_m
+    = merger->m_map_regions_from_b_to_m.get_dst_for_src (b_rid);
+
+  /* "null_p" here means "we've seen this ptr-value before".
+     If we've seen one but not the other, or we have different
+     regions, then the merged ptr has to be "unknown".  */
+  if (a_rid_in_m != b_rid_in_m)
+    {
+      gcc_unreachable ();
+      svalue *merged_sval = new unknown_svalue (type);
+      *merged_sid = merger->m_merged_model->add_svalue (merged_sval);
+      return true;
+    }
+
+  /* Have we seen this yet?  If so, reuse the value.  */
+  if (!a_rid_in_m.null_p ())
+    {
+      *merged_sid
+	= merger->m_merged_model->get_or_create_ptr_svalue (type, a_rid_in_m);
+      return true;
+    }
+
+  /* Otherwise we have A/B regions that haven't been referenced yet.  */
+  // TODO
+
+  /* If we have two regions that haven't been referenced yet,
+     and they're both in the heap, create a new heap region, and record
+     that.
+     Various other cases.
+     Probably move this to a region_svalue function.  */
+
+  /* FIXME: maybe use get_representative_tree?
+     or introduce a new thing on regions to test for equivalence?
+     but presumably we'd need the representative tree to do the lookup in
+     the merged model - assuming the region exists - aha: but get_lvalue
+     would presumably add it lazily.  */
+  path_var pv_a = merger->m_model_a->get_representative_path_var (a_rid);
+  path_var pv_b = merger->m_model_b->get_representative_path_var (b_rid);
+
+  /* FIXME: we probably need a deeper test here e.g. for fields of structs.  */
+  if (pv_a.m_tree
+      && pv_a == pv_b)
+    {
+      region_id merged_pointee_rid
+	= merger->m_merged_model->get_lvalue (pv_a, NULL);
+      *merged_sid
+	= merger->m_merged_model->get_or_create_ptr_svalue (type,
+							    merged_pointee_rid);
+      merger->record_regions (a_rid, b_rid, merged_pointee_rid);
+      return true;
+    }
+
+  /* FIXME: what about heap regions?
+     (and descendents of heap regions)
+     Perhaps have get_representative_tree return "*expr" for some expr.
+     Presumably we'd still have to create a heap region in the merged model,
+     though.
+     Or have something like get_repr_tree that returns a richer object that
+     "knows" about heap regions.
+     e.g. an expression of a "root", and a tree relative to it
+     (a) root == root_region, a tree (as before)
+     (b) root == heap region 0, a tree relative to that heap region
+     etc
+     But what about e.g. a linked list, or other chain of pointers that
+     reaches a heap region?
+  */
+  /* Maybe for now it's good enough to detect if both are heap regions
+     i.e. that their parents are the heap_region.
+   */
+#if 0
+  if (heap_region *heap_a
+      = merger->get_root_a ()->get_heap_region (merger->m_model_a))
+    if (heap_region *heap_b
+	= merger->get_root_b ()->get_heap_region (merger->m_model_b))
+#endif
+#if 1
+  region *region_a = merger->m_model_a->get_region (a_rid);
+  region *region_b = merger->m_model_b->get_region (b_rid);
+  region_id a_parent_rid = region_a->get_parent ();
+  region_id b_parent_rid = region_b->get_parent ();
+  region *parent_region_a = merger->m_model_a->get_region (a_parent_rid);
+  region *parent_region_b = merger->m_model_b->get_region (b_parent_rid);
+  if (parent_region_a
+      && parent_region_b
+      && parent_region_a->get_kind () == RK_HEAP
+      && parent_region_b->get_kind () == RK_HEAP)
+    {
+#if 0
+      tree_a = merger->m_model_a->get_representative_tree (a_sid);
+      tree_b = merger->m_model_b->get_representative_tree (b_sid);
+      // FIXME: again, do we need a deeper test here?
+      if (tree_a
+	  && tree_a == tree_b)
+#endif
+      /* FIXME: but isn't the above guaranteed, because of the way we're
+	 being called?  */
+
+	/* We have an A/B pair of ptrs that both point at heap regions.  */
+	/* presumably we want to see if each A/B heap region already
+	   has a merged region, and, if so, is it the same one.  */
+	// This check is above
+
+	region_id merged_pointee_rid
+	  = merger->m_merged_model->add_new_malloc_region ();
+	// TODO: merge values within the heap regions
+	*merged_sid
+	  = merger->m_merged_model->get_or_create_ptr_svalue
+	     (type, merged_pointee_rid);
+	merger->record_regions (a_rid, b_rid, merged_pointee_rid);
+	return true;
+    }
+#endif
+
+#if 0
+    /* FIXME: is it OK to just check that the region_id is the same?  */
+    if (rid_a == rid_b)
+	{
+	  region_id merged_pointee_rid; // FIXME: what should this be???
+	  svalue *merged_sval
+	    = new region_svalue (type, merged_pointee_rid);
+	  *merged_sid = merger->m_merged_model->add_svalue (merged_sval);
+	  return true;
+	}
+      else
+#endif
+	/* FIXME: for now, always just merge to unknown for two non-NULL
+	   pointers, even if they might point to the same thing in
+	   A and B.  */
+	{
+	  /* Two different non-NULL pointers?  Merge to unknown.  */
+	  // TODO: add a constraint?
+	  svalue *merged_sval = new unknown_svalue (type);
+	  *merged_sid = merger->m_merged_model->add_svalue (merged_sval);
+	  return true;
+	}
+}
+
+/* Implementation of svalue::walk_for_canonicalization vfunc for
+   region_svalue.  */
+
+void
+region_svalue::walk_for_canonicalization (canonicalization *c) const
+{
+  c->walk_rid (m_rid);
+}
+
+/* Evaluate the condition LHS OP RHS.
+   Subroutine of region_model::eval_condition for when we have a pair of
+   pointers.  */
+
+tristate
+region_svalue::eval_condition (region_svalue *lhs,
+			       enum tree_code op,
+			       region_svalue *rhs)
+{
+  /* See if they point to the same region.  */
+  /* FIXME: what about child regions where the child is the first child
+     (or descendent)?  */
+  region_id lhs_rid = lhs->get_pointee ();
+  region_id rhs_rid = rhs->get_pointee ();
+  switch (op)
+    {
+    default:
+      gcc_unreachable ();
+
+    case EQ_EXPR:
+      if (lhs_rid == rhs_rid)
+	return tristate::TS_TRUE;
+      else
+	return tristate::TS_FALSE;
+      break;
+
+    case NE_EXPR:
+      if (lhs_rid != rhs_rid)
+	return tristate::TS_TRUE;
+      else
+	return tristate::TS_FALSE;
+      break;
+
+    case GE_EXPR:
+    case LE_EXPR:
+      if (lhs_rid == rhs_rid)
+	return tristate::TS_TRUE;
+      break;
+
+    case GT_EXPR:
+    case LT_EXPR:
+      if (lhs_rid == rhs_rid)
+	return tristate::TS_FALSE;
+      break;
+    }
+
+  return tristate::TS_UNKNOWN;
+}
+
+/* class constant_svalue : public svalue.  */
+
+/* Compare the fields of this constant_svalue with OTHER, returning true
+   if they are equal.
+   For use by svalue::operator==.  */
+
+bool
+constant_svalue::compare_fields (const constant_svalue &other) const
+{
+  // FIXME: should we do tree equality here?
+  return m_cst_expr == other.m_cst_expr;
+}
+
+/* Implementation of svalue::hash vfunc for constant_svalue.  */
+
+hashval_t
+constant_svalue::hash () const
+{
+  inchash::hash hstate;
+  // FIXME: what if it's a fndecl?
+  inchash::add_expr (m_cst_expr, hstate);
+  return hstate.end ();
+}
+
+/* FIXME.  */
+
+bool
+constant_svalue::can_merge_values_p (const constant_svalue &cst_sval_a,
+				     svalue_id sid_a ATTRIBUTE_UNUSED,
+				     const constant_svalue &cst_sval_b,
+				     svalue_id sid_b ATTRIBUTE_UNUSED,
+				     svalue_id *merged_sid,
+				     model_merger *merger)
+{
+  tree cst_a = cst_sval_a.get_constant ();
+  tree cst_b = cst_sval_b.get_constant ();
+  svalue *merged_sval;
+  if (cst_a == cst_b) // FIXME: something other than pointer equality?
+    {
+      /* If they are the same constant, merge as that constant value.  */
+      merged_sval = new constant_svalue (cst_a);
+    }
+  else
+    {
+      /* Otherwise, we have two different constant values.
+	 Merge as an unknown value.
+	 TODO: impose constraints on the value?
+	 (maybe just based on A, to avoid infinite chains)  */
+      merged_sval = new unknown_svalue (TREE_TYPE (cst_a));
+    }
+  *merged_sid = merger->m_merged_model->add_svalue (merged_sval);
+  return true;
+}
+
+/* Evaluate the condition LHS OP RHS.
+   Subroutine of region_model::eval_condition for when we have a pair of
+   constants.  */
+
+tristate
+constant_svalue::eval_condition (constant_svalue *lhs,
+				 enum tree_code op,
+				 constant_svalue *rhs)
+{
+  tree lhs_const = lhs->get_constant ();
+  tree rhs_const = rhs->get_constant ();
+
+  /* If one or both of the "constants" are function pointers, then
+     we have equality iff the trees are equal.  */
+  if (TREE_CODE (lhs_const) == FUNCTION_DECL
+      || TREE_CODE (rhs_const) == FUNCTION_DECL)
+    {
+      bool eq = (lhs_const == rhs_const);
+      switch (op)
+	{
+	default:
+	  gcc_unreachable ();
+
+	case EQ_EXPR:
+	  return tristate (eq);
+
+	case GE_EXPR:
+	case LE_EXPR:
+	  if (eq)
+	    return tristate (true);
+	  else
+	    return tristate (tristate::TS_UNKNOWN);
+
+	case NE_EXPR:
+	  return tristate (lhs_const != rhs_const);
+
+	case GT_EXPR:
+	case LT_EXPR:
+	  if (eq)
+	    return tristate (false);
+	  return tristate::TS_UNKNOWN;
+	}
+    }
+
+  /* Otherwise, we have a pair of constants; compare them.  */
+  gcc_assert (CONSTANT_CLASS_P (lhs_const));
+  gcc_assert (CONSTANT_CLASS_P (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);
+  return tristate::TS_UNKNOWN;
+}
+
+/* FIXME.  */
+
+void
+constant_svalue::print_details (const region_model &model ATTRIBUTE_UNUSED,
+				svalue_id this_sid ATTRIBUTE_UNUSED,
+				pretty_printer *pp) const
+{
+PUSH_IGNORE_WFORMAT
+  if (TREE_CODE (m_cst_expr) == FUNCTION_DECL)
+    pp_printf (pp, "%qD", m_cst_expr);
+  else
+    pp_printf (pp, "%qE", m_cst_expr);
+POP_IGNORE_WFORMAT
+}
+
+/* class unknown_svalue : public svalue.  */
+
+/* Compare the fields of this unknown_svalue with OTHER, returning true
+   if they are equal.
+   For use by svalue::operator==.  */
+
+bool
+unknown_svalue::compare_fields (const unknown_svalue &) const
+{
+  /* I *think* we want to return true here, in that when comparing
+     two region models, we want two peer unknown_svalue instances
+     to be the "same".  */
+  return true;
+}
+
+/* Implementation of svalue::hash vfunc for unknown_svalue.  */
+
+hashval_t
+unknown_svalue::hash () const
+{
+  return 0;
+}
+
+/* FIXME.  */
+
+void
+unknown_svalue::print_details (const region_model &model ATTRIBUTE_UNUSED,
+			       svalue_id this_sid ATTRIBUTE_UNUSED,
+			       pretty_printer *pp) const
+{
+  pp_string (pp, "unknown");
+}
+
+/* FIXME.  */
+
+const char *
+poison_kind_to_str (enum poison_kind kind)
+{
+  switch (kind)
+    {
+    default:
+      gcc_unreachable ();
+    case POISON_KIND_UNINIT:
+      return "uninit";
+    case POISON_KIND_FREED:
+      return "freed";
+    case POISON_KIND_POPPED_STACK:
+      return "popped stack";
+    }
+}
+
+/* class poisoned_svalue : public svalue.  */
+
+/* Compare the fields of this poisoned_svalue with OTHER, returning true
+   if they are equal.
+   For use by svalue::operator==.  */
+
+bool
+poisoned_svalue::compare_fields (const poisoned_svalue &other) const
+{
+  return m_kind == other.m_kind;
+}
+
+/* Implementation of svalue::hash vfunc for poisoned_svalue.  */
+
+hashval_t
+poisoned_svalue::hash () const
+{
+  return m_kind;
+}
+
+/* FIXME.  */
+
+void
+poisoned_svalue::print_details (const region_model &model ATTRIBUTE_UNUSED,
+				svalue_id this_sid ATTRIBUTE_UNUSED,
+				pretty_printer *pp) const
+{
+  pp_printf (pp, "poisoned: %s", poison_kind_to_str (m_kind));
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class region and its various subclasses.  */
+
+/* FIXME.  */
+
+const char *
+region_kind_to_str (enum region_kind kind)
+{
+  switch (kind)
+    {
+    default:
+      gcc_unreachable ();
+    case RK_MAP:
+      return "map";
+    case RK_FRAME:
+      return "frame";
+    case RK_STACK:
+      return "stack";
+    case RK_ROOT:
+      return "root";
+    case RK_SYMBOLIC:
+      return "symbolic";
+    case RK_HEAP:
+      return "heap";
+    }
+}
+
+/* class region.  */
+
+/* FIXME.  */
+
+bool
+region::operator== (const region &other) const
+{
+  if (m_parent_rid != other.m_parent_rid)
+    return false;
+  if (m_sval_id != other.m_sval_id)
+    return false;
+  if (m_type != other.m_type)
+    return false;
+
+  enum region_kind this_kind = get_kind ();
+  enum region_kind other_kind = other.get_kind ();
+  if (this_kind != other_kind)
+    return false;
+
+  switch (this_kind)
+    {
+    default:
+      gcc_unreachable ();
+    case RK_MAP:
+      {
+	const map_region &this_sub
+	  = (const map_region &)*this;
+	const map_region &other_sub
+	  = (const map_region &)other;
+	return this_sub.compare_fields (other_sub);
+      }
+    case RK_FRAME:
+      {
+	const frame_region &this_sub
+	  = (const frame_region &)*this;
+	const frame_region &other_sub
+	  = (const frame_region &)other;
+	return this_sub.compare_fields (other_sub);
+      }
+    case RK_STACK:
+      {
+	const stack_region &this_sub
+	  = (const stack_region &)*this;
+	const stack_region &other_sub
+	  = (const stack_region &)other;
+	return this_sub.compare_fields (other_sub);
+      }
+    case RK_ROOT:
+      {
+	const root_region &this_sub
+	  = (const root_region &)*this;
+	const root_region &other_sub
+	  = (const root_region &)other;
+	return this_sub.compare_fields (other_sub);
+      }
+    case RK_SYMBOLIC:
+      {
+	const symbolic_region &this_sub
+	  = (const symbolic_region &)*this;
+	const symbolic_region &other_sub
+	  = (const symbolic_region &)other;
+	return this_sub.compare_fields (other_sub);
+      }
+    case RK_HEAP:
+      {
+	const heap_region &this_sub
+	  = (const heap_region &)*this;
+	const heap_region &other_sub
+	  = (const heap_region &)other;
+	return this_sub.compare_fields (other_sub);
+      }
+    }
+}
+
+void
+region::set_value (const region_model &model ATTRIBUTE_UNUSED,
+		   svalue_id rhs_sid)
+{
+#if 0
+  svalue *sval = model.get_svalue (rhs_sid);
+
+  // FIXME: we can't enable this until we have working casts
+  /* Type-check where we can.  */
+  if (m_type && sval->get_type ())
+    gcc_assert (m_type == sval->get_type ());
+#endif
+
+  m_sval_id = rhs_sid;
+}
+
+/* FIXME.  */
+
+svalue_id
+region::get_value (region_model &model, bool non_null,
+		   region_model_context *ctxt)
+{
+  /* If this region has a value, use it. */
+  if (!m_sval_id.null_p ())
+    return m_sval_id;
+
+  /* Otherwise, "inherit" value from first ancestor with a
+     non-null value. */
+  for (region *ancestor = model.get_region (m_parent_rid);
+       ancestor;
+       ancestor = model.get_region (ancestor->m_parent_rid))
+    {
+      if (!ancestor->m_sval_id.null_p ())
+	{
+	  /* Clone the ancestor value, so that attempts to update it
+	     (e.g giving a specific value to an inherited "uninitialized"
+	     value) touch the child, and not the ancestor.  */
+	  svalue *ancestor_value = model.get_svalue (ancestor->m_sval_id);
+	  svalue *new_child_value = ancestor_value->clone ();
+	  svalue_id new_child_sid = model.add_svalue (new_child_value);
+	  if (ctxt)
+	    ctxt->on_inherited_svalue (ancestor->m_sval_id, new_child_sid);
+	  m_sval_id = new_child_sid;
+	  return new_child_sid;
+	}
+    }
+
+  /* If a non-null value has been requested, then generate
+     a new unknown value.  Store it, so that repeated reads from this
+     region will yield the same unknown value.  */
+  if (non_null)
+    {
+      svalue_id unknown_sid = model.add_svalue (new unknown_svalue (m_type));
+      m_sval_id = unknown_sid;
+      return unknown_sid;
+    }
+
+  gcc_unreachable ();
+
+  return svalue_id::null ();
+}
+
+/* Generate a hash value for this region.  The work is done by the
+   add_to_hash vfunc.  */
+
+hashval_t
+region::hash () const
+{
+  inchash::hash hstate;
+  add_to_hash (hstate);
+  return hstate.end ();
+}
+
+/* FIXME.  */
+
+void
+region::print (const region_model &model,
+	       region_id this_rid,
+	       pretty_printer *pp) const
+{
+  this_rid.print (pp);
+  pp_string (pp, ": {");
+
+  /* vfunc.  */
+  print_fields (model, this_rid, pp);
+
+  pp_string (pp, "}");
+}
+
+/* FIXME.  */
+
+void
+region::dump_dot_to_pp (const region_model &model,
+			region_id this_rid,
+			pretty_printer *pp) const
+{
+  this_rid.dump_node_name_to_pp (pp);
+  pp_printf (pp, " [shape=%s,style=filled,fillcolor=%s,label=\"",
+	     "record", "lightgrey");
+  pp_write_text_to_stream (pp);
+  print (model, this_rid, pp);
+  pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
+  pp_string (pp, "\"];");
+  pp_newline (pp);
+
+  /* Add edge to svalue.  */
+  if (!m_sval_id.null_p ())
+    {
+      this_rid.dump_node_name_to_pp (pp);
+      pp_string (pp, " -> ");
+      m_sval_id.dump_node_name_to_pp (pp);
+      pp_string (pp, ";");
+      pp_newline (pp);
+    }
+
+  /* Add edge to parent.  */
+  if (!m_parent_rid.null_p ())
+    {
+      this_rid.dump_node_name_to_pp (pp);
+      pp_string (pp, " -> ");
+      m_parent_rid.dump_node_name_to_pp (pp);
+      pp_string (pp, ";");
+      pp_newline (pp);
+    }
+}
+
+/* FIXME.  */
+
+void
+region::dump_to_pp (const region_model &model,
+		    region_id this_rid,
+		    pretty_printer *pp,
+		    const char *prefix,
+		    bool is_last_child) const
+{
+  print (model, this_rid, pp);
+  pp_newline (pp);
+
+  const char *new_prefix;
+  if (!m_parent_rid.null_p ())
+    new_prefix = ACONCAT ((prefix, is_last_child ? "  " : "|  ", NULL));
+  else
+    new_prefix = prefix;
+
+  const char *begin_color = colorize_start (pp_show_color (pp), "note");
+  const char *end_color = colorize_stop (pp_show_color (pp));
+  char *field_prefix
+    = ACONCAT ((begin_color, new_prefix, "|:", end_color, NULL));
+
+  if (!m_sval_id.null_p ())
+    {
+      pp_printf (pp, "%s sval: ", field_prefix);
+      model.get_svalue (m_sval_id)->print (model, m_sval_id, pp);
+      pp_newline (pp);
+    }
+  if (m_type)
+    {
+PUSH_IGNORE_WFORMAT
+      pp_printf (pp, "%s type: %qT", field_prefix, m_type);
+POP_IGNORE_WFORMAT
+      pp_newline (pp);
+    }
+
+  /* Find, then iterate over children.  */
+  /* TODO: we'll also want to label them
+     (potentially with tree labels, or with sval labels).  */
+
+  auto_vec<region_id> child_rids;
+  unsigned i;
+  for (unsigned i = 0; i < model.get_num_regions (); ++i)
+    {
+      region_id rid = region_id::from_int (i);
+      region *child = model.get_region (rid);
+      if (child->m_parent_rid == this_rid)
+	child_rids.safe_push (rid);
+    }
+
+  region_id *child_rid;
+  FOR_EACH_VEC_ELT (child_rids, i, child_rid)
+    {
+      is_last_child = (i == child_rids.length () - 1);
+      if (!this_rid.null_p ())
+	{
+	  // FIXME: colorize hierachical ASCII art
+	  const char *tail = is_last_child ? "`-" : "|-";
+	  pp_printf (pp, "%r%s%s%R", "note", new_prefix, tail);
+	  // FIXME: dedicated color code?
+	}
+      dump_child_label (model, this_rid, *child_rid, pp);
+      model.get_region (*child_rid)->dump_to_pp (model, *child_rid, pp,
+						 new_prefix,
+						 is_last_child);
+    }
+}
+
+/* FIXME.  */
+
+void
+region::dump_child_label (const region_model &model ATTRIBUTE_UNUSED,
+			  region_id this_rid ATTRIBUTE_UNUSED,
+			  region_id child_rid ATTRIBUTE_UNUSED,
+			  pretty_printer *pp ATTRIBUTE_UNUSED) const
+{
+  /* Empty.  */
+}
+
+#if 0
+/* FIXME.  */
+
+std::string
+region::to_str () const
+{
+  pretty_printer pp;
+  print (&pp);
+  return pp_formatted_text (&pp);
+}
+#endif
+
+/* FIXME.  */
+
+void
+region::remap_svalue_ids (const svalue_id_map &map)
+{
+  map.update (&m_sval_id);
+}
+
+/* Base implementation of region::remap_region_ids vfunc; subclasses should
+   chain up to this, updating any region_id data.  */
+
+void
+region::remap_region_ids (const region_id_map &map)
+{
+  map.update (&m_parent_rid);
+}
+
+/* Base implementation of region::add_to_hash vfunc; subclasses should
+   chain up to this.  */
+
+void
+region::add_to_hash (inchash::hash &hstate) const
+{
+  inchash::add (m_parent_rid, hstate);
+  inchash::add (m_sval_id, hstate);
+  hstate.add_ptr (m_type);
+}
+
+/* FIXME.  */
+
+void
+region::print_fields (const region_model &model ATTRIBUTE_UNUSED,
+		      region_id this_rid ATTRIBUTE_UNUSED,
+		      pretty_printer *pp) const
+{
+  pp_printf (pp, "kind: %qs", region_kind_to_str (get_kind ()));
+
+  pp_string (pp, ", parent: ");
+  m_parent_rid.print (pp);
+
+  pp_printf (pp, ", sval: ");
+  m_sval_id.print (pp);
+
+PUSH_IGNORE_WFORMAT
+  if (m_type)
+    pp_printf (pp, ", type: %qT", m_type);
+POP_IGNORE_WFORMAT
+}
+
+/* class map_region : public region.  */
+
+/* map_region's copy ctor.  */
+
+map_region::map_region (const map_region &other)
+: region (other),
+  m_map (other.m_map)
+{
+}
+
+/* Compare the fields of this map_region with OTHER, returning true
+   if they are equal.
+   For use by region::operator==.  */
+
+bool
+map_region::compare_fields (const map_region &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)
+    {
+      tree key = (*iter).first;
+      region_id e = (*iter).second;
+      region_id *other_slot = const_cast <map_t &> (other.m_map).get (key);
+      if (other_slot == NULL)
+	return false;
+      if (e != *other_slot)
+	return false;
+    }
+  return true;
+}
+
+/* Implementation of region::clone vfunc for map_region.  */
+
+region *
+map_region::clone () const
+{
+  return new map_region (*this);
+}
+
+/* FIXME.  */
+
+void
+map_region::print_fields (const region_model &model,
+			  region_id this_rid,
+			  pretty_printer *pp) const
+{
+  region::print_fields (model, this_rid, pp);
+  pp_string (pp, ", map: {");
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      if (iter != m_map.begin ())
+	pp_string (pp, ", ");
+      tree expr = (*iter).first;
+      region_id child_rid = (*iter).second;
+PUSH_IGNORE_WFORMAT
+      pp_printf (pp, "%qE: ", expr);
+POP_IGNORE_WFORMAT
+      child_rid.print (pp);
+    }
+  pp_string (pp, "}");
+}
+
+/* FIXME.  */
+
+void
+map_region::dump_dot_to_pp (const region_model &model,
+			    region_id this_rid,
+			    pretty_printer *pp) const
+{
+  region::dump_dot_to_pp (model, this_rid, pp);
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      // TODO: add nodes/edges to label things
+
+      tree expr = (*iter).first;
+      region_id child_rid = (*iter).second;
+
+      pp_printf (pp, "rid_label_%i [label=\"", child_rid.as_int ());
+      pp_write_text_to_stream (pp);
+PUSH_IGNORE_WFORMAT
+      pp_printf (pp, "%qE", expr);
+POP_IGNORE_WFORMAT
+      pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/false);
+      pp_string (pp, "\"];");
+      pp_newline (pp);
+
+      pp_printf (pp, "rid_label_%i", child_rid.as_int ());
+      pp_string (pp, " -> ");
+      child_rid.dump_node_name_to_pp (pp);
+      pp_string (pp, ";");
+      pp_newline (pp);
+    }
+}
+
+/* FIXME.  */
+
+void
+map_region::dump_child_label (const region_model &model ATTRIBUTE_UNUSED,
+			      region_id this_rid ATTRIBUTE_UNUSED,
+			      region_id child_rid,
+			      pretty_printer *pp) const
+{
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      if (child_rid == (*iter).second)
+	{
+	  tree expr = (*iter).first;
+PUSH_IGNORE_WFORMAT
+	  pp_printf (pp, "%qE: ", expr);
+POP_IGNORE_WFORMAT
+	}
+    }
+}
+
+/* Look for a child region for EXPR within this map_region.
+   If it doesn't already exist, create a child map_region, using TYPE for
+   its type.
+   Return the region_id of the child (whether pre-existing, or
+   newly-created).  */
+
+region_id
+map_region::get_or_create (region_model *model,
+			   region_id this_rid,
+			   tree expr,
+			   tree type)
+{
+  gcc_assert (expr);
+  region_id *slot = m_map.get (expr);
+  if (slot)
+    return *slot;
+  // FIXME: what kind of region?
+  region_id child_rid = model->add_region (new map_region (this_rid, type));
+  m_map.put (expr, child_rid);
+  return child_rid;
+}
+
+/* Implementation of region::add_to_hash vfunc for map_region.  */
+
+void
+map_region::add_to_hash (inchash::hash &hstate) const
+{
+  region::add_to_hash (hstate);
+  // TODO
+}
+
+/* Implementation of region::remap_region_ids vfunc for map_region.  */
+
+void
+map_region::remap_region_ids (const region_id_map &map)
+{
+  region::remap_region_ids (map);
+
+  /* Remap the region ids within the map entries.  */
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end (); ++iter)
+    map.update (&(*iter).second);
+}
+
+/* Remove the binding of EXPR to its child region (but not the
+   child region itself).
+   For use when purging unneeded SSA names.  */
+
+void
+map_region::unbind (tree expr)
+{
+  gcc_assert (expr);
+  m_map.remove (expr);
+}
+
+/* FIXME.  */
+
+tree
+map_region::get_tree_for_child_region (region_id child_rid) const
+{
+  // FIXME: for now, a linear search through map
+  // FIXME: do we want to store an inverse map?
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      tree key = (*iter).first;
+      region_id r = (*iter).second;
+      if (r == child_rid)
+	return key;
+    }
+
+  return NULL_TREE;
+}
+
+/* Comparator for trees to impose a deterministic ordering on
+   T1 and T2.  */
+
+static int
+tree_cmp (const_tree t1, const_tree t2)
+{
+  gcc_assert (t1);
+  gcc_assert (t2);
+
+  /* Test tree codes first.  */
+  if (TREE_CODE (t1) != TREE_CODE (t2))
+    return TREE_CODE (t1) - TREE_CODE (t2);
+
+  /* From this point on, we know T1 and T2 have the same tree code.  */
+
+  if (DECL_P (t1))
+    {
+      if (DECL_NAME (t1) && DECL_NAME (t2))
+	return strcmp (IDENTIFIER_POINTER (DECL_NAME (t1)),
+		       IDENTIFIER_POINTER (DECL_NAME (t2)));
+      else
+	{
+	  if (DECL_NAME (t1))
+	    return -1;
+	  else if (DECL_NAME (t2))
+	    return 1;
+	  else
+	    {
+	      // FIXME
+	      gcc_unreachable ();
+	    }
+	}
+    }
+
+  switch (TREE_CODE (t1))
+    {
+    case SSA_NAME:
+      {
+	if (SSA_NAME_VAR (t1) && SSA_NAME_VAR (t2))
+	  {
+	    int var_cmp = tree_cmp (SSA_NAME_VAR (t1), SSA_NAME_VAR (t2));
+	    if (var_cmp)
+	      return var_cmp;
+	    return SSA_NAME_VERSION (t1) - SSA_NAME_VERSION (t2);
+	  }
+	else
+	  {
+	    if (SSA_NAME_VAR (t1))
+	      return -1;
+	    else if (SSA_NAME_VAR (t2))
+	      return 1;
+	    else
+	      return SSA_NAME_VERSION (t1) - SSA_NAME_VERSION (t2);
+	  }
+      }
+      break;
+
+    case INTEGER_CST:
+      return tree_int_cst_compare (t1, t2);
+
+    case REAL_CST:
+      {
+	real_value *rv1 = TREE_REAL_CST_PTR (t1);
+	real_value *rv2 = TREE_REAL_CST_PTR (t2);
+	if (real_compare (LT_EXPR, rv1, rv2))
+	  return -1;
+	if (real_compare (LT_EXPR, rv2, rv1))
+	  return 1;
+	return 0;
+      }
+
+    case STRING_CST:
+      // FIXME: does this assume that they're nil-terminated?
+      // FIXME: are they?
+      return strcmp (TREE_STRING_POINTER (t1),
+		     TREE_STRING_POINTER (t2));
+
+    default:
+      gcc_unreachable ();
+      break;
+    }
+
+  gcc_unreachable ();
+
+  return 0;
+}
+
+/* qsort comparator for trees to impose a deterministic ordering on
+   P1 and P2.  */
+
+static int
+tree_cmp (const void *p1, const void *p2)
+{
+  const_tree t1 = *(const_tree const *)p1;
+  const_tree t2 = *(const_tree const *)p2;
+
+  int result = tree_cmp (t1, t2);
+
+  /* Check that the ordering is symmetric  */
+#if CHECKING_P
+  int reversed = tree_cmp (t2, t1);
+  gcc_assert (reversed == -result);
+#endif
+
+  /* We should only have 0 for equal pairs.  */
+#if 0
+  gcc_assert (result != 0
+	      || t1 == t2);
+#endif
+
+  return result;
+}
+
+/* Attempt to merge MAP_REGION_A and MAP_REGION_B into MERGED_MAP_REGION,
+   which has region_id MERGED_RID, using MERGER.
+   Return true if the merger is possible, false otherwise.  */
+
+bool
+map_region::can_merge_p (const map_region *map_region_a,
+			 const map_region *map_region_b,
+			 map_region *merged_map_region,
+			 region_id merged_rid,
+			 model_merger *merger)
+{
+  // TODO:
+  /* Sort keys into a consistent order, to avoid cache misses
+     due to svalues/regions being in different orders between
+     two region_model instances.  */
+  auto_vec<tree> keys;
+  for (map_t::iterator iter = map_region_a->m_map.begin ();
+       iter != map_region_a->m_map.end ();
+       ++iter)
+    {
+      tree key_a = (*iter).first;
+      keys.safe_push (key_a);
+    }
+#if 0
+  keys.qsort (tree_cmp);
+#endif
+
+  unsigned i;
+  tree key_a;
+  FOR_EACH_VEC_ELT (keys, i, key_a)
+    {
+      region_id rid_a
+	= *const_cast<map_region *>(map_region_a)->m_map.get (key_a);
+
+      if (const region_id *slot_b
+	  = const_cast<map_region *>(map_region_b)->m_map.get (key_a))
+	{
+	  region_id rid_b = *slot_b;
+
+	  /* FIXME: assume that they too are map regions.
+	     We want to recurse.
+	     We also want to do something about values.  */
+	  map_region *child_region_a
+	    = merger->get_region_a <map_region> (rid_a);
+	  map_region *child_region_b
+	    = merger->get_region_b <map_region> (rid_b);
+
+	  gcc_assert (child_region_a->get_type ()
+		      == child_region_b->get_type ());
+
+	  region_id child_merged_rid
+	    = merged_map_region->get_or_create (merger->m_merged_model,
+						merged_rid,
+						key_a,
+						child_region_a->get_type ());
+
+	  map_region *child_merged_region
+	    = merger->m_merged_model->get_region<map_region> (child_merged_rid);
+
+	  /* TODO: consider values.  */
+	  svalue_id child_a_sid = child_region_a->get_value_direct ();
+	  svalue_id child_b_sid = child_region_b->get_value_direct ();
+	  svalue_id child_merged_sid;
+	  if (!merger->can_merge_values_p (child_a_sid, child_b_sid,
+					   &child_merged_sid))
+	    return false;
+	  if (!child_merged_sid.null_p ())
+	    child_merged_region->set_value (*merger->m_merged_model,
+					    child_merged_sid);
+
+	  /* Recurse.  */
+	  if (!can_merge_p (child_region_a,
+			    child_region_b,
+			    child_merged_region,
+			    child_merged_rid,
+			    merger))
+	    return false;
+	}
+      else
+	{
+	  /* region is present in A, but absent in B.  */
+	  // TODO
+	}
+
+      // TODO
+    }
+
+  // TODO: should probably also check for things in b that aren't in a.
+
+  return true;
+}
+
+
+/* Implementation of region::walk_for_canonicalization vfunc for
+   map_region.  */
+
+void
+map_region::walk_for_canonicalization (canonicalization *c) const
+{
+  auto_vec<tree> keys;
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      tree key_a = (*iter).first;
+      keys.safe_push (key_a);
+    }
+  keys.qsort (tree_cmp);
+
+  unsigned i;
+  tree key;
+  FOR_EACH_VEC_ELT (keys, i, key)
+    {
+      region_id rid = *const_cast<map_region *>(this)->m_map.get (key);
+      c->walk_rid (rid);
+    }
+}
+
+/* For debugging purposes: look for a child region for a decl named
+   IDENTIFIER (or an SSA_NAME for such a decl), returning its value,
+   or svalue_id::null if none are found.  */
+
+svalue_id
+map_region::get_value_by_name (tree identifier,
+			       const region_model &model) const
+{
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      tree key = (*iter).first;
+      if (TREE_CODE (key) == SSA_NAME)
+	if (SSA_NAME_VAR (key))
+	  key = SSA_NAME_VAR (key);
+      if (DECL_P (key))
+	if (DECL_NAME (key) == identifier)
+	  {
+	    region_id rid = (*iter).second;
+	    region *region = model.get_region (rid);
+	    return region->get_value (const_cast<region_model &>(model),
+				      false, NULL);
+
+	  }
+    }
+  return svalue_id::null ();
+}
+
+/* class frame_region : public map_region.  */
+
+/* Compare the fields of this frame_region with OTHER, returning true
+   if they are equal.
+   For use by region::operator==.  */
+
+bool
+frame_region::compare_fields (const frame_region &other) const
+{
+  if (!map_region::compare_fields (other))
+    return false;
+  if (m_fun != other.m_fun)
+    return false;
+  if (m_depth != other.m_depth)
+    return false;
+  return true;
+}
+
+/* Implementation of region::clone vfunc for frame_region.  */
+
+region *
+frame_region::clone () const
+{
+  return new frame_region (*this);
+}
+
+/* FIXME.  */
+
+void
+frame_region::print_fields (const region_model &model,
+			    region_id this_rid,
+			    pretty_printer *pp) const
+{
+  map_region::print_fields (model, this_rid, pp);
+  pp_printf (pp, ", function: %qs, depth: %i", function_name (m_fun), m_depth);
+}
+
+/* Implementation of region::add_to_hash vfunc for frame_region.  */
+
+void
+frame_region::add_to_hash (inchash::hash &hstate) const
+{
+  map_region::add_to_hash (hstate);
+  hstate.add_ptr (m_fun);
+  hstate.add_int (m_depth);
+}
+
+/* class stack_region : public region.  */
+
+/* stack_region's copy ctor.  */
+
+stack_region::stack_region (const stack_region &other)
+: region (other),
+  m_frame_rids (other.m_frame_rids.length ())
+{
+  int i;
+  region_id *frame_rid;
+  FOR_EACH_VEC_ELT (other.m_frame_rids, i, frame_rid)
+    m_frame_rids.quick_push (*frame_rid);
+}
+
+/* Compare the fields of this stack_region with OTHER, returning true
+   if they are equal.
+   For use by region::operator==.  */
+
+bool
+stack_region::compare_fields (const stack_region &other) const
+{
+  if (m_frame_rids.length () != other.m_frame_rids.length ())
+    return false;
+
+  int i;
+  region_id *frame_rid;
+  FOR_EACH_VEC_ELT (m_frame_rids, i, frame_rid)
+    if (m_frame_rids[i] != other.m_frame_rids[i])
+      return false;
+
+  return true;
+}
+
+/* Implementation of region::clone vfunc for stack_region.  */
+
+region *
+stack_region::clone () const
+{
+  return new stack_region (*this);
+}
+
+/* FIXME.  */
+
+void
+stack_region::print_fields (const region_model &model,
+			    region_id this_rid,
+			    pretty_printer *pp) const
+{
+  region::print_fields (model, this_rid, pp);
+  // TODO
+}
+
+/* FIXME.  */
+
+void
+stack_region::dump_child_label (const region_model &model,
+				region_id this_rid ATTRIBUTE_UNUSED,
+				region_id child_rid,
+				pretty_printer *pp) const
+{
+  function *fun = model.get_region<frame_region> (child_rid)->get_function ();
+  pp_printf (pp, "frame for %qs: ", function_name (fun));
+}
+
+/* FIXME.  */
+
+void
+stack_region::push_frame (region_id frame_rid)
+{
+  m_frame_rids.safe_push (frame_rid);
+}
+
+/* FIXME.  */
+
+region_id
+stack_region::get_current_frame_id () const
+{
+  if (m_frame_rids.length () > 0)
+    return m_frame_rids[m_frame_rids.length () - 1];
+  else
+    return region_id::null ();
+}
+
+/* Pop the topmost frame_region from this stack.
+
+   Purge the frame region and all its descendent regions.
+   Convert any pointers that point into such regions into
+   POISON_KIND_POPPED_STACK svalues.
+
+   Return the ID of any return value from the frame.
+
+   Purge all unused svalues, with the exception of any
+   return value for the frame, which is temporarily
+   preserved in case no regions reference it, so it can
+   be written into a region in the caller.
+
+   Accumulate stats on purged entities into STATS.  */
+
+svalue_id
+stack_region::pop_frame (region_model *model, purge_stats *stats,
+			 region_model_context *ctxt)
+{
+  gcc_assert (m_frame_rids.length () > 0);
+
+  region_id frame_rid = get_current_frame_id ();
+  frame_region *frame = model->get_region<frame_region> (frame_rid);
+
+  /* Evaluate the result, within the callee frame.  */
+  svalue_id result_sid;
+  tree fndecl = frame->get_function ()->decl;
+  tree result = DECL_RESULT (fndecl);
+  if (result)
+    result_sid = model->get_rvalue (result, ctxt);
+
+  /* Pop the frame RID.  */
+  m_frame_rids.pop ();
+
+  model->delete_region_and_descendents (frame_rid,
+					POISON_KIND_POPPED_STACK,
+					stats,
+					ctxt ? ctxt->get_logger () : NULL);
+
+  /* Delete unused svalues, but don't delete the return value.  */
+  model->purge_unused_svalues (stats, ctxt, &result_sid);
+
+  model->validate ();
+
+  return result_sid;
+}
+
+/* Implementation of region::add_to_hash vfunc for stack_region.  */
+
+void
+stack_region::add_to_hash (inchash::hash &hstate) const
+{
+  region::add_to_hash (hstate);
+
+  int i;
+  region_id *frame_rid;
+  FOR_EACH_VEC_ELT (m_frame_rids, i, frame_rid)
+    inchash::add (*frame_rid, hstate);
+}
+
+/* Implementation of region::remap_region_ids vfunc for stack_region.  */
+
+void
+stack_region::remap_region_ids (const region_id_map &map)
+{
+  region::remap_region_ids (map);
+  int i;
+  region_id *frame_rid;
+  FOR_EACH_VEC_ELT (m_frame_rids, i, frame_rid)
+    map.update (&m_frame_rids[i]);
+}
+
+/* Attempt to merge STACK_REGION_A and STACK_REGION_B using MERGER.
+   Return true if the merger is possible, false otherwise.  */
+
+bool
+stack_region::can_merge_p (const stack_region *stack_region_a,
+			   const stack_region *stack_region_b,
+			   model_merger *merger)
+{
+  if (stack_region_a->get_num_frames ()
+      != stack_region_b->get_num_frames ())
+    return false;
+
+  for (unsigned i = 0; i < stack_region_a->get_num_frames (); i++)
+    {
+      region_id rid_a = stack_region_a->get_frame_rid (i);
+      frame_region *frame_a = merger->get_region_a <frame_region> (rid_a);
+
+      region_id rid_b = stack_region_b->get_frame_rid (i);
+      frame_region *frame_b = merger->get_region_b <frame_region> (rid_b);
+
+      if (frame_a->get_function () != frame_b->get_function ())
+	return false;
+      merger->m_merged_model->push_frame (frame_a->get_function (), NULL);
+
+      region_id rid_merged = merger->m_merged_model->get_current_frame_id ();
+      frame_region *merged_frame
+	= merger->m_merged_model->get_region <frame_region> (rid_merged);
+
+      if (!map_region::can_merge_p (frame_a, frame_b,
+				    merged_frame, rid_merged,
+				    merger))
+	return false;
+    }
+
+  return true;
+}
+
+/* Implementation of region::walk_for_canonicalization vfunc for
+   stack_region.  */
+
+void
+stack_region::walk_for_canonicalization (canonicalization *c) const
+{
+  int i;
+  region_id *frame_rid;
+  FOR_EACH_VEC_ELT (m_frame_rids, i, frame_rid)
+    c->walk_rid (*frame_rid);
+}
+
+/* For debugging purposes: look for a grandchild region within one of
+   the child frame regions, where the grandchild is for a decl named
+   IDENTIFIER (or an SSA_NAME for such a decl):
+
+     stack_region
+     `-frame_region
+       `-region for decl named IDENTIFIER
+
+   returning its value, or svalue_id::null if none are found.  */
+
+svalue_id
+stack_region::get_value_by_name (tree identifier,
+				 const region_model &model) const
+{
+  int i;
+  region_id *frame_rid;
+  FOR_EACH_VEC_ELT (m_frame_rids, i, frame_rid)
+    {
+      frame_region *frame = model.get_region<frame_region> (*frame_rid);
+      svalue_id sid = frame->get_value_by_name (identifier, model);
+      if (!sid.null_p ())
+	return sid;
+    }
+
+  return svalue_id::null ();
+}
+
+/* class heap_region : public region.  */
+
+/* heap_region's copy ctor.  */
+
+heap_region::heap_region (const heap_region &other)
+: region (other)
+{
+}
+
+/* Compare the fields of this heap_region with OTHER, returning true
+   if they are equal.
+   For use by region::operator==.  */
+
+bool
+heap_region::compare_fields (const heap_region &) const
+{
+  /* Empty.  */
+  return true;
+}
+
+/* Implementation of region::clone vfunc for heap_region.  */
+
+region *
+heap_region::clone () const
+{
+  return new heap_region (*this);
+}
+
+/* FIXME.  */
+
+bool
+heap_region::can_merge_p (const heap_region *heap_a ATTRIBUTE_UNUSED,
+			  region_id heap_a_rid ATTRIBUTE_UNUSED,
+			  const heap_region *heap_b ATTRIBUTE_UNUSED,
+			  region_id heap_b_rid ATTRIBUTE_UNUSED,
+			  heap_region *merged_heap ATTRIBUTE_UNUSED,
+			  region_id merged_heap_rid ATTRIBUTE_UNUSED,
+			  model_merger *merger ATTRIBUTE_UNUSED)
+{
+  /* TODO:
+     iterate through descendents of each, cloning
+     But how to match them up between the two?
+     Assume they're in the same order?
+     Or do it based on what's pointing to them?
+     Maybe do it from the region_svalue instances, as needed, when
+     seeing pairs of region_svalue instances.  */
+
+  //gcc_unreachable (); // TODO
+  //return false;
+  return true;
+}
+
+/* Implementation of region::walk_for_canonicalization vfunc for
+   heap_region.  */
+
+void
+heap_region::walk_for_canonicalization (canonicalization *) const
+{
+  /* Empty.  */
+}
+
+/* class root_region : public region.  */
+
+/* root_region's default ctor.  */
+
+root_region::root_region ()
+: region (region_id::null (),
+	  svalue_id::null (),
+	  NULL_TREE)
+{
+}
+
+/* root_region's copy ctor.  */
+
+root_region::root_region (const root_region &other)
+: region (other),
+  m_stack_rid (other.m_stack_rid),
+  m_globals_rid (other.m_globals_rid),
+  m_heap_rid (other.m_heap_rid)
+{
+}
+
+/* Compare the fields of this root_region with OTHER, returning true
+   if they are equal.
+   For use by region::operator==.  */
+
+bool
+root_region::compare_fields (const root_region &other) const
+{
+  if (m_stack_rid != other.m_stack_rid)
+    return false;
+  if (m_globals_rid != other.m_globals_rid)
+    return false;
+  if (m_heap_rid != other.m_heap_rid)
+    return false;
+  return true;
+}
+
+/* Implementation of region::clone vfunc for root_region.  */
+
+region *
+root_region::clone () const
+{
+  return new root_region (*this);
+}
+/* FIXME.  */
+
+void
+root_region::print_fields (const region_model &model,
+			   region_id this_rid,
+			   pretty_printer *pp) const
+{
+  region::print_fields (model, this_rid, pp);
+  // TODO
+}
+
+/* FIXME.  */
+
+void
+root_region::dump_child_label (const region_model &model ATTRIBUTE_UNUSED,
+			       region_id this_rid ATTRIBUTE_UNUSED,
+			       region_id child_rid,
+			       pretty_printer *pp) const
+{
+  if (child_rid == m_stack_rid)
+    pp_printf (pp, "stack: ");
+  else if (child_rid == m_globals_rid)
+    pp_printf (pp, "globals: ");
+  else if (child_rid == m_heap_rid)
+    pp_printf (pp, "heap: ");
+}
+
+/* FIXME.  */
+
+region_id
+root_region::push_frame (region_model *model, function *fun,
+			 vec<svalue_id> *arg_sids)
+{
+  gcc_assert (fun);
+  /* arg_sids can be NULL.  */
+
+  ensure_stack_region (model);
+  stack_region *stack = model->get_region <stack_region> (m_stack_rid);
+
+  frame_region *region = new frame_region (m_stack_rid, fun,
+					   stack->get_num_frames ());
+  region_id frame_rid = model->add_region (region);
+
+  // TODO: unify these cases by building a vec of unknown?
+
+  if (arg_sids)
+    {
+      /* Arguments supplied from a caller frame.  */
+
+      tree fndecl = fun->decl;
+      int idx = 0;
+      for (tree iter_parm = DECL_ARGUMENTS (fndecl); iter_parm;
+	   iter_parm = DECL_CHAIN (iter_parm), ++idx)
+	{
+	  svalue_id arg_sid = (*arg_sids)[idx];
+	  region_id parm_rid
+	    = region->get_or_create (model, frame_rid, iter_parm,
+				     TREE_TYPE (iter_parm));
+	  model->set_value (parm_rid, arg_sid);
+
+	  /* Also do it for default SSA name (sharing the same unknown
+	     value).  */
+	  tree parm_default_ssa = ssa_default_def (fun, iter_parm);
+	  if (parm_default_ssa)
+	    {
+	      region_id defssa_rid
+		= region->get_or_create (model, frame_rid, parm_default_ssa,
+					 TREE_TYPE (iter_parm));
+	      model->set_value (defssa_rid, arg_sid);
+	    }
+	}
+    }
+  else
+    {
+      /* No known arguments (a top-level call within the analysis).  */
+
+      /* Params have a defined, unknown value; they should not inherit
+	 from the poisoned uninit value.  */
+      tree fndecl = fun->decl;
+      for (tree iter_parm = DECL_ARGUMENTS (fndecl); iter_parm;
+	   iter_parm = DECL_CHAIN (iter_parm))
+	{
+	  region_id parm_rid
+	    = region->get_or_create (model, frame_rid, iter_parm,
+				     TREE_TYPE (iter_parm));
+	  svalue_id parm_sid
+	    = model->set_to_new_unknown_value (parm_rid, TREE_TYPE (iter_parm));
+
+	  /* Also do it for default SSA name (sharing the same unknown
+	     value).  */
+	  tree parm_default_ssa = ssa_default_def (fun, iter_parm);
+	  if (parm_default_ssa)
+	    {
+	      region_id defssa_rid
+		= region->get_or_create (model, frame_rid, parm_default_ssa,
+					 TREE_TYPE (iter_parm));
+	      model->get_region (defssa_rid)->set_value (*model, parm_sid);
+	    }
+	}
+    }
+
+  stack->push_frame (frame_rid);
+
+  return frame_rid;
+}
+
+/* FIXME.  */
+
+region_id
+root_region::get_current_frame_id (const region_model &model) const
+{
+  stack_region *stack = model.get_region <stack_region> (m_stack_rid);
+  if (stack)
+    return stack->get_current_frame_id ();
+  else
+    return region_id::null ();
+}
+
+/* FIXME.  */
+
+svalue_id
+root_region::pop_frame (region_model *model, purge_stats *out,
+			region_model_context *ctxt)
+{
+  stack_region *stack = model->get_region <stack_region> (m_stack_rid);
+  return stack->pop_frame (model, out, ctxt);
+}
+
+/* Return the region_id of the stack region, creating it if doesn't
+   already exist.  */
+
+region_id
+root_region::ensure_stack_region (region_model *model)
+{
+  if (m_stack_rid.null_p ())
+    {
+      svalue_id uninit_sid
+	= model->add_svalue (new poisoned_svalue (POISON_KIND_UNINIT));
+      m_stack_rid
+	= model->add_region (new stack_region (model->get_root_rid (),
+					       uninit_sid));
+    }
+  return m_stack_rid;
+}
+
+/* Return the stack region (which could be NULL).  */
+
+stack_region *
+root_region::get_stack_region (const region_model *model) const
+{
+  return model->get_region <stack_region> (m_stack_rid);
+}
+
+/* Return the region_id of the globals region, creating it if doesn't
+   already exist.  */
+
+region_id
+root_region::ensure_globals_region (region_model *model)
+{
+  if (m_globals_rid.null_p ())
+    m_globals_rid
+      = model->add_region (new map_region (model->get_root_rid (), NULL_TREE));
+  return m_globals_rid;
+}
+
+/* Return the globals region (which could be NULL).  */
+
+map_region *
+root_region::get_globals_region (const region_model *model) const
+{
+  return model->get_region <map_region> (m_globals_rid);
+}
+
+/* Return the region_id of the heap region, creating it if doesn't
+   already exist.  */
+
+region_id
+root_region::ensure_heap_region (region_model *model)
+{
+  if (m_heap_rid.null_p ())
+    {
+      svalue_id uninit_sid
+	= model->add_svalue (new poisoned_svalue (POISON_KIND_UNINIT));
+      // FIXME: maybe share uninit values?
+      m_heap_rid
+	= model->add_region (new heap_region (model->get_root_rid (),
+					      uninit_sid));
+    }
+  return m_heap_rid;
+}
+
+/* Return the heap region (which could be NULL).  */
+
+heap_region *
+root_region::get_heap_region (const region_model *model) const
+{
+  return model->get_region <heap_region> (m_heap_rid);
+}
+
+/* Implementation of region::remap_region_ids vfunc for root_region.  */
+
+void
+root_region::remap_region_ids (const region_id_map &map)
+{
+  map.update (&m_stack_rid);
+  map.update (&m_globals_rid);
+  map.update (&m_heap_rid);
+}
+
+/* Attempt to merge ROOT_REGION_A and ROOT_REGION_B into
+   MERGED_ROOT_REGION using MERGER.
+   Return true if the merger is possible, false otherwise.  */
+
+bool
+root_region::can_merge_p (const root_region *root_region_a,
+			  const root_region *root_region_b,
+			  root_region *merged_root_region,
+			  model_merger *merger)
+{
+  /* We can only merge if the stacks are sufficiently similar.  */
+  stack_region *stack_a = root_region_a->get_stack_region (merger->m_model_a);
+  stack_region *stack_b = root_region_b->get_stack_region (merger->m_model_b);
+  if (stack_a && stack_b)
+    {
+      /* If the two models both have a stack, attempt to merge them.  */
+      merged_root_region->ensure_stack_region (merger->m_merged_model);
+      if (!stack_region::can_merge_p (stack_a, stack_b, merger))
+	return false;
+    }
+  else if (stack_a || stack_b)
+    /* Don't attempt to merge if one model has a stack and the other
+       doesn't.  */
+    return false;
+
+  map_region *globals_a = root_region_a->get_globals_region (merger->m_model_a);
+  map_region *globals_b = root_region_b->get_globals_region (merger->m_model_b);
+  if (globals_a && globals_b)
+    {
+      /* If both models have globals regions, attempt to merge them.  */
+      region_id merged_globals_rid
+	= merged_root_region->ensure_globals_region (merger->m_merged_model);
+      map_region *merged_globals
+	= merged_root_region->get_globals_region (merger->m_merged_model);
+      if (!map_region::can_merge_p (globals_a, globals_b,
+				    merged_globals, merged_globals_rid,
+				    merger))
+	return false;
+    }
+  /* otherwise, merge as "no globals".  */
+
+  heap_region *heap_a = root_region_a->get_heap_region (merger->m_model_a);
+  heap_region *heap_b = root_region_b->get_heap_region (merger->m_model_b);
+  if (heap_a && heap_b)
+    {
+      region_id merged_heap_rid
+	= merged_root_region->ensure_heap_region (merger->m_merged_model);
+      heap_region *merged_heap
+	= merged_root_region->get_heap_region (merger->m_merged_model);
+      if (!heap_region::can_merge_p (heap_a, root_region_a->m_heap_rid,
+				     heap_b, root_region_b->m_heap_rid,
+				     merged_heap, merged_heap_rid,
+				     merger))
+	return false;
+    }
+  /* otherwise, merge as "no heap".  */
+
+  return true;
+}
+
+/* Implementation of region::add_to_hash vfunc for root_region.  */
+
+void
+root_region::add_to_hash (inchash::hash &hstate) const
+{
+  region::add_to_hash (hstate);
+  inchash::add (m_stack_rid, hstate);
+  inchash::add (m_globals_rid, hstate);
+  inchash::add (m_heap_rid, hstate);
+}
+
+/* Implementation of region::walk_for_canonicalization vfunc for
+   root_region.  */
+
+void
+root_region::walk_for_canonicalization (canonicalization *c) const
+{
+  c->walk_rid (m_stack_rid);
+  c->walk_rid (m_globals_rid);
+  c->walk_rid (m_heap_rid);
+}
+
+/* For debugging purposes: look for a descendant region for a local
+   or global decl named IDENTIFIER (or an SSA_NAME for such a decl),
+   returning its value, or svalue_id::null if none are found.  */
+
+svalue_id
+root_region::get_value_by_name (tree identifier,
+				const region_model &model) const
+{
+  if (stack_region *stack = get_stack_region (&model))
+    {
+      svalue_id sid = stack->get_value_by_name (identifier, model);
+      if (!sid.null_p ())
+	return sid;
+    }
+  if (map_region *globals = get_globals_region (&model))
+    {
+      svalue_id sid = globals->get_value_by_name (identifier, model);
+      if (!sid.null_p ())
+	return sid;
+    }
+  return svalue_id::null ();
+}
+
+/* class symbolic_region : public map_region.  */
+
+/* symbolic_region's copy ctor.  */
+
+symbolic_region::symbolic_region (const symbolic_region &other)
+: map_region (other)
+{
+}
+
+/* Compare the fields of this symbolic_region with OTHER, returning true
+   if they are equal.
+   For use by region::operator==.  */
+
+bool
+symbolic_region::compare_fields (const symbolic_region &) const
+{
+  /* Empty.  */
+  return true;
+}
+
+/* Implementation of region::clone vfunc for symbolic_region.  */
+
+region *
+symbolic_region::clone () const
+{
+  return new symbolic_region (*this);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class region_model.  */
+
+/* region_model's default ctor.  */
+
+region_model::region_model ()
+{
+  m_root_rid = add_region (new root_region ());
+  m_constraints = new impl_constraint_manager (this);
+  // TODO
+}
+
+/* region_model's copy ctor.  */
+
+region_model::region_model (const region_model &other)
+: m_svalues (other.m_svalues.length ()),
+  m_regions (other.m_regions.length ()),
+  m_root_rid (other.m_root_rid)
+{
+  /* Clone the svalues and regions.  */
+  int i;
+
+  svalue *svalue;
+  FOR_EACH_VEC_ELT (other.m_svalues, i, svalue)
+    m_svalues.quick_push (svalue->clone ());
+
+  region *region;
+  FOR_EACH_VEC_ELT (other.m_regions, i, region)
+    m_regions.quick_push (region->clone ());
+
+  m_constraints = other.m_constraints->clone (this);
+}
+
+/* region_model's dtor.  */
+
+region_model::~region_model ()
+{
+  delete m_constraints;
+}
+
+/* region_model's assignment operator.  */
+
+region_model &
+region_model::operator= (const region_model &other)
+{
+  unsigned i;
+  svalue *svalue;
+  region *region;
+
+  /* Delete existing content.  */
+  FOR_EACH_VEC_ELT (m_svalues, i, svalue)
+    delete svalue;
+  m_svalues.truncate (0);
+
+  FOR_EACH_VEC_ELT (m_regions, i, region)
+    delete region;
+  m_regions.truncate (0);
+
+  delete m_constraints;
+
+  /* Clone the svalues and regions.  */
+  m_svalues.reserve (other.m_svalues.length (), true);
+  FOR_EACH_VEC_ELT (other.m_svalues, i, svalue)
+    m_svalues.quick_push (svalue->clone ());
+
+  m_regions.reserve (other.m_regions.length (), true);
+  FOR_EACH_VEC_ELT (other.m_regions, i, region)
+    m_regions.quick_push (region->clone ());
+
+  m_root_rid = other.m_root_rid;
+
+  m_constraints = other.m_constraints->clone (this);
+
+  return *this;
+}
+
+/* FIXME.  */
+
+bool
+region_model::operator== (const region_model &other) const
+{
+  /* FIXME: for now, simple equality checking, rather than an attempt
+     to do it independent of ordering.  */
+
+  if (m_root_rid != other.m_root_rid)
+    return false;
+
+  if (m_svalues.length () != other.m_svalues.length ())
+    return false;
+
+  if (m_regions.length () != other.m_regions.length ())
+    return false;
+
+  if (*m_constraints != *other.m_constraints)
+    return false;
+
+  unsigned i;
+  svalue *svalue;
+  FOR_EACH_VEC_ELT (other.m_svalues, i, svalue)
+    if (!(*m_svalues[i] == *other.m_svalues[i]))
+      return false;
+
+  region *region;
+  FOR_EACH_VEC_ELT (other.m_regions, i, region)
+    if (!(*m_regions[i] == *other.m_regions[i]))
+      return false;
+
+  return true;
+}
+
+/* Generate a hash value for this region_model.  */
+
+hashval_t
+region_model::hash () const
+{
+  hashval_t result = 0;
+  int i;
+
+  svalue *svalue;
+  FOR_EACH_VEC_ELT (m_svalues, i, svalue)
+    result ^= svalue->hash ();
+
+  region *region;
+  FOR_EACH_VEC_ELT (m_regions, i, region)
+    result ^= region->hash ();
+
+  // FIXME: constraints
+
+  return result;
+}
+
+/* FIXME.  */
+
+void
+region_model::print (pretty_printer *pp) const
+{
+  // TODO
+
+  int i;
+
+  pp_string (pp, "svalues: [");
+  svalue *svalue;
+  FOR_EACH_VEC_ELT (m_svalues, i, svalue)
+    {
+      if (i > 0)
+	pp_string (pp, ", ");
+      print_svalue (svalue_id::from_int (i), pp);
+    }
+
+  pp_string (pp, "], regions: [");
+
+  region *region;
+  FOR_EACH_VEC_ELT (m_regions, i, region)
+    {
+      if (i > 0)
+	pp_string (pp, ", ");
+      region->print (*this, region_id::from_int (i), pp);
+    }
+
+  pp_string (pp, "], constraints: ");
+
+  m_constraints->print (pp);
+}
+
+/* FIXME.  */
+
+std::string
+region_model::to_str () const
+{
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  print (&pp);
+  return pp_formatted_text (&pp);
+}
+
+/* FIXME.  */
+
+void
+region_model::print_svalue (svalue_id sid, pretty_printer *pp) const
+{
+  get_svalue (sid)->print (*this, sid, pp);
+}
+
+/* FIXME.  */
+
+void
+region_model::dump_dot_to_pp (pretty_printer *pp) const
+{
+  graphviz_out gv (pp);
+
+  pp_string (pp, "digraph \"");
+  pp_write_text_to_stream (pp);
+  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");
+
+  int i;
+
+  svalue *svalue;
+  FOR_EACH_VEC_ELT (m_svalues, i, svalue)
+    svalue->dump_dot_to_pp (*this, svalue_id::from_int (i), pp);
+
+  region *region;
+  FOR_EACH_VEC_ELT (m_regions, i, region)
+    region->dump_dot_to_pp (*this, region_id::from_int (i), pp);
+
+  // FIXME: constraints
+
+  /* Terminate "digraph" */
+  gv.outdent ();
+  pp_string (pp, "}");
+  pp_newline (pp);
+}
+
+/* FIXME.  */
+
+void
+region_model::dump_dot_to_file (FILE *fp) const
+{
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  pp.buffer->stream = fp;
+  dump_dot_to_pp (&pp);
+  pp_flush (&pp);
+}
+
+/* FIXME.  */
+
+void
+region_model::dump_dot (const char *path) const
+{
+  FILE *fp = fopen (path, "w");
+  dump_dot_to_file (fp);
+  fclose (fp);
+}
+
+/* Dump a multiline representation of this model to PP, showing the
+   region hierarchy, the svalues, and any contraints.  */
+
+void
+region_model::dump_to_pp (pretty_printer *pp) const
+{
+  get_region (m_root_rid)->dump_to_pp (*this, m_root_rid, pp, "", true);
+
+  pp_string (pp, "svalues:");
+  pp_newline (pp);
+  int i;
+  svalue *svalue;
+  FOR_EACH_VEC_ELT (m_svalues, i, svalue)
+    {
+      pp_string (pp, "  ");
+      svalue_id sid = svalue_id::from_int (i);
+      print_svalue (sid, pp);
+      pp_newline (pp);
+    }
+
+  pp_string (pp, "constraint manager:");
+  pp_newline (pp);
+  m_constraints->dump_to_pp (pp);
+}
+
+/* Dump a multiline representation of this model to FILE.  */
+
+void
+region_model::dump (FILE *fp) const
+{
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  pp_show_color (&pp) = pp_show_color (global_dc->printer);
+  pp.buffer->stream = fp;
+  dump_to_pp (&pp);
+  pp_flush (&pp);
+}
+
+/* Dump a multiline representation of this model to stderr.  */
+
+DEBUG_FUNCTION void
+region_model::dump () const
+{
+  dump (stderr);
+}
+
+/* Assert that this object is valid.  */
+
+void
+region_model::validate () const
+{
+  /* Skip this in a release build.  */
+#if !CHECKING_P
+  return;
+#endif
+
+  m_constraints->validate ();
+
+  unsigned i;
+  region *r;
+  FOR_EACH_VEC_ELT (m_regions, i, r)
+    {
+      svalue_id sid = r->get_value_direct ();
+      if (!sid.null_p ())
+	gcc_assert (sid.as_int () < (signed)m_svalues.length ());
+    }
+
+  // TODO: anything else?
+
+  /* Verify that the stack region (if any) has an "uninitialized" value.  */
+  region *stack_region = get_root_region ()->get_stack_region (this);
+  if (stack_region)
+    {
+      svalue_id stack_value_sid = stack_region->get_value_direct ();
+      svalue *stack_value = get_svalue (stack_value_sid);
+      gcc_assert (stack_value->get_kind () == SK_POISONED);
+      poisoned_svalue *subclass = stack_value->dyn_cast_poisoned_svalue ();
+      gcc_assert (subclass);
+      gcc_assert (subclass->get_poison_kind () == POISON_KIND_UNINIT);
+    }
+
+  /* FIXME.  Can use this as a hook for detecting bogus states, to try
+     to track down where they are introduced.  */
+#if 0
+  {
+    /* Find a local matching "ptr".  */
+    svalue_id sid = get_value_by_name ("ptr");
+    if (!sid.null_p ())
+      {
+	svalue *sval = get_svalue (sid);
+	gcc_assert (sval->get_kind () != SK_UNKNOWN);
+      }
+  }
+#endif
+}
+
+/* Global data for use by svalue_id_cmp_by_svalue.  */
+
+static region_model *svalue_id_cmp_by_svalue_model = NULL;
+
+/* Comparator for use by region_model::canonicalize.  */
+
+static int
+svalue_id_cmp_by_svalue (const void *p1, const void *p2)
+{
+  const svalue_id *sid1 = (const svalue_id *)p1;
+  const svalue_id *sid2 = (const svalue_id *)p2;
+  gcc_assert (!sid1->null_p ());
+  gcc_assert (!sid2->null_p ());
+  gcc_assert (svalue_id_cmp_by_svalue_model);
+  const svalue &sval1 = *svalue_id_cmp_by_svalue_model->get_svalue (*sid1);
+  const svalue &sval2 = *svalue_id_cmp_by_svalue_model->get_svalue (*sid2);
+  if (sval1.get_kind () != sval2.get_kind ())
+    return sval1.get_kind () - sval2.get_kind ();
+  switch (sval1.get_kind ())
+    {
+    default:
+      gcc_unreachable ();
+    case SK_CONSTANT:
+      {
+	tree cst1 = ((const constant_svalue &)sval1).get_constant ();
+	tree cst2 = ((const constant_svalue &)sval2).get_constant ();
+	return tree_cmp (cst1, cst2);
+      }
+      break;
+    case SK_POISONED:
+      {
+	const poisoned_svalue &sval1_sub
+	  = (const poisoned_svalue &)sval1;
+	const poisoned_svalue &sval2_sub
+	  = (const poisoned_svalue &)sval2;
+	return sval1_sub.get_poison_kind () - sval2_sub.get_poison_kind ();
+      }
+      break;
+    }
+  // TODO
+}
+
+
+/* Reorder the regions and svalues into a deterministic "canonical" order,
+   to maximize the chance of equality.
+   If non-NULL, notify CTXT about the svalue id remapping.  */
+
+void
+region_model::canonicalize (region_model_context *ctxt)
+{
+#if 0
+  // FIXME: disabled for now
+  return;
+#endif
+
+  /* Walk all regions and values in a deterministic order, visiting
+     rids and sids, generating a rid and sid map.  */
+  canonicalization c (*this);
+
+  /* (1): Walk all svalues, putting poisoned values first, then
+     constants, sorting the constants (thus imposing an ordering on any
+     constants that are purely referenced by constraints).
+     Ignore other svalues for now.  */
+  {
+    unsigned i;
+    auto_vec<svalue_id> sids;
+    svalue *sval;
+    FOR_EACH_VEC_ELT (m_svalues, i, sval)
+      {
+	if (sval->get_kind () == SK_POISONED
+	    || sval->get_kind () == SK_CONSTANT)
+	  sids.safe_push (svalue_id::from_int (i));
+      }
+    svalue_id_cmp_by_svalue_model = this;
+    sids.qsort (svalue_id_cmp_by_svalue);
+    svalue_id_cmp_by_svalue_model = NULL;
+    svalue_id *sid;
+    FOR_EACH_VEC_ELT (sids, i, sid)
+      c.walk_sid (*sid);
+  }
+
+  /* (2): Walk all regions (and thus their values) in a deterministic
+     order.  */
+  c.walk_rid (m_root_rid);
+
+  /* (3): Ensure we've visited everything, as we don't want to purge
+     at this stage.  Anything we visit for the first time here has
+     arbitrary order.  */
+  {
+    unsigned i;
+    region *region;
+    FOR_EACH_VEC_ELT (m_regions, i, region)
+      c.walk_rid (region_id::from_int (i));
+    svalue *sval;
+    FOR_EACH_VEC_ELT (m_svalues, i, sval)
+      c.walk_sid (svalue_id::from_int (i));
+  }
+
+  /* (4): We now have a reordering of the regions and values.
+     Apply it.  */
+  remap_svalue_ids (c.m_sid_map);
+  remap_region_ids (c.m_rid_map);
+  if (ctxt)
+    ctxt->remap_svalue_ids (c.m_sid_map);
+
+  /* (5): Canonicalize the constraint_manager (it has already had its
+     svalue_ids remapped above).  This makes use of the new svalue_id
+     values, and so must happen last.  */
+  m_constraints->canonicalize (get_num_svalues ());
+
+  validate ();
+}
+
+/* Update this model for the ASSIGN stmt, using CTXT to report any
+   diagnostics.  */
+
+void
+region_model::on_assignment (const gassign *assign, region_model_context *ctxt)
+{
+  tree lhs = gimple_assign_lhs (assign);
+  tree rhs1 = gimple_assign_rhs1 (assign);
+
+  region_id lhs_rid = get_lvalue (lhs, ctxt);
+
+  if (lhs_rid.null_p ())
+    return;
+  // TODO: issue a warning for this case
+
+  enum tree_code op = gimple_assign_rhs_code (assign);
+  switch (op)
+    {
+    default:
+      gcc_unreachable ();
+      break;
+
+    case BIT_FIELD_REF:
+      {
+	// TODO
+      }
+      break;
+
+    case CONSTRUCTOR:
+      {
+	/* e.g. "x ={v} {CLOBBER};"  */
+	// TODO
+      }
+      break;
+
+    case POINTER_PLUS_EXPR:
+      {
+	/* e.g. "_1 = a_10(D) + 12;" */
+	tree ptr = rhs1;
+	tree offset = gimple_assign_rhs2 (assign);
+
+	region_id buffer_rid = deref_rvalue (ptr, ctxt);
+#if 0
+	svalue_id offset_sid = get_rvalue (offset, ctxt);
+	/* FIXME: offset is in bytes; we presumably want to
+	   map back to an index.  */
+#endif
+
+	map_region *array_region = get_region<map_region> (buffer_rid);
+	tree element_type = TREE_TYPE (TREE_TYPE (ptr));
+#if 0
+	tree index = offset;
+	/* FIXME: offset is in bytes; we presumably want to
+	   map back to an index.  */
+#endif
+
+	region_id element_rid
+	  = array_region->get_or_create (this, buffer_rid, offset,
+					 element_type);
+	svalue_id element_ptr_sid
+	  = get_or_create_ptr_svalue (TREE_TYPE (ptr), element_rid);
+	set_value (lhs_rid, element_ptr_sid);
+      }
+      break;
+
+    case POINTER_DIFF_EXPR:
+      {
+	/* e.g. "_1 = p_2(D) - q_3(D);".  */
+
+	/* TODO.  */
+
+	set_to_new_unknown_value (lhs_rid, TREE_TYPE (lhs));
+      }
+      break;
+
+    case ADDR_EXPR:
+      {
+	/* LHS = &RHS;  */
+	svalue_id ptr_sid = get_rvalue (rhs1, ctxt);
+	set_value (lhs_rid, ptr_sid);
+      }
+      break;
+
+    case MEM_REF:
+      {
+	region_id rhs_rid = get_lvalue (rhs1, ctxt);
+	svalue_id rhs_sid
+	  = get_region (rhs_rid)->get_value (*this, true, ctxt);
+	set_value (lhs_rid, rhs_sid);
+      }
+      break;
+
+    case REAL_CST:
+    case INTEGER_CST:
+    case ARRAY_REF:
+      {
+	/* LHS = RHS;  */
+	svalue_id cst_sid = get_rvalue (rhs1, ctxt);
+	set_value (lhs_rid, cst_sid);
+      }
+      break;
+
+    case FIX_TRUNC_EXPR:
+    case FLOAT_EXPR:
+    case NOP_EXPR:
+      // cast: TODO
+      // fall though for now
+    case SSA_NAME:
+    case VAR_DECL:
+    case PARM_DECL:
+      {
+	/* LHS = VAR;  */
+	region_id rhs_rid = get_lvalue (rhs1, ctxt);
+	svalue_id var_sid = get_region (rhs_rid)->get_value (*this, true, ctxt);
+	set_value (lhs_rid, var_sid);
+      }
+      break;
+
+    case EQ_EXPR:
+    case GE_EXPR:
+    case LE_EXPR:
+    case NE_EXPR:
+    case GT_EXPR:
+    case LT_EXPR:
+      {
+	tree rhs2 = gimple_assign_rhs2 (assign);
+
+	// TODO: constraints between svalues
+	svalue_id rhs1_sid = get_rvalue (rhs1, ctxt);
+	svalue_id rhs2_sid = get_rvalue (rhs2, ctxt);
+
+	tristate t = eval_condition (rhs1_sid, op, rhs2_sid);
+	if (t.is_known ())
+	  set_value (lhs_rid,
+		     get_rvalue (t.is_true ()
+				 ? boolean_true_node
+				 : boolean_false_node,
+				 ctxt));
+      }
+      break;
+
+    case NEGATE_EXPR:
+    case BIT_NOT_EXPR:
+      {
+	// TODO: unary ops
+
+	// TODO: constant?
+
+	set_to_new_unknown_value (lhs_rid, TREE_TYPE (lhs));
+      }
+      break;
+
+    case PLUS_EXPR:
+    case MINUS_EXPR:
+    case MULT_EXPR:
+    case TRUNC_DIV_EXPR:
+    case TRUNC_MOD_EXPR:
+    case LSHIFT_EXPR:
+    case RSHIFT_EXPR:
+    case BIT_IOR_EXPR:
+    case BIT_XOR_EXPR:
+    case BIT_AND_EXPR:
+    case MIN_EXPR:
+    case MAX_EXPR:
+      {
+	/* Binary ops.  */
+	tree rhs2 = gimple_assign_rhs2 (assign);
+
+	svalue_id rhs1_sid = get_rvalue (rhs1, ctxt);
+	svalue_id rhs2_sid = get_rvalue (rhs2, ctxt);
+
+	if (tree rhs1_cst = maybe_get_constant (rhs1_sid))
+	  if (tree rhs2_cst = maybe_get_constant (rhs2_sid))
+	    {
+	      tree result = fold_build2 (op, TREE_TYPE (lhs),
+					 rhs1_cst, rhs2_cst);
+	      if (CONSTANT_CLASS_P (result))
+		{
+		  svalue_id result_sid
+		    = get_or_create_constant_svalue (result);
+		  set_value (lhs_rid, result_sid);
+		  return;
+		}
+	    }
+	set_to_new_unknown_value (lhs_rid, TREE_TYPE (lhs));
+      }
+      break;
+
+    case COMPONENT_REF:
+      {
+	/* LHS = op0.op1;  */
+	region_id child_rid = get_lvalue (rhs1, ctxt);
+	svalue_id child_sid
+	  = get_region (child_rid)->get_value (*this, true, ctxt);
+	set_value (lhs_rid, child_sid);
+      }
+      break;
+    }
+}
+
+/* Update this model for the CALL stmt, using CTXT to report any
+   diagnostics.  */
+
+void
+region_model::on_call (const gcall *call, region_model_context *ctxt)
+{
+  region_id lhs_rid;
+  tree lhs_type = NULL_TREE;
+  if (tree lhs = gimple_call_lhs (call))
+    {
+      lhs_rid = get_lvalue (lhs, ctxt);
+      lhs_type = TREE_TYPE (lhs);
+    }
+
+  if (is_named_call_p (call, "malloc", 1))
+    {
+      // FIXME: we should split state into two outcomes: success and failure
+      // TODO:
+      // FIXME: do we want a new subclass for malloc regions?
+      // FIXME: possibly capturing size as a svalue?
+
+      region_id new_rid = add_new_malloc_region ();
+      if (!lhs_rid.null_p ())
+	{
+	  svalue_id ptr_sid
+	    = get_or_create_ptr_svalue (lhs_type, new_rid);
+	  set_value (lhs_rid, ptr_sid);
+	}
+      return;
+    }
+  else if (is_named_call_p (call, "free", 1))
+    {
+      tree ptr = gimple_call_arg (call, 0);
+      svalue_id ptr_sid = get_rvalue (ptr, ctxt);
+      svalue *ptr_sval = get_svalue (ptr_sid);
+      if (region_svalue *ptr_to_region_sval
+	  = ptr_sval->dyn_cast_region_svalue ())
+	{
+	  /* If the ptr points to an underlying heap region, delete it,
+	     poisoning pointers.  */
+	  region_id pointee_rid = ptr_to_region_sval->get_pointee ();
+	  region_id heap_rid = get_root_region ()->ensure_heap_region (this);
+	  if (!pointee_rid.null_p ()
+	      && get_region (pointee_rid)->get_parent () == heap_rid)
+	    {
+	      purge_stats stats;
+	      delete_region_and_descendents (pointee_rid,
+					     POISON_KIND_FREED,
+					     &stats, ctxt->get_logger ());
+	      purge_unused_svalues (&stats, ctxt);
+	      validate ();
+	      // TODO: do anything with stats?
+	    }
+	}
+      return;
+    }
+  else if (is_named_call_p (call, "__builtin_alloca", 1))
+    {
+      region_id frame_rid = get_current_frame_id ();
+      region_id new_rid = add_region (new symbolic_region (frame_rid));
+      if (!lhs_rid.null_p ())
+	{
+	  svalue_id ptr_sid
+	    = get_or_create_ptr_svalue (lhs_type, new_rid);
+	  set_value (lhs_rid, ptr_sid);
+	}
+      return;
+    }
+  else if (is_named_call_p (call, "strlen", 1))
+    {
+      region_id buf_rid = deref_rvalue (gimple_call_arg (call, 0), ctxt);
+      svalue_id buf_sid = get_region (buf_rid)->get_value (*this, true, ctxt);
+      if (tree cst_expr = maybe_get_constant (buf_sid))
+	{
+	  if (TREE_CODE (cst_expr) == STRING_CST
+	      && !lhs_rid.null_p ())
+	    {
+	      /* TREE_STRING_LENGTH is sizeof, not strlen.  */
+	      int sizeof_cst = TREE_STRING_LENGTH (cst_expr);
+	      int strlen_cst = sizeof_cst - 1;
+	      tree t_cst = build_int_cst (lhs_type, strlen_cst);
+	      svalue_id result_sid
+		= get_or_create_constant_svalue (t_cst);
+	      set_value (lhs_rid, result_sid);
+	      return;
+	    }
+	}
+
+      // FIXME: otherwise an unknown value
+#if 0
+      gcc_unreachable ();
+#endif
+    }
+  else if (is_named_call_p (call, "__analyzer_dump_num_heap_regions", 0))
+    {
+      /* Handle the builtin "__analyzer_dump_num_heap_regions" by emitting
+	 a warning (for use in DejaGnu tests).  */
+      int num_heap_regions = 0;
+      region_id heap_rid = get_root_region ()->ensure_heap_region (this);
+      unsigned i;
+      region *region;
+      FOR_EACH_VEC_ELT (m_regions, i, region)
+	if (region->get_parent () == heap_rid)
+	  num_heap_regions++;
+      /* Use quotes to ensure the output isn't truncated.  */
+      warning_at (call->location, 0,
+		  "num heap regions: %qi", num_heap_regions);
+    }
+
+  /* Unrecognized call.  */
+
+  /* Unknown return value.  */
+  if (!lhs_rid.null_p ())
+    set_to_new_unknown_value (lhs_rid, lhs_type);
+
+  /* TODO: also, any pointer arguments might have been written through,
+     or the things they point to (implying a graph traversal, which
+     presumably we need to do before overwriting the old value).  */
+}
+
+/* Update this model for the RETURN_STMT, using CTXT to report any
+   diagnostics.  */
+
+void
+region_model::on_return (const greturn *return_stmt, region_model_context *ctxt)
+{
+  tree callee = get_current_function ()->decl;
+  tree lhs = DECL_RESULT (callee);
+  tree rhs = gimple_return_retval (return_stmt);
+
+  if (lhs && rhs)
+    set_value (get_lvalue (lhs, ctxt), get_rvalue (rhs, ctxt));
+}
+
+/* FIXME.  */
+
+void
+region_model::handle_phi (tree lhs, tree rhs, bool is_back_edge,
+			  region_model_context *ctxt)
+{
+  /* For now, don't bother tracking the .MEM SSA names.  */
+  if (tree var = SSA_NAME_VAR (lhs))
+    if (TREE_CODE (var) == VAR_DECL)
+      if (VAR_DECL_IS_VIRTUAL_OPERAND (var))
+	return;
+
+  svalue_id rhs_sid = get_rvalue (rhs, ctxt);
+
+  if (is_back_edge && get_svalue (rhs_sid)->get_kind () != SK_UNKNOWN)
+    {
+      /* If we have a back edge, we probably have a loop.
+	 Use an unknown value, to avoid effectively unrolling the
+	 loop.
+	 To terminate, we need to avoid generating a series of
+	 models with an unbounded monotonically increasing number of
+	 redundant unknown values; hence we need to purge svalues
+	 before inserting the state into the exploded graph, to
+	 collect unused svalues.  */
+      set_to_new_unknown_value (get_lvalue (lhs, ctxt), TREE_TYPE (lhs));
+    }
+  else
+    set_value (get_lvalue (lhs, ctxt), rhs_sid);
+}
+
+/* FIXME.  */
+
+region_id
+region_model::get_lvalue (path_var pv, region_model_context *ctxt)
+{
+  tree expr = pv.m_tree; // FIXME: take account of stack depth!
+
+  if (expr == NULL_TREE)
+    return region_id::null ();
+
+  switch (TREE_CODE (expr))
+    {
+    default:
+      gcc_unreachable ();
+
+    case ARRAY_REF:
+      {
+	tree array = TREE_OPERAND (expr, 0);
+	tree index = TREE_OPERAND (expr, 1);
+	// TODO: operands 2 and 3, if present:
+	gcc_assert (TREE_OPERAND (expr, 2) == NULL_TREE);
+	gcc_assert (TREE_OPERAND (expr, 3) == NULL_TREE);
+	region_id array_rid = get_lvalue (array, ctxt);
+#if 0
+	svalue_id index_sid = get_rvalue (index, ctxt);
+#endif
+	map_region *array_region = get_region<map_region> (array_rid);
+	tree element_type = TREE_TYPE (TREE_TYPE (array));
+	region_id element_rid
+	  = array_region->get_or_create (this, array_rid, index, element_type);
+	/* FIXME: ^^^ should be indexed by svalue, rather than by tree?
+	   What about unknown indices?  */
+	return element_rid;
+      }
+      break; // FIXME
+
+    case MEM_REF:
+      {
+	tree ptr = TREE_OPERAND (expr, 0);
+#if 0
+	tree offset = TREE_OPERAND (expr, 1);
+	// FIXME: what about nonzero offset?
+#endif
+
+	return deref_rvalue (ptr, ctxt);
+      }
+      break; // FIXME
+
+    case VAR_DECL:
+      /* Handle globals.  */
+      if (is_global_var (expr))
+	{
+	  region_id globals_rid
+	    = get_root_region ()->ensure_globals_region (this);
+	  map_region *globals = get_region<map_region> (globals_rid);
+	  region_id var_rid = globals->get_or_create (this, globals_rid, expr,
+						      TREE_TYPE (expr));
+	  return var_rid;
+	}
+
+      /* Fall through.  */
+
+    case SSA_NAME:
+    case PARM_DECL:
+    case RESULT_DECL:
+      {
+	// FIXME:
+	gcc_assert (TREE_CODE (expr) == SSA_NAME
+		    || TREE_CODE (expr) == PARM_DECL
+		    || TREE_CODE (expr) == VAR_DECL
+		    || TREE_CODE (expr) == RESULT_DECL);
+
+	/* FIXME: this is really stack height, rather than depth.  */
+	int stack_depth = pv.m_stack_depth;
+	stack_region *stack = get_root_region ()->get_stack_region (this);
+	gcc_assert (stack);
+	region_id frame_rid = stack->get_frame_rid (stack_depth);
+	frame_region *frame = get_region <frame_region> (frame_rid);
+	gcc_assert (frame);
+	region_id child_rid = frame->get_or_create (this, frame_rid, expr,
+						    TREE_TYPE (expr));
+	return child_rid;
+      }
+
+    case COMPONENT_REF:
+      {
+	/* obj.field  */
+	tree obj = TREE_OPERAND (expr, 0);
+	tree field = TREE_OPERAND (expr, 1);
+	region_id obj_rid = get_lvalue (obj, ctxt);
+	map_region *obj_region = get_region<map_region> (obj_rid);
+	region_id child_rid = obj_region->get_or_create (this, obj_rid, field,
+							 TREE_TYPE (field));
+	return child_rid;
+      }
+      break;
+
+    case STRING_CST:
+      {
+	// FIXME: should map to a unique, custom region, containing the constant
+	tree cst_type = TREE_TYPE (expr);
+	map_region *cst_region = new map_region (m_root_rid, cst_type);
+	svalue_id cst_sid = get_or_create_constant_svalue (expr);
+	cst_region->set_value (*this, cst_sid);
+	region_id cst_rid = add_region (cst_region);
+	return cst_rid;
+      }
+      break;
+    }
+}
+
+/* FIXME.  */
+
+region_id
+region_model::get_lvalue (tree expr, region_model_context *ctxt)
+{
+  return get_lvalue (path_var (expr, get_stack_depth () - 1), ctxt);
+}
+
+/* FIXME.  */
+
+svalue_id
+region_model::get_rvalue (path_var pv, region_model_context *ctxt)
+{
+  tree expr = pv.m_tree; // FIXME: take account of stack depth!
+  if (expr == NULL_TREE)
+    return svalue_id::null ();
+
+  switch (TREE_CODE (expr))
+    {
+    default:
+      gcc_unreachable ();
+
+    case ADDR_EXPR:
+      {
+	/* "&EXPR".  */
+	if (TREE_CODE (TREE_OPERAND (expr, 0)) == FUNCTION_DECL)
+	  return get_svalue_for_fndecl (TREE_OPERAND (expr, 0));
+	region_id expr_rid = get_lvalue (TREE_OPERAND (expr, 0), ctxt);
+	return get_or_create_ptr_svalue (TREE_TYPE (expr), expr_rid);
+      }
+      break;
+
+    case ARRAY_REF:
+      {
+	region_id element_rid = get_lvalue (pv, ctxt);
+	return get_region (element_rid)->get_value (*this, true, ctxt);
+      }
+
+    case INTEGER_CST:
+    case REAL_CST:
+    case STRING_CST:
+      return get_or_create_constant_svalue (expr);
+
+    case COMPONENT_REF:
+    case MEM_REF:
+    case SSA_NAME:
+    case VAR_DECL:
+    case PARM_DECL:
+    case RESULT_DECL:
+      {
+	region_id var_rid = get_lvalue (pv, ctxt);
+	return get_region (var_rid)->get_value (*this, true, ctxt);
+      }
+    }
+}
+
+/* FIXME.  */
+
+svalue_id
+region_model::get_rvalue (tree expr, region_model_context *ctxt)
+{
+  return get_rvalue (path_var (expr, get_stack_depth () - 1), ctxt);
+}
+
+/* FIXME.  */
+
+svalue_id
+region_model::get_or_create_ptr_svalue (tree ptr_type, region_id rid)
+{
+  /* Reuse existing region_svalue, if one of the right type is
+     available.  */
+  /* In theory we could stash a svalue_id in "region", but differing
+     pointer types muddles things.
+     For now, just do a linear search through all existing svalues.  */
+  int i;
+  svalue *svalue;
+  FOR_EACH_VEC_ELT (m_svalues, i, svalue)
+    if (region_svalue *ptr_svalue = svalue->dyn_cast_region_svalue ())
+      if (ptr_svalue->get_pointee () == rid
+	  && ptr_svalue->get_type () == ptr_type)
+	return svalue_id::from_int (i);
+
+  return add_svalue (new region_svalue (ptr_type, rid));
+
+#if 0
+  // FIXME: but what about types?
+  region *region = get_region (rid);
+  if (region->m_pointer_sid.null_p ())
+    {
+      tree type = region->get_type ();
+      // FIXME: ^^^ should this be a pointer type to type?
+    }
+  return region->m_pointer_sid;
+#endif
+}
+
+/* Return an svalue_id for a constant_svalue for CST_EXPR,
+   creating the constant_svalue if necessary.
+   The constant_svalue instances are reused, based on pointer equality
+   of trees  */
+
+svalue_id
+region_model::get_or_create_constant_svalue (tree cst_expr)
+{
+  /* Reuse one if it already exists.  */
+  // FIXME: maybe store a map, rather than do linear search?
+  int i;
+  svalue *svalue;
+  FOR_EACH_VEC_ELT (m_svalues, i, svalue)
+    if (constant_svalue *cst_svalue = svalue->dyn_cast_constant_svalue ())
+      if (cst_svalue->get_constant () == cst_expr)
+	return svalue_id::from_int (i);
+
+  svalue_id cst_sid = add_svalue (new constant_svalue (cst_expr));
+  return cst_sid;
+}
+
+/* Return an svalue_id for a constant_svalue for FNDECL,
+   creating the constant_svalue if necessary.
+   The constant_svalue instances are reused, based on pointer equality
+   of trees  */
+
+svalue_id
+region_model::get_svalue_for_fndecl (tree fndecl)
+{
+  return get_or_create_constant_svalue (fndecl);
+}
+
+/* FIXME.  */
+
+class deref_diagnostic : public pending_diagnostic_subclass<deref_diagnostic>
+{
+public:
+  deref_diagnostic (tree ptr)
+  : m_ptr (ptr)
+  {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "deref_diagnostic"; }
+
+  bool operator== (const deref_diagnostic &other) const
+  {
+    return m_ptr == other.m_ptr;
+    // FIXME: and m_sval ?
+  }
+
+protected:
+  tree m_ptr;
+};
+
+/* FIXME.  */
+
+class deref_of_poisoned_pointer : public deref_diagnostic
+{
+public:
+  deref_of_poisoned_pointer (tree ptr, enum poison_kind pkind)
+  : deref_diagnostic (ptr),
+    m_pkind (pkind)
+  {}
+
+  void emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    metadata m;
+    // FIXME: CWEs?
+    switch (m_pkind)
+      {
+      default:
+	gcc_unreachable ();
+      case POISON_KIND_UNINIT:
+	warning_at (rich_loc, m,
+		    OPT_Wanalyzer_dereference_of_uninitialized_pointer,
+		    "dereference of uninitialized pointer %qE",
+		    m_ptr);
+	break;
+      case POISON_KIND_FREED:
+	warning_at (rich_loc, m,
+		    OPT_Wanalyzer_dereference_of_freed_pointer,
+		    "dereference of freed pointer %qE",
+		    m_ptr);
+	break;
+      case POISON_KIND_POPPED_STACK:
+	warning_at (rich_loc, m,
+		    OPT_Wanalyzer_dereference_of_pointer_in_stale_stack_frame,
+		    "dereference of pointer %qE within stale stack frame",
+		    m_ptr);
+	break;
+      }
+  }
+
+private:
+  enum poison_kind m_pkind;
+};
+
+/* FIXME.  */
+
+region_id
+region_model::deref_rvalue (tree ptr, region_model_context *ctxt)
+{
+  svalue_id ptr_sid = get_rvalue (ptr, ctxt);
+  gcc_assert (!ptr_sid.null_p ());
+  svalue *ptr_svalue = get_svalue (ptr_sid);
+  gcc_assert (ptr_svalue);
+
+  switch (ptr_svalue->get_kind ())
+    {
+    case SK_REGION:
+      {
+	region_svalue *region_sval = ptr_svalue->dyn_cast_region_svalue ();
+	// FIXME: ^^ as_a
+	return region_sval->get_pointee ();
+      }
+
+    case SK_CONSTANT:
+      {
+#if 0
+	constant_svalue *constant_sval
+	  = ptr_svalue->dyn_cast_constant_svalue ();
+	// FIXME: ^^ as_a
+#endif
+
+	/* TODO: issue a warning (presumably using some kind of context
+	   within the region_model?).  */
+	// FIXME: the malloc state machine already complains about this
+#if 0
+	tree tree_cst = constant_sval->get_constant ();
+	if (zerop (tree_cst))
+	  warning (0, "null pointer deref FIXME");
+#endif
+
+	goto create_symbolic_region;
+	// map of regions per constant?
+      }
+
+    case SK_POISONED:
+      {
+	poisoned_svalue *poisoned_sval
+	  = ptr_svalue->dyn_cast_poisoned_svalue ();
+	// FIXME: ^^ as_a
+	enum poison_kind pkind = poisoned_sval->get_poison_kind ();
+	if (ctxt)
+	  ctxt->warn (new deref_of_poisoned_pointer (ptr, pkind));
+	goto create_symbolic_region;
+      }
+
+    case SK_UNKNOWN:
+      {
+      create_symbolic_region:
+	/* We need a symbolic_region to represent this unknown region.
+	   We don't know if it on the heap, stack, or a global,
+	   so use the root region as parent.  */
+	region_id new_rid = add_region (new symbolic_region (m_root_rid));
+
+	/* We need to write the region back into the pointer,
+	   or we'll get a new, different region each time.
+	   We do this by changing the meaning of ptr_sid, replacing
+	   the unknown value with the ptr to the new region.
+	   We replace the meaning of the ID rather than simply writing
+	   to PTR's lvalue since there could be several places sharing
+	   the same unknown ptr value.  */
+	svalue *ptr_val = new region_svalue (TREE_TYPE (ptr), new_rid);
+	replace_svalue (ptr_sid, ptr_val);
+
+	return new_rid;
+      }
+    }
+
+  gcc_unreachable ();
+
+  // FIXME: how to hit this path?
+  return region_id::null ();
+}
+
+/* Set the value of the region given by LHS_RID to the value given
+   by RHS_SID.  */
+
+void
+region_model::set_value (region_id lhs_rid, svalue_id rhs_sid)
+{
+  gcc_assert (!lhs_rid.null_p ());
+  gcc_assert (!rhs_sid.null_p ());
+  get_region (lhs_rid)->set_value (*this, rhs_sid);
+}
+
+/* Determine what is known about the condition "LHS_SID OP RHS_SID" within
+   this model.  */
+
+tristate
+region_model::eval_condition (svalue_id lhs_sid,
+			      enum tree_code op,
+			      svalue_id rhs_sid) const
+{
+  svalue *lhs = get_svalue (lhs_sid);
+  svalue *rhs = get_svalue (rhs_sid);
+  gcc_assert (lhs);
+  gcc_assert (rhs);
+
+  /* See what we know based on the values.  */
+  if (lhs && rhs)
+    {
+      if (lhs == rhs)
+	{
+	  /* If we have the same svalue, then we have equality.  */
+	  // FIXME: but what about poisoned values?
+	  switch (op)
+	    {
+	    default:
+	      gcc_unreachable ();
+
+	    case EQ_EXPR:
+	    case GE_EXPR:
+	    case LE_EXPR:
+	      return tristate::TS_TRUE;
+
+	    case NE_EXPR:
+	    case GT_EXPR:
+	    case LT_EXPR:
+	      return tristate::TS_FALSE;
+	    }
+	}
+
+      /* If we have a pair of region_svalues, compare them.  */
+      if (region_svalue *lhs_ptr = lhs->dyn_cast_region_svalue ())
+	if (region_svalue *rhs_ptr = rhs->dyn_cast_region_svalue ())
+	  {
+	    tristate res = region_svalue::eval_condition (lhs_ptr, op, rhs_ptr);
+	    if (res.is_known ())
+	      return res;
+	    /* Otherwise, try constraints.  */
+	  }
+
+      /* If we have a pair of constants, compare them.  */
+      if (constant_svalue *cst_lhs = lhs->dyn_cast_constant_svalue ())
+	if (constant_svalue *cst_rhs = rhs->dyn_cast_constant_svalue ())
+	  return constant_svalue::eval_condition (cst_lhs, op, cst_rhs);
+    }
+
+  /* Otherwise, try constraints.  */
+  return m_constraints->eval_condition (lhs_sid, op, rhs_sid);
+}
+
+/* Attempt to add the constraint "LHS OP RHS" to this region_model.
+   If it is consistent with existing constraints, add it, and return true.
+   Return false if it contradicts existing constraints.
+   Use CTXT for reporting any diagnostics associated with the accesses.  */
+
+bool
+region_model::add_constraint (tree lhs, enum tree_code op, tree rhs,
+			      region_model_context *ctxt)
+{
+  svalue_id lhs_sid = get_rvalue (lhs, ctxt);
+  svalue_id rhs_sid = get_rvalue (rhs, ctxt);
+
+  tristate t_cond = eval_condition (lhs_sid, op, rhs_sid);
+
+  /* If we already have the condition, do nothing.  */
+  if (t_cond.is_true ())
+    return true;
+
+  /* Reject a constraint that would contradict existing knowledge, as
+     unsatisfiable.  */
+  if (t_cond.is_false ())
+    return false;
+
+  /* Store the constraint.  */
+  m_constraints->add_constraint (lhs_sid, op, rhs_sid);
+
+  return true;
+}
+
+/* Determine what is known about the condition "LHS OP RHS" within
+   this model.
+   Use CTXT for reporting any diagnostics associated with the accesses.  */
+
+tristate
+region_model::eval_condition (tree lhs,
+			      enum tree_code op,
+			      tree rhs,
+			      region_model_context *ctxt)
+{
+  return eval_condition (get_rvalue (lhs, ctxt), op, get_rvalue (rhs, ctxt));
+}
+
+/* If SID is a constant value, return the underlying tree constant.
+   Otherwise, return NULL_TREE.  */
+
+tree
+region_model::maybe_get_constant (svalue_id sid)
+{
+  gcc_assert (!sid.null_p ());
+  svalue *sval = get_svalue (sid);
+  if (constant_svalue *cst_sval = sval->dyn_cast_constant_svalue ())
+    return cst_sval->get_constant ();
+  return NULL_TREE;
+}
+
+/* Create a new child region of the heap (creating the heap region if
+   necessary).
+   Return the region_id of the new child region.  */
+
+region_id
+region_model::add_new_malloc_region ()
+{
+  region_id heap_rid
+    = get_root_region ()->ensure_heap_region (this);
+  return add_region (new symbolic_region (heap_rid));
+}
+
+/* FIXME.  */
+
+tree
+region_model::get_representative_tree (svalue_id sid) const
+{
+  if (sid.null_p ())
+    return NULL_TREE;
+
+  unsigned i;
+  region *region;
+  FOR_EACH_VEC_ELT (m_regions, i, region)
+    if (sid == region->get_value_direct ())
+      {
+	path_var pv = get_representative_path_var (region_id::from_int (i));
+	if (pv.m_tree)
+	  return pv.m_tree;
+      }
+
+  return NULL_TREE; // FIXME
+}
+
+/* FIXME.  */
+// FIXME: better name than "path_var"?
+
+path_var
+region_model::get_representative_path_var (region_id rid) const
+{
+  region *reg = get_region (rid);
+  region *parent_region = get_region (reg->get_parent ());
+  region_id stack_rid = get_stack_region_id ();
+  if (!stack_rid.null_p ())
+    if (parent_region->get_parent () == stack_rid)
+      {
+	frame_region *parent_frame = (frame_region *)parent_region;
+	tree t = parent_frame->get_tree_for_child_region (rid);
+	return path_var (t, parent_frame->get_depth ());
+    }
+  if (reg->get_parent () == get_globals_region_id ())
+    {
+      map_region *globals = get_root_region ()->get_globals_region (this);
+      if (globals)
+	return path_var (globals->get_tree_for_child_region (rid), -1);
+    }
+
+  /* Handle e.g. fields of a local by recursing.  */
+  region_id parent_rid = reg->get_parent ();
+  region *parent_reg = get_region (parent_rid);
+  if (parent_reg)
+    {
+      if (parent_reg->get_kind () == RK_MAP)
+	{
+	  map_region *parent_map_region = (map_region *)parent_reg;
+	  tree child_key = parent_map_region->get_tree_for_child_region (rid);
+	  path_var parent_pv = get_representative_path_var (parent_rid);
+	  /* FIXME: it is always a COMPONENT_REF?  */
+	  if (parent_pv.m_tree)
+	    return path_var (build3 (COMPONENT_REF,
+				     NULL_TREE, /* FIXME: type */
+				     parent_pv.m_tree, child_key, NULL_TREE),
+			     parent_pv.m_stack_depth);
+	}
+    }
+
+  return path_var (NULL_TREE, 0); // FIXME
+}
+
+/* FIXME.  */
+
+void
+region_model::get_path_vars_for_svalue (svalue_id sid, vec<path_var> *out) const
+{
+  unsigned i;
+  region *region;
+  FOR_EACH_VEC_ELT (m_regions, i, region)
+    if (sid == region->get_value_direct ())
+      {
+	path_var pv = get_representative_path_var (region_id::from_int (i));
+	if (pv.m_tree)
+	  out->safe_push (pv);
+      }
+}
+
+/* Set DST_RID value to be a new unknown value of type TYPE.  */
+
+svalue_id
+region_model::set_to_new_unknown_value (region_id dst_rid, tree type)
+{
+  gcc_assert (!dst_rid.null_p ());
+  svalue_id new_sid = add_svalue (new unknown_svalue (type));
+  set_value (dst_rid, new_sid);
+
+  // TODO: presumably purge all child regions too (but do this in set_value?)
+
+  return new_sid;
+}
+
+/* FIXME.  */
+
+void
+region_model::update_for_phis (const supernode *snode,
+			       const cfg_superedge *last_cfg_superedge,
+			       region_model_context *ctxt)
+{
+  gcc_assert (last_cfg_superedge);
+
+  for (gphi_iterator gpi = const_cast<supernode *>(snode)->start_phis ();
+       !gsi_end_p (gpi); gsi_next (&gpi))
+    {
+      gphi *phi = gpi.phi ();
+
+      tree src = last_cfg_superedge->get_phi_arg (phi);
+      tree lhs = gimple_phi_result (phi);
+
+      /* Update next_state based on phi.  */
+      bool is_back_edge = last_cfg_superedge->back_edge_p ();
+      handle_phi (lhs, src, is_back_edge, ctxt);
+    }
+}
+
+/* Attempt to update this model for taking EDGE (where the last statement
+   was LAST_STMT), returning true if the edge can be taken, false
+   otherwise.
+
+   For CFG superedges where LAST_STMT is a conditional or a switch
+   statement, attempt to add the relevant conditions for EDGE to this
+   model, returning true if they are feasible, or false if they are
+   impossible.
+
+   For call superedges, push frame information and store arguments
+   into parameters.
+
+   For return superedges, pop frame information and store return
+   values into any lhs.
+
+   Rejection of call/return superedges happens elsewhere, in
+   program_point::on_edge (i.e. based on program point, rather
+   than program state).  */
+
+bool
+region_model::maybe_update_for_edge (const superedge &edge,
+				     const gimple *last_stmt,
+				     region_model_context *ctxt)
+{
+  /* Handle frame updates for interprocedural edges.  */
+  switch (edge.m_kind)
+    {
+    default:
+      break;
+
+    case SUPEREDGE_CALL:
+      {
+	const call_superedge *call_edge
+	  = edge.dyn_cast_call_superedge ();
+	// FIXME: should be an as_a
+	update_for_call_superedge (*call_edge, ctxt);
+      }
+      break;
+
+    case SUPEREDGE_RETURN:
+      {
+	const return_superedge *return_edge
+	  = edge.dyn_cast_return_superedge ();
+	// FIXME: should be an as_a
+	update_for_return_superedge (*return_edge, ctxt);
+      }
+      break;
+    }
+
+  // TODO: update constraints
+
+  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, ctxt);
+
+  // 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, ctxt);
+
+  return true;
+}
+
+/* Push a new frame_region on to the stack region.
+   Populate the frame_region with child regions for the function call's
+   parameters, using values from the arguments at the callsite in the
+   caller's frame.  */
+
+void
+region_model::update_for_call_superedge (const call_superedge &call_edge,
+					 region_model_context *ctxt)
+{
+  /* Build a vec of argument svalue_id, using the current top
+     frame for resolving tree expressions.  */
+  auto_vec<svalue_id> arg_sids;
+
+  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 arg = gimple_call_arg (call_stmt, i);
+      arg_sids.safe_push (get_rvalue (arg, ctxt));
+    }
+
+  push_frame (call_edge.get_callee_function (), &arg_sids);
+}
+
+/* Pop the top-most frame_region from the stack, and store the svalue
+   for any returned value into the region for the lvalue of the LHS of
+   the call (if any).  */
+
+void
+region_model::update_for_return_superedge (const return_superedge &return_edge,
+					   region_model_context *ctxt)
+{
+  purge_stats stats;
+  svalue_id result_sid = pop_frame (&stats, ctxt);
+  // TODO: do something with the stats?
+
+  // FIXME: result_sid could change meaning within pop_frame;
+  // maybe we can return the new sid for the result (if any)
+
+  /* Set the result of the call, within the caller frame.  */
+  gcall *call_stmt = return_edge.get_call_stmt ();
+  tree lhs = gimple_call_lhs (call_stmt);
+  if (lhs)
+    set_value (get_lvalue (lhs, ctxt), result_sid);
+  else
+    {
+      /* This could be a leak; try purging again, but this time,
+	 don't special-case the result_sid.  */
+      purge_stats stats;
+      purge_unused_svalues (&stats, ctxt);
+    }
+}
+
+/* Given a true or false edge guarded by conditional statement COND_STMT,
+   determine appropriate constraints for the edge to be taken.
+
+   If they are feasible, add the constraints and return true.
+
+   Return false if the constraints contradict existing knowledge
+   (and so the edge should not be taken).  */
+
+bool
+region_model::apply_constraints_for_gcond (const superedge &edge,
+					   const gcond *cond_stmt,
+					   region_model_context *ctxt)
+{
+  ::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 (lhs, op, rhs, ctxt);
+}
+
+/* Given an EDGE guarded by SWITCH_STMT, determine appropriate constraints
+   for the edge to be taken.
+
+   If they are feasible, add the constraints and return true.
+
+   Return false if the constraints contradict existing knowledge
+   (and so the edge should not be taken).  */
+
+bool
+region_model::apply_constraints_for_gswitch (const superedge &edge,
+					     const gswitch *switch_stmt,
+					     region_model_context *ctxt)
+{
+  // FIXME: use an as_a for this cast:
+  const switch_cfg_superedge &switch_edge = (const switch_cfg_superedge &)edge;
+  tree index  = gimple_switch_index (switch_stmt);
+  tree case_label = switch_edge.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)
+    {
+      if (upper_bound)
+	{
+	  /* Range.  */
+	  if (!add_constraint (index, GE_EXPR, lower_bound, ctxt))
+	    return false;
+	  return add_constraint (index, LE_EXPR, upper_bound, ctxt);
+	}
+      else
+	/* Single-value.  */
+	return add_constraint (index, EQ_EXPR, lower_bound, ctxt);
+    }
+  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);
+	  tree other_lower_bound = CASE_LOW (other_label);
+	  tree other_upper_bound = CASE_HIGH (other_label);
+	  gcc_assert (other_lower_bound);
+	  if (other_upper_bound)
+	    {
+	      /* 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, ctxt))
+		return false;
+	      if (!add_constraint (index, NE_EXPR, other_upper_bound, ctxt))
+		return false;
+	    }
+	  else
+	    /* Exclude this single-valued case.  */
+	    if (!add_constraint (index, NE_EXPR, other_lower_bound, ctxt))
+	      return false;
+	}
+      return true;
+    }
+}
+
+/* Get the root_region within this model (guaranteed to be non-null).  */
+
+root_region *
+region_model::get_root_region () const
+{
+  return get_region<root_region> (m_root_rid);
+}
+
+/* Get the region_id of this model's stack region (if any).  */
+
+region_id
+region_model::get_stack_region_id () const
+{
+  return get_root_region ()->get_stack_region_id ();
+}
+
+/* FIXME.
+   Return the region_id of the new frame_region.  */
+
+region_id
+region_model::push_frame (function *fun, vec<svalue_id> *arg_sids)
+{
+  return get_root_region ()->push_frame (this, fun, arg_sids);
+}
+
+/* FIXME.  */
+
+region_id
+region_model::get_current_frame_id () const
+{
+  return get_root_region ()->get_current_frame_id (*this);
+}
+
+/* FIXME.  */
+
+function *
+region_model::get_current_function () const
+{
+  region_id frame_id = get_current_frame_id ();
+  frame_region *frame = get_region<frame_region> (frame_id);
+  return frame->get_function ();
+}
+
+/* FIXME.  */
+
+svalue_id
+region_model::pop_frame (purge_stats *out, region_model_context *ctxt)
+{
+  return get_root_region ()->pop_frame (this, out, ctxt);
+}
+
+/* FIXME.  */
+
+int
+region_model::get_stack_depth () const
+{
+  stack_region *stack = get_root_region ()->get_stack_region (this);
+  if (stack)
+    return stack->get_num_frames ();
+  else
+    return 0;
+}
+
+/* Get the region_id of this model's globals region (if any).  */
+
+region_id
+region_model::get_globals_region_id () const
+{
+  return get_root_region ()->get_globals_region_id ();
+}
+
+/* Add SVAL to this model, taking ownership, and returning its new
+   svalue_id.  */
+
+svalue_id
+region_model::add_svalue (svalue *sval)
+{
+  gcc_assert (sval);
+  m_svalues.safe_push (sval);
+  return svalue_id::from_int (m_svalues.length () - 1);
+}
+
+/* Change the meaning of SID to be NEW_SVAL
+   (e.g. when deferencing an unknown pointer, the pointer
+   becomes a pointer to a symbolic region, so that all users
+   of the former unknown pointer are now effectively pointing
+   at the same region).  */
+
+void
+region_model::replace_svalue (svalue_id sid, svalue *new_sval)
+{
+  gcc_assert (!sid.null_p ());
+  int idx = sid.as_int ();
+
+  gcc_assert (m_svalues[idx]);
+  delete m_svalues[idx];
+
+  m_svalues[idx] = new_sval;
+}
+
+/* Add region R to this model, taking ownership, and returning its new
+   region_id.  */
+
+region_id
+region_model::add_region (region *r)
+{
+  gcc_assert (r);
+  m_regions.safe_push (r);
+  return region_id::from_int (m_regions.length () - 1);
+}
+
+/* Return the svalue with id SVAL_ID, or NULL for a null id.  */
+
+svalue *
+region_model::get_svalue (svalue_id sval_id) const
+{
+  if (sval_id.null_p ())
+    return NULL;
+  return m_svalues[sval_id.as_int ()];
+}
+
+/* Return the region with id RID, or NULL for a null id.  */
+
+region *
+region_model::get_region (region_id rid) const
+{
+  if (rid.null_p ())
+    return NULL;
+  return m_regions[rid.as_int ()];
+}
+
+/* Helper class for region_model::purge_unused_svalues.  */
+
+class restrict_to_used_svalues : public purge_criteria
+{
+public:
+  restrict_to_used_svalues (const auto_sbitmap &used) : m_used (used) {}
+
+  bool should_purge_p (svalue_id sid) const FINAL OVERRIDE
+  {
+    gcc_assert (!sid.null_p ());
+    return !bitmap_bit_p (m_used, sid.as_int ());
+  }
+
+private:
+  const auto_sbitmap &m_used;
+};
+
+/* Remove unused svalues from this model, accumulating stats into STATS.
+   Unused svalues are deleted.  Doing so could reorder the svalues, and
+   thus change the meaning of svalue_ids.
+
+   If CTXT is non-NULL, then it is notified about svalue_id remappings,
+   and about svalue_ids that are about to be deleted.  This allows e.g.
+   for warning about resource leaks, for the case where the svalue
+   represents a resource handle in the user code (e.g. a FILE * or a malloc
+   buffer).
+
+   Amongst other things, removing unused svalues is important for ensuring
+   that the analysis of loops terminates.  Otherwise, we could generate a
+   succession of models with unreferenced "unknown" values, where the
+   number of redundant unknown values could grow without bounds, and each
+   such model would be treated as distinct.
+
+   If KNOWN_USED is non-NULL, treat *KNOWN_USED as used (this is for
+   handling values being returned from functions as their frame is popped,
+   since otherwise we'd have to simultaneously determine both the rvalue
+   of the return expr in the callee frame and the lvalue for the gcall's
+   assignment in the caller frame, and it seems cleaner to express all
+   lvalue and rvalue lookups implicitly relative to a "current" frame).  */
+
+void
+region_model::purge_unused_svalues (purge_stats *stats,
+				    region_model_context *ctxt,
+				    svalue_id *known_used_sid)
+{
+  // FIXME: might want to avoid a vfunc call just to do logging here:
+  logger *logger = ctxt ? ctxt->get_logger () : NULL;
+
+  //svalue_id_set used (*this);
+  auto_sbitmap used (m_svalues.length ());
+  bitmap_clear (used); // FIXME: is this needed?
+
+  if (known_used_sid)
+    if (!known_used_sid->null_p ())
+      bitmap_set_bit (used, known_used_sid->as_int ());
+
+  /* Walk the regions, marking sids that are used.  */
+  unsigned i;
+  region *r;
+  FOR_EACH_VEC_ELT (m_regions, i, r)
+    {
+      svalue_id sid = r->get_value_direct ();
+      if (!sid.null_p ())
+	bitmap_set_bit (used, sid.as_int ());
+    }
+
+  /* Now purge any constraints involving svalues we don't care about.  */
+  restrict_to_used_svalues criterion (used);
+  m_constraints->purge (criterion, stats);
+
+  /* Mark any sids that are in constraints that survived.  */
+  {
+    equiv_class *ec;
+    FOR_EACH_VEC_ELT (m_constraints->m_equiv_classes, i, ec)
+      {
+	int j;
+	svalue_id *sid;
+	FOR_EACH_VEC_ELT (ec->m_vars, j, sid)
+	  {
+	    gcc_assert (!sid->null_p ());
+	    bitmap_set_bit (used, sid->as_int ());
+	  }
+	// FIXME: should we just do this for constants?
+	// is everything else purged?
+      }
+  }
+
+#if 0
+  /* Don't purge SIDs that have non-purgeable sm_state_map
+     information.  */
+  if (ctxt)
+    for (unsigned i = 0; i < m_svalues.length (); i++)
+      {
+	if (!bitmap_bit_p (used, i))
+	  if (!ctxt->can_purge_p (svalue_id::from_int (i)))
+	    bitmap_set_bit (used, i);
+      }
+#endif
+
+  /* Build a mapping from old-sid to new-sid so that we can preserve
+     order of the used IDs and move all redundant ones to the end.
+     Iterate though svalue IDs, adding used ones to the front of
+     the new list, and unused ones to the back.  */
+  svalue_id_map map (m_svalues.length ());
+  int next_used_new_sid = 0;
+  int after_next_unused_new_sid = m_svalues.length ();
+  for (unsigned i = 0; i < m_svalues.length (); i++)
+    {
+      svalue_id src (svalue_id::from_int (i));
+      if (bitmap_bit_p (used, i))
+	{
+	  if (logger)
+	    logger->log ("sv%i is used", i);
+	  map.put (src, svalue_id::from_int (next_used_new_sid++));
+	}
+      else
+	{
+	  if (logger)
+	    logger->log ("sv%i is unused", i);
+	  map.put (src, svalue_id::from_int (--after_next_unused_new_sid));
+	}
+    }
+  /* The two insertion points should have met.  */
+  gcc_assert (next_used_new_sid == after_next_unused_new_sid);
+
+  /* Now walk the regions and the constraints, remapping sids,
+     so that all the redundant svalues are at the end.  */
+  remap_svalue_ids (map);
+  if (logger)
+    logger->log ("map: %s", map.to_str ().c_str ());
+
+  /* Notify any client about the remapping and pending deletion.  */
+  // FIXME: potentially this could trigger leak warnings
+  if (ctxt)
+    {
+      ctxt->remap_svalue_ids (map);
+      stats->m_num_client_items +=
+	ctxt->on_svalue_purge (svalue_id::from_int (next_used_new_sid), map);
+    }
+
+  /* Drop the redundant svalues from the end of the vector.  */
+  while ((signed)m_svalues.length () > next_used_new_sid)
+    {
+      if (logger)
+	{
+	  svalue_id victim = svalue_id::from_int (m_svalues.length () - 1);
+	  logger->log ("deleting sv%i (was sv%i)",
+		       victim.as_int (),
+		       map.get_src_for_dst (victim).as_int ());
+	}
+      delete m_svalues.pop ();
+      stats->m_num_svalues++;
+    }
+
+  if (known_used_sid)
+    map.update (known_used_sid);
+
+  validate ();
+}
+
+/* Renumber the svalues within this model according to MAP.  */
+
+void
+region_model::remap_svalue_ids (const svalue_id_map &map)
+{
+  /* Update IDs within regions.  */
+  unsigned i;
+  region *r;
+  FOR_EACH_VEC_ELT (m_regions, i, r)
+    r->remap_svalue_ids (map);
+
+  /* Update IDs within ECs within constraints.  */
+  m_constraints->remap_svalue_ids (map);
+
+  /* Build a reordered svalues vector.  */
+  auto_vec<svalue *> new_svalues (m_svalues.length ());
+  for (unsigned i = 0; i < m_svalues.length (); i++)
+    {
+      svalue_id dst (svalue_id::from_int (i));
+      svalue_id src = map.get_src_for_dst (dst);
+      new_svalues.quick_push (get_svalue (src));
+    }
+
+  /* Copy over the reordered vec to m_svalues.  */
+  m_svalues.truncate (0);
+  gcc_assert (m_svalues.space (new_svalues.length ()));
+  svalue *sval;
+  FOR_EACH_VEC_ELT (new_svalues, i, sval)
+    m_svalues.quick_push (sval);
+}
+
+/* Renumber the regions within this model according to MAP.  */
+
+void
+region_model::remap_region_ids (const region_id_map &map)
+{
+  /* Update IDs within regions.  */
+  unsigned i;
+  region *r;
+  FOR_EACH_VEC_ELT (m_regions, i, r)
+    r->remap_region_ids (map);
+
+  /* Update IDs within svalues.  */
+  svalue *sval;
+  FOR_EACH_VEC_ELT (m_svalues, i, sval)
+    sval->remap_region_ids (map);
+
+  /* Build a reordered regions vector.  */
+  auto_vec<region *> new_regions (m_regions.length ());
+  for (unsigned i = 0; i < m_regions.length (); i++)
+    {
+      region_id dst (region_id::from_int (i));
+      region_id src = map.get_src_for_dst (dst);
+      new_regions.quick_push (get_region (src));
+    }
+
+  /* Copy over the reordered vec to m_regions.  */
+  m_regions.truncate (0);
+  gcc_assert (m_regions.space (new_regions.length ()));
+  FOR_EACH_VEC_ELT (new_regions, i, r)
+    m_regions.quick_push (r);
+}
+
+/* Delete all regions within SET_TO_PURGE, remapping region IDs for
+   other regions.  It's required that there are no uses of the
+   regions within the set (or the region IDs will become invalid).
+
+   Accumulate stats to STATS.  */
+
+void
+region_model::purge_regions (const region_id_set &set_to_purge,
+			     purge_stats *stats,
+			     logger *)
+{
+  /* Build a mapping from old-rid to new-rid so that we can preserve
+     order of the used IDs and move all redundant ones to the end.
+     Iterate though region IDs, adding used ones to the front of
+     the new list, and unused ones to the back.  */
+  region_id_map map (m_regions.length ());
+  int next_used_new_rid = 0;
+  int after_next_unused_new_rid = m_regions.length ();
+  for (unsigned i = 0; i < m_regions.length (); i++)
+    {
+      region_id src (region_id::from_int (i));
+      if (set_to_purge.region_p (src))
+	map.put (src, region_id::from_int (--after_next_unused_new_rid));
+      else
+	map.put (src, region_id::from_int (next_used_new_rid++));
+    }
+  /* The two insertion points should have met.  */
+  gcc_assert (next_used_new_rid == after_next_unused_new_rid);
+
+  /* Now walk the regions and svalues, remapping rids,
+     so that all the redundant regions are at the end.  */
+  remap_region_ids (map);
+
+  /* Drop the redundant regions from the end of the vector.  */
+  while ((signed)m_regions.length () > next_used_new_rid)
+    {
+      delete m_regions.pop ();
+      stats->m_num_regions++;
+    }
+}
+
+/* Populate *OUT with RID and all of its descendents.  */
+
+void
+region_model::get_descendents (region_id rid, region_id_set *out) const
+{
+  out->add_region (rid);
+
+  bool changed = true;
+  while (changed)
+    {
+      changed = false;
+      unsigned i;
+      region *r;
+      FOR_EACH_VEC_ELT (m_regions, i, r)
+	{
+	  region_id iter_rid = region_id::from_int (i);
+	  if (!out->region_p (iter_rid))
+	    {
+	      region_id parent_rid = r->get_parent ();
+	      if (!parent_rid.null_p ())
+		if (out->region_p (parent_rid))
+		  {
+		    out->add_region (iter_rid);
+		    changed = true;
+		  }
+	    }
+	}
+    }
+}
+
+/* Delete RID and all descendent regions.
+   Find any pointers to such regions; convert convert them to
+   poisoned values of kind PKIND.
+   Accumulate stats on purged entities into STATS.  */
+
+void
+region_model::delete_region_and_descendents (region_id rid,
+					     enum poison_kind pkind,
+					     purge_stats *stats,
+					     logger *logger)
+{
+  /* Find all child and descendent regions.  */
+  region_id_set descendents (this);
+  get_descendents (rid, &descendents);
+
+  /* Find any pointers to such regions; convert to poisoned.  */
+  poison_any_pointers_to_bad_regions (descendents, pkind);
+
+  /* Delete all such regions.  */
+  purge_regions (descendents, stats, logger);
+}
+
+/* Find any pointers to regions within BAD_REGIONS; convert them to
+   poisoned values of kind PKIND.  */
+
+void
+region_model::poison_any_pointers_to_bad_regions (const region_id_set &
+						    bad_regions,
+						  enum poison_kind pkind)
+{
+  int i;
+  svalue *sval;
+  FOR_EACH_VEC_ELT (m_svalues, i, sval)
+    if (region_svalue *ptr_sval = sval->dyn_cast_region_svalue ())
+      {
+	region_id ptr_dst = ptr_sval->get_pointee ();
+	if (!ptr_dst.null_p ())
+	  if (bad_regions.region_p (ptr_dst))
+	    replace_svalue
+	      (svalue_id::from_int (i),
+	       new poisoned_svalue (pkind));
+      }
+}
+
+/* Attempt to merge THIS with OTHER_MODEL, writing the result
+   to OUT_MODEL, and populating SID_MAPPING.  */
+
+bool
+region_model::can_merge_with_p (const region_model &other_model,
+				region_model *out_model,
+				svalue_id_merger_mapping *sid_mapping) const
+{
+  gcc_assert (m_root_rid == other_model.m_root_rid);
+  gcc_assert (m_root_rid.as_int () == 0);
+  gcc_assert (sid_mapping);
+  gcc_assert (out_model);
+
+  model_merger merger (this, &other_model, out_model, sid_mapping);
+
+  if (!root_region::can_merge_p (get_root_region (),
+				 other_model.get_root_region (),
+				 out_model->get_root_region (),
+				 &merger))
+    return false;
+
+  /* Merge constraints.  */
+  constraint_manager::merge (*m_constraints,
+			     *other_model.m_constraints,
+			     out_model->m_constraints,
+			     merger);
+
+  out_model->validate ();
+
+  return true;
+}
+
+/* As above, but supply a placeholder svalue_id_merger_mapping
+   instance to be used and receive output.  For use in selftests.  */
+
+bool
+region_model::can_merge_with_p (const region_model &other_model,
+				region_model *out_model) const
+{
+  svalue_id_merger_mapping sid_mapping (*this, other_model);
+  return can_merge_with_p (other_model, out_model, &sid_mapping);
+}
+
+/* For debugging purposes: look for a region within this region_model
+   for a decl named NAME (or an SSA_NAME for such a decl),
+   returning its value, or svalue_id::null if none are found.  */
+
+svalue_id
+region_model::get_value_by_name (const char *name) const
+{
+  gcc_assert (name);
+  tree identifier = get_identifier (name);
+  return get_root_region ()->get_value_by_name (identifier, *this);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* struct model_merger.  */
+
+/* Dump a multiline representation of this merger to PP.  */
+
+void
+model_merger::dump_to_pp (pretty_printer *pp) const
+{
+  pp_string (pp, "model A:");
+  pp_newline (pp);
+  m_model_a->dump_to_pp (pp);
+  pp_newline (pp);
+
+  pp_string (pp, "model B:");
+  pp_newline (pp);
+  m_model_b->dump_to_pp (pp);
+  pp_newline (pp);
+
+  pp_string (pp, "merged model:");
+  pp_newline (pp);
+  m_merged_model->dump_to_pp (pp);
+  pp_newline (pp);
+
+  pp_string (pp, "region map: model A to merged model:");
+  pp_newline (pp);
+  m_map_regions_from_a_to_m.dump_to_pp (pp);
+  pp_newline (pp);
+
+  pp_string (pp, "region map: model B to merged model:");
+  pp_newline (pp);
+  m_map_regions_from_b_to_m.dump_to_pp (pp);
+  pp_newline (pp);
+
+  m_sid_mapping->dump_to_pp (pp);
+}
+
+/* Dump a multiline representation of this merger to FILE.  */
+
+void
+model_merger::dump (FILE *fp) const
+{
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  pp_show_color (&pp) = pp_show_color (global_dc->printer);
+  pp.buffer->stream = fp;
+  dump_to_pp (&pp);
+  pp_flush (&pp);
+}
+
+/* Dump a multiline representation of this merger to stderr.  */
+
+DEBUG_FUNCTION void
+model_merger::dump () const
+{
+  dump (stderr);
+}
+
+/* Attempt to merge the svalues of SID_A and SID_B (from their
+   respective models), writing the id of the resulting svalue
+   into *MERGED_SID.
+   Return true if the merger is possible, false otherwise.  */
+
+bool
+model_merger::can_merge_values_p (svalue_id sid_a,
+				  svalue_id sid_b,
+				  svalue_id *merged_sid)
+{
+  gcc_assert (merged_sid);
+  svalue *sval_a = m_model_a->get_svalue (sid_a);
+  svalue *sval_b = m_model_b->get_svalue (sid_b);
+
+  /* If both are NULL, then the "values" are trivially mergeable.  */
+  if (!sval_a && !sval_b)
+    return true;
+
+  // FIXME:
+  /* If one is NULL and the other non-NULL, then the "values"
+     are not mergeable.  */
+  if (!(sval_a && sval_b))
+    return false;
+
+  /* Have they both already been mapped to the same new svalue_id?
+     If so, use it.  */
+  svalue_id sid_a_in_m
+    = m_sid_mapping->m_map_from_a_to_m.get_dst_for_src (sid_a);
+  svalue_id sid_b_in_m
+    = m_sid_mapping->m_map_from_b_to_m.get_dst_for_src (sid_b);
+  if (!sid_a_in_m.null_p ()
+      && !sid_b_in_m.null_p ()
+      && sid_a_in_m == sid_b_in_m)
+    {
+      *merged_sid = sid_a_in_m;
+      return true;
+    }
+
+#if 0
+  gcc_assert (sval_a->get_type () == sval_b->get_type ()
+	      || sval_a->get_type () == NULL_TREE
+	      || sval_b->get_type () == NULL_TREE);
+#endif
+  tree type = sval_a->get_type ();
+  if (type == NULL_TREE)
+    type = sval_b->get_type ();
+
+  /* If the values have different kinds, or are both unknown,
+     then merge as "unknown".  */
+  if (sval_a->get_kind () != sval_b->get_kind ()
+      || sval_a->get_kind () == SK_UNKNOWN)
+    {
+      svalue *merged_sval = new unknown_svalue (type);
+      *merged_sid = m_merged_model->add_svalue (merged_sval);
+      record_svalues (sid_a, sid_b, *merged_sid);
+      return true;
+    }
+
+  gcc_assert (sval_a->get_kind () == sval_b->get_kind ());
+
+  switch (sval_a->get_kind ())
+    {
+    default:
+      gcc_unreachable ();
+
+    case SK_REGION:
+      {
+	/* If we have two regions... .  */
+	const region_svalue &region_sval_a = *(const region_svalue *)sval_a;
+	const region_svalue &region_sval_b = *(const region_svalue *)sval_b;
+	if (!region_svalue::can_merge_values_p (region_sval_a, region_sval_b,
+						merged_sid, type,
+						this))
+	  return false;
+	record_svalues (sid_a, sid_b, *merged_sid);
+	return true;
+      }
+      break;
+    case SK_CONSTANT:
+      {
+	/* If we have two constants, then we can merge.  */
+	const constant_svalue &cst_sval_a = *(const constant_svalue *)sval_a;
+	const constant_svalue &cst_sval_b = *(const constant_svalue *)sval_b;
+	if (!constant_svalue::can_merge_values_p (cst_sval_a, sid_a,
+						  cst_sval_b, sid_b,
+						  merged_sid,
+						  this))
+	  return false;
+	record_svalues (sid_a, sid_b, *merged_sid);
+	return true;
+      }
+      break;
+
+    case SK_POISONED:
+      {
+      }
+      break;
+    case SK_UNKNOWN:
+      {
+      }
+      break;
+    }
+
+  // TODO: move these to svalue subclasses
+
+  return false;
+}
+
+/* Record that A_RID in model A and B_RID in model B
+   correspond to MERGED_RID in the merged model, so
+   that pointers can be accurately merged.  */
+
+void
+model_merger::record_regions (region_id a_rid,
+			      region_id b_rid,
+			      region_id merged_rid)
+{
+  m_map_regions_from_a_to_m.put (a_rid, merged_rid);
+  m_map_regions_from_b_to_m.put (b_rid, merged_rid);
+}
+
+/* FIXME.  */
+
+void
+model_merger::record_svalues (svalue_id a_sid,
+			      svalue_id b_sid,
+			      svalue_id merged_sid)
+{
+  gcc_assert (m_sid_mapping);
+  m_sid_mapping->m_map_from_a_to_m.put (a_sid, merged_sid);
+  m_sid_mapping->m_map_from_b_to_m.put (b_sid, merged_sid);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* struct svalue_id_merger_mapping.  */
+
+/* FIXME.  */
+
+svalue_id_merger_mapping::svalue_id_merger_mapping (const region_model &a,
+						    const region_model &b)
+: m_map_from_a_to_m (a.get_num_svalues ()),
+  m_map_from_b_to_m (b.get_num_svalues ())
+{
+}
+
+/* Dump a multiline representation of this to PP.  */
+
+void
+svalue_id_merger_mapping::dump_to_pp (pretty_printer *pp) const
+{
+  pp_string (pp, "svalue_id map: model A to merged model:");
+  pp_newline (pp);
+  m_map_from_a_to_m.dump_to_pp (pp);
+  pp_newline (pp);
+
+  pp_string (pp, "svalue_id map: model B to merged model:");
+  pp_newline (pp);
+  m_map_from_b_to_m.dump_to_pp (pp);
+  pp_newline (pp);
+}
+
+/* Dump a multiline representation of this to FILE.  */
+
+void
+svalue_id_merger_mapping::dump (FILE *fp) const
+{
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  pp_show_color (&pp) = pp_show_color (global_dc->printer);
+  pp.buffer->stream = fp;
+  dump_to_pp (&pp);
+  pp_flush (&pp);
+}
+
+/* Dump a multiline representation of this to stderr.  */
+
+DEBUG_FUNCTION void
+svalue_id_merger_mapping::dump () const
+{
+  dump (stderr);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* struct canonicalization.  */
+
+/* canonicalization's ctor.  */
+
+canonicalization::canonicalization (const region_model &model)
+: m_model (model),
+  m_rid_map (model.get_num_regions ()),
+  m_sid_map (model.get_num_svalues ()),
+  m_next_rid_int (0),
+  m_next_sid_int (0)
+{
+}
+
+/* If we've not seen RID yet, assign it a canonicalized region_id,
+   and walk the region's svalue and then the region.  */
+
+void
+canonicalization::walk_rid (region_id rid)
+{
+  /* Stop if we've already seen RID.  */
+  if (!m_rid_map.get_dst_for_src (rid).null_p ())
+    return;
+
+  region *region = m_model.get_region (rid);
+  if (region)
+    {
+      m_rid_map.put (rid, region_id::from_int (m_next_rid_int++));
+      walk_sid (region->get_value_direct ());
+      region->walk_for_canonicalization (this);
+    }
+}
+
+/* If we've not seen SID yet, assign it a canonicalized svalue_id,
+   and walk the svalue (and potentially regions e.g. for ptr values).  */
+
+void
+canonicalization::walk_sid (svalue_id sid)
+{
+  /* Stop if we've already seen SID.  */
+  if (!m_sid_map.get_dst_for_src (sid).null_p ())
+    return;
+
+  svalue *sval = m_model.get_svalue (sid);
+  if (sval)
+    {
+      m_sid_map.put (sid, svalue_id::from_int (m_next_sid_int++));
+      /* Potentially walk regions e.g. for ptrs.  */
+      sval->walk_for_canonicalization (this);
+    }
+}
+
+/* Dump a multiline representation of this to PP.  */
+
+void
+canonicalization::dump_to_pp (pretty_printer *pp) const
+{
+  pp_string (pp, "region_id map:");
+  pp_newline (pp);
+  m_rid_map.dump_to_pp (pp);
+  pp_newline (pp);
+
+  pp_string (pp, "svalue_id map:");
+  pp_newline (pp);
+  m_sid_map.dump_to_pp (pp);
+  pp_newline (pp);
+}
+
+/* Dump a multiline representation of this to FILE.  */
+
+void
+canonicalization::dump (FILE *fp) const
+{
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  pp_show_color (&pp) = pp_show_color (global_dc->printer);
+  pp.buffer->stream = fp;
+  dump_to_pp (&pp);
+  pp_flush (&pp);
+}
+
+/* Dump a multiline representation of this to stderr.  */
+
+DEBUG_FUNCTION void
+canonicalization::dump () const
+{
+  dump (stderr);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
+void
+inchash::add (svalue_id sid, inchash::hash &hstate)
+{
+  hstate.add_int (sid.as_int ());
+}
+
+/* FIXME.  */
+
+void
+inchash::add (region_id rid, inchash::hash &hstate)
+{
+  hstate.add_int (rid.as_int ());
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+// FIXME:
+
+DEBUG_FUNCTION void
+debug (const region_model &rmodel)
+{
+  fprintf (stderr, "%s\n", rmodel.to_str ().c_str ());
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Implementation detail of the ASSERT_CONDITION_* macros.  */
+
+void
+assert_condition (const location &loc,
+		  region_model &model,
+		  tree lhs, tree_code op, tree rhs,
+		  tristate expected)
+{
+  tristate actual = model.eval_condition (lhs, op, rhs, NULL);
+  ASSERT_EQ_AT (loc, actual, expected);
+}
+
+/* Smoketest for region_model::dump_to_pp.  */
+
+static void
+test_dump ()
+{
+  region_model model;
+  model.get_root_region ()->ensure_stack_region (&model);
+  model.get_root_region ()->ensure_globals_region (&model);
+  model.get_root_region ()->ensure_heap_region (&model);
+
+  auto_fix_quotes sentinel;
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  model.dump_to_pp (&pp);
+  ASSERT_STREQ (pp_formatted_text (&pp),
+		"r0: {kind: `root', parent: null, sval: null}\n"
+		"|-stack: r1: {kind: `stack', parent: r0, sval: sv0}\n"
+		"|  |: sval: sv0: {poisoned: uninit}\n"
+		"|-globals: r2: {kind: `map', parent: r0, sval: null, map: {}}\n"
+		"`-heap: r3: {kind: `heap', parent: r0, sval: sv1}\n"
+		"  |: sval: sv1: {poisoned: uninit}\n"
+		"svalues:\n"
+		"  sv0: {poisoned: uninit}\n"
+		"  sv1: {poisoned: uninit}\n"
+		"constraint manager:\n"
+		"  equiv classes:\n"
+		"  constraints:\n");
+}
+
+/* Verify that calling region_model::get_rvalue repeatedly on the same
+   tree constant retrieves the same svalue_id.  */
+
+static void
+test_unique_constants ()
+{
+  tree int_0 = build_int_cst (integer_type_node, 0);
+  tree int_42 = build_int_cst (integer_type_node, 42);
+
+  test_region_model_context ctxt;
+  region_model model;
+  ASSERT_EQ (model.get_rvalue (int_0, &ctxt), model.get_rvalue (int_0, &ctxt));
+  ASSERT_EQ (model.get_rvalue (int_42, &ctxt),
+	     model.get_rvalue (int_42, &ctxt));
+  ASSERT_NE (model.get_rvalue (int_0, &ctxt), model.get_rvalue (int_42, &ctxt));
+  ASSERT_EQ (ctxt.get_num_diagnostics (), 0);
+}
+
+/* Check that operator== and hashing works as expected for the
+   various svalue subclasses.  */
+
+static void
+test_svalue_equality ()
+{
+  tree int_42 = build_int_cst (integer_type_node, 42);
+  tree int_0 = build_int_cst (integer_type_node, 0);
+
+  /* Create pairs instances of the various subclasses of svalue,
+     testing for hash and equality between (this, this) and
+     (this, other of same subclass). */
+  svalue *ptr_to_r0
+    = new region_svalue (ptr_type_node, region_id::from_int (0));
+  svalue *ptr_to_r1
+    = new region_svalue (ptr_type_node, region_id::from_int (1));
+
+  ASSERT_EQ (ptr_to_r0->hash (), ptr_to_r0->hash ());
+  ASSERT_EQ (*ptr_to_r0, *ptr_to_r0);
+
+  ASSERT_NE (ptr_to_r0->hash (), ptr_to_r1->hash ());
+  ASSERT_NE (*ptr_to_r0, *ptr_to_r1);
+
+  svalue *cst_int_42 = new constant_svalue (int_42);
+  svalue *cst_int_0 = new constant_svalue (int_0);
+
+  ASSERT_EQ (cst_int_42->hash (), cst_int_42->hash ());
+  ASSERT_EQ (*cst_int_42, *cst_int_42);
+
+  ASSERT_NE (cst_int_42->hash (), cst_int_0->hash ());
+  ASSERT_NE (*cst_int_42, *cst_int_0);
+
+  svalue *uninit = new poisoned_svalue (POISON_KIND_UNINIT);
+  svalue *freed = new poisoned_svalue (POISON_KIND_FREED);
+
+  ASSERT_EQ (uninit->hash (), uninit->hash ());
+  ASSERT_EQ (*uninit, *uninit);
+
+  ASSERT_NE (uninit->hash (), freed->hash ());
+  ASSERT_NE (*uninit, *freed);
+
+  svalue *unknown_0 = new unknown_svalue (ptr_type_node);
+  svalue *unknown_1 = new unknown_svalue (ptr_type_node);
+  ASSERT_EQ (unknown_0->hash (), unknown_0->hash ());
+  ASSERT_EQ (*unknown_0, *unknown_0);
+  ASSERT_EQ (*unknown_1, *unknown_1);
+
+  /* Comparisons between different kinds of svalue.  */
+  ASSERT_NE (*ptr_to_r0, *cst_int_42);
+  ASSERT_NE (*ptr_to_r0, *uninit);
+  ASSERT_NE (*ptr_to_r0, *unknown_0);
+  ASSERT_NE (*cst_int_42, *ptr_to_r0);
+  ASSERT_NE (*cst_int_42, *uninit);
+  ASSERT_NE (*cst_int_42, *unknown_0);
+  ASSERT_NE (*uninit, *ptr_to_r0);
+  ASSERT_NE (*uninit, *cst_int_42);
+  ASSERT_NE (*uninit, *unknown_0);
+  ASSERT_NE (*unknown_0, *ptr_to_r0);
+  ASSERT_NE (*unknown_0, *cst_int_42);
+  ASSERT_NE (*unknown_0, *uninit);
+
+  delete ptr_to_r0;
+  delete ptr_to_r1;
+  delete cst_int_42;
+  delete cst_int_0;
+  delete uninit;
+  delete freed;
+  delete unknown_0;
+  delete unknown_1;
+}
+
+/* Check that operator== and hashing works as expected for the
+   various region subclasses.  */
+
+static void
+test_region_equality ()
+{
+  region *r0 = new map_region (region_id::from_int (3), integer_type_node);
+  region *r1 = new map_region (region_id::from_int (4), integer_type_node);
+
+  ASSERT_EQ (*r0, *r0);
+  ASSERT_EQ (r0->hash (), r0->hash ());
+  ASSERT_NE (*r0, *r1);
+  ASSERT_NE (r0->hash (), r1->hash ());
+
+  // TODO: test coverage for the map within a map_region
+
+  delete r0;
+  delete r1;
+}
+
+/* A subclass of purge_criteria for selftests: purge all svalue_id instances.  */
+
+class purge_all_svalue_ids : public purge_criteria
+{
+public:
+  bool should_purge_p (svalue_id) const FINAL OVERRIDE
+  {
+    return true;
+  }
+};
+
+/* A subclass of purge_criteria: purge a specific svalue_id.  */
+
+class purge_one_svalue_id : public purge_criteria
+{
+public:
+  purge_one_svalue_id (svalue_id victim) : m_victim (victim) {}
+
+  purge_one_svalue_id (region_model model, tree expr)
+  : m_victim (model.get_rvalue (expr, NULL)) {}
+
+  bool should_purge_p (svalue_id sid) const FINAL OVERRIDE
+  {
+    return sid == m_victim;
+  }
+
+private:
+  svalue_id m_victim;
+};
+
+/* Check that constraint_manager::purge works for individual svalue_ids.  */
+
+static void
+test_purging_by_criteria ()
+{
+  tree int_42 = build_int_cst (integer_type_node, 42);
+  tree int_0 = build_int_cst (integer_type_node, 0);
+
+  tree x = build_global_decl ("x", integer_type_node);
+  tree y = build_global_decl ("y", integer_type_node);
+
+  {
+    region_model model0;
+    region_model model1;
+
+    ADD_SAT_CONSTRAINT (model1, x, EQ_EXPR, y);
+    ASSERT_NE (model0, model1);
+
+    {
+      purge_stats stats;
+      purge_one_svalue_id px (model1, x);
+      model1.get_constraints ()->purge (px, &stats);
+      // FIXME: check stats
+    }
+
+    {
+      purge_stats stats;
+      purge_one_svalue_id py (model1.get_rvalue (y, NULL));
+      model1.get_constraints ()->purge (py, &stats);
+      // FIXME: check stats
+    }
+
+    ASSERT_EQ (*model0.get_constraints (), *model1.get_constraints ());
+  }
+
+  {
+    region_model model0;
+    region_model model1;
+
+    ADD_SAT_CONSTRAINT (model1, x, EQ_EXPR, int_42);
+    ASSERT_NE (model0, model1);
+    ASSERT_CONDITION_TRUE (model1, x, EQ_EXPR, int_42);
+
+    purge_stats stats;
+    model1.get_constraints ()->purge (purge_one_svalue_id (model1, x), &stats);
+
+    ASSERT_CONDITION_UNKNOWN (model1, x, EQ_EXPR, int_42);
+  }
+
+  {
+    region_model model0;
+    region_model model1;
+
+    ADD_SAT_CONSTRAINT (model1, x, GE_EXPR, int_0);
+    ADD_SAT_CONSTRAINT (model1, x, LE_EXPR, int_42);
+    ASSERT_NE (model0, model1);
+
+    ASSERT_CONDITION_TRUE (model1, x, GE_EXPR, int_0);
+    ASSERT_CONDITION_TRUE (model1, x, LE_EXPR, int_42);
+
+    purge_stats stats;
+    model1.get_constraints ()->purge (purge_one_svalue_id (model1, x), &stats);
+
+    ASSERT_CONDITION_UNKNOWN (model1, x, GE_EXPR, int_0);
+    ASSERT_CONDITION_UNKNOWN (model1, x, LE_EXPR, int_42);
+  }
+
+  {
+    region_model model0;
+    region_model model1;
+
+    ADD_SAT_CONSTRAINT (model1, x, NE_EXPR, int_42);
+    ADD_SAT_CONSTRAINT (model1, y, NE_EXPR, int_0);
+    ASSERT_NE (model0, model1);
+    ASSERT_CONDITION_TRUE (model1, x, NE_EXPR, int_42);
+    ASSERT_CONDITION_TRUE (model1, y, NE_EXPR, int_0);
+
+    purge_stats stats;
+    model1.get_constraints ()->purge (purge_one_svalue_id (model1, x), &stats);
+    ASSERT_NE (model0, model1);
+
+    ASSERT_CONDITION_UNKNOWN (model1, x, NE_EXPR, int_42);
+    ASSERT_CONDITION_TRUE (model1, y, NE_EXPR, int_0);
+  }
+
+  {
+    region_model model0;
+    region_model model1;
+
+    ADD_SAT_CONSTRAINT (model1, x, NE_EXPR, int_42);
+    ADD_SAT_CONSTRAINT (model1, y, NE_EXPR, int_0);
+    ASSERT_NE (model0, model1);
+    ASSERT_CONDITION_TRUE (model1, x, NE_EXPR, int_42);
+    ASSERT_CONDITION_TRUE (model1, y, NE_EXPR, int_0);
+
+    purge_stats stats;
+    model1.get_constraints ()->purge (purge_all_svalue_ids (), &stats);
+    ASSERT_CONDITION_UNKNOWN (model1, x, NE_EXPR, int_42);
+    ASSERT_CONDITION_UNKNOWN (model1, y, NE_EXPR, int_0);
+  }
+
+}
+
+/* Test that region_model::purge_unused_svalues works as expected.  */
+
+static void
+test_purge_unused_svalues ()
+{
+  tree int_42 = build_int_cst (integer_type_node, 42);
+  tree int_0 = build_int_cst (integer_type_node, 0);
+  tree x = build_global_decl ("x", integer_type_node);
+  tree y = build_global_decl ("y", integer_type_node);
+
+  test_region_model_context ctxt;
+  region_model model;
+  model.set_to_new_unknown_value (model.get_lvalue (x, &ctxt), TREE_TYPE (x));
+  model.set_to_new_unknown_value (model.get_lvalue (x, &ctxt), TREE_TYPE (x));
+  model.set_to_new_unknown_value (model.get_lvalue (x, &ctxt), TREE_TYPE (x));
+  model.add_constraint (x, NE_EXPR, int_42, &ctxt);
+
+  model.set_value (model.get_lvalue (x, &ctxt),
+		   model.get_rvalue (int_42, &ctxt));
+  model.add_constraint (y, GT_EXPR, int_0, &ctxt);
+
+  /* The redundant unknown values should have been purged.  */
+  purge_stats purged;
+  model.purge_unused_svalues (&purged, NULL);
+  ASSERT_EQ (purged.m_num_svalues, 3);
+
+  /* and the redundant constraint on an old, unknown value for x should
+     have been purged.  */
+  ASSERT_EQ (purged.m_num_equiv_classes, 1);
+  ASSERT_EQ (purged.m_num_constraints, 1);
+  ASSERT_EQ (model.get_constraints ()->m_constraints.length (), 2);
+
+  /* ...but we should still have x == 42.  */
+  ASSERT_EQ (model.eval_condition (x, EQ_EXPR, int_42, &ctxt),
+	     tristate::TS_TRUE);
+
+  /* ...and we should still have the constraint on y.  */
+  ASSERT_EQ (model.eval_condition (y, GT_EXPR, int_0, &ctxt),
+	     tristate::TS_TRUE);
+
+  ASSERT_EQ (ctxt.get_num_diagnostics (), 0);
+}
+
+/* FIXME.  */
+
+static void
+test_assignment ()
+{
+  tree int_0 = build_int_cst (integer_type_node, 0);
+  tree x = build_global_decl ("x", integer_type_node);
+  tree y = build_global_decl ("y", integer_type_node);
+
+  /* "x == 0", then use of y, then "y = 0;".  */
+  region_model model;
+  ADD_SAT_CONSTRAINT (model, x, EQ_EXPR, int_0);
+  ASSERT_CONDITION_UNKNOWN (model, y, EQ_EXPR, int_0);
+  model.set_value (model.get_lvalue (y, NULL),
+		   model.get_rvalue (int_0, NULL));
+  ASSERT_CONDITION_TRUE (model, y, EQ_EXPR, int_0);
+  ASSERT_CONDITION_TRUE (model, y, EQ_EXPR, x);
+}
+
+/* Verify the details of pushing and popping stack frames.  */
+
+static void
+test_stack_frames ()
+{
+  tree int_42 = build_int_cst (integer_type_node, 42);
+  tree int_10 = build_int_cst (integer_type_node, 10);
+  tree int_5 = build_int_cst (integer_type_node, 5);
+  tree int_0 = build_int_cst (integer_type_node, 0);
+
+  auto_vec <tree> param_types;
+  tree parent_fndecl = make_fndecl (integer_type_node,
+				    "parent_fn",
+				    param_types);
+  allocate_struct_function (parent_fndecl, true);
+
+  tree child_fndecl = make_fndecl (integer_type_node,
+				   "child_fn",
+				   param_types);
+  allocate_struct_function (child_fndecl, true);
+
+  /* "a" and "b" in the parent frame.  */
+  tree a = build_decl (UNKNOWN_LOCATION, PARM_DECL,
+		       get_identifier ("a"),
+		       integer_type_node);
+  tree b = build_decl (UNKNOWN_LOCATION, PARM_DECL,
+		       get_identifier ("b"),
+		       integer_type_node);
+  /* "x" and "y" in a child frame.  */
+  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);
+
+  /* "p" global.  */
+  tree p = build_global_decl ("p", ptr_type_node);
+
+  /* "q" global.  */
+  tree q = build_global_decl ("q", ptr_type_node);
+
+  test_region_model_context ctxt;
+  region_model model;
+
+  /* Push stack frame for "parent_fn".  */
+  region_id parent_frame_rid
+    = model.push_frame (DECL_STRUCT_FUNCTION (parent_fndecl), NULL);
+  ASSERT_EQ (model.get_current_frame_id (), parent_frame_rid);
+  region_id a_in_parent_rid = model.get_lvalue (a, &ctxt);
+  model.set_value (a_in_parent_rid, model.get_rvalue (int_42, &ctxt));
+  model.set_to_new_unknown_value (model.get_lvalue (b, &ctxt),
+				  integer_type_node);
+  model.add_constraint (b, LT_EXPR, int_10, &ctxt);
+  ASSERT_EQ (model.eval_condition (b, LT_EXPR, int_10, &ctxt),
+	     tristate (tristate::TS_TRUE));
+
+  /* Push stack frame for "child_fn".  */
+  region_id child_frame_rid
+    = model.push_frame (DECL_STRUCT_FUNCTION (child_fndecl), NULL);
+  ASSERT_EQ (model.get_current_frame_id (), child_frame_rid);
+  region_id x_in_child_rid = model.get_lvalue (x, &ctxt);
+  model.set_value (x_in_child_rid, model.get_rvalue (int_0, &ctxt));
+  model.set_to_new_unknown_value (model.get_lvalue (y, &ctxt),
+				  integer_type_node);
+  model.add_constraint (y, NE_EXPR, int_5, &ctxt);
+  ASSERT_EQ (model.eval_condition (y, NE_EXPR, int_5, &ctxt),
+	     tristate (tristate::TS_TRUE));
+
+  /* Point a global pointer at a local in the child frame:  p = &x.  */
+  region_id p_in_globals_rid = model.get_lvalue (p, &ctxt);
+  model.set_value (p_in_globals_rid,
+		   model.get_or_create_ptr_svalue (ptr_type_node,
+						   x_in_child_rid));
+
+  /* Point another global pointer at p: q = &p.  */
+  region_id q_in_globals_rid = model.get_lvalue (q, &ctxt);
+  model.set_value (q_in_globals_rid,
+		   model.get_or_create_ptr_svalue (ptr_type_node,
+						   p_in_globals_rid));
+
+  /* Test get_descendents.  */
+  region_id_set descendents (&model);
+  model.get_descendents (child_frame_rid, &descendents);
+  ASSERT_TRUE (descendents.region_p (child_frame_rid));
+  ASSERT_TRUE (descendents.region_p (x_in_child_rid));
+  ASSERT_FALSE (descendents.region_p (a_in_parent_rid));
+  ASSERT_EQ (descendents.num_regions (), 3);
+
+  /* Pop the "child_fn" frame from the stack.  */
+  purge_stats purged;
+  model.pop_frame (&purged, &ctxt);
+
+  /* We should have purged the unknown values for x and y. */
+  ASSERT_EQ (purged.m_num_svalues, 2);
+
+  /* We should have purged the frame region and the regions for x and y. */
+  ASSERT_EQ (purged.m_num_regions, 3);
+
+  /* We should have purged the constraint on y.  */
+  ASSERT_EQ (purged.m_num_equiv_classes, 1);
+  ASSERT_EQ (purged.m_num_constraints, 1);
+
+  /* Verify that p (which was pointing at the local "x" in the popped
+     frame) has been poisoned.  */
+  svalue *new_p_sval = model.get_svalue (model.get_rvalue (p, &ctxt));
+  ASSERT_EQ (new_p_sval->get_kind (), SK_POISONED);
+  ASSERT_EQ (new_p_sval->dyn_cast_poisoned_svalue ()->get_poison_kind (),
+	     POISON_KIND_POPPED_STACK);
+
+  /* Verify that q still points to p, in spite of the region
+     renumbering.  */
+  svalue *new_q_sval = model.get_svalue (model.get_rvalue (q, &ctxt));
+  ASSERT_EQ (new_q_sval->get_kind (), SK_REGION);
+  ASSERT_EQ (new_q_sval->dyn_cast_region_svalue ()->get_pointee (),
+	     model.get_lvalue (p, &ctxt));
+
+  /* Verify that top of stack has been updated.  */
+  ASSERT_EQ (model.get_current_frame_id (), parent_frame_rid);
+
+  /* Verify locals in parent frame.  */
+  /* Verify "a" still has its value.  */
+  svalue *new_a_sval = model.get_svalue (model.get_rvalue (a, &ctxt));
+  ASSERT_EQ (new_a_sval->get_kind (), SK_CONSTANT);
+  ASSERT_EQ (new_a_sval->dyn_cast_constant_svalue ()->get_constant (),
+	     int_42);
+  /* Verify "b" still has its constraint.  */
+  ASSERT_EQ (model.eval_condition (b, LT_EXPR, int_10, &ctxt),
+	     tristate (tristate::TS_TRUE));
+}
+
+/* FIXME.  */
+
+static void
+test_get_representative_path_var ()
+{
+  auto_vec <tree> param_types;
+  tree fndecl = make_fndecl (integer_type_node,
+			     "factorial",
+			     param_types);
+  allocate_struct_function (fndecl, true);
+
+  /* Parm "n".  */
+  tree n = build_decl (UNKNOWN_LOCATION, PARM_DECL,
+		       get_identifier ("n"),
+		       integer_type_node);
+
+  region_model model;
+
+  /* Push 5 stack frames for "factorial", each with a param  */
+  auto_vec<region_id> parm_rids;
+  auto_vec<svalue_id> parm_sids;
+  for (int depth = 0; depth < 5; depth++)
+    {
+      region_id frame_rid
+	= model.push_frame (DECL_STRUCT_FUNCTION (fndecl), NULL);
+      region_id rid_n = model.get_lvalue (path_var (n, depth), NULL);
+      parm_rids.safe_push (rid_n);
+
+      ASSERT_EQ (model.get_region (rid_n)->get_parent (), frame_rid);
+
+      svalue_id sid_n
+	= model.set_to_new_unknown_value (rid_n, integer_type_node);
+      parm_sids.safe_push (sid_n);
+      // FIXME: do something with these?
+    }
+
+  /* Verify that we can recognize that the regions are the parms,
+     at every depth.  */
+  for (int depth = 0; depth < 5; depth++)
+    {
+      ASSERT_EQ (model.get_representative_path_var (parm_rids[depth]),
+		 path_var (n, depth));
+      /* ...and that we can lookup lvalues for locals for all frames,
+	 not just the top.  */
+      ASSERT_EQ (model.get_lvalue (path_var (n, depth), NULL),
+		 parm_rids[depth]);
+      /* ...and that we can locate the svalues.  */
+      auto_vec<path_var> pvs;
+      model.get_path_vars_for_svalue (parm_sids[depth], &pvs);
+      ASSERT_EQ (pvs.length (), 1);
+      ASSERT_EQ (pvs[0], path_var (n, depth));
+    }
+}
+
+/* Verify that the core regions within a region_model are in a consistent
+   order after canonicalization.  */
+
+static void
+test_canonicalization_1 ()
+{
+  region_model model0;
+  model0.get_root_region ()->ensure_stack_region (&model0);
+  model0.get_root_region ()->ensure_globals_region (&model0);
+
+  region_model model1;
+  model1.get_root_region ()->ensure_globals_region (&model1);
+  model1.get_root_region ()->ensure_stack_region (&model1);
+
+  model0.canonicalize (NULL);
+  model1.canonicalize (NULL);
+  ASSERT_EQ (model0, model1);
+}
+
+/* Verify that region models for
+      x = 42; y = 113;
+   and
+      y = 113; x = 42;
+   are equal after canonicalization.  */
+
+static void
+test_canonicalization_2 ()
+{
+  tree int_42 = build_int_cst (integer_type_node, 42);
+  tree int_113 = build_int_cst (integer_type_node, 113);
+  tree x = build_global_decl ("x", integer_type_node);
+  tree y = build_global_decl ("y", integer_type_node);
+
+  region_model model0;
+  model0.set_value (model0.get_lvalue (x, NULL),
+		    model0.get_rvalue (int_42, NULL));
+  model0.set_value (model0.get_lvalue (y, NULL),
+		    model0.get_rvalue (int_113, NULL));
+
+  region_model model1;
+  model1.set_value (model1.get_lvalue (y, NULL),
+		    model1.get_rvalue (int_113, NULL));
+  model1.set_value (model1.get_lvalue (x, NULL),
+		    model1.get_rvalue (int_42, NULL));
+
+  model0.canonicalize (NULL);
+  model1.canonicalize (NULL);
+  ASSERT_EQ (model0, model1);
+}
+
+/* Verify that constraints for
+     x > 3 && y > 42
+   and
+     y > 42 && x > 3
+   are equal after canonicalization.  */
+
+static void
+test_canonicalization_3 ()
+{
+  tree int_3 = build_int_cst (integer_type_node, 3);
+  tree int_42 = build_int_cst (integer_type_node, 42);
+  tree x = build_global_decl ("x", integer_type_node);
+  tree y = build_global_decl ("y", integer_type_node);
+
+  region_model model0;
+  model0.add_constraint (x, GT_EXPR, int_3, NULL);
+  model0.add_constraint (y, GT_EXPR, int_42, NULL);
+
+  region_model model1;
+  model1.add_constraint (y, GT_EXPR, int_42, NULL);
+  model1.add_constraint (x, GT_EXPR, int_3, NULL);
+
+  model0.canonicalize (NULL);
+  model1.canonicalize (NULL);
+  ASSERT_EQ (model0, model1);
+}
+
+// FIXME: test for ordering within an EC
+
+#if 0
+
+struct region_model_merger_test
+{
+  test_region_model_context ctxt;
+  region_model m_model0;
+  region_model m_model1;
+  region_model m_merged;
+};
+
+#endif
+
+/* Assert that if we have two region_model instances
+   with values VAL_A and VAL_B for EXPR that they are
+   mergable.  Write the merged model to *OUT_MERGED_MODEL,
+   and the merged svalue ptr to *OUT_MERGED_SVALUE.
+   If VAL_A or VAL_B are NULL_TREE, don't populate EXPR
+   for that region_model.  */
+
+static void
+assert_region_models_merge (tree expr, tree val_a, tree val_b,
+			    region_model *out_merged_model,
+			    svalue **out_merged_svalue)
+{
+  // FIXME: break this out into a helper function:
+  /* FIXME: might have to be a a class, so that the values can outlive
+     the setup, so we can have custom tests for them.  */
+  test_region_model_context ctxt;
+  region_model model0;
+  region_model model1;
+  if (val_a)
+    model0.set_value (model0.get_lvalue (expr, &ctxt),
+		      model0.get_rvalue (val_a, &ctxt));
+  if (val_b)
+    model1.set_value (model1.get_lvalue (expr, &ctxt),
+		      model1.get_rvalue (val_b, &ctxt));
+
+  /* They should be mergeable.  */
+  ASSERT_TRUE (model0.can_merge_with_p (model1, out_merged_model));
+
+  svalue_id merged_svalue_sid = out_merged_model->get_rvalue (expr, &ctxt);
+  *out_merged_svalue = out_merged_model->get_svalue (merged_svalue_sid);
+
+  // FIXME: leave verifications to the caller
+#if 0
+  /* In particular, there should be a constant value for "expr".  */
+  svalue *merged_expr_sval = merged.get_svalue (merged.get_rvalue (expr, &ctxt));
+  ASSERT_EQ (merged_expr_sval->get_kind (), SK_CONSTANT);
+  // FIXME: check cst
+#endif
+
+}
+
+/* Verify that we can merge region_model instances.  */
+
+static void
+test_state_merging ()
+{
+  tree int_42 = build_int_cst (integer_type_node, 42);
+  tree int_113 = build_int_cst (integer_type_node, 113);
+  tree x = build_global_decl ("x", integer_type_node);
+  tree y = build_global_decl ("y", integer_type_node);
+  tree z = build_global_decl ("z", integer_type_node);
+  tree p = build_global_decl ("p", ptr_type_node);
+
+  tree addr_of_y = build1 (ADDR_EXPR, ptr_type_node, y);
+  tree addr_of_z = build1 (ADDR_EXPR, ptr_type_node, z);
+
+  auto_vec <tree> param_types;
+  tree test_fndecl = make_fndecl (integer_type_node, "test_fn", param_types);
+  allocate_struct_function (test_fndecl, true);
+
+  /* Param "a".  */
+  tree a = build_decl (UNKNOWN_LOCATION, PARM_DECL,
+		       get_identifier ("a"),
+		       integer_type_node);
+  tree addr_of_a = build1 (ADDR_EXPR, ptr_type_node, a);
+
+  {
+    region_model model0;
+    region_model model1;
+    region_model merged;
+    /* Verify empty models can be merged.  */
+    ASSERT_TRUE (model0.can_merge_with_p (model1, &merged));
+    ASSERT_EQ (model0, merged);
+  }
+
+  /* Verify that we can merge two contradictory constraints on the
+     value for a global.  */
+  /* TODO: verify that the merged model doesn't have a value for
+     the global  */
+  {
+    region_model model0;
+    region_model model1;
+    region_model merged;
+    test_region_model_context ctxt;
+    model0.add_constraint (x, EQ_EXPR, int_42, &ctxt);
+    model1.add_constraint (x, EQ_EXPR, int_113, &ctxt);
+    ASSERT_TRUE (model0.can_merge_with_p (model1, &merged));
+    ASSERT_NE (model0, merged);
+    ASSERT_NE (model1, merged);
+  }
+
+  /* Verify handling of a PARM_DECL.  */
+  {
+    test_region_model_context ctxt;
+    region_model model0;
+    region_model model1;
+    model0.push_frame (DECL_STRUCT_FUNCTION (test_fndecl), NULL);
+    model1.push_frame (DECL_STRUCT_FUNCTION (test_fndecl), NULL);
+    svalue_id sid_a
+      = model0.set_to_new_unknown_value (model0.get_lvalue (a, &ctxt),
+					 integer_type_node);
+    model1.set_to_new_unknown_value (model1.get_lvalue (a, &ctxt),
+				     integer_type_node);
+    ASSERT_EQ (model0, model1);
+
+    /* Check that get_value_by_name works for locals.  */
+    ASSERT_EQ (model0.get_value_by_name ("a"), sid_a);
+
+    /* They should be mergeable, and the result should be the same.  */
+    region_model merged;
+    ASSERT_TRUE (model0.can_merge_with_p (model1, &merged));
+    ASSERT_EQ (model0, merged);
+    /* In particular, there should be an unknown value for "a".  */
+    svalue *merged_a_sval = merged.get_svalue (merged.get_rvalue (a, &ctxt));
+    ASSERT_EQ (merged_a_sval->get_kind (), SK_UNKNOWN);
+  }
+
+  /* Verify handling of a global.  */
+  {
+    test_region_model_context ctxt;
+    region_model model0;
+    region_model model1;
+    svalue_id sid_x
+      = model0.set_to_new_unknown_value (model0.get_lvalue (x, &ctxt),
+					 integer_type_node);
+    model1.set_to_new_unknown_value (model1.get_lvalue (x, &ctxt),
+				     integer_type_node);
+    ASSERT_EQ (model0, model1);
+
+    /* Check that get_value_by_name works for globals.  */
+    ASSERT_EQ (model0.get_value_by_name ("x"), sid_x);
+
+    /* They should be mergeable, and the result should be the same.  */
+    region_model merged;
+    ASSERT_TRUE (model0.can_merge_with_p (model1, &merged));
+    ASSERT_EQ (model0, merged);
+    /* In particular, there should be an unknown value for "x".  */
+    svalue *merged_x_sval = merged.get_svalue (merged.get_rvalue (x, &ctxt));
+    ASSERT_EQ (merged_x_sval->get_kind (), SK_UNKNOWN);
+  }
+
+  /* Use global-handling to verify various combinations of values.  */
+
+  /* Two equal constant values.  */
+  {
+    region_model merged;
+    svalue *merged_x_sval;
+    assert_region_models_merge (x, int_42, int_42, &merged, &merged_x_sval);
+
+    /* In particular, there should be a constant value for "x".  */
+    ASSERT_EQ (merged_x_sval->get_kind (), SK_CONSTANT);
+    ASSERT_EQ (merged_x_sval->dyn_cast_constant_svalue ()->get_constant (),
+	       int_42);
+  }
+
+  /* Two non-equal constant values.  */
+  {
+    region_model merged;
+    svalue *merged_x_sval;
+    assert_region_models_merge (x, int_42, int_113, &merged, &merged_x_sval);
+
+    /* In particular, there should be an unknown value for "x".  */
+    ASSERT_EQ (merged_x_sval->get_kind (), SK_UNKNOWN);
+    // FIXME: constraints on them?
+  }
+
+  /* Uninit and constant.  */
+  {
+    region_model merged;
+    svalue *merged_x_sval;
+    assert_region_models_merge (x, NULL_TREE, int_113, &merged, &merged_x_sval);
+
+    /* In particular, there should be an unknown value for "x".  */
+    ASSERT_EQ (merged_x_sval->get_kind (), SK_UNKNOWN);
+    // FIXME: constraints on them?
+  }
+
+  /* Constant and uninit.  */
+  {
+    region_model merged;
+    svalue *merged_x_sval;
+    assert_region_models_merge (x, int_42, NULL_TREE, &merged, &merged_x_sval);
+
+    /* In particular, there should be an unknown value for "x".  */
+    ASSERT_EQ (merged_x_sval->get_kind (), SK_UNKNOWN);
+    // FIXME: constraints on them?
+  }
+
+  /* Unknown and constant.  */
+  {
+#if 0
+    region_model_merger_test test;
+    test.m_model0.
+    region_model merged;
+    svalue *merged_x_sval;
+    assert_region_models_merge (x, int_42, int_113, &merged, &merged_x_sval);
+
+    /* In particular, there should be an unknown value for "x".  */
+    ASSERT_EQ (merged_x_sval->get_kind (), SK_UNKNOWN);
+    // FIXME: constraints on them?
+#endif
+  }
+
+  /* Pointers: NULL and NULL.  */
+  // TODO
+
+  /* Pointers: NULL and non-NULL.  */
+  // TODO
+
+  /* Pointers: non-NULL and non-NULL: ptr to a local.  */
+  {
+    region_model model0;
+    model0.push_frame (DECL_STRUCT_FUNCTION (test_fndecl), NULL);
+    model0.set_to_new_unknown_value (model0.get_lvalue (a, NULL),
+				     integer_type_node);
+    model0.set_value (model0.get_lvalue (p, NULL),
+		      model0.get_rvalue (addr_of_a, NULL));
+
+    region_model model1 (model0);
+    ASSERT_EQ (model0, model1);
+
+    /* They should be mergeable, and the result should be the same.  */
+    region_model merged;
+    ASSERT_TRUE (model0.can_merge_with_p (model1, &merged));
+    ASSERT_EQ (model0, merged);
+  }
+
+  /* Pointers: non-NULL and non-NULL: ptr to a global.  */
+  {
+    region_model merged;
+    /* x == &y in both input models.  */
+    svalue *merged_x_sval;
+    assert_region_models_merge (x, addr_of_y, addr_of_y, &merged,
+				&merged_x_sval);
+
+    /* We should get x == &y in the merged model.  */
+    ASSERT_EQ (merged_x_sval->get_kind (), SK_REGION);
+    region_svalue *merged_x_ptr = merged_x_sval->dyn_cast_region_svalue ();
+    region_id merged_x_star_rid = merged_x_ptr->get_pointee ();
+    ASSERT_EQ (merged_x_star_rid, merged.get_lvalue (y, NULL));
+  }
+
+  /* Pointers: non-NULL ptrs to different globals: should be unknown.  */
+  {
+    region_model merged;
+    /* x == &y vs x == &z in the input models.  */
+    svalue *merged_x_sval;
+    assert_region_models_merge (x, addr_of_y, addr_of_z, &merged,
+				&merged_x_sval);
+
+    /* We should get x == unknown in the merged model.  */
+    ASSERT_EQ (merged_x_sval->get_kind (), SK_UNKNOWN);
+  }
+
+  /* Pointers: non-NULL and non-NULL: ptr to a heap region.  */
+  {
+    test_region_model_context ctxt;
+    region_model model0;
+    region_id new_rid = model0.add_new_malloc_region ();
+    svalue_id ptr_sid
+      = model0.get_or_create_ptr_svalue (ptr_type_node, new_rid);
+    model0.set_value (model0.get_lvalue (x, &ctxt),
+		      ptr_sid);
+    model0.canonicalize (&ctxt);
+
+    region_model model1 (model0);
+
+    ASSERT_EQ (model0, model1);
+
+    region_model merged;
+    ASSERT_TRUE (model0.can_merge_with_p (model1, &merged));
+
+    merged.canonicalize (&ctxt);
+
+    /* The merged model ought to be identical (after canonicalization,
+       at least).  */
+    ASSERT_EQ (model0, merged);
+  }
+
+  /* Two regions sharing the same unknown svalue should continue sharing
+     an unknown svalue after self-merger.  */
+  {
+    test_region_model_context ctxt;
+    region_model model0;
+    svalue_id sid
+      = model0.set_to_new_unknown_value (model0.get_lvalue (x, &ctxt),
+					 integer_type_node);
+    model0.set_value (model0.get_lvalue (y, &ctxt), sid);
+    region_model model1 (model0);
+
+    /* They should be mergeable, and the result should be the same.  */
+    region_model merged;
+    ASSERT_TRUE (model0.can_merge_with_p (model1, &merged));
+    ASSERT_EQ (model0, merged);
+
+    /* In particular, we should have x == y.  */
+    ASSERT_EQ (merged.eval_condition (x, EQ_EXPR, y, &ctxt),
+	       tristate (tristate::TS_TRUE));
+  }
+
+#if 0
+  {
+    region_model model0;
+    region_model model1;
+    test_region_model_context ctxt;
+    model0.add_constraint (x, EQ_EXPR, int_42, &ctxt);
+    model1.add_constraint (x, NE_EXPR, int_42, &ctxt);
+    ASSERT_TRUE (model0.can_merge_with_p (model1));
+  }
+
+  {
+    region_model model0;
+    region_model model1;
+    test_region_model_context ctxt;
+    model0.add_constraint (x, EQ_EXPR, int_42, &ctxt);
+    model1.add_constraint (x, NE_EXPR, int_42, &ctxt);
+    model1.add_constraint (x, EQ_EXPR, int_113, &ctxt);
+    ASSERT_TRUE (model0.can_merge_with_p (model1));
+  }
+#endif
+
+  // TODO: what can't we merge? need at least one such test
+
+  /* TODO: various things
+     - heap regions
+     - value merging:
+       - every combination, but in particular
+          - pairs of regions
+   */
+}
+
+/* Verify that constraints are correctly merged when merging region_model
+   instances.  */
+
+static void
+test_constraint_merging ()
+{
+  tree int_0 = build_int_cst (integer_type_node, 0);
+  tree int_5 = build_int_cst (integer_type_node, 5);
+  tree x = build_global_decl ("x", integer_type_node);
+  tree y = build_global_decl ("y", integer_type_node);
+  tree z = build_global_decl ("z", integer_type_node);
+  tree n = build_global_decl ("n", integer_type_node);
+
+  test_region_model_context ctxt;
+
+  /* model0: 0 <= (x == y) < n.  */
+  region_model model0;
+  model0.set_to_new_unknown_value (model0.get_lvalue (x, &ctxt),
+				   integer_type_node);
+  model0.add_constraint (x, EQ_EXPR, y, &ctxt);
+  model0.add_constraint (x, GE_EXPR, int_0, NULL);
+  model0.add_constraint (x, LT_EXPR, n, NULL);
+
+  /* model1: z != 5 && (0 <= x < n).  */
+  region_model model1;
+  model1.set_to_new_unknown_value (model1.get_lvalue (x, &ctxt),
+				   integer_type_node);
+  model1.add_constraint (z, NE_EXPR, int_5, NULL);
+  model1.add_constraint (x, GE_EXPR, int_0, NULL);
+  model1.add_constraint (x, LT_EXPR, n, NULL);
+
+  /* They should be mergeable; the merged constraints should
+     be: (0 <= x < n).  */
+  region_model merged;
+  ASSERT_TRUE (model0.can_merge_with_p (model1, &merged));
+
+  ASSERT_EQ (merged.eval_condition (x, GE_EXPR, int_0, &ctxt),
+	     tristate (tristate::TS_TRUE));
+  ASSERT_EQ (merged.eval_condition (x, LT_EXPR, n, &ctxt),
+	     tristate (tristate::TS_TRUE));
+
+  ASSERT_EQ (merged.eval_condition (z, NE_EXPR, int_5, &ctxt),
+	     tristate (tristate::TS_UNKNOWN));
+  ASSERT_EQ (merged.eval_condition (x, LT_EXPR, y, &ctxt),
+	     tristate (tristate::TS_UNKNOWN));
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+analyzer_region_model_cc_tests ()
+{
+  test_dump ();
+  test_unique_constants ();
+  test_svalue_equality ();
+  test_region_equality ();
+  test_purging_by_criteria ();
+  test_purge_unused_svalues ();
+  test_assignment ();
+  test_stack_frames ();
+  test_get_representative_path_var ();
+  test_canonicalization_1 ();
+  test_canonicalization_2 ();
+  test_canonicalization_3 ();
+  test_state_merging ();
+  test_constraint_merging ();
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
new file mode 100644
index 0000000..abd158f
--- /dev/null
+++ b/gcc/analyzer/region-model.h
@@ -0,0 +1,1492 @@
+/* Classes for modeling the state of memory.
+   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_REGION_MODEL_H
+#define GCC_ANALYZER_REGION_MODEL_H
+
+#include "analyzer/sm.h"
+
+/* Implemention of the region-based ternary model described in:
+     "A Memory Model for Static Analysis of C Programs"
+      (Zhongxing Xu, Ted Kremenek, and Jian Zhang)
+     http://lcs.ios.ac.cn/~xuzb/canalyze/memmodel.pdf  */
+
+class superedge;
+
+class svalue;
+  class region_svalue;
+  class constant_svalue;
+  class poisoned_svalue;
+  class unknown_svalue;
+class region;
+class region_model;
+class region_model_context;
+struct model_merger;
+struct svalue_id_merger_mapping;
+struct canonicalization;
+class pending_diagnostic;
+
+class constraint_manager;
+
+/* A region_model is effectively a graph of regions and symbolic values.
+   We store per-model IDs rather than pointers to make it easier to clone
+   and to compare graphs.  */
+
+/* An ID for an svalue within a region_model.  Internally, this is an index
+   into a vector of svalue * within the region_model.  */
+
+class svalue_id
+{
+public:
+  static svalue_id null () { return svalue_id (-1); }
+
+  svalue_id () : m_idx (-1) {}
+
+  bool operator== (const svalue_id &other) const
+  {
+    return m_idx == other.m_idx;
+  }
+
+  bool operator!= (const svalue_id &other) const
+  {
+    return m_idx != other.m_idx;
+  }
+
+  bool null_p () const { return m_idx == -1; }
+
+  static svalue_id from_int (int idx) { return svalue_id (idx); }
+  int as_int () const { return m_idx; }
+
+  void print (pretty_printer *pp) const;
+  void dump_node_name_to_pp (pretty_printer *pp) const;
+  std::string to_str () const;
+
+private:
+  svalue_id (int idx) : m_idx (idx) {}
+
+  int m_idx;
+};
+
+/* An ID for a region within a region_model.  Internally, this is an index
+   into a vector of region * within the region_model.  */
+
+class region_id
+{
+public:
+  static region_id null () { return region_id (-1); }
+
+  region_id () : m_idx (-1) {}
+
+  bool operator== (const region_id &other) const
+  {
+    return m_idx == other.m_idx;
+  }
+
+  bool operator!= (const region_id &other) const
+  {
+    return m_idx != other.m_idx;
+  }
+
+  bool null_p () const { return m_idx == -1; }
+
+  static region_id from_int (int idx) { return region_id (idx); }
+  int as_int () const { return m_idx; }
+
+  void print (pretty_printer *pp) const;
+  void dump_node_name_to_pp (pretty_printer *pp) const;
+
+private:
+  region_id (int idx) : m_idx (idx) {}
+
+  int m_idx;
+};
+
+/* A class for renumbering IDs within a region_model, mapping old IDs
+   to new IDs (e.g. when removing one or more elements, thus needing to
+   renumber).  */
+// TODO: could this be useful for equiv_class_ids?
+
+template <typename T>
+class id_map
+{
+ public:
+  id_map (int num_ids);
+  void put (T src, T dst);
+  T get_dst_for_src (T src) const;
+  T get_src_for_dst (T dst) const;
+  void dump_to_pp (pretty_printer *pp) const;
+  void dump () const;
+  std::string to_str () const;
+  void update (T *) const;
+
+ private:
+  auto_vec<T> m_src_to_dst;
+  auto_vec<T> m_dst_to_src;
+};
+
+typedef id_map<svalue_id> svalue_id_map;
+typedef id_map<region_id> region_id_map;
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class id_map.  */
+
+/* id_map's ctor, which populates the map with dummy null values.  */
+
+template <typename T>
+inline id_map<T>::id_map (int num_svalues)
+: m_src_to_dst (num_svalues),
+  m_dst_to_src (num_svalues)
+{
+  for (int i = 0; i < num_svalues; i++)
+    {
+      m_src_to_dst.quick_push (T::null ());
+      m_dst_to_src.quick_push (T::null ());
+    }
+}
+
+/* Record that SRC is to be mapped to DST.  */
+
+template <typename T>
+inline void
+id_map<T>::put (T src, T dst)
+{
+  m_src_to_dst[src.as_int ()] = dst;
+  m_dst_to_src[dst.as_int ()] = src;
+}
+
+/* Get the new value for SRC within the map.  */
+
+template <typename T>
+inline T
+id_map<T>::get_dst_for_src (T src) const
+{
+  if (src.null_p ())
+    return src;
+  return m_src_to_dst[src.as_int ()];
+}
+
+/* Given DST, a new value, determine which old value will be mapped to it
+   (the inverse of the map).  */
+
+template <typename T>
+inline T
+id_map<T>::get_src_for_dst (T dst) const
+{
+  if (dst.null_p ())
+    return dst;
+  return m_dst_to_src[dst.as_int ()];
+}
+
+/* Dump this id_map to PP.  */
+
+template <typename T>
+inline void
+id_map<T>::dump_to_pp (pretty_printer *pp) const
+{
+  pp_string (pp, "src to dst: {");
+  unsigned i;
+  T *dst;
+  FOR_EACH_VEC_ELT (m_src_to_dst, i, dst)
+    {
+      if (i > 0)
+	pp_string (pp, ", ");
+      T src (T::from_int (i));
+      src.print (pp);
+      pp_string (pp, " -> ");
+      dst->print (pp);
+    }
+  pp_string (pp, "}");
+  pp_newline (pp);
+
+  pp_string (pp, "dst to src: {");
+  T *src;
+  FOR_EACH_VEC_ELT (m_dst_to_src, i, src)
+    {
+      if (i > 0)
+	pp_string (pp, ", ");
+      T dst (T::from_int (i));
+      dst.print (pp);
+      pp_string (pp, " <- ");
+      src->print (pp);
+    }
+  pp_string (pp, "}");
+  pp_newline (pp);
+}
+
+/* Dump this id_map to stderr.  */
+
+template <typename T>
+DEBUG_FUNCTION inline void
+id_map<T>::dump () const
+{
+  pretty_printer pp;
+  pp.buffer->stream = stderr;
+  dump_to_pp (&pp);
+  pp_flush (&pp);
+}
+
+/* Make a std::string based on the dump of this id_map.  */
+
+template <typename T>
+inline std::string
+id_map<T>::to_str () const
+{
+  pretty_printer pp;
+  dump_to_pp (&pp);
+  return pp_formatted_text (&pp);
+}
+
+/* Update *ID from the old value to its new value in this map.  */
+
+template <typename T>
+inline void
+id_map<T>::update (T *id) const
+{
+  *id = get_dst_for_src (*id);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Variant of the above, which only stores things in one direction.
+   (e.g. for merging, when the number of destination regions is not
+   the same of the src regions, and can grow).  */
+
+template <typename T>
+class one_way_id_map
+{
+ public:
+  one_way_id_map (int num_ids);
+  void put (T src, T dst);
+  T get_dst_for_src (T src) const;
+  void dump_to_pp (pretty_printer *pp) const;
+  void dump () const;
+  std::string to_str () const;
+  void update (T *) const;
+
+ private:
+  auto_vec<T> m_src_to_dst;
+};
+
+typedef one_way_id_map<svalue_id> one_way_svalue_id_map;
+typedef one_way_id_map<region_id> one_way_region_id_map;
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class one_way_id_map.  */
+
+/* one_way_id_map's ctor, which populates the map with dummy null values.  */
+
+template <typename T>
+inline one_way_id_map<T>::one_way_id_map (int num_svalues)
+: m_src_to_dst (num_svalues)
+{
+  for (int i = 0; i < num_svalues; i++)
+    m_src_to_dst.quick_push (T::null ());
+}
+
+/* Record that SRC is to be mapped to DST.  */
+
+template <typename T>
+inline void
+one_way_id_map<T>::put (T src, T dst)
+{
+  m_src_to_dst[src.as_int ()] = dst;
+}
+
+/* Get the new value for SRC within the map.  */
+
+template <typename T>
+inline T
+one_way_id_map<T>::get_dst_for_src (T src) const
+{
+  if (src.null_p ())
+    return src;
+  return m_src_to_dst[src.as_int ()];
+}
+
+/* Dump this map to PP.  */
+
+template <typename T>
+inline void
+one_way_id_map<T>::dump_to_pp (pretty_printer *pp) const
+{
+  pp_string (pp, "src to dst: {");
+  unsigned i;
+  T *dst;
+  FOR_EACH_VEC_ELT (m_src_to_dst, i, dst)
+    {
+      if (i > 0)
+	pp_string (pp, ", ");
+      T src (T::from_int (i));
+      src.print (pp);
+      pp_string (pp, " -> ");
+      dst->print (pp);
+    }
+  pp_string (pp, "}");
+  pp_newline (pp);
+}
+
+/* Dump this map to stderr.  */
+
+template <typename T>
+DEBUG_FUNCTION inline void
+one_way_id_map<T>::dump () const
+{
+  pretty_printer pp;
+  pp.buffer->stream = stderr;
+  dump_to_pp (&pp);
+  pp_flush (&pp);
+}
+
+/* Generate a std::string describing this map.  */
+
+template <typename T>
+inline std::string
+one_way_id_map<T>::to_str () const
+{
+  pretty_printer pp;
+  dump_to_pp (&pp);
+  return pp_formatted_text (&pp);
+}
+
+/* Update *ID from the old value to its new value in this map.  */
+
+template <typename T>
+inline void
+one_way_id_map<T>::update (T *id) const
+{
+  *id = get_dst_for_src (*id);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* A set of IDs within a region_model (either svalue_id or region_id).  */
+
+template <typename T>
+class id_set
+{
+public:
+  id_set (const region_model *model);
+
+  void add_region (T id)
+  {
+    if (!id.null_p ())
+      bitmap_set_bit (m_bitmap, id.as_int ());
+  }
+
+  bool region_p (T id) const
+  {
+    gcc_assert (!id.null_p ());
+    return bitmap_bit_p (const_cast <auto_sbitmap &> (m_bitmap),
+			 id.as_int ());
+  }
+
+  unsigned int num_regions ()
+  {
+    return bitmap_count_bits (m_bitmap);
+  }
+
+private:
+  auto_sbitmap m_bitmap;
+};
+
+typedef id_set<region_id> region_id_set;
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Various operations delete information from a region_model.
+
+   This struct tracks how many of each kind of entity were purged (e.g.
+   for selftests, and for debugging).  */
+
+struct purge_stats
+{
+  purge_stats ()
+  : m_num_svalues (0),
+    m_num_regions (0),
+    m_num_equiv_classes (0),
+    m_num_constraints (0),
+    m_num_client_items (0)
+  {}
+
+  int m_num_svalues;
+  int m_num_regions;
+  int m_num_equiv_classes;
+  int m_num_constraints;
+  int m_num_client_items;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* An enum for discriminating between the different concrete subclasses
+   of svalue.  */
+
+enum svalue_kind
+{
+  SK_REGION,
+  SK_CONSTANT,
+  SK_POISONED,
+  SK_UNKNOWN
+};
+
+/* An abstract base class representing a value held by a region of memory.  */
+
+class svalue
+{
+public:
+  virtual ~svalue () {}
+
+  bool operator== (const svalue &other) const;
+  bool operator!= (const svalue &other) const { return !(*this == other); }
+
+  virtual svalue *clone () const = 0;
+
+  tree get_type () const { return m_type; }
+
+  virtual enum svalue_kind get_kind () const = 0;
+
+  virtual hashval_t hash () const = 0;
+
+  void print (const region_model &model,
+	      svalue_id this_sid,
+	      pretty_printer *pp) const;
+
+  virtual void dump_dot_to_pp (const region_model &model,
+			       svalue_id this_sid,
+			       pretty_printer *pp) const;
+
+  std::string to_str () const;
+
+  virtual region_svalue *dyn_cast_region_svalue () { return NULL; }
+  virtual constant_svalue *dyn_cast_constant_svalue () { return NULL; }
+  virtual poisoned_svalue *dyn_cast_poisoned_svalue () { return NULL; }
+  virtual unknown_svalue *dyn_cast_unknown_svalue () { return NULL; }
+
+  virtual void remap_region_ids (const region_id_map &map);
+
+  virtual void walk_for_canonicalization (canonicalization *c) const;
+
+ protected:
+  svalue (tree type) : m_type (type) {}
+
+ private:
+  virtual void print_details (const region_model &model,
+			      svalue_id this_sid,
+			      pretty_printer *pp) const = 0;
+
+  // FIXME: hash this:
+  tree m_type;
+};
+
+/* Concrete subclass of svalue representing a pointer value that points to
+   a known region  */
+
+class region_svalue : public svalue
+{
+public:
+  region_svalue (tree type, region_id rid) : svalue (type), m_rid (rid)
+  {
+    /* Should we support NULL ptrs here?  */
+    gcc_assert (!rid.null_p ());
+  }
+
+  bool compare_fields (const region_svalue &other) const;
+
+  svalue *clone () const FINAL OVERRIDE
+  { return new region_svalue (get_type (), m_rid); }
+
+  enum svalue_kind get_kind () const FINAL OVERRIDE { return SK_REGION; }
+
+  hashval_t hash () const FINAL OVERRIDE;
+
+  void dump_dot_to_pp (const region_model &model,
+		       svalue_id this_sid,
+		       pretty_printer *pp) const
+    FINAL OVERRIDE;
+
+  region_svalue *dyn_cast_region_svalue () FINAL OVERRIDE { return this; }
+
+  region_id get_pointee () const { return m_rid; }
+
+  void remap_region_ids (const region_id_map &map) FINAL OVERRIDE;
+
+  static bool can_merge_values_p (const region_svalue &region_sval_a,
+				  const region_svalue &region_sval_b,
+				  svalue_id *merged_sid,
+				  tree type,
+				  model_merger *merger);
+
+  void walk_for_canonicalization (canonicalization *c) const FINAL OVERRIDE;
+
+  static tristate eval_condition (region_svalue *lhs_ptr,
+				  enum tree_code op,
+				  region_svalue *rhs_ptr);
+
+ private:
+  void print_details (const region_model &model,
+		      svalue_id this_sid,
+		      pretty_printer *pp) const
+    FINAL OVERRIDE;
+
+  region_id m_rid;
+};
+
+/* Concrete subclass of svalue representing a specific constant value
+   (or a FUNCTION_DECL).  */
+
+class constant_svalue : public svalue
+{
+public:
+  constant_svalue (tree cst_expr)
+  : svalue (TREE_TYPE (cst_expr)), m_cst_expr (cst_expr)
+  {
+    gcc_assert (cst_expr);
+    gcc_assert (TREE_CODE (cst_expr) == FUNCTION_DECL
+		|| CONSTANT_CLASS_P (cst_expr));
+  }
+
+  bool compare_fields (const constant_svalue &other) const;
+
+  svalue *clone () const FINAL OVERRIDE
+  { return new constant_svalue (m_cst_expr); }
+
+  enum svalue_kind get_kind () const FINAL OVERRIDE { return SK_CONSTANT; }
+
+  hashval_t hash () const FINAL OVERRIDE;
+
+  constant_svalue *dyn_cast_constant_svalue () FINAL OVERRIDE { return this; }
+
+  tree get_constant () const { return m_cst_expr; }
+
+  static bool can_merge_values_p (const constant_svalue &cst_sval_a,
+				  svalue_id sid_a,
+				  const constant_svalue &cst_sval_b,
+				  svalue_id sid_b,
+				  svalue_id *merged_sid,
+				  model_merger *merger);
+
+  static tristate eval_condition (constant_svalue *lhs,
+				  enum tree_code op,
+				  constant_svalue *rhs);
+
+ private:
+  void print_details (const region_model &model,
+		      svalue_id this_sid,
+		      pretty_printer *pp) const
+    FINAL OVERRIDE;
+
+  tree m_cst_expr;
+};
+
+/* Concrete subclass of svalue representing a unique but unknown value.
+   Comparisons of variables that share the same unknown value are known
+   to be equal, even if we don't know what the value is.  */
+
+class unknown_svalue : public svalue
+{
+public:
+  unknown_svalue (tree type)
+  : svalue (type)
+  {}
+
+  bool compare_fields (const unknown_svalue &other) const;
+
+  svalue *clone () const FINAL OVERRIDE
+  { return new unknown_svalue (get_type ()); }
+
+  enum svalue_kind get_kind () const FINAL OVERRIDE { return SK_UNKNOWN; }
+
+  hashval_t hash () const FINAL OVERRIDE;
+
+  unknown_svalue *dyn_cast_unknown_svalue () FINAL OVERRIDE { return this; }
+
+ private:
+  void print_details (const region_model &model,
+		      svalue_id this_sid,
+		      pretty_printer *pp) const
+    FINAL OVERRIDE;
+};
+
+/* An enum describing a particular kind of "poisoned" value.  */
+
+enum poison_kind
+{
+  /* For use to describe uninitialized memory.  */
+  POISON_KIND_UNINIT,
+
+  /* For use to describe freed memory.  */
+  POISON_KIND_FREED,
+
+  /* For use on pointers to regions within popped stack frames.  */
+  POISON_KIND_POPPED_STACK
+};
+
+extern const char *poison_kind_to_str (enum poison_kind);
+
+/* Concrete subclass of svalue representing a value that should not
+   be used (e.g. uninitialized memory, freed memory).  */
+
+class poisoned_svalue : public svalue
+{
+public:
+  poisoned_svalue (enum poison_kind kind) : svalue (NULL), m_kind (kind) {}
+
+  bool compare_fields (const poisoned_svalue &other) const;
+
+  svalue *clone () const FINAL OVERRIDE
+  { return new poisoned_svalue (m_kind); }
+
+  enum svalue_kind get_kind () const FINAL OVERRIDE { return SK_POISONED; }
+
+  hashval_t hash () const FINAL OVERRIDE;
+
+  poisoned_svalue *dyn_cast_poisoned_svalue () FINAL OVERRIDE { return this; }
+
+  enum poison_kind get_poison_kind () const { return m_kind; }
+
+ private:
+  void print_details (const region_model &model,
+		      svalue_id this_sid,
+		      pretty_printer *pp) const
+    FINAL OVERRIDE;
+
+  enum poison_kind m_kind;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* An enum for discriminating between the different concrete subclasses
+   of region.  */
+
+enum region_kind
+{
+  RK_MAP,
+  RK_FRAME,
+  RK_STACK,
+  RK_HEAP,
+  RK_ROOT,
+  RK_SYMBOLIC
+};
+
+extern const char *region_kind_to_str (enum region_kind);
+
+/* Abstract base class representing a chunk of memory.
+
+   Regions form a tree-like hierarchy, with a root region at the base,
+   with memory space regions within it, representing the stack and
+   globals, with frames within the stack, and regions for variables
+   within the frames and the "globals" region.  Regions for structs
+   can have subregions for fields.
+
+   A region can optionally have a value, or inherit its value from
+   the first ancestor with a value.  For example, the stack region
+   has a "uninitialized" poison value which is inherited by all
+   descendent regions that don't themselves have a value.  */
+
+class region
+{
+public:
+  virtual ~region () {}
+
+  bool operator== (const region &other) const;
+  bool operator!= (const region &other) const { return !(*this == other); }
+
+  virtual region *clone () const = 0;
+
+  virtual enum region_kind get_kind () const = 0;
+
+  region_id get_parent () const { return m_parent_rid; }
+
+  void set_value (const region_model &model, svalue_id rhs_sid);
+  svalue_id get_value (region_model &model, bool non_null,
+		       region_model_context *ctxt);
+  svalue_id get_value_direct () const { return m_sval_id; }
+
+  tree get_type () const { return m_type; }
+
+  hashval_t hash () const;
+
+  void print (const region_model &model,
+	      region_id this_rid,
+	      pretty_printer *pp) const;
+
+  virtual void dump_dot_to_pp (const region_model &model,
+			       region_id this_rid,
+			       pretty_printer *pp) const;
+
+  void dump_to_pp (const region_model &model,
+		   region_id this_rid,
+		   pretty_printer *pp,
+		   const char *prefix,
+		   bool is_last_child) const;
+  virtual void dump_child_label (const region_model &model,
+				 region_id this_rid,
+				 region_id child_rid,
+				 pretty_printer *pp) const;
+
+  std::string to_str () const;
+
+  void remap_svalue_ids (const svalue_id_map &map);
+  virtual void remap_region_ids (const region_id_map &map);
+
+  virtual void walk_for_canonicalization (canonicalization *c) const = 0;
+
+ protected:
+  region (region_id parent_rid, svalue_id sval_id, tree type)
+  : m_parent_rid (parent_rid), m_sval_id (sval_id), m_type (type)
+  {
+    gcc_assert (type == NULL_TREE || TYPE_P (type));
+  }
+
+  region (const region &other)
+  : m_parent_rid (other.m_parent_rid), m_sval_id (other.m_sval_id),
+    m_type (other.m_type)
+  {
+  }
+
+  virtual void add_to_hash (inchash::hash &hstate) const;
+  virtual void print_fields (const region_model &model,
+			     region_id this_rid,
+			     pretty_printer *pp) const;
+
+ private:
+  region_id m_parent_rid;
+  svalue_id m_sval_id;
+  tree m_type;
+};
+
+/* A region that has children identified by tree keys.
+   For example a stack frame has subregions per local, and a region
+   for a struct has subregions per field.  */
+
+class map_region : public region
+{
+public:
+  typedef ordered_hash_map<tree, region_id> map_t;
+  typedef map_t::iterator iterator_t;
+
+  map_region (region_id parent_rid, tree type)
+  : region (parent_rid, svalue_id::null (), type)
+  {}
+  map_region (const map_region &other);
+
+  bool compare_fields (const map_region &other) const;
+
+  region *clone () const OVERRIDE;
+
+  enum region_kind get_kind () const OVERRIDE { return RK_MAP; }
+
+  void dump_dot_to_pp (const region_model &model,
+		       region_id this_rid,
+		       pretty_printer *pp) const
+    FINAL OVERRIDE;
+
+  void dump_child_label (const region_model &model,
+			 region_id this_rid,
+			 region_id child_rid,
+			 pretty_printer *pp) const
+    FINAL OVERRIDE;
+
+  region_id get_or_create (region_model *model,
+			   region_id this_rid,
+			   tree expr, tree type);
+  void unbind (tree expr);
+
+  void remap_region_ids (const region_id_map &map) FINAL OVERRIDE;
+
+  tree get_tree_for_child_region (region_id child_rid) const;
+
+  static bool can_merge_p (const map_region *map_region_a,
+			   const map_region *map_region_b,
+			   map_region *merged_map_region,
+			   region_id merged_rid,
+			   model_merger *merger);
+
+  void walk_for_canonicalization (canonicalization *c) const FINAL OVERRIDE;
+
+  svalue_id get_value_by_name (tree identifier,
+			       const region_model &model) const;
+
+  iterator_t begin () { return m_map.begin (); }
+  iterator_t end () { return m_map.end (); }
+
+ protected:
+  void add_to_hash (inchash::hash &hstate) const OVERRIDE;
+  void print_fields (const region_model &model,
+		     region_id this_rid,
+		     pretty_printer *pp) const
+    OVERRIDE;
+
+ private:
+  /* Mapping from tree to child region.  */
+  map_t m_map;
+};
+
+/* A region representing a function frame on the stack, to contain
+   the locals.  */
+
+class frame_region : public map_region
+{
+public:
+  frame_region (region_id parent_rid, function *fun, int depth)
+  : map_region (parent_rid, NULL_TREE), m_fun (fun), m_depth (depth)
+  {}
+
+  frame_region (const frame_region &other)
+  : map_region (other), m_fun (other.m_fun), m_depth (other.m_depth)
+  {
+  }
+
+  bool compare_fields (const frame_region &other) const;
+
+  region *clone () const FINAL OVERRIDE;
+
+  enum region_kind get_kind () const FINAL OVERRIDE { return RK_FRAME; }
+
+  function *get_function () const { return m_fun; }
+
+  int get_depth () const { return m_depth; }
+
+ private:
+  void add_to_hash (inchash::hash &hstate) const FINAL OVERRIDE;
+  void print_fields (const region_model &model,
+		     region_id this_rid,
+		     pretty_printer *pp) const
+    FINAL OVERRIDE;
+
+  function *m_fun;
+  int m_depth;
+};
+
+/* A region containing all stack frames, and implicitly providing
+   a POISON_KIND_UNINIT value to all child regions by default.  */
+
+class stack_region : public region
+{
+public:
+  stack_region (region_id parent_rid, svalue_id sval_id)
+  : region (parent_rid, sval_id, NULL_TREE)
+  {}
+
+  stack_region (const stack_region &other);
+
+  bool compare_fields (const stack_region &other) const;
+
+  region *clone () const FINAL OVERRIDE;
+
+  enum region_kind get_kind () const FINAL OVERRIDE { return RK_STACK; }
+
+  void dump_child_label (const region_model &model,
+			 region_id this_rid,
+			 region_id child_rid,
+			 pretty_printer *pp) const
+    FINAL OVERRIDE;
+
+  void push_frame (region_id frame_rid);
+  region_id get_current_frame_id () const;
+  svalue_id pop_frame (region_model *model, purge_stats *stats,
+		       region_model_context *ctxt);
+
+  void remap_region_ids (const region_id_map &map) FINAL OVERRIDE;
+
+  unsigned get_num_frames () const { return m_frame_rids.length (); }
+  region_id get_frame_rid (unsigned i) const { return m_frame_rids[i]; }
+
+  static bool can_merge_p (const stack_region *stack_region_a,
+			   const stack_region *stack_region_b,
+			   model_merger *merger);
+
+  void walk_for_canonicalization (canonicalization *c) const FINAL OVERRIDE;
+
+  svalue_id get_value_by_name (tree identifier,
+			       const region_model &model) const;
+
+ private:
+  void add_to_hash (inchash::hash &hstate) const FINAL OVERRIDE;
+  void print_fields (const region_model &model,
+		     region_id this_rid,
+		     pretty_printer *pp) const
+    FINAL OVERRIDE;
+
+  auto_vec<region_id> m_frame_rids;
+};
+
+/* FIXME.  */
+
+class heap_region : public region
+{
+public:
+  heap_region (region_id parent_rid, svalue_id sval_id)
+  : region (parent_rid, sval_id, NULL_TREE)
+  {}
+  heap_region (const heap_region &other);
+
+  bool compare_fields (const heap_region &other) const;
+
+  region *clone () const OVERRIDE;
+
+  enum region_kind get_kind () const OVERRIDE { return RK_HEAP; }
+
+  static bool can_merge_p (const heap_region *heap_a, region_id heap_a_rid,
+			   const heap_region *heap_b, region_id heap_b_rid,
+			   heap_region *merged_heap, region_id merged_heap_rid,
+			   model_merger *merger);
+
+  void walk_for_canonicalization (canonicalization *c) const FINAL OVERRIDE;
+
+};
+
+/* The root region, containing all regions (either directly, or as
+   descendents).
+   Unique within a region_model.  */
+
+class root_region : public region
+{
+public:
+  root_region ();
+  root_region (const root_region &other);
+
+  bool compare_fields (const root_region &other) const;
+
+  region *clone () const FINAL OVERRIDE;
+
+  enum region_kind get_kind () const FINAL OVERRIDE { return RK_ROOT; }
+
+  void dump_child_label (const region_model &model,
+			 region_id this_rid,
+			 region_id child_rid,
+			 pretty_printer *pp) const
+    FINAL OVERRIDE;
+
+  region_id push_frame (region_model *model, function *fun,
+			vec<svalue_id> *arg_sids);
+  region_id get_current_frame_id (const region_model &model) const;
+  svalue_id pop_frame (region_model *model, purge_stats *stats,
+		       region_model_context *ctxt);
+
+  region_id ensure_stack_region (region_model *model);
+  region_id get_stack_region_id () const { return m_stack_rid; }
+  stack_region *get_stack_region (const region_model *model) const;
+
+  region_id ensure_globals_region (region_model *model);
+  region_id get_globals_region_id () const { return m_globals_rid; }
+  map_region *get_globals_region (const region_model *model) const;
+
+  region_id ensure_heap_region (region_model *model);
+  heap_region *get_heap_region (const region_model *model) const;
+
+  void remap_region_ids (const region_id_map &map) FINAL OVERRIDE;
+
+  static bool can_merge_p (const root_region *root_region_a,
+			   const root_region *root_region_b,
+			   root_region *merged_root_region,
+			   model_merger *merger);
+
+  void walk_for_canonicalization (canonicalization *c) const FINAL OVERRIDE;
+
+  svalue_id get_value_by_name (tree identifier,
+			       const region_model &model) const;
+
+private:
+  void add_to_hash (inchash::hash &hstate) const FINAL OVERRIDE;
+  void print_fields (const region_model &model,
+		     region_id this_rid,
+		     pretty_printer *pp) const
+    FINAL OVERRIDE;
+
+  region_id m_stack_rid;
+  region_id m_globals_rid;
+  region_id m_heap_rid;
+};
+
+/* FIXME.  */
+
+class symbolic_region : public map_region
+{
+public:
+  symbolic_region (region_id parent_rid)
+  : map_region (parent_rid, NULL_TREE)
+  {}
+  symbolic_region (const symbolic_region &other);
+
+  bool compare_fields (const symbolic_region &other) const;
+
+  region *clone () const OVERRIDE;
+
+  enum region_kind get_kind () const OVERRIDE { return RK_SYMBOLIC; }
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* A region_model encapsulates a representation of the state of memory, with
+   a tree of regions, along with their associated values.
+   The representation is graph-like because values can be pointers to
+   regions.
+   It also stores a constraint_manager, capturing relationships between
+   the values.  */
+
+class region_model
+{
+ public:
+  region_model ();
+  region_model (const region_model &other);
+  ~region_model ();
+
+#if 0//__cplusplus >= 201103
+  region_model (region_model &&other);
+#endif
+
+  region_model &operator= (const region_model &other);
+
+  bool operator== (const region_model &other) const;
+  bool operator!= (const region_model &other) const
+  {
+    return !(*this == other);
+  }
+
+  hashval_t hash () const;
+
+  void print (pretty_printer *pp) const;
+  std::string to_str () const;
+
+  void print_svalue (svalue_id sid, pretty_printer *pp) const;
+
+  void dump_dot_to_pp (pretty_printer *pp) const;
+  void dump_dot_to_file (FILE *fp) const;
+  void dump_dot (const char *path) const;
+
+  void dump_to_pp (pretty_printer *pp) const;
+  void dump (FILE *fp) const;
+  void dump () const;
+
+  void validate () const;
+
+  void canonicalize (region_model_context *ctxt);
+
+  void on_assignment (const gassign *stmt, region_model_context *ctxt);
+  void on_call (const gcall *stmt, region_model_context *ctxt);
+  void on_return (const greturn *stmt, region_model_context *ctxt);
+
+  void update_for_phis (const supernode *snode,
+			const cfg_superedge *last_cfg_superedge,
+			region_model_context *ctxt);
+
+  void handle_phi (tree lhs, tree rhs, bool is_back_edge,
+		   region_model_context *ctxt);
+
+  bool maybe_update_for_edge (const superedge &edge,
+			      const gimple *last_stmt,
+			      region_model_context *ctxt);
+
+  region_id get_root_rid () const { return m_root_rid; }
+  root_region *get_root_region () const;
+
+  region_id get_stack_region_id () const;
+  region_id push_frame (function *fun, vec<svalue_id> *arg_sids);
+  region_id get_current_frame_id () const;
+  function * get_current_function () const;
+  svalue_id pop_frame (purge_stats *stats, region_model_context *ctxt);
+  int get_stack_depth () const;
+
+  region_id get_globals_region_id () const;
+
+  svalue_id add_svalue (svalue *sval);
+  void replace_svalue (svalue_id sid, svalue *new_sval);
+
+  region_id add_region (region *r);
+
+  svalue *get_svalue (svalue_id sval_id) const;
+  region *get_region (region_id rid) const;
+
+  template <typename Subclass>
+  Subclass *get_region (region_id rid) const
+  {
+    return (Subclass *)get_region (rid); // TODO: checking
+  }
+
+#if 0
+  void assign_to_lvalue (lvalue *lval,
+			 rvalue *rval);
+#endif
+  region_id get_lvalue (path_var pv, region_model_context *ctxt);
+  region_id get_lvalue (tree expr, region_model_context *ctxt);
+  svalue_id get_rvalue (path_var pv, region_model_context *ctxt);
+  svalue_id get_rvalue (tree expr, region_model_context *ctxt);
+
+  svalue_id get_or_create_ptr_svalue (tree ptr_type, region_id id);
+  svalue_id get_or_create_constant_svalue (tree cst_expr);
+  svalue_id get_svalue_for_fndecl (tree fndecl);
+
+  region_id deref_rvalue (tree ptr, region_model_context *ctxt);
+
+  void set_value (region_id lhs_rid, svalue_id rhs_sid);
+  svalue_id set_to_new_unknown_value (region_id dst_rid, tree type);
+
+  tristate eval_condition (svalue_id lhs,
+			   enum tree_code op,
+			   svalue_id rhs) const;
+  tristate eval_condition (tree lhs,
+			   enum tree_code op,
+			   tree rhs,
+			   region_model_context *ctxt);
+  bool add_constraint (tree lhs, enum tree_code op, tree rhs,
+		       region_model_context *ctxt);
+
+  tree maybe_get_constant (svalue_id sid);
+
+  region_id add_new_malloc_region ();
+
+  tree get_representative_tree (svalue_id sid) const;
+  path_var get_representative_path_var (region_id rid) const;
+  void get_path_vars_for_svalue (svalue_id sid, vec<path_var> *out) const;
+
+  void purge_unused_svalues (purge_stats *out,
+			     region_model_context *ctxt,
+			     svalue_id *known_used_sid = NULL);
+  void remap_svalue_ids (const svalue_id_map &map);
+  void remap_region_ids (const region_id_map &map);
+
+  void purge_regions (const region_id_set &set,
+		      purge_stats *stats,
+		      logger *logger);
+
+  unsigned get_num_svalues () const { return m_svalues.length (); }
+  unsigned get_num_regions () const { return m_regions.length (); }
+
+  /* For selftests.  */
+  constraint_manager *get_constraints ()
+  {
+    return m_constraints;
+  }
+
+  void get_descendents (region_id rid, region_id_set *out) const;
+
+  void delete_region_and_descendents (region_id rid,
+				      enum poison_kind pkind,
+				      purge_stats *stats,
+				      logger *logger);
+
+  bool can_merge_with_p (const region_model &other_model,
+			 region_model *out_model,
+			 svalue_id_merger_mapping *out) const;
+  bool can_merge_with_p (const region_model &other_model,
+			 region_model *out_model) const;
+
+  svalue_id get_value_by_name (const char *name) const;
+
+ private:
+  void update_for_call_superedge (const call_superedge &call_edge,
+				  region_model_context *ctxt);
+  void update_for_return_superedge (const return_superedge &return_edge,
+				    region_model_context *ctxt);
+  bool apply_constraints_for_gcond (const superedge &edge,
+				    const gcond *cond_stmt,
+				    region_model_context *ctxt);
+  bool apply_constraints_for_gswitch (const superedge &edge,
+				      const gswitch *switch_stmt,
+				      region_model_context *ctxt);
+
+  void poison_any_pointers_to_bad_regions (const region_id_set &bad_regions,
+					   enum poison_kind pkind);
+
+  auto_delete_vec<svalue> m_svalues;
+  auto_delete_vec<region> m_regions;
+  region_id m_root_rid;
+  constraint_manager *m_constraints; // FIXME: embed, rather than dynalloc?
+};
+
+/* Some region_model activity could lead to warnings (e.g. attempts to use an
+   uninitialized value).  This abstract base class encapsulates an interface
+   for the region model to use when emitting such warnings.
+
+   It also provides an interface for being notified about svalue_ids being
+   remapped, and being deleted.
+
+   Having this as an abstract base class allows us to support the various
+   operations needed by program_state in the analyzer within region_model,
+   whilst keeping them somewhat modularized.  */
+
+class region_model_context
+{
+ public:
+  virtual void warn (pending_diagnostic *d) = 0;
+
+  /* Hook for clients that store svalue_id instances, so that they
+     can remap their IDs when the underlying region_model renumbers
+     the IDs.  */
+  virtual void remap_svalue_ids (const svalue_id_map &map) = 0;
+
+  /* Return true if if's OK to purge SID when simplifying state.
+     Subclasses can return false for values that have sm state,
+     to avoid generating "leak" false positives.  */
+  virtual bool can_purge_p (svalue_id sid) = 0;
+
+  /* Hook for clients to be notified when a range of SIDs have
+     been purged, so that they can purge state relating to those
+     values (and potentially emit warnings about leaks).
+     All SIDs from FIRST_PURGED_SID numerically upwards are being
+     purged.
+     The return values is a count of how many items of data the client
+     has purged (potentially for use in selftests).
+     MAP has already been applied to the IDs, but is provided in case
+     the client needs to figure out the old IDs.  */
+  virtual int on_svalue_purge (svalue_id first_purged_sid,
+			       const svalue_id_map &map) = 0;
+
+  virtual logger *get_logger () = 0;
+
+  virtual void on_state_leak (const state_machine &sm,
+			      int sm_idx,
+			      svalue_id sid,
+			      const svalue_id_map &map,
+			      state_machine::state_t state) = 0;
+
+  /* Hook for clients to be notified when CHILD_SID is created
+     from PARENT_SID, when "inheriting" a value for a region from a
+     parent region.
+     This exists so that state machines that inherit state can
+     propagate the state from parent to child.  */
+  virtual void on_inherited_svalue (svalue_id parent_sid,
+				    svalue_id child_sid) = 0;
+
+};
+
+/* A bundle of data for use when attempting to merge two region_model
+   instances to make a third.  */
+
+struct model_merger
+{
+  model_merger (const region_model *model_a,
+		const region_model *model_b,
+		region_model *merged_model,
+		svalue_id_merger_mapping *sid_mapping)
+  : m_model_a (model_a), m_model_b (model_b),
+    m_merged_model (merged_model),
+    m_map_regions_from_a_to_m (model_a->get_num_regions ()),
+    m_map_regions_from_b_to_m (model_b->get_num_regions ()),
+    m_sid_mapping (sid_mapping)
+  {
+    gcc_assert (sid_mapping);
+  }
+
+  void dump_to_pp (pretty_printer *pp) const;
+  void dump (FILE *fp) const;
+  void dump () const;
+
+  template <typename Subclass>
+  Subclass *get_region_a (region_id rid_a) const
+  {
+    return m_model_a->get_region <Subclass> (rid_a);
+  }
+
+  template <typename Subclass>
+  Subclass *get_region_b (region_id rid_b) const
+  {
+    return m_model_b->get_region <Subclass> (rid_b);
+  }
+
+  bool can_merge_values_p (svalue_id sid_a,
+			   svalue_id sid_b,
+			   svalue_id *merged_sid);
+
+  void record_regions (region_id a_rid,
+		       region_id b_rid,
+		       region_id merged_rid);
+
+  void record_svalues (svalue_id a_sid,
+		       svalue_id b_sid,
+		       svalue_id merged_sid);
+
+  const region_model *m_model_a;
+  const region_model *m_model_b;
+  region_model *m_merged_model;
+
+  one_way_region_id_map m_map_regions_from_a_to_m;
+  one_way_region_id_map m_map_regions_from_b_to_m;
+  svalue_id_merger_mapping *m_sid_mapping;
+};
+
+/* A bundle of data that can be optionally generated during merger of two
+   region_models that describes how svalue_ids in each of the two inputs
+   are mapped to svalue_ids in the merged output.
+
+   For use when merging sm-states within program_state.  */
+
+struct svalue_id_merger_mapping
+{
+  svalue_id_merger_mapping (const region_model &a,
+			    const region_model &b);
+
+  void dump_to_pp (pretty_printer *pp) const;
+  void dump (FILE *fp) const;
+  void dump () const;
+
+  one_way_svalue_id_map m_map_from_a_to_m;
+  one_way_svalue_id_map m_map_from_b_to_m;
+};
+
+/* A bundle of data used when canonicalizing a region_model so that the
+   order of regions and svalues is in a predictable order (thus increasing
+   the chance of two region_models being equal).
+
+   This object is used to keep track of a recursive traversal across the
+   svalues and regions within the model, made in a deterministic order,
+   assigning new ids the first time each region or svalue is
+   encountered.  */
+
+struct canonicalization
+{
+  canonicalization (const region_model &model);
+  void walk_rid (region_id rid);
+  void walk_sid (svalue_id sid);
+
+  void dump_to_pp (pretty_printer *pp) const;
+  void dump (FILE *fp) const;
+  void dump () const;
+
+  const region_model &m_model;
+  /* Maps from existing IDs to new IDs.  */
+  region_id_map m_rid_map;
+  svalue_id_map m_sid_map;
+  /* The next IDs to hand out.  */
+  int m_next_rid_int;
+  int m_next_sid_int;
+};
+
+namespace inchash
+{
+  extern void add (svalue_id sid, hash &hstate);
+  extern void add (region_id rid, hash &hstate);
+} // namespace inchash
+
+extern void debug (const region_model &rmodel);
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* An implementation of region_model_context for use in selftests, which
+   stores any pending_diagnostic instances passed to it.  */
+
+class test_region_model_context : public region_model_context
+{
+public:
+  void warn (pending_diagnostic *d) FINAL OVERRIDE
+  {
+    m_diagnostics.safe_push (d);
+  }
+
+  void remap_svalue_ids (const svalue_id_map &) FINAL OVERRIDE
+  {
+    /* Empty.  */
+  }
+
+  bool can_purge_p (svalue_id) FINAL OVERRIDE
+  {
+    return true;
+  }
+
+  int on_svalue_purge (svalue_id, const svalue_id_map &) FINAL OVERRIDE
+  {
+    /* Empty.  */
+    return 0;
+  }
+
+  logger *get_logger () FINAL OVERRIDE { return NULL; }
+
+  void on_state_leak (const state_machine &,
+		      int,
+		      svalue_id,
+		      const svalue_id_map &,
+		      state_machine::state_t) FINAL OVERRIDE
+  {
+  }
+
+  void on_inherited_svalue (svalue_id parent_sid ATTRIBUTE_UNUSED,
+			    svalue_id child_sid  ATTRIBUTE_UNUSED)
+    FINAL OVERRIDE
+  {
+  }
+
+  unsigned get_num_diagnostics () const { return m_diagnostics.length (); }
+
+private:
+  /* Implicitly delete any diagnostics in the dtor.  */
+  auto_delete_vec<pending_diagnostic> m_diagnostics;
+};
+
+/* Attempt to add the constraint (LHS OP RHS) to MODEL.
+   Verify that MODEL remains satisfiable.  */
+
+#define ADD_SAT_CONSTRAINT(MODEL, LHS, OP, RHS)	\
+  SELFTEST_BEGIN_STMT					\
+    bool sat = (MODEL).add_constraint (LHS, OP, RHS, NULL);	\
+    ASSERT_TRUE (sat);					\
+  SELFTEST_END_STMT
+
+/* Attempt to add the constraint (LHS OP RHS) to MODEL.
+   Verify that the result is not satisfiable.  */
+
+#define ADD_UNSAT_CONSTRAINT(MODEL, LHS, OP, RHS)	\
+  SELFTEST_BEGIN_STMT					\
+    bool sat = (MODEL).add_constraint (LHS, OP, RHS, NULL);	\
+    ASSERT_FALSE (sat);				\
+  SELFTEST_END_STMT
+
+/* Implementation detail of the ASSERT_CONDITION_* macros.  */
+
+void assert_condition (const location &loc,
+		       region_model &model,
+		       tree lhs, tree_code op, tree rhs,
+		       tristate expected);
+
+/* Assert that REGION_MODEL evaluates the condition "LHS OP RHS"
+   as "true".  */
+
+#define ASSERT_CONDITION_TRUE(REGION_MODEL, LHS, OP, RHS) \
+  SELFTEST_BEGIN_STMT							\
+  assert_condition (SELFTEST_LOCATION, REGION_MODEL, LHS, OP, RHS,	\
+		    tristate (tristate::TS_TRUE));		\
+  SELFTEST_END_STMT
+
+/* Assert that REGION_MODEL evaluates the condition "LHS OP RHS"
+   as "false".  */
+
+#define ASSERT_CONDITION_FALSE(REGION_MODEL, LHS, OP, RHS) \
+  SELFTEST_BEGIN_STMT							\
+  assert_condition (SELFTEST_LOCATION, REGION_MODEL, LHS, OP, RHS,	\
+		    tristate (tristate::TS_FALSE));		\
+  SELFTEST_END_STMT
+
+/* Assert that REGION_MODEL evaluates the condition "LHS OP RHS"
+   as "unknown".  */
+
+#define ASSERT_CONDITION_UNKNOWN(REGION_MODEL, LHS, OP, RHS) \
+  SELFTEST_BEGIN_STMT							\
+  assert_condition (SELFTEST_LOCATION, REGION_MODEL, LHS, OP, RHS,	\
+		    tristate (tristate::TS_UNKNOWN));		\
+  SELFTEST_END_STMT
+
+} /* end of namespace selftest.  */
+
+#endif /* #if CHECKING_P */
+
+#endif /* GCC_ANALYZER_REGION_MODEL_H */
diff --git a/gcc/analyzer/sm-file.cc b/gcc/analyzer/sm-file.cc
new file mode 100644
index 0000000..26e6466
--- /dev/null
+++ b/gcc/analyzer/sm-file.cc
@@ -0,0 +1,349 @@
+/* A state machine for detecting misuses of <stdio.h>'s FILE * API.
+   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/>.  */
+
+/* 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/analyzer.h"
+#include "analyzer/graphviz.h"
+#include "cgraph.h"
+#include "diagnostic-path.h"
+#include "gcc-rich-location.h"
+#include <string>
+#include "analyzer/supergraph.h"
+#include "analyzer/sm.h"
+#include "analyzer/pending-diagnostic.h"
+
+static const bool DEBUG = false;
+//static const bool DEBUG = true;
+
+/* A state machine for detecting misuses of <stdio.h>'s FILE * API.  */
+
+class fileptr_state_machine : public state_machine
+{
+public:
+  fileptr_state_machine (logger *logger);
+
+  bool inherited_state_p () const FINAL OVERRIDE { return false; }
+
+  bool on_stmt (sm_context *sm_ctxt,
+		const supernode *node,
+		const gimple *stmt) const FINAL OVERRIDE;
+
+  void on_edge (sm_context *sm_ctxt,
+		const gimple *last_stmt,
+		const superedge *edge) const FINAL OVERRIDE;
+
+  void on_leak (sm_context *sm_ctxt,
+		const supernode *node,
+		const gimple *stmt,
+		tree var,
+		state_machine::state_t state) const FINAL OVERRIDE;
+  bool can_purge_p (state_t s) const FINAL OVERRIDE;
+
+  state_t m_start;
+  state_t m_unchecked;
+  state_t m_null;
+  state_t m_nonnull;
+  state_t m_closed;
+  state_t m_stop;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+class file_diagnostic : public pending_diagnostic
+{
+public:
+  file_diagnostic (const fileptr_state_machine &sm, tree arg)
+  : m_sm (sm), m_arg (arg)
+  {}
+
+  bool subclass_equal_p (const pending_diagnostic &base_other) const OVERRIDE
+  {
+    return m_arg == ((const file_diagnostic &)base_other).m_arg;
+  }
+
+  label_text describe_state_change (const evdesc::state_change &change)
+    OVERRIDE
+  {
+    if (change.m_old_state == m_sm.m_start
+	&& change.m_new_state == m_sm.m_unchecked)
+      // TODO: verify that it's the fopen stmt, not a copy
+      return label_text::borrow ("opened here");
+    if (change.m_old_state == m_sm.m_unchecked
+	&& change.m_new_state == m_sm.m_nonnull)
+      return formatted_print ("assuming %qE is non-NULL",
+			      change.m_expr);
+    if (change.m_new_state == m_sm.m_null)
+      return formatted_print ("assuming %qE is NULL",
+			      change.m_expr);
+    return label_text ();
+  }
+
+protected:
+  const fileptr_state_machine &m_sm;
+  tree m_arg;
+};
+
+class double_fclose : public file_diagnostic
+{
+public:
+  double_fclose (const fileptr_state_machine &sm, tree arg)
+    : file_diagnostic (sm, arg)
+  {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "double_fclose"; }
+
+  void emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    warning_at (rich_loc, OPT_Wanalyzer_double_fclose,
+		"double %<fclose%> of FILE %qE",
+		m_arg);
+  }
+
+  label_text describe_state_change (const evdesc::state_change &change)
+    OVERRIDE
+  {
+    if (change.m_new_state == m_sm.m_closed)
+      {
+	m_first_fclose_event = change.m_event_id;
+	return formatted_print ("first %qs here", "fclose");
+      }
+    return file_diagnostic::describe_state_change (change);
+  }
+
+  label_text describe_final_event (const evdesc::final_event &) FINAL OVERRIDE
+  {
+    if (m_first_fclose_event.known_p ())
+      return formatted_print ("second %qs here; first %qs was at (%i)",
+			      "fclose", "fclose", m_first_fclose_event.one_based ());
+    return formatted_print ("second %qs here", "fclose");
+  }
+
+private:
+  event_id_t m_first_fclose_event;
+};
+
+class file_leak : public file_diagnostic
+{
+public:
+  file_leak (const fileptr_state_machine &sm, tree arg)
+    : file_diagnostic (sm, arg)
+  {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "file_leak"; }
+
+  void emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    metadata m;
+    /* CWE-775: "Missing Release of File Descriptor or Handle after
+       Effective Lifetime". */
+    m.add_cwe (775);
+    warning_at (rich_loc, m, OPT_Wanalyzer_file_leak,
+		"leak of FILE %qE",
+		m_arg);
+  }
+
+  label_text describe_state_change (const evdesc::state_change &change)
+    FINAL OVERRIDE
+  {
+    if (change.m_new_state == m_sm.m_unchecked)
+      {
+	m_fopen_event = change.m_event_id;
+	return label_text::borrow ("opened here");
+      }
+    return file_diagnostic::describe_state_change (change);
+  }
+
+  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  {
+    if (m_fopen_event.known_p ())
+      return formatted_print ("%qE leaks here; was opened at (%i)",
+			      ev.m_expr, m_fopen_event.one_based ());
+    else
+      return formatted_print ("%qE leaks here", ev.m_expr);
+  }
+
+private:
+  event_id_t m_fopen_event;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+fileptr_state_machine::fileptr_state_machine (logger *logger)
+: state_machine ("file", logger)
+{
+  // TODO: parse all of this from a file:
+  m_start = add_state ("start");
+  m_unchecked = add_state ("unchecked");
+  m_null = add_state ("null");
+  m_nonnull = add_state ("nonnull");
+  m_closed = add_state ("closed");
+  m_stop = add_state ("stop");
+}
+
+bool
+fileptr_state_machine::on_stmt (sm_context *sm_ctxt,
+				const supernode *node,
+				const gimple *stmt) const
+{
+  //tree fndecl = node->m_fun->decl;
+
+  // FIXME: do all of this from data
+  if (const gcall *call = dyn_cast <const gcall *> (stmt))
+    {
+      if (is_named_call_p (call, "fopen", 2))
+	{
+	  if (DEBUG)
+	    inform (stmt->location, "DBG: got fopen");
+	  tree lhs = gimple_call_lhs (call);
+	  if (lhs)
+	    {
+	      lhs = sm_ctxt->get_readable_tree (lhs);
+	      sm_ctxt->on_transition (node, stmt, lhs, m_start, m_unchecked);
+	    }
+	  else
+	    {
+	      /* TODO: report leak.  */
+	    }
+	  return true;
+	}
+
+      if (is_named_call_p (call, "fclose", 1))
+	{
+	  tree arg = gimple_call_arg (call, 0);
+	  if (DEBUG)
+	    inform (stmt->location, "DBG: got fclose");
+	  arg = sm_ctxt->get_readable_tree (arg);
+
+	  sm_ctxt->on_transition (node, stmt, arg, m_start, m_closed);
+
+	  // TODO: is it safe to call fclose (NULL) ?
+	  sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_closed);
+	  sm_ctxt->on_transition (node, stmt, arg, m_null, m_closed);
+
+	  sm_ctxt->on_transition (node, stmt , arg, m_nonnull, m_closed);
+
+	  sm_ctxt->warn_for_state (node, stmt, arg, m_closed,
+				   new double_fclose (*this, arg));
+	  sm_ctxt->on_transition (node, stmt, arg, m_closed, m_stop);
+	  return true;
+	}
+
+      // TODO: operations on closed file
+      // etc
+    }
+  return false;
+}
+
+/* FIXME.  */
+
+void
+fileptr_state_machine::on_edge (sm_context *sm_ctxt,
+				const gimple *last_stmt,
+				const superedge *edge) const
+{
+  if (last_stmt == NULL)
+    return;
+
+  if (const gcond *cond_stmt = dyn_cast <const gcond *> (last_stmt))
+    {
+      tree arg;
+      enum tree_code op;
+      if (is_comparison_against_zero (cond_stmt, &arg, &op))
+	{
+	  // FIXME: has to be a FILE *, specifically
+	  if (TREE_CODE (TREE_TYPE (arg)) != POINTER_TYPE)
+	    return;
+
+	  ::edge cfg_edge = edge->get_any_cfg_edge ();
+	  gcc_assert (cfg_edge != NULL);
+	  gcc_assert (cfg_edge->flags & (EDGE_TRUE_VALUE | EDGE_FALSE_VALUE));
+	  
+	  if (op == NE_EXPR)
+	    {
+	      log ("got 'ARG != 0' match");
+	      sm_ctxt->on_transition (edge->m_src, last_stmt, arg, m_unchecked,
+				      (cfg_edge->flags & EDGE_TRUE_VALUE
+				       ? m_nonnull : m_null));
+	    }
+	  else if (op == EQ_EXPR)
+	    {
+	      log ("got 'ARG == 0' match");
+	      sm_ctxt->on_transition (edge->m_src, last_stmt, arg, m_unchecked,
+				      (cfg_edge->flags & EDGE_TRUE_VALUE
+				       ? m_null : m_nonnull));
+	    }
+	}
+    }
+
+  // TODO: something similar for switch statements?
+}
+
+void
+fileptr_state_machine::on_leak (sm_context *sm_ctxt,
+				const supernode *node,
+				const gimple *stmt,
+				tree var,
+				state_machine::state_t state ATTRIBUTE_UNUSED)
+  const
+{
+  sm_ctxt->warn_for_state (node, stmt, var, m_unchecked,
+			   new file_leak (*this, var));
+  sm_ctxt->warn_for_state (node, stmt, var, m_nonnull,
+			   new file_leak (*this, var));
+}
+
+bool
+fileptr_state_machine::can_purge_p (state_t s) const
+{
+  return s != m_unchecked && s != m_nonnull;
+}
+
+state_machine *
+make_fileptr_state_machine (logger *logger)
+{
+  return new fileptr_state_machine (logger);
+}
diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc
new file mode 100644
index 0000000..a3f0736
--- /dev/null
+++ b/gcc/analyzer/sm-malloc.cc
@@ -0,0 +1,807 @@
+/* A state machine for detecting misuses of the malloc/free API.
+   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/>.  */
+/* 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/analyzer.h"
+#include "analyzer/graphviz.h"
+#include "cgraph.h"
+#include "diagnostic-path.h"
+#include "gcc-rich-location.h"
+#include <string>
+#include "analyzer/supergraph.h"
+#include "analyzer/sm.h"
+#include "analyzer/pending-diagnostic.h"
+
+static const bool DEBUG = false;
+//static const bool DEBUG = true;
+
+////////////////////////////////////////////////////////////////////////////
+
+/* A state machine for detecting misuses of the malloc/free API.  */
+
+class malloc_state_machine : public state_machine
+{
+public:
+  malloc_state_machine (logger *logger);
+
+  bool inherited_state_p () const FINAL OVERRIDE { return false; }
+
+  bool on_stmt (sm_context *sm_ctxt,
+		const supernode *node,
+		const gimple *stmt) const FINAL OVERRIDE;
+
+  void on_edge (sm_context *sm_ctxt,
+		const gimple *last_stmt,
+		const superedge *edge) const FINAL OVERRIDE;
+
+  void on_leak (sm_context *sm_ctxt,
+		const supernode *node,
+		const gimple *stmt,
+		tree var,
+		state_machine::state_t state) const FINAL OVERRIDE;
+  bool can_purge_p (state_t s) const FINAL OVERRIDE;
+
+  state_t m_start;
+  state_t m_unchecked;
+  state_t m_null;
+  state_t m_nonnull;
+  state_t m_free;
+  state_t m_non_heap; // FIXME: or should this be a different state machine?
+  // or do we need child values etc?
+  state_t m_stop;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+class malloc_diagnostic : public pending_diagnostic
+{
+public:
+  malloc_diagnostic (const malloc_state_machine &sm, tree arg)
+  : m_sm (sm), m_arg (arg)
+  {}
+
+  bool subclass_equal_p (const pending_diagnostic &base_other) const OVERRIDE
+  {
+    return m_arg == ((const malloc_diagnostic &)base_other).m_arg;
+  }
+
+  label_text describe_state_change (const evdesc::state_change &change)
+    OVERRIDE
+  {
+    if (change.m_old_state == m_sm.m_start
+	&& change.m_new_state == m_sm.m_unchecked)
+      // TODO: verify that it's the allocation stmt, not a copy
+      return label_text::borrow ("allocated here");
+    if (change.m_old_state == m_sm.m_unchecked
+	&& change.m_new_state == m_sm.m_nonnull)
+      return formatted_print ("assuming %qE is non-NULL",
+			      change.m_expr);
+    if (change.m_new_state == m_sm.m_null)
+      return formatted_print ("assuming %qE is NULL",
+			      change.m_expr);
+    return label_text ();
+  }
+
+protected:
+  const malloc_state_machine &m_sm;
+  tree m_arg;
+};
+
+class double_free : public malloc_diagnostic
+{
+public:
+  double_free (const malloc_state_machine &sm, tree arg)
+  : malloc_diagnostic (sm, arg)
+  {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "double_free"; }
+
+  void emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    auto_diagnostic_group d;
+    metadata m;
+    m.add_cwe (415); /* CWE-415: Double Free.  */
+    warning_at (rich_loc, m, OPT_Wanalyzer_double_free,
+		"double-free of %qE", m_arg);
+
+#if 0
+    if (!rich_loc->get_path ())
+      return;
+
+    event_id_t second_free_event
+      = rich_loc->get_path ()->get_final_event_id ();
+
+    if (m_first_free_event.known_p ()
+	&& second_free_event.known_p ())
+      inform (rich_loc->get_loc (),
+	      "pointer freed at (%i) was already freed at (%i)",
+	      second_free_event.one_based (),
+	      m_first_free_event.one_based ());
+#endif
+  }
+
+  label_text describe_state_change (const evdesc::state_change &change)
+    FINAL OVERRIDE
+  {
+    if (change.m_new_state == m_sm.m_free)
+      {
+	m_first_free_event = change.m_event_id;
+	return formatted_print ("first %qs here", "free");
+      }
+    return malloc_diagnostic::describe_state_change (change);
+  }
+
+  label_text describe_call_with_state (const evdesc::call_with_state &info)
+    FINAL OVERRIDE
+  {
+    if (info.m_state == m_sm.m_free)
+      return formatted_print
+	("passing freed pointer %qE in call to %qE from %qE",
+	 info.m_expr, info.m_callee_fndecl, info.m_caller_fndecl);
+    return label_text ();
+  }
+
+  label_text describe_final_event (const evdesc::final_event &) FINAL OVERRIDE
+  {
+    if (m_first_free_event.known_p ())
+      return formatted_print ("second %qs here; first %qs was at (%i)",
+			      "free", "free", m_first_free_event.one_based ());
+    return formatted_print ("second %qs here", "free");
+  }
+
+  /* FIXME: maybe also something like this.  */
+#if 0
+  label_test label_critical_state (int event_idx)
+  //FINAL OVERRIDE
+  {
+    return label_text::take (xvsprintf ("freed at (%i)"));
+  }
+#endif
+
+private:
+  event_id_t m_first_free_event;
+};
+
+/* Abstract subclass for describing possible bad uses of NULL.
+   Responsible for describing the call that could return NULL.  */
+
+class possible_null : public malloc_diagnostic
+{
+public:
+  possible_null (const malloc_state_machine &sm, tree arg)
+  : malloc_diagnostic (sm, arg)
+  {}
+
+  label_text describe_state_change (const evdesc::state_change &change)
+    FINAL OVERRIDE
+  {
+    if (change.m_old_state == m_sm.m_start
+	&& change.m_new_state == m_sm.m_unchecked)
+      return label_text::borrow ("this call could return NULL");
+    return malloc_diagnostic::describe_state_change (change);
+  }
+
+  label_text describe_return_of_state (const evdesc::return_of_state &info)
+    FINAL OVERRIDE
+  {
+    if (info.m_state == m_sm.m_unchecked)
+      return formatted_print ("possible return of NULL to %qE from %qE",
+			      info.m_caller_fndecl, info.m_callee_fndecl);
+    return label_text ();
+  }
+};
+
+/* Concrete subclass for describing dereference of a possible NULL
+   value.  */
+
+class possible_null_deref : public possible_null
+{
+public:
+  possible_null_deref (const malloc_state_machine &sm, tree arg)
+  : possible_null (sm, arg)
+  {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "possible_null_deref"; }
+
+  void emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    /* CWE-690: Unchecked Return Value to NULL Pointer Dereference.  */
+    metadata m;
+    m.add_cwe (690);
+    warning_at (rich_loc, m, OPT_Wanalyzer_possible_null_dereference,
+		"use of possibly-NULL %qE", m_arg);
+  }
+
+  /* FIXME.  */
+  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  {
+    return formatted_print ("%qE could be NULL", ev.m_expr);
+  }
+
+};
+
+/* Subroutine for use by possible_null_arg::emit and null_arg::emit.
+   Issue a note informing that the pertinent argument must be non-NULL.  */
+
+static void
+inform_nonnull_attribute (tree fndecl, int arg_idx)
+{
+  inform (DECL_SOURCE_LOCATION (fndecl),
+	  "argument %u of %qD must be non-null",
+	  arg_idx + 1, fndecl);
+  /* FIXME: ideally, use the location of the parm
+     and underline the attribute also (but we probably don't have the
+     location_t...)  */
+
+  /* C and C++ FEs have get_fndecl_argument_location.  */
+}
+
+/* Concrete subclass for describing passing a possibly-NULL value to a
+   function marked with __attribute__((nonnull)).  */
+
+class possible_null_arg : public possible_null
+{
+public:
+  possible_null_arg (const malloc_state_machine &sm, tree arg,
+		     tree fndecl, int arg_idx)
+  : possible_null (sm, arg),
+    m_fndecl (fndecl), m_arg_idx (arg_idx)
+  {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "possible_null_arg"; }
+
+  bool subclass_equal_p (const pending_diagnostic &base_other) const
+  {
+    const possible_null_arg &sub_other
+      = (const possible_null_arg &)base_other;
+    return (m_arg == sub_other.m_arg
+	    && m_fndecl == sub_other.m_fndecl
+	    && m_arg_idx == sub_other.m_arg_idx);
+  }
+
+
+  void emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    /* CWE-690: Unchecked Return Value to NULL Pointer Dereference.  */
+    auto_diagnostic_group d;
+    metadata m;
+    m.add_cwe (690);
+    warning_at (rich_loc, m, OPT_Wanalyzer_possible_null_argument,
+		"use of possibly-NULL %qE where non-null expected",
+		m_arg);
+    inform_nonnull_attribute (m_fndecl, m_arg_idx);
+  }
+
+  /* FIXME.  */
+  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  {
+    return formatted_print ("argument %u (%qE) could be NULL"
+			    " where non-null expected",
+			    m_arg_idx + 1, ev.m_expr);
+  }
+
+  // TODO
+
+private:
+  tree m_fndecl;
+  int m_arg_idx;
+};
+
+/* Concrete subclass for describing a dereference of a NULL value.  */
+
+class null_deref : public malloc_diagnostic
+{
+public:
+  null_deref (const malloc_state_machine &sm, tree arg)
+  : malloc_diagnostic (sm, arg) {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "null_deref"; }
+
+  void emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    /* CWE-690: Unchecked Return Value to NULL Pointer Dereference.  */
+    metadata m;
+    m.add_cwe (690);
+    warning_at (rich_loc, m, OPT_Wanalyzer_null_dereference,
+		"use of NULL %qE", m_arg);
+  }
+};
+
+/* Concrete subclass for describing passing a NULL value to a
+   function marked with __attribute__((nonnull)).  */
+
+class null_arg : public malloc_diagnostic
+{
+public:
+  null_arg (const malloc_state_machine &sm, tree arg,
+	    tree fndecl, int arg_idx)
+  : malloc_diagnostic (sm, arg),
+    m_fndecl (fndecl), m_arg_idx (arg_idx)
+  {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "null_arg"; }
+
+  bool subclass_equal_p (const pending_diagnostic &base_other) const
+  {
+    const null_arg &sub_other
+      = (const null_arg &)base_other;
+    return (m_arg == sub_other.m_arg
+	    && m_fndecl == sub_other.m_fndecl
+	    && m_arg_idx == sub_other.m_arg_idx);
+  }
+
+  void emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    /* CWE-690: Unchecked Return Value to NULL Pointer Dereference.  */
+    auto_diagnostic_group d;
+    metadata m;
+    m.add_cwe (690);
+    warning_at (rich_loc, m, OPT_Wanalyzer_null_argument,
+		"use of NULL %qE where non-null expected", m_arg);
+    inform_nonnull_attribute (m_fndecl, m_arg_idx);
+  }
+
+  /* FIXME.  */
+  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  {
+    return formatted_print ("argument %u (%qE) NULL"
+			    " where non-null expected",
+			    m_arg_idx + 1, ev.m_expr);
+  }
+
+private:
+  tree m_fndecl;
+  int m_arg_idx;
+};
+
+class use_after_free : public malloc_diagnostic
+{
+public:
+  use_after_free (const malloc_state_machine &sm, tree arg)
+  : malloc_diagnostic (sm, arg)
+  {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "use_after_free"; }
+
+  void emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    /* CWE-416: Use After Free.  */
+    metadata m;
+    m.add_cwe (416);
+    warning_at (rich_loc, m, OPT_Wanalyzer_use_after_free,
+		"use after free of %qE", m_arg);
+  }
+
+  label_text describe_state_change (const evdesc::state_change &change)
+    FINAL OVERRIDE
+  {
+    if (change.m_new_state == m_sm.m_free)
+      {
+	m_free_event = change.m_event_id;
+	return label_text::borrow ("freed here");
+      }
+    return malloc_diagnostic::describe_state_change (change);
+  }
+
+  /* FIXME.  */
+  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  {
+    if (m_free_event.known_p ())
+      return formatted_print ("use after %<free%> of %qE; freed at (%i)",
+			      ev.m_expr, m_free_event.one_based ());
+    else
+      return formatted_print ("use after %<free%> of %qE", ev.m_expr);
+  }
+
+private:
+  event_id_t m_free_event;
+};
+
+class malloc_leak : public malloc_diagnostic
+{
+public:
+  malloc_leak (const malloc_state_machine &sm, tree arg)
+  : malloc_diagnostic (sm, arg) {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "malloc_leak"; }
+
+  void emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    metadata m;
+    m.add_cwe (401);
+    warning_at (rich_loc, m, OPT_Wanalyzer_malloc_leak,
+		"leak of %qE", m_arg);
+  }
+
+  label_text describe_state_change (const evdesc::state_change &change)
+    FINAL OVERRIDE
+  {
+    if (change.m_new_state == m_sm.m_unchecked)
+      {
+	m_malloc_event = change.m_event_id;
+	return label_text::borrow ("allocated here");
+      }
+    return malloc_diagnostic::describe_state_change (change);
+  }
+
+  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  {
+    if (m_malloc_event.known_p ())
+      return formatted_print ("%qE leaks here; was allocated at (%i)",
+			      ev.m_expr, m_malloc_event.one_based ());
+    else
+      return formatted_print ("%qE leaks here", ev.m_expr);
+  }
+
+private:
+  event_id_t m_malloc_event;
+};
+
+class free_of_non_heap : public malloc_diagnostic
+{
+public:
+  free_of_non_heap (const malloc_state_machine &sm, tree arg)
+  : malloc_diagnostic (sm, arg), m_kind (KIND_UNKNOWN)
+  {
+  }
+
+  const char *get_kind () const FINAL OVERRIDE { return "free_of_non_heap"; }
+
+  bool subclass_equal_p (const pending_diagnostic &base_other) const
+    FINAL OVERRIDE
+  {
+    const free_of_non_heap &other = (const free_of_non_heap &)base_other;
+    return (m_arg == other.m_arg && m_kind == other.m_kind);
+  }
+
+  void emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    auto_diagnostic_group d;
+    metadata m;
+    m.add_cwe (590); /* CWE-590: Free of Memory not on the Heap.  */
+    switch (m_kind)
+      {
+      default:
+	gcc_unreachable ();
+      case KIND_UNKNOWN:
+	warning_at (rich_loc, m, OPT_Wanalyzer_free_of_non_heap,
+		    "free of %qE which points to memory not on the heap",
+		    m_arg);
+	break;
+      case KIND_ALLOCA:
+	warning_at (rich_loc, m, OPT_Wanalyzer_free_of_non_heap,
+		    "free of memory allocated on the stack by %qs (%qE)"
+		    " will corrupt the heap",
+		    "alloca", m_arg);
+	break;
+      }
+  }
+
+  label_text describe_state_change (const evdesc::state_change &change)
+    FINAL OVERRIDE
+  {
+    /* Attempt to reconstruct what kind of pointer it is.
+       (It seems neater for this to be a part of the state, though).  */
+    if (TREE_CODE (change.m_expr) == SSA_NAME)
+      {
+	gimple *def_stmt = SSA_NAME_DEF_STMT (change.m_expr);
+	if (gcall *call = dyn_cast <gcall *> (def_stmt))
+	  {
+	    if (is_named_call_p (call, "alloca", 1)
+		|| is_named_call_p (call, "__builtin_alloca", 1))
+	      {
+		m_kind = KIND_ALLOCA;
+		return label_text::borrow
+		  ("memory is allocated on the stack here");
+	      }
+	  }
+      }
+    return label_text::borrow ("pointer is from here");
+  }
+
+  /* FIXME.  */
+  label_text describe_final_event (const evdesc::final_event &) FINAL OVERRIDE
+  {
+    return formatted_print ("call to %qs here", "free");
+  }
+
+private:
+  enum kind
+  {
+    KIND_UNKNOWN,
+    KIND_ALLOCA
+  };
+  enum kind m_kind;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+malloc_state_machine::malloc_state_machine (logger *logger)
+: state_machine ("malloc", logger)
+{
+  // TODO: parse all of this from a file:
+  m_start = add_state ("start");
+  m_unchecked = add_state ("unchecked");
+  m_null = add_state ("null");
+  m_nonnull = add_state ("nonnull");
+  m_free = add_state ("free");
+  m_non_heap = add_state ("non-heap");
+  m_stop = add_state ("stop");
+}
+
+bool
+malloc_state_machine::on_stmt (sm_context *sm_ctxt,
+			       const supernode *node,
+			       const gimple *stmt) const
+{
+  //tree fndecl = node->m_fun->decl;
+
+  // FIXME: do all of this from data
+  if (const gcall *call = dyn_cast <const gcall *> (stmt))
+    {
+      if (is_named_call_p (call, "malloc", 1)
+	  || is_named_call_p (call, "calloc", 2))
+	{
+	  if (DEBUG)
+	    inform (stmt->location, "DBG: got malloc");
+	  tree lhs = gimple_call_lhs (call);
+	  if (lhs)
+	    {
+	      lhs = sm_ctxt->get_readable_tree (lhs);
+	      sm_ctxt->on_transition (node, stmt, lhs, m_start, m_unchecked);
+	    }
+	  else
+	    {
+	      /* TODO: report leak.  */
+	    }
+	  return true;
+	}
+
+      if (is_named_call_p (call, "alloca", 1)
+	  || is_named_call_p (call, "__builtin_alloca", 1))
+	{
+	  tree lhs = gimple_call_lhs (call);
+	  if (lhs)
+	    {
+	      lhs = sm_ctxt->get_readable_tree (lhs);
+	      sm_ctxt->on_transition (node, stmt, lhs, m_start, m_non_heap);
+	    }
+	  return true;
+	}
+
+      if (is_named_call_p (call, "free", 1))
+	{
+	  tree arg = gimple_call_arg (call, 0);
+	  if (DEBUG)
+	    inform (stmt->location, "DBG: got free");
+
+	  arg = sm_ctxt->get_readable_tree (arg);
+
+	  /* start/unchecked/nonnull -> free.  */
+	  sm_ctxt->on_transition (node, stmt, arg, m_start, m_free);
+	  sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_free);
+	  sm_ctxt->on_transition (node, stmt, arg, m_nonnull, m_free);
+
+	  /* Keep state "null" as-is, rather than transitioning to "free";
+	     we don't want want to complain about double-free of NULL.  */
+
+	  /* free -> stop, with warning.  */
+	  sm_ctxt->warn_for_state (node, stmt, arg, m_free,
+				   new double_free (*this, arg));
+	  sm_ctxt->on_transition (node, stmt, arg, m_free, m_stop);
+
+	  /* non-heap -> stop, with warning.  */
+	  sm_ctxt->warn_for_state (node, stmt, arg, m_non_heap,
+				   new free_of_non_heap (*this, arg));
+	  sm_ctxt->on_transition (node, stmt, arg, m_non_heap, m_stop);
+	  return true;
+	}
+
+      /* TODO: handle "__attribute__((nonnull))".   */
+      if (tree callee_fndecl = gimple_call_fndecl (stmt))
+	{
+	  tree fntype = TREE_TYPE (callee_fndecl);
+	  bitmap nonnull_args = get_nonnull_args (fntype);
+	  if (nonnull_args)
+	    {
+	      for (unsigned i = 0; i < gimple_call_num_args (stmt); i++)
+		{
+		  tree arg = gimple_call_arg (stmt, i);
+		  if (TREE_CODE (TREE_TYPE (arg)) != POINTER_TYPE)
+		    continue;
+		  /* If we have a nonnull-args, and either all pointers, or just
+		     the specified pointers.  */
+		  if (bitmap_empty_p (nonnull_args)
+		      || bitmap_bit_p (nonnull_args, i))
+		    {
+		      sm_ctxt->warn_for_state
+			(node, stmt, arg, m_unchecked,
+			 new possible_null_arg (*this, arg, callee_fndecl, i));
+		      sm_ctxt->on_transition (node, stmt, arg, m_unchecked,
+					      m_nonnull);
+
+		      sm_ctxt->warn_for_state
+			(node, stmt, arg, m_null,
+			 new null_arg (*this, arg, callee_fndecl, i));
+		      sm_ctxt->on_transition (node, stmt, arg, m_null, m_stop);
+		    }
+		}
+	      BITMAP_FREE (nonnull_args);
+	    }
+	}
+    }
+
+  /* FIXME: It's not clear if we need this, due to the SSA representation:
+     each assignment creates a new SSA_NAME.  */
+  if (tree lhs = is_zero_assignment (stmt))
+    {
+      if (any_pointer_p (lhs))
+	{
+	  sm_ctxt->on_transition (node, stmt, lhs, m_start, m_null);
+	  sm_ctxt->on_transition (node, stmt, lhs, m_unchecked, m_null);
+	  sm_ctxt->on_transition (node, stmt, lhs, m_nonnull, m_null);
+	  sm_ctxt->on_transition (node, stmt, lhs, m_free, m_null);
+	}
+    }
+
+  if (const gassign *assign_stmt = dyn_cast <const gassign *> (stmt))
+    {
+      enum tree_code op = gimple_assign_rhs_code (assign_stmt);
+      if (op == ADDR_EXPR)
+	{
+	  tree lhs = gimple_assign_lhs (assign_stmt);
+	  if (lhs)
+	    {
+	      lhs = sm_ctxt->get_readable_tree (lhs);
+	      sm_ctxt->on_transition (node, stmt, lhs, m_start, m_non_heap);
+	    }
+	}
+    }
+
+  // TODO: dereferences
+  for (unsigned i = 0; i < gimple_num_ops (stmt); i++)
+    {
+      tree op = gimple_op (stmt, i);
+#if 0
+      if (op)
+	inform (stmt->location, "op %i: %qE", i, op);
+      else
+	inform (stmt->location, "op %i: NULL_TREE", i);
+#endif
+      if (!op)
+	continue;
+      if (TREE_CODE (op) == COMPONENT_REF)
+	op = TREE_OPERAND (op, 0);
+
+      if (TREE_CODE (op) == MEM_REF)
+	{
+	  tree arg = TREE_OPERAND (op, 0);
+	  arg = sm_ctxt->get_readable_tree (arg);
+
+	  sm_ctxt->warn_for_state (node, stmt, arg, m_unchecked,
+				   new possible_null_deref (*this, arg));
+	  sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_nonnull);
+
+	  sm_ctxt->warn_for_state (node, stmt, arg, m_null,
+				   new null_deref (*this, arg));
+	  sm_ctxt->on_transition (node, stmt, arg, m_null, m_stop);
+
+	  sm_ctxt->warn_for_state (node, stmt, arg, m_free,
+				   new use_after_free (*this, arg));
+	  sm_ctxt->on_transition (node, stmt, arg, m_free, m_stop);
+	}
+    }
+  return false;
+}
+
+/* FIXME.  */
+
+void
+malloc_state_machine::on_edge (sm_context *sm_ctxt,
+			       const gimple *last_stmt,
+			       const superedge *edge) const
+{
+  if (last_stmt == NULL)
+    return;
+
+  if (const gcond *cond_stmt = dyn_cast <const gcond *> (last_stmt))
+    {
+      tree arg;
+      enum tree_code op;
+      if (is_comparison_against_zero (cond_stmt, &arg, &op))
+	{
+	  if (!any_pointer_p (arg))
+	    return;
+
+	  ::edge cfg_edge = edge->get_any_cfg_edge ();
+	  gcc_assert (cfg_edge != NULL);
+	  gcc_assert (cfg_edge->flags & (EDGE_TRUE_VALUE | EDGE_FALSE_VALUE));
+	  
+	  if (op == NE_EXPR)
+	    {
+	      log ("got 'ARG != 0' match");
+	      sm_ctxt->on_transition (edge->m_src, last_stmt, arg, m_unchecked,
+				      (cfg_edge->flags & EDGE_TRUE_VALUE
+				       ? m_nonnull : m_null));
+	    }
+	  else if (op == EQ_EXPR)
+	    {
+	      log ("got 'ARG == 0' match");
+	      sm_ctxt->on_transition (edge->m_src, last_stmt, arg, m_unchecked,
+				      (cfg_edge->flags & EDGE_TRUE_VALUE
+				       ? m_null : m_nonnull));
+	    }
+	}
+    }
+
+  // TODO: something similar for switch statements?
+}
+
+void
+malloc_state_machine::on_leak (sm_context *sm_ctxt,
+			       const supernode *node,
+			       const gimple *stmt,
+			       tree var,
+			       state_machine::state_t state ATTRIBUTE_UNUSED)
+  const
+{
+  sm_ctxt->warn_for_state (node, stmt, var, m_unchecked,
+			   new malloc_leak (*this, var));
+  sm_ctxt->warn_for_state (node, stmt, var, m_nonnull,
+			   new malloc_leak (*this, var));
+}
+
+bool
+malloc_state_machine::can_purge_p (state_t s) const
+{
+  return s != m_unchecked && s != m_nonnull;
+}
+
+state_machine *
+make_malloc_state_machine (logger *logger)
+{
+  return new malloc_state_machine (logger);
+}
diff --git a/gcc/analyzer/sm-pattern-test.cc b/gcc/analyzer/sm-pattern-test.cc
new file mode 100644
index 0000000..5025e3a
--- /dev/null
+++ b/gcc/analyzer/sm-pattern-test.cc
@@ -0,0 +1,208 @@
+/* A state machine for use in DejaGnu tests, to check that
+   pattern-matching works as expected.
+
+   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/>.  */
+/* 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/analyzer.h"
+#include "analyzer/graphviz.h"
+#include "cgraph.h"
+#include "diagnostic-path.h"
+#include "gcc-rich-location.h"
+#include <string>
+#include "analyzer/supergraph.h"
+#include "analyzer/sm.h"
+#include "analyzer/pending-diagnostic.h"
+
+static const bool DEBUG = false;
+//static const bool DEBUG = true;
+
+/* A state machine for use in DejaGnu tests, to check that
+   pattern-matching works as expected.  */
+
+class pattern_test_state_machine : public state_machine
+{
+public:
+  pattern_test_state_machine (logger *logger);
+
+  bool inherited_state_p () const FINAL OVERRIDE { return false; }
+
+  bool on_stmt (sm_context *sm_ctxt,
+		const supernode *node,
+		const gimple *stmt) const FINAL OVERRIDE;
+
+  void on_edge (sm_context *sm_ctxt,
+		const gimple *last_stmt,
+		const superedge *edge) const FINAL OVERRIDE;
+
+  void on_leak (sm_context *sm_ctxt,
+		const supernode *node,
+		const gimple *stmt,
+		tree var,
+		state_machine::state_t state) const FINAL OVERRIDE;
+  bool can_purge_p (state_t s) const FINAL OVERRIDE;
+
+private:
+  state_t m_start;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+class pattern_match : public pending_diagnostic_subclass<pattern_match>
+{
+public:
+  pattern_match (tree arg, const char *msg) : m_arg (arg), m_msg (msg) {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "pattern_match"; }
+
+  bool operator== (const pattern_match &other) const
+  {
+    return (m_arg == other.m_arg
+	    // FIXME: should this be string equality?
+	    && m_msg == other.m_msg);
+  }
+
+  void emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    // TODO:
+    warning_at (rich_loc, 0, "pattern match on %qE: %s", m_arg, m_msg);
+  }
+
+private:
+  tree m_arg;
+  const char *m_msg;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+pattern_test_state_machine::pattern_test_state_machine (logger *logger)
+: state_machine ("pattern-test", logger)
+{
+  // TODO: parse all of this from a file:
+  m_start = add_state ("start");
+}
+
+bool
+pattern_test_state_machine::on_stmt (sm_context *sm_ctxt ATTRIBUTE_UNUSED,
+				     const supernode *node ATTRIBUTE_UNUSED,
+				     const gimple *stmt ATTRIBUTE_UNUSED) const
+{
+  //tree fndecl = node->m_fun->decl;
+
+#if 0
+  // FIXME: do all of this from data
+  if (const gcall *call = dyn_cast <const gcall *> (stmt))
+    {
+    }
+#endif
+  return false;
+}
+
+/* FIXME.  */
+
+void
+pattern_test_state_machine::on_edge (sm_context *sm_ctxt,
+				     const gimple *last_stmt,
+				     const superedge *edge) const
+{
+  if (last_stmt == NULL)
+    return;
+
+  const supernode *node = edge->m_src;
+
+  if (const gcond *cond_stmt = dyn_cast <const gcond *> (last_stmt))
+    {
+      tree arg;
+      enum tree_code op;
+      if (is_comparison_against_zero (cond_stmt, &arg, &op))
+	{
+	  ::edge cfg_edge = edge->get_any_cfg_edge ();
+	  gcc_assert (cfg_edge != NULL);
+	  gcc_assert (cfg_edge->flags & (EDGE_TRUE_VALUE | EDGE_FALSE_VALUE));
+
+	  bool is_true = cfg_edge->flags & EDGE_TRUE_VALUE;
+
+	  if (op == NE_EXPR)
+	    {
+	      pending_diagnostic *diag
+		= new pattern_match (arg,
+				     is_true ? "!= 0" : "== 0");
+	      sm_ctxt->warn_for_state (node, last_stmt, arg, m_start, diag);
+	    }
+	  else if (op == EQ_EXPR)
+	    {
+	      pending_diagnostic *diag
+		= new pattern_match (arg,
+				     is_true ? "== 0" : "!= 0");
+	      sm_ctxt->warn_for_state (node, last_stmt, arg, m_start, diag);
+	    }
+	}
+    }
+}
+
+void
+pattern_test_state_machine::on_leak (sm_context *sm_ctxt ATTRIBUTE_UNUSED,
+				     const supernode *node ATTRIBUTE_UNUSED,
+				     const gimple *stmt ATTRIBUTE_UNUSED,
+				     tree var ATTRIBUTE_UNUSED,
+				     state_machine::state_t state
+				       ATTRIBUTE_UNUSED)
+  const
+{
+  /* Empty.  */
+}
+
+bool
+pattern_test_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const
+{
+  return true;
+}
+
+state_machine *
+make_pattern_test_state_machine (logger *logger)
+{
+  return new pattern_test_state_machine (logger);
+}
diff --git a/gcc/analyzer/sm-sensitive.cc b/gcc/analyzer/sm-sensitive.cc
new file mode 100644
index 0000000..a8d899e
--- /dev/null
+++ b/gcc/analyzer/sm-sensitive.cc
@@ -0,0 +1,217 @@
+/* An experimental state machine, for tracking exposure of sensitive
+   data (e.g. through logging).
+   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/>.  */
+/* 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/analyzer.h"
+#include "analyzer/graphviz.h"
+#include "cgraph.h"
+#include "diagnostic-path.h"
+#include "gcc-rich-location.h"
+#include <string>
+#include "analyzer/supergraph.h"
+#include "analyzer/sm.h"
+#include "analyzer/pending-diagnostic.h"
+
+static const bool DEBUG = false;
+//static const bool DEBUG = true;
+
+/* An experimental state machine, for tracking exposure of sensitive
+   data (e.g. through logging).  */
+
+class sensitive_state_machine : public state_machine
+{
+public:
+  sensitive_state_machine (logger *logger);
+
+  bool inherited_state_p () const FINAL OVERRIDE { return true; }
+
+  bool on_stmt (sm_context *sm_ctxt,
+		const supernode *node,
+		const gimple *stmt) const FINAL OVERRIDE;
+
+  void on_edge (sm_context *sm_ctxt,
+		const gimple *last_stmt,
+		const superedge *edge) const FINAL OVERRIDE;
+
+  void on_leak (sm_context *sm_ctxt,
+		const supernode *node,
+		const gimple *stmt,
+		tree var,
+		state_machine::state_t state) const FINAL OVERRIDE;
+  bool can_purge_p (state_t s) const FINAL OVERRIDE;
+
+private:
+  void warn_for_any_exposure (sm_context *sm_ctxt,
+			      const supernode *node,
+			      const gimple *stmt,
+			      tree arg) const;
+
+  state_t m_start;
+  state_t m_sensitive;
+  state_t m_stop;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+class exposure_through_output_file
+  : public pending_diagnostic_subclass<exposure_through_output_file>
+{
+public:
+  exposure_through_output_file (tree arg) : m_arg (arg) {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "exposure_through_output_file"; }
+
+  bool operator== (const exposure_through_output_file &other) const
+  {
+    return m_arg == other.m_arg;
+  }
+
+  void emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    metadata m;
+    /* CWE-532: Information Exposure Through Log Files */
+    m.add_cwe (532);
+    warning_at (rich_loc, m, OPT_Wanalyzer_exposure_through_output_file,
+		"sensitive value %qE written to output file",
+		m_arg);
+  }
+
+private:
+  tree m_arg;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+sensitive_state_machine::sensitive_state_machine (logger *logger)
+: state_machine ("sensitive", logger)
+{
+  // TODO: parse all of this from a file:
+  m_start = add_state ("start");
+  m_sensitive = add_state ("sensitive");
+  m_stop = add_state ("stop");
+}
+
+void
+sensitive_state_machine::warn_for_any_exposure (sm_context *sm_ctxt,
+						const supernode *node,
+						const gimple *stmt,
+						tree arg) const
+{
+  sm_ctxt->warn_for_state (node, stmt, arg, m_sensitive,
+			   new exposure_through_output_file (arg));
+}
+
+bool
+sensitive_state_machine::on_stmt (sm_context *sm_ctxt,
+				  const supernode *node,
+				  const gimple *stmt) const
+{
+  //tree fndecl = node->m_fun->decl;
+
+  // FIXME: do all of this from data
+  if (const gcall *call = dyn_cast <const gcall *> (stmt))
+    {
+      if (is_named_call_p (call, "getpass", 1))
+	{
+	  tree lhs = gimple_call_lhs (call);
+	  if (lhs)
+	    sm_ctxt->on_transition (node, stmt, lhs, m_start, m_sensitive);
+	  return true;
+	}
+      else if (is_named_call_p (call, "fprintf")
+	       || is_named_call_p (call, "printf"))
+	{
+	  /* Handle a match at any position in varargs.  */
+	  for (unsigned idx = 1; idx < gimple_call_num_args (call); idx++)
+	    {
+	      tree arg = gimple_call_arg (call, idx);
+	      warn_for_any_exposure (sm_ctxt, node, stmt, arg);
+	    }
+	  return true;
+	}
+      else if (is_named_call_p (call, "fwrite", 4))
+	{
+	  tree arg = gimple_call_arg (call, 0);
+	  warn_for_any_exposure (sm_ctxt, node, stmt, arg);
+	  return true;
+	}
+    }
+  return false;
+}
+
+/* FIXME.  */
+
+void
+sensitive_state_machine::on_edge (sm_context *sm_ctxt ATTRIBUTE_UNUSED,
+				  const gimple *last_stmt ATTRIBUTE_UNUSED,
+				  const superedge *edge ATTRIBUTE_UNUSED) const
+{
+  /* Empty.  */
+}
+
+void
+sensitive_state_machine::on_leak (sm_context *sm_ctxt ATTRIBUTE_UNUSED,
+				  const supernode *node ATTRIBUTE_UNUSED,
+				  const gimple *stmt ATTRIBUTE_UNUSED,
+				  tree var ATTRIBUTE_UNUSED,
+				  state_machine::state_t state ATTRIBUTE_UNUSED)
+  const
+{
+  /* Empty.  */
+}
+
+bool
+sensitive_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const
+{
+  return true;
+}
+
+state_machine *
+make_sensitive_state_machine (logger *logger)
+{
+  return new sensitive_state_machine (logger);
+}
diff --git a/gcc/analyzer/sm-taint.cc b/gcc/analyzer/sm-taint.cc
new file mode 100644
index 0000000..4275a1b
--- /dev/null
+++ b/gcc/analyzer/sm-taint.cc
@@ -0,0 +1,297 @@
+/* An experimental state machine, for tracking "taint": unsanitized uses
+   of data potentially under an attacker's control.
+
+   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/>.  */
+/* 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/analyzer.h"
+#include "analyzer/graphviz.h"
+#include "cgraph.h"
+#include "diagnostic-path.h"
+#include "gcc-rich-location.h"
+#include <string>
+#include "analyzer/supergraph.h"
+#include "analyzer/sm.h"
+#include "analyzer/pending-diagnostic.h"
+
+static const bool DEBUG = false;
+//static const bool DEBUG = true;
+
+/* An experimental state machine, for tracking "taint": unsanitized uses
+   of data potentially under an attacker's control.  */
+
+class taint_state_machine : public state_machine
+{
+public:
+  taint_state_machine (logger *logger);
+
+  bool inherited_state_p () const FINAL OVERRIDE { return true; }
+
+  bool on_stmt (sm_context *sm_ctxt,
+		const supernode *node,
+		const gimple *stmt) const FINAL OVERRIDE;
+
+  void on_edge (sm_context *sm_ctxt,
+		const gimple *last_stmt,
+		const superedge *edge) const FINAL OVERRIDE;
+
+  void on_leak (sm_context *sm_ctxt,
+		const supernode *node,
+		const gimple *stmt,
+		tree var,
+		state_machine::state_t state) const FINAL OVERRIDE;
+  bool can_purge_p (state_t s) const FINAL OVERRIDE;
+
+private:
+  state_t m_start;
+  state_t m_tainted;
+  state_t m_has_lb;
+  state_t m_has_ub;
+  state_t m_stop;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+class tainted_array_index
+  : public pending_diagnostic_subclass<tainted_array_index>
+{
+public:
+  tainted_array_index (tree arg) : m_arg (arg) {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "tainted_array_index"; }
+
+  bool operator== (const tainted_array_index &other) const
+  {
+    return m_arg == other.m_arg;
+  }
+
+  void emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    metadata m;
+    m.add_cwe (129);
+    warning_at (rich_loc, m, OPT_Wanalyzer_tainted_array_index,
+		"use of tainted value %qE in array lookup without bounds checking",
+		m_arg);
+  }
+
+private:
+  tree m_arg;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+taint_state_machine::taint_state_machine (logger *logger)
+: state_machine ("taint", logger)
+{
+  // TODO: parse all of this from a file:
+  m_start = add_state ("start");
+  m_tainted = add_state ("tainted");
+  m_has_lb = add_state ("has_lb");
+  m_has_ub = add_state ("has_ub");
+  m_stop = add_state ("stop");
+}
+
+bool
+taint_state_machine::on_stmt (sm_context *sm_ctxt,
+			       const supernode *node,
+			       const gimple *stmt) const
+{
+  //tree fndecl = node->m_fun->decl;
+
+  // FIXME: do all of this from data
+  if (const gcall *call = dyn_cast <const gcall *> (stmt))
+    {
+      if (is_named_call_p (call, "fread", 4))
+	{
+	  if (DEBUG)
+	    inform (stmt->location, "DBG: got taint");
+	  tree arg = gimple_call_arg (call, 0);
+	  arg = sm_ctxt->get_readable_tree (arg);
+
+	  sm_ctxt->on_transition (node, stmt, arg, m_start, m_tainted);
+
+	  // FIXME: dereference an ADDR_EXPR:
+	  // FIXME: should the engine do this?
+	  if (TREE_CODE (arg) == ADDR_EXPR)
+	    sm_ctxt->on_transition (node, stmt, TREE_OPERAND (arg, 0),
+				    m_start, m_tainted);
+	  return true;
+	}
+    }
+
+#if 0
+  /* FIXME: It's not clear if we need this, due to the SSA representation:
+     each assignment creates a new SSA_NAME.  */
+  if (tree lhs = is_zero_assignment (stmt))
+    {
+      if (any_pointer_p (lhs))
+	{
+	  sm_ctxt->on_transition (node, stmt, lhs, m_start, m_null);
+	  sm_ctxt->on_transition (node, stmt, lhs, m_unchecked, m_null);
+	  sm_ctxt->on_transition (node, stmt, lhs, m_nonnull, m_null);
+	  sm_ctxt->on_transition (node, stmt, lhs, m_free, m_null);
+	}
+    }
+#endif
+
+  if (const gassign *assign = dyn_cast <const gassign *> (stmt))
+    {
+      //tree lhs = gimple_assign_lhs (assign);
+      tree rhs1 = gimple_assign_rhs1 (assign);
+      enum tree_code op = gimple_assign_rhs_code (assign);
+      if (op == ARRAY_REF)
+	{
+	  tree arg = TREE_OPERAND (rhs1, 1);
+	  arg = sm_ctxt->get_readable_tree (arg);
+	  sm_ctxt->warn_for_state  (node, stmt, arg, m_tainted,
+				    new tainted_array_index (arg));
+	  sm_ctxt->on_transition (node, stmt, arg, m_tainted, m_stop);
+	}
+    }
+
+#if 0
+  // TODO: dereferences
+  for (unsigned i = 0; i < gimple_num_ops (stmt); i++)
+    {
+      tree op = gimple_op (stmt, i);
+#if 0
+      if (op)
+	inform (stmt->location, "op %i: %qE", i, op);
+      else
+	inform (stmt->location, "op %i: NULL_TREE", i);
+#endif
+      if (!op)
+	continue;
+      if (TREE_CODE (op) == MEM_REF)
+	{
+	  tree arg = TREE_OPERAND (op, 0);
+	  sm_ctxt->warn_for_state (node, stmt, arg, m_unchecked,
+				   "use of possibly-NULL %qE", arg);
+	  sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_nonnull);
+
+	  sm_ctxt->warn_for_state (node, stmt, arg, m_null,
+				   "use of NULL %qE", arg);
+	  sm_ctxt->on_transition (node, stmt, arg, m_null, m_stop);
+
+	  sm_ctxt->warn_for_state (node, stmt, arg, m_free,
+				   "use after free of %qE", arg);
+	  sm_ctxt->on_transition (node, stmt, arg, m_free, m_stop);
+	}
+    }
+#endif
+  return false;
+}
+
+/* FIXME.  */
+
+void
+taint_state_machine::on_edge (sm_context *sm_ctxt,
+			       const gimple *last_stmt,
+			       const superedge *edge) const
+{
+  if (last_stmt == NULL)
+    return;
+
+  if (const gcond *cond_stmt = dyn_cast <const gcond *> (last_stmt))
+    {
+      enum tree_code op = gimple_cond_code (cond_stmt);
+      tree lhs = gimple_cond_lhs (cond_stmt);
+      //tree rhs = gimple_cond_rhs (cond_stmt);
+      // TODO: make it symmetric?
+
+      switch (op)
+	{
+	  //case NE_EXPR:
+	  //case EQ_EXPR:
+	case GE_EXPR:
+	case GT_EXPR:
+	  {
+	    sm_ctxt->on_transition (edge->m_src, last_stmt, lhs, m_tainted,
+				    m_has_lb);
+	    sm_ctxt->on_transition (edge->m_src, last_stmt, lhs, m_has_ub,
+				    m_stop);
+	  }
+	  break;
+	case LE_EXPR:
+	case LT_EXPR:
+	  {
+	    sm_ctxt->on_transition (edge->m_src, last_stmt, lhs, m_tainted,
+				    m_has_ub);
+	    sm_ctxt->on_transition (edge->m_src, last_stmt, lhs, m_has_lb,
+				    m_stop);
+	  }
+	  break;
+
+	default:
+	  break;
+	}
+    }
+
+  // TODO: something similar for switch statements?
+}
+
+void
+taint_state_machine::on_leak (sm_context *sm_ctxt ATTRIBUTE_UNUSED,
+			      const supernode *node ATTRIBUTE_UNUSED,
+			      const gimple *stmt ATTRIBUTE_UNUSED,
+			      tree var ATTRIBUTE_UNUSED,
+			      state_machine::state_t state ATTRIBUTE_UNUSED)
+  const
+{
+  /* Empty.  */
+}
+
+bool
+taint_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const
+{
+  return true;
+}
+
+state_machine *
+make_taint_state_machine (logger *logger)
+{
+  return new taint_state_machine (logger);
+}
diff --git a/gcc/analyzer/sm.cc b/gcc/analyzer/sm.cc
new file mode 100644
index 0000000..8efc598
--- /dev/null
+++ b/gcc/analyzer/sm.cc
@@ -0,0 +1,163 @@
+/* Modeling API uses and misuses via state machines.
+   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/>.  */
+/* 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/analyzer.h"
+#include "graphviz.h"
+#include "cgraph.h"
+#include "diagnostic-path.h"
+#include "gcc-rich-location.h"
+#include <string>
+#include "analyzer/supergraph.h"
+#include "analyzer/sm.h"
+
+static const bool DEBUG = false;
+//static const bool DEBUG = true;
+
+////////////////////////////////////////////////////////////////////////////
+
+/* If STMT is an assignment to zero, return the LHS.  */
+
+tree
+is_zero_assignment (const gimple *stmt)
+{
+  const gassign *assign_stmt = dyn_cast <const gassign *> (stmt);
+  if (!assign_stmt)
+    return NULL_TREE;
+
+  enum tree_code op = gimple_assign_rhs_code (assign_stmt);
+  if (op != INTEGER_CST)
+    return NULL_TREE;
+
+  if (!zerop (gimple_assign_rhs1 (assign_stmt)))
+    return NULL_TREE;
+
+  return gimple_assign_lhs (assign_stmt);
+}
+
+/* FIXME.  */
+bool
+is_comparison_against_zero (const gcond *cond_stmt,
+			    tree *out_lhs, enum tree_code *out_op)
+{
+  enum tree_code op = gimple_cond_code (cond_stmt);
+  tree lhs = gimple_cond_lhs (cond_stmt);
+  tree rhs = gimple_cond_rhs (cond_stmt);
+  if (!zerop (rhs))
+    return false;
+  // TODO: make it symmetric?
+
+  switch (op)
+    {
+    case NE_EXPR:
+    case EQ_EXPR:
+      *out_lhs = lhs;
+      *out_op = op;
+      return true;
+
+    default:
+      return false;
+    }
+}
+
+bool
+any_pointer_p (tree var)
+{
+  if (TREE_CODE (TREE_TYPE (var)) != POINTER_TYPE)
+    return false;
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+state_machine::state_t
+state_machine::add_state (const char *name)
+{
+  m_state_names.safe_push (name);
+  return m_state_names.length () - 1;
+}
+
+const char *
+state_machine::get_state_name (state_t s) const
+{
+  return m_state_names[s];
+}
+
+void
+state_machine::validate (state_t s) const
+{
+  gcc_assert (s < m_state_names.length ());
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+void
+make_checkers (auto_delete_vec <state_machine> &out, logger *logger)
+{
+  out.safe_push (make_malloc_state_machine (logger));
+  out.safe_push (make_fileptr_state_machine (logger));
+  out.safe_push (make_taint_state_machine (logger));
+  out.safe_push (make_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 (make_pattern_test_state_machine (logger));
+
+  if (flag_analyzer_checker)
+    {
+      unsigned read_index, write_index;
+      state_machine **sm;
+
+      // FIXME: this leaks the machines
+      // FIXME: would be nice to log the things that were removed
+      VEC_ORDERED_REMOVE_IF (out, read_index, write_index, sm,
+			     0 != strcmp (flag_analyzer_checker,
+					  (*sm)->get_name ()));
+    }
+
+}
diff --git a/gcc/analyzer/sm.h b/gcc/analyzer/sm.h
new file mode 100644
index 0000000..7c6905f
--- /dev/null
+++ b/gcc/analyzer/sm.h
@@ -0,0 +1,160 @@
+/* Modeling API uses and misuses via state machines.
+   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_SM_H
+#define GCC_ANALYZER_SM_H
+
+#include "analyzer/analyzer-logging.h"
+
+class supernode;
+class superedge;
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Utility functions for use by state machines.  */
+
+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;
+class pending_diagnostic;
+
+/* An abstract base class for a state machine describing an API.
+   A mapping from state IDs to names, and various virtual functions
+   for pattern-matching on statements.  */
+
+class state_machine : public log_user
+{
+public:
+  typedef unsigned state_t;
+
+  state_machine (const char *name, logger *logger)
+  : log_user (logger), m_name (name) {}
+
+  virtual ~state_machine () {}
+
+  /* Should states be inherited from a parent region to a child region,
+     when first accessing a child region?
+     For example we should inherit the taintedness of a subregion,
+     but we should not inherit the "malloc:non-null" state of a field
+     within a heap-allocated struct.  */
+  virtual bool inherited_state_p () const = 0;
+
+  const char *get_name () const { return m_name; }
+
+  const char *get_state_name (state_t s) const;
+
+  /* Return true if STMT is a function call recognized by this sm.  */
+  virtual bool on_stmt (sm_context *sm_ctxt,
+			const supernode *node,
+			const gimple *stmt) const = 0;
+
+  virtual void on_edge (sm_context *sm_ctxt,
+			const gimple *last_stmt,
+			const superedge *edge) const = 0;
+
+  virtual void on_leak (sm_context *sm_ctxt,
+			const supernode *node,
+			const gimple *stmt,
+			tree var,
+			state_machine::state_t state) const = 0;
+
+  /* Return true if it safe to discard the given state (to help
+     when simplifying state objects).
+     States that need leak detection should return false.  */
+  virtual bool can_purge_p (state_t s) const = 0;
+
+  void validate (state_t s) const;
+
+protected:
+  state_t add_state (const char *name);
+
+private:
+  const char *m_name;
+  auto_vec<const char *> m_state_names;
+};
+
+/* Is STATE the start state?  (zero is hardcoded as the start state).  */
+
+static inline bool
+start_start_p (state_machine::state_t state)
+{
+  return state == 0;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Abstract base class giving an interface for the state machine to call
+   the checker engine, at a particular stmt.  */
+
+class sm_context
+{
+public:
+  virtual ~sm_context () {}
+
+  /* Called by state_machine in response to pattern matches:
+     if VAR is in state FROM, transition it to state TO, potentially
+     recording the "origin" of the state as ORIGIN.
+     Use NODE and STMT for location information.  */
+   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;
+
+  /* Called by state_machine in response to pattern matches:
+     issue a diagnostic D if VAR is in state STATE, using NODE and STMT
+     for location information.  */
+  virtual void warn_for_state (const supernode *node, const gimple *stmt,
+			       tree var, state_machine::state_t state,
+			       pending_diagnostic *d) = 0;
+
+  virtual tree get_readable_tree (tree expr)
+  {
+    return expr;
+  }
+
+protected:
+  sm_context (const state_machine &sm) : m_sm (sm) {}
+
+  const state_machine &m_sm;
+};
+
+
+////////////////////////////////////////////////////////////////////////////
+
+/* The various state_machine subclasses are hidden in their respective
+   implementation files.  */
+
+extern void make_checkers (auto_delete_vec <state_machine> &out,
+			   logger *logger);
+
+extern state_machine *make_malloc_state_machine (logger *logger);
+extern state_machine *make_fileptr_state_machine (logger *logger);
+extern state_machine *make_taint_state_machine (logger *logger);
+extern state_machine *make_sensitive_state_machine (logger *logger);
+extern state_machine *make_pattern_test_state_machine (logger *logger);
+
+#endif /* GCC_ANALYZER_SM_H */
diff --git a/gcc/analyzer/supergraph.cc b/gcc/analyzer/supergraph.cc
new file mode 100644
index 0000000..b6ef813
--- /dev/null
+++ b/gcc/analyzer/supergraph.cc
@@ -0,0 +1,1017 @@
+/* "Supergraph" classes that combine CFGs and callgraph into one digraph.
+   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 "analyzer/supergraph.h"
+#include "analyzer/analyzer-logging.h"
+#include "tree-diagnostic.h"
+#include "tree-dfa.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;
+}
+
+/* supergraph's ctor.  Walk the callgraph, building supernodes for each
+   CFG basic block, splitting the basic blocks at callsites.  Join
+   together the supernodes with interprocedural and intraprocedural
+   superedges as appropriate.  */
+
+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 ();
+
+      /* Ensure that EDGE_DFS_BACK is correct for every CFG edge in
+	 the supergraph (by doing it per-function).  */
+      auto_cfun sentinel (fun);
+      mark_dfs_back_edges ();
+
+      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);
+	  for (gphi_iterator gpi = gsi_start_phis (bb); !gsi_end_p (gpi);
+	       gsi_next (&gpi))
+	    {
+	      gimple *stmt = gsi_stmt (gpi);
+	      m_stmt_to_node_t.put (stmt, 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);
+	      m_stmt_to_node_t.put (stmt, node_for_stmts);
+	      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);
+	      cfg_superedge *cfg_sedge
+		= add_cfg_edge (src_supernode, dest_supernode, cfg_edge, idx);
+	      m_cfg_edge_to_cfg_superedge.put (cfg_edge, cfg_sedge);
+	    }
+      }
+
+    /* 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,
+			    const dump_args_t &dump_args) 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 (dump_args.m_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, dump_args);
+
+	    if (dump_args.m_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, dump_args);
+
+  /* Terminate "digraph" */
+  gv.outdent ();
+  gv.println ("}");
+}
+
+/* FIXME.  */
+
+void
+supergraph::dump_dot_to_file (FILE *fp, const dump_args_t &dump_args) const
+{
+  pretty_printer *pp = global_dc->printer->clone ();
+  pp_show_color (pp) = 0;
+  /* %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 (pp) = default_tree_printer;
+
+  pp->buffer->stream = fp;
+  dump_dot_to_pp (pp, dump_args);
+  pp_flush (pp);
+  delete pp;
+}
+
+/* FIXME.  */
+
+void
+supergraph::dump_dot (const char *path, const dump_args_t &dump_args) const
+{
+  FILE *fp = fopen (path, "w");
+  dump_dot_to_file (fp, dump_args);
+  fclose (fp);
+}
+
+/* FIXME.  */
+
+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;
+}
+
+/* FIXME.  */
+
+cfg_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 ();
+  cfg_superedge *new_edge;
+  if (stmt && stmt->code == GIMPLE_SWITCH)
+    new_edge = new switch_cfg_superedge (src, dest, e, idx);
+  else
+    new_edge = new cfg_superedge (src, dest, e);
+  add_edge (new_edge);
+  return new_edge;
+}
+
+/* FIXME.  */
+
+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;
+}
+
+/* FIXME.  */
+
+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;
+}
+
+/* FIXME.  */
+
+std::string
+supernode::to_str () const
+{
+  return std::to_string (m_index);
+}
+
+/* FIXME.  */
+
+void
+supernode::dump_dot (graphviz_out *gv, const dump_args_t &args) 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 (args.m_node_annotator)
+    args.m_node_annotator->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 (args.m_node_annotator)
+	args.m_node_annotator->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);
+      if (args.m_node_annotator)
+	args.m_node_annotator->add_stmt_annotations (pp, stmt);
+      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 (args.m_node_annotator)
+	args.m_node_annotator->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;
+}
+
+/* FIXME.  */
+
+unsigned int
+supernode::get_stmt_index (const gimple *stmt) const
+{
+  unsigned i;
+  gimple *iter_stmt;
+  FOR_EACH_VEC_ELT (m_stmts, i, iter_stmt)
+    if (iter_stmt == stmt)
+      return i;
+  gcc_unreachable ();
+}
+
+/* FIXME.  */
+
+void
+superedge::dump_dot (graphviz_out *gv, const dump_args_t &) 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, false);
+
+  pp_printf (gv->m_pp, "\"];\n");
+}
+
+/* If this is an intraprocedural superedge, return the associated
+   CFG edge.  Otherwise, return NULL.  */
+
+::edge
+superedge::get_any_cfg_edge () const
+{
+  if (const cfg_superedge *sub = dyn_cast_cfg_superedge ())
+    return sub->get_cfg_edge ();
+  return NULL;
+}
+
+/* If this is an interprocedural superedge, return the associated
+   cgraph_edge *.  Otherwise, return NULL.  */
+
+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 (bool user_facing) const
+{
+  pretty_printer pp;
+  dump_label_to_pp (&pp, user_facing);
+  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,
+				 bool user_facing) const
+{
+  if (true_value_p ())
+    pp_printf (pp, "true");
+  else if (false_value_p ())
+    pp_printf (pp, "false");
+
+  if (user_facing)
+    return;
+
+  /* Express edge flags as a string with " | " separator.
+     e.g. (flags "FALLTHRU | DFS_BACK").  */
+  if (get_flags ())
+    {
+      pp_string (pp, " (flags ");
+      bool seen_flag = false;
+#define DEF_EDGE_FLAG(NAME,IDX)			\
+  do {						\
+    if (get_flags () & EDGE_##NAME)			\
+      {						\
+	if (seen_flag)				\
+	  pp_string (pp, " | ");			\
+	pp_printf (pp, "%s", (#NAME));		\
+	seen_flag = true;			\
+      }						\
+  } while (0);
+#include "cfg-flags.def"
+#undef DEF_EDGE_FLAG
+      pp_string (pp, ")");
+    }
+
+  /* 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,
+					bool user_facing ATTRIBUTE_UNUSED) 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:");
+}
+
+/* Get the case label for this "switch" superedge.  */
+
+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,
+				       bool user_facing ATTRIBUTE_UNUSED) 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;
+    }
+}
+
+/* Get the function that was called at this interprocedural call/return
+   edge.  */
+
+function *
+callgraph_superedge::get_callee_function () const
+{
+  return m_cedge->callee->get_fun ();
+}
+
+/* Get the calling function at this interprocedural call/return edge.  */
+
+function *
+callgraph_superedge::get_caller_function () const
+{
+  return m_cedge->caller->get_fun ();
+}
+
+/* Get the fndecl that was called at this interprocedural call/return
+   edge.  */
+
+tree
+callgraph_superedge::get_callee_decl () const
+{
+  return get_callee_function ()->decl;
+}
+
+/* Get the calling fndecl at this interprocedural call/return edge.  */
+
+tree
+callgraph_superedge::get_caller_decl () const
+{
+  return get_caller_function ()->decl;
+}
+
+/* FIXME.  */
+
+tree
+callgraph_superedge::get_arg_for_parm (tree parm_to_find,
+				       callsite_expr *out) 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)
+	{
+	  if (out)
+	    *out = callsite_expr::from_zero_based_param (i);
+	  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,
+				       callsite_expr *out) 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)
+    {
+      tree param = gimple_call_arg (get_call_stmt (), i);
+      if (arg_to_find == param)
+	{
+	  if (out)
+	    *out = callsite_expr::from_zero_based_param (i);
+	  //return iter_parm;
+	  // FIXME: should we get at default def:
+	  return ssa_default_def (get_callee_function (), 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.
+   If non-NULL is returned, populate OUT.  */
+//#error
+tree
+callgraph_superedge::map_expr_from_caller_to_callee (tree caller_expr,
+						     callsite_expr *out) const
+{
+  /* Is it an argument (actual param)?  If so, convert to
+     parameter (formal param).  */
+  tree parm = get_parm_for_arg (caller_expr, out);
+  if (parm)
+    return parm;
+  /* Otherwise try return value.  */
+  if (caller_expr == gimple_call_lhs (get_call_stmt ()))
+    {
+      if (out)
+	*out = callsite_expr::from_return_value ();
+      return DECL_RESULT (get_callee_decl ());
+    }
+
+  return NULL_TREE;
+}
+
+/* Map callee_expr back to an expr within the caller, or return NULL_TREE.
+   If non-NULL is returned, populate OUT.  */
+//#error
+
+tree
+callgraph_superedge::map_expr_from_callee_to_caller (tree callee_expr,
+						     callsite_expr *out) const
+{
+  if (callee_expr == NULL_TREE)
+    return NULL_TREE;
+
+  /* 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, out);
+
+  /* Similar for the default SSA name of the PARM_DECL.  */
+  if (TREE_CODE (callee_expr) == SSA_NAME
+      && SSA_NAME_IS_DEFAULT_DEF (callee_expr)
+      && TREE_CODE (SSA_NAME_VAR (callee_expr)) == PARM_DECL)
+    return get_arg_for_parm (SSA_NAME_VAR (callee_expr), out);
+
+  /* Otherwise try return value.  */
+  if (callee_expr == DECL_RESULT (get_callee_decl ()))
+    {
+      if (out)
+	*out = callsite_expr::from_return_value ();
+      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..4988d74
--- /dev/null
+++ b/gcc/analyzer/supergraph.h
@@ -0,0 +1,509 @@
+/* "Supergraph" classes that combine CFGs and callgraph into one digraph.
+   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
+
+#include "ordered-hash-map.h"
+#include "analyzer/digraph.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 supercluster;
+class dot_annotator;
+
+class logger;
+
+/* An enum for discriminating between superedge subclasses.  */
+
+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)
+};
+
+/* A traits struct describing the family of node, edge and digraph
+   classes for supergraphs.  */
+
+struct supergraph_traits
+{
+  typedef supernode node_t;
+  typedef superedge edge_t;
+  typedef supergraph graph_t;
+  struct dump_args_t
+  {
+    dump_args_t (enum supergraph_dot_flags flags,
+		 const dot_annotator *node_annotator)
+    : m_flags (flags),
+      m_node_annotator (node_annotator)
+    {}
+
+    enum supergraph_dot_flags m_flags;
+    const dot_annotator *m_node_annotator;
+  };
+  typedef supercluster cluster_t;
+};
+
+/* A "supergraph" is a directed graph formed by joining together all CFGs,
+   linking them via interprocedural call and return edges.
+
+   Basic blocks are split at callsites, so that a call statement occurs
+   twice: once at the end of a supernode, and a second instance at the
+   start of the next supernode (to handle the return).  */
+
+class supergraph : public digraph<supergraph_traits>
+{
+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));
+  }
+
+  cfg_superedge *get_edge_for_cfg_edge (edge e) const
+  {
+    return (*const_cast <cfg_edge_to_cfg_superedge_t &>
+	      (m_cfg_edge_to_cfg_superedge).get (e));
+  }
+
+  supernode *get_supernode_for_stmt (const gimple *stmt) const
+  {
+    return (*const_cast <stmt_to_node_t &>(m_stmt_to_node_t).get
+	    (const_cast <gimple *> (stmt)));
+  }
+
+  void dump_dot_to_pp (pretty_printer *pp, const dump_args_t &) const;
+  void dump_dot_to_file (FILE *fp, const dump_args_t &) const;
+  void dump_dot (const char *path, const dump_args_t &) 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);
+  cfg_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);
+
+  /* Data.  */
+
+  typedef ordered_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 ordered_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 ordered_hash_map<::edge, cfg_superedge *>
+    cfg_edge_to_cfg_superedge_t;
+  cfg_edge_to_cfg_superedge_t m_cfg_edge_to_cfg_superedge;
+
+  typedef ordered_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 ordered_hash_map<cgraph_edge *, return_superedge *>
+    cgraph_edge_to_return_superedge_t;
+  cgraph_edge_to_return_superedge_t m_cgraph_edge_to_return_superedge;
+
+  typedef ordered_hash_map<gimple *, supernode *> stmt_to_node_t;
+  stmt_to_node_t m_stmt_to_node_t;
+};
+
+/* A node within a supergraph.  */
+
+class supernode : public dnode<supergraph_traits>
+{
+ public:
+  supernode (function *fun, basic_block bb, gcall *returning_call,
+	     gimple_seq phi_nodes, int index)
+  : m_fun (fun), m_bb (bb), m_returning_call (returning_call),
+    m_phi_nodes (phi_nodes), m_index (index)
+  {}
+
+  bool entry_p () const
+  {
+    return m_bb == ENTRY_BLOCK_PTR_FOR_FN (m_fun);
+  }
+
+  bool return_p () const
+  {
+    return m_bb == EXIT_BLOCK_PTR_FOR_FN (m_fun);
+  }
+
+  std::string to_str () const;
+
+  void dump_dot (graphviz_out *gv, const dump_args_t &args) const OVERRIDE;
+  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);
+  }
+
+  unsigned int get_stmt_index (const gimple *stmt) const;
+
+  function * const m_fun; // alternatively could be stored as runs of indices within the supergraph
+  const basic_block m_bb;
+  gcall * const 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;
+  const int m_index; /* unique within the supergraph as a whole.  */
+};
+
+/* An abstract base class encapsulating an edge within a supergraph.
+   Edges can be CFG edges, or calls/returns for callgraph edges.  */
+
+class superedge : public dedge<supergraph_traits>
+{
+ public:
+  virtual ~superedge () {}
+
+  void dump_dot (graphviz_out *gv, const dump_args_t &args) const;
+
+  virtual void dump_label_to_pp (pretty_printer *pp,
+				 bool user_facing) 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 (bool user_facing) const;
+
+  std::string to_str () const;
+
+ protected:
+  superedge (supernode *src, supernode *dest, enum edge_kind kind)
+  : dedge (src, dest),
+    m_kind (kind)
+  {}
+
+ public:
+  const enum edge_kind m_kind;
+};
+
+/* FIXME.  */
+// Either a parm, or the return value.  */
+
+class callsite_expr
+{
+ public:
+  callsite_expr () : m_val (-1) {}
+
+  static callsite_expr from_zero_based_param (int idx)
+  {
+    return callsite_expr (idx + 1);
+  }
+
+  static callsite_expr from_return_value ()
+  {
+    return callsite_expr (0);
+  }
+
+  bool param_p () const
+  {
+    return m_val > 0;
+  }
+
+  bool return_value_p () const
+  {
+    return m_val == 0;
+  }
+
+ private:
+  callsite_expr (int val) : m_val (val) {}
+
+  int m_val; /* 1-based parm, 0 for return value, or -1 for "unknown".  */
+};
+
+/* A subclass of superedge with an associated callgraph edge (either a
+   call or a return).  */
+
+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, bool user_facing) const OVERRIDE;
+
+  function *get_callee_function () const;
+  function *get_caller_function () const;
+  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, callsite_expr *out) const;
+  tree get_parm_for_arg (tree arg, callsite_expr *out) const;
+  tree map_expr_from_caller_to_callee (tree caller_expr,
+				       callsite_expr *out) const;
+  tree map_expr_from_callee_to_caller (tree callee_expr,
+				       callsite_expr *out) const;
+
+  cgraph_edge *const m_cedge;
+};
+
+/* A subclass of superedge representing an interprocedural call.  */
+
+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);
+  }
+};
+
+/* A subclass of superedge represesnting an interprocedural return.  */
+
+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, bool user_facing) const OVERRIDE;
+  cfg_superedge *dyn_cast_cfg_superedge () FINAL OVERRIDE { return this; }
+  const cfg_superedge *dyn_cast_cfg_superedge () const FINAL OVERRIDE { return this; }
+
+  ::edge get_cfg_edge () const { return m_cfg_edge; }
+  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; }
+  int back_edge_p () const { return get_flags () & EDGE_DFS_BACK; }
+
+  tree get_phi_arg (const gphi *phi) const;
+
+ private:
+  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, bool user_facing) const
+    FINAL OVERRIDE;
+
+  gswitch *get_switch_stmt () const
+  {
+    return as_a <gswitch *> (m_src->get_last_stmt ());
+  }
+
+  tree get_case_label () const;
+
+ private:
+  const 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..ac16129
--- /dev/null
+++ b/gcc/analyzer/tristate.cc
@@ -0,0 +1,222 @@
+/* "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/>.  */
+
+#include "config.h"
+#include "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "analyzer/tristate.h"
+#include "selftest.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:
+      if (other.is_true ())
+	return tristate (TS_TRUE);
+      else
+	return tristate (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:
+      if (other.is_false ())
+	return tristate (TS_FALSE);
+      else
+	return tristate (TS_UNKNOWN);
+    case TS_TRUE:
+      return other;
+    case TS_FALSE:
+      return tristate (TS_FALSE);
+    }
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+#define ASSERT_TRISTATE_TRUE(TRISTATE) \
+  SELFTEST_BEGIN_STMT					\
+  ASSERT_EQ (TRISTATE, tristate (tristate::TS_TRUE));	\
+  SELFTEST_END_STMT
+
+#define ASSERT_TRISTATE_FALSE(TRISTATE) \
+  SELFTEST_BEGIN_STMT					\
+  ASSERT_EQ (TRISTATE, tristate (tristate::TS_FALSE));	\
+  SELFTEST_END_STMT
+
+#define ASSERT_TRISTATE_UNKNOWN(TRISTATE) \
+  SELFTEST_BEGIN_STMT						\
+  ASSERT_EQ (TRISTATE, tristate (tristate::TS_UNKNOWN));	\
+  SELFTEST_END_STMT
+
+/* Test tristate's ctors, along with is_*, as_string, operator==, and
+   operator!=.  */
+
+static void
+test_ctors ()
+{
+  tristate u (tristate::TS_UNKNOWN);
+  ASSERT_FALSE (u.is_known ());
+  ASSERT_FALSE (u.is_true ());
+  ASSERT_FALSE (u.is_false ());
+  ASSERT_STREQ (u.as_string (), "UNKNOWN");
+
+  tristate t (tristate::TS_TRUE);
+  ASSERT_TRUE (t.is_known ());
+  ASSERT_TRUE (t.is_true ());
+  ASSERT_FALSE (t.is_false ());
+  ASSERT_STREQ (t.as_string (), "TRUE");
+
+  tristate f (tristate::TS_FALSE);
+  ASSERT_TRUE (f.is_known ());
+  ASSERT_FALSE (f.is_true ());
+  ASSERT_TRUE (f.is_false ());
+  ASSERT_STREQ (f.as_string (), "FALSE");
+
+  ASSERT_EQ (u, u);
+  ASSERT_EQ (t, t);
+  ASSERT_EQ (f, f);
+  ASSERT_NE (u, t);
+  ASSERT_NE (u, f);
+  ASSERT_NE (t, f);
+
+  tristate t2 (true);
+  ASSERT_TRUE (t2.is_true ());
+  ASSERT_EQ (t, t2);
+
+  tristate f2 (false);
+  ASSERT_TRUE (f2.is_false ());
+  ASSERT_EQ (f, f2);
+
+  tristate u2 (tristate::unknown ());
+  ASSERT_TRUE (!u2.is_known ());
+  ASSERT_EQ (u, u2);
+}
+
+/* Test && on tristate instances.  */
+
+static void
+test_and ()
+{
+  ASSERT_TRISTATE_UNKNOWN (tristate::unknown () && tristate::unknown ());
+
+  ASSERT_TRISTATE_FALSE (tristate (false) && tristate (false));
+  ASSERT_TRISTATE_FALSE (tristate (false) && tristate (true));
+  ASSERT_TRISTATE_FALSE (tristate (true) && tristate (false));
+  ASSERT_TRISTATE_TRUE (tristate (true) && tristate (true));
+
+  ASSERT_TRISTATE_UNKNOWN (tristate::unknown () && tristate (true));
+  ASSERT_TRISTATE_UNKNOWN (tristate (true) && tristate::unknown ());
+
+  ASSERT_TRISTATE_FALSE (tristate::unknown () && tristate (false));
+  ASSERT_TRISTATE_FALSE (tristate (false) && tristate::unknown ());
+}
+
+/* Test || on tristate instances.  */
+
+static void
+test_or ()
+{
+  ASSERT_TRISTATE_UNKNOWN (tristate::unknown () || tristate::unknown ());
+
+  ASSERT_TRISTATE_FALSE (tristate (false) || tristate (false));
+  ASSERT_TRISTATE_TRUE (tristate (false) || tristate (true));
+  ASSERT_TRISTATE_TRUE (tristate (true) || tristate (false));
+  ASSERT_TRISTATE_TRUE (tristate (true) || tristate (true));
+
+  ASSERT_TRISTATE_TRUE (tristate::unknown () || tristate (true));
+  ASSERT_TRISTATE_TRUE (tristate (true) || tristate::unknown ());
+
+  ASSERT_TRISTATE_UNKNOWN (tristate::unknown () || tristate (false));
+  ASSERT_TRISTATE_UNKNOWN (tristate (false) || tristate::unknown ());
+}
+
+/* Test ! on tristate instances.  */
+
+static void
+test_not ()
+{
+  ASSERT_TRISTATE_UNKNOWN (!tristate::unknown ());
+  ASSERT_TRISTATE_FALSE (!tristate (true));
+  ASSERT_TRISTATE_TRUE (!tristate (false));
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+analyzer_tristate_cc_tests ()
+{
+  test_ctors ();
+  test_and ();
+  test_or ();
+  test_not ();
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
diff --git a/gcc/analyzer/tristate.h b/gcc/analyzer/tristate.h
new file mode 100644
index 0000000..4678066
--- /dev/null
+++ b/gcc/analyzer/tristate.h
@@ -0,0 +1,80 @@
+/* "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) {}
+  static tristate unknown () { return tristate (TS_UNKNOWN); }
+
+  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;
+  }
+
+  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..74e0f34 100644
--- a/gcc/builtins.def
+++ b/gcc/builtins.def
@@ -1096,4 +1096,30 @@ 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_DUMP_REGION_MODEL,
+	    "__analyzer_dump_region_model",
+	    BUILT_IN_NORMAL, BT_FN_VOID, BT_LAST,
+	    false, false, false, ATTR_NULL, true, true)
+
+DEF_BUILTIN (BUILT_IN_ANALYZER_DUMP_NUM_HEAP_REGIONS,
+	    "__analyzer_dump_num_heap_regions",
+	    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)
+
+DEF_BUILTIN (BUILT_IN_ANALYZER_BREAK, "__analyzer_break", BUILT_IN_NORMAL, BT_FN_VOID, BT_LAST,
+	     false, false, false, ATTR_NULL, true, true)
+
+DEF_BUILTIN (BUILT_IN_ANALYZER_DUMP_EXPLODED_NODES,
+	     "__analyzer_dump_exploded_nodes",
+	     BUILT_IN_NORMAL, BT_FN_VOID_INT, BT_LAST,
+	     false, false, false, ATTR_NULL, true, true)
+
 #undef DEF_BUILTIN
diff --git a/gcc/common.opt b/gcc/common.opt
index ed88ed0..c20e9db 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)
 
@@ -538,6 +541,66 @@ Waggressive-loop-optimizations
 Common Var(warn_aggressive_loop_optimizations) Init(1) Warning
 Warn if a loop with constant number of iterations triggers undefined behavior.
 
+Wanalyzer-double-fclose
+Common Init(1) Warning
+FIXME
+
+Wanalyzer-double-free
+Common Init(1) Warning
+FIXME
+
+Wanalyzer-dereference-of-uninitialized-pointer
+Common Init(1) Warning
+FIXME
+
+Wanalyzer-dereference-of-freed-pointer
+Common Init(1) Warning
+FIXME
+
+Wanalyzer-dereference-of-pointer-in-stale-stack-frame
+Common Init(1) Warning
+FIXME
+
+Wanalyzer-exposure-through-output-file
+Common Init(1) Warning
+FIXME
+
+Wanalyzer-file-leak
+Common Init(1) Warning
+FIXME
+
+Wanalyzer-free-of-non-heap
+Common Init(1) Warning
+FIXME
+
+Wanalyzer-malloc-leak
+Common Init(1) Warning
+FIXME
+
+Wanalyzer-possible-null-argument
+Common Init(1) Warning
+FIXME
+
+Wanalyzer-possible-null-dereference
+Common Init(1) Warning
+FIXME
+
+Wanalyzer-null-argument
+Common Init(1) Warning
+FIXME
+
+Wanalyzer-null-dereference
+Common Init(1) Warning
+FIXME
+
+Wanalyzer-tainted-array-index
+Common Init(1) Warning
+FIXME
+
+Wanalyzer-use-after-free
+Common Init(1) Warning
+FIXME
+
 Warray-bounds
 Common Var(warn_array_bounds) Warning
 Warn if an array is accessed out of bounds.
@@ -989,6 +1052,22 @@ Align the start of loops.
 falign-loops=
 Common RejectNegative Joined Var(str_align_loops) Optimization
 
+fanalyzer-checker=
+Common Joined RejectNegative Var(flag_analyzer_checker)
+Restrict the analyzer to run just the named checker (TODO: docs)
+
+fanalyzer-impl=
+Common Joined RejectNegative UInteger Var(flag_analyzer_impl)
+FIXME: for selecting older implementations of the analyzer, to be removed
+
+fanalyzer-state-purge
+Common Var(flag_analyzer_state_purge) Init(1)
+Purge unneeded state during analysis (TODO: docs)
+
+fanalyzer-state-merge
+Common Var(flag_analyzer_state_merge) Init(1)
+FIXME: merge similar-enough states during analysis (TODO: docs)
+
 fargument-alias
 Common Ignore
 Does nothing. Preserved for backward compatibility.
@@ -1358,6 +1437,42 @@ 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)
+FIXME
+
+fdump-analyzer-summaries
+Common RejectNegative Var(flag_dump_analyzer_summaries)
+FIXME
+
+fdump-analyzer-supergraph
+Common RejectNegative Var(flag_dump_analyzer_supergraph)
+FIXME
+
+fdump-analyzer-exploded-graph
+Common RejectNegative Var(flag_dump_analyzer_exploded_graph)
+FIXME
+
+fdump-analyzer-exploded-nodes
+Common RejectNegative Var(flag_dump_analyzer_exploded_nodes)
+FIXME
+
+fdump-analyzer-exploded-nodes-2
+Common RejectNegative Var(flag_dump_analyzer_exploded_nodes_2)
+FIXME
+
+fanalyzer-verbosity=
+Common Joined UInteger Var(analyzer_verbosity) Init(2)
+FIXME
+
+fanalyzer-verbose-edges
+Common Var(flag_analyzer_verbose_edges) Init(0)
+FIXME
+
+fanalyzer-verbose-state-changes
+Common Var(flag_analyzer_verbose_state_changes) Init(0)
+FIXME
+
 fdump-final-insns
 Driver RejectNegative
 
diff --git a/gcc/gcc.c b/gcc/gcc.c
index 0a22d2a..95fca22 100644
--- a/gcc/gcc.c
+++ b/gcc/gcc.c
@@ -4253,6 +4253,21 @@ driver_handle_option (struct gcc_options *opts,
       handle_foffload_option (arg);
       break;
 
+    case OPT__analyzer:
+      {
+	validated = true;
+	// FIXME:
+	save_switch (
+#if CHECKING_P
+		     "-iplugindir=/home/david/more-coding/gcc-git-analysis-output/build/gcc",
+#else
+		     "-iplugindir=/home/david/more-coding/gcc-git-analysis-output/build-release/gcc",
+#endif
+		     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/params.def b/gcc/params.def
index 00389d8..5677f68 100644
--- a/gcc/params.def
+++ b/gcc/params.def
@@ -1378,6 +1378,11 @@ DEFPARAM(PARAM_LOOP_VERSIONING_MAX_OUTER_INSNS,
 	 " loops.",
 	 100, 0, 0)
 
+DEFPARAM(PARAM_ANALYZER_BB_EXPLOSION_FACTOR,
+	 "analyzer-bb-explosion-factor",
+	 "FIXME.",
+	 5, 1, 0)
+
 /*
 
 Local variables:
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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/analyzer/CVE-2005-1689-minimal.c b/gcc/testsuite/gcc.dg/analyzer/CVE-2005-1689-minimal.c
new file mode 100644
index 0000000..1e5a581
--- /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); /* { dg-message "first 'free' here" } */
+  free(inbuf.data); /* { dg-warning "double-free of 'inbuf.data'" } */
+}
+
+void
+test_2 (krb5_data inbuf, int flag)
+{
+  if (flag) {
+    free(inbuf.data); /* { dg-message "first 'free' here" } */
+  }
+  free(inbuf.data); /* { dg-warning "double-free of 'inbuf.data'" } */
+}
+
+void
+test_3 (krb5_data inbuf, int flag)
+{
+  if (flag) {
+    free((char *)inbuf.data); /* { dg-message "first 'free' here" } */
+  }
+  free((char *)inbuf.data); /* { dg-warning "double-free of 'inbuf.data'" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-CVE-2005-1689-krb5-recvauth.c b/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-CVE-2005-1689-krb5-recvauth.c
new file mode 100644
index 0000000..d7d33b8
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-CVE-2005-1689-krb5-recvauth.c
@@ -0,0 +1,10854 @@
+/* { dg-additional-options "-fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fdiagnostics-path-format=inline-events" } */
+
+typedef unsigned char __u_char;
+typedef unsigned short int __u_short;
+typedef unsigned int __u_int;
+typedef unsigned long int __u_long;
+
+
+typedef signed char __int8_t;
+typedef unsigned char __uint8_t;
+typedef signed short int __int16_t;
+typedef unsigned short int __uint16_t;
+typedef signed int __int32_t;
+typedef unsigned int __uint32_t;
+
+typedef signed long int __int64_t;
+typedef unsigned long int __uint64_t;
+
+
+
+
+
+
+
+typedef long int __quad_t;
+typedef unsigned long int __u_quad_t;
+
+
+typedef unsigned long int __dev_t;
+typedef unsigned int __uid_t;
+typedef unsigned int __gid_t;
+typedef unsigned long int __ino_t;
+typedef unsigned long int __ino64_t;
+typedef unsigned int __mode_t;
+typedef unsigned long int __nlink_t;
+typedef long int __off_t;
+typedef long int __off64_t;
+typedef int __pid_t;
+typedef struct { int __val[2]; } __fsid_t;
+typedef long int __clock_t;
+typedef unsigned long int __rlim_t;
+typedef unsigned long int __rlim64_t;
+typedef unsigned int __id_t;
+typedef long int __time_t;
+typedef unsigned int __useconds_t;
+typedef long int __suseconds_t;
+
+typedef int __daddr_t;
+typedef int __key_t;
+
+
+typedef int __clockid_t;
+
+
+typedef void * __timer_t;
+
+
+typedef long int __blksize_t;
+
+
+
+
+typedef long int __blkcnt_t;
+typedef long int __blkcnt64_t;
+
+
+typedef unsigned long int __fsblkcnt_t;
+typedef unsigned long int __fsblkcnt64_t;
+
+
+typedef unsigned long int __fsfilcnt_t;
+typedef unsigned long int __fsfilcnt64_t;
+
+
+typedef long int __fsword_t;
+
+typedef long int __ssize_t;
+
+
+typedef long int __syscall_slong_t;
+
+typedef unsigned long int __syscall_ulong_t;
+
+
+
+typedef __off64_t __loff_t;
+typedef __quad_t *__qaddr_t;
+typedef char *__caddr_t;
+
+
+typedef long int __intptr_t;
+
+
+typedef unsigned int __socklen_t;
+
+
+
+typedef __u_char u_char;
+typedef __u_short u_short;
+typedef __u_int u_int;
+typedef __u_long u_long;
+typedef __quad_t quad_t;
+typedef __u_quad_t u_quad_t;
+typedef __fsid_t fsid_t;
+
+
+
+
+typedef __loff_t loff_t;
+
+
+
+typedef __ino_t ino_t;
+typedef __dev_t dev_t;
+
+
+
+
+typedef __gid_t gid_t;
+
+
+
+
+typedef __mode_t mode_t;
+
+
+
+
+typedef __nlink_t nlink_t;
+
+
+
+
+typedef __uid_t uid_t;
+
+
+
+
+
+typedef __off_t off_t;
+typedef __pid_t pid_t;
+
+
+
+
+
+typedef __id_t id_t;
+
+
+
+
+typedef __ssize_t ssize_t;
+
+
+
+
+
+typedef __daddr_t daddr_t;
+typedef __caddr_t caddr_t;
+
+
+
+
+
+typedef __key_t key_t;
+
+
+typedef __clock_t clock_t;
+
+
+
+
+
+typedef __time_t time_t;
+
+
+
+typedef __clockid_t clockid_t;
+typedef __timer_t timer_t;
+typedef long unsigned int size_t;
+
+
+
+typedef unsigned long int ulong;
+typedef unsigned short int ushort;
+typedef unsigned int uint;
+typedef int int8_t __attribute__ ((__mode__ (__QI__)));
+typedef int int16_t __attribute__ ((__mode__ (__HI__)));
+typedef int int32_t __attribute__ ((__mode__ (__SI__)));
+typedef int int64_t __attribute__ ((__mode__ (__DI__)));
+
+
+typedef unsigned int u_int8_t __attribute__ ((__mode__ (__QI__)));
+typedef unsigned int u_int16_t __attribute__ ((__mode__ (__HI__)));
+typedef unsigned int u_int32_t __attribute__ ((__mode__ (__SI__)));
+typedef unsigned int u_int64_t __attribute__ ((__mode__ (__DI__)));
+
+typedef int register_t __attribute__ ((__mode__ (__word__)));
+
+
+
+
+
+
+static __inline unsigned int
+__bswap_32 (unsigned int __bsx)
+{
+  return __builtin_bswap32 (__bsx);
+}
+static __inline __uint64_t
+__bswap_64 (__uint64_t __bsx)
+{
+  return __builtin_bswap64 (__bsx);
+}
+
+
+
+
+typedef int __sig_atomic_t;
+
+
+
+
+typedef struct
+  {
+    unsigned long int __val[(1024 / (8 * sizeof (unsigned long int)))];
+  } __sigset_t;
+
+
+
+typedef __sigset_t sigset_t;
+
+
+
+
+
+struct timespec
+  {
+    __time_t tv_sec;
+    __syscall_slong_t tv_nsec;
+  };
+
+struct timeval
+  {
+    __time_t tv_sec;
+    __suseconds_t tv_usec;
+  };
+
+
+typedef __suseconds_t suseconds_t;
+
+
+
+
+
+typedef long int __fd_mask;
+typedef struct
+  {
+
+
+
+
+
+
+    __fd_mask __fds_bits[1024 / (8 * (int) sizeof (__fd_mask))];
+
+
+  } fd_set;
+
+
+
+
+
+
+typedef __fd_mask fd_mask;
+
+extern int select (int __nfds, fd_set *__restrict __readfds,
+     fd_set *__restrict __writefds,
+     fd_set *__restrict __exceptfds,
+     struct timeval *__restrict __timeout);
+extern int pselect (int __nfds, fd_set *__restrict __readfds,
+      fd_set *__restrict __writefds,
+      fd_set *__restrict __exceptfds,
+      const struct timespec *__restrict __timeout,
+      const __sigset_t *__restrict __sigmask);
+
+
+
+
+
+__extension__
+extern unsigned int gnu_dev_major (unsigned long long int __dev)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__));
+__extension__
+extern unsigned int gnu_dev_minor (unsigned long long int __dev)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__));
+__extension__
+extern unsigned long long int gnu_dev_makedev (unsigned int __major,
+            unsigned int __minor)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__));
+
+
+__extension__ extern __inline __attribute__ ((__gnu_inline__)) __attribute__ ((__const__)) unsigned int
+__attribute__ ((__nothrow__ , __leaf__)) gnu_dev_major (unsigned long long int __dev)
+{
+  return ((__dev >> 8) & 0xfff) | ((unsigned int) (__dev >> 32) & ~0xfff);
+}
+
+__extension__ extern __inline __attribute__ ((__gnu_inline__)) __attribute__ ((__const__)) unsigned int
+__attribute__ ((__nothrow__ , __leaf__)) gnu_dev_minor (unsigned long long int __dev)
+{
+  return (__dev & 0xff) | ((unsigned int) (__dev >> 12) & ~0xff);
+}
+
+__extension__ extern __inline __attribute__ ((__gnu_inline__)) __attribute__ ((__const__)) unsigned long long int
+__attribute__ ((__nothrow__ , __leaf__)) gnu_dev_makedev (unsigned int __major, unsigned int __minor)
+{
+  return ((__minor & 0xff) | ((__major & 0xfff) << 8)
+   | (((unsigned long long int) (__minor & ~0xff)) << 12)
+   | (((unsigned long long int) (__major & ~0xfff)) << 32));
+}
+
+
+
+
+
+
+
+typedef __blksize_t blksize_t;
+
+
+
+
+
+
+typedef __blkcnt_t blkcnt_t;
+
+
+
+typedef __fsblkcnt_t fsblkcnt_t;
+
+
+
+typedef __fsfilcnt_t fsfilcnt_t;
+typedef unsigned long int pthread_t;
+
+
+union pthread_attr_t
+{
+  char __size[56];
+  long int __align;
+};
+
+typedef union pthread_attr_t pthread_attr_t;
+
+
+
+
+
+typedef struct __pthread_internal_list
+{
+  struct __pthread_internal_list *__prev;
+  struct __pthread_internal_list *__next;
+} __pthread_list_t;
+typedef union
+{
+  struct __pthread_mutex_s
+  {
+    int __lock;
+    unsigned int __count;
+    int __owner;
+
+    unsigned int __nusers;
+
+
+
+    int __kind;
+
+    short __spins;
+    short __elision;
+    __pthread_list_t __list;
+  } __data;
+  char __size[40];
+  long int __align;
+} pthread_mutex_t;
+
+typedef union
+{
+  char __size[4];
+  int __align;
+} pthread_mutexattr_t;
+
+
+
+
+typedef union
+{
+  struct
+  {
+    int __lock;
+    unsigned int __futex;
+    __extension__ unsigned long long int __total_seq;
+    __extension__ unsigned long long int __wakeup_seq;
+    __extension__ unsigned long long int __woken_seq;
+    void *__mutex;
+    unsigned int __nwaiters;
+    unsigned int __broadcast_seq;
+  } __data;
+  char __size[48];
+  __extension__ long long int __align;
+} pthread_cond_t;
+
+typedef union
+{
+  char __size[4];
+  int __align;
+} pthread_condattr_t;
+
+
+
+typedef unsigned int pthread_key_t;
+
+
+
+typedef int pthread_once_t;
+
+
+
+
+
+typedef union
+{
+
+  struct
+  {
+    int __lock;
+    unsigned int __nr_readers;
+    unsigned int __readers_wakeup;
+    unsigned int __writer_wakeup;
+    unsigned int __nr_readers_queued;
+    unsigned int __nr_writers_queued;
+    int __writer;
+    int __shared;
+    unsigned long int __pad1;
+    unsigned long int __pad2;
+
+
+    unsigned int __flags;
+
+  } __data;
+  char __size[56];
+  long int __align;
+} pthread_rwlock_t;
+
+typedef union
+{
+  char __size[8];
+  long int __align;
+} pthread_rwlockattr_t;
+
+
+
+
+
+typedef volatile int pthread_spinlock_t;
+
+
+
+
+typedef union
+{
+  char __size[32];
+  long int __align;
+} pthread_barrier_t;
+
+typedef union
+{
+  char __size[4];
+  int __align;
+} pthread_barrierattr_t;
+
+
+
+
+
+
+extern void __assert_fail (const char *__assertion, const char *__file,
+      unsigned int __line, const char *__function)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__noreturn__));
+
+
+extern void __assert_perror_fail (int __errnum, const char *__file,
+      unsigned int __line, const char *__function)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__noreturn__));
+
+
+
+
+extern void __assert (const char *__assertion, const char *__file, int __line)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__noreturn__));
+
+
+
+
+
+
+
+typedef struct {
+    const char *filename;
+    short lineno;
+} k5_debug_loc;
+typedef char k5_debug_mutex_stats;
+enum k5_mutex_init_states {
+    K5_MUTEX_DEBUG_PARTLY_INITIALIZED = 0x12,
+    K5_MUTEX_DEBUG_INITIALIZED,
+    K5_MUTEX_DEBUG_DESTROYED
+};
+enum k5_mutex_flag_states {
+    K5_MUTEX_DEBUG_UNLOCKED = 0x23,
+    K5_MUTEX_DEBUG_LOCKED
+};
+
+typedef struct {
+    enum k5_mutex_init_states initialized;
+    enum k5_mutex_flag_states locked;
+} k5_os_nothread_mutex;
+typedef unsigned char k5_os_nothread_once_t;
+
+
+
+
+struct sched_param
+  {
+    int __sched_priority;
+  };
+
+
+
+
+
+
+
+
+
+
+struct __sched_param
+  {
+    int __sched_priority;
+  };
+typedef unsigned long int __cpu_mask;
+
+
+
+
+
+
+typedef struct
+{
+  __cpu_mask __bits[1024 / (8 * sizeof (__cpu_mask))];
+} cpu_set_t;
+
+
+extern int __sched_cpucount (size_t __setsize, const cpu_set_t *__setp)
+  __attribute__ ((__nothrow__ , __leaf__));
+extern cpu_set_t *__sched_cpualloc (size_t __count) __attribute__ ((__nothrow__ , __leaf__)) ;
+extern void __sched_cpufree (cpu_set_t *__set) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+
+
+
+extern int sched_setparam (__pid_t __pid, const struct sched_param *__param)
+     __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern int sched_getparam (__pid_t __pid, struct sched_param *__param) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern int sched_setscheduler (__pid_t __pid, int __policy,
+          const struct sched_param *__param) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern int sched_getscheduler (__pid_t __pid) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern int sched_yield (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern int sched_get_priority_max (int __algorithm) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern int sched_get_priority_min (int __algorithm) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern int sched_rr_get_interval (__pid_t __pid, struct timespec *__t) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+struct tm
+{
+  int tm_sec;
+  int tm_min;
+  int tm_hour;
+  int tm_mday;
+  int tm_mon;
+  int tm_year;
+  int tm_wday;
+  int tm_yday;
+  int tm_isdst;
+
+
+  long int tm_gmtoff;
+  const char *tm_zone;
+
+
+
+
+};
+
+
+
+
+
+
+
+
+struct itimerspec
+  {
+    struct timespec it_interval;
+    struct timespec it_value;
+  };
+
+
+struct sigevent;
+
+
+
+extern clock_t clock (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern time_t time (time_t *__timer) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern double difftime (time_t __time1, time_t __time0)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__));
+
+
+extern time_t mktime (struct tm *__tp) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+extern size_t strftime (char *__restrict __s, size_t __maxsize,
+   const char *__restrict __format,
+   const struct tm *__restrict __tp) __attribute__ ((__nothrow__ , __leaf__));
+
+typedef struct __locale_struct
+{
+
+  struct __locale_data *__locales[13];
+
+
+  const unsigned short int *__ctype_b;
+  const int *__ctype_tolower;
+  const int *__ctype_toupper;
+
+
+  const char *__names[13];
+} *__locale_t;
+
+
+typedef __locale_t locale_t;
+
+extern size_t strftime_l (char *__restrict __s, size_t __maxsize,
+     const char *__restrict __format,
+     const struct tm *__restrict __tp,
+     __locale_t __loc) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+extern struct tm *gmtime (const time_t *__timer) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+extern struct tm *localtime (const time_t *__timer) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+extern struct tm *gmtime_r (const time_t *__restrict __timer,
+       struct tm *__restrict __tp) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+extern struct tm *localtime_r (const time_t *__restrict __timer,
+          struct tm *__restrict __tp) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+extern char *asctime (const struct tm *__tp) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern char *ctime (const time_t *__timer) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+
+extern char *asctime_r (const struct tm *__restrict __tp,
+   char *__restrict __buf) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern char *ctime_r (const time_t *__restrict __timer,
+        char *__restrict __buf) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+extern char *__tzname[2];
+extern int __daylight;
+extern long int __timezone;
+
+
+
+
+extern char *tzname[2];
+
+
+
+extern void tzset (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+extern int daylight;
+extern long int timezone;
+
+
+
+
+
+extern int stime (const time_t *__when) __attribute__ ((__nothrow__ , __leaf__));
+extern time_t timegm (struct tm *__tp) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern time_t timelocal (struct tm *__tp) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern int dysize (int __year) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__));
+extern int nanosleep (const struct timespec *__requested_time,
+        struct timespec *__remaining);
+
+
+
+extern int clock_getres (clockid_t __clock_id, struct timespec *__res) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern int clock_gettime (clockid_t __clock_id, struct timespec *__tp) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern int clock_settime (clockid_t __clock_id, const struct timespec *__tp)
+     __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+extern int clock_nanosleep (clockid_t __clock_id, int __flags,
+       const struct timespec *__req,
+       struct timespec *__rem);
+
+
+extern int clock_getcpuclockid (pid_t __pid, clockid_t *__clock_id) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+extern int timer_create (clockid_t __clock_id,
+    struct sigevent *__restrict __evp,
+    timer_t *__restrict __timerid) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern int timer_delete (timer_t __timerid) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern int timer_settime (timer_t __timerid, int __flags,
+     const struct itimerspec *__restrict __value,
+     struct itimerspec *__restrict __ovalue) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern int timer_gettime (timer_t __timerid, struct itimerspec *__value)
+     __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern int timer_getoverrun (timer_t __timerid) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+extern int timespec_get (struct timespec *__ts, int __base)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+
+
+typedef long int __jmp_buf[8];
+
+
+
+enum
+{
+  PTHREAD_CREATE_JOINABLE,
+
+  PTHREAD_CREATE_DETACHED
+
+};
+
+
+
+enum
+{
+  PTHREAD_MUTEX_TIMED_NP,
+  PTHREAD_MUTEX_RECURSIVE_NP,
+  PTHREAD_MUTEX_ERRORCHECK_NP,
+  PTHREAD_MUTEX_ADAPTIVE_NP
+
+  ,
+  PTHREAD_MUTEX_NORMAL = PTHREAD_MUTEX_TIMED_NP,
+  PTHREAD_MUTEX_RECURSIVE = PTHREAD_MUTEX_RECURSIVE_NP,
+  PTHREAD_MUTEX_ERRORCHECK = PTHREAD_MUTEX_ERRORCHECK_NP,
+  PTHREAD_MUTEX_DEFAULT = PTHREAD_MUTEX_NORMAL
+
+
+
+
+
+};
+
+
+
+
+enum
+{
+  PTHREAD_MUTEX_STALLED,
+  PTHREAD_MUTEX_STALLED_NP = PTHREAD_MUTEX_STALLED,
+  PTHREAD_MUTEX_ROBUST,
+  PTHREAD_MUTEX_ROBUST_NP = PTHREAD_MUTEX_ROBUST
+};
+
+
+
+
+
+enum
+{
+  PTHREAD_PRIO_NONE,
+  PTHREAD_PRIO_INHERIT,
+  PTHREAD_PRIO_PROTECT
+};
+enum
+{
+  PTHREAD_RWLOCK_PREFER_READER_NP,
+  PTHREAD_RWLOCK_PREFER_WRITER_NP,
+  PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP,
+  PTHREAD_RWLOCK_DEFAULT_NP = PTHREAD_RWLOCK_PREFER_READER_NP
+};
+enum
+{
+  PTHREAD_INHERIT_SCHED,
+
+  PTHREAD_EXPLICIT_SCHED
+
+};
+
+
+
+enum
+{
+  PTHREAD_SCOPE_SYSTEM,
+
+  PTHREAD_SCOPE_PROCESS
+
+};
+
+
+
+enum
+{
+  PTHREAD_PROCESS_PRIVATE,
+
+  PTHREAD_PROCESS_SHARED
+
+};
+struct _pthread_cleanup_buffer
+{
+  void (*__routine) (void *);
+  void *__arg;
+  int __canceltype;
+  struct _pthread_cleanup_buffer *__prev;
+};
+
+
+enum
+{
+  PTHREAD_CANCEL_ENABLE,
+
+  PTHREAD_CANCEL_DISABLE
+
+};
+enum
+{
+  PTHREAD_CANCEL_DEFERRED,
+
+  PTHREAD_CANCEL_ASYNCHRONOUS
+
+};
+
+
+
+
+
+extern int pthread_create (pthread_t *__restrict __newthread,
+      const pthread_attr_t *__restrict __attr,
+      void *(*__start_routine) (void *),
+      void *__restrict __arg) __attribute__ ((__nothrow__)) __attribute__ ((__nonnull__ (1, 3)));
+
+
+
+
+
+extern void pthread_exit (void *__retval) __attribute__ ((__noreturn__));
+
+
+
+
+
+
+
+extern int pthread_join (pthread_t __th, void **__thread_return);
+extern int pthread_detach (pthread_t __th) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+extern pthread_t pthread_self (void) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__));
+
+
+extern int pthread_equal (pthread_t __thread1, pthread_t __thread2)
+  __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__));
+
+
+
+
+
+
+
+extern int pthread_attr_init (pthread_attr_t *__attr) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int pthread_attr_destroy (pthread_attr_t *__attr)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int pthread_attr_getdetachstate (const pthread_attr_t *__attr,
+     int *__detachstate)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern int pthread_attr_setdetachstate (pthread_attr_t *__attr,
+     int __detachstate)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+extern int pthread_attr_getguardsize (const pthread_attr_t *__attr,
+          size_t *__guardsize)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern int pthread_attr_setguardsize (pthread_attr_t *__attr,
+          size_t __guardsize)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+extern int pthread_attr_getschedparam (const pthread_attr_t *__restrict __attr,
+           struct sched_param *__restrict __param)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern int pthread_attr_setschedparam (pthread_attr_t *__restrict __attr,
+           const struct sched_param *__restrict
+           __param) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern int pthread_attr_getschedpolicy (const pthread_attr_t *__restrict
+     __attr, int *__restrict __policy)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern int pthread_attr_setschedpolicy (pthread_attr_t *__attr, int __policy)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int pthread_attr_getinheritsched (const pthread_attr_t *__restrict
+      __attr, int *__restrict __inherit)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern int pthread_attr_setinheritsched (pthread_attr_t *__attr,
+      int __inherit)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+extern int pthread_attr_getscope (const pthread_attr_t *__restrict __attr,
+      int *__restrict __scope)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern int pthread_attr_setscope (pthread_attr_t *__attr, int __scope)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int pthread_attr_getstackaddr (const pthread_attr_t *__restrict
+          __attr, void **__restrict __stackaddr)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2))) __attribute__ ((__deprecated__));
+
+
+
+
+
+extern int pthread_attr_setstackaddr (pthread_attr_t *__attr,
+          void *__stackaddr)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) __attribute__ ((__deprecated__));
+
+
+extern int pthread_attr_getstacksize (const pthread_attr_t *__restrict
+          __attr, size_t *__restrict __stacksize)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+
+extern int pthread_attr_setstacksize (pthread_attr_t *__attr,
+          size_t __stacksize)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+extern int pthread_attr_getstack (const pthread_attr_t *__restrict __attr,
+      void **__restrict __stackaddr,
+      size_t *__restrict __stacksize)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2, 3)));
+
+
+
+
+extern int pthread_attr_setstack (pthread_attr_t *__attr, void *__stackaddr,
+      size_t __stacksize) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+extern int pthread_setschedparam (pthread_t __target_thread, int __policy,
+      const struct sched_param *__param)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (3)));
+
+
+extern int pthread_getschedparam (pthread_t __target_thread,
+      int *__restrict __policy,
+      struct sched_param *__restrict __param)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2, 3)));
+
+
+extern int pthread_setschedprio (pthread_t __target_thread, int __prio)
+     __attribute__ ((__nothrow__ , __leaf__));
+extern int pthread_once (pthread_once_t *__once_control,
+    void (*__init_routine) (void)) __attribute__ ((__nonnull__ (1, 2)));
+extern int pthread_setcancelstate (int __state, int *__oldstate);
+
+
+
+extern int pthread_setcanceltype (int __type, int *__oldtype);
+
+
+extern int pthread_cancel (pthread_t __th);
+
+
+
+
+extern void pthread_testcancel (void);
+
+
+
+
+typedef struct
+{
+  struct
+  {
+    __jmp_buf __cancel_jmp_buf;
+    int __mask_was_saved;
+  } __cancel_jmp_buf[1];
+  void *__pad[4];
+} __pthread_unwind_buf_t __attribute__ ((__aligned__));
+struct __pthread_cleanup_frame
+{
+  void (*__cancel_routine) (void *);
+  void *__cancel_arg;
+  int __do_it;
+  int __cancel_type;
+};
+extern void __pthread_register_cancel (__pthread_unwind_buf_t *__buf)
+     ;
+extern void __pthread_unregister_cancel (__pthread_unwind_buf_t *__buf)
+  ;
+extern void __pthread_unwind_next (__pthread_unwind_buf_t *__buf)
+     __attribute__ ((__noreturn__))
+
+
+
+     ;
+
+
+
+struct __jmp_buf_tag;
+extern int __sigsetjmp (struct __jmp_buf_tag *__env, int __savemask) __attribute__ ((__nothrow__));
+
+
+
+
+
+extern int pthread_mutex_init (pthread_mutex_t *__mutex,
+          const pthread_mutexattr_t *__mutexattr)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int pthread_mutex_destroy (pthread_mutex_t *__mutex)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int pthread_mutex_trylock (pthread_mutex_t *__mutex)
+     __attribute__ ((__nothrow__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int pthread_mutex_lock (pthread_mutex_t *__mutex)
+     __attribute__ ((__nothrow__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+extern int pthread_mutex_timedlock (pthread_mutex_t *__restrict __mutex,
+        const struct timespec *__restrict
+        __abstime) __attribute__ ((__nothrow__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+extern int pthread_mutex_unlock (pthread_mutex_t *__mutex)
+     __attribute__ ((__nothrow__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+extern int pthread_mutex_getprioceiling (const pthread_mutex_t *
+      __restrict __mutex,
+      int *__restrict __prioceiling)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+extern int pthread_mutex_setprioceiling (pthread_mutex_t *__restrict __mutex,
+      int __prioceiling,
+      int *__restrict __old_ceiling)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 3)));
+
+
+
+
+extern int pthread_mutex_consistent (pthread_mutex_t *__mutex)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+extern int pthread_mutexattr_init (pthread_mutexattr_t *__attr)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int pthread_mutexattr_destroy (pthread_mutexattr_t *__attr)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int pthread_mutexattr_getpshared (const pthread_mutexattr_t *
+      __restrict __attr,
+      int *__restrict __pshared)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern int pthread_mutexattr_setpshared (pthread_mutexattr_t *__attr,
+      int __pshared)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+extern int pthread_mutexattr_gettype (const pthread_mutexattr_t *__restrict
+          __attr, int *__restrict __kind)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+
+extern int pthread_mutexattr_settype (pthread_mutexattr_t *__attr, int __kind)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+extern int pthread_mutexattr_getprotocol (const pthread_mutexattr_t *
+       __restrict __attr,
+       int *__restrict __protocol)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+extern int pthread_mutexattr_setprotocol (pthread_mutexattr_t *__attr,
+       int __protocol)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int pthread_mutexattr_getprioceiling (const pthread_mutexattr_t *
+          __restrict __attr,
+          int *__restrict __prioceiling)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern int pthread_mutexattr_setprioceiling (pthread_mutexattr_t *__attr,
+          int __prioceiling)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+extern int pthread_mutexattr_getrobust (const pthread_mutexattr_t *__attr,
+     int *__robustness)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+
+
+
+
+extern int pthread_mutexattr_setrobust (pthread_mutexattr_t *__attr,
+     int __robustness)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+extern int pthread_rwlock_init (pthread_rwlock_t *__restrict __rwlock,
+    const pthread_rwlockattr_t *__restrict
+    __attr) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int pthread_rwlock_destroy (pthread_rwlock_t *__rwlock)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int pthread_rwlock_rdlock (pthread_rwlock_t *__rwlock)
+     __attribute__ ((__nothrow__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int pthread_rwlock_tryrdlock (pthread_rwlock_t *__rwlock)
+  __attribute__ ((__nothrow__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+extern int pthread_rwlock_timedrdlock (pthread_rwlock_t *__restrict __rwlock,
+           const struct timespec *__restrict
+           __abstime) __attribute__ ((__nothrow__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+extern int pthread_rwlock_wrlock (pthread_rwlock_t *__rwlock)
+     __attribute__ ((__nothrow__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int pthread_rwlock_trywrlock (pthread_rwlock_t *__rwlock)
+     __attribute__ ((__nothrow__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+extern int pthread_rwlock_timedwrlock (pthread_rwlock_t *__restrict __rwlock,
+           const struct timespec *__restrict
+           __abstime) __attribute__ ((__nothrow__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+extern int pthread_rwlock_unlock (pthread_rwlock_t *__rwlock)
+     __attribute__ ((__nothrow__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+extern int pthread_rwlockattr_init (pthread_rwlockattr_t *__attr)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int pthread_rwlockattr_destroy (pthread_rwlockattr_t *__attr)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int pthread_rwlockattr_getpshared (const pthread_rwlockattr_t *
+       __restrict __attr,
+       int *__restrict __pshared)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern int pthread_rwlockattr_setpshared (pthread_rwlockattr_t *__attr,
+       int __pshared)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int pthread_rwlockattr_getkind_np (const pthread_rwlockattr_t *
+       __restrict __attr,
+       int *__restrict __pref)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern int pthread_rwlockattr_setkind_np (pthread_rwlockattr_t *__attr,
+       int __pref) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+
+
+extern int pthread_cond_init (pthread_cond_t *__restrict __cond,
+         const pthread_condattr_t *__restrict __cond_attr)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int pthread_cond_destroy (pthread_cond_t *__cond)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int pthread_cond_signal (pthread_cond_t *__cond)
+     __attribute__ ((__nothrow__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int pthread_cond_broadcast (pthread_cond_t *__cond)
+     __attribute__ ((__nothrow__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+
+extern int pthread_cond_wait (pthread_cond_t *__restrict __cond,
+         pthread_mutex_t *__restrict __mutex)
+     __attribute__ ((__nonnull__ (1, 2)));
+extern int pthread_cond_timedwait (pthread_cond_t *__restrict __cond,
+       pthread_mutex_t *__restrict __mutex,
+       const struct timespec *__restrict __abstime)
+     __attribute__ ((__nonnull__ (1, 2, 3)));
+
+
+
+
+extern int pthread_condattr_init (pthread_condattr_t *__attr)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int pthread_condattr_destroy (pthread_condattr_t *__attr)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int pthread_condattr_getpshared (const pthread_condattr_t *
+     __restrict __attr,
+     int *__restrict __pshared)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern int pthread_condattr_setpshared (pthread_condattr_t *__attr,
+     int __pshared) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+extern int pthread_condattr_getclock (const pthread_condattr_t *
+          __restrict __attr,
+          __clockid_t *__restrict __clock_id)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern int pthread_condattr_setclock (pthread_condattr_t *__attr,
+          __clockid_t __clock_id)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+extern int pthread_spin_init (pthread_spinlock_t *__lock, int __pshared)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int pthread_spin_destroy (pthread_spinlock_t *__lock)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int pthread_spin_lock (pthread_spinlock_t *__lock)
+     __attribute__ ((__nothrow__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int pthread_spin_trylock (pthread_spinlock_t *__lock)
+     __attribute__ ((__nothrow__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int pthread_spin_unlock (pthread_spinlock_t *__lock)
+     __attribute__ ((__nothrow__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+
+extern int pthread_barrier_init (pthread_barrier_t *__restrict __barrier,
+     const pthread_barrierattr_t *__restrict
+     __attr, unsigned int __count)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int pthread_barrier_destroy (pthread_barrier_t *__barrier)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int pthread_barrier_wait (pthread_barrier_t *__barrier)
+     __attribute__ ((__nothrow__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+extern int pthread_barrierattr_init (pthread_barrierattr_t *__attr)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int pthread_barrierattr_destroy (pthread_barrierattr_t *__attr)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int pthread_barrierattr_getpshared (const pthread_barrierattr_t *
+        __restrict __attr,
+        int *__restrict __pshared)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern int pthread_barrierattr_setpshared (pthread_barrierattr_t *__attr,
+        int __pshared)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+extern int pthread_key_create (pthread_key_t *__key,
+          void (*__destr_function) (void *))
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int pthread_key_delete (pthread_key_t __key) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern void *pthread_getspecific (pthread_key_t __key) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern int pthread_setspecific (pthread_key_t __key,
+    const void *__pointer) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+extern int pthread_getcpuclockid (pthread_t __thread_id,
+      __clockid_t *__clock_id)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2)));
+extern int pthread_atfork (void (*__prepare) (void),
+      void (*__parent) (void),
+      void (*__child) (void)) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+extern __inline __attribute__ ((__gnu_inline__)) int
+__attribute__ ((__nothrow__ , __leaf__)) pthread_equal (pthread_t __thread1, pthread_t __thread2)
+{
+  return __thread1 == __thread2;
+}
+
+
+
+
+
+typedef struct {
+    pthread_once_t o;
+    k5_os_nothread_once_t n;
+} k5_once_t;
+typedef struct {
+    pthread_mutex_t p;
+
+    pthread_t owner;
+
+
+
+
+} k5_os_mutex;
+static __inline__ void k5_pthread_assert_unlocked(pthread_mutex_t *m) { }
+static __inline__ int k5_os_mutex_finish_init(k5_os_mutex *m) { return 0; }
+typedef struct {
+    k5_debug_loc loc_last, loc_created;
+    k5_os_mutex os;
+    k5_debug_mutex_stats stats;
+} k5_mutex_t;
+
+
+
+static __inline__ int k5_mutex_init_1(k5_mutex_t *m, k5_debug_loc l)
+{
+    int err = pthread_mutex_init(&(&m->os)->p, 0);
+    if (err) return err;
+    m->loc_created = m->loc_last = l;
+    err = (*(&m->stats) = 's', 0);
+    
+   ((
+   err == 0
+   ) ? (void) (0) : __assert_fail (
+   "err == 0"
+   , "../../../include/k5-thread.h", 659, __PRETTY_FUNCTION__))
+                   ;
+    return 0;
+}
+
+static __inline__ int k5_mutex_finish_init_1(k5_mutex_t *m, k5_debug_loc l)
+{
+    int err = k5_os_mutex_finish_init(&m->os);
+    if (err) return err;
+    m->loc_created = m->loc_last = l;
+    err = (0);
+    
+   ((
+   err == 0
+   ) ? (void) (0) : __assert_fail (
+   "err == 0"
+   , "../../../include/k5-thread.h", 669, __PRETTY_FUNCTION__))
+                   ;
+    return 0;
+}
+typedef enum {
+    K5_KEY_COM_ERR,
+    K5_KEY_GSS_KRB5_SET_CCACHE_OLD_NAME,
+    K5_KEY_GSS_KRB5_CCACHE_NAME,
+    K5_KEY_MAX
+} k5_key_t;
+
+
+
+
+
+extern int krb5int_key_register(k5_key_t, void (*)(void *));
+extern void *krb5int_getspecific(k5_key_t);
+extern int krb5int_setspecific(k5_key_t, void *);
+extern int krb5int_key_delete(k5_key_t);
+typedef struct { k5_once_t once; int error, did_run; void (*fn)(void); } k5_init_t;
+
+typedef unsigned char uint8_t;
+typedef unsigned short int uint16_t;
+
+typedef unsigned int uint32_t;
+
+
+
+typedef unsigned long int uint64_t;
+typedef signed char int_least8_t;
+typedef short int int_least16_t;
+typedef int int_least32_t;
+
+typedef long int int_least64_t;
+
+
+
+
+
+
+typedef unsigned char uint_least8_t;
+typedef unsigned short int uint_least16_t;
+typedef unsigned int uint_least32_t;
+
+typedef unsigned long int uint_least64_t;
+typedef signed char int_fast8_t;
+
+typedef long int int_fast16_t;
+typedef long int int_fast32_t;
+typedef long int int_fast64_t;
+typedef unsigned char uint_fast8_t;
+
+typedef unsigned long int uint_fast16_t;
+typedef unsigned long int uint_fast32_t;
+typedef unsigned long int uint_fast64_t;
+typedef long int intptr_t;
+
+
+typedef unsigned long int uintptr_t;
+typedef long int intmax_t;
+typedef unsigned long int uintmax_t;
+
+
+typedef int __gwchar_t;
+
+
+
+
+
+typedef struct
+  {
+    long int quot;
+    long int rem;
+  } imaxdiv_t;
+extern intmax_t imaxabs (intmax_t __n) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__));
+
+
+extern imaxdiv_t imaxdiv (intmax_t __numer, intmax_t __denom)
+      __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__));
+
+
+extern intmax_t strtoimax (const char *__restrict __nptr,
+      char **__restrict __endptr, int __base) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern uintmax_t strtoumax (const char *__restrict __nptr,
+       char ** __restrict __endptr, int __base) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern intmax_t wcstoimax (const __gwchar_t *__restrict __nptr,
+      __gwchar_t **__restrict __endptr, int __base)
+     __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern uintmax_t wcstoumax (const __gwchar_t *__restrict __nptr,
+       __gwchar_t ** __restrict __endptr, int __base)
+     __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+extern long int __strtol_internal (const char *__restrict __nptr,
+       char **__restrict __endptr,
+       int __base, int __group)
+  __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+
+extern __inline __attribute__ ((__gnu_inline__)) intmax_t
+__attribute__ ((__nothrow__ , __leaf__)) strtoimax (const char *__restrict nptr, char **__restrict endptr, int base)
+
+{
+  return __strtol_internal (nptr, endptr, base, 0);
+}
+
+extern unsigned long int __strtoul_internal (const char *__restrict __nptr,
+          char ** __restrict __endptr,
+          int __base, int __group)
+  __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+
+extern __inline __attribute__ ((__gnu_inline__)) uintmax_t
+__attribute__ ((__nothrow__ , __leaf__)) strtoumax (const char *__restrict nptr, char **__restrict endptr, int base)
+
+{
+  return __strtoul_internal (nptr, endptr, base, 0);
+}
+
+extern long int __wcstol_internal (const __gwchar_t * __restrict __nptr,
+       __gwchar_t **__restrict __endptr,
+       int __base, int __group)
+  __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+
+extern __inline __attribute__ ((__gnu_inline__)) intmax_t
+__attribute__ ((__nothrow__ , __leaf__)) wcstoimax (const __gwchar_t *__restrict nptr, __gwchar_t **__restrict endptr, int base)
+
+{
+  return __wcstol_internal (nptr, endptr, base, 0);
+}
+
+extern unsigned long int __wcstoul_internal (const __gwchar_t *
+          __restrict __nptr,
+          __gwchar_t **
+          __restrict __endptr,
+          int __base, int __group)
+  __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+
+extern __inline __attribute__ ((__gnu_inline__)) uintmax_t
+__attribute__ ((__nothrow__ , __leaf__)) wcstoumax (const __gwchar_t *__restrict nptr, __gwchar_t **__restrict endptr, int base)
+
+{
+  return __wcstoul_internal (nptr, endptr, base, 0);
+}
+
+
+
+
+
+
+
+
+
+
+
+static __inline__ void
+store_16_be (unsigned int val, unsigned char *p)
+{
+    p[0] = (val >> 8) & 0xff;
+    p[1] = (val ) & 0xff;
+}
+static __inline__ void
+store_16_le (unsigned int val, unsigned char *p)
+{
+    p[1] = (val >> 8) & 0xff;
+    p[0] = (val ) & 0xff;
+}
+static __inline__ void
+store_32_be (unsigned int val, unsigned char *p)
+{
+    p[0] = (val >> 24) & 0xff;
+    p[1] = (val >> 16) & 0xff;
+    p[2] = (val >> 8) & 0xff;
+    p[3] = (val ) & 0xff;
+}
+static __inline__ void
+store_32_le (unsigned int val, unsigned char *p)
+{
+    p[3] = (val >> 24) & 0xff;
+    p[2] = (val >> 16) & 0xff;
+    p[1] = (val >> 8) & 0xff;
+    p[0] = (val ) & 0xff;
+}
+static __inline__ void
+store_64_be (uint64_t val, unsigned char *p)
+{
+    p[0] = (unsigned char)((val >> 56) & 0xff);
+    p[1] = (unsigned char)((val >> 48) & 0xff);
+    p[2] = (unsigned char)((val >> 40) & 0xff);
+    p[3] = (unsigned char)((val >> 32) & 0xff);
+    p[4] = (unsigned char)((val >> 24) & 0xff);
+    p[5] = (unsigned char)((val >> 16) & 0xff);
+    p[6] = (unsigned char)((val >> 8) & 0xff);
+    p[7] = (unsigned char)((val ) & 0xff);
+}
+static __inline__ void
+store_64_le (uint64_t val, unsigned char *p)
+{
+    p[7] = (unsigned char)((val >> 56) & 0xff);
+    p[6] = (unsigned char)((val >> 48) & 0xff);
+    p[5] = (unsigned char)((val >> 40) & 0xff);
+    p[4] = (unsigned char)((val >> 32) & 0xff);
+    p[3] = (unsigned char)((val >> 24) & 0xff);
+    p[2] = (unsigned char)((val >> 16) & 0xff);
+    p[1] = (unsigned char)((val >> 8) & 0xff);
+    p[0] = (unsigned char)((val ) & 0xff);
+}
+static __inline__ unsigned short
+load_16_be (unsigned char *p)
+{
+    return (p[1] | (p[0] << 8));
+}
+static __inline__ unsigned short
+load_16_le (unsigned char *p)
+{
+    return (p[0] | (p[1] << 8));
+}
+static __inline__ unsigned int
+load_32_be (unsigned char *p)
+{
+    return (p[3] | (p[2] << 8) | (p[1] << 16) | (p[0] << 24));
+}
+static __inline__ unsigned int
+load_32_le (unsigned char *p)
+{
+    return (p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24));
+}
+static __inline__ uint64_t
+load_64_be (unsigned char *p)
+{
+    return ((uint64_t)load_32_be(p) << 32) | load_32_be(p+4);
+}
+static __inline__ uint64_t
+load_64_le (unsigned char *p)
+{
+    return ((uint64_t)load_32_le(p+4) << 32) | load_32_le(p);
+}
+
+typedef uint64_t krb5_ui_8;
+typedef int64_t krb5_int64;
+
+
+
+
+
+
+
+
+extern int *__errno_location (void) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__));
+
+typedef int wchar_t;
+
+
+
+
+
+
+
+
+union wait
+  {
+    int w_status;
+    struct
+      {
+
+ unsigned int __w_termsig:7;
+ unsigned int __w_coredump:1;
+ unsigned int __w_retcode:8;
+ unsigned int:16;
+
+
+
+
+
+
+
+      } __wait_terminated;
+    struct
+      {
+
+ unsigned int __w_stopval:8;
+ unsigned int __w_stopsig:8;
+ unsigned int:16;
+
+
+
+
+
+
+      } __wait_stopped;
+  };
+typedef union
+  {
+    union wait *__uptr;
+    int *__iptr;
+  } __WAIT_STATUS __attribute__ ((__transparent_union__));
+
+
+typedef struct
+  {
+    int quot;
+    int rem;
+  } div_t;
+
+
+
+typedef struct
+  {
+    long int quot;
+    long int rem;
+  } ldiv_t;
+
+
+
+
+
+
+
+__extension__ typedef struct
+  {
+    long long int quot;
+    long long int rem;
+  } lldiv_t;
+
+
+extern size_t __ctype_get_mb_cur_max (void) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+extern double atof (const char *__nptr)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1))) ;
+
+extern int atoi (const char *__nptr)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1))) ;
+
+extern long int atol (const char *__nptr)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1))) ;
+
+
+
+
+
+__extension__ extern long long int atoll (const char *__nptr)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1))) ;
+
+
+
+
+
+extern double strtod (const char *__restrict __nptr,
+        char **__restrict __endptr)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+extern float strtof (const char *__restrict __nptr,
+       char **__restrict __endptr) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+extern long double strtold (const char *__restrict __nptr,
+       char **__restrict __endptr)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+extern long int strtol (const char *__restrict __nptr,
+   char **__restrict __endptr, int __base)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+extern unsigned long int strtoul (const char *__restrict __nptr,
+      char **__restrict __endptr, int __base)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+__extension__
+extern long long int strtoq (const char *__restrict __nptr,
+        char **__restrict __endptr, int __base)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+__extension__
+extern unsigned long long int strtouq (const char *__restrict __nptr,
+           char **__restrict __endptr, int __base)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+__extension__
+extern long long int strtoll (const char *__restrict __nptr,
+         char **__restrict __endptr, int __base)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+__extension__
+extern unsigned long long int strtoull (const char *__restrict __nptr,
+     char **__restrict __endptr, int __base)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern __inline __attribute__ ((__gnu_inline__)) int
+__attribute__ ((__nothrow__ , __leaf__)) atoi (const char *__nptr)
+{
+  return (int) strtol (__nptr, (char **) ((void *)0), 10);
+}
+extern __inline __attribute__ ((__gnu_inline__)) long int
+__attribute__ ((__nothrow__ , __leaf__)) atol (const char *__nptr)
+{
+  return strtol (__nptr, (char **) ((void *)0), 10);
+}
+
+
+
+
+__extension__ extern __inline __attribute__ ((__gnu_inline__)) long long int
+__attribute__ ((__nothrow__ , __leaf__)) atoll (const char *__nptr)
+{
+  return strtoll (__nptr, (char **) ((void *)0), 10);
+}
+
+extern char *l64a (long int __n) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+extern long int a64l (const char *__s)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1))) ;
+extern long int random (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern void srandom (unsigned int __seed) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+extern char *initstate (unsigned int __seed, char *__statebuf,
+   size_t __statelen) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2)));
+
+
+
+extern char *setstate (char *__statebuf) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+
+
+struct random_data
+  {
+    int32_t *fptr;
+    int32_t *rptr;
+    int32_t *state;
+    int rand_type;
+    int rand_deg;
+    int rand_sep;
+    int32_t *end_ptr;
+  };
+
+extern int random_r (struct random_data *__restrict __buf,
+       int32_t *__restrict __result) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+extern int srandom_r (unsigned int __seed, struct random_data *__buf)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2)));
+
+extern int initstate_r (unsigned int __seed, char *__restrict __statebuf,
+   size_t __statelen,
+   struct random_data *__restrict __buf)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2, 4)));
+
+extern int setstate_r (char *__restrict __statebuf,
+         struct random_data *__restrict __buf)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+
+
+
+extern int rand (void) __attribute__ ((__nothrow__ , __leaf__));
+
+extern void srand (unsigned int __seed) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+extern int rand_r (unsigned int *__seed) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+
+extern double drand48 (void) __attribute__ ((__nothrow__ , __leaf__));
+extern double erand48 (unsigned short int __xsubi[3]) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern long int lrand48 (void) __attribute__ ((__nothrow__ , __leaf__));
+extern long int nrand48 (unsigned short int __xsubi[3])
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern long int mrand48 (void) __attribute__ ((__nothrow__ , __leaf__));
+extern long int jrand48 (unsigned short int __xsubi[3])
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern void srand48 (long int __seedval) __attribute__ ((__nothrow__ , __leaf__));
+extern unsigned short int *seed48 (unsigned short int __seed16v[3])
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+extern void lcong48 (unsigned short int __param[7]) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+struct drand48_data
+  {
+    unsigned short int __x[3];
+    unsigned short int __old_x[3];
+    unsigned short int __c;
+    unsigned short int __init;
+    __extension__ unsigned long long int __a;
+
+  };
+
+
+extern int drand48_r (struct drand48_data *__restrict __buffer,
+        double *__restrict __result) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+extern int erand48_r (unsigned short int __xsubi[3],
+        struct drand48_data *__restrict __buffer,
+        double *__restrict __result) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern int lrand48_r (struct drand48_data *__restrict __buffer,
+        long int *__restrict __result)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+extern int nrand48_r (unsigned short int __xsubi[3],
+        struct drand48_data *__restrict __buffer,
+        long int *__restrict __result)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern int mrand48_r (struct drand48_data *__restrict __buffer,
+        long int *__restrict __result)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+extern int jrand48_r (unsigned short int __xsubi[3],
+        struct drand48_data *__restrict __buffer,
+        long int *__restrict __result)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern int srand48_r (long int __seedval, struct drand48_data *__buffer)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2)));
+
+extern int seed48_r (unsigned short int __seed16v[3],
+       struct drand48_data *__buffer) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+extern int lcong48_r (unsigned short int __param[7],
+        struct drand48_data *__buffer)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+
+
+
+
+
+
+extern void *malloc (size_t __size) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__malloc__)) ;
+
+extern void *calloc (size_t __nmemb, size_t __size)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__malloc__)) ;
+
+
+
+
+
+
+
+
+
+
+extern void *realloc (void *__ptr, size_t __size)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__warn_unused_result__));
+
+extern void free (void *__ptr) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+extern void cfree (void *__ptr) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+
+
+
+
+extern void *alloca (size_t __size) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+
+
+
+
+
+extern void *valloc (size_t __size) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__malloc__)) ;
+
+
+
+
+extern int posix_memalign (void **__memptr, size_t __alignment, size_t __size)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+
+
+
+
+extern void *aligned_alloc (size_t __alignment, size_t __size)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__malloc__)) __attribute__ ((__alloc_size__ (2))) ;
+
+
+
+
+extern void abort (void) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__noreturn__));
+
+
+
+extern int atexit (void (*__func) (void)) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+
+
+extern int at_quick_exit (void (*__func) (void)) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+
+
+extern int on_exit (void (*__func) (int __status, void *__arg), void *__arg)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+
+extern void exit (int __status) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__noreturn__));
+
+
+
+
+
+extern void quick_exit (int __status) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__noreturn__));
+
+
+
+
+
+
+
+extern void _Exit (int __status) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__noreturn__));
+
+
+
+
+
+
+extern char *getenv (const char *__name) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+
+extern int putenv (char *__string) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+extern int setenv (const char *__name, const char *__value, int __replace)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2)));
+
+
+extern int unsetenv (const char *__name) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+
+extern int clearenv (void) __attribute__ ((__nothrow__ , __leaf__));
+extern char *mktemp (char *__template) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+extern int mkstemp (char *__template) __attribute__ ((__nonnull__ (1))) ;
+extern int mkstemps (char *__template, int __suffixlen) __attribute__ ((__nonnull__ (1))) ;
+extern char *mkdtemp (char *__template) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+
+
+
+
+
+extern int system (const char *__command) ;
+
+extern char *realpath (const char *__restrict __name,
+         char *__restrict __resolved) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+
+
+typedef int (*__compar_fn_t) (const void *, const void *);
+
+
+
+extern void *bsearch (const void *__key, const void *__base,
+        size_t __nmemb, size_t __size, __compar_fn_t __compar)
+     __attribute__ ((__nonnull__ (1, 2, 5))) ;
+
+
+extern __inline __attribute__ ((__gnu_inline__)) void *
+bsearch (const void *__key, const void *__base, size_t __nmemb, size_t __size,
+  __compar_fn_t __compar)
+{
+  size_t __l, __u, __idx;
+  const void *__p;
+  int __comparison;
+
+  __l = 0;
+  __u = __nmemb;
+  while (__l < __u)
+    {
+      __idx = (__l + __u) / 2;
+      __p = (void *) (((const char *) __base) + (__idx * __size));
+      __comparison = (*__compar) (__key, __p);
+      if (__comparison < 0)
+ __u = __idx;
+      else if (__comparison > 0)
+ __l = __idx + 1;
+      else
+ return (void *) __p;
+    }
+
+  return ((void *)0);
+}
+
+
+
+
+extern void qsort (void *__base, size_t __nmemb, size_t __size,
+     __compar_fn_t __compar) __attribute__ ((__nonnull__ (1, 4)));
+extern int abs (int __x) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__)) ;
+extern long int labs (long int __x) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__)) ;
+
+
+
+__extension__ extern long long int llabs (long long int __x)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__)) ;
+
+
+
+
+
+
+
+extern div_t div (int __numer, int __denom)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__)) ;
+extern ldiv_t ldiv (long int __numer, long int __denom)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__)) ;
+
+
+
+
+__extension__ extern lldiv_t lldiv (long long int __numer,
+        long long int __denom)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__)) ;
+
+extern char *ecvt (double __value, int __ndigit, int *__restrict __decpt,
+     int *__restrict __sign) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (3, 4))) ;
+
+
+
+
+extern char *fcvt (double __value, int __ndigit, int *__restrict __decpt,
+     int *__restrict __sign) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (3, 4))) ;
+
+
+
+
+extern char *gcvt (double __value, int __ndigit, char *__buf)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (3))) ;
+
+
+
+
+extern char *qecvt (long double __value, int __ndigit,
+      int *__restrict __decpt, int *__restrict __sign)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (3, 4))) ;
+extern char *qfcvt (long double __value, int __ndigit,
+      int *__restrict __decpt, int *__restrict __sign)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (3, 4))) ;
+extern char *qgcvt (long double __value, int __ndigit, char *__buf)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (3))) ;
+
+
+
+
+extern int ecvt_r (double __value, int __ndigit, int *__restrict __decpt,
+     int *__restrict __sign, char *__restrict __buf,
+     size_t __len) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (3, 4, 5)));
+extern int fcvt_r (double __value, int __ndigit, int *__restrict __decpt,
+     int *__restrict __sign, char *__restrict __buf,
+     size_t __len) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (3, 4, 5)));
+
+extern int qecvt_r (long double __value, int __ndigit,
+      int *__restrict __decpt, int *__restrict __sign,
+      char *__restrict __buf, size_t __len)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (3, 4, 5)));
+extern int qfcvt_r (long double __value, int __ndigit,
+      int *__restrict __decpt, int *__restrict __sign,
+      char *__restrict __buf, size_t __len)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (3, 4, 5)));
+
+
+
+
+
+
+extern int mblen (const char *__s, size_t __n) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern int mbtowc (wchar_t *__restrict __pwc,
+     const char *__restrict __s, size_t __n) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern int wctomb (char *__s, wchar_t __wchar) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+extern size_t mbstowcs (wchar_t *__restrict __pwcs,
+   const char *__restrict __s, size_t __n) __attribute__ ((__nothrow__ , __leaf__));
+
+extern size_t wcstombs (char *__restrict __s,
+   const wchar_t *__restrict __pwcs, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+
+
+extern int rpmatch (const char *__response) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+extern int getsubopt (char **__restrict __optionp,
+        char *const *__restrict __tokens,
+        char **__restrict __valuep)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2, 3))) ;
+extern int getloadavg (double __loadavg[], int __nelem)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+extern __inline __attribute__ ((__gnu_inline__)) double
+__attribute__ ((__nothrow__ , __leaf__)) atof (const char *__nptr)
+{
+  return strtod (__nptr, (char **) ((void *)0));
+}
+
+
+
+
+
+
+
+
+
+
+struct _profile_t;
+typedef unsigned char krb5_octet;
+
+
+
+
+
+typedef short krb5_int16;
+typedef unsigned short krb5_ui_2;
+
+
+
+
+
+typedef int krb5_int32;
+typedef unsigned int krb5_ui_4;
+typedef unsigned int krb5_boolean;
+typedef unsigned int krb5_msgtype;
+typedef unsigned int krb5_kvno;
+
+typedef krb5_int32 krb5_addrtype;
+typedef krb5_int32 krb5_enctype;
+typedef krb5_int32 krb5_cksumtype;
+typedef krb5_int32 krb5_authdatatype;
+typedef krb5_int32 krb5_keyusage;
+
+typedef krb5_int32 krb5_preauthtype;
+typedef krb5_int32 krb5_flags;
+typedef krb5_int32 krb5_timestamp;
+typedef krb5_int32 krb5_error_code;
+typedef krb5_int32 krb5_deltat;
+
+typedef krb5_error_code krb5_magic;
+
+typedef struct _krb5_data {
+ krb5_magic magic;
+ unsigned int length;
+ char *data;
+} krb5_data;
+typedef void * krb5_pointer;
+typedef void const * krb5_const_pointer;
+
+typedef struct krb5_principal_data {
+    krb5_magic magic;
+    krb5_data realm;
+    krb5_data *data;
+    krb5_int32 length;
+    krb5_int32 type;
+} krb5_principal_data;
+
+typedef krb5_principal_data * krb5_principal;
+typedef const krb5_principal_data *krb5_const_principal;
+typedef struct _krb5_address {
+    krb5_magic magic;
+    krb5_addrtype addrtype;
+    unsigned int length;
+    krb5_octet *contents;
+} krb5_address;
+struct _krb5_context;
+typedef struct _krb5_context * krb5_context;
+
+struct _krb5_auth_context;
+typedef struct _krb5_auth_context * krb5_auth_context;
+
+struct _krb5_cryptosystem_entry;
+
+
+
+
+
+typedef struct _krb5_keyblock {
+    krb5_magic magic;
+    krb5_enctype enctype;
+    unsigned int length;
+    krb5_octet *contents;
+} krb5_keyblock;
+
+
+typedef struct _krb5_encrypt_block {
+    krb5_magic magic;
+    krb5_enctype crypto_entry;
+
+
+    krb5_keyblock *key;
+} krb5_encrypt_block;
+
+
+typedef struct _krb5_checksum {
+    krb5_magic magic;
+    krb5_cksumtype checksum_type;
+    unsigned int length;
+    krb5_octet *contents;
+} krb5_checksum;
+
+typedef struct _krb5_enc_data {
+    krb5_magic magic;
+    krb5_enctype enctype;
+    krb5_kvno kvno;
+    krb5_data ciphertext;
+} krb5_enc_data;
+enum {
+  KRB5_C_RANDSOURCE_OLDAPI = 0,
+  KRB5_C_RANDSOURCE_OSRAND = 1,
+  KRB5_C_RANDSOURCE_TRUSTEDPARTY = 2,
+
+
+
+
+  KRB5_C_RANDSOURCE_TIMING = 3,
+  KRB5_C_RANDSOURCE_EXTERNAL_PROTOCOL = 4,
+  KRB5_C_RANDSOURCE_MAX = 5
+};
+krb5_error_code
+    krb5_c_encrypt
+    (krb5_context context, const krb5_keyblock *key,
+      krb5_keyusage usage, const krb5_data *cipher_state,
+      const krb5_data *input, krb5_enc_data *output);
+
+krb5_error_code
+    krb5_c_decrypt
+    (krb5_context context, const krb5_keyblock *key,
+      krb5_keyusage usage, const krb5_data *cipher_state,
+      const krb5_enc_data *input, krb5_data *output);
+
+krb5_error_code
+    krb5_c_encrypt_length
+    (krb5_context context, krb5_enctype enctype,
+      size_t inputlen, size_t *length);
+
+krb5_error_code
+    krb5_c_block_size
+    (krb5_context context, krb5_enctype enctype,
+      size_t *blocksize);
+
+krb5_error_code
+ krb5_c_init_state
+(krb5_context context,
+const krb5_keyblock *key, krb5_keyusage usage,
+krb5_data *new_state);
+
+krb5_error_code
+ krb5_c_free_state
+(krb5_context context, const krb5_keyblock *key, krb5_data *state);
+
+krb5_error_code
+    krb5_c_make_random_key
+    (krb5_context context, krb5_enctype enctype,
+      krb5_keyblock *k5_random_key);
+
+
+
+
+
+krb5_error_code
+ krb5_c_random_add_entropy
+(krb5_context context, unsigned int randsource_id, const krb5_data *data);
+
+
+krb5_error_code
+    krb5_c_random_make_octets
+    (krb5_context context, krb5_data *data);
+krb5_error_code
+krb5_c_random_os_entropy
+(krb5_context context, int strong, int *success);
+
+               krb5_error_code
+    krb5_c_random_seed
+    (krb5_context context, krb5_data *data);
+
+krb5_error_code
+    krb5_c_string_to_key
+    (krb5_context context, krb5_enctype enctype,
+      const krb5_data *string, const krb5_data *salt,
+      krb5_keyblock *key);
+krb5_error_code
+krb5_c_string_to_key_with_params(krb5_context context,
+     krb5_enctype enctype,
+     const krb5_data *string,
+     const krb5_data *salt,
+     const krb5_data *params,
+     krb5_keyblock *key);
+
+krb5_error_code
+    krb5_c_enctype_compare
+    (krb5_context context, krb5_enctype e1, krb5_enctype e2,
+      krb5_boolean *similar);
+
+krb5_error_code
+    krb5_c_make_checksum
+    (krb5_context context, krb5_cksumtype cksumtype,
+      const krb5_keyblock *key, krb5_keyusage usage,
+      const krb5_data *input, krb5_checksum *cksum);
+
+krb5_error_code
+    krb5_c_verify_checksum
+    (krb5_context context,
+      const krb5_keyblock *key, krb5_keyusage usage,
+      const krb5_data *data,
+      const krb5_checksum *cksum,
+      krb5_boolean *valid);
+
+krb5_error_code
+    krb5_c_checksum_length
+    (krb5_context context, krb5_cksumtype cksumtype,
+      size_t *length);
+
+krb5_error_code
+    krb5_c_keyed_checksum_types
+    (krb5_context context, krb5_enctype enctype,
+      unsigned int *count, krb5_cksumtype **cksumtypes);
+krb5_boolean krb5_c_valid_enctype
+ (krb5_enctype ktype);
+krb5_boolean krb5_c_valid_cksumtype
+ (krb5_cksumtype ctype);
+krb5_boolean krb5_c_is_coll_proof_cksum
+ (krb5_cksumtype ctype);
+krb5_boolean krb5_c_is_keyed_cksum
+ (krb5_cksumtype ctype);
+
+
+
+krb5_boolean valid_enctype
+ (krb5_enctype ktype);
+krb5_boolean valid_cksumtype
+ (krb5_cksumtype ctype);
+krb5_boolean is_coll_proof_cksum
+ (krb5_cksumtype ctype);
+krb5_boolean is_keyed_cksum
+ (krb5_cksumtype ctype);
+
+
+
+
+
+
+
+krb5_error_code krb5_encrypt
+ (krb5_context context,
+  krb5_const_pointer inptr,
+  krb5_pointer outptr,
+  size_t size,
+  krb5_encrypt_block * eblock,
+  krb5_pointer ivec);
+krb5_error_code krb5_decrypt
+ (krb5_context context,
+  krb5_const_pointer inptr,
+  krb5_pointer outptr,
+  size_t size,
+  krb5_encrypt_block * eblock,
+  krb5_pointer ivec);
+krb5_error_code krb5_process_key
+ (krb5_context context,
+  krb5_encrypt_block * eblock,
+  const krb5_keyblock * key);
+krb5_error_code krb5_finish_key
+ (krb5_context context,
+  krb5_encrypt_block * eblock);
+krb5_error_code krb5_string_to_key
+ (krb5_context context,
+  const krb5_encrypt_block * eblock,
+  krb5_keyblock * keyblock,
+  const krb5_data * data,
+  const krb5_data * salt);
+krb5_error_code krb5_init_random_key
+ (krb5_context context,
+  const krb5_encrypt_block * eblock,
+  const krb5_keyblock * keyblock,
+  krb5_pointer * ptr);
+krb5_error_code krb5_finish_random_key
+ (krb5_context context,
+  const krb5_encrypt_block * eblock,
+  krb5_pointer * ptr);
+krb5_error_code krb5_random_key
+ (krb5_context context,
+  const krb5_encrypt_block * eblock,
+  krb5_pointer ptr,
+  krb5_keyblock ** keyblock);
+krb5_enctype krb5_eblock_enctype
+ (krb5_context context,
+  const krb5_encrypt_block * eblock);
+krb5_error_code krb5_use_enctype
+ (krb5_context context,
+  krb5_encrypt_block * eblock,
+  krb5_enctype enctype);
+size_t krb5_encrypt_size
+ (size_t length,
+  krb5_enctype crypto);
+size_t krb5_checksum_size
+ (krb5_context context,
+  krb5_cksumtype ctype);
+krb5_error_code krb5_calculate_checksum
+ (krb5_context context,
+  krb5_cksumtype ctype,
+  krb5_const_pointer in, size_t in_length,
+  krb5_const_pointer seed, size_t seed_length,
+  krb5_checksum * outcksum);
+krb5_error_code krb5_verify_checksum
+ (krb5_context context,
+  krb5_cksumtype ctype,
+  const krb5_checksum * cksum,
+  krb5_const_pointer in, size_t in_length,
+  krb5_const_pointer seed, size_t seed_length);
+
+
+krb5_error_code krb5_random_confounder
+ (size_t, krb5_pointer);
+
+krb5_error_code krb5_encrypt_data
+ (krb5_context context, krb5_keyblock *key,
+  krb5_pointer ivec, krb5_data *data,
+  krb5_enc_data *enc_data);
+
+krb5_error_code krb5_decrypt_data
+ (krb5_context context, krb5_keyblock *key,
+  krb5_pointer ivec, krb5_enc_data *data,
+  krb5_data *enc_data);
+typedef struct _krb5_ticket_times {
+    krb5_timestamp authtime;
+
+    krb5_timestamp starttime;
+
+    krb5_timestamp endtime;
+    krb5_timestamp renew_till;
+} krb5_ticket_times;
+
+
+typedef struct _krb5_authdata {
+    krb5_magic magic;
+    krb5_authdatatype ad_type;
+    unsigned int length;
+    krb5_octet *contents;
+} krb5_authdata;
+
+
+typedef struct _krb5_transited {
+    krb5_magic magic;
+    krb5_octet tr_type;
+    krb5_data tr_contents;
+} krb5_transited;
+
+typedef struct _krb5_enc_tkt_part {
+    krb5_magic magic;
+
+    krb5_flags flags;
+    krb5_keyblock *session;
+    krb5_principal client;
+    krb5_transited transited;
+    krb5_ticket_times times;
+    krb5_address **caddrs;
+    krb5_authdata **authorization_data;
+} krb5_enc_tkt_part;
+
+typedef struct _krb5_ticket {
+    krb5_magic magic;
+
+    krb5_principal server;
+    krb5_enc_data enc_part;
+
+    krb5_enc_tkt_part *enc_part2;
+
+} krb5_ticket;
+
+
+typedef struct _krb5_authenticator {
+    krb5_magic magic;
+    krb5_principal client;
+    krb5_checksum *checksum;
+    krb5_int32 cusec;
+    krb5_timestamp ctime;
+    krb5_keyblock *subkey;
+    krb5_ui_4 seq_number;
+    krb5_authdata **authorization_data;
+} krb5_authenticator;
+
+typedef struct _krb5_tkt_authent {
+    krb5_magic magic;
+    krb5_ticket *ticket;
+    krb5_authenticator *authenticator;
+    krb5_flags ap_options;
+} krb5_tkt_authent;
+
+
+typedef struct _krb5_creds {
+    krb5_magic magic;
+    krb5_principal client;
+    krb5_principal server;
+    krb5_keyblock keyblock;
+    krb5_ticket_times times;
+    krb5_boolean is_skey;
+
+    krb5_flags ticket_flags;
+    krb5_address **addresses;
+    krb5_data ticket;
+    krb5_data second_ticket;
+
+
+    krb5_authdata **authdata;
+} krb5_creds;
+
+
+typedef struct _krb5_last_req_entry {
+    krb5_magic magic;
+    krb5_int32 lr_type;
+    krb5_timestamp value;
+} krb5_last_req_entry;
+
+
+typedef struct _krb5_pa_data {
+    krb5_magic magic;
+    krb5_preauthtype pa_type;
+    unsigned int length;
+    krb5_octet *contents;
+} krb5_pa_data;
+
+typedef struct _krb5_kdc_req {
+    krb5_magic magic;
+    krb5_msgtype msg_type;
+    krb5_pa_data **padata;
+
+    krb5_flags kdc_options;
+    krb5_principal client;
+    krb5_principal server;
+
+    krb5_timestamp from;
+    krb5_timestamp till;
+    krb5_timestamp rtime;
+    krb5_int32 nonce;
+    int nktypes;
+    krb5_enctype *ktype;
+    krb5_address **addresses;
+    krb5_enc_data authorization_data;
+    krb5_authdata **unenc_authdata;
+
+    krb5_ticket **second_ticket;
+} krb5_kdc_req;
+
+typedef struct _krb5_enc_kdc_rep_part {
+    krb5_magic magic;
+
+    krb5_msgtype msg_type;
+    krb5_keyblock *session;
+    krb5_last_req_entry **last_req;
+    krb5_int32 nonce;
+    krb5_timestamp key_exp;
+    krb5_flags flags;
+    krb5_ticket_times times;
+    krb5_principal server;
+    krb5_address **caddrs;
+
+} krb5_enc_kdc_rep_part;
+
+typedef struct _krb5_kdc_rep {
+    krb5_magic magic;
+
+    krb5_msgtype msg_type;
+    krb5_pa_data **padata;
+    krb5_principal client;
+    krb5_ticket *ticket;
+    krb5_enc_data enc_part;
+
+    krb5_enc_kdc_rep_part *enc_part2;
+} krb5_kdc_rep;
+
+
+typedef struct _krb5_error {
+    krb5_magic magic;
+
+    krb5_timestamp ctime;
+    krb5_int32 cusec;
+    krb5_int32 susec;
+    krb5_timestamp stime;
+    krb5_ui_4 error;
+    krb5_principal client;
+
+    krb5_principal server;
+    krb5_data text;
+    krb5_data e_data;
+} krb5_error;
+
+typedef struct _krb5_ap_req {
+    krb5_magic magic;
+    krb5_flags ap_options;
+    krb5_ticket *ticket;
+    krb5_enc_data authenticator;
+} krb5_ap_req;
+
+typedef struct _krb5_ap_rep {
+    krb5_magic magic;
+    krb5_enc_data enc_part;
+} krb5_ap_rep;
+
+typedef struct _krb5_ap_rep_enc_part {
+    krb5_magic magic;
+    krb5_timestamp ctime;
+    krb5_int32 cusec;
+    krb5_keyblock *subkey;
+    krb5_ui_4 seq_number;
+} krb5_ap_rep_enc_part;
+
+typedef struct _krb5_response {
+    krb5_magic magic;
+    krb5_octet message_type;
+    krb5_data response;
+    krb5_int32 expected_nonce;
+    krb5_timestamp request_time;
+} krb5_response;
+
+typedef struct _krb5_cred_info {
+    krb5_magic magic;
+    krb5_keyblock *session;
+
+    krb5_principal client;
+    krb5_principal server;
+    krb5_flags flags;
+    krb5_ticket_times times;
+
+    krb5_address **caddrs;
+} krb5_cred_info;
+
+typedef struct _krb5_cred_enc_part {
+    krb5_magic magic;
+    krb5_int32 nonce;
+    krb5_timestamp timestamp;
+    krb5_int32 usec;
+    krb5_address *s_address;
+    krb5_address *r_address;
+    krb5_cred_info **ticket_info;
+} krb5_cred_enc_part;
+
+typedef struct _krb5_cred {
+    krb5_magic magic;
+    krb5_ticket **tickets;
+    krb5_enc_data enc_part;
+    krb5_cred_enc_part *enc_part2;
+} krb5_cred;
+
+
+typedef struct _passwd_phrase_element {
+    krb5_magic magic;
+    krb5_data *passwd;
+    krb5_data *phrase;
+} passwd_phrase_element;
+
+typedef struct _krb5_pwd_data {
+    krb5_magic magic;
+    int sequence_count;
+    passwd_phrase_element **element;
+} krb5_pwd_data;
+typedef struct krb5_replay_data {
+    krb5_timestamp timestamp;
+    krb5_int32 usec;
+    krb5_ui_4 seq;
+} krb5_replay_data;
+typedef krb5_error_code
+( * krb5_mk_req_checksum_func) (krb5_context, krb5_auth_context , void *,
+          krb5_data **);
+typedef krb5_pointer krb5_cc_cursor;
+
+struct _krb5_ccache;
+typedef struct _krb5_ccache *krb5_ccache;
+struct _krb5_cc_ops;
+typedef struct _krb5_cc_ops krb5_cc_ops;
+const char *
+krb5_cc_get_name (krb5_context context, krb5_ccache cache);
+
+krb5_error_code
+krb5_cc_gen_new (krb5_context context, krb5_ccache *cache);
+
+krb5_error_code
+krb5_cc_initialize(krb5_context context, krb5_ccache cache,
+     krb5_principal principal);
+
+krb5_error_code
+krb5_cc_destroy (krb5_context context, krb5_ccache cache);
+
+krb5_error_code
+krb5_cc_close (krb5_context context, krb5_ccache cache);
+
+krb5_error_code
+krb5_cc_store_cred (krb5_context context, krb5_ccache cache,
+      krb5_creds *creds);
+
+krb5_error_code
+krb5_cc_retrieve_cred (krb5_context context, krb5_ccache cache,
+         krb5_flags flags, krb5_creds *mcreds,
+         krb5_creds *creds);
+
+krb5_error_code
+krb5_cc_get_principal (krb5_context context, krb5_ccache cache,
+         krb5_principal *principal);
+
+krb5_error_code
+krb5_cc_start_seq_get (krb5_context context, krb5_ccache cache,
+         krb5_cc_cursor *cursor);
+
+krb5_error_code
+krb5_cc_next_cred (krb5_context context, krb5_ccache cache,
+     krb5_cc_cursor *cursor, krb5_creds *creds);
+
+krb5_error_code
+krb5_cc_end_seq_get (krb5_context context, krb5_ccache cache,
+       krb5_cc_cursor *cursor);
+
+krb5_error_code
+krb5_cc_remove_cred (krb5_context context, krb5_ccache cache, krb5_flags flags,
+       krb5_creds *creds);
+
+krb5_error_code
+krb5_cc_set_flags (krb5_context context, krb5_ccache cache, krb5_flags flags);
+
+const char *
+krb5_cc_get_type (krb5_context context, krb5_ccache cache);
+struct krb5_rc_st;
+typedef struct krb5_rc_st *krb5_rcache;
+typedef krb5_pointer krb5_kt_cursor;
+
+typedef struct krb5_keytab_entry_st {
+    krb5_magic magic;
+    krb5_principal principal;
+    krb5_timestamp timestamp;
+    krb5_kvno vno;
+    krb5_keyblock key;
+} krb5_keytab_entry;
+
+
+struct _krb5_kt_ops;
+typedef struct _krb5_kt {
+    krb5_magic magic;
+    const struct _krb5_kt_ops *ops;
+    krb5_pointer data;
+} *krb5_keytab;
+
+
+
+
+
+char *
+krb5_kt_get_type (krb5_context, krb5_keytab keytab);
+krb5_error_code
+krb5_kt_get_name(krb5_context context, krb5_keytab keytab, char *name,
+   unsigned int namelen);
+krb5_error_code
+krb5_kt_close(krb5_context context, krb5_keytab keytab);
+krb5_error_code
+krb5_kt_get_entry(krb5_context context, krb5_keytab keytab,
+    krb5_const_principal principal, krb5_kvno vno,
+    krb5_enctype enctype, krb5_keytab_entry *entry);
+krb5_error_code
+krb5_kt_start_seq_get(krb5_context context, krb5_keytab keytab,
+        krb5_kt_cursor *cursor);
+krb5_error_code
+krb5_kt_next_entry(krb5_context context, krb5_keytab keytab,
+     krb5_keytab_entry *entry, krb5_kt_cursor *cursor);
+krb5_error_code
+krb5_kt_end_seq_get(krb5_context context, krb5_keytab keytab,
+      krb5_kt_cursor *cursor);
+krb5_error_code krb5_init_context
+ (krb5_context *);
+krb5_error_code krb5_init_secure_context
+ (krb5_context *);
+void krb5_free_context
+ (krb5_context);
+
+
+krb5_error_code krb5_set_default_in_tkt_ktypes
+ (krb5_context,
+  const krb5_enctype *);
+krb5_error_code krb5_get_default_in_tkt_ktypes
+ (krb5_context,
+  krb5_enctype **);
+
+krb5_error_code krb5_set_default_tgs_ktypes
+ (krb5_context,
+  const krb5_enctype *);
+
+
+krb5_error_code
+krb5_set_default_tgs_enctypes
+ (krb5_context,
+  const krb5_enctype *);
+
+krb5_error_code krb5_get_tgs_ktypes
+ (krb5_context,
+  krb5_const_principal,
+  krb5_enctype **);
+
+
+krb5_error_code krb5_get_permitted_enctypes
+ (krb5_context, krb5_enctype **);
+
+
+void krb5_free_ktypes
+ (krb5_context, krb5_enctype *);
+
+krb5_boolean krb5_is_permitted_enctype
+ (krb5_context, krb5_enctype);
+
+
+krb5_boolean krb5_is_thread_safe(void);
+
+
+
+krb5_error_code krb5_kdc_rep_decrypt_proc
+ (krb5_context,
+  const krb5_keyblock *,
+  krb5_const_pointer,
+  krb5_kdc_rep * );
+krb5_error_code krb5_decrypt_tkt_part
+ (krb5_context,
+  const krb5_keyblock *,
+  krb5_ticket * );
+krb5_error_code krb5_get_cred_from_kdc
+ (krb5_context,
+  krb5_ccache,
+
+  krb5_creds *,
+  krb5_creds **,
+  krb5_creds *** );
+krb5_error_code krb5_get_cred_from_kdc_validate
+ (krb5_context,
+  krb5_ccache,
+
+  krb5_creds *,
+  krb5_creds **,
+  krb5_creds *** );
+krb5_error_code krb5_get_cred_from_kdc_renew
+ (krb5_context,
+  krb5_ccache,
+
+  krb5_creds *,
+  krb5_creds **,
+  krb5_creds *** );
+
+
+void krb5_free_tgt_creds
+ (krb5_context,
+  krb5_creds **);
+
+
+
+
+krb5_error_code krb5_get_credentials
+ (krb5_context,
+  krb5_flags,
+  krb5_ccache,
+  krb5_creds *,
+  krb5_creds **);
+krb5_error_code krb5_get_credentials_validate
+ (krb5_context,
+  krb5_flags,
+  krb5_ccache,
+  krb5_creds *,
+  krb5_creds **);
+krb5_error_code krb5_get_credentials_renew
+ (krb5_context,
+  krb5_flags,
+  krb5_ccache,
+  krb5_creds *,
+  krb5_creds **);
+
+krb5_error_code krb5_get_cred_via_tkt
+ (krb5_context,
+     krb5_creds *,
+     krb5_flags,
+     krb5_address * const *,
+     krb5_creds *,
+     krb5_creds **);
+
+krb5_error_code krb5_mk_req
+ (krb5_context,
+  krb5_auth_context *,
+  krb5_flags,
+  char *,
+  char *,
+  krb5_data *,
+  krb5_ccache,
+  krb5_data * );
+krb5_error_code krb5_mk_req_extended
+ (krb5_context,
+  krb5_auth_context *,
+  krb5_flags,
+  krb5_data *,
+  krb5_creds *,
+  krb5_data * );
+krb5_error_code krb5_mk_rep
+ (krb5_context,
+  krb5_auth_context,
+  krb5_data *);
+krb5_error_code krb5_rd_rep
+ (krb5_context,
+  krb5_auth_context,
+  const krb5_data *,
+  krb5_ap_rep_enc_part **);
+krb5_error_code krb5_mk_error
+ (krb5_context,
+  const krb5_error *,
+  krb5_data * );
+krb5_error_code krb5_rd_error
+ (krb5_context,
+  const krb5_data *,
+  krb5_error ** );
+krb5_error_code krb5_rd_safe
+ (krb5_context,
+  krb5_auth_context,
+  const krb5_data *,
+  krb5_data *,
+  krb5_replay_data *);
+krb5_error_code krb5_rd_priv
+ (krb5_context,
+  krb5_auth_context,
+  const krb5_data *,
+  krb5_data *,
+  krb5_replay_data *);
+krb5_error_code krb5_parse_name
+ (krb5_context,
+  const char *,
+  krb5_principal * );
+krb5_error_code krb5_unparse_name
+ (krb5_context,
+  krb5_const_principal,
+  char ** );
+krb5_error_code krb5_unparse_name_ext
+ (krb5_context,
+  krb5_const_principal,
+  char **,
+  unsigned int *);
+
+krb5_error_code krb5_set_principal_realm
+ (krb5_context, krb5_principal, const char *);
+
+krb5_boolean krb5_address_search
+ (krb5_context,
+  const krb5_address *,
+  krb5_address * const *);
+krb5_boolean krb5_address_compare
+ (krb5_context,
+  const krb5_address *,
+  const krb5_address *);
+int krb5_address_order
+ (krb5_context,
+  const krb5_address *,
+  const krb5_address *);
+krb5_boolean krb5_realm_compare
+ (krb5_context,
+  krb5_const_principal,
+  krb5_const_principal);
+krb5_boolean krb5_principal_compare
+ (krb5_context,
+  krb5_const_principal,
+  krb5_const_principal);
+krb5_error_code krb5_init_keyblock
+  (krb5_context, krb5_enctype enctype,
+  size_t length, krb5_keyblock **out);
+
+
+
+
+
+
+krb5_error_code krb5_copy_keyblock
+ (krb5_context,
+  const krb5_keyblock *,
+  krb5_keyblock **);
+krb5_error_code krb5_copy_keyblock_contents
+ (krb5_context,
+  const krb5_keyblock *,
+  krb5_keyblock *);
+krb5_error_code krb5_copy_creds
+ (krb5_context,
+  const krb5_creds *,
+  krb5_creds **);
+krb5_error_code krb5_copy_data
+ (krb5_context,
+  const krb5_data *,
+  krb5_data **);
+krb5_error_code krb5_copy_principal
+ (krb5_context,
+  krb5_const_principal,
+  krb5_principal *);
+
+krb5_error_code krb5_copy_addr
+ (krb5_context,
+  const krb5_address *,
+  krb5_address **);
+
+krb5_error_code krb5_copy_addresses
+ (krb5_context,
+  krb5_address * const *,
+  krb5_address ***);
+krb5_error_code krb5_copy_ticket
+ (krb5_context,
+  const krb5_ticket *,
+  krb5_ticket **);
+krb5_error_code krb5_copy_authdata
+ (krb5_context,
+  krb5_authdata * const *,
+  krb5_authdata ***);
+krb5_error_code krb5_copy_authenticator
+ (krb5_context,
+  const krb5_authenticator *,
+  krb5_authenticator **);
+krb5_error_code krb5_copy_checksum
+ (krb5_context,
+  const krb5_checksum *,
+  krb5_checksum **);
+
+void krb5_init_ets
+ (krb5_context);
+void krb5_free_ets
+ (krb5_context);
+krb5_error_code krb5_generate_subkey
+ (krb5_context,
+  const krb5_keyblock *, krb5_keyblock **);
+krb5_error_code krb5_generate_seq_number
+ (krb5_context,
+  const krb5_keyblock *, krb5_ui_4 *);
+
+krb5_error_code krb5_get_server_rcache
+ (krb5_context,
+  const krb5_data *, krb5_rcache *);
+krb5_error_code krb5_build_principal_ext
+ (krb5_context, krb5_principal *, unsigned int, const char *, ...);
+krb5_error_code krb5_build_principal
+ (krb5_context, krb5_principal *, unsigned int, const char *, ...);
+
+
+
+
+
+
+
+krb5_error_code krb5_425_conv_principal
+ (krb5_context,
+  const char *name,
+  const char *instance, const char *realm,
+  krb5_principal *princ);
+
+krb5_error_code krb5_524_conv_principal
+ (krb5_context context, krb5_const_principal princ,
+  char *name, char *inst, char *realm);
+
+struct credentials;
+int krb5_524_convert_creds
+ (krb5_context context, krb5_creds *v5creds,
+  struct credentials *v4creds);
+
+
+
+
+
+
+
+krb5_error_code krb5_kt_register
+ (krb5_context,
+  const struct _krb5_kt_ops * );
+
+
+krb5_error_code krb5_kt_resolve
+ (krb5_context,
+  const char *,
+  krb5_keytab * );
+krb5_error_code krb5_kt_default_name
+ (krb5_context,
+  char *,
+  int );
+krb5_error_code krb5_kt_default
+ (krb5_context,
+  krb5_keytab * );
+krb5_error_code krb5_free_keytab_entry_contents
+ (krb5_context,
+  krb5_keytab_entry * );
+
+
+krb5_error_code krb5_kt_free_entry
+ (krb5_context,
+  krb5_keytab_entry * );
+
+
+
+krb5_error_code krb5_kt_remove_entry
+ (krb5_context,
+  krb5_keytab,
+  krb5_keytab_entry * );
+krb5_error_code krb5_kt_add_entry
+ (krb5_context,
+  krb5_keytab,
+  krb5_keytab_entry * );
+krb5_error_code krb5_principal2salt
+ (krb5_context,
+  krb5_const_principal, krb5_data *);
+
+krb5_error_code krb5_principal2salt_norealm
+ (krb5_context,
+  krb5_const_principal, krb5_data *);
+
+
+
+
+krb5_error_code krb5_cc_resolve
+ (krb5_context,
+  const char *,
+  krb5_ccache * );
+const char * krb5_cc_default_name
+ (krb5_context);
+krb5_error_code krb5_cc_set_default_name
+ (krb5_context, const char *);
+krb5_error_code krb5_cc_default
+ (krb5_context,
+  krb5_ccache *);
+
+unsigned int krb5_get_notification_message
+ (void);
+
+
+krb5_error_code krb5_cc_copy_creds
+ (krb5_context context,
+   krb5_ccache incc,
+   krb5_ccache outcc);
+
+
+
+
+krb5_error_code krb5_check_transited_list
+ (krb5_context, const krb5_data *trans,
+  const krb5_data *realm1, const krb5_data *realm2);
+
+
+
+
+void krb5_free_realm_tree
+ (krb5_context,
+  krb5_principal *);
+
+
+
+void krb5_free_principal
+ (krb5_context, krb5_principal );
+void krb5_free_authenticator
+ (krb5_context, krb5_authenticator * );
+
+void krb5_free_authenticator_contents
+ (krb5_context, krb5_authenticator * );
+
+void krb5_free_addresses
+ (krb5_context, krb5_address ** );
+
+void krb5_free_address
+ (krb5_context, krb5_address * );
+
+void krb5_free_authdata
+ (krb5_context, krb5_authdata ** );
+
+void krb5_free_enc_tkt_part
+ (krb5_context, krb5_enc_tkt_part * );
+
+void krb5_free_ticket
+ (krb5_context, krb5_ticket * );
+
+void krb5_free_tickets
+ (krb5_context, krb5_ticket ** );
+void krb5_free_kdc_req
+ (krb5_context, krb5_kdc_req * );
+void krb5_free_kdc_rep
+ (krb5_context, krb5_kdc_rep * );
+void krb5_free_last_req
+ (krb5_context, krb5_last_req_entry ** );
+void krb5_free_enc_kdc_rep_part
+ (krb5_context, krb5_enc_kdc_rep_part * );
+
+void krb5_free_error
+ (krb5_context, krb5_error * );
+
+void krb5_free_ap_req
+ (krb5_context, krb5_ap_req * );
+void krb5_free_ap_rep
+ (krb5_context, krb5_ap_rep * );
+void krb5_free_cred
+ (krb5_context, krb5_cred *);
+
+void krb5_free_creds
+ (krb5_context, krb5_creds *);
+void krb5_free_cred_contents
+ (krb5_context, krb5_creds *);
+
+void krb5_free_cred_enc_part
+ (krb5_context, krb5_cred_enc_part *);
+
+void krb5_free_checksum
+ (krb5_context, krb5_checksum *);
+void krb5_free_checksum_contents
+ (krb5_context, krb5_checksum *);
+void krb5_free_keyblock
+ (krb5_context, krb5_keyblock *);
+void krb5_free_keyblock_contents
+ (krb5_context, krb5_keyblock *);
+
+void krb5_free_pa_data
+ (krb5_context, krb5_pa_data **);
+
+void krb5_free_ap_rep_enc_part
+ (krb5_context, krb5_ap_rep_enc_part *);
+
+void krb5_free_tkt_authent
+ (krb5_context, krb5_tkt_authent *);
+void krb5_free_pwd_data
+ (krb5_context, krb5_pwd_data *);
+void krb5_free_pwd_sequences
+ (krb5_context, passwd_phrase_element **);
+
+void krb5_free_data
+ (krb5_context, krb5_data *);
+void krb5_free_data_contents
+ (krb5_context, krb5_data *);
+void krb5_free_unparsed_name
+ (krb5_context, char *);
+void krb5_free_cksumtypes
+ (krb5_context, krb5_cksumtype *);
+
+
+krb5_error_code krb5_us_timeofday
+ (krb5_context,
+  krb5_int32 *,
+  krb5_int32 * );
+krb5_error_code krb5_timeofday
+ (krb5_context,
+  krb5_int32 * );
+
+krb5_error_code krb5_os_localaddr
+ (krb5_context,
+  krb5_address ***);
+krb5_error_code krb5_get_default_realm
+ (krb5_context,
+   char ** );
+krb5_error_code krb5_set_default_realm
+ (krb5_context,
+     const char * );
+void krb5_free_default_realm
+ (krb5_context,
+     char * );
+krb5_error_code krb5_sname_to_principal
+ (krb5_context,
+  const char *,
+     const char *,
+     krb5_int32,
+     krb5_principal *);
+krb5_error_code
+krb5_change_password
+ (krb5_context context, krb5_creds *creds, char *newpw,
+   int *result_code, krb5_data *result_code_string,
+   krb5_data *result_string);
+krb5_error_code
+krb5_set_password
+ (krb5_context context, krb5_creds *creds, char *newpw, krb5_principal change_password_for,
+   int *result_code, krb5_data *result_code_string, krb5_data *result_string);
+krb5_error_code
+krb5_set_password_using_ccache
+ (krb5_context context, krb5_ccache ccache, char *newpw, krb5_principal change_password_for,
+   int *result_code, krb5_data *result_code_string, krb5_data *result_string);
+
+
+krb5_error_code krb5_set_config_files
+ (krb5_context, const char **);
+
+krb5_error_code krb5_get_default_config_files
+ (char ***filenames);
+
+void krb5_free_config_files
+ (char **filenames);
+
+
+krb5_error_code
+krb5_get_profile
+ (krb5_context, struct _profile_t * *);
+
+
+krb5_error_code krb5_send_tgs
+ (krb5_context,
+  krb5_flags,
+  const krb5_ticket_times *,
+  const krb5_enctype *,
+  krb5_const_principal,
+  krb5_address * const *,
+  krb5_authdata * const *,
+  krb5_pa_data * const *,
+  const krb5_data *,
+  krb5_creds *,
+  krb5_response * );
+
+
+
+krb5_error_code krb5_get_in_tkt
+ (krb5_context,
+  krb5_flags,
+  krb5_address * const *,
+  krb5_enctype *,
+  krb5_preauthtype *,
+  krb5_error_code ( * )(krb5_context,
+     krb5_enctype,
+     krb5_data *,
+     krb5_const_pointer,
+     krb5_keyblock **),
+  krb5_const_pointer,
+  krb5_error_code ( * )(krb5_context,
+     const krb5_keyblock *,
+     krb5_const_pointer,
+     krb5_kdc_rep * ),
+  krb5_const_pointer,
+  krb5_creds *,
+  krb5_ccache,
+  krb5_kdc_rep ** );
+
+krb5_error_code krb5_get_in_tkt_with_password
+ (krb5_context,
+  krb5_flags,
+  krb5_address * const *,
+  krb5_enctype *,
+  krb5_preauthtype *,
+  const char *,
+  krb5_ccache,
+  krb5_creds *,
+  krb5_kdc_rep ** );
+
+krb5_error_code krb5_get_in_tkt_with_skey
+ (krb5_context,
+  krb5_flags,
+  krb5_address * const *,
+  krb5_enctype *,
+  krb5_preauthtype *,
+  const krb5_keyblock *,
+  krb5_ccache,
+  krb5_creds *,
+  krb5_kdc_rep ** );
+
+krb5_error_code krb5_get_in_tkt_with_keytab
+ (krb5_context,
+  krb5_flags,
+  krb5_address * const *,
+  krb5_enctype *,
+  krb5_preauthtype *,
+  krb5_keytab,
+  krb5_ccache,
+  krb5_creds *,
+  krb5_kdc_rep ** );
+
+
+
+krb5_error_code krb5_decode_kdc_rep
+ (krb5_context,
+  krb5_data *,
+  const krb5_keyblock *,
+  krb5_kdc_rep ** );
+
+
+krb5_error_code krb5_rd_req
+ (krb5_context,
+  krb5_auth_context *,
+  const krb5_data *,
+  krb5_const_principal,
+  krb5_keytab,
+  krb5_flags *,
+  krb5_ticket **);
+
+
+krb5_error_code krb5_rd_req_decoded
+ (krb5_context,
+  krb5_auth_context *,
+  const krb5_ap_req *,
+  krb5_const_principal,
+  krb5_keytab,
+  krb5_flags *,
+  krb5_ticket **);
+
+krb5_error_code krb5_rd_req_decoded_anyflag
+ (krb5_context,
+  krb5_auth_context *,
+  const krb5_ap_req *,
+  krb5_const_principal,
+  krb5_keytab,
+  krb5_flags *,
+  krb5_ticket **);
+
+
+krb5_error_code krb5_kt_read_service_key
+ (krb5_context,
+  krb5_pointer,
+  krb5_principal,
+  krb5_kvno,
+  krb5_enctype,
+  krb5_keyblock **);
+krb5_error_code krb5_mk_safe
+ (krb5_context,
+  krb5_auth_context,
+  const krb5_data *,
+  krb5_data *,
+  krb5_replay_data *);
+krb5_error_code krb5_mk_priv
+ (krb5_context,
+  krb5_auth_context,
+  const krb5_data *,
+  krb5_data *,
+  krb5_replay_data *);
+
+krb5_error_code krb5_cc_register
+ (krb5_context,
+  krb5_cc_ops *,
+  krb5_boolean );
+
+
+krb5_error_code krb5_sendauth
+ (krb5_context,
+  krb5_auth_context *,
+  krb5_pointer,
+  char *,
+  krb5_principal,
+  krb5_principal,
+  krb5_flags,
+  krb5_data *,
+  krb5_creds *,
+  krb5_ccache,
+  krb5_error **,
+  krb5_ap_rep_enc_part **,
+  krb5_creds **);
+
+krb5_error_code krb5_recvauth
+ (krb5_context,
+  krb5_auth_context *,
+  krb5_pointer,
+  char *,
+  krb5_principal,
+  krb5_int32,
+  krb5_keytab,
+  krb5_ticket **);
+krb5_error_code krb5_recvauth_version
+ (krb5_context,
+  krb5_auth_context *,
+  krb5_pointer,
+  krb5_principal,
+  krb5_int32,
+  krb5_keytab,
+  krb5_ticket **,
+  krb5_data *);
+
+
+krb5_error_code krb5_walk_realm_tree
+ (krb5_context,
+  const krb5_data *,
+  const krb5_data *,
+  krb5_principal **,
+  int);
+
+
+krb5_error_code krb5_mk_ncred
+ (krb5_context,
+  krb5_auth_context,
+  krb5_creds **,
+  krb5_data **,
+  krb5_replay_data *);
+
+krb5_error_code krb5_mk_1cred
+ (krb5_context,
+  krb5_auth_context,
+  krb5_creds *,
+  krb5_data **,
+  krb5_replay_data *);
+
+krb5_error_code krb5_rd_cred
+ (krb5_context,
+  krb5_auth_context,
+  krb5_data *,
+  krb5_creds ***,
+  krb5_replay_data *);
+
+krb5_error_code krb5_fwd_tgt_creds
+ (krb5_context,
+  krb5_auth_context,
+  char *,
+  krb5_principal,
+  krb5_principal,
+  krb5_ccache,
+  int forwardable,
+  krb5_data *);
+
+krb5_error_code krb5_auth_con_init
+ (krb5_context,
+  krb5_auth_context *);
+
+krb5_error_code krb5_auth_con_free
+ (krb5_context,
+  krb5_auth_context);
+
+krb5_error_code krb5_auth_con_setflags
+ (krb5_context,
+  krb5_auth_context,
+  krb5_int32);
+
+krb5_error_code krb5_auth_con_getflags
+ (krb5_context,
+  krb5_auth_context,
+  krb5_int32 *);
+
+krb5_error_code
+krb5_auth_con_set_checksum_func (krb5_context, krb5_auth_context,
+     krb5_mk_req_checksum_func, void *);
+
+krb5_error_code
+krb5_auth_con_get_checksum_func( krb5_context, krb5_auth_context,
+     krb5_mk_req_checksum_func *, void **);
+
+krb5_error_code krb5_auth_con_setaddrs
+ (krb5_context,
+  krb5_auth_context,
+  krb5_address *,
+  krb5_address *);
+
+krb5_error_code krb5_auth_con_getaddrs
+ (krb5_context,
+  krb5_auth_context,
+  krb5_address **,
+  krb5_address **);
+
+krb5_error_code krb5_auth_con_setports
+ (krb5_context,
+  krb5_auth_context,
+  krb5_address *,
+  krb5_address *);
+
+krb5_error_code krb5_auth_con_setuseruserkey
+ (krb5_context,
+  krb5_auth_context,
+  krb5_keyblock *);
+
+krb5_error_code krb5_auth_con_getkey
+ (krb5_context,
+  krb5_auth_context,
+  krb5_keyblock **);
+
+krb5_error_code krb5_auth_con_getsendsubkey(
+    krb5_context, krb5_auth_context, krb5_keyblock **);
+
+krb5_error_code krb5_auth_con_getrecvsubkey(
+    krb5_context, krb5_auth_context, krb5_keyblock **);
+
+krb5_error_code krb5_auth_con_setsendsubkey(
+    krb5_context, krb5_auth_context, krb5_keyblock *);
+
+krb5_error_code krb5_auth_con_setrecvsubkey(
+    krb5_context, krb5_auth_context, krb5_keyblock *);
+
+
+krb5_error_code krb5_auth_con_getlocalsubkey
+ (krb5_context,
+  krb5_auth_context,
+  krb5_keyblock **);
+
+krb5_error_code krb5_auth_con_getremotesubkey
+ (krb5_context,
+  krb5_auth_context,
+  krb5_keyblock **);
+
+
+
+krb5_error_code krb5_auth_con_set_req_cksumtype
+ (krb5_context,
+  krb5_auth_context,
+  krb5_cksumtype);
+
+krb5_error_code krb5_auth_con_set_safe_cksumtype
+ (krb5_context,
+  krb5_auth_context,
+  krb5_cksumtype);
+
+
+krb5_error_code krb5_auth_con_getlocalseqnumber
+ (krb5_context,
+  krb5_auth_context,
+  krb5_int32 *);
+
+krb5_error_code krb5_auth_con_getremoteseqnumber
+ (krb5_context,
+  krb5_auth_context,
+  krb5_int32 *);
+
+
+krb5_error_code krb5_auth_con_initivector
+ (krb5_context,
+  krb5_auth_context);
+
+
+
+krb5_error_code krb5_auth_con_setivector
+ (krb5_context,
+  krb5_auth_context,
+  krb5_pointer);
+
+krb5_error_code krb5_auth_con_getivector
+ (krb5_context,
+  krb5_auth_context,
+  krb5_pointer *);
+
+
+krb5_error_code krb5_auth_con_setrcache
+ (krb5_context,
+  krb5_auth_context,
+  krb5_rcache);
+
+krb5_error_code krb5_auth_con_getrcache
+ (krb5_context,
+  krb5_auth_context,
+  krb5_rcache *);
+
+
+krb5_error_code krb5_auth_con_setpermetypes
+ (krb5_context,
+     krb5_auth_context,
+     const krb5_enctype *);
+
+krb5_error_code krb5_auth_con_getpermetypes
+ (krb5_context,
+     krb5_auth_context,
+     krb5_enctype **);
+
+
+krb5_error_code krb5_auth_con_getauthenticator
+ (krb5_context,
+  krb5_auth_context,
+  krb5_authenticator **);
+krb5_error_code krb5_read_message (krb5_context, krb5_pointer, krb5_data *);
+krb5_error_code krb5_write_message (krb5_context, krb5_pointer, krb5_data *);
+int krb5_net_read (krb5_context, int , char *, int);
+int krb5_net_write (krb5_context, int , const char *, int);
+
+
+krb5_error_code krb5_read_password
+ (krb5_context,
+  const char *,
+  const char *,
+  char *,
+  unsigned int * );
+krb5_error_code krb5_aname_to_localname
+ (krb5_context,
+  krb5_const_principal,
+  int,
+  char * );
+krb5_error_code krb5_get_host_realm
+ (krb5_context,
+  const char *,
+  char *** );
+krb5_error_code krb5_free_host_realm
+ (krb5_context,
+  char * const * );
+
+krb5_error_code krb5_get_realm_domain
+ (krb5_context,
+  const char *,
+  char ** );
+
+krb5_boolean krb5_kuserok
+ (krb5_context,
+  krb5_principal, const char *);
+krb5_error_code krb5_auth_con_genaddrs
+ (krb5_context,
+  krb5_auth_context,
+  int, int);
+
+krb5_error_code krb5_gen_portaddr
+ (krb5_context,
+  const krb5_address *,
+  krb5_const_pointer,
+  krb5_address **);
+krb5_error_code krb5_gen_replay_name
+ (krb5_context,
+  const krb5_address *,
+  const char *,
+  char **);
+krb5_error_code krb5_make_fulladdr
+ (krb5_context,
+  krb5_address *,
+  krb5_address *,
+  krb5_address *);
+
+
+krb5_error_code krb5_set_real_time
+ (krb5_context, krb5_int32, krb5_int32);
+
+
+krb5_error_code krb5_set_debugging_time
+ (krb5_context, krb5_int32, krb5_int32);
+krb5_error_code krb5_use_natural_time
+ (krb5_context);
+
+krb5_error_code krb5_get_time_offsets
+ (krb5_context, krb5_int32 *, krb5_int32 *);
+
+krb5_error_code krb5_set_time_offsets
+ (krb5_context, krb5_int32, krb5_int32);
+
+
+
+krb5_error_code krb5_string_to_enctype
+ (char *, krb5_enctype *);
+krb5_error_code krb5_string_to_salttype
+ (char *, krb5_int32 *);
+krb5_error_code krb5_string_to_cksumtype
+ (char *, krb5_cksumtype *);
+krb5_error_code krb5_string_to_timestamp
+ (char *, krb5_timestamp *);
+krb5_error_code krb5_string_to_deltat
+ (char *, krb5_deltat *);
+krb5_error_code krb5_enctype_to_string
+ (krb5_enctype, char *, size_t);
+krb5_error_code krb5_salttype_to_string
+ (krb5_int32, char *, size_t);
+krb5_error_code krb5_cksumtype_to_string
+ (krb5_cksumtype, char *, size_t);
+krb5_error_code krb5_timestamp_to_string
+ (krb5_timestamp, char *, size_t);
+krb5_error_code krb5_timestamp_to_sfstring
+ (krb5_timestamp, char *, size_t, char *);
+krb5_error_code krb5_deltat_to_string
+ (krb5_deltat, char *, size_t);
+typedef struct _krb5_prompt {
+    char *prompt;
+    int hidden;
+    krb5_data *reply;
+} krb5_prompt;
+
+typedef krb5_error_code ( *krb5_prompter_fct)(krb5_context context,
+          void *data,
+          const char *name,
+          const char *banner,
+          int num_prompts,
+          krb5_prompt prompts[]);
+
+
+krb5_error_code
+krb5_prompter_posix (krb5_context context,
+  void *data,
+  const char *name,
+  const char *banner,
+  int num_prompts,
+  krb5_prompt prompts[]);
+
+typedef struct _krb5_get_init_creds_opt {
+    krb5_flags flags;
+    krb5_deltat tkt_life;
+    krb5_deltat renew_life;
+    int forwardable;
+    int proxiable;
+    krb5_enctype *etype_list;
+    int etype_list_length;
+    krb5_address **address_list;
+    krb5_preauthtype *preauth_list;
+    int preauth_list_length;
+    krb5_data *salt;
+} krb5_get_init_creds_opt;
+void
+krb5_get_init_creds_opt_init
+(krb5_get_init_creds_opt *opt);
+
+void
+krb5_get_init_creds_opt_set_tkt_life
+(krb5_get_init_creds_opt *opt,
+  krb5_deltat tkt_life);
+
+void
+krb5_get_init_creds_opt_set_renew_life
+(krb5_get_init_creds_opt *opt,
+  krb5_deltat renew_life);
+
+void
+krb5_get_init_creds_opt_set_forwardable
+(krb5_get_init_creds_opt *opt,
+  int forwardable);
+
+void
+krb5_get_init_creds_opt_set_proxiable
+(krb5_get_init_creds_opt *opt,
+  int proxiable);
+
+void
+krb5_get_init_creds_opt_set_etype_list
+(krb5_get_init_creds_opt *opt,
+  krb5_enctype *etype_list,
+  int etype_list_length);
+
+void
+krb5_get_init_creds_opt_set_address_list
+(krb5_get_init_creds_opt *opt,
+  krb5_address **addresses);
+
+void
+krb5_get_init_creds_opt_set_preauth_list
+(krb5_get_init_creds_opt *opt,
+  krb5_preauthtype *preauth_list,
+  int preauth_list_length);
+
+void
+krb5_get_init_creds_opt_set_salt
+(krb5_get_init_creds_opt *opt,
+  krb5_data *salt);
+
+
+
+krb5_error_code
+krb5_get_init_creds_password
+(krb5_context context,
+  krb5_creds *creds,
+  krb5_principal client,
+  char *password,
+  krb5_prompter_fct prompter,
+  void *data,
+  krb5_deltat start_time,
+  char *in_tkt_service,
+  krb5_get_init_creds_opt *k5_gic_options);
+
+krb5_error_code
+krb5_get_init_creds_keytab
+(krb5_context context,
+  krb5_creds *creds,
+  krb5_principal client,
+  krb5_keytab arg_keytab,
+  krb5_deltat start_time,
+  char *in_tkt_service,
+  krb5_get_init_creds_opt *k5_gic_options);
+
+typedef struct _krb5_verify_init_creds_opt {
+    krb5_flags flags;
+    int ap_req_nofail;
+} krb5_verify_init_creds_opt;
+
+
+
+void
+krb5_verify_init_creds_opt_init
+(krb5_verify_init_creds_opt *k5_vic_options);
+void
+krb5_verify_init_creds_opt_set_ap_req_nofail
+(krb5_verify_init_creds_opt *k5_vic_options,
+  int ap_req_nofail);
+
+krb5_error_code
+krb5_verify_init_creds
+(krb5_context context,
+  krb5_creds *creds,
+  krb5_principal ap_req_server,
+  krb5_keytab ap_req_keytab,
+  krb5_ccache *ccache,
+  krb5_verify_init_creds_opt *k5_vic_options);
+
+krb5_error_code
+krb5_get_validated_creds
+(krb5_context context,
+  krb5_creds *creds,
+  krb5_principal client,
+  krb5_ccache ccache,
+  char *in_tkt_service);
+
+krb5_error_code
+krb5_get_renewed_creds
+(krb5_context context,
+  krb5_creds *creds,
+  krb5_principal client,
+  krb5_ccache ccache,
+  char *in_tkt_service);
+
+krb5_error_code
+krb5_decode_ticket
+(const krb5_data *code,
+  krb5_ticket **rep);
+
+void
+krb5_appdefault_string
+(krb5_context context,
+  const char *appname,
+         const krb5_data *realm,
+   const char *option,
+  const char *default_value,
+  char ** ret_value);
+
+void
+krb5_appdefault_boolean
+(krb5_context context,
+  const char *appname,
+         const krb5_data *realm,
+   const char *option,
+  int default_value,
+  int *ret_value);
+
+
+
+
+
+
+krb5_error_code krb5_realm_iterator_create
+ (krb5_context context, void **iter_p);
+
+krb5_error_code krb5_realm_iterator
+ (krb5_context context, void **iter_p, char **ret_realm);
+
+void krb5_realm_iterator_free
+ (krb5_context context, void **iter_p);
+
+void krb5_free_realm_string
+ (krb5_context context, char *str);
+typedef krb5_int32 krb5_prompt_type;
+
+krb5_prompt_type* krb5_get_prompt_types
+ (krb5_context context);
+
+
+
+
+
+
+
+typedef __builtin_va_list __gnuc_va_list;
+typedef __gnuc_va_list va_list;
+
+
+typedef long errcode_t;
+typedef void (*et_old_error_hook_func) (const char *, errcode_t,
+     const char *, va_list ap);
+
+struct error_table {
+              char const * const * msgs;
+        long base;
+ unsigned int n_msgs;
+};
+
+
+
+
+
+
+extern void com_err
+ (const char *, errcode_t, const char *, ...);
+extern void com_err_va
+ (const char *whoami, errcode_t code, const char *fmt,
+  va_list ap);
+extern const char * error_message
+ (errcode_t)
+                                   ;
+extern errcode_t add_error_table
+ ( const struct error_table *)
+                                   ;
+extern errcode_t remove_error_table
+ (const struct error_table *)
+                                   ;
+extern et_old_error_hook_func set_com_err_hook (et_old_error_hook_func);
+extern et_old_error_hook_func reset_com_err_hook (void);
+extern const struct error_table et_krb5_error_table;
+
+
+
+extern void initialize_krb5_error_table (void) ;
+extern const struct error_table et_kdb5_error_table;
+
+
+
+extern void initialize_kdb5_error_table (void) ;
+extern const struct error_table et_kv5m_error_table;
+
+
+
+extern void initialize_kv5m_error_table (void) ;
+extern const struct error_table et_k524_error_table;
+
+
+
+extern void initialize_k524_error_table (void) ;
+extern const struct error_table et_asn1_error_table;
+
+
+
+extern void initialize_asn1_error_table (void) ;
+typedef struct _profile_t *profile_t;
+typedef char* profile_filespec_t;
+typedef char* profile_filespec_list_t;
+typedef const char * const_profile_filespec_t;
+typedef const char * const_profile_filespec_list_t;
+
+long profile_init
+ (const_profile_filespec_t *files, profile_t *ret_profile);
+
+long profile_init_path
+ (const_profile_filespec_list_t filelist, profile_t *ret_profile);
+
+long profile_flush
+ (profile_t profile);
+long profile_flush_to_file
+ (profile_t profile, const_profile_filespec_t outfile);
+long profile_flush_to_buffer
+ (profile_t profile, char **bufp);
+void profile_free_buffer
+ (profile_t profile, char *buf);
+
+long profile_is_writable
+ (profile_t profile, int *writable);
+long profile_is_modified
+ (profile_t profile, int *modified);
+
+void profile_abandon
+ (profile_t profile);
+
+void profile_release
+ (profile_t profile);
+
+long profile_get_values
+ (profile_t profile, const char *const *names, char ***ret_values);
+
+void profile_free_list
+ (char **list);
+
+long profile_get_string
+ (profile_t profile, const char *name, const char *subname,
+   const char *subsubname, const char *def_val,
+   char **ret_string);
+long profile_get_integer
+ (profile_t profile, const char *name, const char *subname,
+   const char *subsubname, int def_val,
+   int *ret_default);
+
+long profile_get_boolean
+ (profile_t profile, const char *name, const char *subname,
+   const char *subsubname, int def_val,
+   int *ret_default);
+
+long profile_get_relation_names
+ (profile_t profile, const char **names, char ***ret_names);
+
+long profile_get_subsection_names
+ (profile_t profile, const char **names, char ***ret_names);
+
+long profile_iterator_create
+ (profile_t profile, const char *const *names,
+     int flags, void **ret_iter);
+
+void profile_iterator_free
+ (void **iter_p);
+
+long profile_iterator
+ (void **iter_p, char **ret_name, char **ret_value);
+
+void profile_release_string (char *str);
+
+long profile_update_relation
+ (profile_t profile, const char **names,
+     const char *old_value, const char *new_value);
+
+long profile_clear_relation
+ (profile_t profile, const char **names);
+
+long profile_rename_section
+ (profile_t profile, const char **names,
+     const char *new_name);
+
+long profile_add_relation
+ (profile_t profile, const char **names,
+     const char *new_value);
+extern const struct error_table et_prof_error_table;
+
+
+
+extern void initialize_prof_error_table (void) ;
+
+
+
+
+
+
+
+
+
+
+struct iovec
+  {
+    void *iov_base;
+    size_t iov_len;
+  };
+extern ssize_t readv (int __fd, const struct iovec *__iovec, int __count)
+  ;
+extern ssize_t writev (int __fd, const struct iovec *__iovec, int __count)
+  ;
+extern ssize_t preadv (int __fd, const struct iovec *__iovec, int __count,
+         __off_t __offset) ;
+extern ssize_t pwritev (int __fd, const struct iovec *__iovec, int __count,
+   __off_t __offset) ;
+
+
+
+
+
+
+
+typedef __socklen_t socklen_t;
+
+
+
+
+enum __socket_type
+{
+  SOCK_STREAM = 1,
+
+
+  SOCK_DGRAM = 2,
+
+
+  SOCK_RAW = 3,
+
+  SOCK_RDM = 4,
+
+  SOCK_SEQPACKET = 5,
+
+
+  SOCK_DCCP = 6,
+
+  SOCK_PACKET = 10,
+
+
+
+
+
+
+
+  SOCK_CLOEXEC = 02000000,
+
+
+  SOCK_NONBLOCK = 00004000
+
+
+};
+typedef unsigned short int sa_family_t;
+
+
+struct sockaddr
+  {
+    sa_family_t sa_family;
+    char sa_data[14];
+  };
+struct sockaddr_storage
+  {
+    sa_family_t ss_family;
+    unsigned long int __ss_align;
+    char __ss_padding[(128 - (2 * sizeof (unsigned long int)))];
+  };
+
+
+
+enum
+  {
+    MSG_OOB = 0x01,
+
+    MSG_PEEK = 0x02,
+
+    MSG_DONTROUTE = 0x04,
+
+
+
+
+
+
+    MSG_CTRUNC = 0x08,
+
+    MSG_PROXY = 0x10,
+
+    MSG_TRUNC = 0x20,
+
+    MSG_DONTWAIT = 0x40,
+
+    MSG_EOR = 0x80,
+
+    MSG_WAITALL = 0x100,
+
+    MSG_FIN = 0x200,
+
+    MSG_SYN = 0x400,
+
+    MSG_CONFIRM = 0x800,
+
+    MSG_RST = 0x1000,
+
+    MSG_ERRQUEUE = 0x2000,
+
+    MSG_NOSIGNAL = 0x4000,
+
+    MSG_MORE = 0x8000,
+
+    MSG_WAITFORONE = 0x10000,
+
+    MSG_FASTOPEN = 0x20000000,
+
+
+    MSG_CMSG_CLOEXEC = 0x40000000
+
+
+
+  };
+
+
+
+
+struct msghdr
+  {
+    void *msg_name;
+    socklen_t msg_namelen;
+
+    struct iovec *msg_iov;
+    size_t msg_iovlen;
+
+    void *msg_control;
+    size_t msg_controllen;
+
+
+
+
+    int msg_flags;
+  };
+
+
+struct cmsghdr
+  {
+    size_t cmsg_len;
+
+
+
+
+    int cmsg_level;
+    int cmsg_type;
+
+    __extension__ unsigned char __cmsg_data [];
+
+  };
+extern struct cmsghdr *__cmsg_nxthdr (struct msghdr *__mhdr,
+          struct cmsghdr *__cmsg) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+extern __inline __attribute__ ((__gnu_inline__)) struct cmsghdr *
+__attribute__ ((__nothrow__ , __leaf__)) __cmsg_nxthdr (struct msghdr *__mhdr, struct cmsghdr *__cmsg)
+{
+  if ((size_t) __cmsg->cmsg_len < sizeof (struct cmsghdr))
+
+    return (struct cmsghdr *) 0;
+
+  __cmsg = (struct cmsghdr *) ((unsigned char *) __cmsg
+          + (((__cmsg->cmsg_len) + sizeof (size_t) - 1) & (size_t) ~(sizeof (size_t) - 1)));
+  if ((unsigned char *) (__cmsg + 1) > ((unsigned char *) __mhdr->msg_control
+     + __mhdr->msg_controllen)
+      || ((unsigned char *) __cmsg + (((__cmsg->cmsg_len) + sizeof (size_t) - 1) & (size_t) ~(sizeof (size_t) - 1))
+   > ((unsigned char *) __mhdr->msg_control + __mhdr->msg_controllen)))
+
+    return (struct cmsghdr *) 0;
+  return __cmsg;
+}
+
+
+
+
+enum
+  {
+    SCM_RIGHTS = 0x01
+
+
+
+
+
+  };
+
+
+
+struct linger
+  {
+    int l_onoff;
+    int l_linger;
+  };
+
+
+
+
+struct osockaddr
+  {
+    unsigned short int sa_family;
+    unsigned char sa_data[14];
+  };
+
+
+
+
+enum
+{
+  SHUT_RD = 0,
+
+  SHUT_WR,
+
+  SHUT_RDWR
+
+};
+extern int socket (int __domain, int __type, int __protocol) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+extern int socketpair (int __domain, int __type, int __protocol,
+         int __fds[2]) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern int bind (int __fd, const struct sockaddr * __addr, socklen_t __len)
+     __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern int getsockname (int __fd, struct sockaddr *__restrict __addr,
+   socklen_t *__restrict __len) __attribute__ ((__nothrow__ , __leaf__));
+extern int connect (int __fd, const struct sockaddr * __addr, socklen_t __len);
+
+
+
+extern int getpeername (int __fd, struct sockaddr *__restrict __addr,
+   socklen_t *__restrict __len) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+extern ssize_t send (int __fd, const void *__buf, size_t __n, int __flags);
+
+
+
+
+
+
+extern ssize_t recv (int __fd, void *__buf, size_t __n, int __flags);
+
+
+
+
+
+
+extern ssize_t sendto (int __fd, const void *__buf, size_t __n,
+         int __flags, const struct sockaddr * __addr,
+         socklen_t __addr_len);
+extern ssize_t recvfrom (int __fd, void *__restrict __buf, size_t __n,
+    int __flags, struct sockaddr *__restrict __addr,
+    socklen_t *__restrict __addr_len);
+
+
+
+
+
+
+
+extern ssize_t sendmsg (int __fd, const struct msghdr *__message,
+   int __flags);
+extern ssize_t recvmsg (int __fd, struct msghdr *__message, int __flags);
+extern int getsockopt (int __fd, int __level, int __optname,
+         void *__restrict __optval,
+         socklen_t *__restrict __optlen) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+extern int setsockopt (int __fd, int __level, int __optname,
+         const void *__optval, socklen_t __optlen) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+extern int listen (int __fd, int __n) __attribute__ ((__nothrow__ , __leaf__));
+extern int accept (int __fd, struct sockaddr *__restrict __addr,
+     socklen_t *__restrict __addr_len);
+extern int shutdown (int __fd, int __how) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+extern int sockatmark (int __fd) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+
+extern int isfdtype (int __fd, int __fdtype) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+
+enum
+  {
+    IPPROTO_IP = 0,
+
+    IPPROTO_HOPOPTS = 0,
+
+    IPPROTO_ICMP = 1,
+
+    IPPROTO_IGMP = 2,
+
+    IPPROTO_IPIP = 4,
+
+    IPPROTO_TCP = 6,
+
+    IPPROTO_EGP = 8,
+
+    IPPROTO_PUP = 12,
+
+    IPPROTO_UDP = 17,
+
+    IPPROTO_IDP = 22,
+
+    IPPROTO_TP = 29,
+
+    IPPROTO_DCCP = 33,
+
+    IPPROTO_IPV6 = 41,
+
+    IPPROTO_ROUTING = 43,
+
+    IPPROTO_FRAGMENT = 44,
+
+    IPPROTO_RSVP = 46,
+
+    IPPROTO_GRE = 47,
+
+    IPPROTO_ESP = 50,
+
+    IPPROTO_AH = 51,
+
+    IPPROTO_ICMPV6 = 58,
+
+    IPPROTO_NONE = 59,
+
+    IPPROTO_DSTOPTS = 60,
+
+    IPPROTO_MTP = 92,
+
+    IPPROTO_ENCAP = 98,
+
+    IPPROTO_PIM = 103,
+
+    IPPROTO_COMP = 108,
+
+    IPPROTO_SCTP = 132,
+
+    IPPROTO_UDPLITE = 136,
+
+    IPPROTO_RAW = 255,
+
+    IPPROTO_MAX
+  };
+
+
+
+typedef uint16_t in_port_t;
+
+
+enum
+  {
+    IPPORT_ECHO = 7,
+    IPPORT_DISCARD = 9,
+    IPPORT_SYSTAT = 11,
+    IPPORT_DAYTIME = 13,
+    IPPORT_NETSTAT = 15,
+    IPPORT_FTP = 21,
+    IPPORT_TELNET = 23,
+    IPPORT_SMTP = 25,
+    IPPORT_TIMESERVER = 37,
+    IPPORT_NAMESERVER = 42,
+    IPPORT_WHOIS = 43,
+    IPPORT_MTP = 57,
+
+    IPPORT_TFTP = 69,
+    IPPORT_RJE = 77,
+    IPPORT_FINGER = 79,
+    IPPORT_TTYLINK = 87,
+    IPPORT_SUPDUP = 95,
+
+
+    IPPORT_EXECSERVER = 512,
+    IPPORT_LOGINSERVER = 513,
+    IPPORT_CMDSERVER = 514,
+    IPPORT_EFSSERVER = 520,
+
+
+    IPPORT_BIFFUDP = 512,
+    IPPORT_WHOSERVER = 513,
+    IPPORT_ROUTESERVER = 520,
+
+
+    IPPORT_RESERVED = 1024,
+
+
+    IPPORT_USERRESERVED = 5000
+  };
+
+
+
+typedef uint32_t in_addr_t;
+struct in_addr
+  {
+    in_addr_t s_addr;
+  };
+struct in6_addr
+  {
+    union
+      {
+ uint8_t __u6_addr8[16];
+
+ uint16_t __u6_addr16[8];
+ uint32_t __u6_addr32[4];
+
+      } __in6_u;
+
+
+
+
+
+  };
+
+extern const struct in6_addr in6addr_any;
+extern const struct in6_addr in6addr_loopback;
+struct sockaddr_in
+  {
+    sa_family_t sin_family;
+    in_port_t sin_port;
+    struct in_addr sin_addr;
+
+
+    unsigned char sin_zero[sizeof (struct sockaddr) -
+      (sizeof (unsigned short int)) -
+      sizeof (in_port_t) -
+      sizeof (struct in_addr)];
+  };
+
+
+struct sockaddr_in6
+  {
+    sa_family_t sin6_family;
+    in_port_t sin6_port;
+    uint32_t sin6_flowinfo;
+    struct in6_addr sin6_addr;
+    uint32_t sin6_scope_id;
+  };
+
+
+
+
+struct ip_mreq
+  {
+
+    struct in_addr imr_multiaddr;
+
+
+    struct in_addr imr_interface;
+  };
+
+struct ip_mreq_source
+  {
+
+    struct in_addr imr_multiaddr;
+
+
+    struct in_addr imr_interface;
+
+
+    struct in_addr imr_sourceaddr;
+  };
+
+
+
+
+struct ipv6_mreq
+  {
+
+    struct in6_addr ipv6mr_multiaddr;
+
+
+    unsigned int ipv6mr_interface;
+  };
+
+
+
+
+struct group_req
+  {
+
+    uint32_t gr_interface;
+
+
+    struct sockaddr_storage gr_group;
+  };
+
+struct group_source_req
+  {
+
+    uint32_t gsr_interface;
+
+
+    struct sockaddr_storage gsr_group;
+
+
+    struct sockaddr_storage gsr_source;
+  };
+
+
+
+struct ip_msfilter
+  {
+
+    struct in_addr imsf_multiaddr;
+
+
+    struct in_addr imsf_interface;
+
+
+    uint32_t imsf_fmode;
+
+
+    uint32_t imsf_numsrc;
+
+    struct in_addr imsf_slist[1];
+  };
+
+
+
+
+
+struct group_filter
+  {
+
+    uint32_t gf_interface;
+
+
+    struct sockaddr_storage gf_group;
+
+
+    uint32_t gf_fmode;
+
+
+    uint32_t gf_numsrc;
+
+    struct sockaddr_storage gf_slist[1];
+};
+struct ip_opts
+  {
+    struct in_addr ip_dst;
+    char ip_opts[40];
+  };
+
+
+struct ip_mreqn
+  {
+    struct in_addr imr_multiaddr;
+    struct in_addr imr_address;
+    int imr_ifindex;
+  };
+
+
+struct in_pktinfo
+  {
+    int ipi_ifindex;
+    struct in_addr ipi_spec_dst;
+    struct in_addr ipi_addr;
+  };
+extern uint32_t ntohl (uint32_t __netlong) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__));
+extern uint16_t ntohs (uint16_t __netshort)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__));
+extern uint32_t htonl (uint32_t __hostlong)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__));
+extern uint16_t htons (uint16_t __hostshort)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__));
+
+
+
+
+extern int bindresvport (int __sockfd, struct sockaddr_in *__sock_in) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern int bindresvport6 (int __sockfd, struct sockaddr_in6 *__sock_in)
+     __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+extern in_addr_t inet_addr (const char *__cp) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern in_addr_t inet_lnaof (struct in_addr __in) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+extern struct in_addr inet_makeaddr (in_addr_t __net, in_addr_t __host)
+     __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern in_addr_t inet_netof (struct in_addr __in) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+extern in_addr_t inet_network (const char *__cp) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+extern char *inet_ntoa (struct in_addr __in) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+extern int inet_pton (int __af, const char *__restrict __cp,
+        void *__restrict __buf) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+extern const char *inet_ntop (int __af, const void *__restrict __cp,
+         char *__restrict __buf, socklen_t __len)
+     __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+extern int inet_aton (const char *__cp, struct in_addr *__inp) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+extern char *inet_neta (in_addr_t __net, char *__buf, size_t __len) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+extern char *inet_net_ntop (int __af, const void *__cp, int __bits,
+       char *__buf, size_t __len) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+extern int inet_net_pton (int __af, const char *__cp,
+     void *__buf, size_t __len) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+extern unsigned int inet_nsap_addr (const char *__cp,
+        unsigned char *__buf, int __len) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+extern char *inet_nsap_ntoa (int __len, const unsigned char *__cp,
+        char *__buf) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+struct rpcent
+{
+  char *r_name;
+  char **r_aliases;
+  int r_number;
+};
+
+extern void setrpcent (int __stayopen) __attribute__ ((__nothrow__ , __leaf__));
+extern void endrpcent (void) __attribute__ ((__nothrow__ , __leaf__));
+extern struct rpcent *getrpcbyname (const char *__name) __attribute__ ((__nothrow__ , __leaf__));
+extern struct rpcent *getrpcbynumber (int __number) __attribute__ ((__nothrow__ , __leaf__));
+extern struct rpcent *getrpcent (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern int getrpcbyname_r (const char *__name, struct rpcent *__result_buf,
+      char *__buffer, size_t __buflen,
+      struct rpcent **__result) __attribute__ ((__nothrow__ , __leaf__));
+
+extern int getrpcbynumber_r (int __number, struct rpcent *__result_buf,
+        char *__buffer, size_t __buflen,
+        struct rpcent **__result) __attribute__ ((__nothrow__ , __leaf__));
+
+extern int getrpcent_r (struct rpcent *__result_buf, char *__buffer,
+   size_t __buflen, struct rpcent **__result) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+struct netent
+{
+  char *n_name;
+  char **n_aliases;
+  int n_addrtype;
+  uint32_t n_net;
+};
+
+
+
+
+
+
+
+
+extern int *__h_errno_location (void) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__));
+extern void herror (const char *__str) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern const char *hstrerror (int __err_num) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+struct hostent
+{
+  char *h_name;
+  char **h_aliases;
+  int h_addrtype;
+  int h_length;
+  char **h_addr_list;
+
+
+
+};
+
+
+
+
+
+
+extern void sethostent (int __stay_open);
+
+
+
+
+
+extern void endhostent (void);
+
+
+
+
+
+
+extern struct hostent *gethostent (void);
+
+
+
+
+
+
+extern struct hostent *gethostbyaddr (const void *__addr, __socklen_t __len,
+          int __type);
+
+
+
+
+
+extern struct hostent *gethostbyname (const char *__name);
+extern struct hostent *gethostbyname2 (const char *__name, int __af);
+extern int gethostent_r (struct hostent *__restrict __result_buf,
+    char *__restrict __buf, size_t __buflen,
+    struct hostent **__restrict __result,
+    int *__restrict __h_errnop);
+
+extern int gethostbyaddr_r (const void *__restrict __addr, __socklen_t __len,
+       int __type,
+       struct hostent *__restrict __result_buf,
+       char *__restrict __buf, size_t __buflen,
+       struct hostent **__restrict __result,
+       int *__restrict __h_errnop);
+
+extern int gethostbyname_r (const char *__restrict __name,
+       struct hostent *__restrict __result_buf,
+       char *__restrict __buf, size_t __buflen,
+       struct hostent **__restrict __result,
+       int *__restrict __h_errnop);
+
+extern int gethostbyname2_r (const char *__restrict __name, int __af,
+        struct hostent *__restrict __result_buf,
+        char *__restrict __buf, size_t __buflen,
+        struct hostent **__restrict __result,
+        int *__restrict __h_errnop);
+extern void setnetent (int __stay_open);
+
+
+
+
+
+extern void endnetent (void);
+
+
+
+
+
+
+extern struct netent *getnetent (void);
+
+
+
+
+
+
+extern struct netent *getnetbyaddr (uint32_t __net, int __type);
+
+
+
+
+
+extern struct netent *getnetbyname (const char *__name);
+extern int getnetent_r (struct netent *__restrict __result_buf,
+   char *__restrict __buf, size_t __buflen,
+   struct netent **__restrict __result,
+   int *__restrict __h_errnop);
+
+extern int getnetbyaddr_r (uint32_t __net, int __type,
+      struct netent *__restrict __result_buf,
+      char *__restrict __buf, size_t __buflen,
+      struct netent **__restrict __result,
+      int *__restrict __h_errnop);
+
+extern int getnetbyname_r (const char *__restrict __name,
+      struct netent *__restrict __result_buf,
+      char *__restrict __buf, size_t __buflen,
+      struct netent **__restrict __result,
+      int *__restrict __h_errnop);
+
+
+
+
+struct servent
+{
+  char *s_name;
+  char **s_aliases;
+  int s_port;
+  char *s_proto;
+};
+
+
+
+
+
+
+extern void setservent (int __stay_open);
+
+
+
+
+
+extern void endservent (void);
+
+
+
+
+
+
+extern struct servent *getservent (void);
+
+
+
+
+
+
+extern struct servent *getservbyname (const char *__name, const char *__proto);
+
+
+
+
+
+
+extern struct servent *getservbyport (int __port, const char *__proto);
+extern int getservent_r (struct servent *__restrict __result_buf,
+    char *__restrict __buf, size_t __buflen,
+    struct servent **__restrict __result);
+
+extern int getservbyname_r (const char *__restrict __name,
+       const char *__restrict __proto,
+       struct servent *__restrict __result_buf,
+       char *__restrict __buf, size_t __buflen,
+       struct servent **__restrict __result);
+
+extern int getservbyport_r (int __port, const char *__restrict __proto,
+       struct servent *__restrict __result_buf,
+       char *__restrict __buf, size_t __buflen,
+       struct servent **__restrict __result);
+
+
+
+
+struct protoent
+{
+  char *p_name;
+  char **p_aliases;
+  int p_proto;
+};
+
+
+
+
+
+
+extern void setprotoent (int __stay_open);
+
+
+
+
+
+extern void endprotoent (void);
+
+
+
+
+
+
+extern struct protoent *getprotoent (void);
+
+
+
+
+
+extern struct protoent *getprotobyname (const char *__name);
+
+
+
+
+
+extern struct protoent *getprotobynumber (int __proto);
+extern int getprotoent_r (struct protoent *__restrict __result_buf,
+     char *__restrict __buf, size_t __buflen,
+     struct protoent **__restrict __result);
+
+extern int getprotobyname_r (const char *__restrict __name,
+        struct protoent *__restrict __result_buf,
+        char *__restrict __buf, size_t __buflen,
+        struct protoent **__restrict __result);
+
+extern int getprotobynumber_r (int __proto,
+          struct protoent *__restrict __result_buf,
+          char *__restrict __buf, size_t __buflen,
+          struct protoent **__restrict __result);
+extern int setnetgrent (const char *__netgroup);
+
+
+
+
+
+
+
+extern void endnetgrent (void);
+extern int getnetgrent (char **__restrict __hostp,
+   char **__restrict __userp,
+   char **__restrict __domainp);
+extern int innetgr (const char *__netgroup, const char *__host,
+      const char *__user, const char *__domain);
+
+
+
+
+
+
+
+extern int getnetgrent_r (char **__restrict __hostp,
+     char **__restrict __userp,
+     char **__restrict __domainp,
+     char *__restrict __buffer, size_t __buflen);
+extern int rcmd (char **__restrict __ahost, unsigned short int __rport,
+   const char *__restrict __locuser,
+   const char *__restrict __remuser,
+   const char *__restrict __cmd, int *__restrict __fd2p);
+extern int rcmd_af (char **__restrict __ahost, unsigned short int __rport,
+      const char *__restrict __locuser,
+      const char *__restrict __remuser,
+      const char *__restrict __cmd, int *__restrict __fd2p,
+      sa_family_t __af);
+extern int rexec (char **__restrict __ahost, int __rport,
+    const char *__restrict __name,
+    const char *__restrict __pass,
+    const char *__restrict __cmd, int *__restrict __fd2p);
+extern int rexec_af (char **__restrict __ahost, int __rport,
+       const char *__restrict __name,
+       const char *__restrict __pass,
+       const char *__restrict __cmd, int *__restrict __fd2p,
+       sa_family_t __af);
+extern int ruserok (const char *__rhost, int __suser,
+      const char *__remuser, const char *__locuser);
+extern int ruserok_af (const char *__rhost, int __suser,
+         const char *__remuser, const char *__locuser,
+         sa_family_t __af);
+extern int iruserok (uint32_t __raddr, int __suser,
+       const char *__remuser, const char *__locuser);
+extern int iruserok_af (const void *__raddr, int __suser,
+   const char *__remuser, const char *__locuser,
+   sa_family_t __af);
+extern int rresvport (int *__alport);
+extern int rresvport_af (int *__alport, sa_family_t __af);
+
+
+
+
+
+
+struct addrinfo
+{
+  int ai_flags;
+  int ai_family;
+  int ai_socktype;
+  int ai_protocol;
+  socklen_t ai_addrlen;
+  struct sockaddr *ai_addr;
+  char *ai_canonname;
+  struct addrinfo *ai_next;
+};
+extern int getaddrinfo (const char *__restrict __name,
+   const char *__restrict __service,
+   const struct addrinfo *__restrict __req,
+   struct addrinfo **__restrict __pai);
+
+
+extern void freeaddrinfo (struct addrinfo *__ai) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern const char *gai_strerror (int __ecode) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+extern int getnameinfo (const struct sockaddr *__restrict __sa,
+   socklen_t __salen, char *__restrict __host,
+   socklen_t __hostlen, char *__restrict __serv,
+   socklen_t __servlen, int __flags);
+
+
+
+
+
+
+
+
+
+
+
+extern int __sigismember (const __sigset_t *, int);
+extern int __sigaddset (__sigset_t *, int);
+extern int __sigdelset (__sigset_t *, int);
+extern __inline __attribute__ ((__gnu_inline__)) int __sigismember (const __sigset_t *__set, int __sig) { unsigned long int __mask = (((unsigned long int) 1) << (((__sig) - 1) % (8 * sizeof (unsigned long int)))); unsigned long int __word = (((__sig) - 1) / (8 * sizeof (unsigned long int))); return (__set->__val[__word] & __mask) ? 1 : 0; }
+extern __inline __attribute__ ((__gnu_inline__)) int __sigaddset ( __sigset_t *__set, int __sig) { unsigned long int __mask = (((unsigned long int) 1) << (((__sig) - 1) % (8 * sizeof (unsigned long int)))); unsigned long int __word = (((__sig) - 1) / (8 * sizeof (unsigned long int))); return ((__set->__val[__word] |= __mask), 0); }
+extern __inline __attribute__ ((__gnu_inline__)) int __sigdelset ( __sigset_t *__set, int __sig) { unsigned long int __mask = (((unsigned long int) 1) << (((__sig) - 1) % (8 * sizeof (unsigned long int)))); unsigned long int __word = (((__sig) - 1) / (8 * sizeof (unsigned long int))); return ((__set->__val[__word] &= ~__mask), 0); }
+
+
+
+
+
+
+
+typedef __sig_atomic_t sig_atomic_t;
+
+
+
+
+
+
+
+
+typedef union sigval
+  {
+    int sival_int;
+    void *sival_ptr;
+  } sigval_t;
+typedef __clock_t __sigchld_clock_t;
+
+
+
+typedef struct
+  {
+    int si_signo;
+    int si_errno;
+
+    int si_code;
+
+    union
+      {
+ int _pad[((128 / sizeof (int)) - 4)];
+
+
+ struct
+   {
+     __pid_t si_pid;
+     __uid_t si_uid;
+   } _kill;
+
+
+ struct
+   {
+     int si_tid;
+     int si_overrun;
+     sigval_t si_sigval;
+   } _timer;
+
+
+ struct
+   {
+     __pid_t si_pid;
+     __uid_t si_uid;
+     sigval_t si_sigval;
+   } _rt;
+
+
+ struct
+   {
+     __pid_t si_pid;
+     __uid_t si_uid;
+     int si_status;
+     __sigchld_clock_t si_utime;
+     __sigchld_clock_t si_stime;
+   } _sigchld;
+
+
+ struct
+   {
+     void *si_addr;
+     short int si_addr_lsb;
+   } _sigfault;
+
+
+ struct
+   {
+     long int si_band;
+     int si_fd;
+   } _sigpoll;
+
+
+ struct
+   {
+     void *_call_addr;
+     int _syscall;
+     unsigned int _arch;
+   } _sigsys;
+      } _sifields;
+  } siginfo_t ;
+enum
+{
+  SI_ASYNCNL = -60,
+
+  SI_TKILL = -6,
+
+  SI_SIGIO,
+
+  SI_ASYNCIO,
+
+  SI_MESGQ,
+
+  SI_TIMER,
+
+  SI_QUEUE,
+
+  SI_USER,
+
+  SI_KERNEL = 0x80
+
+};
+
+
+
+enum
+{
+  ILL_ILLOPC = 1,
+
+  ILL_ILLOPN,
+
+  ILL_ILLADR,
+
+  ILL_ILLTRP,
+
+  ILL_PRVOPC,
+
+  ILL_PRVREG,
+
+  ILL_COPROC,
+
+  ILL_BADSTK
+
+};
+
+
+enum
+{
+  FPE_INTDIV = 1,
+
+  FPE_INTOVF,
+
+  FPE_FLTDIV,
+
+  FPE_FLTOVF,
+
+  FPE_FLTUND,
+
+  FPE_FLTRES,
+
+  FPE_FLTINV,
+
+  FPE_FLTSUB
+
+};
+
+
+enum
+{
+  SEGV_MAPERR = 1,
+
+  SEGV_ACCERR
+
+};
+
+
+enum
+{
+  BUS_ADRALN = 1,
+
+  BUS_ADRERR,
+
+  BUS_OBJERR,
+
+  BUS_MCEERR_AR,
+
+  BUS_MCEERR_AO
+
+};
+
+
+enum
+{
+  TRAP_BRKPT = 1,
+
+  TRAP_TRACE
+
+};
+
+
+enum
+{
+  CLD_EXITED = 1,
+
+  CLD_KILLED,
+
+  CLD_DUMPED,
+
+  CLD_TRAPPED,
+
+  CLD_STOPPED,
+
+  CLD_CONTINUED
+
+};
+
+
+enum
+{
+  POLL_IN = 1,
+
+  POLL_OUT,
+
+  POLL_MSG,
+
+  POLL_ERR,
+
+  POLL_PRI,
+
+  POLL_HUP
+
+};
+typedef struct sigevent
+  {
+    sigval_t sigev_value;
+    int sigev_signo;
+    int sigev_notify;
+
+    union
+      {
+ int _pad[((64 / sizeof (int)) - 4)];
+
+
+
+ __pid_t _tid;
+
+ struct
+   {
+     void (*_function) (sigval_t);
+     pthread_attr_t *_attribute;
+   } _sigev_thread;
+      } _sigev_un;
+  } sigevent_t;
+
+
+
+
+
+
+enum
+{
+  SIGEV_SIGNAL = 0,
+
+  SIGEV_NONE,
+
+  SIGEV_THREAD,
+
+
+  SIGEV_THREAD_ID = 4
+
+};
+
+
+
+
+typedef void (*__sighandler_t) (int);
+
+
+
+
+extern __sighandler_t __sysv_signal (int __sig, __sighandler_t __handler)
+     __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern __sighandler_t signal (int __sig, __sighandler_t __handler)
+     __attribute__ ((__nothrow__ , __leaf__));
+
+extern int kill (__pid_t __pid, int __sig) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+extern int killpg (__pid_t __pgrp, int __sig) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+extern int raise (int __sig) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+extern __sighandler_t ssignal (int __sig, __sighandler_t __handler)
+     __attribute__ ((__nothrow__ , __leaf__));
+extern int gsignal (int __sig) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+extern void psignal (int __sig, const char *__s);
+
+
+
+
+extern void psiginfo (const siginfo_t *__pinfo, const char *__s);
+extern int __sigpause (int __sig_or_mask, int __is_sig);
+extern int sigblock (int __mask) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__deprecated__));
+
+
+extern int sigsetmask (int __mask) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__deprecated__));
+
+
+extern int siggetmask (void) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__deprecated__));
+typedef __sighandler_t sig_t;
+
+
+
+
+
+extern int sigemptyset (sigset_t *__set) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int sigfillset (sigset_t *__set) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int sigaddset (sigset_t *__set, int __signo) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int sigdelset (sigset_t *__set, int __signo) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int sigismember (const sigset_t *__set, int __signo)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+struct sigaction
+  {
+
+
+    union
+      {
+
+ __sighandler_t sa_handler;
+
+ void (*sa_sigaction) (int, siginfo_t *, void *);
+      }
+    __sigaction_handler;
+
+
+
+
+
+
+
+    __sigset_t sa_mask;
+
+
+    int sa_flags;
+
+
+    void (*sa_restorer) (void);
+  };
+
+
+extern int sigprocmask (int __how, const sigset_t *__restrict __set,
+   sigset_t *__restrict __oset) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+extern int sigsuspend (const sigset_t *__set) __attribute__ ((__nonnull__ (1)));
+
+
+extern int sigaction (int __sig, const struct sigaction *__restrict __act,
+        struct sigaction *__restrict __oact) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern int sigpending (sigset_t *__set) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+
+extern int sigwait (const sigset_t *__restrict __set, int *__restrict __sig)
+     __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+
+
+
+extern int sigwaitinfo (const sigset_t *__restrict __set,
+   siginfo_t *__restrict __info) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+
+extern int sigtimedwait (const sigset_t *__restrict __set,
+    siginfo_t *__restrict __info,
+    const struct timespec *__restrict __timeout)
+     __attribute__ ((__nonnull__ (1)));
+
+
+
+extern int sigqueue (__pid_t __pid, int __sig, const union sigval __val)
+     __attribute__ ((__nothrow__ , __leaf__));
+extern const char *const _sys_siglist[65];
+extern const char *const sys_siglist[65];
+
+
+struct sigvec
+  {
+    __sighandler_t sv_handler;
+    int sv_mask;
+
+    int sv_flags;
+
+  };
+extern int sigvec (int __sig, const struct sigvec *__vec,
+     struct sigvec *__ovec) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+struct _fpx_sw_bytes
+{
+  __uint32_t magic1;
+  __uint32_t extended_size;
+  __uint64_t xstate_bv;
+  __uint32_t xstate_size;
+  __uint32_t padding[7];
+};
+
+struct _fpreg
+{
+  unsigned short significand[4];
+  unsigned short exponent;
+};
+
+struct _fpxreg
+{
+  unsigned short significand[4];
+  unsigned short exponent;
+  unsigned short padding[3];
+};
+
+struct _xmmreg
+{
+  __uint32_t element[4];
+};
+struct _fpstate
+{
+
+  __uint16_t cwd;
+  __uint16_t swd;
+  __uint16_t ftw;
+  __uint16_t fop;
+  __uint64_t rip;
+  __uint64_t rdp;
+  __uint32_t mxcsr;
+  __uint32_t mxcr_mask;
+  struct _fpxreg _st[8];
+  struct _xmmreg _xmm[16];
+  __uint32_t padding[24];
+};
+
+struct sigcontext
+{
+  __uint64_t r8;
+  __uint64_t r9;
+  __uint64_t r10;
+  __uint64_t r11;
+  __uint64_t r12;
+  __uint64_t r13;
+  __uint64_t r14;
+  __uint64_t r15;
+  __uint64_t rdi;
+  __uint64_t rsi;
+  __uint64_t rbp;
+  __uint64_t rbx;
+  __uint64_t rdx;
+  __uint64_t rax;
+  __uint64_t rcx;
+  __uint64_t rsp;
+  __uint64_t rip;
+  __uint64_t eflags;
+  unsigned short cs;
+  unsigned short gs;
+  unsigned short fs;
+  unsigned short __pad0;
+  __uint64_t err;
+  __uint64_t trapno;
+  __uint64_t oldmask;
+  __uint64_t cr2;
+  __extension__ union
+    {
+      struct _fpstate * fpstate;
+      __uint64_t __fpstate_word;
+    };
+  __uint64_t __reserved1 [8];
+};
+
+
+
+struct _xsave_hdr
+{
+  __uint64_t xstate_bv;
+  __uint64_t reserved1[2];
+  __uint64_t reserved2[5];
+};
+
+struct _ymmh_state
+{
+  __uint32_t ymmh_space[64];
+};
+
+struct _xstate
+{
+  struct _fpstate fpstate;
+  struct _xsave_hdr xstate_hdr;
+  struct _ymmh_state ymmh;
+};
+
+
+extern int sigreturn (struct sigcontext *__scp) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+
+
+
+
+extern int siginterrupt (int __sig, int __interrupt) __attribute__ ((__nothrow__ , __leaf__));
+
+struct sigstack
+  {
+    void *ss_sp;
+    int ss_onstack;
+  };
+
+
+
+enum
+{
+  SS_ONSTACK = 1,
+
+  SS_DISABLE
+
+};
+typedef struct sigaltstack
+  {
+    void *ss_sp;
+    int ss_flags;
+    size_t ss_size;
+  } stack_t;
+
+
+__extension__ typedef long long int greg_t;
+
+
+
+
+
+typedef greg_t gregset_t[23];
+struct _libc_fpxreg
+{
+  unsigned short int significand[4];
+  unsigned short int exponent;
+  unsigned short int padding[3];
+};
+
+struct _libc_xmmreg
+{
+  __uint32_t element[4];
+};
+
+struct _libc_fpstate
+{
+
+  __uint16_t cwd;
+  __uint16_t swd;
+  __uint16_t ftw;
+  __uint16_t fop;
+  __uint64_t rip;
+  __uint64_t rdp;
+  __uint32_t mxcsr;
+  __uint32_t mxcr_mask;
+  struct _libc_fpxreg _st[8];
+  struct _libc_xmmreg _xmm[16];
+  __uint32_t padding[24];
+};
+
+
+typedef struct _libc_fpstate *fpregset_t;
+
+
+typedef struct
+  {
+    gregset_t gregs;
+
+    fpregset_t fpregs;
+    __extension__ unsigned long long __reserved1 [8];
+} mcontext_t;
+
+
+typedef struct ucontext
+  {
+    unsigned long int uc_flags;
+    struct ucontext *uc_link;
+    stack_t uc_stack;
+    mcontext_t uc_mcontext;
+    __sigset_t uc_sigmask;
+    struct _libc_fpstate __fpregs_mem;
+  } ucontext_t;
+
+
+
+
+
+extern int sigstack (struct sigstack *__ss, struct sigstack *__oss)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__deprecated__));
+
+
+
+extern int sigaltstack (const struct sigaltstack *__restrict __ss,
+   struct sigaltstack *__restrict __oss) __attribute__ ((__nothrow__ , __leaf__));
+extern int pthread_sigmask (int __how,
+       const __sigset_t *__restrict __newmask,
+       __sigset_t *__restrict __oldmask)__attribute__ ((__nothrow__ , __leaf__));
+
+
+extern int pthread_kill (pthread_t __threadid, int __signo) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+extern int __libc_current_sigrtmin (void) __attribute__ ((__nothrow__ , __leaf__));
+
+extern int __libc_current_sigrtmax (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+
+
+
+
+
+struct timezone
+  {
+    int tz_minuteswest;
+    int tz_dsttime;
+  };
+
+typedef struct timezone *__restrict __timezone_ptr_t;
+extern int gettimeofday (struct timeval *__restrict __tv,
+    __timezone_ptr_t __tz) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+extern int settimeofday (const struct timeval *__tv,
+    const struct timezone *__tz)
+     __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+extern int adjtime (const struct timeval *__delta,
+      struct timeval *__olddelta) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+enum __itimer_which
+  {
+
+    ITIMER_REAL = 0,
+
+
+    ITIMER_VIRTUAL = 1,
+
+
+
+    ITIMER_PROF = 2
+
+  };
+
+
+
+struct itimerval
+  {
+
+    struct timeval it_interval;
+
+    struct timeval it_value;
+  };
+
+
+
+
+
+
+typedef int __itimer_which_t;
+
+
+
+
+extern int getitimer (__itimer_which_t __which,
+        struct itimerval *__value) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+extern int setitimer (__itimer_which_t __which,
+        const struct itimerval *__restrict __new,
+        struct itimerval *__restrict __old) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+extern int utimes (const char *__file, const struct timeval __tvp[2])
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+extern int lutimes (const char *__file, const struct timeval __tvp[2])
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int futimes (int __fd, const struct timeval __tvp[2]) __attribute__ ((__nothrow__ , __leaf__));
+
+struct if_nameindex
+  {
+    unsigned int if_index;
+    char *if_name;
+  };
+
+
+
+
+enum
+  {
+    IFF_UP = 0x1,
+
+    IFF_BROADCAST = 0x2,
+
+    IFF_DEBUG = 0x4,
+
+    IFF_LOOPBACK = 0x8,
+
+    IFF_POINTOPOINT = 0x10,
+
+    IFF_NOTRAILERS = 0x20,
+
+    IFF_RUNNING = 0x40,
+
+    IFF_NOARP = 0x80,
+
+    IFF_PROMISC = 0x100,
+
+
+
+    IFF_ALLMULTI = 0x200,
+
+
+    IFF_MASTER = 0x400,
+
+    IFF_SLAVE = 0x800,
+
+
+    IFF_MULTICAST = 0x1000,
+
+
+    IFF_PORTSEL = 0x2000,
+
+    IFF_AUTOMEDIA = 0x4000,
+
+    IFF_DYNAMIC = 0x8000
+
+  };
+
+
+
+
+
+
+struct ifaddr
+  {
+    struct sockaddr ifa_addr;
+    union
+      {
+ struct sockaddr ifu_broadaddr;
+ struct sockaddr ifu_dstaddr;
+      } ifa_ifu;
+    struct iface *ifa_ifp;
+    struct ifaddr *ifa_next;
+  };
+struct ifmap
+  {
+    unsigned long int mem_start;
+    unsigned long int mem_end;
+    unsigned short int base_addr;
+    unsigned char irq;
+    unsigned char dma;
+    unsigned char port;
+
+  };
+
+
+
+
+
+struct ifreq
+  {
+
+
+    union
+      {
+ char ifrn_name[16];
+      } ifr_ifrn;
+
+    union
+      {
+ struct sockaddr ifru_addr;
+ struct sockaddr ifru_dstaddr;
+ struct sockaddr ifru_broadaddr;
+ struct sockaddr ifru_netmask;
+ struct sockaddr ifru_hwaddr;
+ short int ifru_flags;
+ int ifru_ivalue;
+ int ifru_mtu;
+ struct ifmap ifru_map;
+ char ifru_slave[16];
+ char ifru_newname[16];
+ __caddr_t ifru_data;
+      } ifr_ifru;
+  };
+struct ifconf
+  {
+    int ifc_len;
+    union
+      {
+ __caddr_t ifcu_buf;
+ struct ifreq *ifcu_req;
+      } ifc_ifcu;
+  };
+
+
+
+
+
+
+
+
+extern unsigned int if_nametoindex (const char *__ifname) __attribute__ ((__nothrow__ , __leaf__));
+extern char *if_indextoname (unsigned int __ifindex, char *__ifname) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern struct if_nameindex *if_nameindex (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern void if_freenameindex (struct if_nameindex *__ptr) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+typedef struct iovec sg_buf;
+static __inline__ struct sockaddr_in *sa2sin (struct sockaddr *sa)
+{
+    return (struct sockaddr_in *) (void *) sa;
+}
+
+static __inline__ struct sockaddr_in6 *sa2sin6 (struct sockaddr *sa)
+{
+    return (struct sockaddr_in6 *) (void *) sa;
+}
+
+static __inline__ struct sockaddr *ss2sa (struct sockaddr_storage *ss)
+{
+    return (struct sockaddr *) ss;
+}
+static __inline__ struct sockaddr_in *ss2sin (struct sockaddr_storage *ss)
+{
+    return (struct sockaddr_in *) ss;
+}
+
+static __inline__ struct sockaddr_in6 *ss2sin6 (struct sockaddr_storage *ss)
+{
+    return (struct sockaddr_in6 *) ss;
+}
+typedef struct _krb5_alt_method {
+ krb5_magic magic;
+ krb5_int32 method;
+ unsigned int length;
+ krb5_octet *data;
+} krb5_alt_method;
+typedef struct _krb5_etype_info_entry {
+ krb5_magic magic;
+ krb5_enctype etype;
+ unsigned int length;
+ krb5_octet *salt;
+    krb5_data s2kparams;
+} krb5_etype_info_entry;
+typedef krb5_etype_info_entry ** krb5_etype_info;
+typedef struct _krb5_predicted_sam_response {
+ krb5_magic magic;
+ krb5_keyblock sam_key;
+ krb5_flags sam_flags;
+ krb5_timestamp stime;
+ krb5_int32 susec;
+ krb5_principal client;
+ krb5_data msd;
+} krb5_predicted_sam_response;
+
+typedef struct _krb5_sam_challenge {
+ krb5_magic magic;
+ krb5_int32 sam_type;
+ krb5_flags sam_flags;
+ krb5_data sam_type_name;
+ krb5_data sam_track_id;
+ krb5_data sam_challenge_label;
+ krb5_data sam_challenge;
+ krb5_data sam_response_prompt;
+ krb5_data sam_pk_for_sad;
+ krb5_int32 sam_nonce;
+ krb5_checksum sam_cksum;
+} krb5_sam_challenge;
+
+typedef struct _krb5_sam_key {
+ krb5_magic magic;
+ krb5_keyblock sam_key;
+} krb5_sam_key;
+
+typedef struct _krb5_enc_sam_response_enc {
+ krb5_magic magic;
+ krb5_int32 sam_nonce;
+ krb5_timestamp sam_timestamp;
+ krb5_int32 sam_usec;
+ krb5_data sam_sad;
+} krb5_enc_sam_response_enc;
+
+typedef struct _krb5_sam_response {
+ krb5_magic magic;
+ krb5_int32 sam_type;
+ krb5_flags sam_flags;
+ krb5_data sam_track_id;
+ krb5_enc_data sam_enc_key;
+ krb5_enc_data sam_enc_nonce_or_ts;
+ krb5_int32 sam_nonce;
+ krb5_timestamp sam_patimestamp;
+} krb5_sam_response;
+
+typedef struct _krb5_sam_challenge_2 {
+ krb5_data sam_challenge_2_body;
+ krb5_checksum **sam_cksum;
+} krb5_sam_challenge_2;
+
+typedef struct _krb5_sam_challenge_2_body {
+ krb5_magic magic;
+ krb5_int32 sam_type;
+ krb5_flags sam_flags;
+ krb5_data sam_type_name;
+ krb5_data sam_track_id;
+ krb5_data sam_challenge_label;
+ krb5_data sam_challenge;
+ krb5_data sam_response_prompt;
+ krb5_data sam_pk_for_sad;
+ krb5_int32 sam_nonce;
+ krb5_enctype sam_etype;
+} krb5_sam_challenge_2_body;
+
+typedef struct _krb5_sam_response_2 {
+ krb5_magic magic;
+ krb5_int32 sam_type;
+ krb5_flags sam_flags;
+ krb5_data sam_track_id;
+ krb5_enc_data sam_enc_nonce_or_sad;
+ krb5_int32 sam_nonce;
+} krb5_sam_response_2;
+
+typedef struct _krb5_enc_sam_response_enc_2 {
+ krb5_magic magic;
+ krb5_int32 sam_nonce;
+ krb5_data sam_sad;
+} krb5_enc_sam_response_enc_2;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+extern void *memcpy (void *__restrict __dest, const void *__restrict __src,
+       size_t __n) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern void *memmove (void *__dest, const void *__src, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+
+
+
+extern void *memccpy (void *__restrict __dest, const void *__restrict __src,
+        int __c, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+
+
+extern void *memset (void *__s, int __c, size_t __n) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int memcmp (const void *__s1, const void *__s2, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1, 2)));
+extern void *memchr (const void *__s, int __c, size_t __n)
+      __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+extern char *strcpy (char *__restrict __dest, const char *__restrict __src)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+extern char *strncpy (char *__restrict __dest,
+        const char *__restrict __src, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern char *strcat (char *__restrict __dest, const char *__restrict __src)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+extern char *strncat (char *__restrict __dest, const char *__restrict __src,
+        size_t __n) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern int strcmp (const char *__s1, const char *__s2)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1, 2)));
+
+extern int strncmp (const char *__s1, const char *__s2, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern int strcoll (const char *__s1, const char *__s2)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1, 2)));
+
+extern size_t strxfrm (char *__restrict __dest,
+         const char *__restrict __src, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2)));
+
+extern int strcoll_l (const char *__s1, const char *__s2, __locale_t __l)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1, 2, 3)));
+
+extern size_t strxfrm_l (char *__dest, const char *__src, size_t __n,
+    __locale_t __l) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2, 4)));
+
+
+
+
+
+extern char *strdup (const char *__s)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__malloc__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+
+extern char *strndup (const char *__string, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__malloc__)) __attribute__ ((__nonnull__ (1)));
+
+extern char *strchr (const char *__s, int __c)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1)));
+extern char *strrchr (const char *__s, int __c)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+extern size_t strcspn (const char *__s, const char *__reject)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern size_t strspn (const char *__s, const char *__accept)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1, 2)));
+extern char *strpbrk (const char *__s, const char *__accept)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1, 2)));
+extern char *strstr (const char *__haystack, const char *__needle)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+
+extern char *strtok (char *__restrict __s, const char *__restrict __delim)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2)));
+
+
+
+
+extern char *__strtok_r (char *__restrict __s,
+    const char *__restrict __delim,
+    char **__restrict __save_ptr)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2, 3)));
+
+extern char *strtok_r (char *__restrict __s, const char *__restrict __delim,
+         char **__restrict __save_ptr)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2, 3)));
+
+
+extern size_t strlen (const char *__s)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+extern size_t strnlen (const char *__string, size_t __maxlen)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+extern char *strerror (int __errnum) __attribute__ ((__nothrow__ , __leaf__));
+
+extern int strerror_r (int __errnum, char *__buf, size_t __buflen) __asm__ ("" "__xpg_strerror_r") __attribute__ ((__nothrow__ , __leaf__))
+
+                        __attribute__ ((__nonnull__ (2)));
+extern char *strerror_l (int __errnum, __locale_t __l) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+extern void __bzero (void *__s, size_t __n) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+extern void bcopy (const void *__src, void *__dest, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern void bzero (void *__s, size_t __n) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int bcmp (const void *__s1, const void *__s2, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1, 2)));
+extern char *index (const char *__s, int __c)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1)));
+extern char *rindex (const char *__s, int __c)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+extern int ffs (int __i) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__));
+extern int strcasecmp (const char *__s1, const char *__s2)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern int strncasecmp (const char *__s1, const char *__s2, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1, 2)));
+extern char *strsep (char **__restrict __stringp,
+       const char *__restrict __delim)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+
+extern char *strsignal (int __sig) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern char *__stpcpy (char *__restrict __dest, const char *__restrict __src)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+extern char *stpcpy (char *__restrict __dest, const char *__restrict __src)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+extern char *__stpncpy (char *__restrict __dest,
+   const char *__restrict __src, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+extern char *stpncpy (char *__restrict __dest,
+        const char *__restrict __src, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern void *__rawmemchr (const void *__s, int __c);
+extern __inline __attribute__ ((__gnu_inline__)) size_t __strcspn_c1 (const char *__s, int __reject);
+extern __inline __attribute__ ((__gnu_inline__)) size_t
+__strcspn_c1 (const char *__s, int __reject)
+{
+  size_t __result = 0;
+  while (__s[__result] != '\0' && __s[__result] != __reject)
+    ++__result;
+  return __result;
+}
+
+extern __inline __attribute__ ((__gnu_inline__)) size_t __strcspn_c2 (const char *__s, int __reject1,
+         int __reject2);
+extern __inline __attribute__ ((__gnu_inline__)) size_t
+__strcspn_c2 (const char *__s, int __reject1, int __reject2)
+{
+  size_t __result = 0;
+  while (__s[__result] != '\0' && __s[__result] != __reject1
+  && __s[__result] != __reject2)
+    ++__result;
+  return __result;
+}
+
+extern __inline __attribute__ ((__gnu_inline__)) size_t __strcspn_c3 (const char *__s, int __reject1,
+         int __reject2, int __reject3);
+extern __inline __attribute__ ((__gnu_inline__)) size_t
+__strcspn_c3 (const char *__s, int __reject1, int __reject2,
+       int __reject3)
+{
+  size_t __result = 0;
+  while (__s[__result] != '\0' && __s[__result] != __reject1
+  && __s[__result] != __reject2 && __s[__result] != __reject3)
+    ++__result;
+  return __result;
+}
+extern __inline __attribute__ ((__gnu_inline__)) size_t __strspn_c1 (const char *__s, int __accept);
+extern __inline __attribute__ ((__gnu_inline__)) size_t
+__strspn_c1 (const char *__s, int __accept)
+{
+  size_t __result = 0;
+
+  while (__s[__result] == __accept)
+    ++__result;
+  return __result;
+}
+
+extern __inline __attribute__ ((__gnu_inline__)) size_t __strspn_c2 (const char *__s, int __accept1,
+        int __accept2);
+extern __inline __attribute__ ((__gnu_inline__)) size_t
+__strspn_c2 (const char *__s, int __accept1, int __accept2)
+{
+  size_t __result = 0;
+
+  while (__s[__result] == __accept1 || __s[__result] == __accept2)
+    ++__result;
+  return __result;
+}
+
+extern __inline __attribute__ ((__gnu_inline__)) size_t __strspn_c3 (const char *__s, int __accept1,
+        int __accept2, int __accept3);
+extern __inline __attribute__ ((__gnu_inline__)) size_t
+__strspn_c3 (const char *__s, int __accept1, int __accept2, int __accept3)
+{
+  size_t __result = 0;
+
+  while (__s[__result] == __accept1 || __s[__result] == __accept2
+  || __s[__result] == __accept3)
+    ++__result;
+  return __result;
+}
+extern __inline __attribute__ ((__gnu_inline__)) char *__strpbrk_c2 (const char *__s, int __accept1,
+        int __accept2);
+extern __inline __attribute__ ((__gnu_inline__)) char *
+__strpbrk_c2 (const char *__s, int __accept1, int __accept2)
+{
+
+  while (*__s != '\0' && *__s != __accept1 && *__s != __accept2)
+    ++__s;
+  return *__s == '\0' ? ((void *)0) : (char *) (size_t) __s;
+}
+
+extern __inline __attribute__ ((__gnu_inline__)) char *__strpbrk_c3 (const char *__s, int __accept1,
+        int __accept2, int __accept3);
+extern __inline __attribute__ ((__gnu_inline__)) char *
+__strpbrk_c3 (const char *__s, int __accept1, int __accept2, int __accept3)
+{
+
+  while (*__s != '\0' && *__s != __accept1 && *__s != __accept2
+  && *__s != __accept3)
+    ++__s;
+  return *__s == '\0' ? ((void *)0) : (char *) (size_t) __s;
+}
+extern __inline __attribute__ ((__gnu_inline__)) char *__strtok_r_1c (char *__s, char __sep, char **__nextp);
+extern __inline __attribute__ ((__gnu_inline__)) char *
+__strtok_r_1c (char *__s, char __sep, char **__nextp)
+{
+  char *__result;
+  if (__s == ((void *)0))
+    __s = *__nextp;
+  while (*__s == __sep)
+    ++__s;
+  __result = ((void *)0);
+  if (*__s != '\0')
+    {
+      __result = __s++;
+      while (*__s != '\0')
+ if (*__s++ == __sep)
+   {
+     __s[-1] = '\0';
+     break;
+   }
+    }
+  *__nextp = __s;
+  return __result;
+}
+extern char *__strsep_g (char **__stringp, const char *__delim);
+extern __inline __attribute__ ((__gnu_inline__)) char *__strsep_1c (char **__s, char __reject);
+extern __inline __attribute__ ((__gnu_inline__)) char *
+__strsep_1c (char **__s, char __reject)
+{
+  char *__retval = *__s;
+  if (__retval != ((void *)0) && (*__s = (__extension__ (__builtin_constant_p (__reject) && !__builtin_constant_p (__retval) && (__reject) == '\0' ? (char *) __rawmemchr (__retval, __reject) : __builtin_strchr (__retval, __reject)))) != ((void *)0))
+    *(*__s)++ = '\0';
+  return __retval;
+}
+
+extern __inline __attribute__ ((__gnu_inline__)) char *__strsep_2c (char **__s, char __reject1, char __reject2);
+extern __inline __attribute__ ((__gnu_inline__)) char *
+__strsep_2c (char **__s, char __reject1, char __reject2)
+{
+  char *__retval = *__s;
+  if (__retval != ((void *)0))
+    {
+      char *__cp = __retval;
+      while (1)
+ {
+   if (*__cp == '\0')
+     {
+       __cp = ((void *)0);
+   break;
+     }
+   if (*__cp == __reject1 || *__cp == __reject2)
+     {
+       *__cp++ = '\0';
+       break;
+     }
+   ++__cp;
+ }
+      *__s = __cp;
+    }
+  return __retval;
+}
+
+extern __inline __attribute__ ((__gnu_inline__)) char *__strsep_3c (char **__s, char __reject1, char __reject2,
+       char __reject3);
+extern __inline __attribute__ ((__gnu_inline__)) char *
+__strsep_3c (char **__s, char __reject1, char __reject2, char __reject3)
+{
+  char *__retval = *__s;
+  if (__retval != ((void *)0))
+    {
+      char *__cp = __retval;
+      while (1)
+ {
+   if (*__cp == '\0')
+     {
+       __cp = ((void *)0);
+   break;
+     }
+   if (*__cp == __reject1 || *__cp == __reject2 || *__cp == __reject3)
+     {
+       *__cp++ = '\0';
+       break;
+     }
+   ++__cp;
+ }
+      *__s = __cp;
+    }
+  return __retval;
+}
+extern char *__strdup (const char *__string) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__malloc__));
+extern char *__strndup (const char *__string, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__malloc__));
+
+
+
+
+
+
+
+
+
+
+
+typedef __useconds_t useconds_t;
+extern int access (const char *__name, int __type) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+extern int faccessat (int __fd, const char *__file, int __type, int __flag)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2))) ;
+extern __off_t lseek (int __fd, __off_t __offset, int __whence) __attribute__ ((__nothrow__ , __leaf__));
+extern int close (int __fd);
+
+
+
+
+
+
+extern ssize_t read (int __fd, void *__buf, size_t __nbytes) ;
+
+
+
+
+
+extern ssize_t write (int __fd, const void *__buf, size_t __n) ;
+extern ssize_t pread (int __fd, void *__buf, size_t __nbytes,
+        __off_t __offset) ;
+
+
+
+
+
+
+extern ssize_t pwrite (int __fd, const void *__buf, size_t __n,
+         __off_t __offset) ;
+extern int pipe (int __pipedes[2]) __attribute__ ((__nothrow__ , __leaf__)) ;
+extern unsigned int alarm (unsigned int __seconds) __attribute__ ((__nothrow__ , __leaf__));
+extern unsigned int sleep (unsigned int __seconds);
+
+
+
+
+
+
+
+extern __useconds_t ualarm (__useconds_t __value, __useconds_t __interval)
+     __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+extern int usleep (__useconds_t __useconds);
+extern int pause (void);
+
+
+
+extern int chown (const char *__file, __uid_t __owner, __gid_t __group)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+
+
+
+extern int fchown (int __fd, __uid_t __owner, __gid_t __group) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+extern int lchown (const char *__file, __uid_t __owner, __gid_t __group)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+
+
+
+
+
+
+extern int fchownat (int __fd, const char *__file, __uid_t __owner,
+       __gid_t __group, int __flag)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2))) ;
+
+
+
+extern int chdir (const char *__path) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+
+
+
+extern int fchdir (int __fd) __attribute__ ((__nothrow__ , __leaf__)) ;
+extern char *getcwd (char *__buf, size_t __size) __attribute__ ((__nothrow__ , __leaf__)) ;
+extern char *getwd (char *__buf)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) __attribute__ ((__deprecated__)) ;
+
+
+
+
+extern int dup (int __fd) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+extern int dup2 (int __fd, int __fd2) __attribute__ ((__nothrow__ , __leaf__));
+extern char **__environ;
+
+
+
+
+
+
+
+extern int execve (const char *__path, char *const __argv[],
+     char *const __envp[]) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+
+extern int fexecve (int __fd, char *const __argv[], char *const __envp[])
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2)));
+
+
+
+
+extern int execv (const char *__path, char *const __argv[])
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+extern int execle (const char *__path, const char *__arg, ...)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+extern int execl (const char *__path, const char *__arg, ...)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+extern int execvp (const char *__file, char *const __argv[])
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+
+extern int execlp (const char *__file, const char *__arg, ...)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+extern int nice (int __inc) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+extern void _exit (int __status) __attribute__ ((__noreturn__));
+
+
+
+
+
+enum
+  {
+    _PC_LINK_MAX,
+
+    _PC_MAX_CANON,
+
+    _PC_MAX_INPUT,
+
+    _PC_NAME_MAX,
+
+    _PC_PATH_MAX,
+
+    _PC_PIPE_BUF,
+
+    _PC_CHOWN_RESTRICTED,
+
+    _PC_NO_TRUNC,
+
+    _PC_VDISABLE,
+
+    _PC_SYNC_IO,
+
+    _PC_ASYNC_IO,
+
+    _PC_PRIO_IO,
+
+    _PC_SOCK_MAXBUF,
+
+    _PC_FILESIZEBITS,
+
+    _PC_REC_INCR_XFER_SIZE,
+
+    _PC_REC_MAX_XFER_SIZE,
+
+    _PC_REC_MIN_XFER_SIZE,
+
+    _PC_REC_XFER_ALIGN,
+
+    _PC_ALLOC_SIZE_MIN,
+
+    _PC_SYMLINK_MAX,
+
+    _PC_2_SYMLINKS
+
+  };
+
+
+enum
+  {
+    _SC_ARG_MAX,
+
+    _SC_CHILD_MAX,
+
+    _SC_CLK_TCK,
+
+    _SC_NGROUPS_MAX,
+
+    _SC_OPEN_MAX,
+
+    _SC_STREAM_MAX,
+
+    _SC_TZNAME_MAX,
+
+    _SC_JOB_CONTROL,
+
+    _SC_SAVED_IDS,
+
+    _SC_REALTIME_SIGNALS,
+
+    _SC_PRIORITY_SCHEDULING,
+
+    _SC_TIMERS,
+
+    _SC_ASYNCHRONOUS_IO,
+
+    _SC_PRIORITIZED_IO,
+
+    _SC_SYNCHRONIZED_IO,
+
+    _SC_FSYNC,
+
+    _SC_MAPPED_FILES,
+
+    _SC_MEMLOCK,
+
+    _SC_MEMLOCK_RANGE,
+
+    _SC_MEMORY_PROTECTION,
+
+    _SC_MESSAGE_PASSING,
+
+    _SC_SEMAPHORES,
+
+    _SC_SHARED_MEMORY_OBJECTS,
+
+    _SC_AIO_LISTIO_MAX,
+
+    _SC_AIO_MAX,
+
+    _SC_AIO_PRIO_DELTA_MAX,
+
+    _SC_DELAYTIMER_MAX,
+
+    _SC_MQ_OPEN_MAX,
+
+    _SC_MQ_PRIO_MAX,
+
+    _SC_VERSION,
+
+    _SC_PAGESIZE,
+
+
+    _SC_RTSIG_MAX,
+
+    _SC_SEM_NSEMS_MAX,
+
+    _SC_SEM_VALUE_MAX,
+
+    _SC_SIGQUEUE_MAX,
+
+    _SC_TIMER_MAX,
+
+
+
+
+    _SC_BC_BASE_MAX,
+
+    _SC_BC_DIM_MAX,
+
+    _SC_BC_SCALE_MAX,
+
+    _SC_BC_STRING_MAX,
+
+    _SC_COLL_WEIGHTS_MAX,
+
+    _SC_EQUIV_CLASS_MAX,
+
+    _SC_EXPR_NEST_MAX,
+
+    _SC_LINE_MAX,
+
+    _SC_RE_DUP_MAX,
+
+    _SC_CHARCLASS_NAME_MAX,
+
+
+    _SC_2_VERSION,
+
+    _SC_2_C_BIND,
+
+    _SC_2_C_DEV,
+
+    _SC_2_FORT_DEV,
+
+    _SC_2_FORT_RUN,
+
+    _SC_2_SW_DEV,
+
+    _SC_2_LOCALEDEF,
+
+
+    _SC_PII,
+
+    _SC_PII_XTI,
+
+    _SC_PII_SOCKET,
+
+    _SC_PII_INTERNET,
+
+    _SC_PII_OSI,
+
+    _SC_POLL,
+
+    _SC_SELECT,
+
+    _SC_UIO_MAXIOV,
+
+    _SC_IOV_MAX = _SC_UIO_MAXIOV,
+
+    _SC_PII_INTERNET_STREAM,
+
+    _SC_PII_INTERNET_DGRAM,
+
+    _SC_PII_OSI_COTS,
+
+    _SC_PII_OSI_CLTS,
+
+    _SC_PII_OSI_M,
+
+    _SC_T_IOV_MAX,
+
+
+
+    _SC_THREADS,
+
+    _SC_THREAD_SAFE_FUNCTIONS,
+
+    _SC_GETGR_R_SIZE_MAX,
+
+    _SC_GETPW_R_SIZE_MAX,
+
+    _SC_LOGIN_NAME_MAX,
+
+    _SC_TTY_NAME_MAX,
+
+    _SC_THREAD_DESTRUCTOR_ITERATIONS,
+
+    _SC_THREAD_KEYS_MAX,
+
+    _SC_THREAD_STACK_MIN,
+
+    _SC_THREAD_THREADS_MAX,
+
+    _SC_THREAD_ATTR_STACKADDR,
+
+    _SC_THREAD_ATTR_STACKSIZE,
+
+    _SC_THREAD_PRIORITY_SCHEDULING,
+
+    _SC_THREAD_PRIO_INHERIT,
+
+    _SC_THREAD_PRIO_PROTECT,
+
+    _SC_THREAD_PROCESS_SHARED,
+
+
+    _SC_NPROCESSORS_CONF,
+
+    _SC_NPROCESSORS_ONLN,
+
+    _SC_PHYS_PAGES,
+
+    _SC_AVPHYS_PAGES,
+
+    _SC_ATEXIT_MAX,
+
+    _SC_PASS_MAX,
+
+
+    _SC_XOPEN_VERSION,
+
+    _SC_XOPEN_XCU_VERSION,
+
+    _SC_XOPEN_UNIX,
+
+    _SC_XOPEN_CRYPT,
+
+    _SC_XOPEN_ENH_I18N,
+
+    _SC_XOPEN_SHM,
+
+
+    _SC_2_CHAR_TERM,
+
+    _SC_2_C_VERSION,
+
+    _SC_2_UPE,
+
+
+    _SC_XOPEN_XPG2,
+
+    _SC_XOPEN_XPG3,
+
+    _SC_XOPEN_XPG4,
+
+
+    _SC_CHAR_BIT,
+
+    _SC_CHAR_MAX,
+
+    _SC_CHAR_MIN,
+
+    _SC_INT_MAX,
+
+    _SC_INT_MIN,
+
+    _SC_LONG_BIT,
+
+    _SC_WORD_BIT,
+
+    _SC_MB_LEN_MAX,
+
+    _SC_NZERO,
+
+    _SC_SSIZE_MAX,
+
+    _SC_SCHAR_MAX,
+
+    _SC_SCHAR_MIN,
+
+    _SC_SHRT_MAX,
+
+    _SC_SHRT_MIN,
+
+    _SC_UCHAR_MAX,
+
+    _SC_UINT_MAX,
+
+    _SC_ULONG_MAX,
+
+    _SC_USHRT_MAX,
+
+
+    _SC_NL_ARGMAX,
+
+    _SC_NL_LANGMAX,
+
+    _SC_NL_MSGMAX,
+
+    _SC_NL_NMAX,
+
+    _SC_NL_SETMAX,
+
+    _SC_NL_TEXTMAX,
+
+
+    _SC_XBS5_ILP32_OFF32,
+
+    _SC_XBS5_ILP32_OFFBIG,
+
+    _SC_XBS5_LP64_OFF64,
+
+    _SC_XBS5_LPBIG_OFFBIG,
+
+
+    _SC_XOPEN_LEGACY,
+
+    _SC_XOPEN_REALTIME,
+
+    _SC_XOPEN_REALTIME_THREADS,
+
+
+    _SC_ADVISORY_INFO,
+
+    _SC_BARRIERS,
+
+    _SC_BASE,
+
+    _SC_C_LANG_SUPPORT,
+
+    _SC_C_LANG_SUPPORT_R,
+
+    _SC_CLOCK_SELECTION,
+
+    _SC_CPUTIME,
+
+    _SC_THREAD_CPUTIME,
+
+    _SC_DEVICE_IO,
+
+    _SC_DEVICE_SPECIFIC,
+
+    _SC_DEVICE_SPECIFIC_R,
+
+    _SC_FD_MGMT,
+
+    _SC_FIFO,
+
+    _SC_PIPE,
+
+    _SC_FILE_ATTRIBUTES,
+
+    _SC_FILE_LOCKING,
+
+    _SC_FILE_SYSTEM,
+
+    _SC_MONOTONIC_CLOCK,
+
+    _SC_MULTI_PROCESS,
+
+    _SC_SINGLE_PROCESS,
+
+    _SC_NETWORKING,
+
+    _SC_READER_WRITER_LOCKS,
+
+    _SC_SPIN_LOCKS,
+
+    _SC_REGEXP,
+
+    _SC_REGEX_VERSION,
+
+    _SC_SHELL,
+
+    _SC_SIGNALS,
+
+    _SC_SPAWN,
+
+    _SC_SPORADIC_SERVER,
+
+    _SC_THREAD_SPORADIC_SERVER,
+
+    _SC_SYSTEM_DATABASE,
+
+    _SC_SYSTEM_DATABASE_R,
+
+    _SC_TIMEOUTS,
+
+    _SC_TYPED_MEMORY_OBJECTS,
+
+    _SC_USER_GROUPS,
+
+    _SC_USER_GROUPS_R,
+
+    _SC_2_PBS,
+
+    _SC_2_PBS_ACCOUNTING,
+
+    _SC_2_PBS_LOCATE,
+
+    _SC_2_PBS_MESSAGE,
+
+    _SC_2_PBS_TRACK,
+
+    _SC_SYMLOOP_MAX,
+
+    _SC_STREAMS,
+
+    _SC_2_PBS_CHECKPOINT,
+
+
+    _SC_V6_ILP32_OFF32,
+
+    _SC_V6_ILP32_OFFBIG,
+
+    _SC_V6_LP64_OFF64,
+
+    _SC_V6_LPBIG_OFFBIG,
+
+
+    _SC_HOST_NAME_MAX,
+
+    _SC_TRACE,
+
+    _SC_TRACE_EVENT_FILTER,
+
+    _SC_TRACE_INHERIT,
+
+    _SC_TRACE_LOG,
+
+
+    _SC_LEVEL1_ICACHE_SIZE,
+
+    _SC_LEVEL1_ICACHE_ASSOC,
+
+    _SC_LEVEL1_ICACHE_LINESIZE,
+
+    _SC_LEVEL1_DCACHE_SIZE,
+
+    _SC_LEVEL1_DCACHE_ASSOC,
+
+    _SC_LEVEL1_DCACHE_LINESIZE,
+
+    _SC_LEVEL2_CACHE_SIZE,
+
+    _SC_LEVEL2_CACHE_ASSOC,
+
+    _SC_LEVEL2_CACHE_LINESIZE,
+
+    _SC_LEVEL3_CACHE_SIZE,
+
+    _SC_LEVEL3_CACHE_ASSOC,
+
+    _SC_LEVEL3_CACHE_LINESIZE,
+
+    _SC_LEVEL4_CACHE_SIZE,
+
+    _SC_LEVEL4_CACHE_ASSOC,
+
+    _SC_LEVEL4_CACHE_LINESIZE,
+
+
+
+    _SC_IPV6 = _SC_LEVEL1_ICACHE_SIZE + 50,
+
+    _SC_RAW_SOCKETS,
+
+
+    _SC_V7_ILP32_OFF32,
+
+    _SC_V7_ILP32_OFFBIG,
+
+    _SC_V7_LP64_OFF64,
+
+    _SC_V7_LPBIG_OFFBIG,
+
+
+    _SC_SS_REPL_MAX,
+
+
+    _SC_TRACE_EVENT_NAME_MAX,
+
+    _SC_TRACE_NAME_MAX,
+
+    _SC_TRACE_SYS_MAX,
+
+    _SC_TRACE_USER_EVENT_MAX,
+
+
+    _SC_XOPEN_STREAMS,
+
+
+    _SC_THREAD_ROBUST_PRIO_INHERIT,
+
+    _SC_THREAD_ROBUST_PRIO_PROTECT
+
+  };
+
+
+enum
+  {
+    _CS_PATH,
+
+
+    _CS_V6_WIDTH_RESTRICTED_ENVS,
+
+
+
+    _CS_GNU_LIBC_VERSION,
+
+    _CS_GNU_LIBPTHREAD_VERSION,
+
+
+    _CS_V5_WIDTH_RESTRICTED_ENVS,
+
+
+
+    _CS_V7_WIDTH_RESTRICTED_ENVS,
+
+
+
+    _CS_LFS_CFLAGS = 1000,
+
+    _CS_LFS_LDFLAGS,
+
+    _CS_LFS_LIBS,
+
+    _CS_LFS_LINTFLAGS,
+
+    _CS_LFS64_CFLAGS,
+
+    _CS_LFS64_LDFLAGS,
+
+    _CS_LFS64_LIBS,
+
+    _CS_LFS64_LINTFLAGS,
+
+
+    _CS_XBS5_ILP32_OFF32_CFLAGS = 1100,
+
+    _CS_XBS5_ILP32_OFF32_LDFLAGS,
+
+    _CS_XBS5_ILP32_OFF32_LIBS,
+
+    _CS_XBS5_ILP32_OFF32_LINTFLAGS,
+
+    _CS_XBS5_ILP32_OFFBIG_CFLAGS,
+
+    _CS_XBS5_ILP32_OFFBIG_LDFLAGS,
+
+    _CS_XBS5_ILP32_OFFBIG_LIBS,
+
+    _CS_XBS5_ILP32_OFFBIG_LINTFLAGS,
+
+    _CS_XBS5_LP64_OFF64_CFLAGS,
+
+    _CS_XBS5_LP64_OFF64_LDFLAGS,
+
+    _CS_XBS5_LP64_OFF64_LIBS,
+
+    _CS_XBS5_LP64_OFF64_LINTFLAGS,
+
+    _CS_XBS5_LPBIG_OFFBIG_CFLAGS,
+
+    _CS_XBS5_LPBIG_OFFBIG_LDFLAGS,
+
+    _CS_XBS5_LPBIG_OFFBIG_LIBS,
+
+    _CS_XBS5_LPBIG_OFFBIG_LINTFLAGS,
+
+
+    _CS_POSIX_V6_ILP32_OFF32_CFLAGS,
+
+    _CS_POSIX_V6_ILP32_OFF32_LDFLAGS,
+
+    _CS_POSIX_V6_ILP32_OFF32_LIBS,
+
+    _CS_POSIX_V6_ILP32_OFF32_LINTFLAGS,
+
+    _CS_POSIX_V6_ILP32_OFFBIG_CFLAGS,
+
+    _CS_POSIX_V6_ILP32_OFFBIG_LDFLAGS,
+
+    _CS_POSIX_V6_ILP32_OFFBIG_LIBS,
+
+    _CS_POSIX_V6_ILP32_OFFBIG_LINTFLAGS,
+
+    _CS_POSIX_V6_LP64_OFF64_CFLAGS,
+
+    _CS_POSIX_V6_LP64_OFF64_LDFLAGS,
+
+    _CS_POSIX_V6_LP64_OFF64_LIBS,
+
+    _CS_POSIX_V6_LP64_OFF64_LINTFLAGS,
+
+    _CS_POSIX_V6_LPBIG_OFFBIG_CFLAGS,
+
+    _CS_POSIX_V6_LPBIG_OFFBIG_LDFLAGS,
+
+    _CS_POSIX_V6_LPBIG_OFFBIG_LIBS,
+
+    _CS_POSIX_V6_LPBIG_OFFBIG_LINTFLAGS,
+
+
+    _CS_POSIX_V7_ILP32_OFF32_CFLAGS,
+
+    _CS_POSIX_V7_ILP32_OFF32_LDFLAGS,
+
+    _CS_POSIX_V7_ILP32_OFF32_LIBS,
+
+    _CS_POSIX_V7_ILP32_OFF32_LINTFLAGS,
+
+    _CS_POSIX_V7_ILP32_OFFBIG_CFLAGS,
+
+    _CS_POSIX_V7_ILP32_OFFBIG_LDFLAGS,
+
+    _CS_POSIX_V7_ILP32_OFFBIG_LIBS,
+
+    _CS_POSIX_V7_ILP32_OFFBIG_LINTFLAGS,
+
+    _CS_POSIX_V7_LP64_OFF64_CFLAGS,
+
+    _CS_POSIX_V7_LP64_OFF64_LDFLAGS,
+
+    _CS_POSIX_V7_LP64_OFF64_LIBS,
+
+    _CS_POSIX_V7_LP64_OFF64_LINTFLAGS,
+
+    _CS_POSIX_V7_LPBIG_OFFBIG_CFLAGS,
+
+    _CS_POSIX_V7_LPBIG_OFFBIG_LDFLAGS,
+
+    _CS_POSIX_V7_LPBIG_OFFBIG_LIBS,
+
+    _CS_POSIX_V7_LPBIG_OFFBIG_LINTFLAGS,
+
+
+    _CS_V6_ENV,
+
+    _CS_V7_ENV
+
+  };
+
+
+extern long int pathconf (const char *__path, int __name)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern long int fpathconf (int __fd, int __name) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern long int sysconf (int __name) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+extern size_t confstr (int __name, char *__buf, size_t __len) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+extern __pid_t getpid (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern __pid_t getppid (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+extern __pid_t getpgrp (void) __attribute__ ((__nothrow__ , __leaf__));
+extern __pid_t __getpgid (__pid_t __pid) __attribute__ ((__nothrow__ , __leaf__));
+
+extern __pid_t getpgid (__pid_t __pid) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+extern int setpgid (__pid_t __pid, __pid_t __pgid) __attribute__ ((__nothrow__ , __leaf__));
+extern int setpgrp (void) __attribute__ ((__nothrow__ , __leaf__));
+extern __pid_t setsid (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+extern __pid_t getsid (__pid_t __pid) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+extern __uid_t getuid (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern __uid_t geteuid (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern __gid_t getgid (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern __gid_t getegid (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+extern int getgroups (int __size, __gid_t __list[]) __attribute__ ((__nothrow__ , __leaf__)) ;
+extern int setuid (__uid_t __uid) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+extern int setreuid (__uid_t __ruid, __uid_t __euid) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+extern int seteuid (__uid_t __uid) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+
+
+extern int setgid (__gid_t __gid) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+extern int setregid (__gid_t __rgid, __gid_t __egid) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+extern int setegid (__gid_t __gid) __attribute__ ((__nothrow__ , __leaf__)) ;
+extern __pid_t fork (void) __attribute__ ((__nothrow__));
+
+
+
+
+
+
+
+extern __pid_t vfork (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+extern char *ttyname (int __fd) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+extern int ttyname_r (int __fd, char *__buf, size_t __buflen)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2))) ;
+
+
+
+extern int isatty (int __fd) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+extern int ttyslot (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+extern int link (const char *__from, const char *__to)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2))) ;
+
+
+
+
+extern int linkat (int __fromfd, const char *__from, int __tofd,
+     const char *__to, int __flags)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2, 4))) ;
+
+
+
+
+extern int symlink (const char *__from, const char *__to)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2))) ;
+
+
+
+
+extern ssize_t readlink (const char *__restrict __path,
+    char *__restrict __buf, size_t __len)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2))) ;
+
+
+
+
+extern int symlinkat (const char *__from, int __tofd,
+        const char *__to) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 3))) ;
+
+
+extern ssize_t readlinkat (int __fd, const char *__restrict __path,
+      char *__restrict __buf, size_t __len)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2, 3))) ;
+
+
+
+extern int unlink (const char *__name) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+extern int unlinkat (int __fd, const char *__name, int __flag)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2)));
+
+
+
+extern int rmdir (const char *__path) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+extern __pid_t tcgetpgrp (int __fd) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern int tcsetpgrp (int __fd, __pid_t __pgrp_id) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+extern char *getlogin (void);
+
+
+
+
+
+
+
+extern int getlogin_r (char *__name, size_t __name_len) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+extern int setlogin (const char *__name) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+extern char *optarg;
+extern int optind;
+
+
+
+
+extern int opterr;
+
+
+
+extern int optopt;
+extern int getopt (int ___argc, char *const *___argv, const char *__shortopts)
+       __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+
+extern int gethostname (char *__name, size_t __len) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+
+extern int sethostname (const char *__name, size_t __len)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+
+
+
+extern int sethostid (long int __id) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+
+extern int getdomainname (char *__name, size_t __len)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+extern int setdomainname (const char *__name, size_t __len)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+
+
+
+
+
+extern int vhangup (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern int revoke (const char *__file) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+
+
+
+
+
+
+
+extern int profil (unsigned short int *__sample_buffer, size_t __size,
+     size_t __offset, unsigned int __scale)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+extern int acct (const char *__name) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+extern char *getusershell (void) __attribute__ ((__nothrow__ , __leaf__));
+extern void endusershell (void) __attribute__ ((__nothrow__ , __leaf__));
+extern void setusershell (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+extern int daemon (int __nochdir, int __noclose) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+
+
+extern int chroot (const char *__path) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+
+
+
+extern char *getpass (const char *__prompt) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+
+
+extern int fsync (int __fd);
+extern long int gethostid (void);
+
+
+extern void sync (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+extern int getpagesize (void) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__));
+
+
+
+
+extern int getdtablesize (void) __attribute__ ((__nothrow__ , __leaf__));
+extern int truncate (const char *__file, __off_t __length)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+extern int ftruncate (int __fd, __off_t __length) __attribute__ ((__nothrow__ , __leaf__)) ;
+extern int brk (void *__addr) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+
+extern void *sbrk (intptr_t __delta) __attribute__ ((__nothrow__ , __leaf__));
+extern long int syscall (long int __sysno, ...) __attribute__ ((__nothrow__ , __leaf__));
+extern int lockf (int __fd, int __cmd, __off_t __len) ;
+extern int fdatasync (int __fildes);
+
+
+
+struct stat
+  {
+    __dev_t st_dev;
+
+
+
+
+    __ino_t st_ino;
+
+
+
+
+
+
+
+    __nlink_t st_nlink;
+    __mode_t st_mode;
+
+    __uid_t st_uid;
+    __gid_t st_gid;
+
+    int __pad0;
+
+    __dev_t st_rdev;
+
+
+
+
+    __off_t st_size;
+
+
+
+    __blksize_t st_blksize;
+
+    __blkcnt_t st_blocks;
+    struct timespec st_atim;
+    struct timespec st_mtim;
+    struct timespec st_ctim;
+    __syscall_slong_t __unused[3];
+  };
+extern int stat (const char *__restrict __file,
+   struct stat *__restrict __buf) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+extern int fstat (int __fd, struct stat *__buf) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2)));
+extern int fstatat (int __fd, const char *__restrict __file,
+      struct stat *__restrict __buf, int __flag)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2, 3)));
+extern int lstat (const char *__restrict __file,
+    struct stat *__restrict __buf) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+extern int chmod (const char *__file, __mode_t __mode)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+extern int lchmod (const char *__file, __mode_t __mode)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+extern int fchmod (int __fd, __mode_t __mode) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+extern int fchmodat (int __fd, const char *__file, __mode_t __mode,
+       int __flag)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2))) ;
+
+
+
+
+
+
+extern __mode_t umask (__mode_t __mask) __attribute__ ((__nothrow__ , __leaf__));
+extern int mkdir (const char *__path, __mode_t __mode)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+extern int mkdirat (int __fd, const char *__path, __mode_t __mode)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2)));
+
+
+
+
+
+
+extern int mknod (const char *__path, __mode_t __mode, __dev_t __dev)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+extern int mknodat (int __fd, const char *__path, __mode_t __mode,
+      __dev_t __dev) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2)));
+
+
+
+
+
+extern int mkfifo (const char *__path, __mode_t __mode)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+extern int mkfifoat (int __fd, const char *__path, __mode_t __mode)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2)));
+
+
+
+
+
+extern int utimensat (int __fd, const char *__path,
+        const struct timespec __times[2],
+        int __flags)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2)));
+
+
+
+
+extern int futimens (int __fd, const struct timespec __times[2]) __attribute__ ((__nothrow__ , __leaf__));
+extern int __fxstat (int __ver, int __fildes, struct stat *__stat_buf)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (3)));
+extern int __xstat (int __ver, const char *__filename,
+      struct stat *__stat_buf) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2, 3)));
+extern int __lxstat (int __ver, const char *__filename,
+       struct stat *__stat_buf) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2, 3)));
+extern int __fxstatat (int __ver, int __fildes, const char *__filename,
+         struct stat *__stat_buf, int __flag)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (3, 4)));
+extern int __xmknod (int __ver, const char *__path, __mode_t __mode,
+       __dev_t *__dev) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2, 4)));
+
+extern int __xmknodat (int __ver, int __fd, const char *__path,
+         __mode_t __mode, __dev_t *__dev)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (3, 5)));
+
+
+
+
+extern __inline __attribute__ ((__gnu_inline__)) int
+__attribute__ ((__nothrow__ , __leaf__)) stat (const char *__path, struct stat *__statbuf)
+{
+  return __xstat (1, __path, __statbuf);
+}
+
+
+extern __inline __attribute__ ((__gnu_inline__)) int
+__attribute__ ((__nothrow__ , __leaf__)) lstat (const char *__path, struct stat *__statbuf)
+{
+  return __lxstat (1, __path, __statbuf);
+}
+
+
+extern __inline __attribute__ ((__gnu_inline__)) int
+__attribute__ ((__nothrow__ , __leaf__)) fstat (int __fd, struct stat *__statbuf)
+{
+  return __fxstat (1, __fd, __statbuf);
+}
+
+
+extern __inline __attribute__ ((__gnu_inline__)) int
+__attribute__ ((__nothrow__ , __leaf__)) fstatat (int __fd, const char *__filename, struct stat *__statbuf, int __flag)
+
+{
+  return __fxstatat (1, __fd, __filename, __statbuf, __flag);
+}
+
+
+
+extern __inline __attribute__ ((__gnu_inline__)) int
+__attribute__ ((__nothrow__ , __leaf__)) mknod (const char *__path, __mode_t __mode, __dev_t __dev)
+{
+  return __xmknod (0, __path, __mode, &__dev);
+}
+
+
+
+extern __inline __attribute__ ((__gnu_inline__)) int
+__attribute__ ((__nothrow__ , __leaf__)) mknodat (int __fd, const char *__path, __mode_t __mode, __dev_t __dev)
+
+{
+  return __xmknodat (0, __fd, __path, __mode, &__dev);
+}
+
+
+
+
+
+
+
+
+struct flock
+  {
+    short int l_type;
+    short int l_whence;
+
+    __off_t l_start;
+    __off_t l_len;
+
+
+
+
+    __pid_t l_pid;
+  };
+
+
+extern int fcntl (int __fd, int __cmd, ...);
+extern int open (const char *__file, int __oflag, ...) __attribute__ ((__nonnull__ (1)));
+extern int openat (int __fd, const char *__file, int __oflag, ...)
+     __attribute__ ((__nonnull__ (2)));
+extern int creat (const char *__file, mode_t __mode) __attribute__ ((__nonnull__ (1)));
+extern int posix_fadvise (int __fd, off_t __offset, off_t __len,
+     int __advise) __attribute__ ((__nothrow__ , __leaf__));
+extern int posix_fallocate (int __fd, off_t __offset, off_t __len);
+
+
+
+
+extern int flock (int __fd, int __operation) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+
+struct _IO_FILE;
+
+
+
+typedef struct _IO_FILE FILE;
+
+
+
+
+
+typedef struct _IO_FILE __FILE;
+
+
+
+
+typedef struct
+{
+  int __count;
+  union
+  {
+
+    unsigned int __wch;
+
+
+
+    char __wchb[4];
+  } __value;
+} __mbstate_t;
+typedef struct
+{
+  __off_t __pos;
+  __mbstate_t __state;
+} _G_fpos_t;
+typedef struct
+{
+  __off64_t __pos;
+  __mbstate_t __state;
+} _G_fpos64_t;
+struct _IO_jump_t; struct _IO_FILE;
+typedef void _IO_lock_t;
+
+
+
+
+
+struct _IO_marker {
+  struct _IO_marker *_next;
+  struct _IO_FILE *_sbuf;
+
+
+
+  int _pos;
+};
+
+
+enum __codecvt_result
+{
+  __codecvt_ok,
+  __codecvt_partial,
+  __codecvt_error,
+  __codecvt_noconv
+};
+struct _IO_FILE {
+  int _flags;
+
+
+
+
+  char* _IO_read_ptr;
+  char* _IO_read_end;
+  char* _IO_read_base;
+  char* _IO_write_base;
+  char* _IO_write_ptr;
+  char* _IO_write_end;
+  char* _IO_buf_base;
+  char* _IO_buf_end;
+
+  char *_IO_save_base;
+  char *_IO_backup_base;
+  char *_IO_save_end;
+
+  struct _IO_marker *_markers;
+
+  struct _IO_FILE *_chain;
+
+  int _fileno;
+
+
+
+  int _flags2;
+
+  __off_t _old_offset;
+
+
+
+  unsigned short _cur_column;
+  signed char _vtable_offset;
+  char _shortbuf[1];
+
+
+
+  _IO_lock_t *_lock;
+  __off64_t _offset;
+  void *__pad1;
+  void *__pad2;
+  void *__pad3;
+  void *__pad4;
+  size_t __pad5;
+
+  int _mode;
+
+  char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
+
+};
+
+
+typedef struct _IO_FILE _IO_FILE;
+
+
+struct _IO_FILE_plus;
+
+extern struct _IO_FILE_plus _IO_2_1_stdin_;
+extern struct _IO_FILE_plus _IO_2_1_stdout_;
+extern struct _IO_FILE_plus _IO_2_1_stderr_;
+typedef __ssize_t __io_read_fn (void *__cookie, char *__buf, size_t __nbytes);
+
+
+
+
+
+
+
+typedef __ssize_t __io_write_fn (void *__cookie, const char *__buf,
+     size_t __n);
+
+
+
+
+
+
+
+typedef int __io_seek_fn (void *__cookie, __off64_t *__pos, int __w);
+
+
+typedef int __io_close_fn (void *__cookie);
+extern int __underflow (_IO_FILE *);
+extern int __uflow (_IO_FILE *);
+extern int __overflow (_IO_FILE *, int);
+extern int _IO_getc (_IO_FILE *__fp);
+extern int _IO_putc (int __c, _IO_FILE *__fp);
+extern int _IO_feof (_IO_FILE *__fp) __attribute__ ((__nothrow__ , __leaf__));
+extern int _IO_ferror (_IO_FILE *__fp) __attribute__ ((__nothrow__ , __leaf__));
+
+extern int _IO_peekc_locked (_IO_FILE *__fp);
+
+
+
+
+
+extern void _IO_flockfile (_IO_FILE *) __attribute__ ((__nothrow__ , __leaf__));
+extern void _IO_funlockfile (_IO_FILE *) __attribute__ ((__nothrow__ , __leaf__));
+extern int _IO_ftrylockfile (_IO_FILE *) __attribute__ ((__nothrow__ , __leaf__));
+extern int _IO_vfscanf (_IO_FILE * __restrict, const char * __restrict,
+   __gnuc_va_list, int *__restrict);
+extern int _IO_vfprintf (_IO_FILE *__restrict, const char *__restrict,
+    __gnuc_va_list);
+extern __ssize_t _IO_padn (_IO_FILE *, int, __ssize_t);
+extern size_t _IO_sgetn (_IO_FILE *, void *, size_t);
+
+extern __off64_t _IO_seekoff (_IO_FILE *, __off64_t, int, int);
+extern __off64_t _IO_seekpos (_IO_FILE *, __off64_t, int);
+
+extern void _IO_free_backup_area (_IO_FILE *) __attribute__ ((__nothrow__ , __leaf__));
+
+
+typedef _G_fpos_t fpos_t;
+
+
+
+
+
+
+
+extern struct _IO_FILE *stdin;
+extern struct _IO_FILE *stdout;
+extern struct _IO_FILE *stderr;
+
+
+
+
+
+
+
+extern int remove (const char *__filename) __attribute__ ((__nothrow__ , __leaf__));
+
+extern int rename (const char *__old, const char *__new) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+extern int renameat (int __oldfd, const char *__old, int __newfd,
+       const char *__new) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+
+
+extern FILE *tmpfile (void) ;
+extern char *tmpnam (char *__s) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+
+extern char *tmpnam_r (char *__s) __attribute__ ((__nothrow__ , __leaf__)) ;
+extern char *tempnam (const char *__dir, const char *__pfx)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__malloc__)) ;
+
+
+
+
+
+
+
+
+extern int fclose (FILE *__stream);
+
+
+
+
+extern int fflush (FILE *__stream);
+
+extern int fflush_unlocked (FILE *__stream);
+
+
+
+
+
+
+extern FILE *fopen (const char *__restrict __filename,
+      const char *__restrict __modes) ;
+
+
+
+
+extern FILE *freopen (const char *__restrict __filename,
+        const char *__restrict __modes,
+        FILE *__restrict __stream) ;
+
+extern FILE *fdopen (int __fd, const char *__modes) __attribute__ ((__nothrow__ , __leaf__)) ;
+extern FILE *fmemopen (void *__s, size_t __len, const char *__modes)
+  __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+extern FILE *open_memstream (char **__bufloc, size_t *__sizeloc) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+
+
+extern void setbuf (FILE *__restrict __stream, char *__restrict __buf) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+extern int setvbuf (FILE *__restrict __stream, char *__restrict __buf,
+      int __modes, size_t __n) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+extern void setbuffer (FILE *__restrict __stream, char *__restrict __buf,
+         size_t __size) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern void setlinebuf (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+
+
+extern int fprintf (FILE *__restrict __stream,
+      const char *__restrict __format, ...);
+
+
+
+
+extern int printf (const char *__restrict __format, ...);
+
+extern int sprintf (char *__restrict __s,
+      const char *__restrict __format, ...) __attribute__ ((__nothrow__));
+
+
+
+
+
+extern int vfprintf (FILE *__restrict __s, const char *__restrict __format,
+       __gnuc_va_list __arg);
+
+
+
+
+extern int vprintf (const char *__restrict __format, __gnuc_va_list __arg);
+
+extern int vsprintf (char *__restrict __s, const char *__restrict __format,
+       __gnuc_va_list __arg) __attribute__ ((__nothrow__));
+
+
+
+
+
+extern int snprintf (char *__restrict __s, size_t __maxlen,
+       const char *__restrict __format, ...)
+     __attribute__ ((__nothrow__)) __attribute__ ((__format__ (__printf__, 3, 4)));
+
+extern int vsnprintf (char *__restrict __s, size_t __maxlen,
+        const char *__restrict __format, __gnuc_va_list __arg)
+     __attribute__ ((__nothrow__)) __attribute__ ((__format__ (__printf__, 3, 0)));
+
+extern int vdprintf (int __fd, const char *__restrict __fmt,
+       __gnuc_va_list __arg)
+     __attribute__ ((__format__ (__printf__, 2, 0)));
+extern int dprintf (int __fd, const char *__restrict __fmt, ...)
+     __attribute__ ((__format__ (__printf__, 2, 3)));
+
+
+
+
+
+
+
+
+extern int fscanf (FILE *__restrict __stream,
+     const char *__restrict __format, ...) ;
+
+
+
+
+extern int scanf (const char *__restrict __format, ...) ;
+
+extern int sscanf (const char *__restrict __s,
+     const char *__restrict __format, ...) __attribute__ ((__nothrow__ , __leaf__));
+extern int fscanf (FILE *__restrict __stream, const char *__restrict __format, ...) __asm__ ("" "__isoc99_fscanf")
+
+                               ;
+extern int scanf (const char *__restrict __format, ...) __asm__ ("" "__isoc99_scanf")
+                              ;
+extern int sscanf (const char *__restrict __s, const char *__restrict __format, ...) __asm__ ("" "__isoc99_sscanf") __attribute__ ((__nothrow__ , __leaf__))
+
+                      ;
+
+
+
+
+
+
+
+
+extern int vfscanf (FILE *__restrict __s, const char *__restrict __format,
+      __gnuc_va_list __arg)
+     __attribute__ ((__format__ (__scanf__, 2, 0))) ;
+
+
+
+
+
+extern int vscanf (const char *__restrict __format, __gnuc_va_list __arg)
+     __attribute__ ((__format__ (__scanf__, 1, 0))) ;
+
+
+extern int vsscanf (const char *__restrict __s,
+      const char *__restrict __format, __gnuc_va_list __arg)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__format__ (__scanf__, 2, 0)));
+extern int vfscanf (FILE *__restrict __s, const char *__restrict __format, __gnuc_va_list __arg) __asm__ ("" "__isoc99_vfscanf")
+
+
+
+     __attribute__ ((__format__ (__scanf__, 2, 0))) ;
+extern int vscanf (const char *__restrict __format, __gnuc_va_list __arg) __asm__ ("" "__isoc99_vscanf")
+
+     __attribute__ ((__format__ (__scanf__, 1, 0))) ;
+extern int vsscanf (const char *__restrict __s, const char *__restrict __format, __gnuc_va_list __arg) __asm__ ("" "__isoc99_vsscanf") __attribute__ ((__nothrow__ , __leaf__))
+
+
+
+     __attribute__ ((__format__ (__scanf__, 2, 0)));
+
+
+
+
+
+
+
+
+
+extern int fgetc (FILE *__stream);
+extern int getc (FILE *__stream);
+
+
+
+
+
+extern int getchar (void);
+
+extern int getc_unlocked (FILE *__stream);
+extern int getchar_unlocked (void);
+extern int fgetc_unlocked (FILE *__stream);
+
+
+
+
+
+
+
+
+
+
+
+extern int fputc (int __c, FILE *__stream);
+extern int putc (int __c, FILE *__stream);
+
+
+
+
+
+extern int putchar (int __c);
+
+extern int fputc_unlocked (int __c, FILE *__stream);
+
+
+
+
+
+
+
+extern int putc_unlocked (int __c, FILE *__stream);
+extern int putchar_unlocked (int __c);
+
+
+
+
+
+
+extern int getw (FILE *__stream);
+
+
+extern int putw (int __w, FILE *__stream);
+
+
+
+
+
+
+
+
+extern char *fgets (char *__restrict __s, int __n, FILE *__restrict __stream)
+     ;
+
+extern __ssize_t __getdelim (char **__restrict __lineptr,
+          size_t *__restrict __n, int __delimiter,
+          FILE *__restrict __stream) ;
+extern __ssize_t getdelim (char **__restrict __lineptr,
+        size_t *__restrict __n, int __delimiter,
+        FILE *__restrict __stream) ;
+
+
+
+
+
+
+
+extern __ssize_t getline (char **__restrict __lineptr,
+       size_t *__restrict __n,
+       FILE *__restrict __stream) ;
+
+
+
+
+
+
+
+
+extern int fputs (const char *__restrict __s, FILE *__restrict __stream);
+
+
+
+
+
+extern int puts (const char *__s);
+
+
+
+
+
+
+extern int ungetc (int __c, FILE *__stream);
+
+
+
+
+
+
+extern size_t fread (void *__restrict __ptr, size_t __size,
+       size_t __n, FILE *__restrict __stream) ;
+
+
+
+
+extern size_t fwrite (const void *__restrict __ptr, size_t __size,
+        size_t __n, FILE *__restrict __s);
+
+extern size_t fread_unlocked (void *__restrict __ptr, size_t __size,
+         size_t __n, FILE *__restrict __stream) ;
+extern size_t fwrite_unlocked (const void *__restrict __ptr, size_t __size,
+          size_t __n, FILE *__restrict __stream);
+
+
+
+
+
+
+
+
+extern int fseek (FILE *__stream, long int __off, int __whence);
+
+
+
+
+extern long int ftell (FILE *__stream) ;
+
+
+
+
+extern void rewind (FILE *__stream);
+
+extern int fseeko (FILE *__stream, __off_t __off, int __whence);
+
+
+
+
+extern __off_t ftello (FILE *__stream) ;
+
+
+
+
+
+
+extern int fgetpos (FILE *__restrict __stream, fpos_t *__restrict __pos);
+
+
+
+
+extern int fsetpos (FILE *__stream, const fpos_t *__pos);
+
+
+
+extern void clearerr (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
+
+extern int feof (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+extern int ferror (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+extern void clearerr_unlocked (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
+extern int feof_unlocked (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;
+extern int ferror_unlocked (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+
+
+
+
+extern void perror (const char *__s);
+
+
+
+
+
+
+extern int sys_nerr;
+extern const char *const sys_errlist[];
+
+
+
+
+extern int fileno (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+extern int fileno_unlocked (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;
+extern FILE *popen (const char *__command, const char *__modes) ;
+
+
+
+
+
+extern int pclose (FILE *__stream);
+
+
+
+
+
+extern char *ctermid (char *__s) __attribute__ ((__nothrow__ , __leaf__));
+extern void flockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
+extern __inline __attribute__ ((__gnu_inline__)) int
+vprintf (const char *__restrict __fmt, __gnuc_va_list __arg)
+{
+  return vfprintf (stdout, __fmt, __arg);
+}
+
+
+
+extern __inline __attribute__ ((__gnu_inline__)) int
+getchar (void)
+{
+  return _IO_getc (stdin);
+}
+
+
+
+
+extern __inline __attribute__ ((__gnu_inline__)) int
+fgetc_unlocked (FILE *__fp)
+{
+  return (__builtin_expect (((__fp)->_IO_read_ptr >= (__fp)->_IO_read_end), 0) ? __uflow (__fp) : *(unsigned char *) (__fp)->_IO_read_ptr++);
+}
+
+
+
+
+
+extern __inline __attribute__ ((__gnu_inline__)) int
+getc_unlocked (FILE *__fp)
+{
+  return (__builtin_expect (((__fp)->_IO_read_ptr >= (__fp)->_IO_read_end), 0) ? __uflow (__fp) : *(unsigned char *) (__fp)->_IO_read_ptr++);
+}
+
+
+extern __inline __attribute__ ((__gnu_inline__)) int
+getchar_unlocked (void)
+{
+  return (__builtin_expect (((stdin)->_IO_read_ptr >= (stdin)->_IO_read_end), 0) ? __uflow (stdin) : *(unsigned char *) (stdin)->_IO_read_ptr++);
+}
+
+
+
+
+extern __inline __attribute__ ((__gnu_inline__)) int
+putchar (int __c)
+{
+  return _IO_putc (__c, stdout);
+}
+
+
+
+
+extern __inline __attribute__ ((__gnu_inline__)) int
+fputc_unlocked (int __c, FILE *__stream)
+{
+  return (__builtin_expect (((__stream)->_IO_write_ptr >= (__stream)->_IO_write_end), 0) ? __overflow (__stream, (unsigned char) (__c)) : (unsigned char) (*(__stream)->_IO_write_ptr++ = (__c)));
+}
+
+
+
+
+
+extern __inline __attribute__ ((__gnu_inline__)) int
+putc_unlocked (int __c, FILE *__stream)
+{
+  return (__builtin_expect (((__stream)->_IO_write_ptr >= (__stream)->_IO_write_end), 0) ? __overflow (__stream, (unsigned char) (__c)) : (unsigned char) (*(__stream)->_IO_write_ptr++ = (__c)));
+}
+
+
+extern __inline __attribute__ ((__gnu_inline__)) int
+putchar_unlocked (int __c)
+{
+  return (__builtin_expect (((stdout)->_IO_write_ptr >= (stdout)->_IO_write_end), 0) ? __overflow (stdout, (unsigned char) (__c)) : (unsigned char) (*(stdout)->_IO_write_ptr++ = (__c)));
+}
+extern __inline __attribute__ ((__gnu_inline__)) int
+__attribute__ ((__nothrow__ , __leaf__)) feof_unlocked (FILE *__stream)
+{
+  return (((__stream)->_flags & 0x10) != 0);
+}
+
+
+extern __inline __attribute__ ((__gnu_inline__)) int
+__attribute__ ((__nothrow__ , __leaf__)) ferror_unlocked (FILE *__stream)
+{
+  return (((__stream)->_flags & 0x20) != 0);
+}
+
+
+
+struct addrlist;
+
+
+krb5_error_code krb5_lock_file (krb5_context, int, int);
+krb5_error_code krb5_unlock_file (krb5_context, int);
+krb5_error_code krb5_sendto_kdc (krb5_context, const krb5_data *,
+     const krb5_data *, krb5_data *, int *, int);
+krb5_error_code krb5int_sendto (krb5_context, const krb5_data *,
+    const struct addrlist *, krb5_data *,
+    struct sockaddr *, socklen_t *, int *);
+krb5_error_code krb5_get_krbhst (krb5_context, const krb5_data *, char *** );
+krb5_error_code krb5_free_krbhst (krb5_context, char * const * );
+krb5_error_code krb5_create_secure_file (krb5_context, const char * pathname);
+krb5_error_code krb5_sync_disk_file (krb5_context, FILE *fp);
+
+krb5_error_code krb5int_get_fq_local_hostname (char *, size_t);
+
+krb5_error_code krb5_os_init_context (krb5_context);
+
+void krb5_os_free_context (krb5_context);
+
+
+
+krb5_error_code os_get_default_config_files
+    (profile_filespec_t **pfiles, krb5_boolean secure);
+
+krb5_error_code krb5_find_config_files (void);
+
+krb5_error_code krb5_os_hostaddr
+ (krb5_context, const char *, krb5_address ***);
+
+
+
+struct addrlist {
+    struct addrinfo **addrs;
+    int naddrs;
+    int space;
+};
+
+extern void krb5int_free_addrlist (struct addrlist *);
+extern int krb5int_grow_addrlist (struct addrlist *, int);
+extern int krb5int_add_host_to_list (struct addrlist *, const char *,
+         int, int, int, int);
+
+krb5_error_code
+krb5int_locate_server (krb5_context,
+         const krb5_data *realm,
+         struct addrlist *,
+
+         int want_masters,
+
+         const char *profilename,
+
+         const char *dnsname,
+         int is_stream_service,
+
+
+
+
+
+         int dflport1, int dflport2,
+         int family);
+
+
+
+
+
+struct krb5_enc_provider {
+
+
+    size_t block_size, keybytes, keylength;
+
+
+    krb5_error_code (*encrypt) (const krb5_keyblock *key,
+    const krb5_data *cipher_state,
+    const krb5_data *input,
+    krb5_data *output);
+
+    krb5_error_code (*decrypt) (const krb5_keyblock *key,
+    const krb5_data *ivec,
+    const krb5_data *input,
+    krb5_data *output);
+
+    krb5_error_code (*make_key) (const krb5_data *randombits,
+     krb5_keyblock *key);
+
+  krb5_error_code (*init_state) (const krb5_keyblock *key,
+     krb5_keyusage keyusage, krb5_data *out_state);
+  krb5_error_code (*free_state) (krb5_data *state);
+
+};
+
+struct krb5_hash_provider {
+    size_t hashsize, blocksize;
+
+
+    krb5_error_code (*hash) (unsigned int icount, const krb5_data *input,
+        krb5_data *output);
+};
+
+struct krb5_keyhash_provider {
+    size_t hashsize;
+
+    krb5_error_code (*hash) (const krb5_keyblock *key,
+        krb5_keyusage keyusage,
+        const krb5_data *ivec,
+        const krb5_data *input,
+        krb5_data *output);
+
+    krb5_error_code (*verify) (const krb5_keyblock *key,
+          krb5_keyusage keyusage,
+          const krb5_data *ivec,
+          const krb5_data *input,
+          const krb5_data *hash,
+          krb5_boolean *valid);
+};
+
+typedef void (*krb5_encrypt_length_func) (const struct krb5_enc_provider *enc,
+  const struct krb5_hash_provider *hash,
+  size_t inputlen, size_t *length);
+
+typedef krb5_error_code (*krb5_crypt_func) (const struct krb5_enc_provider *enc,
+  const struct krb5_hash_provider *hash,
+  const krb5_keyblock *key, krb5_keyusage keyusage,
+  const krb5_data *ivec,
+  const krb5_data *input, krb5_data *output);
+
+typedef krb5_error_code (*krb5_str2key_func) (const struct krb5_enc_provider *enc, const krb5_data *string,
+  const krb5_data *salt, const krb5_data *parm, krb5_keyblock *key);
+
+struct krb5_keytypes {
+    krb5_enctype etype;
+    char *in_string;
+    char *out_string;
+    const struct krb5_enc_provider *enc;
+    const struct krb5_hash_provider *hash;
+    krb5_encrypt_length_func encrypt_len;
+    krb5_crypt_func encrypt;
+    krb5_crypt_func decrypt;
+    krb5_str2key_func str2key;
+    krb5_cksumtype required_ctype;
+};
+
+struct krb5_cksumtypes {
+    krb5_cksumtype ctype;
+    unsigned int flags;
+    char *in_string;
+    char *out_string;
+
+
+
+
+    krb5_enctype keyed_etype;
+
+
+
+
+
+    const struct krb5_keyhash_provider *keyhash;
+    const struct krb5_hash_provider *hash;
+
+
+
+
+
+    unsigned int trunc_size;
+};
+void krb5_nfold
+(unsigned int inbits, const unsigned char *in,
+  unsigned int outbits, unsigned char *out);
+
+krb5_error_code krb5_hmac
+(const struct krb5_hash_provider *hash,
+  const krb5_keyblock *key, unsigned int icount,
+  const krb5_data *input, krb5_data *output);
+
+krb5_error_code krb5int_pbkdf2_hmac_sha1 (const krb5_data *, unsigned long,
+       const krb5_data *,
+       const krb5_data *);
+krb5_error_code krb5int_des_init_state
+(const krb5_keyblock *key, krb5_keyusage keyusage, krb5_data *new_state);
+
+
+
+
+
+krb5_error_code krb5int_default_free_state
+(krb5_data *state);
+
+
+
+
+
+krb5_error_code krb5int_c_combine_keys
+(krb5_context context, krb5_keyblock *key1, krb5_keyblock *key2,
+  krb5_keyblock *outkey);
+
+
+
+
+extern void krb5int_prng_cleanup (void);
+
+
+
+
+
+
+
+extern const struct krb5_enc_provider krb5int_enc_arcfour;
+extern const struct krb5_hash_provider krb5int_hash_md5;
+
+
+
+
+
+krb5_error_code krb5_crypto_os_localaddr
+ (krb5_address ***);
+
+krb5_error_code krb5_crypto_us_timeofday
+ (krb5_int32 *,
+  krb5_int32 *);
+
+time_t gmt_mktime (struct tm *);
+
+
+
+
+
+krb5_error_code krb5_encrypt_helper
+(krb5_context context, const krb5_keyblock *key,
+  krb5_keyusage keyusage, const krb5_data *plain,
+  krb5_enc_data *cipher);
+typedef struct _krb5_tl_data {
+    struct _krb5_tl_data* tl_data_next;
+    krb5_int16 tl_data_type;
+    krb5_ui_2 tl_data_length;
+    krb5_octet * tl_data_contents;
+} krb5_tl_data;
+
+
+
+
+
+
+
+typedef struct _krb5_key_data {
+    krb5_int16 key_data_ver;
+    krb5_int16 key_data_kvno;
+    krb5_int16 key_data_type[2];
+    krb5_ui_2 key_data_length[2];
+    krb5_octet * key_data_contents[2];
+} krb5_key_data;
+
+
+
+typedef struct _krb5_keysalt {
+    krb5_int16 type;
+    krb5_data data;
+} krb5_keysalt;
+
+typedef struct _krb5_db_entry_new {
+    krb5_magic magic;
+    krb5_ui_2 len;
+    krb5_flags attributes;
+    krb5_deltat max_life;
+    krb5_deltat max_renewable_life;
+    krb5_timestamp expiration;
+    krb5_timestamp pw_expiration;
+    krb5_timestamp last_success;
+    krb5_timestamp last_failed;
+    krb5_kvno fail_auth_count;
+    krb5_int16 n_tl_data;
+    krb5_int16 n_key_data;
+    krb5_ui_2 e_length;
+    krb5_octet * e_data;
+
+    krb5_principal princ;
+    krb5_tl_data * tl_data;
+    krb5_key_data * key_data;
+} krb5_db_entry;
+extern char *krb5_mkey_pwd_prompt1;
+extern char *krb5_mkey_pwd_prompt2;
+krb5_error_code krb5_db_set_name (krb5_context, char * );
+krb5_error_code krb5_db_init (krb5_context);
+krb5_error_code krb5_db_fini (krb5_context);
+krb5_error_code krb5_db_get_age (krb5_context, char *, time_t * );
+krb5_error_code krb5_db_create (krb5_context, char *, krb5_int32 );
+krb5_error_code krb5_db_rename (krb5_context, char *, char * );
+krb5_error_code krb5_db_get_principal (krb5_context, krb5_const_principal ,
+           krb5_db_entry *, int *,
+           krb5_boolean * );
+void krb5_db_free_principal (krb5_context, krb5_db_entry *, int );
+krb5_error_code krb5_db_put_principal (krb5_context, krb5_db_entry *, int * );
+krb5_error_code krb5_db_delete_principal (krb5_context, krb5_const_principal,
+       int * );
+krb5_error_code krb5_db_iterate (krb5_context,
+     krb5_error_code (* ) (krb5_pointer,
+             krb5_db_entry *),
+     krb5_pointer);
+krb5_error_code krb5_db_iterate_ext (krb5_context,
+         krb5_error_code (* ) (krb5_pointer,
+                   krb5_db_entry *),
+         krb5_pointer, int, int);
+krb5_error_code krb5_db_verify_master_key (krb5_context, krb5_principal,
+        krb5_keyblock *);
+krb5_error_code krb5_db_store_mkey (krb5_context, char *, krb5_principal,
+        krb5_keyblock *);
+
+krb5_error_code krb5_db_setup_mkey_name (krb5_context, const char *,
+      const char *, char **,
+      krb5_principal *);
+
+krb5_error_code krb5_db_set_mkey (krb5_context, krb5_keyblock *);
+
+krb5_error_code krb5_db_get_mkey (krb5_context, krb5_keyblock **);
+krb5_error_code krb5_db_destroy (krb5_context, char * );
+krb5_error_code krb5_db_lock (krb5_context, int );
+krb5_error_code krb5_db_unlock (krb5_context);
+krb5_error_code krb5_db_set_nonblocking (krb5_context, krb5_boolean,
+      krb5_boolean * );
+krb5_boolean krb5_db_set_lockmode (krb5_context, krb5_boolean);
+krb5_error_code krb5_db_fetch_mkey (krb5_context, krb5_principal, krb5_enctype,
+        krb5_boolean, krb5_boolean, char *,
+        krb5_data *,
+        krb5_keyblock * );
+
+krb5_error_code krb5_db_open_database (krb5_context);
+krb5_error_code krb5_db_close_database (krb5_context);
+
+krb5_error_code krb5_dbekd_encrypt_key_data (krb5_context,
+          const krb5_keyblock *,
+          const krb5_keyblock *,
+          const krb5_keysalt *,
+          int,
+          krb5_key_data *);
+krb5_error_code krb5_dbekd_decrypt_key_data (krb5_context,
+          const krb5_keyblock *,
+          const krb5_key_data *,
+          krb5_keyblock *,
+          krb5_keysalt *);
+krb5_error_code krb5_dbe_create_key_data (krb5_context,
+       krb5_db_entry *);
+krb5_error_code krb5_dbe_update_tl_data (krb5_context,
+      krb5_db_entry *,
+      krb5_tl_data *);
+krb5_error_code krb5_dbe_lookup_tl_data (krb5_context,
+      krb5_db_entry *,
+      krb5_tl_data *);
+krb5_error_code krb5_dbe_update_last_pwd_change (krb5_context,
+       krb5_db_entry *,
+       krb5_timestamp);
+krb5_error_code krb5_dbe_lookup_last_pwd_change (krb5_context,
+       krb5_db_entry *,
+       krb5_timestamp *);
+krb5_error_code krb5_dbe_update_mod_princ_data (krb5_context,
+      krb5_db_entry *,
+      krb5_timestamp,
+      krb5_const_principal);
+krb5_error_code krb5_dbe_lookup_mod_princ_data (krb5_context,
+      krb5_db_entry *,
+      krb5_timestamp *,
+      krb5_principal *);
+int krb5_encode_princ_dbkey (krb5_context, krb5_data *, krb5_const_principal);
+void krb5_free_princ_dbkey (krb5_context, krb5_data *);
+krb5_error_code krb5_encode_princ_contents (krb5_context, krb5_data *,
+         krb5_db_entry *);
+void krb5_free_princ_contents (krb5_context, krb5_data *);
+krb5_error_code krb5_decode_princ_contents (krb5_context, krb5_data *,
+         krb5_db_entry *);
+void krb5_dbe_free_contents (krb5_context, krb5_db_entry *);
+
+krb5_error_code krb5_dbe_find_enctype (krb5_context, krb5_db_entry *,
+           krb5_int32,
+           krb5_int32,
+           krb5_int32,
+           krb5_key_data **);
+
+krb5_error_code krb5_dbe_search_enctype (krb5_context,
+      krb5_db_entry *,
+      krb5_int32 *,
+      krb5_int32,
+      krb5_int32,
+      krb5_int32,
+      krb5_key_data **);
+
+struct __krb5_key_salt_tuple;
+
+krb5_error_code krb5_dbe_cpw (krb5_context,
+         krb5_keyblock *,
+         struct __krb5_key_salt_tuple *,
+         int,
+         char *,
+         int,
+         krb5_boolean,
+         krb5_db_entry *);
+krb5_error_code krb5_dbe_apw (krb5_context,
+         krb5_keyblock *,
+         struct __krb5_key_salt_tuple *,
+         int,
+         char *,
+         krb5_db_entry *);
+krb5_error_code krb5_dbe_crk (krb5_context,
+         krb5_keyblock *,
+         struct __krb5_key_salt_tuple *,
+         int,
+         krb5_boolean,
+         krb5_db_entry *);
+krb5_error_code krb5_dbe_ark (krb5_context,
+         krb5_keyblock *,
+         struct __krb5_key_salt_tuple *,
+         int,
+         krb5_db_entry *);
+
+krb5_error_code krb5_ser_db_context_init (krb5_context);
+
+
+
+
+
+
+
+typedef struct _krb5_os_context {
+ krb5_magic magic;
+ krb5_int32 time_offset;
+ krb5_int32 usec_offset;
+ krb5_int32 os_flags;
+ char * default_ccname;
+} *krb5_os_context;
+typedef struct _krb5_pa_enc_ts {
+    krb5_timestamp patimestamp;
+    krb5_int32 pausec;
+} krb5_pa_enc_ts;
+
+typedef krb5_error_code (*krb5_preauth_obtain_proc)
+    (krb5_context,
+      krb5_pa_data *,
+      krb5_etype_info,
+      krb5_keyblock *,
+      krb5_error_code ( * )(krb5_context,
+       const krb5_enctype,
+       krb5_data *,
+       krb5_const_pointer,
+       krb5_keyblock **),
+      krb5_const_pointer,
+      krb5_creds *,
+      krb5_kdc_req *,
+      krb5_pa_data **);
+
+typedef krb5_error_code (*krb5_preauth_process_proc)
+    (krb5_context,
+      krb5_pa_data *,
+      krb5_kdc_req *,
+      krb5_kdc_rep *,
+      krb5_error_code ( * )(krb5_context,
+       const krb5_enctype,
+       krb5_data *,
+       krb5_const_pointer,
+       krb5_keyblock **),
+      krb5_const_pointer,
+      krb5_error_code ( * )(krb5_context,
+       const krb5_keyblock *,
+       krb5_const_pointer,
+       krb5_kdc_rep * ),
+      krb5_keyblock **,
+      krb5_creds *,
+      krb5_int32 *,
+      krb5_int32 *);
+
+typedef struct _krb5_preauth_ops {
+    krb5_magic magic;
+    int type;
+    int flags;
+    krb5_preauth_obtain_proc obtain;
+    krb5_preauth_process_proc process;
+} krb5_preauth_ops;
+
+krb5_error_code krb5_obtain_padata
+     (krb5_context,
+  krb5_pa_data **,
+  krb5_error_code ( * )(krb5_context,
+            const krb5_enctype,
+            krb5_data *,
+            krb5_const_pointer,
+            krb5_keyblock **),
+  krb5_const_pointer,
+  krb5_creds *,
+  krb5_kdc_req *);
+
+krb5_error_code krb5_process_padata
+ (krb5_context,
+  krb5_kdc_req *,
+  krb5_kdc_rep *,
+  krb5_error_code ( * )(krb5_context,
+            const krb5_enctype,
+            krb5_data *,
+            krb5_const_pointer,
+            krb5_keyblock **),
+  krb5_const_pointer,
+  krb5_error_code ( * )(krb5_context,
+            const krb5_keyblock *,
+            krb5_const_pointer,
+            krb5_kdc_rep * ),
+  krb5_keyblock **,
+  krb5_creds *,
+  krb5_int32 *);
+
+void krb5_free_etype_info
+    (krb5_context, krb5_etype_info);
+krb5_error_code
+krb5int_copy_data_contents (krb5_context, const krb5_data *, krb5_data *);
+
+typedef krb5_error_code (*krb5_gic_get_as_key_fct)
+    (krb5_context,
+       krb5_principal,
+       krb5_enctype,
+       krb5_prompter_fct,
+       void *prompter_data,
+       krb5_data *salt,
+     krb5_data *s2kparams,
+       krb5_keyblock *as_key,
+       void *gak_data);
+
+krb5_error_code
+krb5_get_init_creds
+(krb5_context context,
+  krb5_creds *creds,
+  krb5_principal client,
+  krb5_prompter_fct prompter,
+  void *prompter_data,
+  krb5_deltat start_time,
+  char *in_tkt_service,
+  krb5_get_init_creds_opt *gic_options,
+  krb5_gic_get_as_key_fct gak,
+  void *gak_data,
+  int *master,
+  krb5_kdc_rep **as_reply);
+
+void krb5int_populate_gic_opt (
+    krb5_context, krb5_get_init_creds_opt *,
+    krb5_flags options, krb5_address * const *addrs, krb5_enctype *ktypes,
+    krb5_preauthtype *pre_auth_types, krb5_creds *creds);
+
+
+krb5_error_code krb5_do_preauth
+(krb5_context, krb5_kdc_req *,
+  krb5_pa_data **, krb5_pa_data ***,
+  krb5_data *salt, krb5_data *s2kparams,
+ krb5_enctype *,
+  krb5_keyblock *,
+  krb5_prompter_fct, void *,
+  krb5_gic_get_as_key_fct, void *);
+
+void krb5_free_sam_challenge
+ (krb5_context, krb5_sam_challenge * );
+void krb5_free_sam_challenge_2
+ (krb5_context, krb5_sam_challenge_2 * );
+void krb5_free_sam_challenge_2_body
+ (krb5_context, krb5_sam_challenge_2_body *);
+void krb5_free_sam_response
+ (krb5_context, krb5_sam_response * );
+void krb5_free_sam_response_2
+ (krb5_context, krb5_sam_response_2 * );
+void krb5_free_predicted_sam_response
+ (krb5_context, krb5_predicted_sam_response * );
+void krb5_free_enc_sam_response_enc
+ (krb5_context, krb5_enc_sam_response_enc * );
+void krb5_free_enc_sam_response_enc_2
+ (krb5_context, krb5_enc_sam_response_enc_2 * );
+void krb5_free_sam_challenge_contents
+ (krb5_context, krb5_sam_challenge * );
+void krb5_free_sam_challenge_2_contents
+ (krb5_context, krb5_sam_challenge_2 * );
+void krb5_free_sam_challenge_2_body_contents
+ (krb5_context, krb5_sam_challenge_2_body * );
+void krb5_free_sam_response_contents
+ (krb5_context, krb5_sam_response * );
+void krb5_free_sam_response_2_contents
+ (krb5_context, krb5_sam_response_2 *);
+void krb5_free_predicted_sam_response_contents
+ (krb5_context, krb5_predicted_sam_response * );
+void krb5_free_enc_sam_response_enc_contents
+ (krb5_context, krb5_enc_sam_response_enc * );
+void krb5_free_enc_sam_response_enc_2_contents
+ (krb5_context, krb5_enc_sam_response_enc_2 * );
+
+void krb5_free_pa_enc_ts
+ (krb5_context, krb5_pa_enc_ts *);
+
+
+
+struct _krb5_context {
+ krb5_magic magic;
+ krb5_enctype *in_tkt_ktypes;
+ int in_tkt_ktype_count;
+ krb5_enctype *tgs_ktypes;
+ int tgs_ktype_count;
+ struct _krb5_os_context os_context[1];
+ char *default_realm;
+ profile_t profile;
+ void *db_context;
+ int ser_ctx_count;
+ void *ser_ctx;
+
+ krb5_deltat clockskew;
+ krb5_cksumtype kdc_req_sumtype;
+ krb5_cksumtype default_ap_req_sumtype;
+ krb5_cksumtype default_safe_sumtype;
+ krb5_flags kdc_default_options;
+ krb5_flags library_options;
+ krb5_boolean profile_secure;
+ int fcc_default_format;
+ int scc_default_format;
+ krb5_prompt_type *prompt_types;
+
+
+
+ int udp_pref_limit;
+
+
+
+
+
+
+
+ krb5_enctype *conf_tgs_ktypes;
+ int conf_tgs_ktypes_count;
+
+ krb5_boolean use_conf_ktypes;
+
+
+        krb5_boolean profile_in_memory;
+
+};
+typedef struct _krb5_safe {
+    krb5_magic magic;
+    krb5_data user_data;
+    krb5_timestamp timestamp;
+    krb5_int32 usec;
+
+    krb5_ui_4 seq_number;
+    krb5_address *s_address;
+    krb5_address *r_address;
+    krb5_checksum *checksum;
+} krb5_safe;
+
+typedef struct _krb5_priv {
+    krb5_magic magic;
+    krb5_enc_data enc_part;
+} krb5_priv;
+
+typedef struct _krb5_priv_enc_part {
+    krb5_magic magic;
+    krb5_data user_data;
+    krb5_timestamp timestamp;
+    krb5_int32 usec;
+    krb5_ui_4 seq_number;
+    krb5_address *s_address;
+    krb5_address *r_address;
+} krb5_priv_enc_part;
+
+void krb5_free_safe
+ (krb5_context, krb5_safe * );
+void krb5_free_priv
+ (krb5_context, krb5_priv * );
+void krb5_free_priv_enc_part
+ (krb5_context, krb5_priv_enc_part * );
+krb5_error_code encode_krb5_authenticator
+ (const krb5_authenticator *rep, krb5_data **code);
+
+krb5_error_code encode_krb5_ticket
+ (const krb5_ticket *rep, krb5_data **code);
+
+krb5_error_code encode_krb5_encryption_key
+ (const krb5_keyblock *rep, krb5_data **code);
+
+krb5_error_code encode_krb5_enc_tkt_part
+ (const krb5_enc_tkt_part *rep, krb5_data **code);
+
+krb5_error_code encode_krb5_enc_kdc_rep_part
+ (const krb5_enc_kdc_rep_part *rep, krb5_data **code);
+
+
+krb5_error_code encode_krb5_as_rep
+ (const krb5_kdc_rep *rep, krb5_data **code);
+
+
+krb5_error_code encode_krb5_tgs_rep
+ (const krb5_kdc_rep *rep, krb5_data **code);
+
+krb5_error_code encode_krb5_ap_req
+ (const krb5_ap_req *rep, krb5_data **code);
+
+krb5_error_code encode_krb5_ap_rep
+ (const krb5_ap_rep *rep, krb5_data **code);
+
+krb5_error_code encode_krb5_ap_rep_enc_part
+ (const krb5_ap_rep_enc_part *rep, krb5_data **code);
+
+krb5_error_code encode_krb5_as_req
+ (const krb5_kdc_req *rep, krb5_data **code);
+
+krb5_error_code encode_krb5_tgs_req
+ (const krb5_kdc_req *rep, krb5_data **code);
+
+krb5_error_code encode_krb5_kdc_req_body
+ (const krb5_kdc_req *rep, krb5_data **code);
+
+krb5_error_code encode_krb5_safe
+ (const krb5_safe *rep, krb5_data **code);
+
+krb5_error_code encode_krb5_safe_with_body
+ (const krb5_safe *rep, const krb5_data *body, krb5_data **code);
+
+krb5_error_code encode_krb5_priv
+ (const krb5_priv *rep, krb5_data **code);
+
+krb5_error_code encode_krb5_enc_priv_part
+ (const krb5_priv_enc_part *rep, krb5_data **code);
+
+krb5_error_code encode_krb5_cred
+ (const krb5_cred *rep, krb5_data **code);
+
+krb5_error_code encode_krb5_enc_cred_part
+ (const krb5_cred_enc_part *rep, krb5_data **code);
+
+krb5_error_code encode_krb5_error
+ (const krb5_error *rep, krb5_data **code);
+
+krb5_error_code encode_krb5_authdata
+ (const krb5_authdata **rep, krb5_data **code);
+
+krb5_error_code encode_krb5_pwd_sequence
+ (const passwd_phrase_element *rep, krb5_data **code);
+
+krb5_error_code encode_krb5_pwd_data
+ (const krb5_pwd_data *rep, krb5_data **code);
+
+krb5_error_code encode_krb5_padata_sequence
+ (const krb5_pa_data ** rep, krb5_data **code);
+
+krb5_error_code encode_krb5_alt_method
+ (const krb5_alt_method *, krb5_data **code);
+
+krb5_error_code encode_krb5_etype_info
+ (const krb5_etype_info_entry **, krb5_data **code);
+krb5_error_code encode_krb5_etype_info2
+ (const krb5_etype_info_entry **, krb5_data **code);
+
+krb5_error_code encode_krb5_enc_data
+     (const krb5_enc_data *, krb5_data **);
+
+krb5_error_code encode_krb5_pa_enc_ts
+     (const krb5_pa_enc_ts *, krb5_data **);
+
+krb5_error_code encode_krb5_sam_challenge
+ (const krb5_sam_challenge * , krb5_data **);
+
+krb5_error_code encode_krb5_sam_key
+ (const krb5_sam_key * , krb5_data **);
+
+krb5_error_code encode_krb5_enc_sam_response_enc
+ (const krb5_enc_sam_response_enc * , krb5_data **);
+
+krb5_error_code encode_krb5_sam_response
+ (const krb5_sam_response * , krb5_data **);
+
+krb5_error_code encode_krb5_sam_challenge_2
+ (const krb5_sam_challenge_2 * , krb5_data **);
+
+krb5_error_code encode_krb5_sam_challenge_2_body
+ (const krb5_sam_challenge_2_body * , krb5_data **);
+
+krb5_error_code encode_krb5_enc_sam_response_enc_2
+ (const krb5_enc_sam_response_enc_2 * , krb5_data **);
+
+krb5_error_code encode_krb5_sam_response_2
+ (const krb5_sam_response_2 * , krb5_data **);
+
+krb5_error_code encode_krb5_predicted_sam_response
+ (const krb5_predicted_sam_response * , krb5_data **);
+
+krb5_error_code encode_krb5_sam_challenge
+       (const krb5_sam_challenge * , krb5_data **);
+
+krb5_error_code encode_krb5_sam_key
+       (const krb5_sam_key * , krb5_data **);
+
+krb5_error_code encode_krb5_enc_sam_response_enc
+       (const krb5_enc_sam_response_enc * , krb5_data **);
+
+krb5_error_code encode_krb5_sam_response
+       (const krb5_sam_response * , krb5_data **);
+
+krb5_error_code encode_krb5_predicted_sam_response
+       (const krb5_predicted_sam_response * , krb5_data **);
+
+krb5_error_code encode_krb5_setpw_req
+(const krb5_principal target, char *password, krb5_data **code);
+
+
+
+
+
+krb5_error_code decode_krb5_sam_challenge
+       (const krb5_data *, krb5_sam_challenge **);
+
+krb5_error_code decode_krb5_enc_sam_key
+       (const krb5_data *, krb5_sam_key **);
+
+krb5_error_code decode_krb5_enc_sam_response_enc
+       (const krb5_data *, krb5_enc_sam_response_enc **);
+
+krb5_error_code decode_krb5_sam_response
+       (const krb5_data *, krb5_sam_response **);
+
+krb5_error_code decode_krb5_predicted_sam_response
+       (const krb5_data *, krb5_predicted_sam_response **);
+
+krb5_error_code decode_krb5_sam_challenge_2
+ (const krb5_data *, krb5_sam_challenge_2 **);
+
+krb5_error_code decode_krb5_sam_challenge_2_body
+ (const krb5_data *, krb5_sam_challenge_2_body **);
+
+krb5_error_code decode_krb5_enc_sam_response_enc_2
+ (const krb5_data *, krb5_enc_sam_response_enc_2 **);
+
+krb5_error_code decode_krb5_sam_response_2
+ (const krb5_data *, krb5_sam_response_2 **);
+
+
+
+
+
+
+krb5_error_code krb5_validate_times
+       (krb5_context,
+         krb5_ticket_times *);
+krb5_error_code decode_krb5_authenticator
+ (const krb5_data *code, krb5_authenticator **rep);
+
+krb5_error_code decode_krb5_ticket
+ (const krb5_data *code, krb5_ticket **rep);
+
+krb5_error_code decode_krb5_encryption_key
+ (const krb5_data *output, krb5_keyblock **rep);
+
+krb5_error_code decode_krb5_enc_tkt_part
+ (const krb5_data *output, krb5_enc_tkt_part **rep);
+
+krb5_error_code decode_krb5_enc_kdc_rep_part
+ (const krb5_data *output, krb5_enc_kdc_rep_part **rep);
+
+krb5_error_code decode_krb5_as_rep
+ (const krb5_data *output, krb5_kdc_rep **rep);
+
+krb5_error_code decode_krb5_tgs_rep
+ (const krb5_data *output, krb5_kdc_rep **rep);
+
+krb5_error_code decode_krb5_ap_req
+ (const krb5_data *output, krb5_ap_req **rep);
+
+krb5_error_code decode_krb5_ap_rep
+ (const krb5_data *output, krb5_ap_rep **rep);
+
+krb5_error_code decode_krb5_ap_rep_enc_part
+ (const krb5_data *output, krb5_ap_rep_enc_part **rep);
+
+krb5_error_code decode_krb5_as_req
+ (const krb5_data *output, krb5_kdc_req **rep);
+
+krb5_error_code decode_krb5_tgs_req
+ (const krb5_data *output, krb5_kdc_req **rep);
+
+krb5_error_code decode_krb5_kdc_req_body
+ (const krb5_data *output, krb5_kdc_req **rep);
+
+krb5_error_code decode_krb5_safe
+ (const krb5_data *output, krb5_safe **rep);
+
+krb5_error_code decode_krb5_safe_with_body
+ (const krb5_data *output, krb5_safe **rep, krb5_data *body);
+
+krb5_error_code decode_krb5_priv
+ (const krb5_data *output, krb5_priv **rep);
+
+krb5_error_code decode_krb5_enc_priv_part
+ (const krb5_data *output, krb5_priv_enc_part **rep);
+
+krb5_error_code decode_krb5_cred
+ (const krb5_data *output, krb5_cred **rep);
+
+krb5_error_code decode_krb5_enc_cred_part
+ (const krb5_data *output, krb5_cred_enc_part **rep);
+
+krb5_error_code decode_krb5_error
+ (const krb5_data *output, krb5_error **rep);
+
+krb5_error_code decode_krb5_authdata
+ (const krb5_data *output, krb5_authdata ***rep);
+
+krb5_error_code decode_krb5_pwd_sequence
+ (const krb5_data *output, passwd_phrase_element **rep);
+
+krb5_error_code decode_krb5_pwd_data
+ (const krb5_data *output, krb5_pwd_data **rep);
+
+krb5_error_code decode_krb5_padata_sequence
+ (const krb5_data *output, krb5_pa_data ***rep);
+
+krb5_error_code decode_krb5_alt_method
+ (const krb5_data *output, krb5_alt_method **rep);
+
+krb5_error_code decode_krb5_etype_info
+ (const krb5_data *output, krb5_etype_info_entry ***rep);
+
+krb5_error_code decode_krb5_etype_info2
+ (const krb5_data *output, krb5_etype_info_entry ***rep);
+
+krb5_error_code decode_krb5_enc_data
+ (const krb5_data *output, krb5_enc_data **rep);
+
+krb5_error_code decode_krb5_pa_enc_ts
+ (const krb5_data *output, krb5_pa_enc_ts **rep);
+
+krb5_error_code decode_krb5_sam_challenge
+ (const krb5_data *, krb5_sam_challenge **);
+
+krb5_error_code decode_krb5_sam_key
+ (const krb5_data *, krb5_sam_key **);
+
+krb5_error_code decode_krb5_enc_sam_response_enc
+ (const krb5_data *, krb5_enc_sam_response_enc **);
+
+krb5_error_code decode_krb5_sam_response
+ (const krb5_data *, krb5_sam_response **);
+
+krb5_error_code decode_krb5_predicted_sam_response
+ (const krb5_data *, krb5_predicted_sam_response **);
+krb5_error_code krb5_encrypt_tkt_part
+ (krb5_context,
+  const krb5_keyblock *,
+  krb5_ticket * );
+
+
+krb5_error_code krb5_encode_kdc_rep
+ (krb5_context,
+  const krb5_msgtype,
+  const krb5_enc_kdc_rep_part *,
+  int using_subkey,
+  const krb5_keyblock *,
+  krb5_kdc_rep *,
+  krb5_data ** );
+
+krb5_error_code krb5_validate_times
+ (krb5_context,
+  krb5_ticket_times *);
+krb5_boolean krb5int_auth_con_chkseqnum
+ (krb5_context ctx, krb5_auth_context ac, krb5_ui_4 in_seq);
+
+
+
+struct __krb5_serializer {
+    krb5_magic odtype;
+    krb5_error_code (*sizer) (krb5_context,
+        krb5_pointer,
+        size_t *);
+    krb5_error_code (*externalizer) (krb5_context,
+        krb5_pointer,
+        krb5_octet **,
+        size_t *);
+    krb5_error_code (*internalizer) (krb5_context,
+        krb5_pointer *,
+        krb5_octet **,
+        size_t *);
+};
+typedef const struct __krb5_serializer * krb5_ser_handle;
+typedef struct __krb5_serializer krb5_ser_entry;
+
+krb5_ser_handle krb5_find_serializer
+ (krb5_context,
+  krb5_magic);
+krb5_error_code krb5_register_serializer
+ (krb5_context,
+   const krb5_ser_entry *);
+
+
+krb5_error_code krb5_size_opaque
+ (krb5_context,
+  krb5_magic,
+  krb5_pointer,
+  size_t *);
+
+
+krb5_error_code krb5_externalize_opaque
+ (krb5_context,
+  krb5_magic,
+  krb5_pointer,
+  krb5_octet **,
+  size_t *);
+
+
+krb5_error_code krb5_internalize_opaque
+ (krb5_context,
+  krb5_magic,
+  krb5_pointer *,
+  krb5_octet **,
+  size_t *);
+
+
+krb5_error_code krb5_externalize_data
+ (krb5_context,
+  krb5_pointer,
+  krb5_octet **,
+  size_t *);
+
+
+
+
+
+krb5_error_code krb5_ser_context_init
+ (krb5_context);
+
+
+krb5_error_code krb5_ser_auth_context_init
+ (krb5_context);
+
+
+krb5_error_code krb5_ser_keytab_init
+ (krb5_context);
+
+
+krb5_error_code krb5_ser_ccache_init
+ (krb5_context);
+
+
+krb5_error_code krb5_ser_rcache_init
+ (krb5_context);
+
+
+krb5_error_code krb5_ser_pack_int32
+ (krb5_int32,
+  krb5_octet **,
+  size_t *);
+krb5_error_code krb5_ser_unpack_int32
+ (krb5_int32 *,
+  krb5_octet **,
+  size_t *);
+
+krb5_error_code krb5_ser_pack_int64
+ (krb5_int64, krb5_octet **, size_t *);
+krb5_error_code krb5_ser_unpack_int64
+ (krb5_int64 *, krb5_octet **, size_t *);
+
+krb5_error_code krb5_ser_pack_bytes
+ (krb5_octet *,
+  size_t,
+  krb5_octet **,
+  size_t *);
+krb5_error_code krb5_ser_unpack_bytes
+ (krb5_octet *,
+  size_t,
+  krb5_octet **,
+  size_t *);
+
+krb5_error_code krb5int_cc_default
+ (krb5_context, krb5_ccache *);
+
+krb5_error_code krb5_cc_retrieve_cred_default
+ (krb5_context, krb5_ccache, krb5_flags,
+   krb5_creds *, krb5_creds *);
+
+void krb5int_set_prompt_types
+ (krb5_context, krb5_prompt_type *);
+
+krb5_error_code
+krb5int_generate_and_save_subkey (krb5_context, krb5_auth_context,
+      krb5_keyblock * );
+
+
+
+krb5_error_code krb5int_mk_chpw_req
+ (krb5_context context, krb5_auth_context auth_context,
+    krb5_data *ap_req, char *passwd, krb5_data *packet);
+krb5_error_code krb5int_rd_chpw_rep
+ (krb5_context context, krb5_auth_context auth_context,
+         krb5_data *packet, int *result_code,
+         krb5_data *result_data);
+krb5_error_code krb5_chpw_result_code_string
+ (krb5_context context, int result_code,
+   char **result_codestr);
+krb5_error_code krb5int_mk_setpw_req
+ (krb5_context context, krb5_auth_context auth_context,
+    krb5_data *ap_req, krb5_principal targetprinc, char *passwd, krb5_data *packet);
+krb5_error_code krb5int_rd_setpw_rep
+ (krb5_context context, krb5_auth_context auth_context,
+         krb5_data *packet, int *result_code,
+         krb5_data *result_data);
+krb5_error_code krb5int_setpw_result_code_string
+ (krb5_context context, int result_code,
+   const char **result_codestr);
+
+struct srv_dns_entry {
+    struct srv_dns_entry *next;
+    int priority;
+    int weight;
+    unsigned short port;
+    char *host;
+};
+krb5_error_code
+krb5int_make_srv_query_realm(const krb5_data *realm,
+        const char *service,
+        const char *protocol,
+        struct srv_dns_entry **answers);
+void krb5int_free_srv_dns_data(struct srv_dns_entry *);
+struct ktext;
+
+typedef struct _krb5int_access {
+
+    const struct krb5_hash_provider *md5_hash_provider;
+    const struct krb5_enc_provider *arcfour_enc_provider;
+    krb5_error_code (* krb5_hmac) (const struct krb5_hash_provider *hash,
+       const krb5_keyblock *key,
+       unsigned int icount, const krb5_data *input,
+       krb5_data *output);
+
+    krb5_error_code (*locate_server) (krb5_context, const krb5_data *,
+          struct addrlist *, int,
+          const char *, const char *,
+          int, int, int, int);
+    krb5_error_code (*sendto_udp) (krb5_context, const krb5_data *msg,
+       const struct addrlist *, krb5_data *reply,
+       struct sockaddr *, socklen_t *, int *);
+    krb5_error_code (*add_host_to_list)(struct addrlist *lp,
+     const char *hostname,
+     int port, int secport,
+     int socktype, int family);
+    void (*free_addrlist) (struct addrlist *);
+
+    krb5_error_code (*make_srv_query_realm)(const krb5_data *realm,
+         const char *service,
+         const char *protocol,
+         struct srv_dns_entry **answers);
+    void (*free_srv_dns_data)(struct srv_dns_entry *);
+    int (*use_dns_kdc)(krb5_context);
+
+
+    krb5_int32 (*krb_life_to_time)(krb5_int32, int);
+    int (*krb_time_to_life)(krb5_int32, krb5_int32);
+    int (*krb524_encode_v4tkt)(struct ktext *, char *, unsigned int *);
+    krb5_error_code (*krb5int_c_mandatory_cksumtype)
+        (krb5_context, krb5_enctype, krb5_cksumtype *);
+    krb5_error_code ( *krb5_ser_pack_int64)
+        (krb5_int64, krb5_octet **, size_t *);
+    krb5_error_code ( *krb5_ser_unpack_int64)
+        (krb5_int64 *, krb5_octet **, size_t *);
+} krb5int_access;
+
+
+
+
+
+krb5_error_code krb5int_accessor
+ (krb5int_access*, krb5_int32);
+extern krb5_int32 krb5int_krb_life_to_time(krb5_int32, int);
+extern int krb5int_krb_time_to_life(krb5_int32, krb5_int32);
+
+
+int krb5int_encode_v4tkt
+ (struct ktext *v4tkt, char *buf, unsigned int *encoded_len);
+
+
+int krb5int_524_sendto_kdc
+        (krb5_context context, const krb5_data * message,
+  const krb5_data * realm, krb5_data * reply,
+  struct sockaddr *, socklen_t *);
+
+
+
+struct _krb5_ccache {
+    krb5_magic magic;
+    const struct _krb5_cc_ops *ops;
+    krb5_pointer data;
+};
+
+struct _krb5_cc_ops {
+    krb5_magic magic;
+    char *prefix;
+    const char * ( *get_name) (krb5_context, krb5_ccache);
+    krb5_error_code ( *resolve) (krb5_context, krb5_ccache *,
+         const char *);
+    krb5_error_code ( *gen_new) (krb5_context, krb5_ccache *);
+    krb5_error_code ( *init) (krb5_context, krb5_ccache,
+         krb5_principal);
+    krb5_error_code ( *destroy) (krb5_context, krb5_ccache);
+    krb5_error_code ( *close) (krb5_context, krb5_ccache);
+    krb5_error_code ( *store) (krb5_context, krb5_ccache,
+         krb5_creds *);
+    krb5_error_code ( *retrieve) (krb5_context, krb5_ccache,
+         krb5_flags, krb5_creds *,
+         krb5_creds *);
+    krb5_error_code ( *get_princ) (krb5_context, krb5_ccache,
+         krb5_principal *);
+    krb5_error_code ( *get_first) (krb5_context, krb5_ccache,
+         krb5_cc_cursor *);
+    krb5_error_code ( *get_next) (krb5_context, krb5_ccache,
+         krb5_cc_cursor *, krb5_creds *);
+    krb5_error_code ( *end_get) (krb5_context, krb5_ccache,
+         krb5_cc_cursor *);
+    krb5_error_code ( *remove_cred) (krb5_context, krb5_ccache,
+         krb5_flags, krb5_creds *);
+    krb5_error_code ( *set_flags) (krb5_context, krb5_ccache,
+         krb5_flags);
+};
+
+extern const krb5_cc_ops *krb5_cc_dfl_ops;
+
+typedef struct _krb5_donot_replay {
+    krb5_magic magic;
+    krb5_ui_4 hash;
+    char *server;
+    char *client;
+    krb5_int32 cusec;
+    krb5_timestamp ctime;
+} krb5_donot_replay;
+
+krb5_error_code krb5_rc_default
+ (krb5_context,
+  krb5_rcache *);
+krb5_error_code krb5_rc_resolve_type
+ (krb5_context,
+  krb5_rcache *,char *);
+krb5_error_code krb5_rc_resolve_full
+ (krb5_context,
+  krb5_rcache *,char *);
+char * krb5_rc_get_type
+ (krb5_context,
+  krb5_rcache);
+char * krb5_rc_default_type
+ (krb5_context);
+char * krb5_rc_default_name
+ (krb5_context);
+krb5_error_code krb5_auth_to_rep
+ (krb5_context,
+  krb5_tkt_authent *,
+  krb5_donot_replay *);
+
+
+krb5_error_code krb5_rc_initialize
+ (krb5_context, krb5_rcache,krb5_deltat);
+krb5_error_code krb5_rc_recover_or_initialize
+ (krb5_context, krb5_rcache,krb5_deltat);
+krb5_error_code krb5_rc_recover
+ (krb5_context, krb5_rcache);
+krb5_error_code krb5_rc_destroy
+ (krb5_context, krb5_rcache);
+krb5_error_code krb5_rc_close
+ (krb5_context, krb5_rcache);
+krb5_error_code krb5_rc_store
+ (krb5_context, krb5_rcache,krb5_donot_replay *);
+krb5_error_code krb5_rc_expunge
+ (krb5_context, krb5_rcache);
+krb5_error_code krb5_rc_get_lifespan
+ (krb5_context, krb5_rcache,krb5_deltat *);
+char * krb5_rc_get_name
+ (krb5_context, krb5_rcache);
+krb5_error_code krb5_rc_resolve
+ (krb5_context, krb5_rcache, char *);
+
+typedef struct _krb5_kt_ops {
+    krb5_magic magic;
+    char *prefix;
+
+    krb5_error_code ( *resolve)
+ (krb5_context,
+   const char *,
+   krb5_keytab *);
+    krb5_error_code ( *get_name)
+ (krb5_context,
+   krb5_keytab,
+   char *,
+   unsigned int);
+    krb5_error_code ( *close)
+ (krb5_context,
+   krb5_keytab);
+    krb5_error_code ( *get)
+ (krb5_context,
+   krb5_keytab,
+   krb5_const_principal,
+   krb5_kvno,
+   krb5_enctype,
+   krb5_keytab_entry *);
+    krb5_error_code ( *start_seq_get)
+ (krb5_context,
+   krb5_keytab,
+   krb5_kt_cursor *);
+    krb5_error_code ( *get_next)
+ (krb5_context,
+   krb5_keytab,
+   krb5_keytab_entry *,
+   krb5_kt_cursor *);
+    krb5_error_code ( *end_get)
+ (krb5_context,
+   krb5_keytab,
+   krb5_kt_cursor *);
+
+    krb5_error_code ( *add)
+ (krb5_context,
+   krb5_keytab,
+   krb5_keytab_entry *);
+    krb5_error_code ( *remove)
+ (krb5_context,
+   krb5_keytab,
+    krb5_keytab_entry *);
+
+
+    const krb5_ser_entry *serializer;
+} krb5_kt_ops;
+
+extern const krb5_kt_ops krb5_kt_dfl_ops;
+
+extern krb5_error_code krb5int_translate_gai_error (int);
+
+
+extern krb5_error_code
+krb5int_c_mandatory_cksumtype (krb5_context, krb5_enctype, krb5_cksumtype *);
+
+extern int krb5int_crypto_init (void);
+extern int krb5int_prng_init(void);
+
+
+
+
+struct _krb5_auth_context {
+    krb5_magic magic;
+    krb5_address * remote_addr;
+    krb5_address * remote_port;
+    krb5_address * local_addr;
+    krb5_address * local_port;
+    krb5_keyblock * keyblock;
+    krb5_keyblock * send_subkey;
+    krb5_keyblock * recv_subkey;
+
+    krb5_int32 auth_context_flags;
+    krb5_ui_4 remote_seq_number;
+    krb5_ui_4 local_seq_number;
+    krb5_authenticator *authentp;
+    krb5_cksumtype req_cksumtype;
+    krb5_cksumtype safe_cksumtype;
+    krb5_pointer i_vector;
+    krb5_rcache rcache;
+    krb5_enctype * permitted_etypes;
+  krb5_mk_req_checksum_func checksum_func;
+  void *checksum_func_data;
+};
+
+
+
+
+static const char sendauth_version[] = "KRB5_SENDAUTH_V1.0";
+
+static krb5_error_code
+recvauth_common(krb5_context context,
+  krb5_auth_context * auth_context,
+
+  krb5_pointer fd,
+  char *appl_version,
+  krb5_principal server,
+  krb5_int32 flags,
+  krb5_keytab keytab,
+
+  krb5_ticket ** ticket,
+  krb5_data *version)
+{
+    krb5_auth_context new_auth_context;
+    krb5_flags ap_option;
+    krb5_error_code retval, problem;
+    krb5_data inbuf;
+    krb5_data outbuf;
+    krb5_rcache rcache = 0;
+    krb5_octet response;
+    krb5_data null_server;
+    int need_error_free = 0;
+    int local_rcache = 0, local_authcon = 0;
+
+
+
+
+
+
+
+ problem = 0;
+
+ if (!(flags & 0x0001)) {
+
+
+
+     if ((retval = krb5_read_message(context, fd, &inbuf)))
+  return(retval);
+     if (
+        __extension__ ({ size_t __s1_len, __s2_len; (__builtin_constant_p (
+        inbuf.data
+        ) && __builtin_constant_p (
+        sendauth_version
+        ) && (__s1_len = strlen (
+        inbuf.data
+        ), __s2_len = strlen (
+        sendauth_version
+        ), (!((size_t)(const void *)((
+        inbuf.data
+        ) + 1) - (size_t)(const void *)(
+        inbuf.data
+        ) == 1) || __s1_len >= 4) && (!((size_t)(const void *)((
+        sendauth_version
+        ) + 1) - (size_t)(const void *)(
+        sendauth_version
+        ) == 1) || __s2_len >= 4)) ? __builtin_strcmp (
+        inbuf.data
+        , 
+        sendauth_version
+        ) : (__builtin_constant_p (
+        inbuf.data
+        ) && ((size_t)(const void *)((
+        inbuf.data
+        ) + 1) - (size_t)(const void *)(
+        inbuf.data
+        ) == 1) && (__s1_len = strlen (
+        inbuf.data
+        ), __s1_len < 4) ? (__builtin_constant_p (
+        sendauth_version
+        ) && ((size_t)(const void *)((
+        sendauth_version
+        ) + 1) - (size_t)(const void *)(
+        sendauth_version
+        ) == 1) ? __builtin_strcmp (
+        inbuf.data
+        , 
+        sendauth_version
+        ) : (__extension__ ({ const unsigned char *__s2 = (const unsigned char *) (const char *) (
+        sendauth_version
+        ); int __result = (((const unsigned char *) (const char *) (
+        inbuf.data
+        ))[0] - __s2[0]); if (__s1_len > 0 && __result == 0) { __result = (((const unsigned char *) (const char *) (
+        inbuf.data
+        ))[1] - __s2[1]); if (__s1_len > 1 && __result == 0) { __result = (((const unsigned char *) (const char *) (
+        inbuf.data
+        ))[2] - __s2[2]); if (__s1_len > 2 && __result == 0) __result = (((const unsigned char *) (const char *) (
+        inbuf.data
+        ))[3] - __s2[3]); } } __result; }))) : (__builtin_constant_p (
+        sendauth_version
+        ) && ((size_t)(const void *)((
+        sendauth_version
+        ) + 1) - (size_t)(const void *)(
+        sendauth_version
+        ) == 1) && (__s2_len = strlen (
+        sendauth_version
+        ), __s2_len < 4) ? (__builtin_constant_p (
+        inbuf.data
+        ) && ((size_t)(const void *)((
+        inbuf.data
+        ) + 1) - (size_t)(const void *)(
+        inbuf.data
+        ) == 1) ? __builtin_strcmp (
+        inbuf.data
+        , 
+        sendauth_version
+        ) : (- (__extension__ ({ const unsigned char *__s2 = (const unsigned char *) (const char *) (
+        inbuf.data
+        ); int __result = (((const unsigned char *) (const char *) (
+        sendauth_version
+        ))[0] - __s2[0]); if (__s2_len > 0 && __result == 0) { __result = (((const unsigned char *) (const char *) (
+        sendauth_version
+        ))[1] - __s2[1]); if (__s2_len > 1 && __result == 0) { __result = (((const unsigned char *) (const char *) (
+        sendauth_version
+        ))[2] - __s2[2]); if (__s2_len > 2 && __result == 0) __result = (((const unsigned char *) (const char *) (
+        sendauth_version
+        ))[3] - __s2[3]); } } __result; })))) : __builtin_strcmp (
+        inbuf.data
+        , 
+        sendauth_version
+        )))); })
+                                            ) {
+  free((char *)(inbuf.data));
+  problem = (-1765328180L);
+     }
+     free((char *)(inbuf.data));
+ }
+ if (flags & 0x0002)
+     problem = (-1765328180L);
+
+
+
+
+ if ((retval = krb5_read_message(context, fd, &inbuf)))
+  return(retval);
+ if (appl_version && 
+                    __extension__ ({ size_t __s1_len, __s2_len; (__builtin_constant_p (
+                    inbuf.data
+                    ) && __builtin_constant_p (
+                    appl_version
+                    ) && (__s1_len = strlen (
+                    inbuf.data
+                    ), __s2_len = strlen (
+                    appl_version
+                    ), (!((size_t)(const void *)((
+                    inbuf.data
+                    ) + 1) - (size_t)(const void *)(
+                    inbuf.data
+                    ) == 1) || __s1_len >= 4) && (!((size_t)(const void *)((
+                    appl_version
+                    ) + 1) - (size_t)(const void *)(
+                    appl_version
+                    ) == 1) || __s2_len >= 4)) ? __builtin_strcmp (
+                    inbuf.data
+                    , 
+                    appl_version
+                    ) : (__builtin_constant_p (
+                    inbuf.data
+                    ) && ((size_t)(const void *)((
+                    inbuf.data
+                    ) + 1) - (size_t)(const void *)(
+                    inbuf.data
+                    ) == 1) && (__s1_len = strlen (
+                    inbuf.data
+                    ), __s1_len < 4) ? (__builtin_constant_p (
+                    appl_version
+                    ) && ((size_t)(const void *)((
+                    appl_version
+                    ) + 1) - (size_t)(const void *)(
+                    appl_version
+                    ) == 1) ? __builtin_strcmp (
+                    inbuf.data
+                    , 
+                    appl_version
+                    ) : (__extension__ ({ const unsigned char *__s2 = (const unsigned char *) (const char *) (
+                    appl_version
+                    ); int __result = (((const unsigned char *) (const char *) (
+                    inbuf.data
+                    ))[0] - __s2[0]); if (__s1_len > 0 && __result == 0) { __result = (((const unsigned char *) (const char *) (
+                    inbuf.data
+                    ))[1] - __s2[1]); if (__s1_len > 1 && __result == 0) { __result = (((const unsigned char *) (const char *) (
+                    inbuf.data
+                    ))[2] - __s2[2]); if (__s1_len > 2 && __result == 0) __result = (((const unsigned char *) (const char *) (
+                    inbuf.data
+                    ))[3] - __s2[3]); } } __result; }))) : (__builtin_constant_p (
+                    appl_version
+                    ) && ((size_t)(const void *)((
+                    appl_version
+                    ) + 1) - (size_t)(const void *)(
+                    appl_version
+                    ) == 1) && (__s2_len = strlen (
+                    appl_version
+                    ), __s2_len < 4) ? (__builtin_constant_p (
+                    inbuf.data
+                    ) && ((size_t)(const void *)((
+                    inbuf.data
+                    ) + 1) - (size_t)(const void *)(
+                    inbuf.data
+                    ) == 1) ? __builtin_strcmp (
+                    inbuf.data
+                    , 
+                    appl_version
+                    ) : (- (__extension__ ({ const unsigned char *__s2 = (const unsigned char *) (const char *) (
+                    inbuf.data
+                    ); int __result = (((const unsigned char *) (const char *) (
+                    appl_version
+                    ))[0] - __s2[0]); if (__s2_len > 0 && __result == 0) { __result = (((const unsigned char *) (const char *) (
+                    appl_version
+                    ))[1] - __s2[1]); if (__s2_len > 1 && __result == 0) { __result = (((const unsigned char *) (const char *) (
+                    appl_version
+                    ))[2] - __s2[2]); if (__s2_len > 2 && __result == 0) __result = (((const unsigned char *) (const char *) (
+                    appl_version
+                    ))[3] - __s2[3]); } } __result; })))) : __builtin_strcmp (
+                    inbuf.data
+                    , 
+                    appl_version
+                    )))); })
+                                                    ) {
+  free((char *)(inbuf.data));
+  if (!problem)
+   problem = (-1765328179L);
+ }
+ if (version && !problem)
+     *version = inbuf;
+ else
+     free((char *)(inbuf.data));
+
+
+
+
+
+ switch (problem) {
+ case 0:
+  response = 0;
+  break;
+ case (-1765328180L):
+  response = 1;
+  break;
+ case (-1765328179L):
+  response = 2;
+  break;
+ default:
+
+
+
+  response = 255;
+
+
+
+
+
+  break;
+ }
+
+
+
+
+ if ((krb5_net_write(context, *((int *)fd), (char *)&response, 1)) < 0) {
+  return(problem);
+ }
+ if (problem)
+     return(problem);
+
+
+
+
+
+
+    if ((retval = krb5_read_message(context, fd, &inbuf)))
+        return retval;
+
+    if (*auth_context == 
+                        ((void *)0)
+                            ) {
+ problem = krb5_auth_con_init(context, &new_auth_context);
+ *auth_context = new_auth_context;
+ local_authcon = 1;
+    }
+    krb5_auth_con_getrcache(context, *auth_context, &rcache);
+    if ((!problem) && rcache == 
+                               ((void *)0)
+                                   ) {
+
+
+
+        if (server) {
+            problem = krb5_get_server_rcache(context,
+   (((0) < (server)->length) ? (server)->data + (0) : 
+  ((void *)0)
+  ), &rcache);
+        } else {
+         null_server.length = 7;
+         null_server.data = "default";
+         problem = krb5_get_server_rcache(context, &null_server, &rcache);
+        }
+        if (!problem)
+     problem = krb5_auth_con_setrcache(context, *auth_context, rcache);
+ local_rcache = 1;
+    }
+    if (!problem) {
+ problem = krb5_rd_req(context, auth_context, &inbuf, server,
+         keytab, &ap_option, ticket);
+ free((char *)(inbuf.data)); /* { dg-message "double-free of 'inbuf.data'" } */
+    }
+
+
+
+
+
+
+    if (problem) {
+ krb5_error error;
+ const char *message;
+
+ memset((char *)&error, 0, sizeof(error));
+ krb5_us_timeofday(context, &error.stime, &error.susec);
+ if(server)
+  error.server = server;
+ else {
+
+
+  (void) krb5_parse_name(context, "????", &error.server);
+  need_error_free = 1;
+ }
+
+ error.error = problem - (-1765328384L);
+ if (error.error > 127)
+  error.error = 60;
+ message = error_message(problem);
+ error.text.length = strlen(message) + 1;
+ if (!(error.text.data = malloc(error.text.length))) {
+     retval = 
+             12
+                   ;
+     goto cleanup;
+ }
+ strcpy(error.text.data, message);
+ if ((retval = krb5_mk_error(context, &error, &outbuf))) {
+     free(error.text.data);
+     goto cleanup;
+ }
+ free(error.text.data);
+ if(need_error_free)
+  krb5_free_principal(context, error.server);
+
+    } else {
+ outbuf.length = 0;
+ outbuf.data = 0;
+    }
+
+    retval = krb5_write_message(context, fd, &outbuf);
+    if (outbuf.data) {
+ free((char *)(outbuf.data));
+
+     retval = problem;
+     goto cleanup;
+    }
+    if (retval)
+ goto cleanup;
+
+
+    if ((ap_option & 0x20000000)) {
+ if ((retval = krb5_mk_rep(context, *auth_context, &outbuf))) {
+     return(retval);
+ }
+ retval = krb5_write_message(context, fd, &outbuf);
+ free((char *)(outbuf.data));
+    }
+
+cleanup:;
+    if (retval) {
+ if (local_authcon) {
+     krb5_auth_con_free(context, *auth_context);
+ } else if (local_rcache && rcache != 
+                                     ((void *)0)
+                                         ) {
+     krb5_rc_close(context, rcache);
+     krb5_auth_con_setrcache(context, *auth_context, 
+                                                    ((void *)0)
+                                                        );
+ }
+    }
+    return retval;
+}
+
+krb5_error_code
+krb5_recvauth(krb5_context context, krb5_auth_context *auth_context, krb5_pointer fd, char *appl_version, krb5_principal server, krb5_int32 flags, krb5_keytab keytab, krb5_ticket **ticket)
+{
+    return recvauth_common (context, auth_context, fd, appl_version,
+       server, flags, keytab, ticket, 0);
+}
+
+krb5_error_code
+krb5_recvauth_version(krb5_context context,
+        krb5_auth_context *auth_context,
+
+        krb5_pointer fd,
+        krb5_principal server,
+        krb5_int32 flags,
+        krb5_keytab keytab,
+
+        krb5_ticket **ticket,
+        krb5_data *version)
+{
+    return recvauth_common (context, auth_context, fd, 0,
+       server, flags, keytab, ticket, version);
+}
+
+/* { dg-begin-multiline-output "" }
+  'krb5_recvauth': events 1-2
+         |
+         |   NN | krb5_recvauth(krb5_context context, krb5_auth_context *auth_context, krb5_pointer fd, char *appl_version, krb5_principal server, krb5_int32 flags, krb5_keytab keytab, krb5_ticket **ticket)
+         |      | ^~~~~~~~~~~~~
+         |      | |
+         |      | (1) entry to 'krb5_recvauth'
+         |   NN | {
+         |   NN |     return recvauth_common (context, auth_context, fd, appl_version,
+         |      |            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+         |      |            |
+         |      |            (2) calling 'recvauth_common' from 'krb5_recvauth'
+         |   NN |        server, flags, keytab, ticket, 0);
+         |      |        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+         |
+         +--> 'recvauth_common': events 3-20
+                      |
+                      |   NN | recvauth_common(krb5_context context,
+                      |      | ^~~~~~~~~~~~~~~
+                      |      | |
+                      |      | (3) entry to 'recvauth_common'
+                      |......
+                      |   NN |  if (!(flags & 0x0001)) {
+                      |      |     ~
+                      |      |     |
+                      |      |     (4) following 'false' branch
+                      |......
+                      |   NN |  if (flags & 0x0002)
+                      |      |     ~
+                      |      |     |
+                      |      |     (5) following 'false' branch
+                      |......
+                      |   NN |  if ((retval = krb5_read_message(context, fd, &inbuf)))
+                      |      |     ~
+                      |      |     |
+                      |      |     (6) following 'false' branch
+                      |   NN |   return(retval);
+                      |   NN |  if (appl_version &&
+                      |      |     ~
+                      |      |     |
+                      |      |     (7) following 'false' branch
+                      |......
+                      |   NN |  if (version && !problem)
+                      |      |     ~
+                      |      |     |
+                      |      |     (8) following 'false' branch
+                      |......
+                      |   NN |      free((char *)(inbuf.data));
+                      |      |      ~~~~~~~~~~~~~~~~~~~~~~~~~~
+                      |      |      |
+                      |      |      (9) first 'free' here
+                      |......
+                      |   NN |  switch (problem) {
+                      |      |  ~~~~~~
+                      |      |  |
+                      |      |  (10) following 'case 0:' branch
+                      |......
+                      |   NN |  if ((krb5_net_write(context, *((int *)fd), (char *)&response, 1)) < 0) {
+                      |      |     ~
+                      |      |     |
+                      |      |     (11) following 'false' branch
+                      |......
+                      |   NN |  if (problem)
+                      |      |     ~
+                      |      |     |
+                      |      |     (12) following 'false' branch
+                      |......
+                      |   NN |     if ((retval = krb5_read_message(context, fd, &inbuf)))
+                      |      |        ~
+                      |      |        |
+                      |      |        (13) following 'false' branch
+                      |......
+                      |   NN |     if (*auth_context ==
+                      |      |        ~
+                      |      |        |
+                      |      |        (14) following 'false' branch
+                      |......
+                      |   NN |     if ((!problem) && rcache ==
+                      |      |        ~~~~~~~~~~~~~~~~~~~~~~~~
+                      |      |        |           |
+                      |      |        |           (16) following 'true' branch
+                      |      |        (15) following 'true' branch
+                      |   NN |                                ((void *)0)
+                      |      |                                ~~~~~~~~~~~
+                      |......
+                      |   NN |         if (server) {
+                      |      |            ~
+                      |      |            |
+                      |      |            (17) following 'false' branch
+                      |......
+                      |   NN |         if (!problem)
+                      |      |            ~
+                      |      |            |
+                      |      |            (18) following 'true' branch
+                      |......
+                      |   NN |     if (!problem) {
+                      |      |        ~
+                      |      |        |
+                      |      |        (19) following 'true' branch
+                      |......
+                      |   NN |  free((char *)(inbuf.data));
+                      |      |  ~~~~~~~~~~~~~~~~~~~~~~~~~~
+                      |      |  |
+                      |      |  (20) second 'free' here; first 'free' was at (9)
+                      |
+  { dg-end-multiline-output "" } */
+
+/* { dg-regexp "\[^\n\r\]+: warning: analysis bailed out early \\(\[0-9\]+ 'after-snode' enodes; \[0-9\]+ enodes\\)" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-clang-paper-example-1.c b/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-clang-paper-example-1.c
new file mode 100644
index 0000000..77e4965
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-clang-paper-example-1.c
@@ -0,0 +1,10 @@
+void test_1 (void)
+{
+  int x, y;
+  int *p = &x;
+  x = 3;
+  *p = 4;
+  y = x;
+
+  __analyzer_eval (y == 4); /* { dg-warning "TRUE" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-clang-paper-example-2.c b/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-clang-paper-example-2.c
new file mode 100644
index 0000000..3622c4b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-clang-paper-example-2.c
@@ -0,0 +1,17 @@
+#include <stdlib.h>
+
+void test_2 (void)
+{
+  void *p = malloc (10);
+  char *buf1 = (char*)p;
+  buf1[0] = 'a'; /* { dg-warning "use of possibly-NULL 'buf1'" } */
+
+  int *buf2 = (int*)p;
+  buf2[0] = 0;
+
+  char c = buf1[0];
+
+  __analyzer_eval (c == 0); /* { dg-warning "TRUE" } */
+
+  free (p);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-clang-paper-example-3.c b/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-clang-paper-example-3.c
new file mode 100644
index 0000000..005ebbd
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-clang-paper-example-3.c
@@ -0,0 +1,22 @@
+#include <stdlib.h>
+
+struct s1
+{
+  int d;
+};
+struct s2
+{
+  struct s1 *p;
+};
+void test_3 (void)
+{
+  struct s1 data;
+  struct s2 *sp;
+  int a[2];
+  sp = malloc(sizeof(struct s2)); /* { dg-message "allocated here" } */
+  sp->p = &data; /* { dg-warning "use of possibly-NULL 'sp'" } */
+  sp->p->d = 3;
+  a[1] = data.d;
+  __analyzer_eval (a[1] == 3); /* { dg-warning "TRUE" } */
+
+} /* { dg-warning "leak of 'sp'" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-clang-paper-example-4.c b/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-clang-paper-example-4.c
new file mode 100644
index 0000000..12be723
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-clang-paper-example-4.c
@@ -0,0 +1,29 @@
+#include <stddef.h>
+
+struct s { int data[2]; };
+void test_4 (struct s buf)
+{
+  int i = 3, *q = NULL;
+  struct s* p;
+  if (buf.data[1] == 1)
+    q = &i;
+  p = &buf;
+  if (p->data[1] == 1)
+    p->data[0] = *q; /* { dg-bogus "use of NULL 'q'" } */
+  return;
+}
+
+/* To avoid a false positive read-through-uninit at *q, we need to handle
+   phi nodes, and to enforce constraints.
+
+   region_model's constraint model is now good enough to rule out
+   the path as infeasible.
+
+   Looks like at the first:
+
+     if (buf.data[1] == 1)
+
+   we have an unknown value for buf.data[1], but it's the unknown
+   value for "buf" as a whole; we should have created unknown values
+   for the child (or for accesses to the parent unknown value).  */
+
diff --git a/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-zlib-1.1.3-deflate.c b/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-zlib-1.1.3-deflate.c
new file mode 100644
index 0000000..c391341
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-zlib-1.1.3-deflate.c
@@ -0,0 +1,4047 @@
+typedef unsigned char Byte;
+
+typedef unsigned int uInt;
+typedef unsigned long uLong;
+
+
+
+
+
+   typedef Byte Bytef;
+
+typedef char charf;
+typedef int intf;
+typedef uInt uIntf;
+typedef uLong uLongf;
+
+
+   typedef void *voidpf;
+   typedef void *voidp;
+
+
+
+
+
+
+
+
+
+
+
+
+typedef unsigned char __u_char;
+typedef unsigned short int __u_short;
+typedef unsigned int __u_int;
+typedef unsigned long int __u_long;
+
+
+typedef signed char __int8_t;
+typedef unsigned char __uint8_t;
+typedef signed short int __int16_t;
+typedef unsigned short int __uint16_t;
+typedef signed int __int32_t;
+typedef unsigned int __uint32_t;
+
+typedef signed long int __int64_t;
+typedef unsigned long int __uint64_t;
+
+
+
+
+
+
+
+typedef long int __quad_t;
+typedef unsigned long int __u_quad_t;
+
+
+typedef unsigned long int __dev_t;
+typedef unsigned int __uid_t;
+typedef unsigned int __gid_t;
+typedef unsigned long int __ino_t;
+typedef unsigned long int __ino64_t;
+typedef unsigned int __mode_t;
+typedef unsigned long int __nlink_t;
+typedef long int __off_t;
+typedef long int __off64_t;
+typedef int __pid_t;
+typedef struct { int __val[2]; } __fsid_t;
+typedef long int __clock_t;
+typedef unsigned long int __rlim_t;
+typedef unsigned long int __rlim64_t;
+typedef unsigned int __id_t;
+typedef long int __time_t;
+typedef unsigned int __useconds_t;
+typedef long int __suseconds_t;
+
+typedef int __daddr_t;
+typedef int __key_t;
+
+
+typedef int __clockid_t;
+
+
+typedef void * __timer_t;
+
+
+typedef long int __blksize_t;
+
+
+
+
+typedef long int __blkcnt_t;
+typedef long int __blkcnt64_t;
+
+
+typedef unsigned long int __fsblkcnt_t;
+typedef unsigned long int __fsblkcnt64_t;
+
+
+typedef unsigned long int __fsfilcnt_t;
+typedef unsigned long int __fsfilcnt64_t;
+
+
+typedef long int __fsword_t;
+
+typedef long int __ssize_t;
+
+
+typedef long int __syscall_slong_t;
+
+typedef unsigned long int __syscall_ulong_t;
+
+
+
+typedef __off64_t __loff_t;
+typedef __quad_t *__qaddr_t;
+typedef char *__caddr_t;
+
+
+typedef long int __intptr_t;
+
+
+typedef unsigned int __socklen_t;
+
+
+
+typedef __u_char u_char;
+typedef __u_short u_short;
+typedef __u_int u_int;
+typedef __u_long u_long;
+typedef __quad_t quad_t;
+typedef __u_quad_t u_quad_t;
+typedef __fsid_t fsid_t;
+
+
+
+
+typedef __loff_t loff_t;
+
+
+
+typedef __ino_t ino_t;
+typedef __dev_t dev_t;
+
+
+
+
+typedef __gid_t gid_t;
+
+
+
+
+typedef __mode_t mode_t;
+
+
+
+
+typedef __nlink_t nlink_t;
+
+
+
+
+typedef __uid_t uid_t;
+
+
+
+
+
+typedef __off_t off_t;
+typedef __pid_t pid_t;
+
+
+
+
+
+typedef __id_t id_t;
+
+
+
+
+typedef __ssize_t ssize_t;
+
+
+
+
+
+typedef __daddr_t daddr_t;
+typedef __caddr_t caddr_t;
+
+
+
+
+
+typedef __key_t key_t;
+
+
+typedef __clock_t clock_t;
+
+
+
+
+
+typedef __time_t time_t;
+
+
+
+typedef __clockid_t clockid_t;
+typedef __timer_t timer_t;
+typedef long unsigned int size_t;
+
+
+
+typedef unsigned long int ulong;
+typedef unsigned short int ushort;
+typedef unsigned int uint;
+typedef int int8_t __attribute__ ((__mode__ (__QI__)));
+typedef int int16_t __attribute__ ((__mode__ (__HI__)));
+typedef int int32_t __attribute__ ((__mode__ (__SI__)));
+typedef int int64_t __attribute__ ((__mode__ (__DI__)));
+
+
+typedef unsigned int u_int8_t __attribute__ ((__mode__ (__QI__)));
+typedef unsigned int u_int16_t __attribute__ ((__mode__ (__HI__)));
+typedef unsigned int u_int32_t __attribute__ ((__mode__ (__SI__)));
+typedef unsigned int u_int64_t __attribute__ ((__mode__ (__DI__)));
+
+typedef int register_t __attribute__ ((__mode__ (__word__)));
+
+
+
+
+
+
+static __inline unsigned int
+__bswap_32 (unsigned int __bsx)
+{
+  return __builtin_bswap32 (__bsx);
+}
+static __inline __uint64_t
+__bswap_64 (__uint64_t __bsx)
+{
+  return __builtin_bswap64 (__bsx);
+}
+
+
+
+
+typedef int __sig_atomic_t;
+
+
+
+
+typedef struct
+  {
+    unsigned long int __val[(1024 / (8 * sizeof (unsigned long int)))];
+  } __sigset_t;
+
+
+
+typedef __sigset_t sigset_t;
+
+
+
+
+
+struct timespec
+  {
+    __time_t tv_sec;
+    __syscall_slong_t tv_nsec;
+  };
+
+struct timeval
+  {
+    __time_t tv_sec;
+    __suseconds_t tv_usec;
+  };
+
+
+typedef __suseconds_t suseconds_t;
+
+
+
+
+
+typedef long int __fd_mask;
+typedef struct
+  {
+
+
+
+
+
+
+    __fd_mask __fds_bits[1024 / (8 * (int) sizeof (__fd_mask))];
+
+
+  } fd_set;
+
+
+
+
+
+
+typedef __fd_mask fd_mask;
+
+extern int select (int __nfds, fd_set *__restrict __readfds,
+     fd_set *__restrict __writefds,
+     fd_set *__restrict __exceptfds,
+     struct timeval *__restrict __timeout);
+extern int pselect (int __nfds, fd_set *__restrict __readfds,
+      fd_set *__restrict __writefds,
+      fd_set *__restrict __exceptfds,
+      const struct timespec *__restrict __timeout,
+      const __sigset_t *__restrict __sigmask);
+
+
+
+
+
+__extension__
+extern unsigned int gnu_dev_major (unsigned long long int __dev)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__));
+__extension__
+extern unsigned int gnu_dev_minor (unsigned long long int __dev)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__));
+__extension__
+extern unsigned long long int gnu_dev_makedev (unsigned int __major,
+            unsigned int __minor)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__));
+
+
+__extension__ extern __inline __attribute__ ((__gnu_inline__)) __attribute__ ((__const__)) unsigned int
+__attribute__ ((__nothrow__ , __leaf__)) gnu_dev_major (unsigned long long int __dev)
+{
+  return ((__dev >> 8) & 0xfff) | ((unsigned int) (__dev >> 32) & ~0xfff);
+}
+
+__extension__ extern __inline __attribute__ ((__gnu_inline__)) __attribute__ ((__const__)) unsigned int
+__attribute__ ((__nothrow__ , __leaf__)) gnu_dev_minor (unsigned long long int __dev)
+{
+  return (__dev & 0xff) | ((unsigned int) (__dev >> 12) & ~0xff);
+}
+
+__extension__ extern __inline __attribute__ ((__gnu_inline__)) __attribute__ ((__const__)) unsigned long long int
+__attribute__ ((__nothrow__ , __leaf__)) gnu_dev_makedev (unsigned int __major, unsigned int __minor)
+{
+  return ((__minor & 0xff) | ((__major & 0xfff) << 8)
+   | (((unsigned long long int) (__minor & ~0xff)) << 12)
+   | (((unsigned long long int) (__major & ~0xfff)) << 32));
+}
+
+
+
+
+
+
+
+typedef __blksize_t blksize_t;
+
+
+
+
+
+
+typedef __blkcnt_t blkcnt_t;
+
+
+
+typedef __fsblkcnt_t fsblkcnt_t;
+
+
+
+typedef __fsfilcnt_t fsfilcnt_t;
+typedef unsigned long int pthread_t;
+
+
+union pthread_attr_t
+{
+  char __size[56];
+  long int __align;
+};
+
+typedef union pthread_attr_t pthread_attr_t;
+
+
+
+
+
+typedef struct __pthread_internal_list
+{
+  struct __pthread_internal_list *__prev;
+  struct __pthread_internal_list *__next;
+} __pthread_list_t;
+typedef union
+{
+  struct __pthread_mutex_s
+  {
+    int __lock;
+    unsigned int __count;
+    int __owner;
+
+    unsigned int __nusers;
+
+
+
+    int __kind;
+
+    short __spins;
+    short __elision;
+    __pthread_list_t __list;
+  } __data;
+  char __size[40];
+  long int __align;
+} pthread_mutex_t;
+
+typedef union
+{
+  char __size[4];
+  int __align;
+} pthread_mutexattr_t;
+
+
+
+
+typedef union
+{
+  struct
+  {
+    int __lock;
+    unsigned int __futex;
+    __extension__ unsigned long long int __total_seq;
+    __extension__ unsigned long long int __wakeup_seq;
+    __extension__ unsigned long long int __woken_seq;
+    void *__mutex;
+    unsigned int __nwaiters;
+    unsigned int __broadcast_seq;
+  } __data;
+  char __size[48];
+  __extension__ long long int __align;
+} pthread_cond_t;
+
+typedef union
+{
+  char __size[4];
+  int __align;
+} pthread_condattr_t;
+
+
+
+typedef unsigned int pthread_key_t;
+
+
+
+typedef int pthread_once_t;
+
+
+
+
+
+typedef union
+{
+
+  struct
+  {
+    int __lock;
+    unsigned int __nr_readers;
+    unsigned int __readers_wakeup;
+    unsigned int __writer_wakeup;
+    unsigned int __nr_readers_queued;
+    unsigned int __nr_writers_queued;
+    int __writer;
+    int __shared;
+    unsigned long int __pad1;
+    unsigned long int __pad2;
+
+
+    unsigned int __flags;
+
+  } __data;
+  char __size[56];
+  long int __align;
+} pthread_rwlock_t;
+
+typedef union
+{
+  char __size[8];
+  long int __align;
+} pthread_rwlockattr_t;
+
+
+
+
+
+typedef volatile int pthread_spinlock_t;
+
+
+
+
+typedef union
+{
+  char __size[32];
+  long int __align;
+} pthread_barrier_t;
+
+typedef union
+{
+  char __size[4];
+  int __align;
+} pthread_barrierattr_t;
+
+
+
+
+
+
+
+typedef __useconds_t useconds_t;
+typedef __intptr_t intptr_t;
+
+
+
+
+
+
+typedef __socklen_t socklen_t;
+extern int access (const char *__name, int __type) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+extern int faccessat (int __fd, const char *__file, int __type, int __flag)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2))) ;
+extern __off_t lseek (int __fd, __off_t __offset, int __whence) __attribute__ ((__nothrow__ , __leaf__));
+extern int close (int __fd);
+
+
+
+
+
+
+extern ssize_t read (int __fd, void *__buf, size_t __nbytes) ;
+
+
+
+
+
+extern ssize_t write (int __fd, const void *__buf, size_t __n) ;
+extern ssize_t pread (int __fd, void *__buf, size_t __nbytes,
+        __off_t __offset) ;
+
+
+
+
+
+
+extern ssize_t pwrite (int __fd, const void *__buf, size_t __n,
+         __off_t __offset) ;
+extern int pipe (int __pipedes[2]) __attribute__ ((__nothrow__ , __leaf__)) ;
+extern unsigned int alarm (unsigned int __seconds) __attribute__ ((__nothrow__ , __leaf__));
+extern unsigned int sleep (unsigned int __seconds);
+
+
+
+
+
+
+
+extern __useconds_t ualarm (__useconds_t __value, __useconds_t __interval)
+     __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+extern int usleep (__useconds_t __useconds);
+extern int pause (void);
+
+
+
+extern int chown (const char *__file, __uid_t __owner, __gid_t __group)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+
+
+
+extern int fchown (int __fd, __uid_t __owner, __gid_t __group) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+extern int lchown (const char *__file, __uid_t __owner, __gid_t __group)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+
+
+
+
+
+
+extern int fchownat (int __fd, const char *__file, __uid_t __owner,
+       __gid_t __group, int __flag)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2))) ;
+
+
+
+extern int chdir (const char *__path) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+
+
+
+extern int fchdir (int __fd) __attribute__ ((__nothrow__ , __leaf__)) ;
+extern char *getcwd (char *__buf, size_t __size) __attribute__ ((__nothrow__ , __leaf__)) ;
+extern char *getwd (char *__buf)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) __attribute__ ((__deprecated__)) ;
+
+
+
+
+extern int dup (int __fd) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+extern int dup2 (int __fd, int __fd2) __attribute__ ((__nothrow__ , __leaf__));
+extern char **__environ;
+
+
+
+
+
+
+
+extern int execve (const char *__path, char *const __argv[],
+     char *const __envp[]) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+
+extern int fexecve (int __fd, char *const __argv[], char *const __envp[])
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2)));
+
+
+
+
+extern int execv (const char *__path, char *const __argv[])
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+extern int execle (const char *__path, const char *__arg, ...)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+extern int execl (const char *__path, const char *__arg, ...)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+extern int execvp (const char *__file, char *const __argv[])
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+
+extern int execlp (const char *__file, const char *__arg, ...)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+extern int nice (int __inc) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+extern void _exit (int __status) __attribute__ ((__noreturn__));
+
+
+
+
+
+enum
+  {
+    _PC_LINK_MAX,
+
+    _PC_MAX_CANON,
+
+    _PC_MAX_INPUT,
+
+    _PC_NAME_MAX,
+
+    _PC_PATH_MAX,
+
+    _PC_PIPE_BUF,
+
+    _PC_CHOWN_RESTRICTED,
+
+    _PC_NO_TRUNC,
+
+    _PC_VDISABLE,
+
+    _PC_SYNC_IO,
+
+    _PC_ASYNC_IO,
+
+    _PC_PRIO_IO,
+
+    _PC_SOCK_MAXBUF,
+
+    _PC_FILESIZEBITS,
+
+    _PC_REC_INCR_XFER_SIZE,
+
+    _PC_REC_MAX_XFER_SIZE,
+
+    _PC_REC_MIN_XFER_SIZE,
+
+    _PC_REC_XFER_ALIGN,
+
+    _PC_ALLOC_SIZE_MIN,
+
+    _PC_SYMLINK_MAX,
+
+    _PC_2_SYMLINKS
+
+  };
+
+
+enum
+  {
+    _SC_ARG_MAX,
+
+    _SC_CHILD_MAX,
+
+    _SC_CLK_TCK,
+
+    _SC_NGROUPS_MAX,
+
+    _SC_OPEN_MAX,
+
+    _SC_STREAM_MAX,
+
+    _SC_TZNAME_MAX,
+
+    _SC_JOB_CONTROL,
+
+    _SC_SAVED_IDS,
+
+    _SC_REALTIME_SIGNALS,
+
+    _SC_PRIORITY_SCHEDULING,
+
+    _SC_TIMERS,
+
+    _SC_ASYNCHRONOUS_IO,
+
+    _SC_PRIORITIZED_IO,
+
+    _SC_SYNCHRONIZED_IO,
+
+    _SC_FSYNC,
+
+    _SC_MAPPED_FILES,
+
+    _SC_MEMLOCK,
+
+    _SC_MEMLOCK_RANGE,
+
+    _SC_MEMORY_PROTECTION,
+
+    _SC_MESSAGE_PASSING,
+
+    _SC_SEMAPHORES,
+
+    _SC_SHARED_MEMORY_OBJECTS,
+
+    _SC_AIO_LISTIO_MAX,
+
+    _SC_AIO_MAX,
+
+    _SC_AIO_PRIO_DELTA_MAX,
+
+    _SC_DELAYTIMER_MAX,
+
+    _SC_MQ_OPEN_MAX,
+
+    _SC_MQ_PRIO_MAX,
+
+    _SC_VERSION,
+
+    _SC_PAGESIZE,
+
+
+    _SC_RTSIG_MAX,
+
+    _SC_SEM_NSEMS_MAX,
+
+    _SC_SEM_VALUE_MAX,
+
+    _SC_SIGQUEUE_MAX,
+
+    _SC_TIMER_MAX,
+
+
+
+
+    _SC_BC_BASE_MAX,
+
+    _SC_BC_DIM_MAX,
+
+    _SC_BC_SCALE_MAX,
+
+    _SC_BC_STRING_MAX,
+
+    _SC_COLL_WEIGHTS_MAX,
+
+    _SC_EQUIV_CLASS_MAX,
+
+    _SC_EXPR_NEST_MAX,
+
+    _SC_LINE_MAX,
+
+    _SC_RE_DUP_MAX,
+
+    _SC_CHARCLASS_NAME_MAX,
+
+
+    _SC_2_VERSION,
+
+    _SC_2_C_BIND,
+
+    _SC_2_C_DEV,
+
+    _SC_2_FORT_DEV,
+
+    _SC_2_FORT_RUN,
+
+    _SC_2_SW_DEV,
+
+    _SC_2_LOCALEDEF,
+
+
+    _SC_PII,
+
+    _SC_PII_XTI,
+
+    _SC_PII_SOCKET,
+
+    _SC_PII_INTERNET,
+
+    _SC_PII_OSI,
+
+    _SC_POLL,
+
+    _SC_SELECT,
+
+    _SC_UIO_MAXIOV,
+
+    _SC_IOV_MAX = _SC_UIO_MAXIOV,
+
+    _SC_PII_INTERNET_STREAM,
+
+    _SC_PII_INTERNET_DGRAM,
+
+    _SC_PII_OSI_COTS,
+
+    _SC_PII_OSI_CLTS,
+
+    _SC_PII_OSI_M,
+
+    _SC_T_IOV_MAX,
+
+
+
+    _SC_THREADS,
+
+    _SC_THREAD_SAFE_FUNCTIONS,
+
+    _SC_GETGR_R_SIZE_MAX,
+
+    _SC_GETPW_R_SIZE_MAX,
+
+    _SC_LOGIN_NAME_MAX,
+
+    _SC_TTY_NAME_MAX,
+
+    _SC_THREAD_DESTRUCTOR_ITERATIONS,
+
+    _SC_THREAD_KEYS_MAX,
+
+    _SC_THREAD_STACK_MIN,
+
+    _SC_THREAD_THREADS_MAX,
+
+    _SC_THREAD_ATTR_STACKADDR,
+
+    _SC_THREAD_ATTR_STACKSIZE,
+
+    _SC_THREAD_PRIORITY_SCHEDULING,
+
+    _SC_THREAD_PRIO_INHERIT,
+
+    _SC_THREAD_PRIO_PROTECT,
+
+    _SC_THREAD_PROCESS_SHARED,
+
+
+    _SC_NPROCESSORS_CONF,
+
+    _SC_NPROCESSORS_ONLN,
+
+    _SC_PHYS_PAGES,
+
+    _SC_AVPHYS_PAGES,
+
+    _SC_ATEXIT_MAX,
+
+    _SC_PASS_MAX,
+
+
+    _SC_XOPEN_VERSION,
+
+    _SC_XOPEN_XCU_VERSION,
+
+    _SC_XOPEN_UNIX,
+
+    _SC_XOPEN_CRYPT,
+
+    _SC_XOPEN_ENH_I18N,
+
+    _SC_XOPEN_SHM,
+
+
+    _SC_2_CHAR_TERM,
+
+    _SC_2_C_VERSION,
+
+    _SC_2_UPE,
+
+
+    _SC_XOPEN_XPG2,
+
+    _SC_XOPEN_XPG3,
+
+    _SC_XOPEN_XPG4,
+
+
+    _SC_CHAR_BIT,
+
+    _SC_CHAR_MAX,
+
+    _SC_CHAR_MIN,
+
+    _SC_INT_MAX,
+
+    _SC_INT_MIN,
+
+    _SC_LONG_BIT,
+
+    _SC_WORD_BIT,
+
+    _SC_MB_LEN_MAX,
+
+    _SC_NZERO,
+
+    _SC_SSIZE_MAX,
+
+    _SC_SCHAR_MAX,
+
+    _SC_SCHAR_MIN,
+
+    _SC_SHRT_MAX,
+
+    _SC_SHRT_MIN,
+
+    _SC_UCHAR_MAX,
+
+    _SC_UINT_MAX,
+
+    _SC_ULONG_MAX,
+
+    _SC_USHRT_MAX,
+
+
+    _SC_NL_ARGMAX,
+
+    _SC_NL_LANGMAX,
+
+    _SC_NL_MSGMAX,
+
+    _SC_NL_NMAX,
+
+    _SC_NL_SETMAX,
+
+    _SC_NL_TEXTMAX,
+
+
+    _SC_XBS5_ILP32_OFF32,
+
+    _SC_XBS5_ILP32_OFFBIG,
+
+    _SC_XBS5_LP64_OFF64,
+
+    _SC_XBS5_LPBIG_OFFBIG,
+
+
+    _SC_XOPEN_LEGACY,
+
+    _SC_XOPEN_REALTIME,
+
+    _SC_XOPEN_REALTIME_THREADS,
+
+
+    _SC_ADVISORY_INFO,
+
+    _SC_BARRIERS,
+
+    _SC_BASE,
+
+    _SC_C_LANG_SUPPORT,
+
+    _SC_C_LANG_SUPPORT_R,
+
+    _SC_CLOCK_SELECTION,
+
+    _SC_CPUTIME,
+
+    _SC_THREAD_CPUTIME,
+
+    _SC_DEVICE_IO,
+
+    _SC_DEVICE_SPECIFIC,
+
+    _SC_DEVICE_SPECIFIC_R,
+
+    _SC_FD_MGMT,
+
+    _SC_FIFO,
+
+    _SC_PIPE,
+
+    _SC_FILE_ATTRIBUTES,
+
+    _SC_FILE_LOCKING,
+
+    _SC_FILE_SYSTEM,
+
+    _SC_MONOTONIC_CLOCK,
+
+    _SC_MULTI_PROCESS,
+
+    _SC_SINGLE_PROCESS,
+
+    _SC_NETWORKING,
+
+    _SC_READER_WRITER_LOCKS,
+
+    _SC_SPIN_LOCKS,
+
+    _SC_REGEXP,
+
+    _SC_REGEX_VERSION,
+
+    _SC_SHELL,
+
+    _SC_SIGNALS,
+
+    _SC_SPAWN,
+
+    _SC_SPORADIC_SERVER,
+
+    _SC_THREAD_SPORADIC_SERVER,
+
+    _SC_SYSTEM_DATABASE,
+
+    _SC_SYSTEM_DATABASE_R,
+
+    _SC_TIMEOUTS,
+
+    _SC_TYPED_MEMORY_OBJECTS,
+
+    _SC_USER_GROUPS,
+
+    _SC_USER_GROUPS_R,
+
+    _SC_2_PBS,
+
+    _SC_2_PBS_ACCOUNTING,
+
+    _SC_2_PBS_LOCATE,
+
+    _SC_2_PBS_MESSAGE,
+
+    _SC_2_PBS_TRACK,
+
+    _SC_SYMLOOP_MAX,
+
+    _SC_STREAMS,
+
+    _SC_2_PBS_CHECKPOINT,
+
+
+    _SC_V6_ILP32_OFF32,
+
+    _SC_V6_ILP32_OFFBIG,
+
+    _SC_V6_LP64_OFF64,
+
+    _SC_V6_LPBIG_OFFBIG,
+
+
+    _SC_HOST_NAME_MAX,
+
+    _SC_TRACE,
+
+    _SC_TRACE_EVENT_FILTER,
+
+    _SC_TRACE_INHERIT,
+
+    _SC_TRACE_LOG,
+
+
+    _SC_LEVEL1_ICACHE_SIZE,
+
+    _SC_LEVEL1_ICACHE_ASSOC,
+
+    _SC_LEVEL1_ICACHE_LINESIZE,
+
+    _SC_LEVEL1_DCACHE_SIZE,
+
+    _SC_LEVEL1_DCACHE_ASSOC,
+
+    _SC_LEVEL1_DCACHE_LINESIZE,
+
+    _SC_LEVEL2_CACHE_SIZE,
+
+    _SC_LEVEL2_CACHE_ASSOC,
+
+    _SC_LEVEL2_CACHE_LINESIZE,
+
+    _SC_LEVEL3_CACHE_SIZE,
+
+    _SC_LEVEL3_CACHE_ASSOC,
+
+    _SC_LEVEL3_CACHE_LINESIZE,
+
+    _SC_LEVEL4_CACHE_SIZE,
+
+    _SC_LEVEL4_CACHE_ASSOC,
+
+    _SC_LEVEL4_CACHE_LINESIZE,
+
+
+
+    _SC_IPV6 = _SC_LEVEL1_ICACHE_SIZE + 50,
+
+    _SC_RAW_SOCKETS,
+
+
+    _SC_V7_ILP32_OFF32,
+
+    _SC_V7_ILP32_OFFBIG,
+
+    _SC_V7_LP64_OFF64,
+
+    _SC_V7_LPBIG_OFFBIG,
+
+
+    _SC_SS_REPL_MAX,
+
+
+    _SC_TRACE_EVENT_NAME_MAX,
+
+    _SC_TRACE_NAME_MAX,
+
+    _SC_TRACE_SYS_MAX,
+
+    _SC_TRACE_USER_EVENT_MAX,
+
+
+    _SC_XOPEN_STREAMS,
+
+
+    _SC_THREAD_ROBUST_PRIO_INHERIT,
+
+    _SC_THREAD_ROBUST_PRIO_PROTECT
+
+  };
+
+
+enum
+  {
+    _CS_PATH,
+
+
+    _CS_V6_WIDTH_RESTRICTED_ENVS,
+
+
+
+    _CS_GNU_LIBC_VERSION,
+
+    _CS_GNU_LIBPTHREAD_VERSION,
+
+
+    _CS_V5_WIDTH_RESTRICTED_ENVS,
+
+
+
+    _CS_V7_WIDTH_RESTRICTED_ENVS,
+
+
+
+    _CS_LFS_CFLAGS = 1000,
+
+    _CS_LFS_LDFLAGS,
+
+    _CS_LFS_LIBS,
+
+    _CS_LFS_LINTFLAGS,
+
+    _CS_LFS64_CFLAGS,
+
+    _CS_LFS64_LDFLAGS,
+
+    _CS_LFS64_LIBS,
+
+    _CS_LFS64_LINTFLAGS,
+
+
+    _CS_XBS5_ILP32_OFF32_CFLAGS = 1100,
+
+    _CS_XBS5_ILP32_OFF32_LDFLAGS,
+
+    _CS_XBS5_ILP32_OFF32_LIBS,
+
+    _CS_XBS5_ILP32_OFF32_LINTFLAGS,
+
+    _CS_XBS5_ILP32_OFFBIG_CFLAGS,
+
+    _CS_XBS5_ILP32_OFFBIG_LDFLAGS,
+
+    _CS_XBS5_ILP32_OFFBIG_LIBS,
+
+    _CS_XBS5_ILP32_OFFBIG_LINTFLAGS,
+
+    _CS_XBS5_LP64_OFF64_CFLAGS,
+
+    _CS_XBS5_LP64_OFF64_LDFLAGS,
+
+    _CS_XBS5_LP64_OFF64_LIBS,
+
+    _CS_XBS5_LP64_OFF64_LINTFLAGS,
+
+    _CS_XBS5_LPBIG_OFFBIG_CFLAGS,
+
+    _CS_XBS5_LPBIG_OFFBIG_LDFLAGS,
+
+    _CS_XBS5_LPBIG_OFFBIG_LIBS,
+
+    _CS_XBS5_LPBIG_OFFBIG_LINTFLAGS,
+
+
+    _CS_POSIX_V6_ILP32_OFF32_CFLAGS,
+
+    _CS_POSIX_V6_ILP32_OFF32_LDFLAGS,
+
+    _CS_POSIX_V6_ILP32_OFF32_LIBS,
+
+    _CS_POSIX_V6_ILP32_OFF32_LINTFLAGS,
+
+    _CS_POSIX_V6_ILP32_OFFBIG_CFLAGS,
+
+    _CS_POSIX_V6_ILP32_OFFBIG_LDFLAGS,
+
+    _CS_POSIX_V6_ILP32_OFFBIG_LIBS,
+
+    _CS_POSIX_V6_ILP32_OFFBIG_LINTFLAGS,
+
+    _CS_POSIX_V6_LP64_OFF64_CFLAGS,
+
+    _CS_POSIX_V6_LP64_OFF64_LDFLAGS,
+
+    _CS_POSIX_V6_LP64_OFF64_LIBS,
+
+    _CS_POSIX_V6_LP64_OFF64_LINTFLAGS,
+
+    _CS_POSIX_V6_LPBIG_OFFBIG_CFLAGS,
+
+    _CS_POSIX_V6_LPBIG_OFFBIG_LDFLAGS,
+
+    _CS_POSIX_V6_LPBIG_OFFBIG_LIBS,
+
+    _CS_POSIX_V6_LPBIG_OFFBIG_LINTFLAGS,
+
+
+    _CS_POSIX_V7_ILP32_OFF32_CFLAGS,
+
+    _CS_POSIX_V7_ILP32_OFF32_LDFLAGS,
+
+    _CS_POSIX_V7_ILP32_OFF32_LIBS,
+
+    _CS_POSIX_V7_ILP32_OFF32_LINTFLAGS,
+
+    _CS_POSIX_V7_ILP32_OFFBIG_CFLAGS,
+
+    _CS_POSIX_V7_ILP32_OFFBIG_LDFLAGS,
+
+    _CS_POSIX_V7_ILP32_OFFBIG_LIBS,
+
+    _CS_POSIX_V7_ILP32_OFFBIG_LINTFLAGS,
+
+    _CS_POSIX_V7_LP64_OFF64_CFLAGS,
+
+    _CS_POSIX_V7_LP64_OFF64_LDFLAGS,
+
+    _CS_POSIX_V7_LP64_OFF64_LIBS,
+
+    _CS_POSIX_V7_LP64_OFF64_LINTFLAGS,
+
+    _CS_POSIX_V7_LPBIG_OFFBIG_CFLAGS,
+
+    _CS_POSIX_V7_LPBIG_OFFBIG_LDFLAGS,
+
+    _CS_POSIX_V7_LPBIG_OFFBIG_LIBS,
+
+    _CS_POSIX_V7_LPBIG_OFFBIG_LINTFLAGS,
+
+
+    _CS_V6_ENV,
+
+    _CS_V7_ENV
+
+  };
+
+
+extern long int pathconf (const char *__path, int __name)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern long int fpathconf (int __fd, int __name) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern long int sysconf (int __name) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+extern size_t confstr (int __name, char *__buf, size_t __len) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+extern __pid_t getpid (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern __pid_t getppid (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+extern __pid_t getpgrp (void) __attribute__ ((__nothrow__ , __leaf__));
+extern __pid_t __getpgid (__pid_t __pid) __attribute__ ((__nothrow__ , __leaf__));
+
+extern __pid_t getpgid (__pid_t __pid) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+extern int setpgid (__pid_t __pid, __pid_t __pgid) __attribute__ ((__nothrow__ , __leaf__));
+extern int setpgrp (void) __attribute__ ((__nothrow__ , __leaf__));
+extern __pid_t setsid (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+extern __pid_t getsid (__pid_t __pid) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+extern __uid_t getuid (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern __uid_t geteuid (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern __gid_t getgid (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern __gid_t getegid (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+extern int getgroups (int __size, __gid_t __list[]) __attribute__ ((__nothrow__ , __leaf__)) ;
+extern int setuid (__uid_t __uid) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+extern int setreuid (__uid_t __ruid, __uid_t __euid) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+extern int seteuid (__uid_t __uid) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+
+
+extern int setgid (__gid_t __gid) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+extern int setregid (__gid_t __rgid, __gid_t __egid) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+extern int setegid (__gid_t __gid) __attribute__ ((__nothrow__ , __leaf__)) ;
+extern __pid_t fork (void) __attribute__ ((__nothrow__));
+
+
+
+
+
+
+
+extern __pid_t vfork (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+extern char *ttyname (int __fd) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+extern int ttyname_r (int __fd, char *__buf, size_t __buflen)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2))) ;
+
+
+
+extern int isatty (int __fd) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+extern int ttyslot (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+extern int link (const char *__from, const char *__to)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2))) ;
+
+
+
+
+extern int linkat (int __fromfd, const char *__from, int __tofd,
+     const char *__to, int __flags)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2, 4))) ;
+
+
+
+
+extern int symlink (const char *__from, const char *__to)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2))) ;
+
+
+
+
+extern ssize_t readlink (const char *__restrict __path,
+    char *__restrict __buf, size_t __len)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2))) ;
+
+
+
+
+extern int symlinkat (const char *__from, int __tofd,
+        const char *__to) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 3))) ;
+
+
+extern ssize_t readlinkat (int __fd, const char *__restrict __path,
+      char *__restrict __buf, size_t __len)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2, 3))) ;
+
+
+
+extern int unlink (const char *__name) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+extern int unlinkat (int __fd, const char *__name, int __flag)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2)));
+
+
+
+extern int rmdir (const char *__path) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+extern __pid_t tcgetpgrp (int __fd) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern int tcsetpgrp (int __fd, __pid_t __pgrp_id) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+extern char *getlogin (void);
+
+
+
+
+
+
+
+extern int getlogin_r (char *__name, size_t __name_len) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+extern int setlogin (const char *__name) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+extern char *optarg;
+extern int optind;
+
+
+
+
+extern int opterr;
+
+
+
+extern int optopt;
+extern int getopt (int ___argc, char *const *___argv, const char *__shortopts)
+       __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+
+extern int gethostname (char *__name, size_t __len) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+
+extern int sethostname (const char *__name, size_t __len)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+
+
+
+extern int sethostid (long int __id) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+
+extern int getdomainname (char *__name, size_t __len)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+extern int setdomainname (const char *__name, size_t __len)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+
+
+
+
+
+extern int vhangup (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern int revoke (const char *__file) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+
+
+
+
+
+
+
+extern int profil (unsigned short int *__sample_buffer, size_t __size,
+     size_t __offset, unsigned int __scale)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+extern int acct (const char *__name) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+extern char *getusershell (void) __attribute__ ((__nothrow__ , __leaf__));
+extern void endusershell (void) __attribute__ ((__nothrow__ , __leaf__));
+extern void setusershell (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+extern int daemon (int __nochdir, int __noclose) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+
+
+extern int chroot (const char *__path) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+
+
+
+extern char *getpass (const char *__prompt) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+
+
+extern int fsync (int __fd);
+extern long int gethostid (void);
+
+
+extern void sync (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+extern int getpagesize (void) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__));
+
+
+
+
+extern int getdtablesize (void) __attribute__ ((__nothrow__ , __leaf__));
+extern int truncate (const char *__file, __off_t __length)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+extern int ftruncate (int __fd, __off_t __length) __attribute__ ((__nothrow__ , __leaf__)) ;
+extern int brk (void *__addr) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+
+extern void *sbrk (intptr_t __delta) __attribute__ ((__nothrow__ , __leaf__));
+extern long int syscall (long int __sysno, ...) __attribute__ ((__nothrow__ , __leaf__));
+extern int lockf (int __fd, int __cmd, __off_t __len) ;
+extern int fdatasync (int __fildes);
+
+
+typedef voidpf (*alloc_func) (voidpf opaque, uInt items, uInt size);
+typedef void (*free_func) (voidpf opaque, voidpf address);
+
+struct internal_state;
+
+typedef struct z_stream_s {
+    Bytef *next_in;
+    uInt avail_in;
+    uLong total_in;
+
+    Bytef *next_out;
+    uInt avail_out;
+    uLong total_out;
+
+    char *msg;
+    struct internal_state *state;
+
+    alloc_func zalloc;
+    free_func zfree;
+    voidpf opaque;
+
+    int data_type;
+    uLong adler;
+    uLong reserved;
+} z_stream;
+
+typedef z_stream *z_streamp;
+extern const char * zlibVersion (void);
+extern int deflate (z_streamp strm, int flush);
+extern int deflateEnd (z_streamp strm);
+extern int inflate (z_streamp strm, int flush);
+extern int inflateEnd (z_streamp strm);
+extern int deflateSetDictionary (z_streamp strm, const Bytef *dictionary, uInt dictLength)
+
+                                                               ;
+extern int deflateCopy (z_streamp dest, z_streamp source)
+                                                      ;
+extern int deflateReset (z_streamp strm);
+extern int deflateParams (z_streamp strm, int level, int strategy)
+
+                        ;
+extern int inflateSetDictionary (z_streamp strm, const Bytef *dictionary, uInt dictLength)
+
+                                                               ;
+extern int inflateSync (z_streamp strm);
+extern int inflateReset (z_streamp strm);
+extern int compress (Bytef *dest, uLongf *destLen, const Bytef *source, uLong sourceLen)
+                                                                       ;
+extern int compress2 (Bytef *dest, uLongf *destLen, const Bytef *source, uLong sourceLen, int level)
+
+                                             ;
+extern int uncompress (Bytef *dest, uLongf *destLen, const Bytef *source, uLong sourceLen)
+                                                                         ;
+typedef voidp gzFile;
+
+extern gzFile gzopen (const char *path, const char *mode);
+extern gzFile gzdopen (int fd, const char *mode);
+extern int gzsetparams (gzFile file, int level, int strategy);
+
+
+
+
+
+
+
+extern int gzread (gzFile file, voidp buf, unsigned len);
+
+
+
+
+
+
+
+extern int gzwrite (gzFile file, const voidp buf, unsigned len)
+                                      ;
+
+
+
+
+
+
+extern int gzprintf (gzFile file, const char *format, ...);
+
+
+
+
+
+
+extern int gzputs (gzFile file, const char *s);
+
+
+
+
+
+
+extern char * gzgets (gzFile file, char *buf, int len);
+extern int gzputc (gzFile file, int c);
+
+
+
+
+
+extern int gzgetc (gzFile file);
+
+
+
+
+
+extern int gzflush (gzFile file, int flush);
+extern off_t gzseek (gzFile file, off_t offset, int whence)
+                                      ;
+extern int gzrewind (gzFile file);
+
+
+
+
+
+
+extern off_t gztell (gzFile file);
+extern int gzeof (gzFile file);
+
+
+
+
+
+extern int gzclose (gzFile file);
+
+
+
+
+
+
+extern const char * gzerror (gzFile file, int *errnum);
+extern uLong adler32 (uLong adler, const Bytef *buf, uInt len);
+extern uLong crc32 (uLong crc, const Bytef *buf, uInt len);
+extern int deflateInit_ (z_streamp strm, int level, const char *version, int stream_size)
+                                                                           ;
+extern int inflateInit_ (z_streamp strm, const char *version, int stream_size)
+                                                                           ;
+extern int deflateInit2_ (z_streamp strm, int level, int method, int windowBits, int memLevel, int strategy, const char *version, int stream_size)
+
+
+                                                       ;
+extern int inflateInit2_ (z_streamp strm, int windowBits, const char *version, int stream_size)
+                                                                            ;
+extern const char * zError (int err);
+extern int inflateSyncPoint (z_streamp z);
+extern const uLongf * get_crc_table (void);
+
+
+
+typedef long int ptrdiff_t;
+typedef int wchar_t;
+typedef struct {
+  long long __max_align_ll __attribute__((__aligned__(__alignof__(long long))));
+  long double __max_align_ld __attribute__((__aligned__(__alignof__(long double))));
+} max_align_t;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+extern void *memcpy (void *__restrict __dest, const void *__restrict __src,
+       size_t __n) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern void *memmove (void *__dest, const void *__src, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+
+
+
+extern void *memccpy (void *__restrict __dest, const void *__restrict __src,
+        int __c, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+
+
+extern void *memset (void *__s, int __c, size_t __n) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int memcmp (const void *__s1, const void *__s2, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1, 2)));
+extern void *memchr (const void *__s, int __c, size_t __n)
+      __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+extern char *strcpy (char *__restrict __dest, const char *__restrict __src)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+extern char *strncpy (char *__restrict __dest,
+        const char *__restrict __src, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern char *strcat (char *__restrict __dest, const char *__restrict __src)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+extern char *strncat (char *__restrict __dest, const char *__restrict __src,
+        size_t __n) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern int strcmp (const char *__s1, const char *__s2)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1, 2)));
+
+extern int strncmp (const char *__s1, const char *__s2, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern int strcoll (const char *__s1, const char *__s2)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1, 2)));
+
+extern size_t strxfrm (char *__restrict __dest,
+         const char *__restrict __src, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2)));
+
+
+
+
+
+
+typedef struct __locale_struct
+{
+
+  struct __locale_data *__locales[13];
+
+
+  const unsigned short int *__ctype_b;
+  const int *__ctype_tolower;
+  const int *__ctype_toupper;
+
+
+  const char *__names[13];
+} *__locale_t;
+
+
+typedef __locale_t locale_t;
+
+
+extern int strcoll_l (const char *__s1, const char *__s2, __locale_t __l)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1, 2, 3)));
+
+extern size_t strxfrm_l (char *__dest, const char *__src, size_t __n,
+    __locale_t __l) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2, 4)));
+
+
+
+
+
+extern char *strdup (const char *__s)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__malloc__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+
+extern char *strndup (const char *__string, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__malloc__)) __attribute__ ((__nonnull__ (1)));
+
+extern char *strchr (const char *__s, int __c)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1)));
+extern char *strrchr (const char *__s, int __c)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+extern size_t strcspn (const char *__s, const char *__reject)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern size_t strspn (const char *__s, const char *__accept)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1, 2)));
+extern char *strpbrk (const char *__s, const char *__accept)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1, 2)));
+extern char *strstr (const char *__haystack, const char *__needle)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+
+extern char *strtok (char *__restrict __s, const char *__restrict __delim)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2)));
+
+
+
+
+extern char *__strtok_r (char *__restrict __s,
+    const char *__restrict __delim,
+    char **__restrict __save_ptr)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2, 3)));
+
+extern char *strtok_r (char *__restrict __s, const char *__restrict __delim,
+         char **__restrict __save_ptr)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2, 3)));
+
+
+extern size_t strlen (const char *__s)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+extern size_t strnlen (const char *__string, size_t __maxlen)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+extern char *strerror (int __errnum) __attribute__ ((__nothrow__ , __leaf__));
+
+extern int strerror_r (int __errnum, char *__buf, size_t __buflen) __asm__ ("" "__xpg_strerror_r") __attribute__ ((__nothrow__ , __leaf__))
+
+                        __attribute__ ((__nonnull__ (2)));
+extern char *strerror_l (int __errnum, __locale_t __l) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+extern void __bzero (void *__s, size_t __n) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+extern void bcopy (const void *__src, void *__dest, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern void bzero (void *__s, size_t __n) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int bcmp (const void *__s1, const void *__s2, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1, 2)));
+extern char *index (const char *__s, int __c)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1)));
+extern char *rindex (const char *__s, int __c)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+extern int ffs (int __i) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__));
+extern int strcasecmp (const char *__s1, const char *__s2)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern int strncasecmp (const char *__s1, const char *__s2, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1, 2)));
+extern char *strsep (char **__restrict __stringp,
+       const char *__restrict __delim)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+
+extern char *strsignal (int __sig) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern char *__stpcpy (char *__restrict __dest, const char *__restrict __src)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+extern char *stpcpy (char *__restrict __dest, const char *__restrict __src)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+extern char *__stpncpy (char *__restrict __dest,
+   const char *__restrict __src, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+extern char *stpncpy (char *__restrict __dest,
+        const char *__restrict __src, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern void *__rawmemchr (const void *__s, int __c);
+extern __inline __attribute__ ((__gnu_inline__)) size_t __strcspn_c1 (const char *__s, int __reject);
+extern __inline __attribute__ ((__gnu_inline__)) size_t
+__strcspn_c1 (const char *__s, int __reject)
+{
+  size_t __result = 0;
+  while (__s[__result] != '\0' && __s[__result] != __reject)
+    ++__result;
+  return __result;
+}
+
+extern __inline __attribute__ ((__gnu_inline__)) size_t __strcspn_c2 (const char *__s, int __reject1,
+         int __reject2);
+extern __inline __attribute__ ((__gnu_inline__)) size_t
+__strcspn_c2 (const char *__s, int __reject1, int __reject2)
+{
+  size_t __result = 0;
+  while (__s[__result] != '\0' && __s[__result] != __reject1
+  && __s[__result] != __reject2)
+    ++__result;
+  return __result;
+}
+
+extern __inline __attribute__ ((__gnu_inline__)) size_t __strcspn_c3 (const char *__s, int __reject1,
+         int __reject2, int __reject3);
+extern __inline __attribute__ ((__gnu_inline__)) size_t
+__strcspn_c3 (const char *__s, int __reject1, int __reject2,
+       int __reject3)
+{
+  size_t __result = 0;
+  while (__s[__result] != '\0' && __s[__result] != __reject1
+  && __s[__result] != __reject2 && __s[__result] != __reject3)
+    ++__result;
+  return __result;
+}
+extern __inline __attribute__ ((__gnu_inline__)) size_t __strspn_c1 (const char *__s, int __accept);
+extern __inline __attribute__ ((__gnu_inline__)) size_t
+__strspn_c1 (const char *__s, int __accept)
+{
+  size_t __result = 0;
+
+  while (__s[__result] == __accept)
+    ++__result;
+  return __result;
+}
+
+extern __inline __attribute__ ((__gnu_inline__)) size_t __strspn_c2 (const char *__s, int __accept1,
+        int __accept2);
+extern __inline __attribute__ ((__gnu_inline__)) size_t
+__strspn_c2 (const char *__s, int __accept1, int __accept2)
+{
+  size_t __result = 0;
+
+  while (__s[__result] == __accept1 || __s[__result] == __accept2)
+    ++__result;
+  return __result;
+}
+
+extern __inline __attribute__ ((__gnu_inline__)) size_t __strspn_c3 (const char *__s, int __accept1,
+        int __accept2, int __accept3);
+extern __inline __attribute__ ((__gnu_inline__)) size_t
+__strspn_c3 (const char *__s, int __accept1, int __accept2, int __accept3)
+{
+  size_t __result = 0;
+
+  while (__s[__result] == __accept1 || __s[__result] == __accept2
+  || __s[__result] == __accept3)
+    ++__result;
+  return __result;
+}
+extern __inline __attribute__ ((__gnu_inline__)) char *__strpbrk_c2 (const char *__s, int __accept1,
+        int __accept2);
+extern __inline __attribute__ ((__gnu_inline__)) char *
+__strpbrk_c2 (const char *__s, int __accept1, int __accept2)
+{
+
+  while (*__s != '\0' && *__s != __accept1 && *__s != __accept2)
+    ++__s;
+  return *__s == '\0' ? ((void *)0) : (char *) (size_t) __s;
+}
+
+extern __inline __attribute__ ((__gnu_inline__)) char *__strpbrk_c3 (const char *__s, int __accept1,
+        int __accept2, int __accept3);
+extern __inline __attribute__ ((__gnu_inline__)) char *
+__strpbrk_c3 (const char *__s, int __accept1, int __accept2, int __accept3)
+{
+
+  while (*__s != '\0' && *__s != __accept1 && *__s != __accept2
+  && *__s != __accept3)
+    ++__s;
+  return *__s == '\0' ? ((void *)0) : (char *) (size_t) __s;
+}
+extern __inline __attribute__ ((__gnu_inline__)) char *__strtok_r_1c (char *__s, char __sep, char **__nextp);
+extern __inline __attribute__ ((__gnu_inline__)) char *
+__strtok_r_1c (char *__s, char __sep, char **__nextp)
+{
+  char *__result;
+  if (__s == ((void *)0))
+    __s = *__nextp;
+  while (*__s == __sep)
+    ++__s;
+  __result = ((void *)0);
+  if (*__s != '\0')
+    {
+      __result = __s++;
+      while (*__s != '\0')
+ if (*__s++ == __sep)
+   {
+     __s[-1] = '\0';
+     break;
+   }
+    }
+  *__nextp = __s;
+  return __result;
+}
+extern char *__strsep_g (char **__stringp, const char *__delim);
+extern __inline __attribute__ ((__gnu_inline__)) char *__strsep_1c (char **__s, char __reject);
+extern __inline __attribute__ ((__gnu_inline__)) char *
+__strsep_1c (char **__s, char __reject)
+{
+  char *__retval = *__s;
+  if (__retval != ((void *)0) && (*__s = (__extension__ (__builtin_constant_p (__reject) && !__builtin_constant_p (__retval) && (__reject) == '\0' ? (char *) __rawmemchr (__retval, __reject) : __builtin_strchr (__retval, __reject)))) != ((void *)0))
+    *(*__s)++ = '\0';
+  return __retval;
+}
+
+extern __inline __attribute__ ((__gnu_inline__)) char *__strsep_2c (char **__s, char __reject1, char __reject2);
+extern __inline __attribute__ ((__gnu_inline__)) char *
+__strsep_2c (char **__s, char __reject1, char __reject2)
+{
+  char *__retval = *__s;
+  if (__retval != ((void *)0))
+    {
+      char *__cp = __retval;
+      while (1)
+ {
+   if (*__cp == '\0')
+     {
+       __cp = ((void *)0);
+   break;
+     }
+   if (*__cp == __reject1 || *__cp == __reject2)
+     {
+       *__cp++ = '\0';
+       break;
+     }
+   ++__cp;
+ }
+      *__s = __cp;
+    }
+  return __retval;
+}
+
+extern __inline __attribute__ ((__gnu_inline__)) char *__strsep_3c (char **__s, char __reject1, char __reject2,
+       char __reject3);
+extern __inline __attribute__ ((__gnu_inline__)) char *
+__strsep_3c (char **__s, char __reject1, char __reject2, char __reject3)
+{
+  char *__retval = *__s;
+  if (__retval != ((void *)0))
+    {
+      char *__cp = __retval;
+      while (1)
+ {
+   if (*__cp == '\0')
+     {
+       __cp = ((void *)0);
+   break;
+     }
+   if (*__cp == __reject1 || *__cp == __reject2 || *__cp == __reject3)
+     {
+       *__cp++ = '\0';
+       break;
+     }
+   ++__cp;
+ }
+      *__s = __cp;
+    }
+  return __retval;
+}
+
+
+
+
+extern void *malloc (size_t __size) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__malloc__)) ;
+
+extern void *calloc (size_t __nmemb, size_t __size)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__malloc__)) ;
+
+
+
+
+
+
+extern char *__strdup (const char *__string) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__malloc__));
+extern char *__strndup (const char *__string, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__malloc__));
+
+
+
+
+
+
+
+
+
+union wait
+  {
+    int w_status;
+    struct
+      {
+
+ unsigned int __w_termsig:7;
+ unsigned int __w_coredump:1;
+ unsigned int __w_retcode:8;
+ unsigned int:16;
+
+
+
+
+
+
+
+      } __wait_terminated;
+    struct
+      {
+
+ unsigned int __w_stopval:8;
+ unsigned int __w_stopsig:8;
+ unsigned int:16;
+
+
+
+
+
+
+      } __wait_stopped;
+  };
+typedef union
+  {
+    union wait *__uptr;
+    int *__iptr;
+  } __WAIT_STATUS __attribute__ ((__transparent_union__));
+
+
+typedef struct
+  {
+    int quot;
+    int rem;
+  } div_t;
+
+
+
+typedef struct
+  {
+    long int quot;
+    long int rem;
+  } ldiv_t;
+
+
+
+
+
+
+
+__extension__ typedef struct
+  {
+    long long int quot;
+    long long int rem;
+  } lldiv_t;
+
+
+extern size_t __ctype_get_mb_cur_max (void) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+extern double atof (const char *__nptr)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1))) ;
+
+extern int atoi (const char *__nptr)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1))) ;
+
+extern long int atol (const char *__nptr)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1))) ;
+
+
+
+
+
+__extension__ extern long long int atoll (const char *__nptr)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1))) ;
+
+
+
+
+
+extern double strtod (const char *__restrict __nptr,
+        char **__restrict __endptr)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+extern float strtof (const char *__restrict __nptr,
+       char **__restrict __endptr) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+extern long double strtold (const char *__restrict __nptr,
+       char **__restrict __endptr)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+extern long int strtol (const char *__restrict __nptr,
+   char **__restrict __endptr, int __base)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+extern unsigned long int strtoul (const char *__restrict __nptr,
+      char **__restrict __endptr, int __base)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+__extension__
+extern long long int strtoq (const char *__restrict __nptr,
+        char **__restrict __endptr, int __base)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+__extension__
+extern unsigned long long int strtouq (const char *__restrict __nptr,
+           char **__restrict __endptr, int __base)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+__extension__
+extern long long int strtoll (const char *__restrict __nptr,
+         char **__restrict __endptr, int __base)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+__extension__
+extern unsigned long long int strtoull (const char *__restrict __nptr,
+     char **__restrict __endptr, int __base)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern __inline __attribute__ ((__gnu_inline__)) int
+__attribute__ ((__nothrow__ , __leaf__)) atoi (const char *__nptr)
+{
+  return (int) strtol (__nptr, (char **) ((void *)0), 10);
+}
+extern __inline __attribute__ ((__gnu_inline__)) long int
+__attribute__ ((__nothrow__ , __leaf__)) atol (const char *__nptr)
+{
+  return strtol (__nptr, (char **) ((void *)0), 10);
+}
+
+
+
+
+__extension__ extern __inline __attribute__ ((__gnu_inline__)) long long int
+__attribute__ ((__nothrow__ , __leaf__)) atoll (const char *__nptr)
+{
+  return strtoll (__nptr, (char **) ((void *)0), 10);
+}
+
+extern char *l64a (long int __n) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+extern long int a64l (const char *__s)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1))) ;
+extern long int random (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern void srandom (unsigned int __seed) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+extern char *initstate (unsigned int __seed, char *__statebuf,
+   size_t __statelen) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2)));
+
+
+
+extern char *setstate (char *__statebuf) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+
+
+struct random_data
+  {
+    int32_t *fptr;
+    int32_t *rptr;
+    int32_t *state;
+    int rand_type;
+    int rand_deg;
+    int rand_sep;
+    int32_t *end_ptr;
+  };
+
+extern int random_r (struct random_data *__restrict __buf,
+       int32_t *__restrict __result) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+extern int srandom_r (unsigned int __seed, struct random_data *__buf)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2)));
+
+extern int initstate_r (unsigned int __seed, char *__restrict __statebuf,
+   size_t __statelen,
+   struct random_data *__restrict __buf)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2, 4)));
+
+extern int setstate_r (char *__restrict __statebuf,
+         struct random_data *__restrict __buf)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+
+
+
+extern int rand (void) __attribute__ ((__nothrow__ , __leaf__));
+
+extern void srand (unsigned int __seed) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+extern int rand_r (unsigned int *__seed) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+
+extern double drand48 (void) __attribute__ ((__nothrow__ , __leaf__));
+extern double erand48 (unsigned short int __xsubi[3]) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern long int lrand48 (void) __attribute__ ((__nothrow__ , __leaf__));
+extern long int nrand48 (unsigned short int __xsubi[3])
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern long int mrand48 (void) __attribute__ ((__nothrow__ , __leaf__));
+extern long int jrand48 (unsigned short int __xsubi[3])
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern void srand48 (long int __seedval) __attribute__ ((__nothrow__ , __leaf__));
+extern unsigned short int *seed48 (unsigned short int __seed16v[3])
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+extern void lcong48 (unsigned short int __param[7]) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+struct drand48_data
+  {
+    unsigned short int __x[3];
+    unsigned short int __old_x[3];
+    unsigned short int __c;
+    unsigned short int __init;
+    __extension__ unsigned long long int __a;
+
+  };
+
+
+extern int drand48_r (struct drand48_data *__restrict __buffer,
+        double *__restrict __result) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+extern int erand48_r (unsigned short int __xsubi[3],
+        struct drand48_data *__restrict __buffer,
+        double *__restrict __result) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern int lrand48_r (struct drand48_data *__restrict __buffer,
+        long int *__restrict __result)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+extern int nrand48_r (unsigned short int __xsubi[3],
+        struct drand48_data *__restrict __buffer,
+        long int *__restrict __result)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern int mrand48_r (struct drand48_data *__restrict __buffer,
+        long int *__restrict __result)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+extern int jrand48_r (unsigned short int __xsubi[3],
+        struct drand48_data *__restrict __buffer,
+        long int *__restrict __result)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern int srand48_r (long int __seedval, struct drand48_data *__buffer)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2)));
+
+extern int seed48_r (unsigned short int __seed16v[3],
+       struct drand48_data *__buffer) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+extern int lcong48_r (unsigned short int __param[7],
+        struct drand48_data *__buffer)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+
+
+
+extern void *realloc (void *__ptr, size_t __size)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__warn_unused_result__));
+
+extern void free (void *__ptr) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+extern void cfree (void *__ptr) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+
+
+
+
+extern void *alloca (size_t __size) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+
+
+
+
+
+extern void *valloc (size_t __size) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__malloc__)) ;
+
+
+
+
+extern int posix_memalign (void **__memptr, size_t __alignment, size_t __size)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+
+
+
+
+extern void *aligned_alloc (size_t __alignment, size_t __size)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__malloc__)) __attribute__ ((__alloc_size__ (2))) ;
+
+
+
+
+extern void abort (void) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__noreturn__));
+
+
+
+extern int atexit (void (*__func) (void)) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+
+
+extern int at_quick_exit (void (*__func) (void)) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+
+
+extern int on_exit (void (*__func) (int __status, void *__arg), void *__arg)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+
+extern void exit (int __status) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__noreturn__));
+
+
+
+
+
+extern void quick_exit (int __status) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__noreturn__));
+
+
+
+
+
+
+
+extern void _Exit (int __status) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__noreturn__));
+
+
+
+
+
+
+extern char *getenv (const char *__name) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+
+extern int putenv (char *__string) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+extern int setenv (const char *__name, const char *__value, int __replace)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2)));
+
+
+extern int unsetenv (const char *__name) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+
+extern int clearenv (void) __attribute__ ((__nothrow__ , __leaf__));
+extern char *mktemp (char *__template) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+extern int mkstemp (char *__template) __attribute__ ((__nonnull__ (1))) ;
+extern int mkstemps (char *__template, int __suffixlen) __attribute__ ((__nonnull__ (1))) ;
+extern char *mkdtemp (char *__template) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+
+
+
+
+
+extern int system (const char *__command) ;
+
+extern char *realpath (const char *__restrict __name,
+         char *__restrict __resolved) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+
+
+typedef int (*__compar_fn_t) (const void *, const void *);
+
+
+
+extern void *bsearch (const void *__key, const void *__base,
+        size_t __nmemb, size_t __size, __compar_fn_t __compar)
+     __attribute__ ((__nonnull__ (1, 2, 5))) ;
+
+
+extern __inline __attribute__ ((__gnu_inline__)) void *
+bsearch (const void *__key, const void *__base, size_t __nmemb, size_t __size,
+  __compar_fn_t __compar)
+{
+  size_t __l, __u, __idx;
+  const void *__p;
+  int __comparison;
+
+  __l = 0;
+  __u = __nmemb;
+  while (__l < __u)
+    {
+      __idx = (__l + __u) / 2;
+      __p = (void *) (((const char *) __base) + (__idx * __size));
+      __comparison = (*__compar) (__key, __p);
+      if (__comparison < 0)
+ __u = __idx;
+      else if (__comparison > 0)
+ __l = __idx + 1;
+      else
+ return (void *) __p;
+    }
+
+  return ((void *)0);
+}
+
+
+
+
+extern void qsort (void *__base, size_t __nmemb, size_t __size,
+     __compar_fn_t __compar) __attribute__ ((__nonnull__ (1, 4)));
+extern int abs (int __x) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__)) ;
+extern long int labs (long int __x) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__)) ;
+
+
+
+__extension__ extern long long int llabs (long long int __x)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__)) ;
+
+
+
+
+
+
+
+extern div_t div (int __numer, int __denom)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__)) ;
+extern ldiv_t ldiv (long int __numer, long int __denom)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__)) ;
+
+
+
+
+__extension__ extern lldiv_t lldiv (long long int __numer,
+        long long int __denom)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__)) ;
+
+extern char *ecvt (double __value, int __ndigit, int *__restrict __decpt,
+     int *__restrict __sign) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (3, 4))) ;
+
+
+
+
+extern char *fcvt (double __value, int __ndigit, int *__restrict __decpt,
+     int *__restrict __sign) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (3, 4))) ;
+
+
+
+
+extern char *gcvt (double __value, int __ndigit, char *__buf)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (3))) ;
+
+
+
+
+extern char *qecvt (long double __value, int __ndigit,
+      int *__restrict __decpt, int *__restrict __sign)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (3, 4))) ;
+extern char *qfcvt (long double __value, int __ndigit,
+      int *__restrict __decpt, int *__restrict __sign)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (3, 4))) ;
+extern char *qgcvt (long double __value, int __ndigit, char *__buf)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (3))) ;
+
+
+
+
+extern int ecvt_r (double __value, int __ndigit, int *__restrict __decpt,
+     int *__restrict __sign, char *__restrict __buf,
+     size_t __len) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (3, 4, 5)));
+extern int fcvt_r (double __value, int __ndigit, int *__restrict __decpt,
+     int *__restrict __sign, char *__restrict __buf,
+     size_t __len) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (3, 4, 5)));
+
+extern int qecvt_r (long double __value, int __ndigit,
+      int *__restrict __decpt, int *__restrict __sign,
+      char *__restrict __buf, size_t __len)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (3, 4, 5)));
+extern int qfcvt_r (long double __value, int __ndigit,
+      int *__restrict __decpt, int *__restrict __sign,
+      char *__restrict __buf, size_t __len)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (3, 4, 5)));
+
+
+
+
+
+
+extern int mblen (const char *__s, size_t __n) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern int mbtowc (wchar_t *__restrict __pwc,
+     const char *__restrict __s, size_t __n) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern int wctomb (char *__s, wchar_t __wchar) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+extern size_t mbstowcs (wchar_t *__restrict __pwcs,
+   const char *__restrict __s, size_t __n) __attribute__ ((__nothrow__ , __leaf__));
+
+extern size_t wcstombs (char *__restrict __s,
+   const wchar_t *__restrict __pwcs, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+
+
+extern int rpmatch (const char *__response) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+extern int getsubopt (char **__restrict __optionp,
+        char *const *__restrict __tokens,
+        char **__restrict __valuep)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2, 3))) ;
+extern int getloadavg (double __loadavg[], int __nelem)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+extern __inline __attribute__ ((__gnu_inline__)) double
+__attribute__ ((__nothrow__ , __leaf__)) atof (const char *__nptr)
+{
+  return strtod (__nptr, (char **) ((void *)0));
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+extern int *__errno_location (void) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__));
+
+
+
+
+
+
+
+
+
+typedef unsigned char uch;
+typedef uch uchf;
+typedef unsigned short ush;
+typedef ush ushf;
+typedef unsigned long ulg;
+
+extern const char *z_errmsg[10];
+typedef uLong ( *check_func) (uLong check, const Bytef *buf, uInt len)
+                     ;
+voidpf zcalloc (voidpf opaque, unsigned items, unsigned size);
+void zcfree (voidpf opaque, voidpf ptr);
+typedef struct ct_data_s {
+    union {
+        ush freq;
+        ush code;
+    } fc;
+    union {
+        ush dad;
+        ush len;
+    } dl;
+} ct_data;
+
+
+
+
+
+
+typedef struct static_tree_desc_s static_tree_desc;
+
+typedef struct tree_desc_s {
+    ct_data *dyn_tree;
+    int max_code;
+    static_tree_desc *stat_desc;
+} tree_desc;
+
+typedef ush Pos;
+typedef Pos Posf;
+typedef unsigned IPos;
+
+
+
+
+
+typedef struct internal_state {
+    z_streamp strm;
+    int status;
+    Bytef *pending_buf;
+    ulg pending_buf_size;
+    Bytef *pending_out;
+    int pending;
+    int noheader;
+    Byte data_type;
+    Byte method;
+    int last_flush;
+
+
+
+    uInt w_size;
+    uInt w_bits;
+    uInt w_mask;
+
+    Bytef *window;
+    ulg window_size;
+
+
+
+
+    Posf *prev;
+
+
+
+
+
+    Posf *head;
+
+    uInt ins_h;
+    uInt hash_size;
+    uInt hash_bits;
+    uInt hash_mask;
+
+    uInt hash_shift;
+
+
+
+
+
+
+    long block_start;
+
+
+
+
+    uInt match_length;
+    IPos prev_match;
+    int match_available;
+    uInt strstart;
+    uInt match_start;
+    uInt lookahead;
+
+    uInt prev_length;
+
+
+
+
+    uInt max_chain_length;
+
+
+
+
+
+    uInt max_lazy_match;
+    int level;
+    int strategy;
+
+    uInt good_match;
+
+
+    int nice_match;
+
+
+
+    struct ct_data_s dyn_ltree[(2*(256 +1+29)+1)];
+    struct ct_data_s dyn_dtree[2*30 +1];
+    struct ct_data_s bl_tree[2*19 +1];
+
+    struct tree_desc_s l_desc;
+    struct tree_desc_s d_desc;
+    struct tree_desc_s bl_desc;
+
+    ush bl_count[15 +1];
+
+
+    int heap[2*(256 +1+29)+1];
+    int heap_len;
+    int heap_max;
+
+
+
+
+    uch depth[2*(256 +1+29)+1];
+
+
+
+    uchf *l_buf;
+
+    uInt lit_bufsize;
+    uInt last_lit;
+
+    ushf *d_buf;
+
+
+
+
+
+    ulg opt_len;
+    ulg static_len;
+    uInt matches;
+    int last_eob_len;
+
+
+
+
+
+
+    ush bi_buf;
+
+
+
+    int bi_valid;
+
+
+
+
+} deflate_state;
+void _tr_init (deflate_state *s);
+int _tr_tally (deflate_state *s, unsigned dist, unsigned lc);
+void _tr_flush_block (deflate_state *s, charf *buf, ulg stored_len, int eof)
+              ;
+void _tr_align (deflate_state *s);
+void _tr_stored_block (deflate_state *s, charf *buf, ulg stored_len, int eof)
+                                   ;
+  extern const uch _length_code[];
+  extern const uch _dist_code[];
+
+const char deflate_copyright[] =
+   " deflate 1.1.3 Copyright 1995-1998 Jean-loup Gailly ";
+typedef enum {
+    need_more,
+    block_done,
+    finish_started,
+    finish_done
+} block_state;
+
+typedef block_state (*compress_func) (deflate_state *s, int flush);
+
+
+static void fill_window (deflate_state *s);
+static block_state deflate_stored (deflate_state *s, int flush);
+static block_state deflate_fast (deflate_state *s, int flush);
+static block_state deflate_slow (deflate_state *s, int flush);
+static void lm_init (deflate_state *s);
+static void putShortMSB (deflate_state *s, uInt b);
+static void flush_pending (z_streamp strm);
+static int read_buf (z_streamp strm, Bytef *buf, unsigned size);
+
+
+
+
+static uInt longest_match (deflate_state *s, IPos cur_match);
+typedef struct config_s {
+   ush good_length;
+   ush max_lazy;
+   ush nice_length;
+   ush max_chain;
+   compress_func func;
+} config;
+
+static const config configuration_table[10] = {
+
+        {0, 0, 0, 0, deflate_stored},
+        {4, 4, 8, 4, deflate_fast},
+        {4, 5, 16, 8, deflate_fast},
+        {4, 6, 32, 32, deflate_fast},
+
+        {4, 4, 16, 16, deflate_slow},
+        {8, 16, 32, 32, deflate_slow},
+        {8, 16, 128, 128, deflate_slow},
+        {8, 32, 128, 256, deflate_slow},
+        {32, 128, 258, 1024, deflate_slow},
+        {32, 258, 258, 4096, deflate_slow}};
+struct static_tree_desc_s {int dummy;};
+int deflateInit_(strm, level, version, stream_size)
+    z_streamp strm;
+    int level;
+    const char *version;
+    int stream_size;
+{
+    return deflateInit2_(strm, level, 8, 15, 8,
+    0, version, stream_size);
+
+}
+
+
+int deflateInit2_(strm, level, method, windowBits, memLevel, strategy,
+    version, stream_size)
+    z_streamp strm;
+    int level;
+    int method;
+    int windowBits;
+    int memLevel;
+    int strategy;
+    const char *version;
+    int stream_size;
+{
+    deflate_state *s;
+    int noheader = 0;
+    static const char* my_version = "1.1.3";
+
+    ushf *overlay;
+
+
+
+
+    if (version == 0 || version[0] != my_version[0] ||
+        stream_size != sizeof(z_stream)) {
+ return (-6);
+    }
+    if (strm == 0) return (-2);
+
+    strm->msg = 0;
+    if (strm->zalloc == 0) {
+ strm->zalloc = zcalloc;
+ strm->opaque = (voidpf)0;
+    }
+    if (strm->zfree == 0) strm->zfree = zcfree;
+
+    if (level == (-1)) level = 6;
+
+
+
+
+    if (windowBits < 0) {
+        noheader = 1;
+        windowBits = -windowBits;
+    }
+    if (memLevel < 1 || memLevel > 9 || method != 8 ||
+        windowBits < 8 || windowBits > 15 || level < 0 || level > 9 ||
+ strategy < 0 || strategy > 2) {
+        return (-2);
+    }
+    s = (deflate_state *) (*((strm)->zalloc))((strm)->opaque, (1), (sizeof(deflate_state)));
+    if (s == 0) return (-4);
+    strm->state = (struct internal_state *)s;
+    s->strm = strm;
+
+    s->noheader = noheader;
+    s->w_bits = windowBits;
+    s->w_size = 1 << s->w_bits;
+    s->w_mask = s->w_size - 1;
+
+    s->hash_bits = memLevel + 7;
+    s->hash_size = 1 << s->hash_bits;
+    s->hash_mask = s->hash_size - 1;
+    s->hash_shift = ((s->hash_bits+3 -1)/3);
+
+    s->window = (Bytef *) (*((strm)->zalloc))((strm)->opaque, (s->w_size), (2*sizeof(Byte)));
+    s->prev = (Posf *) (*((strm)->zalloc))((strm)->opaque, (s->w_size), (sizeof(Pos)));
+    s->head = (Posf *) (*((strm)->zalloc))((strm)->opaque, (s->hash_size), (sizeof(Pos)));
+
+    s->lit_bufsize = 1 << (memLevel + 6);
+
+    overlay = (ushf *) (*((strm)->zalloc))((strm)->opaque, (s->lit_bufsize), (sizeof(ush)+2));
+    s->pending_buf = (uchf *) overlay;
+    s->pending_buf_size = (ulg)s->lit_bufsize * (sizeof(ush)+2L);
+
+    if (s->window == 0 || s->prev == 0 || s->head == 0 ||
+        s->pending_buf == 0) {
+        strm->msg = (char*)z_errmsg[2 -((-4))];
+        deflateEnd (strm);
+        return (-4);
+    }
+    s->d_buf = overlay + s->lit_bufsize/sizeof(ush);
+    s->l_buf = s->pending_buf + (1+sizeof(ush))*s->lit_bufsize;
+
+    s->level = level;
+    s->strategy = strategy;
+    s->method = (Byte)method;
+
+    return deflateReset(strm);
+}
+
+
+int deflateSetDictionary (strm, dictionary, dictLength)
+    z_streamp strm;
+    const Bytef *dictionary;
+    uInt dictLength;
+{
+    deflate_state *s;
+    uInt length = dictLength;
+    uInt n;
+    IPos hash_head = 0;
+
+    if (strm == 0 || strm->state == 0 || dictionary == 0 ||
+        strm->state->status != 42) return (-2);
+
+    s = strm->state;
+    strm->adler = adler32(strm->adler, dictionary, dictLength);
+
+    if (length < 3) return 0;
+    if (length > ((s)->w_size-(258 +3 +1))) {
+ length = ((s)->w_size-(258 +3 +1));
+
+ dictionary += dictLength - length;
+
+    }
+    memcpy(s->window, dictionary, length);
+    s->strstart = length;
+    s->block_start = (long)length;
+
+
+
+
+
+    s->ins_h = s->window[0];
+    (s->ins_h = (((s->ins_h)<<s->hash_shift) ^ (s->window[1])) & s->hash_mask);
+    for (n = 0; n <= length - 3; n++) {
+ ((s->ins_h = (((s->ins_h)<<s->hash_shift) ^ (s->window[(n) + (3 -1)])) & s->hash_mask), s->prev[(n) & s->w_mask] = hash_head = s->head[s->ins_h], s->head[s->ins_h] = (Pos)(n));
+    }
+    if (hash_head) hash_head = 0;
+    return 0;
+}
+
+
+int deflateReset (strm)
+    z_streamp strm;
+{
+    deflate_state *s;
+
+    if (strm == 0 || strm->state == 0 ||
+        strm->zalloc == 0 || strm->zfree == 0) return (-2);
+
+    strm->total_in = strm->total_out = 0;
+    strm->msg = 0;
+    strm->data_type = 2;
+
+    s = (deflate_state *)strm->state;
+    s->pending = 0;
+    s->pending_out = s->pending_buf;
+
+    if (s->noheader < 0) {
+        s->noheader = 0;
+    }
+    s->status = s->noheader ? 113 : 42;
+    strm->adler = 1;
+    s->last_flush = 0;
+
+    _tr_init(s);
+    lm_init(s);
+
+    return 0;
+}
+
+
+int deflateParams(strm, level, strategy)
+    z_streamp strm;
+    int level;
+    int strategy;
+{
+    deflate_state *s;
+    compress_func func;
+    int err = 0;
+
+    if (strm == 0 || strm->state == 0) return (-2);
+    s = strm->state;
+
+    if (level == (-1)) {
+ level = 6;
+    }
+    if (level < 0 || level > 9 || strategy < 0 || strategy > 2) {
+ return (-2);
+    }
+    func = configuration_table[s->level].func;
+
+    if (func != configuration_table[level].func && strm->total_in != 0) {
+
+ err = deflate(strm, 1);
+    }
+    if (s->level != level) {
+ s->level = level;
+ s->max_lazy_match = configuration_table[level].max_lazy;
+ s->good_match = configuration_table[level].good_length;
+ s->nice_match = configuration_table[level].nice_length;
+ s->max_chain_length = configuration_table[level].max_chain;
+    }
+    s->strategy = strategy;
+    return err;
+}
+
+
+
+
+
+
+static void putShortMSB (s, b)
+    deflate_state *s;
+    uInt b;
+{
+    {s->pending_buf[s->pending++] = ((Byte)(b >> 8));};
+    {s->pending_buf[s->pending++] = ((Byte)(b & 0xff));};
+}
+
+
+
+
+
+
+
+static void flush_pending(strm)
+    z_streamp strm;
+{
+    unsigned len = strm->state->pending;
+
+    if (len > strm->avail_out) len = strm->avail_out;
+    if (len == 0) return;
+
+    memcpy(strm->next_out, strm->state->pending_out, len);
+    strm->next_out += len;
+    strm->state->pending_out += len;
+    strm->total_out += len;
+    strm->avail_out -= len;
+    strm->state->pending -= len;
+    if (strm->state->pending == 0) {
+        strm->state->pending_out = strm->state->pending_buf;
+    }
+}
+
+
+int deflate (strm, flush)
+    z_streamp strm;
+    int flush;
+{
+    int old_flush;
+    deflate_state *s;
+
+    if (strm == 0 || strm->state == 0 ||
+ flush > 4 || flush < 0) {
+        return (-2);
+    }
+    s = strm->state;
+
+    if (strm->next_out == 0 ||
+        (strm->next_in == 0 && strm->avail_in != 0) ||
+ (s->status == 666 && flush != 4)) {
+        return (strm->msg = (char*)z_errmsg[2 -((-2))], ((-2)));
+    }
+    if (strm->avail_out == 0) return (strm->msg = (char*)z_errmsg[2 -((-5))], ((-5)));
+
+    s->strm = strm;
+    old_flush = s->last_flush;
+    s->last_flush = flush;
+
+
+    if (s->status == 42) {
+
+        uInt header = (8 + ((s->w_bits-8)<<4)) << 8;
+        uInt level_flags = (s->level-1) >> 1;
+
+        if (level_flags > 3) level_flags = 3;
+        header |= (level_flags << 6);
+ if (s->strstart != 0) header |= 0x20;
+        header += 31 - (header % 31);
+
+        s->status = 113;
+        putShortMSB(s, header);
+
+
+ if (s->strstart != 0) {
+     putShortMSB(s, (uInt)(strm->adler >> 16));
+     putShortMSB(s, (uInt)(strm->adler & 0xffff));
+ }
+ strm->adler = 1L;
+    }
+
+
+    if (s->pending != 0) {
+        flush_pending(strm);
+        if (strm->avail_out == 0) {
+
+
+
+
+
+
+     s->last_flush = -1;
+     return 0;
+ }
+
+
+
+
+
+    } else if (strm->avail_in == 0 && flush <= old_flush &&
+        flush != 4) {
+        return (strm->msg = (char*)z_errmsg[2 -((-5))], ((-5)));
+    }
+
+
+    if (s->status == 666 && strm->avail_in != 0) {
+        return (strm->msg = (char*)z_errmsg[2 -((-5))], ((-5)));
+    }
+
+
+
+    if (strm->avail_in != 0 || s->lookahead != 0 ||
+        (flush != 0 && s->status != 666)) {
+        block_state bstate;
+
+ bstate = (*(configuration_table[s->level].func))(s, flush);
+
+        if (bstate == finish_started || bstate == finish_done) {
+            s->status = 666;
+        }
+        if (bstate == need_more || bstate == finish_started) {
+     if (strm->avail_out == 0) {
+         s->last_flush = -1;
+     }
+     return 0;
+
+
+
+
+
+
+
+ }
+        if (bstate == block_done) {
+            if (flush == 1) {
+                _tr_align(s);
+            } else {
+                _tr_stored_block(s, (char*)0, 0L, 0);
+
+
+
+                if (flush == 3) {
+                    s->head[s->hash_size-1] = 0; memset((Bytef *)s->head, 0, (unsigned)(s->hash_size-1)*sizeof(*s->head));;
+                }
+            }
+            flush_pending(strm);
+     if (strm->avail_out == 0) {
+       s->last_flush = -1;
+       return 0;
+     }
+        }
+    }
+    ;
+
+    if (flush != 4) return 0;
+    if (s->noheader) return 1;
+
+
+    putShortMSB(s, (uInt)(strm->adler >> 16));
+    putShortMSB(s, (uInt)(strm->adler & 0xffff));
+    flush_pending(strm);
+
+
+
+    s->noheader = -1;
+    return s->pending != 0 ? 0 : 1;
+}
+
+
+int deflateEnd (strm)
+    z_streamp strm;
+{
+    int status;
+
+    if (strm == 0 || strm->state == 0) return (-2);
+
+    status = strm->state->status;
+    if (status != 42 && status != 113 &&
+ status != 666) {
+      return (-2);
+    }
+
+
+    {if (strm->state->pending_buf) (*((strm)->zfree))((strm)->opaque, (voidpf)(strm->state->pending_buf));};
+    {if (strm->state->head) (*((strm)->zfree))((strm)->opaque, (voidpf)(strm->state->head));};
+    {if (strm->state->prev) (*((strm)->zfree))((strm)->opaque, (voidpf)(strm->state->prev));};
+    {if (strm->state->window) (*((strm)->zfree))((strm)->opaque, (voidpf)(strm->state->window));};
+
+    (*((strm)->zfree))((strm)->opaque, (voidpf)(strm->state));
+    strm->state = 0;
+
+    return status == 113 ? (-3) : 0;
+}
+
+
+
+
+
+
+int deflateCopy (dest, source)
+    z_streamp dest;
+    z_streamp source;
+{
+
+
+
+    deflate_state *ds;
+    deflate_state *ss;
+    ushf *overlay;
+
+
+    if (source == 0 || dest == 0 || source->state == 0) {
+        return (-2);
+    }
+
+    ss = source->state;
+
+    *dest = *source;
+
+    ds = (deflate_state *) (*((dest)->zalloc))((dest)->opaque, (1), (sizeof(deflate_state)));
+    if (ds == 0) return (-4);
+    dest->state = (struct internal_state *) ds;
+    *ds = *ss;
+    ds->strm = dest;
+
+    ds->window = (Bytef *) (*((dest)->zalloc))((dest)->opaque, (ds->w_size), (2*sizeof(Byte)));
+    ds->prev = (Posf *) (*((dest)->zalloc))((dest)->opaque, (ds->w_size), (sizeof(Pos)));
+    ds->head = (Posf *) (*((dest)->zalloc))((dest)->opaque, (ds->hash_size), (sizeof(Pos)));
+    overlay = (ushf *) (*((dest)->zalloc))((dest)->opaque, (ds->lit_bufsize), (sizeof(ush)+2));
+    ds->pending_buf = (uchf *) overlay;
+
+    if (ds->window == 0 || ds->prev == 0 || ds->head == 0 ||
+        ds->pending_buf == 0) {
+        deflateEnd (dest);
+        return (-4);
+    }
+
+    memcpy(ds->window, ss->window, ds->w_size * 2 * sizeof(Byte));
+    memcpy(ds->prev, ss->prev, ds->w_size * sizeof(Pos));
+    memcpy(ds->head, ss->head, ds->hash_size * sizeof(Pos));
+    memcpy(ds->pending_buf, ss->pending_buf, (uInt)ds->pending_buf_size);
+
+    ds->pending_out = ds->pending_buf + (ss->pending_out - ss->pending_buf);
+    ds->d_buf = overlay + ds->lit_bufsize/sizeof(ush);
+    ds->l_buf = ds->pending_buf + (1+sizeof(ush))*ds->lit_bufsize;
+
+    ds->l_desc.dyn_tree = ds->dyn_ltree;
+    ds->d_desc.dyn_tree = ds->dyn_dtree;
+    ds->bl_desc.dyn_tree = ds->bl_tree;
+
+    return 0;
+
+}
+static int read_buf(strm, buf, size)
+    z_streamp strm;
+    Bytef *buf;
+    unsigned size;
+{
+    unsigned len = strm->avail_in;
+
+    if (len > size) len = size;
+    if (len == 0) return 0;
+
+    strm->avail_in -= len;
+
+    if (!strm->state->noheader) {
+        strm->adler = adler32(strm->adler, strm->next_in, len);
+    }
+    memcpy(buf, strm->next_in, len);
+    strm->next_in += len;
+    strm->total_in += len;
+
+    return (int)len;
+}
+
+
+
+
+static void lm_init (s)
+    deflate_state *s;
+{
+    s->window_size = (ulg)2L*s->w_size;
+
+    s->head[s->hash_size-1] = 0; memset((Bytef *)s->head, 0, (unsigned)(s->hash_size-1)*sizeof(*s->head));;
+
+
+
+    s->max_lazy_match = configuration_table[s->level].max_lazy;
+    s->good_match = configuration_table[s->level].good_length;
+    s->nice_match = configuration_table[s->level].nice_length;
+    s->max_chain_length = configuration_table[s->level].max_chain;
+
+    s->strstart = 0;
+    s->block_start = 0L;
+    s->lookahead = 0;
+    s->match_length = s->prev_length = 3 -1;
+    s->match_available = 0;
+    s->ins_h = 0;
+
+
+
+}
+static uInt longest_match(s, cur_match)
+    deflate_state *s;
+    IPos cur_match;
+{
+    unsigned chain_length = s->max_chain_length;
+    register Bytef *scan = s->window + s->strstart;
+    register Bytef *match;
+    register int len;
+    int best_len = s->prev_length;
+    int nice_match = s->nice_match;
+    IPos limit = s->strstart > (IPos)((s)->w_size-(258 +3 +1)) ?
+        s->strstart - (IPos)((s)->w_size-(258 +3 +1)) : 0;
+
+
+
+    Posf *prev = s->prev;
+    uInt wmask = s->w_mask;
+    register Bytef *strend = s->window + s->strstart + 258;
+    register Byte scan_end1 = scan[best_len-1];
+    register Byte scan_end = scan[best_len];
+
+
+
+
+
+    ;
+
+
+    if (s->prev_length >= s->good_match) {
+        chain_length >>= 2;
+    }
+
+
+
+    if ((uInt)nice_match > s->lookahead) nice_match = s->lookahead;
+
+    ;
+
+    do {
+        ;
+        match = s->window + cur_match;
+        if (match[best_len] != scan_end ||
+            match[best_len-1] != scan_end1 ||
+            *match != *scan ||
+            *++match != scan[1]) continue;
+
+
+
+
+
+
+
+        scan += 2, match++;
+        ;
+
+
+
+
+        do {
+        } while (*++scan == *++match && *++scan == *++match &&
+                 *++scan == *++match && *++scan == *++match &&
+                 *++scan == *++match && *++scan == *++match &&
+                 *++scan == *++match && *++scan == *++match &&
+                 scan < strend);
+
+        ;
+
+        len = 258 - (int)(strend - scan);
+        scan = strend - 258;
+
+
+
+        if (len > best_len) {
+            s->match_start = cur_match;
+            best_len = len;
+            if (len >= nice_match) break;
+
+
+
+            scan_end1 = scan[best_len-1];
+            scan_end = scan[best_len];
+
+        }
+    } while ((cur_match = prev[cur_match & wmask]) > limit
+             && --chain_length != 0);
+
+    if ((uInt)best_len <= s->lookahead) return (uInt)best_len;
+    return s->lookahead;
+}
+static void fill_window(s)
+    deflate_state *s;
+{
+    register unsigned n, m;
+    register Posf *p;
+    unsigned more;
+    uInt wsize = s->w_size;
+
+    do {
+        more = (unsigned)(s->window_size -(ulg)s->lookahead -(ulg)s->strstart);
+
+
+        if (more == 0 && s->strstart == 0 && s->lookahead == 0) {
+            more = wsize;
+
+        } else if (more == (unsigned)(-1)) {
+
+
+
+            more--;
+
+
+
+
+        } else if (s->strstart >= wsize+((s)->w_size-(258 +3 +1))) {
+
+            memcpy(s->window, s->window+wsize, (unsigned)wsize);
+            s->match_start -= wsize;
+            s->strstart -= wsize;
+            s->block_start -= (long) wsize;
+
+
+
+
+
+
+
+     n = s->hash_size;
+     p = &s->head[n];
+     do {
+  m = *--p;
+  *p = (Pos)(m >= wsize ? m-wsize : 0);
+     } while (--n);
+
+     n = wsize;
+
+     p = &s->prev[n];
+     do {
+  m = *--p;
+  *p = (Pos)(m >= wsize ? m-wsize : 0);
+
+
+
+     } while (--n);
+
+            more += wsize;
+        }
+        if (s->strm->avail_in == 0) return;
+        ;
+
+        n = read_buf(s->strm, s->window + s->strstart + s->lookahead, more);
+        s->lookahead += n;
+
+
+        if (s->lookahead >= 3) {
+            s->ins_h = s->window[s->strstart];
+            (s->ins_h = (((s->ins_h)<<s->hash_shift) ^ (s->window[s->strstart+1])) & s->hash_mask);
+
+
+
+        }
+
+
+
+
+    } while (s->lookahead < (258 +3 +1) && s->strm->avail_in != 0);
+}
+static block_state deflate_stored(s, flush)
+    deflate_state *s;
+    int flush;
+{
+
+
+
+    ulg max_block_size = 0xffff;
+    ulg max_start;
+
+    if (max_block_size > s->pending_buf_size - 5) {
+        max_block_size = s->pending_buf_size - 5;
+    }
+
+
+    for (;;) {
+
+        if (s->lookahead <= 1) {
+
+           
+                                                         ;
+
+            fill_window(s);
+            if (s->lookahead == 0 && flush == 0) return need_more;
+
+            if (s->lookahead == 0) break;
+        }
+ ;
+
+ s->strstart += s->lookahead;
+ s->lookahead = 0;
+
+
+  max_start = s->block_start + max_block_size;
+        if (s->strstart == 0 || (ulg)s->strstart >= max_start) {
+
+     s->lookahead = (uInt)(s->strstart - max_start);
+     s->strstart = (uInt)max_start;
+            { { _tr_flush_block(s, (s->block_start >= 0L ? (charf *)&s->window[(unsigned)s->block_start] : (charf *)0), (ulg)((long)s->strstart - s->block_start), (0)); s->block_start = s->strstart; flush_pending(s->strm); ; }; if (s->strm->avail_out == 0) return (0) ? finish_started : need_more; };
+ }
+
+
+
+        if (s->strstart - (uInt)s->block_start >= ((s)->w_size-(258 +3 +1))) {
+            { { _tr_flush_block(s, (s->block_start >= 0L ? (charf *)&s->window[(unsigned)s->block_start] : (charf *)0), (ulg)((long)s->strstart - s->block_start), (0)); s->block_start = s->strstart; flush_pending(s->strm); ; }; if (s->strm->avail_out == 0) return (0) ? finish_started : need_more; };
+ }
+    }
+    { { _tr_flush_block(s, (s->block_start >= 0L ? (charf *)&s->window[(unsigned)s->block_start] : (charf *)0), (ulg)((long)s->strstart - s->block_start), (flush == 4)); s->block_start = s->strstart; flush_pending(s->strm); ; }; if (s->strm->avail_out == 0) return (flush == 4) ? finish_started : need_more; };
+    return flush == 4 ? finish_done : block_done;
+}
+static block_state deflate_fast(s, flush)
+    deflate_state *s;
+    int flush;
+{
+    IPos hash_head = 0;
+    int bflush;
+
+    for (;;) {
+
+
+
+
+
+        if (s->lookahead < (258 +3 +1)) {
+            fill_window(s);
+            if (s->lookahead < (258 +3 +1) && flush == 0) {
+         return need_more;
+     }
+            if (s->lookahead == 0) break;
+        }
+
+
+
+
+        if (s->lookahead >= 3) {
+            ((s->ins_h = (((s->ins_h)<<s->hash_shift) ^ (s->window[(s->strstart) + (3 -1)])) & s->hash_mask), s->prev[(s->strstart) & s->w_mask] = hash_head = s->head[s->ins_h], s->head[s->ins_h] = (Pos)(s->strstart));
+        }
+
+
+
+
+        if (hash_head != 0 && s->strstart - hash_head <= ((s)->w_size-(258 +3 +1))) {
+
+
+
+
+            if (s->strategy != 2) {
+                s->match_length = longest_match (s, hash_head);
+            }
+
+        }
+        if (s->match_length >= 3) {
+            ;
+
+            { uch len = (s->match_length - 3); ush dist = (s->strstart - s->match_start); s->d_buf[s->last_lit] = dist; s->l_buf[s->last_lit++] = len; dist--; s->dyn_ltree[_length_code[len]+256 +1].fc.freq++; s->dyn_dtree[((dist) < 256 ? _dist_code[dist] : _dist_code[256+((dist)>>7)])].fc.freq++; bflush = (s->last_lit == s->lit_bufsize-1); }
+                                                               ;
+
+            s->lookahead -= s->match_length;
+
+
+
+
+
+            if (s->match_length <= s->max_lazy_match &&
+                s->lookahead >= 3) {
+                s->match_length--;
+                do {
+                    s->strstart++;
+                    ((s->ins_h = (((s->ins_h)<<s->hash_shift) ^ (s->window[(s->strstart) + (3 -1)])) & s->hash_mask), s->prev[(s->strstart) & s->w_mask] = hash_head = s->head[s->ins_h], s->head[s->ins_h] = (Pos)(s->strstart));
+
+
+
+                } while (--s->match_length != 0);
+                s->strstart++;
+            } else
+
+     {
+                s->strstart += s->match_length;
+                s->match_length = 0;
+                s->ins_h = s->window[s->strstart];
+                (s->ins_h = (((s->ins_h)<<s->hash_shift) ^ (s->window[s->strstart+1])) & s->hash_mask);
+
+
+
+
+
+
+            }
+        } else {
+
+            ;
+            { uch cc = (s->window[s->strstart]); s->d_buf[s->last_lit] = 0; s->l_buf[s->last_lit++] = cc; s->dyn_ltree[cc].fc.freq++; bflush = (s->last_lit == s->lit_bufsize-1); };
+            s->lookahead--;
+            s->strstart++;
+        }
+        if (bflush) { { _tr_flush_block(s, (s->block_start >= 0L ? (charf *)&s->window[(unsigned)s->block_start] : (charf *)0), (ulg)((long)s->strstart - s->block_start), (0)); s->block_start = s->strstart; flush_pending(s->strm); ; }; if (s->strm->avail_out == 0) return (0) ? finish_started : need_more; };
+    }
+    { { _tr_flush_block(s, (s->block_start >= 0L ? (charf *)&s->window[(unsigned)s->block_start] : (charf *)0), (ulg)((long)s->strstart - s->block_start), (flush == 4)); s->block_start = s->strstart; flush_pending(s->strm); ; }; if (s->strm->avail_out == 0) return (flush == 4) ? finish_started : need_more; };
+    return flush == 4 ? finish_done : block_done;
+}
+
+
+
+
+
+
+static block_state deflate_slow(s, flush)
+    deflate_state *s;
+    int flush;
+{
+    IPos hash_head = 0;
+    int bflush;
+
+
+    for (;;) {
+
+
+
+
+
+        if (s->lookahead < (258 +3 +1)) {
+            fill_window(s);
+            if (s->lookahead < (258 +3 +1) && flush == 0) {
+         return need_more;
+     }
+            if (s->lookahead == 0) break;
+        }
+
+
+
+
+        if (s->lookahead >= 3) {
+            ((s->ins_h = (((s->ins_h)<<s->hash_shift) ^ (s->window[(s->strstart) + (3 -1)])) & s->hash_mask), s->prev[(s->strstart) & s->w_mask] = hash_head = s->head[s->ins_h], s->head[s->ins_h] = (Pos)(s->strstart));
+        }
+
+
+
+        s->prev_length = s->match_length, s->prev_match = s->match_start;
+        s->match_length = 3 -1;
+
+        if (hash_head != 0 && s->prev_length < s->max_lazy_match &&
+            s->strstart - hash_head <= ((s)->w_size-(258 +3 +1))) {
+
+
+
+
+            if (s->strategy != 2) {
+                s->match_length = longest_match (s, hash_head);
+            }
+
+
+            if (s->match_length <= 5 && (s->strategy == 1 ||
+                 (s->match_length == 3 &&
+                  s->strstart - s->match_start > 4096))) {
+
+
+
+
+                s->match_length = 3 -1;
+            }
+        }
+
+
+
+        if (s->prev_length >= 3 && s->match_length <= s->prev_length) {
+            uInt max_insert = s->strstart + s->lookahead - 3;
+
+
+            ;
+
+            { uch len = (s->prev_length - 3); ush dist = (s->strstart -1 - s->prev_match); s->d_buf[s->last_lit] = dist; s->l_buf[s->last_lit++] = len; dist--; s->dyn_ltree[_length_code[len]+256 +1].fc.freq++; s->dyn_dtree[((dist) < 256 ? _dist_code[dist] : _dist_code[256+((dist)>>7)])].fc.freq++; bflush = (s->last_lit == s->lit_bufsize-1); }
+                                         ;
+
+
+
+
+
+
+            s->lookahead -= s->prev_length-1;
+            s->prev_length -= 2;
+            do {
+                if (++s->strstart <= max_insert) {
+                    ((s->ins_h = (((s->ins_h)<<s->hash_shift) ^ (s->window[(s->strstart) + (3 -1)])) & s->hash_mask), s->prev[(s->strstart) & s->w_mask] = hash_head = s->head[s->ins_h], s->head[s->ins_h] = (Pos)(s->strstart));
+                }
+            } while (--s->prev_length != 0);
+            s->match_available = 0;
+            s->match_length = 3 -1;
+            s->strstart++;
+
+            if (bflush) { { _tr_flush_block(s, (s->block_start >= 0L ? (charf *)&s->window[(unsigned)s->block_start] : (charf *)0), (ulg)((long)s->strstart - s->block_start), (0)); s->block_start = s->strstart; flush_pending(s->strm); ; }; if (s->strm->avail_out == 0) return (0) ? finish_started : need_more; };
+
+        } else if (s->match_available) {
+
+
+
+
+            ;
+     { uch cc = (s->window[s->strstart-1]); s->d_buf[s->last_lit] = 0; s->l_buf[s->last_lit++] = cc; s->dyn_ltree[cc].fc.freq++; bflush = (s->last_lit == s->lit_bufsize-1); };
+     if (bflush) {
+                { _tr_flush_block(s, (s->block_start >= 0L ? (charf *)&s->window[(unsigned)s->block_start] : (charf *)0), (ulg)((long)s->strstart - s->block_start), (0)); s->block_start = s->strstart; flush_pending(s->strm); ; };
+            }
+            s->strstart++;
+            s->lookahead--;
+            if (s->strm->avail_out == 0) return need_more;
+        } else {
+
+
+
+            s->match_available = 1;
+            s->strstart++;
+            s->lookahead--;
+        }
+    }
+    ;
+    if (s->match_available) {
+        ;
+        { uch cc = (s->window[s->strstart-1]); s->d_buf[s->last_lit] = 0; s->l_buf[s->last_lit++] = cc; s->dyn_ltree[cc].fc.freq++; bflush = (s->last_lit == s->lit_bufsize-1); };
+        s->match_available = 0;
+    }
+    { { _tr_flush_block(s, (s->block_start >= 0L ? (charf *)&s->window[(unsigned)s->block_start] : (charf *)0), (ulg)((long)s->strstart - s->block_start), (flush == 4)); s->block_start = s->strstart; flush_pending(s->strm); ; }; if (s->strm->avail_out == 0) return (flush == 4) ? finish_started : need_more; };
+    return flush == 4 ? finish_done : block_done;
+}
+
+/* { dg-regexp "\[^\n\r\]+: warning: analysis bailed out early \\(\[0-9\]+ 'after-snode' enodes; \[0-9\]+ enodes\\)" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-zlib-1.1.3-infblock.c b/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-zlib-1.1.3-infblock.c
new file mode 100644
index 0000000..3fea1de
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-zlib-1.1.3-infblock.c
@@ -0,0 +1,3363 @@
+
+
+
+
+
+typedef unsigned char Byte;
+
+typedef unsigned int uInt;
+typedef unsigned long uLong;
+
+
+
+
+
+   typedef Byte Bytef;
+
+typedef char charf;
+typedef int intf;
+typedef uInt uIntf;
+typedef uLong uLongf;
+
+
+   typedef void *voidpf;
+   typedef void *voidp;
+
+
+
+
+
+
+
+
+
+
+
+
+typedef unsigned char __u_char;
+typedef unsigned short int __u_short;
+typedef unsigned int __u_int;
+typedef unsigned long int __u_long;
+
+
+typedef signed char __int8_t;
+typedef unsigned char __uint8_t;
+typedef signed short int __int16_t;
+typedef unsigned short int __uint16_t;
+typedef signed int __int32_t;
+typedef unsigned int __uint32_t;
+
+typedef signed long int __int64_t;
+typedef unsigned long int __uint64_t;
+
+
+
+
+
+
+
+typedef long int __quad_t;
+typedef unsigned long int __u_quad_t;
+
+
+typedef unsigned long int __dev_t;
+typedef unsigned int __uid_t;
+typedef unsigned int __gid_t;
+typedef unsigned long int __ino_t;
+typedef unsigned long int __ino64_t;
+typedef unsigned int __mode_t;
+typedef unsigned long int __nlink_t;
+typedef long int __off_t;
+typedef long int __off64_t;
+typedef int __pid_t;
+typedef struct { int __val[2]; } __fsid_t;
+typedef long int __clock_t;
+typedef unsigned long int __rlim_t;
+typedef unsigned long int __rlim64_t;
+typedef unsigned int __id_t;
+typedef long int __time_t;
+typedef unsigned int __useconds_t;
+typedef long int __suseconds_t;
+
+typedef int __daddr_t;
+typedef int __key_t;
+
+
+typedef int __clockid_t;
+
+
+typedef void * __timer_t;
+
+
+typedef long int __blksize_t;
+
+
+
+
+typedef long int __blkcnt_t;
+typedef long int __blkcnt64_t;
+
+
+typedef unsigned long int __fsblkcnt_t;
+typedef unsigned long int __fsblkcnt64_t;
+
+
+typedef unsigned long int __fsfilcnt_t;
+typedef unsigned long int __fsfilcnt64_t;
+
+
+typedef long int __fsword_t;
+
+typedef long int __ssize_t;
+
+
+typedef long int __syscall_slong_t;
+
+typedef unsigned long int __syscall_ulong_t;
+
+
+
+typedef __off64_t __loff_t;
+typedef __quad_t *__qaddr_t;
+typedef char *__caddr_t;
+
+
+typedef long int __intptr_t;
+
+
+typedef unsigned int __socklen_t;
+
+
+
+typedef __u_char u_char;
+typedef __u_short u_short;
+typedef __u_int u_int;
+typedef __u_long u_long;
+typedef __quad_t quad_t;
+typedef __u_quad_t u_quad_t;
+typedef __fsid_t fsid_t;
+
+
+
+
+typedef __loff_t loff_t;
+
+
+
+typedef __ino_t ino_t;
+typedef __dev_t dev_t;
+
+
+
+
+typedef __gid_t gid_t;
+
+
+
+
+typedef __mode_t mode_t;
+
+
+
+
+typedef __nlink_t nlink_t;
+
+
+
+
+typedef __uid_t uid_t;
+
+
+
+
+
+typedef __off_t off_t;
+typedef __pid_t pid_t;
+
+
+
+
+
+typedef __id_t id_t;
+
+
+
+
+typedef __ssize_t ssize_t;
+
+
+
+
+
+typedef __daddr_t daddr_t;
+typedef __caddr_t caddr_t;
+
+
+
+
+
+typedef __key_t key_t;
+
+
+typedef __clock_t clock_t;
+
+
+
+
+
+typedef __time_t time_t;
+
+
+
+typedef __clockid_t clockid_t;
+typedef __timer_t timer_t;
+typedef long unsigned int size_t;
+
+
+
+typedef unsigned long int ulong;
+typedef unsigned short int ushort;
+typedef unsigned int uint;
+typedef int int8_t __attribute__ ((__mode__ (__QI__)));
+typedef int int16_t __attribute__ ((__mode__ (__HI__)));
+typedef int int32_t __attribute__ ((__mode__ (__SI__)));
+typedef int int64_t __attribute__ ((__mode__ (__DI__)));
+
+
+typedef unsigned int u_int8_t __attribute__ ((__mode__ (__QI__)));
+typedef unsigned int u_int16_t __attribute__ ((__mode__ (__HI__)));
+typedef unsigned int u_int32_t __attribute__ ((__mode__ (__SI__)));
+typedef unsigned int u_int64_t __attribute__ ((__mode__ (__DI__)));
+
+typedef int register_t __attribute__ ((__mode__ (__word__)));
+
+
+
+
+
+
+static __inline unsigned int
+__bswap_32 (unsigned int __bsx)
+{
+  return __builtin_bswap32 (__bsx);
+}
+static __inline __uint64_t
+__bswap_64 (__uint64_t __bsx)
+{
+  return __builtin_bswap64 (__bsx);
+}
+
+
+
+
+typedef int __sig_atomic_t;
+
+
+
+
+typedef struct
+  {
+    unsigned long int __val[(1024 / (8 * sizeof (unsigned long int)))];
+  } __sigset_t;
+
+
+
+typedef __sigset_t sigset_t;
+
+
+
+
+
+struct timespec
+  {
+    __time_t tv_sec;
+    __syscall_slong_t tv_nsec;
+  };
+
+struct timeval
+  {
+    __time_t tv_sec;
+    __suseconds_t tv_usec;
+  };
+
+
+typedef __suseconds_t suseconds_t;
+
+
+
+
+
+typedef long int __fd_mask;
+typedef struct
+  {
+
+
+
+
+
+
+    __fd_mask __fds_bits[1024 / (8 * (int) sizeof (__fd_mask))];
+
+
+  } fd_set;
+
+
+
+
+
+
+typedef __fd_mask fd_mask;
+
+extern int select (int __nfds, fd_set *__restrict __readfds,
+     fd_set *__restrict __writefds,
+     fd_set *__restrict __exceptfds,
+     struct timeval *__restrict __timeout);
+extern int pselect (int __nfds, fd_set *__restrict __readfds,
+      fd_set *__restrict __writefds,
+      fd_set *__restrict __exceptfds,
+      const struct timespec *__restrict __timeout,
+      const __sigset_t *__restrict __sigmask);
+
+
+
+
+
+__extension__
+extern unsigned int gnu_dev_major (unsigned long long int __dev)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__));
+__extension__
+extern unsigned int gnu_dev_minor (unsigned long long int __dev)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__));
+__extension__
+extern unsigned long long int gnu_dev_makedev (unsigned int __major,
+            unsigned int __minor)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__));
+
+
+__extension__ extern __inline __attribute__ ((__gnu_inline__)) __attribute__ ((__const__)) unsigned int
+__attribute__ ((__nothrow__ , __leaf__)) gnu_dev_major (unsigned long long int __dev)
+{
+  return ((__dev >> 8) & 0xfff) | ((unsigned int) (__dev >> 32) & ~0xfff);
+}
+
+__extension__ extern __inline __attribute__ ((__gnu_inline__)) __attribute__ ((__const__)) unsigned int
+__attribute__ ((__nothrow__ , __leaf__)) gnu_dev_minor (unsigned long long int __dev)
+{
+  return (__dev & 0xff) | ((unsigned int) (__dev >> 12) & ~0xff);
+}
+
+__extension__ extern __inline __attribute__ ((__gnu_inline__)) __attribute__ ((__const__)) unsigned long long int
+__attribute__ ((__nothrow__ , __leaf__)) gnu_dev_makedev (unsigned int __major, unsigned int __minor)
+{
+  return ((__minor & 0xff) | ((__major & 0xfff) << 8)
+   | (((unsigned long long int) (__minor & ~0xff)) << 12)
+   | (((unsigned long long int) (__major & ~0xfff)) << 32));
+}
+
+
+
+
+
+
+
+typedef __blksize_t blksize_t;
+
+
+
+
+
+
+typedef __blkcnt_t blkcnt_t;
+
+
+
+typedef __fsblkcnt_t fsblkcnt_t;
+
+
+
+typedef __fsfilcnt_t fsfilcnt_t;
+typedef unsigned long int pthread_t;
+
+
+union pthread_attr_t
+{
+  char __size[56];
+  long int __align;
+};
+
+typedef union pthread_attr_t pthread_attr_t;
+
+
+
+
+
+typedef struct __pthread_internal_list
+{
+  struct __pthread_internal_list *__prev;
+  struct __pthread_internal_list *__next;
+} __pthread_list_t;
+typedef union
+{
+  struct __pthread_mutex_s
+  {
+    int __lock;
+    unsigned int __count;
+    int __owner;
+
+    unsigned int __nusers;
+
+
+
+    int __kind;
+
+    short __spins;
+    short __elision;
+    __pthread_list_t __list;
+  } __data;
+  char __size[40];
+  long int __align;
+} pthread_mutex_t;
+
+typedef union
+{
+  char __size[4];
+  int __align;
+} pthread_mutexattr_t;
+
+
+
+
+typedef union
+{
+  struct
+  {
+    int __lock;
+    unsigned int __futex;
+    __extension__ unsigned long long int __total_seq;
+    __extension__ unsigned long long int __wakeup_seq;
+    __extension__ unsigned long long int __woken_seq;
+    void *__mutex;
+    unsigned int __nwaiters;
+    unsigned int __broadcast_seq;
+  } __data;
+  char __size[48];
+  __extension__ long long int __align;
+} pthread_cond_t;
+
+typedef union
+{
+  char __size[4];
+  int __align;
+} pthread_condattr_t;
+
+
+
+typedef unsigned int pthread_key_t;
+
+
+
+typedef int pthread_once_t;
+
+
+
+
+
+typedef union
+{
+
+  struct
+  {
+    int __lock;
+    unsigned int __nr_readers;
+    unsigned int __readers_wakeup;
+    unsigned int __writer_wakeup;
+    unsigned int __nr_readers_queued;
+    unsigned int __nr_writers_queued;
+    int __writer;
+    int __shared;
+    unsigned long int __pad1;
+    unsigned long int __pad2;
+
+
+    unsigned int __flags;
+
+  } __data;
+  char __size[56];
+  long int __align;
+} pthread_rwlock_t;
+
+typedef union
+{
+  char __size[8];
+  long int __align;
+} pthread_rwlockattr_t;
+
+
+
+
+
+typedef volatile int pthread_spinlock_t;
+
+
+
+
+typedef union
+{
+  char __size[32];
+  long int __align;
+} pthread_barrier_t;
+
+typedef union
+{
+  char __size[4];
+  int __align;
+} pthread_barrierattr_t;
+
+
+
+
+
+
+
+typedef __useconds_t useconds_t;
+typedef __intptr_t intptr_t;
+
+
+
+
+
+
+typedef __socklen_t socklen_t;
+extern int access (const char *__name, int __type) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+extern int faccessat (int __fd, const char *__file, int __type, int __flag)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2))) ;
+extern __off_t lseek (int __fd, __off_t __offset, int __whence) __attribute__ ((__nothrow__ , __leaf__));
+extern int close (int __fd);
+
+
+
+
+
+
+extern ssize_t read (int __fd, void *__buf, size_t __nbytes) ;
+
+
+
+
+
+extern ssize_t write (int __fd, const void *__buf, size_t __n) ;
+extern ssize_t pread (int __fd, void *__buf, size_t __nbytes,
+        __off_t __offset) ;
+
+
+
+
+
+
+extern ssize_t pwrite (int __fd, const void *__buf, size_t __n,
+         __off_t __offset) ;
+extern int pipe (int __pipedes[2]) __attribute__ ((__nothrow__ , __leaf__)) ;
+extern unsigned int alarm (unsigned int __seconds) __attribute__ ((__nothrow__ , __leaf__));
+extern unsigned int sleep (unsigned int __seconds);
+
+
+
+
+
+
+
+extern __useconds_t ualarm (__useconds_t __value, __useconds_t __interval)
+     __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+extern int usleep (__useconds_t __useconds);
+extern int pause (void);
+
+
+
+extern int chown (const char *__file, __uid_t __owner, __gid_t __group)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+
+
+
+extern int fchown (int __fd, __uid_t __owner, __gid_t __group) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+extern int lchown (const char *__file, __uid_t __owner, __gid_t __group)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+
+
+
+
+
+
+extern int fchownat (int __fd, const char *__file, __uid_t __owner,
+       __gid_t __group, int __flag)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2))) ;
+
+
+
+extern int chdir (const char *__path) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+
+
+
+extern int fchdir (int __fd) __attribute__ ((__nothrow__ , __leaf__)) ;
+extern char *getcwd (char *__buf, size_t __size) __attribute__ ((__nothrow__ , __leaf__)) ;
+extern char *getwd (char *__buf)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) __attribute__ ((__deprecated__)) ;
+
+
+
+
+extern int dup (int __fd) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+extern int dup2 (int __fd, int __fd2) __attribute__ ((__nothrow__ , __leaf__));
+extern char **__environ;
+
+
+
+
+
+
+
+extern int execve (const char *__path, char *const __argv[],
+     char *const __envp[]) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+
+extern int fexecve (int __fd, char *const __argv[], char *const __envp[])
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2)));
+
+
+
+
+extern int execv (const char *__path, char *const __argv[])
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+extern int execle (const char *__path, const char *__arg, ...)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+extern int execl (const char *__path, const char *__arg, ...)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+extern int execvp (const char *__file, char *const __argv[])
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+
+extern int execlp (const char *__file, const char *__arg, ...)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+extern int nice (int __inc) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+extern void _exit (int __status) __attribute__ ((__noreturn__));
+
+
+
+
+
+enum
+  {
+    _PC_LINK_MAX,
+
+    _PC_MAX_CANON,
+
+    _PC_MAX_INPUT,
+
+    _PC_NAME_MAX,
+
+    _PC_PATH_MAX,
+
+    _PC_PIPE_BUF,
+
+    _PC_CHOWN_RESTRICTED,
+
+    _PC_NO_TRUNC,
+
+    _PC_VDISABLE,
+
+    _PC_SYNC_IO,
+
+    _PC_ASYNC_IO,
+
+    _PC_PRIO_IO,
+
+    _PC_SOCK_MAXBUF,
+
+    _PC_FILESIZEBITS,
+
+    _PC_REC_INCR_XFER_SIZE,
+
+    _PC_REC_MAX_XFER_SIZE,
+
+    _PC_REC_MIN_XFER_SIZE,
+
+    _PC_REC_XFER_ALIGN,
+
+    _PC_ALLOC_SIZE_MIN,
+
+    _PC_SYMLINK_MAX,
+
+    _PC_2_SYMLINKS
+
+  };
+
+
+enum
+  {
+    _SC_ARG_MAX,
+
+    _SC_CHILD_MAX,
+
+    _SC_CLK_TCK,
+
+    _SC_NGROUPS_MAX,
+
+    _SC_OPEN_MAX,
+
+    _SC_STREAM_MAX,
+
+    _SC_TZNAME_MAX,
+
+    _SC_JOB_CONTROL,
+
+    _SC_SAVED_IDS,
+
+    _SC_REALTIME_SIGNALS,
+
+    _SC_PRIORITY_SCHEDULING,
+
+    _SC_TIMERS,
+
+    _SC_ASYNCHRONOUS_IO,
+
+    _SC_PRIORITIZED_IO,
+
+    _SC_SYNCHRONIZED_IO,
+
+    _SC_FSYNC,
+
+    _SC_MAPPED_FILES,
+
+    _SC_MEMLOCK,
+
+    _SC_MEMLOCK_RANGE,
+
+    _SC_MEMORY_PROTECTION,
+
+    _SC_MESSAGE_PASSING,
+
+    _SC_SEMAPHORES,
+
+    _SC_SHARED_MEMORY_OBJECTS,
+
+    _SC_AIO_LISTIO_MAX,
+
+    _SC_AIO_MAX,
+
+    _SC_AIO_PRIO_DELTA_MAX,
+
+    _SC_DELAYTIMER_MAX,
+
+    _SC_MQ_OPEN_MAX,
+
+    _SC_MQ_PRIO_MAX,
+
+    _SC_VERSION,
+
+    _SC_PAGESIZE,
+
+
+    _SC_RTSIG_MAX,
+
+    _SC_SEM_NSEMS_MAX,
+
+    _SC_SEM_VALUE_MAX,
+
+    _SC_SIGQUEUE_MAX,
+
+    _SC_TIMER_MAX,
+
+
+
+
+    _SC_BC_BASE_MAX,
+
+    _SC_BC_DIM_MAX,
+
+    _SC_BC_SCALE_MAX,
+
+    _SC_BC_STRING_MAX,
+
+    _SC_COLL_WEIGHTS_MAX,
+
+    _SC_EQUIV_CLASS_MAX,
+
+    _SC_EXPR_NEST_MAX,
+
+    _SC_LINE_MAX,
+
+    _SC_RE_DUP_MAX,
+
+    _SC_CHARCLASS_NAME_MAX,
+
+
+    _SC_2_VERSION,
+
+    _SC_2_C_BIND,
+
+    _SC_2_C_DEV,
+
+    _SC_2_FORT_DEV,
+
+    _SC_2_FORT_RUN,
+
+    _SC_2_SW_DEV,
+
+    _SC_2_LOCALEDEF,
+
+
+    _SC_PII,
+
+    _SC_PII_XTI,
+
+    _SC_PII_SOCKET,
+
+    _SC_PII_INTERNET,
+
+    _SC_PII_OSI,
+
+    _SC_POLL,
+
+    _SC_SELECT,
+
+    _SC_UIO_MAXIOV,
+
+    _SC_IOV_MAX = _SC_UIO_MAXIOV,
+
+    _SC_PII_INTERNET_STREAM,
+
+    _SC_PII_INTERNET_DGRAM,
+
+    _SC_PII_OSI_COTS,
+
+    _SC_PII_OSI_CLTS,
+
+    _SC_PII_OSI_M,
+
+    _SC_T_IOV_MAX,
+
+
+
+    _SC_THREADS,
+
+    _SC_THREAD_SAFE_FUNCTIONS,
+
+    _SC_GETGR_R_SIZE_MAX,
+
+    _SC_GETPW_R_SIZE_MAX,
+
+    _SC_LOGIN_NAME_MAX,
+
+    _SC_TTY_NAME_MAX,
+
+    _SC_THREAD_DESTRUCTOR_ITERATIONS,
+
+    _SC_THREAD_KEYS_MAX,
+
+    _SC_THREAD_STACK_MIN,
+
+    _SC_THREAD_THREADS_MAX,
+
+    _SC_THREAD_ATTR_STACKADDR,
+
+    _SC_THREAD_ATTR_STACKSIZE,
+
+    _SC_THREAD_PRIORITY_SCHEDULING,
+
+    _SC_THREAD_PRIO_INHERIT,
+
+    _SC_THREAD_PRIO_PROTECT,
+
+    _SC_THREAD_PROCESS_SHARED,
+
+
+    _SC_NPROCESSORS_CONF,
+
+    _SC_NPROCESSORS_ONLN,
+
+    _SC_PHYS_PAGES,
+
+    _SC_AVPHYS_PAGES,
+
+    _SC_ATEXIT_MAX,
+
+    _SC_PASS_MAX,
+
+
+    _SC_XOPEN_VERSION,
+
+    _SC_XOPEN_XCU_VERSION,
+
+    _SC_XOPEN_UNIX,
+
+    _SC_XOPEN_CRYPT,
+
+    _SC_XOPEN_ENH_I18N,
+
+    _SC_XOPEN_SHM,
+
+
+    _SC_2_CHAR_TERM,
+
+    _SC_2_C_VERSION,
+
+    _SC_2_UPE,
+
+
+    _SC_XOPEN_XPG2,
+
+    _SC_XOPEN_XPG3,
+
+    _SC_XOPEN_XPG4,
+
+
+    _SC_CHAR_BIT,
+
+    _SC_CHAR_MAX,
+
+    _SC_CHAR_MIN,
+
+    _SC_INT_MAX,
+
+    _SC_INT_MIN,
+
+    _SC_LONG_BIT,
+
+    _SC_WORD_BIT,
+
+    _SC_MB_LEN_MAX,
+
+    _SC_NZERO,
+
+    _SC_SSIZE_MAX,
+
+    _SC_SCHAR_MAX,
+
+    _SC_SCHAR_MIN,
+
+    _SC_SHRT_MAX,
+
+    _SC_SHRT_MIN,
+
+    _SC_UCHAR_MAX,
+
+    _SC_UINT_MAX,
+
+    _SC_ULONG_MAX,
+
+    _SC_USHRT_MAX,
+
+
+    _SC_NL_ARGMAX,
+
+    _SC_NL_LANGMAX,
+
+    _SC_NL_MSGMAX,
+
+    _SC_NL_NMAX,
+
+    _SC_NL_SETMAX,
+
+    _SC_NL_TEXTMAX,
+
+
+    _SC_XBS5_ILP32_OFF32,
+
+    _SC_XBS5_ILP32_OFFBIG,
+
+    _SC_XBS5_LP64_OFF64,
+
+    _SC_XBS5_LPBIG_OFFBIG,
+
+
+    _SC_XOPEN_LEGACY,
+
+    _SC_XOPEN_REALTIME,
+
+    _SC_XOPEN_REALTIME_THREADS,
+
+
+    _SC_ADVISORY_INFO,
+
+    _SC_BARRIERS,
+
+    _SC_BASE,
+
+    _SC_C_LANG_SUPPORT,
+
+    _SC_C_LANG_SUPPORT_R,
+
+    _SC_CLOCK_SELECTION,
+
+    _SC_CPUTIME,
+
+    _SC_THREAD_CPUTIME,
+
+    _SC_DEVICE_IO,
+
+    _SC_DEVICE_SPECIFIC,
+
+    _SC_DEVICE_SPECIFIC_R,
+
+    _SC_FD_MGMT,
+
+    _SC_FIFO,
+
+    _SC_PIPE,
+
+    _SC_FILE_ATTRIBUTES,
+
+    _SC_FILE_LOCKING,
+
+    _SC_FILE_SYSTEM,
+
+    _SC_MONOTONIC_CLOCK,
+
+    _SC_MULTI_PROCESS,
+
+    _SC_SINGLE_PROCESS,
+
+    _SC_NETWORKING,
+
+    _SC_READER_WRITER_LOCKS,
+
+    _SC_SPIN_LOCKS,
+
+    _SC_REGEXP,
+
+    _SC_REGEX_VERSION,
+
+    _SC_SHELL,
+
+    _SC_SIGNALS,
+
+    _SC_SPAWN,
+
+    _SC_SPORADIC_SERVER,
+
+    _SC_THREAD_SPORADIC_SERVER,
+
+    _SC_SYSTEM_DATABASE,
+
+    _SC_SYSTEM_DATABASE_R,
+
+    _SC_TIMEOUTS,
+
+    _SC_TYPED_MEMORY_OBJECTS,
+
+    _SC_USER_GROUPS,
+
+    _SC_USER_GROUPS_R,
+
+    _SC_2_PBS,
+
+    _SC_2_PBS_ACCOUNTING,
+
+    _SC_2_PBS_LOCATE,
+
+    _SC_2_PBS_MESSAGE,
+
+    _SC_2_PBS_TRACK,
+
+    _SC_SYMLOOP_MAX,
+
+    _SC_STREAMS,
+
+    _SC_2_PBS_CHECKPOINT,
+
+
+    _SC_V6_ILP32_OFF32,
+
+    _SC_V6_ILP32_OFFBIG,
+
+    _SC_V6_LP64_OFF64,
+
+    _SC_V6_LPBIG_OFFBIG,
+
+
+    _SC_HOST_NAME_MAX,
+
+    _SC_TRACE,
+
+    _SC_TRACE_EVENT_FILTER,
+
+    _SC_TRACE_INHERIT,
+
+    _SC_TRACE_LOG,
+
+
+    _SC_LEVEL1_ICACHE_SIZE,
+
+    _SC_LEVEL1_ICACHE_ASSOC,
+
+    _SC_LEVEL1_ICACHE_LINESIZE,
+
+    _SC_LEVEL1_DCACHE_SIZE,
+
+    _SC_LEVEL1_DCACHE_ASSOC,
+
+    _SC_LEVEL1_DCACHE_LINESIZE,
+
+    _SC_LEVEL2_CACHE_SIZE,
+
+    _SC_LEVEL2_CACHE_ASSOC,
+
+    _SC_LEVEL2_CACHE_LINESIZE,
+
+    _SC_LEVEL3_CACHE_SIZE,
+
+    _SC_LEVEL3_CACHE_ASSOC,
+
+    _SC_LEVEL3_CACHE_LINESIZE,
+
+    _SC_LEVEL4_CACHE_SIZE,
+
+    _SC_LEVEL4_CACHE_ASSOC,
+
+    _SC_LEVEL4_CACHE_LINESIZE,
+
+
+
+    _SC_IPV6 = _SC_LEVEL1_ICACHE_SIZE + 50,
+
+    _SC_RAW_SOCKETS,
+
+
+    _SC_V7_ILP32_OFF32,
+
+    _SC_V7_ILP32_OFFBIG,
+
+    _SC_V7_LP64_OFF64,
+
+    _SC_V7_LPBIG_OFFBIG,
+
+
+    _SC_SS_REPL_MAX,
+
+
+    _SC_TRACE_EVENT_NAME_MAX,
+
+    _SC_TRACE_NAME_MAX,
+
+    _SC_TRACE_SYS_MAX,
+
+    _SC_TRACE_USER_EVENT_MAX,
+
+
+    _SC_XOPEN_STREAMS,
+
+
+    _SC_THREAD_ROBUST_PRIO_INHERIT,
+
+    _SC_THREAD_ROBUST_PRIO_PROTECT
+
+  };
+
+
+enum
+  {
+    _CS_PATH,
+
+
+    _CS_V6_WIDTH_RESTRICTED_ENVS,
+
+
+
+    _CS_GNU_LIBC_VERSION,
+
+    _CS_GNU_LIBPTHREAD_VERSION,
+
+
+    _CS_V5_WIDTH_RESTRICTED_ENVS,
+
+
+
+    _CS_V7_WIDTH_RESTRICTED_ENVS,
+
+
+
+    _CS_LFS_CFLAGS = 1000,
+
+    _CS_LFS_LDFLAGS,
+
+    _CS_LFS_LIBS,
+
+    _CS_LFS_LINTFLAGS,
+
+    _CS_LFS64_CFLAGS,
+
+    _CS_LFS64_LDFLAGS,
+
+    _CS_LFS64_LIBS,
+
+    _CS_LFS64_LINTFLAGS,
+
+
+    _CS_XBS5_ILP32_OFF32_CFLAGS = 1100,
+
+    _CS_XBS5_ILP32_OFF32_LDFLAGS,
+
+    _CS_XBS5_ILP32_OFF32_LIBS,
+
+    _CS_XBS5_ILP32_OFF32_LINTFLAGS,
+
+    _CS_XBS5_ILP32_OFFBIG_CFLAGS,
+
+    _CS_XBS5_ILP32_OFFBIG_LDFLAGS,
+
+    _CS_XBS5_ILP32_OFFBIG_LIBS,
+
+    _CS_XBS5_ILP32_OFFBIG_LINTFLAGS,
+
+    _CS_XBS5_LP64_OFF64_CFLAGS,
+
+    _CS_XBS5_LP64_OFF64_LDFLAGS,
+
+    _CS_XBS5_LP64_OFF64_LIBS,
+
+    _CS_XBS5_LP64_OFF64_LINTFLAGS,
+
+    _CS_XBS5_LPBIG_OFFBIG_CFLAGS,
+
+    _CS_XBS5_LPBIG_OFFBIG_LDFLAGS,
+
+    _CS_XBS5_LPBIG_OFFBIG_LIBS,
+
+    _CS_XBS5_LPBIG_OFFBIG_LINTFLAGS,
+
+
+    _CS_POSIX_V6_ILP32_OFF32_CFLAGS,
+
+    _CS_POSIX_V6_ILP32_OFF32_LDFLAGS,
+
+    _CS_POSIX_V6_ILP32_OFF32_LIBS,
+
+    _CS_POSIX_V6_ILP32_OFF32_LINTFLAGS,
+
+    _CS_POSIX_V6_ILP32_OFFBIG_CFLAGS,
+
+    _CS_POSIX_V6_ILP32_OFFBIG_LDFLAGS,
+
+    _CS_POSIX_V6_ILP32_OFFBIG_LIBS,
+
+    _CS_POSIX_V6_ILP32_OFFBIG_LINTFLAGS,
+
+    _CS_POSIX_V6_LP64_OFF64_CFLAGS,
+
+    _CS_POSIX_V6_LP64_OFF64_LDFLAGS,
+
+    _CS_POSIX_V6_LP64_OFF64_LIBS,
+
+    _CS_POSIX_V6_LP64_OFF64_LINTFLAGS,
+
+    _CS_POSIX_V6_LPBIG_OFFBIG_CFLAGS,
+
+    _CS_POSIX_V6_LPBIG_OFFBIG_LDFLAGS,
+
+    _CS_POSIX_V6_LPBIG_OFFBIG_LIBS,
+
+    _CS_POSIX_V6_LPBIG_OFFBIG_LINTFLAGS,
+
+
+    _CS_POSIX_V7_ILP32_OFF32_CFLAGS,
+
+    _CS_POSIX_V7_ILP32_OFF32_LDFLAGS,
+
+    _CS_POSIX_V7_ILP32_OFF32_LIBS,
+
+    _CS_POSIX_V7_ILP32_OFF32_LINTFLAGS,
+
+    _CS_POSIX_V7_ILP32_OFFBIG_CFLAGS,
+
+    _CS_POSIX_V7_ILP32_OFFBIG_LDFLAGS,
+
+    _CS_POSIX_V7_ILP32_OFFBIG_LIBS,
+
+    _CS_POSIX_V7_ILP32_OFFBIG_LINTFLAGS,
+
+    _CS_POSIX_V7_LP64_OFF64_CFLAGS,
+
+    _CS_POSIX_V7_LP64_OFF64_LDFLAGS,
+
+    _CS_POSIX_V7_LP64_OFF64_LIBS,
+
+    _CS_POSIX_V7_LP64_OFF64_LINTFLAGS,
+
+    _CS_POSIX_V7_LPBIG_OFFBIG_CFLAGS,
+
+    _CS_POSIX_V7_LPBIG_OFFBIG_LDFLAGS,
+
+    _CS_POSIX_V7_LPBIG_OFFBIG_LIBS,
+
+    _CS_POSIX_V7_LPBIG_OFFBIG_LINTFLAGS,
+
+
+    _CS_V6_ENV,
+
+    _CS_V7_ENV
+
+  };
+
+
+extern long int pathconf (const char *__path, int __name)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern long int fpathconf (int __fd, int __name) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern long int sysconf (int __name) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+extern size_t confstr (int __name, char *__buf, size_t __len) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+extern __pid_t getpid (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern __pid_t getppid (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+extern __pid_t getpgrp (void) __attribute__ ((__nothrow__ , __leaf__));
+extern __pid_t __getpgid (__pid_t __pid) __attribute__ ((__nothrow__ , __leaf__));
+
+extern __pid_t getpgid (__pid_t __pid) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+extern int setpgid (__pid_t __pid, __pid_t __pgid) __attribute__ ((__nothrow__ , __leaf__));
+extern int setpgrp (void) __attribute__ ((__nothrow__ , __leaf__));
+extern __pid_t setsid (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+extern __pid_t getsid (__pid_t __pid) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+extern __uid_t getuid (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern __uid_t geteuid (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern __gid_t getgid (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern __gid_t getegid (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+extern int getgroups (int __size, __gid_t __list[]) __attribute__ ((__nothrow__ , __leaf__)) ;
+extern int setuid (__uid_t __uid) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+extern int setreuid (__uid_t __ruid, __uid_t __euid) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+extern int seteuid (__uid_t __uid) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+
+
+extern int setgid (__gid_t __gid) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+extern int setregid (__gid_t __rgid, __gid_t __egid) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+extern int setegid (__gid_t __gid) __attribute__ ((__nothrow__ , __leaf__)) ;
+extern __pid_t fork (void) __attribute__ ((__nothrow__));
+
+
+
+
+
+
+
+extern __pid_t vfork (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+extern char *ttyname (int __fd) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+extern int ttyname_r (int __fd, char *__buf, size_t __buflen)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2))) ;
+
+
+
+extern int isatty (int __fd) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+extern int ttyslot (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+extern int link (const char *__from, const char *__to)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2))) ;
+
+
+
+
+extern int linkat (int __fromfd, const char *__from, int __tofd,
+     const char *__to, int __flags)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2, 4))) ;
+
+
+
+
+extern int symlink (const char *__from, const char *__to)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2))) ;
+
+
+
+
+extern ssize_t readlink (const char *__restrict __path,
+    char *__restrict __buf, size_t __len)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2))) ;
+
+
+
+
+extern int symlinkat (const char *__from, int __tofd,
+        const char *__to) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 3))) ;
+
+
+extern ssize_t readlinkat (int __fd, const char *__restrict __path,
+      char *__restrict __buf, size_t __len)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2, 3))) ;
+
+
+
+extern int unlink (const char *__name) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+extern int unlinkat (int __fd, const char *__name, int __flag)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2)));
+
+
+
+extern int rmdir (const char *__path) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+extern __pid_t tcgetpgrp (int __fd) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern int tcsetpgrp (int __fd, __pid_t __pgrp_id) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+extern char *getlogin (void);
+
+
+
+
+
+
+
+extern int getlogin_r (char *__name, size_t __name_len) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+extern int setlogin (const char *__name) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+extern char *optarg;
+extern int optind;
+
+
+
+
+extern int opterr;
+
+
+
+extern int optopt;
+extern int getopt (int ___argc, char *const *___argv, const char *__shortopts)
+       __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+
+extern int gethostname (char *__name, size_t __len) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+
+extern int sethostname (const char *__name, size_t __len)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+
+
+
+extern int sethostid (long int __id) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+
+extern int getdomainname (char *__name, size_t __len)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+extern int setdomainname (const char *__name, size_t __len)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+
+
+
+
+
+extern int vhangup (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern int revoke (const char *__file) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+
+
+
+
+
+
+
+extern int profil (unsigned short int *__sample_buffer, size_t __size,
+     size_t __offset, unsigned int __scale)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+extern int acct (const char *__name) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+extern char *getusershell (void) __attribute__ ((__nothrow__ , __leaf__));
+extern void endusershell (void) __attribute__ ((__nothrow__ , __leaf__));
+extern void setusershell (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+extern int daemon (int __nochdir, int __noclose) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+
+
+extern int chroot (const char *__path) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+
+
+
+extern char *getpass (const char *__prompt) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+
+
+extern int fsync (int __fd);
+extern long int gethostid (void);
+
+
+extern void sync (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+extern int getpagesize (void) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__));
+
+
+
+
+extern int getdtablesize (void) __attribute__ ((__nothrow__ , __leaf__));
+extern int truncate (const char *__file, __off_t __length)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+extern int ftruncate (int __fd, __off_t __length) __attribute__ ((__nothrow__ , __leaf__)) ;
+extern int brk (void *__addr) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+
+extern void *sbrk (intptr_t __delta) __attribute__ ((__nothrow__ , __leaf__));
+extern long int syscall (long int __sysno, ...) __attribute__ ((__nothrow__ , __leaf__));
+extern int lockf (int __fd, int __cmd, __off_t __len) ;
+extern int fdatasync (int __fildes);
+
+
+typedef voidpf (*alloc_func) (voidpf opaque, uInt items, uInt size);
+typedef void (*free_func) (voidpf opaque, voidpf address);
+
+struct internal_state;
+
+typedef struct z_stream_s {
+    Bytef *next_in;
+    uInt avail_in;
+    uLong total_in;
+
+    Bytef *next_out;
+    uInt avail_out;
+    uLong total_out;
+
+    char *msg;
+    struct internal_state *state;
+
+    alloc_func zalloc;
+    free_func zfree;
+    voidpf opaque;
+
+    int data_type;
+    uLong adler;
+    uLong reserved;
+} z_stream;
+
+typedef z_stream *z_streamp;
+extern const char * zlibVersion (void);
+extern int deflate (z_streamp strm, int flush);
+extern int deflateEnd (z_streamp strm);
+extern int inflate (z_streamp strm, int flush);
+extern int inflateEnd (z_streamp strm);
+extern int deflateSetDictionary (z_streamp strm, const Bytef *dictionary, uInt dictLength)
+
+                                                               ;
+extern int deflateCopy (z_streamp dest, z_streamp source)
+                                                      ;
+extern int deflateReset (z_streamp strm);
+extern int deflateParams (z_streamp strm, int level, int strategy)
+
+                        ;
+extern int inflateSetDictionary (z_streamp strm, const Bytef *dictionary, uInt dictLength)
+
+                                                               ;
+extern int inflateSync (z_streamp strm);
+extern int inflateReset (z_streamp strm);
+extern int compress (Bytef *dest, uLongf *destLen, const Bytef *source, uLong sourceLen)
+                                                                       ;
+extern int compress2 (Bytef *dest, uLongf *destLen, const Bytef *source, uLong sourceLen, int level)
+
+                                             ;
+extern int uncompress (Bytef *dest, uLongf *destLen, const Bytef *source, uLong sourceLen)
+                                                                         ;
+typedef voidp gzFile;
+
+extern gzFile gzopen (const char *path, const char *mode);
+extern gzFile gzdopen (int fd, const char *mode);
+extern int gzsetparams (gzFile file, int level, int strategy);
+
+
+
+
+
+
+
+extern int gzread (gzFile file, voidp buf, unsigned len);
+
+
+
+
+
+
+
+extern int gzwrite (gzFile file, const voidp buf, unsigned len)
+                                      ;
+
+
+
+
+
+
+extern int gzprintf (gzFile file, const char *format, ...);
+
+
+
+
+
+
+extern int gzputs (gzFile file, const char *s);
+
+
+
+
+
+
+extern char * gzgets (gzFile file, char *buf, int len);
+extern int gzputc (gzFile file, int c);
+
+
+
+
+
+extern int gzgetc (gzFile file);
+
+
+
+
+
+extern int gzflush (gzFile file, int flush);
+extern off_t gzseek (gzFile file, off_t offset, int whence)
+                                      ;
+extern int gzrewind (gzFile file);
+
+
+
+
+
+
+extern off_t gztell (gzFile file);
+extern int gzeof (gzFile file);
+
+
+
+
+
+extern int gzclose (gzFile file);
+
+
+
+
+
+
+extern const char * gzerror (gzFile file, int *errnum);
+extern uLong adler32 (uLong adler, const Bytef *buf, uInt len);
+extern uLong crc32 (uLong crc, const Bytef *buf, uInt len);
+extern int deflateInit_ (z_streamp strm, int level, const char *version, int stream_size)
+                                                                           ;
+extern int inflateInit_ (z_streamp strm, const char *version, int stream_size)
+                                                                           ;
+extern int deflateInit2_ (z_streamp strm, int level, int method, int windowBits, int memLevel, int strategy, const char *version, int stream_size)
+
+
+                                                       ;
+extern int inflateInit2_ (z_streamp strm, int windowBits, const char *version, int stream_size)
+                                                                            ;
+extern const char * zError (int err);
+extern int inflateSyncPoint (z_streamp z);
+extern const uLongf * get_crc_table (void);
+
+
+
+typedef long int ptrdiff_t;
+typedef int wchar_t;
+typedef struct {
+  long long __max_align_ll __attribute__((__aligned__(__alignof__(long long))));
+  long double __max_align_ld __attribute__((__aligned__(__alignof__(long double))));
+} max_align_t;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+extern void *memcpy (void *__restrict __dest, const void *__restrict __src,
+       size_t __n) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern void *memmove (void *__dest, const void *__src, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+
+
+
+extern void *memccpy (void *__restrict __dest, const void *__restrict __src,
+        int __c, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+
+
+extern void *memset (void *__s, int __c, size_t __n) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int memcmp (const void *__s1, const void *__s2, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1, 2)));
+extern void *memchr (const void *__s, int __c, size_t __n)
+      __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+extern char *strcpy (char *__restrict __dest, const char *__restrict __src)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+extern char *strncpy (char *__restrict __dest,
+        const char *__restrict __src, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern char *strcat (char *__restrict __dest, const char *__restrict __src)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+extern char *strncat (char *__restrict __dest, const char *__restrict __src,
+        size_t __n) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern int strcmp (const char *__s1, const char *__s2)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1, 2)));
+
+extern int strncmp (const char *__s1, const char *__s2, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern int strcoll (const char *__s1, const char *__s2)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1, 2)));
+
+extern size_t strxfrm (char *__restrict __dest,
+         const char *__restrict __src, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2)));
+
+
+
+
+
+
+typedef struct __locale_struct
+{
+
+  struct __locale_data *__locales[13];
+
+
+  const unsigned short int *__ctype_b;
+  const int *__ctype_tolower;
+  const int *__ctype_toupper;
+
+
+  const char *__names[13];
+} *__locale_t;
+
+
+typedef __locale_t locale_t;
+
+
+extern int strcoll_l (const char *__s1, const char *__s2, __locale_t __l)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1, 2, 3)));
+
+extern size_t strxfrm_l (char *__dest, const char *__src, size_t __n,
+    __locale_t __l) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2, 4)));
+
+
+
+
+
+extern char *strdup (const char *__s)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__malloc__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+
+extern char *strndup (const char *__string, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__malloc__)) __attribute__ ((__nonnull__ (1)));
+
+extern char *strchr (const char *__s, int __c)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1)));
+extern char *strrchr (const char *__s, int __c)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+extern size_t strcspn (const char *__s, const char *__reject)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern size_t strspn (const char *__s, const char *__accept)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1, 2)));
+extern char *strpbrk (const char *__s, const char *__accept)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1, 2)));
+extern char *strstr (const char *__haystack, const char *__needle)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+
+extern char *strtok (char *__restrict __s, const char *__restrict __delim)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2)));
+
+
+
+
+extern char *__strtok_r (char *__restrict __s,
+    const char *__restrict __delim,
+    char **__restrict __save_ptr)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2, 3)));
+
+extern char *strtok_r (char *__restrict __s, const char *__restrict __delim,
+         char **__restrict __save_ptr)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2, 3)));
+
+
+extern size_t strlen (const char *__s)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+extern size_t strnlen (const char *__string, size_t __maxlen)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+extern char *strerror (int __errnum) __attribute__ ((__nothrow__ , __leaf__));
+
+extern int strerror_r (int __errnum, char *__buf, size_t __buflen) __asm__ ("" "__xpg_strerror_r") __attribute__ ((__nothrow__ , __leaf__))
+
+                        __attribute__ ((__nonnull__ (2)));
+extern char *strerror_l (int __errnum, __locale_t __l) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+extern void __bzero (void *__s, size_t __n) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+extern void bcopy (const void *__src, void *__dest, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern void bzero (void *__s, size_t __n) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern int bcmp (const void *__s1, const void *__s2, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1, 2)));
+extern char *index (const char *__s, int __c)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1)));
+extern char *rindex (const char *__s, int __c)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+extern int ffs (int __i) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__));
+extern int strcasecmp (const char *__s1, const char *__s2)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern int strncasecmp (const char *__s1, const char *__s2, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1, 2)));
+extern char *strsep (char **__restrict __stringp,
+       const char *__restrict __delim)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+
+extern char *strsignal (int __sig) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern char *__stpcpy (char *__restrict __dest, const char *__restrict __src)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+extern char *stpcpy (char *__restrict __dest, const char *__restrict __src)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+extern char *__stpncpy (char *__restrict __dest,
+   const char *__restrict __src, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+extern char *stpncpy (char *__restrict __dest,
+        const char *__restrict __src, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern void *__rawmemchr (const void *__s, int __c);
+extern __inline __attribute__ ((__gnu_inline__)) size_t __strcspn_c1 (const char *__s, int __reject);
+extern __inline __attribute__ ((__gnu_inline__)) size_t
+__strcspn_c1 (const char *__s, int __reject)
+{
+  size_t __result = 0;
+  while (__s[__result] != '\0' && __s[__result] != __reject)
+    ++__result;
+  return __result;
+}
+
+extern __inline __attribute__ ((__gnu_inline__)) size_t __strcspn_c2 (const char *__s, int __reject1,
+         int __reject2);
+extern __inline __attribute__ ((__gnu_inline__)) size_t
+__strcspn_c2 (const char *__s, int __reject1, int __reject2)
+{
+  size_t __result = 0;
+  while (__s[__result] != '\0' && __s[__result] != __reject1
+  && __s[__result] != __reject2)
+    ++__result;
+  return __result;
+}
+
+extern __inline __attribute__ ((__gnu_inline__)) size_t __strcspn_c3 (const char *__s, int __reject1,
+         int __reject2, int __reject3);
+extern __inline __attribute__ ((__gnu_inline__)) size_t
+__strcspn_c3 (const char *__s, int __reject1, int __reject2,
+       int __reject3)
+{
+  size_t __result = 0;
+  while (__s[__result] != '\0' && __s[__result] != __reject1
+  && __s[__result] != __reject2 && __s[__result] != __reject3)
+    ++__result;
+  return __result;
+}
+extern __inline __attribute__ ((__gnu_inline__)) size_t __strspn_c1 (const char *__s, int __accept);
+extern __inline __attribute__ ((__gnu_inline__)) size_t
+__strspn_c1 (const char *__s, int __accept)
+{
+  size_t __result = 0;
+
+  while (__s[__result] == __accept)
+    ++__result;
+  return __result;
+}
+
+extern __inline __attribute__ ((__gnu_inline__)) size_t __strspn_c2 (const char *__s, int __accept1,
+        int __accept2);
+extern __inline __attribute__ ((__gnu_inline__)) size_t
+__strspn_c2 (const char *__s, int __accept1, int __accept2)
+{
+  size_t __result = 0;
+
+  while (__s[__result] == __accept1 || __s[__result] == __accept2)
+    ++__result;
+  return __result;
+}
+
+extern __inline __attribute__ ((__gnu_inline__)) size_t __strspn_c3 (const char *__s, int __accept1,
+        int __accept2, int __accept3);
+extern __inline __attribute__ ((__gnu_inline__)) size_t
+__strspn_c3 (const char *__s, int __accept1, int __accept2, int __accept3)
+{
+  size_t __result = 0;
+
+  while (__s[__result] == __accept1 || __s[__result] == __accept2
+  || __s[__result] == __accept3)
+    ++__result;
+  return __result;
+}
+extern __inline __attribute__ ((__gnu_inline__)) char *__strpbrk_c2 (const char *__s, int __accept1,
+        int __accept2);
+extern __inline __attribute__ ((__gnu_inline__)) char *
+__strpbrk_c2 (const char *__s, int __accept1, int __accept2)
+{
+
+  while (*__s != '\0' && *__s != __accept1 && *__s != __accept2)
+    ++__s;
+  return *__s == '\0' ? ((void *)0) : (char *) (size_t) __s;
+}
+
+extern __inline __attribute__ ((__gnu_inline__)) char *__strpbrk_c3 (const char *__s, int __accept1,
+        int __accept2, int __accept3);
+extern __inline __attribute__ ((__gnu_inline__)) char *
+__strpbrk_c3 (const char *__s, int __accept1, int __accept2, int __accept3)
+{
+
+  while (*__s != '\0' && *__s != __accept1 && *__s != __accept2
+  && *__s != __accept3)
+    ++__s;
+  return *__s == '\0' ? ((void *)0) : (char *) (size_t) __s;
+}
+extern __inline __attribute__ ((__gnu_inline__)) char *__strtok_r_1c (char *__s, char __sep, char **__nextp);
+extern __inline __attribute__ ((__gnu_inline__)) char *
+__strtok_r_1c (char *__s, char __sep, char **__nextp)
+{
+  char *__result;
+  if (__s == ((void *)0))
+    __s = *__nextp;
+  while (*__s == __sep)
+    ++__s;
+  __result = ((void *)0);
+  if (*__s != '\0')
+    {
+      __result = __s++;
+      while (*__s != '\0')
+ if (*__s++ == __sep)
+   {
+     __s[-1] = '\0';
+     break;
+   }
+    }
+  *__nextp = __s;
+  return __result;
+}
+extern char *__strsep_g (char **__stringp, const char *__delim);
+extern __inline __attribute__ ((__gnu_inline__)) char *__strsep_1c (char **__s, char __reject);
+extern __inline __attribute__ ((__gnu_inline__)) char *
+__strsep_1c (char **__s, char __reject)
+{
+  char *__retval = *__s;
+  if (__retval != ((void *)0) && (*__s = (__extension__ (__builtin_constant_p (__reject) && !__builtin_constant_p (__retval) && (__reject) == '\0' ? (char *) __rawmemchr (__retval, __reject) : __builtin_strchr (__retval, __reject)))) != ((void *)0))
+    *(*__s)++ = '\0';
+  return __retval;
+}
+
+extern __inline __attribute__ ((__gnu_inline__)) char *__strsep_2c (char **__s, char __reject1, char __reject2);
+extern __inline __attribute__ ((__gnu_inline__)) char *
+__strsep_2c (char **__s, char __reject1, char __reject2)
+{
+  char *__retval = *__s;
+  if (__retval != ((void *)0))
+    {
+      char *__cp = __retval;
+      while (1)
+ {
+   if (*__cp == '\0')
+     {
+       __cp = ((void *)0);
+   break;
+     }
+   if (*__cp == __reject1 || *__cp == __reject2)
+     {
+       *__cp++ = '\0';
+       break;
+     }
+   ++__cp;
+ }
+      *__s = __cp;
+    }
+  return __retval;
+}
+
+extern __inline __attribute__ ((__gnu_inline__)) char *__strsep_3c (char **__s, char __reject1, char __reject2,
+       char __reject3);
+extern __inline __attribute__ ((__gnu_inline__)) char *
+__strsep_3c (char **__s, char __reject1, char __reject2, char __reject3)
+{
+  char *__retval = *__s;
+  if (__retval != ((void *)0))
+    {
+      char *__cp = __retval;
+      while (1)
+ {
+   if (*__cp == '\0')
+     {
+       __cp = ((void *)0);
+   break;
+     }
+   if (*__cp == __reject1 || *__cp == __reject2 || *__cp == __reject3)
+     {
+       *__cp++ = '\0';
+       break;
+     }
+   ++__cp;
+ }
+      *__s = __cp;
+    }
+  return __retval;
+}
+
+
+
+
+extern void *malloc (size_t __size) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__malloc__)) ;
+
+extern void *calloc (size_t __nmemb, size_t __size)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__malloc__)) ;
+
+
+
+
+
+
+extern char *__strdup (const char *__string) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__malloc__));
+extern char *__strndup (const char *__string, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__malloc__));
+
+
+
+
+
+
+
+
+
+union wait
+  {
+    int w_status;
+    struct
+      {
+
+ unsigned int __w_termsig:7;
+ unsigned int __w_coredump:1;
+ unsigned int __w_retcode:8;
+ unsigned int:16;
+
+
+
+
+
+
+
+      } __wait_terminated;
+    struct
+      {
+
+ unsigned int __w_stopval:8;
+ unsigned int __w_stopsig:8;
+ unsigned int:16;
+
+
+
+
+
+
+      } __wait_stopped;
+  };
+typedef union
+  {
+    union wait *__uptr;
+    int *__iptr;
+  } __WAIT_STATUS __attribute__ ((__transparent_union__));
+
+
+typedef struct
+  {
+    int quot;
+    int rem;
+  } div_t;
+
+
+
+typedef struct
+  {
+    long int quot;
+    long int rem;
+  } ldiv_t;
+
+
+
+
+
+
+
+__extension__ typedef struct
+  {
+    long long int quot;
+    long long int rem;
+  } lldiv_t;
+
+
+extern size_t __ctype_get_mb_cur_max (void) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+extern double atof (const char *__nptr)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1))) ;
+
+extern int atoi (const char *__nptr)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1))) ;
+
+extern long int atol (const char *__nptr)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1))) ;
+
+
+
+
+
+__extension__ extern long long int atoll (const char *__nptr)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1))) ;
+
+
+
+
+
+extern double strtod (const char *__restrict __nptr,
+        char **__restrict __endptr)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+extern float strtof (const char *__restrict __nptr,
+       char **__restrict __endptr) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+extern long double strtold (const char *__restrict __nptr,
+       char **__restrict __endptr)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+extern long int strtol (const char *__restrict __nptr,
+   char **__restrict __endptr, int __base)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+extern unsigned long int strtoul (const char *__restrict __nptr,
+      char **__restrict __endptr, int __base)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+__extension__
+extern long long int strtoq (const char *__restrict __nptr,
+        char **__restrict __endptr, int __base)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+__extension__
+extern unsigned long long int strtouq (const char *__restrict __nptr,
+           char **__restrict __endptr, int __base)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+__extension__
+extern long long int strtoll (const char *__restrict __nptr,
+         char **__restrict __endptr, int __base)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+__extension__
+extern unsigned long long int strtoull (const char *__restrict __nptr,
+     char **__restrict __endptr, int __base)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern __inline __attribute__ ((__gnu_inline__)) int
+__attribute__ ((__nothrow__ , __leaf__)) atoi (const char *__nptr)
+{
+  return (int) strtol (__nptr, (char **) ((void *)0), 10);
+}
+extern __inline __attribute__ ((__gnu_inline__)) long int
+__attribute__ ((__nothrow__ , __leaf__)) atol (const char *__nptr)
+{
+  return strtol (__nptr, (char **) ((void *)0), 10);
+}
+
+
+
+
+__extension__ extern __inline __attribute__ ((__gnu_inline__)) long long int
+__attribute__ ((__nothrow__ , __leaf__)) atoll (const char *__nptr)
+{
+  return strtoll (__nptr, (char **) ((void *)0), 10);
+}
+
+extern char *l64a (long int __n) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+extern long int a64l (const char *__s)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__pure__)) __attribute__ ((__nonnull__ (1))) ;
+extern long int random (void) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern void srandom (unsigned int __seed) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+extern char *initstate (unsigned int __seed, char *__statebuf,
+   size_t __statelen) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2)));
+
+
+
+extern char *setstate (char *__statebuf) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+
+
+struct random_data
+  {
+    int32_t *fptr;
+    int32_t *rptr;
+    int32_t *state;
+    int rand_type;
+    int rand_deg;
+    int rand_sep;
+    int32_t *end_ptr;
+  };
+
+extern int random_r (struct random_data *__restrict __buf,
+       int32_t *__restrict __result) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+extern int srandom_r (unsigned int __seed, struct random_data *__buf)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2)));
+
+extern int initstate_r (unsigned int __seed, char *__restrict __statebuf,
+   size_t __statelen,
+   struct random_data *__restrict __buf)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2, 4)));
+
+extern int setstate_r (char *__restrict __statebuf,
+         struct random_data *__restrict __buf)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+
+
+
+extern int rand (void) __attribute__ ((__nothrow__ , __leaf__));
+
+extern void srand (unsigned int __seed) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+extern int rand_r (unsigned int *__seed) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+
+extern double drand48 (void) __attribute__ ((__nothrow__ , __leaf__));
+extern double erand48 (unsigned short int __xsubi[3]) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern long int lrand48 (void) __attribute__ ((__nothrow__ , __leaf__));
+extern long int nrand48 (unsigned short int __xsubi[3])
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern long int mrand48 (void) __attribute__ ((__nothrow__ , __leaf__));
+extern long int jrand48 (unsigned short int __xsubi[3])
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+extern void srand48 (long int __seedval) __attribute__ ((__nothrow__ , __leaf__));
+extern unsigned short int *seed48 (unsigned short int __seed16v[3])
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+extern void lcong48 (unsigned short int __param[7]) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+struct drand48_data
+  {
+    unsigned short int __x[3];
+    unsigned short int __old_x[3];
+    unsigned short int __c;
+    unsigned short int __init;
+    __extension__ unsigned long long int __a;
+
+  };
+
+
+extern int drand48_r (struct drand48_data *__restrict __buffer,
+        double *__restrict __result) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+extern int erand48_r (unsigned short int __xsubi[3],
+        struct drand48_data *__restrict __buffer,
+        double *__restrict __result) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern int lrand48_r (struct drand48_data *__restrict __buffer,
+        long int *__restrict __result)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+extern int nrand48_r (unsigned short int __xsubi[3],
+        struct drand48_data *__restrict __buffer,
+        long int *__restrict __result)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern int mrand48_r (struct drand48_data *__restrict __buffer,
+        long int *__restrict __result)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+extern int jrand48_r (unsigned short int __xsubi[3],
+        struct drand48_data *__restrict __buffer,
+        long int *__restrict __result)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+extern int srand48_r (long int __seedval, struct drand48_data *__buffer)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2)));
+
+extern int seed48_r (unsigned short int __seed16v[3],
+       struct drand48_data *__buffer) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+extern int lcong48_r (unsigned short int __param[7],
+        struct drand48_data *__buffer)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
+
+
+
+
+
+
+extern void *realloc (void *__ptr, size_t __size)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__warn_unused_result__));
+
+extern void free (void *__ptr) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+extern void cfree (void *__ptr) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+
+
+
+
+extern void *alloca (size_t __size) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+
+
+
+
+
+extern void *valloc (size_t __size) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__malloc__)) ;
+
+
+
+
+extern int posix_memalign (void **__memptr, size_t __alignment, size_t __size)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+
+
+
+
+extern void *aligned_alloc (size_t __alignment, size_t __size)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__malloc__)) __attribute__ ((__alloc_size__ (2))) ;
+
+
+
+
+extern void abort (void) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__noreturn__));
+
+
+
+extern int atexit (void (*__func) (void)) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+
+
+extern int at_quick_exit (void (*__func) (void)) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+
+
+extern int on_exit (void (*__func) (int __status, void *__arg), void *__arg)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+
+extern void exit (int __status) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__noreturn__));
+
+
+
+
+
+extern void quick_exit (int __status) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__noreturn__));
+
+
+
+
+
+
+
+extern void _Exit (int __status) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__noreturn__));
+
+
+
+
+
+
+extern char *getenv (const char *__name) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+
+extern int putenv (char *__string) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+extern int setenv (const char *__name, const char *__value, int __replace)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2)));
+
+
+extern int unsetenv (const char *__name) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+
+
+
+extern int clearenv (void) __attribute__ ((__nothrow__ , __leaf__));
+extern char *mktemp (char *__template) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+extern int mkstemp (char *__template) __attribute__ ((__nonnull__ (1))) ;
+extern int mkstemps (char *__template, int __suffixlen) __attribute__ ((__nonnull__ (1))) ;
+extern char *mkdtemp (char *__template) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+
+
+
+
+
+extern int system (const char *__command) ;
+
+extern char *realpath (const char *__restrict __name,
+         char *__restrict __resolved) __attribute__ ((__nothrow__ , __leaf__)) ;
+
+
+
+
+
+
+typedef int (*__compar_fn_t) (const void *, const void *);
+
+
+
+extern void *bsearch (const void *__key, const void *__base,
+        size_t __nmemb, size_t __size, __compar_fn_t __compar)
+     __attribute__ ((__nonnull__ (1, 2, 5))) ;
+
+
+extern __inline __attribute__ ((__gnu_inline__)) void *
+bsearch (const void *__key, const void *__base, size_t __nmemb, size_t __size,
+  __compar_fn_t __compar)
+{
+  size_t __l, __u, __idx;
+  const void *__p;
+  int __comparison;
+
+  __l = 0;
+  __u = __nmemb;
+  while (__l < __u)
+    {
+      __idx = (__l + __u) / 2;
+      __p = (void *) (((const char *) __base) + (__idx * __size));
+      __comparison = (*__compar) (__key, __p);
+      if (__comparison < 0)
+ __u = __idx;
+      else if (__comparison > 0)
+ __l = __idx + 1;
+      else
+ return (void *) __p;
+    }
+
+  return ((void *)0);
+}
+
+
+
+
+extern void qsort (void *__base, size_t __nmemb, size_t __size,
+     __compar_fn_t __compar) __attribute__ ((__nonnull__ (1, 4)));
+extern int abs (int __x) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__)) ;
+extern long int labs (long int __x) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__)) ;
+
+
+
+__extension__ extern long long int llabs (long long int __x)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__)) ;
+
+
+
+
+
+
+
+extern div_t div (int __numer, int __denom)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__)) ;
+extern ldiv_t ldiv (long int __numer, long int __denom)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__)) ;
+
+
+
+
+__extension__ extern lldiv_t lldiv (long long int __numer,
+        long long int __denom)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__)) ;
+
+extern char *ecvt (double __value, int __ndigit, int *__restrict __decpt,
+     int *__restrict __sign) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (3, 4))) ;
+
+
+
+
+extern char *fcvt (double __value, int __ndigit, int *__restrict __decpt,
+     int *__restrict __sign) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (3, 4))) ;
+
+
+
+
+extern char *gcvt (double __value, int __ndigit, char *__buf)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (3))) ;
+
+
+
+
+extern char *qecvt (long double __value, int __ndigit,
+      int *__restrict __decpt, int *__restrict __sign)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (3, 4))) ;
+extern char *qfcvt (long double __value, int __ndigit,
+      int *__restrict __decpt, int *__restrict __sign)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (3, 4))) ;
+extern char *qgcvt (long double __value, int __ndigit, char *__buf)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (3))) ;
+
+
+
+
+extern int ecvt_r (double __value, int __ndigit, int *__restrict __decpt,
+     int *__restrict __sign, char *__restrict __buf,
+     size_t __len) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (3, 4, 5)));
+extern int fcvt_r (double __value, int __ndigit, int *__restrict __decpt,
+     int *__restrict __sign, char *__restrict __buf,
+     size_t __len) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (3, 4, 5)));
+
+extern int qecvt_r (long double __value, int __ndigit,
+      int *__restrict __decpt, int *__restrict __sign,
+      char *__restrict __buf, size_t __len)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (3, 4, 5)));
+extern int qfcvt_r (long double __value, int __ndigit,
+      int *__restrict __decpt, int *__restrict __sign,
+      char *__restrict __buf, size_t __len)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (3, 4, 5)));
+
+
+
+
+
+
+extern int mblen (const char *__s, size_t __n) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern int mbtowc (wchar_t *__restrict __pwc,
+     const char *__restrict __s, size_t __n) __attribute__ ((__nothrow__ , __leaf__));
+
+
+extern int wctomb (char *__s, wchar_t __wchar) __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+extern size_t mbstowcs (wchar_t *__restrict __pwcs,
+   const char *__restrict __s, size_t __n) __attribute__ ((__nothrow__ , __leaf__));
+
+extern size_t wcstombs (char *__restrict __s,
+   const wchar_t *__restrict __pwcs, size_t __n)
+     __attribute__ ((__nothrow__ , __leaf__));
+
+
+
+
+
+
+
+
+extern int rpmatch (const char *__response) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1))) ;
+extern int getsubopt (char **__restrict __optionp,
+        char *const *__restrict __tokens,
+        char **__restrict __valuep)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2, 3))) ;
+extern int getloadavg (double __loadavg[], int __nelem)
+     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
+
+
+
+extern __inline __attribute__ ((__gnu_inline__)) double
+__attribute__ ((__nothrow__ , __leaf__)) atof (const char *__nptr)
+{
+  return strtod (__nptr, (char **) ((void *)0));
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+extern int *__errno_location (void) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__));
+
+
+
+
+
+
+
+
+
+typedef unsigned char uch;
+typedef uch uchf;
+typedef unsigned short ush;
+typedef ush ushf;
+typedef unsigned long ulg;
+
+extern const char *z_errmsg[10];
+typedef uLong ( *check_func) (uLong check, const Bytef *buf, uInt len)
+                     ;
+voidpf zcalloc (voidpf opaque, unsigned items, unsigned size);
+void zcfree (voidpf opaque, voidpf ptr);
+struct inflate_blocks_state;
+typedef struct inflate_blocks_state inflate_blocks_statef;
+
+extern inflate_blocks_statef * inflate_blocks_new ( z_streamp z, check_func c, uInt w)
+
+
+            ;
+
+extern int inflate_blocks ( inflate_blocks_statef *, z_streamp , int)
+
+
+         ;
+
+extern void inflate_blocks_reset ( inflate_blocks_statef *, z_streamp , uLongf *)
+
+
+              ;
+
+extern int inflate_blocks_free ( inflate_blocks_statef *, z_streamp)
+
+               ;
+
+extern void inflate_set_dictionary ( inflate_blocks_statef *s, const Bytef *d, uInt n)
+
+
+             ;
+
+extern int inflate_blocks_sync_point ( inflate_blocks_statef *s)
+                              ;
+typedef struct inflate_huft_s inflate_huft;
+
+struct inflate_huft_s {
+  union {
+    struct {
+      Byte Exop;
+      Byte Bits;
+    } what;
+    uInt pad;
+  } word;
+  uInt base;
+
+};
+extern int inflate_trees_bits ( uIntf *, uIntf *, inflate_huft * *, inflate_huft *, z_streamp)
+
+
+
+
+               ;
+
+extern int inflate_trees_dynamic ( uInt, uInt, uIntf *, uIntf *, uIntf *, inflate_huft * *, inflate_huft * *, inflate_huft *, z_streamp)
+               ;
+
+extern int inflate_trees_fixed ( uIntf *, uIntf *, inflate_huft * *, inflate_huft * *, z_streamp)
+
+
+
+
+               ;
+struct inflate_codes_state;
+typedef struct inflate_codes_state inflate_codes_statef;
+
+extern inflate_codes_statef *inflate_codes_new ( uInt, uInt, inflate_huft *, inflate_huft *, z_streamp )
+
+
+                ;
+
+extern int inflate_codes ( inflate_blocks_statef *, z_streamp , int)
+
+
+         ;
+
+extern void inflate_codes_free ( inflate_codes_statef *, z_streamp )
+
+                ;
+typedef enum {
+      TYPE,
+      LENS,
+      STORED,
+      TABLE,
+      BTREE,
+      DTREE,
+      CODES,
+      DRY,
+      DONE,
+      BAD}
+inflate_block_mode;
+
+
+struct inflate_blocks_state {
+
+
+  inflate_block_mode mode;
+
+
+  union {
+    uInt left;
+    struct {
+      uInt table;
+      uInt index;
+      uIntf *blens;
+      uInt bb;
+      inflate_huft *tb;
+    } trees;
+    struct {
+      inflate_codes_statef
+         *codes;
+    } decode;
+  } sub;
+  uInt last;
+
+
+  uInt bitk;
+  uLong bitb;
+  inflate_huft *hufts;
+  Bytef *window;
+  Bytef *end;
+  Bytef *read;
+  Bytef *write;
+  check_func checkfn;
+  uLong check;
+
+};
+extern uInt inflate_mask[17];
+
+
+extern int inflate_flush ( inflate_blocks_statef *, z_streamp , int)
+
+
+         ;
+
+struct internal_state {int dummy;};
+
+struct inflate_codes_state {int dummy;};
+
+
+
+
+
+
+static const uInt border[] = {
+        16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};
+void inflate_blocks_reset(s, z, c)
+inflate_blocks_statef *s;
+z_streamp z;
+uLongf *c;
+{
+  if (c != 0)
+    *c = s->check;
+  if (s->mode == BTREE || s->mode == DTREE)
+    (*((z)->zfree))((z)->opaque, (voidpf)(s->sub.trees.blens));
+  if (s->mode == CODES)
+    inflate_codes_free(s->sub.decode.codes, z);
+  s->mode = TYPE;
+  s->bitk = 0;
+  s->bitb = 0;
+  s->read = s->write = s->window;
+  if (s->checkfn != 0)
+    z->adler = s->check = (*s->checkfn)(0L, (const Bytef *)0, 0);
+  ;
+}
+
+
+inflate_blocks_statef *inflate_blocks_new(z, c, w)
+z_streamp z;
+check_func c;
+uInt w;
+{
+  inflate_blocks_statef *s;
+
+  if ((s = (inflate_blocks_statef *)(*((z)->zalloc))((z)->opaque, (1), (sizeof(struct inflate_blocks_state)))
+                                                ) == 0)
+    return s;
+  if ((s->hufts =
+       (inflate_huft *)(*((z)->zalloc))((z)->opaque, (sizeof(inflate_huft)), (1440))) == 0)
+  {
+    (*((z)->zfree))((z)->opaque, (voidpf)(s));
+    return 0;
+  }
+  if ((s->window = (Bytef *)(*((z)->zalloc))((z)->opaque, (1), (w))) == 0)
+  {
+    (*((z)->zfree))((z)->opaque, (voidpf)(s->hufts));
+    (*((z)->zfree))((z)->opaque, (voidpf)(s));
+    return 0;
+  }
+  s->end = s->window + w;
+  s->checkfn = c;
+  s->mode = TYPE;
+  ;
+  inflate_blocks_reset(s, z, 0);
+  return s;
+}
+
+
+int inflate_blocks(s, z, r)
+inflate_blocks_statef *s;
+z_streamp z;
+int r;
+{
+  uInt t;
+  uLong b;
+  uInt k;
+  Bytef *p;
+  uInt n;
+  Bytef *q;
+  uInt m;
+
+
+  {{p=z->next_in;n=z->avail_in;b=s->bitb;k=s->bitk;} {q=s->write;m=(uInt)(uInt)(q<s->read?s->read-q-1:s->end-q);}}
+
+
+  while (1) switch (s->mode)
+  {
+    case TYPE:
+      {while(k<(3)){{if(n)r=0;else {{{s->bitb=b;s->bitk=k;} {z->avail_in=n;z->total_in+=p-z->next_in;z->next_in=p;} {s->write=q;}} return inflate_flush(s,z,r);}};b|=((uLong)(n--,*p++))<<k;k+=8;}}
+      t = (uInt)b & 7;
+      s->last = t & 1;
+      switch (t >> 1)
+      {
+        case 0:
+         
+                                           ;
+          {b>>=(3);k-=(3);}
+          t = k & 7;
+          {b>>=(t);k-=(t);}
+          s->mode = LENS;
+          break;
+        case 1:
+         
+                                           ;
+          {
+            uInt bl, bd;
+            inflate_huft *tl, *td;
+
+            inflate_trees_fixed(&bl, &bd, &tl, &td, z);
+            s->sub.decode.codes = inflate_codes_new(bl, bd, tl, td, z);
+            if (s->sub.decode.codes == 0)
+            {
+              r = (-4);
+              {{{s->bitb=b;s->bitk=k;} {z->avail_in=n;z->total_in+=p-z->next_in;z->next_in=p;} {s->write=q;}} return inflate_flush(s,z,r);}
+            }
+          }
+          {b>>=(3);k-=(3);}
+          s->mode = CODES;
+          break;
+        case 2:
+         
+                                           ;
+          {b>>=(3);k-=(3);}
+          s->mode = TABLE;
+          break;
+        case 3:
+          {b>>=(3);k-=(3);}
+          s->mode = BAD;
+          z->msg = (char*)"invalid block type";
+          r = (-3);
+          {{{s->bitb=b;s->bitk=k;} {z->avail_in=n;z->total_in+=p-z->next_in;z->next_in=p;} {s->write=q;}} return inflate_flush(s,z,r);}
+      }
+      break;
+    case LENS:
+      {while(k<(32)){{if(n)r=0;else {{{s->bitb=b;s->bitk=k;} {z->avail_in=n;z->total_in+=p-z->next_in;z->next_in=p;} {s->write=q;}} return inflate_flush(s,z,r);}};b|=((uLong)(n--,*p++))<<k;k+=8;}}
+      if ((((~b) >> 16) & 0xffff) != (b & 0xffff))
+      {
+        s->mode = BAD;
+        z->msg = (char*)"invalid stored block lengths";
+        r = (-3);
+        {{{s->bitb=b;s->bitk=k;} {z->avail_in=n;z->total_in+=p-z->next_in;z->next_in=p;} {s->write=q;}} return inflate_flush(s,z,r);}
+      }
+      s->sub.left = (uInt)b & 0xffff;
+      b = k = 0;
+      ;
+      s->mode = s->sub.left ? STORED : (s->last ? DRY : TYPE);
+      break;
+    case STORED:
+      if (n == 0)
+        {{{s->bitb=b;s->bitk=k;} {z->avail_in=n;z->total_in+=p-z->next_in;z->next_in=p;} {s->write=q;}} return inflate_flush(s,z,r);}
+      {if(m==0){{if(q==s->end&&s->read!=s->window){q=s->window;m=(uInt)(uInt)(q<s->read?s->read-q-1:s->end-q);}} if(m==0){{{s->write=q;} r=inflate_flush(s,z,r); {q=s->write;m=(uInt)(uInt)(q<s->read?s->read-q-1:s->end-q);}} {if(q==s->end&&s->read!=s->window){q=s->window;m=(uInt)(uInt)(q<s->read?s->read-q-1:s->end-q);}} if(m==0) {{{s->bitb=b;s->bitk=k;} {z->avail_in=n;z->total_in+=p-z->next_in;z->next_in=p;} {s->write=q;}} return inflate_flush(s,z,r);}}}r=0;}
+      t = s->sub.left;
+      if (t > n) t = n;
+      if (t > m) t = m;
+      memcpy(q, p, t);
+      p += t; n -= t;
+      q += t; m -= t;
+      if ((s->sub.left -= t) != 0)
+        break;
+     
+
+                                                     ;
+      s->mode = s->last ? DRY : TYPE;
+      break;
+    case TABLE:
+      {while(k<(14)){{if(n)r=0;else {{{s->bitb=b;s->bitk=k;} {z->avail_in=n;z->total_in+=p-z->next_in;z->next_in=p;} {s->write=q;}} return inflate_flush(s,z,r);}};b|=((uLong)(n--,*p++))<<k;k+=8;}}
+      s->sub.trees.table = t = (uInt)b & 0x3fff;
+
+      if ((t & 0x1f) > 29 || ((t >> 5) & 0x1f) > 29)
+      {
+        s->mode = BAD;
+        z->msg = (char*)"too many length or distance symbols";
+        r = (-3);
+        {{{s->bitb=b;s->bitk=k;} {z->avail_in=n;z->total_in+=p-z->next_in;z->next_in=p;} {s->write=q;}} return inflate_flush(s,z,r);}
+      }
+
+      t = 258 + (t & 0x1f) + ((t >> 5) & 0x1f);
+      if ((s->sub.trees.blens = (uIntf*)(*((z)->zalloc))((z)->opaque, (t), (sizeof(uInt)))) == 0)
+      {
+        r = (-4);
+        {{{s->bitb=b;s->bitk=k;} {z->avail_in=n;z->total_in+=p-z->next_in;z->next_in=p;} {s->write=q;}} return inflate_flush(s,z,r);}
+      }
+      {b>>=(14);k-=(14);}
+      s->sub.trees.index = 0;
+      ;
+      s->mode = BTREE;
+    case BTREE:
+      while (s->sub.trees.index < 4 + (s->sub.trees.table >> 10))
+      {
+        {while(k<(3)){{if(n)r=0;else {{{s->bitb=b;s->bitk=k;} {z->avail_in=n;z->total_in+=p-z->next_in;z->next_in=p;} {s->write=q;}} return inflate_flush(s,z,r);}};b|=((uLong)(n--,*p++))<<k;k+=8;}}
+        s->sub.trees.blens[border[s->sub.trees.index++]] = (uInt)b & 7;
+        {b>>=(3);k-=(3);}
+      }
+      while (s->sub.trees.index < 19)
+        s->sub.trees.blens[border[s->sub.trees.index++]] = 0;
+      s->sub.trees.bb = 7;
+      t = inflate_trees_bits(s->sub.trees.blens, &s->sub.trees.bb,
+                             &s->sub.trees.tb, s->hufts, z);
+      if (t != 0)
+      {
+        (*((z)->zfree))((z)->opaque, (voidpf)(s->sub.trees.blens));
+        r = t;
+        if (r == (-3))
+          s->mode = BAD;
+        {{{s->bitb=b;s->bitk=k;} {z->avail_in=n;z->total_in+=p-z->next_in;z->next_in=p;} {s->write=q;}} return inflate_flush(s,z,r);}
+      }
+      s->sub.trees.index = 0;
+      ;
+      s->mode = DTREE;
+    case DTREE:
+      while (t = s->sub.trees.table,
+             s->sub.trees.index < 258 + (t & 0x1f) + ((t >> 5) & 0x1f))
+      {
+        inflate_huft *h;
+        uInt i, j, c;
+
+        t = s->sub.trees.bb;
+        {while(k<(t)){{if(n)r=0;else {{{s->bitb=b;s->bitk=k;} {z->avail_in=n;z->total_in+=p-z->next_in;z->next_in=p;} {s->write=q;}} return inflate_flush(s,z,r);}};b|=((uLong)(n--,*p++))<<k;k+=8;}}
+        h = s->sub.trees.tb + ((uInt)b & inflate_mask[t]);
+        t = h->word.what.Bits;
+        c = h->base;
+        if (c < 16)
+        {
+          {b>>=(t);k-=(t);}
+          s->sub.trees.blens[s->sub.trees.index++] = c;
+        }
+        else
+        {
+          i = c == 18 ? 7 : c - 14;
+          j = c == 18 ? 11 : 3;
+          {while(k<(t + i)){{if(n)r=0;else {{{s->bitb=b;s->bitk=k;} {z->avail_in=n;z->total_in+=p-z->next_in;z->next_in=p;} {s->write=q;}} return inflate_flush(s,z,r);}};b|=((uLong)(n--,*p++))<<k;k+=8;}}
+          {b>>=(t);k-=(t);}
+          j += (uInt)b & inflate_mask[i];
+          {b>>=(i);k-=(i);}
+          i = s->sub.trees.index;
+          t = s->sub.trees.table;
+          if (i + j > 258 + (t & 0x1f) + ((t >> 5) & 0x1f) ||
+              (c == 16 && i < 1))
+          {
+            (*((z)->zfree))((z)->opaque, (voidpf)(s->sub.trees.blens));
+            s->mode = BAD;
+            z->msg = (char*)"invalid bit length repeat";
+            r = (-3);
+            {{{s->bitb=b;s->bitk=k;} {z->avail_in=n;z->total_in+=p-z->next_in;z->next_in=p;} {s->write=q;}} return inflate_flush(s,z,r);}
+          }
+          c = c == 16 ? s->sub.trees.blens[i - 1] : 0;
+          do {
+            s->sub.trees.blens[i++] = c;
+          } while (--j);
+          s->sub.trees.index = i;
+        }
+      }
+      s->sub.trees.tb = 0;
+      {
+        uInt bl, bd;
+        inflate_huft *tl, *td;
+        inflate_codes_statef *c;
+
+        bl = 9;
+        bd = 6;
+        t = s->sub.trees.table;
+        t = inflate_trees_dynamic(257 + (t & 0x1f), 1 + ((t >> 5) & 0x1f),
+                                  s->sub.trees.blens, &bl, &bd, &tl, &td,
+                                  s->hufts, z);
+        (*((z)->zfree))((z)->opaque, (voidpf)(s->sub.trees.blens));
+        if (t != 0)
+        {
+          if (t == (uInt)(-3))
+            s->mode = BAD;
+          r = t;
+          {{{s->bitb=b;s->bitk=k;} {z->avail_in=n;z->total_in+=p-z->next_in;z->next_in=p;} {s->write=q;}} return inflate_flush(s,z,r);}
+        }
+        ;
+        if ((c = inflate_codes_new(bl, bd, tl, td, z)) == 0)
+        {
+          r = (-4);
+          {{{s->bitb=b;s->bitk=k;} {z->avail_in=n;z->total_in+=p-z->next_in;z->next_in=p;} {s->write=q;}} return inflate_flush(s,z,r);}
+        }
+        s->sub.decode.codes = c;
+      }
+      s->mode = CODES;
+    case CODES:
+      {{s->bitb=b;s->bitk=k;} {z->avail_in=n;z->total_in+=p-z->next_in;z->next_in=p;} {s->write=q;}}
+      if ((r = inflate_codes(s, z, r)) != 1)
+        return inflate_flush(s, z, r);
+      r = 0;
+      inflate_codes_free(s->sub.decode.codes, z);
+      {{p=z->next_in;n=z->avail_in;b=s->bitb;k=s->bitk;} {q=s->write;m=(uInt)(uInt)(q<s->read?s->read-q-1:s->end-q);}}
+     
+
+                                                     ;
+      if (!s->last)
+      {
+        s->mode = TYPE;
+        break;
+      }
+      s->mode = DRY;
+    case DRY:
+      {{s->write=q;} r=inflate_flush(s,z,r); {q=s->write;m=(uInt)(uInt)(q<s->read?s->read-q-1:s->end-q);}}
+      if (s->read != s->write)
+        {{{s->bitb=b;s->bitk=k;} {z->avail_in=n;z->total_in+=p-z->next_in;z->next_in=p;} {s->write=q;}} return inflate_flush(s,z,r);}
+      s->mode = DONE;
+    case DONE:
+      r = 1;
+      {{{s->bitb=b;s->bitk=k;} {z->avail_in=n;z->total_in+=p-z->next_in;z->next_in=p;} {s->write=q;}} return inflate_flush(s,z,r);}
+    case BAD:
+      r = (-3);
+      {{{s->bitb=b;s->bitk=k;} {z->avail_in=n;z->total_in+=p-z->next_in;z->next_in=p;} {s->write=q;}} return inflate_flush(s,z,r);}
+    default:
+      r = (-2);
+      {{{s->bitb=b;s->bitk=k;} {z->avail_in=n;z->total_in+=p-z->next_in;z->next_in=p;} {s->write=q;}} return inflate_flush(s,z,r);}
+  }
+}
+
+
+int inflate_blocks_free(s, z)
+inflate_blocks_statef *s;
+z_streamp z;
+{
+  inflate_blocks_reset(s, z, 0);
+  (*((z)->zfree))((z)->opaque, (voidpf)(s->window));
+  (*((z)->zfree))((z)->opaque, (voidpf)(s->hufts));
+  (*((z)->zfree))((z)->opaque, (voidpf)(s));
+  ;
+  return 0;
+}
+
+
+void inflate_set_dictionary(s, d, n)
+inflate_blocks_statef *s;
+const Bytef *d;
+uInt n;
+{
+  memcpy(s->window, d, n);
+  s->read = s->write = s->window + n;
+}
+
+
+
+
+
+
+int inflate_blocks_sync_point(s)
+inflate_blocks_statef *s;
+{
+  return s->mode == LENS;
+}
+
+/* { dg-regexp "\[^\n\r\]+: warning: analysis bailed out early \\(\[0-9\]+ 'after-snode' enodes; \[0-9\]+ enodes\\)" } */
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/alloca-leak.c b/gcc/testsuite/gcc.dg/analyzer/alloca-leak.c
new file mode 100644
index 0000000..6d9fe34
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/alloca-leak.c
@@ -0,0 +1,8 @@
+#include <alloca.h>
+
+void *test (void)
+{
+  void *ptr = alloca (64);
+  return ptr;
+}
+/* TODO: warn about escaping alloca.  */
diff --git a/gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-0.c b/gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-0.c
new file mode 100644
index 0000000..c9569fb
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-0.c
@@ -0,0 +1,131 @@
+/* { dg-additional-options "-fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fdiagnostics-path-format=inline-events -fanalyzer-verbosity=0" } */
+
+#include <stdlib.h>
+
+void calls_free_1 (void *ptr)
+{
+  free (ptr); /* { dg-warning "double-free of 'ptr'" } */
+}
+
+void test_1 (void *ptr, int a, int b)
+{
+  if (a)
+    calls_free_1 (ptr);
+
+  if (b)
+    {
+    }
+  else
+    calls_free_1 (ptr);
+}
+
+/* { dg-begin-multiline-output "" }
+  'test_1': event 1
+      |
+      |   NN |     calls_free_1 (ptr);
+      |      |     ^~~~~~~~~~~~~~~~~~
+      |      |     |
+      |      |     (1) calling 'calls_free_1' from 'test_1'
+      |
+      +--> 'calls_free_1': event 2
+                  |
+                  |   NN |   free (ptr);
+                  |      |   ^~~~~~~~~~
+                  |      |   |
+                  |      |   (2) first 'free' here
+                  |
+      <-----------+
+      |
+  'test_1': events 3-4
+      |
+      |   NN |     calls_free_1 (ptr);
+      |      |     ^~~~~~~~~~~~~~~~~~
+      |      |     |
+      |      |     (3) returning to 'test_1' from 'calls_free_1'
+      |......
+      |   NN |     calls_free_1 (ptr);
+      |      |     ~~~~~~~~~~~~~~~~~~
+      |      |     |
+      |      |     (4) passing freed pointer 'ptr' in call to 'calls_free_1' from 'test_1'
+      |
+      +--> 'calls_free_1': event 5
+                  |
+                  |   NN |   free (ptr);
+                  |      |   ^~~~~~~~~~
+                  |      |   |
+                  |      |   (5) second 'free' here; first 'free' was at (2)
+                  |
+  { dg-end-multiline-output "" } */
+
+void calls_free_2 (void *ptr)
+{
+  free (ptr); /* { dg-warning "double-free of 'ptr'" } */
+}
+
+void test_2 (void *ptr, int a, int b)
+{
+  switch (a)
+    {
+    default:
+      break;
+    case 1:
+      break;
+    case 3:
+      calls_free_2 (ptr);
+      break;
+    }
+
+  switch (b)
+    {
+    default:
+      calls_free_2 (ptr);
+      break;
+    case 1:
+      break;
+    case 42:
+      break;
+    }
+}
+
+/* { dg-begin-multiline-output "" }
+  'test_2': event 1
+      |
+      |   NN |       calls_free_2 (ptr);
+      |      |       ^~~~~~~~~~~~~~~~~~
+      |      |       |
+      |      |       (1) calling 'calls_free_2' from 'test_2'
+      |
+      +--> 'calls_free_2': event 2
+                  |
+                  |   NN |   free (ptr);
+                  |      |   ^~~~~~~~~~
+                  |      |   |
+                  |      |   (2) first 'free' here
+                  |
+      <-----------+
+      |
+  'test_2': events 3-4
+      |
+      |   NN |       calls_free_2 (ptr);
+      |      |       ^~~~~~~~~~~~~~~~~~
+      |      |       |
+      |      |       (3) returning to 'test_2' from 'calls_free_2'
+      |......
+      |   NN |       calls_free_2 (ptr);
+      |      |       ~~~~~~~~~~~~~~~~~~
+      |      |       |
+      |      |       (4) passing freed pointer 'ptr' in call to 'calls_free_2' from 'test_2'
+      |
+      +--> 'calls_free_2': event 5
+                  |
+                  |   NN |   free (ptr);
+                  |      |   ^~~~~~~~~~
+                  |      |   |
+                  |      |   (5) second 'free' here; first 'free' was at (2)
+                  |
+  { dg-end-multiline-output "" } */
+
+// TODO: range cases
+
+// FIXME: show the inline view even when it's not interprocedural?
+
diff --git a/gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-1.c b/gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-1.c
new file mode 100644
index 0000000..9138d18
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-1.c
@@ -0,0 +1,158 @@
+/* { dg-additional-options "-fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fdiagnostics-path-format=inline-events -fanalyzer-verbosity=1" } */
+
+#include <stdlib.h>
+
+void calls_free_1 (void *ptr)
+{
+  free (ptr); /* { dg-warning "double-free of 'ptr'" } */
+}
+
+void test_1 (void *ptr, int a, int b)
+{
+  if (a)
+    calls_free_1 (ptr);
+
+  if (b)
+    {
+    }
+  else
+    calls_free_1 (ptr);
+}
+
+/* { dg-begin-multiline-output "" }
+  'test_1': events 1-2
+      |
+      |   NN | void test_1 (void *ptr, int a, int b)
+      |      |      ^~~~~~
+      |      |      |
+      |      |      (1) entry to 'test_1'
+      |......
+      |   NN |     calls_free_1 (ptr);
+      |      |     ~~~~~~~~~~~~~~~~~~
+      |      |     |
+      |      |     (2) calling 'calls_free_1' from 'test_1'
+      |
+      +--> 'calls_free_1': events 3-4
+                  |
+                  |   NN | void calls_free_1 (void *ptr)
+                  |      |      ^~~~~~~~~~~~
+                  |      |      |
+                  |      |      (3) entry to 'calls_free_1'
+                  |   NN | {
+                  |   NN |   free (ptr);
+                  |      |   ~~~~~~~~~~
+                  |      |   |
+                  |      |   (4) first 'free' here
+                  |
+      <-----------+
+      |
+  'test_1': events 5-6
+      |
+      |   NN |     calls_free_1 (ptr);
+      |      |     ^~~~~~~~~~~~~~~~~~
+      |      |     |
+      |      |     (5) returning to 'test_1' from 'calls_free_1'
+      |......
+      |   NN |     calls_free_1 (ptr);
+      |      |     ~~~~~~~~~~~~~~~~~~
+      |      |     |
+      |      |     (6) passing freed pointer 'ptr' in call to 'calls_free_1' from 'test_1'
+      |
+      +--> 'calls_free_1': events 7-8
+                  |
+                  |   NN | void calls_free_1 (void *ptr)
+                  |      |      ^~~~~~~~~~~~
+                  |      |      |
+                  |      |      (7) entry to 'calls_free_1'
+                  |   NN | {
+                  |   NN |   free (ptr);
+                  |      |   ~~~~~~~~~~
+                  |      |   |
+                  |      |   (8) second 'free' here; first 'free' was at (4)
+                  |
+  { dg-end-multiline-output "" } */
+
+void calls_free_2 (void *ptr)
+{
+  free (ptr); /* { dg-warning "double-free of 'ptr'" } */
+}
+
+void test_2 (void *ptr, int a, int b)
+{
+  switch (a)
+    {
+    default:
+      break;
+    case 1:
+      break;
+    case 3:
+      calls_free_2 (ptr);
+      break;
+    }
+
+  switch (b)
+    {
+    default:
+      calls_free_2 (ptr);
+      break;
+    case 1:
+      break;
+    case 42:
+      break;
+    }
+}
+
+/* { dg-begin-multiline-output "" }
+  'test_2': events 1-2
+      |
+      |   NN | void test_2 (void *ptr, int a, int b)
+      |      |      ^~~~~~
+      |      |      |
+      |      |      (1) entry to 'test_2'
+      |......
+      |   NN |       calls_free_2 (ptr);
+      |      |       ~~~~~~~~~~~~~~~~~~
+      |      |       |
+      |      |       (2) calling 'calls_free_2' from 'test_2'
+      |
+      +--> 'calls_free_2': events 3-4
+                  |
+                  |   NN | void calls_free_2 (void *ptr)
+                  |      |      ^~~~~~~~~~~~
+                  |      |      |
+                  |      |      (3) entry to 'calls_free_2'
+                  |   NN | {
+                  |   NN |   free (ptr);
+                  |      |   ~~~~~~~~~~
+                  |      |   |
+                  |      |   (4) first 'free' here
+                  |
+      <-----------+
+      |
+  'test_2': events 5-6
+      |
+      |   NN |       calls_free_2 (ptr);
+      |      |       ^~~~~~~~~~~~~~~~~~
+      |      |       |
+      |      |       (5) returning to 'test_2' from 'calls_free_2'
+      |......
+      |   NN |       calls_free_2 (ptr);
+      |      |       ~~~~~~~~~~~~~~~~~~
+      |      |       |
+      |      |       (6) passing freed pointer 'ptr' in call to 'calls_free_2' from 'test_2'
+      |
+      +--> 'calls_free_2': events 7-8
+                  |
+                  |   NN | void calls_free_2 (void *ptr)
+                  |      |      ^~~~~~~~~~~~
+                  |      |      |
+                  |      |      (7) entry to 'calls_free_2'
+                  |   NN | {
+                  |   NN |   free (ptr);
+                  |      |   ~~~~~~~~~~
+                  |      |   |
+                  |      |   (8) second 'free' here
+                  |
+  { dg-end-multiline-output "" } */
+
+// FIXME: show the inline view even when it's not interprocedural?
diff --git a/gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-2.c b/gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-2.c
new file mode 100644
index 0000000..b6e89f1
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-2.c
@@ -0,0 +1,179 @@
+/* { dg-additional-options "-fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fdiagnostics-path-format=inline-events -fanalyzer-verbosity=2" } */
+
+#include <stdlib.h>
+
+void calls_free_1 (void *ptr)
+{
+  free (ptr); /* { dg-warning "double-free of 'ptr'" } */
+}
+
+void test_1 (void *ptr, int a, int b)
+{
+  if (a)
+    calls_free_1 (ptr);
+
+  if (b)
+    {
+    }
+  else
+    calls_free_1 (ptr);
+}
+
+/* { dg-begin-multiline-output "" }
+  'test_1': events 1-3
+      |
+      |   NN | void test_1 (void *ptr, int a, int b)
+      |      |      ^~~~~~
+      |      |      |
+      |      |      (1) entry to 'test_1'
+      |   NN | {
+      |   NN |   if (a)
+      |      |      ~
+      |      |      |
+      |      |      (2) following 'true' branch
+      |   NN |     calls_free_1 (ptr);
+      |      |     ~~~~~~~~~~~~~~~~~~
+      |      |     |
+      |      |     (3) calling 'calls_free_1' from 'test_1'
+      |
+      +--> 'calls_free_1': events 4-5
+                  |
+                  |   NN | void calls_free_1 (void *ptr)
+                  |      |      ^~~~~~~~~~~~
+                  |      |      |
+                  |      |      (4) entry to 'calls_free_1'
+                  |   NN | {
+                  |   NN |   free (ptr);
+                  |      |   ~~~~~~~~~~
+                  |      |   |
+                  |      |   (5) first 'free' here
+                  |
+      <-----------+
+      |
+  'test_1': events 6-8
+      |
+      |   NN |     calls_free_1 (ptr);
+      |      |     ^~~~~~~~~~~~~~~~~~
+      |      |     |
+      |      |     (6) returning to 'test_1' from 'calls_free_1'
+      |   NN | 
+      |   NN |   if (b)
+      |      |      ~
+      |      |      |
+      |      |      (7) following 'false' branch
+      |......
+      |   NN |     calls_free_1 (ptr);
+      |      |     ~~~~~~~~~~~~~~~~~~
+      |      |     |
+      |      |     (8) passing freed pointer 'ptr' in call to 'calls_free_1' from 'test_1'
+      |
+      +--> 'calls_free_1': events 9-10
+                  |
+                  |   NN | void calls_free_1 (void *ptr)
+                  |      |      ^~~~~~~~~~~~
+                  |      |      |
+                  |      |      (9) entry to 'calls_free_1'
+                  |   NN | {
+                  |   NN |   free (ptr);
+                  |      |   ~~~~~~~~~~
+                  |      |   |
+                  |      |   (10) second 'free' here; first 'free' was at (5)
+                  |
+  { dg-end-multiline-output "" } */
+
+void calls_free_2 (void *ptr)
+{
+  free (ptr); /* { dg-warning "double-free of 'ptr'" } */
+}
+
+void test_2 (void *ptr, int a, int b)
+{
+  switch (a)
+    {
+    default:
+      break;
+    case 1:
+      break;
+    case 3:
+      calls_free_2 (ptr);
+      break;
+    }
+
+  switch (b)
+    {
+    default:
+      calls_free_2 (ptr);
+      break;
+    case 1:
+      break;
+    case 42:
+      break;
+    }
+}
+
+/* { dg-begin-multiline-output "" }
+  'test_2': events 1-3
+      |
+      |   NN | void test_2 (void *ptr, int a, int b)
+      |      |      ^~~~~~
+      |      |      |
+      |      |      (1) entry to 'test_2'
+      |   NN | {
+      |   NN |   switch (a)
+      |      |   ~~~~~~
+      |      |   |
+      |      |   (2) following 'case 3:' branch
+      |......
+      |   NN |       calls_free_2 (ptr);
+      |      |       ~~~~~~~~~~~~~~~~~~
+      |      |       |
+      |      |       (3) calling 'calls_free_2' from 'test_2'
+      |
+      +--> 'calls_free_2': events 4-5
+                  |
+                  |   NN | void calls_free_2 (void *ptr)
+                  |      |      ^~~~~~~~~~~~
+                  |      |      |
+                  |      |      (4) entry to 'calls_free_2'
+                  |   NN | {
+                  |   NN |   free (ptr);
+                  |      |   ~~~~~~~~~~
+                  |      |   |
+                  |      |   (5) first 'free' here
+                  |
+      <-----------+
+      |
+  'test_2': events 6-8
+      |
+      |   NN |       calls_free_2 (ptr);
+      |      |       ^~~~~~~~~~~~~~~~~~
+      |      |       |
+      |      |       (6) returning to 'test_2' from 'calls_free_2'
+      |......
+      |   NN |   switch (b)
+      |      |   ~~~~~~
+      |      |   |
+      |      |   (7) following 'default:' branch
+      |......
+      |   NN |       calls_free_2 (ptr);
+      |      |       ~~~~~~~~~~~~~~~~~~
+      |      |       |
+      |      |       (8) passing freed pointer 'ptr' in call to 'calls_free_2' from 'test_2'
+      |
+      +--> 'calls_free_2': events 9-10
+                  |
+                  |   NN | void calls_free_2 (void *ptr)
+                  |      |      ^~~~~~~~~~~~
+                  |      |      |
+                  |      |      (9) entry to 'calls_free_2'
+                  |   NN | {
+                  |   NN |   free (ptr);
+                  |      |   ~~~~~~~~~~
+                  |      |   |
+                  |      |   (10) second 'free' here; first 'free' was at (5)
+                  |
+  { dg-end-multiline-output "" } */
+
+// TODO: range cases
+
+// FIXME: show the inline view even when it's not interprocedural?
diff --git a/gcc/testsuite/gcc.dg/analyzer/analyzer.exp b/gcc/testsuite/gcc.dg/analyzer/analyzer.exp
new file mode 100644
index 0000000..19afe57
--- /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-format=separate-events"
+}
+
+# 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/attribute-nonnull.c b/gcc/testsuite/gcc.dg/analyzer/attribute-nonnull.c
new file mode 100644
index 0000000..ab44d07
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/attribute-nonnull.c
@@ -0,0 +1,57 @@
+#include <stdlib.h>
+
+extern void foo(void *ptrA, void *ptrB, void *ptrC) /* { dg-message "argument 1 of 'foo' must be non-null" } */
+  __attribute__((nonnull (1, 3)));
+
+extern void bar(void *ptrA, void *ptrB, void *ptrC) /* { dg-message "argument 1 of 'bar' must be non-null" } */
+  __attribute__((nonnull));
+
+// TODO: complain about NULL and possible NULL args
+// FIXME: ought to complain about NULL args
+
+void test_1 (void *p, void *q, void *r)
+{
+  foo(p, q, r);
+  foo(NULL, q, r);
+  foo(p, NULL, r);
+  foo(p, q, NULL);
+}
+
+void test_1a (void *q, void *r)
+{
+  void *p = NULL;
+  foo(p, q, r); /* { dg-warning "use of NULL 'p' where non-null expected" } */
+  /* { dg-message "argument 1 \\('p'\\) NULL where non-null expected" "" { target *-*-* } .-1 } */
+}
+
+void test_2 (void *p, void *q, void *r)
+{
+  bar(p, q, r);
+  bar(NULL, q, r);
+  bar(p, NULL, r);
+  bar(p, q, NULL);
+}
+
+void test_3 (void *q, void *r)
+{
+  void *p = malloc(1024); /* { dg-message "this call could return NULL" } */
+
+  foo(p, q, r); /* { dg-warning "use of possibly-NULL 'p' where non-null expected" } */
+  /* { dg-message "argument 1 \\('p'\\) could be NULL where non-null expected" "" { target *-*-* } .-1 } */
+
+  foo(p, q, r);
+
+  free(p);
+}
+
+void test_4 (void *q, void *r)
+{
+  void *p = malloc(1024); /* { dg-message "this call could return NULL" } */
+
+  bar(p, q, r); /* { dg-warning "use of possibly-NULL 'p' where non-null expected" } */
+  /* { dg-message "argument 1 \\('p'\\) could be NULL where non-null expected" "" { target *-*-* } .-1 } */
+
+  bar(p, q, r);
+
+  free(p);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/conditionals.c b/gcc/testsuite/gcc.dg/analyzer/conditionals.c
new file mode 100644
index 0000000..c0cab96
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/conditionals.c
@@ -0,0 +1,141 @@
+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_3 (int flag, unsigned int i)
+{
+  if (!flag) {
+    return;
+  }
+
+  __analyzer_eval (flag); /* { dg-warning "TRUE" } */
+      
+  if (i>0) {
+    __analyzer_eval (i > 0); /* { dg-warning "TRUE" } */
+    __analyzer_eval (flag); /* { dg-warning "TRUE" } */
+  } else {
+    __analyzer_eval (i <= 0); /* { dg-warning "TRUE" } */
+    __analyzer_eval (flag); /* { dg-warning "TRUE" } */
+  }
+
+  __analyzer_eval (flag); /* { 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" } */
+}
+
+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" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-1.c b/gcc/testsuite/gcc.dg/analyzer/data-model-1.c
new file mode 100644
index 0000000..cd1e5cd
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-1.c
@@ -0,0 +1,896 @@
+#include <stdlib.h>
+#include <string.h>
+
+struct foo
+{
+  int i;
+};
+
+/* Fields of a local.  */
+
+void test_1 (void)
+{
+  struct foo f;
+  f.i = 1;
+  __analyzer_eval (f.i == 1); /* { dg-warning "TRUE" } */
+}
+
+/* Fields of a param.  */
+
+void test_2 (struct foo f)
+{
+  __analyzer_eval (f.i == 2); /* { dg-warning "UNKNOWN" } */
+  f.i = 2;
+  __analyzer_eval (f.i == 2); /* { dg-warning "TRUE" } */
+}
+
+/* Fields of a param ptr.  */
+
+void test_3 (struct foo *f)
+{
+  __analyzer_eval (f->i == 3); /* { dg-warning "UNKNOWN" } */
+  f->i = 3;
+  __analyzer_eval (f->i == 3); /* { dg-warning "TRUE" } */
+}
+
+/* Fields of a global ptr.  */
+struct foo *global_foo_ptr;
+
+void test_3a (void)
+{
+  struct foo *tmp = global_foo_ptr;
+  __analyzer_eval (global_foo_ptr->i == 3); /* { dg-warning "UNKNOWN" } */
+  global_foo_ptr->i = 3;
+  __analyzer_eval (global_foo_ptr->i == 3); /* { dg-warning "TRUE" } */
+}
+
+/* Pointer to a local.  */
+
+void test_4 (void)
+{
+  int i;
+  int *p = &i;
+  i = 1;
+  *p = 2;
+  __analyzer_eval (i == 2); /* { dg-warning "TRUE" } */
+}
+
+/* Local array.  */
+
+void test_5 (void)
+{
+  int a[10];
+  a[3] = 5; /* ARRAY_REF.  */
+  __analyzer_eval (a[3] == 5); /* { dg-warning "TRUE" } */
+}
+
+/* Local array, but using an unknown index.  */
+
+void test_5a (int idx)
+{
+  int a[10];
+  a[idx] = 5; /* ARRAY_REF.  */
+  __analyzer_eval (a[idx] == 5); /* { dg-warning "TRUE" } */
+}
+
+/* Array passed in as a param.  */
+
+void test_6 (int a[10])
+{
+  /* POINTER_PLUS_EXPR then a MEM_REF.  */
+  __analyzer_eval (a[3] == 42); /* { dg-warning "UNKNOWN" } */
+  a[3] = 42;
+  __analyzer_eval (a[3] == 42); /* { dg-warning "TRUE" } */
+}
+
+/* Array passed in as a param ptr.  */
+
+void test_7 (int *a)
+{
+  __analyzer_eval (a[3] == 42); /* { dg-warning "UNKNOWN" } */
+  a[3] = 42;
+  __analyzer_eval (a[3] == 42); /* { dg-warning "TRUE" } */
+}
+
+/* Globals.  */
+
+int glob_a;
+
+void test_10 (void)
+{
+  __analyzer_eval (glob_a == 42); /* { dg-warning "UNKNOWN" } */
+  glob_a = 42;
+  __analyzer_eval (glob_a == 42); /* { dg-warning "TRUE" } */
+}
+
+/* malloc.  */
+
+void test_11 (void)
+{
+  void *p = malloc (256);
+  void *q = malloc (256);
+
+  /* malloc results should be unique.  */
+  __analyzer_eval (p == q); /* { dg-warning "FALSE" } */
+  __analyzer_eval (p != q); /* { dg-warning "TRUE" } */
+  __analyzer_eval (p <= q); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (p >= q); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (p < q); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (p > q); /* { dg-warning "UNKNOWN" } */
+
+  __analyzer_eval (p == p); /* { dg-warning "TRUE" } */
+  __analyzer_eval (p != p); /* { dg-warning "FALSE" } */
+  __analyzer_eval (p <= p); /* { dg-warning "TRUE" } */
+  __analyzer_eval (p >= p); /* { dg-warning "TRUE" } */
+  __analyzer_eval (p < p); /* { dg-warning "FALSE" } */
+  __analyzer_eval (p > p); /* { dg-warning "FALSE" } */
+
+  free (p);
+  free (q);
+  // TODO: mark freed memory as freed
+  //__analyzer_break ();
+}
+
+/* alloca.  */
+
+void test_12 (void)
+{
+  void *p = alloca (256);
+  void *q = alloca (256);
+
+  /* alloca results should be unique.  */
+  __analyzer_eval (p == q); /* { dg-warning "FALSE" } */
+
+  // FIXME: complain about uses of poisoned values
+}
+
+/* Use of uninit value.  */
+int test_12a (void)
+{
+  int i;
+  return i; // FIXME: do we see the return stmt?
+}
+
+void test_12b (void *p, void *q)
+{
+  __analyzer_eval (p == q); /* { dg-warning "UNKNOWN" } */
+}
+
+int test_12c (void)
+{
+  int i;
+  int j;
+
+  j = i; // FIXME: should complain about this
+
+  return j;
+}
+
+struct coord
+{
+  int x;
+  int y;
+};
+
+int test_12d (struct coord c)
+{
+  struct coord d;
+  d = c;
+  __analyzer_eval (d.x == c.x); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  /* TODO(xfail): c and d share the same unknown value of type "coord", but
+     attempts to access the fields lead to different unknown values.  */
+
+  __analyzer_eval (d.y == c.y); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  // TODO(xfail): likewise
+
+  __analyzer_eval (d.x == d.y); /* { dg-warning "UNKNOWN" } */
+  /* d and c share an unknown value of type "struct coord".
+     But d.x and d.y should be different unknown values (although they inherit
+     from d's region).  */
+}
+
+/* Nested structs.  */
+
+struct outer
+{
+  struct middle {
+    struct inner {
+      float f;
+    } in;
+  } mid;
+};
+
+void test_13 (struct outer *o)
+{
+  __analyzer_eval (o->mid.in.f == 0.f); /* { dg-warning "UNKNOWN" } */
+  o->mid.in.f = 0.f;
+  __analyzer_eval (o->mid.in.f == 0.f); /* { dg-warning "TRUE" } */
+}
+
+void test_14 (struct outer o)
+{
+  __analyzer_eval (o.mid.in.f == 0.f); /* { dg-warning "UNKNOWN" } */
+  o.mid.in.f = 0.f;
+  __analyzer_eval (o.mid.in.f == 0.f); /* { dg-warning "TRUE" } */
+}
+
+void test_15 (const char *str)
+{
+  char ch = str[0];
+  __analyzer_eval (ch == 'a'); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (ch == str[0]); /* { dg-warning "TRUE" } */
+
+  ch = 'a';
+  __analyzer_eval (ch == 'a'); /* { dg-warning "TRUE" } */
+  __analyzer_eval (str[0] == 'a'); /* { dg-warning "UNKNOWN" } */
+}
+
+void test_16 (void)
+{
+  const char *msg = "hello world";
+  __analyzer_eval (msg[0] == 'h'); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  // TODO(xfail)
+
+  __analyzer_eval (msg[1] == 'e'); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  // TODO(xfail)
+
+  __analyzer_eval (strlen (msg) == 11); /* { dg-warning "TRUE" } */
+}
+
+void test_16a (const char *msg)
+{
+  __analyzer_eval (strlen (msg) == 11); /* { dg-warning "UNKNOWN" } */
+}
+
+void test_16b (const char *msg)
+{
+  __analyzer_eval (strlen (msg) == strlen (msg)); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  // TODO(xfail)
+}
+
+extern int unknown_result (void);
+
+void test_16c (int i)
+{
+  int j;
+
+  j = i;
+  __analyzer_eval (j == i); /* { dg-warning "TRUE" } */
+
+  j = unknown_result ();
+  __analyzer_eval (j == i); /* { dg-warning "UNKNOWN" } */
+}
+
+void test_16c_a (void)
+{
+  int i, j;
+
+  i = unknown_result ();
+  j = unknown_result ();
+  __analyzer_eval (i == j); /* { dg-warning "UNKNOWN" } */
+}
+
+int global_int_16d;
+
+void test_16d (int i)
+{
+  global_int_16d = i;
+  __analyzer_eval (global_int_16d == i); /* { dg-warning "TRUE" } */
+
+  global_int_16d = unknown_result ();
+  __analyzer_eval (global_int_16d == i); /* { dg-warning "UNKNOWN" } */
+}
+
+extern void might_write_to (int *);
+
+void test_16e (int i)
+{
+  int j;
+
+  j = i;
+  __analyzer_eval (j == i); /* { dg-warning "TRUE" } */
+
+  might_write_to (&j);
+  __analyzer_eval (j == i); /* { dg-warning "UNKNOWN" "" { xfail *-*-* } } */
+  /* { dg-warning "TRUE" "" { target *-*-* } .-1 } */
+  // TODO(xfail)
+}
+
+/* TODO: and more complicated graph-like examples, where anything that's
+   reachable from the pointer might be modified.  */
+
+void test_17 (int i)
+{
+  int j = 42;
+  __analyzer_eval (j == 42); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (i == j); /* { dg-warning "UNKNOWN" } */
+  i = j;
+  __analyzer_eval (i == j); /* { dg-warning "TRUE" } */
+}
+
+void test_18 (int i)
+{
+  int j;
+  __analyzer_eval (i == 42); /* { dg-warning "UNKNOWN" } */
+
+  j = i;
+
+  __analyzer_eval (i == j); /* { dg-warning "TRUE" } */
+  __analyzer_eval (i >= j); /* { dg-warning "TRUE" } */
+  __analyzer_eval (i <= j); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (i != j); /* { dg-warning "FALSE" } */
+  __analyzer_eval (i > j); /* { dg-warning "FALSE" } */
+  __analyzer_eval (i < j); /* { dg-warning "FALSE" } */
+}
+
+void test_19 (void)
+{
+  int i, j;
+  /* Compare two uninitialized locals.  */
+    __analyzer_eval (i == j); /* { dg-warning "UNKNOWN" } */
+}
+
+void test_20 (int i, int j)
+{
+  __analyzer_eval (i + 1); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (i + j); /* { dg-warning "UNKNOWN" } */
+
+  __analyzer_eval (i - 1); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (i - j); /* { dg-warning "UNKNOWN" } */
+
+  __analyzer_eval (i * 2); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (i * j); /* { dg-warning "UNKNOWN" } */
+
+  __analyzer_eval (i / 2); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (i / j); /* { dg-warning "UNKNOWN" } */
+
+  __analyzer_eval (i % 2); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (i % j); /* { dg-warning "UNKNOWN" } */
+
+  __analyzer_eval (i & 1); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (i & j); /* { dg-warning "UNKNOWN" } */
+
+  __analyzer_eval (i | 1); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (i | j); /* { dg-warning "UNKNOWN" } */
+
+  __analyzer_eval (i ^ 1); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (i ^ j); /* { dg-warning "UNKNOWN" } */
+
+  __analyzer_eval (i >> 1); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (i >> j); /* { dg-warning "UNKNOWN" } */
+
+  __analyzer_eval (i << 1); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (i << j); /* { dg-warning "UNKNOWN" } */
+
+  __analyzer_eval (i && 0); /* { dg-warning "FALSE" } */
+  __analyzer_eval (i && 1); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (i && j); /* { dg-warning "UNKNOWN" } */
+
+  __analyzer_eval (i || 0); /* { dg-warning "UNKNOWN" } */
+
+  __analyzer_eval (i || 1); /* { dg-warning "TRUE" } */
+  __analyzer_eval (i || j); /* { dg-warning "UNKNOWN" } */
+
+  __analyzer_eval (~i); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (-i); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (+i); /* { dg-warning "UNKNOWN" } */
+
+  /* TODO:
+     - division by zero
+     - any other operators?  */
+  // etc
+
+  /* Anything added above should be added to the next function also.  */
+}
+
+/* As above, but where the values are known due to the region model,
+   but not known to GCC's regular optimizations (folding and SSA).  */
+
+void test_21 (void)
+{
+  int i, j, zero;
+  int *pi = &i;
+  int *pj = &j;
+  int *pzero = &zero;
+  *pi = 5;
+  *pj = 3;
+  *pzero = 0;
+
+  __analyzer_eval (i + j == 8); /* { dg-warning "TRUE" } */
+  __analyzer_eval (i - j == 2); /* { dg-warning "TRUE" } */
+  __analyzer_eval (i * j == 15); /* { dg-warning "TRUE" } */
+  __analyzer_eval (i / j == 1); /* { dg-warning "TRUE" } */
+  __analyzer_eval (i % j == 2); /* { dg-warning "TRUE" } */
+
+  /* Division by zero.  */
+  // TODO: should we warn for this?
+  __analyzer_eval (i / zero); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (i % zero); /* { dg-warning "UNKNOWN" } */
+
+  __analyzer_eval ((i & 1) == (5 & 1)); /* { dg-warning "TRUE" } */
+  __analyzer_eval ((i & j) == (5 & 3)); /* { dg-warning "TRUE" } */
+  __analyzer_eval ((i | 1) == (5 | 1)); /* { dg-warning "TRUE" } */
+  __analyzer_eval ((i | j) == (5 | 3)); /* { dg-warning "TRUE" } */
+  __analyzer_eval ((i ^ 1) == (5 ^ 1)); /* { dg-warning "TRUE" } */
+  __analyzer_eval ((i ^ j) == (5 ^ 3)); /* { dg-warning "TRUE" } */
+  __analyzer_eval ((i >> 1) == (5 >> 1)); /* { dg-warning "TRUE" } */
+  __analyzer_eval ((i >> j) == (5 >> 3)); /* { dg-warning "TRUE" } */
+  __analyzer_eval ((i << 1) == (5 << 1)); /* { dg-warning "TRUE" } */
+  __analyzer_eval ((i << j) == (5 << 3)); /* { dg-warning "TRUE" } */
+  __analyzer_eval (i && 0); /* { dg-warning "FALSE" } */
+  __analyzer_eval (i && 1); /* { dg-warning "TRUE" } */
+  __analyzer_eval (i && j); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (i || 0); /* { dg-warning "TRUE" } */
+  __analyzer_eval (i || 1); /* { dg-warning "TRUE" } */
+  __analyzer_eval (i || j); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (~i == ~5); /* { dg-warning "TRUE" } */
+  __analyzer_eval (-i == -5); /* { dg-warning "TRUE" } */
+  __analyzer_eval (+i == +5); /* { dg-warning "TRUE" } */
+}
+
+void test_22 (int i, int j)
+{
+  __analyzer_eval (i + j == i + j); /* { dg-warning "TRUE" } */
+  // FIXME: this is getting folded; can we build a non-folded equivalent?
+}
+
+void test_23 (struct foo *f, struct foo *g)
+{
+  int i, j, k;
+  i = f->i + g->i;
+  j = f->i + g->i;
+  k = f->i * g->i;
+  __analyzer_eval (i == j); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  /* TODO(xfail): we'd need to record that the two unknown values are both
+     the sum of the two unknown input values (and thus are the same); not
+     yet sure if we want arbitrary expression trees in the representation
+     (analysis termination concerns).  */
+
+  __analyzer_eval (i == k); /* { dg-warning "UNKNOWN" } */
+}
+
+void test_24 (struct foo *f)
+{
+  struct foo g;
+  g.i = 42;
+  __analyzer_eval (g.i == 42); /* { dg-warning "TRUE" } */
+
+  /* Overwriting a whole struct should invalidate our knowledge
+     about fields within it.  */
+  g = *f;
+  __analyzer_eval (g.i == 42); /* { dg-warning "UNKNOWN" "" { xfail *-*-* } } */
+  /* { dg-warning "TRUE" "" { target *-*-* } .-1 } */
+  // TODO(xfail)
+}
+
+void test_25 (struct foo *f)
+{
+  struct foo g;
+  g.i = 42;
+  f->i = 43;
+  __analyzer_eval (f->i == 43); /* { dg-warning "TRUE" } */
+  __analyzer_eval (g.i == 42); /* { dg-warning "TRUE" } */
+
+  /* Overwriting a whole struct where we know things about the
+     source value should update our knowledge about fields within
+     the dest value.  */
+  g = *f;
+  __analyzer_eval (g.i == 43); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "FALSE" "" { target *-*-* } .-1 } */
+  // TODO(xfail)
+}
+
+void test_26 (struct coord *p, struct coord *q)
+{
+  p->x = 42;
+  q->y = 17;
+  __analyzer_eval (p->x == 42); /* { dg-warning "TRUE" } */
+  __analyzer_eval (p->y); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (q->x); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (q->y == 17); /* { dg-warning "TRUE" } */
+
+  /* Overwriting a whole struct where we know some things about the
+     source value should update our knowledge about fields within
+     the dest value.  */
+  *p = *q;
+  __analyzer_eval (p->x); /* { dg-warning "UNKNOWN" "" { xfail *-*-* } } */
+  /* { dg-warning "TRUE" "" { target *-*-* } .-1 } */
+  // TODO(xfail): should have been overwritten
+  __analyzer_eval (p->y == 17); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  // TODO(xfail): should have been overwritten with q->y
+
+  __analyzer_eval (q->x); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (q->y == 17); /* { dg-warning "TRUE" } */
+}
+
+void test_27 (struct coord *p)
+{
+  memset (p, 0, sizeof (struct coord));
+  __analyzer_eval (p->x == 0); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  // TODO(xfail):
+  __analyzer_eval (p->y == 0); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  // TODO(xfail):
+}
+
+void test_28 (struct coord *p)
+{
+  memset (p, 0, sizeof (struct coord) * 10);
+  __analyzer_eval (p[0].x == 0); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  // TODO(xfail):
+  __analyzer_eval (p[0].y == 0); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  // TODO(xfail):
+
+  __analyzer_eval (p[9].x == 0); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  // TODO(xfail):
+  __analyzer_eval (p[9].y == 0); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  // TODO(xfail):
+
+  __analyzer_eval (p[10].x == 0); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (p[10].y == 0); /* { dg-warning "UNKNOWN" } */
+}
+
+void test_29 (struct coord *p)
+{
+  struct coord *q;
+
+  p[0].x = 100024;
+  p[0].y = 100025;
+
+  p[7].x = 107024;
+  p[7].y = 107025;
+
+  p[9].x = 109024;
+  p[9].y = 109025;
+
+  __analyzer_eval (p[0].x == 100024); /* { dg-warning "TRUE" } */
+  __analyzer_eval (p[0].y == 100025); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (p[7].x == 107024); /* { dg-warning "TRUE" } */
+  __analyzer_eval (p[7].y == 107025); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (p[9].x == 109024); /* { dg-warning "TRUE" } */
+  __analyzer_eval (p[9].y == 109025); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (p[10].x == 0); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (p[10].y == 0); /* { dg-warning "UNKNOWN" } */
+
+  q = &p[7];
+
+  __analyzer_eval (q->x == 107024); /* { dg-warning "TRUE" } */
+  __analyzer_eval (q->y == 107025); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (q[2].x == 109024); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  // TODO(xfail):
+  __analyzer_eval (q[2].y == 109025); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  // TODO(xfail):
+
+  q += 2;
+
+  __analyzer_eval (q->x == 109024); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  // TODO(xfail):
+  __analyzer_eval (q->y == 109025); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  // TODO(xfail):  
+}
+
+void test_30 (void *ptr)
+{
+  struct coord *p = (struct coord *)ptr;
+  struct coord *q = (struct coord *)ptr;
+
+  p->x = 42;
+
+  __analyzer_eval (p->x == 42); /* { dg-warning "TRUE" } */
+  __analyzer_eval (q->x == 42); /* { dg-warning "TRUE" } */
+}
+
+void test_31 (unsigned i)
+{
+  int j, k;
+
+  j = i < 100 ? i : 100; /* MIN_EXPR.  */
+  k = i < 100 ? 100 : i; /* MAX_EXPR.  */
+}
+
+enum color
+{
+  RED,
+  GREEN,
+  BLUE
+};
+
+void test_32 (enum color c)
+{
+  __analyzer_eval (c == GREEN); /* { dg-warning "UNKNOWN" } */
+
+  c = RED;
+
+  __analyzer_eval (c == RED); /* { dg-warning "TRUE" } */
+  __analyzer_eval (c == GREEN); /* { dg-warning "FALSE" } */
+}
+
+void test_33 (void)
+{
+  static int s;
+
+  __analyzer_eval (s == 42); /* { dg-warning "UNKNOWN" } */
+
+  s = 42;
+
+  __analyzer_eval (s == 42); /* { dg-warning "TRUE" } */
+}
+
+static int __attribute__((noinline))
+only_called_by_test_34 (int parm)
+{
+  __analyzer_eval (parm == 42); /* { dg-warning "TRUE" } */
+
+  return parm * 2;
+}
+
+void test_34 (void)
+{
+  int result = only_called_by_test_34 (42);
+  __analyzer_eval (result == 84); /* { dg-warning "TRUE" } */
+}
+
+void test_35 (int i, int j)
+{
+  __analyzer_eval (&i == &i); /* { dg-warning "TRUE" } */
+  __analyzer_eval (&i != &j); /* { dg-warning "TRUE" } */
+}
+
+static void __attribute__((noinline))
+write_through_ptr (int *dst, int val)
+{
+  *dst = val;
+}
+
+void test_36 (int i)
+{
+  __analyzer_eval (i == 42); /* { dg-warning "UNKNOWN" } */
+
+  write_through_ptr (&i, 42);
+
+  __analyzer_eval (i == 42); /* { dg-warning "TRUE" } */
+}
+
+/* Read through uninitialized pointer.  */
+
+int test_37 (void)
+{
+  int *ptr;
+  return *ptr; /* { dg-warning "dereference of uninitialized pointer 'ptr'" } */
+}
+
+/* Write through uninitialized pointer.  */
+
+void test_37a (int i)
+{
+  int *ptr;
+  *ptr = i; /* { dg-warning "dereference of uninitialized pointer 'ptr'" } */
+}
+
+// TODO: the various other ptr deref poisonings
+
+/* Read through NULL pointer.  */
+
+int test_38 (void)
+{
+  int *ptr = NULL;
+  return *ptr; /* { dg-warning "use of NULL 'ptr'" } */
+}
+
+/* Write through NULL pointer.  */
+
+int test_38a (int i)
+{
+  int *ptr = NULL;
+  *ptr = i; /* { dg-warning "use of NULL 'ptr'" } */
+}
+
+/* Read through non-NULL constant pointer.  */
+
+int test_39 (void)
+{
+  int *ptr = (int *)0x1000;
+  return *ptr;
+}
+
+int test_40 (int flag)
+{
+  int i;
+  if (flag)
+    i = 43;
+  else
+    i = 17;
+
+  /* With state-merging, we lose the relationship between 'flag' and 'i'.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+  if (flag)
+    __analyzer_eval (i == 43); /* { dg-warning "UNKNOWN" } */
+  else
+    __analyzer_eval (i == 17); /* { dg-warning "UNKNOWN" } */
+}
+
+// TODO:
+
+struct link
+{
+  struct link *next;
+};
+
+// TODO:
+
+union u
+{
+  int i;
+  int *ptr;
+};
+
+void test_41 (void)
+{
+  union u u;
+  u.i = 42;
+  __analyzer_eval (u.i == 42); /* { dg-warning "TRUE" } */
+}
+
+// TODO: writes to a union member invalidating knowledge about other members
+
+void test_42 (void)
+{
+  int i;
+  float f;
+  i = 42;
+  f = i;
+  __analyzer_eval (f == 42.0); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  // TODO(xfail)
+}
+
+void test_43 (void)
+{
+  int i;
+  float f;
+  f = 42.0f;
+  i = f;
+  __analyzer_eval (i == 42);  /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  // TODO(xfail)
+}
+
+struct sbits
+{
+  int b0 : 1;
+  int b123 : 3;
+  int b456 : 3;
+  int b7 : 1;
+};
+
+void test_44 (void)
+{
+  struct sbits bits;
+  bits.b0 = 1;
+  __analyzer_eval (bits.b0 == 1); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "FALSE" "" { target *-*-* } .-1 } */
+  // TODO(xfail): ^^^^
+
+  bits.b456 = 5;
+  __analyzer_eval (bits.b456 == 5); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "FALSE" "" { target *-*-* } .-1 } */
+  // TODO(xfail): ^^^^
+};
+
+struct ubits
+{
+  unsigned int b0 : 1;
+  unsigned int b123 : 3;
+  unsigned int b456 : 3;
+  unsigned int b7 : 1;
+};
+
+/* FIXME: this requires BIT_FIELD_REF to work.  */
+
+void test_45 (void)
+{
+  struct ubits bits;
+  bits.b0 = 1;
+  __analyzer_eval (bits.b0 == 1); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  // TODO(xfail): ^^^^
+
+  bits.b456 = 5;
+  __analyzer_eval (bits.b456 == 5); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  // TODO(xfail): ^^^^
+};
+
+extern const char *char_ptr;
+
+int test_46 (void)
+{
+  if (strcmp("literal", char_ptr))
+    return 1;
+  return 0;
+}
+
+char test_47 (void)
+{
+  static const char* my_version = "1.1.3";
+  return my_version[0];
+}
+
+unsigned test_48 (unsigned char *p, unsigned char *q)
+{
+  return (unsigned int)(p - q);
+}
+
+/* Coverage:
+
+   DONE (perhaps with xfail):
+   - pointers
+   - structs
+   - enums
+   - static variables
+   - interprocedural
+   - null pointer dereference
+   - constant pointer dereference (but not zero)
+   - deref of uninit pointer
+   - phi nodes
+   - constraints
+   - bitfields
+
+   TODO:
+   - alloca
+   - malloc
+   - realloc
+   - char
+   - float/double
+   - string constants
+   - arrays
+   - aliasing
+   - unions
+   - bitfield operations
+   - globals
+   - memcpy, memmove, bcopy, memccpy, meppcpy, strcpy, etc from <string.h>
+   - loops
+     - known bound
+     - unknown bound
+   - goto
+   - computed goto/labels as values
+   - third-party libraries
+   - recursive functions
+   - mutually-recursive functions
+   - pointer arithmetic
+   - casting from pointer to integer
+   - casting from integer to pointer
+   - widening and narrowing conversions
+   - int <-> float conversions
+   - other casts
+   - try it on a bunch of real-world code
+   - setjmp/longjmp
+   - function pointers
+   - C++
+   etc.  */
diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-2.c b/gcc/testsuite/gcc.dg/analyzer/data-model-2.c
new file mode 100644
index 0000000..d5be03b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-2.c
@@ -0,0 +1,13 @@
+/* { dg-additional-options "-O2" } */
+/* TODO:is there a way to automatically run the tests on various
+   optimizations levels, and with/without debuginfo, rather than
+   hardcoding options?  Adapt from torture .exp, presumably.  */
+
+
+#include <stdlib.h>
+#include <string.h>
+
+int test_1 (void)
+{
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-3.c b/gcc/testsuite/gcc.dg/analyzer/data-model-3.c
new file mode 100644
index 0000000..3d572eb
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-3.c
@@ -0,0 +1,15 @@
+/* { dg-additional-options "-O2" } */
+/* TODO:is there a way to automatically run the tests on various
+   optimizations levels, and with/without debuginfo, rather than
+   hardcoding options?  Adapt from torture .exp, presumably.  */
+
+#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/analyzer/data-model-4.c b/gcc/testsuite/gcc.dg/analyzer/data-model-4.c
new file mode 100644
index 0000000..33f9087
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-4.c
@@ -0,0 +1,16 @@
+/* { dg-additional-options "-fexceptions" } */
+/* TODO:is there a way to automatically run the tests on various
+   optimizations levels, and with/without debuginfo, rather than
+   hardcoding options?  Adapt from torture .exp, presumably.  */
+
+#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/analyzer/data-model-5.c b/gcc/testsuite/gcc.dg/analyzer/data-model-5.c
new file mode 100644
index 0000000..dd98e30
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-5.c
@@ -0,0 +1,124 @@
+/* A toy re-implementation of CPython's object model.  */
+
+#include <stddef.h>
+#include <string.h>
+#include <stdlib.h>
+
+typedef struct base_obj
+{
+  struct type_obj *ob_type;
+  int ob_refcnt;
+} base_obj;
+
+typedef struct type_obj
+{
+  base_obj tp_base;
+  void (*tp_dealloc) (base_obj *);
+} type_obj;
+
+typedef struct tuple_obj
+{
+  base_obj tup_base;
+  int num_elements;
+  base_obj elements[];
+} tuple_obj;
+
+typedef struct list_obj
+{
+  base_obj list_base;
+  int num_elements;
+  base_obj *elements;
+} list_obj;
+
+typedef struct string_obj
+{
+  base_obj str_base;
+  size_t str_len;
+  char str_buf[];
+} string_obj;
+
+extern void type_del (base_obj *);
+extern void tuple_del (base_obj *);
+extern void str_del (base_obj *);
+
+type_obj type_type = {
+  { &type_type, 1},
+  type_del
+};
+
+type_obj tuple_type = {
+  { &type_type, 1},
+  tuple_del
+};
+
+type_obj str_type = {
+  { &str_type, 1},
+  str_del
+};
+
+base_obj *alloc_obj (type_obj *ob_type, size_t sz)
+{
+  base_obj *obj = (base_obj *)malloc (sz);
+  if (!obj)
+    return NULL;
+  obj->ob_type = ob_type;
+  obj->ob_refcnt = 1;
+  return obj;
+}
+
+base_obj *new_string_obj (const char *str)
+{
+  //__analyzer_dump ();
+  size_t len = strlen (str);
+#if 1
+  string_obj *str_obj
+    = (string_obj *)alloc_obj (&str_type, sizeof (string_obj) + len + 1);
+#else
+  string_obj *str_obj = (string_obj *)malloc (sizeof (string_obj) + len + 1);
+  if (!str_obj)
+    return NULL;
+  str_obj->str_base.ob_type = &str_type;
+  str_obj->str_base.ob_refcnt = 1;
+#endif
+  str_obj->str_len = len; /* { dg-warning "use of NULL 'str_obj'" } */
+  memcpy (str_obj->str_buf, str, len);
+  str_obj->str_buf[len] = '\0';
+  return (base_obj *)str_obj;
+}
+
+void unref (base_obj *obj)
+{
+  if (--obj->ob_refcnt == 0) /* { dg-bogus "dereference of uninitialized pointer 'obj'" } */
+    obj->ob_type->tp_dealloc (obj);
+}
+
+void test_1 (const char *str)
+{
+  base_obj *obj = new_string_obj (str);
+  //__analyzer_dump();
+  unref (obj);
+} /* { dg-bogus "leak" } */
+
+/* FIXME:
+   - ought to complain about NULL deref for when the malloc fails
+   -for some reason it's complaining about deref of uninit ptr in various places
+     - looks like something's going wrong with svalue reordering, and things start using the wrong IDs
+     e.g. the stack svalue stops being "sv0 uninit" and becomes something else
+     Seems to happen at:
+    entering: get_or_create_node
+     point: callstring: [(SN: 9 -> SN: 3 in test_1)] after (SN: 6 stmt: 0):  _1 = obj_8(D)->ob_refcnt;
+
+where at EN: 320
+     point: callstring: [(SN: 9 -> SN: 3 in test_1)] before (SN: 6 stmt: 0):  _1 = obj_8(D)->ob_refcnt;
+we have:
+  sv0: {poisoned: uninit}
+but at:
+
+../../src/gcc/testsuite/gcc.dg/analyzer/data-model-5.c:94:12: warning: deref of uninitialized pointer
+     point: callstring: [(SN: 9 -> SN: 3 in test_1)] after (SN: 6 stmt: 0):  _1 = obj_8(D)->ob_refcnt;
+we have:
+  sv0: {type: ‘struct base_obj *’, &r18}
+in "created EN: 321" (before pruning)
+
+Maybe put an assertion in the "validate" method?
+*/
diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-5b.c b/gcc/testsuite/gcc.dg/analyzer/data-model-5b.c
new file mode 100644
index 0000000..1f910c5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-5b.c
@@ -0,0 +1,91 @@
+/* A toy re-implementation of CPython's object model.  */
+
+#include <stddef.h>
+#include <string.h>
+#include <stdlib.h>
+
+typedef struct base_obj base_obj;
+typedef struct type_obj type_obj;
+typedef struct string_obj string_obj;
+
+struct base_obj
+{
+  struct type_obj *ob_type;
+  int ob_refcnt;
+};
+
+struct type_obj
+{
+  base_obj tp_base;
+  void (*tp_dealloc) (base_obj *);
+};
+
+struct string_obj
+{
+  base_obj str_base;
+  size_t str_len;
+  char str_buf[];
+};
+
+extern void type_del (base_obj *);
+extern void str_del (base_obj *);
+
+type_obj type_type = {
+  { &type_type, 1},
+  type_del
+};
+
+type_obj str_type = {
+  { &str_type, 1},
+  str_del
+};
+
+base_obj *alloc_obj (type_obj *ob_type, size_t sz)
+{
+  base_obj *obj = (base_obj *)malloc (sz);
+  if (!obj)
+    return NULL;
+  obj->ob_type = ob_type;
+  obj->ob_refcnt = 1;
+  return obj;
+}
+
+string_obj *new_string_obj (const char *str)
+{
+  //__analyzer_dump ();
+  size_t len = strlen (str);
+#if 1
+  string_obj *str_obj
+    = (string_obj *)alloc_obj (&str_type, sizeof (string_obj) + len + 1);
+#else
+  string_obj *str_obj = (string_obj *)malloc (sizeof (string_obj) + len + 1);
+  if (!str_obj)
+    return NULL;
+  str_obj->str_base.ob_type = &str_type;
+  str_obj->str_base.ob_refcnt = 1;
+#endif
+  str_obj->str_len = len; /* { dg-warning "use of NULL 'str_obj'" } */
+  memcpy (str_obj->str_buf, str, len);
+  str_obj->str_buf[len] = '\0';
+  return str_obj;
+}
+
+void unref (string_obj *obj)
+{
+  //__analyzer_dump();
+  if (--obj->str_base.ob_refcnt == 0)
+    {
+      //__analyzer_dump();
+      obj->str_base.ob_type->tp_dealloc ((base_obj *)obj); /* { dg-bogus "dereference of uninitialized pointer '<unknown>'" "" { xfail *-*-* } } */
+      // TODO (xfail): not sure what's going on here
+    }
+}
+
+void test_1 (const char *str)
+{
+  string_obj *obj = new_string_obj (str);
+  //__analyzer_dump();
+  if (obj)
+    unref (obj);
+} /* { dg-bogus "leak of 'obj'" "" { xfail *-*-* } } */
+// TODO (xfail): not sure why this is treated as leaking
diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-5c.c b/gcc/testsuite/gcc.dg/analyzer/data-model-5c.c
new file mode 100644
index 0000000..a522038
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-5c.c
@@ -0,0 +1,84 @@
+/* A toy re-implementation of CPython's object model.  */
+
+#include <stddef.h>
+#include <string.h>
+#include <stdlib.h>
+
+typedef struct base_obj base_obj;
+typedef struct type_obj type_obj;
+typedef struct string_obj string_obj;
+
+struct base_obj
+{
+  struct type_obj *ob_type;
+  int ob_refcnt;
+};
+
+struct type_obj
+{
+  base_obj tp_base;
+};
+
+struct string_obj
+{
+  base_obj str_base;
+  size_t str_len;
+  char str_buf[];
+};
+
+type_obj type_type = {
+  { &type_type, 1},
+};
+
+type_obj str_type = {
+  { &str_type, 1},
+};
+
+base_obj *alloc_obj (type_obj *ob_type, size_t sz)
+{
+  base_obj *obj = (base_obj *)malloc (sz);
+  if (!obj)
+    return NULL;
+  obj->ob_type = ob_type;
+  obj->ob_refcnt = 1;
+  return obj;
+}
+
+string_obj *new_string_obj (const char *str)
+{
+  //__analyzer_dump ();
+  size_t len = strlen (str);
+#if 1
+  string_obj *str_obj
+    = (string_obj *)alloc_obj (&str_type, sizeof (string_obj) + len + 1);
+#else
+  string_obj *str_obj = (string_obj *)malloc (sizeof (string_obj) + len + 1);
+  if (!str_obj)
+    return NULL;
+  str_obj->str_base.ob_type = &str_type;
+  str_obj->str_base.ob_refcnt = 1;
+#endif
+  str_obj->str_len = len; /* { dg-warning "use of NULL 'str_obj'" } */
+  memcpy (str_obj->str_buf, str, len);
+  str_obj->str_buf[len] = '\0';
+  return str_obj;
+}
+
+void unref (string_obj *obj)
+{
+  //__analyzer_dump();
+  if (--obj->str_base.ob_refcnt == 0)
+    {
+      //__analyzer_dump();
+      free (obj);
+    }
+}
+
+void test_1 (const char *str)
+{
+  string_obj *obj = new_string_obj (str);
+  //__analyzer_dump();
+  if (obj)
+    unref (obj);
+} /* { dg-bogus "leak of 'obj'" "" { xfail *-*-* } } */
+// TODO (xfail): not sure why this is treated as leaking
diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-5d.c b/gcc/testsuite/gcc.dg/analyzer/data-model-5d.c
new file mode 100644
index 0000000..50ab0ac
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-5d.c
@@ -0,0 +1,63 @@
+/* A toy re-implementation of CPython's object model.  */
+
+#include <stddef.h>
+#include <string.h>
+#include <stdlib.h>
+
+typedef struct base_obj base_obj;
+typedef struct type_obj type_obj;
+typedef struct string_obj string_obj;
+
+struct base_obj
+{
+  struct type_obj *ob_type;
+  int ob_refcnt;
+};
+
+struct type_obj
+{
+  base_obj tp_base;
+};
+
+struct string_obj
+{
+  base_obj str_base;
+  size_t str_len;
+  char str_buf[];
+};
+
+type_obj type_type = {
+  { &type_type, 1},
+};
+
+type_obj str_type = {
+  { &str_type, 1},
+};
+
+base_obj *alloc_obj (type_obj *ob_type, size_t sz)
+{
+  base_obj *obj = (base_obj *)malloc (sz);
+  if (!obj)
+    return NULL;
+  obj->ob_type = ob_type;
+  obj->ob_refcnt = 1;
+  return obj;
+}
+
+void unref (base_obj *obj)
+{
+  //__analyzer_dump();
+  if (--obj->ob_refcnt == 0)
+    free (obj);
+}
+
+void test_1 ()
+{
+  base_obj *obj = alloc_obj (&str_type, sizeof (string_obj));
+  if (obj)
+    {
+      __analyzer_dump_num_heap_regions (); /* { dg-warning "num heap regions: '1'" } */
+      unref (obj);
+      __analyzer_dump_num_heap_regions (); /* { dg-warning "num heap regions: '0'" } */
+    }
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-6.c b/gcc/testsuite/gcc.dg/analyzer/data-model-6.c
new file mode 100644
index 0000000..8b63ab2
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-6.c
@@ -0,0 +1,13 @@
+#include <stdlib.h>
+
+/* Verify that we don't accumulate state after a malloc/free pair.  */
+
+void test (void)
+{
+  void *ptr;
+  __analyzer_dump_num_heap_regions (); /* { dg-warning "num heap regions: '0'" } */
+  ptr = malloc (1024);
+  __analyzer_dump_num_heap_regions (); /* { dg-warning "num heap regions: '1'" } */
+  free (ptr);
+  __analyzer_dump_num_heap_regions (); /* { dg-warning "num heap regions: '0'" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-7.c b/gcc/testsuite/gcc.dg/analyzer/data-model-7.c
new file mode 100644
index 0000000..6c8ab0a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-7.c
@@ -0,0 +1,19 @@
+/* { dg-additional-options "-fno-analyzer-state-merge" } */
+
+int test_40 (int flag)
+{
+  int i;
+  if (flag)
+    i = 43;
+  else
+    i = 17;
+
+  /* Without state-merging, we retain the relationship between 'flag' and 'i'.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  if (flag)
+    __analyzer_eval (i == 43); /* { dg-warning "TRUE" } */
+  else
+    __analyzer_eval (i == 17); /* { dg-warning "TRUE" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-path-1.c b/gcc/testsuite/gcc.dg/analyzer/data-model-path-1.c
new file mode 100644
index 0000000..10205ae
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-path-1.c
@@ -0,0 +1,15 @@
+#include <stddef.h>
+
+static int *__attribute__((noinline))
+callee (void)
+{
+  return NULL;
+}
+
+void test_1 (void)
+{
+  int *p = callee ();
+  *p = 42; /* { dg-warning "use of NULL 'p'" } */
+}
+
+/* FIXME: maybe better path events describing what happened?  */
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..0094618
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/fibonacci.c
@@ -0,0 +1,10 @@
+int fib (int n)
+{
+  if (n > 1)
+    return fib (n - 1) + fib (n - 2);
+  else
+    return n;
+} 
+
+/* { dg-regexp "\[^\n\r\]+: warning: analysis bailed out early \\(\[0-9\]+ 'after-snode' enodes; \[0-9\]+ enodes\\)" } */
+/* FIXME.  */
diff --git a/gcc/testsuite/gcc.dg/analyzer/fields.c b/gcc/testsuite/gcc.dg/analyzer/fields.c
new file mode 100644
index 0000000..de55208
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/fields.c
@@ -0,0 +1,41 @@
+typedef long unsigned int size_t;
+
+extern size_t strlen (const char *__s)
+  __attribute__ ((__nothrow__ , __leaf__))
+  __attribute__ ((__pure__))
+  __attribute__ ((__nonnull__ (1)));
+
+extern void *malloc (size_t __size)
+  __attribute__ ((__nothrow__ , __leaf__))
+  __attribute__ ((__malloc__)) ;
+
+extern void free (void *__ptr)
+  __attribute__ ((__nothrow__ , __leaf__));
+
+typedef struct _krb5_data {
+  unsigned int length;
+  char *data;
+} krb5_data;
+
+typedef struct _krb5_error {
+  krb5_data text;
+} krb5_error;
+
+extern const char *error_message (int);
+
+int
+recvauth_common (int problem)
+{
+  if (problem) {
+    krb5_error error;
+    const char *message = error_message(problem);
+    error.text.length = strlen(message) + 1;
+    if (!(error.text.data = malloc(error.text.length))) {
+      goto cleanup;
+    }
+    free(error.text.data);
+  }
+
+ cleanup:
+  return problem; /* { dg-bogus "leak" } */
+}
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..55f6977
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/file-1.c
@@ -0,0 +1,36 @@
+#include <stdio.h>
+
+void
+test_1 (const char *path)
+{
+  FILE *f = fopen (path, "r"); /* { dg-message "opened here" } */
+  if (!f)
+    return;
+
+  fclose (f); /* { dg-message "\\(4\\) first 'fclose' here" } */
+  fclose (f); /* { dg-warning "double 'fclose' of FILE 'f'" } */ 
+  /* { dg-message "second 'fclose' here; first 'fclose' was at \\(4\\)" "" { target *-*-* } .-1 } */
+}
+
+void
+test_2 (const char *src, const char *dst)
+{
+  FILE *f_in = fopen (src, "r"); /* { dg-message "\\(1\\) opened here" } */
+  if (!f_in)
+    return;
+
+  FILE *f_out = fopen (src, "w");
+  if (!f_out)
+    return; /* { dg-warning "leak of FILE 'f_in'" } */
+/* { dg-message "\\(5\\) 'f_in' leaks here; was opened at \\(1\\)" "" { target *-*-* } .-1 } */
+
+  fclose (f_out);
+  fclose (f_in);
+}
+
+void
+test_3 (const char *path)
+{
+  FILE *f = fopen (path, "r"); /* { dg-message "opened here" } */
+  return; /* { dg-warning "leak of FILE 'f'" } */ 
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/file-2.c b/gcc/testsuite/gcc.dg/analyzer/file-2.c
new file mode 100644
index 0000000..aa04570
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/file-2.c
@@ -0,0 +1,18 @@
+#include <stdio.h>
+
+struct foo
+{
+  FILE *m_f;
+};
+
+void test (const char *path)
+{
+  struct foo f;
+  f.m_f = fopen (path, "r");
+
+  if (!f.m_f)
+    return; /* { dg-bogus "leak of FILE" } */
+
+  fclose (f.m_f);
+  fclose (f.m_f); /* { dg-warning "double 'fclose' of FILE 'f.m_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..967aa76
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/function-ptr-2.c
@@ -0,0 +1,43 @@
+#include <stdlib.h>
+
+typedef void (*fn_ptr_t) (void *);
+
+void
+calls_free (void *victim)
+{
+  free (victim);
+}
+
+void
+no_op (void *ptr)
+{
+}
+
+void test_1 (void *ptr)
+{
+  fn_ptr_t fn_ptr = calls_free;
+  __analyzer_eval (fn_ptr == calls_free); /* { dg-warning "TRUE" } */
+  __analyzer_eval (fn_ptr != NULL); /* { dg-warning "TRUE" } */
+  __analyzer_eval (fn_ptr == NULL); /* { dg-warning "FALSE" } */
+  __analyzer_eval (fn_ptr == no_op); /* { dg-warning "FALSE" } */
+
+  fn_ptr (ptr);
+  fn_ptr (ptr);
+}
+// TODO: issue a double-free warning at 2nd call to fn_ptr.
+
+/* As above, but with an extra indirection to try to thwart
+   the optimizer.  */
+
+void test_2 (void *ptr, fn_ptr_t *fn_ptr)
+{
+  *fn_ptr = calls_free;
+  __analyzer_eval (*fn_ptr == calls_free); /* { dg-warning "TRUE" } */
+  __analyzer_eval (*fn_ptr != NULL); /* { dg-warning "TRUE" } */
+  __analyzer_eval (*fn_ptr == NULL); /* { dg-warning "FALSE" } */
+  __analyzer_eval (*fn_ptr == no_op); /* { dg-warning "FALSE" } */
+
+  (*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..54efa77
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/gzio.c
@@ -0,0 +1,17 @@
+#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); /* { dg-bogus "leak" } */
+}
+void gzopen(const char *path)
+{
+  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-2.c b/gcc/testsuite/gcc.dg/analyzer/loop-2.c
new file mode 100644
index 0000000..acbb7a9
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/loop-2.c
@@ -0,0 +1,45 @@
+/* FIXME: what if the variant is embedded in a non-SSA var e.g. a field of
+   a struct?
+   Do we still get termination?
+
+   FIXME: no we don't; it effectively counts up to N within the region for
+   the field.
+
+   So we need something smarter to cope with this case.  */
+
+/* { dg-additional-options "-fno-analyzer-state-purge" } */
+
+struct s
+{
+  int i;
+};
+
+void test(void)
+{
+  struct s s;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+
+  for (s.i=0; s.i<256; s.i++) {
+    __analyzer_eval (s.i < 256); /* { dg-warning "TRUE" } */
+      /* (should report TRUE twice). */
+
+    __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+    //__analyzer_eval (s.i == 0); /* { d-todo-g-warning "UNKNOWN" "" { xfail *-*-* } } */
+      /* { d-todo-g-warning "TRUE" "" { target *-*-* } .-1 } */
+      /* TODO(xfail^^^): we're only capturing the first iteration, so
+	 we erroneously get i == 0.  */
+
+      //__analyzer_eval (s.i >= 0); /* { d-todo-g-warning "TRUE" } */
+  }
+
+  __analyzer_eval (s.i >= 256); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (s.i == 256); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
+  /* TODO(xfail^^^): ideally it should figure out i == 256 at exit.  */
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/loop-3.c b/gcc/testsuite/gcc.dg/analyzer/loop-3.c
new file mode 100644
index 0000000..975afe4
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/loop-3.c
@@ -0,0 +1,17 @@
+#include <stdlib.h>
+
+void test(int c)
+{
+  int i;
+  char *buffer = (char*)malloc(256);
+
+  for (i=0; i<255; i++) {
+    buffer[i] = c; /* { dg-warning "dereference of freed pointer 'buffer'" } */
+		   /* BUG: the malloc could have failed
+		      TODO: the checker doesn't yet pick up on this, perhaps
+		      due to the pointer arithmetic not picking up on the
+		      state */
+    free(buffer); /* { dg-warning "double-free of 'buffer'" } */
+  }
+
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/loop.c b/gcc/testsuite/gcc.dg/analyzer/loop.c
new file mode 100644
index 0000000..19c258d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/loop.c
@@ -0,0 +1,34 @@
+// FIXME:
+/* { dg-additional-options "-fno-analyzer-state-purge" } */
+
+void test(void)
+{
+  int i;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+  for (i=0; i<256; i++) {
+      __analyzer_eval (i < 256); /* { dg-warning "TRUE" } */
+      /* (should report TRUE twice). */
+
+      __analyzer_eval (i == 0); /* { dg-warning "TRUE" } */
+      /* { dg-warning "FALSE" "" { xfail *-*-* } .-1 } */
+      /* { dg-warning "UNKNOWN" "" { target *-*-* } .-2 } */
+      /* TODO(xfail^^^): ideally we ought to figure out i > 0 after 1st iteration.  */
+
+      __analyzer_eval (i >= 0); /* { dg-warning "TRUE" } */
+      /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+      /* { dg-warning "UNKNOWN" "" { target *-*-* } .-2 } */
+      /* TODO(xfail^^^): ideally we ought to figure out i >= 0 for all iterations.  */
+
+      __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  }
+
+  __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.  */
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+}
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..e50496d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-1.c
@@ -0,0 +1,525 @@
+#include <stdlib.h>
+#include <string.h>
+
+extern int foo (void);
+extern int bar (void);
+extern void could_free (void *);
+extern void cant_free (const void *); /* since it's a const 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); /* { dg-message "allocated here" } */
+  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-message "allocated here" } */
+} /* { dg-warning "leak of 'ptr'" } */ 
+
+void test_18 (void)
+{
+  void *ptr = malloc (64); /* { dg-message "allocated here" } */
+  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;
+}
+
+struct link *test_29a (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;
+}
+
+/* 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-message "allocated here" } */
+} /* { dg-warning "leak of 'tmp.m_ptr'" } */ 
+
+void test_31 (void)
+{
+  struct link tmp;
+  void *ptr = malloc (sizeof (struct link)); /* { dg-message "allocated here" } */
+  tmp.m_ptr = (struct link *)ptr;
+} /* { dg-warning "leak of 'ptr'" } */ 
+
+void test_32 (void)
+{
+  void *ptr = malloc (1024);
+  could_free (ptr);
+} /* { dg-bogus "leak" } */
+
+void test_33 (void)
+{
+  void *ptr = malloc (1024); /* { dg-message "allocated here" } */
+  cant_free (ptr);
+} /* { dg-warning "leak of 'ptr'" } */ 
+
+void test_34 (void)
+{
+  float *q;
+  struct coord *p = malloc (sizeof (struct coord));
+  if (!p)
+    return;
+  p->x = 0.0f;
+  q = &p->x;
+  free (p);
+  *q = 1.0f; /* { dg-warning "dereference of freed pointer 'q'" } */
+  // TODO: better wording? it's freed memory, not a freed pointer
+};
+
+int test_35 (void)
+{
+  void *ptr = malloc(4096);
+  if (!ptr)
+    return -1;
+  memset(ptr, 0, 4096);
+  free(ptr);
+  return 0;
+}
+
+void test_36 (void)
+{
+  void *ptr = malloc(4096);
+  if (!ptr)
+    return;
+  memset(ptr, 0, 4096);
+  free(ptr);
+}
+
+void *test_37a (void)
+{
+  void *ptr = malloc(4096); /* { dg-message "this call could return NULL" } */
+  memset(ptr, 0, 4096); /* { dg-warning "use of possibly-NULL 'ptr' where non-null expected" } */
+  return ptr;
+}
+
+int test_37b (void)
+{
+  void *p = malloc(4096);
+  void *q = malloc(4096); /* { dg-message "this call could return NULL" } */
+  if (p) {
+    memset(p, 0, 4096); /* Not a bug: checked */
+  } else {
+    memset(q, 0, 4096); /* { dg-warning "use of possibly-NULL 'q' where non-null expected" } */
+  }
+  free(p);
+  free(q);
+  return 0;
+}
+
+extern void might_use_ptr (void *ptr);
+
+void test_38(int i)
+{
+  void *p;
+
+  p = malloc(1024);
+  if (p) {
+    free(p);
+    might_use_ptr(p); /* TODO: should complain of use-after-free here (CWE-416).  */
+  }
+}
+
+int *
+test_39 (int i)
+{
+  int *p = (int*)malloc(sizeof(int*)); /* { dg-message "this call could return NULL" } */
+  *p = i; /* { dg-warning "use of possibly-NULL 'p'" } */
+  return p;
+}
+
+int *
+test_40 (int i)
+{
+  int *p = (int*)malloc(sizeof(int*));
+  i = *p; /* { dg-warning "use of possibly-NULL 'p'" } */
+  /* TODO: (it's also uninitialized) */
+  return p;
+}
+
+char *
+test_41 (int flag)
+{
+  char *buffer;
+
+  if (flag) {
+    buffer = (char*)malloc(4096);
+  } else {
+    buffer = NULL;
+  }
+
+  buffer[0] = 'a'; /* { dg-warning "use of possibly-NULL 'buffer'" } */
+  /* { dg-warning "use of NULL 'buffer'" "" { target *-*-* } .-1 } */
+
+  return buffer;
+}
+
+void test_42a (void)
+{
+  void *p = malloc (1024); /* { dg-message "allocated here" } */
+  free (p + 64); /* this could well corrupt the heap.  */
+  /* TODO: ^^^ we should warn about this.  */
+} /* { dg-warning "leak of 'p'" } */
+/* TODO: presumably we should complain about the bogus free, but then
+   maybe not complain about the leak.  */
+// CWE-761: Free of Pointer not at Start of Buffer
+
+void test_42b (void)
+{
+  void *p = malloc (1024); /* { dg-message "allocated here" } */
+  free (p - 64); /* this could well corrupt the heap.  */
+  /* TODO: ^^^ we should warn about this.  */
+} /* { dg-warning "leak of 'p'" } */
+/* TODO: presumably we should complain about the bogus free, but then
+   maybe not complain about the leak.  */
+// CWE-761: Free of Pointer not at Start of Buffer
+
+void test_42c (void)
+{
+  void *p = malloc (1024);
+  void *q = p + 64;
+  free (q - 64); /* this is probably OK.  */
+} /* { dg-bogus "leak of 'p'" "" { xfail *-*-* } } */
+// TODO(xfail)
+
+#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
+
+struct link global_link;
+
+void test_43 (void)
+{
+  global_link.m_ptr = malloc (sizeof (struct link)); /* { dg-message "allocated here" } */
+  global_link.m_ptr = NULL;
+} /* { dg-warning "leak of '<unknown>'" } */
+/* TODO: should be more precise than just '<unknown>', and
+   ideally would be at the assigment to NULL.  */
+
+struct link *global_ptr;
+
+void test_44 (void)
+{
+  global_ptr = malloc (sizeof (struct link));
+  if (!global_ptr)
+    return;
+  global_ptr->m_ptr = malloc (sizeof (struct link)); /* { dg-message "allocated here" } */
+  free (global_ptr); /* { dg-warning "leak of '<unknown>'" } */
+  /* TODO: should be more precise than just '<unknown>'.  */
+}
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-dedupe-1.c b/gcc/testsuite/gcc.dg/analyzer/malloc-dedupe-1.c
new file mode 100644
index 0000000..7eecadf
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-dedupe-1.c
@@ -0,0 +1,46 @@
+#include <stdlib.h>
+
+extern void foo (void);
+extern void bar (void);
+
+void test_1 (int flag, int n);
+
+void caller_1_of_test_1 (int n)
+{
+  test_1 (1, n); /* { dg-bogus "test_1" } */
+  test_1 (0, n); /* { dg-bogus "test_1" } */
+}
+
+void __attribute__((noinline))
+test_1 (int flag, int n)
+{
+  int *ptr = (int *)malloc (sizeof (int));
+
+  if (flag)
+    {
+      int i;
+      for (i = 0; i < n; i++)
+	foo ();
+    }
+  else
+    bar ();
+
+  free (ptr);
+  free (ptr); /* { dg-warning "double-free" } */
+  /* FIXME: we get duplicates intraprocedurally, as there are two paths
+     through the function.
+     The calls in test_2 also generate additional duplicates.
+     How to verify lack of duplicates?
+     Putting a bogus on the interprocedual one detects that, at least.  */
+
+  if (flag)
+    foo ();
+  else
+    bar ();
+}
+
+void caller_2_of_test_1 (int n)
+{
+  test_1 (1, n); /* { dg-bogus "test_1" } */
+  test_1 (0, n); /* { dg-bogus "test_1" } */
+}
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..d235138
--- /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; first 'free' was at \\(4\\)" "" { 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\\) passing freed pointer 'ptr' in call to '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..3408a1d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-11.c
@@ -0,0 +1,91 @@
+/* { dg-additional-options "-fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fdiagnostics-path-format=inline-events" } */
+
+#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'" } */ 
+}
+
+void test (void *ptr)
+{
+  do_stuff (1);
+
+  may_call_free (ptr);
+
+  do_stuff (2);
+
+  may_call_free (ptr);
+
+  do_stuff (3);
+}
+
+/* { dg-begin-multiline-output "" }
+  'test': events 1-2
+     |
+     |   NN | void test (void *ptr)
+     |      |      ^~~~
+     |      |      |
+     |      |      (1) entry to 'test'
+     |......
+     |   NN |   may_call_free (ptr);
+     |      |   ~~~~~~~~~~~~~~~~~~~
+     |      |   |
+     |      |   (2) calling 'may_call_free' from 'test'
+     |
+     +--> 'may_call_free': events 3-5
+                 |
+                 |   NN | may_call_free (void *victim)
+                 |      | ^~~~~~~~~~~~~
+                 |      | |
+                 |      | (3) entry to 'may_call_free'
+                 |   NN | {
+                 |   NN |   if (some_condition ())
+                 |      |      ~
+                 |      |      |
+                 |      |      (4) following 'false' branch
+                 |......
+                 |   NN |   free (victim);
+                 |      |   ~~~~~~~~~~~~~
+                 |      |   |
+                 |      |   (5) first 'free' here
+                 |
+     <-----------+
+     |
+  'test': events 6-7
+     |
+     |   NN |   may_call_free (ptr);
+     |      |   ^~~~~~~~~~~~~~~~~~~
+     |      |   |
+     |      |   (6) returning to 'test' from 'may_call_free'
+     |......
+     |   NN |   may_call_free (ptr);
+     |      |   ~~~~~~~~~~~~~~~~~~~
+     |      |   |
+     |      |   (7) passing freed pointer 'ptr' in call to 'may_call_free' from 'test'
+     |
+     +--> 'may_call_free': events 8-10
+                 |
+                 |   NN | may_call_free (void *victim)
+                 |      | ^~~~~~~~~~~~~
+                 |      | |
+                 |      | (8) entry to 'may_call_free'
+                 |   NN | {
+                 |   NN |   if (some_condition ())
+                 |      |      ~
+                 |      |      |
+                 |      |      (9) following 'false' branch
+                 |......
+                 |   NN |   free (victim);
+                 |      |   ~~~~~~~~~~~~~
+                 |      |   |
+                 |      |   (10) second 'free' here; first 'free' was at (5)
+                 |
+  { dg-end-multiline-output "" } */
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-13.c b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-13.c
new file mode 100644
index 0000000..ddb5ff7
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-13.c
@@ -0,0 +1,30 @@
+/* { dg-additional-options "-fanalyzer-verbosity=1" } */
+
+#include <stdlib.h>
+
+void
+calls_free (void *victim)
+{
+  free (victim); /* { dg-warning "double-free of 'victim'" } */
+}
+
+extern void do_stuff (void);
+
+struct foo
+{
+  void *m_p;
+};
+
+void test (struct foo f)
+{
+  do_stuff ();
+
+  calls_free (f.m_p);
+
+  do_stuff ();
+
+  calls_free (f.m_p); /* { dg-message "passing freed pointer '<unknown>' in call to 'calls_free' from 'test'" } */
+  // TODO: something better than '<unknown>'
+
+  do_stuff ();
+}
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..75c07d5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-2.c
@@ -0,0 +1,34 @@
+/* { dg-additional-options "-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; first 'free' was at \\(4\\)" "" { 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\\) passing freed pointer 'ptr' in call to '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..62f8b55
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-6.c
@@ -0,0 +1,22 @@
+#include <stdlib.h>
+
+void *
+calls_malloc (void)
+{
+  void *result = malloc (1024); /* { dg-message "allocated here" } */
+  return result; /* { dg-warning "leak of 'result'" } */
+}
+
+void test_1 ()
+{
+  calls_malloc (); /* { dg-message "calling 'calls_malloc' from 'test_1'" } */
+}
+
+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..29910f4
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-double-free.c
@@ -0,0 +1,166 @@
+/* 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 | {
+     |   NN |   boxed_int *obj = make_boxed_int (i);
+     |      |                    ~~~~~~~~~~~~~~~~~~
+     |      |                    |
+     |      |                    (2) calling 'make_boxed_int' from 'test'
+     |
+     +--> 'make_boxed_int': events 3-5
+                  |
+                  |   NN | make_boxed_int (int i)
+                  |      | ^~~~~~~~~~~~~~
+                  |      | |
+                  |      | (3) entry to 'make_boxed_int'
+                  |......
+                  |   NN |   if (!result)
+                  |      |      ~
+                  |      |      |
+                  |      |      (4) following 'false' branch
+                  |......
+                  |   NN |   return result;
+                  |      |          ~~~~~~
+                  |      |          |
+                  |      |          (5) state of '<return-value>': 'start' -> 'nonnull' (origin: NULL)
+                  |
+     <------------+
+     |
+  'test': events 6-7
+     |
+     |   NN |   boxed_int *obj = make_boxed_int (i);
+     |      |                    ^~~~~~~~~~~~~~~~~~
+     |      |                    |
+     |      |                    (6) returning to 'test' from 'make_boxed_int'
+     |   NN | 
+     |   NN |   free_boxed_int (obj);
+     |      |   ~~~~~~~~~~~~~~~~~~~~
+     |      |   |
+     |      |   (7) calling 'free_boxed_int' from 'test'
+     |
+     +--> 'free_boxed_int': events 8-9
+                  |
+                  |   NN | free_boxed_int (boxed_int *bi)
+                  |      | ^~~~~~~~~~~~~~
+                  |      | |
+                  |      | (8) entry to 'free_boxed_int'
+                  |   NN | {
+                  |   NN |   wrapped_free (bi);
+                  |      |   ~~~~~~~~~~~~~~~~~
+                  |      |   |
+                  |      |   (9) calling 'wrapped_free' from 'free_boxed_int'
+                  |
+                  +--> 'wrapped_free': events 10-11
+                              |
+                              |   NN | void wrapped_free (void *ptr)
+                              |      |      ^~~~~~~~~~~~
+                              |      |      |
+                              |      |      (10) entry to 'wrapped_free'
+                              |   NN | {
+                              |   NN |   free (ptr);
+                              |      |   ~~~~~~~~~~
+                              |      |   |
+                              |      |   (11) first 'free' here (state of 'ptr': 'nonnull' -> 'free', origin: NULL)
+                              |
+                  <-----------+
+                  |
+          'free_boxed_int': event 12
+                  |
+                  |   NN |   wrapped_free (bi);
+                  |      |   ^~~~~~~~~~~~~~~~~
+                  |      |   |
+                  |      |   (12) returning to 'free_boxed_int' from 'wrapped_free'
+                  |
+     <------------+
+     |
+  'test': events 13-14
+     |
+     |   NN |   free_boxed_int (obj);
+     |      |   ^~~~~~~~~~~~~~~~~~~~
+     |      |   |
+     |      |   (13) returning to 'test' from 'free_boxed_int'
+     |   NN | 
+     |   NN |   free_boxed_int (obj);
+     |      |   ~~~~~~~~~~~~~~~~~~~~
+     |      |   |
+     |      |   (14) passing freed pointer 'obj' in call to 'free_boxed_int' from 'test'
+     |
+     +--> 'free_boxed_int': events 15-16
+                  |
+                  |   NN | free_boxed_int (boxed_int *bi)
+                  |      | ^~~~~~~~~~~~~~
+                  |      | |
+                  |      | (15) entry to 'free_boxed_int'
+                  |   NN | {
+                  |   NN |   wrapped_free (bi);
+                  |      |   ~~~~~~~~~~~~~~~~~
+                  |      |   |
+                  |      |   (16) passing freed pointer 'bi' in call to 'wrapped_free' from 'free_boxed_int'
+                  |
+                  +--> 'wrapped_free': events 17-18
+                              |
+                              |   NN | void wrapped_free (void *ptr)
+                              |      |      ^~~~~~~~~~~~
+                              |      |      |
+                              |      |      (17) entry to 'wrapped_free'
+                              |   NN | {
+                              |   NN |   free (ptr);
+                              |      |   ~~~~~~~~~~
+                              |      |   |
+                              |      |   (18) second 'free' here; first 'free' was at (11) ('ptr' is in state 'free')
+                              |
+   { dg-end-multiline-output "" } */
+
+/* TODO: the event describing the allocation is uninteresting and probably
+   should be purged.  */
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..282a577
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-unchecked.c
@@ -0,0 +1,64 @@
+/* Example of a multilevel wrapper around malloc, with an unchecked write.  */
+
+/* { dg-options "--analyzer -fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fdiagnostics-path-format=inline-events -fanalyzer-checker=malloc -fanalyzer-verbose-state-changes" } */
+
+#include <stdlib.h>
+
+void *wrapped_malloc (size_t size)
+{
+  return malloc (size);
+}
+
+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'" } */
+  return result;
+}
+
+/* "use of possibly-NULL 'result' [CWE-690]".  */
+/* { 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-4
+                       |
+                       |   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 '<return-value>': 'start' -> 'unchecked', origin: NULL)
+                       |
+          <------------+
+          |
+  'make_boxed_int': events 5-6
+          |
+          |   NN |   boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+          |      |                                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+          |      |                                    |
+          |      |                                    (5) possible return of NULL to 'make_boxed_int' from 'wrapped_malloc'
+          |   NN |   result->i = i;
+          |      |   ~~~~~~~~~~~~~                     
+          |      |             |
+          |      |             (6) '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..18e365d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-9.c
@@ -0,0 +1,18 @@
+/* { dg-options "--analyzer -fdiagnostics-path-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-1.c b/gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-1.c
new file mode 100644
index 0000000..82ad547
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-1.c
@@ -0,0 +1,14 @@
+#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;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-2.c b/gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-2.c
new file mode 100644
index 0000000..eb48c63
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-2.c
@@ -0,0 +1,96 @@
+#include <stdlib.h>
+
+/* 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 (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);
+}
+
+/* { dg-regexp "\[^\n\r\]+: warning: analysis bailed out early \\(\[0-9\]+ 'after-snode' enodes; \[0-9\]+ enodes\\)" } */
+/* FIXME.  */
+
+/* FIXME: why the explosion with the region model?
+
+   Why is it not e.g. merging nodes at "before (SN: 3 stmt: 0)"?
+   We have two enodes for that point:
+
+created EN: 17
+     point: callstring: [] before (SN: 3 stmt: 0):  _4 = obj_02_17(D)->ob_refcnt;
+     pruned_state: ps: {ec0: {(‘*(obj_01).ob_refcnt’ @ 0)}, ec1: {(‘<unknown>_10’ @ 0)}  |  }@0
+rmodel: svalues: [sv0: {poisoned: uninit}, sv1: {type: ‘struct PyObject *’, &r8}, sv2: {type: ‘struct PyObject *’, unknown}, sv3: {type: ‘struct PyObject *’, unknown}, sv4: {type: ‘int’, unknown}, sv5: {type: ‘int’, ‘0’}], regions: [r0: {kind: ‘root’, parent: null, sval: null}, r1: {kind: ‘stack’, parent: r0, sval: sv0}, r2: {kind: ‘frame’, parent: r1, sval: null, map: {‘obj_02_17(D)’: r5, ‘obj_03_20(D)’: r7, ‘obj_01’: r3, ‘obj_03’: r6, ‘obj_02’: r4}, function: ‘test’}, r3: {kind: ‘map’, parent: r2, sval: sv1, type: ‘struct PyObject *’, map: {}}, r4: {kind: ‘map’, parent: r2, sval: sv2, type: ‘struct PyObject *’, map: {}}, r5: {kind: ‘map’, parent: r2, sval: sv2, type: ‘struct PyObject *’, map: {}}, r6: {kind: ‘map’, parent: r2, sval: sv3, type: ‘struct PyObject *’, map: {}}, r7: {kind: ‘map’, parent: r2, sval: sv3, type: ‘struct PyObject *’, map: {}}, r8: {kind: ‘symbolic’, parent: r0, sval: null, map: {‘ob_refcnt’: r9}}, r9: {kind: ‘map’, parent: r8, sval: sv4, type: ‘int’, map: {}}], constraints: {ec0: {sv5 == ‘0’}, ec1: {sv4}  |  ec1: {sv4} != ec0: {sv5 == ‘0’}}
+
+created EN: 92
+     point: callstring: [] before (SN: 3 stmt: 0):  _4 = obj_02_17(D)->ob_refcnt;
+     pruned_state: ps: {ec0: {(‘*(obj_01).ob_refcnt’ @ 0)}, ec1: {(‘<unknown>_10’ @ 0)}  |  }@0
+rmodel: svalues: [sv0: {poisoned: uninit}, sv1: {type: ‘struct PyObject *’, &r8}, sv2: {type: ‘struct PyObject *’, unknown}, sv3: {type: ‘struct PyObject *’, unknown}, sv4: {type: ‘int’, unknown}, sv5: {type: ‘int’, ‘0’}], regions: [r0: {kind: ‘root’, parent: null, sval: null}, r1: {kind: ‘stack’, parent: r0, sval: sv0}, r2: {kind: ‘frame’, parent: r1, sval: null, map: {‘obj_02_17(D)’: r5, ‘obj_03_20(D)’: r7, ‘obj_01’: r3, ‘obj_03’: r6, ‘obj_02’: r4}, function: ‘test’}, r3: {kind: ‘map’, parent: r2, sval: sv1, type: ‘struct PyObject *’, map: {}}, r4: {kind: ‘map’, parent: r2, sval: sv2, type: ‘struct PyObject *’, map: {}}, r5: {kind: ‘map’, parent: r2, sval: sv2, type: ‘struct PyObject *’, map: {}}, r6: {kind: ‘map’, parent: r2, sval: sv3, type: ‘struct PyObject *’, map: {}}, r7: {kind: ‘map’, parent: r2, sval: sv3, type: ‘struct PyObject *’, map: {}}, r8: {kind: ‘symbolic’, parent: r0, sval: null, map: {‘ob_refcnt’: r9}}, r9: {kind: ‘map’, parent: r8, sval: sv4, type: ‘int’, map: {}}], constraints: {ec0: {sv4 == sv5 == ‘0’}  |  }
+
+They are identical apart from the constraints:
+-constraints: {ec0: {sv5 == ‘0’}, ec1: {sv4}  |  ec1: {sv4} != ec0: {sv5 == ‘0’}}
++constraints: {ec0: {sv4 == sv5 == ‘0’}  |  }
+
+i.e. in both cases sv5 == 0, but they differ in whether sv4 == 0 or != 0
+
+r0: {kind: ‘root’, parent: null, sval: null}
+|-stack: r1: {kind: ‘stack’, parent: r0, sval: sv0}
+|  |: sval: sv0: {poisoned: uninit}
+|  `-frame for ‘test’: r2: {kind: ‘frame’, parent: r1, sval: null, map: {‘obj_02_17(D)’: r5, ‘obj_03_20(D)’: r7, ‘obj_03’: r6, ‘obj_01’: r3, ‘obj_02’: r4}, function: ‘test’}
+|    |-‘obj_01’: r3: {kind: ‘map’, parent: r2, sval: sv1, type: ‘struct PyObject *’, map: {}}
+|    |  |: sval: sv1: {type: ‘struct PyObject *’, &r8}
+|    |  |: type: ‘struct PyObject *’
+|    |-‘obj_02’: r4: {kind: ‘map’, parent: r2, sval: sv2, type: ‘struct PyObject *’, map: {}}
+|    |  |: sval: sv2: {type: ‘struct PyObject *’, unknown}
+|    |  |: type: ‘struct PyObject *’
+|    |-‘obj_02_17(D)’: r5: {kind: ‘map’, parent: r2, sval: sv2, type: ‘struct PyObject *’, map: {}}
+|    |  |: sval: sv2: {type: ‘struct PyObject *’, unknown}
+|    |  |: type: ‘struct PyObject *’
+|    |-‘obj_03’: r6: {kind: ‘map’, parent: r2, sval: sv3, type: ‘struct PyObject *’, map: {}}
+|    |  |: sval: sv3: {type: ‘struct PyObject *’, unknown}
+|    |  |: type: ‘struct PyObject *’
+|    `-‘obj_03_20(D)’: r7: {kind: ‘map’, parent: r2, sval: sv3, type: ‘struct PyObject *’, map: {}}
+|      |: sval: sv3: {type: ‘struct PyObject *’, unknown}
+|      |: type: ‘struct PyObject *’
+`-r8: {kind: ‘symbolic’, parent: r0, sval: null, map: {‘ob_refcnt’: r9}}
+  `-‘ob_refcnt’: r9: {kind: ‘map’, parent: r8, sval: sv4, type: ‘int’, map: {}}
+    |: sval: sv4: {type: ‘int’, unknown}
+    |: type: ‘int’
+svalues:
+  sv0: {poisoned: uninit}
+  sv1: {type: ‘struct PyObject *’, &r8}
+  sv2: {type: ‘struct PyObject *’, unknown}
+  sv3: {type: ‘struct PyObject *’, unknown}
+  sv4: {type: ‘int’, unknown}
+  sv5: {type: ‘int’, ‘0’}
+constraint manager:
+  equiv classes:
+    ec0: {sv5 == ‘0’}
+    ec1: {sv4}
+  constraints:
+    0: ec1: {sv4} != ec0: {sv5 == ‘0’}
+
+i.e. it's capturing whether obj_01->ob_refcnt is zero or non-zero
+
+Does obj_01 ever get reused, and if not, can we purge all of that state?
+Hmmm: none of these are SSA names, so they don't get purged.   */
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-3.c b/gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-3.c
new file mode 100644
index 0000000..56cd0a6
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-many-paths-3.c
@@ -0,0 +1,36 @@
+#include <stdlib.h>
+
+extern int foo (void);
+
+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'" } */
+}
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..39092b4
--- /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\\) allocated here" "" { target *-*-* } malloc } */
+  /* { dg-message "\\(2\\) first 'free' here" "" { target *-*-* } first_free } */
+  /* { dg-message "\\(3\\) second 'free' here; first 'free' was at \\(2\\)" "" { 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..17a22a9
--- /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; first 'free' was at \\(1\\)" "" { 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..f8a2686
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-4.c
@@ -0,0 +1,19 @@
+/* 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\\) allocated here" "" { target *-*-* } malloc } */
+  /* { dg-message "\\(2\\) following 'false' branch" "" { target *-*-* } cond } */
+  /* { dg-message "\\(3\\) assuming 'ptr' is NULL" "" { target *-*-* } cond } */
+  /* { dg-message "\\(4\\) 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..3ac3735
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-5.c
@@ -0,0 +1,41 @@
+#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\\) allocated here" "" { target *-*-* } malloc_of_p } */
+  /* { dg-message "\\(2\\) following 'false' branch" "" { target *-*-* } test_of_p } */
+  /* { dg-message "\\(3\\) assuming 'p' is non-NULL" "" { target *-*-* } test_of_p } */
+  /* { dg-message "\\(4\\) following 'true' branch" "" { target *-*-* } test_of_q } */
+  /* { dg-message "\\(5\\) first 'free' here" "" { target *-*-* } first_free_of_p } */
+  /* { dg-message "\\(6\\) second 'free' here; first 'free' was at \\(5\\)" "" { target *-*-* } second_free_of_p } */
+
+  /* We don't care about the state changes to 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..219e6b7
--- /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) /* { dg-message "\\(2\\) following 'true' branch" } */ 
+    {
+      free (q);
+      return; /* { dg-warning "leak of 'p'" } */ 
+      /* { dg-message "\\(3\\) 'p' leaks here; was allocated at \\(1\\)" "" { 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..162799b
--- /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 (const 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/malloc-paths-9.c b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-9.c
new file mode 100644
index 0000000..c0931ad
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-paths-9.c
@@ -0,0 +1,254 @@
+/* { dg-additional-options "-fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fdiagnostics-path-format=inline-events" } */
+
+#include <stdlib.h>
+
+void test_1 (void)
+{
+  void *ptr = malloc (1024);
+  free (ptr);
+  free (ptr); /* { dg-warning "double-free of 'ptr'"  } */
+}
+/* { dg-begin-multiline-output "" }
+  'test_1': events 1-3
+      |
+      |   NN |   void *ptr = malloc (1024);
+      |      |               ^~~~~~~~~~~~~
+      |      |               |
+      |      |               (1) allocated here
+      |   NN |   free (ptr);
+      |      |   ~~~~~~~~~~   
+      |      |   |
+      |      |   (2) first 'free' here
+      |   NN |   free (ptr);
+      |      |   ~~~~~~~~~~   
+      |      |   |
+      |      |   (3) second 'free' here; first 'free' was at (2)
+      |
+   { dg-end-multiline-output "" } */
+
+void test_2 (int x, int y)
+{
+  void *ptr = malloc (1024);
+  if (x)
+    free (ptr);
+  if (y)
+    free (ptr); /* { dg-warning "double-free of 'ptr'"  } */
+} /* { dg-warning "leak of 'ptr'"  } */
+
+/* "double-free of 'ptr'".  */
+/* { dg-begin-multiline-output "" }
+  'test_2': events 1-5
+      |
+      |   NN |   void *ptr = malloc (1024);
+      |      |               ^~~~~~~~~~~~~
+      |      |               |
+      |      |               (1) allocated here
+      |   NN |   if (x)
+      |      |      ~         
+      |      |      |
+      |      |      (2) following 'true' branch
+      |   NN |     free (ptr);
+      |      |     ~~~~~~~~~~ 
+      |      |     |
+      |      |     (3) first 'free' here
+      |   NN |   if (y)
+      |      |      ~         
+      |      |      |
+      |      |      (4) following 'true' branch
+      |   NN |     free (ptr);
+      |      |     ~~~~~~~~~~ 
+      |      |     |
+      |      |     (5) second 'free' here; first 'free' was at (3)
+      |
+   { dg-end-multiline-output "" } */
+
+/* "leak of 'ptr'.  */
+/* { dg-begin-multiline-output "" }
+  'test_2': events 1-4
+      |
+      |   NN |   void *ptr = malloc (1024);
+      |      |               ^~~~~~~~~~~~~
+      |      |               |
+      |      |               (1) allocated here
+      |   NN |   if (x)
+      |      |      ~         
+      |      |      |
+      |      |      (2) following 'false' branch
+      |   NN |     free (ptr);
+      |   NN |   if (y)
+      |      |      ~         
+      |      |      |
+      |      |      (3) following 'false' branch
+      |   NN |     free (ptr);
+      |   NN | }
+      |      | ~              
+      |      | |
+      |      | (4) 'ptr' leaks here; was allocated at (1)
+      |
+   { dg-end-multiline-output "" } */
+
+int test_3 (int x, int y)
+{
+  int *ptr = (int *)malloc (sizeof (int));
+  *ptr = 42; /* { dg-warning "use of possibly-NULL 'ptr'" } */
+  if (x)
+    free (ptr);
+
+  *ptr = 19; /* { dg-warning "use after free of 'ptr'" } */
+  /* { dg-warning "dereference of freed pointer 'ptr'" "" { target *-*-* } .-1 } */
+  // TODO: why two warnings here?
+
+  if (y)
+    free (ptr); /* No double-free warning: we've already attempted
+		   to dereference it above.  */
+  return *ptr; /* { dg-warning "use after free of 'ptr'" } */
+  /* { dg-warning "dereference of freed pointer 'ptr'" "" { target *-*-* } .-1 } */
+  // TODO: why two warnings here?
+  /* { dg-warning "leak of 'ptr'" "" { target *-*-* } .-3 } */
+}
+
+/* "use of possibly-NULL 'ptr'".  */
+/* { dg-begin-multiline-output "" }
+  'test_3': events 1-2
+      |
+      |   NN |   int *ptr = (int *)malloc (sizeof (int));
+      |      |                     ^~~~~~~~~~~~~~~~~~~~~
+      |      |                     |
+      |      |                     (1) this call could return NULL
+      |   NN |   *ptr = 42;
+      |      |   ~~~~~~~~~          
+      |      |        |
+      |      |        (2) 'ptr' could be NULL
+      |
+   { dg-end-multiline-output "" } */
+
+/* "use after free of 'ptr'".  */
+/* { dg-begin-multiline-output "" }
+  'test_3': events 1-5
+      |
+      |   NN |   int *ptr = (int *)malloc (sizeof (int));
+      |      |                     ^~~~~~~~~~~~~~~~~~~~~
+      |      |                     |
+      |      |                     (1) allocated here
+      |   NN |   *ptr = 42;
+      |      |   ~~~~~~~~~          
+      |      |        |
+      |      |        (2) assuming 'ptr' is non-NULL
+      |   NN |   if (x)
+      |      |      ~               
+      |      |      |
+      |      |      (3) following 'true' branch
+      |   NN |     free (ptr);
+      |      |     ~~~~~~~~~~       
+      |      |     |
+      |      |     (4) freed here
+      |   NN | 
+      |   NN |   *ptr = 19;
+      |      |   ~~~~~~~~~          
+      |      |        |
+      |      |        (5) use after 'free' of 'ptr'; freed at (4)
+      |
+   { dg-end-multiline-output "" } */
+
+/* "dereference of freed pointer 'ptr'".  */
+/* { dg-begin-multiline-output "" }
+  'test_3': events 1-2
+      |
+      |   NN |   if (x)
+      |      |      ^
+      |      |      |
+      |      |      (1) following 'true' branch
+      |......
+      |   NN |   *ptr = 19;
+      |      |   ~~~~~~~~~
+      |      |        |
+      |      |        (2) here
+      |
+   { dg-end-multiline-output "" } */
+/* TODO: this is really a duplicate; can we either eliminate it, or
+   improve the path?  */
+
+/* "use after free of 'ptr'".  */
+/* { dg-begin-multiline-output "" }
+  'test_3': events 1-6
+      |
+      |   NN |   int *ptr = (int *)malloc (sizeof (int));
+      |      |                     ^~~~~~~~~~~~~~~~~~~~~
+      |      |                     |
+      |      |                     (1) allocated here
+      |   NN |   *ptr = 42;
+      |      |   ~~~~~~~~~          
+      |      |        |
+      |      |        (2) assuming 'ptr' is non-NULL
+      |   NN |   if (x)
+      |      |      ~               
+      |      |      |
+      |      |      (3) following 'false' branch
+      |......
+      |   NN |   if (y)
+      |      |      ~               
+      |      |      |
+      |      |      (4) following 'true' branch
+      |   NN |     free (ptr);
+      |      |     ~~~~~~~~~~       
+      |      |     |
+      |      |     (5) freed here
+      |   NN |
+      |   NN |   return *ptr;
+      |      |          ~~~~        
+      |      |          |
+      |      |          (6) use after 'free' of 'ptr'; freed at (5)
+      |
+   { dg-end-multiline-output "" } */
+
+/* "leak of 'ptr'".  */
+/* { dg-begin-multiline-output "" }
+  'test_3': events 1-5
+      |
+      |   NN |   int *ptr = (int *)malloc (sizeof (int));
+      |      |                     ^~~~~~~~~~~~~~~~~~~~~
+      |      |                     |
+      |      |                     (1) allocated here
+      |   NN |   *ptr = 42;
+      |      |   ~~~~~~~~~          
+      |      |        |
+      |      |        (2) assuming 'ptr' is non-NULL
+      |   NN |   if (x)
+      |      |      ~               
+      |      |      |
+      |      |      (3) following 'false' branch
+      |......
+      |   NN |   if (y)
+      |      |      ~               
+      |      |      |
+      |      |      (4) following 'false' branch
+      |......
+      |   NN |   return *ptr;
+      |      |          ~~~~        
+      |      |          |
+      |      |          (5) 'ptr' leaks here; was allocated at (1)
+      |
+   { dg-end-multiline-output "" } */
+
+/* "dereference of freed pointer 'ptr'".  */
+/* { dg-begin-multiline-output "" }
+  'test_3': events 1-3
+      |
+      |   NN |   if (x)
+      |      |      ^
+      |      |      |
+      |      |      (1) following 'false' branch
+      |......
+      |   NN |   if (y)
+      |      |      ~
+      |      |      |
+      |      |      (2) following 'true' branch
+      |......
+      |   NN |   return *ptr;
+      |      |          ~~~~
+      |      |          |
+      |      |          (3) here
+      |
+   { dg-end-multiline-output "" } */
+/* TODO: this is really a duplicate; can we either eliminate it, or
+   improve the path?  */
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-2.c b/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-2.c
new file mode 100644
index 0000000..d36e1fe
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-2.c
@@ -0,0 +1,178 @@
+#include <stdlib.h>
+
+extern int foo (int);
+
+static int __attribute__((noinline))
+do_stuff_2 (int *p, int n)
+{
+  return 0;
+}
+
+/* As malloc-vs-local.c, but hand-inlining the logic.  */
+
+/* Repeated (n > 10) predicate.  */
+
+int test_repeated_predicate_1 (int n)
+{
+  int buf[10];
+  int *ptr;
+  int result;
+
+  if (n > 10)
+    ptr = (int *)malloc (sizeof (int) * n);
+  else
+    ptr = buf;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  {
+    int *p = ptr;
+    int sum = 0;
+    int i;
+    for (i = 0; i < n; i++)
+      p[i] = i;
+    for (i = 0; i < n; i++)
+      sum += foo (p[i]); /* { dg-bogus "uninitialized" } */
+    result = sum;
+  }
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  if (n > 10)
+    free (ptr); /* { dg-bogus "not on the heap" } */
+
+  return result; /* { dg-bogus "leak" } */
+}
+
+/* As above, but with just one loop.  */
+
+int test_repeated_predicate_1a (int n)
+{
+  int buf[10];
+  int *ptr;
+  int result;
+
+  if (n > 10)
+    ptr = (int *)malloc (sizeof (int) * n);
+  else
+    ptr = buf;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  {
+    int *p = ptr;
+    int sum = 0;
+    int i;
+    for (i = 0; i < n; i++)
+      p[i] = i;
+    result = sum;
+  }
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  if (n > 10)
+    free (ptr); /* { dg-bogus "not on the heap" } */
+
+  return result; /* { dg-bogus "leak" } */
+}
+
+/* A simpler version of the above.  */
+
+int test_repeated_predicate_2 (int n)
+{
+  int buf[10];
+  int *ptr;
+  int result;
+
+  if (n > 10)
+    ptr = (int *)malloc (sizeof (int) * n);
+  else
+    ptr = buf;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  result = do_stuff_2 (ptr, n);
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  if (n > 10)
+    free (ptr); /* { dg-bogus "not on the heap" } */
+
+  return result; /* { dg-bogus "leak" } */
+}
+
+/* A predicate that sets a flag for the 2nd test.  */
+
+int test_explicit_flag (int n)
+{
+  int buf[10];
+  int *ptr;
+  int result;
+  int need_to_free = 0;
+
+  if (n > 10)
+    {
+      ptr = (int *)malloc (sizeof (int) * n);
+      need_to_free = 1;
+    }
+  else
+    ptr = buf;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  {
+    int *p = ptr;
+    int sum = 0;
+    int i;
+    for (i = 0; i < n; i++)
+      p[i] = i;
+    for (i = 0; i < n; i++)
+      sum += foo (p[i]); /* { dg-bogus "uninitialized" } */
+    result = sum;
+  }
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  if (need_to_free)
+    free (ptr); /* { dg-bogus "not on the heap" } */
+
+  return result; /* { dg-bogus "leak" } */
+}
+
+/* Pointer comparison.  */
+
+int test_pointer_comparison (int n)
+{
+  int buf[10];
+  int *ptr;
+  int result;
+
+  if (n > 10)
+    ptr = (int *)malloc (sizeof (int) * n);
+  else
+    ptr = buf;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  {
+    int *p = ptr;
+    int sum = 0;
+    int i;
+    for (i = 0; i < n; i++)
+      p[i] = i;
+    for (i = 0; i < n; i++)
+      sum += foo (p[i]); /* { dg-bogus "uninitialized" } */
+    result = sum;
+  }
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  if (ptr != buf)
+    free (ptr); /* { dg-bogus "not on the heap" } */
+
+  return result; /* { dg-bogus "leak" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-3.c b/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-3.c
new file mode 100644
index 0000000..8558176
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-3.c
@@ -0,0 +1,65 @@
+#include <stdlib.h>
+
+extern int foo (int);
+
+static int __attribute__((noinline))
+do_stuff_2 (int *p, int n)
+{
+  return 0;
+}
+
+/* As malloc-vs-local-2.c, but with a memory leak for the "on the heap case"
+   by not attempting to free at the end.  */
+
+int test_1 (int n)
+{
+  int buf[10];
+  int *ptr;
+  int result;
+
+  if (n > 10)
+    ptr = (int *)malloc (sizeof (int) * n);
+  else
+    ptr = buf;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  {
+    int *p = ptr;
+    int sum = 0;
+    int i;
+    for (i = 0; i < n; i++)
+      p[i] = i;
+    for (i = 0; i < n; i++)
+      sum += foo (p[i]); /* { dg-bogus "uninitialized" } */
+    result = sum;
+  }
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  return result; /* { dg-message "leak of 'p'" } */
+  /* FIXME: should this be 'ptr'?  */
+}
+
+/* A simpler version of the above.  */
+
+int test_2 (int n)
+{
+  int buf[10];
+  int *ptr;
+  int result;
+
+  if (n > 10)
+    ptr = (int *)malloc (sizeof (int) * n);
+  else
+    ptr = buf;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  result = do_stuff_2 (ptr, n);
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  return result; /* { dg-message "leak of 'ptr'" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local.c b/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local.c
new file mode 100644
index 0000000..b829332
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-vs-local.c
@@ -0,0 +1,178 @@
+#include <stdlib.h>
+
+extern int foo (int);
+
+static int __attribute__((noinline))
+do_stuff (int *p, int n)
+{
+  int sum = 0;
+  int i;
+  for (i = 0; i < n; i++)
+    p[i] = i;
+  for (i = 0; i < n; i++)
+    sum += foo (p[i]); /* { dg-bogus "uninitialized" } */
+  return sum;
+}
+
+static int __attribute__((noinline))
+do_stuff_2 (int *p, int n)
+{
+  return 0;
+}
+
+/* Various examples of functions that use either a malloc buffer
+   or a local buffer, do something, then conditionally free the
+   buffer, tracking whether "free" is necessary in various
+   ways.
+
+   In each case, there ought to be only two paths through the function,
+   not four.  */
+
+/* Repeated (n > 10) predicate.  */
+
+int test_repeated_predicate_1 (int n)
+{
+  int buf[10];
+  int *ptr;
+  int result;
+
+  if (n > 10)
+    ptr = (int *)malloc (sizeof (int) * n);
+  else
+    ptr = buf;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  result = do_stuff (ptr, n);
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "4 exploded nodes" } */
+  // FIXME: why 4 here?
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "4 exploded nodes" } */
+  // FIXME: why 4 here?
+
+  if (n > 10)
+    free (ptr); /* { dg-bogus "not on the heap" } */
+
+  return result; /* { dg-bogus "leak" } */
+}
+
+/* A simpler version of the above.  */
+
+int test_repeated_predicate_2 (int n)
+{
+  int buf[10];
+  int *ptr;
+  int result;
+
+  if (n > 10)
+    ptr = (int *)malloc (sizeof (int) * n);
+  else
+    ptr = buf;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  result = do_stuff_2 (ptr, n);
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  if (n > 10)
+    free (ptr); /* { dg-bogus "not on the heap" } */
+
+  return result; /* { dg-bogus "leak" } */
+}
+
+/* A predicate that sets a flag for the 2nd test.  */
+
+int test_explicit_flag (int n)
+{
+  int buf[10];
+  int *ptr;
+  int result;
+  int need_to_free = 0;
+
+  if (n > 10)
+    {
+      ptr = (int *)malloc (sizeof (int) * n);
+      need_to_free = 1;
+    }
+  else
+    ptr = buf;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  result = do_stuff (ptr, n);
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "4 exploded nodes" } */
+  // FIXME: why 4 here?
+
+  if (need_to_free)
+    free (ptr); /* { dg-bogus "not on the heap" } */
+
+  return result; /* { dg-bogus "leak" } */
+}
+
+/* Pointer comparison.  */
+
+int test_pointer_comparison (int n)
+{
+  int buf[10];
+  int *ptr;
+  int result;
+
+  if (n > 10)
+    ptr = (int *)malloc (sizeof (int) * n);
+  else
+    ptr = buf;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  result = do_stuff (ptr, n);
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "4 exploded nodes" } */
+  // FIXME: why 4 here?
+
+  if (ptr != buf)
+    free (ptr); /* { dg-bogus "not on the heap" } */
+
+  return result; /* { dg-bogus "leak" } */
+}
+
+/* Set a flag based on a conditional, then use it, then reuse the
+   conditional.  */
+
+int test_initial_flag (int n)
+{
+  int buf[10];
+  int *ptr;
+  int result;
+  int on_heap = 0;
+
+  if (n > 10)
+    on_heap = 1;
+  else
+    on_heap = 0;
+
+  /* Due to state-merging, we lose the relationship between 'n > 10'
+     and 'on_heap' here; we have to rely on feasibility-checking
+     in the diagnostic_manager to reject the false warnings.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+  if (on_heap)
+    ptr = (int *)malloc (sizeof (int) * n);
+  else
+    ptr = buf;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  result = do_stuff (ptr, n);
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "6 exploded nodes" } */
+  // FIXME: why 6 here?
+
+  if (n > 10)
+    free (ptr); /* { dg-bogus "not on the heap" } */
+
+  return result; /* { dg-bogus "leak" } */
+}
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/paths-1.c b/gcc/testsuite/gcc.dg/analyzer/paths-1.c
new file mode 100644
index 0000000..246f6cd
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/paths-1.c
@@ -0,0 +1,16 @@
+struct foo
+{
+  int m_flag;
+};
+
+extern void bar (int);
+
+void test (struct foo *pf)
+{
+  if (pf->m_flag)
+    bar (0);
+  else
+    bar (1);
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "3 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/paths-2.c b/gcc/testsuite/gcc.dg/analyzer/paths-2.c
new file mode 100644
index 0000000..8637c8c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/paths-2.c
@@ -0,0 +1,25 @@
+int test (int a)
+{
+  if (a != 42 && a != 113) {
+    return (-2);
+  }
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "3 exploded nodes" } */
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+  return 0;
+}
+
+int test_2 (int a)
+{
+  if (a != 42 && a != 113 && a != 666) {
+    return (-2);
+  }
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "4 exploded nodes" } */
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/paths-3.c b/gcc/testsuite/gcc.dg/analyzer/paths-3.c
new file mode 100644
index 0000000..cfbc0f8
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/paths-3.c
@@ -0,0 +1,46 @@
+#include <stdlib.h>
+
+int test_1 (int a, int b)
+{
+  void *p;
+
+  if (a > 5)
+    if (b)
+      p = malloc (16);
+    else
+      p = malloc (32);
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "4 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  if (a > 5)
+    {
+      free (p);
+      __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+    }
+
+  return 0; /* { dg-bogus "leak" } */
+}
+
+int test_2 (int a, int b)
+{
+  void *p;
+
+  if (a > 5)
+    if (b)
+      p = malloc (16);
+    else
+      p = malloc (32);
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "4 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  if (a > 6) /* different condition */
+    {
+      free (p);
+      __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+    }
+
+  return 0; /* { dg-warning "leak of 'p'" } */
+  /* leaks when a == 5.  */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/paths-4.c b/gcc/testsuite/gcc.dg/analyzer/paths-4.c
new file mode 100644
index 0000000..307bc97
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/paths-4.c
@@ -0,0 +1,49 @@
+struct state
+{
+  int mode;
+  int data;
+};
+
+extern void do_stuff (struct state *, int);
+
+int test_1 (struct state *s)
+{
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+  while (1)
+    {
+      __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+      __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+      do_stuff (s, s->mode);
+    }
+}
+
+int test_2 (struct state *s)
+{
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+  while (1)
+    {
+      __analyzer_dump_exploded_nodes (0); /* { dg-warning "3 exploded nodes" } */
+      __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+      switch (s->mode)
+	{
+	case 0:
+	  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+	  do_stuff (s, 0);
+	  break;
+	case 1:
+	  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+	  do_stuff (s, 17);
+	  break;
+	case 2:
+	  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+	  do_stuff (s, 5);
+	  break;
+	case 3:
+	  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+	  return 42;
+	case 4:
+	  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+	  return -3;
+	}
+    }
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/paths-5.c b/gcc/testsuite/gcc.dg/analyzer/paths-5.c
new file mode 100644
index 0000000..d3589b5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/paths-5.c
@@ -0,0 +1,10 @@
+void test (int *p, int n)
+{
+  int i;
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+  for (i = 0; i < n; i++)
+    {
+      p[i] = i; /* { dg-bogus "uninitialized" } */
+      __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+    }
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/paths-6.c b/gcc/testsuite/gcc.dg/analyzer/paths-6.c
new file mode 100644
index 0000000..09f4f6f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/paths-6.c
@@ -0,0 +1,118 @@
+#include <stdlib.h>
+
+/* Verify that ordering of writes doesn't matter when merging states.  */
+
+/* Test with locals.  */
+
+void test_1 (int flag)
+{
+  int a, b;
+  if (flag)
+    {
+      a = 3;
+      b = 4;
+    }
+  else
+    {
+      b = 4;
+      a = 3;
+    }
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  // FIXME: the above can vary between 2 and 3 exploded nodes
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+  __analyzer_eval (a == 3); /* { dg-warning "TRUE" } */
+  __analyzer_eval (b == 4); /* { dg-warning "TRUE" } */
+}
+
+/* Test with globals.  */
+
+int f, g, h;
+void test_2 (int flag)
+{
+  if (flag)
+    {
+      f = 3;
+      g = 4;
+    }
+  else
+    {
+      g = 4;
+      f = 3;
+    }
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+  __analyzer_eval (f == 3); /* { dg-warning "TRUE" } */
+  __analyzer_eval (g == 4); /* { dg-warning "TRUE" } */
+}
+
+/* All 6 orderings of writes to 3 globals.  */
+
+void test_3 (int i)
+{
+  switch (i)
+    {
+    default:
+    case 0:
+      f = 3;
+      g = 4;
+      h = 5;
+      break;
+
+    case 1:
+      f = 3;
+      h = 5;
+      g = 4;
+      break;
+
+    case 2:
+      g = 4;
+      f = 3;
+      h = 5;
+      break;
+
+    case 3:
+      g = 4;
+      h = 5;
+      f = 3;
+      break;
+
+    case 4:
+      h = 5;
+      f = 3;
+      g = 4;
+      break;
+
+    case 5:
+      h = 5;
+      g = 4;
+      f = 3;
+      break;
+    }
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "6 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+  __analyzer_eval (f == 3); /* { dg-warning "TRUE" } */
+  __analyzer_eval (g == 4); /* { dg-warning "TRUE" } */
+  __analyzer_eval (h == 5); /* { dg-warning "TRUE" } */
+}
+
+void test_4 (int flag)
+{
+  void *p, *q;
+  if (flag)
+    {
+      p = malloc (256);
+      q = malloc (256);
+    }
+  else
+    {
+      q = malloc (256);
+      p = malloc (256);
+    }
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+  free (p);
+  free (q);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/paths-7.c b/gcc/testsuite/gcc.dg/analyzer/paths-7.c
new file mode 100644
index 0000000..56d0085
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/paths-7.c
@@ -0,0 +1,58 @@
+#include <stdlib.h>
+
+extern int foo (int);
+
+int test (int flag, void *ptr, int *p, int n)
+{
+  int result;
+  int sum = 0;
+  int i;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+  if (flag)
+    free (ptr);
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  for (i = 0; i < n; i++)
+    p[i] = i;
+  for (i = 0; i < n; i++)
+    sum += foo (p[i]); /* { dg-bogus "uninitialized" } */
+  result = sum;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  if (flag)
+    free (ptr); /* { dg-warning "double-free of 'ptr'" } */
+  return result;
+}
+
+int test_2 (int flag, int *p, int n)
+{
+  int result;
+  int sum = 0;
+  int i;
+
+  void *ptr = malloc (16);
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+  if (flag)
+    free (ptr);
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+
+  for (i = 0; i < n; i++)
+    p[i] = i;
+  for (i = 0; i < n; i++)
+    sum += foo (p[i]); /* { dg-bogus "uninitialized" } */
+  result = sum;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "4 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "4 exploded nodes" } */
+  // FIXME: why 4 here?
+
+  free (ptr); /* { dg-warning "double-free of 'ptr'" } */
+  return result;
+}
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..b68e605
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/pattern-test-1.c
@@ -0,0 +1,14 @@
+/* { dg-additional-options "-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/pragma-1.c b/gcc/testsuite/gcc.dg/analyzer/pragma-1.c
new file mode 100644
index 0000000..56abb84
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/pragma-1.c
@@ -0,0 +1,26 @@
+/* Verify that we can disable analyzer warnings via pragmas.  */
+
+#include <stdlib.h>
+
+void test_1 (void *ptr)
+{
+  free (ptr);
+  free (ptr); /* { dg-warning "double-free" } */
+}
+
+void test_2 (void *ptr)
+{
+  _Pragma("GCC diagnostic push")
+  _Pragma("GCC diagnostic ignored \"-Wanalyzer-double-free\"")
+
+  free (ptr);
+  free (ptr);  /* { dg-bogus "double-free" } */
+
+  _Pragma("GCC diagnostic pop")
+}
+
+void test_3 (void *ptr)
+{
+  free (ptr);
+  free (ptr);  /* { dg-warning "double-free" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/scope-1.c b/gcc/testsuite/gcc.dg/analyzer/scope-1.c
new file mode 100644
index 0000000..f8acffa
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/scope-1.c
@@ -0,0 +1,23 @@
+#include <stdlib.h>
+
+int test_1 (void)
+{
+  {
+    int *q = malloc (1024);
+  }
+
+  return 42; /* { dg-warning "leak of 'q'" } */
+  // FIXME: would be better to report it at the close-of-scope
+}
+
+int test_2 (void)
+{
+  {
+    void *q = malloc (1024);
+  }
+
+  int q = 42;
+
+  return q; /* { dg-warning "leak of 'q'" } */
+  // FIXME: would be better to report it at the close-of-scope
+}
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..22f01dc
--- /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'" } */
+                                             /* { dg-message "\\(2\\) following 'true' branch" "" { target *-*-* } .-1 } */
+    /* 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 'tmp.i' in array lookup without bounds checking" } */
+    /* { dg-message "23: \\(3\\) state of 'tmp.i': 'start' -> 'tainted'" "" { target *-*-* } .-1 } */
+    /* { dg-message "\\(4\\) here \\('tmp.i' is in state 'tainted'\\)" "" { target *-*-* } .-2 } */
+    
+    // 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/analyzer/zlib-1.c b/gcc/testsuite/gcc.dg/analyzer/zlib-1.c
new file mode 100644
index 0000000..a031dbd
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/zlib-1.c
@@ -0,0 +1,71 @@
+/* { dg-additional-options "--param analyzer-bb-explosion-factor=20" } */
+/* TODO: we ought go be able to fully analyze this function without needing
+   the limit.  */
+
+typedef void (*free_func)(void *opaque, void *address);
+
+typedef struct z_stream_s {
+  struct internal_state *state;
+  free_func zfree;
+  void *opaque;
+} z_stream;
+
+struct internal_state {
+  z_stream *strm;
+  int status;
+  unsigned char *pending_buf;
+  unsigned char *window;
+  unsigned short *prev;
+  unsigned short *head;
+};
+
+int deflateEnd(z_stream *strm)
+{
+  int status;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+  if (strm == 0 || strm->state == 0)
+    return (-2);
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+  status = strm->state->status;
+  if (status != 42 && status != 113 && status != 666) {
+    return (-2);
+  }
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "4 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+  if (strm->state->pending_buf)
+    (*(strm->zfree))(strm->opaque, (void *)(strm->state->pending_buf));
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+  if (strm->state->head)
+      (*(strm->zfree))(strm->opaque, (void *)(strm->state->head));
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+  if (strm->state->prev)
+    (*(strm->zfree))(strm->opaque, (void *)(strm->state->prev));
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+  if (strm->state->window)
+    (*(strm->zfree))(strm->opaque, (void *)(strm->state->window));
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+  (*(strm->zfree))(strm->opaque, (void *)(strm->state));
+  strm->state = 0;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+  return status == 113 ? (-3) : 0;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/zlib-2.c b/gcc/testsuite/gcc.dg/analyzer/zlib-2.c
new file mode 100644
index 0000000..62163a0
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/zlib-2.c
@@ -0,0 +1,49 @@
+typedef void * (*alloc_func)(void * opaque, unsigned items, unsigned size);
+typedef void (*free_func)(void * opaque, void * address);
+
+typedef struct z_stream_s {
+  char *msg;
+  alloc_func zalloc;
+  free_func zfree;
+  void * opaque;
+} z_stream;
+
+void * zcalloc(void * opaque, unsigned items, unsigned size);
+void zcfree(void * opaque, void * ptr);
+
+int deflateInit2_(z_stream *strm, int level, int method, int windowBits,
+                  int memLevel, int strategy, const char *version,
+                  int stream_size) {
+  int noheader = 0;
+  static const char *my_version = "1.1.3";
+
+  if (version == 0 || version[0] != my_version[0] ||
+      stream_size != sizeof(z_stream)) {
+    return (-6);
+  }
+  if (strm == 0)
+    return (-2);
+
+  strm->msg = 0;
+  if (strm->zalloc == 0) {
+    strm->zalloc = zcalloc;
+    strm->opaque = (void *)0;
+  }
+  if (strm->zfree == 0)
+    strm->zfree = zcfree;
+
+  if (level == (-1))
+    level = 6;
+
+  if (windowBits < 0) {
+    noheader = 1;
+    windowBits = -windowBits;
+  }
+  if (memLevel < 1 || memLevel > 9 || method != 8 || windowBits < 8 ||
+      windowBits > 15 || level < 0 || level > 9 || strategy < 0 ||
+      strategy > 2) {
+    return (-2);
+  }
+  (*((strm)->zalloc))((strm)->opaque, (1), 112);
+  return 0;
+}
diff --git a/notes.txt b/notes.txt
new file mode 100644
index 0000000..2be7cce
--- /dev/null
+++ b/notes.txt
@@ -0,0 +1,75 @@
+Notes
+=====
+
+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.
+
+We collate warnings and only emit them for simplest path
+e.g. for a bug in a utility function, with lots of routes to calling it,
+we only emit the simplest path (which could be intraprocedural, if
+it can be reproduced without a caller).
+
+
+Useful debugging tips
+=====================
+
+Add:
+  __analyzer_break ();
+to the source being analyzed to trigger a breakpoint in the analyzer when
+that source is reached.  By putting a series of these in the source, it's
+much easier to effectively step through the program state as it's analyzed.
+
+__analyzer_dump ();
+
+will dump the copious information about the analyzer's state each time it
+reaches the call in its traversal of the source.
+
+The builtin __analyzer_dump_exploded_nodes will dump information after
+analysis on all of the exploded nodes at that program point:
+
+  __analyzer_dump_exploded_nodes (0);
+
+will dump just the number of nodes, and their IDs.
+
+  __analyzer_dump_exploded_nodes (1);
+
+will also dump all of the states within those nodes.
+
+region_model::get_value_by_name can be used when inserting custom
+debugging code (e.g. adding it region_model::validate to detect the point
+at which a named variable acquires an unexpected state).
+
+Re pending_diagnostic's describe_* vfuncs:
+     I would have given all these the name "describe" and let overloading
+     distinguish them, but I was getting numerous warnings from
+     -Woverloaded-virtual.
+
+
+User-facing Docs (putting them here to avoid the tedious texinfo rebuild times):
+================================================================================
+
+-fanalyzer
+Enables a more detailed, but much more time-consuming analysis pass.
+
+-fanalyzer-verbosity=0
+  Only show interprocedural calls and the most critical state-change
+  events.
+
+-fanalyzer-verbosity=1
+  Show interprocedural calls and entrypoints, and the most critical
+  state-change events.
+
+-fanalyzer-verbosity=2 (the default)
+  Show interprocedural calls and entrypoints, branches taken, and the
+  most critical state-change events.
+
+-fanalyzer-verbosity=3
+  Show all events, including statements, and state-change events that
+  are unrelated to the diagnostic in question.  This is more intended
+  for debugging the analyzer itself.
-- 
1.8.5.3

