From 5ec376fbb0883b260f1f3155cfbf46a7f5df366f Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Mon, 23 Mar 2026 16:51:32 -0400
Subject: [PATCH 26/98] analyzer: split setjmp/longjmp implementation to their
 own file

No functional change intended.

gcc/ChangeLog:
	* Makefile.in (ANALYZER_OBJS): Add analyzer/setjmp-longjmp.o.

gcc/analyzer/ChangeLog:
	* analyzer.cc (is_setjmp_call_p): Move to setjmp-longjmp.cc.
	(is_longjmp_call_p): Likewise.
	* engine.cc (setjmp_record::cmp): Likewise.
	(setjmp_svalue::accept): Likewise.
	(setjmp_svalue::dump_to_pp): Likewise.
	(setjmp_svalue::print_dump_widget_label): Likewise.
	(setjmp_svalue::add_dump_widget_children): Likewise.
	(setjmp_svalue::get_enode_index): Likewise.
	(valid_longjmp_stack_p): Likewise.
	(class stale_jmp_buf): Likewise.
	(exploded_node::on_longjmp): Likewise.
	(rewind_info_t::update_model): Likewise.
	(rewind_info_t::add_events_to_path): Likewise.
	* region-model.cc (region_model::on_setjmp): Likewise.
	(region_model::on_longjmp): Likewise.
	* setjmp-longjmp.cc: New file, made from the above material.
	* svalue.cc: Update comment.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>

FIXME: followup to 'analyzer: split setjmp/longjmp implementation to their own file'
---
 gcc/Makefile.in                |   1 +
 gcc/analyzer/analyzer.cc       |  29 --
 gcc/analyzer/engine.cc         | 346 -----------------------
 gcc/analyzer/region-model.cc   |  89 ------
 gcc/analyzer/setjmp-longjmp.cc | 499 +++++++++++++++++++++++++++++++++
 gcc/analyzer/svalue.cc         |   3 +-
 6 files changed, 501 insertions(+), 466 deletions(-)
 create mode 100644 gcc/analyzer/setjmp-longjmp.cc

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 170f065e2431..8ff8dde2ab06 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1373,6 +1373,7 @@ ANALYZER_OBJS = \
 	analyzer/region-model-asm.o \
 	analyzer/region-model-manager.o \
 	analyzer/region-model-reachability.o \
+	analyzer/setjmp-longjmp.o \
 	analyzer/shift-diagnostics.o \
 	analyzer/sm.o \
 	analyzer/sm-file.o \
diff --git a/gcc/analyzer/analyzer.cc b/gcc/analyzer/analyzer.cc
index 1a067c76764d..cf9d822629dc 100644
--- a/gcc/analyzer/analyzer.cc
+++ b/gcc/analyzer/analyzer.cc
@@ -420,35 +420,6 @@ is_std_named_call_p (const_tree fndecl, const char *funcname,
   return true;
 }
 
-/* Return true if stmt is a setjmp or sigsetjmp call.  */
-
-bool
-is_setjmp_call_p (const gcall &call)
-{
-  if (is_special_named_call_p (call, "setjmp", 1)
-      || is_special_named_call_p (call, "sigsetjmp", 2))
-    /* region_model::on_setjmp requires a pointer.  */
-    if (POINTER_TYPE_P (TREE_TYPE (gimple_call_arg (&call, 0))))
-      return true;
-
-  return false;
-}
-
-/* Return true if stmt is a longjmp or siglongjmp call.  */
-
-bool
-is_longjmp_call_p (const gcall &call)
-{
-  if (is_special_named_call_p (call, "longjmp", 2)
-      || is_special_named_call_p (call, "siglongjmp", 2))
-    /* exploded_node::on_longjmp requires a pointer for the initial
-       argument.  */
-    if (POINTER_TYPE_P (TREE_TYPE (gimple_call_arg (&call, 0))))
-      return true;
-
-  return false;
-}
-
 bool
 is_cxa_throw_p (const gcall &call)
 {
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index a4d870bd3d24..2a03a0b2e729 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -209,66 +209,6 @@ impl_region_model_context::terminate_path ()
     return m_path_ctxt->terminate_path ();
 }
 
-/* struct setjmp_record.  */
-
-int
-setjmp_record::cmp (const setjmp_record &rec1, const setjmp_record &rec2)
-{
-  if (int cmp_enode = rec1.m_enode->m_index - rec2.m_enode->m_index)
-    return cmp_enode;
-  gcc_assert (&rec1 == &rec2);
-  return 0;
-}
-
-/* class setjmp_svalue : public svalue.  */
-
-/* Implementation of svalue::accept vfunc for setjmp_svalue.  */
-
-void
-setjmp_svalue::accept (visitor *v) const
-{
-  v->visit_setjmp_svalue (this);
-}
-
-/* Implementation of svalue::dump_to_pp vfunc for setjmp_svalue.  */
-
-void
-setjmp_svalue::dump_to_pp (pretty_printer *pp, bool simple) const
-{
-  if (simple)
-    pp_printf (pp, "SETJMP(EN: %i)", get_enode_index ());
-  else
-    pp_printf (pp, "setjmp_svalue(EN%i)", get_enode_index ());
-}
-
-/* Implementation of svalue::print_dump_widget_label vfunc for
-   setjmp_svalue.  */
-
-void
-setjmp_svalue::print_dump_widget_label (pretty_printer *pp) const
-{
-  pp_printf (pp, "setjmp_svalue(EN: %i)", get_enode_index ());
-}
-
-/* Implementation of svalue::add_dump_widget_children vfunc for
-   setjmp_svalue.  */
-
-void
-setjmp_svalue::
-add_dump_widget_children (text_art::tree_widget &,
-			  const text_art::dump_widget_info &) const
-{
-  /* No children.  */
-}
-
-/* Get the index of the stored exploded_node.  */
-
-int
-setjmp_svalue::get_enode_index () const
-{
-  return m_setjmp_record.m_enode->m_index;
-}
-
 bool
 impl_region_model_context::
 get_state_map_by_name (const char *name,
@@ -1302,234 +1242,6 @@ fndecl_has_gimple_body_p (tree fndecl)
 
 namespace ana {
 
-/* 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)
-{
-  const call_string &cs_at_longjmp = longjmp_point.get_call_string ();
-  const call_string &cs_at_setjmp = setjmp_point.get_call_string ();
-
-  if (cs_at_longjmp.length () < cs_at_setjmp.length ())
-    return false;
-
-  /* Check that the call strings match, up to the depth of the
-     setjmp point.  */
-  for (unsigned depth = 0; depth < cs_at_setjmp.length (); depth++)
-    if (cs_at_longjmp[depth] != cs_at_setjmp[depth])
-      return false;
-
-  return true;
-}
-
-/* A pending_diagnostic subclass for complaining about bad longjmps,
-   where the enclosing function of the "setjmp" has returned (and thus
-   the stack frame no longer exists).  */
-
-class stale_jmp_buf : public pending_diagnostic_subclass<stale_jmp_buf>
-{
-public:
-  stale_jmp_buf (const gcall &setjmp_call, const gcall &longjmp_call,
-		 const program_point &setjmp_point)
-  : m_setjmp_call (setjmp_call), m_longjmp_call (longjmp_call),
-    m_setjmp_point (setjmp_point), m_stack_pop_event (nullptr)
-  {}
-
-  int get_controlling_option () const final override
-  {
-    return OPT_Wanalyzer_stale_setjmp_buffer;
-  }
-
-  bool emit (diagnostic_emission_context &ctxt) final override
-  {
-    return ctxt.warn ("%qs called after enclosing function of %qs has returned",
-		      get_user_facing_name (m_longjmp_call),
-		      get_user_facing_name (m_setjmp_call));
-  }
-
-  const char *get_kind () const final override
-  { return "stale_jmp_buf"; }
-
-  bool operator== (const stale_jmp_buf &other) const
-  {
-    return (&m_setjmp_call == &other.m_setjmp_call
-	    && &m_longjmp_call == &other.m_longjmp_call);
-  }
-
-  bool
-  maybe_add_custom_events_for_eedge (const exploded_edge &eedge,
-				     checker_path *emission_path)
-    final override
-  {
-    /* Detect exactly when the stack first becomes invalid,
-       and issue an event then.  */
-    if (m_stack_pop_event)
-      return false;
-    const exploded_node *src_node = eedge.m_src;
-    const program_point &src_point = src_node->get_point ();
-    const exploded_node *dst_node = eedge.m_dest;
-    const program_point &dst_point = dst_node->get_point ();
-    if (valid_longjmp_stack_p (src_point, m_setjmp_point)
-	&& !valid_longjmp_stack_p (dst_point, m_setjmp_point))
-      {
-	/* Compare with diagnostic_manager::add_events_for_superedge.  */
-	const int src_stack_depth = src_point.get_stack_depth ();
-	m_stack_pop_event = new precanned_custom_event
-	  (event_loc_info (src_point.get_location (),
-			   src_point.get_fndecl (),
-			   src_stack_depth),
-	   "stack frame is popped here, invalidating saved environment");
-	emission_path->add_event
-	  (std::unique_ptr<custom_event> (m_stack_pop_event));
-	return false;
-      }
-    return false;
-  }
-
-  bool
-  describe_final_event (pretty_printer &pp,
-			const evdesc::final_event &) final override
-  {
-    if (m_stack_pop_event)
-      pp_printf (&pp,
-		 "%qs called after enclosing function of %qs returned at %@",
-		 get_user_facing_name (m_longjmp_call),
-		 get_user_facing_name (m_setjmp_call),
-		 m_stack_pop_event->get_id_ptr ());
-    else
-      pp_printf (&pp,
-		 "%qs called after enclosing function of %qs has returned",
-		 get_user_facing_name (m_longjmp_call),
-		 get_user_facing_name (m_setjmp_call));
-    return true;
-  }
-
-
-private:
-  const gcall &m_setjmp_call;
-  const gcall &m_longjmp_call;
-  program_point m_setjmp_point;
-  custom_event *m_stack_pop_event;
-};
-
-/* Handle LONGJMP_CALL, a call to longjmp or siglongjmp.
-
-   Attempt to locate where setjmp/sigsetjmp was called on the jmp_buf and build
-   an exploded_node and exploded_edge to it representing a rewind to that frame,
-   handling the various kinds of failure that can occur.  */
-
-void
-exploded_node::on_longjmp (exploded_graph &eg,
-			   const gcall &longjmp_call,
-			   program_state *new_state,
-			   region_model_context *ctxt)
-{
-  tree buf_ptr = gimple_call_arg (&longjmp_call, 0);
-  gcc_assert (POINTER_TYPE_P (TREE_TYPE (buf_ptr)));
-
-  region_model *new_region_model = new_state->m_region_model;
-  const svalue *buf_ptr_sval = new_region_model->get_rvalue (buf_ptr, ctxt);
-  const region *buf = new_region_model->deref_rvalue (buf_ptr_sval, buf_ptr,
-						       ctxt);
-
-  const svalue *buf_content_sval
-    = new_region_model->get_store_value (buf, ctxt);
-  const setjmp_svalue *setjmp_sval
-    = buf_content_sval->dyn_cast_setjmp_svalue ();
-  if (!setjmp_sval)
-    return;
-
-  const setjmp_record tmp_setjmp_record = setjmp_sval->get_setjmp_record ();
-
-  /* Build a custom enode and eedge for rewinding from the longjmp/siglongjmp
-     call back to the setjmp/sigsetjmp.  */
-  rewind_info_t rewind_info (tmp_setjmp_record, longjmp_call);
-
-  const gcall &setjmp_call = rewind_info.get_setjmp_call ();
-  const program_point point_before_setjmp = rewind_info.get_point_before_setjmp ();
-  const program_point point_after_setjmp = rewind_info.get_point_after_setjmp ();
-
-  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, point_after_setjmp))
-    {
-      ctxt->warn (std::make_unique<stale_jmp_buf> (setjmp_call,
-						   longjmp_call,
-						   point_before_setjmp));
-      return;
-    }
-
-  gcc_assert (longjmp_point.get_stack_depth ()
-	      >= point_after_setjmp.get_stack_depth ());
-
-  /* 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 longjmp is rewinding to.  */
-
-  diagnostic_manager *dm = &eg.get_diagnostic_manager ();
-  unsigned prev_num_diagnostics = dm->get_num_diagnostics ();
-
-  new_region_model->on_longjmp (longjmp_call, setjmp_call,
-				point_after_setjmp.get_stack_depth (), ctxt);
-
-  /* Detect leaks in the new state relative to the old state.  */
-  program_state::detect_leaks (get_state (), *new_state, nullptr,
-				eg.get_ext_state (), ctxt);
-  exploded_node *next
-    = eg.get_or_create_node (point_after_setjmp, *new_state, this);
-
-  /* Create custom exploded_edge for a longjmp.  */
-  if (next)
-    {
-      exploded_edge *eedge
-	= eg.add_edge (const_cast<exploded_node *> (this), next, nullptr, true,
-		       std::make_unique<rewind_info_t> (tmp_setjmp_record,
-							longjmp_call));
-
-      /* 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;
-	}
-    }
-}
-
 /* Subclass of call_info for exploded edges that express
    a throw or rethrow of an exception (actually a call
    to __cxa_throw or __cxa_rethrow).  */
@@ -2009,64 +1721,6 @@ interprocedural_return::add_events_to_path (checker_path *emission_path,
 			dst_point.get_stack_depth ())));
 }
 
-/* class rewind_info_t : public custom_edge_info.  */
-
-/* Implementation of custom_edge_info::update_model vfunc
-   for rewind_info_t.
-
-   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).  */
-
-bool
-rewind_info_t::update_model (region_model *model,
-			     const exploded_edge *eedge,
-			     region_model_context *) const
-{
-  gcc_assert (eedge);
-  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 (get_longjmp_call (),
-		     get_setjmp_call (),
-		     setjmp_point.get_stack_depth (), nullptr);
-  return true;
-}
-
-/* Implementation of custom_edge_info::add_events_to_path vfunc
-   for rewind_info_t.  */
-
-void
-rewind_info_t::add_events_to_path (checker_path *emission_path,
-				   const exploded_edge &eedge,
-				   pending_diagnostic &) const
-{
-  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 ();
-
-  emission_path->add_event
-    (std::make_unique<rewind_from_longjmp_event>
-       (&eedge,
-	event_loc_info (get_longjmp_call ().location,
-			src_point.get_fndecl (),
-			src_stack_depth),
-	this));
-  emission_path->add_event
-    (std::make_unique<rewind_to_setjmp_event>
-       (&eedge,
-	event_loc_info (get_setjmp_call ().location,
-			dst_point.get_fndecl (),
-			dst_stack_depth),
-	this));
-}
-
 /* class exploded_edge : public dedge<eg_traits>.  */
 
 /* exploded_edge's ctor.  */
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index 14dfa230eb10..e4bafebbaa49 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -2646,95 +2646,6 @@ region_model::on_return (const greturn *return_stmt, region_model_context *ctxt)
     }
 }
 
-/* Update this model for a call and return of setjmp/sigsetjmp at CALL within
-   ENODE, using CTXT to report any diagnostics.
-
-   This is for the initial direct invocation of setjmp/sigsetjmp (which returns
-   0), as opposed to any second return due to longjmp/sigsetjmp.  */
-
-void
-region_model::on_setjmp (const gcall &call,
-			 const exploded_node &enode,
-			 const superedge &sedge,
-			 region_model_context *ctxt)
-{
-  const svalue *buf_ptr = get_rvalue (gimple_call_arg (&call, 0), ctxt);
-  const region *buf_reg = deref_rvalue (buf_ptr, gimple_call_arg (&call, 0),
-					 ctxt);
-
-  /* Create a setjmp_svalue for this call and store it in BUF_REG's
-     region.  */
-  if (buf_reg)
-    {
-      setjmp_record r (&enode, &sedge, call);
-      const svalue *sval
-	= m_mgr->get_or_create_setjmp_svalue (r, buf_reg->get_type ());
-      set_value (buf_reg, sval, ctxt);
-    }
-
-  /* Direct calls to setjmp return 0.  */
-  if (tree lhs = gimple_call_lhs (&call))
-    {
-      const svalue *new_sval
-	= m_mgr->get_or_create_int_cst (TREE_TYPE (lhs), 0);
-      const region *lhs_reg = get_lvalue (lhs, ctxt);
-      set_value (lhs_reg, new_sval, ctxt);
-    }
-}
-
-/* Update this region_model for rewinding from a "longjmp" at LONGJMP_CALL
-   to a "setjmp" at SETJMP_CALL where the final stack depth should be
-   SETJMP_STACK_DEPTH.  Pop any stack frames.  Leak detection is *not*
-   done, and should be done by the caller.  */
-
-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);
-  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 (nullptr, nullptr, ctxt, nullptr, false);
-
-  gcc_assert (get_stack_depth () == setjmp_stack_depth);
-
-  /* Assign to LHS of "setjmp" in new_state.  */
-  if (tree lhs = gimple_call_lhs (&setjmp_call))
-    {
-      /* Passing 0 as the val to longjmp leads to setjmp returning 1.  */
-      const svalue *zero_sval
-	= m_mgr->get_or_create_int_cst (TREE_TYPE (fake_retval), 0);
-      tristate eq_zero = eval_condition (fake_retval_sval, EQ_EXPR, zero_sval);
-      /* If we have 0, use 1.  */
-      if (eq_zero.is_true ())
-	{
-	  const svalue *one_sval
-	    = m_mgr->get_or_create_int_cst (TREE_TYPE (fake_retval), 1);
-	  fake_retval_sval = one_sval;
-	}
-      else
-	{
-	  /* Otherwise note that the value is nonzero.  */
-	  m_constraints->add_constraint (fake_retval_sval, NE_EXPR, zero_sval);
-	}
-
-      /* Decorate the return value from setjmp as being unmergeable,
-	 so that we don't attempt to merge states with it as zero
-	 with states in which it's nonzero, leading to a clean distinction
-	 in the exploded_graph betweeen the first return and the second
-	 return.  */
-      fake_retval_sval = m_mgr->get_or_create_unmergeable (fake_retval_sval);
-
-      const region *lhs_reg = get_lvalue (lhs, ctxt);
-      set_value (lhs_reg, fake_retval_sval, ctxt);
-    }
-}
-
 /* Implementation of region_model::get_lvalue; the latter adds type-checking.
 
    Get the id of the region for PV within this region_model,
diff --git a/gcc/analyzer/setjmp-longjmp.cc b/gcc/analyzer/setjmp-longjmp.cc
new file mode 100644
index 000000000000..711dfb3e8d0b
--- /dev/null
+++ b/gcc/analyzer/setjmp-longjmp.cc
@@ -0,0 +1,499 @@
+/* setjmp/longjmp handling
+   Copyright (C) 2019-2026 Free Software Foundation, Inc.
+   Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#include "analyzer/common.h"
+
+#include "analyzer/region-model.h"
+#include "analyzer/checker-path.h"
+#include "analyzer/checker-event.h"
+#include "analyzer/exploded-graph.h"
+#include "analyzer/constraint-manager.h"
+
+#if ENABLE_ANALYZER
+
+/* Return true if stmt is a setjmp or sigsetjmp call.  */
+
+bool
+is_setjmp_call_p (const gcall &call)
+{
+  if (is_special_named_call_p (call, "setjmp", 1)
+      || is_special_named_call_p (call, "sigsetjmp", 2))
+    /* region_model::on_setjmp requires a pointer.  */
+    if (POINTER_TYPE_P (TREE_TYPE (gimple_call_arg (&call, 0))))
+      return true;
+
+  return false;
+}
+
+/* Return true if stmt is a longjmp or siglongjmp call.  */
+
+bool
+is_longjmp_call_p (const gcall &call)
+{
+  if (is_special_named_call_p (call, "longjmp", 2)
+      || is_special_named_call_p (call, "siglongjmp", 2))
+    /* exploded_node::on_longjmp requires a pointer for the initial
+       argument.  */
+    if (POINTER_TYPE_P (TREE_TYPE (gimple_call_arg (&call, 0))))
+      return true;
+
+  return false;
+}
+
+namespace ana {
+
+/* struct setjmp_record.  */
+
+int
+setjmp_record::cmp (const setjmp_record &rec1, const setjmp_record &rec2)
+{
+  if (int cmp_enode = rec1.m_enode->m_index - rec2.m_enode->m_index)
+    return cmp_enode;
+  gcc_assert (&rec1 == &rec2);
+  return 0;
+}
+
+/* class setjmp_svalue : public svalue.  */
+
+/* Implementation of svalue::accept vfunc for setjmp_svalue.  */
+
+void
+setjmp_svalue::accept (visitor *v) const
+{
+  v->visit_setjmp_svalue (this);
+}
+
+/* Implementation of svalue::dump_to_pp vfunc for setjmp_svalue.  */
+
+void
+setjmp_svalue::dump_to_pp (pretty_printer *pp, bool simple) const
+{
+  if (simple)
+    pp_printf (pp, "SETJMP(EN: %i)", get_enode_index ());
+  else
+    pp_printf (pp, "setjmp_svalue(EN%i)", get_enode_index ());
+}
+
+/* Implementation of svalue::print_dump_widget_label vfunc for
+   setjmp_svalue.  */
+
+void
+setjmp_svalue::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_printf (pp, "setjmp_svalue(EN: %i)", get_enode_index ());
+}
+
+/* Implementation of svalue::add_dump_widget_children vfunc for
+   setjmp_svalue.  */
+
+void
+setjmp_svalue::
+add_dump_widget_children (text_art::tree_widget &,
+			  const text_art::dump_widget_info &) const
+{
+  /* No children.  */
+}
+
+/* Get the index of the stored exploded_node.  */
+
+int
+setjmp_svalue::get_enode_index () const
+{
+  return m_setjmp_record.m_enode->m_index;
+}
+
+/* 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)
+{
+  const call_string &cs_at_longjmp = longjmp_point.get_call_string ();
+  const call_string &cs_at_setjmp = setjmp_point.get_call_string ();
+
+  if (cs_at_longjmp.length () < cs_at_setjmp.length ())
+    return false;
+
+  /* Check that the call strings match, up to the depth of the
+     setjmp point.  */
+  for (unsigned depth = 0; depth < cs_at_setjmp.length (); depth++)
+    if (cs_at_longjmp[depth] != cs_at_setjmp[depth])
+      return false;
+
+  return true;
+}
+
+/* A pending_diagnostic subclass for complaining about bad longjmps,
+   where the enclosing function of the "setjmp" has returned (and thus
+   the stack frame no longer exists).  */
+
+class stale_jmp_buf : public pending_diagnostic_subclass<stale_jmp_buf>
+{
+public:
+  stale_jmp_buf (const gcall &setjmp_call, const gcall &longjmp_call,
+		 const program_point &setjmp_point)
+  : m_setjmp_call (setjmp_call), m_longjmp_call (longjmp_call),
+    m_setjmp_point (setjmp_point), m_stack_pop_event (nullptr)
+  {}
+
+  int get_controlling_option () const final override
+  {
+    return OPT_Wanalyzer_stale_setjmp_buffer;
+  }
+
+  bool emit (diagnostic_emission_context &ctxt) final override
+  {
+    return ctxt.warn ("%qs called after enclosing function of %qs has returned",
+		      get_user_facing_name (m_longjmp_call),
+		      get_user_facing_name (m_setjmp_call));
+  }
+
+  const char *get_kind () const final override
+  { return "stale_jmp_buf"; }
+
+  bool operator== (const stale_jmp_buf &other) const
+  {
+    return (&m_setjmp_call == &other.m_setjmp_call
+	    && &m_longjmp_call == &other.m_longjmp_call);
+  }
+
+  bool
+  maybe_add_custom_events_for_eedge (const exploded_edge &eedge,
+				     checker_path *emission_path)
+    final override
+  {
+    /* Detect exactly when the stack first becomes invalid,
+       and issue an event then.  */
+    if (m_stack_pop_event)
+      return false;
+    const exploded_node *src_node = eedge.m_src;
+    const program_point &src_point = src_node->get_point ();
+    const exploded_node *dst_node = eedge.m_dest;
+    const program_point &dst_point = dst_node->get_point ();
+    if (valid_longjmp_stack_p (src_point, m_setjmp_point)
+	&& !valid_longjmp_stack_p (dst_point, m_setjmp_point))
+      {
+	/* Compare with diagnostic_manager::add_events_for_superedge.  */
+	const int src_stack_depth = src_point.get_stack_depth ();
+	m_stack_pop_event = new precanned_custom_event
+	  (event_loc_info (src_point.get_location (),
+			   src_point.get_fndecl (),
+			   src_stack_depth),
+	   "stack frame is popped here, invalidating saved environment");
+	emission_path->add_event
+	  (std::unique_ptr<custom_event> (m_stack_pop_event));
+	return false;
+      }
+    return false;
+  }
+
+  bool
+  describe_final_event (pretty_printer &pp,
+			const evdesc::final_event &) final override
+  {
+    if (m_stack_pop_event)
+      pp_printf (&pp,
+		 "%qs called after enclosing function of %qs returned at %@",
+		 get_user_facing_name (m_longjmp_call),
+		 get_user_facing_name (m_setjmp_call),
+		 m_stack_pop_event->get_id_ptr ());
+    else
+      pp_printf (&pp,
+		 "%qs called after enclosing function of %qs has returned",
+		 get_user_facing_name (m_longjmp_call),
+		 get_user_facing_name (m_setjmp_call));
+    return true;
+  }
+
+
+private:
+  const gcall &m_setjmp_call;
+  const gcall &m_longjmp_call;
+  program_point m_setjmp_point;
+  custom_event *m_stack_pop_event;
+};
+
+/* Update this model for a call and return of setjmp/sigsetjmp at CALL within
+   ENODE, using CTXT to report any diagnostics.
+
+   This is for the initial direct invocation of setjmp/sigsetjmp (which returns
+   0), as opposed to any second return due to longjmp/sigsetjmp.  */
+
+void
+region_model::on_setjmp (const gcall &call,
+			 const exploded_node &enode,
+			 const superedge &sedge,
+			 region_model_context *ctxt)
+{
+  const svalue *buf_ptr = get_rvalue (gimple_call_arg (&call, 0), ctxt);
+  const region *buf_reg = deref_rvalue (buf_ptr, gimple_call_arg (&call, 0),
+					 ctxt);
+
+  /* Create a setjmp_svalue for this call and store it in BUF_REG's
+     region.  */
+  if (buf_reg)
+    {
+      setjmp_record r (&enode, &sedge, call);
+      const svalue *sval
+	= m_mgr->get_or_create_setjmp_svalue (r, buf_reg->get_type ());
+      set_value (buf_reg, sval, ctxt);
+    }
+
+  /* Direct calls to setjmp return 0.  */
+  if (tree lhs = gimple_call_lhs (&call))
+    {
+      const svalue *new_sval
+	= m_mgr->get_or_create_int_cst (TREE_TYPE (lhs), 0);
+      const region *lhs_reg = get_lvalue (lhs, ctxt);
+      set_value (lhs_reg, new_sval, ctxt);
+    }
+}
+
+/* Update this region_model for rewinding from a "longjmp" at LONGJMP_CALL
+   to a "setjmp" at SETJMP_CALL where the final stack depth should be
+   SETJMP_STACK_DEPTH.  Pop any stack frames.  Leak detection is *not*
+   done, and should be done by the caller.  */
+
+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);
+  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 (nullptr, nullptr, ctxt, nullptr, false);
+
+  gcc_assert (get_stack_depth () == setjmp_stack_depth);
+
+  /* Assign to LHS of "setjmp" in new_state.  */
+  if (tree lhs = gimple_call_lhs (&setjmp_call))
+    {
+      /* Passing 0 as the val to longjmp leads to setjmp returning 1.  */
+      const svalue *zero_sval
+	= m_mgr->get_or_create_int_cst (TREE_TYPE (fake_retval), 0);
+      tristate eq_zero = eval_condition (fake_retval_sval, EQ_EXPR, zero_sval);
+      /* If we have 0, use 1.  */
+      if (eq_zero.is_true ())
+	{
+	  const svalue *one_sval
+	    = m_mgr->get_or_create_int_cst (TREE_TYPE (fake_retval), 1);
+	  fake_retval_sval = one_sval;
+	}
+      else
+	{
+	  /* Otherwise note that the value is nonzero.  */
+	  m_constraints->add_constraint (fake_retval_sval, NE_EXPR, zero_sval);
+	}
+
+      /* Decorate the return value from setjmp as being unmergeable,
+	 so that we don't attempt to merge states with it as zero
+	 with states in which it's nonzero, leading to a clean distinction
+	 in the exploded_graph betweeen the first return and the second
+	 return.  */
+      fake_retval_sval = m_mgr->get_or_create_unmergeable (fake_retval_sval);
+
+      const region *lhs_reg = get_lvalue (lhs, ctxt);
+      set_value (lhs_reg, fake_retval_sval, ctxt);
+    }
+}
+
+/* Handle LONGJMP_CALL, a call to longjmp or siglongjmp.
+
+   Attempt to locate where setjmp/sigsetjmp was called on the jmp_buf and build
+   an exploded_node and exploded_edge to it representing a rewind to that frame,
+   handling the various kinds of failure that can occur.  */
+
+void
+exploded_node::on_longjmp (exploded_graph &eg,
+			   const gcall &longjmp_call,
+			   program_state *new_state,
+			   region_model_context *ctxt)
+{
+  tree buf_ptr = gimple_call_arg (&longjmp_call, 0);
+  gcc_assert (POINTER_TYPE_P (TREE_TYPE (buf_ptr)));
+
+  region_model *new_region_model = new_state->m_region_model;
+  const svalue *buf_ptr_sval = new_region_model->get_rvalue (buf_ptr, ctxt);
+  const region *buf = new_region_model->deref_rvalue (buf_ptr_sval, buf_ptr,
+						       ctxt);
+
+  const svalue *buf_content_sval
+    = new_region_model->get_store_value (buf, ctxt);
+  const setjmp_svalue *setjmp_sval
+    = buf_content_sval->dyn_cast_setjmp_svalue ();
+  if (!setjmp_sval)
+    return;
+
+  const setjmp_record tmp_setjmp_record = setjmp_sval->get_setjmp_record ();
+
+  /* Build a custom enode and eedge for rewinding from the longjmp/siglongjmp
+     call back to the setjmp/sigsetjmp.  */
+  rewind_info_t rewind_info (tmp_setjmp_record, longjmp_call);
+
+  const gcall &setjmp_call = rewind_info.get_setjmp_call ();
+  const program_point point_before_setjmp = rewind_info.get_point_before_setjmp ();
+  const program_point point_after_setjmp = rewind_info.get_point_after_setjmp ();
+
+  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, point_after_setjmp))
+    {
+      ctxt->warn (std::make_unique<stale_jmp_buf> (setjmp_call,
+						   longjmp_call,
+						   point_before_setjmp));
+      return;
+    }
+
+  gcc_assert (longjmp_point.get_stack_depth ()
+	      >= point_after_setjmp.get_stack_depth ());
+
+  /* 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 longjmp is rewinding to.  */
+
+  diagnostic_manager *dm = &eg.get_diagnostic_manager ();
+  unsigned prev_num_diagnostics = dm->get_num_diagnostics ();
+
+  new_region_model->on_longjmp (longjmp_call, setjmp_call,
+				point_after_setjmp.get_stack_depth (), ctxt);
+
+  /* Detect leaks in the new state relative to the old state.  */
+  program_state::detect_leaks (get_state (), *new_state, nullptr,
+				eg.get_ext_state (), ctxt);
+  exploded_node *next
+    = eg.get_or_create_node (point_after_setjmp, *new_state, this);
+
+  /* Create custom exploded_edge for a longjmp.  */
+  if (next)
+    {
+      exploded_edge *eedge
+	= eg.add_edge (const_cast<exploded_node *> (this), next, nullptr, true,
+		       std::make_unique<rewind_info_t> (tmp_setjmp_record,
+							longjmp_call));
+
+      /* 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;
+	}
+    }
+}
+
+/* class rewind_info_t : public custom_edge_info.  */
+
+/* Implementation of custom_edge_info::update_model vfunc
+   for rewind_info_t.
+
+   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).  */
+
+bool
+rewind_info_t::update_model (region_model *model,
+			     const exploded_edge *eedge,
+			     region_model_context *) const
+{
+  gcc_assert (eedge);
+  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 (get_longjmp_call (),
+		     get_setjmp_call (),
+		     setjmp_point.get_stack_depth (), nullptr);
+  return true;
+}
+
+/* Implementation of custom_edge_info::add_events_to_path vfunc
+   for rewind_info_t.  */
+
+void
+rewind_info_t::add_events_to_path (checker_path *emission_path,
+				   const exploded_edge &eedge,
+				   pending_diagnostic &) const
+{
+  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 ();
+
+  emission_path->add_event
+    (std::make_unique<rewind_from_longjmp_event>
+       (&eedge,
+	event_loc_info (get_longjmp_call ().location,
+			src_point.get_fndecl (),
+			src_stack_depth),
+	this));
+  emission_path->add_event
+    (std::make_unique<rewind_to_setjmp_event>
+       (&eedge,
+	event_loc_info (get_setjmp_call ().location,
+			dst_point.get_fndecl (),
+			dst_stack_depth),
+	this));
+}
+
+} // namespace ana
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/svalue.cc b/gcc/analyzer/svalue.cc
index 30283e6356c8..1c0041ab75bb 100644
--- a/gcc/analyzer/svalue.cc
+++ b/gcc/analyzer/svalue.cc
@@ -1374,8 +1374,7 @@ poisoned_svalue::maybe_fold_bits_within (tree type,
   return mgr->get_or_create_poisoned_svalue (m_kind, type);
 }
 
-/* class setjmp_svalue's implementation is in engine.cc, so that it can use
-   the declaration of exploded_node.  */
+/* class setjmp_svalue's implementation is in setjmp-longjmp.cc.  */
 
 /* class initial_svalue : public svalue.  */
 
-- 
2.49.0

