PR analyzer/98447 covers a false positive from -fanalyzer.
The root cause is that the analyzer's constraint-handling code
doesn't "know" that if X := Y / 64, then X < 64.

This patch adds some minimal usage of value_range and range_op to
-fanalyzer.  It adds a new svalue::maybe_get_value_range vfunc, which
attempts to get a value_range for an svalue (but bails out for awkward
cases), and if value_ranges are available, uses them in eval_condition
to determine if the result is known based on range_op.

Doing so fixes the above false positive, improves a couple of other
tests in the DejaGnu testsuite, and eliminates 31 false +ves in the
analyzer integration testsuite (out of ~2200), along with a slight
speedup to the latter (albeit testing with a debug build of gcc).

A deeper integration with ranger could probably be made, but that is
clearly stage 1 material.

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
Successful run of analyzer integration tests on x86_64-pc-linux-gnu.

Andrew: is this OK for trunk? (from a value_range/range_op perspective)
Jakub/Richi: is it OK to push this in stage 4, or do you want me to
wait to stage 1?

Thanks
Dave

gcc/analyzer/ChangeLog:
        PR analyzer/98447
        * common.h: Include "value-range.h".
        * region-model.cc: Include "value-relation.h" and "range-op.h".
        (region_model::eval_condition): Try using range_op to see if we
        can get a known boolean from the value_ranges of the operands.
        * svalue.cc: Include "value-relation.h" and "range-op.h".
        (type_can_have_value_range_p): New.
        (svalue::maybe_get_value_range): New.
        (constant_svalue::maybe_get_value_range): New.
        (unknown_svalue::maybe_get_value_range): New.
        (unaryop_svalue::maybe_get_value_range): New.
        (binop_svalue::maybe_get_value_range): New.
        * svalue.h (svalue::maybe_get_value_range): New vfunc decl.
        (constant_svalue::maybe_get_value_range): New decl.
        (unknown_svalue::maybe_get_value_range): New decl.
        (unaryop_svalue::maybe_get_value_range): New decl.
        (binop_svalue::maybe_get_value_range): New decl.

gcc/testsuite/ChangeLog:
        PR analyzer/98447
        * c-c++-common/analyzer/conditionals-pr98447-1.c: New test.
        * c-c++-common/analyzer/conditionals-pr98447-2.c: New test.
        * c-c++-common/analyzer/null-deref-pr108400-SoftEtherVPN-WebUi.c:
        Updated for false positive being fixed for C.
        * gcc.dg/analyzer/data-model-1.c: Update expected output to
        reflect improved output.

Signed-off-by: David Malcolm <[email protected]>
---
 gcc/analyzer/common.h                         |   1 +
 gcc/analyzer/region-model.cc                  |  26 ++++
 gcc/analyzer/svalue.cc                        | 113 ++++++++++++++++++
 gcc/analyzer/svalue.h                         |  12 ++
 .../analyzer/conditionals-pr98447-1.c         |  28 +++++
 .../analyzer/conditionals-pr98447-2.c         |  40 +++++++
 .../null-deref-pr108400-SoftEtherVPN-WebUi.c  |   4 +-
 gcc/testsuite/gcc.dg/analyzer/data-model-1.c  |   2 +-
 8 files changed, 223 insertions(+), 3 deletions(-)
 create mode 100644 gcc/testsuite/c-c++-common/analyzer/conditionals-pr98447-1.c
 create mode 100644 gcc/testsuite/c-c++-common/analyzer/conditionals-pr98447-2.c

diff --git a/gcc/analyzer/common.h b/gcc/analyzer/common.h
index 27ca49ee1c81..cbe36dfb9c99 100644
--- a/gcc/analyzer/common.h
+++ b/gcc/analyzer/common.h
@@ -41,6 +41,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "function.h"
 #include "json.h"
 #include "tristate.h"
+#include "value-range.h"
 
 class graphviz_out;
 
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index 1efb19b07761..1c851130c45c 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -46,6 +46,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "selftest-tree.h"
 #include "context.h"
 #include "channels.h"
+#include "value-relation.h"
+#include "range-op.h"
 
 #include "text-art/tree-widget.h"
 
@@ -5348,6 +5350,30 @@ region_model::eval_condition (const svalue *lhs,
        }
     }
 
+  /* Try range_op, but avoid cases where we have been sloppy about types.  */
+  if (lhs->get_type ()
+      && rhs->get_type ()
+      && range_compatible_p (lhs->get_type (), rhs->get_type ()))
+    {
+      value_range lhs_vr, rhs_vr;
+      if (lhs->maybe_get_value_range (lhs_vr))
+       if (rhs->maybe_get_value_range (rhs_vr))
+         {
+           range_op_handler handler (op);
+           if (handler)
+             {
+               int_range_max out;
+               if (handler.fold_range (out, boolean_type_node, lhs_vr, rhs_vr))
+                 {
+                   if (out.zero_p ())
+                     return tristate::TS_FALSE;
+                   if (out.nonzero_p ())
+                     return tristate::TS_TRUE;
+                 }
+             }
+         }
+    }
+
   /* Attempt to unwrap cast if there is one, and the types match.  */
   tree lhs_type = lhs->get_type ();
   tree rhs_type = rhs->get_type ();
diff --git a/gcc/analyzer/svalue.cc b/gcc/analyzer/svalue.cc
index 0b3ef6e5f050..5cb0fafbf5da 100644
--- a/gcc/analyzer/svalue.cc
+++ b/gcc/analyzer/svalue.cc
@@ -25,6 +25,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "fold-const.h"
 #include "diagnostic.h"
 #include "tree-diagnostic.h"
+#include "value-relation.h"
+#include "range-op.h"
 
 #include "text-art/dump.h"
 
@@ -880,6 +882,34 @@ svalue::maybe_get_type_from_typeinfo () const
   return NULL_TREE;
 }
 
+/* Return true if we can get a value_range for TYPE (which could be
+   NULL_TREE); false otherwise.  */
+
+static bool
+type_can_have_value_range_p (tree type)
+{
+  if (!type)
+    return false;
+  if (!irange::supports_p (type))
+    return false;
+  return true;
+}
+
+/* Base implementation of svalue::maybe_get_value_range vfunc.
+   If there is a suitable underlying type, write a "varying" for it to OUT
+   (for "any value of that type") and return true; otherwise return false.  */
+
+bool
+svalue::maybe_get_value_range (value_range &out) const
+{
+  tree type = get_type ();
+  if (!type_can_have_value_range_p (type))
+    return false;
+
+  out.set_varying (type);
+  return true;
+}
+
 /* class region_svalue : public svalue.  */
 
 /* Implementation of svalue::dump_to_pp vfunc for region_svalue.  */
@@ -1165,6 +1195,22 @@ constant_svalue::all_zeroes_p () const
   return zerop (m_cst_expr);
 }
 
+
+/* Implementation of svalue::maybe_get_value_range for constant_svalue.
+   If there is a suitable underlying type, write the value_range for the
+   single value of m_cst_expr to OUT and return true; otherwise return
+   false.  */
+
+bool
+constant_svalue::maybe_get_value_range (value_range &out) const
+{
+  if (!type_can_have_value_range_p (get_type ()))
+    return false;
+
+  out = value_range (m_cst_expr, m_cst_expr);
+  return true;
+}
+
 /* class unknown_svalue : public svalue.  */
 
 /* Implementation of svalue::dump_to_pp vfunc for unknown_svalue.  */
@@ -1228,6 +1274,14 @@ unknown_svalue::maybe_fold_bits_within (tree type,
   return mgr->get_or_create_unknown_svalue (type);
 }
 
+bool
+unknown_svalue::maybe_get_value_range (value_range &) const
+{
+  /* Don't attempt to participate in range ops.  */
+  return false;
+}
+
+
 /* Get a string for KIND for use in debug dumps.  */
 
 const char *
@@ -1518,6 +1572,33 @@ unaryop_svalue::maybe_fold_bits_within (tree type,
   return nullptr;
 }
 
+/* Implementation of svalue::maybe_get_value_range for unaryop_svalue.  */
+
+bool
+unaryop_svalue::maybe_get_value_range (value_range &out) const
+{
+  tree type = get_type ();
+  if (!type_can_have_value_range_p (type))
+    return false;
+
+  value_range arg_vr;
+  if (m_arg->maybe_get_value_range (arg_vr))
+    {
+      range_op_handler handler (m_op);
+      if (handler)
+       {
+         /* For unary ops, range_op_hander::fold_range expects
+            a VARYING of the unknown value as the 2nd operand.  */
+         value_range varying (type);
+         varying.set_varying (type);
+         out.set_type (type);
+         if (handler.fold_range (out, type, arg_vr, varying))
+           return true;
+       }
+    }
+  return false;
+}
+
 /* class binop_svalue : public svalue.  */
 
 /* Return whether OP be printed as an infix operator.  */
@@ -1634,6 +1715,38 @@ sub_svalue::sub_svalue (symbol::id_t id,
   gcc_assert (parent_svalue->can_have_associated_state_p ());
 }
 
+/* Implementation of svalue::maybe_get_value_range for binop_svalue.  */
+
+bool
+binop_svalue::maybe_get_value_range (value_range &out) const
+{
+  tree type = get_type ();
+  if (!type_can_have_value_range_p (type))
+    return false;
+
+  /* Avoid cases where we have been sloppy about types.  */
+  if (!m_arg0->get_type ())
+    return false;
+  if (!m_arg1->get_type ())
+    return false;
+  if (!range_compatible_p (m_arg0->get_type (), m_arg1->get_type ()))
+    return false;
+
+  value_range lhs, rhs;
+  if (m_arg0->maybe_get_value_range (lhs))
+    if (m_arg1->maybe_get_value_range (rhs))
+      {
+       range_op_handler handler (m_op);
+       if (handler)
+         {
+           out.set_type (type);
+           if (handler.fold_range (out, get_type (), lhs, rhs))
+             return true;
+         }
+      }
+  return false;
+}
+
 /* Implementation of svalue::dump_to_pp vfunc for sub_svalue.  */
 
 void
diff --git a/gcc/analyzer/svalue.h b/gcc/analyzer/svalue.h
index 5025d966f8f0..bd3ffdd52d8f 100644
--- a/gcc/analyzer/svalue.h
+++ b/gcc/analyzer/svalue.h
@@ -190,6 +190,10 @@ public:
 
   tree maybe_get_type_from_typeinfo () const;
 
+  /* If we can get a value_range for this svalue, write it to OUT
+     and return true.  Otherwise return false.  */
+  virtual bool maybe_get_value_range (value_range &out) const;
+
  protected:
   svalue (complexity c, symbol::id_t id, tree type)
   : symbol (c, id), m_type (type)
@@ -366,6 +370,8 @@ public:
 
   bool all_zeroes_p () const final override;
 
+  bool maybe_get_value_range (value_range &out) const final override;
+
  private:
   tree m_cst_expr;
 };
@@ -417,6 +423,8 @@ public:
                          const bit_range &subrange,
                          region_model_manager *mgr) const final override;
 
+  bool maybe_get_value_range (value_range &out) const final override;
+
   /* Unknown values are singletons per-type, so can't have state.  */
   bool can_have_associated_state_p () const final override { return false; }
 };
@@ -763,6 +771,8 @@ public:
                          const bit_range &subrange,
                          region_model_manager *mgr) const final override;
 
+  bool maybe_get_value_range (value_range &out) const final override;
+
  private:
   enum tree_code m_op;
   const svalue *m_arg;
@@ -859,6 +869,8 @@ public:
   bool implicitly_live_p (const svalue_set *,
                          const region_model *) const final override;
 
+  bool maybe_get_value_range (value_range &out) const final override;
+
   enum tree_code get_op () const { return m_op; }
   const svalue *get_arg0 () const { return m_arg0; }
   const svalue *get_arg1 () const { return m_arg1; }
diff --git a/gcc/testsuite/c-c++-common/analyzer/conditionals-pr98447-1.c 
b/gcc/testsuite/c-c++-common/analyzer/conditionals-pr98447-1.c
new file mode 100644
index 000000000000..b94d1ae9d67d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/analyzer/conditionals-pr98447-1.c
@@ -0,0 +1,28 @@
+void
+pr98447_original (unsigned long *p, int r, int i)
+{
+  int b = 63, n = r % 16;
+
+  while (i >= 0)
+    {
+      if (n > b)
+        {
+          p[i--] = b + 1 >= 64 ? 0UL : 1UL << (b + 1); /* { dg-bogus 
"-Wanalyzer-shift-count-overflow" } */
+          b += 64;
+        }
+      b -= n;
+    }
+}
+
+void
+pr98477_comment_4 (unsigned long *p, int r, int i)
+{
+  int b = 64, n = r % 64;
+
+  while (i >= 0 && b >= 0)
+    {
+      if (b <= n)
+        p[i--] = 1UL << b; /* { dg-bogus "-Wanalyzer-shift-count-overflow" } */
+      b -= n;
+    }
+}
diff --git a/gcc/testsuite/c-c++-common/analyzer/conditionals-pr98447-2.c 
b/gcc/testsuite/c-c++-common/analyzer/conditionals-pr98447-2.c
new file mode 100644
index 000000000000..73f87af19e7f
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/analyzer/conditionals-pr98447-2.c
@@ -0,0 +1,40 @@
+#include "analyzer-decls.h"
+
+void test_1 (int i)
+{
+  __analyzer_eval (64 <= (i % 64)); /* { dg-warning "FALSE" } */
+  __analyzer_eval (64 < (i % 64)); /* { dg-warning "FALSE" } */
+  __analyzer_eval (63 <= (i % 64)); /* { dg-warning "UNKNOWN" } */
+
+  /* (i % 64) > X for various X.  */
+  __analyzer_eval ((i % 64) > -65); /* { dg-warning "TRUE" } */
+  __analyzer_eval ((i % 64) > -64); /* { dg-warning "TRUE" } */
+  __analyzer_eval ((i % 64) > -63); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval ((i % 64) > 62); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval ((i % 64) > 63); /* { dg-warning "FALSE" } */
+  __analyzer_eval ((i % 64) > 64); /* { dg-warning "FALSE" } */
+
+  /* (i % 64) >= X for various X.  */
+  __analyzer_eval ((i % 64) >= -64); /* { dg-warning "TRUE" } */
+  __analyzer_eval ((i % 64) >= -63); /* { dg-warning "TRUE" } */
+  __analyzer_eval ((i % 64) >= -62); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval ((i % 64) >= 62); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval ((i % 64) >= 63); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval ((i % 64) >= 64); /* { dg-warning "FALSE" } */
+
+  /* (i % 64) < X for various X.  */
+  __analyzer_eval ((i % 64) < -64); /* { dg-warning "FALSE" } */
+  __analyzer_eval ((i % 64) < -63); /* { dg-warning "FALSE" } */
+  __analyzer_eval ((i % 64) < -62); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval ((i % 64) < 63); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval ((i % 64) < 64); /* { dg-warning "TRUE" } */
+  __analyzer_eval ((i % 64) < 65); /* { dg-warning "TRUE" } */
+
+  /* (i % 64) <= X for various X.  */
+  __analyzer_eval ((i % 64) <= -64); /* { dg-warning "FALSE" } */
+  __analyzer_eval ((i % 64) <= -63); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval ((i % 64) <= -62); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval ((i % 64) <= 62); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval ((i % 64) <= 63); /* { dg-warning "TRUE" } */
+  __analyzer_eval ((i % 64) <= 64); /* { dg-warning "TRUE" } */
+}
diff --git 
a/gcc/testsuite/c-c++-common/analyzer/null-deref-pr108400-SoftEtherVPN-WebUi.c 
b/gcc/testsuite/c-c++-common/analyzer/null-deref-pr108400-SoftEtherVPN-WebUi.c
index 0ebeeff8348c..1ae93b1fa212 100644
--- 
a/gcc/testsuite/c-c++-common/analyzer/null-deref-pr108400-SoftEtherVPN-WebUi.c
+++ 
b/gcc/testsuite/c-c++-common/analyzer/null-deref-pr108400-SoftEtherVPN-WebUi.c
@@ -60,8 +60,8 @@ void WuExpireSessionKey(WEBUI *wu)
 
        for(i=0; i<LIST_NUM(wu->Contexts); i++)
        {
-               STRMAP_ENTRY *entry = (STRMAP_ENTRY*)LIST_DATA(wu->Contexts, 
i); /* { dg-message "'entry' is NULL" } */
-               WU_CONTEXT *context = (WU_CONTEXT*)entry->Value; /* { dg-bogus 
"dereference of NULL 'entry'" "PR analyzer/108400" { xfail *-*-* } } */
+               STRMAP_ENTRY *entry = (STRMAP_ENTRY*)LIST_DATA(wu->Contexts, 
i); /* { dg-bogus "'entry' is NULL" "" { xfail c++ } } */
+               WU_CONTEXT *context = (WU_CONTEXT*)entry->Value; /* { dg-bogus 
"dereference of NULL 'entry'" "PR analyzer/108400" { xfail c++ } } */
                if(context->ExpireDate < Tick64())
                {
                        Add(Expired, entry);
diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-1.c 
b/gcc/testsuite/gcc.dg/analyzer/data-model-1.c
index b7301616edd1..ce77d1254772 100644
--- a/gcc/testsuite/gcc.dg/analyzer/data-model-1.c
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-1.c
@@ -374,7 +374,7 @@ void test_20 (int i, int j)
   __analyzer_eval (i & 1); /* { dg-warning "UNKNOWN" } */
   __analyzer_eval (i & j); /* { dg-warning "UNKNOWN" } */
 
-  __analyzer_eval (i | 1); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (i | 1); /* { dg-warning "TRUE" } */
   __analyzer_eval (i | j); /* { dg-warning "UNKNOWN" } */
 
   __analyzer_eval (i ^ 1); /* { dg-warning "UNKNOWN" } */
-- 
2.26.3

Reply via email to