blob: 7c791cc2b932eea148ab9524afc52a5f3af3d457 [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 static lifetimes.
#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, MaybeReturnStatic) {
// Check that we don't infer 'static for the parameter or the return value,
// which would be overly restrictive.
EXPECT_THAT(GetLifetimes(R"(
int* target(int* i_non_static) {
if (*i_non_static > 0) {
return i_non_static;
} else {
static int i_static;
return &i_static;
}
}
)"),
LifetimesAre({{"target", "a -> a"}}));
}
TEST_F(LifetimeAnalysisTest, MaybeReturnStaticConst) {
// Same as above, but return a pointer-to-const. This should have no
// influence on the outcome.
EXPECT_THAT(GetLifetimes(R"(
const int* target(int* i_non_static) {
if (*i_non_static > 0) {
return i_non_static;
} else {
static int i_static;
return &i_static;
}
}
)"),
LifetimesAre({{"target", "a -> a"}}));
}
TEST_F(LifetimeAnalysisTest, StaticPointerOutParam) {
EXPECT_THAT(GetLifetimes(R"(
void f(int** p) {
static int i = 42;
*p = &i;
}
)"),
LifetimesAre({{"f", "(a, b)"}}));
}
TEST_F(LifetimeAnalysisTest, MaybeReturnStaticStruct) {
// We infer a `static` lifetime parameter for `s_static` because any pointers
// contained in it need to outlive the struct itself. This implies that the
// lifetime parameter for the return value also needs to be `static`, and
// hence the lifetime parameter on `*s_input` needs to be `static` too.
EXPECT_THAT(GetLifetimes(R"(
struct [[clang::annotate("lifetime_params", "a")]] S {
[[clang::annotate("member_lifetimes", "a", "a")]]
int** pp;
};
S* target(S* s_input) {
if (**s_input->pp > 0) {
return s_input;
} else {
static S s_static;
return &s_static;
}
}
)"),
LifetimesAre({{"target", "(static, a) -> (static, a)"}}));
}
TEST_F(LifetimeAnalysisTest, MaybeReturnStaticStructConst) {
// Same as above, but return a pointer-to-const. This shouldn't affect the
// result, as it's still possible to modify `*s.pp` even if for a `const S s`.
EXPECT_THAT(GetLifetimes(R"(
struct [[clang::annotate("lifetime_params", "a")]] S {
[[clang::annotate("member_lifetimes", "a", "a")]]
int** pp;
};
const S* target(S* s_input) {
if (**s_input->pp > 0) {
return s_input;
} else {
static S s_static;
return &s_static;
}
}
)"),
LifetimesAre({{"target", "(static, a) -> (static, a)"}}));
}
TEST_F(LifetimeAnalysisTest, MaybeReturnStaticStructConstWithoutPointer) {
// Same as above, but with a struct that doesn't actually contain any
// pointers. This changes the result, as a 'static struct without any pointer
// can be used in place of a struct of the same type of any lifetime.
EXPECT_THAT(GetLifetimes(R"(
struct S {
int i;
};
const S* target(S* s_input) {
if (s_input->i > 0) {
return s_input;
} else {
static S s_static;
return &s_static;
}
}
)"),
LifetimesAre({{"target", "a -> a"}}));
}
TEST_F(LifetimeAnalysisTest, ReturnStaticDoublePointerWithConditional) {
EXPECT_THAT(
GetLifetimes(R"(
struct [[clang::annotate("lifetime_params", "a")]] S {
[[clang::annotate("member_lifetimes", "a")]]
int* p;
};
int** target(int** pp1, int** pp2) {
// Force *pp1 to have static lifetime.
static S s;
s.p = *pp1;
if (**pp1 > 0) {
return pp1;
} else {
return pp2;
}
}
)"),
LifetimesAre({{"target", "(static, a), (static, a) -> (static, a)"}}));
}
TEST_F(LifetimeAnalysisTest, ReturnStaticConstDoublePointerWithConditional) {
EXPECT_THAT(GetLifetimes(R"(
struct [[clang::annotate("lifetime_params", "a")]] S {
[[clang::annotate("member_lifetimes", "a")]]
int* p;
};
int* const * target(int** pp1, int** pp2) {
// Force *pp1 to have static lifetime.
static S s;
s.p = *pp1;
if (**pp1 > 0) {
return pp1;
} else {
return pp2;
}
}
)"),
LifetimesAre({{"target", "(static, a), (b, a) -> (b, a)"}}));
}
TEST_F(LifetimeAnalysisTest, StaticParameterChainedCall) {
EXPECT_THAT(GetLifetimes(R"(
class S {};
void f1(S* s) {
static S* s_static = s;
}
void f2(S* s) {
f1(s);
}
)"),
LifetimesAre({{"f1", "static"}, {"f2", "static"}}));
}
TEST_F(LifetimeAnalysisTest, ConstructorStoresThisPointerInStatic) {
EXPECT_THAT(GetLifetimes(R"(
struct S {
S() {
static S* last_constructed = this;
}
};
void construct_static_variable() {
static S s;
}
void construct_local_variable() {
S s;
}
)"),
// Because S() stores the `this` pointer in a static variable, the
// lifetime of the `this` pointer needs to be static. This means
// that any instances of `S` that are constructed need to have
// static lifetime.
LifetimesAre({{"S::S", "static:"},
{"construct_static_variable", ""},
{"construct_local_variable",
"ERROR: Function assigns local to static"}}));
}
TEST_F(LifetimeAnalysisTest, ConstructorStoresThisPointerInStatic_WithField) {
EXPECT_THAT(GetLifetimes(R"(
struct S {
S() {
static S* last_constructed = this;
}
};
struct T {
// Ensure that T() isn't defaulted because we don't want to trigger the
// special logic for defaulted functions.
T() {}
S s;
};
)"),
LifetimesAre({{"S::S", "static:"}, {"T::T", "static:"}}));
}
TEST_F(LifetimeAnalysisTest,
ConstructorStoresThisPointerInStatic_WithDerivedClass) {
EXPECT_THAT(GetLifetimes(R"(
struct S {
S() {
static S* last_constructed = this;
}
};
struct T : public S {
// Ensure that T() isn't defaulted because we don't want to trigger the
// special logic for defaulted functions.
T() {}
};
)"),
LifetimesAre({{"S::S", "static:"}, {"T::T", "static:"}}));
}
} // namespace
} // namespace lifetimes
} // namespace tidy
} // namespace clang