Skip to content

Commit c92065b

Browse files
committed
[clang] Diagnose dangling references for parethesized aggregate initialization.
1 parent 1b2c8f1 commit c92065b

File tree

5 files changed

+84
-2
lines changed

5 files changed

+84
-2
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,8 @@ Improvements to Clang's diagnostics
587587
- For an rvalue reference bound to a temporary struct with an integer member, Clang will detect constant integer overflow
588588
in the initializer for the integer member (#GH46755).
589589

590+
- Clang now diagnoses dangling references for C++20's parenthesized aggregate initialization (#101957).
591+
590592
Improvements to Clang's time-trace
591593
----------------------------------
592594

clang/lib/Sema/CheckExprLifetime.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "clang/Sema/Initialization.h"
1414
#include "clang/Sema/Sema.h"
1515
#include "llvm/ADT/PointerIntPair.h"
16+
#include "llvm/ADT/STLExtras.h"
1617

1718
namespace clang::sema {
1819
namespace {
@@ -203,6 +204,7 @@ struct IndirectLocalPathEntry {
203204
GslPointerInit,
204205
GslPointerAssignment,
205206
DefaultArg,
207+
ParenAggInit,
206208
} Kind;
207209
Expr *E;
208210
union {
@@ -961,6 +963,16 @@ static void visitLocalsRetainedByInitializer(IndirectLocalPath &Path,
961963
if (isa<CallExpr>(Init) || isa<CXXConstructExpr>(Init))
962964
return visitFunctionCallArguments(Path, Init, Visit);
963965

966+
if (auto *CPE = dyn_cast<CXXParenListInitExpr>(Init)) {
967+
Path.push_back({IndirectLocalPathEntry::ParenAggInit, CPE});
968+
for (auto *I : CPE->getInitExprs()) {
969+
if (I->isGLValue())
970+
visitLocalsRetainedByReferenceBinding(Path, I, RK_ReferenceBinding,
971+
Visit);
972+
else
973+
visitLocalsRetainedByInitializer(Path, I, Visit, true);
974+
}
975+
}
964976
switch (Init->getStmtClass()) {
965977
case Stmt::UnaryOperatorClass: {
966978
auto *UO = cast<UnaryOperator>(Init);
@@ -1057,6 +1069,7 @@ static SourceRange nextPathEntryRange(const IndirectLocalPath &Path, unsigned I,
10571069
case IndirectLocalPathEntry::GslReferenceInit:
10581070
case IndirectLocalPathEntry::GslPointerInit:
10591071
case IndirectLocalPathEntry::GslPointerAssignment:
1072+
case IndirectLocalPathEntry::ParenAggInit:
10601073
// These exist primarily to mark the path as not permitting or
10611074
// supporting lifetime extension.
10621075
break;
@@ -1193,6 +1206,24 @@ checkExprLifetimeImpl(Sema &SemaRef, const InitializedEntity *InitEntity,
11931206
if (pathContainsInit(Path))
11941207
return false;
11951208

1209+
// In C++20, parenthesized aggregate initialization does not extend the
1210+
// lifetime of a temporary object bound to a reference. This can lead to
1211+
// dangling references and clang diagnoses these cases:
1212+
//
1213+
// struct A { const int& r; };
1214+
// A a1(1); // well-formed, but results in a dangling reference.
1215+
//
1216+
// However, to reduce noise, we suppress diagnostics for cases where
1217+
// both the aggregate and the temporary object are destroyed at the end
1218+
// of the full expression:
1219+
// f(A(1));
1220+
if (!Path.empty() &&
1221+
llvm::any_of(llvm::ArrayRef(Path).drop_front(), [](auto &P) {
1222+
return P.Kind == IndirectLocalPathEntry::ParenAggInit;
1223+
})) {
1224+
return false;
1225+
}
1226+
11961227
SemaRef.Diag(DiagLoc, diag::warn_dangling_variable)
11971228
<< RK << !InitEntity->getParent()
11981229
<< ExtendingEntity->getDecl()->isImplicit()
@@ -1368,6 +1399,7 @@ checkExprLifetimeImpl(Sema &SemaRef, const InitializedEntity *InitEntity,
13681399
switch (Elem.Kind) {
13691400
case IndirectLocalPathEntry::AddressOf:
13701401
case IndirectLocalPathEntry::LValToRVal:
1402+
case IndirectLocalPathEntry::ParenAggInit:
13711403
// These exist primarily to mark the path as not permitting or
13721404
// supporting lifetime extension.
13731405
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: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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+
// well-formed, but dangling reference
21+
A a2(1, f()); // expected-warning {{temporary whose address is used as value}}
22+
// well-formed, but dangling reference
23+
A a4(1.0, 1); // expected-warning {{temporary whose address is used as value}}
24+
A a5(1.0, std::move(n)); // OK
25+
26+
27+
28+
struct B {
29+
const int& r;
30+
};
31+
B test(int local) {
32+
return B(1); // expected-warning {{returning address}}
33+
return B(local); // expected-warning {{address of stack memory}}
34+
}
35+
36+
void f(B b);
37+
void test2(int local) {
38+
// No diagnostic on the following cases where both the aggregate object and
39+
// temporary end at the end of the full expression.
40+
f(B(1));
41+
f(B(local));
42+
}

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)