From cfa59ed5440a9939c0382509f61d558d97e5ec10 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Mon, 29 Jul 2019 14:18:05 -0400
Subject: [PATCH 040/124] FIXME: combined conditionals at higher optimization
 levels

---
 gcc/analyzer/impl-4.cc                         | 21 +++++++
 gcc/analyzer/region-model.cc                   | 87 ++++++++++++++++++++++++++
 gcc/analyzer/region-model.h                    |  6 ++
 gcc/analyzer/sm-file.cc                        | 20 ++++++
 gcc/analyzer/sm-malloc.cc                      | 35 +++++++++++
 gcc/analyzer/sm-pattern-test.cc                | 20 ++++++
 gcc/analyzer/sm-sensitive.cc                   | 18 ++++++
 gcc/analyzer/sm-taint.cc                       | 20 ++++++
 gcc/analyzer/sm.h                              |  5 ++
 gcc/testsuite/gcc.dg/analyzer/conditionals-2.c | 44 +++++++++++++
 gcc/testsuite/gcc.dg/analyzer/zlib-5.c         |  3 +
 11 files changed, 279 insertions(+)
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/conditionals-2.c

diff --git a/gcc/analyzer/impl-4.cc b/gcc/analyzer/impl-4.cc
index 6c14d55..cad86b7 100644
--- a/gcc/analyzer/impl-4.cc
+++ b/gcc/analyzer/impl-4.cc
@@ -3121,6 +3121,8 @@ class impl_region_model_context : public region_model_context, public log_user
   void on_inherited_svalue (svalue_id parent_sid,
 			    svalue_id child_sid) FINAL OVERRIDE;
 
+  void on_condition (tree lhs, enum tree_code op, tree rhs) FINAL OVERRIDE;
+
   exploded_graph *m_eg;
   const exploded_node *m_enode;
   program_state *m_new_state;
@@ -3472,6 +3474,25 @@ impl_region_model_context::on_inherited_svalue (svalue_id parent_sid,
     }
 }
 
+/* FIXME.  */
+
+void
+impl_region_model_context::on_condition (tree lhs, enum tree_code op, tree rhs)
+{
+  int sm_idx;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (m_new_state->m_checker_states, sm_idx, smap)
+    {
+      const state_machine &sm = m_ext_state.get_sm (sm_idx);
+      impl_sm_context sm_ctxt (*m_eg, sm, m_enode, m_new_state,
+			       m_enode->get_state ().m_checker_states[sm_idx],
+			       m_new_state->m_checker_states[sm_idx]);
+      sm.on_condition (&sm_ctxt,
+		       m_enode->get_supernode (), m_enode->get_stmt (),
+		       lhs, op, rhs);
+    }
+}
+
 ////////////////////////////////////////////////////////////////////////////
 
 /* class program_state.  */
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index e2f394c..1071e62 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -2965,6 +2965,8 @@ region_model::on_assignment (const gassign *assign, region_model_context *ctxt)
 				 ? boolean_true_node
 				 : boolean_false_node,
 				 ctxt));
+	else
+	  set_to_new_unknown_value (lhs_rid, TREE_TYPE (lhs));
       }
       break;
 
@@ -3667,6 +3669,91 @@ region_model::add_constraint (tree lhs, enum tree_code op, tree rhs,
   /* Store the constraint.  */
   m_constraints->add_constraint (lhs_sid, op, rhs_sid);
 
+  /* Handle optimized && and || conditionals.
+     If we have an SSA_NAME for a boolean compared against 0,
+     look at anything implied by the def stmt.  */
+  if (TREE_CODE (lhs) == SSA_NAME
+      && rhs == boolean_false_node)
+    {
+      gimple *def_stmt = SSA_NAME_DEF_STMT (lhs);
+      if (gassign *assign = dyn_cast<gassign *> (def_stmt))
+	{
+	  enum tree_code rhs_code = gimple_assign_rhs_code (assign);
+	  /* If we have "LHS != false", i.e. LHS is true... */
+	  if (op == NE_EXPR)
+	    {
+	      switch (rhs_code)
+		{
+		default:
+		  break;
+		case BIT_AND_EXPR:
+		  {
+		    /* ...and "LHS == (rhs1 & rhs2) i.e. "(rhs1 & rhs2)" is true
+		       then both rhs1 and rhs2 must be true.  */
+		    tree rhs1 = gimple_assign_rhs1 (assign);
+		    tree rhs2 = gimple_assign_rhs2 (assign);
+		    add_constraint (rhs1, NE_EXPR, boolean_false_node, ctxt);
+		    add_constraint (rhs2, NE_EXPR, boolean_false_node, ctxt);
+		  }
+		  break;
+
+		case EQ_EXPR:
+		case NE_EXPR:
+		  {
+		    /* ...and "LHS == (rhs1 OP rhs2)"
+		       then rhs1 OP rhs2 must be true.  */
+		    tree rhs1 = gimple_assign_rhs1 (assign);
+		    tree rhs2 = gimple_assign_rhs2 (assign);
+		    add_constraint (rhs1, rhs_code, rhs2, ctxt);
+		  }
+		  break;
+		}
+	    }
+
+	  /* If we have "LHS == false", i.e. LHS is false... */
+	  if (op == EQ_EXPR)
+	    {
+	      switch (rhs_code)
+		{
+		default:
+		  break;
+		case BIT_IOR_EXPR:
+		  {
+		    /* ...and "LHS == (rhs1 | rhs2)
+		       i.e. "(rhs1 | rhs2)" is false
+		       then both rhs1 and rhs2 must be false.  */
+		    tree rhs1 = gimple_assign_rhs1 (assign);
+		    tree rhs2 = gimple_assign_rhs2 (assign);
+		    add_constraint (rhs1, EQ_EXPR, boolean_false_node, ctxt);
+		    add_constraint (rhs2, EQ_EXPR, boolean_false_node, ctxt);
+		    //gcc_unreachable ();
+		  }
+		  break;
+		case EQ_EXPR:
+		case NE_EXPR:
+		  {
+		    /* ...and hence "false == (rhs1 OP rhs2)"
+		       then rhs1 OP rhs2 must be false.  */
+		    tree rhs1 = gimple_assign_rhs1 (assign);
+		    tree rhs2 = gimple_assign_rhs2 (assign);
+		    add_constraint (rhs1,
+				    invert_tree_comparison (rhs_code,
+							    false /* honor_nans */),
+				    rhs2, ctxt);
+		  }
+		  break;
+		}
+	    }
+	}
+    }
+
+  /* Notify the context, if any.  This is used to notify program_state
+     FIXME... and thus to set sm-state for e.g. unchecked->checked when
+     synthesizing constraints as above.
+     FIXME: could also use it for edges: but would we need a return value?  */
+  if (ctxt)
+    ctxt->on_condition (lhs, op, rhs);
+
   return true;
 }
 
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index abd158f..d6de239 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -1267,6 +1267,8 @@ class region_model_context
   virtual void on_inherited_svalue (svalue_id parent_sid,
 				    svalue_id child_sid) = 0;
 
+  /* FIXME.  */
+  virtual void on_condition (tree lhs, enum tree_code op, tree rhs) = 0;
 };
 
 /* A bundle of data for use when attempting to merge two region_model
@@ -1428,6 +1430,10 @@ public:
 
   unsigned get_num_diagnostics () const { return m_diagnostics.length (); }
 
+  void on_condition (tree lhs, enum tree_code op, tree rhs) FINAL OVERRIDE
+  {
+  }
+
 private:
   /* Implicitly delete any diagnostics in the dtor.  */
   auto_delete_vec<pending_diagnostic> m_diagnostics;
diff --git a/gcc/analyzer/sm-file.cc b/gcc/analyzer/sm-file.cc
index 26e6466..cba2c95 100644
--- a/gcc/analyzer/sm-file.cc
+++ b/gcc/analyzer/sm-file.cc
@@ -76,6 +76,13 @@ public:
 		const gimple *last_stmt,
 		const superedge *edge) const FINAL OVERRIDE;
 
+  void on_condition (sm_context *sm_ctxt,
+		     const supernode *node,
+		     const gimple *stmt,
+		     tree lhs,
+		     enum tree_code op,
+		     tree rhs) const FINAL OVERRIDE;
+
   void on_leak (sm_context *sm_ctxt,
 		const supernode *node,
 		const gimple *stmt,
@@ -322,6 +329,19 @@ fileptr_state_machine::on_edge (sm_context *sm_ctxt,
   // TODO: something similar for switch statements?
 }
 
+/* FIXME.  */
+
+void
+fileptr_state_machine::on_condition (sm_context *sm_ctxt,
+				     const supernode *node,
+				     const gimple *stmt,
+				     tree lhs,
+				     enum tree_code op,
+				     tree rhs) const
+{
+  // TODO
+}
+
 void
 fileptr_state_machine::on_leak (sm_context *sm_ctxt,
 				const supernode *node,
diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc
index 7eab9f9..9558b1c 100644
--- a/gcc/analyzer/sm-malloc.cc
+++ b/gcc/analyzer/sm-malloc.cc
@@ -77,6 +77,13 @@ public:
 		const gimple *last_stmt,
 		const superedge *edge) const FINAL OVERRIDE;
 
+  void on_condition (sm_context *sm_ctxt,
+		     const supernode *node,
+		     const gimple *stmt,
+		     tree lhs,
+		     enum tree_code op,
+		     tree rhs) const FINAL OVERRIDE;
+
   void on_leak (sm_context *sm_ctxt,
 		const supernode *node,
 		const gimple *stmt,
@@ -796,6 +803,34 @@ malloc_state_machine::on_edge (sm_context *sm_ctxt,
   // TODO: something similar for switch statements?
 }
 
+/* FIXME.  */
+
+void
+malloc_state_machine::on_condition (sm_context *sm_ctxt,
+				    const supernode *node,
+				    const gimple *stmt,
+				    tree lhs,
+				    enum tree_code op,
+				    tree rhs) const
+{
+  // TODO
+  if (!zerop (rhs))
+    return;
+
+  if (op == NE_EXPR)
+    {
+      log ("got 'ARG != 0' match");
+      sm_ctxt->on_transition (node, stmt,
+			      lhs, m_unchecked, m_nonnull);
+    }
+  else if (op == EQ_EXPR)
+    {
+      log ("got 'ARG == 0' match");
+      sm_ctxt->on_transition (node, stmt,
+			      lhs, m_unchecked, m_null);
+    }
+}
+
 void
 malloc_state_machine::on_leak (sm_context *sm_ctxt,
 			       const supernode *node,
diff --git a/gcc/analyzer/sm-pattern-test.cc b/gcc/analyzer/sm-pattern-test.cc
index 5025e3a..8aae9ac 100644
--- a/gcc/analyzer/sm-pattern-test.cc
+++ b/gcc/analyzer/sm-pattern-test.cc
@@ -78,6 +78,13 @@ public:
 		const gimple *last_stmt,
 		const superedge *edge) const FINAL OVERRIDE;
 
+  void on_condition (sm_context *sm_ctxt,
+		     const supernode *node,
+		     const gimple *stmt,
+		     tree lhs,
+		     enum tree_code op,
+		     tree rhs) const FINAL OVERRIDE;
+
   void on_leak (sm_context *sm_ctxt,
 		const supernode *node,
 		const gimple *stmt,
@@ -183,6 +190,19 @@ pattern_test_state_machine::on_edge (sm_context *sm_ctxt,
     }
 }
 
+/* FIXME.  */
+
+void
+pattern_test_state_machine::on_condition (sm_context *sm_ctxt,
+					  const supernode *node,
+					  const gimple *stmt,
+					  tree lhs,
+					  enum tree_code op,
+					  tree rhs) const
+{
+  // TODO
+}
+
 void
 pattern_test_state_machine::on_leak (sm_context *sm_ctxt ATTRIBUTE_UNUSED,
 				     const supernode *node ATTRIBUTE_UNUSED,
diff --git a/gcc/analyzer/sm-sensitive.cc b/gcc/analyzer/sm-sensitive.cc
index a8d899e..dbfc126 100644
--- a/gcc/analyzer/sm-sensitive.cc
+++ b/gcc/analyzer/sm-sensitive.cc
@@ -77,6 +77,11 @@ public:
 		const gimple *last_stmt,
 		const superedge *edge) const FINAL OVERRIDE;
 
+void on_condition (sm_context *sm_ctxt,
+		   const supernode *node,
+		   const gimple *stmt,
+		   tree lhs, enum tree_code op, tree rhs) const FINAL OVERRIDE;
+
   void on_leak (sm_context *sm_ctxt,
 		const supernode *node,
 		const gimple *stmt,
@@ -193,6 +198,19 @@ sensitive_state_machine::on_edge (sm_context *sm_ctxt ATTRIBUTE_UNUSED,
   /* Empty.  */
 }
 
+/* FIXME.  */
+
+void
+sensitive_state_machine::on_condition (sm_context *sm_ctxt,
+				       const supernode *node,
+				       const gimple *stmt,
+				       tree lhs,
+				       enum tree_code op,
+				       tree rhs) const
+{
+  /* Empty.  */
+}
+
 void
 sensitive_state_machine::on_leak (sm_context *sm_ctxt ATTRIBUTE_UNUSED,
 				  const supernode *node ATTRIBUTE_UNUSED,
diff --git a/gcc/analyzer/sm-taint.cc b/gcc/analyzer/sm-taint.cc
index 4275a1b..e8c15b9 100644
--- a/gcc/analyzer/sm-taint.cc
+++ b/gcc/analyzer/sm-taint.cc
@@ -78,6 +78,13 @@ public:
 		const gimple *last_stmt,
 		const superedge *edge) const FINAL OVERRIDE;
 
+  void on_condition (sm_context *sm_ctxt,
+		     const supernode *node,
+		     const gimple *stmt,
+		     tree lhs,
+		     enum tree_code op,
+		     tree rhs) const FINAL OVERRIDE;
+
   void on_leak (sm_context *sm_ctxt,
 		const supernode *node,
 		const gimple *stmt,
@@ -273,6 +280,19 @@ taint_state_machine::on_edge (sm_context *sm_ctxt,
   // TODO: something similar for switch statements?
 }
 
+/* FIXME.  */
+
+void
+taint_state_machine::on_condition (sm_context *sm_ctxt,
+				   const supernode *node,
+				   const gimple *stmt,
+				   tree lhs,
+				   enum tree_code op,
+				   tree rhs) const
+{
+  // TODO
+}
+
 void
 taint_state_machine::on_leak (sm_context *sm_ctxt ATTRIBUTE_UNUSED,
 			      const supernode *node ATTRIBUTE_UNUSED,
diff --git a/gcc/analyzer/sm.h b/gcc/analyzer/sm.h
index 7c6905f..1f64356 100644
--- a/gcc/analyzer/sm.h
+++ b/gcc/analyzer/sm.h
@@ -75,6 +75,11 @@ public:
 			const gimple *last_stmt,
 			const superedge *edge) const = 0;
 
+  virtual void on_condition (sm_context *sm_ctxt,
+			     const supernode *node,
+			     const gimple *stmt,
+			     tree lhs, enum tree_code op, tree rhs) const = 0;
+
   virtual void on_leak (sm_context *sm_ctxt,
 			const supernode *node,
 			const gimple *stmt,
diff --git a/gcc/testsuite/gcc.dg/analyzer/conditionals-2.c b/gcc/testsuite/gcc.dg/analyzer/conditionals-2.c
new file mode 100644
index 0000000..f3f1ac7
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/conditionals-2.c
@@ -0,0 +1,44 @@
+// TODO: run this test case at every optimization level
+/* { dg-additional-options "-O2" } */
+
+#include <stddef.h>
+
+#define Z_NULL 0
+
+static void __attribute__((noinline))
+test_1_callee (void *p, void *q)
+{
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+  __analyzer_eval (p == Z_NULL); /* { dg-warning "FALSE" } */
+  __analyzer_eval (p != Z_NULL); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (q == Z_NULL); /* { dg-warning "FALSE" } */ 
+  __analyzer_eval (q != Z_NULL); /* { dg-warning "TRUE" } */ 
+}
+
+void test_1 (void *p, void *q)
+{
+  if (p == Z_NULL || q == Z_NULL)
+    return;
+
+  test_1_callee (p, q);
+}
+
+static void __attribute__((noinline))
+test_2_callee (void *p, void *q)
+{
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
+  __analyzer_eval (p == Z_NULL); /* { dg-warning "FALSE" } */
+  __analyzer_eval (p != Z_NULL); /* { dg-warning "TRUE" } */
+
+  __analyzer_eval (q == Z_NULL); /* { dg-warning "FALSE" } */ 
+  __analyzer_eval (q != Z_NULL); /* { dg-warning "TRUE" } */ 
+}
+
+void test_2 (void *p, void *q)
+{
+  if (p != Z_NULL && q != Z_NULL)
+    test_2_callee (p, q);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/zlib-5.c b/gcc/testsuite/gcc.dg/analyzer/zlib-5.c
index bb321a6..bcf8a0a 100644
--- a/gcc/testsuite/gcc.dg/analyzer/zlib-5.c
+++ b/gcc/testsuite/gcc.dg/analyzer/zlib-5.c
@@ -39,6 +39,9 @@ int main(int argc, char *argv[]) {
   uncompr = (Byte *)calloc((uInt)uncomprLen, 1);
   if (compr == 0 || uncompr == 0)
     exit(1);
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
+
   test_compress(compr, comprLen, uncompr, uncomprLen);
 
   exit(0);
-- 
1.8.5.3

