llvm-project/clang/test/Sema/warn-lifetime-analysis-capture-by.cpp
Haojian Wu 1374aa35a3
[clang] Don't infer lifetime_capture-by for reference of raw pointer types. (#122240)
When a vector is instantiated with a pointer type (`T` being `const
Foo*`), the inferred annotation becomes `push_back(const Foo*& value
[[clang::lifetime_capture_by(this)]])`.

For reference parameters, the `lifetime_capture_by` attribute treats the
lifetime as referring to the referenced object -- in this case, the
**pointer** itself, not the pointee object. In the `push_back`, we copy
the pointer's value, which does not establish a reference to the
pointer. This behavior is safe and does not capture the pointer's
lifetime.

The annotation should not be inferred for cases where `T` is a pointer
type, as the intended semantics do not align with the annotation.

Fixes #121391
2025-01-09 16:26:56 +01:00

475 lines
23 KiB
C++

// RUN: %clang_cc1 --std=c++20 -fsyntax-only -verify -Wdangling-capture %s
#include "Inputs/lifetime-analysis.h"
// ****************************************************************************
// Capture an integer
// ****************************************************************************
namespace capture_int {
struct X {} x;
void captureInt(const int &i [[clang::lifetime_capture_by(x)]], X &x);
void captureRValInt(int &&i [[clang::lifetime_capture_by(x)]], X &x);
void noCaptureInt(int i [[clang::lifetime_capture_by(x)]], X &x);
void use() {
int local;
captureInt(1, // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}}
x);
captureRValInt(1, x); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}}
captureInt(local, x);
noCaptureInt(1, x);
noCaptureInt(local, x);
}
} // namespace capture_int
// ****************************************************************************
// Capture std::string (gsl owner types)
// ****************************************************************************
namespace capture_string {
struct X {} x;
void captureString(const std::string &s [[clang::lifetime_capture_by(x)]], X &x);
void captureRValString(std::string &&s [[clang::lifetime_capture_by(x)]], X &x);
void use() {
std::string local_string;
captureString(std::string(), x); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}}
captureString(local_string, x);
captureRValString(std::move(local_string), x);
captureRValString(std::string(), x); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}}
}
} // namespace capture_string
// ****************************************************************************
// Capture std::string_view (gsl pointer types)
// ****************************************************************************
namespace capture_string_view {
struct X {} x;
void captureStringView(std::string_view s [[clang::lifetime_capture_by(x)]], X &x);
void captureRValStringView(std::string_view &&sv [[clang::lifetime_capture_by(x)]], X &x);
void noCaptureStringView(std::string_view sv, X &x);
std::string_view getLifetimeBoundView(const std::string& s [[clang::lifetimebound]]);
std::string_view getNotLifetimeBoundView(const std::string& s);
const std::string& getLifetimeBoundString(const std::string &s [[clang::lifetimebound]]);
const std::string& getLifetimeBoundString(std::string_view sv [[clang::lifetimebound]]);
void use() {
std::string_view local_string_view;
std::string local_string;
captureStringView(local_string_view, x);
captureStringView(std::string(), // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}}
x);
captureStringView(getLifetimeBoundView(local_string), x);
captureStringView(getNotLifetimeBoundView(std::string()), x);
captureRValStringView(std::move(local_string_view), x);
captureRValStringView(std::string(), x); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}}
captureRValStringView(std::string_view{"abcd"}, x);
noCaptureStringView(local_string_view, x);
noCaptureStringView(std::string(), x);
// With lifetimebound functions.
captureStringView(getLifetimeBoundView(
std::string() // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}}
), x);
captureRValStringView(getLifetimeBoundView(local_string), x);
captureRValStringView(getLifetimeBoundView(std::string()), x); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}}
captureRValStringView(getNotLifetimeBoundView(std::string()), x);
noCaptureStringView(getLifetimeBoundView(std::string()), x);
captureStringView(getLifetimeBoundString(std::string()), x); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}}
captureStringView(getLifetimeBoundString(getLifetimeBoundView(std::string())), x); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}}
captureStringView(getLifetimeBoundString(getLifetimeBoundString(
std::string() // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}}
)), x);
}
} // namespace capture_string_view
// ****************************************************************************
// Capture pointer (eg: std::string*)
// ****************************************************************************
const std::string* getLifetimeBoundPointer(const std::string &s [[clang::lifetimebound]]);
const std::string* getNotLifetimeBoundPointer(const std::string &s);
namespace capture_pointer {
struct X {} x;
void capturePointer(const std::string* sp [[clang::lifetime_capture_by(x)]], X &x);
void use() {
capturePointer(getLifetimeBoundPointer(std::string()), x); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}}
capturePointer(getLifetimeBoundPointer(*getLifetimeBoundPointer(
std::string() // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}}
)), x);
capturePointer(getNotLifetimeBoundPointer(std::string()), x);
}
} // namespace capture_pointer
// ****************************************************************************
// Arrays and initializer lists.
// ****************************************************************************
namespace init_lists {
struct X {} x;
void captureVector(const std::vector<int> &a [[clang::lifetime_capture_by(x)]], X &x);
void captureArray(int array [[clang::lifetime_capture_by(x)]] [2], X &x);
void captureInitList(std::initializer_list<int> abc [[clang::lifetime_capture_by(x)]], X &x);
std::initializer_list<int> getLifetimeBoundInitList(std::initializer_list<int> abc [[clang::lifetimebound]]);
void use() {
captureVector({1, 2, 3}, x); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}}
captureVector(std::vector<int>{}, x); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}}
std::vector<int> local_vector;
captureVector(local_vector, x);
int local_array[2];
captureArray(local_array, x);
captureInitList({1, 2}, x); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}}
captureInitList(getLifetimeBoundInitList({1, 2}), x); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}}
}
} // namespace init_lists
// ****************************************************************************
// Implicit object param 'this' is captured
// ****************************************************************************
namespace this_is_captured {
struct X {} x;
struct S {
void capture(X &x) [[clang::lifetime_capture_by(x)]];
};
void use() {
S{}.capture(x); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}}
S s;
s.capture(x);
}
} // namespace this_is_captured
namespace temporary_capturing_object {
struct S {
void add(const int& x [[clang::lifetime_capture_by(this)]]);
};
void test() {
// We still give an warning even the capturing object is a temoprary.
// It is possible that the capturing object uses the captured object in its
// destructor.
S().add(1); // expected-warning {{object whose reference is captured}}
S{}.add(1); // expected-warning {{object whose reference is captured}}
}
} // namespace ignore_temporary_class_object
// ****************************************************************************
// Capture by Global and Unknown.
// ****************************************************************************
namespace capture_by_global_unknown {
void captureByGlobal(std::string_view s [[clang::lifetime_capture_by(global)]]);
void captureByUnknown(std::string_view s [[clang::lifetime_capture_by(unknown)]]);
std::string_view getLifetimeBoundView(const std::string& s [[clang::lifetimebound]]);
void use() {
std::string_view local_string_view;
std::string local_string;
// capture by global.
captureByGlobal(std::string()); // expected-warning {{object whose reference is captured will be destroyed at the end of the full-expression}}
captureByGlobal(getLifetimeBoundView(std::string())); // expected-warning {{object whose reference is captured will be destroyed at the end of the full-expression}}
captureByGlobal(local_string);
captureByGlobal(local_string_view);
// capture by unknown.
captureByUnknown(std::string()); // expected-warning {{object whose reference is captured will be destroyed at the end of the full-expression}}
captureByUnknown(getLifetimeBoundView(std::string())); // expected-warning {{object whose reference is captured will be destroyed at the end of the full-expression}}
captureByUnknown(local_string);
captureByUnknown(local_string_view);
}
} // namespace capture_by_global_unknown
// ****************************************************************************
// Member functions: Capture by 'this'
// ****************************************************************************
namespace capture_by_this {
struct S {
void captureInt(const int& x [[clang::lifetime_capture_by(this)]]);
void captureView(std::string_view sv [[clang::lifetime_capture_by(this)]]);
};
std::string_view getLifetimeBoundView(const std::string& s [[clang::lifetimebound]]);
std::string_view getNotLifetimeBoundView(const std::string& s);
const std::string& getLifetimeBoundString(const std::string &s [[clang::lifetimebound]]);
void use() {
S s;
s.captureInt(1); // expected-warning {{object whose reference is captured by 's' will be destroyed at the end of the full-expression}}
s.captureView(std::string()); // expected-warning {{object whose reference is captured by 's' will be destroyed at the end of the full-expression}}
s.captureView(getLifetimeBoundView(std::string())); // expected-warning {{object whose reference is captured by 's' will be destroyed at the end of the full-expression}}
s.captureView(getLifetimeBoundString(std::string())); // expected-warning {{object whose reference is captured by 's' will be destroyed at the end of the full-expression}}
s.captureView(getNotLifetimeBoundView(std::string()));
}
} // namespace capture_by_this
// ****************************************************************************
// Struct with field as a reference
// ****************************************************************************
namespace reference_field {
struct X {} x;
struct Foo {
const int& b;
};
void captureField(Foo param [[clang::lifetime_capture_by(x)]], X &x);
void use() {
captureField(Foo{
1 // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}}
}, x);
int local;
captureField(Foo{local}, x);
}
} // namespace reference_field
// ****************************************************************************
// Capture default argument.
// ****************************************************************************
namespace default_arg {
struct X {} x;
void captureDefaultArg(X &x, std::string_view s [[clang::lifetime_capture_by(x)]] = std::string());
std::string_view getLifetimeBoundView(const std::string& s [[clang::lifetimebound]]);
void useCaptureDefaultArg() {
X x;
captureDefaultArg(x); // FIXME: Diagnose temporary default arg.
captureDefaultArg(x, std::string("temp")); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}}
captureDefaultArg(x, getLifetimeBoundView(std::string())); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}}
std::string local;
captureDefaultArg(x, local);
}
} // namespace default_arg
// ****************************************************************************
// Container: *No* distinction between pointer-like and other element type
// ****************************************************************************
namespace containers_no_distinction {
template<class T>
struct MySet {
void insert(T&& t [[clang::lifetime_capture_by(this)]]);
void insert(const T& t [[clang::lifetime_capture_by(this)]]);
};
void user_defined_containers() {
MySet<int> set_of_int;
set_of_int.insert(1); // expected-warning {{object whose reference is captured by 'set_of_int' will be destroyed at the end of the full-expression}}
MySet<std::string_view> set_of_sv;
set_of_sv.insert(std::string()); // expected-warning {{object whose reference is captured by 'set_of_sv' will be destroyed at the end of the full-expression}}
set_of_sv.insert(std::string_view());
}
} // namespace containers_no_distinction
// ****************************************************************************
// Container: Different for pointer-like and other element type.
// ****************************************************************************
namespace conatiners_with_different {
template<typename T> struct IsPointerLikeTypeImpl : std::false_type {};
template<> struct IsPointerLikeTypeImpl<std::string_view> : std::true_type {};
template<typename T> concept IsPointerLikeType = std::is_pointer<T>::value || IsPointerLikeTypeImpl<T>::value;
template<class T> struct MyVector {
void push_back(T&& t [[clang::lifetime_capture_by(this)]]) requires IsPointerLikeType<T>;
void push_back(const T& t [[clang::lifetime_capture_by(this)]]) requires IsPointerLikeType<T>;
void push_back(T&& t) requires (!IsPointerLikeType<T>);
void push_back(const T& t) requires (!IsPointerLikeType<T>);
};
std::string_view getLifetimeBoundView(const std::string& s [[clang::lifetimebound]]);
void use_container() {
std::string local;
MyVector<std::string> vector_of_string;
vector_of_string.push_back(std::string()); // Ok.
MyVector<std::string_view> vector_of_view;
vector_of_view.push_back(std::string()); // expected-warning {{object whose reference is captured by 'vector_of_view' will be destroyed at the end of the full-expression}}
vector_of_view.push_back(getLifetimeBoundView(std::string())); // expected-warning {{object whose reference is captured by 'vector_of_view' will be destroyed at the end of the full-expression}}
MyVector<const std::string*> vector_of_pointer;
vector_of_pointer.push_back(getLifetimeBoundPointer(std::string())); // expected-warning {{object whose reference is captured by 'vector_of_pointer' will be destroyed at the end of the full-expression}}
vector_of_pointer.push_back(getLifetimeBoundPointer(*getLifetimeBoundPointer(std::string()))); // expected-warning {{object whose reference is captured by 'vector_of_pointer' will be destroyed at the end of the full-expression}}
vector_of_pointer.push_back(getLifetimeBoundPointer(local));
vector_of_pointer.push_back(getNotLifetimeBoundPointer(std::string()));
}
// ****************************************************************************
// Container: For user defined view types
// ****************************************************************************
struct [[gsl::Pointer()]] MyStringView : public std::string_view {
MyStringView();
MyStringView(std::string_view&&);
MyStringView(const MyStringView&);
MyStringView(const std::string&);
};
template<> struct IsPointerLikeTypeImpl<MyStringView> : std::true_type {};
std::optional<std::string_view> getOptionalSV();
std::optional<std::string> getOptionalS();
std::optional<MyStringView> getOptionalMySV();
MyStringView getMySV();
class MyStringViewNotPointer : public std::string_view {};
std::optional<MyStringViewNotPointer> getOptionalMySVNotP();
MyStringViewNotPointer getMySVNotP();
std::string_view getLifetimeBoundView(const std::string& s [[clang::lifetimebound]]);
std::string_view getNotLifetimeBoundView(const std::string& s);
const std::string& getLifetimeBoundString(const std::string &s [[clang::lifetimebound]]);
const std::string& getLifetimeBoundString(std::string_view sv [[clang::lifetimebound]]);
void use_my_view() {
std::string local;
MyVector<MyStringView> vector_of_my_view;
vector_of_my_view.push_back(getMySV());
vector_of_my_view.push_back(MyStringView{});
vector_of_my_view.push_back(std::string_view{});
vector_of_my_view.push_back(std::string{}); // expected-warning {{object whose reference is captured by 'vector_of_my_view' will be destroyed at the end of the full-expression}}
vector_of_my_view.push_back(getLifetimeBoundView(std::string{})); // expected-warning {{object whose reference is captured by 'vector_of_my_view' will be destroyed at the end of the full-expression}}
vector_of_my_view.push_back(getLifetimeBoundString(getLifetimeBoundView(std::string{}))); // expected-warning {{object whose reference is captured by 'vector_of_my_view' will be destroyed at the end of the full-expression}}
vector_of_my_view.push_back(getNotLifetimeBoundView(getLifetimeBoundString(getLifetimeBoundView(std::string{}))));
// Use with container of other view types.
MyVector<std::string_view> vector_of_view;
vector_of_view.push_back(getMySV());
vector_of_view.push_back(getMySVNotP());
}
// ****************************************************************************
// Container: Use with std::optional<view> (owner<pointer> types)
// ****************************************************************************
void use_with_optional_view() {
MyVector<std::string_view> vector_of_view;
std::optional<std::string_view> optional_of_view;
vector_of_view.push_back(optional_of_view.value());
vector_of_view.push_back(getOptionalS().value()); // expected-warning {{object whose reference is captured by 'vector_of_view' will be destroyed at the end of the full-expression}}
vector_of_view.push_back(getOptionalSV().value());
vector_of_view.push_back(getOptionalMySV().value());
vector_of_view.push_back(getOptionalMySVNotP().value());
}
} // namespace conatiners_with_different
// ****************************************************************************
// Capture 'temporary' views
// ****************************************************************************
namespace temporary_views {
void capture1(std::string_view s [[clang::lifetime_capture_by(x)]], std::vector<std::string_view>& x);
// Intended to capture the "string_view" itself
void capture2(const std::string_view& s [[clang::lifetime_capture_by(x)]], std::vector<std::string_view*>& x);
// Intended to capture the pointee of the "string_view"
void capture3(const std::string_view& s [[clang::lifetime_capture_by(x)]], std::vector<std::string_view>& x);
void use() {
std::vector<std::string_view> x1;
capture1(std::string(), x1); // expected-warning {{object whose reference is captured by 'x1' will be destroyed at the end of the full-expression}}
capture1(std::string_view(), x1);
std::vector<std::string_view*> x2;
// Clang considers 'const std::string_view&' to refer to the owner
// 'std::string' and not 'std::string_view'. Therefore no diagnostic here.
capture2(std::string_view(), x2);
capture2(std::string(), x2); // expected-warning {{object whose reference is captured by 'x2' will be destroyed at the end of the full-expression}}
std::vector<std::string_view> x3;
capture3(std::string_view(), x3);
capture3(std::string(), x3); // expected-warning {{object whose reference is captured by 'x3' will be destroyed at the end of the full-expression}}
}
} // namespace temporary_views
// ****************************************************************************
// Inferring annotation for STL containers
// ****************************************************************************
namespace inferred_capture_by {
const std::string* getLifetimeBoundPointer(const std::string &s [[clang::lifetimebound]]);
const std::string* getNotLifetimeBoundPointer(const std::string &s);
std::string_view getLifetimeBoundView(const std::string& s [[clang::lifetimebound]]);
std::string_view getNotLifetimeBoundView(const std::string& s);
void use() {
std::string local;
std::vector<std::string_view> views;
views.push_back(std::string()); // expected-warning {{object whose reference is captured by 'views' will be destroyed at the end of the full-expression}}
views.insert(views.begin(),
std::string()); // expected-warning {{object whose reference is captured by 'views' will be destroyed at the end of the full-expression}}
views.push_back(getLifetimeBoundView(std::string())); // expected-warning {{object whose reference is captured by 'views' will be destroyed at the end of the full-expression}}
views.push_back(getNotLifetimeBoundView(std::string()));
views.push_back(local);
views.insert(views.end(), local);
std::vector<std::string> strings;
strings.push_back(std::string());
strings.insert(strings.begin(), std::string());
std::vector<const std::string*> pointers;
pointers.push_back(getLifetimeBoundPointer(std::string()));
pointers.push_back(&local);
}
namespace with_span {
// Templated view types.
template<typename T>
struct [[gsl::Pointer]] Span {
Span(const std::vector<T> &V);
};
void use() {
std::vector<Span<int>> spans;
spans.push_back(std::vector<int>{1, 2, 3}); // expected-warning {{object whose reference is captured by 'spans' will be destroyed at the end of the full-expression}}
std::vector<int> local;
spans.push_back(local);
}
} // namespace with_span
} // namespace inferred_capture_by
namespace on_constructor {
struct T {
T(const int& t [[clang::lifetime_capture_by(this)]]);
};
struct T2 {
T2(const int& t [[clang::lifetime_capture_by(x)]], int& x);
};
struct T3 {
T3(const T& t [[clang::lifetime_capture_by(this)]]);
};
int foo(const T& t);
int bar(const T& t[[clang::lifetimebound]]);
void test() {
auto x = foo(T(1)); // OK. no diagnosic
T(1); // OK. no diagnostic
T t(1); // expected-warning {{temporary whose address is used}}
auto y = bar(T(1)); // expected-warning {{temporary whose address is used}}
T3 t3(T(1)); // expected-warning {{temporary whose address is used}}
int a;
T2(1, a); // expected-warning {{object whose reference is captured by}}
}
} // namespace on_constructor
namespace GH121391 {
struct Foo {};
template <typename T>
struct Container {
const T& tt() [[clang::lifetimebound]];
};
template<typename T>
struct StatusOr {
T* get() [[clang::lifetimebound]];
};
StatusOr<Container<const Foo*>> getContainer();
void test() {
std::vector<const Foo*> vv;
vv.push_back(getContainer().get()->tt()); // OK
}
} // namespace GH121391