Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
35 changes: 23 additions & 12 deletions clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
138 changes: 138 additions & 0 deletions clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
22 changes: 22 additions & 0 deletions clang/test/Sema/warn-lifetime-safety.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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}}
}
}
21 changes: 21 additions & 0 deletions clang/test/SemaCXX/attr-lifetimebound.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down