[clang] c86c815 - [Sema] Fix lifetime extension for temporaries in range-based for loops in C++23 (#145164)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Jul 9 18:57:11 PDT 2025
Author: Marco Vitale
Date: 2025-07-10T09:57:07+08:00
New Revision: c86c815fc57c098ba14576fe2bb189da1dfc820d
URL: https://github.com/llvm/llvm-project/commit/c86c815fc57c098ba14576fe2bb189da1dfc820d
DIFF: https://github.com/llvm/llvm-project/commit/c86c815fc57c098ba14576fe2bb189da1dfc820d.diff
LOG: [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
Added:
clang/test/SemaCXX/range-for-lifetime-cxx23.cpp
Modified:
clang/docs/ReleaseNotes.rst
clang/include/clang/AST/Decl.h
clang/lib/Sema/CheckExprLifetime.cpp
clang/lib/Sema/SemaStmt.cpp
clang/lib/Serialization/ASTReaderDecl.cpp
clang/lib/Serialization/ASTWriterDecl.cpp
clang/test/SemaCXX/attr-lifetimebound.cpp
Removed:
################################################################################
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 3e84e25f360fa..e37be4d9963b3 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -662,6 +662,10 @@ Improvements to Clang's diagnostics
#GH142457, #GH139913, #GH138850, #GH137867, #GH137860, #GH107840, #GH93308,
#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
diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index f70a039bf3517..de79a9df29a5b 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -1090,6 +1090,11 @@ class VarDecl : public DeclaratorDecl, public Redeclarable<VarDecl> {
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 @@ class VarDecl : public DeclaratorDecl, public Redeclarable<VarDecl> {
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;
diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp
index 060ba31660556..e02e00231e58e 100644
--- a/clang/lib/Sema/CheckExprLifetime.cpp
+++ b/clang/lib/Sema/CheckExprLifetime.cpp
@@ -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.
diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index 923a9e81fbd6a..f85826aecadf3 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -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;
}
diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp
index 8bde213dc4cb3..b918bfbd549c3 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -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
diff --git a/clang/lib/Serialization/ASTWriterDecl.cpp b/clang/lib/Serialization/ASTWriterDecl.cpp
index b8a68afc30738..e414910469a64 100644
--- a/clang/lib/Serialization/ASTWriterDecl.cpp
+++ b/clang/lib/Serialization/ASTWriterDecl.cpp
@@ -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));
diff --git a/clang/test/SemaCXX/attr-lifetimebound.cpp b/clang/test/SemaCXX/attr-lifetimebound.cpp
index 06532550d312e..111bad65f7e1b 100644
--- a/clang/test/SemaCXX/attr-lifetimebound.cpp
+++ b/clang/test/SemaCXX/attr-lifetimebound.cpp
@@ -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>
diff --git a/clang/test/SemaCXX/range-for-lifetime-cxx23.cpp b/clang/test/SemaCXX/range-for-lifetime-cxx23.cpp
new file mode 100644
index 0000000000000..c36fd6c246347
--- /dev/null
+++ b/clang/test/SemaCXX/range-for-lifetime-cxx23.cpp
@@ -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
More information about the cfe-commits
mailing list