blob: cf5dd6fd4ccb19bc49799712904492fe4ab29c97 [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 <functional>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include "third_party/absl/strings/str_cat.h"
#include "lifetime_annotations/function_lifetimes.h"
#include "lifetime_annotations/pointee_type.h"
#include "lifetime_annotations/type_lifetimes.h"
#include "third_party/llvm/llvm-project/clang/include/clang/AST/APValue.h"
#include "third_party/llvm/llvm-project/clang/include/clang/AST/ASTContext.h"
#include "third_party/llvm/llvm-project/clang/include/clang/AST/Attr.h"
#include "third_party/llvm/llvm-project/clang/include/clang/AST/Attrs.inc"
#include "third_party/llvm/llvm-project/clang/include/clang/AST/DeclCXX.h"
#include "third_party/llvm/llvm-project/clang/include/clang/AST/PrettyPrinter.h"
#include "third_party/llvm/llvm-project/clang/include/clang/Basic/LangOptions.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"
#include "third_party/llvm/llvm-project/llvm/include/llvm/ADT/SmallVector.h"
#include "third_party/llvm/llvm-project/llvm/include/llvm/ADT/StringRef.h"
namespace devtools_rust {
namespace {
llvm::Expected<llvm::SmallVector<Lifetime>> GetAnnotatedOrElidedLifetimes(
llvm::ArrayRef<const clang::Attr*> /*attrs*/, size_t num_expected,
LifetimeSymbolTable& /*symbol_table*/,
const std::function<llvm::Expected<Lifetime>()>& elided_lifetime_factory,
const clang::ASTContext& /*ast_context*/) {
assert(num_expected > 0);
llvm::SmallVector<Lifetime> lifetimes;
lifetimes.reserve(num_expected);
// TODO(mboehme): Extract lifetime annotations from `attrs` if present.
// No lifetime annotations: Use elided lifetimes.
for (size_t i = 0; i < num_expected; ++i) {
llvm::Expected<Lifetime> maybe_lifetime = elided_lifetime_factory();
if (maybe_lifetime) {
lifetimes.push_back(maybe_lifetime.get());
} else {
return maybe_lifetime.takeError();
}
}
return lifetimes;
}
llvm::Error AddLifetimeAnnotationsForType(
clang::QualType type, LifetimeSymbolTable& symbol_table,
const std::function<llvm::Expected<Lifetime>()>& elided_lifetime_factory,
const clang::ASTContext& ast_context, TypeLifetimes& lifetimes) {
assert(!type.isNull());
assert(elided_lifetime_factory);
llvm::SmallVector<const clang::Attr*> attrs;
size_t num_lifetime_params = GetLifetimeParameters(type).size();
if (num_lifetime_params > 0) {
llvm::Expected<llvm::SmallVector<Lifetime>> maybe_lifetime_args =
GetAnnotatedOrElidedLifetimes(attrs, num_lifetime_params, symbol_table,
elided_lifetime_factory, ast_context);
if (maybe_lifetime_args) {
lifetimes.append(maybe_lifetime_args.get());
} else {
return maybe_lifetime_args.takeError();
}
}
const llvm::ArrayRef<clang::TemplateArgument> template_args =
GetTemplateArgs(type);
if (!template_args.empty()) {
for (const clang::TemplateArgument& template_arg : template_args) {
if (llvm::Error err = AddLifetimeAnnotationsForType(
template_arg.getAsType(), symbol_table, elided_lifetime_factory,
ast_context, lifetimes)) {
return err;
}
}
return llvm::Error::success();
}
if (clang::QualType pointee_type = PointeeType(type);
!pointee_type.isNull()) {
if (llvm::Error err = AddLifetimeAnnotationsForType(
pointee_type, symbol_table, elided_lifetime_factory, ast_context,
lifetimes)) {
return err;
}
llvm::Expected<llvm::SmallVector<Lifetime>> maybe_pointee_lifetime =
GetAnnotatedOrElidedLifetimes(attrs, 1, symbol_table,
elided_lifetime_factory, ast_context);
if (maybe_pointee_lifetime) {
lifetimes.append(maybe_pointee_lifetime.get());
} else {
return maybe_pointee_lifetime.takeError();
}
}
return llvm::Error::success();
}
llvm::Expected<llvm::SmallVector<Lifetime>> GetThisLifetimes(
const clang::CXXMethodDecl* method, LifetimeSymbolTable& symbol_table,
const std::function<llvm::Expected<Lifetime>()>& elided_lifetime_factory) {
llvm::ArrayRef<const clang::Attr*> attrs;
if (method->hasAttrs()) {
attrs = method->getAttrs();
}
return GetAnnotatedOrElidedLifetimes(
attrs, 1, symbol_table, elided_lifetime_factory, method->getASTContext());
}
// Parse a "(a, b): (a, b), (), a -> b"-style annotation into a
// FunctionLifetimes.
// TODO(veluca): this is a temporary solution.
llvm::Expected<FunctionLifetimes> ParseLifetimeAnnotations(
const clang::FunctionDecl* func, LifetimeSymbolTable& symbol_table,
const clang::AnnotateAttr* attr) {
auto error = [func](absl::string_view detail = absl::string_view()) {
std::string msg = absl::StrCat("Invalid lifetime annotation for function ",
func->getNameAsString());
if (!detail.empty()) {
absl::StrAppend(&msg, ": ", detail);
}
return llvm::createStringError(llvm::inconvertibleErrorCode(), msg);
};
if (attr->args_size() != 1) {
return error("`lifetimes` attribute must have exactly one argument");
}
// The lexer requires a null character at the end of the string, so copy it to
// a std::string to guarantee this.
llvm::StringRef lifetimes;
if (llvm::Error err =
EvaluateAsStringLiteral(*attr->args_begin(), func->getASTContext())
.moveInto(lifetimes)) {
return error(toString(std::move(err)));
}
std::string lifetimes_str = lifetimes.str();
clang::LangOptions lang_opts;
clang::Lexer lexer(attr->getLoc(), lang_opts, lifetimes_str.data(),
lifetimes_str.data(),
lifetimes_str.data() + lifetimes_str.size());
const char* end = lifetimes_str.data() + lifetimes_str.size();
auto tok = [&lexer, &lifetimes_str, end, attr]() -> llvm::StringRef {
clang::Token token;
if (lexer.getBufferLocation() != end) {
lexer.LexFromRawLexer(token);
return llvm::StringRef(lifetimes_str.data() +
token.getLocation().getRawEncoding() -
attr->getLoc().getRawEncoding(),
token.getLength());
}
return "";
};
llvm::SmallVector<TypeLifetimes> fn_lifetimes;
bool has_this_lifetimes = false;
bool has_return_lifetimes = false;
for (llvm::StringRef token; !(token = tok()).empty();) {
if (token == ",") {
continue;
}
if (has_return_lifetimes) {
return error();
}
if (token == ":") {
if (has_this_lifetimes || fn_lifetimes.size() != 1) {
return error();
}
has_this_lifetimes = true;
continue;
}
// Skip the -> and parse return lifetimes. No more lifetimes should be
// parsed afterwards.
if (token == "->") {
has_return_lifetimes = true;
token = tok();
}
fn_lifetimes.emplace_back();
if (token == "(") {
for (; (token = tok()) != ")" && !token.empty();) {
if (token == ",") continue;
fn_lifetimes.back().push_back(
symbol_table.LookupNameAndMaybeDeclare(token));
}
} else {
fn_lifetimes.back().push_back(
symbol_table.LookupNameAndMaybeDeclare(token));
}
}
FunctionLifetimes function_lifetimes;
size_t param_start = 0;
if (has_this_lifetimes) {
function_lifetimes.this_lifetimes = fn_lifetimes[0];
param_start = 1;
}
size_t param_end = fn_lifetimes.size();
if (has_return_lifetimes) {
function_lifetimes.return_lifetimes = fn_lifetimes.back();
param_end -= 1;
}
function_lifetimes.param_lifetimes.assign(fn_lifetimes.begin() + param_start,
fn_lifetimes.begin() + param_end);
if (function_lifetimes.Validate(func)) {
return function_lifetimes;
}
return error();
}
llvm::Expected<FunctionLifetimes> GetLifetimeAnnotationsInternal(
const clang::FunctionDecl* func, LifetimeSymbolTable& symbol_table,
bool elision_enabled) {
FunctionLifetimes result;
const clang::AnnotateAttr* lifetime_annotation = nullptr;
for (const clang::Attr* attr : func->attrs()) {
if (auto annotate = clang::dyn_cast<clang::AnnotateAttr>(attr)) {
if (annotate->getAnnotation() == "lifetimes") {
if (lifetime_annotation != nullptr) {
return llvm::createStringError(
llvm::inconvertibleErrorCode(),
absl::StrCat("Can't extract lifetimes as '",
func->getNameAsString(),
"' has multiple lifetime annotations"));
}
lifetime_annotation = annotate;
}
}
}
if (lifetime_annotation) {
return ParseLifetimeAnnotations(func, symbol_table, lifetime_annotation);
}
std::function<llvm::Expected<Lifetime>()> elided_lifetime_factory;
if (elision_enabled) {
elided_lifetime_factory = [&symbol_table]() -> llvm::Expected<Lifetime> {
Lifetime lifetime = Lifetime::CreateVariable();
symbol_table.LookupLifetimeAndMaybeDeclare(lifetime);
return lifetime;
};
} else {
elided_lifetime_factory = [func]() -> llvm::Expected<Lifetime> {
return llvm::createStringError(
llvm::inconvertibleErrorCode(),
absl::StrCat("Lifetime elision not enabled for '",
func->getNameAsString(), "'"));
};
}
if (const auto* method = clang::dyn_cast<clang::CXXMethodDecl>(func)) {
if (llvm::Error err =
GetThisLifetimes(method, symbol_table, elided_lifetime_factory)
.moveInto(result.this_lifetimes)) {
return std::move(err);
}
}
llvm::SmallVector<Lifetime> all_input_lifetimes;
result.param_lifetimes.resize(func->getNumParams());
for (unsigned i = 0; i < func->getNumParams(); i++) {
const clang::ParmVarDecl* param = func->getParamDecl(i);
if (llvm::Error err = AddLifetimeAnnotationsForType(
param->getType(), symbol_table, elided_lifetime_factory,
func->getASTContext(), result.param_lifetimes[i])) {
return std::move(err);
}
all_input_lifetimes.append(result.param_lifetimes[i]);
}
std::function<llvm::Expected<Lifetime>()> elided_return_lifetime_factory;
if (!elision_enabled) {
elided_return_lifetime_factory = [func]() -> llvm::Expected<Lifetime> {
return llvm::createStringError(
llvm::inconvertibleErrorCode(),
absl::StrCat("Lifetime elision not enabled for '",
func->getNameAsString(), "'"));
};
} else if (!result.this_lifetimes.empty()) {
// If we have an implicit `this` parameter, its lifetime is assigned to all
// output lifetimes.
elided_return_lifetime_factory =
[this_lifetime =
result.this_lifetimes.back()]() -> llvm::Expected<Lifetime> {
return this_lifetime;
};
} else if (all_input_lifetimes.size() == 1) {
// If we have a single input lifetime, its lifetime is assigned to all
// output lifetimes.
// Otherwise, elided_return_lifetime_factory remains empty, and we get an
// error if there are any elided lifetimes in the return type.
elided_return_lifetime_factory =
[return_lifetime =
all_input_lifetimes[0]]() -> llvm::Expected<Lifetime> {
return return_lifetime;
};
} else {
elided_return_lifetime_factory = [func]() -> llvm::Expected<Lifetime> {
return llvm::createStringError(
llvm::inconvertibleErrorCode(),
absl::StrCat(
"Cannot elide output lifetimes for '", func->getNameAsString(),
"' because it is a non-member function that does not have "
"exactly one input lifetime"));
};
}
if (llvm::Error err = AddLifetimeAnnotationsForType(
func->getReturnType(), symbol_table, elided_return_lifetime_factory,
func->getASTContext(), result.return_lifetimes)) {
return std::move(err);
}
return result;
}
} // namespace
llvm::Expected<FunctionLifetimes> GetLifetimeAnnotations(
const clang::FunctionDecl* func, const LifetimeAnnotationContext& context,
LifetimeSymbolTable* symbol_table) {
// TODO(mboehme): if we have multiple declarations of a function, make sure
// they are all annotated with the same lifetimes.
// TODO(veluca): the syntax we are using for lifetime annotations here is just
// a placeholder. Adapt this to the actual syntax once the clang-side support
// is there.
clang::SourceManager& source_manager =
func->getASTContext().getSourceManager();
clang::FileID file_id =
source_manager.getFileID(func->getSourceRange().getBegin());
bool elision_enabled = context.lifetime_elision_files.contains(file_id);
LifetimeSymbolTable throw_away_symbol_table;
if (!symbol_table) {
symbol_table = &throw_away_symbol_table;
}
return GetLifetimeAnnotationsInternal(func, *symbol_table, elision_enabled);
}
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::Preprocessor& preprocessor,
std::shared_ptr<LifetimeAnnotationContext> context) {
// Preprocessor takes ownership of the handler.
preprocessor.AddPragmaHandler("clang",
new LifetimeElisionPragmaHandler(context));
}
llvm::SmallVector<clang::TypeLoc> GetTemplateArgs(clang::TypeLoc type_loc) {
llvm::SmallVector<clang::TypeLoc> args;
if (auto template_specialization_type_loc =
type_loc.getAs<clang::TemplateSpecializationTypeLoc>()) {
for (unsigned i = 0; i < template_specialization_type_loc.getNumArgs();
++i) {
args.push_back(template_specialization_type_loc.getArgLoc(i)
.getTypeSourceInfo()
->getTypeLoc());
}
} else if (auto dependent_template_specialization_type_loc =
type_loc
.getAs<clang::DependentTemplateSpecializationTypeLoc>()) {
// TODO(mboehme): Where does this occur exactly? Do we need to be handling
// it?
// AFAICT, this happens if we're looking at a dependent template name
// (https://en.cppreference.com/w/cpp/language/dependent_name), which
// probably means that this can only happen in template definitions (as
// opposed to template instantiations), and we aren't analyzing those for
// now. At the least, I haven't been able to trigger this case from a test.
// Putting an assertion in here so that we notice this case if it does come
// up.
assert(false);
for (unsigned i = 0;
i < dependent_template_specialization_type_loc.getNumArgs(); ++i) {
args.push_back(dependent_template_specialization_type_loc.getArgLoc(i)
.getTypeSourceInfo()
->getTypeLoc());
}
}
return args;
}
} // namespace devtools_rust