Skip to content

Commit 45c8766

Browse files
authored
Reapply "[Clang][CWG1815] Support lifetime extension of temporary created by aggregate initialization using a default member initializer" (#97308)
The PR reapply #92527. Implemented CWG1815 and fixed the bugs mentioned in the comments of #92527 and #87933. The reason why the original PR was reverted was that errors might occur during the rebuild. --------- Signed-off-by: yronglin <[email protected]>
1 parent dec0781 commit 45c8766

20 files changed

+251
-87
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ C++ Language Changes
108108
- Allow single element access of GCC vector/ext_vector_type object to be
109109
constant expression. Supports the `V.xyzw` syntax and other tidbits
110110
as seen in OpenCL. Selecting multiple elements is left as a future work.
111+
- Implement `CWG1815 <https://wg21.link/CWG1815>`_. Support lifetime extension
112+
of temporary created by aggregate initialization using a default member
113+
initializer.
111114

112115
- Accept C++26 user-defined ``static_assert`` messages in C++11 as an extension.
113116

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10159,13 +10159,6 @@ def warn_dangling_pointer_assignment : Warning<
1015910159
"will be destroyed at the end of the full-expression">,
1016010160
InGroup<DanglingAssignment>;
1016110161

10162-
def warn_unsupported_lifetime_extension : Warning<
10163-
"lifetime extension of "
10164-
"%select{temporary|backing array of initializer list}0 created "
10165-
"by aggregate initialization using a default member initializer "
10166-
"is not yet supported; lifetime of %select{temporary|backing array}0 "
10167-
"will end at the end of the full-expression">, InGroup<Dangling>;
10168-
1016910162
// For non-floating point, expressions of the form x == x or x != x
1017010163
// should result in a warning, since these always evaluate to a constant.
1017110164
// Array comparisons have similar warnings

clang/include/clang/Sema/Sema.h

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6403,6 +6403,9 @@ class Sema final : public SemaBase {
64036403
/// example, in a for-range initializer).
64046404
bool InLifetimeExtendingContext = false;
64056405

6406+
/// Whether we should rebuild CXXDefaultArgExpr and CXXDefaultInitExpr.
6407+
bool RebuildDefaultArgOrDefaultInit = false;
6408+
64066409
// When evaluating immediate functions in the initializer of a default
64076410
// argument or default member initializer, this is the declaration whose
64086411
// default initializer is being evaluated and the location of the call
@@ -7810,9 +7813,11 @@ class Sema final : public SemaBase {
78107813
}
78117814

78127815
bool isInLifetimeExtendingContext() const {
7813-
assert(!ExprEvalContexts.empty() &&
7814-
"Must be in an expression evaluation context");
7815-
return ExprEvalContexts.back().InLifetimeExtendingContext;
7816+
return currentEvaluationContext().InLifetimeExtendingContext;
7817+
}
7818+
7819+
bool needRebuildDefaultArgOrInit() const {
7820+
return currentEvaluationContext().RebuildDefaultArgOrDefaultInit;
78167821
}
78177822

78187823
bool isCheckingDefaultArgumentOrInitializer() const {
@@ -7854,18 +7859,6 @@ class Sema final : public SemaBase {
78547859
return Res;
78557860
}
78567861

7857-
/// keepInLifetimeExtendingContext - Pull down InLifetimeExtendingContext
7858-
/// flag from previous context.
7859-
void keepInLifetimeExtendingContext() {
7860-
if (ExprEvalContexts.size() > 2 &&
7861-
parentEvaluationContext().InLifetimeExtendingContext) {
7862-
auto &LastRecord = ExprEvalContexts.back();
7863-
auto &PrevRecord = parentEvaluationContext();
7864-
LastRecord.InLifetimeExtendingContext =
7865-
PrevRecord.InLifetimeExtendingContext;
7866-
}
7867-
}
7868-
78697862
DefaultedComparisonKind getDefaultedComparisonKind(const FunctionDecl *FD) {
78707863
return getDefaultedFunctionKind(FD).asComparison();
78717864
}

clang/lib/Parse/ParseDecl.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2509,8 +2509,9 @@ Parser::DeclGroupPtrTy Parser::ParseDeclGroup(ParsingDeclSpec &DS,
25092509

25102510
// P2718R0 - Lifetime extension in range-based for loops.
25112511
if (getLangOpts().CPlusPlus23) {
2512-
auto &LastRecord = Actions.ExprEvalContexts.back();
2512+
auto &LastRecord = Actions.currentEvaluationContext();
25132513
LastRecord.InLifetimeExtendingContext = true;
2514+
LastRecord.RebuildDefaultArgOrDefaultInit = true;
25142515
}
25152516

25162517
if (getLangOpts().OpenMP)

clang/lib/Sema/CheckExprLifetime.cpp

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -871,11 +871,6 @@ static void visitLocalsRetainedByInitializer(IndirectLocalPath &Path,
871871
enum PathLifetimeKind {
872872
/// Lifetime-extend along this path.
873873
Extend,
874-
/// We should lifetime-extend, but we don't because (due to technical
875-
/// limitations) we can't. This happens for default member initializers,
876-
/// which we don't clone for every use, so we don't have a unique
877-
/// MaterializeTemporaryExpr to update.
878-
ShouldExtend,
879874
/// Do not lifetime extend along this path.
880875
NoExtend
881876
};
@@ -887,7 +882,7 @@ shouldLifetimeExtendThroughPath(const IndirectLocalPath &Path) {
887882
PathLifetimeKind Kind = PathLifetimeKind::Extend;
888883
for (auto Elem : Path) {
889884
if (Elem.Kind == IndirectLocalPathEntry::DefaultInit)
890-
Kind = PathLifetimeKind::ShouldExtend;
885+
return PathLifetimeKind::Extend;
891886
else if (Elem.Kind != IndirectLocalPathEntry::LambdaCaptureInit)
892887
return PathLifetimeKind::NoExtend;
893888
}
@@ -1034,17 +1029,6 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
10341029
// Also visit the temporaries lifetime-extended by this initializer.
10351030
return true;
10361031

1037-
case PathLifetimeKind::ShouldExtend:
1038-
// We're supposed to lifetime-extend the temporary along this path (per
1039-
// the resolution of DR1815), but we don't support that yet.
1040-
//
1041-
// FIXME: Properly handle this situation. Perhaps the easiest approach
1042-
// would be to clone the initializer expression on each use that would
1043-
// lifetime extend its temporaries.
1044-
SemaRef.Diag(DiagLoc, diag::warn_unsupported_lifetime_extension)
1045-
<< RK << DiagRange;
1046-
break;
1047-
10481032
case PathLifetimeKind::NoExtend:
10491033
// If the path goes through the initialization of a variable or field,
10501034
// it can't possibly reach a temporary created in this full-expression.

clang/lib/Sema/SemaExpr.cpp

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5429,6 +5429,8 @@ struct EnsureImmediateInvocationInDefaultArgs
54295429
EnsureImmediateInvocationInDefaultArgs(Sema &SemaRef)
54305430
: TreeTransform(SemaRef) {}
54315431

5432+
bool AlwaysRebuild() { return true; }
5433+
54325434
// Lambda can only have immediate invocations in the default
54335435
// args of their parameters, which is transformed upon calling the closure.
54345436
// The body is not a subexpression, so we have nothing to do.
@@ -5470,7 +5472,7 @@ ExprResult Sema::BuildCXXDefaultArgExpr(SourceLocation CallLoc,
54705472
assert(Param->hasDefaultArg() && "can't build nonexistent default arg");
54715473

54725474
bool NestedDefaultChecking = isCheckingDefaultArgumentOrInitializer();
5473-
bool InLifetimeExtendingContext = isInLifetimeExtendingContext();
5475+
bool NeedRebuild = needRebuildDefaultArgOrInit();
54745476
std::optional<ExpressionEvaluationContextRecord::InitializationContext>
54755477
InitializationContext =
54765478
OutermostDeclarationWithDelayedImmediateInvocations();
@@ -5506,13 +5508,15 @@ ExprResult Sema::BuildCXXDefaultArgExpr(SourceLocation CallLoc,
55065508

55075509
// Rewrite the call argument that was created from the corresponding
55085510
// parameter's default argument.
5509-
if (V.HasImmediateCalls || InLifetimeExtendingContext) {
5511+
if (V.HasImmediateCalls ||
5512+
(NeedRebuild && isa_and_present<ExprWithCleanups>(Param->getInit()))) {
55105513
if (V.HasImmediateCalls)
55115514
ExprEvalContexts.back().DelayedDefaultInitializationContext = {
55125515
CallLoc, Param, CurContext};
55135516
// Pass down lifetime extending flag, and collect temporaries in
55145517
// CreateMaterializeTemporaryExpr when we rewrite the call argument.
5515-
keepInLifetimeExtendingContext();
5518+
currentEvaluationContext().InLifetimeExtendingContext =
5519+
parentEvaluationContext().InLifetimeExtendingContext;
55165520
EnsureImmediateInvocationInDefaultArgs Immediate(*this);
55175521
ExprResult Res;
55185522
runWithSufficientStackSpace(CallLoc, [&] {
@@ -5558,7 +5562,7 @@ ExprResult Sema::BuildCXXDefaultInitExpr(SourceLocation Loc, FieldDecl *Field) {
55585562
Expr *Init = nullptr;
55595563

55605564
bool NestedDefaultChecking = isCheckingDefaultArgumentOrInitializer();
5561-
5565+
bool NeedRebuild = needRebuildDefaultArgOrInit();
55625566
EnterExpressionEvaluationContext EvalContext(
55635567
*this, ExpressionEvaluationContext::PotentiallyEvaluated, Field);
55645568

@@ -5593,12 +5597,27 @@ ExprResult Sema::BuildCXXDefaultInitExpr(SourceLocation Loc, FieldDecl *Field) {
55935597
ImmediateCallVisitor V(getASTContext());
55945598
if (!NestedDefaultChecking)
55955599
V.TraverseDecl(Field);
5596-
if (V.HasImmediateCalls) {
5600+
5601+
// CWG1815
5602+
// Support lifetime extension of temporary created by aggregate
5603+
// initialization using a default member initializer. We should rebuild
5604+
// the initializer in a lifetime extension context if the initializer
5605+
// expression is an ExprWithCleanups. Then make sure the normal lifetime
5606+
// extension code recurses into the default initializer and does lifetime
5607+
// extension when warranted.
5608+
bool ContainsAnyTemporaries =
5609+
isa_and_present<ExprWithCleanups>(Field->getInClassInitializer());
5610+
if (Field->getInClassInitializer() &&
5611+
!Field->getInClassInitializer()->containsErrors() &&
5612+
(V.HasImmediateCalls || (NeedRebuild && ContainsAnyTemporaries))) {
55975613
ExprEvalContexts.back().DelayedDefaultInitializationContext = {Loc, Field,
55985614
CurContext};
55995615
ExprEvalContexts.back().IsCurrentlyCheckingDefaultArgumentOrInitializer =
56005616
NestedDefaultChecking;
5601-
5617+
// Pass down lifetime extending flag, and collect temporaries in
5618+
// CreateMaterializeTemporaryExpr when we rewrite the call argument.
5619+
currentEvaluationContext().InLifetimeExtendingContext =
5620+
parentEvaluationContext().InLifetimeExtendingContext;
56025621
EnsureImmediateInvocationInDefaultArgs Immediate(*this);
56035622
ExprResult Res;
56045623
runWithSufficientStackSpace(Loc, [&] {
@@ -17675,11 +17694,10 @@ void Sema::PopExpressionEvaluationContext() {
1767517694

1767617695
// Append the collected materialized temporaries into previous context before
1767717696
// exit if the previous also is a lifetime extending context.
17678-
auto &PrevRecord = parentEvaluationContext();
1767917697
if (getLangOpts().CPlusPlus23 && Rec.InLifetimeExtendingContext &&
17680-
PrevRecord.InLifetimeExtendingContext &&
17698+
parentEvaluationContext().InLifetimeExtendingContext &&
1768117699
!Rec.ForRangeLifetimeExtendTemps.empty()) {
17682-
PrevRecord.ForRangeLifetimeExtendTemps.append(
17700+
parentEvaluationContext().ForRangeLifetimeExtendTemps.append(
1768317701
Rec.ForRangeLifetimeExtendTemps);
1768417702
}
1768517703

clang/lib/Sema/SemaExprCXX.cpp

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1540,9 +1540,6 @@ Sema::BuildCXXTypeConstructExpr(TypeSourceInfo *TInfo,
15401540
bool ListInitialization) {
15411541
QualType Ty = TInfo->getType();
15421542
SourceLocation TyBeginLoc = TInfo->getTypeLoc().getBeginLoc();
1543-
1544-
assert((!ListInitialization || Exprs.size() == 1) &&
1545-
"List initialization must have exactly one expression.");
15461543
SourceRange FullRange = SourceRange(TyBeginLoc, RParenOrBraceLoc);
15471544

15481545
InitializedEntity Entity =

clang/lib/Sema/SemaInit.cpp

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -750,8 +750,20 @@ void InitListChecker::FillInEmptyInitForField(unsigned Init, FieldDecl *Field,
750750
if (Field->hasInClassInitializer()) {
751751
if (VerifyOnly)
752752
return;
753-
754-
ExprResult DIE = SemaRef.BuildCXXDefaultInitExpr(Loc, Field);
753+
ExprResult DIE;
754+
{
755+
// Enter a default initializer rebuild context, then we can support
756+
// lifetime extension of temporary created by aggregate initialization
757+
// using a default member initializer.
758+
// CWG1815 (https://wg21.link/CWG1815).
759+
EnterExpressionEvaluationContext RebuildDefaultInit(
760+
SemaRef, Sema::ExpressionEvaluationContext::PotentiallyEvaluated);
761+
// Just copy previous record, make sure we haven't forget anything.
762+
SemaRef.currentEvaluationContext() = SemaRef.parentEvaluationContext();
763+
SemaRef.currentEvaluationContext().RebuildDefaultArgOrDefaultInit =
764+
true;
765+
DIE = SemaRef.BuildCXXDefaultInitExpr(Loc, Field);
766+
}
755767
if (DIE.isInvalid()) {
756768
hadError = true;
757769
return;
@@ -7521,10 +7533,8 @@ Sema::CreateMaterializeTemporaryExpr(QualType T, Expr *Temporary,
75217533
// are done in both CreateMaterializeTemporaryExpr and MaybeBindToTemporary,
75227534
// but there may be a chance to merge them.
75237535
Cleanup.setExprNeedsCleanups(false);
7524-
if (isInLifetimeExtendingContext()) {
7525-
auto &Record = ExprEvalContexts.back();
7526-
Record.ForRangeLifetimeExtendTemps.push_back(MTE);
7527-
}
7536+
if (isInLifetimeExtendingContext())
7537+
currentEvaluationContext().ForRangeLifetimeExtendTemps.push_back(MTE);
75287538
return MTE;
75297539
}
75307540

clang/lib/Sema/SemaTemplateInstantiateDecl.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5481,7 +5481,10 @@ void Sema::InstantiateVariableInitializer(
54815481
EnterExpressionEvaluationContext Evaluated(
54825482
*this, Sema::ExpressionEvaluationContext::PotentiallyEvaluated, Var);
54835483

5484-
keepInLifetimeExtendingContext();
5484+
currentEvaluationContext().InLifetimeExtendingContext =
5485+
parentEvaluationContext().InLifetimeExtendingContext;
5486+
currentEvaluationContext().RebuildDefaultArgOrDefaultInit =
5487+
parentEvaluationContext().RebuildDefaultArgOrDefaultInit;
54855488
// Instantiate the initializer.
54865489
ExprResult Init;
54875490

clang/lib/Sema/TreeTransform.h

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4254,7 +4254,10 @@ ExprResult TreeTransform<Derived>::TransformInitializer(Expr *Init,
42544254
getSema(), EnterExpressionEvaluationContext::InitList,
42554255
Construct->isListInitialization());
42564256

4257-
getSema().keepInLifetimeExtendingContext();
4257+
getSema().currentEvaluationContext().InLifetimeExtendingContext =
4258+
getSema().parentEvaluationContext().InLifetimeExtendingContext;
4259+
getSema().currentEvaluationContext().RebuildDefaultArgOrDefaultInit =
4260+
getSema().parentEvaluationContext().RebuildDefaultArgOrDefaultInit;
42584261
SmallVector<Expr*, 8> NewArgs;
42594262
bool ArgChanged = false;
42604263
if (getDerived().TransformExprs(Construct->getArgs(), Construct->getNumArgs(),
@@ -8924,8 +8927,9 @@ TreeTransform<Derived>::TransformCXXForRangeStmt(CXXForRangeStmt *S) {
89248927

89258928
// P2718R0 - Lifetime extension in range-based for loops.
89268929
if (getSema().getLangOpts().CPlusPlus23) {
8927-
auto &LastRecord = getSema().ExprEvalContexts.back();
8930+
auto &LastRecord = getSema().currentEvaluationContext();
89288931
LastRecord.InLifetimeExtendingContext = true;
8932+
LastRecord.RebuildDefaultArgOrDefaultInit = true;
89298933
}
89308934
StmtResult Init =
89318935
S->getInit() ? getDerived().TransformStmt(S->getInit()) : StmtResult();
@@ -14443,6 +14447,13 @@ TreeTransform<Derived>::TransformCXXTemporaryObjectExpr(
1444314447
if (TransformExprs(E->getArgs(), E->getNumArgs(), true, Args,
1444414448
&ArgumentChanged))
1444514449
return ExprError();
14450+
14451+
if (E->isListInitialization() && !E->isStdInitListInitialization()) {
14452+
ExprResult Res = RebuildInitList(E->getBeginLoc(), Args, E->getEndLoc());
14453+
if (Res.isInvalid())
14454+
return ExprError();
14455+
Args = {Res.get()};
14456+
}
1444614457
}
1444714458

1444814459
if (!getDerived().AlwaysRebuild() &&
@@ -14454,12 +14465,9 @@ TreeTransform<Derived>::TransformCXXTemporaryObjectExpr(
1445414465
return SemaRef.MaybeBindToTemporary(E);
1445514466
}
1445614467

14457-
// FIXME: We should just pass E->isListInitialization(), but we're not
14458-
// prepared to handle list-initialization without a child InitListExpr.
1445914468
SourceLocation LParenLoc = T->getTypeLoc().getEndLoc();
1446014469
return getDerived().RebuildCXXTemporaryObjectExpr(
14461-
T, LParenLoc, Args, E->getEndLoc(),
14462-
/*ListInitialization=*/LParenLoc.isInvalid());
14470+
T, LParenLoc, Args, E->getEndLoc(), E->isListInitialization());
1446314471
}
1446414472

1446514473
template<typename Derived>

0 commit comments

Comments
 (0)