[clang] a3d4579 - [clang][Sema] Separate aggregate default member initializer evaluation (#219288)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Sep 17 10:57:08 PDT 2026
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 <yronglin777 at gmail.com>
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());
+}
More information about the cfe-commits
mailing list