blob: 2838c6b1f2274290d6450664fc8c55ee695e439b [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
#include "lifetime_annotations/lifetime_annotations.h"
#include <string>
#include <utility>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/status/status.h"
#include "common/status_test_matchers.h"
#include "lifetime_annotations/test/named_func_lifetimes.h"
#include "lifetime_annotations/test/run_on_code.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "llvm/Support/FormatVariadic.h"
namespace clang {
namespace tidy {
namespace lifetimes {
namespace {
using crubit::IsOkAndHolds;
using crubit::StatusIs;
using testing::StartsWith;
bool IsOverloaded(const clang::FunctionDecl* func) {
return !func->getDeclContext()->lookup(func->getDeclName()).isSingleResult();
}
std::string QualifiedName(const clang::FunctionDecl* func) {
std::string str;
llvm::raw_string_ostream ostream(str);
func->printQualifiedName(ostream);
if (IsOverloaded(func)) {
ostream << "[" << func->getType().getAsString() << "]";
}
ostream.flush();
return str;
}
class LifetimeAnnotationsTest : public testing::Test {
protected:
absl::StatusOr<NamedFuncLifetimes> GetNamedLifetimeAnnotations(
absl::string_view code,
const clang::tooling::FileContentMappings& file_contents =
clang::tooling::FileContentMappings()) {
absl::StatusOr<NamedFuncLifetimes> result;
bool success = runOnCodeWithLifetimeHandlers(
llvm::StringRef(code.data(), code.size()),
[&result](clang::ASTContext& ast_context,
const LifetimeAnnotationContext& lifetime_context) {
using clang::ast_matchers::findAll;
using clang::ast_matchers::functionDecl;
using clang::ast_matchers::match;
NamedFuncLifetimes named_func_lifetimes;
for (const auto& node :
match(findAll(functionDecl().bind("func")), ast_context)) {
if (const auto* func =
node.getNodeAs<clang::FunctionDecl>("func")) {
LifetimeSymbolTable symbol_table;
llvm::Expected<FunctionLifetimes> func_lifetimes =
GetLifetimeAnnotations(func, lifetime_context, &symbol_table);
if (!func_lifetimes) {
result = absl::UnknownError(
llvm::toString(func_lifetimes.takeError()));
return;
}
std::string func_name = QualifiedName(func);
std::string new_entry =
NameLifetimes(*func_lifetimes, symbol_table);
std::optional<llvm::StringRef> old_entry =
named_func_lifetimes.Get(func_name);
if (old_entry.has_value()) {
if (new_entry != old_entry.value()) {
result = absl::UnknownError(
llvm::formatv(
"Unexpectedly different lifetimes for function '{0}'."
"Old: '{1}'. New: '{2}'.",
func_name, old_entry.value(), new_entry)
.str());
return;
}
} else {
named_func_lifetimes.Add(std::move(func_name),
std::move(new_entry));
}
}
}
result = std::move(named_func_lifetimes);
},
{}, file_contents);
if (!success) {
return absl::UnknownError(
"Error extracting lifetimes. (Compilation error?)");
}
return result;
}
};
TEST_F(LifetimeAnnotationsTest, NoLifetimes) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
int f(int);
)"),
IsOkAndHolds(LifetimesAre({{"f", "()"}})));
}
TEST_F(LifetimeAnnotationsTest, Failure_CompileError) {
EXPECT_THAT(
GetNamedLifetimeAnnotations(R"(
undefined f(undefined);
)"),
StatusIs(absl::StatusCode::kUnknown,
StartsWith("Error extracting lifetimes. (Compilation error?)")));
}
TEST_F(LifetimeAnnotationsTest, Failure_NoAnnotationsNoLifetimeElision) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
int** f(int*);
)"),
StatusIs(absl::StatusCode::kUnknown,
StartsWith("Lifetime elision not enabled")));
}
TEST_F(LifetimeAnnotationsTest, Failure_NoOutputAnnotationNoLifetimeElision) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
int* f();
)"),
StatusIs(absl::StatusCode::kUnknown,
// We specifically want to see this error message rather
// than "Cannot elide output lifetimes".
StartsWith("Lifetime elision not enabled")));
}
TEST_F(LifetimeAnnotationsTest, Failure_NoAnnotationsElisionPragmaInWrongFile) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
#pragma clang lifetime_elision
#include "header.h"
)",
{std::make_pair("header.h", R"(
int** f(int*);
)")}),
StatusIs(absl::StatusCode::kUnknown,
StartsWith("Lifetime elision not enabled")));
}
TEST_F(LifetimeAnnotationsTest, LifetimeElision_OneInputLifetime) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
#pragma clang lifetime_elision
int** f(int*);
)"),
IsOkAndHolds(LifetimesAre({{"f", "a -> (a, a)"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeElision_NoOutputLifetimes) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
#pragma clang lifetime_elision
void f(int**, int *);
)"),
IsOkAndHolds(LifetimesAre({{"f", "(a, b), c"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeElision_Templates) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
#pragma clang lifetime_elision
template <class T> class vector {};
int* f(vector<int *>);
vector<int*> g(int *);
)"),
IsOkAndHolds(LifetimesAre({{"f", "a -> a"}, {"g", "a -> a"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeElision_NestedTemplates) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
#pragma clang lifetime_elision
template <class T>
struct Outer {
template <class U>
struct Inner {
};
};
void f(Outer<int *>::Inner<int *> &);
Outer<int *>::Inner<int *> g(int *);
)"),
IsOkAndHolds(LifetimesAre(
{{"f", "(<a>::<b>, c)"}, {"g", "a -> <a>::<a>"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeElision_LifetimeParameterizedType) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
#pragma clang lifetime_elision
struct [[clang::annotate("lifetime_params", "s")]] string_view{};
string_view f(string_view);
)"),
IsOkAndHolds(LifetimesAre({{"f", "a -> a"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeElision_Method) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
#pragma clang lifetime_elision
struct S {
int** method(int *, int *);
};
)"),
IsOkAndHolds(LifetimesAre({{"S::method", "a: b, c -> (a, a)"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeElision_ExplicitlyDefaultedCtor) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
#pragma clang lifetime_elision
struct S {
S() = default;
};)"),
IsOkAndHolds(LifetimesAre({{"S::S", "a:"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeElision_ImplicitlyDefaultedCtor) {
// Implicitly-defaulted constructors don't have associated `TypeSourceInfo`.
EXPECT_THAT(
GetNamedLifetimeAnnotations(R"(
#pragma clang lifetime_elision
struct S {};
// We need to use the implicitly-defaulted constructors to make
// them appear in the AST so that we can process them.
void foo() { S s; }
)"),
IsOkAndHolds(LifetimesContain({{"S::S[void (void) noexcept]", "a:"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeElision_ArrayParamLifetimes) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
#pragma clang lifetime_elision
void f(int pair[2]);
)"),
IsOkAndHolds(LifetimesAre({{"f", "a"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeElision_ArrayParamAsTypedefLifetimes) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
#pragma clang lifetime_elision
typedef int Arr[2];
void f(Arr);
)"),
IsOkAndHolds(LifetimesAre({{"f", "a"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeElision_FunctionPointerLifetimes) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
#pragma clang lifetime_elision
void f(void (*)());
)"),
IsOkAndHolds(LifetimesAre({{"f", "a"}})));
}
TEST_F(LifetimeAnnotationsTest,
LifetimeElision_FunctionPointerAsTypedefLifetimes) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
#pragma clang lifetime_elision
typedef void (*FunctionPointer)();
void f(FunctionPointer hook);
)"),
IsOkAndHolds(LifetimesAre({{"f", "a"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeElision_FunctionReferenceLifetimes) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
#pragma clang lifetime_elision
typedef void (&FunctionReference)();
void f(FunctionReference hook);
)"),
IsOkAndHolds(LifetimesAre({{"f", "a"}})));
}
TEST_F(LifetimeAnnotationsTest,
LifetimeElision_FunctionReferenceAsTypedefLifetimes) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
#pragma clang lifetime_elision
void f(void (&)());
)"),
IsOkAndHolds(LifetimesAre({{"f", "a"}})));
}
TEST_F(LifetimeAnnotationsTest,
LifetimeElision_PointerToMemberDoesNotGetLifetime) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
#pragma clang lifetime_elision
struct S {};
void f(int S::*ptr_to_member);
)"),
IsOkAndHolds(LifetimesAre({{"f", "()"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeElision_FailureTooFewInputLifetimes) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
#pragma clang lifetime_elision
int* f();
)"),
StatusIs(absl::StatusCode::kUnknown,
StartsWith("Cannot elide output lifetimes")));
}
TEST_F(LifetimeAnnotationsTest, LifetimeElision_FailureTooManyInputLifetimes) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
#pragma clang lifetime_elision
int* f(int**);
)"),
StatusIs(absl::StatusCode::kUnknown,
StartsWith("Cannot elide output lifetimes")));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_NoLifetimes) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"_(
[[clang::annotate("lifetimes", "()")]]
void f(int);
)_"),
IsOkAndHolds(LifetimesAre({{"f", "()"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_Simple) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
[[clang::annotate("lifetimes", "a -> a")]]
int* f(int*);
)"),
IsOkAndHolds(LifetimesAre({{"f", "a -> a"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_MultiplePtr) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
[[clang::annotate("lifetimes", "(a, b) -> a")]]
int* f(int**);
)"),
IsOkAndHolds(LifetimesAre({{"f", "(a, b) -> a"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_MultipleArguments) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
[[clang::annotate("lifetimes", "a, b -> a")]]
int* f(int*, int*);
)"),
IsOkAndHolds(LifetimesAre({{"f", "a, b -> a"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_NoReturn) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
[[clang::annotate("lifetimes", "a, b")]]
void f(int*, int*);
)"),
IsOkAndHolds(LifetimesAre({{"f", "a, b"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_NoLifetimeParam) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
[[clang::annotate("lifetimes", "a, (), a -> a")]]
int* f(int*, int, int*);
)"),
IsOkAndHolds(LifetimesAre({{"f", "a, (), a -> a"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_Method) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
struct S {
[[clang::annotate("lifetimes", "a: -> a")]]
int* f();
};
)"),
IsOkAndHolds(LifetimesAre({{"S::f", "a: -> a"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_MethodWithParam) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
struct S {
[[clang::annotate("lifetimes", "a: b -> a")]]
int* f(int*);
};
)"),
IsOkAndHolds(LifetimesAre({{"S::f", "a: b -> a"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_MethodWithLifetimeParams) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
struct [[clang::annotate("lifetime_params", "x", "y")]] S {
[[clang::annotate("lifetimes", "([x, y], a): -> x")]]
int* f();
};
)"),
IsOkAndHolds(LifetimesAre({{"S::f", "([x, y], a): -> x"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_Invalid_MissingThis) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
struct S {
[[clang::annotate("lifetimes", "-> a")]]
int* f();
};
)"),
StatusIs(absl::StatusCode::kUnknown,
StartsWith("Invalid lifetime annotation")));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_Invalid_ThisOnFreeFunction) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
[[clang::annotate("lifetimes", "a: a -> a")]]
int* f(int*);
)"),
StatusIs(absl::StatusCode::kUnknown,
StartsWith("Invalid lifetime annotation")));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_Invalid_WrongNumber) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
[[clang::annotate("lifetimes", "a -> a")]]
int* f(int**);
)"),
StatusIs(absl::StatusCode::kUnknown,
StartsWith("Invalid lifetime annotation")));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_Callback) {
EXPECT_THAT(
GetNamedLifetimeAnnotations(R"(
[[clang::annotate("lifetimes", "b, ((a -> a), static) -> b")]]
int* f(int*, int* (*)(int*));
)"),
IsOkAndHolds(LifetimesAre({{"f", "b, ((a -> a), static) -> b"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_CallbackMultipleParams) {
EXPECT_THAT(
GetNamedLifetimeAnnotations(R"(
[[clang::annotate("lifetimes", "c, ((a, b -> a), static) -> c")]]
int* f(int*, int* (*)(int*, int*));
)"),
IsOkAndHolds(LifetimesAre({{"f", "c, ((a, b -> a), static) -> c"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_CallbackTmplFunc) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
template <typename Func>
struct function;
[[clang::annotate("lifetimes", "a, ((b -> b)) -> a")]]
int* f(int*, function<int*(int*)>);
)"),
IsOkAndHolds(LifetimesAre({{"f", "a, ((b -> b)) -> a"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_MultipleCallbacks) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
[[clang::annotate("lifetimes", "a, ((b -> b), static), ((c -> c), static) -> a")]]
int* f(int*, int* (*)(int*), int* (*)(int*));
)"),
IsOkAndHolds(LifetimesAre(
{{"f", "a, ((b -> b), static), ((c -> c), static) -> a"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_ReturnFunctionPtr) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"_(
typedef int* (*FP)(int*);
[[clang::annotate("lifetimes", "a -> ((b -> b), static)")]]
FP f(int*);
)_"),
IsOkAndHolds(LifetimesAre({{"f", "a -> ((b -> b), static)"}})));
}
} // namespace
} // namespace lifetimes
} // namespace tidy
} // namespace clang