[clang] [LifetimeSafety] Read lifetimebound attribute on implicit 'this' from all redeclarations (PR #176188)
Utkarsh Saxena via cfe-commits
cfe-commits at lists.llvm.org
Mon Jan 19 01:46:05 PST 2026
https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/176188
>From d89360c098e5ce23eada2490e5a13f0804017046 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Thu, 15 Jan 2026 15:40:13 +0000
Subject: [PATCH] Merge lifetimebound attribute on implicit 'this' across
method redeclarations
---
.../LifetimeSafety/LifetimeAnnotations.h | 1 +
.../LifetimeSafety/LifetimeAnnotations.cpp | 35 +++--
.../Sema/warn-lifetime-analysis-nocfg.cpp | 138 ++++++++++++++++++
clang/test/Sema/warn-lifetime-safety.cpp | 22 +++
clang/test/SemaCXX/attr-lifetimebound.cpp | 21 +++
5 files changed, 205 insertions(+), 12 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
index f96d412aa63d2..760d34d33b15b 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
@@ -10,6 +10,7 @@
#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMEANNOTATIONS_H
#define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMEANNOTATIONS_H
+#include "clang/AST/Attr.h"
#include "clang/AST/DeclCXX.h"
namespace clang ::lifetimes {
diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
index 2772fe20de19b..ced0ad537604a 100644
--- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
@@ -52,22 +52,33 @@ bool isAssignmentOperatorLifetimeBound(const CXXMethodDecl *CMD) {
CMD->getParamDecl(0)->hasAttr<clang::LifetimeBoundAttr>();
}
+/// Check if a function has a lifetimebound attribute on its function type
+/// (which represents the implicit 'this' parameter for methods).
+/// Returns the attribute if found, nullptr otherwise.
+static const LifetimeBoundAttr *
+getLifetimeBoundAttrFromFunctionType(const TypeSourceInfo &TSI) {
+ // Walk through the type layers looking for a lifetimebound attribute.
+ TypeLoc TL = TSI.getTypeLoc();
+ while (true) {
+ auto ATL = TL.getAsAdjusted<AttributedTypeLoc>();
+ if (!ATL)
+ break;
+ if (auto *LBAttr = ATL.getAttrAs<LifetimeBoundAttr>())
+ return LBAttr;
+ TL = ATL.getModifiedLoc();
+ }
+ return nullptr;
+}
+
bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD) {
FD = getDeclWithMergedLifetimeBoundAttrs(FD);
- const TypeSourceInfo *TSI = FD->getTypeSourceInfo();
- if (!TSI)
- return false;
- // Don't declare this variable in the second operand of the for-statement;
- // GCC miscompiles that by ending its lifetime before evaluating the
- // third operand. See gcc.gnu.org/PR86769.
- AttributedTypeLoc ATL;
- for (TypeLoc TL = TSI->getTypeLoc();
- (ATL = TL.getAsAdjusted<AttributedTypeLoc>());
- TL = ATL.getModifiedLoc()) {
- if (ATL.getAttrAs<clang::LifetimeBoundAttr>())
+ // Attribute merging doesn't work well with attributes on function types (like
+ // 'this' param). We need to check all redeclarations.
+ for (const FunctionDecl *Redecl : FD->redecls()) {
+ const TypeSourceInfo *TSI = Redecl->getTypeSourceInfo();
+ if (TSI && getLifetimeBoundAttrFromFunctionType(*TSI))
return true;
}
-
return isNormalAssignmentOperator(FD);
}
diff --git a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
index 7fdc493dbd17a..441f9fc602916 100644
--- a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
+++ b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
@@ -1039,3 +1039,141 @@ const char* foo() {
}
} // namespace GH127195
+
+// Lifetimebound on definition vs declaration on implicit this param.
+namespace GH175391 {
+// Version A: Attribute on declaration only
+class StringA {
+public:
+ const char* data() const [[clang::lifetimebound]]; // Declaration with attribute
+private:
+ char buffer[32] = "hello";
+};
+inline const char* StringA::data() const { // Definition WITHOUT attribute
+ return buffer;
+}
+
+// Version B: Attribute on definition only
+class StringB {
+public:
+ const char* data() const; // No attribute
+private:
+ char buffer[32] = "hello";
+};
+inline const char* StringB::data() const [[clang::lifetimebound]] {
+ return buffer;
+}
+
+// Version C: Attribute on BOTH declaration and definition
+class StringC {
+public:
+ const char* data() const [[clang::lifetimebound]];
+private:
+ char buffer[32] = "hello";
+};
+inline const char* StringC::data() const [[clang::lifetimebound]] {
+ return buffer;
+}
+
+// TEMPLATED VERSIONS
+
+// Template Version A: Attribute on declaration only
+template<typename T>
+class StringTemplateA {
+public:
+ const T* data() const [[clang::lifetimebound]]; // Declaration with attribute
+private:
+ T buffer[32];
+};
+template<typename T>
+inline const T* StringTemplateA<T>::data() const { // Definition WITHOUT attribute
+ return buffer;
+}
+
+// Template Version B: Attribute on definition only
+template<typename T>
+class StringTemplateB {
+public:
+ const T* data() const; // No attribute
+private:
+ T buffer[32];
+};
+template<typename T>
+inline const T* StringTemplateB<T>::data() const [[clang::lifetimebound]] {
+ return buffer;
+}
+
+// Template Version C: Attribute on BOTH declaration and definition
+template<typename T>
+class StringTemplateC {
+public:
+ const T* data() const [[clang::lifetimebound]];
+private:
+ T buffer[32];
+};
+template<typename T>
+inline const T* StringTemplateC<T>::data() const [[clang::lifetimebound]] {
+ return buffer;
+}
+
+// TEMPLATE SPECIALIZATION VERSIONS
+
+// Template predeclarations for specializations
+template<typename T> class StringTemplateSpecA;
+template<typename T> class StringTemplateSpecB;
+template<typename T> class StringTemplateSpecC;
+
+// Template Specialization Version A: Attribute on declaration only - <char> specialization
+template<>
+class StringTemplateSpecA<char> {
+public:
+ const char* data() const [[clang::lifetimebound]]; // Declaration with attribute
+private:
+ char buffer[32] = "hello";
+};
+inline const char* StringTemplateSpecA<char>::data() const { // Definition WITHOUT attribute
+ return buffer;
+}
+
+// Template Specialization Version B: Attribute on definition only - <char> specialization
+template<>
+class StringTemplateSpecB<char> {
+public:
+ const char* data() const; // No attribute
+private:
+ char buffer[32] = "hello";
+};
+inline const char* StringTemplateSpecB<char>::data() const [[clang::lifetimebound]] {
+ return buffer;
+}
+
+// Template Specialization Version C: Attribute on BOTH declaration and definition - <char> specialization
+template<>
+class StringTemplateSpecC<char> {
+public:
+ const char* data() const [[clang::lifetimebound]];
+private:
+ char buffer[32] = "hello";
+};
+inline const char* StringTemplateSpecC<char>::data() const [[clang::lifetimebound]] {
+ return buffer;
+}
+
+void test() {
+ // Non-templated tests
+ const auto ptrA = StringA().data(); // Declaration-only attribute // expected-warning {{temporary whose address is used}}
+ const auto ptrB = StringB().data(); // Definition-only attribute // expected-warning {{temporary whose address is used}}
+ const auto ptrC = StringC().data(); // Both have attribute // expected-warning {{temporary whose address is used}}
+
+ // Templated tests (generic templates)
+ const auto ptrTA = StringTemplateA<char>().data(); // Declaration-only attribute // expected-warning {{temporary whose address is used}}
+ // FIXME: Definition is not instantiated until the end of TU. The attribute is not merged when this call is processed.
+ const auto ptrTB = StringTemplateB<char>().data(); // Definition-only attribute
+ const auto ptrTC = StringTemplateC<char>().data(); // Both have attribute // expected-warning {{temporary whose address is used}}
+
+ // Template specialization tests
+ const auto ptrTSA = StringTemplateSpecA<char>().data(); // Declaration-only attribute // expected-warning {{temporary whose address is used}}
+ const auto ptrTSB = StringTemplateSpecB<char>().data(); // Definition-only attribute // expected-warning {{temporary whose address is used}}
+ const auto ptrTSC = StringTemplateSpecC<char>().data(); // Both have attribute // expected-warning {{temporary whose address is used}}
+}
+} // namespace GH175391
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 0b1962b7cb651..24ac72e7aad4d 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -1431,3 +1431,25 @@ void not_silenced_via_conditional(bool cond) {
(void)v; // expected-note 2 {{later used here}}
}
} // namespace do_not_warn_on_std_move
+
+// Implicit this annotations with redecls.
+namespace GH172013 {
+// https://github.com/llvm/llvm-project/issues/62072
+// https://github.com/llvm/llvm-project/issues/172013
+struct S {
+ View x() const [[clang::lifetimebound]];
+ MyObj i;
+};
+
+View S::x() const { return i; }
+
+void bar() {
+ View x;
+ {
+ S s;
+ x = s.x(); // expected-warning {{object whose reference is captured does not live long enough}}
+ View y = S().x(); // FIXME: Handle temporaries.
+ } // expected-note {{destroyed here}}
+ (void)x; // expected-note {{used here}}
+}
+}
diff --git a/clang/test/SemaCXX/attr-lifetimebound.cpp b/clang/test/SemaCXX/attr-lifetimebound.cpp
index 111bad65f7e1b..9e2aaff6559c4 100644
--- a/clang/test/SemaCXX/attr-lifetimebound.cpp
+++ b/clang/test/SemaCXX/attr-lifetimebound.cpp
@@ -75,6 +75,27 @@ namespace usage_ok {
r = A(1); // expected-warning {{object backing the pointer 'r' will be destroyed at the end of the full-expression}}
}
+ // Test that lifetimebound on implicit 'this' is propagated across redeclarations
+ struct B {
+ int *method() [[clang::lifetimebound]];
+ int i;
+ };
+ int *B::method() { return &i; }
+
+ // Test that lifetimebound on implicit 'this' is propagated across redeclarations
+ struct C {
+ int *method();
+ int i;
+ };
+ int *C::method() [[clang::lifetimebound]] { return &i; }
+
+ void test_lifetimebound_on_implicit_this() {
+ int *t = B().method(); // expected-warning {{temporary whose address is used as value of local variable 't' will be destroyed at the end of the full-expression}}
+ t = {B().method()}; // expected-warning {{object backing the pointer 't' will be destroyed at the end of the full-expression}}
+ t = C().method(); // expected-warning {{object backing the pointer 't' will be destroyed at the end of the full-expression}}
+ t = {C().method()}; // expected-warning {{object backing the pointer 't' will be destroyed at the end of the full-expression}}
+ }
+
struct FieldCheck {
struct Set {
int a;
More information about the cfe-commits
mailing list