From 17edadfcda64a98c3d0dcc8a385a13bd046cdaa3 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Mon, 27 Apr 2020 06:35:56 -0400
Subject: [PATCH 077/251] FIXME: initial implementation of
 handle_unrecognized_call

---
 gcc/analyzer/region-model2.cc                 | 186 ++++++++++++------
 gcc/analyzer/region-model2.h                  |   2 +-
 gcc/analyzer/store2.cc                        |  38 ++++
 gcc/analyzer/store2.h                         |  22 +++
 gcc/testsuite/gcc.dg/analyzer/unknown-fns-2.c |  55 ++++++
 5 files changed, 242 insertions(+), 61 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/unknown-fns-2.c

diff --git a/gcc/analyzer/region-model2.cc b/gcc/analyzer/region-model2.cc
index ad12526d643..f5c37d2991b 100644
--- a/gcc/analyzer/region-model2.cc
+++ b/gcc/analyzer/region-model2.cc
@@ -4112,6 +4112,7 @@ region_model2::on_gcall (const supernode *snode,
 			  integer_zero_node,
 			  NULL/*&ctxt*/); // FIXME
       warning_at (call->location, 0, "%s", t.as_string ());
+      return true;
     }
 
   if (tree callee_fndecl = get_fndecl_for_call (call, ctxt))
@@ -4171,9 +4172,12 @@ region_model2::on_gcall (const supernode *snode,
 	      return true;
 	    }
 	  /* Otherwise an unknown value.  */
+	  return true;
 	}
     }
 
+  handle_unrecognized_call (call, ctxt);
+
   return true;
 }
 
@@ -4368,81 +4372,138 @@ region_model2::on_call_post (const gcall *call,
    track of all region2s that are reachable, and, of those, which are
    mutable.  */
 
-#if 0
 class reachable_region2s
 {
 public:
-  reachable_region2s (region_model2 *model)
-  : m_model (model), m_reachable_regs (), m_mutable_regs ()
+  reachable_region2s (store2 *store)
+  : m_store (store), m_reachable_regs (), m_mutable_regs ()
   {}
 
-  /* Lazily mark RID as being reachable, recursively adding region2s
-     reachable from RID.  */
-  void add (region2 *reg, bool is_mutable)
+  static void add_any_globals_cb (const region2 *base_reg,
+				  reachable_region2s *this_ptr)
+  {
+    this_ptr->add_any_globals (base_reg);
+  }
+
+  void add_any_globals (const region2 *base_reg)
+  {
+    const region2 *parent = base_reg->get_parent_region ();
+    gcc_assert (parent);
+    if (parent->get_kind () == region2::RK_GLOBALS)
+      add (base_reg, true);
+  }
+
+  /* Lazily mark the cluster containing REG as being reachable, recursively
+     adding clusters reachable from REG's cluster.  */
+  void add (const region2 *reg, bool is_mutable)
   {
     gcc_assert (reg);
 
-    unsigned idx = rid.as_int ();
-    /* Bail out if this region2 is already in the sets at the IS_MUTABLE
+    const region2 *base_reg = const_cast <region2 *> (reg->get_base_region ());
+    gcc_assert (base_reg);
+
+    /* Bail out if this cluster is already in the sets at the IS_MUTABLE
        level of mutability.  */
-    if (!is_mutable && bitmap_bit_p (m_reachable_regs, idx))
+    if (!is_mutable && m_reachable_regs.contains (base_reg))
       return;
-    bitmap_set_bit (m_reachable_regs, idx);
+    m_reachable_regs.add (base_reg);
 
     if (is_mutable)
       {
-	if (bitmap_bit_p (m_mutable_regs, idx))
+	if (m_mutable_regs.contains (base_reg))
 	  return;
 	else
-	  bitmap_set_bit (m_mutable_regs, idx);
+	  m_mutable_regs.add (base_reg);
       }
 
-    /* If this region2's value is a pointer, add the pointee.  */
-    region2 *reg = m_model->get_region2 (rid);
-    svalue2 *sval = reg->get_value_direct ();
-    svalue2 *sval = m_model->get_svalue2 (sid);
-    if (sval)
-      if (region_svalue2 *ptr = sval->dyn_cast_region_svalue2 ())
-	{
-	  region2 *pointee_reg = ptr->get_pointee ();
-	  /* Use const-ness of pointer type to affect mutability.  */
-	  bool ptr_is_mutable = true;
-	  if (ptr->get_type ()
-	      && TREE_CODE (ptr->get_type ()) == POINTER_TYPE
-	      && TYPE_READONLY (TREE_TYPE (ptr->get_type ())))
-	    ptr_is_mutable = false;
-	  add (pointee_reg, ptr_is_mutable);
-	}
+    /* If any values within the cluster are pointers, add the pointee.  */
+    binding_cluster2 *bind_cluster = m_store->get_cluster (base_reg);
+    bind_cluster->for_each_value (handle_sval_cb, this);
+  }
+
+  static void handle_sval_cb (svalue2 *sval, reachable_region2s *this_ptr)
+  {
+    this_ptr->handle_sval (sval);
+  }
+
+  /* If SVAL is a pointer, add the pointee.  */
+
+  void handle_sval (svalue2 *sval)
+  {
+    if (region_svalue2 *ptr = sval->dyn_cast_region_svalue2 ())
+      {
+	region2 *pointee = ptr->get_pointee ();
+	/* Use const-ness of pointer type to affect mutability.  */
+	bool ptr_is_mutable = true;
+	if (ptr->get_type ()
+	    && TREE_CODE (ptr->get_type ()) == POINTER_TYPE
+	    && TYPE_READONLY (TREE_TYPE (ptr->get_type ())))
+	  ptr_is_mutable = false;
+	add (pointee, ptr_is_mutable);
+      }
+  }
+
+  /* Update m_store to set the clusters that were found to be mutable
+     to "unknown" values.  */
+
+  void update_bindings (store2_manager *mgr)
+  {
+    for (hash_set<const region2 *>::iterator iter = m_mutable_regs.begin ();
+	 iter != m_mutable_regs.end (); ++iter)
+      {
+	const region2 *base_reg = *iter;
+	m_store->set_cluster_to_unknown (mgr, base_reg);
+      }
+  }
 
-    /* Add descendents of this region2.  */
-    region2_id_set descendents (m_model);
-    m_model->get_descendents (rid, &descendents, region2_id::null ());
-    for (unsigned i = 0; i < m_model->get_num_region2s (); i++)
+  void dump_to_pp (pretty_printer *pp) const
+  {
+    pp_string (pp, "reachable clusters: ");
+    pp_newline (pp);
+    for (hash_set<const region2 *>::iterator iter = m_reachable_regs.begin ();
+	 iter != m_reachable_regs.end (); ++iter)
       {
-	region2 *iter_reg = region2_id::from_int (i);
-	if (descendents.region2_p (iter_reg))
-	  add (iter_reg, is_mutable);
+	(*iter)->dump_to_pp (pp, true);
+	pp_newline (pp);
       }
+    pp_string (pp, "mutable clusters: ");
+    pp_newline (pp);
+    for (hash_set<const region2 *>::iterator iter = m_mutable_regs.begin ();
+	 iter != m_mutable_regs.end (); ++iter)
+      {
+	(*iter)->dump_to_pp (pp, true);
+	pp_newline (pp);
+      }
+  }
+
+  DEBUG_FUNCTION void dump () const
+  {
+    pretty_printer pp;
+    pp_format_decoder (&pp) = default_tree_printer;
+    pp_show_color (&pp) = pp_show_color (global_dc->printer);
+    pp.buffer->stream = stderr;
+    dump_to_pp (&pp);
+    pp_flush (&pp);
   }
 
+#if 0
   bool mutable_p (region2 *reg)
   {
     gcc_assert (reg);
     return bitmap_bit_p (m_mutable_regs, rid.as_int ());
   }
+#endif
 
 private:
-  region_model2 *m_model;
+  store2 *m_store;
 
-  /* The region2 ids already seen.  This has to be an auto_bitmap rather than
-     an auto_sbitmap as new region2s can be created within the model during
-     the traversal.  */
-  auto_bitmap m_reachable_regs;
+  // FIXME: actually, the clusters
+  /* The regions already seen.  */
+  hash_set<const region2 *> m_reachable_regs;
 
-  /* The region2_ids that can be changed (accessed via non-const pointers).  */
-  auto_bitmap m_mutable_regs;
+  /* The regions that can be changed (accessed via non-const pointers).  */
+  hash_set<const region2 *> m_mutable_regs;
 };
-#endif
 
 /* Handle a call CALL to a function with unknown behavior.
 
@@ -4452,22 +4513,24 @@ private:
 
    Set all reachable region2s to new unknown values and purge sm-state
    from their values, and from values that point to them.  */
-#if 0
 void
 region_model2::handle_unrecognized_call (const gcall *call,
-					region_model2_context *ctxt)
+					 region_model2_context *ctxt)
 {
   tree fndecl = get_fndecl_for_call (call, ctxt);
 
-  reachable_region2s reachable_region2s (this);
+  reachable_region2s reachable_regions (&m_store);
 
   /* Determine the reachable region2s and their mutability.  */
   {
     /* Globals.  */
+    m_store.for_each_cluster (reachable_region2s::add_any_globals_cb,
+			      &reachable_regions);
+#if 0
     region2 *globals_reg = get_globals_region2 *();
     if (!globals_reg.null_p ())
       reachable_region2s.add (globals_reg, true);
-
+#endif
     /* Params that are pointers.  */
     tree iter_param_types = NULL_TREE;
     if (fndecl)
@@ -4485,22 +4548,23 @@ region_model2::handle_unrecognized_call (const gcall *call,
 
 	tree parm = gimple_call_arg (call, arg_idx);
 	svalue2 *parm_sval = get_rvalue (parm, ctxt);
-	svalue2 *parm_sval = get_svalue2 (parm_sval);
-	if (parm_sval)
-	  if (region_svalue2 *parm_ptr = parm_sval->dyn_cast_region_svalue2 ())
-	    {
-	      region2 *pointee_reg = parm_ptr->get_pointee ();
-	      bool is_mutable = true;
-	      if (param_type
-		  && TREE_CODE (param_type) == POINTER_TYPE
-		  &&  TYPE_READONLY (TREE_TYPE (param_type)))
-		is_mutable = false;
-	      reachable_region2s.add (pointee_reg, is_mutable);
-	    }
+	if (region_svalue2 *parm_ptr = parm_sval->dyn_cast_region_svalue2 ())
+	  {
+	    region2 *pointee_reg = parm_ptr->get_pointee ();
+	    bool is_mutable = true;
+	    if (param_type
+		&& TREE_CODE (param_type) == POINTER_TYPE
+		&&  TYPE_READONLY (TREE_TYPE (param_type)))
+	      is_mutable = false;
+	    reachable_regions.add (pointee_reg, is_mutable);
+	  }
 	// FIXME: what about compound parms that contain ptrs?
       }
   }
 
+#if 1
+  reachable_regions.update_bindings (m_mgr->get_store2_manager ());
+#else
   /* OK: we now have all reachable region2s.
      Set them all to new unknown values.  */
   for (unsigned i = 0; i < get_num_region2s (); i++)
@@ -4520,7 +4584,9 @@ region_model2::handle_unrecognized_call (const gcall *call,
 				    ctxt);
 	}
     }
+#endif
 
+#if 0
   /* Purge sm-state for any remaining svalue2s that point to region2s that
      were reachable.  This helps suppress leak false-positives.
 
@@ -4540,8 +4606,8 @@ region_model2::handle_unrecognized_call (const gcall *call,
       }
 
   validate ();
-}
 #endif
+}
 
 /* Update this model for the RETURN_STMT, using CTXT to report any
    diagnostics.  */
diff --git a/gcc/analyzer/region-model2.h b/gcc/analyzer/region-model2.h
index 1d3f366b4fe..05e550cdb39 100644
--- a/gcc/analyzer/region-model2.h
+++ b/gcc/analyzer/region-model2.h
@@ -1742,9 +1742,9 @@ class region_model2
   void on_call_post (const gcall *stmt,
 		     bool unknown_side_effects,
 		     region_model2_context *ctxt);
+#endif
   void handle_unrecognized_call (const gcall *call,
 				 region_model2_context *ctxt);
-#endif
   bool on_greturn (const supernode *snode,
 		   const greturn *stmt);
   void on_setjmp (const gcall *stmt, const exploded_node *enode,
diff --git a/gcc/analyzer/store2.cc b/gcc/analyzer/store2.cc
index 2a82b3f00fa..650133e9836 100644
--- a/gcc/analyzer/store2.cc
+++ b/gcc/analyzer/store2.cc
@@ -292,6 +292,15 @@ binding_cluster2::remove_overlapping_bindings (store2_manager *mgr,
     m_map.remove (binding);
 }
 
+/* FIXME.  */
+
+void
+binding_cluster2::set_cluster_to_unknown (store2_manager *mgr)
+{
+  m_map.empty ();
+  // TODO: need to add an "unknown" default value
+}
+
 /* class store2_manager.  */
 
 /* binding consolidation.  */
@@ -509,6 +518,35 @@ store2::clobber_region (store2_manager *mgr, region2 *reg)
 
 /* FIXME.  */
 
+binding_cluster2 *
+store2::get_cluster (const region2 *base_reg)
+{
+  gcc_assert (base_reg);
+  gcc_assert (base_reg->get_base_region () == base_reg);
+  if (binding_cluster2 **slot = m_cluster_map.get (base_reg))
+    return *slot;
+  else
+    return NULL;
+}
+
+/* FIXME.
+   For use when handling an unrecognized function call.  */
+/* FIXME: do we want to mark the cluster as having escaped?
+   Further function calls could touch it, even if the cluster
+   isn't reachable from args of those calls.  */
+
+void
+store2::set_cluster_to_unknown (store2_manager *mgr, const region2 *base_reg)
+{
+  gcc_assert (base_reg);
+  gcc_assert (base_reg->get_base_region () == base_reg);
+
+  if (binding_cluster2 **cluster_slot = m_cluster_map.get (base_reg))
+    (*cluster_slot)->set_cluster_to_unknown (mgr);
+}
+
+/* FIXME.  */
+
 void
 store2::remove_overlapping_bindings (store2_manager *mgr, region2 *reg)
 {
diff --git a/gcc/analyzer/store2.h b/gcc/analyzer/store2.h
index 33c482c9afe..cde6c263171 100644
--- a/gcc/analyzer/store2.h
+++ b/gcc/analyzer/store2.h
@@ -177,6 +177,15 @@ public:
 
   void remove_overlapping_bindings (store2_manager *mgr, region2 *reg);
 
+  template <typename T>
+  void for_each_value (void (*cb) (svalue2 *sval, T user_data), T user_data)
+  {
+    for (map_t::iterator iter = m_map.begin (); iter != m_map.end (); ++iter)
+      cb ((*iter).second, user_data);
+  }
+
+  void set_cluster_to_unknown (store2_manager *mgr);
+
 private:
   void get_overlapping_bindings (store2_manager *mgr, region2 *reg,
 				 auto_vec<const binding_key2 *> *out);
@@ -212,6 +221,19 @@ public:
   void set_value (store2_manager *mgr, region2 *lhs_reg, svalue2 *rhs_sval);
   void clobber_region (store2_manager *mgr, region2 *reg);
 
+  binding_cluster2 *get_cluster (const region2 *base_reg);
+
+  template <typename T>
+  void for_each_cluster (void (*cb) (const region2 *base_reg, T user_data),
+			 T user_data)
+  {
+    for (cluster_map_t::iterator iter = m_cluster_map.begin ();
+	 iter != m_cluster_map.end (); ++iter)
+      cb ((*iter).first, user_data);
+  }
+
+  void set_cluster_to_unknown (store2_manager *mgr, const region2 *base_reg);
+
 private:
   void remove_overlapping_bindings (store2_manager *mgr, region2 *reg);
 
diff --git a/gcc/testsuite/gcc.dg/analyzer/unknown-fns-2.c b/gcc/testsuite/gcc.dg/analyzer/unknown-fns-2.c
new file mode 100644
index 00000000000..61194ab0b33
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/unknown-fns-2.c
@@ -0,0 +1,55 @@
+/* Tests for data model handling of unknown fns.  */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <alloca.h>
+#include "analyzer-decls.h"
+
+void unknown_fn (void *);
+
+void test_1 (void)
+{
+  int i;
+
+  i = 42;
+  __analyzer_eval (i == 42); /* { dg-warning "TRUE" } */
+
+  unknown_fn (NULL);
+  __analyzer_eval (i == 42); /* { dg-warning "TRUE" } */
+  
+  unknown_fn (&i);
+  __analyzer_eval (i == 42); /* { dg-warning "UNKNOWN" } */
+
+  i = 17;
+  __analyzer_eval (i == 17); /* { dg-warning "TRUE" } */
+
+  /* Even though we're not passing &i to unknown_fn, it escaped
+     above, so unknown_fn could write to it.  */
+  unknown_fn (NULL);
+  __analyzer_eval (i == 17); /* { dg-warning "UNKNOWN" } */
+}
+
+int *global_for_test_2;
+
+void test_2 (void)
+{
+  int i;
+
+  i = 42;
+  __analyzer_eval (i == 42); /* { dg-warning "TRUE" } */
+
+  global_for_test_2 = &i;
+  unknown_fn (NULL);
+  __analyzer_eval (i == 42); /* { dg-warning "UNKNOWN" } */
+
+  global_for_test_2 = NULL;
+
+  i = 17;
+  __analyzer_eval (i == 17); /* { dg-warning "TRUE" } */
+
+  /* Even though the global no longer points to i, it escaped
+     above, so unknown_fn could write to it.  */
+  unknown_fn (NULL);
+  __analyzer_eval (i == 17); /* { dg-warning "UNKNOWN" } */
+}
-- 
2.26.2

