| // 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_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 |