Index: clang/lib/Analysis/CFG.cpp =================================================================== --- clang/lib/Analysis/CFG.cpp +++ clang/lib/Analysis/CFG.cpp @@ -525,7 +525,7 @@ 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 @@ 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 @@ 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 @@ // 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 @@ // Generate destructors for temporaries in initialization expression. TempDtorContext Context; VisitForTemporaryDtors(cast(Init)->getSubExpr(), - /*BindToTemporary=*/false, Context); + /*ExternallyDestructed=*/false, Context); } } @@ -2051,7 +2055,8 @@ /// 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 @@ return VisitChooseExpr(cast(S), asc); case Stmt::CompoundStmtClass: - return VisitCompoundStmt(cast(S)); + return VisitCompoundStmt(cast(S), ExternallyDestructed); case Stmt::ConditionalOperatorClass: return VisitConditionalOperator(cast(S), asc); @@ -2108,7 +2113,8 @@ return VisitCXXCatchStmt(cast(S)); case Stmt::ExprWithCleanupsClass: - return VisitExprWithCleanups(cast(S), asc); + return VisitExprWithCleanups(cast(S), + asc, ExternallyDestructed); case Stmt::CXXDefaultArgExprClass: case Stmt::CXXDefaultInitExprClass: @@ -2603,7 +2609,7 @@ 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 @@ 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 @@ // Generate destructors for temporaries in initialization expression. TempDtorContext Context; VisitForTemporaryDtors(cast(Init)->getSubExpr(), - /*BindToTemporary=*/false, Context); + /*ExternallyDestructed=*/true, Context); } } @@ -2980,9 +2991,18 @@ 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(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::VisitSEHFinallyStmt(SEHFinallyStmt *FS) { - return VisitCompoundStmt(FS->getBlock()); + return VisitCompoundStmt(FS->getBlock(), /*ExternallyDestructed=*/false); } CFGBlock *CFGBuilder::VisitSEHLeaveStmt(SEHLeaveStmt *LS) { @@ -3898,7 +3918,7 @@ 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::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 @@ 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 @@ } 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(E), + ExternallyDestructed, Context); case Stmt::CXXBindTemporaryExprClass: return VisitCXXBindTemporaryExprForTemporaryDtors( - cast(E), BindToTemporary, Context); + cast(E), ExternallyDestructed, Context); case Stmt::BinaryConditionalOperatorClass: case Stmt::ConditionalOperatorClass: return VisitConditionalOperatorForTemporaryDtors( - cast(E), BindToTemporary, Context); + cast(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(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(E)->getSubExpr(); goto tryAgain; @@ -4550,7 +4574,7 @@ case Stmt::MaterializeTemporaryExprClass: { const MaterializeTemporaryExpr* MTE = cast(E); - BindToTemporary = (MTE->getStorageDuration() != SD_FullExpression); + ExternallyDestructed = (MTE->getStorageDuration() != SD_FullExpression); SmallVector CommaLHSs; SmallVector Adjustments; // Find the expression whose lifetime needs to be extended. @@ -4561,7 +4585,7 @@ // Visit the skipped comma operator left-hand sides for other temporaries. for (const Expr *CommaLHS : CommaLHSs) { VisitForTemporaryDtors(const_cast(CommaLHS), - /*BindToTemporary=*/false, Context); + /*ExternallyDestructed=*/false, Context); } goto tryAgain; } @@ -4579,13 +4603,18 @@ 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(E)->getExpr(); goto tryAgain; @@ -4597,6 +4626,7 @@ } CFGBlock *CFGBuilder::VisitChildrenForTemporaryDtors(Stmt *E, + bool ExternallyDestructed, TempDtorContext &Context) { if (isa(E)) { // Do not visit the children of lambdas; they have their own CFGs. @@ -4610,14 +4640,22 @@ 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::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 @@ } 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 @@ 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 @@ 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 DE = E.getAs()) { const VarDecl *VD = DE->getVarDecl(); Helper.handleDecl(VD, OS); Index: clang/test/Analysis/auto-obj-dtors-cfg-output.cpp =================================================================== --- clang/test/Analysis/auto-obj-dtors-cfg-output.cpp +++ clang/test/Analysis/auto-obj-dtors-cfg-output.cpp @@ -334,7 +334,7 @@ // 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 @@ // 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: clang/test/Analysis/cfg-rich-constructors.cpp =================================================================== --- clang/test/Analysis/cfg-rich-constructors.cpp +++ clang/test/Analysis/cfg-rich-constructors.cpp @@ -412,7 +412,6 @@ ~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 @@ // 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 @@ // 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 @@ // 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 @@ // 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 @@ // 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 @@ // 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 @@ // 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 @@ // 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 @@ // 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 @@ // 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: clang/test/Analysis/cfg-rich-constructors.mm =================================================================== --- clang/test/Analysis/cfg-rich-constructors.mm +++ clang/test/Analysis/cfg-rich-constructors.mm @@ -59,8 +59,7 @@ // 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: clang/test/Analysis/cfg.cpp =================================================================== --- clang/test/Analysis/cfg.cpp +++ clang/test/Analysis/cfg.cpp @@ -203,7 +203,7 @@ // 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 @@ // 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: clang/test/Analysis/missing-bind-temporary.cpp =================================================================== --- clang/test/Analysis/missing-bind-temporary.cpp +++ clang/test/Analysis/missing-bind-temporary.cpp @@ -31,7 +31,7 @@ // 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 @@ // 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 void foo(T) { B i; i = {}; @@ -114,7 +114,7 @@ // 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 void foo(T) { B i; i = {}; Index: clang/test/Analysis/more-dtors-cfg-output.cpp =================================================================== --- /dev/null +++ clang/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 + 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 &); + void await_resume(); + Foo return_value(const Bar &); + Bar get_return_object(); + void unhandled_exception(); +}; + +namespace std::experimental { + template + struct coroutine_traits; + template <> + struct coroutine_traits { + 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: clang/test/Analysis/scopes-cfg-output.cpp =================================================================== --- clang/test/Analysis/scopes-cfg-output.cpp +++ clang/test/Analysis/scopes-cfg-output.cpp @@ -38,7 +38,7 @@ // 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 @@ // 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: clang/test/Analysis/temporaries.cpp =================================================================== --- clang/test/Analysis/temporaries.cpp +++ clang/test/Analysis/temporaries.cpp @@ -830,12 +830,7 @@ // 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 @@ 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 @@ #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();