Skip to content

[Clang][CWG1815] Support lifetime extension of temporary created by aggregate initialization using a default member initializer #87933

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
May 12, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -10019,12 +10019,6 @@ def warn_new_dangling_initializer_list : Warning<
"the allocated initializer list}0 "
"will be destroyed at the end of the full-expression">,
InGroup<DanglingInitializerList>;
def warn_unsupported_lifetime_extension : Warning<
"lifetime extension of "
"%select{temporary|backing array of initializer list}0 created "
"by aggregate initialization using a default member initializer "
"is not yet supported; lifetime of %select{temporary|backing array}0 "
"will end at the end of the full-expression">, InGroup<Dangling>;

// For non-floating point, expressions of the form x == x or x != x
// should result in a warning, since these always evaluate to a constant.
Expand Down
16 changes: 9 additions & 7 deletions clang/lib/Sema/SemaExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6320,10 +6320,9 @@ ExprResult Sema::BuildCXXDefaultArgExpr(SourceLocation CallLoc,
Res = Immediate.TransformInitializer(Param->getInit(),
/*NotCopy=*/false);
});
if (Res.isInvalid())
return ExprError();
Res = ConvertParamDefaultArgument(Param, Res.get(),
Res.get()->getBeginLoc());
if (Res.isUsable())
Res = ConvertParamDefaultArgument(Param, Res.get(),
Res.get()->getBeginLoc());
if (Res.isInvalid())
return ExprError();
Init = Res.get();
Expand Down Expand Up @@ -6359,7 +6358,7 @@ ExprResult Sema::BuildCXXDefaultInitExpr(SourceLocation Loc, FieldDecl *Field) {
Expr *Init = nullptr;

bool NestedDefaultChecking = isCheckingDefaultArgumentOrInitializer();

bool InLifetimeExtendingContext = isInLifetimeExtendingContext();
EnterExpressionEvaluationContext EvalContext(
*this, ExpressionEvaluationContext::PotentiallyEvaluated, Field);

Expand Down Expand Up @@ -6394,19 +6393,22 @@ ExprResult Sema::BuildCXXDefaultInitExpr(SourceLocation Loc, FieldDecl *Field) {
ImmediateCallVisitor V(getASTContext());
if (!NestedDefaultChecking)
V.TraverseDecl(Field);
if (V.HasImmediateCalls) {
if (V.HasImmediateCalls || InLifetimeExtendingContext) {
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.
keepInLifetimeExtendingContext();

EnsureImmediateInvocationInDefaultArgs Immediate(*this);
ExprResult Res;
runWithSufficientStackSpace(Loc, [&] {
Res = Immediate.TransformInitializer(Field->getInClassInitializer(),
/*CXXDirectInit=*/false);
});
if (!Res.isInvalid())
if (Res.isUsable())
Res = ConvertMemberDefaultInitExpression(Field, Res.get(), Loc);
if (Res.isInvalid()) {
Field->setInvalidDecl();
Expand Down
39 changes: 27 additions & 12 deletions clang/lib/Sema/SemaInit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,26 @@ void InitListChecker::FillInEmptyInitForField(unsigned Init, FieldDecl *Field,
if (VerifyOnly)
return;

// Enter a lifetime extension context, then we can support lifetime
// extension of temporary created by aggregate initialization using a
// default member initializer (DR1815 https://wg21.link/CWG1815).
//
// In a lifetime extension context, BuildCXXDefaultInitExpr will clone the
// initializer expression on each use that would lifetime extend its
// temporaries.
EnterExpressionEvaluationContext LifetimeExtensionContext(
SemaRef, Sema::ExpressionEvaluationContext::PotentiallyEvaluated,
/*LambdaContextDecl=*/nullptr,
Sema::ExpressionEvaluationContextRecord::EK_Other, true);

// Lifetime extension in default-member-init.
auto &LastRecord = SemaRef.ExprEvalContexts.back();

// Just copy previous record, make sure we haven't forget anything.
LastRecord =
SemaRef.ExprEvalContexts[SemaRef.ExprEvalContexts.size() - 2];
LastRecord.InLifetimeExtendingContext = true;

ExprResult DIE = SemaRef.BuildCXXDefaultInitExpr(Loc, Field);
if (DIE.isInvalid()) {
hadError = true;
Expand Down Expand Up @@ -7700,6 +7720,8 @@ static void visitLocalsRetainedByReferenceBinding(IndirectLocalPath &Path,
// Step into CXXDefaultInitExprs so we can diagnose cases where a
// constructor inherits one as an implicit mem-initializer.
if (auto *DIE = dyn_cast<CXXDefaultInitExpr>(Init)) {
assert(DIE->hasRewrittenInit() &&
"CXXDefaultInitExpr must has rewritten init");
Path.push_back(
{IndirectLocalPathEntry::DefaultInit, DIE, DIE->getField()});
Init = DIE->getExpr();
Expand Down Expand Up @@ -8194,25 +8216,18 @@ void Sema::checkInitializerLifetime(const InitializedEntity &Entity,
}

switch (shouldLifetimeExtendThroughPath(Path)) {
case PathLifetimeKind::ShouldExtend:
// We're supposed to lifetime-extend the temporary along this path (per
// the resolution of DR1815), we supported that by clone the initializer
// expression on each use that would lifetime extend its temporaries.
[[fallthrough]];
case PathLifetimeKind::Extend:
// Update the storage duration of the materialized temporary.
// FIXME: Rebuild the expression instead of mutating it.
MTE->setExtendingDecl(ExtendingEntity->getDecl(),
ExtendingEntity->allocateManglingNumber());
// Also visit the temporaries lifetime-extended by this initializer.
return true;

case PathLifetimeKind::ShouldExtend:
// We're supposed to lifetime-extend the temporary along this path (per
// the resolution of DR1815), but we don't support that yet.
//
// FIXME: Properly handle this situation. Perhaps the easiest approach
// would be to clone the initializer expression on each use that would
// lifetime extend its temporaries.
Diag(DiagLoc, diag::warn_unsupported_lifetime_extension)
<< RK << DiagRange;
break;

case PathLifetimeKind::NoExtend:
// If the path goes through the initialization of a variable or field,
// it can't possibly reach a temporary created in this full-expression.
Expand Down
6 changes: 3 additions & 3 deletions clang/test/AST/ast-dump-default-init-json.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -789,10 +789,10 @@ void test() {
// CHECK-NEXT: "valueCategory": "lvalue",
// CHECK-NEXT: "extendingDecl": {
// CHECK-NEXT: "id": "0x{{.*}}",
// CHECK-NEXT: "kind": "FieldDecl",
// CHECK-NEXT: "name": "a",
// CHECK-NEXT: "kind": "VarDecl",
// CHECK-NEXT: "name": "b",
// CHECK-NEXT: "type": {
// CHECK-NEXT: "qualType": "const A &"
// CHECK-NEXT: "qualType": "B"
// CHECK-NEXT: }
// CHECK-NEXT: },
// CHECK-NEXT: "storageDuration": "automatic",
Expand Down
2 changes: 1 addition & 1 deletion clang/test/AST/ast-dump-default-init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ void test() {
}
// 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 Field 0x{{[^ ]*}} 'a' 'const A &'
// 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'
Expand Down
2 changes: 1 addition & 1 deletion clang/test/Analysis/lifetime-extended-regions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ void aggregateWithReferences() {
clang_analyzer_dump(viaReference.ry); // expected-warning-re {{&lifetime_extended_object{Composite, viaReference, S{{[0-9]+}}} }}

// clang does not currently implement extending lifetime of object bound to reference members of aggregates,
// that are created from default member initializer (see `warn_unsupported_lifetime_extension` from `-Wdangling`)
// that are created from default member initializer (see `warn_unsupported_lifetime_extension` from `-Wdangling`) (Supported now).
RefAggregate defaultInitExtended{i}; // clang-bug does not extend `Composite`
clang_analyzer_dump(defaultInitExtended.ry); // expected-warning {{Unknown }}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the bug is fixed, does the analyzer output not change here? In any case, the comment should explain whatever the new behavior is, and shouldn't be mentioning a warning that no longer exists.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, good catch! we need update this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please give me some time to investigate, I'm not very familiar with analyzer.

}
Expand Down
2 changes: 0 additions & 2 deletions clang/test/CXX/drs/dr16xx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -484,8 +484,6 @@ namespace cwg1696 { // cwg1696: 7
const A &a = A(); // #cwg1696-D1-a
};
D1 d1 = {}; // #cwg1696-d1
// 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}}
// since-cxx14-note@#cwg1696-D1-a {{initializing field 'a' with default member initializer}}

struct D2 {
const A &a = A(); // #cwg1696-D2-a
Expand Down
7 changes: 2 additions & 5 deletions clang/test/CXX/drs/dr18xx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -206,13 +206,10 @@ namespace cwg1814 { // cwg1814: yes
#endif
}

namespace cwg1815 { // cwg1815: no
namespace cwg1815 { // cwg1815: yes
#if __cplusplus >= 201402L
// FIXME: needs codegen test
struct A { int &&r = 0; }; // #cwg1815-A
struct A { int &&r = 0; };
A a = {};
// 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
// since-cxx14-note@#cwg1815-A {{initializing field 'r' with default member initializer}}

struct B { int &&r = 0; }; // #cwg1815-B
// since-cxx14-error@-1 {{reference member 'r' binds to a temporary object whose lifetime would be shorter than the lifetime of the constructed object}}
Expand Down
20 changes: 20 additions & 0 deletions clang/test/CXX/special/class.temporary/p6.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,26 @@ void init_capture_init_list() {
// CHECK: }
}

void check_dr1815() { // dr1815: yes
#if __cplusplus >= 201402L

struct A {
int &&r = 0;
~A() {}
};

struct B {
A &&a = A{};
~B() {}
};

// CHECK: void @_Z12check_dr1815v()
// CHECK: call void @_ZZ12check_dr1815vEN1BD1Ev(
// CHECK: call void @_ZZ12check_dr1815vEN1AD1Ev(
B a = {};
#endif
}

namespace P2718R0 {
namespace basic {
template <typename E> using T2 = std::list<E>;
Expand Down
4 changes: 2 additions & 2 deletions clang/test/SemaCXX/constexpr-default-arg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ void test_default_arg2() {
}

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

}
6 changes: 2 additions & 4 deletions clang/test/SemaCXX/eval-crashes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,9 @@ namespace pr33140_0b {
}

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

namespace pr33140_3 {
Expand Down
2 changes: 1 addition & 1 deletion clang/www/cxx_dr_status.html
Original file line number Diff line number Diff line change
Expand Up @@ -10698,7 +10698,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2>
<td><a href="https://cplusplus.github.io/CWG/issues/1815.html">1815</a></td>
<td>CD4</td>
<td>Lifetime extension in aggregate initialization</td>
<td class="none" align="center">No</td>
<td class="full" align="center">Clang 19</td>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<td class="full" align="center">Clang 19</td>
<td class="unreleased" align="center">Clang 19</td>

We use a different CSS class here for features that haven't been in an official release yet.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your tips!

</tr>
<tr id="1816">
<td><a href="https://cplusplus.github.io/CWG/issues/1816.html">1816</a></td>
Expand Down
Loading