Index: include/clang/Analysis/CFG.h =================================================================== --- include/clang/Analysis/CFG.h +++ include/clang/Analysis/CFG.h @@ -189,8 +189,10 @@ // into the constructor. An assertion would require passing an ASTContext // which would mean paying for something we don't use. assert(C && (isa(C) || + // These are possible in C++17 due to mandatory copy elision. isa(C) || - isa(C))); + isa(C) || + isa(C))); Data2.setPointer(const_cast(C)); } Index: include/clang/Analysis/ConstructionContext.h =================================================================== --- include/clang/Analysis/ConstructionContext.h +++ include/clang/Analysis/ConstructionContext.h @@ -102,7 +102,10 @@ CXX17ElidedCopyVariableKind, VARIABLE_BEGIN = SimpleVariableKind, VARIABLE_END = CXX17ElidedCopyVariableKind, - ConstructorInitializerKind, + SimpleConstructorInitializerKind, + CXX17ElidedCopyConstructorInitializerKind, + INITIALIZER_BEGIN = SimpleConstructorInitializerKind, + INITIALIZER_END = CXX17ElidedCopyConstructorInitializerKind, NewAllocatedObjectKind, TemporaryObjectKind, SimpleReturnedValueKind, @@ -202,17 +205,15 @@ } }; -/// Represents construction into a field or a base class within a bigger object -/// via a constructor initializer, eg. T(): field(123) { ... }. +// An abstract base class for constructor-initializer-based constructors. class ConstructorInitializerConstructionContext : public ConstructionContext { const CXXCtorInitializer *I; - friend class ConstructionContext; // Allows to create<>() itself. - +protected: explicit ConstructorInitializerConstructionContext( - const CXXCtorInitializer *I) - : ConstructionContext(ConstructionContext::ConstructorInitializerKind), - I(I) { + ConstructionContext::Kind K, const CXXCtorInitializer *I) + : ConstructionContext(K), I(I) { + assert(classof(this)); assert(I); } @@ -220,7 +221,57 @@ const CXXCtorInitializer *getCXXCtorInitializer() const { return I; } static bool classof(const ConstructionContext *CC) { - return CC->getKind() == ConstructorInitializerKind; + return CC->getKind() >= INITIALIZER_BEGIN && + CC->getKind() <= INITIALIZER_END; + } +}; + +/// Represents construction into a field or a base class within a bigger object +/// via a constructor initializer, eg. T(): field(123) { ... }. +class SimpleConstructorInitializerConstructionContext + : public ConstructorInitializerConstructionContext { + friend class ConstructionContext; // Allows to create<>() itself. + + explicit SimpleConstructorInitializerConstructionContext( + const CXXCtorInitializer *I) + : ConstructorInitializerConstructionContext( + ConstructionContext::SimpleConstructorInitializerKind, I) {} + +public: + static bool classof(const ConstructionContext *CC) { + return CC->getKind() == SimpleConstructorInitializerKind; + } +}; + +/// Represents construction into a field or a base class within a bigger object +/// via a constructor initializer, with a single constructor, eg. +/// T(): field(Field(123)) { ... }. Such construction context may only appear +/// in C++17 because previously it was split into a temporary object constructor +/// and an elidable simple constructor-initializer copy-constructor and we were +/// producing separate construction contexts for these constructors. In C++17 +/// we have a single construction context that combines both. Note that if the +/// object has trivial destructor, then this code is indistinguishable from +/// a simple constructor-initializer constructor on the AST level; in this case +/// we provide a simple constructor-initializer construction context. +class CXX17ElidedCopyConstructorInitializerConstructionContext + : public ConstructorInitializerConstructionContext { + const CXXBindTemporaryExpr *BTE; + + friend class ConstructionContext; // Allows to create<>() itself. + + explicit CXX17ElidedCopyConstructorInitializerConstructionContext( + const CXXCtorInitializer *I, const CXXBindTemporaryExpr *BTE) + : ConstructorInitializerConstructionContext( + CXX17ElidedCopyConstructorInitializerKind, I), + BTE(BTE) { + assert(BTE); + } + +public: + const CXXBindTemporaryExpr *getCXXBindTemporaryExpr() const { return BTE; } + + static bool classof(const ConstructionContext *CC) { + return CC->getKind() == CXX17ElidedCopyConstructorInitializerKind; } }; Index: lib/Analysis/CFG.cpp =================================================================== --- lib/Analysis/CFG.cpp +++ lib/Analysis/CFG.cpp @@ -4911,10 +4911,18 @@ const ConstructionContext *CC) { const Stmt *S1 = nullptr, *S2 = nullptr; switch (CC->getKind()) { - case ConstructionContext::ConstructorInitializerKind: { + case ConstructionContext::SimpleConstructorInitializerKind: { OS << ", "; - const auto *ICC = cast(CC); - print_initializer(OS, Helper, ICC->getCXXCtorInitializer()); + const auto *SICC = cast(CC); + print_initializer(OS, Helper, SICC->getCXXCtorInitializer()); + break; + } + case ConstructionContext::CXX17ElidedCopyConstructorInitializerKind: { + OS << ", "; + const auto *CICC = + cast(CC); + print_initializer(OS, Helper, CICC->getCXXCtorInitializer()); + S2 = CICC->getCXXBindTemporaryExpr(); break; } case ConstructionContext::SimpleVariableKind: { Index: lib/Analysis/ConstructionContext.cpp =================================================================== --- lib/Analysis/ConstructionContext.cpp +++ lib/Analysis/ConstructionContext.cpp @@ -62,21 +62,31 @@ // lifetime extension on the parent layer. if (const ConstructionContextLayer *ParentLayer = TopLayer->getParent()) { assert(ParentLayer->isLast()); + // C++17 *requires* elision of the constructor at the return site + // and at variable/member initialization site, while previous standards + // were allowing an optional elidable constructor. + // This is the C++17 copy-elided construction into a ctor initializer. + if (const CXXCtorInitializer *I = ParentLayer->getTriggerInit()) { + return create< + CXX17ElidedCopyConstructorInitializerConstructionContext>(C, + I, BTE); + } + assert(ParentLayer->getTriggerStmt() && + "Non-statement-based layers have been handled above!"); + // This is the normal, non-C++17 case: a temporary object which has + // both destruction and materialization info attached to it in the AST. if ((MTE = dyn_cast( ParentLayer->getTriggerStmt()))) { - // A temporary object which has both destruction and - // materialization info. return create(C, BTE, MTE); } - // C++17 *requires* elision of the constructor at the return site - // and at variable initialization site, while previous standards - // were allowing an optional elidable constructor. + // This is C++17 copy-elided construction into return statement. if (auto *RS = dyn_cast(ParentLayer->getTriggerStmt())) { assert(!RS->getRetValue()->getType().getCanonicalType() ->getAsCXXRecordDecl()->hasTrivialDestructor()); return create(C, RS, BTE); } + // This is C++17 copy-elided construction into a simple variable. if (auto *DS = dyn_cast(ParentLayer->getTriggerStmt())) { assert(!cast(DS->getSingleDecl())->getType() .getCanonicalType()->getAsCXXRecordDecl() @@ -104,7 +114,7 @@ llvm_unreachable("Unexpected construction context with statement!"); } else if (const CXXCtorInitializer *I = TopLayer->getTriggerInit()) { assert(TopLayer->isLast()); - return create(C, I); + return create(C, I); } llvm_unreachable("Unexpected construction context!"); } Index: lib/StaticAnalyzer/Core/ExprEngineCXX.cpp =================================================================== --- lib/StaticAnalyzer/Core/ExprEngineCXX.cpp +++ lib/StaticAnalyzer/Core/ExprEngineCXX.cpp @@ -134,7 +134,7 @@ makeZeroElementRegion(State, LValue, Ty, CallOpts.IsArrayCtorOrDtor); return LValue.getAsRegion(); } - case ConstructionContext::ConstructorInitializerKind: { + case ConstructionContext::SimpleConstructorInitializerKind: { const auto *ICC = cast(CC); const auto *Init = ICC->getCXXCtorInitializer(); assert(Init->isAnyMemberInitializer()); @@ -217,6 +217,7 @@ } case ConstructionContext::CXX17ElidedCopyVariableKind: case ConstructionContext::CXX17ElidedCopyReturnedValueKind: + case ConstructionContext::CXX17ElidedCopyConstructorInitializerKind: // Not implemented yet. break; } Index: test/Analysis/cfg-rich-constructors.cpp =================================================================== --- test/Analysis/cfg-rich-constructors.cpp +++ test/Analysis/cfg-rich-constructors.cpp @@ -252,6 +252,34 @@ D(double): C(C::get()), c1(new C(C::get())) {} }; +// Let's see if initializers work well for fields with destructors. +class E { +public: + static E get(); + ~E(); +}; + +class F { + E e; + +public: +// FIXME: There should be no temporary destructor in C++17. +// CHECK: F() +// CHECK: 1: E::get +// CHECK-NEXT: 2: [B1.1] (ImplicitCastExpr, FunctionToPointerDecay, class ctor_initializers::E (*)( +// CXX11-NEXT: 3: [B1.2]() (CXXRecordTypedCall, [B1.4], [B1.6]) +// CXX11-NEXT: 4: [B1.3] (BindTemporary) +// CXX11-NEXT: 5: [B1.4] (ImplicitCastExpr, NoOp, const class ctor_initializers::E) +// CXX11-NEXT: 6: [B1.5] +// CXX11-NEXT: 7: [B1.6] (CXXConstructExpr, e([B1.6]) (Member initializer), class ctor_initializers +// CXX11-NEXT: 8: e([B1.7]) (Member initializer) +// CXX11-NEXT: 9: ~ctor_initializers::E() (Temporary object destructor) +// CXX17-NEXT: 3: [B1.2]() (CXXRecordTypedCall, e([B1.4]) (Member initializer), [B1.4]) +// CXX17-NEXT: 4: [B1.3] (BindTemporary) +// CXX17-NEXT: 5: e([B1.4]) (Member initializer) +// CXX17-NEXT: 6: ~ctor_initializers::E() (Temporary object destructor) + F(): e(E::get()) {} +}; } // end namespace ctor_initializers namespace return_stmt_without_dtor {