[clang] 8ef6280 - [analyzer] Structured binding to arrays

via cfe-commits cfe-commits at lists.llvm.org
Thu Jun 23 02:38:28 PDT 2022


Author: isuckatcs
Date: 2022-06-23T11:38:21+02:00
New Revision: 8ef628088b54aebd4a8317ce3a0029e3283b3aa0

URL: https://github.com/llvm/llvm-project/commit/8ef628088b54aebd4a8317ce3a0029e3283b3aa0
DIFF: https://github.com/llvm/llvm-project/commit/8ef628088b54aebd4a8317ce3a0029e3283b3aa0.diff

LOG: [analyzer] Structured binding to arrays

Introducing structured binding to data members and more.
To handle binding to arrays, ArrayInitLoopExpr is also
evaluated, which enables the analyzer to store information
in two more cases. These are:
  - when a lambda-expression captures an array by value
  - in the implicit copy/move constructor for a class
    with an array member

Differential Revision: https://reviews.llvm.org/D126613

Added: 
    clang/test/Analysis/array-init-loop.cpp
    clang/test/Analysis/uninit-structured-binding-array.cpp

Modified: 
    clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
    clang/lib/StaticAnalyzer/Core/ExprEngine.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
index 3787f8f01b34e..415fa05586edf 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
@@ -444,6 +444,10 @@ class ExprEngine {
   ///  other functions that handle specific kinds of statements.
   void Visit(const Stmt *S, ExplodedNode *Pred, ExplodedNodeSet &Dst);
 
+  /// VisitArrayInitLoopExpr - Transfer function for array init loop.
+  void VisitArrayInitLoopExpr(const ArrayInitLoopExpr *Ex, ExplodedNode *Pred,
+                              ExplodedNodeSet &Dst);
+
   /// VisitArraySubscriptExpr - Transfer function for array accesses.
   void VisitArraySubscriptExpr(const ArraySubscriptExpr *Ex,
                                ExplodedNode *Pred,

diff  --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
index 6ba19d52488c7..b4837cb95e183 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -1363,10 +1363,14 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
       break;
     }
 
+    case Stmt::ArrayInitLoopExprClass:
+      Bldr.takeNodes(Pred);
+      VisitArrayInitLoopExpr(cast<ArrayInitLoopExpr>(S), Pred, Dst);
+      Bldr.addNodes(Dst);
+      break;
     // Cases not handled yet; but will handle some day.
     case Stmt::DesignatedInitExprClass:
     case Stmt::DesignatedInitUpdateExprClass:
-    case Stmt::ArrayInitLoopExprClass:
     case Stmt::ArrayInitIndexExprClass:
     case Stmt::ExtVectorElementExprClass:
     case Stmt::ImaginaryLiteralClass:
@@ -2594,18 +2598,38 @@ void ExprEngine::VisitCommonDeclRefExpr(const Expr *Ex, const NamedDecl *D,
   if (const auto *BD = dyn_cast<BindingDecl>(D)) {
     const auto *DD = cast<DecompositionDecl>(BD->getDecomposedDecl());
 
+    SVal Base = state->getLValue(DD, LCtx);
+    if (DD->getType()->isReferenceType()) {
+      Base = state->getSVal(Base.getAsRegion());
+    }
+
+    SVal V = UnknownVal();
+
+    // Handle binding to data members
     if (const auto *ME = dyn_cast<MemberExpr>(BD->getBinding())) {
       const auto *Field = cast<FieldDecl>(ME->getMemberDecl());
+      V = state->getLValue(Field, Base);
+    }
+    // Handle binding to arrays
+    else if (const auto *ASE = dyn_cast<ArraySubscriptExpr>(BD->getBinding())) {
+      SVal Idx = state->getSVal(ASE->getIdx(), LCtx);
 
-      SVal Base = state->getLValue(DD, LCtx);
-      if (DD->getType()->isReferenceType()) {
-        Base = state->getSVal(Base.getAsRegion());
-      }
-
-      SVal V = state->getLValue(Field, Base);
+      // Note: the index of an element in a structured binding is automatically
+      // created and it is a unique identifier of the specific element. Thus it
+      // cannot be a value that varies at runtime.
+      assert(Idx.isConstant() && "BindingDecl array index is not a constant!");
 
-      Bldr.generateNode(Ex, Pred, state->BindExpr(Ex, LCtx, V));
+      V = state->getLValue(BD->getType(), Idx, Base);
     }
+    // Handle binding to tuple-like strcutures
+    else if (BD->getHoldingVar()) {
+      // FIXME: handle tuples
+      return;
+    } else
+      llvm_unreachable("An unknown case of structured binding encountered!");
+
+    Bldr.generateNode(Ex, Pred, state->BindExpr(Ex, LCtx, V), nullptr,
+                      ProgramPoint::PostLValueKind);
 
     return;
   }
@@ -2613,6 +2637,99 @@ void ExprEngine::VisitCommonDeclRefExpr(const Expr *Ex, const NamedDecl *D,
   llvm_unreachable("Support for this Decl not implemented.");
 }
 
+/// VisitArrayInitLoopExpr - Transfer function for array init loop.
+void ExprEngine::VisitArrayInitLoopExpr(const ArrayInitLoopExpr *Ex,
+                                        ExplodedNode *Pred,
+                                        ExplodedNodeSet &Dst) {
+  ExplodedNodeSet CheckerPreStmt;
+  getCheckerManager().runCheckersForPreStmt(CheckerPreStmt, Pred, Ex, *this);
+
+  ExplodedNodeSet EvalSet;
+  StmtNodeBuilder Bldr(CheckerPreStmt, EvalSet, *currBldrCtx);
+
+  const Expr *Arr = Ex->getCommonExpr()->getSourceExpr();
+
+  for (auto *Node : CheckerPreStmt) {
+    const LocationContext *LCtx = Node->getLocationContext();
+    ProgramStateRef state = Node->getState();
+
+    SVal Base = UnknownVal();
+
+    // As in case of this expression the sub-expressions are not visited by any
+    // other transfer functions, they are handled by matching their AST.
+
+    // Case of implicit copy or move ctor of object with array member
+    //
+    // Note: ExprEngine::VisitMemberExpr is not able to bind the array to the
+    // environment.
+    //
+    //    struct S {
+    //      int arr[2];
+    //    };
+    //
+    //
+    //    S a;
+    //    S b = a;
+    //
+    // The AST in case of a *copy constructor* looks like this:
+    //    ArrayInitLoopExpr
+    //    |-OpaqueValueExpr
+    //    | `-MemberExpr              <-- match this
+    //    |   `-DeclRefExpr
+    //    ` ...
+    //
+    //
+    //    S c;
+    //    S d = std::move(d);
+    //
+    // In case of a *move constructor* the resulting AST looks like:
+    //    ArrayInitLoopExpr
+    //    |-OpaqueValueExpr
+    //    | `-MemberExpr              <-- match this first
+    //    |   `-CXXStaticCastExpr     <-- match this after
+    //    |     `-DeclRefExpr
+    //    ` ...
+    if (const auto *ME = dyn_cast<MemberExpr>(Arr)) {
+      Expr *MEBase = ME->getBase();
+
+      // Move ctor
+      if (auto CXXSCE = dyn_cast<CXXStaticCastExpr>(MEBase)) {
+        MEBase = CXXSCE->getSubExpr();
+      }
+
+      auto ObjDeclExpr = cast<DeclRefExpr>(MEBase);
+      SVal Obj = state->getLValue(cast<VarDecl>(ObjDeclExpr->getDecl()), LCtx);
+
+      Base = state->getLValue(cast<FieldDecl>(ME->getMemberDecl()), Obj);
+    }
+
+    // Case of lambda capture and decomposition declaration
+    //
+    //    int arr[2];
+    //
+    //    [arr]{ int a = arr[0]; }();
+    //    auto[a, b] = arr;
+    //
+    // In both of these cases the AST looks like the following:
+    //    ArrayInitLoopExpr
+    //    |-OpaqueValueExpr
+    //    | `-DeclRefExpr             <-- match this
+    //    ` ...
+    if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(Arr))
+      Base = state->getLValue(cast<VarDecl>(DRE->getDecl()), LCtx);
+
+    // Create a lazy compound value to the original array
+    if (const MemRegion *R = Base.getAsRegion())
+      Base = state->getSVal(R);
+    else
+      Base = UnknownVal();
+
+    Bldr.generateNode(Ex, Pred, state->BindExpr(Ex, LCtx, Base));
+  }
+
+  getCheckerManager().runCheckersForPostStmt(Dst, EvalSet, Ex, *this);
+}
+
 /// VisitArraySubscriptExpr - Transfer function for array accesses
 void ExprEngine::VisitArraySubscriptExpr(const ArraySubscriptExpr *A,
                                              ExplodedNode *Pred,

diff  --git a/clang/test/Analysis/array-init-loop.cpp b/clang/test/Analysis/array-init-loop.cpp
new file mode 100644
index 0000000000000..40a60dde582c6
--- /dev/null
+++ b/clang/test/Analysis/array-init-loop.cpp
@@ -0,0 +1,127 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -std=c++17 -verify %s
+
+void clang_analyzer_eval(bool);
+
+void array_init() {
+  int arr[] = {1, 2, 3, 4, 5};
+
+  auto [a, b, c, d, e] = arr;
+
+  clang_analyzer_eval(a == 1); // expected-warning{{TRUE}}
+  clang_analyzer_eval(b == 2); // expected-warning{{TRUE}}
+  clang_analyzer_eval(c == 3); // expected-warning{{TRUE}}
+  clang_analyzer_eval(d == 4); // expected-warning{{TRUE}}
+  clang_analyzer_eval(e == 5); // expected-warning{{TRUE}}
+}
+
+void array_uninit() {
+  int arr[5];
+
+  auto [a, b, c, d, e] = arr;
+
+  int x = e; // expected-warning{{Assigned value is garbage or undefined}}
+}
+
+void lambda_init() {
+  int arr[] = {1, 2, 3, 4, 5};
+
+  auto l = [arr] { return arr[0]; }();
+  clang_analyzer_eval(l == 1); // expected-warning{{TRUE}}
+
+  l = [arr] { return arr[1]; }();
+  clang_analyzer_eval(l == 2); // expected-warning{{TRUE}}
+
+  l = [arr] { return arr[2]; }();
+  clang_analyzer_eval(l == 3); // expected-warning{{TRUE}}
+
+  l = [arr] { return arr[3]; }();
+  clang_analyzer_eval(l == 4); // expected-warning{{TRUE}}
+
+  l = [arr] { return arr[4]; }();
+  clang_analyzer_eval(l == 5); // expected-warning{{TRUE}}
+}
+
+void lambda_uninit() {
+  int arr[5];
+
+  // FIXME: These should be Undefined, but we fail to read Undefined from a lazyCompoundVal
+  int l = [arr] { return arr[0]; }();
+  clang_analyzer_eval(l); // expected-warning{{UNKNOWN}}
+
+  l = [arr] { return arr[1]; }();
+  clang_analyzer_eval(l); // expected-warning{{UNKNOWN}}
+
+  l = [arr] { return arr[2]; }();
+  clang_analyzer_eval(l); // expected-warning{{UNKNOWN}}
+
+  l = [arr] { return arr[3]; }();
+  clang_analyzer_eval(l); // expected-warning{{UNKNOWN}}
+
+  l = [arr] { return arr[4]; }();
+  clang_analyzer_eval(l); // expected-warning{{UNKNOWN}}
+}
+
+struct S {
+  int arr[5];
+};
+
+void copy_ctor_init() {
+  S orig;
+  orig.arr[0] = 1;
+  orig.arr[1] = 2;
+  orig.arr[2] = 3;
+  orig.arr[3] = 4;
+  orig.arr[4] = 5;
+
+  S copy = orig;
+  clang_analyzer_eval(copy.arr[0] == 1); // expected-warning{{TRUE}}
+  clang_analyzer_eval(copy.arr[1] == 2); // expected-warning{{TRUE}}
+  clang_analyzer_eval(copy.arr[2] == 3); // expected-warning{{TRUE}}
+  clang_analyzer_eval(copy.arr[3] == 4); // expected-warning{{TRUE}}
+  clang_analyzer_eval(copy.arr[4] == 5); // expected-warning{{TRUE}}
+}
+
+void copy_ctor_uninit() {
+  S orig;
+
+  S copy = orig;
+
+  // FIXME: These should be Undefined, but we fail to read Undefined from a lazyCompoundVal.
+  // If the struct is not considered a small struct, instead of a copy, we store a lazy compound value.
+  // As the struct has an array data member, it is not considered small.
+  clang_analyzer_eval(copy.arr[0]); // expected-warning{{UNKNOWN}}
+  clang_analyzer_eval(copy.arr[1]); // expected-warning{{UNKNOWN}}
+  clang_analyzer_eval(copy.arr[2]); // expected-warning{{UNKNOWN}}
+  clang_analyzer_eval(copy.arr[3]); // expected-warning{{UNKNOWN}}
+  clang_analyzer_eval(copy.arr[4]); // expected-warning{{UNKNOWN}}
+}
+
+void move_ctor_init() {
+  S orig;
+  orig.arr[0] = 1;
+  orig.arr[1] = 2;
+  orig.arr[2] = 3;
+  orig.arr[3] = 4;
+  orig.arr[4] = 5;
+
+  S moved = (S &&) orig;
+
+  clang_analyzer_eval(moved.arr[0] == 1); // expected-warning{{TRUE}}
+  clang_analyzer_eval(moved.arr[1] == 2); // expected-warning{{TRUE}}
+  clang_analyzer_eval(moved.arr[2] == 3); // expected-warning{{TRUE}}
+  clang_analyzer_eval(moved.arr[3] == 4); // expected-warning{{TRUE}}
+  clang_analyzer_eval(moved.arr[4] == 5); // expected-warning{{TRUE}}
+}
+
+void move_ctor_uninit() {
+  S orig;
+
+  S moved = (S &&) orig;
+
+  // FIXME: These should be Undefined, but we fail to read Undefined from a lazyCompoundVal.
+  clang_analyzer_eval(moved.arr[0]); // expected-warning{{UNKNOWN}}
+  clang_analyzer_eval(moved.arr[1]); // expected-warning{{UNKNOWN}}
+  clang_analyzer_eval(moved.arr[2]); // expected-warning{{UNKNOWN}}
+  clang_analyzer_eval(moved.arr[3]); // expected-warning{{UNKNOWN}}
+  clang_analyzer_eval(moved.arr[4]); // expected-warning{{UNKNOWN}}
+}

diff  --git a/clang/test/Analysis/uninit-structured-binding-array.cpp b/clang/test/Analysis/uninit-structured-binding-array.cpp
new file mode 100644
index 0000000000000..b727cb49daaa4
--- /dev/null
+++ b/clang/test/Analysis/uninit-structured-binding-array.cpp
@@ -0,0 +1,294 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -std=c++17 -verify %s
+
+void clang_analyzer_eval(bool);
+
+void array_value_a(void) {
+  int arr[2];
+  auto [a, b] = arr;
+  arr[0] = 0;
+
+  int x = a; // expected-warning{{Assigned value is garbage or undefined}}
+}
+
+void array_value_b(void) {
+  int arr[] = {1, 2};
+  auto [a, b] = arr;
+
+  clang_analyzer_eval(a == 1); // expected-warning{{TRUE}}
+  clang_analyzer_eval(b == 2); // expected-warning{{TRUE}}
+
+  int x = a; // no-warning
+}
+
+void array_value_c(void) {
+  int arr[3];
+
+  arr[1] = 1;
+
+  auto [a, b, c] = arr;
+
+  clang_analyzer_eval(b == arr[1]); // expected-warning{{TRUE}}
+
+  int y = b; // no-warning
+  int x = a; // expected-warning{{Assigned value is garbage or undefined}}
+}
+
+void array_value_d(void) {
+  int arr[3];
+
+  arr[1] = 1;
+
+  auto [a, b, c] = arr;
+
+  clang_analyzer_eval(b == arr[1]); // expected-warning{{TRUE}}
+
+  int y = b; // no-warning
+  int x = c; // expected-warning{{Assigned value is garbage or undefined}}
+}
+
+void array_value_e(void) {
+  int uninit[2];
+  int init[2] = {0};
+
+  uninit[0] = init[0];
+
+  auto [i, j] = init;
+
+  clang_analyzer_eval(i == 0); // expected-warning{{TRUE}}
+  clang_analyzer_eval(j == 0); // expected-warning{{TRUE}}
+
+  int a = i; // no-warning
+  int b = j; // no-warning
+}
+
+void array_value_f(void) {
+  int uninit[2];
+  int init[2] = {0};
+
+  uninit[0] = init[0];
+
+  auto [i, j] = uninit;
+
+  clang_analyzer_eval(i == 0); // expected-warning{{TRUE}}
+
+  int a = i; // no-warning
+  int b = j; // expected-warning{{Assigned value is garbage or undefined}}
+}
+
+void array_lref_a(void) {
+  int arr[2];
+  auto &[a, b] = arr;
+  int x = a; // expected-warning{{Assigned value is garbage or undefined}}
+}
+
+void array_lref_b(void) {
+  int arr[] = {1, 2};
+  auto &[a, b] = arr;
+
+  clang_analyzer_eval(a == 1); // expected-warning{{TRUE}}
+  clang_analyzer_eval(b == 2); // expected-warning{{TRUE}}
+
+  int x = a; // no-warning
+}
+
+void array_lref_c(void) {
+  int arr[2];
+  auto &[a, b] = arr;
+
+  arr[0] = 1;
+
+  clang_analyzer_eval(a == 1); // expected-warning{{TRUE}}
+
+  int x = a; // no-warning
+  int y = b; // expected-warning{{Assigned value is garbage or undefined}}
+}
+
+void array_lref_d(void) {
+  int arr[3];
+
+  arr[1] = 1;
+
+  auto &[a, b, c] = arr;
+
+  clang_analyzer_eval(b == 1); // expected-warning{{TRUE}}
+
+  int y = b; // no-warning
+  int x = a; // expected-warning{{Assigned value is garbage or undefined}}
+}
+
+void array_lref_e(void) {
+  int arr[3];
+
+  arr[1] = 1;
+
+  auto &[a, b, c] = arr;
+
+  clang_analyzer_eval(b == 1); // expected-warning{{TRUE}}
+
+  int y = b; // no-warning
+  int x = c; // expected-warning{{Assigned value is garbage or undefined}}
+}
+
+void array_lref_f(void) {
+  int uninit[2];
+  int init[2] = {0};
+
+  uninit[0] = init[0];
+
+  auto &[i, j] = init;
+
+  clang_analyzer_eval(i == 0); // expected-warning{{TRUE}}
+  clang_analyzer_eval(j == 0); // expected-warning{{TRUE}}
+
+  int a = i; // no-warning
+  int b = j; // no-warning
+}
+
+void array_lref_g(void) {
+  int uninit[2];
+  int init[2] = {0};
+
+  uninit[0] = init[0];
+
+  auto &[i, j] = uninit;
+
+  clang_analyzer_eval(i == 0); // expected-warning{{TRUE}}
+
+  int a = i; // no-warning
+  int b = j; // expected-warning{{Assigned value is garbage or undefined}}
+}
+
+void array_rref_a(void) {
+  int arr[2];
+  auto &&[a, b] = arr;
+  int x = a; // expected-warning{{Assigned value is garbage or undefined}}
+}
+
+void array_rref_b(void) {
+  int arr[] = {1, 2};
+  auto &&[a, b] = arr;
+
+  clang_analyzer_eval(a == 1); // expected-warning{{TRUE}}
+  clang_analyzer_eval(b == 2); // expected-warning{{TRUE}}
+
+  int x = a; // no-warning
+}
+
+void array_rref_c(void) {
+  int arr[2];
+  auto &&[a, b] = arr;
+
+  arr[0] = 1;
+
+  clang_analyzer_eval(a == 1); // expected-warning{{TRUE}}
+
+  int x = a; // no-warning
+  int y = b; // expected-warning{{Assigned value is garbage or undefined}}
+}
+
+void array_rref_d(void) {
+  int arr[3];
+
+  arr[1] = 1;
+
+  auto &&[a, b, c] = arr;
+
+  clang_analyzer_eval(b == 1); // expected-warning{{TRUE}}
+
+  int y = b; // no-warning
+  int x = a; // expected-warning{{Assigned value is garbage or undefined}}
+}
+
+void array_rref_e(void) {
+  int arr[3];
+
+  arr[1] = 1;
+
+  auto &&[a, b, c] = arr;
+
+  clang_analyzer_eval(b == 1); // expected-warning{{TRUE}}
+
+  int y = b; // no-warning
+  int x = c; // expected-warning{{Assigned value is garbage or undefined}}
+}
+
+void array_rref_f(void) {
+  int uninit[2];
+  int init[2] = {0};
+
+  uninit[0] = init[0];
+
+  auto &&[i, j] = init;
+
+  clang_analyzer_eval(i == 0); // expected-warning{{TRUE}}
+  clang_analyzer_eval(j == 0); // expected-warning{{TRUE}}
+
+  int a = i; // no-warning
+  int b = j; // no-warning
+}
+
+void array_rref_g(void) {
+  int uninit[2];
+  int init[2] = {0};
+
+  uninit[0] = init[0];
+
+  auto &&[i, j] = uninit;
+
+  clang_analyzer_eval(i == 0); // expected-warning{{TRUE}}
+
+  int a = i; // no-warning
+  int b = j; // expected-warning{{Assigned value is garbage or undefined}}
+}
+
+void array_change_a(void) {
+  int arr[] = {1, 2};
+
+  auto [a, b] = arr;
+
+  clang_analyzer_eval(a == 1); // expected-warning{{TRUE}}
+  a = 3;
+  clang_analyzer_eval(a == 3); // expected-warning{{TRUE}}
+
+  clang_analyzer_eval(arr[0] == 1); // expected-warning{{TRUE}}
+  clang_analyzer_eval(arr[1] == 2); // expected-warning{{TRUE}}
+
+  clang_analyzer_eval(b == 2); // expected-warning{{TRUE}}
+}
+
+void array_change_b(void) {
+  int arr[] = {1, 2};
+
+  auto &[a, b] = arr;
+
+  clang_analyzer_eval(a == 1); // expected-warning{{TRUE}}
+  clang_analyzer_eval(b == 2); // expected-warning{{TRUE}}
+
+  a = 3;
+  clang_analyzer_eval(a == 3); // expected-warning{{TRUE}}
+
+  clang_analyzer_eval(arr[0] == 3); // expected-warning{{TRUE}}
+  clang_analyzer_eval(arr[1] == 2); // expected-warning{{TRUE}}
+}
+
+void array_small_a(void) {
+  int arr[5];
+
+  auto [a, b, c, d, e] = arr;
+
+  int x = e; // expected-warning{{Assigned value is garbage or undefined}}
+}
+
+void array_big_a(void) {
+  int arr[6];
+
+  auto [a, b, c, d, e, f] = arr;
+
+  // FIXME: These will be Undefined when we handle reading Undefined values from lazyCompoundVal.
+  clang_analyzer_eval(a == 1); // expected-warning{{UNKNOWN}}
+  clang_analyzer_eval(b == 2); // expected-warning{{UNKNOWN}}
+  clang_analyzer_eval(c == 3); // expected-warning{{UNKNOWN}}
+  clang_analyzer_eval(d == 4); // expected-warning{{UNKNOWN}}
+  clang_analyzer_eval(e == 5); // expected-warning{{UNKNOWN}}
+  clang_analyzer_eval(f == 6); // expected-warning{{UNKNOWN}}
+}


        


More information about the cfe-commits mailing list