From 22306e7b760b0a9e2d87fabea61b367b04d15a5a Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Thu, 4 Mar 2021 16:51:49 -0500
Subject: [PATCH 17/21] FIXME: WIP on better call summaries

---
 gcc/Makefile.in                               |    1 +
 gcc/analyzer/analysis-plan.cc                 |  109 +-
 gcc/analyzer/analysis-plan.h                  |   33 +-
 gcc/analyzer/analyzer.h                       |    2 +
 gcc/analyzer/call-summary.cc                  | 1065 +++++++++++++++++
 gcc/analyzer/call-summary.h                   |  131 ++
 gcc/analyzer/engine.cc                        |  145 ++-
 gcc/analyzer/exploded-graph.h                 |   28 +-
 gcc/analyzer/program-state.cc                 |   16 +
 gcc/analyzer/region-model-manager.cc          |    9 +
 gcc/analyzer/region-model.cc                  |   10 +-
 gcc/analyzer/region-model.h                   |    2 -
 gcc/analyzer/store.h                          |    4 +
 gcc/analyzer/svalue.cc                        |    7 +-
 gcc/analyzer/svalue.h                         |    2 +-
 gcc/cgraph.h                                  |    2 +-
 .../gcc.dg/analyzer/call-summaries-1.c        |   14 -
 .../analyzer/call-summaries-aliasing-1.c      |   42 +
 .../gcc.dg/analyzer/call-summaries-alloca-1.c |   18 +
 .../analyzer/call-summaries-data-model-1.c    |   80 ++
 .../call-summaries-data-model-12.FIXME-c      |   21 +
 .../analyzer/call-summaries-data-model-2.c    |   56 +
 .../analyzer/call-summaries-data-model-3.c    |  113 ++
 .../analyzer/call-summaries-data-model-4.c    |   40 +
 .../analyzer/call-summaries-data-model-5.c    |   54 +
 .../gcc.dg/analyzer/call-summaries-deref-1.c  |   20 +
 .../analyzer/call-summaries-explosion-1.c     |   69 ++
 .../call-summaries-jump-through-fnptr-1.c     |   25 +
 .../gcc.dg/analyzer/call-summaries-malloc-1.c |   18 +
 .../gcc.dg/analyzer/call-summaries-malloc-2.c |   27 +
 .../gcc.dg/analyzer/call-summaries-malloc-3.c |   45 +
 .../gcc.dg/analyzer/call-summaries-malloc-4.c |   24 +
 .../gcc.dg/analyzer/call-summaries-malloc-5.c |   91 ++
 .../gcc.dg/analyzer/call-summaries-malloc-6.c |   23 +
 .../analyzer/call-summaries-noreturn-1.c      |   24 +
 .../analyzer/call-summaries-noreturn-2.c      |   23 +
 .../analyzer/call-summaries-noreturn-3.c      |   32 +
 .../analyzer/call-summaries-return-expr-1.c   |  150 +++
 .../gcc.dg/analyzer/call-summaries-setjmp-1.c |   32 +
 .../analyzer/call-summaries-stale-frame-1.c   |   22 +
 .../gcc.dg/analyzer/call-summaries-strlen-1.c |   20 +
 .../analyzer/call-summaries-unknown-fn-1.c    |   37 +
 .../analyzer/call-summaries-unknown-fn-2.c    |   58 +
 43 files changed, 2650 insertions(+), 94 deletions(-)
 create mode 100644 gcc/analyzer/call-summary.cc
 create mode 100644 gcc/analyzer/call-summary.h
 delete mode 100644 gcc/testsuite/gcc.dg/analyzer/call-summaries-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/call-summaries-aliasing-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/call-summaries-alloca-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/call-summaries-data-model-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/call-summaries-data-model-12.FIXME-c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/call-summaries-data-model-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/call-summaries-data-model-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/call-summaries-data-model-4.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/call-summaries-data-model-5.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/call-summaries-deref-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/call-summaries-explosion-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/call-summaries-jump-through-fnptr-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/call-summaries-malloc-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/call-summaries-malloc-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/call-summaries-malloc-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/call-summaries-malloc-4.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/call-summaries-malloc-5.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/call-summaries-malloc-6.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/call-summaries-noreturn-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/call-summaries-noreturn-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/call-summaries-noreturn-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/call-summaries-return-expr-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/call-summaries-setjmp-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/call-summaries-stale-frame-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/call-summaries-strlen-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/call-summaries-unknown-fn-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/call-summaries-unknown-fn-2.c

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 8a5fb3fd99c..c6c19521345 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1249,6 +1249,7 @@ ANALYZER_OBJS = \
 	analyzer/analyzer-selftests.o \
 	analyzer/bar-chart.o \
 	analyzer/call-string.o \
+	analyzer/call-summary.o \
 	analyzer/checker-path.o \
 	analyzer/complexity.o \
 	analyzer/constraint-manager.o \
diff --git a/gcc/analyzer/analysis-plan.cc b/gcc/analyzer/analysis-plan.cc
index bf341f25bef..4d8ac3696f2 100644
--- a/gcc/analyzer/analysis-plan.cc
+++ b/gcc/analyzer/analysis-plan.cc
@@ -43,6 +43,20 @@ along with GCC; see the file COPYING3.  If not see
 #include "digraph.h"
 #include "cfgloop.h"
 #include "analyzer/supergraph.h"
+#include "tristate.h"
+#include "analyzer/call-string.h"
+#include "analyzer/program-point.h"
+#include "analyzer/store.h"
+#include "selftest.h"
+#include "analyzer/region-model.h"
+#include "analyzer/sm.h"
+#include "analyzer/program-state.h"
+#include "alloc-pool.h"
+#include "fibonacci_heap.h"
+#include "analyzer/diagnostic-manager.h"
+#include "shortest-paths.h"
+#include "sbitmap.h"
+#include "analyzer/exploded-graph.h"
 
 #if ENABLE_ANALYZER
 
@@ -54,7 +68,7 @@ analysis_plan::analysis_plan (const supergraph &sg, logger *logger)
 : log_user (logger), m_sg (sg),
   m_cgraph_node_postorder (XCNEWVEC (struct cgraph_node *,
 				     symtab->cgraph_count)),
-  m_index_by_uid (symtab->cgraph_max_uid)
+  m_function_info_by_uid (symtab->cgraph_max_uid)
 {
   LOG_SCOPE (logger);
   auto_timevar time (TV_ANALYZER_PLAN);
@@ -66,14 +80,15 @@ analysis_plan::analysis_plan (const supergraph &sg, logger *logger)
 		     "analysis_plan", m_cgraph_node_postorder,
 		     m_num_cgraph_nodes);
 
-  /* Populate m_index_by_uid.  */
+  /* Populate m_function_info_by_uid.  */
   for (int i = 0; i < symtab->cgraph_max_uid; i++)
-    m_index_by_uid.quick_push (-1);
+    m_function_info_by_uid.quick_push (NULL);
   for (int i = 0; i < m_num_cgraph_nodes; i++)
     {
-      gcc_assert (m_cgraph_node_postorder[i]->get_uid ()
-		  < symtab->cgraph_max_uid);
-      m_index_by_uid[m_cgraph_node_postorder[i]->get_uid ()] = i;
+      const cgraph_node *cgnode = m_cgraph_node_postorder[i];
+      const int uid = cgnode->get_uid ();
+      gcc_assert (uid < symtab->cgraph_max_uid);
+      m_function_info_by_uid[uid] = new function_info (cgnode, i);
     }
 }
 
@@ -91,11 +106,8 @@ analysis_plan::~analysis_plan ()
 int
 analysis_plan::cmp_function (function *fun_a, function *fun_b) const
 {
-  cgraph_node *node_a = cgraph_node::get (fun_a->decl);
-  cgraph_node *node_b = cgraph_node::get (fun_b->decl);
-
-  int idx_a = m_index_by_uid[node_a->get_uid ()];
-  int idx_b = m_index_by_uid[node_b->get_uid ()];
+  int idx_a = get_function_info (fun_a)->get_postorder_idx ();
+  int idx_b = get_function_info (fun_b)->get_postorder_idx ();
 
   return idx_b - idx_a;
 }
@@ -146,4 +158,79 @@ analysis_plan::use_summary_p (const cgraph_edge *edge) const
   return true;
 }
 
+/* FIXME.  */
+
+function_info *
+analysis_plan::get_function_info (const function *fun) const
+{
+  cgraph_node *cgnode = cgraph_node::get (fun->decl);
+  return get_function_info (cgnode);
+}
+/* FIXME.  */
+
+function_info *
+analysis_plan::get_function_info (const cgraph_node *cgnode) const
+{
+  gcc_assert (cgnode);
+  return m_function_info_by_uid[cgnode->get_uid ()];
+}
+
+/* Dump a representation of this analysis_plan to PP
+   (which must support %E for trees).  */
+
+void
+analysis_plan::dump_to_pp (pretty_printer *pp) const
+{
+  pp_string (pp, "analysis_plan");
+  pp_newline (pp);
+  unsigned i;
+  function_info *info;
+  FOR_EACH_VEC_ELT (m_function_info_by_uid, i, info)
+    {
+      pp_printf (pp, "  uid: %i", i);
+      if (info)
+	{
+	  pp_printf (pp, " postorder_idx: %i: (%qs): ",
+		     info->get_postorder_idx (),
+		     info->get_cgnode ()->dump_name ());
+	  //info->dump_to_pp (pp); // FIXME
+	}
+      else
+	pp_string (pp, " (NULL)");
+      pp_newline (pp);
+    }
+}
+
+/* Dump a multiline representation of this analysis_plan to FP.  */
+
+void
+analysis_plan::dump_to_file (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 analysis_plan to stderr.  */
+
+DEBUG_FUNCTION void
+analysis_plan::dump () const
+{
+  dump_to_file (stderr);
+}
+
+/* class function_info.  */
+
+/* FIXME.  */
+
+function_info::function_info (const cgraph_node *cgnode, int postorder_idx)
+: m_cgnode (cgnode),
+  m_postorder_idx (postorder_idx)
+  //, m_outcomes ()
+{
+}
+
 #endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/analysis-plan.h b/gcc/analyzer/analysis-plan.h
index 1d10b772833..907de51dd4b 100644
--- a/gcc/analyzer/analysis-plan.h
+++ b/gcc/analyzer/analysis-plan.h
@@ -23,6 +23,9 @@ along with GCC; see the file COPYING3.  If not see
 
 namespace ana {
 
+// FIXME: is this new class (and the related changes) actually needed?
+class function_info;
+
 /* A class to encapsulate decisions about how the analysis should happen.
    Examples:
    - the order in which functions should be analyzed, so that function
@@ -41,6 +44,13 @@ public:
 
   bool use_summary_p (const cgraph_edge *edge) const;
 
+  function_info *get_function_info (const function *fun) const;
+  function_info *get_function_info (const cgraph_node *cgnode) const;
+
+  void dump_to_pp (pretty_printer *pp) const;
+  void dump_to_file (FILE *outf) const;
+  void dump () const;
+
 private:
   DISABLE_COPY_AND_ASSIGN (analysis_plan);
 
@@ -50,9 +60,26 @@ private:
   cgraph_node **m_cgraph_node_postorder;
   int m_num_cgraph_nodes;
 
-  /* Index of each node within the postorder ordering,
-     accessed via the "m_uid" field.  */
-  auto_vec<int> m_index_by_uid;
+  /* Data for each cgraph node, accessed via the "m_uid" field.  */
+  auto_delete_vec<function_info> m_function_info_by_uid;
+};
+
+/* FIXME.  */
+
+class function_info
+{
+public:
+  function_info (const cgraph_node *cgnode, int postorder_idx);
+
+  const cgraph_node *get_cgnode () const { return m_cgnode; }
+  int get_postorder_idx () const { return m_postorder_idx; }
+
+  // FIXME:
+  //private:
+  const cgraph_node *m_cgnode;
+
+  /* Index of the cgraph node within the postorder ordering.  */
+  int m_postorder_idx;
 };
 
 } // namespace ana
diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h
index f50ac662516..5775194c757 100644
--- a/gcc/analyzer/analyzer.h
+++ b/gcc/analyzer/analyzer.h
@@ -95,6 +95,8 @@ class state_purge_map;
 class state_purge_per_ssa_name;
 class state_change;
 class rewind_info_t;
+class call_summary;
+class call_outcome;
 
 class engine;
 class state_machine;
diff --git a/gcc/analyzer/call-summary.cc b/gcc/analyzer/call-summary.cc
new file mode 100644
index 00000000000..f7ada2c3214
--- /dev/null
+++ b/gcc/analyzer/call-summary.cc
@@ -0,0 +1,1065 @@
+/* Classes for summarizing the effects of function calls.
+   Copyright (C) 2021 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 "tree.h"
+#include "fold-const.h"
+#include "gcc-rich-location.h"
+#include "alloc-pool.h"
+#include "fibonacci_heap.h"
+#include "shortest-paths.h"
+#include "diagnostic-core.h"
+#include "diagnostic-event-id.h"
+#include "diagnostic-path.h"
+#include "function.h"
+#include "pretty-print.h"
+#include "sbitmap.h"
+#include "bitmap.h"
+#include "tristate.h"
+#include "ordered-hash-map.h"
+#include "selftest.h"
+#include "json.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/analyzer-logging.h"
+#include "analyzer/call-string.h"
+#include "analyzer/program-point.h"
+#include "analyzer/store.h"
+#include "analyzer/region-model.h"
+#include "analyzer/constraint-manager.h"
+#include "analyzer/sm.h"
+#include "analyzer/pending-diagnostic.h"
+#include "analyzer/diagnostic-manager.h"
+#include "cfg.h"
+#include "basic-block.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "gimple-pretty-print.h"
+#include "cgraph.h"
+#include "digraph.h"
+#include "analyzer/supergraph.h"
+#include "analyzer/program-state.h"
+#include "analyzer/exploded-graph.h"
+#include "analyzer/call-summary.h"
+
+#if ENABLE_ANALYZER
+
+namespace ana {
+
+/* Concrete subclass of exploded_edge::custom_info_t for exploded_edges that
+   were created when replaying a call_outcome at a callsite.  */
+
+class custom_info_call_outcome : public exploded_edge::custom_info_t
+{
+public:
+  custom_info_call_outcome (const call_outcome *outcome)
+  : m_outcome (outcome)
+  {
+  }
+
+  void print (pretty_printer *pp) FINAL OVERRIDE
+  {
+    pp_string (pp, "summarized call: ");
+    m_outcome->dump_to_pp (pp);
+  }
+
+  void update_model (region_model *model,
+		     const exploded_edge &eedge) FINAL OVERRIDE
+  {
+    const superedge *sedge = eedge.m_sedge;
+    gcc_assert (sedge);
+    gcc_assert (sedge->get_kind () == SUPEREDGE_INTRAPROCEDURAL_CALL);
+    const callgraph_superedge *cg_sedge
+      = as_a <const callgraph_superedge *> (sedge);
+    //m_outcome->update_model (model, cg_sedge);
+    // FIXME
+  }
+
+  void add_events_to_path (checker_path */*emission_path*/,
+			   const exploded_edge &/*eedge*/) FINAL OVERRIDE
+  {
+    //gcc_unreachable (); // TODO
+  }
+
+private:
+  const call_outcome *m_outcome;
+};
+
+/* class summarization.  */
+
+/* FIXME.  */
+
+void
+summarization::dump_to_pp (pretty_printer *pp, bool simple) const
+{
+  pp_printf (pp, "summarization for call at EN: %i from SN:%i to SN:%i",
+	     m_src_enode->m_index,
+	     m_cg_sedge->m_src->m_index,
+	     m_cg_sedge->m_dest->m_index);
+  pp_newline (pp);
+  call_details cd (get_call_stmt (), m_src_enode->get_state ().m_region_model,
+		   NULL);
+  cd.dump_to_pp (pp, simple);
+  // FIXME: unify this class with call_details, or share more code?
+
+  // TODO: also dump m_region_map, potentially ordering it
+  for (region_map_t::iterator iter = m_region_map.begin ();
+       iter != m_region_map.end (); ++iter)
+    {
+      pp_string (pp, "callee: ");
+      (*iter).first->dump_to_pp (pp, simple);
+      pp_string (pp, " -> caller: ");
+      (*iter).second->dump_to_pp (pp, simple);
+      pp_newline (pp);
+    }
+}
+
+/* FIXME.  */
+
+DEBUG_FUNCTION void
+summarization::dump (bool simple) const
+{
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  pp_show_color (&pp) = pp_show_color (global_dc->printer);
+  pp.buffer->stream = stderr;
+  dump_to_pp (&pp, simple);
+  pp_newline (&pp);
+  pp_flush (&pp);
+}
+
+/* FIXME.  */
+
+void
+summarization::add_enode_and_eedge (const program_point &dest_point,
+				    const program_state &dest_state,
+				    const call_outcome *outcome) const
+{
+  LOG_SCOPE (get_logger ());
+  exploded_node *dest_enode
+    = m_eg->get_or_create_node (dest_point, dest_state, m_src_enode);
+  if (!dest_enode)
+    {
+      if (get_logger ())
+	get_logger ()->log ("unable to create enode");
+      return;
+    }
+
+  if (get_logger ())
+    get_logger ()->log ("created EN: %i from call_outcome",
+			dest_enode->m_index);
+
+  m_eg->add_edge (m_src_enode, dest_enode, m_cg_sedge,
+		  new custom_info_call_outcome (outcome));
+
+#if 0
+#if 0
+	  if (get_logger ())
+	    get_logger ()->log ("creating summary for outcome EN: %i",
+				outcome->m_index);
+#endif
+	  program_point next_point
+	    (get_point_for_outcome (src, sedge, outcome));
+	  program_state next_state (get_state_for_outcome (src, outcome));
+	  if (exploded_node *dest_enode
+		= get_or_create_node (next_point, next_state, src))
+	    {
+	      add_edge (src, dest_enode, sedge,
+			new outcome_custom_info (outcome));
+	    }
+	}
+      else
+	{
+#if 0
+	  if (get_logger ())
+	    get_logger ()->log ("not creating summary for outcome EN: %i",
+				outcome->m_index);
+#endif
+	}
+#endif
+}
+
+/* FIXME.  */
+
+const gcall *
+summarization::get_call_stmt () const
+{
+  const gimple *last_stmt = m_src_enode->get_supernode ()->get_last_stmt ();
+  const gcall *call_stmt = as_a <const gcall *> (last_stmt);
+  return call_stmt;
+}
+
+/* FIXME.  */
+
+const call_string &
+summarization::get_caller_call_string () const
+{
+  return m_src_enode->get_point ().get_call_string ();
+}
+
+/* Get the state of the caller at the call.  */
+
+const program_state &
+summarization::get_caller_state () const
+{
+  return m_src_enode->get_state ();
+}
+
+/* FIXME: can we use lambda yet?
+   For use by summarization::apply_constraints.  */
+
+class outcome_fact_visitor : public fact_visitor
+{
+public:
+  outcome_fact_visitor (summarization *cs,
+			region_model *dest_model)
+  : m_cs (cs),
+    m_dest_model (dest_model),
+    m_valid (true)
+  {
+  }
+
+  void on_fact (const svalue *outcome_lhs,
+		enum tree_code op,
+		const svalue *outcome_rhs) FINAL OVERRIDE
+  {
+    const svalue *caller_lhs = m_cs->map_svalue_from_callee (outcome_lhs,
+							     m_dest_model);
+    const svalue *caller_rhs = m_cs->map_svalue_from_callee (outcome_rhs,
+							     m_dest_model);
+    if (!m_dest_model->get_constraints ()->add_constraint (caller_lhs,
+							   op,
+							   caller_rhs))
+      m_valid = false;
+  }
+
+  bool valid_p () const { return m_valid; }
+
+private:
+  summarization *m_cs;
+  region_model *m_dest_model;
+  bool m_valid;
+};
+
+/* FIXME: Helper subroutine for implementing maybe_extend_egraph vfuncs.
+   Attempt to apply constraints from OUTCOME_MODEL to DEST_MODEL.
+   Return true if the constraints are satisfiable, false otherwise.  */
+
+bool
+summarization::apply_constraints (region_model *outcome_model,
+				  region_model *dest_model)
+{
+  outcome_fact_visitor v (this, dest_model);
+  outcome_model->get_constraints ()->for_each_fact (&v);
+  return v.valid_p ();
+}
+
+/* FIXME.  */
+
+void
+summarization::apply_store (const store *outcome_store,
+			    region_model *dest_model)
+{
+  store *dest_store = dest_model->get_store ();
+
+#if 1
+  /* Did the called function call an unknown function?
+     If so, handle it.  */
+  if (outcome_store->called_unknown_fn_p ())
+    {
+      /* FIXME: this creates conjured values for the call without
+	 specifyin that it's the call within the summary, so there
+	 could be false reuse of values.  */
+      /* FIXME: need to mark things as escaped for this to work, I think.
+	 region_model::handle_unrecognized_call normally does this for
+	 the direct call case.  */
+      /* FIXME: I *think* that we can regions that escaped in the
+	 outcome_store and update the dest_store accordingly.  */
+      // FIXME: probably do this first
+      dest_store->m_called_unknown_fn = true; //on_unknown_fncall (call_stmt, get_store_manager ());
+      //gcc_unreachable (); // FIXME
+    }
+#endif
+
+  /* Sort for determinism.  */
+  auto_vec <const region *> outcome_base_regs;
+  for (store::cluster_map_t::iterator iter = outcome_store->begin ();
+       iter != outcome_store->end (); ++iter)
+    {
+      const region *reg = (*iter).first;
+      outcome_base_regs.safe_push (reg);
+    }
+  outcome_base_regs.qsort (region::cmp_ptr_ptr);
+
+  const region *outcome_base_reg;
+  unsigned i;
+  FOR_EACH_VEC_ELT (outcome_base_regs, i, outcome_base_reg)
+    {
+      /* Ignore bindings within the frame.  */
+      // FIXME: could be another frame
+      if (outcome_base_reg->maybe_get_frame_region ())
+	continue;
+
+      const binding_cluster *outcome_cluster
+	= outcome_store->get_cluster (outcome_base_reg);
+#if 0
+      if (outcome_cluster->escaped_p ())
+	{
+	  gcc_unreachable ();
+	}
+#endif
+#if 0
+      if (outcome_cluster->touched_p ())
+	{
+	  gcc_unreachable ();
+	}
+#endif
+
+      switch (outcome_base_reg->get_kind ())
+	{
+	default:
+	  gcc_unreachable ();
+	case RK_FRAME:
+	  {
+	    gcc_unreachable ();
+	  };
+	  break;
+	case RK_GLOBALS:
+	  {
+	    gcc_unreachable ();
+	  };
+	  break;
+	case RK_CODE:
+	  {
+	    gcc_unreachable ();
+	  };
+	  break;
+	case RK_FUNCTION:
+	  {
+	    gcc_unreachable ();
+	  };
+	  break;
+	case RK_LABEL:
+	  {
+	    gcc_unreachable ();
+	  };
+	  break;
+	case RK_STACK:
+	  {
+	    gcc_unreachable ();
+	  };
+	  break;
+	case RK_HEAP:
+	  {
+	    gcc_unreachable ();
+	  };
+	  break;
+	case RK_ROOT:
+	  {
+	    gcc_unreachable ();
+	  };
+	  break;
+	case RK_SYMBOLIC:
+	  {
+	    const symbolic_region *sym_reg
+	      = as_a <const symbolic_region *> (outcome_base_reg);
+	    const svalue *outcome_ptr = sym_reg->get_pointer ();
+	    const svalue *dest_ptr
+	      = map_svalue_from_callee (outcome_ptr, dest_model);
+
+	    apply_binding_map (outcome_cluster->get_map (),
+			       dest_ptr,
+			       dest_model);
+	  }
+	  break;
+	case RK_DECL:
+	  {
+	    /* Update globals.  */
+	    if (tree decl = outcome_base_reg->maybe_get_decl ())
+	      if (is_global_var (decl))
+		{
+		  binding_cluster *dest_cluster
+		    = dest_store->get_or_create_cluster (outcome_base_reg);
+		  dest_cluster->purge_region (get_store_manager (),
+					      outcome_base_reg);
+
+		  // FIXME: make order deterministic
+		  for (binding_cluster::iterator_t iter
+			 = outcome_cluster->begin ();
+		       iter != outcome_cluster->end ();
+		       ++iter)
+		    {
+		      const binding_key *key = (*iter).first;
+		      const svalue *outcome_sval = (*iter).second;
+		      const svalue *dest_sval
+			= map_svalue_from_callee (outcome_sval,
+						  dest_model);
+		      dest_cluster->bind_key (key, dest_sval);
+		    }
+		  continue;
+		}
+	    gcc_unreachable ();
+	  }
+	  break;
+	case RK_FIELD:
+	  {
+	    gcc_unreachable ();
+	  };
+	  break;
+	case RK_ELEMENT:
+	  {
+	    gcc_unreachable ();
+	  };
+	  break;
+	case RK_OFFSET:
+	  {
+	    gcc_unreachable ();
+	  };
+	  break;
+	case RK_CAST:
+	  {
+	    gcc_unreachable ();
+	  };
+	  break;
+	case RK_HEAP_ALLOCATED:
+	  {
+	    const region *dest_base_reg
+	      = map_region_from_callee (outcome_base_reg,
+					dest_model);
+	    gcc_assert (dest_base_reg->base_region_p ());
+	    // FIXME: what if this isn't true?
+
+	    binding_cluster *dest_cluster
+	      = dest_store->get_or_create_cluster (dest_base_reg);
+#if 0
+	    dest_cluster->purge_region (get_store_manager (),
+					dest_base_reg);
+#endif
+
+	    // FIXME: make order deterministic
+	    for (binding_cluster::iterator_t iter
+		   = outcome_cluster->begin ();
+		 iter != outcome_cluster->end ();
+		 ++iter)
+	      {
+		const binding_key *key = (*iter).first;
+		const svalue *outcome_sval = (*iter).second;
+		const svalue *dest_sval
+		  = map_svalue_from_callee (outcome_sval, dest_model);
+		dest_cluster->bind_key (key, dest_sval);
+	      }
+	    // FIXME: maybe combine with global var code above
+	  }
+	  break;
+	case RK_ALLOCA:
+	  {
+	    gcc_unreachable ();
+	  };
+	  break;
+	case RK_STRING:
+	  {
+	    gcc_unreachable ();
+	  };
+	  break;
+	case RK_UNKNOWN:
+	  {
+	    gcc_unreachable ();
+	  };
+	  break;
+	}
+
+      /* FIXME: what about m_escaped and m_touched?  */
+    }
+}
+
+/* FIXME.  */
+
+void
+summarization::apply_binding_map (const binding_map &outcome_map,
+				  const svalue *dest_ptr,
+				  region_model *dest_model)
+{
+  region_model_manager *mgr = get_manager ();
+  store *dest_store = dest_model->get_store ();
+
+  /* Make order deterministic.  */
+  auto_vec <const binding_key *> outcome_binding_keys;
+  for (binding_map::iterator_t iter = outcome_map.begin ();
+       iter != outcome_map.end ();
+       ++iter)
+    {
+      const binding_key *key = (*iter).first;
+      outcome_binding_keys.safe_push (key);
+    }
+  outcome_binding_keys.qsort (binding_key::cmp_ptrs);
+
+  const binding_key *key;
+  unsigned i;
+  FOR_EACH_VEC_ELT (outcome_binding_keys, i, key)
+    {
+      const svalue *outcome_sval = outcome_map.get (key);
+      const svalue *dest_sval
+	= map_svalue_from_callee (outcome_sval, dest_model);
+      if (const concrete_binding *concrete_key
+	  = key->dyn_cast_concrete_binding ())
+	{
+	  auto_vec <const region *> dest_subregions;
+	  const region *dest_reg
+	    = mgr->get_symbolic_region (dest_ptr);
+	  dest_reg->get_subregions_for_binding
+	    (mgr,
+	     concrete_key->get_start_bit_offset (),
+	     concrete_key->get_size_in_bits (),
+	     dest_sval->get_type (), // FIXME
+	     &dest_subregions);
+	  unsigned i;
+	  const region *dest_subreg;
+	  FOR_EACH_VEC_ELT (dest_subregions, i, dest_subreg)
+	    {
+#if 0
+	      dest_subreg->dump (true);
+	      dest_sval->dump (true);
+#endif
+	      dest_store->set_value (get_store_manager (),
+				     dest_subreg, dest_sval,
+				     key->get_kind ());
+	    }
+	}
+      else
+	{
+	  //gcc_unreachable (); // FIXME
+	}
+    }
+}
+
+/* FIXME: remap values e.g. for the formal params
+   back to the actual params.  */
+
+const svalue *
+summarization::map_svalue_from_callee (const svalue *callee_base_sval,
+				       region_model *dest_model)
+{
+  region_model_manager *mgr = get_manager ();
+  tree type = callee_base_sval->get_type ();
+  switch (callee_base_sval->get_kind ())
+    {
+    default:
+      gcc_unreachable ();
+    case SK_REGION:
+      {
+	const region_svalue *callee_sval
+	  = static_cast<const region_svalue *> (callee_base_sval);
+	const region *callee_reg = callee_sval->get_pointee ();
+	/* Poison ptrs to locals.  */
+	if (callee_reg->maybe_get_frame_region ())
+	  return mgr->get_or_create_poisoned_svalue
+	    (POISON_KIND_POPPED_STACK, type);
+	const region *caller_reg
+	  = map_region_from_callee (callee_reg, dest_model);
+	return mgr->get_ptr_svalue (type, caller_reg);
+      }
+      break;
+    case SK_CONSTANT:
+    case SK_UNKNOWN:
+      return callee_base_sval;
+    case SK_POISONED:
+      gcc_unreachable (); // FIXME
+      break;
+    case SK_SETJMP:
+      gcc_unreachable (); // FIXME
+      break;
+    case SK_INITIAL:
+      {
+	const initial_svalue *callee_sval
+	  = static_cast<const initial_svalue *> (callee_base_sval);
+	tree formal_param = callee_sval->initial_value_of_param_p ();
+	if (formal_param)
+	  {
+	    tree actual_param
+	      = m_cg_sedge->map_expr_from_callee_to_caller (formal_param, NULL);
+	    return dest_model->get_rvalue (actual_param, NULL);
+	    // FIXME: ctxt?
+	  }
+	else
+	  {
+	    /* An initial value of a region within an outcome means
+	       that value of that region at entry to the function.  */
+	    region_model *src_model = get_caller_state ().m_region_model;
+	    const region *outcome_reg = callee_sval->get_region ();
+	    const region *caller_reg
+	      = map_region_from_callee (outcome_reg, dest_model);
+	    return src_model->get_store_value (caller_reg);
+	    // FIXME: ctxt?
+	  }
+      }
+      break;
+    case SK_UNARYOP:
+      {
+	const unaryop_svalue *callee_sval
+	  = static_cast<const unaryop_svalue *> (callee_base_sval);
+	return mgr->get_or_create_unaryop
+	  (type,
+	   callee_sval->get_op (),
+	   map_svalue_from_callee (callee_sval->get_arg (), dest_model));
+      }
+    case SK_BINOP:
+      {
+	const binop_svalue *callee_sval
+	  = static_cast<const binop_svalue *> (callee_base_sval);
+	return mgr->get_or_create_binop
+	  (type,
+	   callee_sval->get_op (),
+	   map_svalue_from_callee (callee_sval->get_arg0 (), dest_model),
+	   map_svalue_from_callee (callee_sval->get_arg1 (), dest_model));
+      }
+    case SK_SUB:
+      gcc_unreachable (); // FIXME
+      break;
+    case SK_UNMERGEABLE:
+      gcc_unreachable (); // FIXME
+      break;
+    case SK_PLACEHOLDER:
+      gcc_unreachable (); // FIXME
+      break;
+    case SK_WIDENING:
+      gcc_unreachable (); // FIXME
+      break;
+    case SK_COMPOUND:
+      {
+	const compound_svalue *callee_sval
+	  = static_cast<const compound_svalue *> (callee_base_sval);
+	const binding_map &outcome_map = callee_sval->get_map ();
+	binding_map caller_map;
+	for (compound_svalue::iterator_t iter = outcome_map.begin ();
+	     iter != outcome_map.end ();
+	     ++iter)
+	  {
+	    const binding_key *outcome_key = (*iter).first;
+	    const svalue *outcome_sval = (*iter).second;
+
+	    const svalue *caller_sval
+	      = map_svalue_from_callee (outcome_sval, dest_model);
+
+	    caller_map.put (outcome_key, caller_sval);
+	    // FIXME: do we need to remap the outcome_key?
+	  }
+	return mgr->get_or_create_compound_svalue (type, caller_map);
+      }
+      break;
+    case SK_CONJURED:
+      return mgr->get_or_create_unknown_svalue (type);
+    }
+}
+
+/* FIXME.  */
+
+const region *
+summarization::map_region_from_callee (const region *callee_reg,
+				       region_model *dest_model)
+{
+  region_model_manager *mgr = get_manager ();
+  switch (callee_reg->get_kind ())
+    {
+    default:
+      gcc_unreachable (); // FIXME
+    case RK_FRAME:
+      {
+	gcc_unreachable (); // FIXME
+      }
+      break;
+    case RK_GLOBALS:
+    case RK_CODE:
+    case RK_FUNCTION:
+    case RK_LABEL:
+    case RK_STACK:
+    case RK_HEAP:
+    case RK_ROOT:
+      return callee_reg;
+
+    case RK_SYMBOLIC:
+      {
+	const symbolic_region *symbolic_reg
+	  = as_a <const symbolic_region *> (callee_reg);
+	const svalue *caller_sval
+	  = map_svalue_from_callee (symbolic_reg->get_pointer (), dest_model);
+	return mgr->get_symbolic_region (caller_sval);
+      }
+      break;
+    case RK_DECL:
+      {
+	// FIXME: what if it's not a global?
+	return callee_reg;
+      }
+      break;
+    case RK_FIELD:
+      {
+	const field_region *callee_field_reg
+	  = as_a <const field_region *> (callee_reg);
+	const region *caller_parent_reg
+	  = map_region_from_callee (callee_reg->get_parent_region (),
+				    dest_model);
+	return mgr->get_field_region (caller_parent_reg,
+				      callee_field_reg->get_field ());
+      }
+      break;
+    case RK_ELEMENT:
+      {
+	gcc_unreachable (); // FIXME
+      }
+      break;
+    case RK_OFFSET:
+      {
+	gcc_unreachable (); // FIXME
+      }
+      break;
+    case RK_CAST:
+      {
+	gcc_unreachable (); // FIXME
+      }
+      break;
+    case RK_HEAP_ALLOCATED:
+      {
+	// TODO: maintain a cached mapping of these
+	if (const region ** slot = m_region_map.get (callee_reg))
+	  return *slot;
+	const region *caller_reg = mgr->create_region_for_heap_alloc ();
+	/* FIXME: eventually will want to propagate stored
+	   from callee_region.  */
+	m_region_map.put (callee_reg, caller_reg);
+	return caller_reg;
+      }
+      break;
+    case RK_ALLOCA:
+      {
+	gcc_unreachable (); // FIXME
+      }
+      break;
+    case RK_STRING:
+    case RK_UNKNOWN:
+      return callee_reg;
+    }
+}
+
+// FIXME:
+/* class call_outcome.  */
+
+/* Concrete subclass of call_outcome representing a standard return from
+   the function being called.  */
+
+class call_outcome_return : public call_outcome
+{
+public:
+  call_outcome_return (const exploded_node *enode)
+  : m_enode (enode)
+  {
+  }
+
+  void dump_to_pp (pretty_printer *pp) const FINAL OVERRIDE
+  {
+    pp_printf (pp, "RETURN(EN:%i)", m_enode->m_index);
+  }
+
+  void
+  maybe_extend_egraph (summarization *cs) const FINAL OVERRIDE;
+
+private:
+  tree get_callee_fndecl () const
+  {
+    return m_enode->get_function ()->decl;
+  }
+
+  /* FIXME: remap values e.g. for the formal params
+     back to the actual params.  */
+#if 0
+  binding_cluster *
+  map_binding_cluster_from_callee (const binding_cluster *callee_cluster,
+				   const callgraph_superedge *cg_sedge,
+				   region_model *dest_model) const
+  {
+    gcc_unreachable (); // TODO
+  }
+#endif
+
+  const exploded_node *m_enode;
+};
+
+/* Concrete subclass of call_outcome representing a longjmp from
+   within the function being called.  */
+
+class call_outcome_longjmp : public call_outcome
+{
+public:
+  call_outcome_longjmp (const program_state &state,
+			const gcall *longjmp_call,
+			const svalue *buf_content_sval)
+  : m_state_at_longjmp (state),
+    m_longjmp_call (longjmp_call),
+    m_buf_content_sval (buf_content_sval)
+  {
+  }
+
+  void dump_to_pp (pretty_printer *pp) const FINAL OVERRIDE
+  {
+    pp_string (pp, "LONGJMP(");
+    m_buf_content_sval->dump_to_pp (pp, true);
+    pp_string (pp, ")");
+  }
+
+  void
+  maybe_extend_egraph (summarization *cs) const FINAL OVERRIDE;
+
+private:
+  program_state m_state_at_longjmp;
+  const gcall *m_longjmp_call;
+  const svalue *m_buf_content_sval;
+};
+
+/* class call_outcome_return : public call_outcome.  */
+
+void
+call_outcome_return:: maybe_extend_egraph (summarization *cs) const
+{
+  logger *logger = cs->get_logger ();
+  LOG_SCOPE (logger);
+  if (logger)
+    logger->log ("considering return outcome (EN: %i)", m_enode->m_index);
+
+  const gcall *call_stmt = cs->get_call_stmt ();
+  const call_string &caller_call_string = cs->get_caller_call_string ();
+  program_point dest_point
+    = program_point::before_supernode (cs->get_superedge ()->m_dest,
+				       cs->get_superedge (),
+				       caller_call_string);
+
+  program_state dest_state (cs->get_caller_state ());
+
+  region_model *dest_model = dest_state.m_region_model;
+
+  const program_state &outcome_state = m_enode->get_state ();
+  region_model *outcome_model = m_enode->get_state ().m_region_model;
+  const store *outcome_store = outcome_model->get_store ();
+
+  // TODO: is the sm-state correct?
+
+  /* Check/add constraints.  */
+  if (!cs->apply_constraints (outcome_model, dest_model))
+    {
+      if (logger)
+	logger->log ("bad constraints;"
+		     " not creating enode for outcome EN: %i",
+		     m_enode->m_index);
+      return;
+    }
+
+  /* Apply store (e.g. updates to global variables).  */
+  cs->apply_store (outcome_store, dest_model);
+
+  // TODO: apply sm-state
+  {
+    unsigned i;
+    sm_state_map *outcome_smap;
+    FOR_EACH_VEC_ELT (outcome_state.m_checker_states, i, outcome_smap)
+      {
+	if (!outcome_smap->is_empty_p ())
+	  {
+	    const state_machine &sm = cs->get_ext_state ().get_sm (i);
+
+	    sm_state_map *dest_smap = dest_state.m_checker_states[i];
+
+#if 1
+	    if (logger)
+	      {
+		pretty_printer *pp = logger->get_printer ();
+		pp_printf (pp, "%s: ", cs->get_ext_state ().get_name (i));
+		outcome_smap->print (outcome_model, false, false, pp);
+		pp_newline (pp);
+		pp_flush (pp);
+	      }
+#endif
+	    if (outcome_smap->get_global_state () != sm.get_start_state ())
+	      {
+		gcc_unreachable (); // FIXME
+	      }
+
+	    // FIXME: nondeterministic order
+	    for (sm_state_map::iterator_t iter = outcome_smap->begin ();
+		 iter != outcome_smap->end ();
+		 ++iter)
+	      {
+		const svalue *outcome_sval = (*iter).first;
+		const sm_state_map::entry_t &outcome_entry = (*iter).second;
+
+		const svalue *caller_sval
+		  = cs->map_svalue_from_callee (outcome_sval, dest_model);
+
+		const svalue *outcome_origin = outcome_entry.m_origin;
+		const svalue *caller_origin = NULL;
+		if (outcome_origin)
+		  caller_origin = cs->map_svalue_from_callee (outcome_origin,
+							      dest_model);
+
+		dest_smap->set_state (dest_model,
+				      caller_sval,
+				      outcome_entry.m_state,
+				      caller_origin,
+				      cs->get_ext_state ());
+	      }
+	  }
+      }
+  }
+
+  /* Update for the return value;
+     compare with region_mode::pop_frame.  */
+  tree lhs = gimple_call_lhs (call_stmt);
+  if (lhs)
+    {
+
+      /* Get the result from the outcome model's callee frame.  */
+      tree callee_fndecl = get_callee_fndecl ();
+      tree callee_result = DECL_RESULT (callee_fndecl);
+      if (TREE_TYPE (callee_result) != void_type_node)
+	{
+	  region_model_context *ctxt = NULL; // FIXME
+	  const region *result_dst_reg
+	    = dest_model->get_lvalue (lhs, ctxt); // FIXME
+	  const svalue *callee_result_sval
+	    = outcome_model->get_rvalue (callee_result, ctxt);
+
+	  /* Map from callee back to caller.  */
+	  const svalue *caller_result_sval
+	    = cs->map_svalue_from_callee (callee_result_sval, dest_model);
+
+	  /* Store the result into the new state's LHS.  */
+	  dest_model->set_value (result_dst_reg,
+				 caller_result_sval, ctxt);
+	}
+    }
+
+  // TODO: plenty of other state to update
+  /* FIXME: should we walk a path through the function and reply
+     all the state activity.
+     If we do, what does the summary buy us?
+     Fewer enodes, I suppose.
+     But doesn't save much computation.
+     But keeps the implementation consistent.
+     Maybe we want a many-to-1 map from enodes to outcomes?
+     Doing so would give a saving, as we could ignore the outcomes
+     which lead to the same enode after state is consolidated.
+     Also: we want to beat the exponential growth by saving work;
+     if we're simply rerunning stuff, that's...
+  */
+
+  cs->add_enode_and_eedge (dest_point, dest_state, this);
+}
+
+/* class call_outcome_longjmp : public call_outcome.  */
+
+void
+call_outcome_longjmp::maybe_extend_egraph (summarization *cs) const
+{
+#if 0
+  const gcall *call_stmt = get_call_stmt (src_enode);
+  const call_string &caller_call_string
+    = src_enode->get_point ().get_call_string ();
+  program_point dest_point
+    = program_point::before_supernode (cg_sedge->m_dest, cg_sedge,
+				       caller_call_string);
+
+#endif
+
+  program_state dest_state (cs->get_caller_state ()); // FIXME: update this
+#if 0
+  dest_state.dump (cs->get_ext_state (), true); // FIXME
+  m_state_at_longjmp.dump (cs->get_ext_state (), true); // FIXME
+#endif
+
+  const svalue *actual_buf_content
+    = cs->map_svalue_from_callee (m_buf_content_sval,
+				  dest_state.m_region_model);
+#if 0
+  actual_buf_content->dump (true);
+#endif
+
+  /* TODO:
+     constraints
+     sm-state
+     unknown-fns
+     where to return to
+     or is this itself a longjmp summary? (nested)
+     etc
+  */
+
+  if (const setjmp_svalue *setjmp_sval
+	= actual_buf_content->dyn_cast_setjmp_svalue ())
+    {
+      const setjmp_record tmp_setjmp_record = setjmp_sval->get_setjmp_record ();
+      rewind_info_t rewind_info (tmp_setjmp_record, m_longjmp_call);
+      const program_point &setjmp_point = rewind_info.get_setjmp_point ();
+
+      program_point next_point
+	= program_point::after_supernode (setjmp_point.get_supernode (),
+					  setjmp_point.get_call_string ());
+
+      /* TODO: need to update state, e.g. return value from
+	 setjmp based on param to longjmp.  */
+
+      // FIXME: compare with exploded_node::on_longjmp
+      // FIXME: we ought to warn for various things etc
+      // FIXME: refactor exploded_node::on_longjmp and share code with it
+      cs->add_enode_and_eedge (next_point, dest_state, this);
+      /* FIXME: this makes an edge info for the outcome, but should it use
+	 rewind_info_t instead?  */
+    }
+  else
+    {
+      gcc_unreachable (); // TODO
+    }
+
+#if 0
+  exploded_node *dest_enode = eg->get_or_create_node (dest_point, dest_state,
+						      src_enode);
+  return dest_enode;
+#endif
+}
+
+/* External interface for creating call_outcome subclass instances.  */
+
+/* FIXME.  */
+
+call_outcome *
+make_call_outcome_return (const exploded_node *enode)
+{
+  return new call_outcome_return (enode);
+}
+
+/* FIXME.  */
+
+call_outcome *
+make_call_outcome_longjmp (const program_state &state,
+			   const gcall *longjmp_call,
+			   const svalue *buf_content_sval)
+{
+  return new call_outcome_longjmp (state, longjmp_call, buf_content_sval);
+}
+
+} // namespace ana
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/call-summary.h b/gcc/analyzer/call-summary.h
new file mode 100644
index 00000000000..3198248386d
--- /dev/null
+++ b/gcc/analyzer/call-summary.h
@@ -0,0 +1,131 @@
+/* Classes for summarizing the effects of function calls.
+   Copyright (C) 2021 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_CALL_SUMMARY_H
+#define GCC_ANALYZER_CALL_SUMMARY_H
+
+namespace ana {
+
+/*  Class for use when evaluating the result of replaying a call_outcome at
+    a specific call site.  */
+
+class summarization
+{
+public:
+  summarization (exploded_graph *eg,
+		 exploded_node *src_enode,
+		 const callgraph_superedge *cg_sedge)
+  : m_eg (eg),
+    m_src_enode (src_enode),
+    m_cg_sedge (cg_sedge)
+  {
+  }
+
+  void dump_to_pp (pretty_printer *pp, bool simple) const;
+  void dump (bool simple) const;
+
+  // TODO:
+  void add_enode_and_eedge (const program_point &dest_point,
+		      const program_state &dest_state,
+		      const call_outcome *outcome) const;
+
+  logger *get_logger () const { return m_eg->get_logger (); }
+  const extrinsic_state &get_ext_state () const
+  {
+    return m_eg->get_ext_state ();
+  }
+
+  const gcall *get_call_stmt () const;
+  const call_string &get_caller_call_string () const;
+  const program_state &get_caller_state () const;
+
+  region_model_manager *get_manager () const
+  {
+    return m_src_enode->get_state ().m_region_model->get_manager ();
+  }
+
+  store_manager *get_store_manager () const
+  {
+    return get_manager ()->get_store_manager ();
+  }
+
+  const callgraph_superedge *get_superedge () const { return m_cg_sedge; }
+
+  bool apply_constraints (region_model *outcome_model,
+			  region_model *dest_model);
+
+  void apply_store (const store *outcome_store,
+		    region_model *dest_model);
+
+  void apply_binding_map (const binding_map &outcome_map,
+			  const svalue *dest_ptr,
+			  region_model *dest_model);
+
+  const svalue *
+  map_svalue_from_callee (const svalue *callee_sval,
+			  region_model *dest_model);
+
+  const region *
+  map_region_from_callee (const region *callee_region,
+			  region_model *dest_model);
+
+private:
+  exploded_graph *m_eg;
+  exploded_node *m_src_enode;
+  const callgraph_superedge *m_cg_sedge;
+
+  typedef ordered_hash_map <const region *, const region *> region_map_t;
+  region_map_t m_region_map;
+};
+
+/* Abstract base class for summarizing one possible outcome of a
+   function call.  */
+
+class call_outcome
+{
+public:
+  virtual ~call_outcome () {}
+  virtual void dump_to_pp (pretty_printer *pp) const = 0;
+
+  /* FIXME.  */
+  virtual void
+  maybe_extend_egraph (summarization *cs) const = 0;
+};
+
+extern call_outcome *make_call_outcome_return (const exploded_node *enode);
+extern call_outcome *make_call_outcome_longjmp (const program_state &state,
+						const gcall *longjmp_call,
+						const svalue *buf_content_sval);
+
+/* FIXME.  */
+
+struct call_summary
+{
+  void add_call_outcome (call_outcome *outcome)
+  {
+    m_outcomes.safe_push (outcome);
+  }
+
+  auto_delete_vec<call_outcome> m_outcomes;
+};
+
+} // namespace ana
+
+#endif /* GCC_ANALYZER_CALL_SUMMARY_H */
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index 51aa110ed1f..13fe1817ee2 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -62,6 +62,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "analyzer/checker-path.h"
 #include "analyzer/state-purge.h"
 #include "analyzer/bar-chart.h"
+#include "analyzer/call-summary.h"
 #include <zlib.h>
 #include "plugin.h"
 
@@ -1423,7 +1424,20 @@ exploded_node::on_longjmp (exploded_graph &eg,
   const setjmp_svalue *setjmp_sval
     = buf_content_sval->dyn_cast_setjmp_svalue ();
   if (!setjmp_sval)
-    return;
+    {
+      // TODO: handle creating a call summary for the abnormal "return"
+      if (flag_analyzer_call_summaries
+	  && get_point ().get_call_string ().empty_p ())
+	{
+	  call_summary *summary
+	    = eg.get_or_create_call_summary (get_function ());
+	  call_outcome *outcome
+	    = make_call_outcome_longjmp (*new_state, longjmp_call,
+					 buf_content_sval);
+	  summary->add_call_outcome (outcome);
+	}
+      return;
+    }
 
   const setjmp_record tmp_setjmp_record = setjmp_sval->get_setjmp_record ();
 
@@ -1700,10 +1714,10 @@ exploded_edge::dump_dot_label (pretty_printer *pp) const
 	      " headlabel=\""),
 	     style, color, weight, constraint);
 
-  if (m_sedge)
-    m_sedge->dump_label_to_pp (pp, false);
-  else if (m_custom_info)
+  if (m_custom_info)
     m_custom_info->print (pp);
+  else if (m_sedge)
+    m_sedge->dump_label_to_pp (pp, false);
 
   //pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/false);
 
@@ -2345,7 +2359,15 @@ exploded_graph::get_or_create_node (const program_point &point,
   per_cs_stats->m_num_nodes[node_pk]++;
 
   if (node_pk == PK_AFTER_SUPERNODE)
-    m_PK_AFTER_SUPERNODE_per_snode[point.get_supernode ()->m_index]++;
+    {
+      m_PK_AFTER_SUPERNODE_per_snode[point.get_supernode ()->m_index]++;
+#if 0
+      const supernode *snode = point.get_supernode ();
+      if (snode->return_p ()
+	  && point.get_call_string ().empty_p ())
+	add_return_outcome (node);
+#endif
+    }
 
   if (logger)
     {
@@ -2385,6 +2407,44 @@ exploded_graph::add_edge (exploded_node *src, exploded_node *dest,
   return e;
 }
 
+/* Concrete subclass of call_outcome representing a function call
+   that does not return (exit/abort/etc), and thus leaks do not need
+   to be reported.
+   FIXME: but does this actually turn into an outcome?
+   I think we instead simply have the path terminate.  */
+
+class call_outcome_exit : public call_outcome
+{
+public:
+  void dump_to_pp (pretty_printer *pp) const FINAL OVERRIDE;
+};
+
+/* FIXME.  */
+
+void
+exploded_graph::handle_call_summary (exploded_node *src,
+				     const superedge *sedge)
+{
+  LOG_SCOPE (get_logger ());
+
+  /* Locate function_info for callee.  */
+  const callgraph_superedge *cg_sedge = sedge->dyn_cast_callgraph_superedge ();
+  gcc_assert (cg_sedge);
+  cgraph_node *callee = cg_sedge->m_cedge->callee;
+
+  call_summary *callee_data
+    = get_or_create_call_summary (callee->get_fun ());
+
+  unsigned i;
+  call_outcome *outcome;
+  FOR_EACH_VEC_ELT (callee_data->m_outcomes, i, outcome)
+    {
+      // TODO: log which outcome
+      summarization cs (this, src, cg_sedge);
+      outcome->maybe_extend_egraph (&cs);
+    }
+}
+
 /* Ensure that this graph has per-program_point-data for POINT;
    borrow a pointer to it.  */
 
@@ -2428,28 +2488,28 @@ exploded_graph::get_or_create_per_call_string_data (const call_string &cs)
   return data;
 }
 
-/* Ensure that this graph has per-function-data for FUN;
+/* Ensure that this graph has per-function-summary for FUN;
    borrow a pointer to it.  */
 
-per_function_data *
-exploded_graph::get_or_create_per_function_data (function *fun)
+call_summary *
+exploded_graph::get_or_create_call_summary (function *fun)
 {
-  if (per_function_data **slot = m_per_function_data.get (fun))
+  if (call_summary **slot = m_call_summaries.get (fun))
     return *slot;
 
-  per_function_data *data = new per_function_data ();
-  m_per_function_data.put (fun, data);
+  call_summary *data = new call_summary ();
+  m_call_summaries.put (fun, data);
   return data;
 }
 
-/* Get this graph's per-function-data for FUN if there is any,
+/* Get this graph's call_summary for FUN if there is any,
    otherwise NULL.  */
 
-per_function_data *
-exploded_graph::get_per_function_data (function *fun) const
+call_summary *
+exploded_graph::get_call_summary (function *fun) const
 {
-  if (per_function_data **slot
-        = const_cast <per_function_data_t &> (m_per_function_data).get (fun))
+  if (call_summary **slot
+	= const_cast <call_summary_t &> (m_call_summaries).get (fun))
     return *slot;
 
   return NULL;
@@ -3141,14 +3201,14 @@ exploded_graph::process_node (exploded_node *node)
 		    pretty_printer *pp = logger->get_printer ();
 		    logger->start_log_line ();
 		    logger->log_partial
-		      ("would create function summary for %qE; state: ",
+		      ("creating function summary for %qE; state: ",
 		       point.get_fndecl ());
 		    state.dump_to_pp (m_ext_state, true, false, pp);
 		    logger->end_log_line ();
 		  }
-		per_function_data *per_fn_data
-		  = get_or_create_per_function_data (point.get_function ());
-		per_fn_data->add_call_summary (node);
+		call_summary *summary
+		  = get_or_create_call_summary (point.get_function ());
+		summary->add_call_outcome (make_call_outcome_return (node));
 	      }
 	  }
 	/* Traverse into successors of the supernode.  */
@@ -3652,7 +3712,9 @@ feasibility_state::maybe_update_for_edge (logger *logger,
     }
 
   const superedge *sedge = eedge->m_sedge;
-  if (sedge)
+  if (eedge->m_custom_info)
+    eedge->m_custom_info->update_model (&m_model, *eedge);
+  else if (sedge)
     {
       if (logger)
 	logger->log ("  sedge: SN:%i -> SN:%i %s",
@@ -3686,10 +3748,6 @@ feasibility_state::maybe_update_for_edge (logger *logger,
 	  if (logger)
 	    logger->log ("  pushing frame for %qD", fun->decl);
 	}
-      else if (eedge->m_custom_info)
-	{
-	  eedge->m_custom_info->update_model (&m_model, *eedge);
-	}
     }
 
   /* Handle phi nodes on an edge leaving a PK_BEFORE_SUPERNODE (to
@@ -4320,8 +4378,9 @@ class viz_callgraph_node : public dnode<viz_callgraph_traits>
   friend class viz_callgraph;
 
 public:
-  viz_callgraph_node (function *fun, int index)
-  : m_fun (fun), m_index (index), m_num_supernodes (0), m_num_superedges (0)
+  viz_callgraph_node (function *fun, const function_info *info, int index)
+  : m_fun (fun), m_info (info), m_index (index),
+    m_num_supernodes (0), m_num_superedges (0)
   {
     gcc_assert (fun);
   }
@@ -4394,14 +4453,25 @@ public:
 	  }
 
 	/* Show any summaries.  */
-	per_function_data *data = args.m_eg->get_per_function_data (m_fun);
-	if (data)
+	call_summary *summary = args.m_eg->get_call_summary (m_fun);
+	if (summary)
 	  {
 	    pp_newline (pp);
 	    gv->begin_trtd ();
-	    pp_printf (pp, "summaries: %i\n", data->m_summaries.length ());
+	    pp_printf (pp, "outcomes: %i\n", summary->m_outcomes.length ());
 	    pp_write_text_as_html_like_dot_to_stream (pp);
 	    gv->end_tdtr ();
+
+	    unsigned i;
+	    call_outcome *outcome;
+	    FOR_EACH_VEC_ELT (summary->m_outcomes, i, outcome)
+	      {
+		gv->begin_trtd ();
+		pp_printf (pp, "outcome: %i\n", i);
+		outcome->dump_to_pp (pp);
+		pp_write_text_as_html_like_dot_to_stream (pp);
+		gv->end_tdtr ();
+	      }
 	  }
       }
 
@@ -4416,6 +4486,7 @@ public:
 
 private:
   function *m_fun;
+  const function_info *m_info;
   int m_index;
   int m_num_supernodes;
   int m_num_superedges;
@@ -4456,7 +4527,7 @@ public:
 class viz_callgraph : public digraph<viz_callgraph_traits>
 {
 public:
-  viz_callgraph (const supergraph &sg);
+  viz_callgraph (const supergraph &sg, const analysis_plan *plan);
 
   viz_callgraph_node *get_vcg_node_for_function (function *fun)
   {
@@ -4480,14 +4551,17 @@ class viz_callgraph_cluster : public cluster<viz_callgraph_traits>
 
 /* viz_callgraph's ctor.  */
 
-viz_callgraph::viz_callgraph (const supergraph &sg)
+viz_callgraph::viz_callgraph (const supergraph &sg, const analysis_plan *plan)
 {
   cgraph_node *node;
   FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
     {
       function *fun = node->get_fun ();
+      function_info *info = NULL;
+      if (plan)
+	info = plan->get_function_info (fun);
       viz_callgraph_node *vcg_node
-	= new viz_callgraph_node (fun, m_nodes.length ());
+	= new viz_callgraph_node (fun, info, m_nodes.length ());
       m_map.put (fun, vcg_node);
       add_node (vcg_node);
     }
@@ -4527,7 +4601,7 @@ dump_callgraph (const supergraph &sg, const char *filename,
     return;
 
   // TODO
-  viz_callgraph vcg (sg);
+  viz_callgraph vcg (sg, &eg->get_analysis_plan ());
   vcg.dump_dot (filename, NULL, viz_callgraph_traits::dump_args_t (eg));
 
   fclose (outf);
@@ -4907,6 +4981,9 @@ impl_run_checkers (logger *logger)
 
   eg.log_stats ();
 
+  if (logger)
+    plan.dump_to_pp (logger->get_printer ());
+
   if (flag_dump_analyzer_callgraph)
     dump_callgraph (sg, &eg);
 
diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h
index 7df12a5cedf..4a12768c5f4 100644
--- a/gcc/analyzer/exploded-graph.h
+++ b/gcc/analyzer/exploded-graph.h
@@ -599,21 +599,6 @@ struct eg_call_string_hash_map_traits
   static const bool empty_zero_p = false;
 };
 
-/* Data about a particular function within an exploded_graph.  */
-
-struct per_function_data
-{
-  per_function_data () {}
-
-  void add_call_summary (exploded_node *node)
-  {
-    m_summaries.safe_push (node);
-  }
-
-  auto_vec<exploded_node *> m_summaries;
-};
-
-
 /* The strongly connected components of a supergraph.
    In particular, this allows us to compute a partial ordering
    of supernodes.  */
@@ -767,6 +752,9 @@ public:
 			   const superedge *sedge,
 			   exploded_edge::custom_info_t *custom = NULL);
 
+  void handle_call_summary (exploded_node *src,
+			    const superedge *sedge);
+
   per_program_point_data *
   get_or_create_per_program_point_data (const program_point &);
   per_program_point_data *
@@ -775,9 +763,9 @@ public:
   per_call_string_data *
   get_or_create_per_call_string_data (const call_string &);
 
-  per_function_data *
-  get_or_create_per_function_data (function *);
-  per_function_data *get_per_function_data (function *) const;
+  call_summary *
+  get_or_create_call_summary (function *);
+  call_summary *get_call_summary (function *) const;
 
   void save_diagnostic (const state_machine &sm,
 			const exploded_node *enode,
@@ -848,8 +836,8 @@ private:
 
   const analysis_plan &m_plan;
 
-  typedef hash_map<function *, per_function_data *> per_function_data_t;
-  per_function_data_t m_per_function_data;
+  typedef hash_map<function *, call_summary *> call_summary_t;
+  call_summary_t m_call_summaries;
 
   diagnostic_manager m_diagnostic_manager;
 
diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc
index 357e55a47ad..70e917e2103 100644
--- a/gcc/analyzer/program-state.cc
+++ b/gcc/analyzer/program-state.cc
@@ -936,6 +936,22 @@ program_state::on_edge (exploded_graph &eg,
   const program_point &point = enode.get_point ();
   const gimple *last_stmt = point.get_supernode ()->get_last_stmt ();
 
+  /* Interprocedural edges are a special case.  We've already
+     decided we're following this one in program_point::on_edge,
+     by using a call summary.
+     However, there can be multiple outcomes for such a summary,
+     so we want to potentially create multiple out-edges here,
+     and return false (to not create a node based on updating this
+     program_state in-place).  */
+  if (succ->get_kind () == SUPEREDGE_INTRAPROCEDURAL_CALL)
+    {
+      eg.handle_call_summary (const_cast <exploded_node *>(&enode), succ);
+      // FIXME: cast!
+
+      /* Don't create a node based on this program_state.  */
+      return false;
+    }
+
   /* For conditionals and switch statements, add the
      relevant conditions (for the specific edge) to new_state;
      skip edges for which the resulting constraints
diff --git a/gcc/analyzer/region-model-manager.cc b/gcc/analyzer/region-model-manager.cc
index dfd2413e914..2de1c46023a 100644
--- a/gcc/analyzer/region-model-manager.cc
+++ b/gcc/analyzer/region-model-manager.cc
@@ -939,6 +939,15 @@ region_model_manager::get_frame_region (const frame_region *calling_frame,
 const region *
 region_model_manager::get_symbolic_region (const svalue *sval)
 {
+  /* Fold *(&REG) to REG.  */
+  if (const region_svalue *region_sval = sval->dyn_cast_region_svalue ())
+    {
+      const region *reg = region_sval->get_pointee ();
+      if (reg->get_type ()
+	  && TYPE_POINTER_TO (reg->get_type ()) == sval->get_type ())
+	return reg;
+    }
+
   symbolic_region::key_t key (&m_root_region, sval);
   if (symbolic_region *reg = m_symbolic_regions.get (key))
     return reg;
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index 96ed549adf6..5848319a403 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -2569,11 +2569,9 @@ region_model::maybe_update_for_edge (const superedge &edge,
       break;
 
     case SUPEREDGE_INTRAPROCEDURAL_CALL:
-      {
-	const callgraph_superedge *cg_sedge
-	  = as_a <const callgraph_superedge *> (&edge);
-	update_for_call_summary (*cg_sedge, ctxt);
-      }
+      /* This should never be called for such edges; they should instead
+	 be handled by FIXME.  */
+      gcc_unreachable ();
       break;
     }
 
@@ -2651,6 +2649,7 @@ region_model::update_for_return_superedge (const return_superedge &return_edge,
   pop_frame (result_dst_reg, NULL, ctxt);
 }
 
+#if 0
 /* Update this region_model with a summary of the effect of calling
    and returning from CG_SEDGE.
 
@@ -2670,6 +2669,7 @@ region_model::update_for_call_summary (const callgraph_superedge &cg_sedge,
 
   // TODO: actually implement some kind of summary here
 }
+#endif
 
 /* Given a true or false edge guarded by conditional statement COND_STMT,
    determine appropriate constraints for the edge to be taken.
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index 54977f947ee..8e7b36a6d08 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -611,8 +611,6 @@ class region_model
 				  region_model_context *ctxt);
   void update_for_return_superedge (const return_superedge &return_edge,
 				    region_model_context *ctxt);
-  void update_for_call_summary (const callgraph_superedge &cg_sedge,
-				region_model_context *ctxt);
   bool apply_constraints_for_gcond (const cfg_superedge &edge,
 				    const gcond *cond_stmt,
 				    region_model_context *ctxt,
diff --git a/gcc/analyzer/store.h b/gcc/analyzer/store.h
index 2bcef6c398a..d3e00008da1 100644
--- a/gcc/analyzer/store.h
+++ b/gcc/analyzer/store.h
@@ -481,7 +481,9 @@ private:
   void bind_compound_sval (store_manager *mgr,
 			   const region *reg,
 			   const compound_svalue *compound_sval);
+public: // FIXME
   void bind_key (const binding_key *key, const svalue *sval);
+private: // FIXME
 
   const region *m_base_region;
 
@@ -603,7 +605,9 @@ private:
      This is to avoid having to mark (and thus explicitly track)
      every global when an unknown function is called; instead, they
      can be tracked implicitly.  */
+public: // FIXME
   bool m_called_unknown_fn;
+private: // FIXME
 };
 
 /* A class responsible for owning and consolidating binding keys
diff --git a/gcc/analyzer/svalue.cc b/gcc/analyzer/svalue.cc
index d6305a37b9a..dbb33042ec3 100644
--- a/gcc/analyzer/svalue.cc
+++ b/gcc/analyzer/svalue.cc
@@ -779,9 +779,10 @@ initial_svalue::implicitly_live_p (const svalue_set *,
   return false;
 }
 
+// FIXME
 /* Return true if this is the initial value of a function parameter.  */
 
-bool
+tree
 initial_svalue::initial_value_of_param_p () const
 {
   if (tree reg_decl = m_reg->maybe_get_decl ())
@@ -791,9 +792,9 @@ initial_svalue::initial_value_of_param_p () const
 	if (SSA_NAME_IS_DEFAULT_DEF (ssa_name)
 	    && SSA_NAME_VAR (ssa_name)
 	    && TREE_CODE (SSA_NAME_VAR (ssa_name)) == PARM_DECL)
-	  return true;
+	  return SSA_NAME_VAR (ssa_name);
       }
-  return false;
+  return NULL_TREE;
 }
 
 /* class unaryop_svalue : public svalue.  */
diff --git a/gcc/analyzer/svalue.h b/gcc/analyzer/svalue.h
index 672a89cccff..60d14c13efb 100644
--- a/gcc/analyzer/svalue.h
+++ b/gcc/analyzer/svalue.h
@@ -496,7 +496,7 @@ public:
   bool implicitly_live_p (const svalue_set *,
 			  const region_model *) const FINAL OVERRIDE;
 
-  bool initial_value_of_param_p () const;
+  tree initial_value_of_param_p () const;
 
   const region *get_region () const { return m_reg; }
 
diff --git a/gcc/cgraph.h b/gcc/cgraph.h
index 4a1f89920f5..156c848862c 100644
--- a/gcc/cgraph.h
+++ b/gcc/cgraph.h
@@ -1282,7 +1282,7 @@ struct GTY((tag ("SYMTAB_FUNCTION"))) cgraph_node : public symtab_node
   }
 
   /* Get unique identifier of the node.  */
-  inline int get_uid ()
+  inline int get_uid () const
   {
     return m_uid;
   }
diff --git a/gcc/testsuite/gcc.dg/analyzer/call-summaries-1.c b/gcc/testsuite/gcc.dg/analyzer/call-summaries-1.c
deleted file mode 100644
index 3cc25cf19c5..00000000000
--- a/gcc/testsuite/gcc.dg/analyzer/call-summaries-1.c
+++ /dev/null
@@ -1,14 +0,0 @@
-/* { dg-additional-options "-fanalyzer-call-summaries" } */
-
-#include "analyzer-std-decls.h"
-
-void calls_free (void *p)
-{
-  free (p); /* { dg-warning "double-'free' of 'p'" } */
-}
-
-void test (void *q)
-{
-  calls_free (q);
-  calls_free (q);
-}
diff --git a/gcc/testsuite/gcc.dg/analyzer/call-summaries-aliasing-1.c b/gcc/testsuite/gcc.dg/analyzer/call-summaries-aliasing-1.c
new file mode 100644
index 00000000000..06cc37c9fda
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/call-summaries-aliasing-1.c
@@ -0,0 +1,42 @@
+/* { dg-additional-options "-fanalyzer-call-summaries" } */
+
+#include "analyzer-decls.h"
+
+void __attribute__((noinline))
+called_by_test_1 (int *p, int *q, int i, int j)
+{
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+
+  *p = i;
+  *q = j;
+}
+
+void test_1 (void)
+{
+  int a, b;
+
+  called_by_test_1 (&a, &a, 0, 1);
+  __analyzer_eval (a == 1); /* { dg-warning "TRUE" } */
+
+  called_by_test_1 (&a, &b, 2, 3);
+  __analyzer_eval (a == 2); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */
+
+  __analyzer_eval (b == 3); /* { dg-warning "TRUE" } */
+
+  called_by_test_1 (&b, &b, 4, 5);
+  __analyzer_eval (b == 5); /* { dg-warning "TRUE" } */
+
+  called_by_test_1 (&b, &a, 6, 7);
+  __analyzer_eval (b == 6); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */
+  __analyzer_eval (a == 7); /* { dg-warning "TRUE" } */
+
+  /* FIXME: fix the xfails above.  */
+  /* FIXME: they may also depend on hash_map ordering,
+     depending on the order in which we unpack the outcome clusters into
+     the destination state.
+     FIXME: yes, it's nondeterministic.  */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/call-summaries-alloca-1.c b/gcc/testsuite/gcc.dg/analyzer/call-summaries-alloca-1.c
new file mode 100644
index 00000000000..f4086ab462f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/call-summaries-alloca-1.c
@@ -0,0 +1,18 @@
+/* { dg-additional-options "-fanalyzer-call-summaries" } */
+
+#include <stdlib.h>
+#include "analyzer-decls.h"
+
+void __attribute__((noinline))
+called_by_test_1 (int **p)
+{
+  *p = alloca (1024);
+  /* FIXME: we should show where the pointer comes from.  */
+}
+
+void test_1 (void)
+{
+  int *q;
+  called_by_test_1 (&q);
+  q[0] = 42; /* { dg-warning "dereferencing pointer 'q' to within stale stack frame" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/call-summaries-data-model-1.c b/gcc/testsuite/gcc.dg/analyzer/call-summaries-data-model-1.c
new file mode 100644
index 00000000000..3f7eb9971f6
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/call-summaries-data-model-1.c
@@ -0,0 +1,80 @@
+/* Verify that call summaries update the store.  */
+
+/* { dg-additional-options "-fanalyzer-call-summaries" } */
+
+#include "analyzer-decls.h"
+
+int g;
+
+int __attribute__((noinline))
+sets_global (int a)
+{
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+
+  int old_g = g;
+  g = a;
+  return old_g;
+}
+
+void test_1 (void)
+{
+  g = 0;
+
+  //__analyzer_dump ();
+
+  int v0 = sets_global (1);
+  //__analyzer_dump ();
+  __analyzer_eval (v0 == 0); /* { dg-warning "TRUE" } */
+  __analyzer_eval (g == 1); /* { dg-warning "TRUE" } */
+
+  //__analyzer_dump ();
+
+  int v1 = sets_global (2);
+
+  //__analyzer_dump ();
+
+  __analyzer_eval (v1 == 1); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (g == 2); /* { dg-warning "TRUE" } */
+
+  //__analyzer_dump ();
+}
+
+/* Verify that function-static vars are correctly updated
+   in call summaries.  */
+
+int __attribute__((noinline))
+called_by_test_2 (int i)
+{
+  static int s = 0;
+
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+
+  int old_val = s;
+  s = i;
+  return old_val;
+}
+
+void test_2 (void)
+{
+  int v0 = called_by_test_2 (1);
+  __analyzer_eval (v0 == 0); /* { dg-warning "UNKNOWN" } */
+  /* unknown when called outside of main.  */
+
+  int v1 = called_by_test_2 (2);
+  __analyzer_eval (v1 == 1); /* { dg-warning "TRUE" } */
+
+  int v2 = called_by_test_2 (3);
+  __analyzer_eval (v2 == 2); /* { dg-warning "TRUE" } */
+}
+
+void main (void)
+{
+  int v0 = called_by_test_2 (1);
+  __analyzer_eval (v0 == 0); /* { dg-warning "TRUE" } */
+  /* Known when called from main.  */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/call-summaries-data-model-12.FIXME-c b/gcc/testsuite/gcc.dg/analyzer/call-summaries-data-model-12.FIXME-c
new file mode 100644
index 00000000000..5b6749c68f9
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/call-summaries-data-model-12.FIXME-c
@@ -0,0 +1,21 @@
+/* { dg-additional-options "-fanalyzer-call-summaries" } */
+
+/* Taken from data-model-12.c.  */
+/* FIXME: generalize analyzer.exp to run tests both
+   with and without -fanalyzer-call-summaries.  */
+
+/* Mismatching decl of foo.  */
+
+int foo ();
+
+int bar (void)
+{
+  return foo() + 1;
+}
+
+int foo (int x, int y)
+{
+  return x * y;
+}
+
+// FIXME: this currently ICEs
diff --git a/gcc/testsuite/gcc.dg/analyzer/call-summaries-data-model-2.c b/gcc/testsuite/gcc.dg/analyzer/call-summaries-data-model-2.c
new file mode 100644
index 00000000000..48ec5d2d70c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/call-summaries-data-model-2.c
@@ -0,0 +1,56 @@
+/* Verify that call summaries respect conditions.  */
+
+/* { dg-additional-options "-fanalyzer-call-summaries" } */
+
+#include "analyzer-decls.h"
+
+struct st
+{
+  int f0;
+  int f1;
+};
+
+void __attribute__((noinline))
+set_field (struct st *s, int which, int i)
+{
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+
+  if (which)
+    s->f1 = i;
+  else
+    s->f0 = i;
+}
+
+int __attribute__((noinline))
+get_field (struct st *s, int which, int i)
+{
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+
+  if (which)
+    return s->f1;
+  else
+    return s->f0;
+}
+
+void test_1 (void)
+{
+  // TODO
+  struct st s;
+  //__analyzer_dump ();
+  set_field (&s, 0, 42);
+  //__analyzer_dump ();
+  set_field (&s, 1, 17);
+  //__analyzer_dump ();
+
+  /* FIXME: the summary of set_field is using the EN for the return,
+     at which point state-merging means that we have "unknown" for
+     both fields within the outcome.  */
+  
+  // TODO
+
+  // TODO: test summary of get_field
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/call-summaries-data-model-3.c b/gcc/testsuite/gcc.dg/analyzer/call-summaries-data-model-3.c
new file mode 100644
index 00000000000..f4285957165
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/call-summaries-data-model-3.c
@@ -0,0 +1,113 @@
+/* FIXME.  */
+
+/* { dg-additional-options "-fanalyzer-call-summaries" } */
+
+#include "analyzer-decls.h"
+
+/* Indexed writes through a pointer at an arbitrary index.  */
+
+void __attribute__((noinline))
+called_by_test_1 (int *p, int a, int b)
+{
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+
+  p[a] = b;
+}
+  
+void
+test_1 (int i, int j)
+{
+  int buf[1024];
+  called_by_test_1 (buf, i, j);
+  __analyzer_eval (buf[i] == j); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */
+  // TODO: xfail
+}
+
+/* Indexed writes through a pointer to a hardcoded index.  */
+
+void __attribute__((noinline))
+called_by_test_1a (int *p, int b)
+{
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+
+  p[10] = b;
+}
+  
+void
+test_1a (int j, int k)
+{
+  int buf[1024];
+  called_by_test_1a (buf, j);
+  __analyzer_eval (buf[10] == j); /* { dg-warning "TRUE" } */
+
+  called_by_test_1a (buf, k);
+  __analyzer_eval (buf[10] == k); /* { dg-warning "TRUE" } */
+}
+
+/* Label.  */
+
+void __attribute__((noinline))
+called_by_test_2 (void **p)
+{
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+
+ label0:
+  *p = &&label0;
+}
+
+void test_2 (void)
+{
+  void *q, *r;
+  called_by_test_2 (&q);
+  called_by_test_2 (&r);
+  __analyzer_eval (q == r); /* { dg-warning "TRUE" } */
+
+ label1:
+  __analyzer_eval (q != &&label1); /* { dg-warning "TRUE" } */
+}
+
+/* Casts.  */
+
+int * __attribute__((noinline))
+cast (void *p)
+{
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+
+  return (int *)p;
+}
+
+void test_3 (void)
+{
+  int i, j;
+  __analyzer_eval (&i == cast (&i)); /* { dg-warning "TRUE" } */
+  __analyzer_eval (&i != cast (&j)); /* { dg-warning "TRUE" } */
+}
+
+void __attribute__((noinline))
+write_through_cast (void *p, int i)
+{
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+
+  *(int *)p = i;
+}
+
+void test_4 (void)
+{
+  int i, j;
+  write_through_cast (&i, 42);
+  write_through_cast (&j, 1066);
+
+  __analyzer_eval (i == 42); /* { dg-warning "TRUE" } */
+  __analyzer_eval (j == 1066); /* { dg-warning "TRUE" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/call-summaries-data-model-4.c b/gcc/testsuite/gcc.dg/analyzer/call-summaries-data-model-4.c
new file mode 100644
index 00000000000..0692f6a4339
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/call-summaries-data-model-4.c
@@ -0,0 +1,40 @@
+/* Verify that we can correctly offset writes into a larger region
+   when processing a call summary.  */
+
+/* { dg-additional-options "-fanalyzer-call-summaries" } */
+
+#include "analyzer-decls.h"
+
+struct s1
+{
+  int f1;
+  int f2; 
+};
+
+struct t1
+{
+  struct s1 g1;
+  struct s1 g2;
+};
+
+void
+called_by_test_1 (struct s1 *p, int a, int b)
+{
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+  p->f1 = a;
+  p->f2 = b;
+}
+  
+void
+test_1 (int i, int j, int k, int l)
+{
+  struct t1 t;
+  called_by_test_1 (&t.g1, i, j);
+  called_by_test_1 (&t.g2, k, l);
+  __analyzer_eval (t.g1.f1 == i); /* { dg-warning "TRUE" } */
+  __analyzer_eval (t.g1.f2 == j); /* { dg-warning "TRUE" } */
+  __analyzer_eval (t.g2.f1 == k); /* { dg-warning "TRUE" } */
+  __analyzer_eval (t.g2.f2 == l); /* { dg-warning "TRUE" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/call-summaries-data-model-5.c b/gcc/testsuite/gcc.dg/analyzer/call-summaries-data-model-5.c
new file mode 100644
index 00000000000..a4a33083d1f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/call-summaries-data-model-5.c
@@ -0,0 +1,54 @@
+/* FIXME.  */
+
+/* { dg-additional-options "-fanalyzer-call-summaries" } */
+
+#include <stdlib.h>
+#include "analyzer-decls.h"
+
+typedef struct st1
+{
+  int x;
+  int y;
+} st1;
+
+st1 * __attribute__((noinline))
+new_st1 (int x, int y)
+{
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+
+  st1 *s = (st1*)malloc (sizeof (st1));
+  if (!s)
+    return NULL;
+  s->x = x;
+  s->y = y;
+  return s;
+}
+
+st1 *test_1 (int i, int j)
+{
+  st1 *t = new_st1 (i, j);
+  if (t)
+    {
+      __analyzer_eval (t->x == i); /* { dg-warning "TRUE" } */
+      __analyzer_eval (t->y == j); /* { dg-warning "TRUE" } */
+    }
+  return t;
+}
+
+void test_2 (int i, int j)
+{
+  st1 *t = new_st1 (i, j); /* { dg-message "allocated here" } */
+} /* { dg-warning "leak of 't'" } */
+/* FIXME: should we expand the path to the malloc inside new_s1?  */
+
+void test_3 (int i, int j)
+{
+  st1 *t = new_st1 (i, j);
+  int r = t->x; /* { dg-warning "possibly-NULL" "" { xfail *-*-* } } */
+  /* { dg-bogus "dereference of NULL 't'" "" { xfail *-*-* } .-1 } */
+  /* FIXME: xfail: would be better to say "possibly-NULL here", rather than NULL.  */
+  /* FIXME: should we expand the path to the malloc inside new_s1?  */
+  free (t);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/call-summaries-deref-1.c b/gcc/testsuite/gcc.dg/analyzer/call-summaries-deref-1.c
new file mode 100644
index 00000000000..be862a0afaf
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/call-summaries-deref-1.c
@@ -0,0 +1,20 @@
+/* { dg-additional-options "-fanalyzer-call-summaries" } */
+
+#include "analyzer-decls.h"
+
+int
+deref (int *p)
+{
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+
+  return *p;
+}
+  
+void
+test_1 (int i)
+{
+  __analyzer_eval (deref (&i) == i); /* { dg-warning "TRUE" } */
+  // TODO: what about NULL?
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/call-summaries-explosion-1.c b/gcc/testsuite/gcc.dg/analyzer/call-summaries-explosion-1.c
new file mode 100644
index 00000000000..add24467079
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/call-summaries-explosion-1.c
@@ -0,0 +1,69 @@
+/* Verify that call summaries avoid exponential blowup, with summaries
+   being created using existing summaries.  */
+
+/* { dg-additional-options "-fanalyzer-call-summaries" } */
+
+#include <stdlib.h>
+#include "analyzer-decls.h"
+
+int __attribute__((noinline))
+add_1 (int i)
+{
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+  return i + 1;
+}
+
+int __attribute__((noinline))
+add_10 (int i)
+{
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+
+  i = add_1 (i);
+  i = add_1 (i);
+  i = add_1 (i);
+  i = add_1 (i);
+  i = add_1 (i);
+  i = add_1 (i);
+  i = add_1 (i);
+  i = add_1 (i);
+  i = add_1 (i);
+  i = add_1 (i);
+  return i;
+}
+
+int __attribute__((noinline))
+add_100 (int i)
+{
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+
+  i = add_10 (i);
+  i = add_10 (i);
+  i = add_10 (i);
+  i = add_10 (i);
+  i = add_10 (i);
+  i = add_10 (i);
+  i = add_10 (i);
+  i = add_10 (i);
+  i = add_10 (i);
+  i = add_10 (i);
+  return i;
+}
+
+void
+test_1 (void)
+{
+  int a = add_1 (7);
+  __analyzer_eval (a == 8); /* { dg-warning "TRUE" } */
+
+  int b = add_10 (0);
+  __analyzer_eval (b == 10); /* { dg-warning "TRUE" } */
+
+  int c = add_100 (0);
+  __analyzer_eval (c == 100); /* { dg-warning "TRUE" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/call-summaries-jump-through-fnptr-1.c b/gcc/testsuite/gcc.dg/analyzer/call-summaries-jump-through-fnptr-1.c
new file mode 100644
index 00000000000..d1b4bc9ef6a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/call-summaries-jump-through-fnptr-1.c
@@ -0,0 +1,25 @@
+/* { dg-additional-options "-fanalyzer-call-summaries" } */
+
+#include "analyzer-decls.h"
+
+int __attribute__((noinline))
+times_two (int i)
+{
+  return i * 2;
+}
+
+int __attribute__((noinline))
+called_by_test_1 (int (*fn) (int), int j)
+{
+  return fn (j);
+}
+
+void test_1 (int k)
+{
+  __analyzer_eval (called_by_test_1 (times_two, 5) == 10); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */
+
+  __analyzer_eval (called_by_test_1 (times_two, k) == 2 * k);  /* { dg-warning "TRUE" "" { xfail *-*-* } } */
+  /* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */
+  /* FIXME: fix the xfails.  */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/call-summaries-malloc-1.c b/gcc/testsuite/gcc.dg/analyzer/call-summaries-malloc-1.c
new file mode 100644
index 00000000000..4ae12386f7b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/call-summaries-malloc-1.c
@@ -0,0 +1,18 @@
+/* { dg-additional-options "-fanalyzer-call-summaries --param=analyzer-min-snodes-for-call-summary=0" } */
+
+#include "analyzer-decls.h"
+
+void calls_free (void *p)
+{
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+
+  __builtin_free (p); /* { dg-warning "double-'free' of 'p'" "" { xfail *-*-* } } */ // TODO
+}
+
+void test (void *q)
+{
+  calls_free (q);
+  calls_free (q);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/call-summaries-malloc-2.c b/gcc/testsuite/gcc.dg/analyzer/call-summaries-malloc-2.c
new file mode 100644
index 00000000000..4dd1bc96e19
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/call-summaries-malloc-2.c
@@ -0,0 +1,27 @@
+/* { dg-additional-options "-fanalyzer-call-summaries" } */
+
+#include <stdlib.h>
+#include "analyzer-decls.h"
+
+void __attribute__((noinline))
+maybe_call_free (int flag, void *p)
+{
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+
+  if (flag)
+    free (p);
+}
+
+void test_1 (void *p)
+{
+  maybe_call_free (1, p);
+  maybe_call_free (1, p);
+}
+
+void test_2 (void *p)
+{
+  maybe_call_free (0, p);
+  maybe_call_free (0, p);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/call-summaries-malloc-3.c b/gcc/testsuite/gcc.dg/analyzer/call-summaries-malloc-3.c
new file mode 100644
index 00000000000..ed50dd9824a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/call-summaries-malloc-3.c
@@ -0,0 +1,45 @@
+/* { dg-additional-options "-fanalyzer-call-summaries" } */
+
+#include <stdlib.h>
+#include "analyzer-decls.h"
+
+void __attribute__((noinline))
+called_by_test_1 (void *p)
+{
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+
+  free (p);
+}
+
+void test_1 (void *p, void *q)
+{
+  called_by_test_1 (p);
+  called_by_test_1 (q);
+}
+
+void __attribute__((noinline))
+called_by_test_2 (void *p, void *q)
+{
+  free (p);
+  free (q);
+}
+
+void test_2 (void *p, void *q)
+{
+  called_by_test_2 (p, q);
+}
+
+void __attribute__((noinline))
+called_by_test_3 (void *p, void *q)
+{
+  free (p);
+  free (q);
+}
+
+void test_3 (void *p)
+{
+  /* FIXME: should be reported as a double-free.  */
+  called_by_test_3 (p, p);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/call-summaries-malloc-4.c b/gcc/testsuite/gcc.dg/analyzer/call-summaries-malloc-4.c
new file mode 100644
index 00000000000..1de4c615e96
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/call-summaries-malloc-4.c
@@ -0,0 +1,24 @@
+/* Verify that call summaries handle sm-state.  */
+
+/* { dg-additional-options "-fanalyzer-call-summaries" } */
+
+#include <stdlib.h>
+#include "analyzer-decls.h"
+
+void *__attribute__((noinline))
+call_malloc (size_t n)
+{
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+
+  return malloc (n);
+}
+
+int *
+test_1 (void)
+{
+  int *p = (int *)call_malloc (1024);
+  *p = 42; /* { dg-warning "dereference of possibly-NULL 'p'" } */
+  return p;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/call-summaries-malloc-5.c b/gcc/testsuite/gcc.dg/analyzer/call-summaries-malloc-5.c
new file mode 100644
index 00000000000..b988846773b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/call-summaries-malloc-5.c
@@ -0,0 +1,91 @@
+/* A toy re-implementation of CPython's object model,
+   with call summaries.  */
+
+/* { dg-additional-options "-fanalyzer-call-summaries" } */
+
+#include "analyzer-std-decls.h"
+#include "analyzer-decls.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 boxed_int_obj
+{
+  base_obj int_base;
+  int int_val;
+} boxed_int_obj;
+
+extern void int_del (base_obj *);
+
+type_obj type_type = {
+  { &type_type, 1}
+};
+
+type_obj boxed_int_type = {
+  { &type_type, 1},
+  int_del
+};
+
+base_obj * __attribute__((noinline))
+alloc_obj (type_obj *ob_type, size_t sz)
+{
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+
+  base_obj *obj = (base_obj *)malloc (sz);
+  if (!obj)
+    return NULL;
+  obj->ob_type = ob_type;
+  obj->ob_refcnt = 1;
+  return obj;
+}
+
+base_obj *__attribute__((noinline))
+new_int_obj (int val)
+{
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+
+  boxed_int_obj *int_obj
+    = (boxed_int_obj *)alloc_obj (&boxed_int_type, sizeof (boxed_int_obj));
+  if (!int_obj)
+    return NULL;
+  int_obj->int_val = val;
+  return (base_obj *)int_obj;
+}
+
+void __attribute__((noinline))
+unref (base_obj *obj)
+{
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+
+  if (--obj->ob_refcnt == 0)
+    obj->ob_type->tp_dealloc (obj);
+}
+
+void __attribute__((noinline))
+test_1 (const char *str)
+{
+  base_obj *obj = new_int_obj (42);
+  if (!obj)
+    return;
+  //__analyzer_dump (); // FIXME
+  __analyzer_eval (((boxed_int_obj *)obj)->int_val == 42); /* { dg-warning "TRUE" } */
+  __analyzer_eval (obj->ob_refcnt == 1); /* { dg-warning "TRUE" } */
+  unref (obj); /* { dg-bogus "leak" "" { xfail *-*-* } } */
+  /* FIXME: currently fails as we don't yet know how to update
+     incoming sm-state at a call summary.  */
+} /* { dg-bogus "leak" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/call-summaries-malloc-6.c b/gcc/testsuite/gcc.dg/analyzer/call-summaries-malloc-6.c
new file mode 100644
index 00000000000..922298914b3
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/call-summaries-malloc-6.c
@@ -0,0 +1,23 @@
+/* { dg-additional-options "-fanalyzer-call-summaries --param=analyzer-min-snodes-for-call-summary=0" } */
+
+#include <stdlib.h>
+#include "analyzer-decls.h"
+
+void  __attribute__((noinline))
+called_by_test_1 (void **p)
+{
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+
+  *p = NULL;
+}
+
+void *
+test_1 (void)
+{
+  void *q = malloc (1024);
+  called_by_test_1 (&q);
+  return q; /* { dg-warning "leak" } */
+}
+/* FIXME: put the leak at a better location.  */
diff --git a/gcc/testsuite/gcc.dg/analyzer/call-summaries-noreturn-1.c b/gcc/testsuite/gcc.dg/analyzer/call-summaries-noreturn-1.c
new file mode 100644
index 00000000000..95d60e67353
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/call-summaries-noreturn-1.c
@@ -0,0 +1,24 @@
+/* Verify that call summaries handle "exit" and other functions
+   that don't return.  */
+
+/* { dg-additional-options "-fanalyzer-call-summaries" } */
+
+#include <stdlib.h>
+#include "analyzer-decls.h"
+
+void __attribute__((noinline))
+calls_exit (void)
+{
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+
+  exit (0);
+}
+
+void test_1 (void)
+{
+  // TODO
+  calls_exit ();
+  __analyzer_dump_path (); /* { dg-bogus "" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/call-summaries-noreturn-2.c b/gcc/testsuite/gcc.dg/analyzer/call-summaries-noreturn-2.c
new file mode 100644
index 00000000000..35a7322425e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/call-summaries-noreturn-2.c
@@ -0,0 +1,23 @@
+/* Verify that call summaries handle conditions.  */
+
+/* { dg-additional-options "-fanalyzer-call-summaries" } */
+
+#include <stdlib.h>
+#include "analyzer-decls.h"
+
+void __attribute__((noinline))
+maybe_call_exit (int a)
+{
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+
+  if (a)
+    exit (0);
+}
+
+void test_1 (int b)
+{
+  maybe_call_exit (b);
+  __analyzer_eval (b); /* { dg-warning "FALSE" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/call-summaries-noreturn-3.c b/gcc/testsuite/gcc.dg/analyzer/call-summaries-noreturn-3.c
new file mode 100644
index 00000000000..1707e5030bb
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/call-summaries-noreturn-3.c
@@ -0,0 +1,32 @@
+/* { dg-additional-options "-fanalyzer-call-summaries" } */
+
+#include "analyzer-decls.h"
+
+void __attribute__((noinline))
+ensure_equal (int a, int b)
+{
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+
+  if (a != b)
+    __builtin_abort ();
+}
+
+void test_1 (int i, int j)
+{
+  __analyzer_eval (i == j); /* { dg-warning "UNKNOWN" } */
+
+  ensure_equal (i, j);
+
+  __analyzer_eval (i == j); /* { dg-warning "TRUE" } */
+}
+
+void test_2 (int p, int q)
+{
+  __analyzer_eval (p == q); /* { dg-warning "UNKNOWN" } */
+
+  ensure_equal (p, q);
+
+  __analyzer_eval (p == q); /* { dg-warning "TRUE" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/call-summaries-return-expr-1.c b/gcc/testsuite/gcc.dg/analyzer/call-summaries-return-expr-1.c
new file mode 100644
index 00000000000..5f09c80a4df
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/call-summaries-return-expr-1.c
@@ -0,0 +1,150 @@
+/* FIXME.
+   Verify that call summaries get the return value correct, where possible.  */
+
+/* { dg-additional-options "-fanalyzer-call-summaries" } */
+
+#include "analyzer-decls.h"
+
+/* Summarization of a constant.  */
+
+int __attribute__((noinline))
+get_cst (void)
+{
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+
+  return 42;
+}
+
+void test_1 (void)
+{
+  __analyzer_eval (get_cst () == 42); /* { dg-warning "TRUE" } */
+}
+
+/* Summarization of a unary op.  */
+
+int __attribute__((noinline))
+negate (int i)
+{
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+
+  return -i;
+}
+
+void test_2 (int a)
+{
+  __analyzer_eval (negate (3) == -3); /* { dg-warning "TRUE" } */
+  __analyzer_eval (negate (a) == -a); /* { dg-warning "TRUE" } */
+}
+
+/* Summarization of a binary op.  */
+
+int __attribute__((noinline))
+mult (int i, int j)
+{
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+
+  return i * j;
+}
+
+void test_3 (int a, int b)
+{
+  __analyzer_eval (mult (4, 5) == 20);  /* { dg-warning "TRUE" } */
+  __analyzer_eval (mult (a, b) == a * b);  /* { dg-warning "TRUE" } */
+}
+
+/* Summarization of a compound value.  */
+
+struct s4
+{
+  int f1;
+  int f2;
+};
+
+struct s4 __attribute__((noinline))
+called_by_test_4 (int i)
+{
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+
+  struct s4 s;
+  s.f1 = 42;
+  s.f2 = i;
+  return s;
+}
+
+void test_4 (int j)
+{
+  struct s4 t = called_by_test_4 (j);
+  __analyzer_eval (t.f1 == 42);  /* { dg-warning "TRUE" } */
+  __analyzer_eval (t.f2 == j);  /* { dg-warning "TRUE" } */
+}
+
+/* Accessing a field by pointer.  */
+
+struct s5
+{
+  int f;
+};
+
+int __attribute__((noinline))
+called_by_test_5 (struct s5 *p)
+{
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+
+  return p->f;
+}
+
+void test_5 (int i)
+{
+  struct s5 s;
+  s.f = i;
+  __analyzer_eval (called_by_test_5 (&s) == i); /* { dg-warning "TRUE" } */
+}
+
+/* String literal.  */
+
+const char * __attribute__((noinline))
+called_by_test_6 (void)
+{
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+
+  return "hello world";
+}
+
+void test_6 (int i)
+{
+  __analyzer_eval (called_by_test_6 ()[0] == 'h'); /* { dg-warning "TRUE" } */
+  __analyzer_eval (called_by_test_6 ()[1] == 'e'); /* { dg-warning "TRUE" } */
+}
+
+/* Function ptr.  */
+
+typedef void (*void_void_fn_ptr) (void);
+
+void test_7 (void);
+
+void_void_fn_ptr __attribute__((noinline))
+called_by_test_7 (void)
+{
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+
+  return test_7;
+}
+
+void test_7 (void)
+{
+  __analyzer_eval (called_by_test_7 () == test_7); /* { dg-warning "TRUE" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/call-summaries-setjmp-1.c b/gcc/testsuite/gcc.dg/analyzer/call-summaries-setjmp-1.c
new file mode 100644
index 00000000000..bf8bae12872
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/call-summaries-setjmp-1.c
@@ -0,0 +1,32 @@
+/* Verify that call summaries respect longjmp (PR analyzer/99391).  */
+
+/* { dg-additional-options "-fanalyzer-call-summaries --param=analyzer-min-snodes-for-call-summary=0 -fno-analyzer-feasibility" } */
+// FIXME: need to properly handle feasibility
+
+#include "test-setjmp.h"
+#include "analyzer-decls.h"
+
+static void inner ();
+
+static jmp_buf env;
+
+void outer (void)
+{
+  if (SETJMP (env))
+    __analyzer_dump_path (); /* { dg-message "path" } */
+  else
+    {
+      inner ();
+      inner ();
+    }
+}
+
+static void inner ()
+{
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+
+  longjmp (env, 42); /* { dg-message "rewinding from 'longjmp' in 'inner'" "" { xfail *-*-*} } */
+  // FIXME: need to properly handle events/use correct edge info
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/call-summaries-stale-frame-1.c b/gcc/testsuite/gcc.dg/analyzer/call-summaries-stale-frame-1.c
new file mode 100644
index 00000000000..c9623d2d23a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/call-summaries-stale-frame-1.c
@@ -0,0 +1,22 @@
+/* { dg-additional-options "-fanalyzer-call-summaries" } */
+
+#include "analyzer-decls.h"
+
+int *global_ptr;
+
+static void __attribute__((noinline))
+called_by_test_1 (void)
+{
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+
+  int i = 42;
+  global_ptr = &i;
+}
+
+int test_1 (void)
+{
+  called_by_test_1 ();
+  return *global_ptr; /* { dg-warning "dereferencing pointer 'global_ptr' to within stale stack frame" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/call-summaries-strlen-1.c b/gcc/testsuite/gcc.dg/analyzer/call-summaries-strlen-1.c
new file mode 100644
index 00000000000..8827b29c46d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/call-summaries-strlen-1.c
@@ -0,0 +1,20 @@
+/* { dg-additional-options "-fanalyzer-call-summaries" } */
+
+#include <string.h>
+#include "analyzer-decls.h"
+
+size_t __attribute__((noinline))
+calls_strlen (const char *s)
+{
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+
+  return strlen (s);
+}
+
+void test_1 (void)
+{
+  __analyzer_eval (calls_strlen ("") == 0); /* { dg-warning "TRUE" "" { xfail *-*-* } } */ // TODO
+  /* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */ // TODO
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/call-summaries-unknown-fn-1.c b/gcc/testsuite/gcc.dg/analyzer/call-summaries-unknown-fn-1.c
new file mode 100644
index 00000000000..b4cc17ad649
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/call-summaries-unknown-fn-1.c
@@ -0,0 +1,37 @@
+/* Verify that call summaries handle calls that call unknown functions.  */
+
+/* { dg-additional-options "-fanalyzer-call-summaries" } */
+
+#include "analyzer-decls.h"
+
+extern int unknown_fn (void *);
+
+int g = 17;
+
+int __attribute__((noinline))
+calls_unknown_fn (void *p)
+{
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+
+  return unknown_fn (p);
+}
+
+int main (void)
+{
+  __analyzer_eval (g == 17); /* { dg-warning "TRUE" } */
+
+  int i = 42;
+  int j = 17;
+  int k = calls_unknown_fn (&i);
+  __analyzer_eval (i == 42); /* { dg-warning "UNKNOWN" "" { xfail *-*-* } } */ // TODO
+  /* { dg-bogus "TRUE" "" { xfail *-*-* } .-1 } */ // TODO
+
+  __analyzer_eval (j == 17); /* { dg-warning "TRUE" } */
+
+  //__analyzer_describe (0, j); /* { d-not-g-warning "UNKNOWN" } */
+  //__analyzer_dump ();
+
+  __analyzer_eval (g == 17); /* { dg-warning "UNKNOWN" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/call-summaries-unknown-fn-2.c b/gcc/testsuite/gcc.dg/analyzer/call-summaries-unknown-fn-2.c
new file mode 100644
index 00000000000..34e2f9c2e1d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/call-summaries-unknown-fn-2.c
@@ -0,0 +1,58 @@
+/* Verify that call summaries handle calls that call unknown functions.  */
+
+/* { dg-additional-options "-fanalyzer-call-summaries" } */
+
+#include "analyzer-decls.h"
+
+extern int escape (int *);
+extern void unknown_fn (void);
+
+int __attribute__((noinline))
+called_by_tests (int *p, int *q)
+{
+  /* This program point should only be processed once;
+     summarization should do the rest.  */
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+
+  *q = 3;
+
+  return escape (p);
+}
+
+void test_1 (void)
+{
+  int i = 42;
+  int j = 17;
+  int k = called_by_tests (&i, &j);
+  __analyzer_eval (i == 42); /* { dg-warning "UNKNOWN" } */
+
+  __analyzer_eval (j == 3); /* { dg-warning "TRUE" "" { xfail *-*-* } } */ // TODO
+  /* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */ // TODO
+
+  i = 1066;
+
+  unknown_fn ();
+  __analyzer_eval (i == 1066); /* { dg-warning "UNKNOWN" "" { xfail *-*-* } } */ // TODO
+  /* { dg-bogus "TRUE" "" { xfail *-*-* } .-1 } */ // TODO
+
+  __analyzer_eval (j == 3); /* { dg-warning "TRUE" "" { xfail *-*-* } } */ // TODO
+  /* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */ // TODO
+}
+
+void test_2 (void)
+{
+  int i = 42;
+  int j = 17;
+  int k = called_by_tests (&i, &i);
+  __analyzer_eval (i == 42); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (j == 17); /* { dg-warning "TRUE" } */
+
+  i = 1066;
+
+  unknown_fn ();
+  __analyzer_eval (i == 1066); /* { dg-warning "UNKNOWN" "" { xfail *-*-* } } */ // TODO
+  /* { dg-bogus "TRUE" "" { xfail *-*-* } .-1 } */ // TODO
+
+  __analyzer_eval (j == 17); /* { dg-warning "TRUE" } */
+
+}
-- 
2.26.2

