forked from OSchip/llvm-project
[analyzer] Lambda capture non-POD type array
This patch introduces a new `ConstructionContext` for lambda capture. This `ConstructionContext` allows the analyzer to construct the captured object directly into it's final region, and makes it possible to capture non-POD arrays. Differential Revision: https://reviews.llvm.org/D129967
This commit is contained in:
parent
ae72cc72d7
commit
996b092c5e
|
@ -202,7 +202,8 @@ public:
|
|||
isa<ReturnedValueConstructionContext>(C) ||
|
||||
isa<VariableConstructionContext>(C) ||
|
||||
isa<ConstructorInitializerConstructionContext>(C) ||
|
||||
isa<ArgumentConstructionContext>(C)));
|
||||
isa<ArgumentConstructionContext>(C) ||
|
||||
isa<LambdaCaptureConstructionContext>(C)));
|
||||
Data2.setPointer(const_cast<ConstructionContext *>(C));
|
||||
}
|
||||
|
||||
|
|
|
@ -36,13 +36,14 @@ public:
|
|||
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 @@ public:
|
|||
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 @@ private:
|
|||
|
||||
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 @@ public:
|
|||
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 @@ public:
|
|||
CXX17ElidedCopyReturnedValueKind,
|
||||
RETURNED_VALUE_BEGIN = SimpleReturnedValueKind,
|
||||
RETURNED_VALUE_END = CXX17ElidedCopyReturnedValueKind,
|
||||
ArgumentKind
|
||||
ArgumentKind,
|
||||
LambdaCaptureKind
|
||||
};
|
||||
|
||||
protected:
|
||||
|
@ -674,6 +681,42 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
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<ArrayInitLoopExpr>(getInitializer());
|
||||
}
|
||||
|
||||
static bool classof(const ConstructionContext *CC) {
|
||||
return CC->getKind() == LambdaCaptureKind;
|
||||
}
|
||||
};
|
||||
|
||||
} // end namespace clang
|
||||
|
||||
#endif // LLVM_CLANG_ANALYSIS_CONSTRUCTIONCONTEXT_H
|
||||
|
|
|
@ -3348,9 +3348,20 @@ CFGBlock *CFGBuilder::VisitBlockExpr(BlockExpr *E, AddStmtChoice asc) {
|
|||
|
||||
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<ArrayInitLoopExpr>(Init);
|
||||
|
||||
findConstructionContexts(ConstructionContextLayer::create(
|
||||
cfg->getBumpVectorContext(), {E, Idx}),
|
||||
AILE ? AILE->getSubExpr() : Init);
|
||||
|
||||
CFGBlock *Tmp = Visit(Init);
|
||||
if (Tmp)
|
||||
LastBlock = Tmp;
|
||||
|
@ -5624,6 +5635,12 @@ static void print_construction_context(raw_ostream &OS,
|
|||
Stmts.push_back(TOCC->getConstructorAfterElision());
|
||||
break;
|
||||
}
|
||||
case ConstructionContext::LambdaCaptureKind: {
|
||||
const auto *LCC = cast<LambdaCaptureConstructionContext>(CC);
|
||||
Helper.handledStmt(const_cast<LambdaExpr *>(LCC->getLambdaExpr()), OS);
|
||||
OS << "+" << LCC->getIndex();
|
||||
return;
|
||||
}
|
||||
case ConstructionContext::ArgumentKind: {
|
||||
const auto *ACC = cast<ArgumentConstructionContext>(CC);
|
||||
if (const Stmt *BTE = ACC->getCXXBindTemporaryExpr()) {
|
||||
|
|
|
@ -156,6 +156,12 @@ const ConstructionContext *ConstructionContext::createBoundTemporaryFromLayers(
|
|||
return create<CXX17ElidedCopyConstructorInitializerConstructionContext>(
|
||||
C, I, BTE);
|
||||
}
|
||||
case ConstructionContextItem::LambdaCaptureKind: {
|
||||
assert(ParentLayer->isLast());
|
||||
const auto *E = cast<LambdaExpr>(ParentItem.getStmt());
|
||||
return create<LambdaCaptureConstructionContext>(C, E,
|
||||
ParentItem.getIndex());
|
||||
}
|
||||
} // switch (ParentItem.getKind())
|
||||
|
||||
llvm_unreachable("Unexpected construction context with destructor!");
|
||||
|
@ -200,6 +206,11 @@ const ConstructionContext *ConstructionContext::createFromLayers(
|
|||
case ConstructionContextItem::ElidableConstructorKind: {
|
||||
llvm_unreachable("The argument needs to be materialized first!");
|
||||
}
|
||||
case ConstructionContextItem::LambdaCaptureKind: {
|
||||
assert(TopLayer->isLast());
|
||||
const auto *E = cast<LambdaExpr>(TopItem.getStmt());
|
||||
return create<LambdaCaptureConstructionContext>(C, E, TopItem.getIndex());
|
||||
}
|
||||
case ConstructionContextItem::InitializerKind: {
|
||||
assert(TopLayer->isLast());
|
||||
const CXXCtorInitializer *I = TopItem.getCXXCtorInitializer();
|
||||
|
|
|
@ -530,6 +530,9 @@ ExprEngine::addObjectUnderConstruction(ProgramStateRef State,
|
|||
Init = VD->getInit();
|
||||
}
|
||||
|
||||
if (auto LE = dyn_cast_or_null<LambdaExpr>(Item.getStmtOrNull()))
|
||||
Init = *(LE->capture_init_begin() + Item.getIndex());
|
||||
|
||||
if (!Init && !Item.getStmtOrNull())
|
||||
Init = Item.getCXXCtorInitializer()->getInit();
|
||||
|
||||
|
|
|
@ -290,6 +290,23 @@ SVal ExprEngine::computeObjectUnderConstruction(
|
|||
|
||||
return loc::MemRegionVal(MRMgr.getCXXTempObjectRegion(E, LCtx));
|
||||
}
|
||||
case ConstructionContext::LambdaCaptureKind: {
|
||||
CallOpts.IsTemporaryCtorOrDtor = true;
|
||||
|
||||
const auto *LCC = cast<LambdaCaptureConstructionContext>(CC);
|
||||
|
||||
SVal Base = loc::MemRegionVal(
|
||||
MRMgr.getCXXTempObjectRegion(LCC->getInitializer(), LCtx));
|
||||
|
||||
const auto *CE = dyn_cast_or_null<CXXConstructExpr>(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 @@ ProgramStateRef ExprEngine::updateObjectsUnderConstruction(
|
|||
|
||||
return State;
|
||||
}
|
||||
case ConstructionContext::LambdaCaptureKind: {
|
||||
const auto *LCC = cast<LambdaCaptureConstructionContext>(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<ElementRegion>(V.getAsRegion()))
|
||||
V = loc::MemRegionVal(EL->getSuperRegion());
|
||||
|
||||
return addObjectUnderConstruction(
|
||||
State, {LCC->getLambdaExpr(), LCC->getIndex()}, LCtx, V);
|
||||
}
|
||||
case ConstructionContext::ArgumentKind: {
|
||||
const auto *ACC = cast<ArgumentConstructionContext>(CC);
|
||||
if (const auto *BTE = ACC->getCXXBindTemporaryExpr())
|
||||
|
@ -1105,19 +1133,40 @@ void ExprEngine::VisitLambdaExpr(const LambdaExpr *LE, ExplodedNode *Pred,
|
|||
|
||||
// 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<ArrayInitLoopExpr>(InitExpr)) {
|
||||
// If the AILE initializes a POD array, we need to keep it as the
|
||||
// InitExpr.
|
||||
if (dyn_cast<CXXConstructExpr>(AILE->getSubExpr()))
|
||||
InitExpr = AILE->getSubExpr();
|
||||
}
|
||||
|
||||
assert(InitExpr && "Capture missing initialization expression");
|
||||
InitVal = State->getSVal(InitExpr, LocCtxt);
|
||||
|
||||
if (dyn_cast<CXXConstructExpr>(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.
|
||||
|
|
|
@ -160,6 +160,11 @@ struct S3 {
|
|||
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 @@ void lambda_init_non_pod() {
|
|||
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
|
||||
|
|
|
@ -400,7 +400,7 @@ int f() {
|
|||
// 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 @@ int f() {
|
|||
// CHECK: Succs (1): B0
|
||||
// CHECK: [B0 (EXIT)]
|
||||
// CHECK: Preds (1): B1
|
||||
|
||||
|
|
Loading…
Reference in New Issue