[clang] [LifetimeSafety] Merge lifetimebound attribute on implicit 'this' across method redeclarations (PR #172146)
Utkarsh Saxena via cfe-commits
cfe-commits at lists.llvm.org
Sat Dec 13 09:54:48 PST 2025
https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/172146
>From 066e8741ca60c2c1307b21b90f45f14a76573a5f Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Sat, 13 Dec 2025 09:42:36 +0000
Subject: [PATCH] merge-attr-implicit-this
---
clang/lib/Sema/SemaDecl.cpp | 64 ++++++++++++++++++++---
clang/test/Sema/warn-lifetime-safety.cpp | 22 ++++++++
clang/test/SemaCXX/attr-lifetimebound.cpp | 12 +++++
3 files changed, 92 insertions(+), 6 deletions(-)
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 47bd7295e93f6..e985b8e5027fa 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -4469,6 +4469,53 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
return true;
}
+/// 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 *
+getLifetimeBoundAttrFromType(const TypeSourceInfo *TSI) {
+ if (!TSI)
+ return nullptr;
+ // 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;
+}
+
+/// Merge lifetimebound attribute on function type (implicit 'this')
+/// from Old to New method declaration.
+static void mergeLifetimeBoundAttrOnMethod(Sema &S, CXXMethodDecl *New,
+ const CXXMethodDecl *Old) {
+ const TypeSourceInfo *OldTSI = Old->getTypeSourceInfo();
+ const TypeSourceInfo *NewTSI = New->getTypeSourceInfo();
+
+ if (!OldTSI || !NewTSI)
+ return;
+
+ const LifetimeBoundAttr *OldLBAttr = getLifetimeBoundAttrFromType(OldTSI);
+ const LifetimeBoundAttr *NewLBAttr = getLifetimeBoundAttrFromType(NewTSI);
+
+ // If Old has lifetimebound but New doesn't, add it to New
+ if (OldLBAttr && !NewLBAttr) {
+ QualType NewMethodType = New->getType();
+ QualType AttributedType =
+ S.Context.getAttributedType(OldLBAttr, NewMethodType, NewMethodType);
+ TypeLocBuilder TLB;
+ TLB.pushFullCopy(NewTSI->getTypeLoc());
+ AttributedTypeLoc TyLoc = TLB.push<AttributedTypeLoc>(AttributedType);
+ TyLoc.setAttr(OldLBAttr);
+ New->setType(AttributedType);
+ New->setTypeSourceInfo(TLB.getTypeSourceInfo(S.Context, AttributedType));
+ }
+}
+
bool Sema::MergeCompatibleFunctionDecls(FunctionDecl *New, FunctionDecl *Old,
Scope *S, bool MergeTypeWithOld) {
// Merge the attributes
@@ -4485,12 +4532,17 @@ bool Sema::MergeCompatibleFunctionDecls(FunctionDecl *New, FunctionDecl *Old,
// Merge attributes from the parameters. These can mismatch with K&R
// declarations.
if (New->getNumParams() == Old->getNumParams())
- for (unsigned i = 0, e = New->getNumParams(); i != e; ++i) {
- ParmVarDecl *NewParam = New->getParamDecl(i);
- ParmVarDecl *OldParam = Old->getParamDecl(i);
- mergeParamDeclAttributes(NewParam, OldParam, *this);
- mergeParamDeclTypes(NewParam, OldParam, *this);
- }
+ for (unsigned i = 0, e = New->getNumParams(); i != e; ++i) {
+ ParmVarDecl *NewParam = New->getParamDecl(i);
+ ParmVarDecl *OldParam = Old->getParamDecl(i);
+ mergeParamDeclAttributes(NewParam, OldParam, *this);
+ mergeParamDeclTypes(NewParam, OldParam, *this);
+ }
+
+ // Merge function type attributes (e.g., lifetimebound on implicit 'this').
+ if (auto *NewMethod = dyn_cast<CXXMethodDecl>(New))
+ if (auto *OldMethod = dyn_cast<CXXMethodDecl>(Old))
+ mergeLifetimeBoundAttrOnMethod(*this, NewMethod, OldMethod);
if (getLangOpts().CPlusPlus)
return MergeCXXFunctionDecl(New, Old, S);
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 1191469e23df1..658d68db44369 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -943,3 +943,25 @@ void parentheses(bool cond) {
} // expected-note 4 {{destroyed here}}
(void)*p; // expected-note 4 {{later used here}}
}
+
+// 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..5b0aa0e518cb3 100644
--- a/clang/test/SemaCXX/attr-lifetimebound.cpp
+++ b/clang/test/SemaCXX/attr-lifetimebound.cpp
@@ -75,6 +75,18 @@ 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; }
+
+ 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}}
+ }
+
struct FieldCheck {
struct Set {
int a;
More information about the cfe-commits
mailing list