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 <mar.vit...@icloud.com> 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<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 { @@ -1584,6 +1589,20 @@ class VarDecl : public DeclaratorDecl, public Redeclarable<VarDecl> { 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<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..fc52de1e6bd89 100644 --- a/clang/lib/Sema/CheckExprLifetime.cpp +++ b/clang/lib/Sema/CheckExprLifetime.cpp @@ -57,6 +57,7 @@ enum LifetimeKind { }; using LifetimeResult = llvm::PointerIntPair<const InitializedEntity *, 3, LifetimeKind>; + } // 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<VarDecl>(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 b/clang/lib/Serialization/ASTReaderDecl.cpp index 0ffd78424be0d..e6a8a0a243401 100644 --- a/clang/lib/Serialization/ASTReaderDecl.cpp +++ b/clang/lib/Serialization/ASTReaderDecl.cpp @@ -1637,6 +1637,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 2e390dbe79ec6..1d4c3e4e7b88a 100644 --- a/clang/lib/Serialization/ASTWriterDecl.cpp +++ b/clang/lib/Serialization/ASTWriterDecl.cpp @@ -1319,6 +1319,7 @@ void ASTDeclWriter::VisitVarDecl(VarDecl *D) { VarDeclBits.addBits(0, /*Width=*/3); VarDeclBits.addBit(D->isObjCForDecl()); + VarDeclBits.addBit(D->isCXXForRangeImplicitVar()); } Record.push_back(VarDeclBits); @@ -2740,6 +2741,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/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 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits