[PATCH] D32291: [analyzer] Implement handling array subscript into null pointer, improve null dereference checks for array subscripts

Artem Dergachev via Phabricator via cfe-commits cfe-commits at lists.llvm.org
Thu Apr 20 06:44:45 PDT 2017


NoQ created this revision.

When encountering an array-to-pointer-decay and the array base is null (or any other concrete pointer value) (eg. it's a member array in a structure, and the structure pointer is null; of course it wouldn't happen to stack-based or global arrays), do not yield UnknownVal; instead, yield that concrete value.

While obvious, this change now triggers false positives because our suppression for inlined defensive checks was not prepared for dealing with array subscripts (the `idcTrackZeroValueThroughUnaryPointerOperatorsWithArrayField` test in `inlining/inline-defensive-checks.cpp` starts failing). So i additionally improve the suppression.

As discussed in https://reviews.llvm.org/D31982, which added the aforementioned test case, `bugreporter::getDerefExpr()` should have been used (we only used to match member expressions earlier, but now that we encountered arrays, we could use all the features it function can offer). Now that the code uses that function, and a few issues within that function were further fixed in order to support the new use case and avoid regressions.


https://reviews.llvm.org/D32291

Files:
  lib/StaticAnalyzer/Core/BugReporterVisitors.cpp
  lib/StaticAnalyzer/Core/RegionStore.cpp
  test/Analysis/null-deref-offsets.c


Index: test/Analysis/null-deref-offsets.c
===================================================================
--- test/Analysis/null-deref-offsets.c
+++ test/Analysis/null-deref-offsets.c
@@ -7,7 +7,7 @@
   int z[2];
 };
 
-void testOffsets(struct S *s) {
+void testOffsets(struct S *s, int coin) {
   if (s != 0)
     return;
 
@@ -21,14 +21,17 @@
 
   // FIXME: These should ideally be true.
   clang_analyzer_eval(&(s->y) == 4); // expected-warning{{FALSE}}
-  clang_analyzer_eval(&(s->z[0]) == 8); // expected-warning{{UNKNOWN}}
-  clang_analyzer_eval(&(s->z[1]) == 12); // expected-warning{{UNKNOWN}}
+  clang_analyzer_eval(&(s->z[0]) == 8); // expected-warning{{FALSE}}
+  clang_analyzer_eval(&(s->z[1]) == 12); // expected-warning{{FALSE}}
 
   // FIXME: These should ideally be false.
   clang_analyzer_eval(&(s->y) == 0); // expected-warning{{TRUE}}
-  clang_analyzer_eval(&(s->z[0]) == 0); // expected-warning{{UNKNOWN}}
-  clang_analyzer_eval(&(s->z[1]) == 0); // expected-warning{{UNKNOWN}}
-
-  // But this should still be a null dereference.
-  s->y = 5; // expected-warning{{Access to field 'y' results in a dereference of a null pointer (loaded from variable 's')}}
+  clang_analyzer_eval(&(s->z[0]) == 0); // expected-warning{{TRUE}}
+  clang_analyzer_eval(&(s->z[1]) == 0); // expected-warning{{TRUE}}
+
+  // But these should still be reported as null dereferences.
+  if (coin)
+    s->y = 5; // expected-warning{{Access to field 'y' results in a dereference of a null pointer (loaded from variable 's')}}
+  else
+    s->z[1] = 6; // expected-warning{{Array access (via field 'z') results in a null pointer dereference}}
 }
Index: lib/StaticAnalyzer/Core/RegionStore.cpp
===================================================================
--- lib/StaticAnalyzer/Core/RegionStore.cpp
+++ lib/StaticAnalyzer/Core/RegionStore.cpp
@@ -1338,6 +1338,9 @@
 ///  the array).  This is called by ExprEngine when evaluating casts
 ///  from arrays to pointers.
 SVal RegionStoreManager::ArrayToPointer(Loc Array, QualType T) {
+  if (Array.getAs<loc::ConcreteInt>())
+    return Array;
+
   if (!Array.getAs<loc::MemRegionVal>())
     return UnknownVal();
 
Index: lib/StaticAnalyzer/Core/BugReporterVisitors.cpp
===================================================================
--- lib/StaticAnalyzer/Core/BugReporterVisitors.cpp
+++ lib/StaticAnalyzer/Core/BugReporterVisitors.cpp
@@ -61,7 +61,9 @@
         return U->getSubExpr()->IgnoreParenCasts();
     }
     else if (const MemberExpr *ME = dyn_cast<MemberExpr>(E)) {
-      if (ME->isArrow() || isDeclRefExprToReference(ME->getBase())) {
+      if (ME->isImplicitAccess()) {
+        return ME;
+      } else if (ME->isArrow() || isDeclRefExprToReference(ME->getBase())) {
         return ME->getBase()->IgnoreParenCasts();
       } else {
         // If we have a member expr with a dot, the base must have been
@@ -73,9 +75,9 @@
       return IvarRef->getBase()->IgnoreParenCasts();
     }
     else if (const ArraySubscriptExpr *AE = dyn_cast<ArraySubscriptExpr>(E)) {
-      return AE->getBase();
+      return getDerefExpr(AE->getBase());
     }
-    else if (isDeclRefExprToReference(E)) {
+    else if (isa<DeclRefExpr>(E)) {
       return E;
     }
     break;
@@ -974,14 +976,11 @@
     // This code interacts heavily with this hack; otherwise the value
     // would not be null at all for most fields, so we'd be unable to track it.
     if (const auto *Op = dyn_cast<UnaryOperator>(Ex))
-      if (Op->getOpcode() == UO_AddrOf && Op->getSubExpr()->isLValue()) {
-        Ex = Op->getSubExpr()->IgnoreParenCasts();
-        while (const auto *ME = dyn_cast<MemberExpr>(Ex)) {
-          Ex = ME->getBase()->IgnoreParenCasts();
-        }
-      }
+      if (Op->getOpcode() == UO_AddrOf && Op->getSubExpr()->isLValue())
+        if (const Expr *DerefEx = getDerefExpr(Op->getSubExpr()))
+          Ex = DerefEx;
 
-    if (ExplodedGraph::isInterestingLValueExpr(Ex) || CallEvent::isCallStmt(Ex))
+    if (Ex && (ExplodedGraph::isInterestingLValueExpr(Ex) || CallEvent::isCallStmt(Ex)))
       Inner = Ex;
   }
 


-------------- next part --------------
A non-text attachment was scrubbed...
Name: D32291.95943.patch
Type: text/x-patch
Size: 4093 bytes
Desc: not available
URL: <http://lists.llvm.org/pipermail/cfe-commits/attachments/20170420/72c120a7/attachment.bin>


More information about the cfe-commits mailing list