Skip to content

Commit d89360c

Browse files
committed
Merge lifetimebound attribute on implicit 'this' across method redeclarations
1 parent 284ef1b commit d89360c

5 files changed

Lines changed: 205 additions & 12 deletions

File tree

clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMEANNOTATIONS_H
1111
#define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMEANNOTATIONS_H
1212

13+
#include "clang/AST/Attr.h"
1314
#include "clang/AST/DeclCXX.h"
1415

1516
namespace clang ::lifetimes {

clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,22 +52,33 @@ bool isAssignmentOperatorLifetimeBound(const CXXMethodDecl *CMD) {
5252
CMD->getParamDecl(0)->hasAttr<clang::LifetimeBoundAttr>();
5353
}
5454

55+
/// Check if a function has a lifetimebound attribute on its function type
56+
/// (which represents the implicit 'this' parameter for methods).
57+
/// Returns the attribute if found, nullptr otherwise.
58+
static const LifetimeBoundAttr *
59+
getLifetimeBoundAttrFromFunctionType(const TypeSourceInfo &TSI) {
60+
// Walk through the type layers looking for a lifetimebound attribute.
61+
TypeLoc TL = TSI.getTypeLoc();
62+
while (true) {
63+
auto ATL = TL.getAsAdjusted<AttributedTypeLoc>();
64+
if (!ATL)
65+
break;
66+
if (auto *LBAttr = ATL.getAttrAs<LifetimeBoundAttr>())
67+
return LBAttr;
68+
TL = ATL.getModifiedLoc();
69+
}
70+
return nullptr;
71+
}
72+
5573
bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD) {
5674
FD = getDeclWithMergedLifetimeBoundAttrs(FD);
57-
const TypeSourceInfo *TSI = FD->getTypeSourceInfo();
58-
if (!TSI)
59-
return false;
60-
// Don't declare this variable in the second operand of the for-statement;
61-
// GCC miscompiles that by ending its lifetime before evaluating the
62-
// third operand. See gcc.gnu.org/PR86769.
63-
AttributedTypeLoc ATL;
64-
for (TypeLoc TL = TSI->getTypeLoc();
65-
(ATL = TL.getAsAdjusted<AttributedTypeLoc>());
66-
TL = ATL.getModifiedLoc()) {
67-
if (ATL.getAttrAs<clang::LifetimeBoundAttr>())
75+
// Attribute merging doesn't work well with attributes on function types (like
76+
// 'this' param). We need to check all redeclarations.
77+
for (const FunctionDecl *Redecl : FD->redecls()) {
78+
const TypeSourceInfo *TSI = Redecl->getTypeSourceInfo();
79+
if (TSI && getLifetimeBoundAttrFromFunctionType(*TSI))
6880
return true;
6981
}
70-
7182
return isNormalAssignmentOperator(FD);
7283
}
7384

clang/test/Sema/warn-lifetime-analysis-nocfg.cpp

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1039,3 +1039,141 @@ const char* foo() {
10391039
}
10401040

10411041
} // namespace GH127195
1042+
1043+
// Lifetimebound on definition vs declaration on implicit this param.
1044+
namespace GH175391 {
1045+
// Version A: Attribute on declaration only
1046+
class StringA {
1047+
public:
1048+
const char* data() const [[clang::lifetimebound]]; // Declaration with attribute
1049+
private:
1050+
char buffer[32] = "hello";
1051+
};
1052+
inline const char* StringA::data() const { // Definition WITHOUT attribute
1053+
return buffer;
1054+
}
1055+
1056+
// Version B: Attribute on definition only
1057+
class StringB {
1058+
public:
1059+
const char* data() const; // No attribute
1060+
private:
1061+
char buffer[32] = "hello";
1062+
};
1063+
inline const char* StringB::data() const [[clang::lifetimebound]] {
1064+
return buffer;
1065+
}
1066+
1067+
// Version C: Attribute on BOTH declaration and definition
1068+
class StringC {
1069+
public:
1070+
const char* data() const [[clang::lifetimebound]];
1071+
private:
1072+
char buffer[32] = "hello";
1073+
};
1074+
inline const char* StringC::data() const [[clang::lifetimebound]] {
1075+
return buffer;
1076+
}
1077+
1078+
// TEMPLATED VERSIONS
1079+
1080+
// Template Version A: Attribute on declaration only
1081+
template<typename T>
1082+
class StringTemplateA {
1083+
public:
1084+
const T* data() const [[clang::lifetimebound]]; // Declaration with attribute
1085+
private:
1086+
T buffer[32];
1087+
};
1088+
template<typename T>
1089+
inline const T* StringTemplateA<T>::data() const { // Definition WITHOUT attribute
1090+
return buffer;
1091+
}
1092+
1093+
// Template Version B: Attribute on definition only
1094+
template<typename T>
1095+
class StringTemplateB {
1096+
public:
1097+
const T* data() const; // No attribute
1098+
private:
1099+
T buffer[32];
1100+
};
1101+
template<typename T>
1102+
inline const T* StringTemplateB<T>::data() const [[clang::lifetimebound]] {
1103+
return buffer;
1104+
}
1105+
1106+
// Template Version C: Attribute on BOTH declaration and definition
1107+
template<typename T>
1108+
class StringTemplateC {
1109+
public:
1110+
const T* data() const [[clang::lifetimebound]];
1111+
private:
1112+
T buffer[32];
1113+
};
1114+
template<typename T>
1115+
inline const T* StringTemplateC<T>::data() const [[clang::lifetimebound]] {
1116+
return buffer;
1117+
}
1118+
1119+
// TEMPLATE SPECIALIZATION VERSIONS
1120+
1121+
// Template predeclarations for specializations
1122+
template<typename T> class StringTemplateSpecA;
1123+
template<typename T> class StringTemplateSpecB;
1124+
template<typename T> class StringTemplateSpecC;
1125+
1126+
// Template Specialization Version A: Attribute on declaration only - <char> specialization
1127+
template<>
1128+
class StringTemplateSpecA<char> {
1129+
public:
1130+
const char* data() const [[clang::lifetimebound]]; // Declaration with attribute
1131+
private:
1132+
char buffer[32] = "hello";
1133+
};
1134+
inline const char* StringTemplateSpecA<char>::data() const { // Definition WITHOUT attribute
1135+
return buffer;
1136+
}
1137+
1138+
// Template Specialization Version B: Attribute on definition only - <char> specialization
1139+
template<>
1140+
class StringTemplateSpecB<char> {
1141+
public:
1142+
const char* data() const; // No attribute
1143+
private:
1144+
char buffer[32] = "hello";
1145+
};
1146+
inline const char* StringTemplateSpecB<char>::data() const [[clang::lifetimebound]] {
1147+
return buffer;
1148+
}
1149+
1150+
// Template Specialization Version C: Attribute on BOTH declaration and definition - <char> specialization
1151+
template<>
1152+
class StringTemplateSpecC<char> {
1153+
public:
1154+
const char* data() const [[clang::lifetimebound]];
1155+
private:
1156+
char buffer[32] = "hello";
1157+
};
1158+
inline const char* StringTemplateSpecC<char>::data() const [[clang::lifetimebound]] {
1159+
return buffer;
1160+
}
1161+
1162+
void test() {
1163+
// Non-templated tests
1164+
const auto ptrA = StringA().data(); // Declaration-only attribute // expected-warning {{temporary whose address is used}}
1165+
const auto ptrB = StringB().data(); // Definition-only attribute // expected-warning {{temporary whose address is used}}
1166+
const auto ptrC = StringC().data(); // Both have attribute // expected-warning {{temporary whose address is used}}
1167+
1168+
// Templated tests (generic templates)
1169+
const auto ptrTA = StringTemplateA<char>().data(); // Declaration-only attribute // expected-warning {{temporary whose address is used}}
1170+
// FIXME: Definition is not instantiated until the end of TU. The attribute is not merged when this call is processed.
1171+
const auto ptrTB = StringTemplateB<char>().data(); // Definition-only attribute
1172+
const auto ptrTC = StringTemplateC<char>().data(); // Both have attribute // expected-warning {{temporary whose address is used}}
1173+
1174+
// Template specialization tests
1175+
const auto ptrTSA = StringTemplateSpecA<char>().data(); // Declaration-only attribute // expected-warning {{temporary whose address is used}}
1176+
const auto ptrTSB = StringTemplateSpecB<char>().data(); // Definition-only attribute // expected-warning {{temporary whose address is used}}
1177+
const auto ptrTSC = StringTemplateSpecC<char>().data(); // Both have attribute // expected-warning {{temporary whose address is used}}
1178+
}
1179+
} // namespace GH175391

clang/test/Sema/warn-lifetime-safety.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1431,3 +1431,25 @@ void not_silenced_via_conditional(bool cond) {
14311431
(void)v; // expected-note 2 {{later used here}}
14321432
}
14331433
} // namespace do_not_warn_on_std_move
1434+
1435+
// Implicit this annotations with redecls.
1436+
namespace GH172013 {
1437+
// https://github.com/llvm/llvm-project/issues/62072
1438+
// https://github.com/llvm/llvm-project/issues/172013
1439+
struct S {
1440+
View x() const [[clang::lifetimebound]];
1441+
MyObj i;
1442+
};
1443+
1444+
View S::x() const { return i; }
1445+
1446+
void bar() {
1447+
View x;
1448+
{
1449+
S s;
1450+
x = s.x(); // expected-warning {{object whose reference is captured does not live long enough}}
1451+
View y = S().x(); // FIXME: Handle temporaries.
1452+
} // expected-note {{destroyed here}}
1453+
(void)x; // expected-note {{used here}}
1454+
}
1455+
}

clang/test/SemaCXX/attr-lifetimebound.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,27 @@ namespace usage_ok {
7575
r = A(1); // expected-warning {{object backing the pointer 'r' will be destroyed at the end of the full-expression}}
7676
}
7777

78+
// Test that lifetimebound on implicit 'this' is propagated across redeclarations
79+
struct B {
80+
int *method() [[clang::lifetimebound]];
81+
int i;
82+
};
83+
int *B::method() { return &i; }
84+
85+
// Test that lifetimebound on implicit 'this' is propagated across redeclarations
86+
struct C {
87+
int *method();
88+
int i;
89+
};
90+
int *C::method() [[clang::lifetimebound]] { return &i; }
91+
92+
void test_lifetimebound_on_implicit_this() {
93+
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}}
94+
t = {B().method()}; // expected-warning {{object backing the pointer 't' will be destroyed at the end of the full-expression}}
95+
t = C().method(); // expected-warning {{object backing the pointer 't' will be destroyed at the end of the full-expression}}
96+
t = {C().method()}; // expected-warning {{object backing the pointer 't' will be destroyed at the end of the full-expression}}
97+
}
98+
7899
struct FieldCheck {
79100
struct Set {
80101
int a;

0 commit comments

Comments
 (0)