From: Devin Coughlin Date: Fri, 4 Dec 2015 05:00:36 +0000 (+0000) Subject: [analyzer] Support inlining lambda-converted blocks. X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=9d545ef730d0252109fd9442f4a47aa1de3d050e;p=clang [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. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@254702 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h b/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h index 77d73cfd1e..2e9add60f3 100644 --- a/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h +++ b/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h @@ -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 { diff --git a/lib/StaticAnalyzer/Core/CallEvent.cpp b/lib/StaticAnalyzer/Core/CallEvent.cpp index 3a55eb1b97..51dd7c8226 100644 --- a/lib/StaticAnalyzer/Core/CallEvent.cpp +++ b/lib/StaticAnalyzer/Core/CallEvent.cpp @@ -598,10 +598,25 @@ void BlockCall::getExtraInvalidatedValues(ValueList &Values, void BlockCall::getInitialStackFrameContents(const StackFrameContext *CalleeCtx, BindingsTy &Bindings) const { - const BlockDecl *D = cast(CalleeCtx->getDecl()); SValBuilder &SVB = getState()->getStateManager().getSValBuilder(); + ArrayRef Params; + if (isConversionFromLambda()) { + auto *LambdaOperatorDecl = cast(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(CalleeCtx->getDecl())->parameters(); + } + addParameterValuesToBindings(CalleeCtx, Bindings, SVB, *this, - D->parameters()); + Params); } diff --git a/lib/StaticAnalyzer/Core/ExprEngineC.cpp b/lib/StaticAnalyzer/Core/ExprEngineC.cpp index a1bca61468..35af305524 100644 --- a/lib/StaticAnalyzer/Core/ExprEngineC.cpp +++ b/lib/StaticAnalyzer/Core/ExprEngineC.cpp @@ -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); } } diff --git a/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp b/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp index 25c0cf29bc..74cc8d2ccb 100644 --- a/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp +++ b/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp @@ -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(Call).isConversionFromLambda()) { const BlockDataRegion *BR = cast(Call).getBlockRegion(); assert(BR && "If we have the block definition we should have its region"); AnalysisDeclContext *BlockCtx = AMgr.getAnalysisDeclContext(D); diff --git a/test/Analysis/lambdas.mm b/test/Analysis/lambdas.mm index 0af916654f..6247f28870 100644 --- a/test/Analysis/lambdas.mm +++ b/test/Analysis/lambdas.mm @@ -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() {