Index: clang/docs/ReleaseNotes.rst =================================================================== --- clang/docs/ReleaseNotes.rst +++ clang/docs/ReleaseNotes.rst @@ -688,6 +688,7 @@ - Implemented "char8_t Compatibility and Portability Fix" (`P2513R3 `_). This change was applied to C++20 as a Defect Report. - Implemented "Permitting static constexpr variables in constexpr functions" (`P2647R1 _`). +- Implemented "Lifetime extension in range-based for loops" (`P2718R0 _`). CUDA/HIP Language Changes in Clang ---------------------------------- Index: clang/include/clang/AST/Decl.h =================================================================== --- clang/include/clang/AST/Decl.h +++ clang/include/clang/AST/Decl.h @@ -1033,6 +1033,10 @@ /// (NRVO). unsigned NRVOVariable : 1; + /// Whether this variable is __range{depth} initialized by the + /// for-range-initializer in a C++0x for-range statement. + unsigned CXXRangeVar : 1; + /// Whether this variable is the for-range-declaration in a C++0x /// for-range statement. unsigned CXXForRangeDecl : 1; @@ -1457,6 +1461,16 @@ NonParmVarDeclBits.NRVOVariable = NRVO; } + /// Determine whether this variable is __range{depth} initialized by the + /// for-range-initializer in a C++0x for-range statement. + bool isCXXRangeVar() const { + return isa(this) ? false : NonParmVarDeclBits.CXXRangeVar; + } + void setCXXRangeVar(bool RV) { + assert(!isa(this)); + NonParmVarDeclBits.CXXRangeVar = RV; + } + /// Determine whether this variable is the for-range-declaration in /// a C++0x for-range statement. bool isCXXForRangeDecl() const { Index: clang/lib/Sema/SemaInit.cpp =================================================================== --- clang/lib/Sema/SemaInit.cpp +++ clang/lib/Sema/SemaInit.cpp @@ -6851,6 +6851,7 @@ /// the initializer are included. struct IndirectLocalPathEntry { enum EntryKind { + CXX2bForRangeInit, DefaultInit, AddressOf, VarInit, @@ -7188,6 +7189,23 @@ {IndirectLocalPathEntry::DefaultInit, DIE, DIE->getField()}); Init = DIE->getExpr(); } + + if (auto *CE = dyn_cast(Init); + CE && !Path.empty() && + Path.front().Kind == IndirectLocalPathEntry::CXX2bForRangeInit) { + if (EnableLifetimeWarnings) + handleGslAnnotatedTypes(Path, Init, Visit); + visitLifetimeBoundArguments(Path, Init, Visit); + if (auto *ME = dyn_cast(CE->getCallee())) { + Init = ME->getBase(); + } else { + for (auto *RSE : CE->getRawSubExprs()) + if (auto *MTE = dyn_cast(RSE)) + if (Visit(Path, Local(MTE), RK)) + visitLocalsRetainedByInitializer(Path, MTE->getSubExpr(), Visit, + true, EnableLifetimeWarnings); + } + } } while (Init != Old); if (auto *MTE = dyn_cast(Init)) { @@ -7356,6 +7374,24 @@ Init = CE->getSubExpr(); } + + if (auto *CE = dyn_cast(Init); + CE && !Path.empty() && + Path.front().Kind == IndirectLocalPathEntry::CXX2bForRangeInit) { + if (EnableLifetimeWarnings) + handleGslAnnotatedTypes(Path, Init, Visit); + visitLifetimeBoundArguments(Path, Init, Visit); + if (auto *ME = dyn_cast(CE->getCallee())) { + visitLocalsRetainedByReferenceBinding(Path, ME->getBase(), + RK_ReferenceBinding, Visit, + EnableLifetimeWarnings); + } else { + for (auto *RSE : CE->getRawSubExprs()) + if (auto *MTE = dyn_cast(RSE)) + visitLocalsRetainedByReferenceBinding( + Path, MTE, RK_ReferenceBinding, Visit, EnableLifetimeWarnings); + } + } } while (Old != Init); // C++17 [dcl.init.list]p6: @@ -7558,7 +7594,8 @@ for (auto Elem : Path) { if (Elem.Kind == IndirectLocalPathEntry::DefaultInit) Kind = PathLifetimeKind::ShouldExtend; - else if (Elem.Kind != IndirectLocalPathEntry::LambdaCaptureInit) + else if (Elem.Kind != IndirectLocalPathEntry::LambdaCaptureInit && + Path.front().Kind != IndirectLocalPathEntry::CXX2bForRangeInit) return PathLifetimeKind::NoExtend; } return Kind; @@ -7575,6 +7612,7 @@ case IndirectLocalPathEntry::TemporaryCopy: case IndirectLocalPathEntry::GslReferenceInit: case IndirectLocalPathEntry::GslPointerInit: + case IndirectLocalPathEntry::CXX2bForRangeInit: // These exist primarily to mark the path as not permitting or // supporting lifetime extension. break; @@ -7824,6 +7862,7 @@ switch (Elem.Kind) { case IndirectLocalPathEntry::AddressOf: case IndirectLocalPathEntry::LValToRVal: + case IndirectLocalPathEntry::CXX2bForRangeInit: // These exist primarily to mark the path as not permitting or // supporting lifetime extension. break; @@ -7873,6 +7912,9 @@ bool EnableLifetimeWarnings = !getDiagnostics().isIgnored( diag::warn_dangling_lifetime_pointer, SourceLocation()); llvm::SmallVector Path; + if (auto *VD = dyn_cast_or_null(Entity.getDecl()); + getLangOpts().CPlusPlus2b && VD && VD->isCXXRangeVar()) + Path.push_back({IndirectLocalPathEntry::CXX2bForRangeInit, nullptr}); if (Init->isGLValue()) visitLocalsRetainedByReferenceBinding(Path, Init, RK_ReferenceBinding, TemporaryVisitor, Index: clang/lib/Sema/SemaStmt.cpp =================================================================== --- clang/lib/Sema/SemaStmt.cpp +++ clang/lib/Sema/SemaStmt.cpp @@ -2529,6 +2529,7 @@ VarDecl *RangeVar = BuildForRangeVarDecl(*this, RangeLoc, Context.getAutoRRefDeductType(), std::string("__range") + DepthStr); + RangeVar->setCXXRangeVar(true); if (FinishForRangeVarDecl(*this, RangeVar, Range, RangeLoc, diag::err_for_range_deduction_failure)) { ActOnInitializerError(LoopVar); Index: clang/test/CXX/stmt.stmt/stmt.iter/stmt.ranged/p1.cpp =================================================================== --- clang/test/CXX/stmt.stmt/stmt.iter/stmt.ranged/p1.cpp +++ clang/test/CXX/stmt.stmt/stmt.iter/stmt.ranged/p1.cpp @@ -1,6 +1,7 @@ -// RUN: %clang_cc1 -std=c++11 -fsyntax-only -verify %s -// RUN: %clang_cc1 -std=c++14 -fsyntax-only -verify %s -// RUN: %clang_cc1 -std=c++17 -fsyntax-only -verify %s +// RUN: %clang_cc1 -std=c++11 -verify=expected,pre-cxx2b %s +// RUN: %clang_cc1 -std=c++14 -verify=expected,pre-cxx2b %s +// RUN: %clang_cc1 -std=c++17 -verify=expected,pre-cxx2b %s +// RUN: %clang_cc1 -std=c++2b -verify %s struct pr12960 { int begin; @@ -342,3 +343,19 @@ for (auto x : f) {} // expected-error {{invalid range expression of type 'NF::F'; no viable 'end' function available}} } } + +namespace p2718r0 { +struct T { + const int *begin() const; + const int *end() const; + T &r() [[clang::lifetimebound]]; +}; + +const T &f1(const T &t [[clang::lifetimebound]]) { return t; } +T g(); + +void foo() { + for (auto e : f1(g())) {} // pre-cxx2b-warning {{temporary implicitly bound to local reference will be destroyed at the end of the full-expression}} + for (auto e : g().r()) {} // pre-cxx2b-warning {{temporary implicitly bound to local reference will be destroyed at the end of the full-expression}} +} +} Index: clang/www/cxx_status.html =================================================================== --- clang/www/cxx_status.html +++ clang/www/cxx_status.html @@ -1533,7 +1533,7 @@ Lifetime extension in range-based for loops P2718R0 - No + Clang 16