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 @@ -438,6 +438,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, Index: clang/lib/StaticAnalyzer/Core/ExprEngine.cpp =================================================================== --- clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -1375,10 +1375,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: @@ -2603,15 +2607,161 @@ // operator&. return; } - if (isa(D)) { - // FIXME: proper support for bound declarations. - // For now, let's just prevent crashing. + if (const auto *BD = dyn_cast(D)) { + const auto *DD = cast(BD->getDecomposedDecl()); + + // Handle binding to arrays + if (const auto *ASE = dyn_cast(BD->getBinding())) { + SVal Base = state->getLValue(DD, LCtx); + SVal Idx = state->getSVal(ASE->getIdx(), LCtx); + + // 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!"); + + if (DD->getType()->isReferenceType()) { + Base = state->getSVal(Base.getAsRegion()); + } + + SVal V = state->getLValue(BD->getType(), Idx, Base); + + Bldr.generateNode(Ex, Pred, state->BindExpr(Ex, LCtx, V), nullptr, + ProgramPoint::PostLValueKind); + return; + } + return; } 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(); + const Expr *Init = Ex->getSubExpr(); + + for (auto *Node : CheckerPreStmt) { + const LocationContext *LCtx = Node->getLocationContext(); + ProgramStateRef state = Node->getState(); + + SVal Base; + + // 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); + + uint64_t ArrSize = Ex->getArraySize().getLimitedValue(); + + // FIXME: We fail to read a value out of a lazyCompoundVal if it is + // Undefined, instead we read Unknown. As a result if the array is + // relatively small, we copy each element, so that we can read each + // element correctly later. + if (ArrSize <= 5) { + llvm::ImmutableList vals = getBasicVals().getEmptySValList(); + + auto Ty = Init->getType(); + + for (uint64_t i = ArrSize; i != 0; --i) { + auto Idx = svalBuilder.makeArrayIndex(i - 1); + + SVal El = state->getLValue(Ty, Idx, Base); + + if (const MemRegion *R = El.getAsRegion()) + El = state->getSVal(R); + else + El = UnknownVal(); + + vals = getBasicVals().prependSVal(El, vals); + } + + Bldr.generateNode( + Ex, Pred, + state->BindExpr(Ex, LCtx, + svalBuilder.makeCompoundVal(Ex->getType(), vals))); + } else { + // Create 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/test/Analysis/uninit-structured-bindings.cpp =================================================================== --- /dev/null +++ clang/test/Analysis/uninit-structured-bindings.cpp @@ -0,0 +1,223 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core -std=c++17 -verify -analyzer-output=text %s + +void array_value_a(void) { + int arr[2]; + auto [a, b] = arr; + arr[0] = 0; + int x = a; + // expected-warning@-1{{Assigned value is garbage or undefined}} + // expected-note@-2{{Assigned value is garbage or undefined}} +} + +void array_value_b(void) { + int arr[] = {1, 2}; + auto [a, b] = arr; + int x = a; // no-warning + (void)x; +} + +void array_value_c(void) { + int arr[3]; + + arr[1] = 1; + + auto [a, b, c] = arr; + int y = b; // no-warning + int x = a; + // expected-warning@-1{{Assigned value is garbage or undefined}} + // expected-note@-2{{Assigned value is garbage or undefined}} +} + +void array_value_d(void) { + int arr[3]; + + arr[1] = 1; + + auto [a, b, c] = arr; + int y = b; // no-warning + int x = c; + // expected-warning@-1{{Assigned value is garbage or undefined}} + // expected-note@-2{{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; + + 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; + + int a = i; // no-warning + int b = j; + // expected-warning@-1{{Assigned value is garbage or undefined}} + // expected-note@-2{{Assigned value is garbage or undefined}} +} + +void array_lref_a(void) { + int arr[2]; + auto &[a, b] = arr; + int x = a; + // expected-warning@-1{{Assigned value is garbage or undefined}} + // expected-note@-2{{Assigned value is garbage or undefined}} +} + +void array_lref_b(void) { + int arr[] = {1, 2}; + auto &[a, b] = arr; + int x = a; // no-warning + (void)x; +} + +void array_lref_c(void) { + int arr[2]; + auto &[a, b] = arr; + + arr[0] = 1; + + int x = a; // no-warning + int y = b; + // expected-warning@-1{{Assigned value is garbage or undefined}} + // expected-note@-2{{Assigned value is garbage or undefined}} + (void)x; +} + +void array_lref_d(void) { + int arr[3]; + + arr[1] = 1; + + auto &[a, b, c] = arr; + int y = b; // no-warning + int x = a; + // expected-warning@-1{{Assigned value is garbage or undefined}} + // expected-note@-2{{Assigned value is garbage or undefined}} +} + +void array_lref_e(void) { + int arr[3]; + + arr[1] = 1; + + auto &[a, b, c] = arr; + int y = b; // no-warning + int x = c; + // expected-warning@-1{{Assigned value is garbage or undefined}} + // expected-note@-2{{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; + + 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; + + int a = i; // no-warning + int b = j; + // expected-warning@-1{{Assigned value is garbage or undefined}} + // expected-note@-2{{Assigned value is garbage or undefined}} +} + +void array_rref_a(void) { + int arr[2]; + auto &&[a, b] = arr; + int x = a; + // expected-warning@-1{{Assigned value is garbage or undefined}} + // expected-note@-2{{Assigned value is garbage or undefined}} +} + +void array_rref_b(void) { + int arr[] = {1, 2}; + auto &&[a, b] = arr; + int x = a; // no-warning + (void)x; +} + +void array_rref_c(void) { + int arr[2]; + auto &&[a, b] = arr; + + arr[0] = 1; + + int x = a; // no-warning + int y = b; + // expected-warning@-1{{Assigned value is garbage or undefined}} + // expected-note@-2{{Assigned value is garbage or undefined}} + (void)x; +} + +void array_rref_d(void) { + int arr[3]; + + arr[1] = 1; + + auto &&[a, b, c] = arr; + int y = b; // no-warning + int x = a; + // expected-warning@-1{{Assigned value is garbage or undefined}} + // expected-note@-2{{Assigned value is garbage or undefined}} +} + +void array_rref_e(void) { + int arr[3]; + + arr[1] = 1; + + auto &&[a, b, c] = arr; + int y = b; // no-warning + int x = c; + // expected-warning@-1{{Assigned value is garbage or undefined}} + // expected-note@-2{{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; + + 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; + + int a = i; // no-warning + int b = j; + // expected-warning@-1{{Assigned value is garbage or undefined}} + // expected-note@-2{{Assigned value is garbage or undefined}} +}