From f8ea57b46ec1f87f06480bb5b2c39a88941aa9cc Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Wed, 13 May 2020 10:08:44 -0400
Subject: [PATCH 119/237] FIXME: initial port of setjmp/longjmp

---
 gcc/analyzer/analyzer.h       |  21 +++++
 gcc/analyzer/engine.cc        | 167 ++++++++++++++++++++++++++++++++--
 gcc/analyzer/exploded-graph.h |   4 +
 gcc/analyzer/region-model.h   |  21 -----
 gcc/analyzer/region-model2.cc |  56 ++++++++----
 gcc/analyzer/region-model2.h  |  80 ++++++++++------
 6 files changed, 273 insertions(+), 76 deletions(-)

diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h
index 68bbf0c4d57..36464312d30 100644
--- a/gcc/analyzer/analyzer.h
+++ b/gcc/analyzer/analyzer.h
@@ -191,6 +191,27 @@ private:
   bool m_is_symbolic;
 };
 
+/* A bundle of information recording a setjmp/sigsetjmp call, corresponding
+   roughly to a jmp_buf.  */
+
+struct setjmp_record
+{
+  setjmp_record (const exploded_node *enode,
+		 const gcall *setjmp_call)
+  : m_enode (enode), m_setjmp_call (setjmp_call)
+  {
+  }
+
+  bool operator== (const setjmp_record &other) const
+  {
+    return (m_enode == other.m_enode
+	    && m_setjmp_call == other.m_setjmp_call);
+  }
+
+  const exploded_node *m_enode;
+  const gcall *m_setjmp_call;
+};
+
 } // namespace ana
 
 extern bool is_special_named_call_p (const gcall *call, const char *funcname,
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index f36cf13f700..43879f255f3 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -265,6 +265,48 @@ setjmp_svalue::print_details (const region_model &model ATTRIBUTE_UNUSED,
   pp_printf (pp, "setjmp: EN: %i", get_enode_index ());
 }
 
+/* class setjmp_svalue2 : public svalue2.  */
+
+/* FIXME.  */
+
+void
+setjmp_svalue2::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_svalue2(EN%i)", get_enode_index ());
+}
+
+/* Compare the fields of this setjmp_svalue2 with OTHER, returning true
+   if they are equal.
+   For use by svalue2::operator==.  */
+
+#if 0
+bool
+setjmp_svalue2::compare_fields (const setjmp_svalue2 &other) const
+{
+  return m_setjmp_record == other.m_setjmp_record;
+}
+#endif
+
+/* Implementation of svalue2::add_to_hash vfunc for setjmp_svalue2.  */
+#if 0
+void
+setjmp_svalue2::add_to_hash (inchash::hash &hstate) const
+{
+  hstate.add_int (m_setjmp_record.m_enode->m_index);
+}
+#endif
+
+/* Get the index of the stored exploded_node.  */
+
+int
+setjmp_svalue2::get_enode_index () const
+{
+  return m_setjmp_record.m_enode->m_index;
+}
+
 /* Concrete implementation of sm_context, wiring it up to the rest of this
    file.  */
 
@@ -1641,9 +1683,7 @@ exploded_node::on_stmt2 (exploded_graph &eg,
 	{
 	  /* Handle the builtin "__analyzer_dump_path" by queuing a
 	     diagnostic at this exploded_node.  */
-#if 0
 	  ctxt.warn (new dump_path_diagnostic ());
-#endif
 	}
       else if (is_special_named_call_p (call, "__analyzer_dump_region_model", 0))
 	{
@@ -1678,17 +1718,11 @@ exploded_node::on_stmt2 (exploded_graph &eg,
 	  /* This is handled elsewhere.  */
 	}
       else if (is_setjmp_call_p (call))
-	gcc_unreachable ();
-#if 0
 	state->m_region_model2->on_setjmp (call, this, &ctxt);
-#endif
       else if (is_longjmp_call_p (call))
 	{
-	  gcc_unreachable ();
-#if 0
-	  on_longjmp (eg, call, state, &ctxt);
+	  on_longjmp2 (eg, call, state, &ctxt);
 	  return on_stmt_flags::terminate_path ();
-#endif
 	}
       else
 	unknown_side_effects = state->m_region_model2->on_call_pre (call, &ctxt);
@@ -1893,7 +1927,120 @@ exploded_node::on_longjmp (exploded_graph &eg,
 				      setjmp_point.get_call_string ());
 
   state_change change;
-  exploded_node *next = eg.get_or_create_node (next_point, *new_state, &change);
+  exploded_node *next
+    = eg.get_or_create_node (next_point, *new_state, this, &change);
+
+  /* Create custom exploded_edge for a longjmp.  */
+  if (next)
+    {
+      exploded_edge *eedge
+	= eg.add_edge (const_cast<exploded_node *> (this), next, NULL,
+		       change,
+		       new 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;
+	}
+    }
+}
+
+/* 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_longjmp2 (exploded_graph &eg,
+			    const gcall *longjmp_call,
+			    program_state *new_state,
+			    region_model2_context *ctxt) const
+{
+  tree buf_ptr = gimple_call_arg (longjmp_call, 0);
+
+  region_model2 *new_region_model = new_state->m_region_model2;
+  const svalue2 *buf_ptr_sval = new_region_model->get_rvalue (buf_ptr, ctxt);
+  const region2 *buf = new_region_model->deref_rvalue (buf_ptr_sval, ctxt);
+  if (!buf)
+    return; // FIXME: can it be NULL?
+
+  const svalue2 *buf_content_sval = new_region_model->get_store_value (buf);
+  if (!buf_content_sval) // FIXME: can it be NULL?
+    return;
+  const setjmp_svalue2 *setjmp_sval
+    = buf_content_sval->dyn_cast_setjmp_svalue2 ();
+  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 &setjmp_point = rewind_info.get_setjmp_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_jmp_buf (setjmp_call, longjmp_call));
+      return;
+    }
+
+  gcc_assert (longjmp_point.get_stack_depth ()
+	      >= setjmp_point.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,
+				setjmp_point.get_stack_depth (), ctxt);
+
+  program_point next_point
+    = program_point::after_supernode (setjmp_point.get_supernode (),
+				      setjmp_point.get_call_string ());
+
+  state_change change;
+  exploded_node *next
+    = eg.get_or_create_node (next_point, *new_state, this, &change);
 
   /* Create custom exploded_edge for a longjmp.  */
   if (next)
diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h
index 57396fc9781..9540928beba 100644
--- a/gcc/analyzer/exploded-graph.h
+++ b/gcc/analyzer/exploded-graph.h
@@ -303,6 +303,10 @@ class exploded_node : public dnode<eg_traits>
 		   const gcall *call,
 		   program_state *new_state,
 		   region_model_context *ctxt) const;
+  void on_longjmp2 (exploded_graph &eg,
+		    const gcall *call,
+		    program_state *new_state,
+		    region_model2_context *ctxt) const;
 
   void detect_leaks (exploded_graph &eg) const;
 
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index b1fe3c796bb..2467b509438 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -693,27 +693,6 @@ is_a_helper <poisoned_svalue *>::test (svalue *sval)
 
 namespace ana {
 
-/* A bundle of information recording a setjmp/sigsetjmp call, corresponding
-   roughly to a jmp_buf.  */
-
-struct setjmp_record
-{
-  setjmp_record (const exploded_node *enode,
-		 const gcall *setjmp_call)
-  : m_enode (enode), m_setjmp_call (setjmp_call)
-  {
-  }
-
-  bool operator== (const setjmp_record &other) const
-  {
-    return (m_enode == other.m_enode
-	    && m_setjmp_call == other.m_setjmp_call);
-  }
-
-  const exploded_node *m_enode;
-  const gcall *m_setjmp_call;
-};
-
 /* Concrete subclass of svalue representing buffers for setjmp/sigsetjmp,
    so that longjmp/siglongjmp can potentially "return" to an entirely
    different function.  */
diff --git a/gcc/analyzer/region-model2.cc b/gcc/analyzer/region-model2.cc
index c7bba5625ab..69c63d36678 100644
--- a/gcc/analyzer/region-model2.cc
+++ b/gcc/analyzer/region-model2.cc
@@ -2823,6 +2823,9 @@ region_model2_manager::~region_model2_manager ()
   for (constants_map_t::iterator iter = m_constants_map.begin ();
        iter != m_constants_map.end (); ++iter)
     delete (*iter).second;
+  for (setjmp_values_map_t::iterator iter = m_setjmp_values_map.begin ();
+       iter != m_setjmp_values_map.end (); ++iter)
+    delete (*iter).second;
   for (poisoned_values_map_t::iterator iter = m_poisoned_values_map.begin ();
        iter != m_poisoned_values_map.end (); ++iter)
     delete (*iter).second;
@@ -2887,6 +2890,18 @@ region_model2_manager::get_or_create_initial_value (const region2 *reg)
   return initial_sval;
 }
 
+const svalue2 *
+region_model2_manager::get_or_create_setjmp_svalue2 (const setjmp_record &r,
+						     tree type)
+{
+  setjmp_svalue2::key_t key (r, type);
+  if (setjmp_svalue2 **slot = m_setjmp_values_map.get (key))
+    return *slot;
+  setjmp_svalue2 *setjmp_sval = new setjmp_svalue2 (r, type);
+  m_setjmp_values_map.put (key, setjmp_sval);
+  return setjmp_sval;
+}
+
 const svalue2 *
 region_model2_manager::get_or_create_poisoned_svalue2 (enum poison_kind kind,
 						       tree type)
@@ -4762,38 +4777,39 @@ region_model2::on_greturn (const supernode */*snode*/,
 
    This is for the initial direct invocation of setjmp/sigsetjmp (which returns
    0), as opposed to any second return due to longjmp/sigsetjmp.  */
-#if 0
+
 void
 region_model2::on_setjmp (const gcall *call, const exploded_node *enode,
 			 region_model2_context *ctxt)
 {
-  region2 *buf_reg = deref_rvalue (gimple_call_arg (call, 0), ctxt);
-  region2 *buf = get_region2 (buf_reg);
+  const svalue2 *buf_ptr = get_rvalue (gimple_call_arg (call, 0), ctxt);
+  const region2 *buf_reg = deref_rvalue (buf_ptr, ctxt);
 
-  /* Create a setjmp_svalue2 for this call and store it in BUF_REG's region2.  */
-  if (buf)
+  /* Create a setjmp_svalue2 for this call and store it in BUF_REG's
+     region2.  */
+  if (buf_reg)
     {
       setjmp_record r (enode, call);
-      const svalue2 *sval = new setjmp_svalue2 (r, buf->get_type ());
-      const svalue2 *new_sval = add_svalue2 (sval);
-      set_value (buf_reg, new_sval, ctxt);
+      const svalue2 *sval
+	= m_mgr->get_or_create_setjmp_svalue2 (r, buf_reg->get_type ());
+      set_value (buf_reg, sval, ctxt);
     }
 
   /* Direct calls to setjmp return 0.  */
   if (tree lhs = gimple_call_lhs (call))
     {
       tree zero = build_int_cst (TREE_TYPE (lhs), 0);
-      const svalue2 *new_sval = get_or_create_constant_svalue2 (zero);
-      region2 *lhs_reg = get_lvalue (lhs, ctxt);
+      const svalue2 *new_sval = m_mgr->get_or_create_constant_svalue2 (zero);
+      const region2 *lhs_reg = get_lvalue (lhs, ctxt);
       set_value (lhs_reg, new_sval, ctxt);
     }
 }
-#endif
+
 /* Update this region_model2 for rewinding from a "longjmp" at LONGJMP_CALL
    to a "setjmp" at SETJMP_CALL where the final stack depth should be
    SETJMP_STACK_DEPTH.  Purge any stack frames, potentially reporting on
    leaks to CTXT.  */
-#if 0
+
 void
 region_model2::on_longjmp (const gcall *longjmp_call, const gcall *setjmp_call,
 			  int setjmp_stack_depth,
@@ -4809,7 +4825,7 @@ region_model2::on_longjmp (const gcall *longjmp_call, const gcall *setjmp_call,
   while (get_stack_depth () > setjmp_stack_depth)
     {
       /* Don't purge unused svalue2s yet, as we're using fake_retval_sval.  */
-      pop_frame (region2_id::null (), false, NULL, ctxt);
+      pop_frame (NULL, false, NULL, ctxt);
     }
 
   gcc_assert (get_stack_depth () == setjmp_stack_depth);
@@ -4819,31 +4835,33 @@ region_model2::on_longjmp (const gcall *longjmp_call, const gcall *setjmp_call,
     {
       /* Passing 0 as the val to longjmp leads to setjmp returning 1.  */
       tree t_zero = build_int_cst (TREE_TYPE (fake_retval), 0);
-      const svalue2 *zero_sval = get_or_create_constant_svalue2 (t_zero);
+      const svalue2 *zero_sval = m_mgr->get_or_create_constant_svalue2 (t_zero);
       tristate eq_zero = eval_condition (fake_retval_sval, EQ_EXPR, zero_sval);
       /* If we have 0, use 1.  */
       if (eq_zero.is_true ())
 	{
 	  tree t_one = build_int_cst (TREE_TYPE (fake_retval), 1);
-	  const svalue2 *one_sval = get_or_create_constant_svalue2 (t_one);
+	  const svalue2 *one_sval
+	    = m_mgr->get_or_create_constant_svalue2 (t_one);
 	  fake_retval_sval = one_sval;
 	}
       else
 	{
 	  /* Otherwise note that the value is nonzero.  */
-	  m_constraints->add_constraint (fake_retval_sval, NE_EXPR, zero_sval);
+	  m_constraints2->add_constraint (fake_retval_sval, NE_EXPR, zero_sval);
 	}
 
-      region2 *lhs_reg = get_lvalue (lhs, ctxt);
+      const region2 *lhs_reg = get_lvalue (lhs, ctxt);
       set_value (lhs_reg, fake_retval_sval, ctxt);
     }
-
+#if 0
   /* Now that we've assigned the fake_retval, we can purge the unused
      svalue2s, which could detect leaks.  */
   purge_unused_svalue2s (NULL, ctxt, NULL);
   validate ();
-}
 #endif
+}
+
 /* Update this region_model2 for a phi stmt of the form
      LHS = PHI <...RHS...>.
    where RHS is for the appropriate edge.  */
diff --git a/gcc/analyzer/region-model2.h b/gcc/analyzer/region-model2.h
index 4bd2b8e8629..0d15da88585 100644
--- a/gcc/analyzer/region-model2.h
+++ b/gcc/analyzer/region-model2.h
@@ -472,28 +472,6 @@ template <> struct default_hash_traits<poisoned_svalue2::key_t>
 
 namespace ana {
 
-#if 0
-/* A bundle of information recording a setjmp/sigsetjmp call, corresponding
-   roughly to a jmp_buf.  */
-
-struct setjmp_record
-{
-  setjmp_record (const exploded_node *enode,
-		 const gcall *setjmp_call)
-  : m_enode (enode), m_setjmp_call (setjmp_call)
-  {
-  }
-
-  bool operator== (const setjmp_record &other) const
-  {
-    return (m_enode == other.m_enode
-	    && m_setjmp_call == other.m_setjmp_call);
-  }
-
-  const exploded_node *m_enode;
-  const gcall *m_setjmp_call;
-};
-
 /* Concrete subclass of svalue2 representing buffers for setjmp/sigsetjmp,
    so that longjmp/siglongjmp can potentially "return" to an entirely
    different function.  */
@@ -501,8 +479,34 @@ struct setjmp_record
 class setjmp_svalue2 : public svalue2
 {
 public:
+  /* A support class for uniquifying instances of poisoned_svalue2.  */
+  struct key_t
+  {
+    key_t (const setjmp_record &record, tree type)
+    : m_record (record), m_type (type)
+    {}
+
+    hashval_t hash () const
+    {
+      return 42; // FIXME
+    }
+
+    bool operator== (const key_t &other) const
+    {
+      return (m_record == other.m_record && m_type == other.m_type);
+    }
+
+    void mark_deleted () { m_type = reinterpret_cast<tree> (1); }
+    void mark_empty () { m_type = NULL_TREE; }
+    bool is_deleted () const { return m_type == reinterpret_cast<tree> (1); }
+    bool is_empty () const { return m_type == NULL_TREE; }
+
+    setjmp_record m_record;
+    tree m_type;
+  };
+
   setjmp_svalue2 (const setjmp_record &setjmp_record,
-		 tree type)
+		  tree type)
   : svalue2 (type), m_setjmp_record (setjmp_record)
   {}
 
@@ -534,7 +538,24 @@ public:
 
   setjmp_record m_setjmp_record;
 };
-#endif
+
+} // namespace ana
+
+template <>
+template <>
+inline bool
+is_a_helper <const setjmp_svalue2 *>::test (const svalue2 *sval)
+{
+  return sval->get_kind () == svalue2::SK_SETJMP;
+}
+
+template <> struct default_hash_traits<setjmp_svalue2::key_t>
+: public member_function_hash_traits<setjmp_svalue2::key_t>
+{
+  static const bool empty_zero_p = true;
+};
+
+namespace ana {
 
 /* Concrete subclass of svalue2 representing the initial value of a
    specific region  */
@@ -1710,6 +1731,8 @@ public:
 
   /* svalue2 consolidation.  */
   const svalue2 *get_or_create_constant_svalue2 (tree cst_expr);
+  const svalue2 *get_or_create_setjmp_svalue2 (const setjmp_record &r,
+					       tree type);
   const svalue2 *get_or_create_poisoned_svalue2 (enum poison_kind kind,
 						 tree type);
   const svalue2 *get_or_create_initial_value (const region2 *reg);
@@ -1779,6 +1802,10 @@ private:
 		   poisoned_svalue2 *> poisoned_values_map_t;
   poisoned_values_map_t m_poisoned_values_map;
 
+  typedef hash_map<setjmp_svalue2::key_t,
+		   setjmp_svalue2 *> setjmp_values_map_t;
+  setjmp_values_map_t m_setjmp_values_map;
+
   typedef hash_map<const region2 *, initial_svalue2 *> initial_values_map_t;
   initial_values_map_t m_initial_values_map;
 
@@ -2055,12 +2082,13 @@ class region_model2
   static void append_ssa_names_cb (const region2 *base_reg,
 				   struct append_ssa_names_cb_data *data);
 
- private:
+  const svalue2 *get_store_value (const region2 *reg);
+
+private:
   const region2 *get_lvalue_1 (path_var pv,
 			       region_model2_context *ctxt);
   const svalue2 *get_rvalue_1 (path_var pv,
 			       region_model2_context *ctxt);
-  const svalue2 *get_store_value (const region2 *reg);
 
 #if 0
   void copy_struct_region2 (region2_id dst_rid, struct_region2 *dst_reg,
-- 
2.26.2

