forked from OSchip/llvm-project
[analyzer] Support inlining lambda-converted blocks.
clang converts C++ lambdas to blocks with an implicit user-defined conversion operator method on the lambda record. This method returns a block that captures a copy of the lambda. To inline a lambda-converted block, the analyzer now calls the lambda records's call operator method on the lambda captured by the block. llvm-svn: 254702
This commit is contained in:
parent
d31c1ba19a
commit
ebeed88078
|
@ -506,8 +506,57 @@ public:
|
|||
return BR->getDecl();
|
||||
}
|
||||
|
||||
bool isConversionFromLambda() const {
|
||||
const BlockDecl *BD = getDecl();
|
||||
if (!BD)
|
||||
return false;
|
||||
|
||||
return BD->isConversionFromLambda();
|
||||
}
|
||||
|
||||
/// \brief For a block converted from a C++ lambda, returns the block
|
||||
/// VarRegion for the variable holding the captured C++ lambda record.
|
||||
const VarRegion *getRegionStoringCapturedLambda() const {
|
||||
assert(isConversionFromLambda());
|
||||
const BlockDataRegion *BR = getBlockRegion();
|
||||
assert(BR && "Block converted from lambda must have a block region");
|
||||
|
||||
BlockDataRegion::referenced_vars_iterator I = BR->referenced_vars_begin(),
|
||||
E = BR->referenced_vars_end();
|
||||
assert(I != E);
|
||||
|
||||
return I.getCapturedRegion();
|
||||
}
|
||||
|
||||
RuntimeDefinition getRuntimeDefinition() const override {
|
||||
return RuntimeDefinition(getDecl());
|
||||
if (!isConversionFromLambda())
|
||||
return RuntimeDefinition(getDecl());
|
||||
|
||||
// Clang converts lambdas to blocks with an implicit user-defined
|
||||
// conversion operator method on the lambda record that looks (roughly)
|
||||
// like:
|
||||
//
|
||||
// typedef R(^block_type)(P1, P2, ...);
|
||||
// operator block_type() const {
|
||||
// auto Lambda = *this;
|
||||
// return ^(P1 p1, P2 p2, ...){
|
||||
// /* return Lambda(p1, p2, ...); */
|
||||
// };
|
||||
// }
|
||||
//
|
||||
// Here R is the return type of the lambda and P1, P2, ... are
|
||||
// its parameter types. 'Lambda' is a fake VarDecl captured by the block
|
||||
// that is initialized to a copy of the the lambda.
|
||||
//
|
||||
// Sema leaves the body of a lambda-converted block empty (it is
|
||||
// produced by CodeGen), so we can't analyze it directly. Instead, we skip
|
||||
// the block body and analyze the operator() method on the the captured
|
||||
// lambda.
|
||||
const VarDecl *LambdaVD = getRegionStoringCapturedLambda()->getDecl();
|
||||
const CXXRecordDecl *LambdaDecl = LambdaVD->getType()->getAsCXXRecordDecl();
|
||||
CXXMethodDecl* LambdaCallOperator = LambdaDecl->getLambdaCallOperator();
|
||||
|
||||
return RuntimeDefinition(LambdaCallOperator);
|
||||
}
|
||||
|
||||
bool argumentsMayEscape() const override {
|
||||
|
|
|
@ -598,10 +598,25 @@ void BlockCall::getExtraInvalidatedValues(ValueList &Values,
|
|||
|
||||
void BlockCall::getInitialStackFrameContents(const StackFrameContext *CalleeCtx,
|
||||
BindingsTy &Bindings) const {
|
||||
const BlockDecl *D = cast<BlockDecl>(CalleeCtx->getDecl());
|
||||
SValBuilder &SVB = getState()->getStateManager().getSValBuilder();
|
||||
ArrayRef<ParmVarDecl*> Params;
|
||||
if (isConversionFromLambda()) {
|
||||
auto *LambdaOperatorDecl = cast<CXXMethodDecl>(CalleeCtx->getDecl());
|
||||
Params = LambdaOperatorDecl->parameters();
|
||||
|
||||
// For blocks converted from a C++ lambda, the callee declaration is the
|
||||
// operator() method on the the lambda so we bind "this" to
|
||||
// the lambda captured by the block.
|
||||
const VarRegion *CapturedLambdaRegion = getRegionStoringCapturedLambda();
|
||||
SVal ThisVal = loc::MemRegionVal(CapturedLambdaRegion);
|
||||
Loc ThisLoc = SVB.getCXXThis(LambdaOperatorDecl, CalleeCtx);
|
||||
Bindings.push_back(std::make_pair(ThisLoc, ThisVal));
|
||||
} else {
|
||||
Params = cast<BlockDecl>(CalleeCtx->getDecl())->parameters();
|
||||
}
|
||||
|
||||
addParameterValuesToBindings(CalleeCtx, Bindings, SVB, *this,
|
||||
D->parameters());
|
||||
Params);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -189,8 +189,9 @@ void ExprEngine::VisitBlockExpr(const BlockExpr *BE, ExplodedNode *Pred,
|
|||
|
||||
CanQualType T = getContext().getCanonicalType(BE->getType());
|
||||
|
||||
const BlockDecl *BD = BE->getBlockDecl();
|
||||
// Get the value of the block itself.
|
||||
SVal V = svalBuilder.getBlockPointer(BE->getBlockDecl(), T,
|
||||
SVal V = svalBuilder.getBlockPointer(BD, T,
|
||||
Pred->getLocationContext(),
|
||||
currBldrCtx->blockCount());
|
||||
|
||||
|
@ -204,11 +205,32 @@ void ExprEngine::VisitBlockExpr(const BlockExpr *BE, ExplodedNode *Pred,
|
|||
BlockDataRegion::referenced_vars_iterator I = BDR->referenced_vars_begin(),
|
||||
E = BDR->referenced_vars_end();
|
||||
|
||||
auto CI = BD->capture_begin();
|
||||
auto CE = BD->capture_end();
|
||||
for (; I != E; ++I) {
|
||||
const MemRegion *capturedR = I.getCapturedRegion();
|
||||
const MemRegion *originalR = I.getOriginalRegion();
|
||||
const VarRegion *capturedR = I.getCapturedRegion();
|
||||
const VarRegion *originalR = I.getOriginalRegion();
|
||||
|
||||
// If the capture had a copy expression, use the result of evaluating
|
||||
// that expression, otherwise use the original value.
|
||||
// We rely on the invariant that the block declaration's capture variables
|
||||
// are a prefix of the BlockDataRegion's referenced vars (which may include
|
||||
// referenced globals, etc.) to enable fast lookup of the capture for a
|
||||
// given referenced var.
|
||||
const Expr *copyExpr = nullptr;
|
||||
if (CI != CE) {
|
||||
assert(CI->getVariable() == capturedR->getDecl());
|
||||
copyExpr = CI->getCopyExpr();
|
||||
CI++;
|
||||
}
|
||||
|
||||
if (capturedR != originalR) {
|
||||
SVal originalV = State->getSVal(loc::MemRegionVal(originalR));
|
||||
SVal originalV;
|
||||
if (copyExpr) {
|
||||
originalV = State->getSVal(copyExpr, Pred->getLocationContext());
|
||||
} else {
|
||||
originalV = State->getSVal(loc::MemRegionVal(originalR));
|
||||
}
|
||||
State = State->bindLoc(loc::MemRegionVal(capturedR), originalV);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -421,7 +421,8 @@ bool ExprEngine::inlineCall(const CallEvent &Call, const Decl *D,
|
|||
const LocationContext *CurLC = Pred->getLocationContext();
|
||||
const StackFrameContext *CallerSFC = CurLC->getCurrentStackFrame();
|
||||
const LocationContext *ParentOfCallee = CallerSFC;
|
||||
if (Call.getKind() == CE_Block) {
|
||||
if (Call.getKind() == CE_Block &&
|
||||
!cast<BlockCall>(Call).isConversionFromLambda()) {
|
||||
const BlockDataRegion *BR = cast<BlockCall>(Call).getBlockRegion();
|
||||
assert(BR && "If we have the block definition we should have its region");
|
||||
AnalysisDeclContext *BlockCtx = AMgr.getAnalysisDeclContext(D);
|
||||
|
|
|
@ -70,9 +70,53 @@ void castToBlockAndInline() {
|
|||
return p;
|
||||
})(7);
|
||||
|
||||
// FIXME: This should be TRUE. We're not handling lambda to block conversions
|
||||
// properly in ExprEngine::VisitBlockExpr.
|
||||
clang_analyzer_eval(result == 7); // expected-warning{{UNKNOWN}}
|
||||
clang_analyzer_eval(result == 7); // expected-warning{{TRUE}}
|
||||
}
|
||||
|
||||
void castToBlockWithCaptureAndInline() {
|
||||
int y = 7;
|
||||
|
||||
auto lambda = [y]{ return y; };
|
||||
int(^block)() = lambda;
|
||||
|
||||
int result = block();
|
||||
clang_analyzer_eval(result == 7); // expected-warning{{TRUE}}
|
||||
}
|
||||
|
||||
void castMutableLambdaToBlock() {
|
||||
int x = 0;
|
||||
|
||||
auto lambda = [x]() mutable {
|
||||
x = x + 1;
|
||||
return x;
|
||||
};
|
||||
|
||||
// The block should copy the lambda before capturing.
|
||||
int(^block)() = lambda;
|
||||
|
||||
int r1 = block();
|
||||
clang_analyzer_eval(r1 == 1); // expected-warning{{TRUE}}
|
||||
|
||||
int r2 = block();
|
||||
clang_analyzer_eval(r2 == 2); // expected-warning{{TRUE}}
|
||||
|
||||
// Because block copied the lambda, r3 should be 1.
|
||||
int r3 = lambda();
|
||||
clang_analyzer_eval(r3 == 1); // expected-warning{{TRUE}}
|
||||
|
||||
// Aliasing the block shouldn't copy the lambda.
|
||||
int(^blockAlias)() = block;
|
||||
|
||||
int r4 = blockAlias();
|
||||
clang_analyzer_eval(r4 == 3); // expected-warning{{TRUE}}
|
||||
|
||||
int r5 = block();
|
||||
clang_analyzer_eval(r5 == 4); // expected-warning{{TRUE}}
|
||||
|
||||
// Another copy of lambda
|
||||
int(^blockSecondCopy)() = lambda;
|
||||
int r6 = blockSecondCopy();
|
||||
clang_analyzer_eval(r6 == 2); // expected-warning{{TRUE}}
|
||||
}
|
||||
|
||||
void castLambdaInLocalBlock() {
|
||||
|
|
Loading…
Reference in New Issue