blob: c2e2d7e68ff3b3d7c2e2da6697d3c2b6d4fac5ef [file] [log] [blame]
// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// Tests involving class templates.
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "lifetime_analysis/test/lifetime_analysis_test.h"
namespace clang {
namespace tidy {
namespace lifetimes {
namespace {
TEST_F(LifetimeAnalysisTest, StructTemplate) {
EXPECT_THAT(GetLifetimes(R"(
template <typename T>
struct S {
T a;
};
int* target(S<int*> s) {
return s.a;
}
)"),
LifetimesAre({{"target", "a -> a"}}));
}
TEST_F(LifetimeAnalysisTest, StructTemplatePtr) {
EXPECT_THAT(GetLifetimes(R"(
template <typename T>
struct S {
T a;
};
int* target(S<int*>* s) {
return s->a;
}
)"),
LifetimesAre({{"target", "(a, b) -> a"}}));
}
TEST_F(LifetimeAnalysisTest, StructTemplateInnerDoubleUsage) {
EXPECT_THAT(GetLifetimes(R"(
template <typename T>
struct S {
T a;
T b;
};
int* target(S<int**>* s) {
int l = 0;
*s->b = &l;
return *s->a;
}
)"),
LifetimesAre({{"target",
"ERROR: function returns reference to a local "
"through parameter 's'"}}));
}
TEST_F(LifetimeAnalysisTest, StructTwoTemplateArguments) {
EXPECT_THAT(GetLifetimes(R"(
template <typename T, typename U>
struct S {
T t;
U u;
};
int* return_t(S<int*, int*>& v) {
return v.t;
}
int* return_u(S<int*, int*>& v) {
return v.u;
}
)"),
LifetimesAre({{"return_t", "(<a, b>, c) -> a"},
{"return_u", "(<a, b>, c) -> b"}}));
}
// Disabled due to b/253992761.
TEST_F(LifetimeAnalysisTest, DISABLED_StructTwoTemplateArgumentsNestedClasses) {
EXPECT_THAT(GetLifetimes(R"(
template <typename T>
struct Outer {
template <typename U>
struct Inner {
T t;
U u;
};
};
int* return_t(Outer<int*>::Inner<int*>& inner) {
return inner.t;
}
int* return_u(Outer<int*>::Inner<int*>& inner) {
return inner.u;
}
)"),
LifetimesAre({{"return_t", "(<a>::<b>, c) -> a"},
{"return_u", "(<a>::<b>, c) -> b"}}));
}
TEST_F(LifetimeAnalysisTest, StructTwoTemplateArgumentsConstructInner) {
EXPECT_THAT(GetLifetimes(R"(
template <typename T>
struct Inner {
Inner (T a): a(a) {}
T a;
};
template <typename T, typename U>
struct Outer {
Outer(T a, U& b): a(a), b(b) {}
T a;
U b;
};
int* target(int* a, int* b) {
Inner<int*> is(b);
Outer<int*, Inner<int*>> s(a, is);
return s.b.a;
}
)"),
LifetimesContain({{"target", "a, b -> b"}}));
}
TEST_F(LifetimeAnalysisTest, StructTwoTemplateArgumentsTernary) {
EXPECT_THAT(GetLifetimes(R"(
template <typename T, typename U>
struct S {
T t;
U u;
};
int* f(S<int*, int*>& v) {
return *v.t < *v.u ? v.t : v.u;
}
)"),
LifetimesAre({{"f", "(<a, a>, b) -> a"}}));
}
TEST_F(LifetimeAnalysisTest, StructTemplateLocalVariable) {
EXPECT_THAT(GetLifetimes(R"(
template <typename T>
struct S {
T a;
};
const int* target(S<int*> s) {
S<const int*> t;
t.a = s.a;
return t.a;
}
)"),
LifetimesAre({{"target", "a -> a"}}));
}
TEST_F(LifetimeAnalysisTest, StructTemplatePointerToMember) {
EXPECT_THAT(GetLifetimes(R"(
template <typename T, typename U>
struct S {
T a;
U b;
};
int** target(S<int*, int*>& s) {
return &s.b;
}
)"),
LifetimesAre({{"target", "(<a, b>, c) -> (b, c)"}}));
}
TEST_F(LifetimeAnalysisTest, StructTemplateWithPointer) {
EXPECT_THAT(GetLifetimes(R"(
template <typename T>
struct [[clang::annotate("lifetime_params", "a")]] S {
[[clang::annotate("member_lifetimes", "a")]]
T* a;
};
int** target(S<int*>& s) {
return s.a;
}
)"),
LifetimesAre({{"target", "(<a> [b], c) -> (a, b)"}}));
}
TEST_F(LifetimeAnalysisTest, StructTemplateWithTemplate) {
EXPECT_THAT(GetLifetimes(R"(
template <typename T>
struct S {
T a;
};
int* target(S<S<int*>> s) {
return s.a.a;
}
)"),
LifetimesAre({{"target", "a -> a"}}));
}
TEST_F(LifetimeAnalysisTest, StructTemplateInnerTemplate) {
EXPECT_THAT(GetLifetimes(R"(
template <typename T>
struct U {
T a;
};
template <typename T>
struct S {
U<T> a;
};
int* target(S<int*>* s) {
return s->a.a;
}
)"),
LifetimesAre({{"target", "(a, b) -> a"}}));
}
TEST_F(LifetimeAnalysisTest, DISABLED_StructTemplateInnerTemplatePtr) {
// TODO(veluca): we don't correctly propagate lifetime arguments when creating
// template arguments for fields that use the template argument indirectly,
// such as behind a pointer or as template arguments to a struct passed as a
// template argument to the member.
EXPECT_THAT(GetLifetimes(R"(
template <typename T>
struct U {
T a;
};
template <typename T>
struct S {
U<T*> a;
};
int* target(S<int*>* s) {
return *s->a.a;
}
)"),
LifetimesAre({{"target", "(a, b) -> a"}}));
}
TEST_F(LifetimeAnalysisTest, StructTemplateSwapArguments) {
EXPECT_THAT(GetLifetimes(R"(
template <typename T, typename U>
struct [[clang::annotate("lifetime_params", "a")]] S {
T a;
U b;
[[clang::annotate("member_lifetimes", "a", "a")]]
S<U, T>* next;
};
int* target(S<int*, int*>* s) {
return s->next->a;
}
int* target_swtwice(S<int*, int*>* s) {
return s->next->next->a;
}
)"),
LifetimesAre({{"target", "(<a, b> [c], d) -> b"},
{"target_swtwice", "(<a, b> [c], d) -> a"}}));
}
TEST_F(LifetimeAnalysisTest, StructTemplateMemberCall) {
EXPECT_THAT(
GetLifetimes(R"(
template <typename T>
// TODO(mboehme): The real `vector` doesn't have lifetime parameters, but
// we use these here as we don't have the ability to do `lifetime_cast`s
// yet.
struct [[clang::annotate("lifetime_params", "a")]] vector {
T& operator[](int i) { return a[i]; }
[[clang::annotate("member_lifetimes", "a")]]
T* a;
};
int* get(vector<int*>& v, int i) {
return v[i];
}
)"),
LifetimesAre({{"vector<int *>::operator[]", "(<a> [b], c): () -> (a, b)"},
{"get", "(<a> [b], c), () -> a"}}));
}
TEST_F(LifetimeAnalysisTest, StructTwoTemplateArgumentsCall) {
EXPECT_THAT(
GetLifetimes(R"(
template <typename T, typename U>
struct S {
T t;
U u;
};
int* f(S<int*, int*>& v) {
return *v.t < *v.u ? v.t : v.u;
}
int* g(S<int*, int*>& v) {
return f(v);
}
)"),
LifetimesAre({{"f", "(<a, a>, b) -> a"}, {"g", "(<a, a>, b) -> a"}}));
}
TEST_F(LifetimeAnalysisTest, StructNoTemplateInnerTemplate) {
EXPECT_THAT(
GetLifetimes(R"(
template <typename T>
struct X {
T field;
};
struct Y {
X<int*> field;
};
int* target_byref(Y& s) {
return s.field.field;
}
int* target_byvalue(Y s) {
return s.field.field;
}
)"),
LifetimesContain({{"target_byref", "a -> a"},
{"target_byvalue",
"ERROR: function returns reference to a local"}}));
}
TEST_F(LifetimeAnalysisTest, StructTemplateReturn) {
EXPECT_THAT(GetLifetimes(R"(
template <typename T>
struct S {
T a;
};
S<int*> target(S<int*>& s) {
return s;
}
)"),
LifetimesAre({{"target", "(a, b) -> a"}}));
}
TEST_F(LifetimeAnalysisTest, StructTemplateReturnXvalue) {
EXPECT_THAT(GetLifetimes(R"(
template <typename T>
struct S {
T a;
};
S<int*> target(S<int*> s) {
return s;
}
)"),
LifetimesAre({{"target", "a -> a"}}));
}
TEST_F(LifetimeAnalysisTest, StructTemplateReturnCall) {
EXPECT_THAT(GetLifetimes(R"(
template <typename T>
struct S {
T a;
};
S<int*> take_by_ref(S<int*>& s) {
return s;
}
S<int*> take_by_value(S<int*> s) {
return s;
}
)"),
LifetimesAre({{"take_by_ref", "(a, b) -> a"},
{"take_by_value", "a -> a"}}));
}
TEST_F(LifetimeAnalysisTest, StructTemplateReturnLocal) {
EXPECT_THAT(GetLifetimes(R"(
template <typename T>
struct S {
T a;
};
S<int*> target(int* a) {
int i = 42;
return { &i };
}
)"),
LifetimesAre({{"target",
"ERROR: function returns reference to a local"}}));
}
TEST_F(LifetimeAnalysisTest, ReturnStructTemporaryConstructor) {
EXPECT_THAT(GetLifetimes(R"(
template <typename T>
struct S {
S(T a) : a(a) {}
T a;
};
S<int*> ConstructorCastSyntax(int* a) {
return S(a);
}
S<int*> ConstructTemporarySyntax(int* a) {
return S{a};
}
)"),
LifetimesAre({{"S<int *>::S", "(a, b): a"},
{"ConstructorCastSyntax", "a -> a"},
{"ConstructTemporarySyntax", "a -> a"}}));
}
TEST_F(LifetimeAnalysisTest, ReturnStructTemporaryInitializerList) {
EXPECT_THAT(GetLifetimes(R"(
template <typename T>
struct S {
T a;
};
S<int*> InitListExpr(int* a) {
return {a};
}
S<int*> CastWithInitListExpr(int* a) {
return S<int*>{a};
}
)"),
LifetimesAre({{"InitListExpr", "a -> a"},
{"CastWithInitListExpr", "a -> a"}}));
}
TEST_F(LifetimeAnalysisTest, ReturnUnionTemporaryInitializerList) {
EXPECT_THAT(GetLifetimes(R"(
template <typename T>
union S {
T a;
};
S<int*> target(int* a) {
return {a};
}
)"),
LifetimesAre({{"target", "a -> a"}}));
}
TEST_F(LifetimeAnalysisTest, DISABLED_StructTemplateReturnPassByValue) {
// TODO(veluca): disabled because calling a function with a pass-by-value
// struct is not yet supported -- see TODO in TransferLifetimesForCall.
EXPECT_THAT(GetLifetimes(R"(
template <typename T>
struct S {
T a;
};
S<int*> t(S<int*> s) {
return s;
}
S<int*> target(S<int*> s) {
return t(s);
}
)"),
LifetimesAre({{"t", "a -> a"}, {"target", "a -> a"}}));
}
TEST_F(LifetimeAnalysisTest, StructWithTemplateArgs) {
EXPECT_THAT(GetLifetimes(R"(
template <typename T, typename U>
struct S {
T t;
U u;
};
int* target(S<int*, int*>* s, int* t, int* u) {
s->t = t;
s->u = u;
return s->t;
}
)"),
// With template arguments, now the struct and its fields can
// have different lifetimes.
LifetimesAre({{"target", "(<a, b>, c), a, b -> a"}}));
}
TEST_F(LifetimeAnalysisTest, ExampleFromRFC) {
// This is an example from the lifetimes RFC.
EXPECT_THAT(GetLifetimes(R"(
template <typename T>
struct R {
R(T t) : t(t) {}
T t;
};
bool some_condition();
template <typename T>
struct S {
S(T a, T b) : r(some_condition() ? R(a) : R(b)) {}
R<T> r;
};
int* target(int* a, int* b) {
S<int*> s(a, b);
return s.r.t;
}
)"),
LifetimesContain({{"R<int *>::R", "(a, b): a"},
{"S<int *>::S", "(a, b): a, a"},
{"target", "a, a -> a"}}));
}
TEST_F(LifetimeAnalysisTest, VariadicTemplate) {
EXPECT_THAT(GetLifetimes(R"(
template <int idx, typename... Args> struct S {};
template <int idx, typename T, typename... Args>
struct S<idx, T, Args...> {
T t;
S<idx+1, Args...> nested;
};
template <typename... Args>
struct tuple: public S<0, Args...> {};
int* target(tuple<int*, int*>& s) {
return s.nested.t;
}
)"),
LifetimesAre({{"target", "(<a, b>, c) -> b"}}));
}
TEST_F(LifetimeAnalysisTest, DISABLED_VariadicTemplateConstructTrivial) {
EXPECT_THAT(GetLifetimes(R"(
template <int idx, typename... Args> struct S {};
template <int idx, typename T, typename... Args>
struct S<idx, T, Args...> {
T t;
S<idx+1, Args...> nested;
};
template <typename... Args>
struct tuple: public S<0, Args...> {};
void target(int* a, int* b) {
tuple<int*, int*> s = {a, b};
}
)"),
LifetimesAre({{"target", "a, b"}}));
}
TEST_F(LifetimeAnalysisTest, VariadicTemplateConstruct) {
EXPECT_THAT(GetLifetimes(R"(
template <typename... Args> struct S { S() {} };
template <typename T, typename... Args>
struct S<T, Args...> {
T t;
S<Args...> nested;
S(T t, Args... args): t(t), nested(args...) {}
};
void target(int* a, int* b) {
S<int*, int*> s = {a, b};
}
)"),
LifetimesContain({{"target", "a, b"}}));
}
// Disabled due to b/253992761.
TEST_F(LifetimeAnalysisTest, DISABLED_NoexceptTemplate) {
EXPECT_THAT(GetLifetimes(R"(
template <typename T>
struct S {
S() noexcept(isnoexcept<T>()) {}
template <typename U>
static constexpr bool isnoexcept() { return true; }
};
void f() {
S<int> s;
}
)"),
LifetimesContain({{"f", ""}}));
}
TEST_F(LifetimeAnalysisTest, TypeTemplateArgAfterNonType) {
// Minimized repro for a crash from b/228325046.
EXPECT_THAT(GetLifetimes(R"(
template<int _Idx, typename _Head>
struct _Head_base
{
constexpr _Head_base(_Head&& __h)
: _M_head_impl(__h) { }
_Head _M_head_impl;
};
void f() {
_Head_base<0, void*> head_base(nullptr);
}
)"),
LifetimesContain({{"f", ""}}));
}
TEST_F(LifetimeAnalysisTest,
TemplateContainingTypedefInstantiatedAnotherTemplate) {
// Minimized repro for a crash from b/228325046.
// The scenario that triggered the crash is:
// - We have a template (in this case `remove_reference`) containing a typedef
// - That typedef depends on a template parameter
// - We instantiate the template with an argument that is another template
// The bug was that we weren't desugaring the typedef and hence coming up with
// a different value for the depth of the template argument than
// TemplateTypeParmType::getDepth() uses.
EXPECT_THAT(GetLifetimes(R"(
namespace std {
template <typename T1, typename T2> struct pair {
T1 t1;
T2 t2;
};
template<typename _Tp>
struct remove_reference
{ typedef _Tp type; };
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type& __t) noexcept
{ return static_cast<_Tp&&>(__t); }
}
void f() {
std::pair<int, int> p;
std::forward<decltype(p)>(p);
}
)"),
LifetimesContain({{"f", ""}}));
}
TEST_F(LifetimeAnalysisTest, DISABLED_ReturnPointerToTemplate) {
EXPECT_THAT(
GetLifetimes(R"(
template <class T> struct S { T t; };
S<int*>* target(S<int*>* s) {
return s;
}
)"),
// TODO(b/230456778): This currently erroneously returns (a, b) -> (c, b)
LifetimesAre({{"f", "(a, b) -> (a, b)"}}));
}
} // namespace
} // namespace lifetimes
} // namespace tidy
} // namespace clang