[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 https://github.com/llvm/llvm-project/issues/109793
This commit is contained in:
parent
f1c4df5b7b
commit
c86c815fc5
@ -663,6 +663,10 @@ Improvements to Clang's diagnostics
|
||||
#GH69470, #GH59391, #GH58172, #GH46215, #GH45915, #GH45891, #GH44490,
|
||||
#GH36703, #GH32903, #GH23312, #GH69874.
|
||||
|
||||
- Clang no longer emits a spurious -Wdangling-gsl warning in C++23 when
|
||||
iterating over an element of a temporary container in a range-based
|
||||
for loop.(#GH109793, #GH145164)
|
||||
|
||||
- Fixed false positives in ``-Wformat-truncation`` and ``-Wformat-overflow``
|
||||
diagnostics when floating-point numbers had both width field and plus or space
|
||||
prefix specified. (#GH143951)
|
||||
|
@ -1090,6 +1090,11 @@ protected:
|
||||
|
||||
LLVM_PREFERRED_TYPE(bool)
|
||||
unsigned IsCXXCondDecl : 1;
|
||||
|
||||
/// Whether this variable is the implicit __range variable in a for-range
|
||||
/// loop.
|
||||
LLVM_PREFERRED_TYPE(bool)
|
||||
unsigned IsCXXForRangeImplicitVar : 1;
|
||||
};
|
||||
|
||||
union {
|
||||
@ -1591,6 +1596,19 @@ public:
|
||||
NonParmVarDeclBits.IsCXXCondDecl = true;
|
||||
}
|
||||
|
||||
/// Whether this variable is the implicit '__range' variable in C++
|
||||
/// range-based for loops.
|
||||
bool isCXXForRangeImplicitVar() const {
|
||||
return isa<ParmVarDecl>(this) ? false
|
||||
: NonParmVarDeclBits.IsCXXForRangeImplicitVar;
|
||||
}
|
||||
|
||||
void setCXXForRangeImplicitVar(bool FRV) {
|
||||
assert(!isa<ParmVarDecl>(this) &&
|
||||
"Cannot set IsCXXForRangeImplicitVar on ParmVarDecl");
|
||||
NonParmVarDeclBits.IsCXXForRangeImplicitVar = FRV;
|
||||
}
|
||||
|
||||
/// Determines if this variable's alignment is dependent.
|
||||
bool hasDependentAlignment() const;
|
||||
|
||||
|
@ -1340,12 +1340,6 @@ checkExprLifetimeImpl(Sema &SemaRef, const InitializedEntity *InitEntity,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsGslPtrValueFromGslTempOwner && DiagLoc.isValid()) {
|
||||
SemaRef.Diag(DiagLoc, diag::warn_dangling_lifetime_pointer)
|
||||
<< DiagRange;
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (shouldLifetimeExtendThroughPath(Path)) {
|
||||
case PathLifetimeKind::Extend:
|
||||
// Update the storage duration of the materialized temporary.
|
||||
@ -1356,6 +1350,20 @@ checkExprLifetimeImpl(Sema &SemaRef, const InitializedEntity *InitEntity,
|
||||
return true;
|
||||
|
||||
case PathLifetimeKind::NoExtend:
|
||||
if (SemaRef.getLangOpts().CPlusPlus23 && InitEntity) {
|
||||
if (const VarDecl *VD =
|
||||
dyn_cast_if_present<VarDecl>(InitEntity->getDecl());
|
||||
VD && VD->isCXXForRangeImplicitVar()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (IsGslPtrValueFromGslTempOwner && DiagLoc.isValid()) {
|
||||
SemaRef.Diag(DiagLoc, diag::warn_dangling_lifetime_pointer)
|
||||
<< DiagRange;
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the path goes through the initialization of a variable or field,
|
||||
// it can't possibly reach a temporary created in this full-expression.
|
||||
// We will have already diagnosed any problems with the initializer.
|
||||
|
@ -2423,6 +2423,7 @@ VarDecl *BuildForRangeVarDecl(Sema &SemaRef, SourceLocation Loc,
|
||||
VarDecl *Decl = VarDecl::Create(SemaRef.Context, DC, Loc, Loc, II, Type,
|
||||
TInfo, SC_None);
|
||||
Decl->setImplicit();
|
||||
Decl->setCXXForRangeImplicitVar(true);
|
||||
return Decl;
|
||||
}
|
||||
|
||||
|
@ -1634,6 +1634,7 @@ RedeclarableResult ASTDeclReader::VisitVarDeclImpl(VarDecl *VD) {
|
||||
VarDeclBits.getNextBits(/*Width*/ 3);
|
||||
|
||||
VD->NonParmVarDeclBits.ObjCForDecl = VarDeclBits.getNextBit();
|
||||
VD->NonParmVarDeclBits.IsCXXForRangeImplicitVar = VarDeclBits.getNextBit();
|
||||
}
|
||||
|
||||
// If this variable has a deduced type, defer reading that type until we are
|
||||
|
@ -1317,6 +1317,7 @@ void ASTDeclWriter::VisitVarDecl(VarDecl *D) {
|
||||
VarDeclBits.addBits(0, /*Width=*/3);
|
||||
|
||||
VarDeclBits.addBit(D->isObjCForDecl());
|
||||
VarDeclBits.addBit(D->isCXXForRangeImplicitVar());
|
||||
}
|
||||
|
||||
Record.push_back(VarDeclBits);
|
||||
@ -2738,6 +2739,7 @@ void ASTWriter::WriteDeclAbbrevs() {
|
||||
// isInline, isInlineSpecified, isConstexpr,
|
||||
// isInitCapture, isPrevDeclInSameScope, hasInitWithSideEffects,
|
||||
// EscapingByref, HasDeducedType, ImplicitParamKind, isObjCForDecl
|
||||
// IsCXXForRangeImplicitVar
|
||||
Abv->Add(BitCodeAbbrevOp(0)); // VarKind (local enum)
|
||||
// Type Source Info
|
||||
Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Array));
|
||||
|
@ -192,8 +192,9 @@ namespace p0936r0_examples {
|
||||
|
||||
std::vector make_vector();
|
||||
void use_reversed_range() {
|
||||
// FIXME: Don't expose the name of the internal range variable.
|
||||
for (auto x : reversed(make_vector())) {} // expected-warning {{temporary implicitly bound to local reference will be destroyed at the end of the full-expression}}
|
||||
// No warning here because C++23 extends the lifetime of the temporary
|
||||
// in a range-based for loop.
|
||||
for (auto x : reversed(make_vector())) {}
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
|
27
clang/test/SemaCXX/range-for-lifetime-cxx23.cpp
Normal file
27
clang/test/SemaCXX/range-for-lifetime-cxx23.cpp
Normal file
@ -0,0 +1,27 @@
|
||||
// RUN: %clang_cc1 -std=c++23 -fsyntax-only -verify %s
|
||||
|
||||
using size_t = decltype(sizeof(void *));
|
||||
|
||||
namespace std {
|
||||
template <typename T> struct vector {
|
||||
T &operator[](size_t I);
|
||||
};
|
||||
|
||||
struct string {
|
||||
const char *begin();
|
||||
const char *end();
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
std::vector<std::string> getData();
|
||||
|
||||
void foo() {
|
||||
// Verifies we don't trigger a diagnostic from -Wdangling-gsl
|
||||
// when iterating over a temporary in C++23.
|
||||
for (auto c : getData()[0]) {
|
||||
(void)c;
|
||||
}
|
||||
}
|
||||
|
||||
// expected-no-diagnostics
|
Loading…
x
Reference in New Issue
Block a user