[clang] [Sema] Fix lifetime extension for temporaries in range-based for loops in C++23 (PR #145164)
mrcvtl wrote: A couple of quick notes: - This is my first LLVM PR, so if there are any issues with code style or conventions, please let me know! - I'm not entirely satisfied with the `VD->getName().starts_with("__range")` check, but it was the most reliable approach I found. Walking up the AST from the node didn’t seem feasible (likely due to optimizations?) and I noticed that pattern here: https://github.com/llvm/llvm-project/blob/075cb691a5e810f7114369c67b475dfd9127d4af/clang/lib/Sema/SemaStmt.cpp#L2481-L2485 https://github.com/llvm/llvm-project/pull/145164 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] [Sema] Fix lifetime extension for temporaries in range-based for loops in C++23 (PR #145164)
https://github.com/mrcvtl ready_for_review https://github.com/llvm/llvm-project/pull/145164 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] [Sema] Fix lifetime extension for temporaries in range-based for loops in C++23 (PR #145164)
mrcvtl wrote: I’ve taken some time to better understand the code and think through the solution. I tried using `isInLifetimeExtendingContext()`, but it still returns false, and I believe I now understand why. In your PR, the flag is set here: https://github.com/llvm/llvm-project/blob/b581f9d056babadf55098b9d5d100271621b90db/clang/lib/Parse/ParseDecl.cpp#L2314-L2318 However, the warning is actually triggered starting from this location: https://github.com/llvm/llvm-project/blob/b581f9d056babadf55098b9d5d100271621b90db/clang/lib/Parse/ParseDecl.cpp#L2266 An alternative approach to apply your logic and set again the flag in here: https://github.com/llvm/llvm-project/blob/43d042b350af8ee8c7401d6b102df68d6c176b5a/clang/lib/Parse/ParseStmt.cpp#L2174-L2179 Maybe we could act on `ForRangeInfo.LifetimeExtendTemps.back()`, or even directly on `Actions.currentEvaluationContext();`. If that works, we might be able to rely solely on `isInLifetimeExtendingContext()` and remove the need for `isRangeBasedForLoopVariable` altogether. What do you think? I'm absolutely open to every approach (the cleaner, the better!). https://github.com/llvm/llvm-project/pull/145164 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] [Sema] Fix lifetime extension for temporaries in range-based for loops in C++23 (PR #145164)
mrcvtl wrote: > Could you associate this PR with the issue to fix, if any? Done! https://github.com/llvm/llvm-project/pull/145164 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] [Sema] Fix lifetime extension for temporaries in range-based for loops in C++23 (PR #145164)
https://github.com/mrcvtl edited https://github.com/llvm/llvm-project/pull/145164 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] [Sema] Fix lifetime extension for temporaries in range-based for loops in C++23 (PR #145164)
https://github.com/mrcvtl created https://github.com/llvm/llvm-project/pull/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. >From 4398de927292be66f8f54c93c1064b6230f5470a Mon Sep 17 00:00:00 2001 From: Marco Vitale Date: Sat, 21 Jun 2025 14:01:53 +0200 Subject: [PATCH] [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. --- clang/lib/Sema/CheckExprLifetime.cpp | 28 +++ .../test/SemaCXX/range-for-lifetime-cxx23.cpp | 79 +++ 2 files changed, 107 insertions(+) create mode 100644 clang/test/SemaCXX/range-for-lifetime-cxx23.cpp diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp index 060ba31660556..0434aa0c29c26 100644 --- a/clang/lib/Sema/CheckExprLifetime.cpp +++ b/clang/lib/Sema/CheckExprLifetime.cpp @@ -57,6 +57,31 @@ enum LifetimeKind { }; using LifetimeResult = llvm::PointerIntPair; + +/// Returns true if the given entity is part of a range-based for loop and +/// should trigger lifetime extension under C++23 rules. +/// +/// This handles both explicit range loop variables and internal compiler- +/// generated variables like `__range1`. +static bool +isRangeBasedForLoopVariable(const Sema &SemaRef, +const InitializedEntity *ExtendingEntity) { + if (!SemaRef.getLangOpts().CPlusPlus23) +return false; + + const Decl *EntityDecl = ExtendingEntity->getDecl(); + if (!EntityDecl) +return false; + + if (const auto *VD = dyn_cast(EntityDecl)) { +if (VD->isCXXForRangeDecl() || VD->getName().starts_with("__range")) { + return true; +} + } + + return false; +} + } // namespace /// Determine the declaration which an initialized entity ultimately refers to, @@ -1341,6 +1366,9 @@ checkExprLifetimeImpl(Sema &SemaRef, const InitializedEntity *InitEntity, } if (IsGslPtrValueFromGslTempOwner && DiagLoc.isValid()) { +if (isRangeBasedForLoopVariable(SemaRef, ExtendingEntity)) + return true; + SemaRef.Diag(DiagLoc, diag::warn_dangling_lifetime_pointer) << DiagRange; return false; 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 0..bb6e06ec4517c --- /dev/null +++ b/clang/test/SemaCXX/range-for-lifetime-cxx23.cpp @@ -0,0 +1,79 @@ +// RUN: %clangxx -std=c++23 -fsyntax-only -Xclang -verify %s + +#include +#include +#include +#include +#include +#include +#include +#include + +static std::vector getVector() { + return {"first", "second", "third"}; +} + +static std::map> getMap() { + return {{"key", {1, 2, 3}}}; +} + +static std::tuple> getTuple() { + return std::make_tuple(std::vector{3.14, 2.71}); +} + +static std::optional> getOptionalColl() { + return std::vector{'x', 'y', 'z'}; +} + +static std::variant getVariant() { + return std::string("variant"); +} + +static const std::array& arrOfConst() { + static const std::array arr = {10, 20, 30, 40}; + return arr; +} + +static void testGetVectorSubscript() { + for (auto e : getVector()[0]) { +(void)e; + } +} + +static void testGetMapSubscript() { + for (auto valueElem : getMap()["key"]) { +(void)valueElem; + } +} + +static void testGetTuple() { + for (auto e : std::get<0>(getTuple())) { +(void)e; + } +} + +static void testOptionalValue() { + for (auto e : getOptionalColl().value()) { +(void)e; + } +} + +static void testVariantGetString() { + for (char c : std::get(getVariant())) { +(void)c; + } +} + +static void testSpanLastFromConstArray() { + for (auto s : std::span{arrOfConst()}.last(2)) { +(void)s; + } +} + +static void testSpanFromVectorPtr() { + for (auto e : std::span(getVector().data(), 2)) { +(void)e; + } +} + +// expected-no-diagnostics ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] [Sema] Fix lifetime extension for temporaries in range-based for loops in C++23 (PR #145164)
mrcvtl wrote: > IMO, we have 2 options: > > 1. Add a flag variable into `ExpressionEvaluationContextRecord`. Represent > that we are initializing the for-range __range variable. >Eg. > > ```diff > diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h > index 9397546c8fc5..c639a4b4af58 100644 > --- a/clang/include/clang/Sema/Sema.h > +++ b/clang/include/clang/Sema/Sema.h > @@ -6786,6 +6786,9 @@ public: > /// Whether we should rebuild CXXDefaultArgExpr and CXXDefaultInitExpr. > bool RebuildDefaultArgOrDefaultInit = false; > > +/// Whether we are initializing a C++ for-range implicit variable > '__range'. > +bool InitCXXForRangeVar = false; > + > // When evaluating immediate functions in the initializer of a default > // argument or default member initializer, this is the declaration whose > // default initializer is being evaluated and the location of the call > diff --git a/clang/lib/Sema/CheckExprLifetime.cpp > b/clang/lib/Sema/CheckExprLifetime.cpp > index 060ba3166055..150e27f8acf7 100644 > --- a/clang/lib/Sema/CheckExprLifetime.cpp > +++ b/clang/lib/Sema/CheckExprLifetime.cpp > @@ -1341,6 +1341,8 @@ checkExprLifetimeImpl(Sema &SemaRef, const > InitializedEntity *InitEntity, >} > >if (IsGslPtrValueFromGslTempOwner && DiagLoc.isValid()) { > +if (SemaRef.currentEvaluationContext().InitCXXForRangeVar) > + return false; > SemaRef.Diag(DiagLoc, diag::warn_dangling_lifetime_pointer) > << DiagRange; > return false; > diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp > index 923a9e81fbd6..da97d34854ac 100644 > --- a/clang/lib/Sema/SemaStmt.cpp > +++ b/clang/lib/Sema/SemaStmt.cpp > @@ -2374,6 +2374,13 @@ static bool FinishForRangeVarDecl(Sema &SemaRef, > VarDecl *Decl, Expr *Init, >SemaRef.ObjC().inferObjCARCLifetime(Decl)) > Decl->setInvalidDecl(); > > + // EnterExpressionEvaluationContext ForRangeInitContext( > + // SemaRef, Sema::ExpressionEvaluationContext::PotentiallyEvaluated, > + // /*LambdaContextDecl=*/nullptr, > + // Sema::ExpressionEvaluationContextRecord::EK_Other, > + // SemaRef.getLangOpts().CPlusPlus23); > + if (SemaRef.getLangOpts().CPlusPlus23) > +SemaRef.currentEvaluationContext().InitCXXForRangeVar = true; >SemaRef.AddInitializerToDecl(Decl, Init, /*DirectInit=*/false); >SemaRef.FinalizeDeclaration(Decl); >SemaRef.CurContext->addHiddenDecl(Decl); > ``` > > 2. Introduce a bitfields into `clang::VarDecl::NonParmVarDeclBitfields`, like > `CXXForRangeDecl`(Eg. `CXXForRangeImplicitVar`) > > CC @cor3ntin I like option one! Maybe a stupid question, but why can’t we use `InLifetimeExtendingContext` flag here? It feels like it’s meant to capture the same kind of situation. Is there a subtle difference I’m missing? I updated (locally) the PR to address the other feedbacks and I will push when I have the green light to use option 1! https://github.com/llvm/llvm-project/pull/145164 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] [Sema] Fix lifetime extension for temporaries in range-based for loops in C++23 (PR #145164)
https://github.com/mrcvtl updated https://github.com/llvm/llvm-project/pull/145164 >From b5146278ce5059b6bf0312f18f509022de5fd661 Mon Sep 17 00:00:00 2001 From: Marco Vitale Date: Sat, 21 Jun 2025 14:01:53 +0200 Subject: [PATCH 1/2] [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. --- clang/docs/ReleaseNotes.rst | 4 +++ clang/lib/Sema/CheckExprLifetime.cpp | 5 clang/lib/Sema/SemaStmt.cpp | 3 +++ .../test/SemaCXX/range-for-lifetime-cxx23.cpp | 27 +++ 4 files changed, 39 insertions(+) create mode 100644 clang/test/SemaCXX/range-for-lifetime-cxx23.cpp diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index d9847fadc21e5..d79df8c9184be 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -648,6 +648,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) - Clang now avoids issuing `-Wreturn-type` warnings in some cases where the final statement of a non-void function is a `throw` expression, or diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp index 060ba31660556..114e4f989ed9f 100644 --- a/clang/lib/Sema/CheckExprLifetime.cpp +++ b/clang/lib/Sema/CheckExprLifetime.cpp @@ -57,6 +57,7 @@ enum LifetimeKind { }; using LifetimeResult = llvm::PointerIntPair; + } // namespace /// Determine the declaration which an initialized entity ultimately refers to, @@ -1341,6 +1342,10 @@ checkExprLifetimeImpl(Sema &SemaRef, const InitializedEntity *InitEntity, } if (IsGslPtrValueFromGslTempOwner && DiagLoc.isValid()) { +if (SemaRef.getLangOpts().CPlusPlus23 && +SemaRef.isInLifetimeExtendingContext()) + return false; + SemaRef.Diag(DiagLoc, diag::warn_dangling_lifetime_pointer) << DiagRange; return false; diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp index 923a9e81fbd6a..633f73946b729 100644 --- a/clang/lib/Sema/SemaStmt.cpp +++ b/clang/lib/Sema/SemaStmt.cpp @@ -2374,6 +2374,9 @@ static bool FinishForRangeVarDecl(Sema &SemaRef, VarDecl *Decl, Expr *Init, SemaRef.ObjC().inferObjCARCLifetime(Decl)) Decl->setInvalidDecl(); + if (SemaRef.getLangOpts().CPlusPlus23) +SemaRef.currentEvaluationContext().InLifetimeExtendingContext = true; + SemaRef.AddInitializerToDecl(Decl, Init, /*DirectInit=*/false); SemaRef.FinalizeDeclaration(Decl); SemaRef.CurContext->addHiddenDecl(Decl); 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 0..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 struct vector { + T &operator[](size_t I); +}; + +struct string { + const char *begin(); + const char *end(); +}; + +} // namespace std + +std::vector 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 >From b2e1333c2b9e61ffb8b85d1613c137f8f96a73e4 Mon Sep 17 00:00:00 2001 From: Marco Vitale Date: Sat, 28 Jun 2025 00:25:33 +0200 Subject: [PATCH 2/2] Add as a flag in VarDecl --- clang/include/clang/AST/Decl.h| 19 +++ clang/lib/Sema/CheckExprLifetime.cpp | 9 ++--- clang/lib/Sema/SemaStmt.cpp | 1 + clang/lib/Serialization/ASTReaderDecl.cpp | 1 + clang/lib/Serialization/ASTWriterDecl.cpp | 2 ++ 5 files changed, 29 insertions(+), 3 deletions(-) diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h index c4202f1f3d07e..eee925b01d9e8 100644 --- a/clang/include/clang/AST/Decl.h +++ b/clang/include/clang/AST/Decl.h @@ -1086,6 +1086,11 @@ class VarDecl : public DeclaratorDecl, public Redeclarable { 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 { @@ -1585
[clang] [Sema] Fix lifetime extension for temporaries in range-based for loops in C++23 (PR #145164)
mrcvtl wrote: > > but why can’t we use InLifetimeExtendingContext flag here? > > Is there a subtle difference I’m missing? > > This flag will tiger subroutines to collect `MaterializedTemporaryExpr` and > rebuild default init/arg。 All we need to know here is that `ExtendingDecl` is > a C++ `__range` var in for-range-loop, so I think we should introduce a new > flag in VarDecl. We may need to also modify handling of VarDecl in ASTWriter > and ASTReader to ensure that the AST serialization is correct. WDYT? Agreed. My last commit should implement this. Let me know if something is wrong! https://github.com/llvm/llvm-project/pull/145164 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] [Sema] Fix lifetime extension for temporaries in range-based for loops in C++23 (PR #145164)
@@ -2740,6 +2741,7 @@ void ASTWriter::WriteDeclAbbrevs() { // isInline, isInlineSpecified, isConstexpr, // isInitCapture, isPrevDeclInSameScope, hasInitWithSideEffects, // EscapingByref, HasDeducedType, ImplicitParamKind, isObjCForDecl +// isCXXForRangeDecl mrcvtl wrote: This should be `IsCXXForRangeImplicitVar` https://github.com/llvm/llvm-project/pull/145164 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] [Sema] Fix lifetime extension for temporaries in range-based for loops in C++23 (PR #145164)
@@ -1585,6 +1590,20 @@ class VarDecl : public DeclaratorDecl, public Redeclarable { NonParmVarDeclBits.IsCXXCondDecl = true; } + /// Determine whether this variable is the compiler-generated '__range' + /// variable used to hold the range expression in a C++11 and later for-range + /// statement. + bool isCXXForRangeImplicitVar() const { +return isa(this) ? false + : NonParmVarDeclBits.IsCXXForRangeImplicitVar; + } + + void setCXXForRangeImplicitVar(bool FRV) { +assert(!isa(this) && + "Cannot set IsCXXForRangeRangeVar on ParmVarDecl"); mrcvtl wrote: Also here should be `Cannot set IsCXXForRangeRangeVar...`. https://github.com/llvm/llvm-project/pull/145164 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] [Sema] Fix lifetime extension for temporaries in range-based for loops in C++23 (PR #145164)
https://github.com/mrcvtl deleted https://github.com/llvm/llvm-project/pull/145164 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] [Sema] Fix lifetime extension for temporaries in range-based for loops in C++23 (PR #145164)
https://github.com/mrcvtl deleted https://github.com/llvm/llvm-project/pull/145164 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] [Sema] Fix lifetime extension for temporaries in range-based for loops in C++23 (PR #145164)
https://github.com/mrcvtl updated https://github.com/llvm/llvm-project/pull/145164 >From 37c57131c397d4aeaef7fe5b860d1983f1e4bdc2 Mon Sep 17 00:00:00 2001 From: Marco Vitale Date: Sat, 21 Jun 2025 14:01:53 +0200 Subject: [PATCH] [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. --- clang/docs/ReleaseNotes.rst | 4 +++ clang/include/clang/AST/Decl.h| 19 + clang/lib/Sema/CheckExprLifetime.cpp | 8 ++ clang/lib/Sema/SemaStmt.cpp | 4 +++ clang/lib/Serialization/ASTReaderDecl.cpp | 1 + clang/lib/Serialization/ASTWriterDecl.cpp | 2 ++ .../test/SemaCXX/range-for-lifetime-cxx23.cpp | 27 +++ 7 files changed, 65 insertions(+) create mode 100644 clang/test/SemaCXX/range-for-lifetime-cxx23.cpp diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 89d86c3371247..184d1f0b188be 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -643,6 +643,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) Improvements to Clang's time-trace diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h index 0da940883b6f5..ab23346cc2fad 100644 --- a/clang/include/clang/AST/Decl.h +++ b/clang/include/clang/AST/Decl.h @@ -1085,6 +1085,11 @@ class VarDecl : public DeclaratorDecl, public Redeclarable { 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 { @@ -1584,6 +1589,20 @@ class VarDecl : public DeclaratorDecl, public Redeclarable { NonParmVarDeclBits.IsCXXCondDecl = true; } + /// Determine whether this variable is the compiler-generated '__range' + /// variable used to hold the range expression in a C++11 and later for-range + /// statement. + bool isCXXForRangeImplicitVar() const { +return isa(this) ? false + : NonParmVarDeclBits.IsCXXForRangeImplicitVar; + } + + void setCXXForRangeImplicitVar(bool FRV) { +assert(!isa(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..fc52de1e6bd89 100644 --- a/clang/lib/Sema/CheckExprLifetime.cpp +++ b/clang/lib/Sema/CheckExprLifetime.cpp @@ -57,6 +57,7 @@ enum LifetimeKind { }; using LifetimeResult = llvm::PointerIntPair; + } // namespace /// Determine the declaration which an initialized entity ultimately refers to, @@ -1341,6 +1342,13 @@ checkExprLifetimeImpl(Sema &SemaRef, const InitializedEntity *InitEntity, } if (IsGslPtrValueFromGslTempOwner && DiagLoc.isValid()) { + +if (SemaRef.getLangOpts().CPlusPlus23) { + if (const VarDecl *VD = cast(InitEntity->getDecl()); + VD && VD->isCXXForRangeImplicitVar()) +return false; +} + SemaRef.Diag(DiagLoc, diag::warn_dangling_lifetime_pointer) << DiagRange; return false; diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp index 923a9e81fbd6a..ef0aff1b2838f 100644 --- a/clang/lib/Sema/SemaStmt.cpp +++ b/clang/lib/Sema/SemaStmt.cpp @@ -2374,6 +2374,9 @@ static bool FinishForRangeVarDecl(Sema &SemaRef, VarDecl *Decl, Expr *Init, SemaRef.ObjC().inferObjCARCLifetime(Decl)) Decl->setInvalidDecl(); + if (SemaRef.getLangOpts().CPlusPlus23) +SemaRef.currentEvaluationContext().InLifetimeExtendingContext = true; + SemaRef.AddInitializerToDecl(Decl, Init, /*DirectInit=*/false); SemaRef.FinalizeDeclaration(Decl); SemaRef.CurContext->addHiddenDecl(Decl); @@ -2423,6 +2426,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
[clang] [Sema] Fix lifetime extension for temporaries in range-based for loops in C++23 (PR #145164)
https://github.com/mrcvtl updated https://github.com/llvm/llvm-project/pull/145164 >From 37c57131c397d4aeaef7fe5b860d1983f1e4bdc2 Mon Sep 17 00:00:00 2001 From: Marco Vitale Date: Sat, 21 Jun 2025 14:01:53 +0200 Subject: [PATCH 1/2] [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. --- clang/docs/ReleaseNotes.rst | 4 +++ clang/include/clang/AST/Decl.h| 19 + clang/lib/Sema/CheckExprLifetime.cpp | 8 ++ clang/lib/Sema/SemaStmt.cpp | 4 +++ clang/lib/Serialization/ASTReaderDecl.cpp | 1 + clang/lib/Serialization/ASTWriterDecl.cpp | 2 ++ .../test/SemaCXX/range-for-lifetime-cxx23.cpp | 27 +++ 7 files changed, 65 insertions(+) create mode 100644 clang/test/SemaCXX/range-for-lifetime-cxx23.cpp diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 89d86c3371247..184d1f0b188be 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -643,6 +643,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) Improvements to Clang's time-trace diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h index 0da940883b6f5..ab23346cc2fad 100644 --- a/clang/include/clang/AST/Decl.h +++ b/clang/include/clang/AST/Decl.h @@ -1085,6 +1085,11 @@ class VarDecl : public DeclaratorDecl, public Redeclarable { 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 { @@ -1584,6 +1589,20 @@ class VarDecl : public DeclaratorDecl, public Redeclarable { NonParmVarDeclBits.IsCXXCondDecl = true; } + /// Determine whether this variable is the compiler-generated '__range' + /// variable used to hold the range expression in a C++11 and later for-range + /// statement. + bool isCXXForRangeImplicitVar() const { +return isa(this) ? false + : NonParmVarDeclBits.IsCXXForRangeImplicitVar; + } + + void setCXXForRangeImplicitVar(bool FRV) { +assert(!isa(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..fc52de1e6bd89 100644 --- a/clang/lib/Sema/CheckExprLifetime.cpp +++ b/clang/lib/Sema/CheckExprLifetime.cpp @@ -57,6 +57,7 @@ enum LifetimeKind { }; using LifetimeResult = llvm::PointerIntPair; + } // namespace /// Determine the declaration which an initialized entity ultimately refers to, @@ -1341,6 +1342,13 @@ checkExprLifetimeImpl(Sema &SemaRef, const InitializedEntity *InitEntity, } if (IsGslPtrValueFromGslTempOwner && DiagLoc.isValid()) { + +if (SemaRef.getLangOpts().CPlusPlus23) { + if (const VarDecl *VD = cast(InitEntity->getDecl()); + VD && VD->isCXXForRangeImplicitVar()) +return false; +} + SemaRef.Diag(DiagLoc, diag::warn_dangling_lifetime_pointer) << DiagRange; return false; diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp index 923a9e81fbd6a..ef0aff1b2838f 100644 --- a/clang/lib/Sema/SemaStmt.cpp +++ b/clang/lib/Sema/SemaStmt.cpp @@ -2374,6 +2374,9 @@ static bool FinishForRangeVarDecl(Sema &SemaRef, VarDecl *Decl, Expr *Init, SemaRef.ObjC().inferObjCARCLifetime(Decl)) Decl->setInvalidDecl(); + if (SemaRef.getLangOpts().CPlusPlus23) +SemaRef.currentEvaluationContext().InLifetimeExtendingContext = true; + SemaRef.AddInitializerToDecl(Decl, Init, /*DirectInit=*/false); SemaRef.FinalizeDeclaration(Decl); SemaRef.CurContext->addHiddenDecl(Decl); @@ -2423,6 +2426,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.c
[clang] [Sema] Fix lifetime extension for temporaries in range-based for loops in C++23 (PR #145164)
mrcvtl wrote: Old clang tidy test failing seems flaky, locally builds correctly. If it continue to fail I can rebase on upstream. https://github.com/llvm/llvm-project/pull/145164 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits