From afef0e947727e346af52520e60ab5216eef3b419 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Thu, 23 May 2019 17:11:36 -0400
Subject: [PATCH 64/67] FIXME: function pointers

---
 gcc/analyzer/region-model.cc                   | 146 +++++++++++++++++++------
 gcc/analyzer/region-model.h                    |   6 +-
 gcc/testsuite/gcc.dg/analyzer/function-ptr-2.c |  28 +++++
 3 files changed, 146 insertions(+), 34 deletions(-)

diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index 1cdee31..13ce827 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -404,6 +404,7 @@ hashval_t
 constant_svalue::hash () const
 {
   inchash::hash hstate;
+  // FIXME: what if it's a fndecl?
   inchash::add_expr (m_cst_expr, hstate);
   return hstate.end ();
 }
@@ -416,7 +417,10 @@ constant_svalue::print_details (const region_model &model ATTRIBUTE_UNUSED,
 				pretty_printer *pp) const
 {
 PUSH_IGNORE_WFORMAT
-  pp_printf (pp, "%qE", m_cst_expr);
+  if (TREE_CODE (m_cst_expr) == FUNCTION_DECL)
+    pp_printf (pp, "%qD", m_cst_expr);
+  else
+    pp_printf (pp, "%qE", m_cst_expr);
 POP_IGNORE_WFORMAT
 }
 
@@ -2055,6 +2059,8 @@ region_model::get_rvalue (tree expr)
     case ADDR_EXPR:
       {
 	/* "&EXPR".  */
+	if (TREE_CODE (TREE_OPERAND (expr, 0)) == FUNCTION_DECL)
+	  return get_svalue_for_fndecl (TREE_OPERAND (expr, 0));
 	region_id expr_rid = get_lvalue (TREE_OPERAND (expr, 0));
 	return get_svalue_for_region (expr_rid);
       }
@@ -2111,6 +2117,12 @@ region_model::get_svalue_for_constant (tree cst_expr)
   return cst_sid;
 }
 
+svalue_id
+region_model::get_svalue_for_fndecl (tree fndecl)
+{
+  return get_svalue_for_constant (fndecl);
+}
+
 /* FIXME.  */
 
 region_id
@@ -2119,44 +2131,75 @@ region_model::deref_rvalue (tree ptr)
   svalue_id ptr_sid = get_rvalue (ptr);
   gcc_assert (!ptr_sid.null_p ());
   svalue *ptr_svalue = get_svalue (ptr_sid);
-  if (ptr_svalue)
+  gcc_assert (ptr_svalue);
+
+  switch (ptr_svalue->get_kind ())
     {
-      if (region_svalue *region_sval
-	  = ptr_svalue->dyn_cast_region_svalue ())
+    case SK_REGION:
+      {
+	region_svalue *region_sval = ptr_svalue->dyn_cast_region_svalue ();
+	// FIXME: ^^ as_a
 	return region_sval->get_pointee ();
+      }
 
-      if (constant_svalue *constant_sval
-	  = ptr_svalue->dyn_cast_constant_svalue ())
-	{
-	  gcc_unreachable (); // TODO
-	}
+    case SK_CONSTANT:
+      {
+	constant_svalue *constant_sval
+	  = ptr_svalue->dyn_cast_constant_svalue ();
+	// FIXME: ^^ as_a
 
-      if (unknown_svalue *unknown_sval
-	  = ptr_svalue->dyn_cast_unknown_svalue ())
-	{
-	  /* We need a symbolic_region to represent this unknown region.
-	     We don't know if it on the heap, stack, or a global,
-	     so use the root region as parent.  */
-	  region_id new_rid = add_region (new symbolic_region (m_root_rid));
-
-	  /* We need to write the region back into the pointer,
-	     or we'll get a new, different region each time.
-	     We do this by changing the meaning of ptr_sid, replacing
-	     the unknown value with the ptr to the new region.
-	     We replace the meaning of the ID rather than simply writing
-	     to PTR's lvalue since there could be several places sharing
-	     the same unknown ptr value.  */
-	  svalue *ptr_val = new region_svalue (TREE_TYPE (ptr), new_rid);
-	  replace_svalue (ptr_sid, ptr_val);
+	/* TODO: issue a warning (presumably using some kind of context
+	   within the region_model?).  */
+	// FIXME: the malloc state machine already complains about this
+#if 0
+	tree tree_cst = constant_sval->get_constant ();
+	if (zerop (tree_cst))
+	  warning (0, "null pointer deref FIXME");
+#endif
 
-	  return new_rid;
-	}
+	goto create_symbolic_region;
+	// map of regions per constant?
+      }
 
-      if (poisoned_svalue *poisoned_sval
-	  = ptr_svalue->dyn_cast_poisoned_svalue ())
-	{
-	  gcc_unreachable (); // TODO
-	}
+    case SK_POISONED:
+      {
+	poisoned_svalue *poisoned_sval
+	  = ptr_svalue->dyn_cast_poisoned_svalue ();
+	// FIXME: ^^ as_a
+	switch (poisoned_sval->get_poison_kind ())
+	  {
+	  default:
+	    gcc_unreachable ();
+	  case POISON_KIND_UNINIT:
+	    warning (0, "deref of uninitialized pointer");
+	    break;
+	  case POISON_KIND_FREED:
+	    warning (0, "use-after-free");
+	    break;
+	  }
+	goto create_symbolic_region;
+      }
+
+    case SK_UNKNOWN:
+      {
+      create_symbolic_region:
+	/* We need a symbolic_region to represent this unknown region.
+	   We don't know if it on the heap, stack, or a global,
+	   so use the root region as parent.  */
+	region_id new_rid = add_region (new symbolic_region (m_root_rid));
+
+	/* We need to write the region back into the pointer,
+	   or we'll get a new, different region each time.
+	   We do this by changing the meaning of ptr_sid, replacing
+	   the unknown value with the ptr to the new region.
+	   We replace the meaning of the ID rather than simply writing
+	   to PTR's lvalue since there could be several places sharing
+	   the same unknown ptr value.  */
+	svalue *ptr_val = new region_svalue (TREE_TYPE (ptr), new_rid);
+	replace_svalue (ptr_sid, ptr_val);
+
+	return new_rid;
+      }
     }
 
   gcc_unreachable ();
@@ -2255,6 +2298,43 @@ region_model::eval_condition (svalue_id lhs_sid,
 	  {
 	    tree lhs_const = cst_lhs->get_constant ();
 	    tree rhs_const = cst_rhs->get_constant ();
+
+	    /* If one or both of the "constants" are function pointers, then
+	       we have equality iff the trees are equal.  */
+	    if (TREE_CODE (lhs_const) == FUNCTION_DECL
+		|| TREE_CODE (rhs_const) == FUNCTION_DECL)
+	      {
+		bool eq = (lhs_const == rhs_const);
+		switch (op)
+		  {
+		  default:
+		    gcc_unreachable ();
+
+		  case EQ_EXPR:
+		    return tristate (eq);
+
+		  case GE_EXPR:
+		  case LE_EXPR:
+		    if (eq)
+		      return tristate (true);
+		    else
+		      return tristate (tristate::TS_UNKNOWN);
+
+		  case NE_EXPR:
+		    return tristate (lhs_const != rhs_const);
+
+		  case GT_EXPR:
+		  case LT_EXPR:
+		    if (eq)
+		      return tristate (false);
+		    return tristate::TS_UNKNOWN;
+		  }
+	      }
+
+	    /* Otherwise, we have a pair of constants; compare them.  */
+	    gcc_assert (CONSTANT_CLASS_P (lhs_const));
+	    gcc_assert (CONSTANT_CLASS_P (rhs_const));
+
 	    tree comparison
 	      = fold_build2 (op, boolean_type_node, lhs_const, rhs_const);
 	    if (comparison == boolean_true_node)
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index c418a06..9fb355f 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -220,7 +220,8 @@ public:
   region_id m_rid;
 };
 
-/* Concrete subclass of svalue representing a specific constant value.  */
+/* Concrete subclass of svalue representing a specific constant value
+   (or a FUNCTION_DECL).  */
 
 class constant_svalue : public svalue
 {
@@ -309,6 +310,8 @@ public:
 
   poisoned_svalue *dyn_cast_poisoned_svalue () FINAL OVERRIDE { return this; }
 
+  enum poison_kind get_poison_kind () const { return m_kind; }
+
  private:
   void print_details (const region_model &model,
 		      svalue_id this_sid,
@@ -678,6 +681,7 @@ class region_model
 
   svalue_id get_svalue_for_region (region_id id);
   svalue_id get_svalue_for_constant (tree cst_expr);
+  svalue_id get_svalue_for_fndecl (tree fndecl);
 
   region_id deref_rvalue (tree ptr);
 
diff --git a/gcc/testsuite/gcc.dg/analyzer/function-ptr-2.c b/gcc/testsuite/gcc.dg/analyzer/function-ptr-2.c
index 8623a9f..44263bf 100644
--- a/gcc/testsuite/gcc.dg/analyzer/function-ptr-2.c
+++ b/gcc/testsuite/gcc.dg/analyzer/function-ptr-2.c
@@ -1,3 +1,5 @@
+/* { dg-additional-options "-fanalyzer-region-model" } */
+
 #include <stdlib.h>
 
 typedef void (*fn_ptr_t) (void *);
@@ -8,10 +10,36 @@ calls_free (void *victim)
   free (victim);
 }
 
+void
+no_op (void *ptr)
+{
+}
+
 void test_1 (void *ptr)
 {
   fn_ptr_t fn_ptr = calls_free;
+  __analyzer_eval (fn_ptr == calls_free); /* { dg-warning "TRUE" } */
+  __analyzer_eval (fn_ptr != NULL); /* { dg-warning "TRUE" } */
+  __analyzer_eval (fn_ptr == NULL); /* { dg-warning "FALSE" } */
+  __analyzer_eval (fn_ptr == no_op); /* { dg-warning "FALSE" } */
+
   fn_ptr (ptr);
   fn_ptr (ptr);
 }
 // TODO: issue a double-free warning at 2nd call to fn_ptr.
+
+/* As above, but with an extra indirection to try to thwart
+   the optimizer.  */
+
+void test_2 (void *ptr, fn_ptr_t *fn_ptr)
+{
+  *fn_ptr = calls_free;
+  __analyzer_eval (*fn_ptr == calls_free); /* { dg-warning "TRUE" } */
+  __analyzer_eval (*fn_ptr != NULL); /* { dg-warning "TRUE" } */
+  __analyzer_eval (*fn_ptr == NULL); /* { dg-warning "FALSE" } */
+  __analyzer_eval (*fn_ptr == no_op); /* { dg-warning "FALSE" } */
+
+  (*fn_ptr) (ptr);
+  (*fn_ptr) (ptr);
+}
+// TODO: issue a double-free warning at 2nd call to fn_ptr.
-- 
1.8.5.3

