r254702 - [analyzer] Support inlining lambda-converted blocks.

Devin Coughlin via cfe-commits cfe-commits at lists.llvm.org
Thu Dec 3 21:00:37 PST 2015


Author: dcoughlin
Date: Thu Dec  3 23:00:36 2015
New Revision: 254702

URL: http://llvm.org/viewvc/llvm-project?rev=254702&view=rev
Log:
[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.

Modified:
    cfe/trunk/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h
    cfe/trunk/lib/StaticAnalyzer/Core/CallEvent.cpp
    cfe/trunk/lib/StaticAnalyzer/Core/ExprEngineC.cpp
    cfe/trunk/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
    cfe/trunk/test/Analysis/lambdas.mm

Modified: cfe/trunk/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h?rev=254702&r1=254701&r2=254702&view=diff
==============================================================================
--- cfe/trunk/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h (original)
+++ cfe/trunk/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h Thu Dec  3 23:00:36 2015
@@ -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 {

Modified: cfe/trunk/lib/StaticAnalyzer/Core/CallEvent.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/StaticAnalyzer/Core/CallEvent.cpp?rev=254702&r1=254701&r2=254702&view=diff
==============================================================================
--- cfe/trunk/lib/StaticAnalyzer/Core/CallEvent.cpp (original)
+++ cfe/trunk/lib/StaticAnalyzer/Core/CallEvent.cpp Thu Dec  3 23:00:36 2015
@@ -598,10 +598,25 @@ void BlockCall::getExtraInvalidatedValue
 
 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);
 }
 
 

Modified: cfe/trunk/lib/StaticAnalyzer/Core/ExprEngineC.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/StaticAnalyzer/Core/ExprEngineC.cpp?rev=254702&r1=254701&r2=254702&view=diff
==============================================================================
--- cfe/trunk/lib/StaticAnalyzer/Core/ExprEngineC.cpp (original)
+++ cfe/trunk/lib/StaticAnalyzer/Core/ExprEngineC.cpp Thu Dec  3 23:00:36 2015
@@ -189,8 +189,9 @@ void ExprEngine::VisitBlockExpr(const Bl
 
   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 Bl
     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);
       }
     }

Modified: cfe/trunk/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp?rev=254702&r1=254701&r2=254702&view=diff
==============================================================================
--- cfe/trunk/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp (original)
+++ cfe/trunk/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp Thu Dec  3 23:00:36 2015
@@ -421,7 +421,8 @@ bool ExprEngine::inlineCall(const CallEv
   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);

Modified: cfe/trunk/test/Analysis/lambdas.mm
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Analysis/lambdas.mm?rev=254702&r1=254701&r2=254702&view=diff
==============================================================================
--- cfe/trunk/test/Analysis/lambdas.mm (original)
+++ cfe/trunk/test/Analysis/lambdas.mm Thu Dec  3 23:00:36 2015
@@ -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() {




More information about the cfe-commits mailing list