diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h +++ b/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, diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -1363,10 +1363,14 @@ break; } + case Stmt::ArrayInitLoopExprClass: + Bldr.takeNodes(Pred); + VisitArrayInitLoopExpr(cast(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(D)) { const auto *DD = cast(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(BD->getBinding())) { const auto *Field = cast(ME->getMemberDecl()); + V = state->getLValue(Field, Base); + } + // Handle binding to arrays + else if (const auto *ASE = dyn_cast(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(Arr)) { + Expr *MEBase = ME->getBase(); + + // Move ctor + if (auto CXXSCE = dyn_cast(MEBase)) { + MEBase = CXXSCE->getSubExpr(); + } + + auto ObjDeclExpr = cast(MEBase); + SVal Obj = state->getLValue(cast(ObjDeclExpr->getDecl()), LCtx); + + Base = state->getLValue(cast(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(Arr)) + Base = state->getLValue(cast(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 --- /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 --- /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}} +}