Googler | b0019ca | 2021-11-26 14:20:10 +0000 | [diff] [blame^] | 1 | // Part of the Crubit project, under the Apache License v2.0 with LLVM |
| 2 | // Exceptions. See /LICENSE for license information. |
| 3 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 4 | |
| 5 | #include "lifetime_annotations/lifetime_annotations.h" |
| 6 | |
| 7 | #include <string> |
| 8 | #include <utility> |
| 9 | |
| 10 | #include "devtools/cymbal/data_flow/testing_support.h" |
| 11 | #include "lifetime_analysis/test/named_func_lifetimes.h" |
| 12 | #include "lifetime_analysis/test/run_on_code.h" |
| 13 | #include "testing/base/public/gunit.h" |
| 14 | #include "third_party/llvm/llvm-project/clang/include/clang/ASTMatchers/ASTMatchFinder.h" |
| 15 | #include "third_party/llvm/llvm-project/clang/include/clang/ASTMatchers/ASTMatchers.h" |
| 16 | |
| 17 | namespace devtools_rust { |
| 18 | namespace { |
| 19 | |
| 20 | using testing::StartsWith; |
| 21 | using testing::status::IsOkAndHolds; |
| 22 | using testing::status::StatusIs; |
| 23 | |
| 24 | std::string QualifiedName(const clang::FunctionDecl* func) { |
| 25 | std::string str; |
| 26 | llvm::raw_string_ostream ostream(str); |
| 27 | func->printQualifiedName(ostream); |
| 28 | ostream.flush(); |
| 29 | return str; |
| 30 | } |
| 31 | |
| 32 | class LifetimeAnnotationsTest : public testing::Test { |
| 33 | protected: |
| 34 | absl::StatusOr<NamedFuncLifetimes> GetNamedLifetimeAnnotations( |
| 35 | absl::string_view code, |
| 36 | const clang::tooling::FileContentMappings& file_contents = |
| 37 | clang::tooling::FileContentMappings()) { |
| 38 | absl::StatusOr<NamedFuncLifetimes> result; |
| 39 | runOnCodeWithLifetimeHandlers( |
| 40 | code, |
| 41 | [&result](clang::ASTContext& ast_context, |
| 42 | const LifetimeAnnotationContext& lifetime_context) { |
| 43 | using clang::ast_matchers::findAll; |
| 44 | using clang::ast_matchers::functionDecl; |
| 45 | using clang::ast_matchers::match; |
| 46 | |
| 47 | NamedFuncLifetimes named_func_lifetimes; |
| 48 | for (const auto& node : |
| 49 | match(findAll(functionDecl().bind("func")), ast_context)) { |
| 50 | if (const auto* func = |
| 51 | node.getNodeAs<clang::FunctionDecl>("func")) { |
| 52 | llvm::Expected<FunctionLifetimes> func_lifetimes = |
| 53 | GetLifetimeAnnotations(func, lifetime_context); |
| 54 | |
| 55 | if (!func_lifetimes) { |
| 56 | result = absl::UnknownError( |
| 57 | llvm::toString(func_lifetimes.takeError())); |
| 58 | return; |
| 59 | } |
| 60 | named_func_lifetimes.Add(QualifiedName(func), |
| 61 | NameLifetimes(*func_lifetimes)); |
| 62 | } |
| 63 | } |
| 64 | |
| 65 | result = std::move(named_func_lifetimes); |
| 66 | }, |
| 67 | {}, file_contents); |
| 68 | |
| 69 | return result; |
| 70 | } |
| 71 | }; |
| 72 | |
| 73 | TEST_F(LifetimeAnnotationsTest, NoLifetimes) { |
| 74 | EXPECT_THAT(GetNamedLifetimeAnnotations(R"( |
| 75 | int f(int); |
| 76 | )"), |
| 77 | IsOkAndHolds(LifetimesAre({{"f", "()"}}))); |
| 78 | } |
| 79 | |
| 80 | TEST_F(LifetimeAnnotationsTest, Failure_NoAnnotationsNoLifetimeElision) { |
| 81 | EXPECT_THAT(GetNamedLifetimeAnnotations(R"( |
| 82 | int** f(int*); |
| 83 | )"), |
| 84 | StatusIs(absl::StatusCode::kUnknown, |
| 85 | StartsWith("Lifetime elision not enabled"))); |
| 86 | } |
| 87 | |
| 88 | TEST_F(LifetimeAnnotationsTest, Failure_NoAnnotationsElisionPragmaInWrongFile) { |
| 89 | EXPECT_THAT(GetNamedLifetimeAnnotations(R"( |
| 90 | #pragma lifetime_elision |
| 91 | #include "header.h" |
| 92 | )", |
| 93 | {std::make_pair("header.h", R"( |
| 94 | int** f(int*); |
| 95 | )")}), |
| 96 | StatusIs(absl::StatusCode::kUnknown, |
| 97 | StartsWith("Lifetime elision not enabled"))); |
| 98 | } |
| 99 | |
| 100 | TEST_F(LifetimeAnnotationsTest, LifetimeElision_OneInputLifetime) { |
| 101 | EXPECT_THAT(GetNamedLifetimeAnnotations(R"( |
| 102 | #pragma lifetime_elision |
| 103 | int** f(int*); |
| 104 | )"), |
| 105 | IsOkAndHolds(LifetimesAre({{"f", "a -> (a, a)"}}))); |
| 106 | } |
| 107 | |
| 108 | TEST_F(LifetimeAnnotationsTest, LifetimeElision_NoOutputLifetimes) { |
| 109 | EXPECT_THAT(GetNamedLifetimeAnnotations(R"( |
| 110 | #pragma lifetime_elision |
| 111 | void f(int**, int *); |
| 112 | )"), |
| 113 | IsOkAndHolds(LifetimesAre({{"f", "(a, b), c"}}))); |
| 114 | } |
| 115 | |
| 116 | TEST_F(LifetimeAnnotationsTest, LifetimeElision_Templates) { |
| 117 | EXPECT_THAT(GetNamedLifetimeAnnotations(R"( |
| 118 | #pragma lifetime_elision |
| 119 | template <class T> class vector {}; |
| 120 | int* f(vector<int *>); |
| 121 | vector<int*> g(int *); |
| 122 | )"), |
| 123 | IsOkAndHolds(LifetimesAre({{"f", "a -> a"}, {"g", "a -> a"}}))); |
| 124 | } |
| 125 | |
| 126 | TEST_F(LifetimeAnnotationsTest, LifetimeElision_Method) { |
| 127 | EXPECT_THAT(GetNamedLifetimeAnnotations(R"( |
| 128 | #pragma lifetime_elision |
| 129 | struct S { |
| 130 | int** method(int *, int *); |
| 131 | }; |
| 132 | )"), |
| 133 | IsOkAndHolds(LifetimesAre({{"S::method", "c: a, b -> (c, c)"}}))); |
| 134 | } |
| 135 | |
| 136 | TEST_F(LifetimeAnnotationsTest, LifetimeElision_FailureTooFewInputLifetimes) { |
| 137 | EXPECT_THAT(GetNamedLifetimeAnnotations(R"( |
| 138 | #pragma lifetime_elision |
| 139 | int* f(); |
| 140 | )"), |
| 141 | StatusIs(absl::StatusCode::kUnknown, |
| 142 | StartsWith("Cannot determine output lifetimes"))); |
| 143 | } |
| 144 | |
| 145 | TEST_F(LifetimeAnnotationsTest, LifetimeElision_FailureTooManyInputLifetimes) { |
| 146 | EXPECT_THAT(GetNamedLifetimeAnnotations(R"( |
| 147 | #pragma lifetime_elision |
| 148 | int* f(int**); |
| 149 | )"), |
| 150 | StatusIs(absl::StatusCode::kUnknown, |
| 151 | StartsWith("Cannot determine output lifetimes"))); |
| 152 | } |
| 153 | |
| 154 | } // namespace |
| 155 | } // namespace devtools_rust |