Skip to content

Commit 17daa20

Browse files
authored
[Clang][CWG1815] Support lifetime extension of temporary created by aggregate initialization using a default member initializer (#87933)
This PR complete [DR1815](https://wg21.link/CWG1815) under the guidance of `FIXME` comments. And reuse `CXXDefaultInitExpr` rewrite machinery to clone the initializer expression on each use that would lifetime extend its temporaries. --------- Signed-off-by: yronglin <[email protected]>
1 parent 78b3a00 commit 17daa20

File tree

12 files changed

+86
-54
lines changed

12 files changed

+86
-54
lines changed

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10035,12 +10035,6 @@ def warn_new_dangling_initializer_list : Warning<
1003510035
"the allocated initializer list}0 "
1003610036
"will be destroyed at the end of the full-expression">,
1003710037
InGroup<DanglingInitializerList>;
10038-
def warn_unsupported_lifetime_extension : Warning<
10039-
"lifetime extension of "
10040-
"%select{temporary|backing array of initializer list}0 created "
10041-
"by aggregate initialization using a default member initializer "
10042-
"is not yet supported; lifetime of %select{temporary|backing array}0 "
10043-
"will end at the end of the full-expression">, InGroup<Dangling>;
1004410038

1004510039
// For non-floating point, expressions of the form x == x or x != x
1004610040
// should result in a warning, since these always evaluate to a constant.

clang/lib/Sema/SemaExpr.cpp

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5777,10 +5777,9 @@ ExprResult Sema::BuildCXXDefaultArgExpr(SourceLocation CallLoc,
57775777
Res = Immediate.TransformInitializer(Param->getInit(),
57785778
/*NotCopy=*/false);
57795779
});
5780-
if (Res.isInvalid())
5781-
return ExprError();
5782-
Res = ConvertParamDefaultArgument(Param, Res.get(),
5783-
Res.get()->getBeginLoc());
5780+
if (Res.isUsable())
5781+
Res = ConvertParamDefaultArgument(Param, Res.get(),
5782+
Res.get()->getBeginLoc());
57845783
if (Res.isInvalid())
57855784
return ExprError();
57865785
Init = Res.get();
@@ -5816,7 +5815,7 @@ ExprResult Sema::BuildCXXDefaultInitExpr(SourceLocation Loc, FieldDecl *Field) {
58165815
Expr *Init = nullptr;
58175816

58185817
bool NestedDefaultChecking = isCheckingDefaultArgumentOrInitializer();
5819-
5818+
bool InLifetimeExtendingContext = isInLifetimeExtendingContext();
58205819
EnterExpressionEvaluationContext EvalContext(
58215820
*this, ExpressionEvaluationContext::PotentiallyEvaluated, Field);
58225821

@@ -5851,19 +5850,35 @@ ExprResult Sema::BuildCXXDefaultInitExpr(SourceLocation Loc, FieldDecl *Field) {
58515850
ImmediateCallVisitor V(getASTContext());
58525851
if (!NestedDefaultChecking)
58535852
V.TraverseDecl(Field);
5854-
if (V.HasImmediateCalls) {
5853+
5854+
// CWG1815
5855+
// Support lifetime extension of temporary created by aggregate
5856+
// initialization using a default member initializer. We should always rebuild
5857+
// the initializer if it contains any temporaries (if the initializer
5858+
// expression is an ExprWithCleanups). Then make sure the normal lifetime
5859+
// extension code recurses into the default initializer and does lifetime
5860+
// extension when warranted.
5861+
bool ContainsAnyTemporaries =
5862+
isa_and_present<ExprWithCleanups>(Field->getInClassInitializer());
5863+
if (V.HasImmediateCalls || InLifetimeExtendingContext ||
5864+
ContainsAnyTemporaries) {
58555865
ExprEvalContexts.back().DelayedDefaultInitializationContext = {Loc, Field,
58565866
CurContext};
58575867
ExprEvalContexts.back().IsCurrentlyCheckingDefaultArgumentOrInitializer =
58585868
NestedDefaultChecking;
5859-
5869+
// Pass down lifetime extending flag, and collect temporaries in
5870+
// CreateMaterializeTemporaryExpr when we rewrite the call argument.
5871+
keepInLifetimeExtendingContext();
58605872
EnsureImmediateInvocationInDefaultArgs Immediate(*this);
58615873
ExprResult Res;
5874+
5875+
// Rebuild CXXDefaultInitExpr might cause diagnostics.
5876+
SFINAETrap Trap(*this);
58625877
runWithSufficientStackSpace(Loc, [&] {
58635878
Res = Immediate.TransformInitializer(Field->getInClassInitializer(),
58645879
/*CXXDirectInit=*/false);
58655880
});
5866-
if (!Res.isInvalid())
5881+
if (Res.isUsable())
58675882
Res = ConvertMemberDefaultInitExpression(Field, Res.get(), Loc);
58685883
if (Res.isInvalid()) {
58695884
Field->setInvalidDecl();

clang/lib/Sema/SemaInit.cpp

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8065,11 +8065,6 @@ static void visitLocalsRetainedByInitializer(IndirectLocalPath &Path,
80658065
enum PathLifetimeKind {
80668066
/// Lifetime-extend along this path.
80678067
Extend,
8068-
/// We should lifetime-extend, but we don't because (due to technical
8069-
/// limitations) we can't. This happens for default member initializers,
8070-
/// which we don't clone for every use, so we don't have a unique
8071-
/// MaterializeTemporaryExpr to update.
8072-
ShouldExtend,
80738068
/// Do not lifetime extend along this path.
80748069
NoExtend
80758070
};
@@ -8081,7 +8076,7 @@ shouldLifetimeExtendThroughPath(const IndirectLocalPath &Path) {
80818076
PathLifetimeKind Kind = PathLifetimeKind::Extend;
80828077
for (auto Elem : Path) {
80838078
if (Elem.Kind == IndirectLocalPathEntry::DefaultInit)
8084-
Kind = PathLifetimeKind::ShouldExtend;
8079+
Kind = PathLifetimeKind::Extend;
80858080
else if (Elem.Kind != IndirectLocalPathEntry::LambdaCaptureInit)
80868081
return PathLifetimeKind::NoExtend;
80878082
}
@@ -8201,18 +8196,6 @@ void Sema::checkInitializerLifetime(const InitializedEntity &Entity,
82018196
ExtendingEntity->allocateManglingNumber());
82028197
// Also visit the temporaries lifetime-extended by this initializer.
82038198
return true;
8204-
8205-
case PathLifetimeKind::ShouldExtend:
8206-
// We're supposed to lifetime-extend the temporary along this path (per
8207-
// the resolution of DR1815), but we don't support that yet.
8208-
//
8209-
// FIXME: Properly handle this situation. Perhaps the easiest approach
8210-
// would be to clone the initializer expression on each use that would
8211-
// lifetime extend its temporaries.
8212-
Diag(DiagLoc, diag::warn_unsupported_lifetime_extension)
8213-
<< RK << DiagRange;
8214-
break;
8215-
82168199
case PathLifetimeKind::NoExtend:
82178200
// If the path goes through the initialization of a variable or field,
82188201
// it can't possibly reach a temporary created in this full-expression.

clang/test/AST/ast-dump-default-init-json.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -789,10 +789,10 @@ void test() {
789789
// CHECK-NEXT: "valueCategory": "lvalue",
790790
// CHECK-NEXT: "extendingDecl": {
791791
// CHECK-NEXT: "id": "0x{{.*}}",
792-
// CHECK-NEXT: "kind": "FieldDecl",
793-
// CHECK-NEXT: "name": "a",
792+
// CHECK-NEXT: "kind": "VarDecl",
793+
// CHECK-NEXT: "name": "b",
794794
// CHECK-NEXT: "type": {
795-
// CHECK-NEXT: "qualType": "const A &"
795+
// CHECK-NEXT: "qualType": "B"
796796
// CHECK-NEXT: }
797797
// CHECK-NEXT: },
798798
// CHECK-NEXT: "storageDuration": "automatic",

clang/test/AST/ast-dump-default-init.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ void test() {
1313
}
1414
// CHECK: -CXXDefaultInitExpr 0x{{[^ ]*}} <{{.*}}> 'const A' lvalue has rewritten init
1515
// CHECK-NEXT: `-ExprWithCleanups 0x{{[^ ]*}} <{{.*}}> 'const A' lvalue
16-
// CHECK-NEXT: `-MaterializeTemporaryExpr 0x{{[^ ]*}} <{{.*}}> 'const A' lvalue extended by Field 0x{{[^ ]*}} 'a' 'const A &'
16+
// CHECK-NEXT: `-MaterializeTemporaryExpr 0x{{[^ ]*}} <{{.*}}> 'const A' lvalue extended by Var 0x{{[^ ]*}} 'b' 'B'
1717
// CHECK-NEXT: `-ImplicitCastExpr 0x{{[^ ]*}} <{{.*}}> 'const A' <NoOp>
1818
// CHECK-NEXT: `-CXXFunctionalCastExpr 0x{{[^ ]*}} <{{.*}}> 'A' functional cast to A <NoOp>
1919
// CHECK-NEXT: `-InitListExpr 0x{{[^ ]*}} <{{.*}}> 'A'

clang/test/Analysis/lifetime-extended-regions.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,11 @@ void aggregateWithReferences() {
120120
clang_analyzer_dump(viaReference); // expected-warning-re {{&lifetime_extended_object{RefAggregate, viaReference, S{{[0-9]+}}} }}
121121
clang_analyzer_dump(viaReference.rx); // expected-warning-re {{&lifetime_extended_object{int, viaReference, S{{[0-9]+}}} }}
122122
clang_analyzer_dump(viaReference.ry); // expected-warning-re {{&lifetime_extended_object{Composite, viaReference, S{{[0-9]+}}} }}
123-
124-
// clang does not currently implement extending lifetime of object bound to reference members of aggregates,
125-
// that are created from default member initializer (see `warn_unsupported_lifetime_extension` from `-Wdangling`)
126-
RefAggregate defaultInitExtended{i}; // clang-bug does not extend `Composite`
123+
124+
// FIXME: clang currently support extending lifetime of object bound to reference members of aggregates,
125+
// that are created from default member initializer. But CFG and ExprEngine need to be updated to address this change.
126+
// The following expect warning: {{&lifetime_extended_object{Composite, defaultInitExtended, S{{[0-9]+}}} }}
127+
RefAggregate defaultInitExtended{i};
127128
clang_analyzer_dump(defaultInitExtended.ry); // expected-warning {{Unknown }}
128129
}
129130

clang/test/CXX/drs/cwg16xx.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -483,8 +483,6 @@ namespace cwg1696 { // cwg1696: 7
483483
const A &a = A(); // #cwg1696-D1-a
484484
};
485485
D1 d1 = {}; // #cwg1696-d1
486-
// since-cxx14-warning@-1 {{lifetime extension of temporary created by aggregate initialization using a default member initializer is not yet supported; lifetime of temporary will end at the end of the full-expression}}
487-
// since-cxx14-note@#cwg1696-D1-a {{initializing field 'a' with default member initializer}}
488486

489487
struct D2 {
490488
const A &a = A(); // #cwg1696-D2-a

clang/test/CXX/drs/cwg18xx.cpp

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -206,19 +206,28 @@ namespace cwg1814 { // cwg1814: yes
206206
#endif
207207
}
208208

209-
namespace cwg1815 { // cwg1815: no
209+
namespace cwg1815 { // cwg1815: yes
210210
#if __cplusplus >= 201402L
211-
// FIXME: needs codegen test
212-
struct A { int &&r = 0; }; // #cwg1815-A
211+
struct A { int &&r = 0; };
213212
A a = {};
214-
// since-cxx14-warning@-1 {{lifetime extension of temporary created by aggregate initialization using a default member initializer is not yet supported; lifetime of temporary will end at the end of the full-expression}} FIXME
215-
// since-cxx14-note@#cwg1815-A {{initializing field 'r' with default member initializer}}
216213

217214
struct B { int &&r = 0; }; // #cwg1815-B
218215
// since-cxx14-error@-1 {{reference member 'r' binds to a temporary object whose lifetime would be shorter than the lifetime of the constructed object}}
219216
// since-cxx14-note@#cwg1815-B {{initializing field 'r' with default member initializer}}
220217
// since-cxx14-note@#cwg1815-b {{in implicit default constructor for 'cwg1815::B' first required here}}
221218
B b; // #cwg1815-b
219+
220+
#if __cplusplus >= 201703L
221+
struct C { const int &r = 0; };
222+
constexpr C c = {}; // OK, since cwg1815
223+
static_assert(c.r == 0);
224+
225+
constexpr int f() {
226+
A a = {}; // OK, since cwg1815
227+
return a.r;
228+
}
229+
static_assert(f() == 0);
230+
#endif
222231
#endif
223232
}
224233

clang/test/CXX/special/class.temporary/p6.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,40 @@ void init_capture_init_list() {
269269
// CHECK: }
270270
}
271271

272+
void check_dr1815() { // dr1815: yes
273+
#if __cplusplus >= 201402L
274+
275+
struct A {
276+
int &&r = 0;
277+
~A() {}
278+
};
279+
280+
struct B {
281+
A &&a = A{};
282+
~B() {}
283+
};
284+
B a = {};
285+
286+
// CHECK: call {{.*}}block_scope_begin_function
287+
extern void block_scope_begin_function();
288+
extern void block_scope_end_function();
289+
block_scope_begin_function();
290+
{
291+
// CHECK: call void @_ZZ12check_dr1815vEN1BD1Ev
292+
// CHECK: call void @_ZZ12check_dr1815vEN1AD1Ev
293+
B b = {};
294+
}
295+
// CHECK: call {{.*}}block_scope_end_function
296+
block_scope_end_function();
297+
298+
// CHECK: call {{.*}}some_other_function
299+
extern void some_other_function();
300+
some_other_function();
301+
// CHECK: call void @_ZZ12check_dr1815vEN1BD1Ev
302+
// CHECK: call void @_ZZ12check_dr1815vEN1AD1Ev
303+
#endif
304+
}
305+
272306
namespace P2718R0 {
273307
namespace basic {
274308
template <typename E> using T2 = std::list<E>;

clang/test/SemaCXX/constexpr-default-arg.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ void test_default_arg2() {
3232
}
3333

3434
// Check that multiple CXXDefaultInitExprs don't cause an assertion failure.
35-
struct A { int &&r = 0; }; // expected-note 2{{default member initializer}}
35+
struct A { int &&r = 0; };
3636
struct B { A x, y; };
37-
B b = {}; // expected-warning 2{{lifetime extension of temporary created by aggregate initialization using a default member initializer is not yet supported}}
37+
B b = {}; // expected-no-diagnostics
3838

3939
}

clang/test/SemaCXX/eval-crashes.cpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,9 @@ namespace pr33140_0b {
2525
}
2626

2727
namespace pr33140_2 {
28-
// FIXME: The declaration of 'b' below should lifetime-extend two int
29-
// temporaries.
30-
struct A { int &&r = 0; }; // expected-note 2{{initializing field 'r' with default member initializer}}
28+
struct A { int &&r = 0; };
3129
struct B { A x, y; };
32-
B b = {}; // expected-warning 2{{lifetime extension of temporary created by aggregate initialization using a default member initializer is not yet supported}}
30+
B b = {};
3331
}
3432

3533
namespace pr33140_3 {

clang/www/cxx_dr_status.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10698,7 +10698,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2>
1069810698
<td><a href="https://cplusplus.github.io/CWG/issues/1815.html">1815</a></td>
1069910699
<td>CD4</td>
1070010700
<td>Lifetime extension in aggregate initialization</td>
10701-
<td class="none" align="center">No</td>
10701+
<td class="unreleased" align="center">Clang 19</td>
1070210702
</tr>
1070310703
<tr id="1816">
1070410704
<td><a href="https://cplusplus.github.io/CWG/issues/1816.html">1816</a></td>

0 commit comments

Comments
 (0)