]> granicus.if.org Git - clang/commitdiff
[CFG] Make representation of destructor calls more accurate.
authorArtem Dergachev <artem.dergachev@gmail.com>
Wed, 28 Aug 2019 18:44:42 +0000 (18:44 +0000)
committerArtem Dergachev <artem.dergachev@gmail.com>
Wed, 28 Aug 2019 18:44:42 +0000 (18:44 +0000)
Respect C++17 copy elision; previously it would generate destructor calls
for elided temporaries, including in initialization and return statements.

Don't generate duplicate destructor calls for statement expressions.

Fix destructors in initialization lists and comma operators.

Improve printing of implicit destructors.

Patch by Nicholas Allegra!

Differential Revision: https://reviews.llvm.org/D66404

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@370247 91177308-0d34-0410-b5e6-96231b3b80d8

lib/Analysis/CFG.cpp
test/Analysis/auto-obj-dtors-cfg-output.cpp
test/Analysis/cfg-rich-constructors.cpp
test/Analysis/cfg-rich-constructors.mm
test/Analysis/cfg.cpp
test/Analysis/missing-bind-temporary.cpp
test/Analysis/more-dtors-cfg-output.cpp [new file with mode: 0644]
test/Analysis/scopes-cfg-output.cpp
test/Analysis/temporaries.cpp

index fb643464fa94063f4f9fa385190d881d9e86f9e9..33c47b24a9a5bd217f61df9f5e9494573d57efd2 100644 (file)
@@ -525,7 +525,7 @@ private:
   CFGBlock *VisitCallExpr(CallExpr *C, AddStmtChoice asc);
   CFGBlock *VisitCaseStmt(CaseStmt *C);
   CFGBlock *VisitChooseExpr(ChooseExpr *C, AddStmtChoice asc);
-  CFGBlock *VisitCompoundStmt(CompoundStmt *C);
+  CFGBlock *VisitCompoundStmt(CompoundStmt *C, bool ExternallyDestructed);
   CFGBlock *VisitConditionalOperator(AbstractConditionalOperator *C,
                                      AddStmtChoice asc);
   CFGBlock *VisitContinueStmt(ContinueStmt *C);
@@ -546,7 +546,8 @@ private:
   CFGBlock *VisitDeclSubExpr(DeclStmt *DS);
   CFGBlock *VisitDefaultStmt(DefaultStmt *D);
   CFGBlock *VisitDoStmt(DoStmt *D);
-  CFGBlock *VisitExprWithCleanups(ExprWithCleanups *E, AddStmtChoice asc);
+  CFGBlock *VisitExprWithCleanups(ExprWithCleanups *E,
+                                  AddStmtChoice asc, bool ExternallyDestructed);
   CFGBlock *VisitForStmt(ForStmt *F);
   CFGBlock *VisitGotoStmt(GotoStmt *G);
   CFGBlock *VisitGCCAsmStmt(GCCAsmStmt *G, AddStmtChoice asc);
@@ -585,7 +586,8 @@ private:
   CFGBlock *VisitUnaryOperator(UnaryOperator *U, AddStmtChoice asc);
   CFGBlock *VisitWhileStmt(WhileStmt *W);
 
-  CFGBlock *Visit(Stmt *S, AddStmtChoice asc = AddStmtChoice::NotAlwaysAdd);
+  CFGBlock *Visit(Stmt *S, AddStmtChoice asc = AddStmtChoice::NotAlwaysAdd,
+                  bool ExternallyDestructed = false);
   CFGBlock *VisitStmt(Stmt *S, AddStmtChoice asc);
   CFGBlock *VisitChildren(Stmt *S);
   CFGBlock *VisitNoRecurse(Expr *E, AddStmtChoice asc);
@@ -656,15 +658,17 @@ private:
 
   // Visitors to walk an AST and generate destructors of temporaries in
   // full expression.
-  CFGBlock *VisitForTemporaryDtors(Stmt *E, bool BindToTemporary,
+  CFGBlock *VisitForTemporaryDtors(Stmt *E, bool ExternallyDestructed,
                                    TempDtorContext &Context);
-  CFGBlock *VisitChildrenForTemporaryDtors(Stmt *E, TempDtorContext &Context);
+  CFGBlock *VisitChildrenForTemporaryDtors(Stmt *E,  bool ExternallyDestructed,
+                                           TempDtorContext &Context);
   CFGBlock *VisitBinaryOperatorForTemporaryDtors(BinaryOperator *E,
+                                                 bool ExternallyDestructed,
                                                  TempDtorContext &Context);
   CFGBlock *VisitCXXBindTemporaryExprForTemporaryDtors(
-      CXXBindTemporaryExpr *E, bool BindToTemporary, TempDtorContext &Context);
+      CXXBindTemporaryExpr *E, bool ExternallyDestructed, TempDtorContext &Context);
   CFGBlock *VisitConditionalOperatorForTemporaryDtors(
-      AbstractConditionalOperator *E, bool BindToTemporary,
+      AbstractConditionalOperator *E, bool ExternallyDestructed,
       TempDtorContext &Context);
   void InsertTempDtorDecisionBlock(const TempDtorContext &Context,
                                    CFGBlock *FalseSucc = nullptr);
@@ -1575,7 +1579,7 @@ CFGBlock *CFGBuilder::addInitializer(CXXCtorInitializer *I) {
       // Generate destructors for temporaries in initialization expression.
       TempDtorContext Context;
       VisitForTemporaryDtors(cast<ExprWithCleanups>(Init)->getSubExpr(),
-                             /*BindToTemporary=*/false, Context);
+                             /*ExternallyDestructed=*/false, Context);
     }
   }
 
@@ -2051,7 +2055,8 @@ CFGBuilder::prependAutomaticObjScopeEndWithTerminator(
 /// 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).
-CFGBlock *CFGBuilder::Visit(Stmt * S, AddStmtChoice asc) {
+CFGBlock *CFGBuilder::Visit(Stmt * S, AddStmtChoice asc,
+                            bool ExternallyDestructed) {
   if (!S) {
     badCFG = true;
     return nullptr;
@@ -2096,7 +2101,7 @@ CFGBlock *CFGBuilder::Visit(Stmt * S, AddStmtChoice asc) {
       return VisitChooseExpr(cast<ChooseExpr>(S), asc);
 
     case Stmt::CompoundStmtClass:
-      return VisitCompoundStmt(cast<CompoundStmt>(S));
+      return VisitCompoundStmt(cast<CompoundStmt>(S), ExternallyDestructed);
 
     case Stmt::ConditionalOperatorClass:
       return VisitConditionalOperator(cast<ConditionalOperator>(S), asc);
@@ -2108,7 +2113,8 @@ CFGBlock *CFGBuilder::Visit(Stmt * S, AddStmtChoice asc) {
       return VisitCXXCatchStmt(cast<CXXCatchStmt>(S));
 
     case Stmt::ExprWithCleanupsClass:
-      return VisitExprWithCleanups(cast<ExprWithCleanups>(S), asc);
+      return VisitExprWithCleanups(cast<ExprWithCleanups>(S),
+                                   asc, ExternallyDestructed);
 
     case Stmt::CXXDefaultArgExprClass:
     case Stmt::CXXDefaultInitExprClass:
@@ -2603,7 +2609,7 @@ CFGBlock *CFGBuilder::VisitChooseExpr(ChooseExpr *C,
   return addStmt(C->getCond());
 }
 
-CFGBlock *CFGBuilder::VisitCompoundStmt(CompoundStmt *C) {
+CFGBlock *CFGBuilder::VisitCompoundStmt(CompoundStmt *C, bool ExternallyDestructed) {
   LocalScope::const_iterator scopeBeginPos = ScopePos;
   addLocalScopeForStmt(C);
 
@@ -2619,11 +2625,16 @@ CFGBlock *CFGBuilder::VisitCompoundStmt(CompoundStmt *C) {
        I != E; ++I ) {
     // If we hit a segment of code just containing ';' (NullStmts), we can
     // get a null block back.  In such cases, just use the LastBlock
-    if (CFGBlock *newBlock = addStmt(*I))
+    CFGBlock *newBlock = Visit(*I, AddStmtChoice::AlwaysAdd,
+                               ExternallyDestructed);
+
+    if (newBlock)
       LastBlock = newBlock;
 
     if (badCFG)
       return nullptr;
+
+    ExternallyDestructed = false;
   }
 
   return LastBlock;
@@ -2766,7 +2777,7 @@ CFGBlock *CFGBuilder::VisitDeclSubExpr(DeclStmt *DS) {
       // Generate destructors for temporaries in initialization expression.
       TempDtorContext Context;
       VisitForTemporaryDtors(cast<ExprWithCleanups>(Init)->getSubExpr(),
-                             /*BindToTemporary=*/false, Context);
+                             /*ExternallyDestructed=*/true, Context);
     }
   }
 
@@ -2980,9 +2991,18 @@ CFGBlock *CFGBuilder::VisitReturnStmt(Stmt *S) {
   if (!Block->hasNoReturnElement())
     addSuccessor(Block, &cfg->getExit());
 
-  // Add the return statement to the block.  This may create new blocks if R
-  // contains control-flow (short-circuit operations).
-  return VisitStmt(S, AddStmtChoice::AlwaysAdd);
+  // Add the return statement to the block.
+  appendStmt(Block, S);
+
+  // Visit children
+  if (ReturnStmt *RS = dyn_cast<ReturnStmt>(S)) {
+    Expr *O = RS->getRetValue();
+    if (O)
+      Visit(O, AddStmtChoice::AlwaysAdd, /*ExternallyDestructed=*/true);
+    return Block;
+  } else { // co_return
+    return VisitChildren(S);
+  }
 }
 
 CFGBlock *CFGBuilder::VisitSEHExceptStmt(SEHExceptStmt *ES) {
@@ -3014,7 +3034,7 @@ CFGBlock *CFGBuilder::VisitSEHExceptStmt(SEHExceptStmt *ES) {
 }
 
 CFGBlock *CFGBuilder::VisitSEHFinallyStmt(SEHFinallyStmt *FS) {
-  return VisitCompoundStmt(FS->getBlock());
+  return VisitCompoundStmt(FS->getBlock(), /*ExternallyDestructed=*/false);
 }
 
 CFGBlock *CFGBuilder::VisitSEHLeaveStmt(SEHLeaveStmt *LS) {
@@ -3898,7 +3918,7 @@ CFGBlock *CFGBuilder::VisitStmtExpr(StmtExpr *SE, AddStmtChoice asc) {
     autoCreateBlock();
     appendStmt(Block, SE);
   }
-  return VisitCompoundStmt(SE->getSubStmt());
+  return VisitCompoundStmt(SE->getSubStmt(), /*ExternallyDestructed=*/true);
 }
 
 CFGBlock *CFGBuilder::VisitSwitchStmt(SwitchStmt *Terminator) {
@@ -4363,12 +4383,12 @@ CFGBlock *CFGBuilder::VisitCXXForRangeStmt(CXXForRangeStmt *S) {
 }
 
 CFGBlock *CFGBuilder::VisitExprWithCleanups(ExprWithCleanups *E,
-    AddStmtChoice asc) {
+    AddStmtChoice asc, bool ExternallyDestructed) {
   if (BuildOpts.AddTemporaryDtors) {
     // If adding implicit destructors visit the full expression for adding
     // destructors of temporaries.
     TempDtorContext Context;
-    VisitForTemporaryDtors(E->getSubExpr(), false, Context);
+    VisitForTemporaryDtors(E->getSubExpr(), ExternallyDestructed, Context);
 
     // Full expression has to be added as CFGStmt so it will be sequenced
     // before destructors of it's temporaries.
@@ -4504,7 +4524,7 @@ CFGBlock *CFGBuilder::VisitIndirectGotoStmt(IndirectGotoStmt *I) {
   return addStmt(I->getTarget());
 }
 
-CFGBlock *CFGBuilder::VisitForTemporaryDtors(Stmt *E, bool BindToTemporary,
+CFGBlock *CFGBuilder::VisitForTemporaryDtors(Stmt *E, bool ExternallyDestructed,
                                              TempDtorContext &Context) {
   assert(BuildOpts.AddImplicitDtors && BuildOpts.AddTemporaryDtors);
 
@@ -4515,28 +4535,32 @@ tryAgain:
   }
   switch (E->getStmtClass()) {
     default:
-      return VisitChildrenForTemporaryDtors(E, Context);
+      return VisitChildrenForTemporaryDtors(E, false, Context);
+
+    case Stmt::InitListExprClass:
+      return VisitChildrenForTemporaryDtors(E, ExternallyDestructed, Context);
 
     case Stmt::BinaryOperatorClass:
       return VisitBinaryOperatorForTemporaryDtors(cast<BinaryOperator>(E),
+                                                  ExternallyDestructed,
                                                   Context);
 
     case Stmt::CXXBindTemporaryExprClass:
       return VisitCXXBindTemporaryExprForTemporaryDtors(
-          cast<CXXBindTemporaryExpr>(E), BindToTemporary, Context);
+          cast<CXXBindTemporaryExpr>(E), ExternallyDestructed, Context);
 
     case Stmt::BinaryConditionalOperatorClass:
     case Stmt::ConditionalOperatorClass:
       return VisitConditionalOperatorForTemporaryDtors(
-          cast<AbstractConditionalOperator>(E), BindToTemporary, Context);
+          cast<AbstractConditionalOperator>(E), ExternallyDestructed, Context);
 
     case Stmt::ImplicitCastExprClass:
-      // For implicit cast we want BindToTemporary to be passed further.
+      // For implicit cast we want ExternallyDestructed to be passed further.
       E = cast<CastExpr>(E)->getSubExpr();
       goto tryAgain;
 
     case Stmt::CXXFunctionalCastExprClass:
-      // For functional cast we want BindToTemporary to be passed further.
+      // For functional cast we want ExternallyDestructed to be passed further.
       E = cast<CXXFunctionalCastExpr>(E)->getSubExpr();
       goto tryAgain;
 
@@ -4550,7 +4574,7 @@ tryAgain:
 
     case Stmt::MaterializeTemporaryExprClass: {
       const MaterializeTemporaryExpr* MTE = cast<MaterializeTemporaryExpr>(E);
-      BindToTemporary = (MTE->getStorageDuration() != SD_FullExpression);
+      ExternallyDestructed = (MTE->getStorageDuration() != SD_FullExpression);
       SmallVector<const Expr *, 2> CommaLHSs;
       SmallVector<SubobjectAdjustment, 2> Adjustments;
       // Find the expression whose lifetime needs to be extended.
@@ -4561,7 +4585,7 @@ tryAgain:
       // Visit the skipped comma operator left-hand sides for other temporaries.
       for (const Expr *CommaLHS : CommaLHSs) {
         VisitForTemporaryDtors(const_cast<Expr *>(CommaLHS),
-                               /*BindToTemporary=*/false, Context);
+                               /*ExternallyDestructed=*/false, Context);
       }
       goto tryAgain;
     }
@@ -4579,13 +4603,18 @@ tryAgain:
       for (Expr *Init : LE->capture_inits()) {
         if (Init) {
           if (CFGBlock *R = VisitForTemporaryDtors(
-                  Init, /*BindToTemporary=*/false, Context))
+                  Init, /*ExternallyDestructed=*/true, Context))
             B = R;
         }
       }
       return B;
     }
 
+    case Stmt::StmtExprClass:
+      // Don't recurse into statement expressions; any cleanups inside them
+      // will be wrapped in their own ExprWithCleanups.
+      return Block;
+
     case Stmt::CXXDefaultArgExprClass:
       E = cast<CXXDefaultArgExpr>(E)->getExpr();
       goto tryAgain;
@@ -4597,6 +4626,7 @@ tryAgain:
 }
 
 CFGBlock *CFGBuilder::VisitChildrenForTemporaryDtors(Stmt *E,
+                                                     bool ExternallyDestructed,
                                                      TempDtorContext &Context) {
   if (isa<LambdaExpr>(E)) {
     // Do not visit the children of lambdas; they have their own CFGs.
@@ -4610,14 +4640,22 @@ CFGBlock *CFGBuilder::VisitChildrenForTemporaryDtors(Stmt *E,
   CFGBlock *B = Block;
   for (Stmt *Child : E->children())
     if (Child)
-      if (CFGBlock *R = VisitForTemporaryDtors(Child, false, Context))
+      if (CFGBlock *R = VisitForTemporaryDtors(Child, ExternallyDestructed, Context))
         B = R;
 
   return B;
 }
 
 CFGBlock *CFGBuilder::VisitBinaryOperatorForTemporaryDtors(
-    BinaryOperator *E, TempDtorContext &Context) {
+    BinaryOperator *E, bool ExternallyDestructed, TempDtorContext &Context) {
+  if (E->isCommaOp()) {
+    // For comma operator LHS expression is visited
+    // before RHS expression. For destructors visit them in reverse order.
+    CFGBlock *RHSBlock = VisitForTemporaryDtors(E->getRHS(), ExternallyDestructed, Context);
+    CFGBlock *LHSBlock = VisitForTemporaryDtors(E->getLHS(), false, Context);
+    return LHSBlock ? LHSBlock : RHSBlock;
+  }
+
   if (E->isLogicalOp()) {
     VisitForTemporaryDtors(E->getLHS(), false, Context);
     TryResult RHSExecuted = tryEvaluateBool(E->getLHS());
@@ -4652,10 +4690,11 @@ CFGBlock *CFGBuilder::VisitBinaryOperatorForTemporaryDtors(
 }
 
 CFGBlock *CFGBuilder::VisitCXXBindTemporaryExprForTemporaryDtors(
-    CXXBindTemporaryExpr *E, bool BindToTemporary, TempDtorContext &Context) {
+    CXXBindTemporaryExpr *E, bool ExternallyDestructed, TempDtorContext &Context) {
   // First add destructors for temporaries in subexpression.
-  CFGBlock *B = VisitForTemporaryDtors(E->getSubExpr(), false, Context);
-  if (!BindToTemporary) {
+  // Because VisitCXXBindTemporaryExpr calls setDestructed:
+  CFGBlock *B = VisitForTemporaryDtors(E->getSubExpr(), true, Context);
+  if (!ExternallyDestructed) {
     // If lifetime of temporary is not prolonged (by assigning to constant
     // reference) add destructor for it.
 
@@ -4703,7 +4742,7 @@ void CFGBuilder::InsertTempDtorDecisionBlock(const TempDtorContext &Context,
 }
 
 CFGBlock *CFGBuilder::VisitConditionalOperatorForTemporaryDtors(
-    AbstractConditionalOperator *E, bool BindToTemporary,
+    AbstractConditionalOperator *E, bool ExternallyDestructed,
     TempDtorContext &Context) {
   VisitForTemporaryDtors(E->getCond(), false, Context);
   CFGBlock *ConditionBlock = Block;
@@ -4714,14 +4753,14 @@ CFGBlock *CFGBuilder::VisitConditionalOperatorForTemporaryDtors(
 
   TempDtorContext TrueContext(
       bothKnownTrue(Context.KnownExecuted, ConditionVal));
-  VisitForTemporaryDtors(E->getTrueExpr(), BindToTemporary, TrueContext);
+  VisitForTemporaryDtors(E->getTrueExpr(), ExternallyDestructed, TrueContext);
   CFGBlock *TrueBlock = Block;
 
   Block = ConditionBlock;
   Succ = ConditionSucc;
   TempDtorContext FalseContext(
       bothKnownTrue(Context.KnownExecuted, NegatedVal));
-  VisitForTemporaryDtors(E->getFalseExpr(), BindToTemporary, FalseContext);
+  VisitForTemporaryDtors(E->getFalseExpr(), ExternallyDestructed, FalseContext);
 
   if (TrueContext.TerminatorExpr && FalseContext.TerminatorExpr) {
     InsertTempDtorDecisionBlock(FalseContext, TrueBlock);
@@ -5349,15 +5388,13 @@ static void print_elem(raw_ostream &OS, StmtPrinterHelper &Helper,
     const VarDecl *VD = DE->getVarDecl();
     Helper.handleDecl(VD, OS);
 
-    ASTContext &ACtx = VD->getASTContext();
     QualType T = VD->getType();
     if (T->isReferenceType())
       T = getReferenceInitTemporaryType(VD->getInit(), nullptr);
-    if (const ArrayType *AT = ACtx.getAsArrayType(T))
-      T = ACtx.getBaseElementType(AT);
 
-    OS << ".~" << T->getAsCXXRecordDecl()->getName().str() << "()";
-    OS << " (Implicit destructor)\n";
+    OS << ".~";
+    T.getUnqualifiedType().print(OS, PrintingPolicy(Helper.getLangOpts()));
+    OS << "() (Implicit destructor)\n";
   } else if (Optional<CFGLifetimeEnds> DE = E.getAs<CFGLifetimeEnds>()) {
     const VarDecl *VD = DE->getVarDecl();
     Helper.handleDecl(VD, OS);
index 7e678a1ec78d45f4e05d25c34fd0ca7cea3f0436..716eaf3c0fb44cbf5c8a71d25b2ef217b8d9a140 100644 (file)
@@ -334,7 +334,7 @@ void test_aggregate_array_lifetime_extension() {
 // CHECK-NEXT:    61: ~A() (Temporary object destructor)
 // CHECK-NEXT:    62: ~A() (Temporary object destructor)
 // CHECK-NEXT:    63: ~A() (Temporary object destructor)
-// CHECK-NEXT:    64: [B1.57].~D() (Implicit destructor)
+// CHECK-NEXT:    64: [B1.57].~D [2]() (Implicit destructor)
 // CHECK-NEXT:    65: [B1.18].~D() (Implicit destructor)
 // CHECK-NEXT:     Preds (1): B2
 // CHECK-NEXT:     Succs (1): B0
@@ -363,7 +363,7 @@ void test_aggregate_with_nontrivial_own_destructor() {
 // WARNINGS-NEXT:   3:  (CXXConstructExpr, class A [0])
 // ANALYZER-NEXT:   3:  (CXXConstructExpr, [B1.4], class A [0])
 // CHECK-NEXT:   4: A b[0];
-// CHECK-NEXT:   5: [B1.2].~A() (Implicit destructor)
+// CHECK-NEXT:   5: [B1.2].~A [2]() (Implicit destructor)
 // CHECK-NEXT:   Preds (1): B2
 // CHECK-NEXT:   Succs (1): B0
 // CHECK:      [B0 (EXIT)]
index cb1ed8eb6b2b3614996792057af2c1458320e99a..e33e57597d9e48eb34cfad32b46ec00d20553d76 100644 (file)
@@ -412,7 +412,6 @@ public:
   ~D();
 };
 
-// FIXME: There should be no temporary destructor in C++17.
 // CHECK:  return_stmt_with_dtor::D returnTemporary()
 // CXX11-ELIDE:          1: return_stmt_with_dtor::D() (CXXConstructExpr, [B1.2], [B1.4], [B1.5], class return_stmt_with_dtor::D)
 // CXX11-NOELIDE:          1: return_stmt_with_dtor::D() (CXXConstructExpr, [B1.2], [B1.4], class return_stmt_with_dtor::D)
@@ -422,15 +421,13 @@ public:
 // CXX11-NEXT:     5: [B1.4] (CXXConstructExpr, [B1.7], class return_stmt_with_dtor::D)
 // CXX11-NEXT:     6: ~return_stmt_with_dtor::D() (Temporary object destructor)
 // CXX11-NEXT:     7: return [B1.5];
-// CXX17:          1: return_stmt_with_dtor::D() (CXXConstructExpr, [B1.4], [B1.2], class return_stmt_w
+// CXX17:          1: return_stmt_with_dtor::D() (CXXConstructExpr, [B1.3], [B1.2], class return_stmt_w
 // CXX17-NEXT:     2: [B1.1] (BindTemporary)
-// CXX17-NEXT:     3: ~return_stmt_with_dtor::D() (Temporary object destructor)
-// CXX17-NEXT:     4: return [B1.2];
+// CXX17-NEXT:     3: return [B1.2];
 D returnTemporary() {
   return D();
 }
 
-// FIXME: There should be no temporary destructor in C++17.
 // CHECK: void returnByValueIntoVariable()
 // CHECK:          1: returnTemporary
 // CHECK-NEXT:     2: [B1.1] (ImplicitCastExpr, FunctionToPointerDecay, class return_stmt_with_dtor::D (*)(void))
@@ -442,12 +439,10 @@ D returnTemporary() {
 // CXX11-NEXT:     7: [B1.6] (CXXConstructExpr, [B1.8], class return_stmt_with_dtor::D)
 // CXX11-NEXT:     8: return_stmt_with_dtor::D d = returnTemporary();
 // CXX11-NEXT:     9: ~return_stmt_with_dtor::D() (Temporary object destructor)
-// CXX11-NEXT:    10: [B1.8].~D() (Implicit destructor)
+// CXX11-NEXT:    10: [B1.8].~return_stmt_with_dtor::D() (Implicit destructor)
 // CXX17-NEXT:     3: [B1.2]() (CXXRecordTypedCall, [B1.5], [B1.4])
 // CXX17-NEXT:     4: [B1.3] (BindTemporary)
 // CXX17-NEXT:     5: return_stmt_with_dtor::D d = returnTemporary();
-// CXX17-NEXT:     6: ~return_stmt_with_dtor::D() (Temporary object destructor)
-// CXX17-NEXT:     7: [B1.5].~D() (Implicit destructor)
 void returnByValueIntoVariable() {
   D d = returnTemporary();
 }
@@ -602,7 +597,7 @@ void temporaryInCondition() {
 // CHECK-NEXT:     3: [B1.2] (BindTemporary)
 // CHECK-NEXT:     4: [B1.3]
 // CHECK-NEXT:     5: const temporary_object_expr_with_dtors::D &d(0);
-// CHECK-NEXT:     6: [B1.5].~D() (Implicit destructor)
+// CHECK-NEXT:     6: [B1.5].~temporary_object_expr_with_dtors::D() (Implicit destructor)
 void referenceVariableWithConstructor() {
   const D &d(0);
 }
@@ -613,14 +608,14 @@ void referenceVariableWithConstructor() {
 // CHECK-NEXT:     3: [B1.2] (ImplicitCastExpr, NoOp, const class temporary_object_expr_with_dtors::D)
 // CHECK-NEXT:     4: [B1.3]
 // CHECK-NEXT:     5: const temporary_object_expr_with_dtors::D &d = temporary_object_expr_with_dtors::D();
-// CHECK-NEXT:     6: [B1.5].~D() (Implicit destructor)
+// CHECK-NEXT:     6: [B1.5].~temporary_object_expr_with_dtors::D() (Implicit destructor)
 void referenceVariableWithInitializer() {
   const D &d = D();
 }
 
 // CHECK: void referenceVariableWithTernaryOperator(bool coin)
 // CXX11:        [B1]
-// CXX11-NEXT:     1: [B4.4].~D() (Implicit destructor)
+// CXX11-NEXT:     1: [B4.4].~temporary_object_expr_with_dtors::D() (Implicit destructor)
 // CXX11:        [B2]
 // CXX11-NEXT:     1: ~temporary_object_expr_with_dtors::D() (Temporary object destructor)
 // CXX11:        [B3]
@@ -660,7 +655,7 @@ void referenceVariableWithInitializer() {
 // CXX17-NEXT:     2: [B1.1] (ImplicitCastExpr, NoOp, const class temporary_object_expr_with_dtors::D)
 // CXX17-NEXT:     3: [B1.2]
 // CXX17-NEXT:     4: const temporary_object_expr_with_dtors::D &d = coin ? D::get() : temporary_object_expr_with_dtors::D(0);
-// CXX17-NEXT:     5: [B1.4].~D() (Implicit destructor)
+// CXX17-NEXT:     5: [B1.4].~temporary_object_expr_with_dtors::D() (Implicit destructor)
 // CXX17:        [B2]
 // CXX17-NEXT:     1: D::get
 // CXX17-NEXT:     2: [B2.1] (ImplicitCastExpr, FunctionToPointerDecay, class temporary_object_expr_with_dtors::D (*)(void))
@@ -686,7 +681,7 @@ void referenceVariableWithTernaryOperator(bool coin) {
 // CHECK-NEXT:     4: temporary_object_expr_with_dtors::D([B1.3]) (CXXFunctionalCastExpr, ConstructorCon
 // CHECK-NEXT:     5: [B1.4]
 // CHECK-NEXT:     6: temporary_object_expr_with_dtors::D &&d = temporary_object_expr_with_dtors::D(1);
-// CHECK-NEXT:     7: [B1.6].~D() (Implicit destructor)
+// CHECK-NEXT:     7: [B1.6].~temporary_object_expr_with_dtors::D() (Implicit destructor)
 void referenceWithFunctionalCast() {
   D &&d = D(1);
 }
@@ -743,13 +738,13 @@ public:
 // CXX11-NEXT:     9: [B1.8] (CXXConstructExpr, [B1.10], class implicit_constructor_conversion::B)
 // CXX11-NEXT:    10: implicit_constructor_conversion::B b = implicit_constructor_conversion::A();
 // CXX11-NEXT:    11: ~implicit_constructor_conversion::B() (Temporary object destructor)
-// CXX11-NEXT:    12: [B1.10].~B() (Implicit destructor)
+// CXX11-NEXT:    12: [B1.10].~implicit_constructor_conversion::B() (Implicit destructor)
 // CXX17-NEXT:     2: [B1.1] (ImplicitCastExpr, NoOp, const class implicit_constructor_conversion::A)
 // CXX17-NEXT:     3: [B1.2]
 // CXX17-NEXT:     4: [B1.3] (CXXConstructExpr, [B1.6], class implicit_constructor_conversion::B)
 // CXX17-NEXT:     5: [B1.4] (ImplicitCastExpr, ConstructorConversion, class implicit_constructor_conversion::B)
 // CXX17-NEXT:     6: implicit_constructor_conversion::B b = implicit_constructor_conversion::A();
-// CXX17-NEXT:     7: [B1.6].~B() (Implicit destructor)
+// CXX17-NEXT:     7: [B1.6].~implicit_constructor_conversion::B() (Implicit destructor)
 void implicitConstructionConversionFromTemporary() {
   B b = A();
 }
@@ -769,11 +764,11 @@ void implicitConstructionConversionFromTemporary() {
 // CXX11-NEXT:    11: [B1.10] (CXXConstructExpr, [B1.12], class implicit_constructor_conversion::B)
 // CXX11-NEXT:    12: implicit_constructor_conversion::B b = get();
 // CXX11-NEXT:    13: ~implicit_constructor_conversion::B() (Temporary object destructor)
-// CXX11-NEXT:    14: [B1.12].~B() (Implicit destructor)
+// CXX11-NEXT:    14: [B1.12].~implicit_constructor_conversion::B() (Implicit destructor)
 // CXX17-NEXT:     6: [B1.5] (CXXConstructExpr, [B1.8], class implicit_constructor_conversion::B)
 // CXX17-NEXT:     7: [B1.6] (ImplicitCastExpr, ConstructorConversion, class implicit_constructor_conversion::B)
 // CXX17-NEXT:     8: implicit_constructor_conversion::B b = get();
-// CXX17-NEXT:     9: [B1.8].~B() (Implicit destructor)
+// CXX17-NEXT:     9: [B1.8].~implicit_constructor_conversion::B() (Implicit destructor)
 void implicitConstructionConversionFromFunctionValue() {
   B b = get();
 }
@@ -787,7 +782,7 @@ void implicitConstructionConversionFromFunctionValue() {
 // CHECK-NEXT:     6: [B1.5] (ImplicitCastExpr, NoOp, const class implicit_constructor_conversion::B)
 // CHECK-NEXT:     7: [B1.6]
 // CHECK-NEXT:     8: const implicit_constructor_conversion::B &b = implicit_constructor_conversion::A();
-// CHECK-NEXT:     9: [B1.8].~B() (Implicit destructor)
+// CHECK-NEXT:     9: [B1.8].~implicit_constructor_conversion::B() (Implicit destructor)
 void implicitConstructionConversionFromTemporaryWithLifetimeExtension() {
   const B &b = A();
 }
@@ -803,7 +798,7 @@ void implicitConstructionConversionFromTemporaryWithLifetimeExtension() {
 // CHECK-NEXT:     8: [B1.7] (ImplicitCastExpr, NoOp, const class implicit_constructor_conversion::B)
 // CHECK-NEXT:     9: [B1.8]
 // CHECK-NEXT:    10: const implicit_constructor_conversion::B &b = get();
-// CHECK-NEXT:    11: [B1.10].~B() (Implicit destructor)
+// CHECK-NEXT:    11: [B1.10].~implicit_constructor_conversion::B() (Implicit destructor)
 void implicitConstructionConversionFromFunctionValueWithLifetimeExtension() {
   const B &b = get(); // no-crash
 }
index 289094293eb5cf8c08c46963a1a7a8e9d39aeb4a..e55928d4c51fa72d21a1ef1a703d4f982df1561b 100644 (file)
@@ -59,8 +59,7 @@ void passArgumentIntoMessage(E *e) {
 // CXX17-NEXT:     3: {{\[}}[B1.2] bar] (CXXRecordTypedCall, [B1.5], [B1.4])
 // CXX17-NEXT:     4: [B1.3] (BindTemporary)
 // CXX17-NEXT:     5: D d = [e bar];
-// CXX17-NEXT:     6: ~D() (Temporary object destructor)
-// CXX17-NEXT:     7: [B1.5].~D() (Implicit destructor)
+// CXX17-NEXT:     6: [B1.5].~D() (Implicit destructor)
 void returnObjectFromMessage(E *e) {
   D d = [e bar];
 }
index ea028e06f347167acbe15ea56a487ce21a785cec..74f4d50f25dfc26c5894dee2140162889f1e1dea 100644 (file)
@@ -203,7 +203,7 @@ namespace NoReturnSingleSuccessor {
 // CHECK-LABEL: int test1(int *x)
 // CHECK: 1: 1
 // CHECK-NEXT: 2: return
-// CHECK-NEXT: ~B() (Implicit destructor)
+// CHECK-NEXT: ~NoReturnSingleSuccessor::B() (Implicit destructor)
 // CHECK-NEXT: Preds (1)
 // CHECK-NEXT: Succs (1): B0
   int test1(int *x) {
@@ -477,7 +477,7 @@ void test_lifetime_extended_temporaries() {
 // WARNINGS-NEXT:    1:  (CXXConstructExpr, struct pr37688_deleted_union_destructor::A)
 // ANALYZER-NEXT:    1:  (CXXConstructExpr, [B1.2], struct pr37688_deleted_union_destructor::A)
 // CHECK-NEXT:    2: pr37688_deleted_union_destructor::A a;
-// CHECK-NEXT:    3: [B1.2].~A() (Implicit destructor)
+// CHECK-NEXT:    3: [B1.2].~pr37688_deleted_union_destructor::A() (Implicit destructor)
 // CHECK-NEXT:    Preds (1): B2
 // CHECK-NEXT:    Succs (1): B0
 // CHECK:  [B0 (EXIT)]
index 7be4e2ddf3007bae8d3b1d36f7bef010ba14e4c7..18204929bcbe0a69d989eb79a533b4451a20a838 100644 (file)
@@ -31,7 +31,7 @@ class B {
 // CHECK-NEXT:    8: [B1.7]
 // CHECK-NEXT:    9: [B1.5] = [B1.8] (OperatorCall)
 // CHECK-NEXT:   10: ~variant_0::B() (Temporary object destructor)
-// CHECK-NEXT:   11: [B1.2].~B() (Implicit destructor)
+// CHECK-NEXT:   11: [B1.2].~variant_0::B() (Implicit destructor)
 void foo(int) {
   B i;
   i = {};
@@ -71,7 +71,7 @@ class B {
 // CHECK-NEXT:    6: {} (CXXConstructExpr, class variant_1::B)
 // CHECK-NEXT:    7: [B1.6]
 // CHECK-NEXT:    8: [B1.5] = [B1.7] (OperatorCall)
-// CHECK-NEXT:    9: [B1.2].~B() (Implicit destructor)
+// CHECK-NEXT:    9: [B1.2].~variant_1::B() (Implicit destructor)
 template <typename T> void foo(T) {
   B i;
   i = {};
@@ -114,7 +114,7 @@ public:
 // CHECK-NEXT:    9: [B1.8]
 // CHECK-NEXT:   10: [B1.5] = [B1.9] (OperatorCall)
 // CHECK-NEXT:   11: ~variant_2::B() (Temporary object destructor)
-// CHECK-NEXT:   12: [B1.2].~B() (Implicit destructor)
+// CHECK-NEXT:   12: [B1.2].~variant_2::B() (Implicit destructor)
 template <typename T> void foo(T) {
   B i;
   i = {};
diff --git a/test/Analysis/more-dtors-cfg-output.cpp b/test/Analysis/more-dtors-cfg-output.cpp
new file mode 100644 (file)
index 0000000..c0df595
--- /dev/null
@@ -0,0 +1,317 @@
+// RUN: rm -f %t.14 %t.2a
+// RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpCFG -std=c++14 -DCXX2A=0 -fblocks -Wall -Wno-unused -Werror %s > %t.14 2>&1
+// RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpCFG -std=c++2a -DCXX2A=1 -fblocks -Wall -Wno-unused -Werror %s > %t.2a 2>&1
+// RUN: FileCheck --input-file=%t.14 -check-prefixes=CHECK,CXX14 -implicit-check-not=destructor %s
+// RUN: FileCheck --input-file=%t.2a -check-prefixes=CHECK,CXX2A -implicit-check-not=destructor %s
+
+int puts(const char *);
+
+struct Foo {
+  Foo() = delete;
+#if CXX2A
+  // Guarantee that the elided examples are actually elided by deleting the
+  // copy constructor.
+  Foo(const Foo &) = delete;
+#else
+  // No elision support, so we need a copy constructor.
+  Foo(const Foo &);
+#endif
+  ~Foo();
+};
+
+struct TwoFoos {
+  Foo foo1, foo2;
+  ~TwoFoos();
+};
+
+Foo get_foo();
+
+struct Bar {
+  Bar();
+  Bar(const Bar &);
+  ~Bar();
+  Bar &operator=(const Bar &);
+};
+
+Bar get_bar();
+
+struct TwoBars {
+  Bar foo1, foo2;
+  ~TwoBars();
+};
+
+// Start of tests:
+
+void elided_assign() {
+  Foo x = get_foo();
+}
+// CHECK: void elided_assign()
+// CXX14: (CXXConstructExpr{{.*}}, struct Foo)
+// CXX14: ~Foo() (Temporary object destructor)
+// CHECK: ~Foo() (Implicit destructor)
+
+void nonelided_assign() {
+  Bar x = (const Bar &)get_bar();
+}
+// CHECK: void nonelided_assign()
+// CHECK: (CXXConstructExpr{{.*}}, struct Bar)
+// CHECK: ~Bar() (Temporary object destructor)
+// CHECK: ~Bar() (Implicit destructor)
+
+void elided_paren_init() {
+  Foo x(get_foo());
+}
+// CHECK: void elided_paren_init()
+// CXX14: (CXXConstructExpr{{.*}}, struct Foo)
+// CXX14: ~Foo() (Temporary object destructor)
+// CHECK: ~Foo() (Implicit destructor)
+
+void nonelided_paren_init() {
+  Bar x((const Bar &)get_bar());
+}
+// CHECK: void nonelided_paren_init()
+// CHECK: (CXXConstructExpr{{.*}}, struct Bar)
+// CHECK: ~Bar() (Temporary object destructor)
+// CHECK: ~Bar() (Implicit destructor)
+
+void elided_brace_init() {
+  Foo x{get_foo()};
+}
+// CHECK: void elided_brace_init()
+// CXX14: (CXXConstructExpr{{.*}}, struct Foo)
+// CXX14: ~Foo() (Temporary object destructor)
+// CHECK: ~Foo() (Implicit destructor)
+
+void nonelided_brace_init() {
+  Bar x{(const Bar &)get_bar()};
+}
+// CHECK: void nonelided_brace_init()
+// CHECK: (CXXConstructExpr{{.*}}, struct Bar)
+// CHECK: ~Bar() (Temporary object destructor)
+// CHECK: ~Bar() (Implicit destructor)
+
+void elided_lambda_capture_init() {
+  // The copy from get_foo() into the lambda should be elided.  Should call
+  // the lambda's destructor, but not ~Foo() separately.
+  // (This syntax is C++14 'generalized lambda captures'.)
+  auto z = [x=get_foo()]() {};
+}
+// CHECK: void elided_lambda_capture_init()
+// CXX14: (CXXConstructExpr{{.*}}, struct Foo)
+// CXX14: ~(lambda at {{.*}})() (Temporary object destructor)
+// CXX14: ~Foo() (Temporary object destructor)
+// CHECK: ~(lambda at {{.*}})() (Implicit destructor)
+
+void nonelided_lambda_capture_init() {
+  // Should call the lambda's destructor as well as ~Bar() for the temporary.
+  auto z = [x((const Bar &)get_bar())]() {};
+}
+// CHECK: void nonelided_lambda_capture_init()
+// CHECK: (CXXConstructExpr{{.*}}, struct Bar)
+// CXX14: ~(lambda at {{.*}})() (Temporary object destructor)
+// CHECK: ~Bar() (Temporary object destructor)
+// CHECK: ~(lambda at {{.*}})() (Implicit destructor)
+
+Foo elided_return_stmt_expr() {
+  // Two copies, both elided in C++17.
+  return ({ get_foo(); });
+}
+// CHECK: Foo elided_return_stmt_expr()
+// CXX14: (CXXConstructExpr{{.*}}, struct Foo)
+// CXX14: ~Foo() (Temporary object destructor)
+// CXX14: (CXXConstructExpr{{.*}}, struct Foo)
+// CXX14: ~Foo() (Temporary object destructor)
+
+void elided_stmt_expr() {
+  // One copy, elided in C++17.
+  ({ get_foo(); });
+}
+// CHECK: void elided_stmt_expr()
+// CXX14: (CXXConstructExpr{{.*}}, struct Foo)
+// CXX14: ~Foo() (Temporary object destructor)
+// CHECK: ~Foo() (Temporary object destructor)
+
+
+void elided_stmt_expr_multiple_stmts() {
+  // Make sure that only the value returned out of a statement expression is
+  // elided.
+  ({ get_bar(); get_foo(); });
+}
+// CHECK: void elided_stmt_expr_multiple_stmts()
+// CHECK: ~Bar() (Temporary object destructor)
+// CXX14: (CXXConstructExpr{{.*}}, struct Foo)
+// CXX14: ~Foo() (Temporary object destructor)
+// CHECK: ~Foo() (Temporary object destructor)
+
+
+void unelided_stmt_expr() {
+  ({ (const Bar &)get_bar(); });
+}
+// CHECK: void unelided_stmt_expr()
+// CHECK: (CXXConstructExpr{{.*}}, struct Bar)
+// CHECK: ~Bar() (Temporary object destructor)
+// CHECK: ~Bar() (Temporary object destructor)
+
+void elided_aggregate_init() {
+  TwoFoos x{get_foo(), get_foo()};
+}
+// CHECK: void elided_aggregate_init()
+// CXX14: (CXXConstructExpr{{.*}}, struct Foo)
+// CXX14: (CXXConstructExpr{{.*}}, struct Foo)
+// CXX14: ~Foo() (Temporary object destructor)
+// CXX14: ~Foo() (Temporary object destructor)
+// CHECK: ~TwoFoos() (Implicit destructor)
+
+void nonelided_aggregate_init() {
+  TwoBars x{(const Bar &)get_bar(), (const Bar &)get_bar()};
+}
+// CHECK: void nonelided_aggregate_init()
+// CHECK: (CXXConstructExpr{{.*}}, struct Bar)
+// CHECK: (CXXConstructExpr{{.*}}, struct Bar)
+// CHECK: ~Bar() (Temporary object destructor)
+// CHECK: ~Bar() (Temporary object destructor)
+// CHECK: ~TwoBars() (Implicit destructor)
+
+TwoFoos return_aggregate_init() {
+  return TwoFoos{get_foo(), get_foo()};
+}
+// CHECK: TwoFoos return_aggregate_init()
+// CXX14: (CXXConstructExpr{{.*}}, struct Foo)
+// CXX14: (CXXConstructExpr{{.*}}, struct Foo)
+// CXX14: ~TwoFoos() (Temporary object destructor)
+// CXX14: ~Foo() (Temporary object destructor)
+// CXX14: ~Foo() (Temporary object destructor)
+
+void lifetime_extended() {
+  const Foo &x = (get_foo(), get_foo());
+  puts("one destroyed before, one destroyed after");
+}
+// CHECK: void lifetime_extended()
+// CHECK: ~Foo() (Temporary object destructor)
+// CHECK: one destroyed before, one destroyed after
+// CHECK: ~Foo() (Implicit destructor)
+
+void not_lifetime_extended() {
+  Foo x = (get_foo(), get_foo());
+  puts("one destroyed before, one destroyed after");
+}
+// CHECK: void not_lifetime_extended()
+// CXX14: (CXXConstructExpr{{.*}}, struct Foo)
+// CHECK: ~Foo() (Temporary object destructor)
+// CXX14: ~Foo() (Temporary object destructor)
+// CHECK: one destroyed before, one destroyed after
+// CHECK: ~Foo() (Implicit destructor)
+
+void compound_literal() {
+  (void)(Bar[]){{}, {}};
+}
+// CHECK: void compound_literal()
+// CHECK: (CXXConstructExpr, struct Bar)
+// CHECK: (CXXConstructExpr, struct Bar)
+// CHECK: ~Bar [2]() (Temporary object destructor)
+
+Foo elided_return() {
+  return get_foo();
+}
+// CHECK: Foo elided_return()
+// CXX14: (CXXConstructExpr{{.*}}, struct Foo)
+// CXX14: ~Foo() (Temporary object destructor)
+
+auto elided_return_lambda() {
+  return [x=get_foo()]() {};
+}
+// CHECK: (lambda at {{.*}}) elided_return_lambda()
+// CXX14: (CXXConstructExpr{{.*}}, class (lambda at {{.*}}))
+// CXX14: ~(lambda at {{.*}})() (Temporary object destructor)
+// CXX14: ~Foo() (Temporary object destructor)
+
+void const_auto_obj() {
+  const Bar bar;
+}
+// CHECK: void const_auto_obj()
+// CHECK: .~Bar() (Implicit destructor)
+
+void has_default_arg(Foo foo = get_foo());
+void test_default_arg() {
+  // FIXME: This emits a destructor but no constructor.  Search CFG.cpp for
+  // 'PR13385' for details.
+  has_default_arg();
+}
+// CHECK: void test_default_arg()
+// CXX14: ~Foo() (Temporary object destructor)
+// CHECK: ~Foo() (Temporary object destructor)
+
+struct DefaultArgInCtor {
+    DefaultArgInCtor(Foo foo = get_foo());
+    ~DefaultArgInCtor();
+};
+
+void default_ctor_with_default_arg() {
+  // FIXME: Default arguments are mishandled in two ways:
+  // - The constructor is not emitted at all (not specific to arrays; see fixme
+  //   in CFG.cpp that mentions PR13385).
+  // - The destructor is emitted once, even though the default argument will be
+  //   constructed and destructed once per array element.
+  // Ideally, the CFG would expand array constructions into a loop that
+  // constructs each array element, allowing default argument
+  // constructor/destructor calls to be correctly placed inside the loop.
+  DefaultArgInCtor qux[3];
+}
+// CHECK: void default_ctor_with_default_arg()
+// CHECK: CXXConstructExpr, {{.*}}, struct DefaultArgInCtor [3]
+// CXX14: ~Foo() (Temporary object destructor)
+// CHECK: ~Foo() (Temporary object destructor)
+// CHECK: .~DefaultArgInCtor [3]() (Implicit destructor)
+
+void new_default_ctor_with_default_arg(long count) {
+  // Same problems as above.
+  new DefaultArgInCtor[count];
+}
+// CHECK: void new_default_ctor_with_default_arg(long count)
+// CHECK: CXXConstructExpr, {{.*}}, struct DefaultArgInCtor []
+// CXX14: ~Foo() (Temporary object destructor)
+// CHECK: ~Foo() (Temporary object destructor)
+
+#if CXX2A
+// Boilerplate needed to test co_return:
+
+namespace std::experimental {
+  template <typename Promise>
+  struct coroutine_handle {
+    static coroutine_handle from_address(void *);
+  };
+}
+
+struct TestPromise {
+  TestPromise initial_suspend();
+  TestPromise final_suspend();
+  bool await_ready();
+  void await_suspend(const std::experimental::coroutine_handle<TestPromise> &);
+  void await_resume();
+  Foo return_value(const Bar &);
+  Bar get_return_object();
+  void unhandled_exception();
+};
+
+namespace std::experimental {
+  template <typename Ret, typename... Args>
+  struct coroutine_traits;
+  template <>
+  struct coroutine_traits<Bar> {
+      using promise_type = TestPromise;
+  };
+}
+
+Bar coreturn() {
+  co_return get_bar();
+  // This expands to something like:
+  //     promise.return_value(get_bar());
+  // get_bar() is passed by reference to return_value() and is then destroyed;
+  // there is no equivalent of RVO.  TestPromise::return_value also returns a
+  // Foo, which should be immediately destroyed.
+  // FIXME: The generated CFG completely ignores get_return_object().
+}
+// CXX2A: Bar coreturn()
+// CXX2A: ~Foo() (Temporary object destructor)
+// CXX2A: ~Bar() (Temporary object destructor)
+#endif
index f8d84b60deb1dd5cd9af8f9c26ee5aff20d15010..4b6e2a92c53add0e3285d4ea1a1c9e8cf3ccd789 100644 (file)
@@ -38,7 +38,7 @@ extern const bool UV;
 // CHECK-NEXT:   3: A a[2];
 // CHECK-NEXT:   4:  (CXXConstructExpr, [B1.5], class A [0])
 // CHECK-NEXT:   5: A b[0];
-// CHECK-NEXT:   6: [B1.3].~A() (Implicit destructor)
+// CHECK-NEXT:   6: [B1.3].~A [2]() (Implicit destructor)
 // CHECK-NEXT:   7: CFGScopeEnd(a)
 // CHECK-NEXT:   Preds (1): B2
 // CHECK-NEXT:   Succs (1): B0
@@ -810,7 +810,7 @@ void test_for_compound_and_break() {
 // CHECK-NEXT:   1: CFGScopeEnd(__end1)
 // CHECK-NEXT:   2: CFGScopeEnd(__begin1)
 // CHECK-NEXT:   3: CFGScopeEnd(__range1)
-// CHECK-NEXT:   4: [B5.3].~A() (Implicit destructor)
+// CHECK-NEXT:   4: [B5.3].~A [10]() (Implicit destructor)
 // CHECK-NEXT:   5: CFGScopeEnd(a)
 // CHECK-NEXT:   Preds (1): B2
 // CHECK-NEXT:   Succs (1): B0
index 6191abfb4d2eaeac9e1f01ced9e8e2c46512ce1f..012cef52f14e3ce52cb9c2aceb0071b48aa65720 100644 (file)
@@ -830,12 +830,7 @@ void test_ternary_temporary_with_copy(int coin) {
   // On each branch the variable is constructed directly.
   if (coin) {
     clang_analyzer_eval(x == 1); // expected-warning{{TRUE}}
-#if __cplusplus < 201703L
     clang_analyzer_eval(y == 1); // expected-warning{{TRUE}}
-#else
-    // FIXME: Destructor called twice in C++17?
-    clang_analyzer_eval(y == 2); // expected-warning{{TRUE}}
-#endif
     clang_analyzer_eval(z == 0); // expected-warning{{TRUE}}
     clang_analyzer_eval(w == 0); // expected-warning{{TRUE}}
 
@@ -843,12 +838,7 @@ void test_ternary_temporary_with_copy(int coin) {
     clang_analyzer_eval(x == 0); // expected-warning{{TRUE}}
     clang_analyzer_eval(y == 0); // expected-warning{{TRUE}}
     clang_analyzer_eval(z == 1); // expected-warning{{TRUE}}
-#if __cplusplus < 201703L
     clang_analyzer_eval(w == 1); // expected-warning{{TRUE}}
-#else
-    // FIXME: Destructor called twice in C++17?
-    clang_analyzer_eval(w == 2); // expected-warning{{TRUE}}
-#endif
   }
 }
 } // namespace test_match_constructors_and_destructors
@@ -1055,16 +1045,11 @@ void foo(void (*bar4)(S)) {
 #endif
 
   bar2(S(2));
-  // FIXME: Why are we losing information in C++17?
   clang_analyzer_eval(glob == 2);
 #ifdef TEMPORARY_DTORS
-#if __cplusplus < 201703L
-  // expected-warning@-3{{TRUE}}
-#else
-  // expected-warning@-5{{UNKNOWN}}
-#endif
+  // expected-warning@-2{{TRUE}}
 #else
-  // expected-warning@-8{{UNKNOWN}}
+  // expected-warning@-4{{UNKNOWN}}
 #endif
 
   C *c = new D();