summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArtem Dergachev <artem.dergachev@gmail.com>2019-08-28 18:44:42 +0000
committerArtem Dergachev <artem.dergachev@gmail.com>2019-08-28 18:44:42 +0000
commit4c11f17103c599c2af4fffcd5d670e495119cc0c (patch)
treeb29200c0cd7d25c173f27ffe3cdb9753656b5b53
parent8c28a172b77676d2c7fc957ec2761e8da8a3e318 (diff)
downloadclang-4c11f17103c599c2af4fffcd5d670e495119cc0c.tar.gz
[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 git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@370247 91177308-0d34-0410-b5e6-96231b3b80d8
-rw-r--r--lib/Analysis/CFG.cpp123
-rw-r--r--test/Analysis/auto-obj-dtors-cfg-output.cpp4
-rw-r--r--test/Analysis/cfg-rich-constructors.cpp33
-rw-r--r--test/Analysis/cfg-rich-constructors.mm3
-rw-r--r--test/Analysis/cfg.cpp4
-rw-r--r--test/Analysis/missing-bind-temporary.cpp6
-rw-r--r--test/Analysis/more-dtors-cfg-output.cpp317
-rw-r--r--test/Analysis/scopes-cfg-output.cpp4
-rw-r--r--test/Analysis/temporaries.cpp19
9 files changed, 423 insertions, 90 deletions
diff --git a/lib/Analysis/CFG.cpp b/lib/Analysis/CFG.cpp
index fb643464fa..33c47b24a9 100644
--- a/lib/Analysis/CFG.cpp
+++ b/lib/Analysis/CFG.cpp
@@ -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);
diff --git a/test/Analysis/auto-obj-dtors-cfg-output.cpp b/test/Analysis/auto-obj-dtors-cfg-output.cpp
index 7e678a1ec7..716eaf3c0f 100644
--- a/test/Analysis/auto-obj-dtors-cfg-output.cpp
+++ b/test/Analysis/auto-obj-dtors-cfg-output.cpp
@@ -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)]
diff --git a/test/Analysis/cfg-rich-constructors.cpp b/test/Analysis/cfg-rich-constructors.cpp
index cb1ed8eb6b..e33e57597d 100644
--- a/test/Analysis/cfg-rich-constructors.cpp
+++ b/test/Analysis/cfg-rich-constructors.cpp
@@ -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
}
diff --git a/test/Analysis/cfg-rich-constructors.mm b/test/Analysis/cfg-rich-constructors.mm
index 289094293e..e55928d4c5 100644
--- a/test/Analysis/cfg-rich-constructors.mm
+++ b/test/Analysis/cfg-rich-constructors.mm
@@ -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];
}
diff --git a/test/Analysis/cfg.cpp b/test/Analysis/cfg.cpp
index ea028e06f3..74f4d50f25 100644
--- a/test/Analysis/cfg.cpp
+++ b/test/Analysis/cfg.cpp
@@ -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)]
diff --git a/test/Analysis/missing-bind-temporary.cpp b/test/Analysis/missing-bind-temporary.cpp
index 7be4e2ddf3..18204929bc 100644
--- a/test/Analysis/missing-bind-temporary.cpp
+++ b/test/Analysis/missing-bind-temporary.cpp
@@ -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
index 0000000000..c0df5953aa
--- /dev/null
+++ b/test/Analysis/more-dtors-cfg-output.cpp
@@ -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
diff --git a/test/Analysis/scopes-cfg-output.cpp b/test/Analysis/scopes-cfg-output.cpp
index f8d84b60de..4b6e2a92c5 100644
--- a/test/Analysis/scopes-cfg-output.cpp
+++ b/test/Analysis/scopes-cfg-output.cpp
@@ -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
diff --git a/test/Analysis/temporaries.cpp b/test/Analysis/temporaries.cpp
index 6191abfb4d..012cef52f1 100644
--- a/test/Analysis/temporaries.cpp
+++ b/test/Analysis/temporaries.cpp
@@ -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();