diff mbox series

[committed] analyzer: remove add_any_constraints_from_ssa_def_stmt

Message ID 20210707233220.1801632-1-dmalcolm@redhat.com
State New
Headers show
Series [committed] analyzer: remove add_any_constraints_from_ssa_def_stmt | expand

Commit Message

David Malcolm July 7, 2021, 11:32 p.m. UTC
I'm working on reimplementing -Wanalyzer-use-of-uninitialized-value, but
I ran into issues with
region_model::add_any_constraints_from_ssa_def_stmt.
This function is from the initial commit of the analyzer and walks the
SSA names finding conditions that were missed due to the GCC 10 era
region_model not retaining useful information on how values were
created; as of GCC 11 the symbolic values contain this information,
and so the conditions can be reconstructed from them instead.

region_model::add_any_constraints_from_ssa_def_stmt is a liability
when tracking uninitialized values as it requires looking up SSA
values when those values may have been purged, thus greatly complicating
detection of uses of uninitialized values.

It's simplest to eliminate it and reimplement the condition-finding
via the makeup of the svalues, which this patch does.  Doing so requires
supporting add_condition on svalues rather than just on trees, which
requires some changes to ana::state_machine and its subclasses.

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
Pushed to trunk as 48e8a7a677b8356df946cd12fbb215538828e747.

gcc/analyzer/ChangeLog:
	* diagnostic-manager.cc (null_assignment_sm_context::get_state):
	New overload.
	(null_assignment_sm_context::set_next_state): New overload.
	(null_assignment_sm_context::get_diagnostic_tree): New.
	* engine.cc (impl_sm_context::get_state): New overload.
	(impl_sm_context::set_next_state): New overload.
	(impl_sm_context::get_diagnostic_tree): New overload.
	(impl_region_model_context::on_condition): Convert params from
	tree to const svalue *.
	* exploded-graph.h (impl_region_model_context::on_condition):
	Likewise.
	* region-model.cc (region_model::on_call_pre): Move handling of
	internal calls to before checking for get_fndecl_for_call.
	(region_model::add_constraints_from_binop): New.
	(region_model::add_constraint): Split out into a new overload
	working on const svalue * rather than tree.  Call
	add_constraints_from_binop.  Drop call to
	add_any_constraints_from_ssa_def_stmt.
	(region_model::add_any_constraints_from_ssa_def_stmt): Delete.
	(region_model::add_any_constraints_from_gassign): Delete.
	(region_model::add_any_constraints_from_gcall): Delete.
	* region-model.h
	(region_model::add_any_constraints_from_ssa_def_stmt): Delete.
	(region_model::add_any_constraints_from_gassign): Delete.
	(region_model::add_any_constraints_from_gcall): Delete.
	(region_model::add_constraint): Add overload decl.
	(region_model::add_constraints_from_binop): New decl.
	(region_model_context::on_condition): Convert params from tree to
	const svalue *.
	(noop_region_model_context::on_condition): Likewise.
	* sm-file.cc (fileptr_state_machine::condition): Likewise.
	* sm-malloc.cc (malloc_state_machine::on_condition): Likewise.
	* sm-pattern-test.cc: Include tristate.h, selftest.h,
	analyzer/call-string.h, analyzer/program-point.h,
	analyzer/store.h, and analyzer/region-model.h.
	(pattern_test_state_machine::on_condition): Convert params from tree to
	const svalue *.
	* sm-sensitive.cc (sensitive_state_machine::on_condition): Delete.
	* sm-signal.cc (signal_state_machine::on_condition): Delete.
	* sm-taint.cc (taint_state_machine::on_condition): Convert params
	from tree to const svalue *.
	* sm.cc: Include tristate.h, selftest.h, analyzer/call-string.h,
	analyzer/program-point.h, analyzer/store.h, and
	analyzer/region-model.h.
	(any_pointer_p): Add overload taking const svalue *sval.
	* sm.h (any_pointer_p): Add overload taking const svalue *sval.
	(state_machine::on_condition): Convert params from tree to
	const svalue *.  Provide no-op default implementation.
	(sm_context::get_state): Add overload taking const svalue *sval.
	(sm_context::set_next_state): Likewise.
	(sm_context::on_transition): Likewise.
	(sm_context::get_diagnostic_tree): Likewise.
	* svalue.cc (svalue::all_zeroes_p): New.
	(constant_svalue::all_zeroes_p): New.
	(repeated_svalue::all_zeroes_p): Convert to vfunc.
	* svalue.h (svalue::all_zeroes_p): New decl.
	(constant_svalue::all_zeroes_p): New decl.
	(repeated_svalue::all_zeroes_p): Convert decl to vfunc.

gcc/testsuite/ChangeLog:
	* gcc.dg/analyzer/pattern-test-2.c: Update expected results.
	* gcc.dg/plugin/analyzer_gil_plugin.c
	(gil_state_machine::on_condition): Remove.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
---
 gcc/analyzer/diagnostic-manager.cc            |  35 ++
 gcc/analyzer/engine.cc                        |  54 +++-
 gcc/analyzer/exploded-graph.h                 |   4 +-
 gcc/analyzer/region-model.cc                  | 304 +++++++++---------
 gcc/analyzer/region-model.h                   |  29 +-
 gcc/analyzer/sm-file.cc                       |  15 +-
 gcc/analyzer/sm-malloc.cc                     |  10 +-
 gcc/analyzer/sm-pattern-test.cc               |  24 +-
 gcc/analyzer/sm-sensitive.cc                  |  18 --
 gcc/analyzer/sm-signal.cc                     |  21 --
 gcc/analyzer/sm-taint.cc                      |   8 +-
 gcc/analyzer/sm.cc                            |  14 +
 gcc/analyzer/sm.h                             |  34 +-
 gcc/analyzer/svalue.cc                        |  24 +-
 gcc/analyzer/svalue.h                         |   6 +-
 .../gcc.dg/analyzer/pattern-test-2.c          |  10 +-
 .../gcc.dg/plugin/analyzer_gil_plugin.c       |  21 --
 17 files changed, 361 insertions(+), 270 deletions(-)
diff mbox series

Patch

diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc
index 7eb4ed8a4f2..b7d263b4217 100644
--- a/gcc/analyzer/diagnostic-manager.cc
+++ b/gcc/analyzer/diagnostic-manager.cc
@@ -1377,6 +1377,14 @@  struct null_assignment_sm_context : public sm_context
     return current;
   }
 
+  state_machine::state_t get_state (const gimple *stmt ATTRIBUTE_UNUSED,
+				    const svalue *sval) FINAL OVERRIDE
+  {
+    const sm_state_map *old_smap = m_old_state->m_checker_states[m_sm_idx];
+    state_machine::state_t current = old_smap->get_state (sval, m_ext_state);
+    return current;
+  }
+
   void set_next_state (const gimple *stmt,
 		       tree var,
 		       state_machine::state_t to,
@@ -1401,6 +1409,28 @@  struct null_assignment_sm_context : public sm_context
 							*m_new_state));
   }
 
+  void set_next_state (const gimple *stmt,
+		       const svalue *sval,
+		       state_machine::state_t to,
+		       tree origin ATTRIBUTE_UNUSED) FINAL OVERRIDE
+  {
+    state_machine::state_t from = get_state (stmt, sval);
+    if (from != m_sm.get_start_state ())
+      return;
+
+    const supernode *supernode = m_point->get_supernode ();
+    int stack_depth = m_point->get_stack_depth ();
+
+    m_emission_path->add_event (new state_change_event (supernode,
+							m_stmt,
+							stack_depth,
+							m_sm,
+							sval,
+							from, to,
+							NULL,
+							*m_new_state));
+  }
+
   void warn (const supernode *, const gimple *,
 	     tree, pending_diagnostic *d) FINAL OVERRIDE
   {
@@ -1412,6 +1442,11 @@  struct null_assignment_sm_context : public sm_context
     return expr;
   }
 
+  tree get_diagnostic_tree (const svalue *sval) FINAL OVERRIDE
+  {
+    return m_new_state->m_region_model->get_representative_tree (sval);
+  }
+
   state_machine::state_t get_global_state () const FINAL OVERRIDE
   {
     return 0;
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index 4456d9b828b..01b83a4ef28 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -245,6 +245,16 @@  public:
       = m_old_smap->get_state (var_old_sval, m_eg.get_ext_state ());
     return current;
   }
+  state_machine::state_t get_state (const gimple *stmt ATTRIBUTE_UNUSED,
+				    const svalue *sval)
+  {
+    logger * const logger = get_logger ();
+    LOG_FUNC (logger);
+    state_machine::state_t current
+      = m_old_smap->get_state (sval, m_eg.get_ext_state ());
+    return current;
+  }
+
 
   void set_next_state (const gimple *stmt,
 		       tree var,
@@ -280,6 +290,41 @@  public:
 			   to, origin_new_sval, m_eg.get_ext_state ());
   }
 
+  void set_next_state (const gimple *stmt,
+		       const svalue *sval,
+		       state_machine::state_t to,
+		       tree origin)
+  {
+    logger * const logger = get_logger ();
+    LOG_FUNC (logger);
+    impl_region_model_context old_ctxt
+      (m_eg, m_enode_for_diag, NULL, NULL/*m_enode->get_state ()*/,
+       NULL, stmt);
+
+    impl_region_model_context new_ctxt (m_eg, m_enode_for_diag,
+					m_old_state, m_new_state,
+					NULL,
+					stmt);
+    const svalue *origin_new_sval
+      = m_new_state->m_region_model->get_rvalue (origin, &new_ctxt);
+
+    state_machine::state_t current
+      = m_old_smap->get_state (sval, m_eg.get_ext_state ());
+    if (logger)
+      {
+	logger->start_log_line ();
+	logger->log_partial ("%s: state transition of ",
+			     m_sm.get_name ());
+	sval->dump_to_pp (logger->get_printer (), true);
+	logger->log_partial (": %s -> %s",
+			     current->get_name (),
+			     to->get_name ());
+	logger->end_log_line ();
+      }
+    m_new_smap->set_state (m_new_state->m_region_model, sval,
+			   to, origin_new_sval, m_eg.get_ext_state ());
+  }
+
   void warn (const supernode *snode, const gimple *stmt,
 	     tree var, pending_diagnostic *d) FINAL OVERRIDE
   {
@@ -323,6 +368,11 @@  public:
       return expr;
   }
 
+  tree get_diagnostic_tree (const svalue *sval) FINAL OVERRIDE
+  {
+    return m_new_state->m_region_model->get_representative_tree (sval);
+  }
+
   state_machine::state_t get_global_state () const FINAL OVERRIDE
   {
     return m_old_state->m_checker_states[m_sm_idx]->get_global_state ();
@@ -654,7 +704,9 @@  impl_region_model_context::on_state_leak (const state_machine &sm,
    state transitions.  */
 
 void
-impl_region_model_context::on_condition (tree lhs, enum tree_code op, tree rhs)
+impl_region_model_context::on_condition (const svalue *lhs,
+					 enum tree_code op,
+					 const svalue *rhs)
 {
   int sm_idx;
   sm_state_map *smap;
diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h
index eb1baefad69..2d25e5e5167 100644
--- a/gcc/analyzer/exploded-graph.h
+++ b/gcc/analyzer/exploded-graph.h
@@ -59,7 +59,9 @@  class impl_region_model_context : public region_model_context
 		      const svalue *sval,
 		      state_machine::state_t state);
 
-  void on_condition (tree lhs, enum tree_code op, tree rhs) FINAL OVERRIDE;
+  void on_condition (const svalue *lhs,
+		     enum tree_code op,
+		     const svalue *rhs) FINAL OVERRIDE;
 
   void on_unknown_change (const svalue *sval, bool is_mutable) FINAL OVERRIDE;
 
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index 4fb6bc9f747..acbbd112543 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -881,12 +881,23 @@  bool
 region_model::on_call_pre (const gcall *call, region_model_context *ctxt,
 			   bool *out_terminate_path)
 {
+  call_details cd (call, this, ctxt);
+
   bool unknown_side_effects = false;
 
-  if (tree callee_fndecl = get_fndecl_for_call (call, ctxt))
+  if (gimple_call_internal_p (call))
     {
-      call_details cd (call, this, ctxt);
+      switch (gimple_call_internal_fn (call))
+       {
+       default:
+	 break;
+       case IFN_BUILTIN_EXPECT:
+	 return impl_call_builtin_expect (cd);
+       }
+    }
 
+  if (tree callee_fndecl = get_fndecl_for_call (call, ctxt))
+    {
       /* The various impl_call_* member functions are implemented
 	 in region-model-impl-calls.cc.
 	 Having them split out into separate functions makes it easier
@@ -958,16 +969,6 @@  region_model::on_call_pre (const gcall *call, region_model_context *ctxt,
 	       on the return value.  */
 	    break;
 	  }
-      else if (gimple_call_internal_p (call))
-	switch (gimple_call_internal_fn (call))
-	  {
-	  default:
-	    if (!DECL_PURE_P (callee_fndecl))
-	      unknown_side_effects = true;
-	    break;
-	  case IFN_BUILTIN_EXPECT:
-	    return impl_call_builtin_expect (cd);
-	  }
       else if (is_named_call_p (callee_fndecl, "malloc", call, 1))
 	return impl_call_malloc (cd);
       else if (is_named_call_p (callee_fndecl, "calloc", call, 2))
@@ -2251,6 +2252,123 @@  region_model::compare_initial_and_pointer (const initial_svalue *init,
   return tristate::TS_UNKNOWN;
 }
 
+/* Handle various constraints of the form:
+     LHS: ((bool)INNER_LHS INNER_OP INNER_RHS))
+     OP : == or !=
+     RHS: zero
+   and (with a cast):
+     LHS: CAST([long]int, ((bool)INNER_LHS INNER_OP INNER_RHS))
+     OP : == or !=
+     RHS: zero
+   by adding constraints for INNER_LHS INNEROP INNER_RHS.
+
+   Return true if this function can fully handle the constraint; if
+   so, add the implied constraint(s) and write true to *OUT if they
+   are consistent with existing constraints, or write false to *OUT
+   if they contradicts existing constraints.
+
+   Return false for cases that this function doeesn't know how to handle.
+
+   For example, if we're checking a stored conditional, we'll have
+   something like:
+     LHS: CAST(long int, (&HEAP_ALLOCATED_REGION(8)!=(int *)0B))
+     OP : NE_EXPR
+     RHS: zero
+   which this function can turn into an add_constraint of:
+     (&HEAP_ALLOCATED_REGION(8) != (int *)0B)
+
+   Similarly, optimized && and || conditionals lead to e.g.
+     if (p && q)
+   becoming gimple like this:
+     _1 = p_6 == 0B;
+     _2 = q_8 == 0B
+     _3 = _1 | _2
+   On the "_3 is false" branch we can have constraints of the form:
+     ((&HEAP_ALLOCATED_REGION(8)!=(int *)0B)
+      | (&HEAP_ALLOCATED_REGION(10)!=(int *)0B))
+     == 0
+   which implies that both _1 and _2 are false,
+   which this function can turn into a pair of add_constraints of
+     (&HEAP_ALLOCATED_REGION(8)!=(int *)0B)
+   and:
+     (&HEAP_ALLOCATED_REGION(10)!=(int *)0B).  */
+
+bool
+region_model::add_constraints_from_binop (const svalue *outer_lhs,
+					  enum tree_code outer_op,
+					  const svalue *outer_rhs,
+					  bool *out,
+					  region_model_context *ctxt)
+{
+  while (const svalue *cast = outer_lhs->maybe_undo_cast ())
+    outer_lhs = cast;
+  const binop_svalue *binop_sval = outer_lhs->dyn_cast_binop_svalue ();
+  if (!binop_sval)
+    return false;
+  if (!outer_rhs->all_zeroes_p ())
+    return false;
+
+  const svalue *inner_lhs = binop_sval->get_arg0 ();
+  enum tree_code inner_op = binop_sval->get_op ();
+  const svalue *inner_rhs = binop_sval->get_arg1 ();
+
+  if (outer_op != NE_EXPR && outer_op != EQ_EXPR)
+    return false;
+
+  /* We have either
+     - "OUTER_LHS != false" (i.e. OUTER is true), or
+     - "OUTER_LHS == false" (i.e. OUTER is false).  */
+  bool is_true = outer_op == NE_EXPR;
+
+  switch (inner_op)
+    {
+    default:
+      return false;
+
+    case EQ_EXPR:
+    case NE_EXPR:
+      {
+	/* ...and "(inner_lhs OP inner_rhs) == 0"
+	   then (inner_lhs OP inner_rhs) must have the same
+	   logical value as LHS.  */
+	if (!is_true)
+	  inner_op = invert_tree_comparison (inner_op, false /* honor_nans */);
+	*out = add_constraint (inner_lhs, inner_op, inner_rhs, ctxt);
+	return true;
+      }
+      break;
+
+    case BIT_AND_EXPR:
+      if (is_true)
+	{
+	  /* ...and "(inner_lhs & inner_rhs) != 0"
+	     then both inner_lhs and inner_rhs must be true.  */
+	  const svalue *false_sval
+	    = m_mgr->get_or_create_constant_svalue (boolean_false_node);
+	  bool sat1 = add_constraint (inner_lhs, NE_EXPR, false_sval, ctxt);
+	  bool sat2 = add_constraint (inner_rhs, NE_EXPR, false_sval, ctxt);
+	  *out = sat1 && sat2;
+	  return true;
+	}
+      return false;
+
+    case BIT_IOR_EXPR:
+      if (!is_true)
+	{
+	  /* ...and "(inner_lhs | inner_rhs) == 0"
+	     i.e. "(inner_lhs | inner_rhs)" is false
+	     then both inner_lhs and inner_rhs must be false.  */
+	  const svalue *false_sval
+	    = m_mgr->get_or_create_constant_svalue (boolean_false_node);
+	  bool sat1 = add_constraint (inner_lhs, EQ_EXPR, false_sval, ctxt);
+	  bool sat2 = add_constraint (inner_rhs, EQ_EXPR, false_sval, ctxt);
+	  *out = sat1 && sat2;
+	  return true;
+	}
+      return false;
+    }
+}
+
 /* Attempt to add the constraint "LHS OP RHS" to this region_model.
    If it is consistent with existing constraints, add it, and return true.
    Return false if it contradicts existing constraints.
@@ -2268,7 +2386,21 @@  region_model::add_constraint (tree lhs, enum tree_code op, tree rhs,
   const svalue *lhs_sval = get_rvalue (lhs, ctxt);
   const svalue *rhs_sval = get_rvalue (rhs, ctxt);
 
-  tristate t_cond = eval_condition (lhs_sval, op, rhs_sval);
+  return add_constraint (lhs_sval, op, rhs_sval, ctxt);
+}
+
+/* Attempt to add the constraint "LHS OP RHS" to this region_model.
+   If it is consistent with existing constraints, add it, and return true.
+   Return false if it contradicts existing constraints.
+   Use CTXT for reporting any diagnostics associated with the accesses.  */
+
+bool
+region_model::add_constraint (const svalue *lhs,
+			      enum tree_code op,
+			      const svalue *rhs,
+			      region_model_context *ctxt)
+{
+  tristate t_cond = eval_condition (lhs, op, rhs);
 
   /* If we already have the condition, do nothing.  */
   if (t_cond.is_true ())
@@ -2279,10 +2411,12 @@  region_model::add_constraint (tree lhs, enum tree_code op, tree rhs,
   if (t_cond.is_false ())
     return false;
 
-  /* Store the constraint.  */
-  m_constraints->add_constraint (lhs_sval, op, rhs_sval);
+  bool out;
+  if (add_constraints_from_binop (lhs, op, rhs, &out, ctxt))
+    return out;
 
-  add_any_constraints_from_ssa_def_stmt (lhs, op, rhs, ctxt);
+  /* Store the constraint.  */
+  m_constraints->add_constraint (lhs, op, rhs);
 
   /* Notify the context, if any.  This exists so that the state machines
      in a program_state can be notified about the condition, and so can
@@ -2293,9 +2427,10 @@  region_model::add_constraint (tree lhs, enum tree_code op, tree rhs,
 
   /* If we have &REGION == NULL, then drop dynamic extents for REGION (for
      the case where REGION is heap-allocated and thus could be NULL).  */
-  if (op == EQ_EXPR && zerop (rhs))
-    if (const region_svalue *region_sval = lhs_sval->dyn_cast_region_svalue ())
-      unset_dynamic_extents (region_sval->get_pointee ());
+  if (tree rhs_cst = rhs->maybe_get_constant ())
+    if (op == EQ_EXPR && zerop (rhs_cst))
+      if (const region_svalue *region_sval = lhs->dyn_cast_region_svalue ())
+	unset_dynamic_extents (region_sval->get_pointee ());
 
   return true;
 }
@@ -2314,137 +2449,6 @@  region_model::add_constraint (tree lhs, enum tree_code op, tree rhs,
   return sat;
 }
 
-/* Subroutine of region_model::add_constraint for handling optimized
-   && and || conditionals.
-
-   If we have an SSA_NAME for a boolean compared against 0,
-   look at anything implied by the def stmt and call add_constraint
-   for it (which could recurse).
-
-   For example, if we have
-      _1 = p_6 == 0B;
-      _2 = p_8 == 0B
-      _3 = _1 | _2
-    and add the constraint
-      (_3 == 0),
-    then the def stmt for _3 implies that _1 and _2 are both false,
-    and hence we can add the constraints:
-      p_6 != 0B
-      p_8 != 0B.  */
-
-void
-region_model::add_any_constraints_from_ssa_def_stmt (tree lhs,
-						     enum tree_code op,
-						     tree rhs,
-						     region_model_context *ctxt)
-{
-  if (TREE_CODE (lhs) != SSA_NAME)
-    return;
-
-  if (!zerop (rhs))
-    return;
-
-  if (op != NE_EXPR && op != EQ_EXPR)
-    return;
-
-  gimple *def_stmt = SSA_NAME_DEF_STMT (lhs);
-  if (const gassign *assign = dyn_cast<gassign *> (def_stmt))
-    add_any_constraints_from_gassign (op, rhs, assign, ctxt);
-  else if (gcall *call = dyn_cast<gcall *> (def_stmt))
-    add_any_constraints_from_gcall (op, rhs, call, ctxt);
-}
-
-/* Add any constraints for an SSA_NAME defined by ASSIGN
-   where the result OP RHS.  */
-
-void
-region_model::add_any_constraints_from_gassign (enum tree_code op,
-						tree rhs,
-						const gassign *assign,
-						region_model_context *ctxt)
-{
-  /* We have either
-     - "LHS != false" (i.e. LHS is true), or
-     - "LHS == false" (i.e. LHS is false).  */
-  bool is_true = op == NE_EXPR;
-
-  enum tree_code rhs_code = gimple_assign_rhs_code (assign);
-
-  switch (rhs_code)
-    {
-    default:
-      break;
-
-    case NOP_EXPR:
-    case VIEW_CONVERT_EXPR:
-      {
-	add_constraint (gimple_assign_rhs1 (assign), op, rhs, ctxt);
-      }
-      break;
-
-    case BIT_AND_EXPR:
-      {
-	if (is_true)
-	  {
-	    /* ...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 BIT_IOR_EXPR:
-      {
-	if (!is_true)
-	  {
-	    /* ...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);
-	  }
-      }
-      break;
-
-    case EQ_EXPR:
-    case NE_EXPR:
-      {
-	/* ...and "LHS == (rhs1 OP rhs2)"
-	   then rhs1 OP rhs2 must have the same logical value as LHS.  */
-	tree rhs1 = gimple_assign_rhs1 (assign);
-	tree rhs2 = gimple_assign_rhs2 (assign);
-	if (!is_true)
-	  rhs_code
-	    = invert_tree_comparison (rhs_code, false /* honor_nans */);
-	add_constraint (rhs1, rhs_code, rhs2, ctxt);
-      }
-      break;
-    }
-}
-
-/* Add any constraints for an SSA_NAME defined by CALL
-   where the result OP RHS.  */
-
-void
-region_model::add_any_constraints_from_gcall (enum tree_code op,
-					      tree rhs,
-					      const gcall *call,
-					      region_model_context *ctxt)
-{
-  if (gimple_call_builtin_p (call, BUILT_IN_EXPECT)
-      || gimple_call_builtin_p (call, BUILT_IN_EXPECT_WITH_PROBABILITY)
-      || gimple_call_internal_p (call, IFN_BUILTIN_EXPECT))
-    {
-      /* __builtin_expect's return value is its initial argument.  */
-      add_constraint (gimple_call_arg (call, 0), op, rhs, ctxt);
-    }
-}
-
 /* Determine what is known about the condition "LHS OP RHS" within
    this model.
    Use CTXT for reporting any diagnostics associated with the accesses.  */
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index b42852b3db9..cf5232dfab8 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -691,18 +691,15 @@  class region_model
   get_representative_path_var_1 (const region *reg,
 				 svalue_set *visited) const;
 
-  void add_any_constraints_from_ssa_def_stmt (tree lhs,
-					      enum tree_code op,
-					      tree rhs,
-					      region_model_context *ctxt);
-  void add_any_constraints_from_gassign (enum tree_code op,
-					 tree rhs,
-					 const gassign *assign,
-					 region_model_context *ctxt);
-  void add_any_constraints_from_gcall (enum tree_code op,
-				       tree rhs,
-				       const gcall *call,
-				       region_model_context *ctxt);
+  bool add_constraint (const svalue *lhs,
+		       enum tree_code op,
+		       const svalue *rhs,
+		       region_model_context *ctxt);
+  bool add_constraints_from_binop (const svalue *outer_lhs,
+				   enum tree_code outer_op,
+				   const svalue *outer_rhs,
+				   bool *out,
+				   region_model_context *ctxt);
 
   void update_for_call_superedge (const call_superedge &call_edge,
 				  region_model_context *ctxt);
@@ -781,7 +778,9 @@  class region_model_context
      and use them to trigger sm-state transitions (e.g. transitions due
      to ptrs becoming known to be NULL or non-NULL, rather than just
      "unchecked") */
-  virtual void on_condition (tree lhs, enum tree_code op, tree rhs) = 0;
+  virtual void on_condition (const svalue *lhs,
+			     enum tree_code op,
+			     const svalue *rhs) = 0;
 
   /* Hooks for clients to be notified when an unknown change happens
      to SVAL (in response to a call to an unknown function).  */
@@ -812,9 +811,9 @@  public:
   void on_liveness_change (const svalue_set &,
 			   const region_model *) OVERRIDE {}
   logger *get_logger () OVERRIDE { return NULL; }
-  void on_condition (tree lhs ATTRIBUTE_UNUSED,
+  void on_condition (const svalue *lhs ATTRIBUTE_UNUSED,
 		     enum tree_code op ATTRIBUTE_UNUSED,
-		     tree rhs ATTRIBUTE_UNUSED) OVERRIDE
+		     const svalue *rhs ATTRIBUTE_UNUSED) OVERRIDE
   {
   }
   void on_unknown_change (const svalue *sval ATTRIBUTE_UNUSED,
diff --git a/gcc/analyzer/sm-file.cc b/gcc/analyzer/sm-file.cc
index 3a5f95def34..b40a9a1edb9 100644
--- a/gcc/analyzer/sm-file.cc
+++ b/gcc/analyzer/sm-file.cc
@@ -77,9 +77,9 @@  public:
   void on_condition (sm_context *sm_ctxt,
 		     const supernode *node,
 		     const gimple *stmt,
-		     tree lhs,
+		     const svalue *lhs,
 		     enum tree_code op,
-		     tree rhs) const FINAL OVERRIDE;
+		     const svalue *rhs) const FINAL OVERRIDE;
 
   bool can_purge_p (state_t s) const FINAL OVERRIDE;
   pending_diagnostic *on_leak (tree var) const FINAL OVERRIDE;
@@ -381,19 +381,18 @@  void
 fileptr_state_machine::on_condition (sm_context *sm_ctxt,
 				     const supernode *node,
 				     const gimple *stmt,
-				     tree lhs,
+				     const svalue *lhs,
 				     enum tree_code op,
-				     tree rhs) const
+				     const svalue *rhs) const
 {
-  if (!zerop (rhs))
+  if (!rhs->all_zeroes_p ())
     return;
 
   // TODO: has to be a FILE *, specifically
-  if (TREE_CODE (TREE_TYPE (lhs)) != POINTER_TYPE)
+  if (!any_pointer_p (lhs))
     return;
-
   // TODO: has to be a FILE *, specifically
-  if (TREE_CODE (TREE_TYPE (rhs)) != POINTER_TYPE)
+  if (!any_pointer_p (rhs))
     return;
 
   if (op == NE_EXPR)
diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc
index a1582ca2f1f..40e64b3630f 100644
--- a/gcc/analyzer/sm-malloc.cc
+++ b/gcc/analyzer/sm-malloc.cc
@@ -375,9 +375,9 @@  public:
   void on_condition (sm_context *sm_ctxt,
 		     const supernode *node,
 		     const gimple *stmt,
-		     tree lhs,
+		     const svalue *lhs,
 		     enum tree_code op,
-		     tree rhs) const FINAL OVERRIDE;
+		     const svalue *rhs) const FINAL OVERRIDE;
 
   bool can_purge_p (state_t s) const FINAL OVERRIDE;
   pending_diagnostic *on_leak (tree var) const FINAL OVERRIDE;
@@ -1863,11 +1863,11 @@  void
 malloc_state_machine::on_condition (sm_context *sm_ctxt,
 				    const supernode *node ATTRIBUTE_UNUSED,
 				    const gimple *stmt,
-				    tree lhs,
+				    const svalue *lhs,
 				    enum tree_code op,
-				    tree rhs) const
+				    const svalue *rhs) const
 {
-  if (!zerop (rhs))
+  if (!rhs->all_zeroes_p ())
     return;
 
   if (!any_pointer_p (lhs))
diff --git a/gcc/analyzer/sm-pattern-test.cc b/gcc/analyzer/sm-pattern-test.cc
index 43b847559f8..4e285492d04 100644
--- a/gcc/analyzer/sm-pattern-test.cc
+++ b/gcc/analyzer/sm-pattern-test.cc
@@ -37,6 +37,12 @@  along with GCC; see the file COPYING3.  If not see
 #include "analyzer/analyzer-logging.h"
 #include "analyzer/sm.h"
 #include "analyzer/pending-diagnostic.h"
+#include "tristate.h"
+#include "selftest.h"
+#include "analyzer/call-string.h"
+#include "analyzer/program-point.h"
+#include "analyzer/store.h"
+#include "analyzer/region-model.h"
 
 #if ENABLE_ANALYZER
 
@@ -61,9 +67,9 @@  public:
   void on_condition (sm_context *sm_ctxt,
 		     const supernode *node,
 		     const gimple *stmt,
-		     tree lhs,
+		     const svalue *lhs,
 		     enum tree_code op,
-		     tree rhs) const FINAL OVERRIDE;
+		     const svalue *rhs) const FINAL OVERRIDE;
 
   bool can_purge_p (state_t s) const FINAL OVERRIDE;
 };
@@ -118,18 +124,22 @@  void
 pattern_test_state_machine::on_condition (sm_context *sm_ctxt,
 					  const supernode *node,
 					  const gimple *stmt,
-					  tree lhs,
+					  const svalue *lhs,
 					  enum tree_code op,
-					  tree rhs) const
+					  const svalue *rhs) const
 {
   if (stmt == NULL)
     return;
 
-  if (!CONSTANT_CLASS_P (rhs))
+  tree rhs_cst = rhs->maybe_get_constant ();
+  if (!rhs_cst)
     return;
 
-  pending_diagnostic *diag = new pattern_match (lhs, op, rhs);
-  sm_ctxt->warn (node, stmt, lhs, diag);
+  if (tree lhs_expr = sm_ctxt->get_diagnostic_tree (lhs))
+    {
+      pending_diagnostic *diag = new pattern_match (lhs_expr, op, rhs_cst);
+      sm_ctxt->warn (node, stmt, lhs_expr, diag);
+    }
 }
 
 bool
diff --git a/gcc/analyzer/sm-sensitive.cc b/gcc/analyzer/sm-sensitive.cc
index 9703f7e7916..4add55e91fb 100644
--- a/gcc/analyzer/sm-sensitive.cc
+++ b/gcc/analyzer/sm-sensitive.cc
@@ -58,13 +58,6 @@  public:
 		const supernode *node,
 		const gimple *stmt) 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;
-
   bool can_purge_p (state_t s) const FINAL OVERRIDE;
 
   /* State for "sensitive" data, such as a password.  */
@@ -222,17 +215,6 @@  sensitive_state_machine::on_stmt (sm_context *sm_ctxt,
   return false;
 }
 
-void
-sensitive_state_machine::on_condition (sm_context *sm_ctxt ATTRIBUTE_UNUSED,
-				       const supernode *node ATTRIBUTE_UNUSED,
-				       const gimple *stmt ATTRIBUTE_UNUSED,
-				       tree lhs ATTRIBUTE_UNUSED,
-				       enum tree_code op ATTRIBUTE_UNUSED,
-				       tree rhs ATTRIBUTE_UNUSED) const
-{
-  /* Empty.  */
-}
-
 bool
 sensitive_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const
 {
diff --git a/gcc/analyzer/sm-signal.cc b/gcc/analyzer/sm-signal.cc
index 42be8094997..fcbf322f502 100644
--- a/gcc/analyzer/sm-signal.cc
+++ b/gcc/analyzer/sm-signal.cc
@@ -81,13 +81,6 @@  public:
 		const supernode *node,
 		const gimple *stmt) 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;
-
   bool can_purge_p (state_t s) const FINAL OVERRIDE;
 
   /* These states are "global", rather than per-expression.  */
@@ -363,20 +356,6 @@  signal_state_machine::on_stmt (sm_context *sm_ctxt,
   return false;
 }
 
-/* Implementation of state_machine::on_condition vfunc for
-   signal_state_machine.  */
-
-void
-signal_state_machine::on_condition (sm_context *sm_ctxt ATTRIBUTE_UNUSED,
-				    const supernode *node ATTRIBUTE_UNUSED,
-				    const gimple *stmt ATTRIBUTE_UNUSED,
-				    tree lhs ATTRIBUTE_UNUSED,
-				    enum tree_code op ATTRIBUTE_UNUSED,
-				    tree rhs ATTRIBUTE_UNUSED) const
-{
-  // Empty
-}
-
 bool
 signal_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const
 {
diff --git a/gcc/analyzer/sm-taint.cc b/gcc/analyzer/sm-taint.cc
index e2460f9cf5c..721d3eabb8f 100644
--- a/gcc/analyzer/sm-taint.cc
+++ b/gcc/analyzer/sm-taint.cc
@@ -61,9 +61,9 @@  public:
   void on_condition (sm_context *sm_ctxt,
 		     const supernode *node,
 		     const gimple *stmt,
-		     tree lhs,
+		     const svalue *lhs,
 		     enum tree_code op,
-		     tree rhs) const FINAL OVERRIDE;
+		     const svalue *rhs) const FINAL OVERRIDE;
 
   bool can_purge_p (state_t s) const FINAL OVERRIDE;
 
@@ -281,9 +281,9 @@  void
 taint_state_machine::on_condition (sm_context *sm_ctxt,
 				   const supernode *node,
 				   const gimple *stmt,
-				   tree lhs,
+				   const svalue *lhs,
 				   enum tree_code op,
-				   tree rhs ATTRIBUTE_UNUSED) const
+				   const svalue *rhs ATTRIBUTE_UNUSED) const
 {
   if (stmt == NULL)
     return;
diff --git a/gcc/analyzer/sm.cc b/gcc/analyzer/sm.cc
index 2d227dd1be0..db07bf31d29 100644
--- a/gcc/analyzer/sm.cc
+++ b/gcc/analyzer/sm.cc
@@ -35,6 +35,11 @@  along with GCC; see the file COPYING3.  If not see
 #include "analyzer/analyzer.h"
 #include "analyzer/analyzer-logging.h"
 #include "analyzer/sm.h"
+#include "tristate.h"
+#include "analyzer/call-string.h"
+#include "analyzer/program-point.h"
+#include "analyzer/store.h"
+#include "analyzer/svalue.h"
 
 #if ENABLE_ANALYZER
 
@@ -48,6 +53,15 @@  any_pointer_p (tree var)
   return POINTER_TYPE_P (TREE_TYPE (var));
 }
 
+/* Return true if SVAL has pointer or reference type.  */
+
+bool
+any_pointer_p (const svalue *sval)
+{
+  if (!sval->get_type ())
+    return false;
+  return POINTER_TYPE_P (sval->get_type ());
+}
 
 /* class state_machine::state.  */
 
diff --git a/gcc/analyzer/sm.h b/gcc/analyzer/sm.h
index 8d4d030394a..6bb036e343b 100644
--- a/gcc/analyzer/sm.h
+++ b/gcc/analyzer/sm.h
@@ -29,7 +29,8 @@  class state_machine;
 class sm_context;
 class pending_diagnostic;
 
-extern bool any_pointer_p (tree var);
+extern bool any_pointer_p (tree expr);
+extern bool any_pointer_p (const svalue *sval);
 
 /* An abstract base class for a state machine describing an API.
    Manages a set of state objects, and has various virtual functions
@@ -89,10 +90,14 @@  public:
   {
   }
 
-  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_condition (sm_context *sm_ctxt ATTRIBUTE_UNUSED,
+			     const supernode *node ATTRIBUTE_UNUSED,
+			     const gimple *stmt ATTRIBUTE_UNUSED,
+			     const svalue *lhs ATTRIBUTE_UNUSED,
+			     enum tree_code op ATTRIBUTE_UNUSED,
+			     const svalue *rhs ATTRIBUTE_UNUSED) const
+  {
+  }
 
   /* Return true if it safe to discard the given state (to help
      when simplifying state objects).
@@ -182,6 +187,8 @@  public:
   /* Get the old state of VAR at STMT.  */
   virtual state_machine::state_t get_state (const gimple *stmt,
 					    tree var) = 0;
+  virtual state_machine::state_t get_state (const gimple *stmt,
+					    const svalue *) = 0;
   /* Set the next state of VAR to be TO, recording the "origin" of the
      state as ORIGIN.
      Use STMT for location information.  */
@@ -189,6 +196,10 @@  public:
 			       tree var,
 			       state_machine::state_t to,
 			       tree origin = NULL_TREE) = 0;
+  virtual void set_next_state (const gimple *stmt,
+			       const svalue *var,
+			       state_machine::state_t to,
+			       tree origin = NULL_TREE) = 0;
 
   /* Called by state_machine in response to pattern matches:
      if VAR is in state FROM, transition it to state TO, potentially
@@ -206,6 +217,18 @@  public:
       set_next_state (stmt, var, to, origin);
   }
 
+  void on_transition (const supernode *node ATTRIBUTE_UNUSED,
+		      const gimple *stmt,
+		      const svalue *var,
+		      state_machine::state_t from,
+		      state_machine::state_t to,
+		      tree origin = NULL_TREE)
+  {
+    state_machine::state_t current = get_state (stmt, var);
+    if (current == from)
+      set_next_state (stmt, var, to, origin);
+  }
+
   /* Called by state_machine in response to pattern matches:
      issue a diagnostic D using NODE and STMT for location information.  */
   virtual void warn (const supernode *node, const gimple *stmt,
@@ -220,6 +243,7 @@  public:
   {
     return expr;
   }
+  virtual tree get_diagnostic_tree (const svalue *) = 0;
 
   virtual state_machine::state_t get_global_state () const = 0;
   virtual void set_global_state (state_machine::state_t) = 0;
diff --git a/gcc/analyzer/svalue.cc b/gcc/analyzer/svalue.cc
index 6c8afef461b..70c23f016f9 100644
--- a/gcc/analyzer/svalue.cc
+++ b/gcc/analyzer/svalue.cc
@@ -557,6 +557,15 @@  svalue::maybe_fold_bits_within (tree,
   return NULL;
 }
 
+/* Base implementation of svalue::all_zeroes_p.
+   Return true if this value is known to be all zeroes.  */
+
+bool
+svalue::all_zeroes_p () const
+{
+  return false;
+}
+
 /* class region_svalue : public svalue.  */
 
 /* Implementation of svalue::dump_to_pp vfunc for region_svalue.  */
@@ -742,6 +751,14 @@  constant_svalue::maybe_fold_bits_within (tree type,
   return NULL;
 }
 
+/* Implementation of svalue::all_zeroes_p for constant_svalue.  */
+
+bool
+constant_svalue::all_zeroes_p () const
+{
+  return zerop (m_cst_expr);
+}
+
 /* class unknown_svalue : public svalue.  */
 
 /* Implementation of svalue::dump_to_pp vfunc for unknown_svalue.  */
@@ -1154,15 +1171,12 @@  repeated_svalue::accept (visitor *v) const
   m_inner_svalue->accept (v);
 }
 
-/* Return true if this value is known to be all zeroes.  */
+/* Implementation of svalue::all_zeroes_p for repeated_svalue.  */
 
 bool
 repeated_svalue::all_zeroes_p () const
 {
-  if (tree cst = m_inner_svalue->maybe_get_constant ())
-    if (zerop (cst))
-      return true;
-  return false;
+  return m_inner_svalue->all_zeroes_p ();
 }
 
 /* Implementation of svalue::maybe_fold_bits_within vfunc
diff --git a/gcc/analyzer/svalue.h b/gcc/analyzer/svalue.h
index 3965a5f805d..5552fcf4c99 100644
--- a/gcc/analyzer/svalue.h
+++ b/gcc/analyzer/svalue.h
@@ -157,6 +157,8 @@  public:
 			  const bit_range &subrange,
 			  region_model_manager *mgr) const;
 
+  virtual bool all_zeroes_p () const;
+
  protected:
   svalue (complexity c, tree type)
   : m_complexity (c), m_type (type)
@@ -277,6 +279,8 @@  public:
 			  const bit_range &subrange,
 			  region_model_manager *mgr) const FINAL OVERRIDE;
 
+  bool all_zeroes_p () const FINAL OVERRIDE;
+
  private:
   tree m_cst_expr;
 };
@@ -858,7 +862,7 @@  public:
   const svalue *get_outer_size () const { return m_outer_size; }
   const svalue *get_inner_svalue () const { return m_inner_svalue; }
 
-  bool all_zeroes_p () const;
+  bool all_zeroes_p () const FINAL OVERRIDE;
 
   const svalue *
   maybe_fold_bits_within (tree type,
diff --git a/gcc/testsuite/gcc.dg/analyzer/pattern-test-2.c b/gcc/testsuite/gcc.dg/analyzer/pattern-test-2.c
index f5424f526f7..7c8d1b33fc9 100644
--- a/gcc/testsuite/gcc.dg/analyzer/pattern-test-2.c
+++ b/gcc/testsuite/gcc.dg/analyzer/pattern-test-2.c
@@ -25,11 +25,8 @@  void test_2 (void *p, void *q)
     return;
   foo(p);
 
-  /* { dg-warning "pattern match on 'tmp1 == 0'" "tmp1 == 0" { target *-*-* } cond_2 } */
-  /* { dg-warning "pattern match on 'tmp2 == 0'" "tmp2 == 0" { target *-*-* } cond_2 } */
-  /* { dg-warning "pattern match on '<unknown> == 0'" "<unknown> == 0" { target *-*-* } cond_2 } */
-  /* { dg-warning "pattern match on '<unknown> != 0'" "<unknown> != 0" { target *-*-* } cond_2 } */
   /* { dg-warning "pattern match on 'p != 0'" "p != 0" { target *-*-* } cond_2 } */
+  /* { dg-warning "pattern match on 'tmp1 | tmp2 != 0'" "tmp1 | tmp2 != 0" { target *-*-* } cond_2 } */
   /* { dg-warning "pattern match on 'q != 0'" "q != 0" { target *-*-* } cond_2 } */
 }
 
@@ -44,10 +41,7 @@  void test_3 (void *p, void *q)
     return;
   foo(p);
 
-  /* { dg-warning "pattern match on 'tmp1 != 0'" "tmp1 != 0" { target *-*-* } cond_3 } */
-  /* { dg-warning "pattern match on 'tmp2 != 0'" "tmp2 != 0" { target *-*-* } cond_3 } */
-  /* { dg-warning "pattern match on '<unknown> == 0'" "<unknown> == 0" { target *-*-* } cond_3 } */
-  /* { dg-warning "pattern match on '<unknown> != 0'" "<unknown> != 0" { target *-*-* } cond_3 } */
   /* { dg-warning "pattern match on 'p == 0'" "p == 0" { target *-*-* } cond_3 } */
+  /* { dg-warning "pattern match on 'tmp1 & tmp2 == 0'" "tmp1 & tmp2 == 0" { target *-*-* } cond_3 } */
   /* { dg-warning "pattern match on 'q == 0'" "q == 0" { target *-*-* } cond_3 } */
 }
diff --git a/gcc/testsuite/gcc.dg/plugin/analyzer_gil_plugin.c b/gcc/testsuite/gcc.dg/plugin/analyzer_gil_plugin.c
index 05133d5250e..61dd490436a 100644
--- a/gcc/testsuite/gcc.dg/plugin/analyzer_gil_plugin.c
+++ b/gcc/testsuite/gcc.dg/plugin/analyzer_gil_plugin.c
@@ -53,13 +53,6 @@  public:
 		const supernode *node,
 		const gimple *stmt) 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;
-
   bool can_purge_p (state_t s) const FINAL OVERRIDE;
 
   void check_for_pyobject_usage_without_gil (sm_context *sm_ctxt,
@@ -365,20 +358,6 @@  gil_state_machine::on_stmt (sm_context *sm_ctxt,
   return false;
 }
 
-/* Implementation of state_machine::on_condition vfunc for
-   gil_state_machine.  */
-
-void
-gil_state_machine::on_condition (sm_context *sm_ctxt ATTRIBUTE_UNUSED,
-				 const supernode *node ATTRIBUTE_UNUSED,
-				 const gimple *stmt ATTRIBUTE_UNUSED,
-				 tree lhs ATTRIBUTE_UNUSED,
-				 enum tree_code op ATTRIBUTE_UNUSED,
-				 tree rhs ATTRIBUTE_UNUSED) const
-{
-  // Empty
-}
-
 bool
 gil_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const
 {