-
Notifications
You must be signed in to change notification settings - Fork 15.5k
[LifetimeSafety] Merge lifetimebound attribute on implicit 'this' across method redeclarations #172146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
This stack of pull requests is managed by Graphite. Learn more about stacking. |
ce0a708 to
166a926
Compare
|
@llvm/pr-subscribers-clang Author: Utkarsh Saxena (usx95) ChangesFollowup on #107627 This PR adds support for merging the The implementation adds helper functions to extract the Full diff: https://github.com/llvm/llvm-project/pull/172146.diff 3 Files Affected:
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 47bd7295e93f6..3dd62c1428271 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -4469,6 +4469,52 @@ 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;
+
+ for (TypeLoc TL = TSI->getTypeLoc();;) {
+ 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 +4531,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;
|
|
@llvm/pr-subscribers-clang-temporal-safety Author: Utkarsh Saxena (usx95) ChangesFollowup on #107627 This PR adds support for merging the The implementation adds helper functions to extract the Full diff: https://github.com/llvm/llvm-project/pull/172146.diff 3 Files Affected:
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 47bd7295e93f6..3dd62c1428271 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -4469,6 +4469,52 @@ 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;
+
+ for (TypeLoc TL = TSI->getTypeLoc();;) {
+ 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 +4531,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;
|
| 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)); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about the opposite scenario?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea is to read mostRecentDecl for analysis and it would reflect all the merged attributes seen until now.
clang/lib/Sema/SemaDecl.cpp
Outdated
| for (TypeLoc TL = TSI->getTypeLoc();;) { | ||
| auto ATL = TL.getAsAdjusted<AttributedTypeLoc>(); | ||
| if (!ATL) | ||
| break; | ||
| if (auto *LBAttr = ATL.getAttrAs<LifetimeBoundAttr>()) | ||
| return LBAttr; | ||
| TL = ATL.getModifiedLoc(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might be a bit too clever - I'd prefer a while(true) with a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
166a926 to
066e874
Compare

Followup on #107627
Fixes #62072
Fixes #172013
This PR adds support for merging the
lifetimeboundattribute on the implicitthisparameter when merging method declarations. Previously, if a method was declared withlifetimeboundon its function type (which represents the implicitthisparameter), this attribute would not be propagated to the method definition, causing lifetime safety warnings to be missed.The implementation adds helper functions to extract the
lifetimeboundattribute from a function type and to merge this attribute from an old method declaration to a new one when appropriate.