Move lifetime_annotations.h/.cc./_test.cc to lifetime_annotations/.
PiperOrigin-RevId: 412439810
diff --git a/lifetime_annotations/lifetime_annotations.cc b/lifetime_annotations/lifetime_annotations.cc
new file mode 100644
index 0000000..dc5281a
--- /dev/null
+++ b/lifetime_annotations/lifetime_annotations.cc
@@ -0,0 +1,128 @@
+// 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 <utility>
+
+#include "third_party/absl/strings/str_cat.h"
+#include "third_party/llvm/llvm-project/clang/include/clang/AST/ASTContext.h"
+#include "third_party/llvm/llvm-project/clang/include/clang/AST/DeclCXX.h"
+#include "third_party/llvm/llvm-project/clang/include/clang/Lex/Pragma.h"
+#include "third_party/llvm/llvm-project/clang/include/clang/Lex/Preprocessor.h"
+
+namespace devtools_rust {
+
+static std::optional<FunctionLifetimes> ElidedLifetimes(
+ const clang::FunctionDecl* func) {
+ FunctionLifetimes result;
+
+ // Every input lifetime is assigned a distinct lifetime.
+ result.param_lifetimes.resize(func->getNumParams());
+ llvm::SmallVector<Lifetime> all_input_lifetimes;
+ for (unsigned i = 0; i < func->getNumParams(); ++i) {
+ const clang::ParmVarDecl* param = func->getParamDecl(i);
+
+ result.param_lifetimes[i] =
+ CreateLifetimesForType(param->getType(), Lifetime::CreateVariable);
+ all_input_lifetimes.append(result.param_lifetimes[i]);
+ }
+
+ if (clang::isa<clang::CXXMethodDecl>(func)) {
+ Lifetime this_lifetime = Lifetime::CreateVariable();
+ result.this_lifetimes.push_back(this_lifetime);
+
+ // If we have an implicit `this` parameter, its lifetime is assigned to all
+ // output lifetimes.
+ result.return_lifetimes = CreateLifetimesForType(
+ func->getReturnType(), [this_lifetime]() { return this_lifetime; });
+ return result;
+ }
+
+ // If we have no output lifetimes, there's nothing left to do.
+ if (CreateLifetimesForType(func->getReturnType(), Lifetime::Static).empty()) {
+ return result;
+ }
+
+ // If we have a single input lifetime, its lifetime is assigned to all output
+ // lifetimes.
+ if (all_input_lifetimes.size() == 1) {
+ result.return_lifetimes = CreateLifetimesForType(
+ func->getReturnType(),
+ [&all_input_lifetimes]() { return all_input_lifetimes[0]; });
+ return result;
+ }
+
+ return std::nullopt;
+}
+
+llvm::Expected<FunctionLifetimes> GetLifetimeAnnotations(
+ const clang::FunctionDecl* func, const LifetimeAnnotationContext& context) {
+ // TODO(mboehme):
+ // - Add support for retrieving actual lifetime annotations (not just
+ // lifetimes implied by elision).
+ // - If we have multiple declarations of a function, make sure they are all
+ // annotated with the same lifetimes.
+
+ // For the time being, we only return elided lifetimes.
+ std::optional<FunctionLifetimes> elided_lifetimes = ElidedLifetimes(func);
+
+ if (!elided_lifetimes.has_value()) {
+ return llvm::createStringError(
+ llvm::inconvertibleErrorCode(),
+ absl::StrCat("Cannot determine output lifetimes for '",
+ func->getNameAsString(),
+ "' because it does not have exactly one input lifetime"));
+ }
+
+ // If the function has any elided lifetimes, we need to check if lifetime
+ // elision is enabled.
+ if (elided_lifetimes->ContainsLifetimes()) {
+ clang::SourceManager& source_manager =
+ func->getASTContext().getSourceManager();
+ clang::FileID file_id =
+ source_manager.getFileID(func->getSourceRange().getBegin());
+ if (!context.lifetime_elision_files.contains(file_id)) {
+ return llvm::createStringError(
+ llvm::inconvertibleErrorCode(),
+ absl::StrCat("Lifetime elision not enabled for '",
+ func->getNameAsString(), "'"));
+ }
+ }
+
+ return *std::move(elided_lifetimes);
+}
+
+namespace {
+
+class LifetimeElisionPragmaHandler : public clang::PragmaHandler {
+ public:
+ explicit LifetimeElisionPragmaHandler(
+ std::shared_ptr<LifetimeAnnotationContext> context)
+ : clang::PragmaHandler("lifetime_elision"), context_(context) {}
+
+ void HandlePragma(clang::Preprocessor& preprocessor,
+ clang::PragmaIntroducer introducer,
+ clang::Token&) override {
+ clang::SourceManager& source_manager = preprocessor.getSourceManager();
+ clang::FileID file_id = source_manager.getFileID(introducer.Loc);
+ context_->lifetime_elision_files.insert(file_id);
+ }
+
+ private:
+ std::shared_ptr<LifetimeAnnotationContext> context_;
+};
+
+} // namespace
+
+void AddLifetimeAnnotationHandlers(
+ clang::CompilerInstance& compiler,
+ std::shared_ptr<LifetimeAnnotationContext> context) {
+ // Preprocessor takes ownership of the handler.
+ compiler.getPreprocessor().AddPragmaHandler(
+ new LifetimeElisionPragmaHandler(context));
+}
+
+} // namespace devtools_rust
diff --git a/lifetime_annotations/lifetime_annotations.h b/lifetime_annotations/lifetime_annotations.h
new file mode 100644
index 0000000..8e34228
--- /dev/null
+++ b/lifetime_annotations/lifetime_annotations.h
@@ -0,0 +1,44 @@
+// 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
+
+#ifndef CRUBIT_LIFETIME_ANNOTATIONS_LIFETIME_ANNOTATIONS_H_
+#define CRUBIT_LIFETIME_ANNOTATIONS_LIFETIME_ANNOTATIONS_H_
+
+#include <memory>
+
+#include "lifetime_annotations/function_lifetimes.h"
+#include "third_party/llvm/llvm-project/clang/include/clang/AST/Decl.h"
+#include "third_party/llvm/llvm-project/clang/include/clang/Frontend/CompilerInstance.h"
+#include "third_party/llvm/llvm-project/llvm/include/llvm/Support/Error.h"
+
+namespace devtools_rust {
+
+// Context that is required to obtain lifetime annotations for a function.
+struct LifetimeAnnotationContext {
+ // Files in which the `lifetime_elision` pragma was specified.
+ llvm::DenseSet<clang::FileID> lifetime_elision_files;
+};
+
+// Returns the lifetimes annotated on `func`.
+// If the file containing the function definition specifies the
+// `lifetime_elision` pragma, lifetime elision rules are used to determine
+// any unannotated lifetimes.
+// Returns an error if the function contains unannotated lifetimes that could
+// not be determined through lifetime elision, either because the
+// `lifetime_elision`pragma was not specified or because the lifetime elision
+// rules were not applicable.
+// TODO(mboehme): This function has only been partially implemented.
+llvm::Expected<FunctionLifetimes> GetLifetimeAnnotations(
+ const clang::FunctionDecl* func, const LifetimeAnnotationContext& context);
+
+// Adds handlers to `compiler` to populate `context`.
+// To be able to use GetLifetimeAnnotations(), call this function to add the
+// necessary handlers before compiling any code.
+void AddLifetimeAnnotationHandlers(
+ clang::CompilerInstance& compiler,
+ std::shared_ptr<LifetimeAnnotationContext> context);
+
+} // namespace devtools_rust
+
+#endif // CRUBIT_LIFETIME_ANNOTATIONS_LIFETIME_ANNOTATIONS_H_
diff --git a/lifetime_annotations/lifetime_annotations_test.cc b/lifetime_annotations/lifetime_annotations_test.cc
new file mode 100644
index 0000000..8b60e37
--- /dev/null
+++ b/lifetime_annotations/lifetime_annotations_test.cc
@@ -0,0 +1,155 @@
+// 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 "devtools/cymbal/data_flow/testing_support.h"
+#include "lifetime_analysis/test/named_func_lifetimes.h"
+#include "lifetime_analysis/test/run_on_code.h"
+#include "testing/base/public/gunit.h"
+#include "third_party/llvm/llvm-project/clang/include/clang/ASTMatchers/ASTMatchFinder.h"
+#include "third_party/llvm/llvm-project/clang/include/clang/ASTMatchers/ASTMatchers.h"
+
+namespace devtools_rust {
+namespace {
+
+using testing::StartsWith;
+using testing::status::IsOkAndHolds;
+using testing::status::StatusIs;
+
+std::string QualifiedName(const clang::FunctionDecl* func) {
+ std::string str;
+ llvm::raw_string_ostream ostream(str);
+ func->printQualifiedName(ostream);
+ 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;
+ runOnCodeWithLifetimeHandlers(
+ code,
+ [&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")) {
+ llvm::Expected<FunctionLifetimes> func_lifetimes =
+ GetLifetimeAnnotations(func, lifetime_context);
+
+ if (!func_lifetimes) {
+ result = absl::UnknownError(
+ llvm::toString(func_lifetimes.takeError()));
+ return;
+ }
+ named_func_lifetimes.Add(QualifiedName(func),
+ NameLifetimes(*func_lifetimes));
+ }
+ }
+
+ result = std::move(named_func_lifetimes);
+ },
+ {}, file_contents);
+
+ return result;
+ }
+};
+
+TEST_F(LifetimeAnnotationsTest, NoLifetimes) {
+ EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
+ int f(int);
+ )"),
+ IsOkAndHolds(LifetimesAre({{"f", "()"}})));
+}
+
+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_NoAnnotationsElisionPragmaInWrongFile) {
+ EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
+ #pragma 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 lifetime_elision
+ int** f(int*);
+ )"),
+ IsOkAndHolds(LifetimesAre({{"f", "a -> (a, a)"}})));
+}
+
+TEST_F(LifetimeAnnotationsTest, LifetimeElision_NoOutputLifetimes) {
+ EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
+ #pragma lifetime_elision
+ void f(int**, int *);
+ )"),
+ IsOkAndHolds(LifetimesAre({{"f", "(a, b), c"}})));
+}
+
+TEST_F(LifetimeAnnotationsTest, LifetimeElision_Templates) {
+ EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
+ #pragma 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_Method) {
+ EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
+ #pragma lifetime_elision
+ struct S {
+ int** method(int *, int *);
+ };
+ )"),
+ IsOkAndHolds(LifetimesAre({{"S::method", "c: a, b -> (c, c)"}})));
+}
+
+TEST_F(LifetimeAnnotationsTest, LifetimeElision_FailureTooFewInputLifetimes) {
+ EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
+ #pragma lifetime_elision
+ int* f();
+ )"),
+ StatusIs(absl::StatusCode::kUnknown,
+ StartsWith("Cannot determine output lifetimes")));
+}
+
+TEST_F(LifetimeAnnotationsTest, LifetimeElision_FailureTooManyInputLifetimes) {
+ EXPECT_THAT(GetNamedLifetimeAnnotations(R"(
+ #pragma lifetime_elision
+ int* f(int**);
+ )"),
+ StatusIs(absl::StatusCode::kUnknown,
+ StartsWith("Cannot determine output lifetimes")));
+}
+
+} // namespace
+} // namespace devtools_rust