From 9a338362bff0511510a568156f37cc093094e017 Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena 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 | 21 ++++++++ 3 files changed, 101 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(); + if (!ATL) + break; + if (auto *LBAttr = ATL.getAttrAs()) + 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(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(New)) + if (auto *OldMethod = dyn_cast(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..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;