blob: e942ff5ec773ab1c7cecc527962df082a4d7fb62 [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 <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(
"clang", new LifetimeElisionPragmaHandler(context));
}
} // namespace devtools_rust