[clang] [clang][Sema] Separate aggregate default member initializer evaluation (PR #219288)
Yihan Wang via cfe-commits
cfe-commits at lists.llvm.org
Thu Aug 27 13:58:26 PDT 2026
https://github.com/yronglin updated https://github.com/llvm/llvm-project/pull/219288
>From cd9fbd40edaaddd8d2c71060625b34ca06b4fd7d Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Thu, 27 Aug 2026 12:44:02 -0700
Subject: [PATCH 1/2] [clang][Sema] Separate aggregate default member
initializer evaluation
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.
Split the two building paths so aggregate initialization rebuilds the initializer in the surrounding evaluation context.
Signed-off-by: yronglin <yronglin777 at gmail.com>
---
clang/docs/ReleaseNotes.md | 4 +
clang/include/clang/AST/ParentMap.h | 31 +++
clang/include/clang/Sema/Sema.h | 23 +-
clang/lib/AST/ByteCode/Compiler.cpp | 64 +++++-
clang/lib/AST/ParentMap.cpp | 17 ++
clang/lib/Analysis/CFG.cpp | 55 ++++-
clang/lib/Analysis/ReachableCode.cpp | 67 ++----
clang/lib/Sema/SemaDeclCXX.cpp | 10 +-
clang/lib/Sema/SemaExpr.cpp | 210 +++++++++++-------
clang/lib/Sema/SemaInit.cpp | 28 +--
clang/lib/Sema/TreeTransform.h | 2 +-
clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 57 +++--
clang/test/AST/ByteCode/records.cpp | 23 +-
clang/test/AST/ast-dump-default-init.cpp | 67 +++---
clang/test/AST/ast-dump-recovery.cpp | 2 +-
.../Analysis/lifetime-extended-regions.cpp | 7 +-
.../aggregate-default-member-initializers.cpp | 42 ++++
.../aggregate-default-member-initializers.cpp | 103 +++++++++
clang/test/SemaCXX/cxx2c-placeholder-vars.cpp | 8 +-
clang/test/SemaCXX/warn-unreachable.cpp | 104 +++++++++
.../UncheckedOptionalAccessModelTest.cpp | 18 ++
21 files changed, 708 insertions(+), 234 deletions(-)
create mode 100644 clang/test/CodeGenCXX/aggregate-default-member-initializers.cpp
create mode 100644 clang/test/SemaCXX/aggregate-default-member-initializers.cpp
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/AST/ParentMap.h b/clang/include/clang/AST/ParentMap.h
index 86e2f048a3445..5853a0be0a483 100644
--- a/clang/include/clang/AST/ParentMap.h
+++ b/clang/include/clang/AST/ParentMap.h
@@ -13,6 +13,8 @@
#ifndef LLVM_CLANG_AST_PARENTMAP_H
#define LLVM_CLANG_AST_PARENTMAP_H
+#include "llvm/Support/Casting.h"
+
namespace clang {
class Stmt;
class Expr;
@@ -39,6 +41,25 @@ class ParentMap {
Stmt *getParentIgnoreParenImpCasts(Stmt *) const;
Stmt *getOuterParenParent(Stmt *) const;
+ template <typename... Ts> Stmt *getOuterMostAncestor(Stmt *S) const {
+ Stmt *Res = nullptr;
+ while (S) {
+ if (llvm::isa<Ts...>(S))
+ Res = S;
+ S = getParent(S);
+ }
+ return Res;
+ }
+
+ template <typename... Ts> Stmt *getInnerMostAncestor(Stmt *S) const {
+ while (S) {
+ if (llvm::isa<Ts...>(S))
+ return S;
+ S = getParent(S);
+ }
+ return nullptr;
+ }
+
const Stmt *getParent(const Stmt* S) const {
return getParent(const_cast<Stmt*>(S));
}
@@ -51,6 +72,16 @@ class ParentMap {
return getParentIgnoreParenCasts(const_cast<Stmt*>(S));
}
+ template <typename... Ts>
+ const Stmt *getOuterMostAncestor(const Stmt *S) const {
+ return getOuterMostAncestor<Ts...>(const_cast<Stmt *>(S));
+ }
+
+ template <typename... Ts>
+ const Stmt *getInnerMostAncestor(const Stmt *S) const {
+ return getInnerMostAncestor<Ts...>(const_cast<Stmt *>(S));
+ }
+
bool hasParent(const Stmt *S) const { return getParent(S) != nullptr; }
bool isConsumedExpr(Expr *E) const;
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/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index c182639ea07f8..6b02a57a0deda 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -50,6 +50,22 @@ static bool isSideEffectFree(const Expr *E) {
return false;
}
+static bool containsDefaultInitExpr(const Expr *E) {
+ class Finder final : public ConstDynamicRecursiveASTVisitor {
+ public:
+ Finder() { ShouldVisitImplicitCode = true; }
+
+ bool VisitCXXDefaultInitExpr(const CXXDefaultInitExpr *) override {
+ Found = true;
+ return true;
+ }
+
+ bool Found = false;
+ } F;
+ F.TraverseStmt(E);
+ return F.Found;
+}
+
/// Scope chain managing the variable lifetimes.
template <class Emitter> class VariableScope {
public:
@@ -265,7 +281,9 @@ template <class Emitter> class InitStackScope final {
public:
InitStackScope(Compiler<Emitter> *Ctx, bool Active)
: Ctx(Ctx), OldValue(Ctx->InitStackActive), Active(Active) {
- Ctx->InitStackActive = Active;
+ // An explicit initializer nested in a default member initializer still
+ // needs the surrounding default initializer's `this` reconstruction.
+ Ctx->InitStackActive = OldValue || Active;
if (Active)
Ctx->InitStack.push_back(InitLink::DIE());
}
@@ -3475,6 +3493,18 @@ bool Compiler<Emitter>::VisitExprWithCleanups(const ExprWithCleanups *E) {
LocalScope<Emitter> ES(this, ScopeKind::FullExpression);
const Expr *SubExpr = E->getSubExpr();
+ if (DiscardResult && !SubExpr->isGLValue() &&
+ !canClassify(SubExpr->getType()) && containsDefaultInitExpr(SubExpr)) {
+ UnsignedOrNone LocalIndex =
+ allocateLocal(SubExpr, QualType(), ScopeKind::FullExpression);
+ if (!LocalIndex)
+ return false;
+ InitLinkScope<Emitter> ILS(this, InitLink::Temp(*LocalIndex));
+ if (!this->emitGetPtrLocal(*LocalIndex, E))
+ return false;
+ return this->visitInitializerPop(SubExpr) && ES.destroyLocals(E);
+ }
+
return this->delegate(SubExpr) && ES.destroyLocals(E);
}
@@ -3541,8 +3571,12 @@ bool Compiler<Emitter>::VisitMaterializeTemporaryExpr(
// Non-primitive values.
if (!this->emitGetPtrGlobal(*GlobalIndex, E))
return false;
+ if (!this->emitStartInit(E))
+ return false;
if (!this->visitInitializer(Inner))
return false;
+ if (!this->emitEndInit(E))
+ return false;
if (IsStatic) {
assert(TempDecl);
return this->emitInitGlobalTempComp(TempDecl, E);
@@ -3585,7 +3619,11 @@ bool Compiler<Emitter>::VisitMaterializeTemporaryExpr(
if (!this->emitGetPtrLocal(*LocalIndex, E))
return false;
- return this->visitInitializer(Inner);
+ if (!this->emitStartInit(E))
+ return false;
+ if (!this->visitInitializer(Inner))
+ return false;
+ return this->emitEndInit(E);
}
return false;
}
@@ -4888,6 +4926,22 @@ bool Compiler<Emitter>::VisitStmtExpr(const StmtExpr *E) {
}
template <class Emitter> bool Compiler<Emitter>::discard(const Expr *E) {
+ // A discarded composite prvalue still needs a result object when a default
+ // member initializer refers to previously initialized subobjects. Let an
+ // ExprWithCleanups establish its full-expression scope before allocating
+ // that object.
+ if (!isa<ExprWithCleanups>(E) && !E->isGLValue() &&
+ !canClassify(E->getType()) && containsDefaultInitExpr(E)) {
+ UnsignedOrNone LocalIndex =
+ allocateLocal(E, QualType(), ScopeKind::FullExpression);
+ if (!LocalIndex)
+ return false;
+ InitLinkScope<Emitter> ILS(this, InitLink::Temp(*LocalIndex));
+ if (!this->emitGetPtrLocal(*LocalIndex, E))
+ return false;
+ return this->visitInitializerPop(E);
+ }
+
OptionScope<Emitter> Scope(this, /*NewDiscardResult=*/true,
/*NewInitializing=*/false, /*ToLValue=*/false);
return this->Visit(E);
@@ -5451,8 +5505,14 @@ bool Compiler<Emitter>::visitExpr(const Expr *E, bool DestroyToplevelScope) {
if (!this->emitGetPtrLocal(*LocalOffset, E))
return false;
+ // A const-qualified result object is writable while it is being
+ // initialized, just like an object evaluated through visitVarDecl().
+ if (!this->emitStartInit(E))
+ return false;
if (!visitInitializer(E))
return false;
+ if (!this->emitEndInit(E))
+ return false;
// We are destroying the locals AFTER the Ret op.
// The Ret op needs to copy the (alive) values, but the
// destructors may still turn the entire expression invalid.
diff --git a/clang/lib/AST/ParentMap.cpp b/clang/lib/AST/ParentMap.cpp
index e62e71bf5a514..580613b2618fb 100644
--- a/clang/lib/AST/ParentMap.cpp
+++ b/clang/lib/AST/ParentMap.cpp
@@ -13,6 +13,7 @@
#include "clang/AST/ParentMap.h"
#include "clang/AST/Decl.h"
#include "clang/AST/Expr.h"
+#include "clang/AST/ExprCXX.h"
#include "clang/AST/StmtObjC.h"
#include "llvm/ADT/DenseMap.h"
@@ -103,6 +104,22 @@ static void BuildParentMap(MapTy& M, Stmt* S,
BuildParentMap(M, SubStmt, OVMode);
}
break;
+ case Stmt::CXXDefaultArgExprClass:
+ if (auto *Arg = dyn_cast<CXXDefaultArgExpr>(S)) {
+ if (Arg->hasRewrittenInit()) {
+ M[Arg->getExpr()] = S;
+ BuildParentMap(M, Arg->getExpr(), OVMode);
+ }
+ }
+ break;
+ case Stmt::CXXDefaultInitExprClass:
+ if (auto *Init = dyn_cast<CXXDefaultInitExpr>(S)) {
+ if (Init->hasRewrittenInit()) {
+ M[Init->getExpr()] = S;
+ BuildParentMap(M, Init->getExpr(), OVMode);
+ }
+ }
+ break;
default:
for (Stmt *SubStmt : S->children()) {
if (SubStmt) {
diff --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp
index 5263114ebca28..f92e6f3dcbf47 100644
--- a/clang/lib/Analysis/CFG.cpp
+++ b/clang/lib/Analysis/CFG.cpp
@@ -581,6 +581,10 @@ class CFGBuilder {
private:
// Visitors to walk an AST and construct the CFG.
+ CFGBlock *VisitCXXDefaultArgExpr(CXXDefaultArgExpr *Default,
+ AddStmtChoice asc);
+ CFGBlock *VisitCXXDefaultInitExpr(CXXDefaultInitExpr *Default,
+ AddStmtChoice asc);
CFGBlock *VisitInitListExpr(InitListExpr *ILE, AddStmtChoice asc);
CFGBlock *VisitAddrLabelExpr(AddrLabelExpr *A, AddStmtChoice asc);
CFGBlock *VisitAttributedStmt(AttributedStmt *A, AddStmtChoice asc);
@@ -2405,16 +2409,10 @@ CFGBlock *CFGBuilder::Visit(Stmt * S, AddStmtChoice asc,
asc, ExternallyDestructed);
case Stmt::CXXDefaultArgExprClass:
+ return VisitCXXDefaultArgExpr(cast<CXXDefaultArgExpr>(S), asc);
+
case Stmt::CXXDefaultInitExprClass:
- // FIXME: The expression inside a CXXDefaultArgExpr is owned by the
- // called function's declaration, not by the caller. If we simply add
- // this expression to the CFG, we could end up with the same Expr
- // appearing multiple times (PR13385).
- //
- // It's likewise possible for multiple CXXDefaultInitExprs for the same
- // expression to be used in the same function (through aggregate
- // initialization).
- return VisitStmt(S, asc);
+ return VisitCXXDefaultInitExpr(cast<CXXDefaultInitExpr>(S), asc);
case Stmt::CXXBindTemporaryExprClass:
return VisitCXXBindTemporaryExpr(cast<CXXBindTemporaryExpr>(S), asc);
@@ -2597,6 +2595,45 @@ CFGBlock *CFGBuilder::VisitCallExprChildren(CallExpr *C) {
return VisitChildren(C);
}
+CFGBlock *CFGBuilder::VisitCXXDefaultArgExpr(CXXDefaultArgExpr *Arg,
+ AddStmtChoice asc) {
+ if (Arg->hasRewrittenInit()) {
+ if (asc.alwaysAdd(*this, Arg)) {
+ autoCreateBlock();
+ appendStmt(Block, Arg);
+ }
+ return VisitStmt(Arg->getExpr()->IgnoreParens(), asc);
+ }
+
+ // We can't add the default argument if it's not rewritten because the
+ // expression inside a CXXDefaultArgExpr is owned by the called function's
+ // declaration, not by the caller. We could end up with the same expression
+ // appearing multiple times.
+ return VisitStmt(Arg, asc);
+}
+
+CFGBlock *CFGBuilder::VisitCXXDefaultInitExpr(CXXDefaultInitExpr *Init,
+ AddStmtChoice asc) {
+ if (Init->hasRewrittenInit()) {
+ if (asc.alwaysAdd(*this, Init)) {
+ autoCreateBlock();
+ appendStmt(Block, Init);
+ }
+
+ // Unlike CXXDefaultArgExpr::getExpr, which strips off the top-level
+ // FullExpr and ConstantExpr, CXXDefaultInitExpr::getExpr does not do this,
+ // so the top level cannot be a ParenExpr. Use Visit rather than VisitStmt
+ // so that control flow inside the initializer (a conditional operator, for
+ // instance) is decomposed into blocks instead of being laid out linearly.
+ return Visit(Init->getExpr(), asc);
+ }
+
+ // We can't add the default initializer if it's not rewritten because multiple
+ // CXXDefaultInitExprs can refer to the same subexpression in the same
+ // function (through aggregate initialization).
+ return VisitStmt(Init, asc);
+}
+
CFGBlock *CFGBuilder::VisitInitListExpr(InitListExpr *ILE, AddStmtChoice asc) {
if (asc.alwaysAdd(*this, ILE)) {
autoCreateBlock();
diff --git a/clang/lib/Analysis/ReachableCode.cpp b/clang/lib/Analysis/ReachableCode.cpp
index 4a9ab5d9f0f73..7d17a0cb4cfd4 100644
--- a/clang/lib/Analysis/ReachableCode.cpp
+++ b/clang/lib/Analysis/ReachableCode.cpp
@@ -25,6 +25,7 @@
#include "clang/Basic/SourceManager.h"
#include "clang/Lex/Preprocessor.h"
#include "llvm/ADT/BitVector.h"
+#include <cstddef>
#include <optional>
using namespace clang;
@@ -396,6 +397,7 @@ namespace {
SmallVector<const CFGBlock *, 10> WorkList;
Preprocessor &PP;
ASTContext &C;
+ AnalysisDeclContext &AC;
typedef SmallVector<std::pair<const CFGBlock *, const Stmt *>, 12>
DeferredLocsTy;
@@ -403,10 +405,10 @@ namespace {
DeferredLocsTy DeferredLocs;
public:
- DeadCodeScan(llvm::BitVector &reachable, Preprocessor &PP, ASTContext &C)
- : Visited(reachable.size()),
- Reachable(reachable),
- PP(PP), C(C) {}
+ DeadCodeScan(llvm::BitVector &reachable, Preprocessor &PP,
+ AnalysisDeclContext &AC)
+ : Visited(reachable.size()), Reachable(reachable), PP(PP),
+ C(AC.getASTContext()), AC(AC) {}
void enqueue(const CFGBlock *block);
unsigned scanBackwards(const CFGBlock *Start,
@@ -453,47 +455,8 @@ bool DeadCodeScan::isDeadCodeRoot(const clang::CFGBlock *Block) {
return isDeadRoot;
}
-// Check if the given `DeadStmt` is a coroutine statement and is a substmt of
-// the coroutine statement. `Block` is the CFGBlock containing the `DeadStmt`.
-static bool isInCoroutineStmt(const Stmt *DeadStmt, const CFGBlock *Block) {
- // The coroutine statement, co_return, co_await, or co_yield.
- const Stmt *CoroStmt = nullptr;
- // Find the first coroutine statement after the DeadStmt in the block.
- bool AfterDeadStmt = false;
- for (const CFGElement &Elem : *Block)
- if (std::optional<CFGStmt> CS = Elem.getAs<CFGStmt>()) {
- const Stmt *S = CS->getStmt();
- if (S == DeadStmt)
- AfterDeadStmt = true;
- if (AfterDeadStmt &&
- // For simplicity, we only check simple coroutine statements.
- (llvm::isa<CoreturnStmt>(S) || llvm::isa<CoroutineSuspendExpr>(S))) {
- CoroStmt = S;
- break;
- }
- }
- if (!CoroStmt)
- return false;
- struct Checker : DynamicRecursiveASTVisitor {
- const Stmt *DeadStmt;
- bool CoroutineSubStmt = false;
- Checker(const Stmt *S) : DeadStmt(S) {
- // Statements captured in the CFG can be implicit.
- ShouldVisitImplicitCode = true;
- }
-
- bool VisitStmt(Stmt *S) override {
- if (S == DeadStmt)
- CoroutineSubStmt = true;
- return true;
- }
- };
- Checker checker(DeadStmt);
- checker.TraverseStmt(const_cast<Stmt *>(CoroStmt));
- return checker.CoroutineSubStmt;
-}
-
-static bool isValidDeadStmt(const Stmt *S, const clang::CFGBlock *Block) {
+static bool isValidDeadStmt(ParentMap &PM, const Stmt *S,
+ const clang::CFGBlock *) {
if (S->getBeginLoc().isInvalid())
return false;
if (const BinaryOperator *BO = dyn_cast<BinaryOperator>(S))
@@ -501,21 +464,27 @@ static bool isValidDeadStmt(const Stmt *S, const clang::CFGBlock *Block) {
// Coroutine statements are never considered dead statements, because removing
// them may change the function semantic if it is the only coroutine statement
// of the coroutine.
- return !isInCoroutineStmt(S, Block);
+ return !PM.getInnerMostAncestor<CoreturnStmt, CoroutineSuspendExpr>(S);
}
const Stmt *DeadCodeScan::findDeadCode(const clang::CFGBlock *Block) {
+ auto &PM = AC.getParentMap();
+
for (CFGBlock::const_iterator I = Block->begin(), E = Block->end(); I!=E; ++I)
if (std::optional<CFGStmt> CS = I->getAs<CFGStmt>()) {
const Stmt *S = CS->getStmt();
- if (isValidDeadStmt(S, Block))
+ auto *RewrittenParent =
+ PM.getOuterMostAncestor<CXXDefaultArgExpr, CXXDefaultInitExpr>(S);
+ if (RewrittenParent)
+ S = RewrittenParent;
+ if (isValidDeadStmt(AC.getParentMap(), S, Block))
return S;
}
CFGTerminator T = Block->getTerminator();
if (T.isStmtBranch()) {
const Stmt *S = T.getStmt();
- if (S && isValidDeadStmt(S, Block))
+ if (S && isValidDeadStmt(AC.getParentMap(), S, Block))
return S;
}
@@ -761,7 +730,7 @@ void FindUnreachableCode(AnalysisDeclContext &AC, Preprocessor &PP,
if (reachable[block->getBlockID()])
continue;
- DeadCodeScan DS(reachable, PP, AC.getASTContext());
+ DeadCodeScan DS(reachable, PP, AC);
numReachable += DS.scanBackwards(block, CB);
if (numReachable == cfg->getNumBlockIDs())
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..31b308ad4b52b 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<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
@@ -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<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();
@@ -5955,52 +5967,92 @@ 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.
- //
- // 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())
+ bool NestedDefaultChecking = isCheckingDefaultArgumentOrInitializer();
+ bool NeedRebuild = needsRebuildOfDefaultArgOrInit();
+
+ // 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);
+
+ ExprResult Init = BuildCXXDefaultInitInternal(
+ Loc, Field,
+ InitializedEntity::InitializeMemberFromDefaultMemberInitializer(Field),
+ NestedDefaultChecking, NeedRebuild);
+ 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);
+
+ // 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..83c125cebde5d 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -3521,7 +3521,7 @@ class TreeTransform {
/// routine to provide different behavior.
ExprResult RebuildCXXDefaultInitExpr(SourceLocation Loc,
FieldDecl *Field) {
- return getSema().BuildCXXDefaultInitExpr(Loc, Field);
+ return getSema().BuildCXXCtorDefaultInitExpr(Loc, Field);
}
/// Build a new C++ zero-initialization expression.
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
index 530fae9ee2dee..e2f30968e02ff 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -1958,32 +1958,47 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
ExplodedNodeSet Tmp;
- const Expr *ArgE;
- if (const auto *DefE = dyn_cast<CXXDefaultArgExpr>(S))
+ bool HasRebuiltInit = false;
+ const Expr *ArgE = nullptr;
+ if (const auto *DefE = dyn_cast<CXXDefaultArgExpr>(S)) {
ArgE = DefE->getExpr();
- else if (const auto *DefE = dyn_cast<CXXDefaultInitExpr>(S))
+ HasRebuiltInit = DefE->hasRewrittenInit();
+ } else if (const auto *DefE = dyn_cast<CXXDefaultInitExpr>(S)) {
ArgE = DefE->getExpr();
- else
+ HasRebuiltInit = DefE->hasRewrittenInit();
+ } else
llvm_unreachable("unknown constant wrapper kind");
- bool IsTemporary = false;
- if (const auto *MTE = dyn_cast<MaterializeTemporaryExpr>(ArgE)) {
- ArgE = MTE->getSubExpr();
- IsTemporary = true;
- }
+ if (HasRebuiltInit) {
+ for (const auto N : PreVisit) {
+ const StackFrame *SF = N->getStackFrame();
+ ProgramStateRef State = N->getState();
+ State = State->BindExpr(cast<Expr>(S), SF, State->getSVal(ArgE, SF));
+ Tmp.insert(Engine.makePostStmtNode(S, State, N));
+ }
+ } else {
+ // If it's not rewritten, the contents of these expressions are not
+ // actually part of the current function, so we fall back to constant
+ // evaluation.
+ bool IsTemporary = false;
+ if (const auto *MTE = dyn_cast<MaterializeTemporaryExpr>(ArgE)) {
+ ArgE = MTE->getSubExpr();
+ IsTemporary = true;
+ }
- std::optional<SVal> ConstantVal = svalBuilder.getConstantVal(ArgE);
- if (!ConstantVal)
- ConstantVal = UnknownVal();
-
- const StackFrame *SF = Pred->getStackFrame();
- for (const auto I : PreVisit) {
- ProgramStateRef State = I->getState();
- State = State->BindExpr(cast<Expr>(S), SF, *ConstantVal);
- if (IsTemporary)
- State = createTemporaryRegionIfNeeded(State, SF, cast<Expr>(S),
- cast<Expr>(S));
- Tmp.insert(Engine.makePostStmtNode(S, State, I));
+ std::optional<SVal> ConstantVal = svalBuilder.getConstantVal(ArgE);
+ if (!ConstantVal)
+ ConstantVal = UnknownVal();
+
+ for (const auto I : PreVisit) {
+ const StackFrame *SF = I->getStackFrame();
+ ProgramStateRef State = I->getState();
+ State = State->BindExpr(cast<Expr>(S), SF, *ConstantVal);
+ if (IsTemporary)
+ State = createTemporaryRegionIfNeeded(State, SF, cast<Expr>(S),
+ cast<Expr>(S));
+ Tmp.insert(Engine.makePostStmtNode(S, State, I));
+ }
}
getCheckerManager().runCheckersForPostStmt(Dst, Tmp, S, *this);
diff --git a/clang/test/AST/ByteCode/records.cpp b/clang/test/AST/ByteCode/records.cpp
index 36b5cb62fe95f..f70c7f641120a 100644
--- a/clang/test/AST/ByteCode/records.cpp
+++ b/clang/test/AST/ByteCode/records.cpp
@@ -1302,11 +1302,30 @@ namespace {
};
constexpr int a() {
int x = 1;
- int f = B{x}.x;
+ {
+ B b{x};
+ }
+ return x;
+ }
+ static_assert(a() == 0);
+
+ constexpr int discarded() {
+ int x = 1;
B{x}; // both-warning {{expression result unused}}
+ return x;
+ }
+ static_assert(discarded() == 0);
- return 1;
+ /// A const-qualified composite result is writable while under construction.
+ constexpr int decrement(int &x) {
+ return --x;
}
+ struct DMIConstComposite {
+ int a;
+ int b = decrement(a);
+ };
+ constexpr DMIConstComposite c{1};
+ static_assert(c.a == 0);
}
#endif
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..02a1210d9af92 100644
--- a/clang/test/Analysis/lifetime-extended-regions.cpp
+++ b/clang/test/Analysis/lifetime-extended-regions.cpp
@@ -121,11 +121,10 @@ 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]+}}} }}
+ // The lifetime of object bound to reference members of aggregates,
+ // that are created from default member initializer was extended.
RefAggregate defaultInitExtended{i};
- clang_analyzer_dump(defaultInitExtended.ry); // expected-warning {{Unknown }}
+ clang_analyzer_dump(defaultInitExtended.ry); // expected-warning-re {{&lifetime_extended_object{Composite, defaultInitExtended, 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..40758d33de4b0
--- /dev/null
+++ b/clang/test/CodeGenCXX/aggregate-default-member-initializers.cpp
@@ -0,0 +1,42 @@
+// RUN: %clang_cc1 -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..fb3db54cf1d91
--- /dev/null
+++ b/clang/test/SemaCXX/aggregate-default-member-initializers.cpp
@@ -0,0 +1,103 @@
+// RUN: %clang_cc1 -std=c++20 -Wno-unused-value -verify %s
+// RUN: %clang_cc1 -std=c++23 -Wno-unused-value -verify %s
+// RUN: %clang_cc1 -std=c++20 -Wno-unused-value -verify %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;
+ const A &a = A{x};
+};
+
+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);
+static_assert(two() == 0);
+static_assert(paren() == 0);
+
+} // 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;
+ }
+ // 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}}
+};
+
+struct Outer {
+ const Inner &inner = Inner{1}; // expected-note {{in the default initializer of 'b'}}
+};
+
+constexpr int value = Outer{}.inner.a;
+static_assert(value == 0);
+
+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/test/SemaCXX/warn-unreachable.cpp b/clang/test/SemaCXX/warn-unreachable.cpp
index e6f5bc5ef8e12..79505a2427426 100644
--- a/clang/test/SemaCXX/warn-unreachable.cpp
+++ b/clang/test/SemaCXX/warn-unreachable.cpp
@@ -414,3 +414,107 @@ void tautological_compare(bool x, int y) {
calledFun();
}
+
+namespace test_rebuilt_default_arg {
+struct A {
+ explicit A(int = __builtin_LINE());
+};
+
+int h(int a) {
+ return 3;
+ A(); // expected-warning {{will never be executed}}
+}
+
+struct Temp {
+ Temp();
+ ~Temp();
+};
+
+struct B {
+ explicit B(const Temp &t = Temp());
+};
+int f(int a) {
+ return 3;
+ B(); // expected-warning {{will never be executed}}
+}
+} // namespace test_rebuilt_default_arg
+namespace test_rebuilt_default_init {
+
+struct A {
+ A();
+ ~A();
+};
+
+struct B {
+ const A &t = A();
+};
+int f(int a) {
+ return 3;
+ A{}; // expected-warning {{will never be executed}}
+}
+} // namespace test_rebuilt_default_init
+
+// This issue reported by the comments in https://github.com/llvm/llvm-project/pull/117437.
+// All block-level expressions should have already been IgnoreParens()ed.
+namespace gh117437_ignore_parens_in_default_arg {
+ class Location {
+ public:
+ static Location Current(int = __builtin_LINE());
+ };
+ class DOMMatrix;
+ class BasicMember {
+ public:
+ BasicMember(DOMMatrix *);
+ };
+ template <typename> using Member = BasicMember;
+ class ExceptionState {
+ public:
+ ExceptionState &ReturnThis();
+ ExceptionState(Location);
+ };
+ class NonThrowableExceptionState : public ExceptionState {
+ public:
+ NonThrowableExceptionState(Location location = Location::Current())
+ : ExceptionState(location) {}
+ };
+ class DOMMatrix {
+ public:
+ static DOMMatrix *
+ Create(int *, ExceptionState & = (NonThrowableExceptionState().ReturnThis()));
+ };
+ class CSSMatrixComponent {
+ int CSSMatrixComponent_matrix;
+ CSSMatrixComponent()
+ : matrix_(DOMMatrix::Create(&CSSMatrixComponent_matrix)) {}
+ Member<DOMMatrix> matrix_;
+ };
+} // namespace gh117437_ignore_parens_in_default_arg
+
+class Location {
+ public:
+ static Location CurrentWithoutFunctionName(
+ const char* file_name = __builtin_FILE(),
+ int line_number = __builtin_LINE());
+ };
+
+ class NotReachedNoreturnError {
+ public:
+ explicit NotReachedNoreturnError(
+ const Location& location =
+ Location::CurrentWithoutFunctionName());
+
+ [[noreturn]] [[clang::nomerge]] [[clang::noinline]] [[clang::not_tail_called]]
+ ~NotReachedNoreturnError();
+ };
+
+ #define NOTREACHED() NotReachedNoreturnError()
+
+ int f() {
+ NOTREACHED();
+ return 4; // expected-warning {{will never be executed}}
+ }
+
+ int g() {
+ return 4;
+ NOTREACHED(); // Ok, no diagnostic.
+ }
diff --git a/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp
index f34568a072c32..76cab4618c5e6 100644
--- a/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp
+++ b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp
@@ -2950,6 +2950,24 @@ TEST_P(UncheckedOptionalAccessTest, ConstructorOtherStructField) {
)cc");
}
+TEST_P(UncheckedOptionalAccessTest,
+ AggregateDefaultInitializerReferencesPriorField) {
+ ExpectDiagnosticsFor(R"cc(
+ #include "unchecked_optional_access_test.h"
+ struct NonTrivDtor {
+ NonTrivDtor(int x);
+ ~NonTrivDtor() {}
+ };
+ struct Other {
+ $ns::$optional<int> x = $ns::nullopt;
+ NonTrivDtor y = x.has_value() ? NonTrivDtor(*x) : NonTrivDtor(-1);
+ };
+ struct target {
+ target() { Other{}; }
+ };
+ )cc");
+}
+
TEST_P(UncheckedOptionalAccessTest, AssertTrueGtestMacro) {
ExpectDiagnosticsFor(R"cc(
#include "unchecked_optional_access_test.h"
>From 8c9405378f2e75a60948af441e5113d719dd6a71 Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Thu, 27 Aug 2026 13:57:57 -0700
Subject: [PATCH 2/2] Merge duplicated code
Signed-off-by: yronglin <yronglin777 at gmail.com>
---
clang/lib/AST/ByteCode/Compiler.cpp | 50 ++++++++++++++---------------
clang/lib/AST/ByteCode/Compiler.h | 6 ++++
2 files changed, 30 insertions(+), 26 deletions(-)
diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index 6b02a57a0deda..cf4945a3f2ff6 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -3493,17 +3493,8 @@ bool Compiler<Emitter>::VisitExprWithCleanups(const ExprWithCleanups *E) {
LocalScope<Emitter> ES(this, ScopeKind::FullExpression);
const Expr *SubExpr = E->getSubExpr();
- if (DiscardResult && !SubExpr->isGLValue() &&
- !canClassify(SubExpr->getType()) && containsDefaultInitExpr(SubExpr)) {
- UnsignedOrNone LocalIndex =
- allocateLocal(SubExpr, QualType(), ScopeKind::FullExpression);
- if (!LocalIndex)
- return false;
- InitLinkScope<Emitter> ILS(this, InitLink::Temp(*LocalIndex));
- if (!this->emitGetPtrLocal(*LocalIndex, E))
- return false;
- return this->visitInitializerPop(SubExpr) && ES.destroyLocals(E);
- }
+ if (DiscardResult && this->discardNeedsResultObject(SubExpr))
+ return this->discardIntoResultObject(SubExpr) && ES.destroyLocals(E);
return this->delegate(SubExpr) && ES.destroyLocals(E);
}
@@ -4925,22 +4916,29 @@ bool Compiler<Emitter>::VisitStmtExpr(const StmtExpr *E) {
return BS.destroyLocals();
}
+template <class Emitter>
+bool Compiler<Emitter>::discardNeedsResultObject(const Expr *E) const {
+ return !E->isGLValue() && !canClassify(E->getType()) &&
+ containsDefaultInitExpr(E);
+}
+
+template <class Emitter>
+bool Compiler<Emitter>::discardIntoResultObject(const Expr *E) {
+ UnsignedOrNone LocalIndex =
+ allocateLocal(E, QualType(), ScopeKind::FullExpression);
+ if (!LocalIndex)
+ return false;
+ InitLinkScope<Emitter> ILS(this, InitLink::Temp(*LocalIndex));
+ if (!this->emitGetPtrLocal(*LocalIndex, E))
+ return false;
+ return this->visitInitializerPop(E);
+}
+
template <class Emitter> bool Compiler<Emitter>::discard(const Expr *E) {
- // A discarded composite prvalue still needs a result object when a default
- // member initializer refers to previously initialized subobjects. Let an
- // ExprWithCleanups establish its full-expression scope before allocating
- // that object.
- if (!isa<ExprWithCleanups>(E) && !E->isGLValue() &&
- !canClassify(E->getType()) && containsDefaultInitExpr(E)) {
- UnsignedOrNone LocalIndex =
- allocateLocal(E, QualType(), ScopeKind::FullExpression);
- if (!LocalIndex)
- return false;
- InitLinkScope<Emitter> ILS(this, InitLink::Temp(*LocalIndex));
- if (!this->emitGetPtrLocal(*LocalIndex, E))
- return false;
- return this->visitInitializerPop(E);
- }
+ // Let an ExprWithCleanups establish its full-expression scope first; it
+ // allocates the result object itself.
+ if (!isa<ExprWithCleanups>(E) && this->discardNeedsResultObject(E))
+ return this->discardIntoResultObject(E);
OptionScope<Emitter> Scope(this, /*NewDiscardResult=*/true,
/*NewInitializing=*/false, /*ToLValue=*/false);
diff --git a/clang/lib/AST/ByteCode/Compiler.h b/clang/lib/AST/ByteCode/Compiler.h
index f34809cd0f14c..6e22f57749e43 100644
--- a/clang/lib/AST/ByteCode/Compiler.h
+++ b/clang/lib/AST/ByteCode/Compiler.h
@@ -321,6 +321,12 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>,
bool visitAsLValue(const Expr *E);
/// Evaluates an expression for side effects and discards the result.
bool discard(const Expr *E);
+ /// Whether discarding \p E still requires a result object: a composite
+ /// prvalue whose default member initializer may refer to previously
+ /// initialized subobjects, so that `this` has something to denote.
+ bool discardNeedsResultObject(const Expr *E) const;
+ /// Allocate that result object and initialize \p E into it.
+ bool discardIntoResultObject(const Expr *E);
/// Just pass evaluation on to \p E. This leaves all the parsing flags
/// intact.
bool delegate(const Expr *E);
More information about the cfe-commits
mailing list