Skip to content

Commit 37c5713

Browse files
mrcvtlMarco Vitale
authored andcommitted
[Sema] Fix lifetime extension for temporaries in range-based for loops in C++23
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.
1 parent b581f9d commit 37c5713

File tree

7 files changed

+65
-0
lines changed

7 files changed

+65
-0
lines changed

clang/docs/ReleaseNotes.rst

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

647651

648652
Improvements to Clang's time-trace

clang/include/clang/AST/Decl.h

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

10861086
LLVM_PREFERRED_TYPE(bool)
10871087
unsigned IsCXXCondDecl : 1;
1088+
1089+
/// Whether this variable is the implicit __range variable in a for-range
1090+
/// loop.
1091+
LLVM_PREFERRED_TYPE(bool)
1092+
unsigned IsCXXForRangeImplicitVar : 1;
10881093
};
10891094

10901095
union {
@@ -1584,6 +1589,20 @@ class VarDecl : public DeclaratorDecl, public Redeclarable<VarDecl> {
15841589
NonParmVarDeclBits.IsCXXCondDecl = true;
15851590
}
15861591

1592+
/// Determine whether this variable is the compiler-generated '__range'
1593+
/// variable used to hold the range expression in a C++11 and later for-range
1594+
/// statement.
1595+
bool isCXXForRangeImplicitVar() const {
1596+
return isa<ParmVarDecl>(this) ? false
1597+
: NonParmVarDeclBits.IsCXXForRangeImplicitVar;
1598+
}
1599+
1600+
void setCXXForRangeImplicitVar(bool FRV) {
1601+
assert(!isa<ParmVarDecl>(this) &&
1602+
"Cannot set IsCXXForRangeImplicitVar on ParmVarDecl");
1603+
NonParmVarDeclBits.IsCXXForRangeImplicitVar = FRV;
1604+
}
1605+
15871606
/// Determines if this variable's alignment is dependent.
15881607
bool hasDependentAlignment() const;
15891608

clang/lib/Sema/CheckExprLifetime.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ enum LifetimeKind {
5757
};
5858
using LifetimeResult =
5959
llvm::PointerIntPair<const InitializedEntity *, 3, LifetimeKind>;
60+
6061
} // namespace
6162

6263
/// Determine the declaration which an initialized entity ultimately refers to,
@@ -1341,6 +1342,13 @@ checkExprLifetimeImpl(Sema &SemaRef, const InitializedEntity *InitEntity,
13411342
}
13421343

13431344
if (IsGslPtrValueFromGslTempOwner && DiagLoc.isValid()) {
1345+
1346+
if (SemaRef.getLangOpts().CPlusPlus23) {
1347+
if (const VarDecl *VD = cast<VarDecl>(InitEntity->getDecl());
1348+
VD && VD->isCXXForRangeImplicitVar())
1349+
return false;
1350+
}
1351+
13441352
SemaRef.Diag(DiagLoc, diag::warn_dangling_lifetime_pointer)
13451353
<< DiagRange;
13461354
return false;

clang/lib/Sema/SemaStmt.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2374,6 +2374,9 @@ static bool FinishForRangeVarDecl(Sema &SemaRef, VarDecl *Decl, Expr *Init,
23742374
SemaRef.ObjC().inferObjCARCLifetime(Decl))
23752375
Decl->setInvalidDecl();
23762376

2377+
if (SemaRef.getLangOpts().CPlusPlus23)
2378+
SemaRef.currentEvaluationContext().InLifetimeExtendingContext = true;
2379+
23772380
SemaRef.AddInitializerToDecl(Decl, Init, /*DirectInit=*/false);
23782381
SemaRef.FinalizeDeclaration(Decl);
23792382
SemaRef.CurContext->addHiddenDecl(Decl);
@@ -2423,6 +2426,7 @@ VarDecl *BuildForRangeVarDecl(Sema &SemaRef, SourceLocation Loc,
24232426
VarDecl *Decl = VarDecl::Create(SemaRef.Context, DC, Loc, Loc, II, Type,
24242427
TInfo, SC_None);
24252428
Decl->setImplicit();
2429+
Decl->setCXXForRangeImplicitVar(true);
24262430
return Decl;
24272431
}
24282432

clang/lib/Serialization/ASTReaderDecl.cpp

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

16391639
VD->NonParmVarDeclBits.ObjCForDecl = VarDeclBits.getNextBit();
1640+
VD->NonParmVarDeclBits.IsCXXForRangeImplicitVar = VarDeclBits.getNextBit();
16401641
}
16411642

16421643
// 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
@@ -1319,6 +1319,7 @@ void ASTDeclWriter::VisitVarDecl(VarDecl *D) {
13191319
VarDeclBits.addBits(0, /*Width=*/3);
13201320

13211321
VarDeclBits.addBit(D->isObjCForDecl());
1322+
VarDeclBits.addBit(D->isCXXForRangeImplicitVar());
13221323
}
13231324

13241325
Record.push_back(VarDeclBits);
@@ -2740,6 +2741,7 @@ void ASTWriter::WriteDeclAbbrevs() {
27402741
// isInline, isInlineSpecified, isConstexpr,
27412742
// isInitCapture, isPrevDeclInSameScope, hasInitWithSideEffects,
27422743
// EscapingByref, HasDeducedType, ImplicitParamKind, isObjCForDecl
2744+
// IsCXXForRangeImplicitVar
27432745
Abv->Add(BitCodeAbbrevOp(0)); // VarKind (local enum)
27442746
// Type Source Info
27452747
Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Array));
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)