diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 35aae605d8476..50cfa0e75188b 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -468,6 +468,10 @@ features cannot lower the translation-unit ABI level; #### Bug Fixes to C++ Support +- Fixed the destruction timing of temporaries created by default member + initializers during aggregate initialization. Such an initializer is part of + the full-expression containing the aggregate initialization. (#GH85601) + - Fixed an issue where `__typeof__` incorrectly rejected cv-qualified function types. - Fixed a bug where top-level CV qualifiers (such as ``const``) were dropped from pointers modified by Microsoft pointer attributes (like ``__ptr32`` and ``__ptr64``) and WebAssembly's ``__funcref``. diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index dcf112fd8eaa4..10a764a403e37 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -5543,6 +5543,10 @@ class Sema final : public SemaBase { ExprResult ConvertMemberDefaultInitExpression(FieldDecl *FD, Expr *InitExpr, SourceLocation InitLoc); + ExprResult ConvertMemberDefaultInitExpression(FieldDecl *FD, + const InitializedEntity &Entity, + Expr *InitExpr, + SourceLocation InitLoc); /// FinalizeVarWithDestructor - Prepare for calling destructor on the /// constructed variable. @@ -7712,7 +7716,24 @@ class Sema final : public SemaBase { /// Emit a warning for all pending noderef expressions that we recorded. void WarnOnPendingNoDerefs(ExpressionEvaluationContextRecord &Rec); - ExprResult BuildCXXDefaultInitExpr(SourceLocation Loc, FieldDecl *Field); +private: + /// Shared logic for building default member initializer which used in a + /// constructor or an aggregate initialization. + /// + /// + /// The caller enters that evaluation context and decides whether the result + /// is finished as a full-expression. \p NestedDefaultChecking and + /// \p NeedRebuild have to be sampled before entering it. + ExprResult BuildCXXDefaultInitInternal(SourceLocation Loc, FieldDecl *Field, + const InitializedEntity &Entity, + bool NestedDefaultChecking, + bool NeedRebuild); + +public: + ExprResult BuildCXXCtorDefaultInitExpr(SourceLocation Loc, FieldDecl *Field); + ExprResult + BuildCXXAggregateDefaultInitExpr(SourceLocation Loc, FieldDecl *Field, + const InitializedEntity &MemberEntity); /// Instantiate or parse a C++ default argument expression as necessary. /// Return true on error. diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 4e90c496de342..f03d615bdce5b 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -4242,6 +4242,12 @@ ExprResult Sema::ConvertMemberDefaultInitExpression(FieldDecl *FD, SourceLocation InitLoc) { InitializedEntity Entity = InitializedEntity::InitializeMemberFromDefaultMemberInitializer(FD); + return ConvertMemberDefaultInitExpression(FD, Entity, InitExpr, InitLoc); +} + +ExprResult Sema::ConvertMemberDefaultInitExpression( + FieldDecl *FD, const InitializedEntity &Entity, Expr *InitExpr, + SourceLocation InitLoc) { InitializationKind Kind = FD->getInClassInitStyle() == ICIS_ListInit ? InitializationKind::CreateDirectList(InitExpr->getBeginLoc(), @@ -5342,7 +5348,7 @@ static bool CollectFieldInitializer(Sema &SemaRef, BaseAndFieldInfo &Info, if (Field->hasInClassInitializer() && !Info.isImplicitCopyOrMove()) { ExprResult DIE = - SemaRef.BuildCXXDefaultInitExpr(Info.Ctor->getLocation(), Field); + SemaRef.BuildCXXCtorDefaultInitExpr(Info.Ctor->getLocation(), Field); if (DIE.isInvalid()) return true; @@ -14129,7 +14135,7 @@ bool SpecialMemberExceptionSpecInfo::visitField(FieldDecl *FD) { // FIXME: We should have a single context note pointing at Loc, and // this location should be MD->getLocation() instead, since that's // the location where we actually use the default init expression. - E = S.BuildCXXDefaultInitExpr(Loc, FD).get(); + E = S.BuildCXXCtorDefaultInitExpr(Loc, FD).get(); if (E) ExceptSpec.CalledExpr(E); } else if (auto *RD = S.Context.getBaseElementType(FD->getType()) diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 2b524a956ecc4..34a0bff7c5289 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -5876,39 +5876,51 @@ static FieldDecl *FindFieldDeclInstantiationPattern(const ASTContext &Ctx, return cast(*Rng.begin()); } -ExprResult Sema::BuildCXXDefaultInitExpr(SourceLocation Loc, FieldDecl *Field) { - assert(Field->hasInClassInitializer()); - - CXXThisScopeRAII This(*this, Field->getParent(), Qualifiers()); - +ExprResult Sema::BuildCXXDefaultInitInternal(SourceLocation Loc, + FieldDecl *Field, + const InitializedEntity &Entity, + bool NestedDefaultChecking, + bool NeedRebuild) { auto *ParentRD = cast(Field->getParent()); - std::optional - InitializationContext = - OutermostDeclarationWithDelayedImmediateInvocations(); - if (!InitializationContext.has_value()) - InitializationContext.emplace(Loc, Field, CurContext); - - Expr *Init = nullptr; - - bool NestedDefaultChecking = isCheckingDefaultArgumentOrInitializer(); - bool NeedRebuild = needsRebuildOfDefaultArgOrInit(); - EnterExpressionEvaluationContext EvalContext( - *this, ExpressionEvaluationContext::PotentiallyEvaluated, Field); - - if (!Field->getInClassInitializer()) { + if (!Field->getInClassInitializer() && + isTemplateInstantiation(ParentRD->getTemplateSpecializationKind())) { // Maybe we haven't instantiated the in-class initializer. Go check the // pattern FieldDecl to see if it has one. - if (isTemplateInstantiation(ParentRD->getTemplateSpecializationKind())) { - FieldDecl *Pattern = - FindFieldDeclInstantiationPattern(getASTContext(), Field); - assert(Pattern && "We must have set the Pattern!"); - if (!Pattern->hasInClassInitializer() || - InstantiateInClassInitializer(Loc, Field, Pattern, - getTemplateInstantiationArgs(Field))) { - return ExprError(); - } - } + FieldDecl *Pattern = + FindFieldDeclInstantiationPattern(getASTContext(), Field); + assert(Pattern && "We must have set the Pattern!"); + if (!Pattern->hasInClassInitializer() || + InstantiateInClassInitializer(Loc, Field, Pattern, + getTemplateInstantiationArgs(Field))) + return ExprError(); + } + + Expr *InClassInit = Field->getInClassInitializer(); + if (!InClassInit) { + // DR1351: + // If the brace-or-equal-initializer of a non-static data member + // invokes a defaulted default constructor of its class or of an + // enclosing class in a potentially evaluated subexpression, the + // program is ill-formed. + // + // This resolution is unworkable: the exception specification of the + // default constructor can be needed in an unevaluated context, in + // particular, in the operand of a noexcept-expression, and we can be + // unable to compute an exception specification for an enclosed class. + // + // Any attempt to resolve the exception specification of a defaulted default + // constructor before the initializer is lexically complete will ultimately + // come here at which point we can diagnose it. + RecordDecl *OutermostClass = ParentRD->getOuterLexicalRecordContext(); + Diag(Loc, diag::err_default_member_initializer_not_yet_parsed) + << OutermostClass << Field; + Diag(Field->getEndLoc(), + diag::note_default_member_initializer_not_yet_parsed); + // Recover by marking the field invalid, unless we're in a SFINAE context. + if (!isSFINAEContext()) + Field->setInvalidDecl(); + return ExprError(); } // CWG2631 @@ -5927,27 +5939,27 @@ ExprResult Sema::BuildCXXDefaultInitExpr(SourceLocation Loc, FieldDecl *Field) { // expression is an ExprWithCleanups. Then make sure the normal lifetime // extension code recurses into the default initializer and does lifetime // extension when warranted. - bool ContainsAnyTemporaries = - isa_and_present(Field->getInClassInitializer()); - if (Field->getInClassInitializer() && - !Field->getInClassInitializer()->containsErrors() && + bool ContainsAnyTemporaries = isa(InClassInit); + Expr *Init = InClassInit; + if (!InClassInit->containsErrors() && (V.HasImmediateCalls || (NeedRebuild && ContainsAnyTemporaries))) { ExprEvalContexts.back().DelayedDefaultInitializationContext = {Loc, Field, CurContext}; ExprEvalContexts.back().IsCurrentlyCheckingDefaultArgumentOrInitializer = NestedDefaultChecking; // Pass down lifetime extending flag, and collect temporaries in - // CreateMaterializeTemporaryExpr when we rewrite the call argument. + // CreateMaterializeTemporaryExpr when we rewrite the initializer. currentEvaluationContext().InLifetimeExtendingContext = parentEvaluationContext().InLifetimeExtendingContext; + EnsureImmediateInvocationInDefaultArgs Immediate(*this); ExprResult Res; runWithSufficientStackSpace(Loc, [&] { - Res = Immediate.TransformInitializer(Field->getInClassInitializer(), + Res = Immediate.TransformInitializer(InClassInit, /*CXXDirectInit=*/false); }); if (!Res.isInvalid()) - Res = ConvertMemberDefaultInitExpression(Field, Res.get(), Loc); + Res = ConvertMemberDefaultInitExpression(Field, Entity, Res.get(), Loc); if (Res.isInvalid()) { Field->setInvalidDecl(); return ExprError(); @@ -5955,52 +5967,108 @@ ExprResult Sema::BuildCXXDefaultInitExpr(SourceLocation Loc, FieldDecl *Field) { Init = Res.get(); } - if (Field->getInClassInitializer()) { - Expr *E = Init ? Init : Field->getInClassInitializer(); - if (!NestedDefaultChecking) - runWithSufficientStackSpace(Loc, [&] { - MarkDeclarationsReferencedInExpr(E, /*SkipLocalVariables=*/false); - }); - if (isInLifetimeExtendingContext()) - DiscardCleanupsInEvaluationContext(); - // C++11 [class.base.init]p7: - // The initialization of each base and member constitutes a - // full-expression. - ExprResult Res = ActOnFinishFullExpr(E, /*DiscardedValue=*/false); - if (Res.isInvalid()) { - Field->setInvalidDecl(); - return ExprError(); - } - Init = Res.get(); + if (!NestedDefaultChecking) + runWithSufficientStackSpace(Loc, [&] { + MarkDeclarationsReferencedInExpr(Init, /*SkipLocalVariables=*/false); + }); + return Init; +} - return CXXDefaultInitExpr::Create(Context, InitializationContext->Loc, - Field, InitializationContext->Context, - Init); - } +ExprResult Sema::BuildCXXCtorDefaultInitExpr(SourceLocation Loc, + FieldDecl *Field) { + assert(Field->hasInClassInitializer()); - // DR1351: - // If the brace-or-equal-initializer of a non-static data member - // invokes a defaulted default constructor of its class or of an - // enclosing class in a potentially evaluated subexpression, the - // program is ill-formed. - // - // This resolution is unworkable: the exception specification of the - // default constructor can be needed in an unevaluated context, in - // particular, in the operand of a noexcept-expression, and we can be - // unable to compute an exception specification for an enclosed class. + bool NestedDefaultChecking = isCheckingDefaultArgumentOrInitializer(); + + // C++11 [class.base.init]p7: + // The initialization of each base and member constitutes a + // full-expression. + // So this initializer gets an evaluation context of its own, and is finished + // as a full-expression below. + EnterExpressionEvaluationContext EvalContext( + *this, ExpressionEvaluationContext::PotentiallyEvaluated, Field); + CXXThisScopeRAII This(*this, Field->getParent(), Qualifiers()); + + auto InitContext = OutermostDeclarationWithDelayedImmediateInvocations(); + if (!InitContext) + InitContext.emplace(Loc, Field, CurContext); + + // [class.temporary]/p7: + // If such a temporary object would otherwise be destroyed at the end of the + // for-range-initializer full-expression, the object persists for the lifetime + // of the reference initialized by the for-range-initializer. // - // Any attempt to resolve the exception specification of a defaulted default - // constructor before the initializer is lexically complete will ultimately - // come here at which point we can diagnose it. - RecordDecl *OutermostClass = ParentRD->getOuterLexicalRecordContext(); - Diag(Loc, diag::err_default_member_initializer_not_yet_parsed) - << OutermostClass << Field; - Diag(Field->getEndLoc(), - diag::note_default_member_initializer_not_yet_parsed); - // Recover by marking the field invalid, unless we're in a SFINAE context. - if (!isSFINAEContext()) + // A default member initializer used by a constructor is a separate + // full-expression, we don't need extend temporaries lifetime in this + // situation, the NeedRebuild will always false. + ExprResult Init = BuildCXXDefaultInitInternal( + Loc, Field, + InitializedEntity::InitializeMemberFromDefaultMemberInitializer(Field), + NestedDefaultChecking, /*NeedRebuild=*/false); + if (Init.isInvalid()) + return ExprError(); + + if (isInLifetimeExtendingContext()) + DiscardCleanupsInEvaluationContext(); + Init = ActOnFinishFullExpr(Init.get(), /*DiscardedValue=*/false); + if (Init.isInvalid()) { Field->setInvalidDecl(); - return ExprError(); + return ExprError(); + } + + return CXXDefaultInitExpr::Create( + Context, InitContext->Loc, Field, InitContext->Context, + Init.get() == Field->getInClassInitializer() ? nullptr : Init.get()); +} + +ExprResult +Sema::BuildCXXAggregateDefaultInitExpr(SourceLocation Loc, FieldDecl *Field, + const InitializedEntity &MemberEntity) { + assert(Field->hasInClassInitializer()); + + bool NestedDefaultChecking = isCheckingDefaultArgumentOrInitializer(); + + // Unlike a mem-initializer, this initializer is a subexpression of the + // full-expression containing the aggregate initialization. It is evaluated + // exactly as that full-expression is, so inherit the enclosing context kind + // rather than forcing a potentially evaluated one. + EnterExpressionEvaluationContext EvalContext( + *this, currentEvaluationContext().Context, Field); + CXXThisScopeRAII This(*this, Field->getParent(), Qualifiers()); + + auto InitContext = OutermostDeclarationWithDelayedImmediateInvocations(); + if (!InitContext) + InitContext.emplace(Loc, Field, CurContext); + + // [class.temporary]/p7: + // If such a temporary object would otherwise be destroyed at the end of the + // for-range-initializer full-expression, the object persists for the lifetime + // of the reference initialized by the for-range-initializer. + // + // A default member initializer used by an aggregate initialization belongs to + // the full-expression containing the aggregate initialization. we need extend + // temporaries lifetime in this situation, the NeedRebuild will always true. + + // CWG1815: always rebuild, never share the AST built when the field was + // declared. Only a copy rebuilt here has its MaterializeTemporaryExprs + // collected in this context, which is what lets the aggregate initialization + // lifetime-extend them; sharing one AST would also make several uses of the + // same field fight over its extension. A mem-initializer has no such need, + // as its temporaries die at the end of the initializer itself. + ExprResult Init = BuildCXXDefaultInitInternal( + Loc, Field, MemberEntity, NestedDefaultChecking, /*NeedRebuild=*/true); + if (Init.isInvalid()) + return ExprError(); + + // Deliberately not finished as a full-expression: leaving the temporaries it + // created on ExprCleanupObjects lets PopExpressionEvaluationContext merge + // them into the enclosing context, which eventually wraps them all in a + // single ExprWithCleanups. They are then destroyed at the end of the + // containing full-expression, in reverse construction order. + + return CXXDefaultInitExpr::Create( + Context, InitContext->Loc, Field, InitContext->Context, + Init.get() == Field->getInClassInitializer() ? nullptr : Init.get()); } VariadicCallType Sema::getVariadicCallType(FunctionDecl *FDecl, diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp index 48ce51863c2c0..2b756b803e205 100644 --- a/clang/lib/Sema/SemaInit.cpp +++ b/clang/lib/Sema/SemaInit.cpp @@ -813,28 +813,15 @@ void InitListChecker::FillInEmptyInitForField(unsigned Init, FieldDecl *Field, if (VerifyOnly) return; - ExprResult DIE; - { - // Enter a default initializer rebuild context, then we can support - // lifetime extension of temporary created by aggregate initialization - // using a default member initializer. - // CWG1815 (https://wg21.link/CWG1815). - EnterExpressionEvaluationContext RebuildDefaultInit( - SemaRef, Sema::ExpressionEvaluationContext::PotentiallyEvaluated); - SemaRef.currentEvaluationContext().RebuildDefaultArgOrDefaultInit = - true; - SemaRef.currentEvaluationContext().DelayedDefaultInitializationContext = - SemaRef.parentEvaluationContext() - .DelayedDefaultInitializationContext; - SemaRef.currentEvaluationContext().InLifetimeExtendingContext = - SemaRef.parentEvaluationContext().InLifetimeExtendingContext; - DIE = SemaRef.BuildCXXDefaultInitExpr(Loc, Field); - } + // A default member initializer used in aggregate initialization is part + // of the full-expression containing the aggregate initialization. Do not + // create or finish a separate expression evaluation context here. + ExprResult DIE = + SemaRef.BuildCXXAggregateDefaultInitExpr(Loc, Field, MemberEntity); if (DIE.isInvalid()) { hadError = true; return; } - SemaRef.checkInitializerLifetime(MemberEntity, DIE.get()); if (Init < NumInits) ILE->setInit(Init, DIE.get()); else { @@ -6152,11 +6139,10 @@ static void TryOrBuildParenListInitialization( // C++ [dcl.init]p16.6.2.2 // The remaining elements are initialized with their default // member initializers, if any - ExprResult DIE = S.BuildCXXDefaultInitExpr( - Kind.getParenOrBraceRange().getEnd(), FD); + ExprResult DIE = S.BuildCXXAggregateDefaultInitExpr( + Kind.getParenOrBraceRange().getEnd(), FD, SubEntity); if (DIE.isInvalid()) return; - S.checkInitializerLifetime(SubEntity, DIE.get()); InitExprs.push_back(DIE.get()); } } else { diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h index 024f726b188b7..9c8d05676f470 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -3519,9 +3519,10 @@ class TreeTransform { /// By default, builds a new default field initialization expression, which /// does not require any semantic analysis. Subclasses may override this /// routine to provide different behavior. - ExprResult RebuildCXXDefaultInitExpr(SourceLocation Loc, - FieldDecl *Field) { - return getSema().BuildCXXDefaultInitExpr(Loc, Field); + ExprResult RebuildCXXDefaultInitExpr(SourceLocation Loc, FieldDecl *Field, + Expr *RewrittenInit) { + return CXXDefaultInitExpr::Create(getSema().Context, Loc, Field, + getSema().CurContext, RewrittenInit); } /// Build a new C++ zero-initialization expression. @@ -15119,11 +15120,23 @@ TreeTransform::TransformCXXDefaultInitExpr(CXXDefaultInitExpr *E) { if (!Field) return ExprError(); + ExprResult InitRes; + if (E->hasRewrittenInit()) { + // The initializer can refer to `this` and to other members, so it has to + // be transformed in the scope of the field's class. + Sema::CXXThisScopeRAII ThisScope(SemaRef, Field->getParent(), Qualifiers()); + InitRes = getDerived().TransformExpr(E->getRewrittenExpr()); + if (InitRes.isInvalid()) + return ExprError(); + } + if (!getDerived().AlwaysRebuild() && Field == E->getField() && - E->getUsedContext() == SemaRef.CurContext) + E->getUsedContext() == SemaRef.CurContext && + InitRes.get() == E->getRewrittenExpr()) return E; - return getDerived().RebuildCXXDefaultInitExpr(E->getExprLoc(), Field); + return getDerived().RebuildCXXDefaultInitExpr(E->getExprLoc(), Field, + InitRes.get()); } template diff --git a/clang/test/AST/ast-dump-default-init.cpp b/clang/test/AST/ast-dump-default-init.cpp index 2c2d670486627..9f972bb3cc921 100644 --- a/clang/test/AST/ast-dump-default-init.cpp +++ b/clang/test/AST/ast-dump-default-init.cpp @@ -13,13 +13,12 @@ void test() { B b{}; } // CHECK: -CXXDefaultInitExpr 0x{{[^ ]*}} <{{.*}}> 'const A' lvalue has rewritten init -// CHECK-NEXT: `-ExprWithCleanups 0x{{[^ ]*}} <{{.*}}> 'const A' lvalue -// CHECK-NEXT: `-MaterializeTemporaryExpr 0x{{[^ ]*}} <{{.*}}> 'const A' lvalue extended by Var 0x{{[^ ]*}} 'b' 'B' -// CHECK-NEXT: `-ImplicitCastExpr 0x{{[^ ]*}} <{{.*}}> 'const A' -// CHECK-NEXT: `-CXXFunctionalCastExpr 0x{{[^ ]*}} <{{.*}}> 'A' functional cast to A -// CHECK-NEXT: `-InitListExpr 0x{{[^ ]*}} <{{.*}}> 'A' -// CHECK-NEXT: `-InitListExpr 0x{{[^ ]*}} <{{.*}}> 'int[1]' -// CHECK-NEXT: `-IntegerLiteral 0x{{[^ ]*}} <{{.*}}> 'int' 0 +// CHECK-NEXT: `-MaterializeTemporaryExpr 0x{{[^ ]*}} <{{.*}}> 'const A' lvalue extended by Var 0x{{[^ ]*}} 'b' 'B' +// CHECK-NEXT: `-ImplicitCastExpr 0x{{[^ ]*}} <{{.*}}> 'const A' +// CHECK-NEXT: `-CXXFunctionalCastExpr 0x{{[^ ]*}} <{{.*}}> 'A' functional cast to A +// CHECK-NEXT: `-InitListExpr 0x{{[^ ]*}} <{{.*}}> 'A' +// CHECK-NEXT: `-InitListExpr 0x{{[^ ]*}} <{{.*}}> 'int[1]' +// CHECK-NEXT: `-IntegerLiteral 0x{{[^ ]*}} <{{.*}}> 'int' 0 // JSON: "kind": "CXXDefaultInitExpr", // JSON: "type": { @@ -30,73 +29,65 @@ void test() { // JSON-NEXT: "inner": [ // JSON-NEXT: { // JSON-NEXT: "id": "0x{{.*}}", -// JSON-NEXT: "kind": "ExprWithCleanups", +// JSON-NEXT: "kind": "MaterializeTemporaryExpr", // JSON: "type": { // JSON-NEXT: "qualType": "const A" // JSON-NEXT: }, // JSON-NEXT: "valueCategory": "lvalue", +// JSON-NEXT: "extendingDecl": { +// JSON-NEXT: "id": "0x{{.*}}", +// JSON-NEXT: "kind": "VarDecl", +// JSON-NEXT: "name": "b", +// JSON-NEXT: "type": { +// JSON-NEXT: "qualType": "B" +// JSON-NEXT: } +// JSON-NEXT: }, +// JSON-NEXT: "storageDuration": "automatic", +// JSON-NEXT: "boundToLValueRef": true, // JSON-NEXT: "inner": [ // JSON-NEXT: { // JSON-NEXT: "id": "0x{{.*}}", -// JSON-NEXT: "kind": "MaterializeTemporaryExpr", +// JSON-NEXT: "kind": "ImplicitCastExpr", // JSON: "type": { // JSON-NEXT: "qualType": "const A" // JSON-NEXT: }, -// JSON-NEXT: "valueCategory": "lvalue", -// JSON-NEXT: "extendingDecl": { -// JSON-NEXT: "id": "0x{{.*}}", -// JSON-NEXT: "kind": "VarDecl", -// JSON-NEXT: "name": "b", -// JSON-NEXT: "type": { -// JSON-NEXT: "qualType": "B" -// JSON-NEXT: } -// JSON-NEXT: }, -// JSON-NEXT: "storageDuration": "automatic", -// JSON-NEXT: "boundToLValueRef": true, +// JSON-NEXT: "valueCategory": "prvalue", +// JSON-NEXT: "castKind": "NoOp", // JSON-NEXT: "inner": [ // JSON-NEXT: { // JSON-NEXT: "id": "0x{{.*}}", -// JSON-NEXT: "kind": "ImplicitCastExpr", +// JSON-NEXT: "kind": "CXXFunctionalCastExpr", // JSON: "type": { -// JSON-NEXT: "qualType": "const A" +// JSON-NEXT: "qualType": "A" // JSON-NEXT: }, // JSON-NEXT: "valueCategory": "prvalue", // JSON-NEXT: "castKind": "NoOp", // JSON-NEXT: "inner": [ // JSON-NEXT: { // JSON-NEXT: "id": "0x{{.*}}", -// JSON-NEXT: "kind": "CXXFunctionalCastExpr", +// JSON-NEXT: "kind": "InitListExpr", // JSON: "type": { // JSON-NEXT: "qualType": "A" // JSON-NEXT: }, // JSON-NEXT: "valueCategory": "prvalue", -// JSON-NEXT: "castKind": "NoOp", // JSON-NEXT: "inner": [ // JSON-NEXT: { // JSON-NEXT: "id": "0x{{.*}}", // JSON-NEXT: "kind": "InitListExpr", // JSON: "type": { -// JSON-NEXT: "qualType": "A" +// JSON-NEXT: "qualType": "int[1]" // JSON-NEXT: }, // JSON-NEXT: "valueCategory": "prvalue", // JSON-NEXT: "inner": [ // JSON-NEXT: { // JSON-NEXT: "id": "0x{{.*}}", -// JSON-NEXT: "kind": "InitListExpr", +// JSON-NEXT: "kind": "IntegerLiteral", // JSON: "type": { -// JSON-NEXT: "qualType": "int[1]" +// JSON-NEXT: "qualType": "int" // JSON-NEXT: }, // JSON-NEXT: "valueCategory": "prvalue", -// JSON-NEXT: "inner": [ -// JSON-NEXT: { -// JSON-NEXT: "id": "0x{{.*}}", -// JSON-NEXT: "kind": "IntegerLiteral", -// JSON: "type": { -// JSON-NEXT: "qualType": "int" -// JSON-NEXT: }, -// JSON-NEXT: "valueCategory": "prvalue", -// JSON-NEXT: "value": "0" -// JSON-NEXT: } -// JSON-NEXT: ] +// JSON-NEXT: "value": "0" // JSON-NEXT: } // JSON-NEXT: ] +// JSON-NEXT: } +// JSON-NEXT: ] diff --git a/clang/test/AST/ast-dump-recovery.cpp b/clang/test/AST/ast-dump-recovery.cpp index 3c1811b2c8d02..790b7bb2025fd 100644 --- a/clang/test/AST/ast-dump-recovery.cpp +++ b/clang/test/AST/ast-dump-recovery.cpp @@ -294,7 +294,7 @@ union U { // CHECK-NEXT: `-DeclStmt {{.*}} // CHECK-NEXT: `-VarDecl {{.*}} g 'U' listinit // CHECK-NEXT: `-InitListExpr {{.*}} 'U' contains-errors field Field {{.*}} 'f' 'int' -// CHECK-NEXT: `-CXXDefaultInitExpr {{.*}} 'int' contains-errors has rewritten init +// CHECK-NEXT: `-CXXDefaultInitExpr {{.*}} 'int' contains-errors // CHECK-NEXT: `-RecoveryExpr {{.*}} 'int' contains-errors // DISABLED-NOT: -RecoveryExpr {{.*}} contains-errors void foo() { diff --git a/clang/test/Analysis/lifetime-extended-regions.cpp b/clang/test/Analysis/lifetime-extended-regions.cpp index 4458ad294af7c..4e5e72eeb6df7 100644 --- a/clang/test/Analysis/lifetime-extended-regions.cpp +++ b/clang/test/Analysis/lifetime-extended-regions.cpp @@ -121,11 +121,16 @@ void aggregateWithReferences() { clang_analyzer_dump(viaReference.rx); // expected-warning-re {{&lifetime_extended_object{int, viaReference, S{{[0-9]+}}} }} clang_analyzer_dump(viaReference.ry); // expected-warning-re {{&lifetime_extended_object{Composite, viaReference, S{{[0-9]+}}} }} - // FIXME: clang currently support extending lifetime of object bound to reference members of aggregates, - // that are created from default member initializer. But CFG and ExprEngine need to be updated to address this change. - // The following expect warning: {{&lifetime_extended_object{Composite, defaultInitExtended, S{{[0-9]+}}} }} + // A temporary created by a default member initializer of an aggregate is now part of the full-expression containing the aggregate initialization, + // so its lifetime is extended along with the aggregate (CWG1815). + // + // FIXME: The analyzer does not model that extension yet and still reports a plain temp_object here; it should report + // &lifetime_extended_object{Composite, defaultInitExtended, S...}. Teaching it requires CFG and ExprEngine to handle + // the rebuilt default member initializer. + // + // Once https://github.com/llvm/llvm-project/pull/146281 landed, this issue will be fixed. RefAggregate defaultInitExtended{i}; - clang_analyzer_dump(defaultInitExtended.ry); // expected-warning {{Unknown }} + clang_analyzer_dump(defaultInitExtended.ry); // expected-warning-re {{&temp_object{Composite, S{{[0-9]+}}} }} } void lambda() { diff --git a/clang/test/CodeGenCXX/aggregate-default-member-initializers.cpp b/clang/test/CodeGenCXX/aggregate-default-member-initializers.cpp new file mode 100644 index 0000000000000..b0a8d63450aff --- /dev/null +++ b/clang/test/CodeGenCXX/aggregate-default-member-initializers.cpp @@ -0,0 +1,42 @@ +// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++20 -Wno-unused-value -emit-llvm -o - %s | FileCheck %s + +struct A { + int &x; + ~A() { x = 0; } +}; + +struct AA { + int &x; + ~AA() { x = -1; } +}; + +struct B { + int &x; + const A &a = A{x}; +}; + +struct BB { + int &x; + const AA &a = AA{x}; +}; + +// CHECK-LABEL: define{{.*}} i32 @_Z3onev() +int one() { + int x = 1; + B{x}; + // CHECK: call void @_ZN1AD{{[012]}}Ev + // CHECK-NEXT: load i32, ptr + return x; +} + +// The default initializers are part of the same full-expression, so their +// temporaries are destroyed in reverse construction order. +// CHECK-LABEL: define{{.*}} i32 @_Z3twov() +int two() { + int x = 1; + B{x}, BB{x}; + // CHECK: call void @_ZN2AAD{{[012]}}Ev + // CHECK-NEXT: call void @_ZN1AD{{[012]}}Ev + // CHECK-NEXT: load i32, ptr + return x; +} diff --git a/clang/test/SemaCXX/aggregate-default-member-initializers.cpp b/clang/test/SemaCXX/aggregate-default-member-initializers.cpp new file mode 100644 index 0000000000000..9a004ea79d84a --- /dev/null +++ b/clang/test/SemaCXX/aggregate-default-member-initializers.cpp @@ -0,0 +1,112 @@ +// RUN: %clang_cc1 -std=c++20 -Wno-unused-value -verify=expected %s +// RUN: %clang_cc1 -std=c++23 -Wno-unused-value -verify=expected %s +// RUN: %clang_cc1 -std=c++20 -Wno-unused-value -verify=expected,bytecode %s \ +// RUN: -fexperimental-new-constant-interpreter + +namespace lifetime { + +struct A { + int &x; + constexpr ~A() { x = 0; } +}; + +struct AA { + int &x; + constexpr ~AA() { x = -1; } +}; + +struct B { + int &x; + // FIXME: The new constant interpreter does not give a discarded composite + // prvalue a result object, so the default member initializer cannot read + // 'x'. This predates this change; the legacy interpreter gets it right. + const A &a = A{x}; // bytecode-note 3{{implicit use of 'this' pointer is only allowed within the evaluation of a call to a 'constexpr' member function}} +}; + +struct BB { + int &x; + const AA &a = AA{x}; +}; + +constexpr int one() { + int x = 1; + B{x}; + return x; +} + +constexpr int two() { + int x = 1; + B{x}, BB{x}; + return x; +} + +constexpr int paren() { + int x = 1; + (B(x)); + return x; +} + +static_assert(one() == 0); // bytecode-error {{static assertion expression is not an integral constant expression}} \ + // bytecode-note {{in call to 'one()'}} +static_assert(two() == 0); // bytecode-error {{static assertion expression is not an integral constant expression}} \ + // bytecode-note {{in call to 'two()'}} +static_assert(paren() == 0); // bytecode-error {{static assertion expression is not an integral constant expression}} \ + // bytecode-note {{in call to 'paren()'}} + +} // namespace lifetime + +namespace unevaluated { + +template int noInstantiate() { + static_assert(false); + return 0; +} + +struct S { + int x = noInstantiate(); +}; + +int size = sizeof(S{}); + +} // namespace unevaluated + +namespace immediate { + +struct Inner { + int a; + static consteval int decrement(int &x) { + return --x; // bytecode-note {{modification of object of const-qualified type 'const int' is not allowed in a constant expression}} + } + // FIXME: The aggregate result object does not exist yet when the immediate + // invocation is checked, so reading 'a' fails. This is long-standing and is + // independent of which full-expression the initializer belongs to. + int b = decrement(a); // expected-error {{call to consteval function 'immediate::Inner::decrement' is not a constant expression}} \ + // expected-note {{implicit use of 'this' pointer is only allowed within the evaluation of a call to a 'constexpr' member function}} \ + // expected-note {{declared here}} \ + // bytecode-note {{in call to 'decrement(Inner{1}.a)'}} +}; + +struct Outer { + const Inner &inner = Inner{1}; // expected-note {{in the default initializer of 'b'}} +}; + +constexpr int value = Outer{}.inner.a; // bytecode-error {{constexpr variable 'value' must be initialized by a constant expression}} \ + // bytecode-note {{declared here}} +static_assert(value == 0); // bytecode-error {{static assertion expression is not an integral constant expression}} \ + // bytecode-note {{initializer of 'value' is not a constant expression}} + +consteval unsigned currentLine(unsigned line = __builtin_LINE()) { + return line; +} + +struct SourceAndRuntime { + unsigned line = currentLine(); + int runtime; +}; + +void sourceAndRuntime(int n) { + // The runtime initializer does not make currentLine() non-constant. + SourceAndRuntime value{.runtime = n}; +} + +} // namespace immediate diff --git a/clang/test/SemaCXX/cxx2c-placeholder-vars.cpp b/clang/test/SemaCXX/cxx2c-placeholder-vars.cpp index 8e428c0ef0427..37824c16f4f05 100644 --- a/clang/test/SemaCXX/cxx2c-placeholder-vars.cpp +++ b/clang/test/SemaCXX/cxx2c-placeholder-vars.cpp @@ -274,16 +274,16 @@ void f() { // CHECK: ClassTemplateSpecializationDecl {{.*}} struct A definition // CHECK: CXXConstructorDecl {{.*}} implicit used constexpr A 'void () noexcept' // CHECK-NEXT: CXXCtorInitializer Field {{.*}} '_' 'int' -// CHECK-NEXT: CXXDefaultInitExpr {{.*}} 'int' has rewritten init +// CHECK-NEXT: CXXDefaultInitExpr {{.*}} 'int' // CHECK-NEXT: IntegerLiteral {{.*}} 'int' 1 // CHECK-NEXT: CXXCtorInitializer Field {{.*}} '_' 'int' -// CHECK-NEXT: CXXDefaultInitExpr {{.*}} 'int' has rewritten init +// CHECK-NEXT: CXXDefaultInitExpr {{.*}} 'int' // CHECK-NEXT: IntegerLiteral {{.*}} 'int' 2 // CHECK-NEXT: CXXCtorInitializer Field {{.*}} 'a' 'int' -// CHECK-NEXT: CXXDefaultInitExpr {{.*}} 'int' has rewritten init +// CHECK-NEXT: CXXDefaultInitExpr {{.*}} 'int' // CHECK-NEXT: IntegerLiteral {{.*}} 'int' 3 // CHECK-NEXT: CXXCtorInitializer Field {{.*}} '_' 'int' -// CHECK-NEXT: CXXDefaultInitExpr {{.*}} 'int' has rewritten init +// CHECK-NEXT: CXXDefaultInitExpr {{.*}} 'int' // CHECK-NEXT: IntegerLiteral {{.*}} 'int' 4 // CHECK-NEXT: CompoundStmt {{.*}} diff --git a/clang/unittests/AST/ASTExprTest.cpp b/clang/unittests/AST/ASTExprTest.cpp index 305e659e573d4..5c9e7928bb804 100644 --- a/clang/unittests/AST/ASTExprTest.cpp +++ b/clang/unittests/AST/ASTExprTest.cpp @@ -12,6 +12,7 @@ #include "ASTPrint.h" #include "clang/AST/ASTContext.h" +#include "clang/AST/DynamicRecursiveASTVisitor.h" #include "clang/AST/Expr.h" #include "clang/AST/IgnoreExpr.h" #include "clang/AST/OpenACCClause.h" @@ -397,3 +398,51 @@ TEST(ASTExpr, IsKnownToHaveBooleanValue) { ExpectKnown("from_bitint1", false, true); ExpectKnown("from_bitint2", false, false); } + +TEST(ASTExpr, CXXDefaultInitExprHasRewrittenInit) { + auto AST = buildASTFromCodeWithArgs(R"cpp( + struct WithDtor { + int &r; + ~WithDtor(); + }; + + struct Agg { + int &r; + const WithDtor &d = WithDtor{r}; + }; + + struct Ctor { + int x = 1; + Ctor(); + }; + Ctor::Ctor() {} + + void aggregate(int &i) { Agg{i}; } + )cpp", + {"-std=c++20", "-Wno-unused-value"}); + + struct Visitor : DynamicRecursiveASTVisitor { + llvm::StringMap ByField; + Visitor() { ShouldVisitImplicitCode = true; } + bool VisitCXXDefaultInitExpr(CXXDefaultInitExpr *E) override { + ByField.try_emplace(E->getField()->getName(), E); + return true; + } + } V; + V.TraverseDecl(AST->getASTContext().getTranslationUnitDecl()); + + // Used by aggregate initialization: rebuilt, so it owns its initializer. + const CXXDefaultInitExpr *AggDIE = V.ByField.lookup("d"); + ASSERT_NE(AggDIE, nullptr); + EXPECT_TRUE(AggDIE->hasRewrittenInit()); + EXPECT_NE(AggDIE->getRewrittenExpr(), nullptr); + EXPECT_EQ(AggDIE->getExpr(), AggDIE->getRewrittenExpr()); + EXPECT_NE(AggDIE->getExpr(), AggDIE->getField()->getInClassInitializer()); + + // Used by a constructor with nothing to rebuild: shares the field's one, so + // getExpr() falls back to it. + const CXXDefaultInitExpr *CtorDIE = V.ByField.lookup("x"); + ASSERT_NE(CtorDIE, nullptr); + EXPECT_FALSE(CtorDIE->hasRewrittenInit()); + EXPECT_EQ(CtorDIE->getExpr(), CtorDIE->getField()->getInClassInitializer()); +}