Skip to content

Commit c86c815

Browse files
authored
[Sema] Fix lifetime extension for temporaries in range-based for loops in C++23 (#145164)
C++23 mandates that temporaries used in range-based for loops are lifetime-extended to cover the full loop. This patch adds a check for loop variables and compiler- generated `__range` bindings to apply the correct extension. Includes test cases based on examples from CWG900/P2644R1. Fixes #109793
1 parent f1c4df5 commit c86c815

File tree

8 files changed

+70
-8
lines changed

8 files changed

+70
-8
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,10 @@ Improvements to Clang's diagnostics
662662
#GH142457, #GH139913, #GH138850, #GH137867, #GH137860, #GH107840, #GH93308,
663663
#GH69470, #GH59391, #GH58172, #GH46215, #GH45915, #GH45891, #GH44490,
664664
#GH36703, #GH32903, #GH23312, #GH69874.
665+
666+
- Clang no longer emits a spurious -Wdangling-gsl warning in C++23 when
667+
iterating over an element of a temporary container in a range-based
668+
for loop.(#GH109793, #GH145164)
665669

666670
- Fixed false positives in ``-Wformat-truncation`` and ``-Wformat-overflow``
667671
diagnostics when floating-point numbers had both width field and plus or space

clang/include/clang/AST/Decl.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1090,6 +1090,11 @@ class VarDecl : public DeclaratorDecl, public Redeclarable<VarDecl> {
10901090

10911091
LLVM_PREFERRED_TYPE(bool)
10921092
unsigned IsCXXCondDecl : 1;
1093+
1094+
/// Whether this variable is the implicit __range variable in a for-range
1095+
/// loop.
1096+
LLVM_PREFERRED_TYPE(bool)
1097+
unsigned IsCXXForRangeImplicitVar : 1;
10931098
};
10941099

10951100
union {
@@ -1591,6 +1596,19 @@ class VarDecl : public DeclaratorDecl, public Redeclarable<VarDecl> {
15911596
NonParmVarDeclBits.IsCXXCondDecl = true;
15921597
}
15931598

1599+
/// Whether this variable is the implicit '__range' variable in C++
1600+
/// range-based for loops.
1601+
bool isCXXForRangeImplicitVar() const {
1602+
return isa<ParmVarDecl>(this) ? false
1603+
: NonParmVarDeclBits.IsCXXForRangeImplicitVar;
1604+
}
1605+
1606+
void setCXXForRangeImplicitVar(bool FRV) {
1607+
assert(!isa<ParmVarDecl>(this) &&
1608+
"Cannot set IsCXXForRangeImplicitVar on ParmVarDecl");
1609+
NonParmVarDeclBits.IsCXXForRangeImplicitVar = FRV;
1610+
}
1611+
15941612
/// Determines if this variable's alignment is dependent.
15951613
bool hasDependentAlignment() const;
15961614

clang/lib/Sema/CheckExprLifetime.cpp

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1340,12 +1340,6 @@ checkExprLifetimeImpl(Sema &SemaRef, const InitializedEntity *InitEntity,
13401340
return false;
13411341
}
13421342

1343-
if (IsGslPtrValueFromGslTempOwner && DiagLoc.isValid()) {
1344-
SemaRef.Diag(DiagLoc, diag::warn_dangling_lifetime_pointer)
1345-
<< DiagRange;
1346-
return false;
1347-
}
1348-
13491343
switch (shouldLifetimeExtendThroughPath(Path)) {
13501344
case PathLifetimeKind::Extend:
13511345
// Update the storage duration of the materialized temporary.
@@ -1356,6 +1350,20 @@ checkExprLifetimeImpl(Sema &SemaRef, const InitializedEntity *InitEntity,
13561350
return true;
13571351

13581352
case PathLifetimeKind::NoExtend:
1353+
if (SemaRef.getLangOpts().CPlusPlus23 && InitEntity) {
1354+
if (const VarDecl *VD =
1355+
dyn_cast_if_present<VarDecl>(InitEntity->getDecl());
1356+
VD && VD->isCXXForRangeImplicitVar()) {
1357+
return false;
1358+
}
1359+
}
1360+
1361+
if (IsGslPtrValueFromGslTempOwner && DiagLoc.isValid()) {
1362+
SemaRef.Diag(DiagLoc, diag::warn_dangling_lifetime_pointer)
1363+
<< DiagRange;
1364+
return false;
1365+
}
1366+
13591367
// If the path goes through the initialization of a variable or field,
13601368
// it can't possibly reach a temporary created in this full-expression.
13611369
// We will have already diagnosed any problems with the initializer.

clang/lib/Sema/SemaStmt.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2423,6 +2423,7 @@ VarDecl *BuildForRangeVarDecl(Sema &SemaRef, SourceLocation Loc,
24232423
VarDecl *Decl = VarDecl::Create(SemaRef.Context, DC, Loc, Loc, II, Type,
24242424
TInfo, SC_None);
24252425
Decl->setImplicit();
2426+
Decl->setCXXForRangeImplicitVar(true);
24262427
return Decl;
24272428
}
24282429

clang/lib/Serialization/ASTReaderDecl.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1634,6 +1634,7 @@ RedeclarableResult ASTDeclReader::VisitVarDeclImpl(VarDecl *VD) {
16341634
VarDeclBits.getNextBits(/*Width*/ 3);
16351635

16361636
VD->NonParmVarDeclBits.ObjCForDecl = VarDeclBits.getNextBit();
1637+
VD->NonParmVarDeclBits.IsCXXForRangeImplicitVar = VarDeclBits.getNextBit();
16371638
}
16381639

16391640
// If this variable has a deduced type, defer reading that type until we are

clang/lib/Serialization/ASTWriterDecl.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1317,6 +1317,7 @@ void ASTDeclWriter::VisitVarDecl(VarDecl *D) {
13171317
VarDeclBits.addBits(0, /*Width=*/3);
13181318

13191319
VarDeclBits.addBit(D->isObjCForDecl());
1320+
VarDeclBits.addBit(D->isCXXForRangeImplicitVar());
13201321
}
13211322

13221323
Record.push_back(VarDeclBits);
@@ -2738,6 +2739,7 @@ void ASTWriter::WriteDeclAbbrevs() {
27382739
// isInline, isInlineSpecified, isConstexpr,
27392740
// isInitCapture, isPrevDeclInSameScope, hasInitWithSideEffects,
27402741
// EscapingByref, HasDeducedType, ImplicitParamKind, isObjCForDecl
2742+
// IsCXXForRangeImplicitVar
27412743
Abv->Add(BitCodeAbbrevOp(0)); // VarKind (local enum)
27422744
// Type Source Info
27432745
Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Array));

clang/test/SemaCXX/attr-lifetimebound.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,8 +192,9 @@ namespace p0936r0_examples {
192192

193193
std::vector make_vector();
194194
void use_reversed_range() {
195-
// FIXME: Don't expose the name of the internal range variable.
196-
for (auto x : reversed(make_vector())) {} // expected-warning {{temporary implicitly bound to local reference will be destroyed at the end of the full-expression}}
195+
// No warning here because C++23 extends the lifetime of the temporary
196+
// in a range-based for loop.
197+
for (auto x : reversed(make_vector())) {}
197198
}
198199

199200
template <typename K, typename V>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// RUN: %clang_cc1 -std=c++23 -fsyntax-only -verify %s
2+
3+
using size_t = decltype(sizeof(void *));
4+
5+
namespace std {
6+
template <typename T> struct vector {
7+
T &operator[](size_t I);
8+
};
9+
10+
struct string {
11+
const char *begin();
12+
const char *end();
13+
};
14+
15+
} // namespace std
16+
17+
std::vector<std::string> getData();
18+
19+
void foo() {
20+
// Verifies we don't trigger a diagnostic from -Wdangling-gsl
21+
// when iterating over a temporary in C++23.
22+
for (auto c : getData()[0]) {
23+
(void)c;
24+
}
25+
}
26+
27+
// expected-no-diagnostics

0 commit comments

Comments
 (0)