Skip to content

Commit 26baa00

Browse files
authored
[clang] Diagnose dangling references for parenthesized aggregate initialization. (#117690)
Unlike brace initialization, the parenthesized aggregate initialization in C++20 does not extend the lifetime of a temporary object bound to a reference in an aggreate. This can lead to dangling references: ``` struct A { const int& r; }; A a1(1); // well-formed, but results in a dangling reference. ``` With this patch, clang will diagnose this common dangling issues. Fixes #101957
1 parent 27e01ee commit 26baa00

File tree

5 files changed

+83
-2
lines changed

5 files changed

+83
-2
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,8 @@ Improvements to Clang's diagnostics
627627
- Clang now supports using alias templates in deduction guides, aligning with the C++ standard,
628628
which treats alias templates as synonyms for their underlying types (#GH54909).
629629

630+
- Clang now diagnoses dangling references for C++20's parenthesized aggregate initialization (#101957).
631+
630632
Improvements to Clang's time-trace
631633
----------------------------------
632634

clang/lib/Sema/CheckExprLifetime.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ struct IndirectLocalPathEntry {
204204
GslPointerInit,
205205
GslPointerAssignment,
206206
DefaultArg,
207+
ParenAggInit,
207208
} Kind;
208209
Expr *E;
209210
union {
@@ -985,6 +986,17 @@ static void visitLocalsRetainedByInitializer(IndirectLocalPath &Path,
985986
if (isa<CallExpr>(Init) || isa<CXXConstructExpr>(Init))
986987
return visitFunctionCallArguments(Path, Init, Visit);
987988

989+
if (auto *CPE = dyn_cast<CXXParenListInitExpr>(Init)) {
990+
RevertToOldSizeRAII RAII(Path);
991+
Path.push_back({IndirectLocalPathEntry::ParenAggInit, CPE});
992+
for (auto *I : CPE->getInitExprs()) {
993+
if (I->isGLValue())
994+
visitLocalsRetainedByReferenceBinding(Path, I, RK_ReferenceBinding,
995+
Visit);
996+
else
997+
visitLocalsRetainedByInitializer(Path, I, Visit, true);
998+
}
999+
}
9881000
switch (Init->getStmtClass()) {
9891001
case Stmt::UnaryOperatorClass: {
9901002
auto *UO = cast<UnaryOperator>(Init);
@@ -1081,6 +1093,7 @@ static SourceRange nextPathEntryRange(const IndirectLocalPath &Path, unsigned I,
10811093
case IndirectLocalPathEntry::GslReferenceInit:
10821094
case IndirectLocalPathEntry::GslPointerInit:
10831095
case IndirectLocalPathEntry::GslPointerAssignment:
1096+
case IndirectLocalPathEntry::ParenAggInit:
10841097
// These exist primarily to mark the path as not permitting or
10851098
// supporting lifetime extension.
10861099
break;
@@ -1392,6 +1405,7 @@ checkExprLifetimeImpl(Sema &SemaRef, const InitializedEntity *InitEntity,
13921405
switch (Elem.Kind) {
13931406
case IndirectLocalPathEntry::AddressOf:
13941407
case IndirectLocalPathEntry::LValToRVal:
1408+
case IndirectLocalPathEntry::ParenAggInit:
13951409
// These exist primarily to mark the path as not permitting or
13961410
// supporting lifetime extension.
13971411
break;

clang/test/AST/ByteCode/records.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,11 +1015,13 @@ namespace ParenInit {
10151015
};
10161016

10171017
/// Not constexpr!
1018-
O o1(0);
1018+
O o1(0); // both-warning {{temporary whose address is used as value of}}
1019+
// FIXME: the secondary warning message is bogus, would be nice to suppress it.
10191020
constinit O o2(0); // both-error {{variable does not have a constant initializer}} \
10201021
// both-note {{required by 'constinit' specifier}} \
10211022
// both-note {{reference to temporary is not a constant expression}} \
1022-
// both-note {{temporary created here}}
1023+
// both-note {{temporary created here}} \
1024+
// both-warning {{temporary whose address is used as value}}
10231025

10241026

10251027
/// Initializing an array.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// RUN: %clang_cc1 -verify -std=c++20 %s -fsyntax-only
2+
3+
namespace std {
4+
template <class T> struct remove_reference { typedef T type; };
5+
template <class T> struct remove_reference<T&> { typedef T type; };
6+
template <class T> struct remove_reference<T&&> { typedef T type; };
7+
8+
template <class T> typename remove_reference<T>::type &&move(T &&t);
9+
} // namespace std
10+
11+
// dcl.init 16.6.2.2
12+
struct A {
13+
int a;
14+
int&& r;
15+
};
16+
17+
int f();
18+
int n = 10;
19+
20+
A a1{1, f()}; // OK, lifetime is extended for direct-list-initialization
21+
// well-formed, but dangling reference
22+
A a2(1, f()); // expected-warning {{temporary whose address is used as value}}
23+
// well-formed, but dangling reference
24+
A a4(1.0, 1); // expected-warning {{temporary whose address is used as value}}
25+
A a5(1.0, std::move(n)); // OK
26+
27+
28+
29+
struct B {
30+
const int& r;
31+
};
32+
B test(int local) {
33+
return B(1); // expected-warning {{returning address}}
34+
return B(local); // expected-warning {{address of stack memory}}
35+
}
36+
37+
void f(B b);
38+
void test2(int local) {
39+
// No diagnostic on the following cases where both the aggregate object and
40+
// temporary end at the end of the full expression.
41+
f(B(1));
42+
f(B(local));
43+
}
44+
45+
// Test nested struct.
46+
struct C {
47+
B b;
48+
};
49+
50+
struct D {
51+
C c;
52+
};
53+
54+
C c1(B(
55+
1 // expected-warning {{temporary whose address is used as value}}
56+
));
57+
D d1(C(B(
58+
1 // expected-warning {{temporary whose address is used as value}}
59+
)));

clang/test/SemaCXX/paren-list-agg-init.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ void foo(int n) { // expected-note {{declared here}}
116116
B b2(A(1), {}, 1);
117117
// beforecxx20-warning@-1 {{aggregate initialization of type 'A' from a parenthesized list of values is a C++20 extension}}
118118
// beforecxx20-warning@-2 {{aggregate initialization of type 'B' from a parenthesized list of values is a C++20 extension}}
119+
// expected-warning@-3 {{temporary whose address is used as value of local variable 'b2' will be destroyed at the end of the full-expression}}
119120

120121
C c(A(1), 1, 2, 3, 4);
121122
// expected-error@-1 {{array initializer must be an initializer list}}
@@ -262,9 +263,12 @@ struct O {
262263

263264
O o1(0, 0, 0); // no-error
264265
// beforecxx20-warning@-1 {{aggregate initialization of type 'O' from a parenthesized list of values is a C++20 extension}}
266+
// expected-warning@-2 {{temporary whose address is used as value of local variable 'o1' will be destroyed at the end of the full-expression}}
267+
// expected-warning@-3 {{temporary whose address is used as value of local variable 'o1' will be destroyed at the end of the full-expression}}
265268

266269
O o2(0, 0); // no-error
267270
// beforecxx20-warning@-1 {{aggregate initialization of type 'O' from a parenthesized list of values is a C++20 extension}}
271+
// expected-warning@-2 {{temporary whose address is used as value of local variable 'o2' will be destroyed at the end of the full-expression}}
268272

269273
O o3(0);
270274
// expected-error@-1 {{reference member of type 'int &&' uninitialized}}

0 commit comments

Comments
 (0)