[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:
Devin Coughlin 2015-12-04 05:00:36 +00:00
parent d31c1ba19a
commit ebeed88078
5 changed files with 142 additions and 11 deletions

View File

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

View File

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

View File

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

View File

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

View File

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