r370247 - [CFG] Make representation of destructor calls more accurate.

Artem Dergachev via cfe-commits cfe-commits at lists.llvm.org
Wed Aug 28 11:44:42 PDT 2019


Author: dergachev
Date: Wed Aug 28 11:44:42 2019
New Revision: 370247

URL: http://llvm.org/viewvc/llvm-project?rev=370247&view=rev
Log:
[CFG] Make representation of destructor calls more accurate.

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

Added:
    cfe/trunk/test/Analysis/more-dtors-cfg-output.cpp
Modified:
    cfe/trunk/lib/Analysis/CFG.cpp
    cfe/trunk/test/Analysis/auto-obj-dtors-cfg-output.cpp
    cfe/trunk/test/Analysis/cfg-rich-constructors.cpp
    cfe/trunk/test/Analysis/cfg-rich-constructors.mm
    cfe/trunk/test/Analysis/cfg.cpp
    cfe/trunk/test/Analysis/missing-bind-temporary.cpp
    cfe/trunk/test/Analysis/scopes-cfg-output.cpp
    cfe/trunk/test/Analysis/temporaries.cpp

Modified: cfe/trunk/lib/Analysis/CFG.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Analysis/CFG.cpp?rev=370247&r1=370246&r2=370247&view=diff
==============================================================================
--- cfe/trunk/lib/Analysis/CFG.cpp (original)
+++ cfe/trunk/lib/Analysis/CFG.cpp Wed Aug 28 11:44:42 2019
@@ -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(CXX
       // 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::prependAutomaticObjScopeEndW
 /// 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, Ad
       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, Ad
       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(Ch
   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(
        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(D
       // 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(St
   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
 }
 
 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(Stmt
     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::VisitCXXForRangeSt
 }
 
 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::VisitIndirectGotoS
   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::VisitChildrenForTe
   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::VisitBinaryOperato
 }
 
 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::InsertTempDtorDecisionB
 }
 
 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::VisitConditionalOp
 
   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,
     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);

Modified: cfe/trunk/test/Analysis/auto-obj-dtors-cfg-output.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Analysis/auto-obj-dtors-cfg-output.cpp?rev=370247&r1=370246&r2=370247&view=diff
==============================================================================
--- cfe/trunk/test/Analysis/auto-obj-dtors-cfg-output.cpp (original)
+++ cfe/trunk/test/Analysis/auto-obj-dtors-cfg-output.cpp Wed Aug 28 11:44:42 2019
@@ -334,7 +334,7 @@ void test_aggregate_array_lifetime_exten
 // 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_
 // 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)]

Modified: cfe/trunk/test/Analysis/cfg-rich-constructors.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Analysis/cfg-rich-constructors.cpp?rev=370247&r1=370246&r2=370247&view=diff
==============================================================================
--- cfe/trunk/test/Analysis/cfg-rich-constructors.cpp (original)
+++ cfe/trunk/test/Analysis/cfg-rich-constructors.cpp Wed Aug 28 11:44:42 2019
@@ -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 referenceVariableWithTernaryOperato
 // 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 implicitConstructionConversionFromT
 // 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 implicitConstructionConversionFromF
 // 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 implicitConstructionConversionFromT
 // 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
 }

Modified: cfe/trunk/test/Analysis/cfg-rich-constructors.mm
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Analysis/cfg-rich-constructors.mm?rev=370247&r1=370246&r2=370247&view=diff
==============================================================================
--- cfe/trunk/test/Analysis/cfg-rich-constructors.mm (original)
+++ cfe/trunk/test/Analysis/cfg-rich-constructors.mm Wed Aug 28 11:44:42 2019
@@ -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];
 }

Modified: cfe/trunk/test/Analysis/cfg.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Analysis/cfg.cpp?rev=370247&r1=370246&r2=370247&view=diff
==============================================================================
--- cfe/trunk/test/Analysis/cfg.cpp (original)
+++ cfe/trunk/test/Analysis/cfg.cpp Wed Aug 28 11:44:42 2019
@@ -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)]

Modified: cfe/trunk/test/Analysis/missing-bind-temporary.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Analysis/missing-bind-temporary.cpp?rev=370247&r1=370246&r2=370247&view=diff
==============================================================================
--- cfe/trunk/test/Analysis/missing-bind-temporary.cpp (original)
+++ cfe/trunk/test/Analysis/missing-bind-temporary.cpp Wed Aug 28 11:44:42 2019
@@ -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 = {};

Added: cfe/trunk/test/Analysis/more-dtors-cfg-output.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Analysis/more-dtors-cfg-output.cpp?rev=370247&view=auto
==============================================================================
--- cfe/trunk/test/Analysis/more-dtors-cfg-output.cpp (added)
+++ cfe/trunk/test/Analysis/more-dtors-cfg-output.cpp Wed Aug 28 11:44:42 2019
@@ -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

Modified: cfe/trunk/test/Analysis/scopes-cfg-output.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Analysis/scopes-cfg-output.cpp?rev=370247&r1=370246&r2=370247&view=diff
==============================================================================
--- cfe/trunk/test/Analysis/scopes-cfg-output.cpp (original)
+++ cfe/trunk/test/Analysis/scopes-cfg-output.cpp Wed Aug 28 11:44:42 2019
@@ -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

Modified: cfe/trunk/test/Analysis/temporaries.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Analysis/temporaries.cpp?rev=370247&r1=370246&r2=370247&view=diff
==============================================================================
--- cfe/trunk/test/Analysis/temporaries.cpp (original)
+++ cfe/trunk/test/Analysis/temporaries.cpp Wed Aug 28 11:44:42 2019
@@ -830,12 +830,7 @@ void test_ternary_temporary_with_copy(in
   // 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(in
     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 at -3{{TRUE}}
-#else
-  // expected-warning at -5{{UNKNOWN}}
-#endif
+  // expected-warning at -2{{TRUE}}
 #else
-  // expected-warning at -8{{UNKNOWN}}
+  // expected-warning at -4{{UNKNOWN}}
 #endif
 
   C *c = new D();




More information about the cfe-commits mailing list