Author: Yihan Wang
Date: 2026-09-18T01:57:02+08:00
New Revision: a3d4579b451804383c49789f963b51631d4b38c7

URL: 
https://github.com/llvm/llvm-project/commit/a3d4579b451804383c49789f963b51631d4b38c7
DIFF: 
https://github.com/llvm/llvm-project/commit/a3d4579b451804383c49789f963b51631d4b38c7.diff

LOG: [clang][Sema] Separate aggregate default member initializer evaluation 
(#219288)

A default member initializer used by a constructor is a separate
full-expression, while one used during aggregate initialization belongs
to the full-expression containing the aggregate initialization.

This patch split the two building paths so aggregate initialization
rebuilds the initializer in the surrounding evaluation context.

Fixes https://github.com/llvm/llvm-project/issues/85601.

---------

Signed-off-by: yronglin <[email protected]>

Added: 
    clang/test/CodeGenCXX/aggregate-default-member-initializers.cpp
    clang/test/SemaCXX/aggregate-default-member-initializers.cpp

Modified: 
    clang/docs/ReleaseNotes.md
    clang/include/clang/Sema/Sema.h
    clang/lib/Sema/SemaDeclCXX.cpp
    clang/lib/Sema/SemaExpr.cpp
    clang/lib/Sema/SemaInit.cpp
    clang/lib/Sema/TreeTransform.h
    clang/test/AST/ast-dump-default-init.cpp
    clang/test/AST/ast-dump-recovery.cpp
    clang/test/Analysis/lifetime-extended-regions.cpp
    clang/test/SemaCXX/cxx2c-placeholder-vars.cpp
    clang/unittests/AST/ASTExprTest.cpp

Removed: 
    


################################################################################
diff  --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index d930e0a9cea9c..f4a34a37aff52 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -580,6 +580,9 @@ 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 false-positive module ODR diagnostics when a type is found through a
   using-declaration in one definition and directly in another. ODR hashing also
   now distinguishes 
diff erently qualified uses of types found through

diff  --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 0864337a9374c..5becfc9fae152 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -5550,6 +5550,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.
@@ -7719,7 +7723,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 ea628f29d8a00..02b4c347dee09 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -4240,6 +4240,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(),
@@ -5340,7 +5346,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;
 
@@ -14124,7 +14130,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 9e5f6a609bb50..eace65a424032 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -5918,39 +5918,51 @@ static FieldDecl 
*FindFieldDeclInstantiationPattern(const ASTContext &Ctx,
   return cast<FieldDecl>(*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<CXXRecordDecl>(Field->getParent());
 
-  std::optional<ExpressionEvaluationContextRecord::InitializationContext>
-      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
@@ -5969,27 +5981,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<ExprWithCleanups>(Field->getInClassInitializer());
-  if (Field->getInClassInitializer() &&
-      !Field->getInClassInitializer()->containsErrors() &&
+  bool ContainsAnyTemporaries = isa<ExprWithCleanups>(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();
@@ -5997,52 +6009,106 @@ 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();
+
+  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 1ff66e0d927df..3f30e94aa8976 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -815,28 +815,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 {
@@ -6154,11 +6141,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 c8458fda58a88..e482d15f31cc5 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -3533,9 +3533,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 
diff erent 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.
@@ -15238,11 +15239,23 @@ 
TreeTransform<Derived>::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<typename Derived>

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' <NoOp>
-// CHECK-NEXT:        `-CXXFunctionalCastExpr 0x{{[^ ]*}} <{{.*}}> 'A' 
functional cast to A <NoOp>
-// 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' <NoOp>
+// CHECK-NEXT:      `-CXXFunctionalCastExpr 0x{{[^ ]*}} <{{.*}}> 'A' 
functional cast to A <NoOp>
+// 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 <typename T> int noInstantiate() {
+  static_assert(false);
+  return 0;
+}
+
+struct S {
+  int x = noInstantiate<int>();
+};
+
+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<const CXXDefaultInitExpr *> 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());
+}


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to