mgehre created this revision.
mgehre added reviewers: krememek, jordan_rose.
mgehre added a subscriber: cfe-commits.

This mimics the implementation for the implicit destructors. The
generation of this scope leaving elements is hidden behind
a flag to the CFGBuilder, thus it should not affect existing code.

Currently, I'm missing a test (it's implicitly tested by the clang-tidy
lifetime checker that I'm proposing).
I though about a test using debug.DumpCFG, but then I would
have to add an option to StaticAnalyzer/Core/AnalyzerOptions
to enable the scope leaving CFGElement,
which would only be useful to that particular test.

Any other ideas how I could make a test for this feature?

http://reviews.llvm.org/D15031

Files:
  include/clang/Analysis/CFG.h
  lib/Analysis/CFG.cpp

Index: lib/Analysis/CFG.cpp
===================================================================
--- lib/Analysis/CFG.cpp
+++ lib/Analysis/CFG.cpp
@@ -578,6 +578,10 @@
   CFGBlock *addInitializer(CXXCtorInitializer *I);
   void addAutomaticObjDtors(LocalScope::const_iterator B,
                             LocalScope::const_iterator E, Stmt *S);
+  void addAutomaticObjLeavesScope(LocalScope::const_iterator B,
+                                  LocalScope::const_iterator E, Stmt *S);
+  void addAutomaticObjHandling(LocalScope::const_iterator B,
+                               LocalScope::const_iterator E, Stmt *S);
   void addImplicitDtorsForDestructor(const CXXDestructorDecl *DD);
 
   // Local scopes creation.
@@ -618,13 +622,20 @@
     B->appendAutomaticObjDtor(VD, S, cfg->getBumpVectorContext());
   }
 
+  void appendAutomaticObjLeavesScope(CFGBlock *B, VarDecl *VD, Stmt *S) {
+    B->appendAutomaticObjLeavesScope(VD, S, cfg->getBumpVectorContext());
+  }
+
   void appendDeleteDtor(CFGBlock *B, CXXRecordDecl *RD, CXXDeleteExpr *DE) {
     B->appendDeleteDtor(RD, DE, cfg->getBumpVectorContext());
   }
 
   void prependAutomaticObjDtorsWithTerminator(CFGBlock *Blk,
       LocalScope::const_iterator B, LocalScope::const_iterator E);
 
+  void prependAutomaticObjLeavesScopeWithTerminator(CFGBlock *Blk,
+      LocalScope::const_iterator B, LocalScope::const_iterator E);
+
   void addSuccessor(CFGBlock *B, CFGBlock *S, bool IsReachable = true) {
     B->addSuccessor(CFGBlock::AdjacentBlock(S, IsReachable),
                     cfg->getBumpVectorContext());
@@ -1033,6 +1044,8 @@
   assert(Succ == &cfg->getExit());
   Block = nullptr;  // the EXIT block is empty.  Create all other blocks lazily.
 
+  assert(!(BuildOpts.AddImplicitDtors && BuildOpts.AddAutomaticObjLeavesScope) && "AddImplicitDtors and AddScopeLeavers cannot be used at the same time");
+
   if (BuildOpts.AddImplicitDtors)
     if (const CXXDestructorDecl *DD = dyn_cast_or_null<CXXDestructorDecl>(D))
       addImplicitDtorsForDestructor(DD);
@@ -1208,7 +1221,41 @@
 
   return Init->getType();
 }
-  
+
+void CFGBuilder::addAutomaticObjHandling(LocalScope::const_iterator B,
+                                         LocalScope::const_iterator E, Stmt *S) {
+  if (BuildOpts.AddImplicitDtors)
+    addAutomaticObjDtors(B, E, S);
+  if (BuildOpts.AddAutomaticObjLeavesScope)
+    addAutomaticObjLeavesScope(B, E, S);
+}
+
+/// addAutomaticObjLeavesScope - Add to current block automatic objects thats leave the scope.
+void CFGBuilder::addAutomaticObjLeavesScope(LocalScope::const_iterator B,
+                                            LocalScope::const_iterator E, Stmt *S) {
+  if (!BuildOpts.AddAutomaticObjLeavesScope)
+    return;
+
+  if (B == E)
+    return;
+
+  int dist = B.distance(E);
+  if (dist <= 0)
+    return;
+
+  // We need to perform the scope leaving in reverse order
+  SmallVector<VarDecl*, 10> Decls;
+  Decls.reserve(dist);
+  for (LocalScope::const_iterator I = B; I != E; ++I)
+    Decls.push_back(*I);
+
+  autoCreateBlock();
+  for (SmallVectorImpl<VarDecl*>::reverse_iterator I = Decls.rbegin(),
+                                                   E = Decls.rend();
+       I != E; ++I)
+    appendAutomaticObjLeavesScope(Block, *I, S);
+}
+
 /// addAutomaticObjDtors - Add to current block automatic objects destructors
 /// for objects in range of local scope positions. Use S as trigger statement
 /// for destructors.
@@ -1308,7 +1355,7 @@
 /// addLocalScopeForStmt - Add LocalScope to local scopes tree for statement
 /// that should create implicit scope (e.g. if/else substatements). 
 void CFGBuilder::addLocalScopeForStmt(Stmt *S) {
-  if (!BuildOpts.AddImplicitDtors)
+  if (!BuildOpts.AddImplicitDtors && !BuildOpts.AddAutomaticObjLeavesScope)
     return;
 
   LocalScope *Scope = nullptr;
@@ -1333,7 +1380,7 @@
 /// reuse Scope if not NULL.
 LocalScope* CFGBuilder::addLocalScopeForDeclStmt(DeclStmt *DS,
                                                  LocalScope* Scope) {
-  if (!BuildOpts.AddImplicitDtors)
+  if (!BuildOpts.AddImplicitDtors && !BuildOpts.AddAutomaticObjLeavesScope)
     return Scope;
 
   for (auto *DI : DS->decls())
@@ -1347,7 +1394,7 @@
 /// const reference. Will reuse Scope if not NULL.
 LocalScope* CFGBuilder::addLocalScopeForVarDecl(VarDecl *VD,
                                                 LocalScope* Scope) {
-  if (!BuildOpts.AddImplicitDtors)
+  if (!BuildOpts.AddImplicitDtors && !BuildOpts.AddAutomaticObjLeavesScope)
     return Scope;
 
   // Check if variable is local.
@@ -1359,55 +1406,61 @@
   default: return Scope;
   }
 
-  // Check for const references bound to temporary. Set type to pointee.
-  QualType QT = VD->getType();
-  if (QT.getTypePtr()->isReferenceType()) {
-    // Attempt to determine whether this declaration lifetime-extends a
-    // temporary.
-    //
-    // FIXME: This is incorrect. Non-reference declarations can lifetime-extend
-    // temporaries, and a single declaration can extend multiple temporaries.
-    // We should look at the storage duration on each nested
-    // MaterializeTemporaryExpr instead.
-    const Expr *Init = VD->getInit();
-    if (!Init)
-      return Scope;
-    if (const ExprWithCleanups *EWC = dyn_cast<ExprWithCleanups>(Init))
-      Init = EWC->getSubExpr();
-    if (!isa<MaterializeTemporaryExpr>(Init))
-      return Scope;
+  if (BuildOpts.AddImplicitDtors) {
+    // Check for const references bound to temporary. Set type to pointee.
+    QualType QT = VD->getType();
+    if (QT.getTypePtr()->isReferenceType()) {
+      // Attempt to determine whether this declaration lifetime-extends a
+      // temporary.
+      //
+      // FIXME: This is incorrect. Non-reference declarations can lifetime-extend
+      // temporaries, and a single declaration can extend multiple temporaries.
+      // We should look at the storage duration on each nested
+      // MaterializeTemporaryExpr instead.
+      const Expr *Init = VD->getInit();
+      if (!Init)
+        return Scope;
+      if (const ExprWithCleanups *EWC = dyn_cast<ExprWithCleanups>(Init))
+        Init = EWC->getSubExpr();
+      if (!isa<MaterializeTemporaryExpr>(Init))
+        return Scope;
+
+      // Lifetime-extending a temporary.
+      QT = getReferenceInitTemporaryType(*Context, Init);
+    }
 
-    // Lifetime-extending a temporary.
-    QT = getReferenceInitTemporaryType(*Context, Init);
-  }
+    // Check for constant size array. Set type to array element type.
+    while (const ConstantArrayType *AT = Context->getAsConstantArrayType(QT)) {
+      if (AT->getSize() == 0)
+        return Scope;
+      QT = AT->getElementType();
+    }
 
-  // Check for constant size array. Set type to array element type.
-  while (const ConstantArrayType *AT = Context->getAsConstantArrayType(QT)) {
-    if (AT->getSize() == 0)
-      return Scope;
-    QT = AT->getElementType();
+    // Check if type is a C++ class with non-trivial destructor.
+    if (const CXXRecordDecl *CD = QT->getAsCXXRecordDecl())
+      if (!CD->hasTrivialDestructor()) {
+        // Add the variable to scope
+        Scope = createOrReuseLocalScope(Scope);
+        Scope->addVar(VD);
+        ScopePos = Scope->begin();
+      }
+    return Scope;
   }
 
-  // Check if type is a C++ class with non-trivial destructor.
-  if (const CXXRecordDecl *CD = QT->getAsCXXRecordDecl())
-    if (!CD->hasTrivialDestructor()) {
-      // Add the variable to scope
-      Scope = createOrReuseLocalScope(Scope);
-      Scope->addVar(VD);
-      ScopePos = Scope->begin();
-    }
+  assert(BuildOpts.AddAutomaticObjLeavesScope);
+  // Add the variable to scope
+  Scope = createOrReuseLocalScope(Scope);
+  Scope->addVar(VD);
+  ScopePos = Scope->begin();
   return Scope;
 }
 
 /// addLocalScopeAndDtors - For given statement add local scope for it and
 /// add destructors that will cleanup the scope. Will reuse Scope if not NULL.
 void CFGBuilder::addLocalScopeAndDtors(Stmt *S) {
-  if (!BuildOpts.AddImplicitDtors)
-    return;
-
   LocalScope::const_iterator scopeBeginPos = ScopePos;
   addLocalScopeForStmt(S);
-  addAutomaticObjDtors(ScopePos, scopeBeginPos, S);
+  addAutomaticObjHandling(ScopePos, scopeBeginPos, S);
 }
 
 /// prependAutomaticObjDtorsWithTerminator - Prepend destructor CFGElements for
@@ -1419,14 +1472,35 @@
 /// no-return destructors properly.
 void CFGBuilder::prependAutomaticObjDtorsWithTerminator(CFGBlock *Blk,
     LocalScope::const_iterator B, LocalScope::const_iterator E) {
+  if (!BuildOpts.AddImplicitDtors)
+    return;
   BumpVectorContext &C = cfg->getBumpVectorContext();
   CFGBlock::iterator InsertPos
     = Blk->beginAutomaticObjDtorsInsert(Blk->end(), B.distance(E), C);
   for (LocalScope::const_iterator I = B; I != E; ++I)
     InsertPos = Blk->insertAutomaticObjDtor(InsertPos, *I,
                                             Blk->getTerminator());
 }
 
+/// prependAutomaticObjLeavesScopeWithTerminator - Prepend destructor CFGElements for
+/// variables with automatic storage duration to CFGBlock's elements vector.
+/// Elements will be prepended to physical beginning of the vector which
+/// happens to be logical end. Use blocks terminator as statement that specifies
+/// destructors call site.
+/// FIXME: This mechanism for adding automatic destructors doesn't handle
+/// no-return destructors properly.
+void CFGBuilder::prependAutomaticObjLeavesScopeWithTerminator(CFGBlock *Blk,
+    LocalScope::const_iterator B, LocalScope::const_iterator E) {
+  if (!BuildOpts.AddAutomaticObjLeavesScope)
+    return;
+  BumpVectorContext &C = cfg->getBumpVectorContext();
+  CFGBlock::iterator InsertPos
+    = Blk->beginAutomaticObjLeavesScopeInsert(Blk->end(), B.distance(E), C);
+  for (LocalScope::const_iterator I = B; I != E; ++I)
+    InsertPos = Blk->insertAutomaticObjLeavesScope(InsertPos, *I,
+                                            Blk->getTerminator());
+}
+
 /// Visit - Walk the subtree of a statement and add extra
 ///   blocks for ternary operators, &&, and ||.  We also process "," and
 ///   DeclStmts (which may contain nested control-flow).
@@ -1811,7 +1885,7 @@
   // If there is no target for the break, then we are looking at an incomplete
   // AST.  This means that the CFG cannot be constructed.
   if (BreakJumpTarget.block) {
-    addAutomaticObjDtors(ScopePos, BreakJumpTarget.scopePosition, B);
+    addAutomaticObjHandling(ScopePos, BreakJumpTarget.scopePosition, B);
     addSuccessor(Block, BreakJumpTarget.block);
   } else
     badCFG = true;
@@ -1943,12 +2017,11 @@
 
 CFGBlock *CFGBuilder::VisitCompoundStmt(CompoundStmt *C) {
   LocalScope::const_iterator scopeBeginPos = ScopePos;
-  if (BuildOpts.AddImplicitDtors) {
-    addLocalScopeForStmt(C);
-  }
+  addLocalScopeForStmt(C);
+
   if (!C->body_empty() && !isa<ReturnStmt>(*C->body_rbegin())) {
     // If the body ends with a ReturnStmt, the dtors will be added in VisitReturnStmt
-    addAutomaticObjDtors(ScopePos, scopeBeginPos, C);
+    addAutomaticObjHandling(ScopePos, scopeBeginPos, C);
   }
 
   CFGBlock *LastBlock = Block;
@@ -2175,7 +2248,7 @@
   if (VarDecl *VD = I->getConditionVariable()) {
     LocalScope::const_iterator BeginScopePos = ScopePos;
     addLocalScopeForVarDecl(VD);
-    addAutomaticObjDtors(ScopePos, BeginScopePos, I);
+    addAutomaticObjHandling(ScopePos, BeginScopePos, I);
   }
 
   // The block we were processing is now finished.  Make it the successor
@@ -2292,7 +2365,7 @@
   // Create the new block.
   Block = createBlock(false);
 
-  addAutomaticObjDtors(ScopePos, LocalScope::const_iterator(), R);
+  addAutomaticObjHandling(ScopePos, LocalScope::const_iterator(), R);
 
   // If the one of the destructors does not return, we already have the Exit
   // block as a successor.
@@ -2361,7 +2434,7 @@
     BackpatchBlocks.push_back(JumpSource(Block, ScopePos));
   else {
     JumpTarget JT = I->second;
-    addAutomaticObjDtors(ScopePos, JT.scopePosition, G);
+    addAutomaticObjHandling(ScopePos, JT.scopePosition, G);
     addSuccessor(Block, JT.block);
   }
 
@@ -2386,7 +2459,7 @@
     addLocalScopeForVarDecl(VD);
   LocalScope::const_iterator ContinueScopePos = ScopePos;
 
-  addAutomaticObjDtors(ScopePos, save_scope_pos.get(), F);
+  addAutomaticObjHandling(ScopePos, save_scope_pos.get(), F);
 
   // "for" is a control-flow statement.  Thus we stop processing the current
   // block.
@@ -2438,7 +2511,7 @@
    ContinueJumpTarget.block->setLoopTarget(F);
 
     // Loop body should end with destructor of Condition variable (if any).
-    addAutomaticObjDtors(ScopePos, LoopBeginScopePos, F);
+    addAutomaticObjHandling(ScopePos, LoopBeginScopePos, F);
 
     // If body is not a compound statement create implicit scope
     // and add destructors.
@@ -2725,7 +2798,7 @@
   LocalScope::const_iterator LoopBeginScopePos = ScopePos;
   if (VarDecl *VD = W->getConditionVariable()) {
     addLocalScopeForVarDecl(VD);
-    addAutomaticObjDtors(ScopePos, LoopBeginScopePos, W);
+    addAutomaticObjHandling(ScopePos, LoopBeginScopePos, W);
   }
 
   // "while" is a control-flow statement.  Thus we stop processing the current
@@ -2760,7 +2833,7 @@
     BreakJumpTarget = JumpTarget(LoopSuccessor, ScopePos);
 
     // Loop body should end with destructor of Condition variable (if any).
-    addAutomaticObjDtors(ScopePos, LoopBeginScopePos, W);
+    addAutomaticObjHandling(ScopePos, LoopBeginScopePos, W);
 
     // If body is not a compound statement create implicit scope
     // and add destructors.
@@ -3003,7 +3076,7 @@
   // If there is no target for the continue, then we are looking at an
   // incomplete AST.  This means the CFG cannot be constructed.
   if (ContinueJumpTarget.block) {
-    addAutomaticObjDtors(ScopePos, ContinueJumpTarget.scopePosition, C);
+    addAutomaticObjHandling(ScopePos, ContinueJumpTarget.scopePosition, C);
     addSuccessor(Block, ContinueJumpTarget.block);
   } else
     badCFG = true;
@@ -3054,7 +3127,7 @@
   if (VarDecl *VD = Terminator->getConditionVariable()) {
     LocalScope::const_iterator SwitchBeginScopePos = ScopePos;
     addLocalScopeForVarDecl(VD);
-    addAutomaticObjDtors(ScopePos, SwitchBeginScopePos, Terminator);
+    addAutomaticObjHandling(ScopePos, SwitchBeginScopePos, Terminator);
   }
 
   if (Block) {
@@ -3337,7 +3410,7 @@
   if (VarDecl *VD = CS->getExceptionDecl()) {
     LocalScope::const_iterator BeginScopePos = ScopePos;
     addLocalScopeForVarDecl(VD);
-    addAutomaticObjDtors(ScopePos, BeginScopePos, CS);
+    addAutomaticObjHandling(ScopePos, BeginScopePos, CS);
   }
 
   if (CS->getHandlerBlock())
@@ -3389,7 +3462,7 @@
     addLocalScopeForStmt(Range);
   if (Stmt *BeginEnd = S->getBeginEndStmt())
     addLocalScopeForStmt(BeginEnd);
-  addAutomaticObjDtors(ScopePos, save_scope_pos.get(), S);
+  addAutomaticObjHandling(ScopePos, save_scope_pos.get(), S);
 
   LocalScope::const_iterator ContinueScopePos = ScopePos;
 
@@ -3855,6 +3928,7 @@
     case CFGElement::Statement:
     case CFGElement::Initializer:
     case CFGElement::NewAllocator:
+    case CFGElement::AutomaticObjLeavesScope:
       llvm_unreachable("getDestructorDecl should only be used with "
                        "ImplicitDtors");
     case CFGElement::AutomaticObjectDtor: {
@@ -4255,6 +4329,13 @@
     OS << ".~" << T->getAsCXXRecordDecl()->getName().str() << "()";
     OS << " (Implicit destructor)\n";
 
+  } else if (Optional<CFGAutomaticObjLeavesScope> DE =
+                       E.getAs<CFGAutomaticObjLeavesScope>()) {
+    const VarDecl *VD = DE->getVarDecl();
+    Helper.handleDecl(VD, OS);
+
+    OS << " (leaves scope)\n";
+
   } else if (Optional<CFGNewAllocator> NE = E.getAs<CFGNewAllocator>()) {
     OS << "CFGNewAllocator(";
     if (const CXXNewExpr *AllocExpr = NE->getAllocatorExpr())
Index: include/clang/Analysis/CFG.h
===================================================================
--- include/clang/Analysis/CFG.h
+++ include/clang/Analysis/CFG.h
@@ -57,6 +57,7 @@
     Statement,
     Initializer,
     NewAllocator,
+    AutomaticObjLeavesScope,
     // dtor kind
     AutomaticObjectDtor,
     DeleteDtor,
@@ -166,6 +167,27 @@
   }
 };
 
+class CFGAutomaticObjLeavesScope : public CFGElement {
+public:
+  explicit CFGAutomaticObjLeavesScope(const VarDecl *var, const Stmt *stmt)
+    : CFGElement(AutomaticObjLeavesScope, var, stmt) {}
+
+  const VarDecl *getVarDecl() const {
+    return static_cast<VarDecl*>(Data1.getPointer());
+  }
+
+  const Stmt *getTriggerStmt() const {
+    return static_cast<Stmt*>(Data2.getPointer());
+  }
+
+private:
+  friend class CFGElement;
+  CFGAutomaticObjLeavesScope() {}
+  static bool isKind(const CFGElement &elem) {
+    return elem.getKind() == AutomaticObjLeavesScope;
+  }
+};
+
 /// CFGImplicitDtor - Represents C++ object destructor implicitly generated
 /// by compiler on various occasions.
 class CFGImplicitDtor : public CFGElement {
@@ -682,6 +704,10 @@
     Elements.push_back(CFGAutomaticObjDtor(VD, S), C);
   }
 
+  void appendAutomaticObjLeavesScope(VarDecl *VD, Stmt *S, BumpVectorContext &C) {
+    Elements.push_back(CFGAutomaticObjLeavesScope(VD, S), C);
+  }
+
   void appendDeleteDtor(CXXRecordDecl *RD, CXXDeleteExpr *DE, BumpVectorContext &C) {
     Elements.push_back(CFGDeleteDtor(RD, DE), C);
   }
@@ -698,6 +724,19 @@
     *I = CFGAutomaticObjDtor(VD, S);
     return ++I;
   }
+
+  // Scope leaving must be performed in reversed order. So insertion is in two
+  // steps. First we prepare space for some number of elements, then we insert
+  // the elements beginning at the last position in prepared space.
+  iterator beginAutomaticObjLeavesScopeInsert(iterator I, size_t Cnt,
+      BumpVectorContext &C) {
+    return iterator(Elements.insert(I.base(), Cnt,
+                                    CFGAutomaticObjLeavesScope(nullptr, nullptr), C));
+  }
+  iterator insertAutomaticObjLeavesScope(iterator I, VarDecl *VD, Stmt *S) {
+    *I = CFGAutomaticObjLeavesScope(VD, S);
+    return ++I;
+  }
 };
 
 /// \brief CFGCallback defines methods that should be called when a logical
@@ -731,6 +770,7 @@
     ForcedBlkExprs **forcedBlkExprs;
     CFGCallback *Observer;
     bool PruneTriviallyFalseEdges;
+    bool AddAutomaticObjLeavesScope;
     bool AddEHEdges;
     bool AddInitializers;
     bool AddImplicitDtors;
@@ -755,7 +795,9 @@
 
     BuildOptions()
       : forcedBlkExprs(nullptr), Observer(nullptr),
-        PruneTriviallyFalseEdges(true), AddEHEdges(false),
+        PruneTriviallyFalseEdges(true),
+        AddAutomaticObjLeavesScope(false),
+        AddEHEdges(false),
         AddInitializers(false), AddImplicitDtors(false),
         AddTemporaryDtors(false), AddStaticInitBranches(false),
         AddCXXNewAllocator(false), AddCXXDefaultInitExprInCtors(false) {}
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to