[clang] [Sema] Fix lifetime extension for temporaries in range-based for loops in C++23 (PR #145164)

Marco Vitale via cfe-commits cfe-commits at lists.llvm.org
Sat Jul 5 09:44:13 PDT 2025


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 <typename T>
> struct ListWrapper {
>   ListWrapper() {}
>   ~ListWrapper() {}
>   const T *begin() const;
>   const T *end() const;
>   ListWrapper& r();
>   ListWrapper g();
> };
> 
> using A = ListWrapper<int>;
> 
> 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<VarDecl>(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 <typename T>
> struct ListWrapper {
>   ListWrapper() {}
>   ~ListWrapper() {}
>   const T *begin() const;
>   const T *end() const;
>   ListWrapper& r();
>   ListWrapper g();
> };
> 
> using A = ListWrapper<int>;
> 
> 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<VarDecl>(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 <typename T>
> struct ListWrapper {
>   ListWrapper() {}
>   ~ListWrapper() {}
>   const T *begin() const;
>   const T *end() const;
>   ListWrapper& r();
>   ListWrapper g();
> };
> 
> using A = ListWrapper<int>;
> 
> 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<VarDecl>(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


More information about the cfe-commits mailing list