diff --git a/clang/include/clang/Analysis/CFG.h b/clang/include/clang/Analysis/CFG.h --- a/clang/include/clang/Analysis/CFG.h +++ b/clang/include/clang/Analysis/CFG.h @@ -202,7 +202,8 @@ isa(C) || isa(C) || isa(C) || - isa(C))); + isa(C) || + isa(C))); Data2.setPointer(const_cast(C)); } diff --git a/clang/include/clang/Analysis/ConstructionContext.h b/clang/include/clang/Analysis/ConstructionContext.h --- a/clang/include/clang/Analysis/ConstructionContext.h +++ b/clang/include/clang/Analysis/ConstructionContext.h @@ -36,13 +36,14 @@ ElidedDestructorKind, ElidableConstructorKind, ArgumentKind, - STATEMENT_WITH_INDEX_KIND_BEGIN=ArgumentKind, - STATEMENT_WITH_INDEX_KIND_END=ArgumentKind, + LambdaCaptureKind, + STATEMENT_WITH_INDEX_KIND_BEGIN = ArgumentKind, + STATEMENT_WITH_INDEX_KIND_END = LambdaCaptureKind, STATEMENT_KIND_BEGIN = VariableKind, - STATEMENT_KIND_END = ArgumentKind, + STATEMENT_KIND_END = LambdaCaptureKind, InitializerKind, - INITIALIZER_KIND_BEGIN=InitializerKind, - INITIALIZER_KIND_END=InitializerKind + INITIALIZER_KIND_BEGIN = InitializerKind, + INITIALIZER_KIND_END = InitializerKind }; LLVM_DUMP_METHOD static StringRef getKindAsString(ItemKind K) { @@ -55,6 +56,8 @@ case ElidedDestructorKind: return "elide destructor"; case ElidableConstructorKind: return "elide constructor"; case ArgumentKind: return "construct into argument"; + case LambdaCaptureKind: + return "construct into lambda captured variable"; case InitializerKind: return "construct into member variable"; }; llvm_unreachable("Unknown ItemKind"); @@ -72,7 +75,7 @@ bool hasIndex() const { return Kind >= STATEMENT_WITH_INDEX_KIND_BEGIN && - Kind >= STATEMENT_WITH_INDEX_KIND_END; + Kind <= STATEMENT_WITH_INDEX_KIND_END; } bool hasInitializer() const { @@ -127,6 +130,9 @@ ConstructionContextItem(const CXXCtorInitializer *Init) : Data(Init), Kind(InitializerKind), Index(0) {} + ConstructionContextItem(const LambdaExpr *LE, unsigned Index) + : Data(LE), Kind(LambdaCaptureKind), Index(Index) {} + ItemKind getKind() const { return Kind; } LLVM_DUMP_METHOD StringRef getKindAsString() const { @@ -254,7 +260,8 @@ CXX17ElidedCopyReturnedValueKind, RETURNED_VALUE_BEGIN = SimpleReturnedValueKind, RETURNED_VALUE_END = CXX17ElidedCopyReturnedValueKind, - ArgumentKind + ArgumentKind, + LambdaCaptureKind }; protected: @@ -674,6 +681,42 @@ } }; +class LambdaCaptureConstructionContext : public ConstructionContext { + // The lambda of which the initializer we capture. + const LambdaExpr *LE; + + // Index of the captured element in the captured list. + unsigned Index; + + friend class ConstructionContext; // Allows to create<>() itself. + + explicit LambdaCaptureConstructionContext(const LambdaExpr *LE, + unsigned Index) + : ConstructionContext(LambdaCaptureKind), LE(LE), Index(Index) {} + +public: + const LambdaExpr *getLambdaExpr() const { return LE; } + unsigned getIndex() const { return Index; } + + const Expr *getInitializer() const { + return *(LE->capture_init_begin() + Index); + } + + const FieldDecl *getFieldDecl() const { + auto It = LE->getLambdaClass()->field_begin(); + std::advance(It, Index); + return *It; + } + + const ArrayInitLoopExpr *getArrayInitLoop() const override { + return dyn_cast_or_null(getInitializer()); + } + + static bool classof(const ConstructionContext *CC) { + return CC->getKind() == LambdaCaptureKind; + } +}; + } // end namespace clang #endif // LLVM_CLANG_ANALYSIS_CONSTRUCTIONCONTEXT_H diff --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp --- a/clang/lib/Analysis/CFG.cpp +++ b/clang/lib/Analysis/CFG.cpp @@ -3348,9 +3348,20 @@ CFGBlock *CFGBuilder::VisitLambdaExpr(LambdaExpr *E, AddStmtChoice asc) { CFGBlock *LastBlock = VisitNoRecurse(E, asc); + + unsigned Idx = 0; for (LambdaExpr::capture_init_iterator it = E->capture_init_begin(), - et = E->capture_init_end(); it != et; ++it) { + et = E->capture_init_end(); + it != et; ++it, ++Idx) { if (Expr *Init = *it) { + // If the initializer is an ArrayInitLoopExpr, we want to extract the + // initializer, that's used for each element. + const auto *AILE = dyn_cast_or_null(Init); + + findConstructionContexts(ConstructionContextLayer::create( + cfg->getBumpVectorContext(), {E, Idx}), + AILE ? AILE->getSubExpr() : Init); + CFGBlock *Tmp = Visit(Init); if (Tmp) LastBlock = Tmp; @@ -5624,6 +5635,12 @@ Stmts.push_back(TOCC->getConstructorAfterElision()); break; } + case ConstructionContext::LambdaCaptureKind: { + const auto *LCC = cast(CC); + Helper.handledStmt(const_cast(LCC->getLambdaExpr()), OS); + OS << "+" << LCC->getIndex(); + return; + } case ConstructionContext::ArgumentKind: { const auto *ACC = cast(CC); if (const Stmt *BTE = ACC->getCXXBindTemporaryExpr()) { diff --git a/clang/lib/Analysis/ConstructionContext.cpp b/clang/lib/Analysis/ConstructionContext.cpp --- a/clang/lib/Analysis/ConstructionContext.cpp +++ b/clang/lib/Analysis/ConstructionContext.cpp @@ -156,6 +156,12 @@ return create( C, I, BTE); } + case ConstructionContextItem::LambdaCaptureKind: { + assert(ParentLayer->isLast()); + const auto *E = cast(ParentItem.getStmt()); + return create(C, E, + ParentItem.getIndex()); + } } // switch (ParentItem.getKind()) llvm_unreachable("Unexpected construction context with destructor!"); @@ -200,6 +206,11 @@ case ConstructionContextItem::ElidableConstructorKind: { llvm_unreachable("The argument needs to be materialized first!"); } + case ConstructionContextItem::LambdaCaptureKind: { + assert(TopLayer->isLast()); + const auto *E = cast(TopItem.getStmt()); + return create(C, E, TopItem.getIndex()); + } case ConstructionContextItem::InitializerKind: { assert(TopLayer->isLast()); const CXXCtorInitializer *I = TopItem.getCXXCtorInitializer(); 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 @@ -530,6 +530,9 @@ Init = VD->getInit(); } + if (auto LE = dyn_cast_or_null(Item.getStmtOrNull())) + Init = *(LE->capture_init_begin() + Item.getIndex()); + if (!Init && !Item.getStmtOrNull()) Init = Item.getCXXCtorInitializer()->getInit(); diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp --- a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp @@ -290,6 +290,23 @@ return loc::MemRegionVal(MRMgr.getCXXTempObjectRegion(E, LCtx)); } + case ConstructionContext::LambdaCaptureKind: { + CallOpts.IsTemporaryCtorOrDtor = true; + + const auto *LCC = cast(CC); + + SVal Base = loc::MemRegionVal( + MRMgr.getCXXTempObjectRegion(LCC->getInitializer(), LCtx)); + + const auto *CE = dyn_cast_or_null(E); + if (getIndexOfElementToConstruct(State, CE, LCtx)) { + CallOpts.IsArrayCtorOrDtor = true; + Base = State->getLValue(E->getType(), svalBuilder.makeArrayIndex(Idx), + Base); + } + + return Base; + } case ConstructionContext::ArgumentKind: { // Arguments are technically temporaries. CallOpts.IsTemporaryCtorOrDtor = true; @@ -450,6 +467,17 @@ return State; } + case ConstructionContext::LambdaCaptureKind: { + const auto *LCC = cast(CC); + + // If we capture and array, we want to store the super region, not a + // sub-region. + if (const auto *EL = dyn_cast_or_null(V.getAsRegion())) + V = loc::MemRegionVal(EL->getSuperRegion()); + + return addObjectUnderConstruction( + State, {LCC->getLambdaExpr(), LCC->getIndex()}, LCtx, V); + } case ConstructionContext::ArgumentKind: { const auto *ACC = cast(CC); if (const auto *BTE = ACC->getCXXBindTemporaryExpr()) @@ -1105,19 +1133,40 @@ // If we created a new MemRegion for the lambda, we should explicitly bind // the captures. + unsigned Idx = 0; CXXRecordDecl::field_iterator CurField = LE->getLambdaClass()->field_begin(); for (LambdaExpr::const_capture_init_iterator i = LE->capture_init_begin(), e = LE->capture_init_end(); - i != e; ++i, ++CurField) { + i != e; ++i, ++CurField, ++Idx) { FieldDecl *FieldForCapture = *CurField; SVal FieldLoc = State->getLValue(FieldForCapture, V); SVal InitVal; if (!FieldForCapture->hasCapturedVLAType()) { Expr *InitExpr = *i; + + if (const auto AILE = dyn_cast(InitExpr)) { + // If the AILE initializes a POD array, we need to keep it as the + // InitExpr. + if (dyn_cast(AILE->getSubExpr())) + InitExpr = AILE->getSubExpr(); + } + assert(InitExpr && "Capture missing initialization expression"); - InitVal = State->getSVal(InitExpr, LocCtxt); + + if (dyn_cast(InitExpr)) { + InitVal = *getObjectUnderConstruction(State, {LE, Idx}, LocCtxt); + InitVal = State->getSVal(InitVal.getAsRegion()); + + State = finishObjectConstruction(State, {LE, Idx}, LocCtxt); + } else + InitVal = State->getSVal(InitExpr, LocCtxt); + } else { + + assert(!getObjectUnderConstruction(State, {LE, Idx}, LocCtxt) && + "VLA capture by value is a compile time error!"); + // The field stores the length of a captured variable-length array. // These captures don't have initialization expressions; instead we // get the length from the VLAType size expression. diff --git a/clang/test/Analysis/array-init-loop.cpp b/clang/test/Analysis/array-init-loop.cpp --- a/clang/test/Analysis/array-init-loop.cpp +++ b/clang/test/Analysis/array-init-loop.cpp @@ -160,6 +160,11 @@ int i; }; +// The duplicate is required to emit a warning at 2 different places. +struct S3_duplicate { + int i; +}; + void array_uninit_non_pod() { S3 arr[1]; @@ -170,24 +175,23 @@ S2::c = 0; S2 arr[4]; - // FIXME: These should be TRUE, but we fail to capture the array properly. auto l = [arr] { return arr[0].i; }(); - clang_analyzer_eval(l == 2); // expected-warning{{TRUE}} // expected-warning{{FALSE}} + clang_analyzer_eval(l == 2); // expected-warning{{TRUE}} l = [arr] { return arr[1].i; }(); - clang_analyzer_eval(l == 3); // expected-warning{{TRUE}} // expected-warning{{FALSE}} + clang_analyzer_eval(l == 3); // expected-warning{{TRUE}} l = [arr] { return arr[2].i; }(); - clang_analyzer_eval(l == 4); // expected-warning{{TRUE}} // expected-warning{{FALSE}} + clang_analyzer_eval(l == 4); // expected-warning{{TRUE}} l = [arr] { return arr[3].i; }(); - clang_analyzer_eval(l == 5); // expected-warning{{TRUE}} // expected-warning{{FALSE}} + clang_analyzer_eval(l == 5); // expected-warning{{TRUE}} } void lambda_uninit_non_pod() { - S3 arr[4]; + S3_duplicate arr[4]; - int l = [arr] { return arr[3].i; }(); + int l = [arr] { return arr[3].i; }(); // expected-warning@164{{ in implicit constructor is garbage or undefined }} } // If this struct is being copy/move constructed by the implicit ctors, ArrayInitLoopExpr diff --git a/clang/test/Analysis/lambdas.cpp b/clang/test/Analysis/lambdas.cpp --- a/clang/test/Analysis/lambdas.cpp +++ b/clang/test/Analysis/lambdas.cpp @@ -400,7 +400,7 @@ // CHECK: [B1] // CHECK: 1: x // CHECK: 2: [B1.1] (ImplicitCastExpr, NoOp, const struct X) -// CHECK: 3: [B1.2] (CXXConstructExpr, struct X) +// CHECK: 3: [B1.2] (CXXConstructExpr[B1.4]+0, struct X) // CHECK: 4: [x] { // CHECK: } // CHECK: 5: (void)[B1.4] (CStyleCastExpr, ToVoid, void) @@ -408,4 +408,3 @@ // CHECK: Succs (1): B0 // CHECK: [B0 (EXIT)] // CHECK: Preds (1): B1 -