From ad8c92c1b7383e68a22d3f070c48f35c1c6976f6 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Tue, 9 Jun 2020 12:22:23 -0400
Subject: [PATCH 218/377] FIXME: replace setjmp merging hack with
 unmergeable_svalue2 (and parts of widening_svalue2)

---
 gcc/analyzer/analyzer.h             |   2 +
 gcc/analyzer/constraint-manager2.cc |   4 +
 gcc/analyzer/diagnostic-manager.cc  |   4 +-
 gcc/analyzer/region-model2.cc       | 154 ++++++++++++++++++++++++-
 gcc/analyzer/region-model2.h        | 170 +++++++++++++++++++++++++++-
 gcc/analyzer/store2.cc              |  14 ---
 6 files changed, 323 insertions(+), 25 deletions(-)

diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h
index 98f1d7cc2fe..9735b826911 100644
--- a/gcc/analyzer/analyzer.h
+++ b/gcc/analyzer/analyzer.h
@@ -63,6 +63,8 @@ class svalue2;
   class initial_svalue2;
   class unaryop_svalue2;
   class binop_svalue2;
+  class unmergeable_svalue2;
+  class widening_svalue2;
 class region2;
   class frame_region2;
   class function_region2;
diff --git a/gcc/analyzer/constraint-manager2.cc b/gcc/analyzer/constraint-manager2.cc
index 83a2f5aeeec..34f753124a9 100644
--- a/gcc/analyzer/constraint-manager2.cc
+++ b/gcc/analyzer/constraint-manager2.cc
@@ -663,6 +663,8 @@ constraint_manager2::add_constraint (const svalue2 *lhs,
 				     enum tree_code op,
 				     const svalue2 *rhs)
 {
+  lhs = lhs->unwrap_any_unmergeable ();
+  rhs = rhs->unwrap_any_unmergeable ();
   equiv_class2_id lhs_ec_id = get_or_add_equiv_class2 (lhs);
   equiv_class2_id rhs_ec_id = get_or_add_equiv_class2 (rhs);
   return add_constraint (lhs_ec_id, op,rhs_ec_id);
@@ -1082,6 +1084,8 @@ constraint_manager2::eval_condition (const svalue2 *lhs,
 				     enum tree_code op,
 				     const svalue2 *rhs)
 {
+  lhs = lhs->unwrap_any_unmergeable ();
+  rhs = rhs->unwrap_any_unmergeable ();
   return eval_condition (get_or_add_equiv_class2 (lhs),
 			 op,
 			 get_or_add_equiv_class2 (rhs));
diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc
index 0f6c2307915..179d1e3af61 100644
--- a/gcc/analyzer/diagnostic-manager.cc
+++ b/gcc/analyzer/diagnostic-manager.cc
@@ -1531,7 +1531,7 @@ diagnostic_manager::prune_for_sm_diagnostic (checker_path *path,
 
 	    if (event->should_filter_p (m_verbosity))
 	      {
-		log ("filtering event %i: CFG edge", idx);
+		log ("filtering events %i and %i: CFG edge", idx, idx + 1);
 		path->delete_event (idx);
 		/* Also delete the corresponding EK_END_CFG_EDGE.  */
 		gcc_assert (path->get_checker_event (idx)->m_kind
@@ -1818,7 +1818,7 @@ diagnostic_manager::prune_for_sm_diagnostic2 (checker_path *path,
 
 	    if (event->should_filter_p (m_verbosity))
 	      {
-		log ("filtering event %i: CFG edge", idx);
+		log ("filtering events %i and %i: CFG edge", idx, idx + 1);
 		path->delete_event (idx);
 		/* Also delete the corresponding EK_END_CFG_EDGE.  */
 		gcc_assert (path->get_checker_event (idx)->m_kind
diff --git a/gcc/analyzer/region-model2.cc b/gcc/analyzer/region-model2.cc
index 885ae73d3fe..59723852569 100644
--- a/gcc/analyzer/region-model2.cc
+++ b/gcc/analyzer/region-model2.cc
@@ -227,6 +227,14 @@ svalue2::maybe_undo_cast () const
   return NULL;
 }
 
+const svalue2 *
+svalue2::unwrap_any_unmergeable () const
+{
+  if (const unmergeable_svalue2 *unmergeable = dyn_cast_unmergeable_svalue2 ())
+    return unmergeable->get_arg ();
+  return this;
+}
+
 /* FIXME.
    Return NULL if not mergeable.  */
 
@@ -234,10 +242,54 @@ const svalue2 *
 svalue2::can_merge_p (const svalue2 *other,
 		      region_model2_manager *mgr) const
 {
-  if (types_compatible_p (get_type (), other->get_type ()))
-    return mgr->get_or_create_unknown_svalue2 (get_type ());
-  else
+  if (!(get_type () && other->get_type ()))
+    return NULL;
+
+  // FIXME: should we assert this instead?
+  if (!types_compatible_p (get_type (), other->get_type ()))
+    return NULL;
+
+  /* Reject attempts to merge unmergeable svalues.  */
+  if ((get_kind () == svalue2::SK_UNMERGEABLE)
+      || (other->get_kind () == svalue2::SK_UNMERGEABLE))
     return NULL;
+
+#if 0
+  /* Widening.  */
+  /* Merge: (new_cst, existing_cst) -> widen (existing, new).  */
+  if (tree cst0 = maybe_get_constant ())
+    if (tree cst1 = other->maybe_get_constant ())
+      {
+	/* FIXME: we also need to distinguish these via the stmt (or
+	   program_point?) so we can handle nested loops.  */
+	return mgr->get_or_create_widening_svalue2 (other->get_type (),
+						    other, this);
+      }
+  /* TODO: also detect widening symbolically via binary ops
+     e.g. for (i = start; i < end; i+= step).  */
+
+  /* Merge: (Widen(existing_val, V), existing_val) -> Widen (existing_val, V)
+     and thus get a fixed point.  */
+  if (const widening_svalue2 *widen_sval = dyn_cast_widening_svalue2 ())
+    {
+      /* FIXME: which direction??? */
+      if (other == widen_sval->get_base_svalue2 ())
+	return this;
+      if (other == widen_sval->get_iter_svalue2 ())
+	return this;
+    }
+  /* Merge: (Widen BINOP X), Y -> Widen
+     FIXME: what conditions?  */
+  if (const binop_svalue2 *binop_sval = dyn_cast_binop_svalue2 ())
+    if (const widening_svalue2 *widen_arg0
+	= binop_sval->get_arg0 ()->dyn_cast_widening_svalue2 ())
+      {
+	// FIXME!
+	return widen_arg0;
+      }
+#endif
+
+  return mgr->get_or_create_unknown_svalue2 (get_type ());
 }
 
 /* class region_svalue2 : public svalue2.  */
@@ -724,6 +776,31 @@ sub_svalue2::dump_to_pp (pretty_printer *pp, bool simple) const
     }
 }
 
+/* class widening_svalue2 : public svalue2.  */
+
+void
+widening_svalue2::dump_to_pp (pretty_printer *pp, bool simple) const
+{
+  if (simple)
+    {
+      pp_string (pp, "WIDENING(");
+      m_base_sval->dump_to_pp (pp, simple);
+      pp_string (pp, ", ");
+      m_iter_sval->dump_to_pp (pp, simple);
+      pp_character (pp, ')');
+    }
+  else
+    {
+      pp_string (pp, "widening_svalue2 (");
+      // TODO: type?
+      pp_string (pp, ", ");
+      m_base_sval->dump_to_pp (pp, simple);
+      pp_string (pp, ", ");
+      m_iter_sval->dump_to_pp (pp, simple);
+      pp_character (pp, ')');
+    }
+}
+
 /* class placeholder_svalue2 : public svalue2.  */
 
 void
@@ -740,6 +817,25 @@ placeholder_svalue2::dump_to_pp (pretty_printer *pp, bool simple) const
     }
 }
 
+/* class unmergeable_svalue2 : public svalue2.  */
+
+void
+unmergeable_svalue2::dump_to_pp (pretty_printer *pp, bool simple) const
+{
+  if (simple)
+    {
+      pp_string (pp, "UNMERGEABLE(");
+      m_arg->dump_to_pp (pp, simple);
+      pp_character (pp, ')');
+    }
+  else
+    {
+      pp_string (pp, "unmergeable_svalue2 (");
+      m_arg->dump_to_pp (pp, simple);
+      pp_character (pp, ')');
+    }
+}
+
 /* class region2 and its various subclasses.  */
 
 /* Get a string for KIND for use in debug dumps.  */
@@ -2853,6 +2949,13 @@ region_model2_manager::~region_model2_manager ()
   for (sub_values_map_t::iterator iter = m_sub_values_map.begin ();
        iter != m_sub_values_map.end (); ++iter)
     delete (*iter).second;
+  for (unmergeable_values_map_t::iterator iter
+	 = m_unmergeable_values_map.begin ();
+       iter != m_unmergeable_values_map.end (); ++iter)
+    delete (*iter).second;
+  for (widening_values_map_t::iterator iter = m_widening_values_map.begin ();
+       iter != m_widening_values_map.end (); ++iter)
+    delete (*iter).second;
 
   /* Delete consolidated regions.  */
   for (fndecls_map_t::iterator iter = m_fndecls_map.begin ();
@@ -3274,6 +3377,37 @@ region_model2_manager::get_or_create_sub_svalue2 (tree type,
   return sub_sval;
 }
 
+/* FIXME.  */
+
+const svalue2 *
+region_model2_manager::get_or_create_unmergeable (const svalue2 *arg)
+{
+  if (arg->get_kind () == svalue2::SK_UNMERGEABLE)
+    return arg;
+
+  if (unmergeable_svalue2 **slot = m_unmergeable_values_map.get (arg))
+    return *slot;
+  unmergeable_svalue2 *unmergeable_sval = new unmergeable_svalue2 (arg);
+  m_unmergeable_values_map.put (arg, unmergeable_sval);
+  return unmergeable_sval;
+}
+
+/* FIXME.  */
+
+const svalue2 *
+region_model2_manager::get_or_create_widening_svalue2 (tree type,
+						       const svalue2 *base_sval,
+						       const svalue2 *iter_sval)
+{
+  widening_svalue2::key_t key (type, base_sval, iter_sval);
+  if (widening_svalue2 **slot = m_widening_values_map.get (key))
+    return *slot;
+  widening_svalue2 *widening_sval
+    = new widening_svalue2 (type, base_sval, iter_sval);
+  m_widening_values_map.put (key, widening_sval);
+  return widening_sval;
+}
+
 /* region2 consolidation.  */
 
 /* FIXME.  */
@@ -3539,6 +3673,9 @@ region_model2_manager::log_stats (logger *logger, bool show_objs) const
   log_uniq_map (logger, show_objs, "unaryop_svalue2", m_unaryop_values_map);
   log_uniq_map (logger, show_objs, "binop_svalue2", m_binop_values_map);
   log_uniq_map (logger, show_objs, "sub_svalue2", m_sub_values_map);
+  log_uniq_map (logger, show_objs, "unmergeable_svalue2",
+		m_unmergeable_values_map);
+  log_uniq_map (logger, show_objs, "widening_svalue2", m_widening_values_map);
   logger->log ("region2 consolidation");
   logger->log ("  next region id: %i", m_next_region_id);
   log_uniq_map (logger, show_objs, "function_region2", m_fndecls_map);
@@ -5198,6 +5335,13 @@ region_model2::on_longjmp (const gcall *longjmp_call, const gcall *setjmp_call,
 	  m_constraints2->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 region2 *lhs_reg = get_lvalue (lhs, ctxt);
       set_value (lhs_reg, fake_retval_sval, ctxt);
     }
@@ -6001,6 +6145,10 @@ region_model2::eval_condition_without_cm (const svalue2 *lhs,
       || (rhs->get_type () && FLOAT_TYPE_P (rhs->get_type ())))
     return tristate::unknown ();
 
+  /* Unwrap any unmergeable values.  */
+  lhs = lhs->unwrap_any_unmergeable ();
+  rhs = rhs->unwrap_any_unmergeable ();
+
   if (lhs == rhs)
     {
       /* If we have the same svalue2, then we have equality
diff --git a/gcc/analyzer/region-model2.h b/gcc/analyzer/region-model2.h
index c9ce9939913..e4a48f2469f 100644
--- a/gcc/analyzer/region-model2.h
+++ b/gcc/analyzer/region-model2.h
@@ -134,7 +134,10 @@ struct purge_stats2
      unaryop_svalue2 (SK_UNARYOP): unary operation on another svalue2
      binop_svalue2 (SK_BINOP): binary operation on two svalue2s
      sub_svalue2 (SK_SUB): the result of accessing a subregion
-     placeholder_svalue2 (SK_PLACEHOLDER): for use in selftests.  */
+     unmergeable_svalue2 (SK_UNMERGEABLE): FIXME
+     placeholder_svalue2 (SK_PLACEHOLDER): for use in selftests.
+     widening_svalue2 (SK_WIDENING): a merger of two svalues (possibly
+                                     in an iteration).  */
 
 /* An abstract base class representing a value held by a region of memory.  */
 
@@ -154,7 +157,9 @@ public:
    SK_UNARYOP,
    SK_BINOP,
    SK_SUB,
-   SK_PLACEHOLDER
+   SK_UNMERGEABLE,
+   SK_PLACEHOLDER,
+   SK_WIDENING
   };
 
   virtual ~svalue2 () {}
@@ -194,9 +199,14 @@ public:
   dyn_cast_unaryop_svalue2 () const { return NULL; }
   virtual const binop_svalue2 *
   dyn_cast_binop_svalue2 () const { return NULL; }
+  virtual const unmergeable_svalue2 *
+  dyn_cast_unmergeable_svalue2 () const { return NULL; }
+  virtual const widening_svalue2 *
+  dyn_cast_widening_svalue2 () const { return NULL; }
 
   tree maybe_get_constant () const;
   const svalue2 *maybe_undo_cast () const;
+  const svalue2 *unwrap_any_unmergeable () const;
 
   const svalue2 *can_merge_p (const svalue2 *other,
 			      region_model2_manager *mgr) const;
@@ -749,10 +759,6 @@ public:
 
   void dump_to_pp (pretty_printer *pp, bool simple) const FINAL OVERRIDE;
 
-#if 0
-  binop_svalue2 *dyn_cast_binop_svalue2 () FINAL OVERRIDE { return this; }
-#endif
-
  private:
   const svalue2 *m_parent_svalue;
   const region2 *m_subregion;
@@ -776,6 +782,64 @@ template <> struct default_hash_traits<sub_svalue2::key_t>
 
 namespace ana {
 
+/* Concrete subclass of svalue2: decorate another svalue2,
+   so that the resulting svalue2 can be identified as being
+   "interesting to control flow".
+   For example, consider the return value from setjmp.  We
+   don't want to merge states in which the result is 0 with
+   those in which the result is non-zero.  By using an
+   unmergeable_svalue2 for the result, we can inhibit such merges
+   and have separate exploded nodes for those states, keeping
+   the first and second returns from setjmp distinct in the exploded
+   graph.
+   FIXME: possibly rename to "significant_svalue2"?
+   FIXME: possibly use this to capture "significant" conditions in
+   control flow, and to use this to tweak descriptions of conditions
+   in paths.  Specifically, could this be used by
+     start_cfg_edge_event::maybe_describe_condition
+   to emit things like
+     following 'true' branch (when value returned from 'setjmp' is non-zero)
+   or somesuch.  */
+
+class unmergeable_svalue2 : public svalue2
+{
+public:
+  unmergeable_svalue2 (const svalue2 *arg)
+    : svalue2 (arg->get_type ()), m_arg (arg)
+  {
+  }
+
+  enum kind get_kind () const FINAL OVERRIDE { return SK_UNMERGEABLE; }
+  const unmergeable_svalue2 *
+  dyn_cast_unmergeable_svalue2 () const FINAL OVERRIDE { return this; }
+
+
+#if 0
+  void dump_dot_to_pp (const region_model2 &model,
+		       pretty_printer *pp) const
+    FINAL OVERRIDE;
+#endif
+
+  void dump_to_pp (pretty_printer *pp, bool simple) const FINAL OVERRIDE;
+
+  const svalue2 *get_arg () const { return m_arg; }
+
+ private:
+  const svalue2 *m_arg;
+};
+
+} // namespace ana
+
+template <>
+template <>
+inline bool
+is_a_helper <const unmergeable_svalue2 *>::test (const svalue2 *sval)
+{
+  return sval->get_kind () == svalue2::SK_UNMERGEABLE;
+}
+
+namespace ana {
+
 /* Concrete subclass of svalue2 for use in selftests, where
    we want a specific but unknown svalue2.
    Unlike other svalue2 subclasses these aren't managed by
@@ -819,6 +883,89 @@ is_a_helper <placeholder_svalue2 *>::test (svalue2 *sval)
 
 namespace ana {
 
+/* Concrete subclass of svalue2 representing a "widening" seen when merging
+   states, widening from a base value to {base value, iter value} and thus
+   representing a possible fixed point in an iteration from the base to
+   +ve infinity, or -ve infinity, and thus useful for representing a value
+   within a loop.
+   FIXME: firm this up; clarify the above.  */
+
+class widening_svalue2 : public svalue2
+{
+public:
+  /* A support class for uniquifying instances of widening_svalue2.  */
+  struct key_t
+  {
+    key_t (tree type, const svalue2 *base_sval, const svalue2 *iter_sval)
+    : m_type (type), m_base_sval (base_sval), m_iter_sval (iter_sval)
+    {}
+
+    hashval_t hash () const
+    {
+      return 42; // FIXME
+    }
+
+    bool operator== (const key_t &other) const
+    {
+      return (m_type == other.m_type
+	      && m_base_sval == other.m_base_sval
+	      && m_iter_sval == other.m_iter_sval);
+    }
+
+    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; }
+
+    tree m_type;
+    const svalue2 *m_base_sval;
+    const svalue2 *m_iter_sval;
+  };
+  widening_svalue2 (tree type, const svalue2 *base_sval,
+		    const svalue2 *iter_sval)
+  : svalue2 (type), m_base_sval (base_sval), m_iter_sval (iter_sval)
+  {
+  }
+
+  enum kind get_kind () const FINAL OVERRIDE { return SK_WIDENING; }
+  const widening_svalue2 *dyn_cast_widening_svalue2 () const { return this; }
+
+#if 0
+  void dump_dot_to_pp (const region_model2 &model,
+		       pretty_printer *pp) const
+    FINAL OVERRIDE;
+#endif
+
+  void dump_to_pp (pretty_printer *pp, bool simple) const FINAL OVERRIDE;
+
+  const svalue2 *get_base_svalue2 () const { return m_base_sval; }
+  const svalue2 *get_iter_svalue2 () const { return m_iter_sval; }
+
+ private:
+  const svalue2 *m_base_sval;
+  const svalue2 *m_iter_sval;
+};
+
+} // namespace ana
+
+template <>
+template <>
+inline bool
+is_a_helper <widening_svalue2 *>::test (svalue2 *sval)
+{
+  return sval->get_kind () == svalue2::SK_WIDENING;
+}
+
+#if 1
+template <> struct default_hash_traits<widening_svalue2::key_t>
+: public member_function_hash_traits<widening_svalue2::key_t>
+{
+  static const bool empty_zero_p = true;
+};
+#endif
+
+namespace ana {
+
 /* Region and its subclasses.
 
    The class hierarchy looks like this (using indentation to show
@@ -1716,6 +1863,10 @@ public:
   const svalue2 *get_or_create_sub_svalue2 (tree type,
 					    const svalue2 *parent_svalue,
 					    const region2 *subregion);
+  const svalue2 *get_or_create_unmergeable (const svalue2 *arg);
+  const svalue2 *get_or_create_widening_svalue2 (tree type,
+						 const svalue2 *base_svalue,
+						 const svalue2 *iter_svalue);
 
   /* region2 consolidation.  */
   const function_region2 *get_region_for_fndecl (tree fndecl);
@@ -1797,6 +1948,13 @@ private:
   typedef hash_map<sub_svalue2::key_t, sub_svalue2 *> sub_values_map_t;
   sub_values_map_t m_sub_values_map;
 
+  typedef hash_map<const svalue2 *,
+		   unmergeable_svalue2 *> unmergeable_values_map_t;
+  unmergeable_values_map_t m_unmergeable_values_map;
+
+  typedef hash_map<widening_svalue2::key_t, widening_svalue2 *> widening_values_map_t;
+  widening_values_map_t m_widening_values_map;
+
   /* region2 consolidation.  */
 
   code_region2 m_code_region;
diff --git a/gcc/analyzer/store2.cc b/gcc/analyzer/store2.cc
index c53ffdc9358..5a21699176e 100644
--- a/gcc/analyzer/store2.cc
+++ b/gcc/analyzer/store2.cc
@@ -650,20 +650,6 @@ binding_cluster2::can_merge_p (const binding_cluster2 *cluster_a,
       const svalue2 *sval_a = cluster_a->get_any_value (key);
       const svalue2 *sval_b = cluster_b->get_any_value (key);
 
-      /* FIXME: hack!
-	 The existing setjmp tests show the control flow properly,
-	 distinguising between the zero and nonzero return paths
-	 from setjmp.
-	 This only worked in the old implementation because
-	 model_merger::can_merge_values_p happened to reject attempts
-	 to merge a region_model containing an SK_SETJMP value.
-	 In the new implementation these will be trivially merged as
-	 they as the same svalue2.  So for now, reject any merger
-	 involving an setjmp_svalue2.  */
-      if ((sval_a && sval_a->get_kind () == svalue2::SK_SETJMP)
-	  || (sval_b && sval_b->get_kind () == svalue2::SK_SETJMP))
-	return false;
-
       if (sval_a == sval_b)
 	{
 	  gcc_assert (sval_a);
-- 
2.26.2

