[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:
isuckatcs 2022-07-17 19:04:45 +02:00
parent ae72cc72d7
commit 996b092c5e
8 changed files with 147 additions and 20 deletions

View File

@ -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));
}

View File

@ -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

View File

@ -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()) {

View File

@ -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();

View File

@ -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();

View File

@ -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.

View File

@ -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

View File

@ -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