This revision was automatically updated to reflect the committed changes.
Closed by commit rG8ef628088b54: [analyzer] Structured binding to arrays 
(authored by isuckatcs).
Herald added a project: clang.
Herald added a subscriber: cfe-commits.

Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D126613/new/

https://reviews.llvm.org/D126613

Files:
  clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
  clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
  clang/test/Analysis/array-init-loop.cpp
  clang/test/Analysis/uninit-structured-binding-array.cpp

Index: clang/test/Analysis/uninit-structured-binding-array.cpp
===================================================================
--- /dev/null
+++ 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}}
+}
Index: clang/test/Analysis/array-init-loop.cpp
===================================================================
--- /dev/null
+++ 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}}
+}
Index: clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
===================================================================
--- clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -1363,10 +1363,14 @@
       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 @@
   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 @@
   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,
Index: clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
===================================================================
--- clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
+++ clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
@@ -444,6 +444,10 @@
   ///  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,
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to