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 <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/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<const InitializedEntity *, 3, LifetimeKind>; + +/// 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<VarDecl>(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 0000000000000..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 <string> +#include <vector> +#include <map> +#include <tuple> +#include <optional> +#include <variant> +#include <array> +#include <span> + +static std::vector<std::string> getVector() { + return {"first", "second", "third"}; +} + +static std::map<std::string, std::vector<int>> getMap() { + return {{"key", {1, 2, 3}}}; +} + +static std::tuple<std::vector<double>> getTuple() { + return std::make_tuple(std::vector<double>{3.14, 2.71}); +} + +static std::optional<std::vector<char>> getOptionalColl() { + return std::vector<char>{'x', 'y', 'z'}; +} + +static std::variant<std::string, int> getVariant() { + return std::string("variant"); +} + +static const std::array<int, 4>& arrOfConst() { + static const std::array<int, 4> 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<std::string>(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