From c0e657eaa422603ccf29cdf70610dcb184066e5d Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Fri, 11 Oct 2019 12:43:29 -0400
Subject: [PATCH 64/64] FIXME: WIP on setjmp/longjmp

---
 gcc/analyzer/analyzer.cc                           |  13 +
 gcc/analyzer/analyzer.h                            |   1 +
 gcc/analyzer/engine.cc                             | 334 +++++++++++++++++++--
 gcc/analyzer/region-model.cc                       | 111 ++++++-
 gcc/analyzer/region-model.h                        |  51 +++-
 .../gcc.dg/analyzer/NOT_FOR_UPSTREAM-setjmp-sj1.c  |  22 ++
 .../analyzer/NOT_FOR_UPSTREAM-setjmp-wikipedia-1.c |  76 ++++-
 .../analyzer/NOT_FOR_UPSTREAM-setjmp-wikipedia-2.c | 189 ++++++++++++
 gcc/testsuite/gcc.dg/analyzer/setjmp-2.c           |  89 ++++++
 gcc/testsuite/gcc.dg/analyzer/setjmp-3.c           |  99 ++++++
 gcc/testsuite/gcc.dg/analyzer/setjmp-4.c           | 102 +++++++
 gcc/testsuite/gcc.dg/analyzer/setjmp-5.c           |  63 ++++
 gcc/testsuite/gcc.dg/analyzer/setjmp-6.c           |  31 ++
 13 files changed, 1146 insertions(+), 35 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-setjmp-sj1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-setjmp-wikipedia-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/setjmp-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/setjmp-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/setjmp-4.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/setjmp-5.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/setjmp-6.c

diff --git a/gcc/analyzer/analyzer.cc b/gcc/analyzer/analyzer.cc
index 5a2d7e2..85ded6d 100644
--- a/gcc/analyzer/analyzer.cc
+++ b/gcc/analyzer/analyzer.cc
@@ -57,3 +57,16 @@ is_named_call_p (const gcall *call, const char *funcname,
 
   return true;
 }
+
+/* FIXME.  */
+
+bool
+is_setjmp_call_p (const gimple *stmt)
+{
+  /* TODO: is there a less hacky way to check for "setjmp"?  */
+  if (const gcall *call = dyn_cast <const gcall *> (stmt))
+    if (is_named_call_p (call, "_setjmp", 1))
+      return true;
+
+  return false;
+}
diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h
index 4eb1f56..e52e223 100644
--- a/gcc/analyzer/analyzer.h
+++ b/gcc/analyzer/analyzer.h
@@ -26,6 +26,7 @@ class graphviz_out;
 extern bool is_named_call_p (const gcall *call, const char *funcname);
 extern bool is_named_call_p (const gcall *call, const char *funcname,
 			     unsigned int num_args);
+extern bool is_setjmp_call_p (const gimple *stmt);
 
 extern void register_analyzer_pass ();
 
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index f902ded..62dfc29 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -189,8 +189,6 @@ public:
 		  const call_superedge *sedge);
   const return_superedge *pop () { return m_return_edges.pop (); }
 
-  int get_stack_depth () const { return m_return_edges.length (); }
-
   int calc_recursion_depth () const;
 
   static int cmp (const call_string &a,
@@ -295,9 +293,15 @@ public:
       return NULL;
   }
 
+  /* Get the number of frames we expect at this program point.
+     This will be one more than the length of the call_string
+     (which stores the parent callsites), apart from the origin
+     node, which doesn't have any frames.  */
   int get_stack_depth () const
   {
-    return m_call_string.get_stack_depth ();
+    if (m_kind == PK_ORIGIN)
+      return 0;
+    return m_call_string.length () + 1;
   }
 
   const gimple *get_stmt () const;
@@ -830,6 +834,10 @@ class exploded_node : public dnode<eg_traits>
 		const superedge *succ,
 		program_point *next_point,
 		program_state *next_state) const;
+  void on_longjmp (exploded_graph &eg,
+		   const gcall *call,
+		   program_state *new_state,
+		   region_model_context *ctxt) const;
 
   void detect_leaks (exploded_graph &eg) const;
 
@@ -878,12 +886,16 @@ class exploded_edge : public dedge<eg_traits>
 {
  public:
   exploded_edge (exploded_node *src, exploded_node *dest,
-		 const superedge *sedge);
+		 const superedge *sedge, const gcall *setjmp_call);
   void dump_dot (graphviz_out *gv, const dump_args_t &args)
     const FINAL OVERRIDE;
 
   //private:
   const superedge *const m_sedge;
+
+  /* NULL for most edges; will be non-NULL for an unwind from a longjmp
+     to a setjmp.  */
+  const gcall *m_setjmp_call;
 };
 
 /* Statistics about aspects of an exploded_graph.  */
@@ -1335,7 +1347,8 @@ public:
   exploded_node *get_or_create_node (const program_point &point,
 				     const program_state &state);
   void add_edge (exploded_node *src, exploded_node *dest,
-		 const superedge *sedge);
+		 const superedge *sedge,
+		 const gcall *setjmp_call = NULL);
 
   per_program_point_data *
   get_or_create_per_program_point_data (const program_point &);
@@ -1489,6 +1502,8 @@ enum event_kind
   EK_END_CFG_EDGE,
   EK_CALL_EDGE,
   EK_RETURN_EDGE,
+  EK_SETJMP,
+  EK_REWIND_FROM_LONGJMP,
   EK_WARNING
 };
 
@@ -1515,6 +1530,10 @@ event_kind_to_string (enum event_kind ek)
       return "EK_CALL_EDGE";
     case EK_RETURN_EDGE:
       return "EK_RETURN_EDGE";
+    case EK_SETJMP:
+      return "EK_SETJMP";
+    case EK_REWIND_FROM_LONGJMP:
+      return "EK_REWIND_FROM_LONGJMP";
     case EK_WARNING:
       return "EK_WARNING";
     }
@@ -2031,6 +2050,67 @@ public:
   }
 };
 
+/* An event subclass for a setjmp call.  */
+
+class setjmp_event : public checker_event
+{
+public:
+  setjmp_event (location_t loc, tree fndecl, int depth)
+  : checker_event (EK_SETJMP, loc, fndecl, depth)
+  {
+  }
+
+  setjmp_event *clone () const FINAL OVERRIDE
+  {
+    return new setjmp_event (m_loc, m_fndecl, m_depth);
+  }
+
+  label_text get_desc (bool can_colorize) const FINAL OVERRIDE
+  {
+    return make_label_text (can_colorize,
+			    "%qs called here",
+			    "setjmp");
+  }
+
+private:
+  const exploded_edge *m_eedge;
+};
+
+/* An event subclass for rewinding from a longjmp to a setjmp,
+   showing the setjmp.  */
+
+class rewind_from_longjmp_event : public checker_event
+{
+public:
+  rewind_from_longjmp_event (const exploded_edge *eedge,
+			     location_t loc, tree fndecl, int depth)
+  : checker_event (EK_REWIND_FROM_LONGJMP, loc, fndecl, depth),
+    m_eedge (eedge)
+  {
+  }
+
+  rewind_from_longjmp_event *clone () const FINAL OVERRIDE
+  {
+    return new rewind_from_longjmp_event (m_eedge, m_loc, m_fndecl, m_depth);
+  }
+
+  label_text get_desc (bool can_colorize) const FINAL OVERRIDE
+  {
+    // FIXME: special-case interprocedural vs intraprocedural
+    /* TODO: ideally identify the ID of the setjmp_event, and
+       only show setjmp_events that matter.  */
+    return make_label_text (can_colorize,
+			    "rewinding from %qs in %qE to %qs in %qE",
+			    "longjmp", // FIXME
+			    m_eedge->m_src->get_function ()->decl,
+			    "setjmp", // FIXME
+			    m_eedge->m_dest->get_function ()->decl);
+  }
+
+private:
+  const exploded_edge *m_eedge;
+};
+
 /* Subclass of checker_event for use at the end of a path: a repeat of
    the warning message at the end of the path, at the point where the
    problem occurs.  */
@@ -2168,6 +2248,47 @@ public:
 
 ////////////////////////////////////////////////////////////////////////////
 
+/* class setjmp_svalue : public svalue.  */
+
+/* Compare the fields of this setjmp_svalue with OTHER, returning true
+   if they are equal.
+   For use by svalue::operator==.  */
+
+bool
+setjmp_svalue::compare_fields (const setjmp_svalue &other) const
+{
+  return m_enode == other.m_enode;
+}
+
+/* Implementation of svalue::hash vfunc for setjmp_svalue.  */
+
+hashval_t
+setjmp_svalue::hash () const
+{
+  return m_enode->m_index;
+}
+
+/* Get the index of the stored exploded_node.  */
+
+int
+setjmp_svalue::get_index () const
+{
+  return m_enode->m_index;
+}
+
+/* Implementation of svalue::print_details vfunc for setjmp_svalue.  */
+
+void
+setjmp_svalue::print_details (const region_model &model ATTRIBUTE_UNUSED,
+			      svalue_id this_sid ATTRIBUTE_UNUSED,
+			      pretty_printer *pp) const
+{
+  pp_printf (pp, "setjmp: EN: %i", m_enode->m_index);
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+
 /* class call_string.  */
 
 /* call_string's copy ctor.  */
@@ -2496,6 +2617,13 @@ program_point::on_edge (exploded_graph &eg,
   switch (succ->m_kind)
     {
     case SUPEREDGE_CFG_EDGE:
+      {
+	const cfg_superedge *cfg_sedge = (const cfg_superedge *)succ;
+
+	/* Reject abnormal edges; we special-case setjmp/longjmp.  */
+	if (cfg_sedge->get_flags () & EDGE_ABNORMAL)
+	  return false;
+      }
       break;
 
     case SUPEREDGE_CALL:
@@ -4333,7 +4461,16 @@ exploded_node::on_stmt (exploded_graph &eg,
 	  raise (SIGINT); // FIXME: cross-platform way to do this?
 	  //gcc_unreachable (); // FIXME: compare to a hard breakpoint
 	}
-      state->m_region_model->on_call (call, &ctxt);
+      else if (is_setjmp_call_p (stmt))
+	state->m_region_model->on_setjmp (call, this, &ctxt);
+      else if (is_named_call_p (call, "longjmp", 2))
+	{
+	  /* TODO: is there a less hacky way to check for "longjmp".  */
+	  on_longjmp (eg, call, state, &ctxt);
+	  return true;
+	}
+      else
+	state->m_region_model->on_call (call, &ctxt);
     }
 
   bool any_sm_changes = false;
@@ -4400,6 +4537,125 @@ exploded_node::on_edge (exploded_graph &eg,
   return true;
 }
 
+/* Verify that the stack at LONGJMP_POINT is still valid, given a call
+   to "setjmp" at SETJMP_POINT - the stack frame that "setjmp" was
+   called in must still be valid.
+
+   Caveat: this merely checks the call_strings in the points; it doesn't
+   detect the case where a frame returns and is then called again.  */
+
+static bool
+valid_longjmp_stack_p (const program_point &longjmp_point,
+		       const program_point &setjmp_point)
+{
+  if (longjmp_point.m_call_string.length ()
+      < setjmp_point.m_call_string.length ())
+    return false;
+
+  /* Check that the call strings match, up to the depth of the
+     setjmp point.  */
+  for (unsigned depth = 0; depth < setjmp_point.m_call_string.length ();
+       depth++)
+    if (longjmp_point.m_call_string[depth]
+	!= setjmp_point.m_call_string[depth])
+      return false;
+
+  return true;
+}
+
+/* A pending_diagnostic subclass for complaining about bad longjmps.
+   FIXME.  */
+
+class stale_longjmp_buffer
+  : public pending_diagnostic_subclass<dump_path_diagnostic>
+{
+public:
+  void emit (rich_location *richloc) FINAL OVERRIDE
+  {
+    // FIXME:
+    warning_at (richloc, 0, //OPT_Wstale_longjmp_buffer,
+		"%qs called after enclosing function of %qs has returned",
+		"longjmp", "setjmp");
+  }
+
+  const char *get_kind () const FINAL OVERRIDE
+  { return "stale_longjmp_buffer"; }
+
+  // FIXME
+  bool operator== (const dump_path_diagnostic &) const
+  {
+    return true;
+  }
+};
+
+
+/* FIXME.  */
+
+void
+exploded_node::on_longjmp (exploded_graph &eg,
+			   const gcall *longjmp_call,
+			   program_state *new_state,
+			   region_model_context *ctxt) const
+{
+  tree buf_ptr = gimple_call_arg (longjmp_call, 0);
+
+  region_model *new_region_model = new_state->m_region_model;
+  region_id buf_rid = new_region_model->deref_rvalue (buf_ptr, ctxt);
+  region *buf = new_region_model->get_region (buf_rid);
+  if (!buf)
+    return;
+
+  svalue_id buf_content_sid
+    = buf->get_value (*new_region_model, false, ctxt);
+  svalue *buf_content_sval = new_region_model->get_svalue (buf_content_sid);
+  if (!buf_content_sval)
+    return;
+  setjmp_svalue *setjmp_sval = buf_content_sval->dyn_cast_setjmp_svalue ();
+  if (!setjmp_sval)
+    return;
+
+  /* Build a custom enode and eedge for rewinding from the longjmp
+     call back to the setjmp.  */
+
+  const exploded_node *enode_origin = setjmp_sval->get_exploded_node ();
+  const program_point &origin_point = enode_origin->get_point ();
+
+  /* "origin_point" ought to be before the call to "setjmp".  */
+  gcc_assert (origin_point.m_kind == PK_BEFORE_STMT);
+
+  const gcall *setjmp_call = as_a <const gcall *> (origin_point.get_stmt ());
+
+  /* TODO: assert that it's the final stmt in its supernode.  */
+
+  const program_point &setjmp_point = origin_point;
+  const program_point &longjmp_point = get_point ();
+
+
+  /* Verify that the setjmp's call_stack hasn't been popped.  */
+  if (!valid_longjmp_stack_p (longjmp_point, setjmp_point))
+    {
+      ctxt->warn (new stale_longjmp_buffer ());
+      return;
+    }
+
+  gcc_assert (longjmp_point.get_stack_depth ()
+	      >= setjmp_point.get_stack_depth ());
+
+  new_region_model->on_longjmp (longjmp_call, setjmp_call,
+				setjmp_point.get_stack_depth (), ctxt);
+
+  program_point next_point
+    = program_point::after_supernode (origin_point.m_supernode,
+				      origin_point.m_call_string);
+
+  exploded_node *next = eg.get_or_create_node (next_point, *new_state);
+
+  /* Create custom exploded_edge for a longjmp.  */
+  if (next)
+    eg.add_edge (const_cast<exploded_node *> (this), next, NULL,
+		 setjmp_call);
+}
+
 // FIXME: this comment needs rewriting:
 /* Called when finding the successors of the supernode for a function exit
    basic block.
@@ -4423,9 +4679,9 @@ exploded_node::detect_leaks (exploded_graph &eg) const
 
   const program_state &old_state = get_state ();
 
-  if (get_point ().get_stack_depth () > 0)
+  if (get_point ().get_stack_depth () > 1)
     return;
-  gcc_assert (get_point ().get_stack_depth () == 0);
+  gcc_assert (get_point ().get_stack_depth () == 1);
 
   /* Work with a temporary copy of the state: pop the frame, and see
      what leaks (via purge_unused_svalues).  */
@@ -4436,7 +4692,7 @@ exploded_node::detect_leaks (exploded_graph &eg) const
   purge_stats stats;
   impl_region_model_context ctxt (eg, this,
 				  &old_state, &new_state, get_stmt ());
-  new_state.m_region_model->pop_frame (&stats, &ctxt);
+  new_state.m_region_model->pop_frame (true, &stats, &ctxt);
 }
 
 /* Dump the successors and predecessors of this enode to OUTF.  */
@@ -4473,8 +4729,9 @@ exploded_node::dump_succs_and_preds (FILE *outf) const
 /* exploded_edge's ctor.  */
 
 exploded_edge::exploded_edge (exploded_node *src, exploded_node *dest,
-			      const superedge *sedge)
-: dedge (src, dest), m_sedge (sedge)
+			      const superedge *sedge,
+			      const gcall *setjmp_call)
+: dedge (src, dest), m_sedge (sedge), m_setjmp_call (setjmp_call)
 {
 }
 
@@ -5086,6 +5343,16 @@ diagnostic_manager::add_events_for_eedge (const exploded_edge &eedge,
       add_state_change_events (eedge, sm, src_smap, dst_smap, emission_path);
     }
 
+  // FIXME:
+  if (eedge.m_setjmp_call)
+    {
+      emission_path->add_event
+	(new rewind_from_longjmp_event
+	 (&eedge, eedge.m_setjmp_call->location,
+	  dst_point.m_supernode->m_fun->decl,
+	  dst_stack_depth));
+    }
+
   /* Add events for superedges, function entries, and for statements.  */
   switch (dst_point.m_kind)
     {
@@ -5108,10 +5375,20 @@ diagnostic_manager::add_events_for_eedge (const exploded_edge &eedge,
 	}
       break;
     case PK_BEFORE_STMT:
-      emission_path->add_event
-	(new statement_event (dst_point.get_stmt (),
-			      dst_point.m_supernode->m_fun->decl,
-			      dst_stack_depth, dst_state));
+      {
+	const gimple *stmt = dst_point.get_stmt ();
+	/* TODO: is there a better way to test for this?  */
+	if (is_setjmp_call_p (stmt))
+	  emission_path->add_event
+	    (new setjmp_event (stmt->location,
+			       dst_point.m_supernode->m_fun->decl,
+			       dst_stack_depth));
+	else
+	  emission_path->add_event
+	    (new statement_event (stmt,
+				  dst_point.m_supernode->m_fun->decl,
+				  dst_stack_depth, dst_state));
+      }
       break;
     }
 }
@@ -5537,6 +5814,10 @@ diagnostic_manager::prune_path (checker_path *path,
 	  }
 	  break;
 
+	case EK_SETJMP:
+	case EK_REWIND_FROM_LONGJMP:
+	  break;
+
 	case EK_WARNING:
 	  break;
 	}
@@ -6087,9 +6368,10 @@ exploded_graph::get_or_create_node (const program_point &point,
 
 void
 exploded_graph::add_edge (exploded_node *src, exploded_node *dest,
-			  const superedge *sedge)
+			  const superedge *sedge,
+			  const gcall *setjmp_call)
 {
-  exploded_edge *e = new exploded_edge (src, dest, sedge);
+  exploded_edge *e = new exploded_edge (src, dest, sedge, setjmp_call);
   digraph::add_edge (e);
 }
 
@@ -6879,6 +7161,24 @@ exploded_path::feasible_p (logger *logger) const
 	      if (logger)
 		logger->log ("  pushing frame for %qD", fun->decl);
 	    }
+	  else if (eedge->m_setjmp_call)
+	    {
+	      /* Update state for the special-case of a rewind of a longjmp
+		 to a setjmp (which doesn't have a superedge, but does affect
+		 state).  */
+	      const gimple *last_stmt = src_point.m_supernode->get_last_stmt ();
+	      gcc_assert (last_stmt);
+	      const gcall *longjmp_call = as_a <const gcall *> (last_stmt);
+
+	      const program_point &longjmp_point = eedge->m_src->get_point ();
+	      const program_point &setjmp_point = eedge->m_dest->get_point ();
+
+	      gcc_assert (longjmp_point.get_stack_depth ()
+			  >= setjmp_point.get_stack_depth ());
+
+	      model.on_longjmp (longjmp_call, eedge->m_setjmp_call,
+				setjmp_point.get_stack_depth (), NULL);
+	    }
 	}
 
       /* Handle phi nodes on an edge leaving a PK_BEFORE_SUPERNODE (to
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index 6c8adf5..b83efff 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -258,6 +258,15 @@ svalue::operator== (const svalue &other) const
 	return this_sub.compare_fields (other_sub);
       }
       break;
+    case SK_SETJMP:
+      {
+	const setjmp_svalue &this_sub
+	  = (const setjmp_svalue &)*this;
+	const setjmp_svalue &other_sub
+	  = (const setjmp_svalue &)other;
+	return this_sub.compare_fields (other_sub);
+      }
+      break;
     }
 }
 
@@ -834,6 +843,9 @@ poisoned_svalue::print_details (const region_model &model ATTRIBUTE_UNUSED,
   pp_printf (pp, "poisoned: %s", poison_kind_to_str (m_kind));
 }
 
+/* class setjmp_svalue's implementation is in engine.cc, so that it can use
+   the declaration of exploded_node.  */
+
 ////////////////////////////////////////////////////////////////////////////
 
 /* class region and its various subclasses.  */
@@ -1003,8 +1015,6 @@ region::get_value (region_model &model, bool non_null,
       return unknown_sid;
     }
 
-  gcc_unreachable ();
-
   return svalue_id::null ();
 }
 
@@ -1813,7 +1823,7 @@ stack_region::get_current_frame_id () const
 
    Return the ID of any return value from the frame.
 
-   Purge all unused svalues, with the exception of any
+   If PURGE, then purge all unused svalues, with the exception of any
    return value for the frame, which is temporarily
    preserved in case no regions reference it, so it can
    be written into a region in the caller.
@@ -1821,7 +1831,7 @@ stack_region::get_current_frame_id () const
    Accumulate stats on purged entities into STATS.  */
 
 svalue_id
-stack_region::pop_frame (region_model *model, purge_stats *stats,
+stack_region::pop_frame (region_model *model, bool purge, purge_stats *stats,
 			 region_model_context *ctxt)
 {
   gcc_assert (m_frame_rids.length () > 0);
@@ -1845,7 +1855,8 @@ stack_region::pop_frame (region_model *model, purge_stats *stats,
 					ctxt ? ctxt->get_logger () : NULL);
 
   /* Delete unused svalues, but don't delete the return value.  */
-  model->purge_unused_svalues (stats, ctxt, &result_sid);
+  if (purge)
+    model->purge_unused_svalues (stats, ctxt, &result_sid);
 
   model->validate ();
 
@@ -2201,11 +2212,11 @@ root_region::get_current_frame_id (const region_model &model) const
    see the comment for stack_region::pop_frame.  */
 
 svalue_id
-root_region::pop_frame (region_model *model, purge_stats *out,
+root_region::pop_frame (region_model *model, bool purge, purge_stats *out,
 			region_model_context *ctxt)
 {
   stack_region *stack = model->get_region <stack_region> (m_stack_rid);
-  return stack->pop_frame (model, out, ctxt);
+  return stack->pop_frame (model, purge, out, ctxt);
 }
 
 /* Return the region_id of the stack region, creating it if doesn't
@@ -2879,6 +2890,11 @@ region_model::dump_summary_of_map (pretty_printer *pp,
 	case SK_UNKNOWN:
 	  unknown_keys.safe_push (key);
 	  break;
+	case SK_SETJMP:
+	  dump_separator (pp, is_first);
+	  pp_printf (pp, "setjmp: EN: %i",
+		     sval->dyn_cast_setjmp_svalue ()->get_index ());
+	  break;
 	}
     }
 
@@ -3381,6 +3397,70 @@ region_model::on_return (const greturn *return_stmt, region_model_context *ctxt)
     set_value (get_lvalue (lhs, ctxt), get_rvalue (rhs, ctxt));
 }
 
+/* Update this model for a call and return of "setjmp" at CALL within ENODE,
+   using CTXT to report any diagnostics.
+
+   This is for the initial direct invocation of setjmp (which returns 0),
+   as opposed to any second return due to longjmp.  */
+
+void
+region_model::on_setjmp (const gcall *call, const exploded_node *enode,
+			 region_model_context *ctxt)
+{
+  region_id buf_rid = deref_rvalue (gimple_call_arg (call, 0), ctxt);
+  region *buf = get_region (buf_rid);
+
+  /* Create a setjmp_svalue for ENODE and store it in BUF_RID's region.  */
+  if (buf)
+    {
+      svalue *sval = new setjmp_svalue (enode, buf->get_type ());
+      svalue_id new_sid = add_svalue (sval);
+      set_value (buf_rid, new_sid);
+    }
+
+  /* Direct calls to setjmp return 0.  */
+  if (tree lhs = gimple_call_lhs (call))
+    {
+      tree zero = build_int_cst (TREE_TYPE (lhs), 0);
+      svalue_id new_sid = add_svalue (new constant_svalue (zero));
+      region_id lhs_rid = get_lvalue (lhs, ctxt);
+      set_value (lhs_rid, new_sid);
+    }
+}
+
+/* FIXME.  */
+
+void
+region_model::on_longjmp (const gcall *longjmp_call, const gcall *setjmp_call,
+			  int setjmp_stack_depth,
+			  region_model_context *ctxt)
+{
+  /* Evaluate the val, using the frame of the "longjmp".  */
+  tree fake_retval = gimple_call_arg (longjmp_call, 1);
+  svalue_id fake_retval_sid = 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)
+    {
+      /* Don't purge unused svalues yet, as we're using fake_retval_sid.  */
+      pop_frame (false, NULL, ctxt);
+    }
+
+  gcc_assert (get_stack_depth () == setjmp_stack_depth);
+
+  /* Assign to LHS in new_state.  */
+  if (tree lhs = gimple_call_lhs (setjmp_call))
+    {
+      region_id lhs_rid = get_lvalue (lhs, ctxt);
+      set_value (lhs_rid, fake_retval_sid);
+      // FIXME: if 0, then 1
+    }
+
+  // FIXME: purge
+}
+
 /* Update this region_model for a phi stmt of the form
      LHS = PHI <...RHS...>.
    where RHS is for the appropriate edge.  */
@@ -3759,6 +3839,7 @@ region_model::deref_rvalue (tree ptr, region_model_context *ctxt)
 	goto create_symbolic_region;
       }
 
+    case SK_SETJMP:
     case SK_UNKNOWN:
       {
       create_symbolic_region:
@@ -4268,7 +4349,7 @@ region_model::update_for_return_superedge (const return_superedge &return_edge,
 					   region_model_context *ctxt)
 {
   purge_stats stats;
-  svalue_id result_sid = pop_frame (&stats, ctxt);
+  svalue_id result_sid = pop_frame (true, &stats, ctxt);
   // TODO: do something with the stats?
 
   // FIXME: result_sid could change meaning within pop_frame;
@@ -4451,9 +4532,10 @@ region_model::get_current_function () const
    see the comment for stack_region::pop_frame.  */
 
 svalue_id
-region_model::pop_frame (purge_stats *out, region_model_context *ctxt)
+region_model::pop_frame (bool purge, purge_stats *out,
+			 region_model_context *ctxt)
 {
-  return get_root_region ()->pop_frame (this, out, ctxt);
+  return get_root_region ()->pop_frame (this, purge, out, ctxt);
 }
 
 /* Get the number of frames in this region_model's stack.  */
@@ -4802,7 +4884,8 @@ region_model::purge_regions (const region_id_set &set_to_purge,
   while ((signed)m_regions.length () > next_used_new_rid)
     {
       delete m_regions.pop ();
-      stats->m_num_regions++;
+      if (stats)
+	stats->m_num_regions++;
     }
 }
 
@@ -5105,6 +5188,10 @@ model_merger::can_merge_values_p (svalue_id sid_a,
       {
       }
       break;
+    case SK_SETJMP:
+      {
+      }
+      break;
     }
 
   // TODO: move these to svalue subclasses
@@ -5772,7 +5859,7 @@ test_stack_frames ()
 
   /* Pop the "child_fn" frame from the stack.  */
   purge_stats purged;
-  model.pop_frame (&purged, &ctxt);
+  model.pop_frame (true, &purged, &ctxt);
 
   /* We should have purged the unknown values for x and y. */
   ASSERT_EQ (purged.m_num_svalues, 2);
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index bf37c3c..3c18e77 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -35,6 +35,7 @@ class svalue;
   class constant_svalue;
   class poisoned_svalue;
   class unknown_svalue;
+  class setjmp_svalue;
 class region;
 class region_model;
 class region_model_context;
@@ -42,6 +43,7 @@ struct model_merger;
 struct svalue_id_merger_mapping;
 struct canonicalization;
 class pending_diagnostic;
+class exploded_node;
 
 class constraint_manager;
 
@@ -451,7 +453,8 @@ enum svalue_kind
   SK_REGION,
   SK_CONSTANT,
   SK_POISONED,
-  SK_UNKNOWN
+  SK_UNKNOWN,
+  SK_SETJMP
 };
 
 /* An abstract base class representing a value held by a region of memory.  */
@@ -484,6 +487,7 @@ public:
   virtual constant_svalue *dyn_cast_constant_svalue () { return NULL; }
   virtual poisoned_svalue *dyn_cast_poisoned_svalue () { return NULL; }
   virtual unknown_svalue *dyn_cast_unknown_svalue () { return NULL; }
+  virtual setjmp_svalue *dyn_cast_setjmp_svalue () { return NULL; }
 
   virtual void remap_region_ids (const region_id_map &map);
 
@@ -713,6 +717,40 @@ is_a_helper <poisoned_svalue *>::test (svalue *sval)
   return sval->get_kind () == SK_POISONED;
 }
 
+/* Concrete subclass of svalue representing setjmp buffers, so that
+   longjmp can potentially "return" to an entirely different function.  */
+
+class setjmp_svalue : public svalue
+{
+public:
+  setjmp_svalue (const exploded_node *enode, tree type)
+  : svalue (type), m_enode (enode)
+  {}
+
+  bool compare_fields (const setjmp_svalue &other) const;
+
+  svalue *clone () const FINAL OVERRIDE
+  { return new setjmp_svalue (m_enode, get_type ()); }
+
+  enum svalue_kind get_kind () const FINAL OVERRIDE { return SK_SETJMP; }
+
+  hashval_t hash () const FINAL OVERRIDE;
+
+  setjmp_svalue *dyn_cast_setjmp_svalue () FINAL OVERRIDE { return this; }
+
+  int get_index () const;
+
+  const exploded_node *get_exploded_node () const { return m_enode; }
+
+ private:
+  void print_details (const region_model &model,
+		      svalue_id this_sid,
+		      pretty_printer *pp) const
+    FINAL OVERRIDE;
+
+  const exploded_node *m_enode;
+};
+
 ////////////////////////////////////////////////////////////////////////////
 
 /* An enum for discriminating between the different concrete subclasses
@@ -950,7 +988,7 @@ public:
 
   void push_frame (region_id frame_rid);
   region_id get_current_frame_id () const;
-  svalue_id pop_frame (region_model *model, purge_stats *stats,
+  svalue_id pop_frame (region_model *model, bool purge, purge_stats *stats,
 		       region_model_context *ctxt);
 
   void remap_region_ids (const region_id_map &map) FINAL OVERRIDE;
@@ -1033,7 +1071,7 @@ public:
   region_id push_frame (region_model *model, function *fun,
 			vec<svalue_id> *arg_sids);
   region_id get_current_frame_id (const region_model &model) const;
-  svalue_id pop_frame (region_model *model, purge_stats *stats,
+  svalue_id pop_frame (region_model *model, bool purge, purge_stats *stats,
 		       region_model_context *ctxt);
 
   region_id ensure_stack_region (region_model *model);
@@ -1144,6 +1182,10 @@ class region_model
   void on_assignment (const gassign *stmt, region_model_context *ctxt);
   void on_call (const gcall *stmt, region_model_context *ctxt);
   void on_return (const greturn *stmt, region_model_context *ctxt);
+  void on_setjmp (const gcall *stmt, const exploded_node *enode,
+		  region_model_context *ctxt);
+  void on_longjmp (const gcall *longjmp_call, const gcall *setjmp_call,
+		   int setjmp_stack_depth, region_model_context *ctxt);
 
   void update_for_phis (const supernode *snode,
 			const cfg_superedge *last_cfg_superedge,
@@ -1163,7 +1205,8 @@ class region_model
   region_id push_frame (function *fun, vec<svalue_id> *arg_sids);
   region_id get_current_frame_id () const;
   function * get_current_function () const;
-  svalue_id pop_frame (purge_stats *stats, region_model_context *ctxt);
+  svalue_id pop_frame (bool purge, purge_stats *stats,
+		       region_model_context *ctxt);
   int get_stack_depth () const;
 
   region_id get_globals_region_id () const;
diff --git a/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-setjmp-sj1.c b/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-setjmp-sj1.c
new file mode 100644
index 0000000..e10ed92
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-setjmp-sj1.c
@@ -0,0 +1,22 @@
+#include <setjmp.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+int main (void)
+{
+  jmp_buf env;
+  int i;
+
+  i = setjmp(env);
+  printf("i = %d\n", i);
+
+  if (i != 0)
+    {
+      __analyzer_dump_path ();
+      exit(0);
+    }
+
+  longjmp(env, 2);
+  printf("Does this line get printed?\n");
+
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-setjmp-wikipedia-1.c b/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-setjmp-wikipedia-1.c
index 1c6b9fd..5062b65 100644
--- a/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-setjmp-wikipedia-1.c
+++ b/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-setjmp-wikipedia-1.c
@@ -1,6 +1,5 @@
 /* Taken from https://en.wikipedia.org/wiki/Setjmp.h  */
-/* FIXME: implement and verify that control flow is sane.  */
-/* { todo-additional-options "-fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fdiagnostics-path-format=inline-events" } */
+/* { dg-additional-options "-fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fdiagnostics-path-format=inline-events" } */
 
 #include <stdio.h>
 #include <setjmp.h>
@@ -27,3 +26,76 @@ int main() {
 
     return 0;
 }
+
+/* { dg-begin-multiline-output "" }
+  'main': event 1
+     |
+     |   NN | int main() {
+     |      |     ^~~~
+     |      |     |
+     |      |     (1) entry to 'main'
+     |
+  'main': event 2
+     |
+     |   NN |     if (!setjmp(buf))
+     |      |          ^~~~~~
+     |      |          |
+     |      |          (2) 'setjmp' called here
+     |
+  'main': events 3-5
+     |
+     |   NN |     if (!setjmp(buf))
+     |      |        ^
+     |      |        |
+     |      |        (3) following 'true' branch...
+     |   NN |         first();                // when executed, setjmp returned 0
+     |      |         ~~~~~~~
+     |      |         |
+     |      |         (4) ...to here
+     |      |         (5) calling 'first' from 'main'
+     |
+     +--> 'first': events 6-7
+             |
+             |   NN | void first() {
+             |      |      ^~~~~
+             |      |      |
+             |      |      (6) entry to 'first'
+             |   NN |     second();
+             |      |     ~~~~~~~~
+             |      |     |
+             |      |     (7) calling 'second' from 'first'
+             |
+             +--> 'second': event 8
+                      |
+                      |   NN | void second() {
+                      |      |      ^~~~~~
+                      |      |      |
+                      |      |      (8) entry to 'second'
+                      |
+     <----------------+
+     |
+  'main': event 9
+     |
+     |   NN |     if (!setjmp(buf))
+     |      |          ^~~~~~
+     |      |          |
+     |      |          (9) rewinding from 'longjmp' in 'second' to 'setjmp' in 'main'
+     |
+  'main': events 10-12
+     |
+     |   NN |     if (!setjmp(buf))
+     |      |        ^
+     |      |        |
+     |      |        (10) following 'false' branch...
+     |......
+     |   NN |         printf("main\n");       // prints
+     |      |         ~~~~~~~~~~~~~~~~
+     |      |         |
+     |      |         (11) ...to here
+     |   NN | 
+     |   NN |     __analyzer_dump_path ();
+     |      |     ~~~~~~~~~~~~~~~~~~~~~~~
+     |      |     |
+     |      |     (12) here
+     |
+    { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-setjmp-wikipedia-2.c b/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-setjmp-wikipedia-2.c
new file mode 100644
index 0000000..cd617bb
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/NOT_FOR_UPSTREAM-setjmp-wikipedia-2.c
@@ -0,0 +1,189 @@
+/* Taken from https://en.wikipedia.org/wiki/Setjmp.h  */
+/* { dg-additional-options "-fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fdiagnostics-path-format=inline-events" } */
+#include <setjmp.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static void first();
+static void second();
+
+/* Use a file scoped static variable for the exception stack so we can access
+ * it anywhere within this translation unit. */
+static jmp_buf exception_env;
+static int exception_type;
+
+int main(void) {
+    char* volatile mem_buffer = NULL;
+
+    if (setjmp(exception_env)) {
+        // if we get here there was an exception
+        printf("first failed, exception type: %d\n", exception_type);
+    } else {
+        // Run code that may signal failure via longjmp.
+        puts("calling first");
+        first();
+
+        mem_buffer = malloc(300); // allocate a resource
+        printf("%s\n", strcpy(mem_buffer, "first succeeded")); // not reached
+    }
+
+    free(mem_buffer); // NULL can be passed to free, no operation is performed
+
+    __analyzer_dump_path (); /* { dg-message "path" } */
+
+    return 0;
+}
+
+static void first() {
+    jmp_buf my_env;
+
+    puts("entering first"); // reached
+
+    memcpy(my_env, exception_env, sizeof my_env);
+
+    switch (setjmp(exception_env)) {
+        case 3: // if we get here there was an exception.
+            puts("second failed, exception type: 3; remapping to type 1");
+            exception_type = 1;
+
+        default: // fall through
+            memcpy(exception_env, my_env, sizeof exception_env); // restore exception stack
+            longjmp(exception_env, exception_type); // continue handling the exception
+
+        case 0: // normal, desired operation
+            puts("calling second"); // reached 
+            second();
+            puts("second succeeded"); // not reached
+    }
+
+    memcpy(exception_env, my_env, sizeof exception_env); // restore exception stack
+
+    puts("leaving first"); // never reached
+}
+
+static void second() {
+    puts("entering second" ); // reached
+
+    exception_type = 3;
+    longjmp(exception_env, exception_type); // declare that the program has failed
+
+    puts("leaving second"); // not reached
+}
+
+/* { dg-begin-multiline-output "" }
+  'main': event 1
+     |
+     |   NN | int main(void) {
+     |      |     ^~~~
+     |      |     |
+     |      |     (1) entry to 'main'
+     |
+  'main': event 2
+     |
+     |   NN |     if (setjmp(exception_env)) {
+     |      |         ^~~~~~
+     |      |         |
+     |      |         (2) 'setjmp' called here
+     |
+  'main': events 3-5
+     |
+     |   NN |     if (setjmp(exception_env)) {
+     |      |        ^
+     |      |        |
+     |      |        (3) following 'false' branch...
+     |......
+     |   NN |         puts("calling first");
+     |      |         ~~~~~~~~~~~~~~~~~~~~~
+     |      |         |
+     |      |         (4) ...to here
+     |   NN |         first();
+     |      |         ~~~~~~~
+     |      |         |
+     |      |         (5) calling 'first' from 'main'
+     |
+     +--> 'first': event 6
+             |
+             |   NN | static void first() {
+             |      |             ^~~~~
+             |      |             |
+             |      |             (6) entry to 'first'
+             |
+          'first': event 7
+             |
+             |   NN |     switch (setjmp(exception_env)) {
+             |      |             ^~~~~~
+             |      |             |
+             |      |             (7) 'setjmp' called here
+             |
+          'first': events 8-10
+             |
+             |   NN |     switch (setjmp(exception_env)) {
+             |      |     ^~~~~~
+             |      |     |
+             |      |     (8) following 'case 0:' branch...
+             |......
+             |   NN |         case 0: // normal, desired operation
+             |      |         ~~~~
+             |      |         |
+             |      |         (9) ...to here
+             |   NN |             puts("calling second"); // reached
+             |   NN |             second();
+             |      |             ~~~~~~~~
+             |      |             |
+             |      |             (10) calling 'second' from 'first'
+             |
+             +--> 'second': event 11
+                      |
+                      |   NN | static void second() {
+                      |      |             ^~~~~~
+                      |      |             |
+                      |      |             (11) entry to 'second'
+                      |
+             <--------+
+             |
+          'first': event 12
+             |
+             |   NN |     switch (setjmp(exception_env)) {
+             |      |             ^~~~~~
+             |      |             |
+             |      |             (12) rewinding from 'longjmp' in 'second' to 'setjmp' in 'first'
+             |
+          'first': events 13-14
+             |
+             |   NN |     switch (setjmp(exception_env)) {
+             |      |     ^~~~~~
+             |      |     |
+             |      |     (13) following 'case 3:' branch...
+             |   NN |         case 3: // if we get here there was an exception.
+             |      |         ~~~~
+             |      |         |
+             |      |         (14) ...to here
+             |
+     <-------+
+     |
+  'main': event 15
+     |
+     |   NN |     if (setjmp(exception_env)) {
+     |      |         ^~~~~~
+     |      |         |
+     |      |         (15) rewinding from 'longjmp' in 'first' to 'setjmp' in 'main'
+     |
+  'main': events 16-18
+     |
+     |   NN |     if (setjmp(exception_env)) {
+     |      |        ^
+     |      |        |
+     |      |        (16) following 'true' branch...
+     |   NN |         // if we get here there was an exception
+     |   NN |         printf("first failed, exception type: %d\n", exception_type);
+     |      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+     |      |         |
+     |      |         (17) ...to here
+     |......
+     |   NN |     __analyzer_dump_path ();
+     |      |     ~~~~~~~~~~~~~~~~~~~~~~~
+     |      |     |
+     |      |     (18) here
+     |
+    { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/setjmp-2.c b/gcc/testsuite/gcc.dg/analyzer/setjmp-2.c
new file mode 100644
index 0000000..872ab3b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/setjmp-2.c
@@ -0,0 +1,89 @@
+/* { dg-additional-options "-fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fdiagnostics-path-format=inline-events" } */
+
+#include <setjmp.h>
+#include <stddef.h>
+
+extern void foo (int);
+
+void test_1 (void)
+{
+  setjmp (NULL);
+}
+
+void test_2 (void)
+{
+  jmp_buf env;
+  int i;
+
+  foo (0);
+
+  i = setjmp(env);
+
+  foo (1);
+
+  if (i != 0)
+    {
+      foo (2);
+      __analyzer_dump_path (); /* { dg-message "path" } */
+    }
+  else
+    longjmp (env, 1);
+
+  foo (3);
+}
+
+/* { dg-begin-multiline-output "" }
+  'test_2': event 1
+      |
+      |   NN |   i = setjmp(env);
+      |      |       ^~~~~~
+      |      |       |
+      |      |       (1) 'setjmp' called here
+      |
+  'test_2': events 2-3
+      |
+      |   NN |   if (i != 0)
+      |      |      ^
+      |      |      |
+      |      |      (2) following 'false' branch...
+      |......
+      |   NN |     longjmp (env, 1);
+      |      |     ~~~~~~~~~~~~~~~~
+      |      |     |
+      |      |     (3) ...to here
+      |
+  'test_2': event 4
+      |
+      |   NN |   i = setjmp(env);
+      |      |       ^~~~~~
+      |      |       |
+      |      |       (4) rewinding from 'longjmp' in 'test_2' to 'setjmp' in 'test_2'
+      |
+  'test_2': events 5-7
+      |
+      |   NN |   if (i != 0)
+      |      |      ^
+      |      |      |
+      |      |      (5) following 'true' branch...
+      |   NN |     {
+      |   NN |       foo (2);
+      |      |       ~~~~~~~
+      |      |       |
+      |      |       (6) ...to here
+      |   NN |       __analyzer_dump_path ();
+      |      |       ~~~~~~~~~~~~~~~~~~~~~~~
+      |      |       |
+      |      |       (7) here
+      |
+    { dg-end-multiline-output "" } */
+
+void test_3 (void)
+{
+  longjmp (NULL, 0);
+}
+
+void test_4 (void)
+{
+  jmp_buf env;
+  longjmp (NULL, 1);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/setjmp-3.c b/gcc/testsuite/gcc.dg/analyzer/setjmp-3.c
new file mode 100644
index 0000000..4b7de4e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/setjmp-3.c
@@ -0,0 +1,99 @@
+/* { dg-additional-options "-fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fdiagnostics-path-format=inline-events" } */
+
+#include <setjmp.h>
+#include <stddef.h>
+
+extern void foo (int);
+
+static jmp_buf env;
+
+static void inner (void)
+{
+  longjmp (env, 1);
+}
+
+void outer (void)
+{
+  int i;
+
+  foo (0);
+
+  i = setjmp(env);
+
+  if (i != 0)
+    {
+      foo (2);
+      __analyzer_dump_path (); /* { dg-message "path" } */
+    }
+  else
+    {
+      foo (1);
+      inner ();
+    }
+  foo (3);
+}
+
+/* { dg-begin-multiline-output "" }
+  'outer': event 1
+     |
+     |   NN | void outer (void)
+     |      |      ^~~~~
+     |      |      |
+     |      |      (1) entry to 'outer'
+     |
+  'outer': event 2
+     |
+     |   NN |   i = setjmp(env);
+     |      |       ^~~~~~
+     |      |       |
+     |      |       (2) 'setjmp' called here
+     |
+  'outer': events 3-5
+     |
+     |   NN |   if (i != 0)
+     |      |      ^
+     |      |      |
+     |      |      (3) following 'false' branch...
+     |......
+     |   NN |       foo (1);
+     |      |       ~~~~~~~
+     |      |       |
+     |      |       (4) ...to here
+     |   NN |       inner ();
+     |      |       ~~~~~~~~
+     |      |       |
+     |      |       (5) calling 'inner' from 'outer'
+     |
+     +--> 'inner': event 6
+             |
+             |   NN | static void inner (void)
+             |      |             ^~~~~
+             |      |             |
+             |      |             (6) entry to 'inner'
+             |
+     <-------+
+     |
+  'outer': event 7
+     |
+     |   NN |   i = setjmp(env);
+     |      |       ^~~~~~
+     |      |       |
+     |      |       (7) rewinding from 'longjmp' in 'inner' to 'setjmp' in 'outer'
+     |
+  'outer': events 8-10
+     |
+     |   NN |   if (i != 0)
+     |      |      ^
+     |      |      |
+     |      |      (8) following 'true' branch...
+     |   NN |     {
+     |   NN |       foo (2);
+     |      |       ~~~~~~~
+     |      |       |
+     |      |       (9) ...to here
+     |   NN |       __analyzer_dump_path ();
+     |      |       ~~~~~~~~~~~~~~~~~~~~~~~
+     |      |       |
+     |      |       (10) here
+     |
+    { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/setjmp-4.c b/gcc/testsuite/gcc.dg/analyzer/setjmp-4.c
new file mode 100644
index 0000000..4df6b90
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/setjmp-4.c
@@ -0,0 +1,102 @@
+/* { dg-additional-options "-fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fdiagnostics-path-format=inline-events" } */
+
+#include <setjmp.h>
+
+extern int foo (int);
+static jmp_buf buf;
+
+void inner (int x)
+{
+  foo (x);
+  longjmp (buf, 1);
+  foo (x);
+}
+
+void outer (int y)
+{
+  foo (y);
+  inner (y);
+  foo (y);
+}
+
+int main (void)
+{   
+  if (!setjmp(buf))
+    outer (42);
+  else
+    __analyzer_dump_path (); /* { dg-message "path" } */
+  return 0;
+}
+
+/* TODO: why doesn't it show the longjmp in the path?  */
+
+/* { dg-begin-multiline-output "" }
+  'main': event 1
+     |
+     |   NN | int main (void)
+     |      |     ^~~~
+     |      |     |
+     |      |     (1) entry to 'main'
+     |
+  'main': event 2
+     |
+     |   NN |   if (!setjmp(buf))
+     |      |        ^~~~~~
+     |      |        |
+     |      |        (2) 'setjmp' called here
+     |
+  'main': events 3-5
+     |
+     |   NN |   if (!setjmp(buf))
+     |      |      ^
+     |      |      |
+     |      |      (3) following 'true' branch...
+     |   NN |     outer (42);
+     |      |     ~~~~~~~~~~
+     |      |     |
+     |      |     (4) ...to here
+     |      |     (5) calling 'outer' from 'main'
+     |
+     +--> 'outer': events 6-7
+             |
+             |   NN | void outer (int y)
+             |      |      ^~~~~
+             |      |      |
+             |      |      (6) entry to 'outer'
+             |......
+             |   NN |   inner (y);
+             |      |   ~~~~~~~~~
+             |      |   |
+             |      |   (7) calling 'inner' from 'outer'
+             |
+             +--> 'inner': event 8
+                     |
+                     |   NN | void inner (int x)
+                     |      |      ^~~~~
+                     |      |      |
+                     |      |      (8) entry to 'inner'
+                     |
+     <---------------+
+     |
+  'main': event 9
+     |
+     |   NN |   if (!setjmp(buf))
+     |      |        ^~~~~~
+     |      |        |
+     |      |        (9) rewinding from 'longjmp' in 'inner' to 'setjmp' in 'main'
+     |
+  'main': events 10-12
+     |
+     |   NN |   if (!setjmp(buf))
+     |      |      ^
+     |      |      |
+     |      |      (10) following 'false' branch...
+     |......
+     |   NN |     __analyzer_dump_path ();
+     |      |     ~~~~~~~~~~~~~~~~~~~~~~~
+     |      |     |
+     |      |     (11) ...to here
+     |      |     (12) here
+     |
+    { dg-end-multiline-output "" } */
+
diff --git a/gcc/testsuite/gcc.dg/analyzer/setjmp-5.c b/gcc/testsuite/gcc.dg/analyzer/setjmp-5.c
new file mode 100644
index 0000000..2180f47
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/setjmp-5.c
@@ -0,0 +1,63 @@
+/* { dg-additional-options "-fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fdiagnostics-path-format=inline-events" } */
+
+#include <setjmp.h>
+#include <stddef.h>
+
+static jmp_buf env;
+
+static void inner (void)
+{
+  setjmp (env);
+}
+
+void outer (void)
+{
+  int i;
+
+  inner ();
+
+  longjmp (env, 42); /* { dg-warning "'longjmp' called after enclosing function of 'setjmp' has returned" } */
+}
+
+/* { dg-begin-multiline-output "" }
+  'outer': events 1-2
+     |
+     |   NN | void outer (void)
+     |      |      ^~~~~
+     |      |      |
+     |      |      (1) entry to 'outer'
+     |......
+     |   NN |   inner ();
+     |      |   ~~~~~~~~
+     |      |   |
+     |      |   (2) calling 'inner' from 'outer'
+     |
+     +--> 'inner': event 3
+             |
+             |   NN | static void inner (void)
+             |      |             ^~~~~
+             |      |             |
+             |      |             (3) entry to 'inner'
+             |
+          'inner': event 4
+             |
+             |   NN |   setjmp (env);
+             |      |   ^~~~~~
+             |      |   |
+             |      |   (4) 'setjmp' called here
+             |
+     <-------+
+     |
+  'outer': events 5-6
+     |
+     |   NN |   inner ();
+     |      |   ^~~~~~~~
+     |      |   |
+     |      |   (5) returning to 'outer' from 'inner'
+     |   NN | 
+     |   NN |   longjmp (env, 42);
+     |      |   ~~~~~~~~~~~~~~~~~
+     |      |   |
+     |      |   (6) here
+     |
+    { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/setjmp-6.c b/gcc/testsuite/gcc.dg/analyzer/setjmp-6.c
new file mode 100644
index 0000000..25e7001
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/setjmp-6.c
@@ -0,0 +1,31 @@
+#include <setjmp.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+extern void foo (int);
+
+static jmp_buf env;
+
+static void inner (void)
+{
+  void *ptr = malloc (1024); /* TODO: complain about a leak here.  */
+  longjmp (env, 1);
+  free (ptr);
+}
+
+void outer (void)
+{
+  int i;
+
+  foo (0);
+
+  i = setjmp(env);
+
+  if (i == 0)
+    {
+      foo (1);
+      inner ();
+    }
+
+  foo (3);
+}
-- 
1.8.5.3

