Skip to content

Commit 2177e45

Browse files
committed
PR47861: Expand dangling reference warning to look through copy
construction, and to assume that assignment operators return *this.
1 parent 9d1409d commit 2177e45

File tree

9 files changed

+192
-27
lines changed

9 files changed

+192
-27
lines changed

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9077,6 +9077,10 @@ def err_ret_local_block : Error<
90779077
def note_local_var_initializer : Note<
90789078
"%select{via initialization of|binding reference}0 variable "
90799079
"%select{%2 |}1here">;
9080+
def note_lambda_capture_initializer : Note<
9081+
"%select{implicitly |}2captured%select{| by reference}3"
9082+
"%select{%select{ due to use|}2 here|"
9083+
" via initialization of lambda capture %0}1">;
90809084
def note_init_with_default_member_initalizer : Note<
90819085
"initializing field %0 with default member initializer">;
90829086

clang/include/clang/Basic/OperatorKinds.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ getRewrittenOverloadedOperator(OverloadedOperatorKind Kind) {
4949
}
5050
}
5151

52+
/// Determine if this is a compound assignment operator.
53+
inline bool isCompoundAssignmentOperator(OverloadedOperatorKind Kind) {
54+
return Kind >= OO_PlusEqual && Kind <= OO_PipeEqual;
55+
}
56+
5257
} // end namespace clang
5358

5459
#endif

clang/lib/Sema/SemaInit.cpp

Lines changed: 102 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6710,15 +6710,22 @@ struct IndirectLocalPathEntry {
67106710
VarInit,
67116711
LValToRVal,
67126712
LifetimeBoundCall,
6713+
TemporaryCopy,
6714+
LambdaCaptureInit,
67136715
GslReferenceInit,
67146716
GslPointerInit
67156717
} Kind;
67166718
Expr *E;
6717-
const Decl *D = nullptr;
6719+
union {
6720+
const Decl *D = nullptr;
6721+
const LambdaCapture *Capture;
6722+
};
67186723
IndirectLocalPathEntry() {}
67196724
IndirectLocalPathEntry(EntryKind K, Expr *E) : Kind(K), E(E) {}
67206725
IndirectLocalPathEntry(EntryKind K, Expr *E, const Decl *D)
67216726
: Kind(K), E(E), D(D) {}
6727+
IndirectLocalPathEntry(EntryKind K, Expr *E, const LambdaCapture *Capture)
6728+
: Kind(K), E(E), Capture(Capture) {}
67226729
};
67236730

67246731
using IndirectLocalPath = llvm::SmallVectorImpl<IndirectLocalPathEntry>;
@@ -6912,6 +6919,26 @@ static bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD) {
69126919
if (ATL.getAttrAs<LifetimeBoundAttr>())
69136920
return true;
69146921
}
6922+
6923+
// Assume that all assignment operators with a "normal" return type return
6924+
// *this, that is, an lvalue reference that is the same type as the implicit
6925+
// object parameter (or the LHS for a non-member operator$=).
6926+
OverloadedOperatorKind OO = FD->getDeclName().getCXXOverloadedOperator();
6927+
if (OO == OO_Equal || isCompoundAssignmentOperator(OO)) {
6928+
QualType RetT = FD->getReturnType();
6929+
if (RetT->isLValueReferenceType()) {
6930+
ASTContext &Ctx = FD->getASTContext();
6931+
QualType LHST;
6932+
auto *MD = dyn_cast<CXXMethodDecl>(FD);
6933+
if (MD && MD->isCXXInstanceMember())
6934+
LHST = Ctx.getLValueReferenceType(MD->getThisObjectType());
6935+
else
6936+
LHST = MD->getParamDecl(0)->getType();
6937+
if (Ctx.hasSameType(RetT, LHST))
6938+
return true;
6939+
}
6940+
}
6941+
69156942
return false;
69166943
}
69176944

@@ -7257,15 +7284,37 @@ static void visitLocalsRetainedByInitializer(IndirectLocalPath &Path,
72577284
// The lifetime of an init-capture is that of the closure object constructed
72587285
// by a lambda-expression.
72597286
if (auto *LE = dyn_cast<LambdaExpr>(Init)) {
7287+
LambdaExpr::capture_iterator CapI = LE->capture_begin();
72607288
for (Expr *E : LE->capture_inits()) {
7289+
assert(CapI != LE->capture_end());
7290+
const LambdaCapture &Cap = *CapI++;
72617291
if (!E)
72627292
continue;
7293+
if (Cap.capturesVariable())
7294+
Path.push_back({IndirectLocalPathEntry::LambdaCaptureInit, E, &Cap});
72637295
if (E->isGLValue())
72647296
visitLocalsRetainedByReferenceBinding(Path, E, RK_ReferenceBinding,
72657297
Visit, EnableLifetimeWarnings);
72667298
else
72677299
visitLocalsRetainedByInitializer(Path, E, Visit, true,
72687300
EnableLifetimeWarnings);
7301+
if (Cap.capturesVariable())
7302+
Path.pop_back();
7303+
}
7304+
}
7305+
7306+
// Assume that a copy or move from a temporary references the same objects
7307+
// that the temporary does.
7308+
if (auto *CCE = dyn_cast<CXXConstructExpr>(Init)) {
7309+
if (CCE->getConstructor()->isCopyOrMoveConstructor()) {
7310+
if (auto *MTE = dyn_cast<MaterializeTemporaryExpr>(CCE->getArg(0))) {
7311+
Expr *Arg = MTE->getSubExpr();
7312+
Path.push_back({IndirectLocalPathEntry::TemporaryCopy, Arg,
7313+
CCE->getConstructor()});
7314+
visitLocalsRetainedByInitializer(Path, Arg, Visit, true,
7315+
/*EnableLifetimeWarnings*/false);
7316+
Path.pop_back();
7317+
}
72697318
}
72707319
}
72717320

@@ -7342,14 +7391,31 @@ static void visitLocalsRetainedByInitializer(IndirectLocalPath &Path,
73427391
}
73437392
}
73447393

7394+
/// Whether a path to an object supports lifetime extension.
7395+
enum PathLifetimeKind {
7396+
/// Lifetime-extend along this path.
7397+
Extend,
7398+
/// We should lifetime-extend, but we don't because (due to technical
7399+
/// limitations) we can't. This happens for default member initializers,
7400+
/// which we don't clone for every use, so we don't have a unique
7401+
/// MaterializeTemporaryExpr to update.
7402+
ShouldExtend,
7403+
/// Do not lifetime extend along this path.
7404+
NoExtend
7405+
};
7406+
73457407
/// Determine whether this is an indirect path to a temporary that we are
7346-
/// supposed to lifetime-extend along (but don't).
7347-
static bool shouldLifetimeExtendThroughPath(const IndirectLocalPath &Path) {
7408+
/// supposed to lifetime-extend along.
7409+
static PathLifetimeKind
7410+
shouldLifetimeExtendThroughPath(const IndirectLocalPath &Path) {
7411+
PathLifetimeKind Kind = PathLifetimeKind::Extend;
73487412
for (auto Elem : Path) {
7349-
if (Elem.Kind != IndirectLocalPathEntry::DefaultInit)
7350-
return false;
7413+
if (Elem.Kind == IndirectLocalPathEntry::DefaultInit)
7414+
Kind = PathLifetimeKind::ShouldExtend;
7415+
else if (Elem.Kind != IndirectLocalPathEntry::LambdaCaptureInit)
7416+
return PathLifetimeKind::NoExtend;
73517417
}
7352-
return true;
7418+
return Kind;
73537419
}
73547420

73557421
/// Find the range for the first interesting entry in the path at or after I.
@@ -7360,6 +7426,7 @@ static SourceRange nextPathEntryRange(const IndirectLocalPath &Path, unsigned I,
73607426
case IndirectLocalPathEntry::AddressOf:
73617427
case IndirectLocalPathEntry::LValToRVal:
73627428
case IndirectLocalPathEntry::LifetimeBoundCall:
7429+
case IndirectLocalPathEntry::TemporaryCopy:
73637430
case IndirectLocalPathEntry::GslReferenceInit:
73647431
case IndirectLocalPathEntry::GslPointerInit:
73657432
// These exist primarily to mark the path as not permitting or
@@ -7372,6 +7439,11 @@ static SourceRange nextPathEntryRange(const IndirectLocalPath &Path, unsigned I,
73727439
LLVM_FALLTHROUGH;
73737440
case IndirectLocalPathEntry::DefaultInit:
73747441
return Path[I].E->getSourceRange();
7442+
7443+
case IndirectLocalPathEntry::LambdaCaptureInit:
7444+
if (!Path[I].Capture->capturesVariable())
7445+
continue;
7446+
return Path[I].E->getSourceRange();
73757447
}
73767448
}
73777449
return E->getSourceRange();
@@ -7449,17 +7521,16 @@ void Sema::checkInitializerLifetime(const InitializedEntity &Entity,
74497521
return false;
74507522
}
74517523

7452-
// Lifetime-extend the temporary.
7453-
if (Path.empty()) {
7524+
switch (shouldLifetimeExtendThroughPath(Path)) {
7525+
case PathLifetimeKind::Extend:
74547526
// Update the storage duration of the materialized temporary.
74557527
// FIXME: Rebuild the expression instead of mutating it.
74567528
MTE->setExtendingDecl(ExtendingEntity->getDecl(),
74577529
ExtendingEntity->allocateManglingNumber());
74587530
// Also visit the temporaries lifetime-extended by this initializer.
74597531
return true;
7460-
}
74617532

7462-
if (shouldLifetimeExtendThroughPath(Path)) {
7533+
case PathLifetimeKind::ShouldExtend:
74637534
// We're supposed to lifetime-extend the temporary along this path (per
74647535
// the resolution of DR1815), but we don't support that yet.
74657536
//
@@ -7468,7 +7539,9 @@ void Sema::checkInitializerLifetime(const InitializedEntity &Entity,
74687539
// lifetime extend its temporaries.
74697540
Diag(DiagLoc, diag::warn_unsupported_lifetime_extension)
74707541
<< RK << DiagRange;
7471-
} else {
7542+
break;
7543+
7544+
case PathLifetimeKind::NoExtend:
74727545
// If the path goes through the initialization of a variable or field,
74737546
// it can't possibly reach a temporary created in this full-expression.
74747547
// We will have already diagnosed any problems with the initializer.
@@ -7479,6 +7552,7 @@ void Sema::checkInitializerLifetime(const InitializedEntity &Entity,
74797552
<< RK << !Entity.getParent()
74807553
<< ExtendingEntity->getDecl()->isImplicit()
74817554
<< ExtendingEntity->getDecl() << Init->isGLValue() << DiagRange;
7555+
break;
74827556
}
74837557
break;
74847558
}
@@ -7499,7 +7573,8 @@ void Sema::checkInitializerLifetime(const InitializedEntity &Entity,
74997573
return false;
75007574
}
75017575
bool IsSubobjectMember = ExtendingEntity != &Entity;
7502-
Diag(DiagLoc, shouldLifetimeExtendThroughPath(Path)
7576+
Diag(DiagLoc, shouldLifetimeExtendThroughPath(Path) !=
7577+
PathLifetimeKind::NoExtend
75037578
? diag::err_dangling_member
75047579
: diag::warn_dangling_member)
75057580
<< ExtendingDecl << IsSubobjectMember << RK << DiagRange;
@@ -7606,6 +7681,7 @@ void Sema::checkInitializerLifetime(const InitializedEntity &Entity,
76067681
break;
76077682

76087683
case IndirectLocalPathEntry::LifetimeBoundCall:
7684+
case IndirectLocalPathEntry::TemporaryCopy:
76097685
case IndirectLocalPathEntry::GslPointerInit:
76107686
case IndirectLocalPathEntry::GslReferenceInit:
76117687
// FIXME: Consider adding a note for these.
@@ -7618,14 +7694,27 @@ void Sema::checkInitializerLifetime(const InitializedEntity &Entity,
76187694
break;
76197695
}
76207696

7621-
case IndirectLocalPathEntry::VarInit:
7697+
case IndirectLocalPathEntry::VarInit: {
76227698
const VarDecl *VD = cast<VarDecl>(Elem.D);
76237699
Diag(VD->getLocation(), diag::note_local_var_initializer)
76247700
<< VD->getType()->isReferenceType()
76257701
<< VD->isImplicit() << VD->getDeclName()
76267702
<< nextPathEntryRange(Path, I + 1, L);
76277703
break;
76287704
}
7705+
7706+
case IndirectLocalPathEntry::LambdaCaptureInit:
7707+
if (!Elem.Capture->capturesVariable())
7708+
break;
7709+
// FIXME: We can't easily tell apart an init-capture from a nested
7710+
// capture of an init-capture.
7711+
const VarDecl *VD = Elem.Capture->getCapturedVar();
7712+
Diag(Elem.Capture->getLocation(), diag::note_lambda_capture_initializer)
7713+
<< VD << VD->isInitCapture() << Elem.Capture->isExplicit()
7714+
<< (Elem.Capture->getCaptureKind() == LCK_ByRef) << VD
7715+
<< nextPathEntryRange(Path, I + 1, L);
7716+
break;
7717+
}
76297718
}
76307719

76317720
// We didn't lifetime-extend, so don't go any further; we don't need more

clang/test/CXX/expr/expr.prim/expr.prim.lambda/p11-1y.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,11 @@ auto bad_init_7 = [a{{1}}] {}; // expected-error {{cannot deduce type for lambda
5454
template<typename...T> void pack_1(T...t) { (void)[a(t...)] {}; } // expected-error {{initializer missing for lambda capture 'a'}}
5555
template void pack_1<>(); // expected-note {{instantiation of}}
5656

57-
// FIXME: Might need lifetime extension for the temporary here.
58-
// See DR1695.
59-
auto a = [a(4), b = 5, &c = static_cast<const int&&>(0)] {
57+
// No lifetime-extension of the temporary here.
58+
auto a_copy = [&c = static_cast<const int&&>(0)] {}; // expected-warning {{temporary whose address is used as value of local variable 'a_copy' will be destroyed at the end of the full-expression}} expected-note {{via initialization of lambda capture 'c'}}
59+
60+
// But there is lifetime extension here.
61+
auto &&a = [a(4), b = 5, &c = static_cast<const int&&>(0)] {
6062
static_assert(sizeof(a) == sizeof(int), "");
6163
static_assert(sizeof(b) == sizeof(int), "");
6264
using T = decltype(c);

clang/test/SemaCXX/conditional-expr.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
// RUN: %clang_cc1 -fcxx-exceptions -fexceptions -fsyntax-only -verify -std=c++11 -Wsign-conversion %s
2-
// RUN: %clang_cc1 -fcxx-exceptions -fexceptions -fsyntax-only -verify -std=c++17 -Wsign-conversion %s
1+
// RUN: %clang_cc1 -fcxx-exceptions -fexceptions -fsyntax-only -verify=expected,expected-cxx11 -std=c++11 -Wsign-conversion %s
2+
// RUN: %clang_cc1 -fcxx-exceptions -fexceptions -fsyntax-only -verify=expected,expected-cxx17 -std=c++17 -Wsign-conversion %s
33

44
// C++ rules for ?: are a lot stricter than C rules, and have to take into
55
// account more conversion options.
@@ -406,7 +406,8 @@ namespace lifetime_extension {
406406

407407
struct D { A &&a; };
408408
void f_indirect(bool b) {
409-
D d = b ? D{B()} : D{C()};
409+
D d = b ? D{B()} // expected-cxx11-warning {{temporary whose address is used as value of local variable 'd' will be destroyed at the end of the full-expression}}
410+
: D{C()}; // expected-cxx11-warning {{temporary whose address is used as value of local variable 'd' will be destroyed at the end of the full-expression}}
410411
}
411412
}
412413

clang/test/SemaCXX/constant-expression-cxx11.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,7 @@ constexpr B &&b4 = ((1, 2), 3, 4, B { {10}, {{20}} });
387387
static_assert(&b4 != &b2, "");
388388

389389
// Proposed DR: copy-elision doesn't trigger lifetime extension.
390+
// expected-warning@+1 2{{temporary whose address is used as value of local variable 'b5' will be destroyed at the end of the full-expression}}
390391
constexpr B b5 = B{ {0}, {0} }; // expected-error {{constant expression}} expected-note {{reference to temporary}} expected-note {{here}}
391392

392393
namespace NestedNonStatic {
@@ -396,6 +397,7 @@ namespace NestedNonStatic {
396397
struct A { int &&r; };
397398
struct B { A &&a; };
398399
constexpr B a = { A{0} }; // ok
400+
// expected-warning@+1 {{temporary bound to reference member of local variable 'b' will be destroyed at the end of the full-expression}}
399401
constexpr B b = { A(A{0}) }; // expected-error {{constant expression}} expected-note {{reference to temporary}} expected-note {{here}}
400402
}
401403

clang/test/SemaCXX/cxx0x-initializer-stdinitializerlist.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,3 +376,13 @@ namespace weird_initlist {
376376
// ... but we do in constant evaluation.
377377
constexpr auto y = {weird{}, weird{}, weird{}, weird{}, weird{}}; // expected-error {{constant}} expected-note {{type 'const std::initializer_list<weird_initlist::weird>' has unexpected layout}}
378378
}
379+
380+
auto v = std::initializer_list<int>{1,2,3}; // expected-warning {{array backing local initializer list 'v' will be destroyed at the end of the full-expression}}
381+
382+
std::initializer_list<int> get(int cond) {
383+
if (cond == 0)
384+
return {};
385+
if (cond == 1)
386+
return {1, 2, 3}; // expected-warning {{returning address of local temporary object}}
387+
return std::initializer_list<int>{1, 2, 3}; // expected-warning {{returning address of local temporary object}}
388+
}

clang/test/SemaCXX/cxx1y-generic-lambdas-capturing.cpp

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -177,19 +177,15 @@ void doit() {
177177
sample::X cx{5};
178178
auto L = [=](auto a) {
179179
const int z = 3;
180-
// FIXME: The warning below is correct but for some reason doesn't show
181-
// up in C++17 mode.
182180
return [&,a](auto b) {
183-
#if __cplusplus > 201702L
184-
// expected-warning@-2 {{address of stack memory associated with local variable 'z' returned}}
181+
// expected-warning@-1 {{address of stack memory associated with local variable 'z' returned}}
185182
// expected-note@#call {{in instantiation of}}
186-
#endif
187-
const int y = 5;
188-
return [=](auto c) {
183+
const int y = 5;
184+
return [=](auto c) {
189185
int d[sizeof(a) == sizeof(c) || sizeof(c) == sizeof(b) ? 2 : 1];
190186
f(x, d);
191187
f(y, d);
192-
f(z, d);
188+
f(z, d); // expected-note {{implicitly captured by reference due to use here}}
193189
decltype(a) A = a;
194190
decltype(b) B = b;
195191
const int &i = cx.i;

0 commit comments

Comments
 (0)