From 539dcacb291094f706a24b4765cc1bac8f2eb758 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Tue, 1 Sep 2020 12:57:18 -0400
Subject: [PATCH 36/47] FIXME: WIP on exception-handling (PR analyzer/97111)

Old commit messages:
  * add WIP except-1.C
  * exceptions WIP: implement __cxa_allocate_exception
  * exceptions: WIP on __cxa_throw
  * exceptions: WIP on __builtin_eh_pointer and __cxa_begin_catch
  * exceptions WIP on filtering paths at EH_DISPATCH
  * further WIP on exceptions (handle try/except in same fn)
  * exceptions WIP: combine dispatch and catch into a single eedge
  * exceptions WIP: throw_event and catch_event
  * more WIP on exception test cases
  * exceptions: handle the 'no catch' case
  * start moving testcases to correct location and cleaning them up
---
 except-1.C                                    |  34 ++
 except-4.C                                    |  23 ++
 except-6.C                                    |  23 ++
 gcc/analyzer/analyzer.h                       |   1 +
 gcc/analyzer/checker-path.cc                  |  64 +++
 gcc/analyzer/checker-path.h                   |  65 +++
 gcc/analyzer/diagnostic-manager.cc            |   5 +
 gcc/analyzer/engine.cc                        | 374 +++++++++++++++++-
 gcc/analyzer/exploded-graph.h                 |   4 +
 gcc/analyzer/program-state.cc                 |   1 +
 gcc/analyzer/region-model-impl-calls.cc       |  41 ++
 gcc/analyzer/region-model-manager.cc          |  14 +
 gcc/analyzer/region-model.cc                  | 211 +++++++++-
 gcc/analyzer/region-model.h                   |  70 +++-
 gcc/analyzer/svalue.cc                        |  93 +++++
 gcc/testsuite/g++.dg/analyzer/except-2.C      |  33 ++
 gcc/testsuite/g++.dg/analyzer/except-3.C      |  25 ++
 gcc/testsuite/g++.dg/analyzer/except-5.C      |  13 +
 .../gcc.dg/analyzer/analyzer-decls.h          |  14 +-
 19 files changed, 1094 insertions(+), 14 deletions(-)
 create mode 100644 except-1.C
 create mode 100644 except-4.C
 create mode 100644 except-6.C
 create mode 100644 gcc/testsuite/g++.dg/analyzer/except-2.C
 create mode 100644 gcc/testsuite/g++.dg/analyzer/except-3.C
 create mode 100644 gcc/testsuite/g++.dg/analyzer/except-5.C

diff --git a/except-1.C b/except-1.C
new file mode 100644
index 00000000000..905fa491979
--- /dev/null
+++ b/except-1.C
@@ -0,0 +1,34 @@
+#include "gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h"
+
+struct range_err
+{
+  range_err (int val) : m_val (val) {}
+  int m_val;
+  char m_dummy[1024];
+};
+
+void foo (int n);
+
+#if 0
+void test_1 (int n)
+{
+  if (n < 0 || n > 10) throw range_err (n);
+  foo (n);
+}
+#endif
+
+static void __attribute__((noinline))
+called_by_test_2 (int n) 
+{
+  if (n < 0 || n > 10) throw range_err (n);
+  foo (n);
+}
+
+void test_2 (int n)
+{
+  try {
+    called_by_test_2 (42);
+  } catch (range_err &e) {
+    __analyzer_eval (e.m_val == 42);
+  }
+}
diff --git a/except-4.C b/except-4.C
new file mode 100644
index 00000000000..22c368d615b
--- /dev/null
+++ b/except-4.C
@@ -0,0 +1,23 @@
+#include "gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h"
+#include <stdlib.h>
+
+struct range_err
+{
+  range_err (int val) : m_val (val) {}
+  int m_val;
+  char m_dummy[1024];
+};
+
+struct other_err
+{
+};
+
+void test_1 (int n)
+{
+  try {
+    void *ptr = malloc (1024); // FIXME: should warn about this!
+    throw range_err (n);
+  } catch (other_err &e) {
+    __analyzer_dump_path (); // { dg-bogus "" }
+  }
+}
diff --git a/except-6.C b/except-6.C
new file mode 100644
index 00000000000..e1921cc7cd4
--- /dev/null
+++ b/except-6.C
@@ -0,0 +1,23 @@
+#include "gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h"
+#include <stdlib.h>
+
+struct base_err
+{
+};
+
+struct range_err : public base_err
+{
+  range_err (int val) : m_val (val) {}
+  int m_val;
+  char m_dummy[1024];
+};
+
+void test_1 (int n)
+{
+  try {
+    void *ptr = malloc (1024); // FIXME: should warn about this!
+    throw range_err (n);
+  } catch (base_err &e) {
+    __analyzer_dump_path ();
+  }
+}
diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h
index aa43b7f66a9..17439175cc9 100644
--- a/gcc/analyzer/analyzer.h
+++ b/gcc/analyzer/analyzer.h
@@ -51,6 +51,7 @@ class svalue;
   class widening_svalue;
   class compound_svalue;
   class conjured_svalue;
+  class exception_state;
 typedef hash_set<const svalue *> svalue_set;
 class region;
   class frame_region;
diff --git a/gcc/analyzer/checker-path.cc b/gcc/analyzer/checker-path.cc
index 1f6d6a86001..443a4649bf9 100644
--- a/gcc/analyzer/checker-path.cc
+++ b/gcc/analyzer/checker-path.cc
@@ -99,6 +99,10 @@ event_kind_to_string (enum event_kind ek)
       return "EK_REWIND_FROM_LONGJMP";
     case EK_REWIND_TO_SETJMP:
       return "EK_REWIND_TO_SETJMP";
+    case EK_THROW:
+      return "EK_THROW";
+    case EK_CATCH:
+      return "EK_CATCH";
     case EK_WARNING:
       return "EK_WARNING";
     }
@@ -850,6 +854,66 @@ rewind_to_setjmp_event::prepare_for_emission (checker_path *path,
 			  &m_original_setjmp_event_id);
 }
 
+/* class exception_event : public checker_event.  */
+
+exception_event::exception_event (const exploded_edge *eedge,
+				  enum event_kind kind,
+				  location_t loc, tree fndecl, int depth)
+: checker_event (kind, loc, fndecl, depth),
+  //m_rewind_info (rewind_info),
+  m_eedge (eedge)
+{
+  //gcc_assert (m_eedge->m_custom_info == m_rewind_info);
+}
+
+/* Get the fndecl containing the site of the longjmp call.  */
+
+tree
+exception_event::get_throw_fndecl () const
+{
+  return m_eedge->m_src->get_function ()->decl;
+}
+
+/* Get the fndecl containing the site of the setjmp call.  */
+
+tree
+exception_event::get_catch_fndecl () const
+{
+  return m_eedge->m_dest->get_function ()->decl;
+}
+
+/* class throw_event : public exception_event.  */
+
+label_text
+throw_event::get_desc (bool can_colorize) const
+{
+  if (get_throw_fndecl () == get_catch_fndecl ())
+    /* Special-case: purely intraprocedural throw/catch.  */
+    return make_label_text (can_colorize,
+			    "throwing %qE...",
+			    m_thrown_type);
+  else
+    return make_label_text (can_colorize,
+			    "throwing %qE from %qE...",
+			    m_thrown_type,
+			    get_throw_fndecl ());
+}
+
+/* class catch_event : public exception_event.  */
+
+label_text
+catch_event::get_desc (bool can_colorize) const
+{
+  if (get_throw_fndecl () == get_catch_fndecl ())
+    /* Special-case: purely intraprocedural throw/catch.  */
+    return make_label_text (can_colorize,
+			    "...caught here");
+  else
+    return make_label_text (can_colorize,
+			    "...caught here in %qE",
+			    get_catch_fndecl ());
+}
+
 /* class warning_event : public checker_event.  */
 
 /* Implementation of diagnostic_event::get_desc vfunc for
diff --git a/gcc/analyzer/checker-path.h b/gcc/analyzer/checker-path.h
index 7b86d48e983..c065196ec70 100644
--- a/gcc/analyzer/checker-path.h
+++ b/gcc/analyzer/checker-path.h
@@ -40,6 +40,8 @@ enum event_kind
   EK_SETJMP,
   EK_REWIND_FROM_LONGJMP,
   EK_REWIND_TO_SETJMP,
+  EK_THROW,
+  EK_CATCH,
   EK_WARNING
 };
 
@@ -67,6 +69,9 @@ extern const char *event_kind_to_string (enum event_kind ek);
        rewind_event
          rewind_from_longjmp_event (EK_REWIND_FROM_LONGJMP)
 	 rewind_to_setjmp_event (EK_REWIND_TO_SETJMP)
+       exception_event
+	 throw_event (EK_THROW)
+	 catch_event (EK_CATCH)
        warning_event (EK_WARNING).  */
 
 /* Abstract subclass of diagnostic_event; the base class for use in
@@ -417,6 +422,66 @@ private:
   diagnostic_event_id_t m_original_setjmp_event_id;
 };
 
+/* An abstract event subclass for handling exceptions.
+
+   FIXME:
+   Base class for two from/to subclasses, showing the two halves of the
+   rewind.  */
+
+class exception_event : public checker_event
+{
+public:
+  tree get_throw_fndecl () const;
+  tree get_catch_fndecl () const;
+  const exploded_edge *get_eedge () const { return m_eedge; }
+
+ protected:
+  exception_event (const exploded_edge *eedge,
+		   enum event_kind kind,
+		   location_t loc, tree fndecl, int depth);
+  /*const rewind_info_t *m_rewind_info;*/
+
+ private:
+  const exploded_edge *m_eedge;
+};
+
+// FIXME:
+/* A concrete event subclass for rewinding from a longjmp to a setjmp,
+   showing the longjmp (or siglongjmp).  */
+
+class throw_event : public exception_event
+{
+public:
+  throw_event (const exploded_edge *eedge,
+	       location_t loc, tree fndecl, int depth,
+	       tree thrown_type)
+  : exception_event (eedge, EK_THROW, loc, fndecl, depth),
+    m_thrown_type (thrown_type)
+  {
+  }
+
+  label_text get_desc (bool can_colorize) const FINAL OVERRIDE;
+
+private:
+  tree m_thrown_type;
+};
+
+// FIXME:
+/* A concrete event subclass for rewinding from a longjmp to a setjmp,
+   showing the setjmp (or sigsetjmp).  */
+
+class catch_event : public exception_event
+{
+public:
+  catch_event (const exploded_edge *eedge,
+	       location_t loc, tree fndecl, int depth)
+  : exception_event (eedge, EK_CATCH, loc, fndecl, depth)
+  {
+  }
+
+  label_text get_desc (bool can_colorize) const FINAL OVERRIDE;
+};
+
 /* Concrete subclass of checker_event for use at the end of a path:
    a repeat of the warning message at the end of the path (perhaps with
    references to pertinent events that occurred on the way), at the point
diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc
index cb95a95ff0b..f4e62ea805c 100644
--- a/gcc/analyzer/diagnostic-manager.cc
+++ b/gcc/analyzer/diagnostic-manager.cc
@@ -1516,6 +1516,11 @@ diagnostic_manager::prune_for_sm_diagnostic (checker_path *path,
 	case EK_REWIND_TO_SETJMP:
 	  break;
 
+	case EK_THROW:
+	case EK_CATCH:
+	  /* Always show exception-handling events for now.  */
+	  break;
+
 	case EK_WARNING:
 	  /* Always show the final "warning" event in the path.  */
 	  break;
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index 7d8e1517d0f..67b57976ac5 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -63,6 +63,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "analyzer/state-purge.h"
 #include "analyzer/bar-chart.h"
 #include <zlib.h>
+#include "except.h"
+#include "tree-cfg.h"
 
 /* For an overview, see gcc/doc/analyzer.texi.  */
 
@@ -1133,6 +1135,11 @@ exploded_node::on_stmt (exploded_graph &eg,
 	    on_longjmp (eg, call, state, &ctxt);
 	    return on_stmt_flags::terminate_path ();
 	  }
+	else if (is_special_named_call_p (call, "__cxa_throw", 3))
+	  {
+	    on_cxa_throw (eg, call, state, &ctxt);
+	    return on_stmt_flags::terminate_path ();
+	  }
 	else
 	  unknown_side_effects
 	    = state->m_region_model->on_call_pre (call, &ctxt);
@@ -1376,6 +1383,369 @@ exploded_node::on_longjmp (exploded_graph &eg,
     }
 }
 
+/* Get the outgoing CFG superedge from SNODE flagged with EDGE_EH, if any.  */
+
+static const cfg_superedge *
+get_eh_sedge (const supernode *snode)
+{
+  unsigned edge_idx;
+  superedge *sedge;
+  FOR_EACH_VEC_ELT (snode->m_succs, edge_idx, sedge)
+    {
+      if (cfg_superedge *cfg_sedge = sedge->dyn_cast_cfg_superedge ())
+	if (cfg_sedge->get_flags () & EDGE_EH)
+	  return cfg_sedge;
+    }
+  return NULL;
+}
+
+/* Get the outgoing CFG superedge from SNODE flagged with EDGE_FALLTHRU,
+   if any.  */
+
+static const cfg_superedge *
+get_fallthrough_sedge (const supernode *snode)
+{
+  unsigned edge_idx;
+  superedge *sedge;
+  FOR_EACH_VEC_ELT (snode->m_succs, edge_idx, sedge)
+    {
+      if (cfg_superedge *cfg_sedge = sedge->dyn_cast_cfg_superedge ())
+	if (cfg_sedge->get_flags () & EDGE_FALLTHRU)
+	  return cfg_sedge;
+    }
+  return NULL;
+}
+
+/* FIXME.
+   Update CS in place to give the call_string of the result.  */
+/* FIXME:
+   This seems to take us to an EH_DISPATCH stmt for a region,
+   which we later need to process.
+   Alternatively, should we simply locate that stmt, and combine
+   the handling?  */
+
+static const supernode *
+get_eh_dest_supernode (const supernode *curr_snode, call_string *cs)
+{
+  if (const cfg_superedge *cfg_sedge = get_eh_sedge (curr_snode))
+    return cfg_sedge->m_dest;
+
+  // FIXME: for now, just pop one frame off the stack
+  while (cs->length () > 0)
+    {
+      const return_superedge *return_sedge = cs->pop ();
+      const supernode *return_dest = return_sedge->m_dest;
+      /* Does return_dest have an EH edge?.
+	 If so, go to that snode.
+	 FIXME: Otherwise, what should we do?  */
+      if (const cfg_superedge *cfg_sedge = get_eh_sedge (return_dest))
+	return cfg_sedge->m_dest;
+    }
+  return NULL;
+}
+
+/* FIXME.  */
+
+static geh_dispatch *
+get_geh_dispatch_at_snode (const supernode *snode)
+{
+  gcc_assert (snode->m_stmts.length () == 2);
+  gcc_assert (snode->m_stmts[0]->code == GIMPLE_LABEL);
+  gcc_assert (snode->m_stmts[1]->code == GIMPLE_EH_DISPATCH);
+  return as_a <geh_dispatch *> (snode->get_last_stmt ());
+}
+
+// FIXME: copied from region-model.cc:
+/* FIXME.  */
+
+static const supernode *
+get_supernode_for_eh_catch (eh_catch_d *ehc,
+			    const function *fun,
+			    const supergraph &sg)
+{
+  tree label = ehc->label;
+  basic_block bb = label_to_block (const_cast <function *> (fun), label);
+  gcc_assert (bb);
+  return sg.get_node_for_block (bb);
+}
+
+/* Determine which successor snode should be followed from DISPATCH_SNODE
+   based on EXC_STATE.
+
+   We expect DISPATCH_SNODE to have just a label and then an EH_DISPATCH
+   stmt.  The EH_DISPATCH is a placeholder for a runtime type comparison
+   that should be made in order to select the action to perform among
+   different CATCH and EH_FILTER regions..  */
+
+static const supernode *
+get_dest_snode_for_eh_dispatch (const supernode *dispatch_snode,
+				const exception_state *exc_state,
+				const supergraph &sg)
+{
+  gcc_assert (dispatch_snode);
+  gcc_assert (exc_state);
+
+  geh_dispatch *dispatch_stmt = get_geh_dispatch_at_snode (dispatch_snode);
+  gcc_assert (dispatch_stmt);
+
+  const function *fun = dispatch_snode->get_function ();
+
+  int region_num = gimple_eh_dispatch_region (dispatch_stmt);
+  eh_region region = (*fun->eh->region_array)[region_num];
+  gcc_assert (region->index == region_num);
+
+  switch (region->type)
+    {
+    default:
+      gcc_unreachable ();
+    case ERT_CLEANUP:
+      gcc_unreachable (); // TODO
+      break;
+    case ERT_TRY:
+      {
+	gcc_assert (exc_state->m_tinfo_ptr_sval);
+	tree rtti_type = exc_state->get_rtti_type ();
+	gcc_assert (rtti_type); // TODO: what if it fails?
+
+	/* Look for a matching catch handler.  */
+	for (eh_catch_d *iter_catch = region->u.eh_try.first_catch;
+	     iter_catch; iter_catch = iter_catch->next_catch)
+	  {
+	    for (tree iter = iter_catch->type_list; iter;
+		 iter = TREE_CHAIN (iter))
+	      {
+		tree type = TREE_VALUE (iter);
+		if (type == rtti_type)
+		  return get_supernode_for_eh_catch (iter_catch, fun, sg);
+	      }
+	  }
+	/* Not found; use the FALLTHRU edge.  */
+	const cfg_superedge *fallthru_sedge
+	  = get_fallthrough_sedge (dispatch_snode);
+	gcc_assert (fallthru_sedge);
+	return fallthru_sedge->m_dest;
+      }
+      break;
+    case ERT_ALLOWED_EXCEPTIONS:
+      gcc_unreachable (); // TODO
+      break;
+    case ERT_MUST_NOT_THROW:
+      gcc_unreachable (); // TODO
+      break;
+    }
+
+  //TODO
+  return NULL;
+}
+
+
+/* FIXME.  */
+
+class cxa_throw_info_t : public exploded_edge::custom_info_t
+{
+public:
+  cxa_throw_info_t (const gcall *cxa_throw_call,
+		    const exception_state *exc_state,
+		    int dest_stack_depth)
+  : m_cxa_throw_call (cxa_throw_call),
+    m_exc_state (exc_state),
+    m_dest_stack_depth (dest_stack_depth)
+  {}
+
+  void print (pretty_printer *pp) FINAL OVERRIDE
+  {
+    pp_string (pp, "throw");
+  }
+
+  void update_model (region_model *model,
+		     const exploded_edge &eedge) FINAL OVERRIDE
+  {
+    model->m_current_exception = m_exc_state;
+    model->unwind_stack (m_dest_stack_depth, NULL);
+  }
+
+  void add_events_to_path (checker_path *emission_path,
+			   const exploded_edge &eedge) FINAL OVERRIDE
+  {
+    const exploded_node *src_node = eedge.m_src;
+    const program_point &src_point = src_node->get_point ();
+    const int src_stack_depth = src_point.get_stack_depth ();
+    const exploded_node *dst_node = eedge.m_dest;
+    const program_point &dst_point = dst_node->get_point ();
+    const int dst_stack_depth = dst_point.get_stack_depth ();
+
+    tree thrown_type = m_exc_state->get_rtti_type ();
+    gcc_assert (thrown_type);
+
+    emission_path->add_event
+      (new throw_event
+       (&eedge, m_cxa_throw_call->location,
+	src_point.get_fndecl (),
+	src_stack_depth, thrown_type));
+    emission_path->add_event
+      (new catch_event
+       (&eedge, dst_node->get_supernode ()->get_start_location (),
+	dst_point.get_fndecl (),
+	dst_stack_depth));  // FIXME: show type that is caught?
+  }
+
+private:
+  const gcall *m_cxa_throw_call;
+  const exception_state *m_exc_state;
+  int m_dest_stack_depth;
+};
+
+
+/* Handle CXA_THROW_CALL, a call to __cxa_throw.
+
+   See e.g. https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html
+   "2.4.3 Throwing the Exception Object".
+
+   We expect this prototype:
+     void
+     __cxa_throw (void *thrown_exception,
+		  std::type_info *tinfo,
+		  void (*dest) (void *) );
+
+   First, follow ah EH edge to locate a "dispatch_snode" containing
+   an EH_DISPATCH stmt, unwinding the stack if necessary.
+
+   Use the current exception state to determine which CFG edge to follow
+   after the EH_DISPATCH.
+
+   Create an enode and eedge expressing going directly from the __cxa_throw
+   to that successor block (e.g. the relevant catch handler), in the
+   appropriate frame.
+   This makes some assumptions about where EH_DISPATCH stmts are located,
+   and bypasses the EH_DISPATCH as seen from the exploded_graph, but
+   hopefully leads to easier-to-read diagnostic paths.  */
+
+void
+exploded_node::on_cxa_throw (exploded_graph &eg,
+			     const gcall *cxa_throw_call,
+			     program_state *new_state,
+			     region_model_context *ctxt) const
+{
+  tree exception_ptr = gimple_call_arg (cxa_throw_call, 0);
+  tree tinfo_ptr = gimple_call_arg (cxa_throw_call, 1);
+  tree dest_ptr = gimple_call_arg (cxa_throw_call, 2);
+
+  region_model *new_region_model = new_state->m_region_model;
+
+  const svalue *exception_ptr_sval
+    = new_region_model->get_rvalue (exception_ptr, ctxt);
+  const svalue *tinfo_ptr_sval
+    = new_region_model->get_rvalue (tinfo_ptr, ctxt);
+  const svalue *dest_ptr_sval
+    = new_region_model->get_rvalue (dest_ptr, ctxt);
+
+  const exception_state *exc_state
+    = new_region_model->get_manager ()->get_or_create_exception_state
+	(exception_ptr_sval,
+	 tinfo_ptr_sval,
+	 dest_ptr_sval);
+
+  /* Where to unwind to?  */
+#if 0
+  // FIXME:
+  debug_eh_tree (get_function ());
+#endif
+
+  /* First, follow ah EH edge to locate a "dispatch_snode" containing
+     an EH_DISPATCH stmt, unwinding the stack if necessary.  */
+  call_string cs (get_point ().get_call_string ());
+  const supernode *dispatch_snode
+    = get_eh_dest_supernode (get_supernode (), &cs);
+  if (dispatch_snode == NULL)
+    {
+      /* If we have a throw with nothing to catch it, then try unwinding
+	 the stack to see if we have any leaks.  */
+      new_region_model->unwind_stack (0, ctxt);
+      /* Detect leaks in the new state relative to the old state.  */
+      program_state::detect_leaks (get_state (), *new_state, NULL,
+				   eg.get_ext_state (), ctxt);
+      return;
+    }
+
+  /* Use exc_state to determine which edge to follow after the EH_DISPATCH.  */
+  const supernode *dest_snode
+    = get_dest_snode_for_eh_dispatch (dispatch_snode, exc_state,
+				      eg.get_supergraph ());
+  gcc_assert (dest_snode);
+
+  /* Create an enode and eedge expressing going directly from the __cxa_throw
+     to the relevant catch handler.  This bypasses the EH_DISPATCH as seen
+     from the exploded_graph, but hopefully leads to easier-to-read
+     diagnostic paths.  */
+
+  const program_point &next_point
+    = program_point::before_supernode (dest_snode, NULL, cs);
+
+  /* Update the state for use by the destination node.  */
+
+  /* Stash the current number of diagnostics so that we can update
+     any that this adds to show where the throw is rewinding to.  */
+
+  diagnostic_manager *dm = &eg.get_diagnostic_manager ();
+  unsigned prev_num_diagnostics = dm->get_num_diagnostics ();
+
+  new_region_model->m_current_exception = exc_state;
+  new_region_model->unwind_stack (next_point.get_stack_depth (), ctxt);
+
+  /* TODO: need to stash the exception information somewhere in the
+     new state, for use by eh_dispatch.  */
+
+  /* Detect leaks in the new state relative to the old state.  */
+  program_state::detect_leaks (get_state (), *new_state, NULL,
+			       eg.get_ext_state (), ctxt);
+
+  exploded_node *next
+    = eg.get_or_create_node (next_point, *new_state, this);
+  /* Create custom exploded_edge for a __cxa_throw.  */
+  if (next)
+    {
+      exploded_edge *eedge
+	= eg.add_edge (const_cast<exploded_node *> (this), next, NULL,
+		       new cxa_throw_info_t (cxa_throw_call, exc_state,
+					     next_point.get_stack_depth ()));
+
+  // FIXME: consolidate this with the longjmp case.
+      /* For any diagnostics that were queued here (such as leaks) we want
+	 the checker_path to show the rewinding events after the "final event"
+	 so that the user sees where the longjmp is rewinding to (otherwise the
+	 path is meaningless).
+
+	 For example, we want to emit something like:
+                        |   NN | {
+                        |   NN |   longjmp (env, 1);
+                        |      |   ~~~~~~~~~~~~~~~~
+                        |      |   |
+                        |      |   (10) 'ptr' leaks here; was allocated at (7)
+                        |      |   (11) rewinding from 'longjmp' in 'inner'...
+                        |
+          <-------------+
+          |
+        'outer': event 12
+          |
+          |   NN |   i = setjmp(env);
+          |      |       ^~~~~~
+          |      |       |
+          |      |       (12) ...to 'setjmp' in 'outer' (saved at (2))
+
+	 where the "final" event above is event (10), but we want to append
+	 events (11) and (12) afterwards.
+
+	 Do this by setting m_trailing_eedge on any diagnostics that were
+	 just saved.  */
+      unsigned num_diagnostics = dm->get_num_diagnostics ();
+      for (unsigned i = prev_num_diagnostics; i < num_diagnostics; i++)
+	{
+	  saved_diagnostic *sd = dm->get_saved_diagnostic (i);
+	  sd->m_trailing_eedge = eedge;
+	}
+    }
+}
+
 /* Subroutine of exploded_graph::process_node for finding the successors
    of the supernode for a function exit basic block.
 
@@ -3320,7 +3690,9 @@ exploded_path::feasible_p (logger *logger, feasibility_problem **out,
 
 	  const gimple *last_stmt = src_point.get_supernode ()->get_last_stmt ();
 	  rejected_constraint *rc = NULL;
-	  if (!model.maybe_update_for_edge (*sedge, last_stmt, NULL, &rc))
+	  if (!model.maybe_update_for_edge (*sedge, last_stmt,
+					    &eg->get_supergraph (),
+					    NULL, &rc))
 	    {
 	      if (logger)
 		{
diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h
index fadf22cbdd0..5c624eeb94b 100644
--- a/gcc/analyzer/exploded-graph.h
+++ b/gcc/analyzer/exploded-graph.h
@@ -227,6 +227,10 @@ class exploded_node : public dnode<eg_traits>
 		   const gcall *call,
 		   program_state *new_state,
 		   region_model_context *ctxt) const;
+  void on_cxa_throw (exploded_graph &eg,
+		     const gcall *call,
+		     program_state *new_state,
+		     region_model_context *ctxt) const;
 
   void detect_leaks (exploded_graph &eg) const;
 
diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc
index 3383b31423a..5b00bd33020 100644
--- a/gcc/analyzer/program-state.cc
+++ b/gcc/analyzer/program-state.cc
@@ -872,6 +872,7 @@ program_state::on_edge (exploded_graph &eg,
 				  last_stmt);
   if (!m_region_model->maybe_update_for_edge (*succ,
 					      last_stmt,
+					      &eg.get_supergraph (),
 					      &ctxt, NULL))
     {
       logger * const logger = eg.get_logger ();
diff --git a/gcc/analyzer/region-model-impl-calls.cc b/gcc/analyzer/region-model-impl-calls.cc
index 009b8c3ecb0..5a3b9d1adc4 100644
--- a/gcc/analyzer/region-model-impl-calls.cc
+++ b/gcc/analyzer/region-model-impl-calls.cc
@@ -207,6 +207,16 @@ region_model::impl_call_analyzer_eval (const gcall *call,
   warning_at (call->location, 0, "%s", t.as_string ());
 }
 
+/* Handle the on_call_pre part of "__builtin_eh_pointer".  */
+
+bool
+region_model::impl_call_builtin_eh_pointer (const call_details &cd)
+{
+  gcc_assert (m_current_exception); // maybe UNKNOWN for this case?
+  cd.maybe_set_lhs (m_current_exception);
+  return false;
+}
+
 /* Handle the on_call_pre part of "__builtin_expect" etc.  */
 
 bool
@@ -240,6 +250,37 @@ region_model::impl_call_calloc (const call_details &cd)
   return true;
 }
 
+/* Handle the on_call_pre part of "__cxa_allocate_exception".  */
+
+bool
+region_model::impl_call_cxa_allocate_exception (const call_details &cd)
+{
+  const svalue *size_sval = cd.get_arg_svalue (0);
+  const region *new_reg = create_region_for_heap_alloc (size_sval);
+  if (cd.get_lhs_type ())
+    {
+      const svalue *ptr_sval
+	= m_mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg);
+      cd.maybe_set_lhs (ptr_sval);
+    }
+  return false;
+}
+
+/* Handle the on_call_pre part of "__cxa_begin_catch".  */
+
+bool
+region_model::impl_call_cxa_begin_catch (const call_details &cd)
+{
+  const svalue *sval = cd.get_arg_svalue (0);
+  if (const svalue *cast_sval = sval->maybe_undo_cast ())
+    sval = cast_sval;
+  if (const exception_state *exc_state = sval->dyn_cast_exception_state ())
+    cd.maybe_set_lhs (exc_state->m_exception_ptr_sval);
+  else
+    gcc_unreachable (); // TODO: maybe UNKNOWN?
+  return false;
+}
+
 /* Handle the on_call_post part of "free", after sm-handling.
 
    If the ptr points to an underlying heap region, delete the region,
diff --git a/gcc/analyzer/region-model-manager.cc b/gcc/analyzer/region-model-manager.cc
index 8dd3ad0020a..9c4e4c78e5a 100644
--- a/gcc/analyzer/region-model-manager.cc
+++ b/gcc/analyzer/region-model-manager.cc
@@ -974,6 +974,20 @@ get_region_for_unexpected_tree_code (region_model_context *ctxt,
   return new_reg;
 }
 
+/* FIXME.  */
+
+const exception_state *
+region_model_manager::
+get_or_create_exception_state (const svalue *exception_ptr_sval,
+			       const svalue *tinfo_ptr_sval,
+			       const svalue *dest_ptr_sval)
+{
+  // FIXME: for now just new, no consolidation, and leak
+  return new exception_state (exception_ptr_sval,
+			      tinfo_ptr_sval,
+			      dest_ptr_sval);
+}
+
 /* Return a new region describing a heap-allocated block of memory.  */
 
 const region *
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index 02cfb6cb8d5..2e22354648a 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -65,6 +65,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "analyzer/region-model-reachability.h"
 #include "analyzer/analyzer-selftests.h"
 #include "stor-layout.h"
+#include "except.h"
+#include "tree-cfg.h"
 
 #if ENABLE_ANALYZER
 
@@ -114,7 +116,8 @@ print_quoted_type (pretty_printer *pp, tree t)
 /* Ctor for region_model: construct an "empty" model.  */
 
 region_model::region_model (region_model_manager *mgr)
-: m_mgr (mgr), m_store (), m_current_frame (NULL)
+: m_mgr (mgr), m_store (), m_current_frame (NULL),
+  m_current_exception (NULL)
 {
   m_constraints = new constraint_manager (mgr);
 }
@@ -124,7 +127,8 @@ region_model::region_model (region_model_manager *mgr)
 region_model::region_model (const region_model &other)
 : m_mgr (other.m_mgr), m_store (other.m_store),
   m_constraints (new constraint_manager (*other.m_constraints)),
-  m_current_frame (other.m_current_frame)
+  m_current_frame (other.m_current_frame),
+  m_current_exception (other.m_current_exception)
 {
 }
 
@@ -235,6 +239,18 @@ region_model::dump_to_pp (pretty_printer *pp, bool simple,
   m_constraints->dump_to_pp (pp, multiline);
   if (!multiline)
     pp_string (pp, "}");
+
+  if (m_current_exception)
+    {
+      pp_string (pp, "exception:");
+      if (multiline)
+	pp_newline (pp);
+      else
+	pp_string (pp, " {");
+      m_current_exception->dump_to_pp (pp, simple, multiline);
+      if (!multiline)
+	pp_string (pp, "}");
+    }
 }
 
 /* Dump a representation of this model to FILE.  */
@@ -667,6 +683,8 @@ region_model::on_call_pre (const gcall *call, region_model_context *ctxt)
 	    return impl_call_alloca (cd);
 	  case BUILT_IN_CALLOC:
 	    return impl_call_calloc (cd);
+	  case BUILT_IN_EH_POINTER:
+	    return impl_call_builtin_eh_pointer (cd);
 	  case BUILT_IN_EXPECT:
 	  case BUILT_IN_EXPECT_WITH_PROBABILITY:
 	    return impl_call_builtin_expect (cd);
@@ -731,6 +749,12 @@ region_model::on_call_pre (const gcall *call, region_model_context *ctxt)
 	return impl_call_malloc (cd);
       else if (is_named_call_p (callee_fndecl, "calloc", call, 2))
 	return impl_call_calloc (cd);
+      else if (is_named_call_p (callee_fndecl,
+				"__cxa_allocate_exception", call, 1))
+	return impl_call_cxa_allocate_exception (cd);
+      else if (is_named_call_p (callee_fndecl,
+				"__cxa_begin_catch", call, 1))
+	return impl_call_cxa_begin_catch (cd);
       else if (is_named_call_p (callee_fndecl, "alloca", call, 1))
 	return impl_call_alloca (cd);
       else if (is_named_call_p (callee_fndecl, "getchar", call, 0))
@@ -992,13 +1016,7 @@ region_model::on_longjmp (const gcall *longjmp_call, const gcall *setjmp_call,
   tree fake_retval = gimple_call_arg (longjmp_call, 1);
   const svalue *fake_retval_sval = get_rvalue (fake_retval, ctxt);
 
-  /* Pop any frames until we reach the stack depth of the function where
-     setjmp was called.  */
-  gcc_assert (get_stack_depth () >= setjmp_stack_depth);
-  while (get_stack_depth () > setjmp_stack_depth)
-    pop_frame (NULL, NULL, ctxt);
-
-  gcc_assert (get_stack_depth () == setjmp_stack_depth);
+  unwind_stack (setjmp_stack_depth, ctxt);
 
   /* Assign to LHS of "setjmp" in new_state.  */
   if (tree lhs = gimple_call_lhs (setjmp_call))
@@ -1033,6 +1051,20 @@ region_model::on_longjmp (const gcall *longjmp_call, const gcall *setjmp_call,
     }
 }
 
+/* Pop any frames until we reach DEST_STACK_DEPTH.
+   Leak detection is *not* done, and should be done by the caller.  */
+
+void
+region_model::unwind_stack (int dest_stack_depth, region_model_context *ctxt)
+{
+  gcc_assert (get_stack_depth () >= dest_stack_depth);
+
+  while (get_stack_depth () > dest_stack_depth)
+    pop_frame (NULL, NULL, ctxt);
+
+  gcc_assert (get_stack_depth () == dest_stack_depth);
+}
+
 /* Update this region_model for a phi stmt of the form
      LHS = PHI <...RHS...>.
    where RHS is for the appropriate edge.  */
@@ -2248,6 +2280,7 @@ region_model::update_for_phis (const supernode *snode,
 bool
 region_model::maybe_update_for_edge (const superedge &edge,
 				     const gimple *last_stmt,
+				     const supergraph *sg,
 				     region_model_context *ctxt,
 				     rejected_constraint **out)
 {
@@ -2305,6 +2338,15 @@ region_model::maybe_update_for_edge (const superedge &edge,
     if (cfg_sedge->get_flags () & EDGE_EH)
       return apply_constraints_for_exception (last_stmt, ctxt, out);
 
+  if (const geh_dispatch *eh_dispatch_stmt
+	= dyn_cast <const geh_dispatch *> (last_stmt))
+    {
+      const cfg_superedge *cfg_sedge = as_a <const cfg_superedge *> (&edge);
+      return apply_constraints_for_geh_dispatch (*cfg_sedge, eh_dispatch_stmt,
+						 sg, ctxt);
+      // TODO: maybe even a custom kind of superedge for these?
+    }
+
   return true;
 }
 
@@ -2502,6 +2544,157 @@ region_model::apply_constraints_for_exception (const gimple *last_stmt,
   return true;
 }
 
+#if 0
+/* FIXME.  */
+
+cfg_edge *
+region_model::
+get_out_edge_for_geh_dispatch (const geh_dispatch *geh_dispatch_stmt)
+{
+  function *fun = get_current_function ();
+  gcc_assert (fun);
+
+  int region_num = gimple_eh_dispatch_region (geh_dispatch_stmt);
+  eh_region region = (*fun->eh->region_array)[region_num];
+  gcc_assert (region->index == region_num);
+
+  switch (region->type)
+    {
+    default:
+      gcc_unreachable ();
+    case ERT_CLEANUP:
+      gcc_unreachable (); // TODO
+      break;
+    case ERT_TRY:
+      /* TODO: I think in theory we ought to be iterating through the
+	 catch handlers and comparing the type of the current exception
+	 to them, and filtering accordingly.  */
+      // gcc_unreachable (); // TODO
+      for (eh_catch_d *iter_catch = region->u.eh_try.first_catch;
+	   iter_catch; iter_catch = iter_catch->next_catch)
+	{
+	  gcc_assert (m_current_exception->m_tinfo_ptr_sval);
+	  gcc_unreachable ();
+	  //iter_catch->label;
+	  // TODO
+	}
+      break;
+    case ERT_ALLOWED_EXCEPTIONS:
+      gcc_unreachable (); // TODO
+      break;
+    case ERT_MUST_NOT_THROW:
+      gcc_unreachable (); // TODO
+      break;
+    }
+}
+#endif
+
+/* FIXME.  */
+
+static const supernode *
+get_supernode_for_eh_catch (eh_catch_d *ehc,
+			    const function *fun,
+			    const supergraph *sg)
+{
+  tree label = ehc->label;
+  basic_block bb = label_to_block (const_cast <function *> (fun), label);
+  gcc_assert (bb);
+  return sg->get_node_for_block (bb);
+}
+
+/* FIXME.
+   Determine which eh_catch_d within an ERT_TRY reaches
+   DEST_SNODE.  */
+static eh_catch_d *
+get_eh_catch_for_supernode (eh_region region,
+			    const supernode *dest_snode,
+			    const function *fun,
+			    const supergraph *sg)
+{
+  gcc_assert (region->type == ERT_TRY);
+  for (eh_catch_d *iter_catch = region->u.eh_try.first_catch;
+       iter_catch; iter_catch = iter_catch->next_catch)
+    {
+      const supernode *iter_dest_snode
+	= get_supernode_for_eh_catch (iter_catch, fun, sg);
+      if (iter_dest_snode == dest_snode)
+	return iter_catch;
+    }
+  return NULL;
+}
+
+/* FIXME.  */
+
+bool
+region_model::
+apply_constraints_for_geh_dispatch (const cfg_superedge &sedge,
+				    const geh_dispatch *geh_dispatch_stmt,
+				    const supergraph *sg,
+				    region_model_context *ctxt)
+{
+  ::edge cfg_edge = sedge.get_cfg_edge ();
+  gcc_assert (cfg_edge != NULL);
+  //gcc_assert (cfg_edge->flags & (EDGE_TRUE_VALUE | EDGE_FALSE_VALUE));
+
+  const function *fun = sedge.m_src->get_function ();
+
+  int region_num = gimple_eh_dispatch_region (geh_dispatch_stmt);
+  eh_region region = (*fun->eh->region_array)[region_num];
+  gcc_assert (region->index == region_num);
+
+  switch (region->type)
+    {
+    default:
+      gcc_unreachable ();
+    case ERT_CLEANUP:
+      gcc_unreachable (); // TODO
+      break;
+    case ERT_TRY:
+      {
+	gcc_assert (m_current_exception->m_tinfo_ptr_sval);
+	tree rtti_type = m_current_exception->get_rtti_type ();
+	gcc_assert (rtti_type); // TODO: what if it fails?
+
+	/* TODO: I think in theory we ought to be iterating through the
+	   catch handlers and comparing the type of the current exception
+	   to them, and filtering accordingly.  */
+	if (eh_catch_d *ehc
+	    = get_eh_catch_for_supernode (region, sedge.m_dest, fun, sg))
+	  {
+	    for (tree iter = ehc->type_list; iter; iter = TREE_CHAIN (iter))
+	      {
+		tree type = TREE_VALUE (iter);
+		if (type == rtti_type)
+		  return true;
+	      }
+	    return false;
+	  }
+	else
+	  {
+	    // TODO: presumably the edge for the "unmatched" case
+	    return false; // FIXME
+	  }
+      }
+      break;
+    case ERT_ALLOWED_EXCEPTIONS:
+      gcc_unreachable (); // TODO
+      break;
+    case ERT_MUST_NOT_THROW:
+      gcc_unreachable (); // TODO
+      break;
+    }
+
+  //gcc_unreachable ();
+#if 0
+  enum tree_code op = gimple_cond_code (cond_stmt);
+  tree lhs = gimple_cond_lhs (cond_stmt);
+  tree rhs = gimple_cond_rhs (cond_stmt);
+  if (cfg_edge->flags & EDGE_FALSE_VALUE)
+    op = invert_tree_comparison (op, false /* honor_nans */);
+  return add_constraint (lhs, op, rhs, ctxt);
+#endif
+}
+
 /* For use with push_frame when handling a top-level call within the analysis.
    PARAM has a defined but unknown initial value.
    Anything it points to has escaped, since the calling context "knows"
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index 4e8eecbe546..e99abaceca7 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -190,6 +190,7 @@ public:
   virtual void visit_widening_svalue (const widening_svalue *) {}
   virtual void visit_compound_svalue (const compound_svalue *) {}
   virtual void visit_conjured_svalue (const conjured_svalue *) {}
+  virtual void visit_exception_state (const exception_state *) {}
 
   virtual void visit_region (const region *) {}
 };
@@ -212,7 +213,8 @@ enum svalue_kind
   SK_PLACEHOLDER,
   SK_WIDENING,
   SK_COMPOUND,
-  SK_CONJURED
+  SK_CONJURED,
+  SK_EXCEPTION_STATE
 };
 
 /* svalue and its subclasses.
@@ -236,7 +238,8 @@ enum svalue_kind
      widening_svalue (SK_WIDENING): a merger of two svalues (possibly
        in an iteration).
      compound_svalue (SK_COMPOUND): a mapping of bit-ranges to svalues
-     conjured_svalue (SK_CONJURED): a value arising from a stmt.  */
+     conjured_svalue (SK_CONJURED): a value arising from a stmt
+     exception_state (SK_EXPEPTION_STATE): FIXME.  */
 
 /* An abstract base class representing a value held by a region of memory.  */
 
@@ -282,6 +285,8 @@ public:
   dyn_cast_compound_svalue () const { return NULL; }
   virtual const conjured_svalue *
   dyn_cast_conjured_svalue () const { return NULL; }
+  virtual const exception_state *
+  dyn_cast_exception_state () const { return NULL; }
 
   tree maybe_get_constant () const;
   const svalue *maybe_undo_cast () const;
@@ -2294,6 +2299,48 @@ public:
   void dump_to_pp (pretty_printer *pp, bool simple) const FINAL OVERRIDE;
 };
 
+/* FIXME.
+
+   An abstraction around struct __cxa_exception from e.g.
+   https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html
+   "2.2.1 C++ Exception Objects".  */
+
+class exception_state : public svalue
+{
+public:
+  exception_state (const svalue *exception_ptr_sval,
+		   const svalue *tinfo_ptr_sval,
+		   const svalue *dest_ptr_sval)
+  : svalue (complexity (0, 0), NULL_TREE),
+    m_exception_ptr_sval (exception_ptr_sval),
+    m_tinfo_ptr_sval (tinfo_ptr_sval),
+    m_dest_ptr_sval (dest_ptr_sval)
+  {}
+
+  enum svalue_kind get_kind () const FINAL OVERRIDE
+  {
+    return SK_EXCEPTION_STATE;
+  }
+
+  const exception_state * dyn_cast_exception_state () const FINAL OVERRIDE
+  {
+    return this;
+  }
+
+  void dump_to_pp (pretty_printer *pp, bool simple) const FINAL OVERRIDE;
+  void accept (visitor *v) const FINAL OVERRIDE;
+
+  void dump_to_pp (pretty_printer *pp, bool simple, bool multiline) const;
+
+  tree get_rtti_var_decl () const;
+  tree get_rtti_type () const;
+
+  //private:
+  const svalue *m_exception_ptr_sval;
+  const svalue *m_tinfo_ptr_sval;
+  const svalue *m_dest_ptr_sval;
+};
+
 /* A class responsible for owning and consolidating region and svalue
    instances.
    region and svalue instances are immutable as far as clients are
@@ -2368,6 +2415,11 @@ public:
 
   unsigned alloc_region_id () { return m_next_region_id++; }
 
+  const exception_state *
+  get_or_create_exception_state (const svalue *exception_ptr_sval,
+				 const svalue *tinfo_ptr_sval,
+				 const svalue *dest_ptr_sval);
+
   store_manager *get_store_manager () { return &m_store_mgr; }
 
   /* Dynamically-allocated region instances.
@@ -2569,8 +2621,11 @@ class region_model
 				    region_model_context *ctxt);
   void impl_call_analyzer_eval (const gcall *call,
 				region_model_context *ctxt);
+  bool impl_call_builtin_eh_pointer (const call_details &cd);
   bool impl_call_builtin_expect (const call_details &cd);
   bool impl_call_calloc (const call_details &cd);
+  bool impl_call_cxa_allocate_exception (const call_details &cd);
+  bool impl_call_cxa_begin_catch (const call_details &cd);
   void impl_call_free (const call_details &cd);
   bool impl_call_malloc (const call_details &cd);
   void impl_call_memcpy (const call_details &cd);
@@ -2591,6 +2646,8 @@ class region_model
   void on_longjmp (const gcall *longjmp_call, const gcall *setjmp_call,
 		   int setjmp_stack_depth, region_model_context *ctxt);
 
+  void unwind_stack (int dest_stack_depth, region_model_context *ctxt);
+
   void update_for_phis (const supernode *snode,
 			const cfg_superedge *last_cfg_superedge,
 			region_model_context *ctxt);
@@ -2600,6 +2657,7 @@ class region_model
 
   bool maybe_update_for_edge (const superedge &edge,
 			      const gimple *last_stmt,
+			      const supergraph *sg,
 			      region_model_context *ctxt,
 			      rejected_constraint **out);
 
@@ -2726,6 +2784,10 @@ class region_model
   bool apply_constraints_for_exception (const gimple *last_stmt,
 					region_model_context *ctxt,
 					rejected_constraint **out);
+  bool apply_constraints_for_geh_dispatch (const cfg_superedge &edge,
+					   const geh_dispatch *stmt,
+					   const supergraph *sg,
+					   region_model_context *ctxt);
 
   int poison_any_pointers_to_descendents (const region *reg,
 					  enum poison_kind pkind);
@@ -2746,6 +2808,10 @@ class region_model
   constraint_manager *m_constraints; // TODO: embed, rather than dynalloc?
 
   const frame_region *m_current_frame;
+
+public: // FIXME
+  const exception_state *m_current_exception;
+  // TODO: ^^ make this affect reachability etc
 };
 
 /* Some region_model activity could lead to warnings (e.g. attempts to use an
diff --git a/gcc/analyzer/svalue.cc b/gcc/analyzer/svalue.cc
index ae3b6783e9c..863cdbea153 100644
--- a/gcc/analyzer/svalue.cc
+++ b/gcc/analyzer/svalue.cc
@@ -1051,6 +1051,99 @@ conjured_svalue::accept (visitor *v) const
   m_id_reg->accept (v);
 }
 
+/* class exception_state : public svalue.  */
+
+/* FIXME.  */
+
+void
+exception_state::dump_to_pp (pretty_printer *pp, bool simple) const
+{
+  dump_to_pp (pp, simple, false);
+}
+
+/* FIXME.  */
+
+void
+exception_state::accept (visitor *v) const
+{
+  v->visit_exception_state (this);
+  m_exception_ptr_sval->accept (v);
+  m_tinfo_ptr_sval->accept (v);
+  m_dest_ptr_sval->accept (v);
+}
+
+/* FIXME.  */
+
+void
+exception_state::dump_to_pp (pretty_printer *pp,
+			     bool simple, bool multiline) const
+{
+  if (!multiline)
+    {
+      if (simple)
+	pp_string (pp, "{");
+      else
+	pp_string (pp, "exception_state(");
+    }
+
+  if (multiline)
+    pp_string (pp, "  ");
+  pp_string (pp, "m_exception_ptr: ");
+  m_exception_ptr_sval->dump_to_pp (pp, simple);
+  if (multiline)
+    pp_newline (pp);
+
+  if (multiline)
+    pp_string (pp, "  ");
+  pp_string (pp, "m_tinfo_ptr: ");
+  m_tinfo_ptr_sval->dump_to_pp (pp, simple);
+  if (multiline)
+    pp_newline (pp);
+
+  if (multiline)
+    pp_string (pp, "  ");
+  pp_string (pp, "m_dest_ptr: ");
+  m_dest_ptr_sval->dump_to_pp (pp, simple);
+  if (multiline)
+    pp_newline (pp);
+
+  if (!multiline)
+    {
+      if (simple)
+	pp_string (pp, "}");
+      else
+	pp_string (pp, ")");
+    }
+}
+
+/* FIXME.  */
+
+tree
+exception_state::get_rtti_var_decl () const
+{
+  if (const region_svalue *region_sval
+	= m_tinfo_ptr_sval->dyn_cast_region_svalue ())
+    if (const decl_region *decl_reg
+	  = region_sval->get_pointee ()->dyn_cast_decl_region ())
+      return decl_reg->get_decl ();
+  return NULL_TREE;
+}
+
+/* Get the type of the current exception, or NULL_TREE.  */
+
+tree
+exception_state::get_rtti_type () const
+{
+  if (tree rtti_var_decl = get_rtti_var_decl ())
+    {
+      /* cp/rtti.c:get_tinfo_decl stashes the type as the type
+	 of the mangled name
+	 TREE_TYPE (name) = type;  */
+      return TREE_TYPE (DECL_NAME (rtti_var_decl));
+    }
+  return NULL_TREE;
+}
+
 } // namespace ana
 
 #endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/testsuite/g++.dg/analyzer/except-2.C b/gcc/testsuite/g++.dg/analyzer/except-2.C
new file mode 100644
index 00000000000..e05df77797a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/analyzer/except-2.C
@@ -0,0 +1,33 @@
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+struct some_err
+{
+};
+
+struct range_err
+{
+  range_err (int val) : m_val (val) {}
+  int m_val;
+};
+
+/* Interprocedural throw/catch.  */
+
+static void __attribute__((noinline))
+called_by_test_1 (int n)
+{
+  if (n < 0 || n > 10) throw range_err (n); // { dg-message "throwing 'range_err' from 'called_by_test_1'" }
+
+  __analyzer_dump_path (); // { dg-bogus "" }
+}
+
+void test_1 (int n)
+{
+  try {
+    called_by_test_1 (42);
+  } catch (some_err &e) {
+    __analyzer_dump_path (); // { dg-bogus "" }
+  } catch (range_err &e) { // { dg-message "caught here in 'test_1'" }
+    __analyzer_eval (e.m_val == 42); // { dg-warning "TRUE" }
+    __analyzer_dump_path (); // { dg-message "path" }
+  }
+}
diff --git a/gcc/testsuite/g++.dg/analyzer/except-3.C b/gcc/testsuite/g++.dg/analyzer/except-3.C
new file mode 100644
index 00000000000..ddedf4a9d87
--- /dev/null
+++ b/gcc/testsuite/g++.dg/analyzer/except-3.C
@@ -0,0 +1,25 @@
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+struct some_err
+{
+};
+
+struct range_err
+{
+  range_err (int val) : m_val (val) {}
+  int m_val;
+};
+
+/* Intraprocedural throw/catch.  */
+
+void test_1 (int n)
+{
+  try {
+    if (n < 0 || n > 10)
+      throw range_err (n); // { dg-message "throwing 'range_err'" }
+  } catch (some_err &e) {
+    __analyzer_dump_path (); // { dg-bogus "" }
+  } catch (range_err &e) { // { dg-message "caught here" }
+    __analyzer_dump_path (); // { dg-message "path" }
+  }
+}
diff --git a/gcc/testsuite/g++.dg/analyzer/except-5.C b/gcc/testsuite/g++.dg/analyzer/except-5.C
new file mode 100644
index 00000000000..bc695c92f07
--- /dev/null
+++ b/gcc/testsuite/g++.dg/analyzer/except-5.C
@@ -0,0 +1,13 @@
+#include <stdlib.h>
+
+struct range_err
+{
+  range_err (int val) : m_val (val) {}
+  int m_val;
+};
+
+void test_1 (int n)
+{
+  void *ptr = malloc (1024); // { dg-message "allocated here" }
+  throw range_err (n); // { dg-warning "leak of 'ptr'" }
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h b/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h
index d96b3f2f29a..efd37479d66 100644
--- a/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h
+++ b/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h
@@ -1,6 +1,16 @@
 #ifndef ANALYZER_DECLS_H
 #define ANALYZER_DECLS_H
 
+#ifdef __cplusplus
+# if __cplusplus >= 201103L
+#   define NOEXCEPT noexcept
+# else
+#   define NOEXCEPT
+# endif
+#else
+#  define NOEXCEPT
+#endif
+
 /* Function decls with special meaning to the analyzer.
    None of these are actually implemented.  */
 
@@ -27,13 +37,13 @@ extern void __analyzer_dump_exploded_nodes (int);
 
 /* Emit a placeholder "note" diagnostic with a path to this call site,
    if the analyzer finds a feasible path to it.  */
-extern void __analyzer_dump_path (void);
+extern void __analyzer_dump_path (void) NOEXCEPT;
 
 /* Dump the region_model's state to stderr.  */
 extern void __analyzer_dump_region_model (void);
 
 /* Emit a warning with text "TRUE", FALSE" or "UNKNOWN" based on the
    truthfulness of the argument.  */
-extern void __analyzer_eval (int);
+extern void __analyzer_eval (int) NOEXCEPT;
 
 #endif /* #ifndef ANALYZER_DECLS_H.  */
-- 
2.26.2

