[clang] [Sema] Fix lifetime extension for temporaries in range-based for loops in C++23 (PR #145164)
yronglin wrote: > LLVM Buildbot has detected a new failure on builder `sanitizer-aarch64-linux` > running on `sanitizer-buildbot7` while building `clang` at step 2 "annotate". > > Full details are available at: > https://lab.llvm.org/buildbot/#/builders/51/builds/19520 > > Here is the relevant piece of the build log for the reference This failure seem not caused by this PR. https://github.com/llvm/llvm-project/pull/145164 ___ cfe-commits mailing list [email protected] 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)
llvm-ci wrote: LLVM Buildbot has detected a new failure on builder `sanitizer-aarch64-linux` running on `sanitizer-buildbot7` while building `clang` at step 2 "annotate". Full details are available at: https://lab.llvm.org/buildbot/#/builders/51/builds/19520 Here is the relevant piece of the build log for the reference ``` Step 2 (annotate) failure: 'python ../sanitizer_buildbot/sanitizers/zorg/buildbot/builders/sanitizers/buildbot_selector.py' (failure) ... llvm-lit: /home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/test/lit.common.cfg.py:60: warning: Path reported by clang does not exist: "/home/b/sanitizer-aarch64-linux/build/compiler_rt_build/lib/aarch64-unknown-linux-gnu". This path was found by running ['/home/b/sanitizer-aarch64-linux/build/build_default/bin/clang', '--target=aarch64-unknown-linux-gnu', '-Wthread-safety', '-Wthread-safety-reference', '-Wthread-safety-beta', '-nobuiltininc', '-I/home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/include', '-idirafter', '/home/b/sanitizer-aarch64-linux/build/build_default/lib/clang/21/include', '-resource-dir=/home/b/sanitizer-aarch64-linux/build/compiler_rt_build', '-Wl,-rpath,/home/b/sanitizer-aarch64-linux/build/compiler_rt_build/lib/linux', '-print-runtime-dir']. llvm-lit: /home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/test/lit.common.cfg.py:60: warning: Path reported by clang does not exist: "/home/b/sanitizer-aarch64-linux/build/compiler_rt_build/lib/aarch64-unknown-linux-gnu". This path was found by running ['/home/b/sanitizer-aarch64-linux/build/build_default/bin/clang', '--target=aarch64-unknown-linux-gnu', '-Wthread-safety', '-Wthread-safety-reference', '-Wthread-safety-beta', '-nobuiltininc', '-I/home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/include', '-idirafter', '/home/b/sanitizer-aarch64-linux/build/build_default/lib/clang/21/include', '-resource-dir=/home/b/sanitizer-aarch64-linux/build/compiler_rt_build', '-Wl,-rpath,/home/b/sanitizer-aarch64-linux/build/compiler_rt_build/lib/linux', '-print-runtime-dir']. llvm-lit: /home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/test/lit.common.cfg.py:60: warning: Path reported by clang does not exist: "/home/b/sanitizer-aarch64-linux/build/compiler_rt_build/lib/aarch64-unknown-linux-gnu". This path was found by running ['/home/b/sanitizer-aarch64-linux/build/build_default/bin/clang', '--target=aarch64-unknown-linux-gnu', '-Wthread-safety', '-Wthread-safety-reference', '-Wthread-safety-beta', '-nobuiltininc', '-I/home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/include', '-idirafter', '/home/b/sanitizer-aarch64-linux/build/build_default/lib/clang/21/include', '-resource-dir=/home/b/sanitizer-aarch64-linux/build/compiler_rt_build', '-Wl,-rpath,/home/b/sanitizer-aarch64-linux/build/compiler_rt_build/lib/linux', '-print-runtime-dir']. llvm-lit: /home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/test/lit.common.cfg.py:60: warning: Path reported by clang does not exist: "/home/b/sanitizer-aarch64-linux/build/compiler_rt_build/lib/aarch64-unknown-linux-gnu". This path was found by running ['/home/b/sanitizer-aarch64-linux/build/build_default/bin/clang', '--target=aarch64-unknown-linux-gnu', '-Wthread-safety', '-Wthread-safety-reference', '-Wthread-safety-beta', '-nobuiltininc', '-I/home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/include', '-idirafter', '/home/b/sanitizer-aarch64-linux/build/build_default/lib/clang/21/include', '-resource-dir=/home/b/sanitizer-aarch64-linux/build/compiler_rt_build', '-Wl,-rpath,/home/b/sanitizer-aarch64-linux/build/compiler_rt_build/lib/linux', '-print-runtime-dir']. llvm-lit: /home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/test/lit.common.cfg.py:60: warning: Path reported by clang does not exist: "/home/b/sanitizer-aarch64-linux/build/compiler_rt_build/lib/aarch64-unknown-linux-gnu". This path was found by running ['/home/b/sanitizer-aarch64-linux/build/build_default/bin/clang', '--target=aarch64-unknown-linux-gnu', '-Wthread-safety', '-Wthread-safety-reference', '-Wthread-safety-beta', '-nobuiltininc', '-I/home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/include', '-idirafter', '/home/b/sanitizer-aarch64-linux/build/build_default/lib/clang/21/include', '-resource-dir=/home/b/sanitizer-aarch64-linux/build/compiler_rt_build', '-Wl,-rpath,/home/b/sanitizer-aarch64-linux/build/compiler_rt_build/lib/linux', '-print-runtime-dir']. llvm-lit: /home/b/sanitizer-aarch64-linux/build/llvm-project/compiler-rt/test/lit.common.cfg.py:60: warning: Path reported by clang does not exist: "/home/b/sanitizer-aarch64-linux/build/compiler_rt_build/lib/aarch64-unknown-linux-gnu". This path was found by running ['/home/b/sanitizer-aarch64-linux/build/build_default/bin/clang', '--target=aarch64-unknown-linux-gnu', '-Wthread-safety', '-Wthread-safety-reference', '-Wthread-safety-be
[clang] [Sema] Fix lifetime extension for temporaries in range-based for loops in C++23 (PR #145164)
yronglin wrote: Congratulations, the first LLVM PR has landed! https://github.com/llvm/llvm-project/pull/145164 ___ cfe-commits mailing list [email protected] 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)
github-actions[bot] wrote: @mrcvtl Congratulations on having your first Pull Request (PR) merged into the LLVM Project! Your changes will be combined with recent changes from other authors, then tested by our [build bots](https://lab.llvm.org/buildbot/). If there is a problem with a build, you may receive a report in an email or a comment on this PR. Please check whether problems have been caused by your change specifically, as the builds can include changes from many authors. It is not uncommon for your change to be included in a build that fails due to someone else's changes, or infrastructure issues. How to do this, and the rest of the post-merge process, is covered in detail [here](https://llvm.org/docs/MyFirstTypoFix.html#myfirsttypofix-issues-after-landing-your-pr). If your change does cause a problem, it may be reverted, or you can revert it yourself. This is a normal part of [LLVM development](https://llvm.org/docs/DeveloperPolicy.html#patch-reversion-policy). You can fix your changes and open a new PR to merge them again. If you don't get any reports, no action is required from you. Your changes are working as expected, well done! https://github.com/llvm/llvm-project/pull/145164 ___ cfe-commits mailing list [email protected] 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/yronglin closed https://github.com/llvm/llvm-project/pull/145164 ___ cfe-commits mailing list [email protected] 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: > Do you need the help to merge this PR? Yes please, also not sure if I can merge being my first PR. Also should we wait on the third review? Not sure about the reviews policies! https://github.com/llvm/llvm-project/pull/145164 ___ cfe-commits mailing list [email protected] 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 1/4] [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)
yronglin wrote: Do you need the help to merge this PR? https://github.com/llvm/llvm-project/pull/145164 ___ cfe-commits mailing list [email protected] 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/yronglin approved this pull request. LGTM https://github.com/llvm/llvm-project/pull/145164 ___ cfe-commits mailing list [email protected] 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/hokein approved this pull request. This change looks good to me. https://github.com/llvm/llvm-project/pull/145164 ___ cfe-commits mailing list [email protected] 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)
hokein wrote: > I have debug the issue, seems we cannot move the __range variable check to > the start of checkExprLifetimeImpl, we may missing extending lifetime of > temporaries. Ah, that makes sense -- I missed the fact that this part of the code is also responsible for extending the object’s lifetime. Thanks a lot for looking into this and clarifying it. https://github.com/llvm/llvm-project/pull/145164 ___ cfe-commits mailing list [email protected] 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 1/4] [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)
yronglin wrote:
> Forgot that the revert wouldn't include the `lifetimebound` attribute. I
> restructured the switch case like that, in order to catch both:
>
> ```
> case LK_Extended: {
> if (!MTE) {
> // The initialized entity has lifetime beyond the full-expression,
> // and the local entity does too, so don't warn.
> //
> // FIXME: We should consider warning if a static / thread storage
> // duration variable retains an automatic storage duration local.
> return false;
> }
>
> switch (shouldLifetimeExtendThroughPath(Path)) {
> case PathLifetimeKind::Extend:
> assert(!IsGslPtrValueFromGslTempOwner); // Just a test, to be sure
> that we aren't losing something here.
> // can be removed safely I
> guess.
>
> // Update the storage duration of the materialized temporary.
> // FIXME: Rebuild the expression instead of mutating it.
> MTE->setExtendingDecl(ExtendingEntity->getDecl(),
> ExtendingEntity->allocateManglingNumber());
> // Also visit the temporaries lifetime-extended by this initializer.
> return true;
>
> case PathLifetimeKind::NoExtend:
> if (SemaRef.getLangOpts().CPlusPlus23 && InitEntity) {
> if (const VarDecl *VD =
> dyn_cast_if_present(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.
> if (pathContainsInit(Path))
> return false;
>
> SemaRef.Diag(DiagLoc, diag::warn_dangling_variable)
> << RK << !InitEntity->getParent()
> << ExtendingEntity->getDecl()->isImplicit()
> << ExtendingEntity->getDecl() << Init->isGLValue() << DiagRange;
> break;
> }
> break;
> }
> ```
Could you push the changes to this PR?
https://github.com/llvm/llvm-project/pull/145164
___
cfe-commits mailing list
[email protected]
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:
Forgot that the revert wouldn't include the `lifetimebound` attribute. I
restructured the switch case like that, in order to catch both:
```
case LK_Extended: {
if (!MTE) {
// The initialized entity has lifetime beyond the full-expression,
// and the local entity does too, so don't warn.
//
// FIXME: We should consider warning if a static / thread storage
// duration variable retains an automatic storage duration local.
return false;
}
switch (shouldLifetimeExtendThroughPath(Path)) {
case PathLifetimeKind::Extend:
assert(!IsGslPtrValueFromGslTempOwner); // Just a test, to be sure that
we aren't losing something here.
// can be removed safely I
guess.
// Update the storage duration of the materialized temporary.
// FIXME: Rebuild the expression instead of mutating it.
MTE->setExtendingDecl(ExtendingEntity->getDecl(),
ExtendingEntity->allocateManglingNumber());
// Also visit the temporaries lifetime-extended by this initializer.
return true;
case PathLifetimeKind::NoExtend:
if (SemaRef.getLangOpts().CPlusPlus23 && InitEntity) {
if (const VarDecl *VD =
dyn_cast_if_present(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.
if (pathContainsInit(Path))
return false;
SemaRef.Diag(DiagLoc, diag::warn_dangling_variable)
<< RK << !InitEntity->getParent()
<< ExtendingEntity->getDecl()->isImplicit()
<< ExtendingEntity->getDecl() << Init->isGLValue() << DiagRange;
break;
}
break;
}
```
https://github.com/llvm/llvm-project/pull/145164
___
cfe-commits mailing list
[email protected]
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 think we should revert to the previous approach, what do you think?
>
> ```c++
> if (IsGslPtrValueFromGslTempOwner && DiagLoc.isValid()) {
> if (const auto *VD =
> dyn_cast_if_present(ExtendingEntity->getDecl());
> SemaRef.getLangOpts().CPlusPlus23 && VD &&
> VD->isCXXForRangeImplicitVar())
> return true;
> SemaRef.Diag(DiagLoc, diag::warn_dangling_lifetime_pointer)
> << DiagRange;
> return false;
> }
> ```
Thanks for debugging that!
I agree btw, didn't think about this side effect before.
https://github.com/llvm/llvm-project/pull/145164
___
cfe-commits mailing list
[email protected]
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 have debug the issue, seems we cannot move the `__range` variable check to
> the start of `checkExprLifetimeImpl`, we may missing extending lifetime of
> temporaries. Eg.
>
> ```c++
> template
> struct ListWrapper {
> ListWrapper() {}
> ~ListWrapper() {}
> const T *begin() const;
> const T *end() const;
> ListWrapper& r();
> ListWrapper g();
> };
>
> using A = ListWrapper;
>
> A g() { return A(); }
> const A &f1(const A &t) { return t; }
>
> void member_call() {
> // CHECK-CXX23: void @_ZN7P2718R011member_call11member_callEv()
> // CHECK-CXX23-LABEL: for.cond.cleanup:
> // CHECK-CXX23-NEXT: call void
> @_ZN7P2718R011member_call11ListWrapperIiED1Ev(
> // CHECK-CXX23-NEXT: call void
> @_ZN7P2718R011member_call11ListWrapperIiED1Ev(
> // CHECK-CXX23-NEXT: call void
> @_ZN7P2718R011member_call11ListWrapperIiED1Ev(
> // CHECK-CXX23-NEXT: call void
> @_ZN7P2718R011member_call11ListWrapperIiED1Ev(
> for (auto e : g().r().g().r().g().r().g()) {}
> }
> ```
>
> I think we should revert to the previous approach, what do you think?
>
> ```c++
> if (IsGslPtrValueFromGslTempOwner && DiagLoc.isValid()) {
> if (const auto *VD =
> dyn_cast_if_present(ExtendingEntity->getDecl());
> SemaRef.getLangOpts().CPlusPlus23 && VD &&
> VD->isCXXForRangeImplicitVar())
> return true;
> SemaRef.Diag(DiagLoc, diag::warn_dangling_lifetime_pointer)
> << DiagRange;
> return false;
> }
> ```
Agree, there are some side effect that I can't fully understand.
> I have debug the issue, seems we cannot move the `__range` variable check to
> the start of `checkExprLifetimeImpl`, we may missing extending lifetime of
> temporaries. Eg.
>
> ```c++
> template
> struct ListWrapper {
> ListWrapper() {}
> ~ListWrapper() {}
> const T *begin() const;
> const T *end() const;
> ListWrapper& r();
> ListWrapper g();
> };
>
> using A = ListWrapper;
>
> A g() { return A(); }
> const A &f1(const A &t) { return t; }
>
> void member_call() {
> // CHECK-CXX23: void @_ZN7P2718R011member_call11member_callEv()
> // CHECK-CXX23-LABEL: for.cond.cleanup:
> // CHECK-CXX23-NEXT: call void
> @_ZN7P2718R011member_call11ListWrapperIiED1Ev(
> // CHECK-CXX23-NEXT: call void
> @_ZN7P2718R011member_call11ListWrapperIiED1Ev(
> // CHECK-CXX23-NEXT: call void
> @_ZN7P2718R011member_call11ListWrapperIiED1Ev(
> // CHECK-CXX23-NEXT: call void
> @_ZN7P2718R011member_call11ListWrapperIiED1Ev(
> for (auto e : g().r().g().r().g().r().g()) {}
> }
> ```
>
> I think we should revert to the previous approach, what do you think?
>
> ```c++
> if (IsGslPtrValueFromGslTempOwner && DiagLoc.isValid()) {
> if (const auto *VD =
> dyn_cast_if_present(ExtendingEntity->getDecl());
> SemaRef.getLangOpts().CPlusPlus23 && VD &&
> VD->isCXXForRangeImplicitVar())
> return true;
> SemaRef.Diag(DiagLoc, diag::warn_dangling_lifetime_pointer)
> << DiagRange;
> return false;
> }
> ```
I agree. There are so
> I have debug the issue, seems we cannot move the `__range` variable check to
> the start of `checkExprLifetimeImpl`, we may missing extending lifetime of
> temporaries. Eg.
>
> ```c++
> template
> struct ListWrapper {
> ListWrapper() {}
> ~ListWrapper() {}
> const T *begin() const;
> const T *end() const;
> ListWrapper& r();
> ListWrapper g();
> };
>
> using A = ListWrapper;
>
> A g() { return A(); }
> const A &f1(const A &t) { return t; }
>
> void member_call() {
> // CHECK-CXX23: void @_ZN7P2718R011member_call11member_callEv()
> // CHECK-CXX23-LABEL: for.cond.cleanup:
> // CHECK-CXX23-NEXT: call void
> @_ZN7P2718R011member_call11ListWrapperIiED1Ev(
> // CHECK-CXX23-NEXT: call void
> @_ZN7P2718R011member_call11ListWrapperIiED1Ev(
> // CHECK-CXX23-NEXT: call void
> @_ZN7P2718R011member_call11ListWrapperIiED1Ev(
> // CHECK-CXX23-NEXT: call void
> @_ZN7P2718R011member_call11ListWrapperIiED1Ev(
> for (auto e : g().r().g().r().g().r().g()) {}
> }
> ```
>
> I think we should revert to the previous approach, what do you think?
>
> ```c++
> if (IsGslPtrValueFromGslTempOwner && DiagLoc.isValid()) {
> if (const auto *VD =
> dyn_cast_if_present(ExtendingEntity->getDecl());
> SemaRef.getLangOpts().CPlusPlus23 && VD &&
> VD->isCXXForRangeImplicitVar())
> return true;
> SemaRef.Diag(DiagLoc, diag::warn_dangling_lifetime_pointer)
> << DiagRange;
> return false;
> }
> ```
Thanks for debugging that!
I agree btw, didn't think about this side effect before.
https://github.com/llvm/llvm-project/pull/145164
___
cfe-commits mailing list
[email protected]
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)
yronglin wrote:
I have debug the issue, seems we cannot move the `__range` variable check to
the start of `checkExprLifetimeImpl`, we may missing extending lifetime of
temporaries.
Eg.
```cpp
template
struct ListWrapper {
ListWrapper() {}
~ListWrapper() {}
const T *begin() const;
const T *end() const;
ListWrapper& r();
ListWrapper g();
};
using A = ListWrapper;
A g() { return A(); }
const A &f1(const A &t) { return t; }
void member_call() {
// CHECK-CXX23: void @_ZN7P2718R011member_call11member_callEv()
// CHECK-CXX23-LABEL: for.cond.cleanup:
// CHECK-CXX23-NEXT: call void @_ZN7P2718R011member_call11ListWrapperIiED1Ev(
// CHECK-CXX23-NEXT: call void @_ZN7P2718R011member_call11ListWrapperIiED1Ev(
// CHECK-CXX23-NEXT: call void @_ZN7P2718R011member_call11ListWrapperIiED1Ev(
// CHECK-CXX23-NEXT: call void @_ZN7P2718R011member_call11ListWrapperIiED1Ev(
for (auto e : g().r().g().r().g().r().g()) {}
}
```
I think we should revert to the previous approach, what do you think?
```cpp
if (IsGslPtrValueFromGslTempOwner && DiagLoc.isValid()) {
if (const auto *VD =
dyn_cast_if_present(ExtendingEntity->getDecl());
SemaRef.getLangOpts().CPlusPlus23 && VD &&
VD->isCXXForRangeImplicitVar())
return true;
SemaRef.Diag(DiagLoc, diag::warn_dangling_lifetime_pointer)
<< DiagRange;
return false;
}
```
https://github.com/llvm/llvm-project/pull/145164
___
cfe-commits mailing list
[email protected]
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)
hokein wrote:
> Yes, indeed. For example, in `AST/ast-dump-for-range-lifetime.cpp` we move
> from: `-MaterializeTemporaryExpr {{.*}} 'C':'P2718R0::C' xvalue extended by
> Var {{.*}} '__range1' 'C &&'` to: `-MaterializeTemporaryExpr {{.*}}
> 'C':'P2718R0::C' xvalue`. At a first look it seems like we are losing info in
> the AST, but also a couple of line later we got: `-MaterializeTemporaryExpr
> {{.*}} 'const C':'const P2718R0::C' lvalue extended by Var {{.*}} '__range1'
> 'C &&'` So it might be fine...
>
> In `special/class.temporary/p6.cpp` instead is a bit more weird, because it
> seems we are losing some IR call in case of for-range loops. You can take a
> look in the logs of the failing test.
>
> Given that I don't know enough about it to say if it is okay or not, I wanted
> to double check with you. What do you think?
Thanks for looking at them.
It seems this patch is causing some unexpected behavior changes. From what I
can tell, this patch only adds a new bit to Decl, which shouldn’t affect the
two failing tests. However, I don't spot any obvious cause by reading the
patch. Maybe @yronglin has some insights here.
https://github.com/llvm/llvm-project/pull/145164
___
cfe-commits mailing list
[email protected]
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:
> There are some test failures in the presubmit checks, you probably need to
> update these tests.
Yes, indeed. For example, in `AST/ast-dump-for-range-lifetime.cpp` we move from:
`-MaterializeTemporaryExpr {{.*}} 'C':'P2718R0::C' xvalue extended by Var
{{.*}} '__range1' 'C &&'`
to:
`-MaterializeTemporaryExpr {{.*}} 'C':'P2718R0::C' xvalue`.
At a first look it seems like we are losing info in the AST, but also a couple
of line later we got:
`-MaterializeTemporaryExpr {{.*}} 'const C':'const P2718R0::C' lvalue extended
by Var {{.*}} '__range1' 'C &&'`
So it might be fine...
In `special/class.temporary/p6.cpp` instead is a bit more weird, because it
seems we are losing some IR call in case of for-range loops. You can take a
look in the logs of the failing test.
Given that I don't know enough about it to say if it is okay or not, I wanted
to double check with you. What do you think?
https://github.com/llvm/llvm-project/pull/145164
___
cfe-commits mailing list
[email protected]
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)
@@ -1304,6 +1304,13 @@ checkExprLifetimeImpl(Sema &SemaRef, const
InitializedEntity *InitEntity,
if (LK == LK_FullExpression)
return;
+ if (LK == LK_Extended && SemaRef.getLangOpts().CPlusPlus23) {
mrcvtl wrote:
Yes, we can. We just need to check also for `InitEntity != nullptr` and it
works I guess.
https://github.com/llvm/llvm-project/pull/145164
___
cfe-commits mailing list
[email protected]
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/hokein commented: There are some test failures in the presubmit checks, you probably need to update these tests. https://github.com/llvm/llvm-project/pull/145164 ___ cfe-commits mailing list [email protected] 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)
@@ -1304,6 +1304,13 @@ checkExprLifetimeImpl(Sema &SemaRef, const
InitializedEntity *InitEntity,
if (LK == LK_FullExpression)
return;
+ if (LK == LK_Extended && SemaRef.getLangOpts().CPlusPlus23) {
hokein wrote:
Is the `LK == LK_Extended` check necessary here? I think we can simply bail out
in this case, regardless of the LK value.
https://github.com/llvm/llvm-project/pull/145164
___
cfe-commits mailing list
[email protected]
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)
@@ -1304,6 +1304,13 @@ checkExprLifetimeImpl(Sema &SemaRef, const
InitializedEntity *InitEntity,
if (LK == LK_FullExpression)
return;
+ if (LK == LK_Extended && SemaRef.getLangOpts().CPlusPlus23) {
mrcvtl wrote:
both this and the previous if condition can also be moved in the switch case
after the temporary visitor. No strong opinion here.
https://github.com/llvm/llvm-project/pull/145164
___
cfe-commits mailing list
[email protected]
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 1/3] [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)
@@ -1341,6 +1341,14 @@ checkExprLifetimeImpl(Sema &SemaRef, const
InitializedEntity *InitEntity,
}
if (IsGslPtrValueFromGslTempOwner && DiagLoc.isValid()) {
+
+if (SemaRef.getLangOpts().CPlusPlus23) {
yronglin wrote:
Only update this test seems make sense.
https://github.com/llvm/llvm-project/pull/145164
___
cfe-commits mailing list
[email protected]
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)
@@ -1341,6 +1341,14 @@ checkExprLifetimeImpl(Sema &SemaRef, const
InitializedEntity *InitEntity,
}
if (IsGslPtrValueFromGslTempOwner && DiagLoc.isValid()) {
+
+if (SemaRef.getLangOpts().CPlusPlus23) {
hokein wrote:
the analysis is done here, and we filter out the result. I think we can filter
out this case before (in `checkExprLifetimeImpl`) running the analysis to save
some cost.
Can you add a testcase (for the `lifetimebound` attr)? Make sure we don't warn
on it
```
using size_t = decltype(sizeof(void *));
namespace my_ns {
template struct vector {
T &operator[](size_t I) [[clang::lifetimebound]];
};
struct string {
const char *begin();
const char *end();
};
} // namespace std
my_ns::vector getData();
void foo() {
for (auto c : getData()[0]) {
(void)c;
}
}
```
https://github.com/llvm/llvm-project/pull/145164
___
cfe-commits mailing list
[email protected]
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/yronglin edited https://github.com/llvm/llvm-project/pull/145164 ___ cfe-commits mailing list [email protected] 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/yronglin approved this pull request. Thanks for this fix! LGTM https://github.com/llvm/llvm-project/pull/145164 ___ cfe-commits mailing list [email protected] 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: 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 [email protected] 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 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)
@@ -2374,6 +2374,9 @@ static bool FinishForRangeVarDecl(Sema &SemaRef, VarDecl *Decl, Expr *Init, SemaRef.ObjC().inferObjCARCLifetime(Decl)) Decl->setInvalidDecl(); + if (SemaRef.getLangOpts().CPlusPlus23) yronglin wrote: I think we don't need this change. We already has a flag in `VarDecl`. https://github.com/llvm/llvm-project/pull/145164 ___ cfe-commits mailing list [email protected] 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)
@@ -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());
yronglin wrote:
```suggestion
if (const VarDecl *VD =
dyn_cast_if_present(InitEntity->getDecl());
```
https://github.com/llvm/llvm-project/pull/145164
___
cfe-commits mailing list
[email protected]
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)
@@ -57,6 +57,7 @@ enum LifetimeKind {
};
using LifetimeResult =
llvm::PointerIntPair;
+
yronglin wrote:
Nit: Let's revert blank modification.
https://github.com/llvm/llvm-project/pull/145164
___
cfe-commits mailing list
[email protected]
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)
@@ -1584,6 +1589,20 @@ class VarDecl : public DeclaratorDecl, public
Redeclarable {
NonParmVarDeclBits.IsCXXCondDecl = true;
}
+ /// Determine whether this variable is the compiler-generated '__range'
yronglin wrote:
```suggestion
/// Whether this variable is the implicit '__range' variable in C++
range-based for loops.
```
https://github.com/llvm/llvm-project/pull/145164
___
cfe-commits mailing list
[email protected]
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 deleted https://github.com/llvm/llvm-project/pull/145164 ___ cfe-commits mailing list [email protected] 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 [email protected] 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
[email protected]
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
[email protected]
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: > > 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 [email protected] 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)
yronglin 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? https://github.com/llvm/llvm-project/pull/145164 ___ cfe-commits mailing list [email protected] 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
[email protected]
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/shafik commented: As a previous comment noted, this needs a release note but I think the summary should also mention that this fixes the warning diagnostic from `-Wdangling-gsl`. Maybe worth a comment in the test explaining these are verifying we don't trigger a diagnostic from `-Wdangling-gsl` https://github.com/llvm/llvm-project/pull/145164 ___ cfe-commits mailing list [email protected] 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)
yronglin 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 would be 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!).
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
https://github.com/llvm/llvm-project/pull/145164
___
cfe-commits mailing list
[email protected]
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)
@@ -0,0 +1,79 @@
+// RUN: %clangxx -std=c++23 -fsyntax-only -Xclang -verify %s
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
yronglin wrote:
I have a more simple reproducer:
```cpp
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
extern "C" int printf(const char *, ...);
std::vector getData();
void foo() {
for (auto c : getData()[0]) {
printf("%c\n", c);
}
}
```
https://github.com/llvm/llvm-project/pull/145164
___
cfe-commits mailing list
[email protected]
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 [email protected] 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)
yronglin 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
Thanks for you fix!
Maybe we can use `isInLifetimeExtendingContext()` instead of check the
`__range` variable name. We usually get into an `LifetimeExtendingContex` in
Sema. FYI, the initial PR is https://github.com/llvm/llvm-project/pull/76361.
https://github.com/llvm/llvm-project/pull/145164
___
cfe-commits mailing list
[email protected]
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)
@@ -0,0 +1,79 @@
+// RUN: %clangxx -std=c++23 -fsyntax-only -Xclang -verify %s
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
yronglin wrote:
We can construct some simple fake std dependencies.
Eg.
```cpp
namespace std {
typedef decltype(sizeof(int)) size_t;
template
struct initializer_list {
const E *begin;
size_t size;
initializer_list() : begin(nullptr), size(0) {}
};
template
struct list {
list() {}
~list() {}
E *begin();
E *end();
const E *begin() const;
const E *end() const;
};
template
struct vector {
vector() {}
vector(std::initializer_list) {}
~vector() {}
E *begin();
E *end();
const E *begin() const;
const E *end() const;
};
} // namespace std
```
https://github.com/llvm/llvm-project/pull/145164
___
cfe-commits mailing list
[email protected]
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)
@@ -0,0 +1,79 @@ +// RUN: %clangxx -std=c++23 -fsyntax-only -Xclang -verify %s zwuis wrote: Use `%clang_cc1` for frontend tests. `-Xclang` can be removed. https://github.com/llvm/llvm-project/pull/145164 ___ cfe-commits mailing list [email protected] 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/zwuis edited https://github.com/llvm/llvm-project/pull/145164 ___ cfe-commits mailing list [email protected] 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)
@@ -0,0 +1,79 @@ +// RUN: %clangxx -std=c++23 -fsyntax-only -Xclang -verify %s + +#include +#include +#include +#include +#include +#include +#include +#include zwuis wrote: Do not include files outside test folders. You can search `namespace std` to see how other test files handle this case. https://github.com/llvm/llvm-project/pull/145164 ___ cfe-commits mailing list [email protected] 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)
@@ -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,
zwuis wrote:
This parameter type can be `const LangOptions &`.
https://github.com/llvm/llvm-project/pull/145164
___
cfe-commits mailing list
[email protected]
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/zwuis commented: Thank you for the patch! Please add a release note in `clang/docs/ReleaseNotes.rst` so that users can know the improvement. https://github.com/llvm/llvm-project/pull/145164 ___ cfe-commits mailing list [email protected] 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)
llvmbot wrote:
@llvm/pr-subscribers-clang
Author: Marco Vitale (mrcvtl)
Changes
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
---
Full diff: https://github.com/llvm/llvm-project/pull/145164.diff
2 Files Affected:
- (modified) clang/lib/Sema/CheckExprLifetime.cpp (+28)
- (added) clang/test/SemaCXX/range-for-lifetime-cxx23.cpp (+79)
``diff
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
``
https://github.com/llvm/llvm-project/pull/145164
___
cfe-commits mailing list
[email protected]
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 [email protected] 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:
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
[email protected]
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 [email protected] 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 [email protected] 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)
frederick-vs-ja wrote: Could you associate this PR with the issue to fix, if any? https://github.com/llvm/llvm-project/pull/145164 ___ cfe-commits mailing list [email protected] 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)
github-actions[bot] wrote: Thank you for submitting a Pull Request (PR) to the LLVM Project! This PR will be automatically labeled and the relevant teams will be notified. If you wish to, you can add reviewers by using the "Reviewers" section on this page. If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using `@` followed by their GitHub username. If you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers. If you have further questions, they may be answered by the [LLVM GitHub User Guide](https://llvm.org/docs/GitHub.html). You can also ask questions in a comment on this PR, on the [LLVM Discord](https://discord.com/invite/xS7Z362) or on the [forums](https://discourse.llvm.org/). https://github.com/llvm/llvm-project/pull/145164 ___ cfe-commits mailing list [email protected] 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
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
