blob: dd61aa2a4a4ffac85e5f21f4c0c62a8e6104f3fb [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 <optional>
#include <string>
#include <utility>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
#include "common/status_test_matchers.h"
#include "lifetime_annotations/lifetime_error.h"
#include "lifetime_annotations/lifetime_symbol_table.h"
#include "lifetime_annotations/test/named_func_lifetimes.h"
#include "lifetime_annotations/test/run_on_code.h"
#include "lifetime_annotations/type_lifetimes.h"
#include "clang/AST/Decl.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/raw_ostream.h"
// This file contains tests both for the "legacy" lifetime annotations
// (`[[clang::annotate("lifetimes", ...)]]` placed on a function declaration)
// and the newer annotations (`[[clang::annotate_type("lifetime", ...")]]`
// placed on a type). This is because we expect we may continue to use the
// "legacy" style of annotations in sidecar files.
//
// Some tests only test one style of annotation where testing the other style
// does not make sense for the particular test.
//
// In addition, this file contains tests for lifetime elision (which can be
// considered an implicit form of lifetime annotation). Lifetime elision tests
// fail if the lifetimes do not have exactly the expected names, even if the
// lifetimes are equivalent up to a renaming. The intent of this is to catch
// cases where additional lifetimes are produced but not used.
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 << "[" << StripAttributes(func->getType()).getAsString() << "]";
}
ostream.flush();
return str;
}
// Prepends definitions for lifetime annotation macros to the code.
std::string WithLifetimeMacros(absl::string_view code) {
std::string result = R"(
// TODO(mboehme): We would prefer `$(...)` to be a variadic macro that
// stringizes each of its macro arguments individually. This is possible but
// requires some contortions: https://stackoverflow.com/a/5958315
#define $(l) [[clang::annotate_type("lifetime", #l)]]
#define $2(l1, l2) [[clang::annotate_type("lifetime", #l1, #l2)]]
#define $3(l1, l2, l3) [[clang::annotate_type("lifetime", #l1, #l2, #l3)]]
)";
for (char l = 'a'; l <= 'z'; ++l) {
absl::StrAppendFormat(&result, "#define $%c $(%c)\n", l, l);
}
absl::StrAppend(&result, "#define $static $(static)\n");
absl::StrAppend(&result, code);
return result;
}
std::string FormatErrorString(llvm::Error err) {
std::string result;
err = llvm::handleErrors(
std::move(err), [&result](const LifetimeError& lifetime_err) {
switch (lifetime_err.type()) {
case LifetimeError::Type::ElisionNotEnabled:
result = "ERROR(ElisionNotEnabled): ";
break;
case LifetimeError::Type::CannotElideOutputLifetimes:
result = "ERROR(CannotElideOutputLifetimes): ";
break;
case LifetimeError::Type::Other:
result = "ERROR(Other): ";
break;
}
absl::StrAppend(&result, lifetime_err.message());
});
if (err) {
result = absl::StrCat("ERROR: ", llvm::toString(std::move(err)));
}
return result;
}
class LifetimeAnnotationsTest : public testing::Test {
protected:
absl::StatusOr<NamedFuncLifetimes> GetNamedLifetimeAnnotations(
absl::string_view code,
const clang::tooling::FileContentMappings& file_contents =
clang::tooling::FileContentMappings(),
bool skip_templates = true) {
absl::StatusOr<NamedFuncLifetimes> result;
bool success = runOnCodeWithLifetimeHandlers(
llvm::StringRef(code.data(), code.size()),
[&result, skip_templates](
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")) {
// Skip various categories of function, unless explicitly
// requested:
// - Template instantiation don't contain any annotations that
// aren't present in the template itself, but they may contain
// reference-like types (which will obviously be unannotated),
// which will generate nuisance "lifetime elision not enabled"
// errors.
// - Implicitly defaulted functions obviously cannot contain
// lifetime annotations. They will need to be handled through
// `AnalyzeDefaultedFunction()` in analyze.cc.
if ((func->isTemplateInstantiation() && skip_templates) ||
(func->isDefaulted() && !func->isExplicitlyDefaulted())) {
continue;
}
LifetimeSymbolTable symbol_table;
llvm::Expected<FunctionLifetimes> func_lifetimes =
GetLifetimeAnnotations(func, lifetime_context, &symbol_table);
std::string new_entry;
if (func_lifetimes) {
new_entry = NameLifetimes(*func_lifetimes, symbol_table);
} else {
new_entry = FormatErrorString(func_lifetimes.takeError());
}
std::string func_name = QualifiedName(func);
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*);
)"),
IsOkAndHolds(LifetimesAre({{"f",
"ERROR(ElisionNotEnabled): Lifetime "
"elision not enabled for 'f'"}})));
}
TEST_F(LifetimeAnnotationsTest, Failure_NoOutputAnnotationNoLifetimeElision) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
int* f();
)"),
// We specifically want to see this error message rather than
// "Cannot elide output lifetimes".
IsOkAndHolds(LifetimesAre({{"f",
"ERROR(ElisionNotEnabled): Lifetime "
"elision not enabled for 'f'"}})));
}
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*);
)")}),
IsOkAndHolds(LifetimesAre({{"f",
"ERROR(ElisionNotEnabled): Lifetime "
"elision not enabled for 'f'"}})));
}
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_TemplatesWithConstant) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
#pragma clang lifetime_elision
template <class T, bool B> class vector {};
int* f(vector<int *, true>);
vector<int*, false> 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_Destructor) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"cc(
// Note: this works even without #pragma clang lifetime_elision
struct S {
~S();
};
)cc"),
IsOkAndHolds(LifetimesAre({{"S::~S", "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_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_LifetimesInsideFunctionPointerParameter) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
#pragma clang lifetime_elision
void f(void (*)(int *, int *));
)"),
IsOkAndHolds(LifetimesAre({{"f", "(a, b)"}})));
}
TEST_F(
LifetimeAnnotationsTest,
LifetimeElision_FunctionPointerParameterDoesNotCountTowardsInputLifetimes) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
#pragma clang lifetime_elision
int* f(int *, void (*)());
)"),
IsOkAndHolds(LifetimesAre({{"f", "a, () -> a"}})));
}
TEST_F(
LifetimeAnnotationsTest,
LifetimeElision_FunctionReferenceParameterDoesNotCountTowardsInputLifetimes) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
#pragma clang lifetime_elision
int* f(int *, void (&)());
)"),
IsOkAndHolds(LifetimesAre({{"f", "a, () -> 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_PointerAlias) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"_(
#pragma clang lifetime_elision
using Alias = int *;
void f(Alias* alias);
)_"),
// TODO(b/290574080): Do we want elision to "see into" the type
// alias? See bug for details.
IsOkAndHolds(LifetimesAre({{"f", "(a, b)"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeElision_AliasTemplate) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
#pragma clang lifetime_elision
template <class T>
using Alias = T;
void f(Alias<int *>);
)"),
// TODO(b/357835254): Should be "a" rather than "b".
// The issue is that `ValueLifetimes::Create()` creates a lifetime
// for the `int *` twice: Once for its occurrence as the template
// argument (adding it to `template_argument_lifetimes_`), and
// once for its occurrence as the type alias's canonical type
// (adding it to `pointee_lifetimes_`). This violates the
// `ValueLifetimes` invariant that only one of
// `template_argument_lifetimes_` or `pointee_lifetimes_` should
// be populated.
// We still end up with only one lifetime in the result because
// `ValueLifetimes::DebugString()` bails out after the
// `!PointeeType(Type()).isNull()` case and therefore ignores the
// lifetimes in `template_argument_lifetimes_`. Because the
// lifetime in `pointee_lifetimes_` is the second one to be
// produced, we end up with "b" rather than "a".
IsOkAndHolds(LifetimesAre({{"f", "b"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeElision_AliasTemplate2) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
#pragma clang lifetime_elision
template <class T>
using PtrToT = T*;
void f(PtrToT<int *>);
)"),
// TODO(b/357835254): Should be "(a, b)" rather than "(b, c)".
// TODO(b/290574080): Also, there's a question of whether we
// want elision to "see into" the type alias? If not, this should
// simply be "a" rather than "(a, b)".See bug for details.
IsOkAndHolds(LifetimesAre({{"f", "(b, c)"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeElision_NullabilityAliasTemplate) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
#pragma clang lifetime_elision
template <class T>
using Nullable = T _Nullable;
void f(Nullable<int *>);
)"),
// TODO(b/357835254): Should be "a" rather than "b".
IsOkAndHolds(LifetimesAre({{"f", "b"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeElision_FunctionAlias) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"_(
#pragma clang lifetime_elision
using Alias = void(int*);
void f(Alias* alias);
)_"),
// TODO(b/290574080): Do we want elision to "see into" the type
// alias? See bug for details.
IsOkAndHolds(LifetimesAre({{"f", "(a)"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeElision_FailureTooFewInputLifetimes) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
#pragma clang lifetime_elision
int* f();
)"),
IsOkAndHolds(LifetimesAre(
{{"f",
"ERROR(CannotElideOutputLifetimes): Cannot elide output "
"lifetimes for 'f' because it is a non-member function "
"that does not have exactly one input lifetime"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeElision_FailureTooManyInputLifetimes) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
#pragma clang lifetime_elision
int* f(int**);
)"),
IsOkAndHolds(LifetimesAre(
{{"f",
"ERROR(CannotElideOutputLifetimes): Cannot elide output "
"lifetimes for 'f' because it is a non-member function "
"that does not have exactly one input lifetime"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_NoLifetimes) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"_(
[[clang::annotate("lifetimes", "()")]]
void f(int);
)_"),
IsOkAndHolds(LifetimesAre({{"f", "()"}})));
}
TEST_F(LifetimeAnnotationsTest,
LifetimeAnnotation_FunctionPointerHasNoLifetimes) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"_(
void f(void (*)());
)_"),
IsOkAndHolds(LifetimesAre({{"f", "()"}})));
}
TEST_F(LifetimeAnnotationsTest,
LifetimeAnnotation_FunctionPointerReturnTypeHasNoLifetimes) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"_(
int (* f())(float, double);
)_"),
IsOkAndHolds(LifetimesAre({{"f", "-> ((), ())"}})));
}
TEST_F(LifetimeAnnotationsTest,
LifetimeAnnotation_FunctionReferenceHasNoLifetimes) {
EXPECT_THAT(GetNamedLifetimeAnnotations(R"_(
void f(void (&)());
)_"),
IsOkAndHolds(LifetimesAre({{"f", "()"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_BadAttributeArgument) {
EXPECT_THAT(
GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
void f(int* [[clang::annotate_type("lifetime", 1)]]);
)")),
IsOkAndHolds(LifetimesAre(
{{"f",
"ERROR(Other): cannot evaluate argument as a string literal"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_Simple) {
EXPECT_THAT(GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
[[clang::annotate("lifetimes", "a -> a")]]
int* f1(int*);
int* $a f2(int* $a);
)")),
IsOkAndHolds(LifetimesAre({{"f1", "a -> a"}, {"f2", "a -> a"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_SimpleRef) {
EXPECT_THAT(GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
[[clang::annotate("lifetimes", "a -> a")]]
int& f1(int&);
int& $a f2(int& $a);
)")),
IsOkAndHolds(LifetimesAre({{"f1", "a -> a"}, {"f2", "a -> a"}})));
}
TEST_F(LifetimeAnnotationsTest,
LifetimeAnnotation_Invalid_LifetimeOnNonReferenceLikeType) {
EXPECT_THAT(
GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
void f(int $a);
)")),
IsOkAndHolds(LifetimesAre(
{{"f", "ERROR(Other): Type may not be annotated with lifetimes"}})));
}
TEST_F(LifetimeAnnotationsTest,
LifetimeAnnotation_Invalid_LifetimeOnFunctionPointer) {
EXPECT_THAT(
GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
void f(void (* $a)());
)")),
IsOkAndHolds(LifetimesAre(
{{"f", "ERROR(Other): Type may not be annotated with lifetimes"}})));
}
TEST_F(LifetimeAnnotationsTest,
LifetimeAnnotation_Invalid_LifetimeOnFunctionPointerReturnType) {
EXPECT_THAT(
GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
int (* $a f())(float, double);
)")),
IsOkAndHolds(LifetimesAre(
{{"f", "ERROR(Other): Type may not be annotated with lifetimes"}})));
}
TEST_F(LifetimeAnnotationsTest,
LifetimeAnnotation_Invalid_LifetimeOnFunctionReference) {
EXPECT_THAT(
GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
void f(void (& $a)());
)")),
IsOkAndHolds(LifetimesAre(
{{"f", "ERROR(Other): Type may not be annotated with lifetimes"}})));
}
TEST_F(LifetimeAnnotationsTest,
LifetimeAnnotation_Invalid_MultipleLifetimesOnPointer) {
EXPECT_THAT(
GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
void f(int* $2(a, b));
)")),
IsOkAndHolds(LifetimesAre(
{{"f",
"ERROR(Other): Expected a single lifetime but 2 were given"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_Static) {
EXPECT_THAT(GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
[[clang::annotate("lifetimes", "static -> static")]]
int* f1(int*);
int* $static f2(int* $static);
)")),
IsOkAndHolds(LifetimesAre(
{{"f1", "static -> static"}, {"f2", "static -> static"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_PartialElision) {
EXPECT_THAT(GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
#pragma clang lifetime_elision
int* $a f(int* $a, int*, int* $a);
)")),
IsOkAndHolds(LifetimesAre({{"f", "a, b, a -> a"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_MultiplePtr) {
EXPECT_THAT(GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
[[clang::annotate("lifetimes", "(a, b) -> a")]]
int* f1(int**);
int* $a f2(int* $a * $b);
)")),
IsOkAndHolds(LifetimesAre(
{{"f1", "(a, b) -> a"}, {"f2", "(a, b) -> a"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_MultipleArguments) {
EXPECT_THAT(
GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
[[clang::annotate("lifetimes", "a, b -> a")]]
int* f1(int*, int*);
int* $a f2(int* $a, int* $b);
)")),
IsOkAndHolds(LifetimesAre({{"f1", "a, b -> a"}, {"f2", "a, b -> a"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_NoReturn) {
EXPECT_THAT(GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
[[clang::annotate("lifetimes", "a, b")]]
void f1(int*, int*);
void f2(int* $a, int* $b);
)")),
IsOkAndHolds(LifetimesAre({{"f1", "a, b"}, {"f2", "a, b"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_ParamWithoutLifetime) {
EXPECT_THAT(GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
[[clang::annotate("lifetimes", "a, (), a -> a")]]
int* f1(int*, int, int*);
int* $a f2(int* $a, int, int* $a);
)")),
IsOkAndHolds(LifetimesAre(
{{"f1", "a, (), a -> a"}, {"f2", "a, (), a -> a"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_LifetimeParameterizedType) {
// Use a custom delimiter so that the `")` in the `clang::annotate` attribute
// below doesn't prematurely terminate the string.
EXPECT_THAT(GetNamedLifetimeAnnotations(WithLifetimeMacros(R"code(
struct [[clang::annotate("lifetime_params", "a", "b")]] S_param {};
[[clang::annotate("lifetimes", "([a, b]) -> ([a, b])")]]
S_param f1(S_param s);
S_param $2(a, b) f2(S_param $2(a, b) s);
)code")),
IsOkAndHolds(LifetimesAre({{"f1", "([a, b]) -> ([a, b])"},
{"f2", "([a, b]) -> ([a, b])"}})));
}
TEST_F(
LifetimeAnnotationsTest,
LifetimeAnnotation_LifetimeParameterizedType_Invalid_WrongNumberOfLifetimes) {
EXPECT_THAT(GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
struct [[clang::annotate("lifetime_params", "a", "b")]] S_param {};
void f(S_param $3(a, b, c) s);
)")),
IsOkAndHolds(LifetimesAre(
{{"f",
"ERROR(Other): Type has 2 lifetime parameters but 3 "
"lifetime arguments were given"}})));
}
TEST_F(
LifetimeAnnotationsTest,
LifetimeAnnotation_LifetimeParameterizedType_Invalid_MultipleAnnotateAttributes) {
EXPECT_THAT(
GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
struct [[clang::annotate("lifetime_params", "a", "b")]] S_param {};
void f(S_param $a $b s);
)")),
IsOkAndHolds(LifetimesAre(
{{"f",
"ERROR(Other): Only one `[[annotate_type(\"lifetime\", ...)]]` "
"attribute may be placed on a type"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_Template) {
EXPECT_THAT(GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
template <class T> class vector {};
[[clang::annotate("lifetimes", "(a, b) -> a")]]
int* f1(const vector<int *> &);
int* $a f2(const vector<int * $a> & $b);
)")),
IsOkAndHolds(LifetimesAre(
{{"f1", "(a, b) -> a"}, {"f2", "(a, b) -> a"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotationTemplateUniversalReference) {
EXPECT_THAT(GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
#pragma clang lifetime_elision
template <typename T>
struct S {
static void f(T&&) {}
};
void g() { int a; S<int&>::f(a); }
)"),
clang::tooling::FileContentMappings(),
/*skip_templates=*/false),
IsOkAndHolds(LifetimesContain({{"S<int &>::f", "a"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_VariadicTemplate) {
EXPECT_THAT(GetNamedLifetimeAnnotations(WithLifetimeMacros(R"code(
template <class... T> class variadic{};
[[clang::annotate("lifetimes", "(<a, b>, c)")]]
void f1(const variadic<int *, int *> &);
void f2(const variadic<int * $a, int * $b> & $c);
)code")),
IsOkAndHolds(LifetimesAre(
{{"f1", "(<a, b>, c)"}, {"f2", "(<a, b>, c)"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_VariadicTemplateWithCtor) {
EXPECT_THAT(GetNamedLifetimeAnnotations(WithLifetimeMacros(R"code(
template <typename... Args> struct S { S() $a {} };
template <typename T, typename... Args>
struct S<T, Args...> {
S(T t, Args... args) $a {}
};
void target(int* $a a, int* $b b) {
S<int*, int*> s = {a, b};
}
)code")),
IsOkAndHolds(LifetimesAre({{"S::S<Args...>", "a:"},
{"S<type-parameter-0-0, "
"type-parameter-0-1...>::"
"S<type-parameter-0-0, "
"type-parameter-0-1...>",
"a: (), ()"},
{"target", "a, b"}})));
}
TEST_F(LifetimeAnnotationsTest,
LifetimeAnnotation_AliasTemplateForClassTemplate) {
EXPECT_THAT(GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
template<class T> struct trait {
using type = T;
};
template<class T> using alias_template = typename trait<T>::type;
void target(alias_template<int>* $a p) {
}
)")),
IsOkAndHolds(LifetimesAre({{"target", "a"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_Method) {
EXPECT_THAT(
GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
struct S {
[[clang::annotate("lifetimes", "a: -> a")]]
int* f1();
int* $a f2() $a;
};
)")),
IsOkAndHolds(LifetimesAre({{"S::f1", "a: -> a"}, {"S::f2", "a: -> a"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_ConstMethod) {
EXPECT_THAT(GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
struct S {
int* $a f2() const $a;
};
)")),
IsOkAndHolds(LifetimesAre({{"S::f2", "a: -> a"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_MethodWithParam) {
EXPECT_THAT(GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
struct S {
[[clang::annotate("lifetimes", "a: b -> a")]]
int* f1(int*);
int* $a f2(int* $b) $a;
};
)")),
IsOkAndHolds(LifetimesAre(
{{"S::f1", "a: b -> a"}, {"S::f2", "a: b -> a"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_MethodWithLifetimeParams) {
EXPECT_THAT(GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
struct [[clang::annotate("lifetime_params", "x", "y")]] S {
[[clang::annotate("lifetimes", "([x, y], a): -> x")]]
int* f1();
// It's implied that the lifetime parameters of `this` are $x and $y
// because this is a member function on struct with those lifetime
// parameters.
// TODO(mboehme): This doesn't work yet. We need some special handling
// to know that in this context, the type `S` doesn't need lifetimes
// put on it.
// TODO(mboehme): How do we resolve this difference relative to the
// "legacy" lifetime annotations? Does this mean that they should also
// not include the lifetimes x and y?
// int* $x f2() $a;
};
)")),
IsOkAndHolds(LifetimesAre({{"S::f1", "([x, y], a): -> x"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_Invalid_MissingThis) {
EXPECT_THAT(
GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
struct S {
[[clang::annotate("lifetimes", "-> a")]]
int* f();
};
)")),
IsOkAndHolds(LifetimesAre(
{{"S::f",
"ERROR(Other): Invalid lifetime annotation: too few lifetimes"}})));
EXPECT_THAT(GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
struct S {
int* $a f();
};
)")),
IsOkAndHolds(LifetimesAre({{"S::f",
"ERROR(ElisionNotEnabled): Lifetime "
"elision not enabled for 'f'"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_Invalid_ThisOnFreeFunction) {
EXPECT_THAT(GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
[[clang::annotate("lifetimes", "a: a -> a")]]
int* f(int*);
)")),
IsOkAndHolds(LifetimesAre({{"f",
"ERROR(Other): Invalid lifetime "
"annotation: too many lifetimes"}})));
EXPECT_THAT(GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
int* $a f(int* $a) $a;
)")),
IsOkAndHolds(LifetimesAre(
{{"f",
"ERROR(Other): Encountered a `this` lifetime on a "
"function with no `this` parameter"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_Invalid_WrongNumber) {
EXPECT_THAT(
GetNamedLifetimeAnnotations(R"(
[[clang::annotate("lifetimes", "a -> a")]]
int* f(int**);
)"),
IsOkAndHolds(LifetimesAre(
{{"f",
"ERROR(Other): Invalid lifetime annotation: too few lifetimes"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_Callback) {
EXPECT_THAT(GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
[[clang::annotate("lifetimes", "b, (a -> a) -> b")]]
int* f1(int*, int* (*)(int*));
int* $b f2(int* $b, int* $a (*)(int* $a));
)")),
IsOkAndHolds(LifetimesAre(
{{"f1", "b, (a -> a) -> b"}, {"f2", "b, (a -> a) -> b"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_CallbackMultipleParams) {
EXPECT_THAT(GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
[[clang::annotate("lifetimes", "c, ((a, b -> a)) -> c")]]
int* f1(int*, int* (*)(int*, int*));
int* $c f2(int* $c, int* $a (*)(int* $a, int* $b));
)")),
IsOkAndHolds(LifetimesAre({{"f1", "c, (a, b -> a) -> c"},
{"f2", "c, (a, b -> a) -> c"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_CallbackTmplFunc) {
EXPECT_THAT(GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
template <typename Func>
struct function;
[[clang::annotate("lifetimes", "a, (b -> b) -> a")]]
int* f1(int*, function<int*(int*)>);
int* $a f2(int* $a, function<int* $b(int* $b)>);
)")),
IsOkAndHolds(LifetimesAre(
{{"f1", "a, (b -> b) -> a"}, {"f2", "a, (b -> b) -> a"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_MultipleCallbacks) {
EXPECT_THAT(
GetNamedLifetimeAnnotations(WithLifetimeMacros(R"(
[[clang::annotate("lifetimes", "a, (b -> b), (c -> c) -> a")]]
int* f1(int*, int* (*)(int*), int* (*)(int*));
int* $a f2(int* $a, int* $b (*)(int* $b), int* $c (*)(int* $c));
)")),
IsOkAndHolds(LifetimesAre({{"f1", "a, (b -> b), (c -> c) -> a"},
{"f2", "a, (b -> b), (c -> c) -> a"}})));
}
TEST_F(LifetimeAnnotationsTest, LifetimeAnnotation_ReturnFunctionPtr) {
EXPECT_THAT(GetNamedLifetimeAnnotations(WithLifetimeMacros(R"_(
typedef int* (*FP)(int*);
[[clang::annotate("lifetimes", "a -> (b -> b)")]]
FP f(int*);
// TODO(mboehme): Need to support lifetime parameters on type aliases to
// be able to express this in the new syntax.
)_")),
IsOkAndHolds(LifetimesAre({{"f", "a -> (b -> b)"}})));
}
} // namespace
} // namespace lifetimes
} // namespace tidy
} // namespace clang