diff --git a/clang/include/clang/Analysis/CFG.h b/clang/include/clang/Analysis/CFG.h --- a/clang/include/clang/Analysis/CFG.h +++ b/clang/include/clang/Analysis/CFG.h @@ -1122,19 +1122,10 @@ Elements.push_back(CFGScopeBegin(VD, S), C); } - void prependScopeBegin(const VarDecl *VD, const Stmt *S, - BumpVectorContext &C) { - Elements.insert(Elements.rbegin(), 1, CFGScopeBegin(VD, S), C); - } - void appendScopeEnd(const VarDecl *VD, const Stmt *S, BumpVectorContext &C) { Elements.push_back(CFGScopeEnd(VD, S), C); } - void prependScopeEnd(const VarDecl *VD, const Stmt *S, BumpVectorContext &C) { - Elements.insert(Elements.rbegin(), 1, CFGScopeEnd(VD, S), C); - } - void appendBaseDtor(const CXXBaseSpecifier *BS, BumpVectorContext &C) { Elements.push_back(CFGBaseDtor(BS), C); } @@ -1162,44 +1153,6 @@ void appendDeleteDtor(CXXRecordDecl *RD, CXXDeleteExpr *DE, BumpVectorContext &C) { Elements.push_back(CFGDeleteDtor(RD, DE), C); } - - // Destructors must be inserted 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 beginAutomaticObjDtorsInsert(iterator I, size_t Cnt, - BumpVectorContext &C) { - return iterator(Elements.insert(I.base(), Cnt, - CFGAutomaticObjDtor(nullptr, nullptr), C)); - } - iterator insertAutomaticObjDtor(iterator I, VarDecl *VD, Stmt *S) { - *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 beginLifetimeEndsInsert(iterator I, size_t Cnt, - BumpVectorContext &C) { - return iterator( - Elements.insert(I.base(), Cnt, CFGLifetimeEnds(nullptr, nullptr), C)); - } - iterator insertLifetimeEnds(iterator I, VarDecl *VD, Stmt *S) { - *I = CFGLifetimeEnds(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 beginScopeEndInsert(iterator I, size_t Cnt, BumpVectorContext &C) { - return iterator( - Elements.insert(I.base(), Cnt, CFGScopeEnd(nullptr, nullptr), C)); - } - iterator insertScopeEnd(iterator I, VarDecl *VD, Stmt *S) { - *I = CFGScopeEnd(VD, S); - return ++I; - } }; /// CFGCallback defines methods that should be called when a logical diff --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp --- a/clang/lib/Analysis/CFG.cpp +++ b/clang/lib/Analysis/CFG.cpp @@ -300,6 +300,7 @@ int distance(const_iterator L); const_iterator shared_parent(const_iterator L); bool pointsToFirstDeclaredVar() { return VarIter == 1; } + bool inSameLocalScope(const_iterator rhs) { return Scope == rhs.Scope; } }; private: @@ -349,18 +350,33 @@ /// between this and shared_parent(L) end. LocalScope::const_iterator LocalScope::const_iterator::shared_parent(LocalScope::const_iterator L) { - llvm::SmallPtrSet ScopesOfL; + // one of iterators is not valid (we are not in scope), so common + // parent is const_iterator() (i.e. sentinel). + if ((*this == const_iterator()) || (L == const_iterator())) { + return const_iterator(); + } + + const_iterator F = *this; + if (F.inSameLocalScope(L)) { + // Iterators are in the same scope, get common subset of variables. + F.VarIter = std::min(F.VarIter, L.VarIter); + return F; + } + + llvm::SmallDenseMap ScopesOfL; while (true) { - ScopesOfL.insert(L.Scope); + ScopesOfL.try_emplace(L.Scope, L.VarIter); if (L == const_iterator()) break; L = L.Scope->Prev; } - const_iterator F = *this; while (true) { - if (ScopesOfL.count(F.Scope)) + if (auto LIt = ScopesOfL.find(F.Scope); LIt != ScopesOfL.end()) { + // Get common subset of variables in given scope + F.VarIter = std::min(F.VarIter, LIt->getSecond()); return F; + } assert(F != const_iterator() && "L iterator is not reachable from F iterator."); F = F.Scope->Prev; @@ -513,9 +529,6 @@ llvm::DenseMap ConstructionContextMap; - using DeclsWithEndedScopeSetTy = llvm::SmallSetVector; - DeclsWithEndedScopeSetTy DeclsWithEndedScope; - bool badCFG = false; const CFG::BuildOptions &BuildOpts; @@ -756,18 +769,20 @@ CFGBlock *addInitializer(CXXCtorInitializer *I); void addLoopExit(const Stmt *LoopStmt); - void addAutomaticObjDtors(LocalScope::const_iterator B, - LocalScope::const_iterator E, Stmt *S); - void addLifetimeEnds(LocalScope::const_iterator B, - LocalScope::const_iterator E, Stmt *S); void addAutomaticObjHandling(LocalScope::const_iterator B, LocalScope::const_iterator E, Stmt *S); + void addAutomaticObjDestruction(LocalScope::const_iterator B, + LocalScope::const_iterator E, Stmt *S); + void addScopeExitHandling(LocalScope::const_iterator B, + LocalScope::const_iterator E, Stmt *S); void addImplicitDtorsForDestructor(const CXXDestructorDecl *DD); - void addScopesEnd(LocalScope::const_iterator B, LocalScope::const_iterator E, - Stmt *S); - - void getDeclsWithEndedScope(LocalScope::const_iterator B, - LocalScope::const_iterator E, Stmt *S); + void addScopeChangesHandling(LocalScope::const_iterator SrcPos, + LocalScope::const_iterator DstPos, + Stmt *S); + CFGBlock *createScopeChangesHandlingBlock(LocalScope::const_iterator SrcPos, + CFGBlock *SrcBlk, + LocalScope::const_iterator DstPost, + CFGBlock *DstBlk); // Local scopes creation. LocalScope* createOrReuseLocalScope(LocalScope* Scope); @@ -878,18 +893,6 @@ B->appendDeleteDtor(RD, DE, cfg->getBumpVectorContext()); } - void prependAutomaticObjDtorsWithTerminator(CFGBlock *Blk, - LocalScope::const_iterator B, LocalScope::const_iterator E); - - void prependAutomaticObjLifetimeWithTerminator(CFGBlock *Blk, - LocalScope::const_iterator B, - LocalScope::const_iterator E); - - const VarDecl * - prependAutomaticObjScopeEndWithTerminator(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()); @@ -907,21 +910,11 @@ B->appendScopeBegin(VD, S, cfg->getBumpVectorContext()); } - void prependScopeBegin(CFGBlock *B, const VarDecl *VD, const Stmt *S) { - if (BuildOpts.AddScopes) - B->prependScopeBegin(VD, S, cfg->getBumpVectorContext()); - } - void appendScopeEnd(CFGBlock *B, const VarDecl *VD, const Stmt *S) { if (BuildOpts.AddScopes) B->appendScopeEnd(VD, S, cfg->getBumpVectorContext()); } - void prependScopeEnd(CFGBlock *B, const VarDecl *VD, const Stmt *S) { - if (BuildOpts.AddScopes) - B->prependScopeEnd(VD, S, cfg->getBumpVectorContext()); - } - /// Find a relational comparison with an expression evaluating to a /// boolean and a constant other than 0 and 1. /// e.g. if ((x < y) == 10) @@ -1538,7 +1531,6 @@ ConstructionContextMap.erase(E); } - /// BuildCFG - Constructs a CFG from an AST (a Stmt*). The AST can represent an /// arbitrary statement. Examples include a single expression or a function /// body (compound statement). The ownership of the returned CFG is @@ -1556,9 +1548,6 @@ assert(Succ == &cfg->getExit()); Block = nullptr; // the EXIT block is empty. Create all other blocks lazily. - assert(!(BuildOpts.AddImplicitDtors && BuildOpts.AddLifetime) && - "AddImplicitDtors and AddLifetime cannot be used at the same time"); - if (BuildOpts.AddImplicitDtors) if (const CXXDestructorDecl *DD = dyn_cast_or_null(D)) addImplicitDtorsForDestructor(DD); @@ -1622,16 +1611,11 @@ if (LI == LabelMap.end()) continue; JumpTarget JT = LI->second; - prependAutomaticObjLifetimeWithTerminator(B, I->scopePosition, - JT.scopePosition); - prependAutomaticObjDtorsWithTerminator(B, I->scopePosition, - JT.scopePosition); - const VarDecl *VD = prependAutomaticObjScopeEndWithTerminator( - B, I->scopePosition, JT.scopePosition); - appendScopeBegin(JT.block, VD, G); - addSuccessor(B, JT.block); - }; - if (auto *G = dyn_cast(B->getTerminator())) { + + CFGBlock *SuccBlk = createScopeChangesHandlingBlock( + I->scopePosition, B, JT.scopePosition, JT.block); + addSuccessor(B, SuccBlk); + } else if (auto *G = dyn_cast(B->getTerminator())) { CFGBlock *Successor = (I+1)->block; for (auto *L : G->labels()) { LabelMapTy::iterator LI = LabelMap.find(L->getLabel()); @@ -1798,143 +1782,195 @@ appendLoopExit(Block, LoopStmt); } -void CFGBuilder::getDeclsWithEndedScope(LocalScope::const_iterator B, - LocalScope::const_iterator E, Stmt *S) { - if (!BuildOpts.AddScopes) +/// Adds the CFG elements for leaving the scope of automatic objects in +/// range [B, E). This include following: +/// * AutomaticObjectDtor for variables with non-trivial destructor +/// * LifetimeEnds for all variables +/// * ScopeEnd for each scope left +void CFGBuilder::addAutomaticObjHandling(LocalScope::const_iterator B, + LocalScope::const_iterator E, + Stmt *S) { + if (!BuildOpts.AddScopes && !BuildOpts.AddImplicitDtors && + !BuildOpts.AddLifetime) return; if (B == E) return; - // To go from B to E, one first goes up the scopes from B to P - // then sideways in one scope from P to P' and then down - // the scopes from P' to E. - // The lifetime of all objects between B and P end. - LocalScope::const_iterator P = B.shared_parent(E); - int Dist = B.distance(P); - if (Dist <= 0) + // Not leaving the scope, only need to handle destruction and lifetime + if (B.inSameLocalScope(E)) { + addAutomaticObjDestruction(B, E, S); return; + } - for (LocalScope::const_iterator I = B; I != P; ++I) - if (I.pointsToFirstDeclaredVar()) - DeclsWithEndedScope.insert(*I); -} + // Extract information about all local scopes that are left + SmallVector LocalScopeEndMarkers; + LocalScopeEndMarkers.push_back(B); + for (LocalScope::const_iterator I = B; I != E; ++I) { + if (!I.inSameLocalScope(LocalScopeEndMarkers.back())) + LocalScopeEndMarkers.push_back(I); + } + LocalScopeEndMarkers.push_back(E); + + // We need to leave the scope in reverse order, so we reverse the end + // markers + std::reverse(LocalScopeEndMarkers.begin(), LocalScopeEndMarkers.end()); + auto Pairwise = + llvm::zip(LocalScopeEndMarkers, llvm::drop_begin(LocalScopeEndMarkers)); + for (auto [E, B] : Pairwise) { + if (!B.inSameLocalScope(E)) + addScopeExitHandling(B, E, S); + addAutomaticObjDestruction(B, E, S); + } +} + +/// Add CFG elements corresponding to call destructor and end of lifetime +/// of all automatic variables with non-trivial destructor in range [B, E). +/// This include AutomaticObjectDtor and LifetimeEnds elements. +void CFGBuilder::addAutomaticObjDestruction(LocalScope::const_iterator B, + LocalScope::const_iterator E, + Stmt *S) { + if (!BuildOpts.AddImplicitDtors && !BuildOpts.AddLifetime) + return; -void CFGBuilder::addAutomaticObjHandling(LocalScope::const_iterator B, - LocalScope::const_iterator E, - Stmt *S) { - getDeclsWithEndedScope(B, E, S); - if (BuildOpts.AddScopes) - addScopesEnd(B, E, S); - if (BuildOpts.AddImplicitDtors) - addAutomaticObjDtors(B, E, S); - if (BuildOpts.AddLifetime) - addLifetimeEnds(B, E, S); + if (B == E) + return; + + SmallVector DeclsNonTrivial; + DeclsNonTrivial.reserve(B.distance(E)); + + for (VarDecl* D : llvm::make_range(B, E)) + if (!hasTrivialDestructor(D)) + DeclsNonTrivial.push_back(D); + + for (VarDecl *VD : llvm::reverse(DeclsNonTrivial)) { + if (BuildOpts.AddImplicitDtors) { + // If this destructor is marked as a no-return destructor, we need to + // create a new block for the destructor which does not have as a + // successor anything built thus far: control won't flow out of this + // block. + QualType Ty = VD->getType(); + if (Ty->isReferenceType()) + Ty = getReferenceInitTemporaryType(VD->getInit()); + Ty = Context->getBaseElementType(Ty); + + if (Ty->getAsCXXRecordDecl()->isAnyDestructorNoReturn()) + Block = createNoReturnBlock(); + } + + autoCreateBlock(); + + // Add LifetimeEnd after automatic obj with non-trivial destructors, + // as they end their lifetime when the destructor returns. For trivial + // objects, we end lifetime with scope end. + if (BuildOpts.AddLifetime) + appendLifetimeEnds(Block, VD, S); + if (BuildOpts.AddImplicitDtors) + appendAutomaticObjDtor(Block, VD, S); + } } -/// Add to current block automatic objects that leave the scope. -void CFGBuilder::addLifetimeEnds(LocalScope::const_iterator B, - LocalScope::const_iterator E, Stmt *S) { - if (!BuildOpts.AddLifetime) +/// Add CFG elements corresponding to leaving a scope. +/// Assumes that range [B, E) corresponds to single scope. +/// This add following elements: +/// * LifetimeEnds for all variables with non-trivial destructor +/// * ScopeEnd for each scope left +void CFGBuilder::addScopeExitHandling(LocalScope::const_iterator B, + LocalScope::const_iterator E, Stmt *S) { + assert(!B.inSameLocalScope(E)); + if (!BuildOpts.AddLifetime && !BuildOpts.AddScopes) return; - if (B == E) - return; + if (BuildOpts.AddScopes) { + autoCreateBlock(); + appendScopeEnd(Block, B.getFirstVarInScope(), S); + } - // To go from B to E, one first goes up the scopes from B to P - // then sideways in one scope from P to P' and then down - // the scopes from P' to E. - // The lifetime of all objects between B and P end. - LocalScope::const_iterator P = B.shared_parent(E); - int dist = B.distance(P); - if (dist <= 0) + if (!BuildOpts.AddLifetime) return; // We need to perform the scope leaving in reverse order SmallVector DeclsTrivial; - SmallVector DeclsNonTrivial; - DeclsTrivial.reserve(dist); - DeclsNonTrivial.reserve(dist); + DeclsTrivial.reserve(B.distance(E)); - for (LocalScope::const_iterator I = B; I != P; ++I) - if (hasTrivialDestructor(*I)) - DeclsTrivial.push_back(*I); - else - DeclsNonTrivial.push_back(*I); + // Objects with trivial destructor ends their lifetime when their storage + // is destroyed, for automatic variables, this happens when the end of the + // scope is added. + for (VarDecl* D : llvm::make_range(B, E)) + if (hasTrivialDestructor(D)) + DeclsTrivial.push_back(D); + + if (DeclsTrivial.empty()) + return; autoCreateBlock(); - // object with trivial destructor end their lifetime last (when storage - // duration ends) for (VarDecl *VD : llvm::reverse(DeclsTrivial)) appendLifetimeEnds(Block, VD, S); - - for (VarDecl *VD : llvm::reverse(DeclsNonTrivial)) - appendLifetimeEnds(Block, VD, S); } -/// Add to current block markers for ending scopes. -void CFGBuilder::addScopesEnd(LocalScope::const_iterator B, - LocalScope::const_iterator E, Stmt *S) { - // If implicit destructors are enabled, we'll add scope ends in - // addAutomaticObjDtors. - if (BuildOpts.AddImplicitDtors) +/// addScopeChangesHandling - appends information about destruction, lifetime +/// and cfgScopeEnd for variables in the scope that was left by the jump, and +/// appends cfgScopeBegin for all scopes that where entered. +/// We insert the cfgScopeBegin at the end of the jump node, as depending on +/// the sourceBlock, each goto, may enter different amount of scopes. +void CFGBuilder::addScopeChangesHandling(LocalScope::const_iterator SrcPos, + LocalScope::const_iterator DstPos, + Stmt *S) { + assert(Block && "Source block should be always crated"); + if (!BuildOpts.AddImplicitDtors && !BuildOpts.AddLifetime && + !BuildOpts.AddScopes) { + return; + } + + if (SrcPos == DstPos) return; - autoCreateBlock(); + // Get common scope, the jump leaves all scopes [SrcPos, BasePos), and + // enter all scopes between [DstPos, BasePos) + LocalScope::const_iterator BasePos = SrcPos.shared_parent(DstPos); - for (VarDecl *VD : llvm::reverse(DeclsWithEndedScope)) - appendScopeEnd(Block, VD, S); + // Append scope begins for scopes entered by goto + if (BuildOpts.AddScopes && !DstPos.inSameLocalScope(BasePos)) { + for (LocalScope::const_iterator I = DstPos; I != BasePos; ++I) + if (I.pointsToFirstDeclaredVar()) + appendScopeBegin(Block, *I, S); + } + + // Append scopeEnds, destructor and lifetime with the terminator for + // block left by goto. + addAutomaticObjHandling(SrcPos, BasePos, S); } -/// addAutomaticObjDtors - Add to current block automatic objects destructors -/// for objects in range of local scope positions. Use S as trigger statement -/// for destructors. -void CFGBuilder::addAutomaticObjDtors(LocalScope::const_iterator B, - LocalScope::const_iterator E, Stmt *S) { - if (!BuildOpts.AddImplicitDtors) - return; +/// createScopeChangesHandlingBlock - Creates a block with cfgElements +/// corresponding to changing the scope from the source scope of the GotoStmt, +/// to destination scope. Add destructor, lifetime and cfgScopeEnd +/// CFGElements to newly created CFGBlock, that will have the CFG terminator +/// transferred. +CFGBlock *CFGBuilder::createScopeChangesHandlingBlock( + LocalScope::const_iterator SrcPos, CFGBlock *SrcBlk, + LocalScope::const_iterator DstPos, CFGBlock *DstBlk) { + if (SrcPos == DstPos) + return DstBlk; - if (B == E) - return; + if (!BuildOpts.AddImplicitDtors && !BuildOpts.AddLifetime && + (!BuildOpts.AddScopes || SrcPos.inSameLocalScope(DstPos))) + return DstBlk; - // We need to append the destructors in reverse order, but any one of them - // may be a no-return destructor which changes the CFG. As a result, buffer - // this sequence up and replay them in reverse order when appending onto the - // CFGBlock(s). - SmallVector Decls; - Decls.reserve(B.distance(E)); - for (LocalScope::const_iterator I = B; I != E; ++I) - Decls.push_back(*I); - - for (VarDecl *VD : llvm::reverse(Decls)) { - if (hasTrivialDestructor(VD)) { - // If AddScopes is enabled and *I is a first variable in a scope, add a - // ScopeEnd marker in a Block. - if (BuildOpts.AddScopes && DeclsWithEndedScope.count(VD)) { - autoCreateBlock(); - appendScopeEnd(Block, VD, S); - } - continue; - } - // If this destructor is marked as a no-return destructor, we need to - // create a new block for the destructor which does not have as a successor - // anything built thus far: control won't flow out of this block. - QualType Ty = VD->getType(); - if (Ty->isReferenceType()) { - Ty = getReferenceInitTemporaryType(VD->getInit()); - } - Ty = Context->getBaseElementType(Ty); + // We will update CFBBuilder when creating new block, restore the + // previous state at exit. + SaveAndRestore save_Block(Block), save_Succ(Succ); - if (Ty->getAsCXXRecordDecl()->isAnyDestructorNoReturn()) - Block = createNoReturnBlock(); - else - autoCreateBlock(); + // Create a new block, and transfer terminator + Block = createBlock(false); + Block->setTerminator(SrcBlk->getTerminator()); + SrcBlk->setTerminator(CFGTerminator()); + addSuccessor(Block, DstBlk); - // Add ScopeEnd just after automatic obj destructor. - if (BuildOpts.AddScopes && DeclsWithEndedScope.count(VD)) - appendScopeEnd(Block, VD, S); - appendAutomaticObjDtor(Block, VD, S); - } + // Fill the created Block with the required elements. + addScopeChangesHandling(SrcPos, DstPos, Block->getTerminatorStmt()); + + assert(Block && "There should be at least one scope changing Block"); + return Block; } /// addImplicitDtorsForDestructor - Add implicit destructors generated for @@ -2079,8 +2115,6 @@ /// const reference. Will reuse Scope if not NULL. LocalScope* CFGBuilder::addLocalScopeForVarDecl(VarDecl *VD, LocalScope* Scope) { - assert(!(BuildOpts.AddImplicitDtors && BuildOpts.AddLifetime) && - "AddImplicitDtors and AddLifetime cannot be used at the same time"); if (!BuildOpts.AddImplicitDtors && !BuildOpts.AddLifetime && !BuildOpts.AddScopes) return Scope; @@ -2089,17 +2123,12 @@ if (!VD->hasLocalStorage()) return Scope; - if (BuildOpts.AddImplicitDtors) { - if (!hasTrivialDestructor(VD) || BuildOpts.AddScopes) { - // Add the variable to scope - Scope = createOrReuseLocalScope(Scope); - Scope->addVar(VD); - ScopePos = Scope->begin(); - } + if (!BuildOpts.AddLifetime && !BuildOpts.AddScopes && + hasTrivialDestructor(VD)) { + assert(BuildOpts.AddImplicitDtors); return Scope; } - assert(BuildOpts.AddLifetime); // Add the variable to scope Scope = createOrReuseLocalScope(Scope); Scope->addVar(VD); @@ -2115,63 +2144,6 @@ addAutomaticObjHandling(ScopePos, scopeBeginPos, S); } -/// prependAutomaticObjDtorsWithTerminator - 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::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->getTerminatorStmt()); -} - -/// prependAutomaticObjLifetimeWithTerminator - Prepend lifetime 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 -/// where lifetime ends. -void CFGBuilder::prependAutomaticObjLifetimeWithTerminator( - CFGBlock *Blk, LocalScope::const_iterator B, LocalScope::const_iterator E) { - if (!BuildOpts.AddLifetime) - return; - BumpVectorContext &C = cfg->getBumpVectorContext(); - CFGBlock::iterator InsertPos = - Blk->beginLifetimeEndsInsert(Blk->end(), B.distance(E), C); - for (LocalScope::const_iterator I = B; I != E; ++I) { - InsertPos = - Blk->insertLifetimeEnds(InsertPos, *I, Blk->getTerminatorStmt()); - } -} - -/// prependAutomaticObjScopeEndWithTerminator - Prepend scope end 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 -/// where scope ends. -const VarDecl * -CFGBuilder::prependAutomaticObjScopeEndWithTerminator( - CFGBlock *Blk, LocalScope::const_iterator B, LocalScope::const_iterator E) { - if (!BuildOpts.AddScopes) - return nullptr; - BumpVectorContext &C = cfg->getBumpVectorContext(); - CFGBlock::iterator InsertPos = - Blk->beginScopeEndInsert(Blk->end(), 1, C); - LocalScope::const_iterator PlaceToInsert = B; - for (LocalScope::const_iterator I = B; I != E; ++I) - PlaceToInsert = I; - Blk->insertScopeEnd(InsertPos, *PlaceToInsert, Blk->getTerminatorStmt()); - return *PlaceToInsert; -} - /// 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). @@ -3458,8 +3430,8 @@ BackpatchBlocks.push_back(JumpSource(Block, ScopePos)); else { JumpTarget JT = I->second; - addAutomaticObjHandling(ScopePos, JT.scopePosition, G); addSuccessor(Block, JT.block); + addScopeChangesHandling(ScopePos, JT.scopePosition, G); } return Block; diff --git a/clang/test/Analysis/auto-obj-dtors-cfg-output.cpp b/clang/test/Analysis/auto-obj-dtors-cfg-output.cpp --- a/clang/test/Analysis/auto-obj-dtors-cfg-output.cpp +++ b/clang/test/Analysis/auto-obj-dtors-cfg-output.cpp @@ -442,7 +442,7 @@ A c; } -// CHECK: [B8 (ENTRY)] +// CHECK: [B9 (ENTRY)] // CHECK-NEXT: Succs (1): B7 // CHECK: [B1] // CHECK: l1: @@ -474,11 +474,8 @@ // CHECK-NEXT: Preds (1): B6 // CHECK-NEXT: Succs (2): B3 B2 // CHECK: [B5] -// CHECK-NEXT: 1: [B6.4].~A() (Implicit destructor) -// CHECK-NEXT: 2: [B6.2].~A() (Implicit destructor) -// CHECK-NEXT: T: goto l0; // CHECK: Preds (1): B6 -// CHECK-NEXT: Succs (1): B6 +// CHECK-NEXT: Succs (1): B8 // CHECK: [B6] // CHECK: l0: // WARNINGS-NEXT: 1: (CXXConstructExpr, A) @@ -490,13 +487,19 @@ // CHECK-NEXT: 5: UV // CHECK-NEXT: 6: [B6.5] (ImplicitCastExpr, LValueToRValue, _Bool) // CHECK-NEXT: T: if [B6.6] -// CHECK-NEXT: Preds (2): B7 B5 +// CHECK-NEXT: Preds (2): B7 B8 // CHECK-NEXT: Succs (2): B5 B4 // CHECK: [B7] // WARNINGS-NEXT: 1: (CXXConstructExpr, A) // ANALYZER-NEXT: 1: (CXXConstructExpr, [B7.2], A) // CHECK-NEXT: 2: A a; -// CHECK-NEXT: Preds (1): B8 +// CHECK-NEXT: Preds (1): B9 +// CHECK-NEXT: Succs (1): B6 +// CHECK: [B8] +// CHECK-NEXT: 1: [B6.4].~A() (Implicit destructor) +// CHECK-NEXT: 2: [B6.2].~A() (Implicit destructor) +// CHECK-NEXT: T: goto l0; +// CHECK-NEXT: Preds (1): B5 // CHECK-NEXT: Succs (1): B6 // CHECK: [B0 (EXIT)] // CHECK-NEXT: Preds (1): B1 diff --git a/clang/test/Analysis/lifetime-cfg-output.cpp b/clang/test/Analysis/lifetime-cfg-output.cpp --- a/clang/test/Analysis/lifetime-cfg-output.cpp +++ b/clang/test/Analysis/lifetime-cfg-output.cpp @@ -752,32 +752,40 @@ ~B(); }; -// CHECK: [B4 (ENTRY)] +// CHECK: [B5 (ENTRY)] // CHECK-NEXT: Succs (1): B3 // CHECK: [B1] // CHECK-NEXT: 1: i // CHECK-NEXT: 2: [B1.1]++ -// CHECK-NEXT: 3: [B2.2] (Lifetime ends) -// CHECK-NEXT: 4: [B3.1] (Lifetime ends) +// CHECK-NEXT: 3: [B2.4] (Lifetime ends) +// CHECK-NEXT: 4: [B2.2] (Lifetime ends) +// CHECK-NEXT: 5: [B3.1] (Lifetime ends) // CHECK-NEXT: Succs (1): B0 // CHECK: [B2] // CHECK-NEXT: label: // CHECK-NEXT: 1: (CXXConstructExpr, B) -// CHECK-NEXT: 2: B b; -// CHECK-NEXT: 3: [B2.2] (Lifetime ends) -// CHECK-NEXT: T: goto label; -// CHECK-NEXT: Preds (2): B3 B2 -// CHECK-NEXT: Succs (1): B2 +// CHECK-NEXT: 2: B b1; +// CHECK-NEXT: 3: (CXXConstructExpr, B) +// CHECK-NEXT: 4: B b2; +// CHECK-NEXT: Preds (2): B3 B4 +// CHECK-NEXT: Succs (1): B4 // CHECK: [B3] // CHECK-NEXT: 1: int i; -// CHECK-NEXT: Preds (1): B4 +// CHECK-NEXT: Preds (1): B5 +// CHECK-NEXT: Succs (1): B2 +// CHECK: [B4] +// CHECK-NEXT: 1: [B2.4] (Lifetime ends) +// CHECK-NEXT: 2: [B2.2] (Lifetime ends) +// CHECK-NEXT: T: goto label; +// CHECK-NEXT: Preds (1): B2 // CHECK-NEXT: Succs (1): B2 // CHECK: [B0 (EXIT)] // CHECK-NEXT: Preds (1): B1 int backpatched_goto() { int i; label: - B b; + B b1; + B b2; goto label; i++; } diff --git a/clang/test/Analysis/no-exit-cfg.c b/clang/test/Analysis/no-exit-cfg.c --- a/clang/test/Analysis/no-exit-cfg.c +++ b/clang/test/Analysis/no-exit-cfg.c @@ -1,4 +1,5 @@ // RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.core -verify %s +// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.core -analyzer-config cfg-scopes=true -verify %s // expected-no-diagnostics // This is a test case for the issue reported in PR 2819: diff --git a/clang/test/Analysis/nonreturn-destructors-cfg-output.cpp b/clang/test/Analysis/nonreturn-destructors-cfg-output.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/nonreturn-destructors-cfg-output.cpp @@ -0,0 +1,132 @@ +// RUN: %clang_analyze_cc1 -std=c++11 -fcxx-exceptions -fexceptions -analyzer-checker=debug.DumpCFG -analyzer-config cfg-rich-constructors=true,cfg-implicit-dtors=true,cfg-lifetime=true,cfg-scopes=true %s > %t 2>&1 +// RUN: FileCheck --input-file=%t -check-prefixes=CHECK %s + +class A { +public: + int x; + [[noreturn]] ~A(); +}; + +void foo(); +extern const bool UV; + +// CHECK: [B3 (ENTRY)] +// CHECK-NEXT: Succs (1): B2 +// +// CHECK: [B1] +// CHECK-NEXT: 1: CFGScopeEnd(a) +// CHECK-NEXT: 2: foo +// CHECK-NEXT: 3: [B1.2] (ImplicitCastExpr, FunctionToPointerDecay, void (*)(void)) +// CHECK-NEXT: 4: [B1.3]() +// CHECK-NEXT: Succs (1): B0 +// +// CHECK: [B2 (NORETURN)] +// CHECK-NEXT: 1: CFGScopeBegin(a) +// CHECK-NEXT: 2: (CXXConstructExpr, [B2.3], A) +// CHECK-NEXT: 3: A a; +// CHECK-NEXT: 4: [B2.3].~A() (Implicit destructor) +// CHECK-NEXT: 5: [B2.3] (Lifetime ends) +// CHECK-NEXT: Preds (1): B3 +// CHECK-NEXT: Succs (1): B0 +// +// CHECK: [B0 (EXIT)] +// CHECK-NEXT: Preds (2): B1 B2 +void test_single_decl() { + { + A a; + } + foo(); +} + +// CHECK: [B6 (ENTRY)] +// CHECK-NEXT: Succs (1): B5 +// +// CHECK: [B1] +// CHECK-NEXT: label: +// CHECK-NEXT: 1: foo +// CHECK-NEXT: 2: [B1.1] (ImplicitCastExpr, FunctionToPointerDecay, void (*)(void)) +// CHECK-NEXT: 3: [B1.2]() +// CHECK-NEXT: Preds (4): B2 B3(Unreachable) B4 B5(Unreachable) +// CHECK-NEXT: Succs (1): B0 +// +// CHECK: [B2] +// CHECK-NEXT: 1: CFGScopeEnd(a) +// CHECK-NEXT: Succs (1): B1 +// +// CHECK: [B3 (NORETURN)] +// CHECK-NEXT: 1: [B5.3].~A() (Implicit destructor) +// CHECK-NEXT: 2: [B5.3] (Lifetime ends) +// CHECK-NEXT: Succs (1): B0 +// +// CHECK: [B4] +// CHECK-NEXT: 1: CFGScopeEnd(a) +// CHECK-NEXT: T: goto label; +// CHECK-NEXT: Succs (1): B1 +// +// CHECK: [B5 (NORETURN)] +// CHECK-NEXT: 1: CFGScopeBegin(a) +// CHECK-NEXT: 2: (CXXConstructExpr, [B5.3], A) +// CHECK-NEXT: 3: A a; +// CHECK-NEXT: 4: [B5.3].~A() (Implicit destructor) +// CHECK-NEXT: 5: [B5.3] (Lifetime ends) +// CHECK-NEXT: Preds (1): B6 +// CHECK-NEXT: Succs (1): B0 +// +// CHECK: [B0 (EXIT)] +// CHECK-NEXT: Preds (3): B1 B3 B5 +void test_forward_goto() { + { + A a; + goto label; + } +label: + foo(); +} + + +// The blocks B3 and B5, are inserted during backpatching goto stmt, to handle +// scope changes. +// CHECK: [B6 (ENTRY)] +// CHECK-NEXT: Succs (1): B3 +// +// CHECK: [B1] +// CHECK-NEXT: 1: CFGScopeEnd(a) +// CHECK-NEXT: 2: foo +// CHECK-NEXT: 3: [B1.2] (ImplicitCastExpr, FunctionToPointerDecay, void (*)(void)) +// CHECK-NEXT: 4: [B1.3]() +// CHECK-NEXT: Succs (1): B0 +// +// CHECK: [B2 (NORETURN)] +// CHECK-NEXT: 1: [B3.3].~A() (Implicit destructor) +// CHECK-NEXT: 2: [B3.3] (Lifetime ends) +// CHECK-NEXT: Succs (1): B0 +// +// CHECK: [B3] +// CHECK-NEXT: label: +// CHECK-NEXT: 1: CFGScopeBegin(a) +// CHECK-NEXT: 2: (CXXConstructExpr, [B3.3], A) +// CHECK-NEXT: 3: A a; +// CHECK-NEXT: Preds (3): B4 B5(Unreachable) B6 +// CHECK-NEXT: Succs (1): B5 +// +// CHECK: [B4] +// CHECK-NEXT: 1: CFGScopeEnd(a) +// CHECK-NEXT: T: goto label; +// CHECK-NEXT: Succs (1): B3 +// +// CHECK: [B5 (NORETURN)] +// CHECK-NEXT: 1: [B3.3].~A() (Implicit destructor) +// CHECK-NEXT: 2: [B3.3] (Lifetime ends) +// CHECK-NEXT: Preds (1): B3 +// CHECK-NEXT: Succs (1): B0 +// +// CHECK: [B0 (EXIT)] +// CHECK-NEXT: Preds (3): B1 B2 B5 +void test_backward_goto() { +label: + { + A a; + goto label; + } + foo(); +} diff --git a/clang/test/Analysis/scopes-cfg-output.cpp b/clang/test/Analysis/scopes-cfg-output.cpp --- a/clang/test/Analysis/scopes-cfg-output.cpp +++ b/clang/test/Analysis/scopes-cfg-output.cpp @@ -674,30 +674,30 @@ A f; } -// CHECK: [B8 (ENTRY)] +// CHECK: [B9 (ENTRY)] // CHECK-NEXT: Succs (1): B7 // CHECK: [B1] // CHECK-NEXT: l1: // CHECK-NEXT: 1: (CXXConstructExpr, [B1.2], A) // CHECK-NEXT: 2: A c; // CHECK-NEXT: 3: [B1.2].~A() (Implicit destructor) -// CHECK-NEXT: 4: [B6.5].~A() (Implicit destructor) -// CHECK-NEXT: 5: [B6.3].~A() (Implicit destructor) +// CHECK-NEXT: 4: [B6.4].~A() (Implicit destructor) +// CHECK-NEXT: 5: [B6.2].~A() (Implicit destructor) // CHECK-NEXT: 6: [B7.3].~A() (Implicit destructor) // CHECK-NEXT: 7: CFGScopeEnd(a) // CHECK-NEXT: Preds (2): B2 B3 // CHECK-NEXT: Succs (1): B0 // CHECK: [B2] // CHECK-NEXT: 1: (CXXConstructExpr, [B2.2], A) -// CHECK-NEXT: 2: A b; +// CHECK-NEXT: 2: A nb; // CHECK-NEXT: 3: [B2.2].~A() (Implicit destructor) -// CHECK-NEXT: 4: [B6.8].~A() (Implicit destructor) -// CHECK-NEXT: 5: CFGScopeEnd(a) +// CHECK-NEXT: 4: [B6.7].~A() (Implicit destructor) +// CHECK-NEXT: 5: CFGScopeEnd(na) // CHECK-NEXT: Preds (1): B4 // CHECK-NEXT: Succs (1): B1 // CHECK: [B3] -// CHECK-NEXT: 1: [B6.8].~A() (Implicit destructor) -// CHECK-NEXT: 2: CFGScopeEnd(a) +// CHECK-NEXT: 1: [B6.7].~A() (Implicit destructor) +// CHECK-NEXT: 2: CFGScopeEnd(na) // CHECK-NEXT: T: goto l1; // CHECK-NEXT: Preds (1): B4 // CHECK-NEXT: Succs (1): B1 @@ -708,33 +708,35 @@ // CHECK-NEXT: Preds (1): B6 // CHECK-NEXT: Succs (2): B3 B2 // CHECK: [B5] -// CHECK-NEXT: 1: [B6.8].~A() (Implicit destructor) -// CHECK-NEXT: 2: [B6.5].~A() (Implicit destructor) -// CHECK-NEXT: 3: [B6.3].~A() (Implicit destructor) -// CHECK-NEXT: 4: CFGScopeEnd(cb) -// CHECK-NEXT: T: goto l0; // CHECK-NEXT: Preds (1): B6 -// CHECK-NEXT: Succs (1): B6 +// CHECK-NEXT: Succs (1): B8 // CHECK: [B6] // CHECK-NEXT: l0: -// CHECK-NEXT: 1: CFGScopeBegin(cb) -// CHECK-NEXT: 2: (CXXConstructExpr, [B6.3], A) -// CHECK-NEXT: 3: A cb; -// CHECK-NEXT: 4: (CXXConstructExpr, [B6.5], A) -// CHECK-NEXT: 5: A b; -// CHECK-NEXT: 6: CFGScopeBegin(a) -// CHECK-NEXT: 7: (CXXConstructExpr, [B6.8], A) -// CHECK-NEXT: 8: A a; -// CHECK-NEXT: 9: UV -// CHECK-NEXT: 10: [B6.9] (ImplicitCastExpr, LValueToRValue, _Bool) -// CHECK-NEXT: T: if [B6.10] -// CHECK-NEXT: Preds (2): B7 B5 +// CHECK-NEXT: 1: (CXXConstructExpr, [B6.2], A) +// CHECK-NEXT: 2: A cb; +// CHECK-NEXT: 3: (CXXConstructExpr, [B6.4], A) +// CHECK-NEXT: 4: A b; +// CHECK-NEXT: 5: CFGScopeBegin(na) +// CHECK-NEXT: 6: (CXXConstructExpr, [B6.7], A) +// CHECK-NEXT: 7: A na; +// CHECK-NEXT: 8: UV +// CHECK-NEXT: 9: [B6.8] (ImplicitCastExpr, LValueToRValue, _Bool) +// CHECK-NEXT: T: if [B6.9] +// CHECK-NEXT: Preds (2): B7 B8 // CHECK-NEXT: Succs (2): B5 B4 // CHECK: [B7] // CHECK-NEXT: 1: CFGScopeBegin(a) // CHECK-NEXT: 2: (CXXConstructExpr, [B7.3], A) // CHECK-NEXT: 3: A a; -// CHECK-NEXT: Preds (1): B8 +// CHECK-NEXT: Preds (1): B9 +// CHECK-NEXT: Succs (1): B6 +// CHECK: [B8] +// CHECK-NEXT: 1: [B6.7].~A() (Implicit destructor) +// CHECK-NEXT: 2: CFGScopeEnd(na) +// CHECK-NEXT: 3: [B6.4].~A() (Implicit destructor) +// CHECK-NEXT: 4: [B6.2].~A() (Implicit destructor) +// CHECK-NEXT: T: goto l0; +// CHECK-NEXT: Preds (1): B5 // CHECK-NEXT: Succs (1): B6 // CHECK: [B0 (EXIT)] // CHECK-NEXT: Preds (1): B1 @@ -743,10 +745,10 @@ l0: A cb; A b; - { A a; + { A na; if (UV) goto l0; if (UV) goto l1; - A b; + A nb; } l1: A c; @@ -1168,3 +1170,184 @@ } } } + +// CHECK: [B4 (ENTRY)] +// CHECK-NEXT: Succs (1): B3 +// CHECK: [B1] +// CHECK-NEXT: label: +// CHECK-NEXT: 1: CFGScopeEnd(n2t) +// CHECK-NEXT: 2: CFGScopeEnd(n1t) +// CHECK-NEXT: 3: [B3.3].~A() (Implicit destructor) +// CHECK-NEXT: 4: CFGScopeEnd(a) +// CHECK-NEXT: Preds (2): B2 B3 +// CHECK-NEXT: Succs (1): B0 +// CHECK: [B2] +// CHECK-NEXT: 1: [B3.9].~A() (Implicit destructor) +// CHECK-NEXT: 2: CFGScopeEnd(n2s) +// CHECK-NEXT: 3: [B3.6].~A() (Implicit destructor) +// CHECK-NEXT: 4: CFGScopeEnd(n1s) +// CHECK-NEXT: 5: CFGScopeBegin(n1t) +// CHECK-NEXT: 6: int n1t; +// CHECK-NEXT: 7: CFGScopeBegin(n2t) +// CHECK-NEXT: 8: int n2t; +// CHECK-NEXT: Succs (1): B1 +// CHECK: [B3] +// CHECK-NEXT: 1: CFGScopeBegin(a) +// CHECK-NEXT: 2: (CXXConstructExpr, [B3.3], A) +// CHECK-NEXT: 3: A a; +// CHECK-NEXT: 4: CFGScopeBegin(n1s) +// CHECK-NEXT: 5: (CXXConstructExpr, [B3.6], A) +// CHECK-NEXT: 6: A n1s; +// CHECK-NEXT: 7: CFGScopeBegin(n2s) +// CHECK-NEXT: 8: (CXXConstructExpr, [B3.9], A) +// CHECK-NEXT: 9: A n2s; +// CHECK-NEXT: 10: [B3.9].~A() (Implicit destructor) +// CHECK-NEXT: 11: CFGScopeEnd(n2s) +// CHECK-NEXT: 12: [B3.6].~A() (Implicit destructor) +// CHECK-NEXT: 13: CFGScopeEnd(n1s) +// CHECK-NEXT: 14: CFGScopeBegin(n1t) +// CHECK-NEXT: 15: CFGScopeBegin(n2t) +// CHECK-NEXT: T: goto label; +// CHECK-NEXT: Preds (1): B4 +// CHECK-NEXT: Succs (1): B1 +// CHECK: [B0 (EXIT)] +// CHECK-NEXT: Preds (1): B1 +void test_goto_multiple_scopes() { + A a; + { + A n1s; + { + A n2s; + goto label; + } + } + { + int n1t; + { + int n2t; +label: + } + } +} + +// CHECK: [B5 (ENTRY)] +// CHECK-NEXT: Succs (1): B3 +// CHECK: [B1] +// CHECK-NEXT: 1: [B2.8].~A() (Implicit destructor) +// CHECK-NEXT: 2: CFGScopeEnd(n2s) +// CHECK-NEXT: 3: [B2.5].~A() (Implicit destructor) +// CHECK-NEXT: 4: CFGScopeEnd(n1s) +// CHECK-NEXT: 5: [B3.3].~A() (Implicit destructor) +// CHECK-NEXT: 6: CFGScopeEnd(a) +// CHECK-NEXT: Succs (1): B0 +// CHECK: [B2] +// CHECK-NEXT: label: +// CHECK-NEXT: 1: CFGScopeEnd(n2t) +// CHECK-NEXT: 2: CFGScopeEnd(n1t) +// CHECK-NEXT: 3: CFGScopeBegin(n1s) +// CHECK-NEXT: 4: (CXXConstructExpr, [B2.5], A) +// CHECK-NEXT: 5: A n1s; +// CHECK-NEXT: 6: CFGScopeBegin(n2s) +// CHECK-NEXT: 7: (CXXConstructExpr, [B2.8], A) +// CHECK-NEXT: 8: A n2s; +// CHECK-NEXT: Preds (2): B3 B4 +// CHECK-NEXT: Succs (1): B4 +// CHECK: [B3] +// CHECK-NEXT: 1: CFGScopeBegin(a) +// CHECK-NEXT: 2: (CXXConstructExpr, [B3.3], A) +// CHECK-NEXT: 3: A a; +// CHECK-NEXT: 4: CFGScopeBegin(n1t) +// CHECK-NEXT: 5: int n1t; +// CHECK-NEXT: 6: CFGScopeBegin(n2t) +// CHECK-NEXT: 7: int n2t; +// CHECK-NEXT: Preds (1): B5 +// CHECK-NEXT: Succs (1): B2 +// CHECK: [B4] +// CHECK-NEXT: 1: [B2.8].~A() (Implicit destructor) +// CHECK-NEXT: 2: CFGScopeEnd(n2s) +// CHECK-NEXT: 3: [B2.5].~A() (Implicit destructor) +// CHECK-NEXT: 4: CFGScopeEnd(n1s) +// CHECK-NEXT: 5: CFGScopeBegin(n1t) +// CHECK-NEXT: 6: CFGScopeBegin(n2t) +// CHECK-NEXT: T: goto label; +// CHECK-NEXT: Preds (1): B2 +// CHECK-NEXT: Succs (1): B2 +// CHECK: [B0 (EXIT)] +// CHECK-NEXT: Preds (1): B1 +void test_backpatched_goto_multiple_scopes() { + A a; + { + int n1t; + { + int n2t; +label: + } + } +{ + A n1s; + { + A n2s; + goto label; + } +} +} + +// CHECK: [B8 (ENTRY)] +// CHECK-NEXT: Succs (1): B7 +// CHECK: [B1] +// CHECK-NEXT: label: +// CHECK-NEXT: 1: CFGScopeEnd(n2t) +// CHECK-NEXT: 2: CFGScopeEnd(n1t) +// CHECK-NEXT: Preds (4): B2 B3 B4 B6 +// CHECK-NEXT: Succs (1): B0 +// CHECK: [B2] +// CHECK-NEXT: T: goto label; +// CHECK-NEXT: Preds (1): B3 +// CHECK-NEXT: Succs (1): B1 +// CHECK: [B3] +// CHECK-NEXT: 1: CFGScopeBegin(n2t) +// CHECK-NEXT: 2: int n2t; +// CHECK-NEXT: 3: UV +// CHECK-NEXT: 4: [B3.3] (ImplicitCastExpr, LValueToRValue, _Bool) +// CHECK-NEXT: T: if [B3.4] +// CHECK-NEXT: Preds (1): B5 +// CHECK-NEXT: Succs (2): B2 B1 +// CHECK: [B4] +// CHECK-NEXT: 1: CFGScopeBegin(n2t) +// CHECK-NEXT: T: goto label; +// CHECK-NEXT: Preds (1): B5 +// CHECK-NEXT: Succs (1): B1 +// CHECK: [B5] +// CHECK-NEXT: 1: CFGScopeBegin(n1t) +// CHECK-NEXT: 2: int n1t; +// CHECK-NEXT: 3: UV +// CHECK-NEXT: 4: [B5.3] (ImplicitCastExpr, LValueToRValue, _Bool) +// CHECK-NEXT: T: if [B5.4] +// CHECK-NEXT: Preds (1): B7 +// CHECK-NEXT: Succs (2): B4 B3 +// CHECK: [B6] +// CHECK-NEXT: 1: CFGScopeBegin(n1t) +// CHECK-NEXT: 2: CFGScopeBegin(n2t) +// CHECK-NEXT: T: goto label; +// CHECK-NEXT: Preds (1): B7 +// CHECK-NEXT: Succs (1): B1 +// CHECK: [B7] +// CHECK-NEXT: 1: UV +// CHECK-NEXT: 2: [B7.1] (ImplicitCastExpr, LValueToRValue, _Bool) +// CHECK-NEXT: T: if [B7.2] +// CHECK-NEXT: Preds (1): B8 +// CHECK-NEXT: Succs (2): B6 B5 +// CHECK: [B0 (EXIT)] +// CHECK-NEXT: Preds (1): B1 +void test_multiple_goto_entering_scopes() { + if (UV) goto label; + { + int n1t; + if (UV) goto label; + { + int n2t; + if (UV) goto label; +label: + } + } +}