blob: abddb1ffa705988ce0c3d6be9ef3c4e68f5204a9 [file] [log] [blame] [edit]
// 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 "rs_bindings_from_cc/importers/function.h"
#include <algorithm>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <variant>
#include <vector>
#include "absl/container/btree_set.h"
#include "absl/log/check.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "common/annotation_reader.h"
#include "lifetime_annotations/lifetime.h"
#include "lifetime_annotations/lifetime_annotations.h"
#include "lifetime_annotations/lifetime_error.h"
#include "lifetime_annotations/lifetime_symbol_table.h"
#include "lifetime_annotations/type_lifetimes.h"
#include "rs_bindings_from_cc/ast_util.h"
#include "rs_bindings_from_cc/decl_importer.h"
#include "rs_bindings_from_cc/ir.h"
#include "rs_bindings_from_cc/recording_diagnostic_consumer.h"
#include "clang/AST/Attr.h"
#include "clang/AST/Attrs.inc"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/Type.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticIDs.h"
#include "clang/Basic/LLVM.h"
#include "clang/Basic/Specifiers.h"
#include "clang/Sema/Scope.h"
#include "clang/Sema/Sema.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Error.h"
namespace crubit {
namespace {
// A collection of `FormattedError` values that enforces uniqueness.
struct Errors {
// `btree_set` is used to ensure stable ordering.
absl::btree_set<FormattedError> error_set;
void Add(FormattedError error) {
auto result = error_set.insert(std::move(error));
CHECK(result.second) << "Duplicated error message";
}
void AddStatus(absl::Status status) {
Add(FormattedError::FromStatus(status));
}
};
SafetyAnnotation GetCrubitSafetyAnnotation(const clang::Decl& decl,
Errors& errors) {
absl::StatusOr<std::optional<AnnotateArgs>> maybe_args =
GetAnnotateAttrArgs(decl, "crubit_override_unsafe");
if (!maybe_args.ok()) {
errors.AddStatus(maybe_args.status());
return SafetyAnnotation::kUnannotated;
}
if (!maybe_args->has_value()) {
return SafetyAnnotation::kUnannotated;
}
const AnnotateArgs& args = **maybe_args;
if (args.size() != 1) {
errors.AddStatus(absl::InvalidArgumentError(
"`crubit_override_unsafe` annotation must have exactly one argument"));
return SafetyAnnotation::kUnannotated;
}
absl::StatusOr<bool> is_unsafe =
GetExprAsBool(*args[0], decl.getASTContext());
if (!is_unsafe.ok()) {
errors.AddStatus(is_unsafe.status());
return SafetyAnnotation::kUnannotated;
}
if (*is_unsafe) {
return SafetyAnnotation::kUnsafe;
} else {
return SafetyAnnotation::kDisableUnsafe;
}
}
SafetyAnnotation GetSafetyAnnotation(const clang::Decl& decl, Errors& errors) {
SafetyAnnotation crubit = GetCrubitSafetyAnnotation(decl, errors);
if (!decl.specific_attrs<clang::UnsafeBufferUsageAttr>().empty()) {
if (crubit == SafetyAnnotation::kUnannotated ||
crubit == SafetyAnnotation::kUnsafe) {
return SafetyAnnotation::kUnsafe;
} else {
errors.Add(FormattedError::Static(
"Function is annotated with both `[[clang::unsafe_buffer_usage]]` "
"and `CRUBIT_UNSAFE`"));
}
}
return crubit;
}
// Applies the ref qualifier to the `this` pointer.
//
// Assume `f` is a method in `void f() && $a`. Converting the `this` parameter
// type of `f` will result in a pointer type, even though the method is rvalue
// ref qualified and has a lifetime. This function will update the `this`
// parameter type to be an rvalue reference instead.
void ApplyRefQualifierToThisPointer(
CcType& this_param_type, clang::RefQualifierKind ref_qualifier_kind) {
auto* pointer = std::get_if<CcType::PointerType>(&this_param_type.variant);
// The CcType of `this` should always be a pointer.
CHECK(pointer != nullptr);
// Now we go back and fix the `this` parameter type to be a reference
// if it was a rvalue ref qualified and had a lifetime.
if (pointer->lifetime.has_value() &&
ref_qualifier_kind == clang::RefQualifierKind::RQ_RValue) {
// It was just a non null pointer, but because of the rvalue ref
// qualification, it should be an rvalue reference.
CHECK(pointer->kind == PointerTypeKind::kNonNull);
pointer->kind = PointerTypeKind::kRValueRef;
}
}
// An RAII guard that sets a fake TU scope for the duration of its lifetime
// and restores the previous TU scope when it goes out of scope.
class FakeTUScope {
public:
// Sets `ctx`'s `Sema`'s `TUScope` to a fake scope that points to the
// translation unit declaration.
explicit FakeTUScope(ImportContext& ctx)
: ctx_(ctx),
scope_(std::make_unique<clang::Scope>(nullptr, clang::Scope::DeclScope,
ctx_.sema_.getDiagnostics())),
old_tu_scope_(ctx_.sema_.TUScope) {
ctx_.sema_.TUScope = scope_.get();
ctx_.sema_.TUScope->setEntity(ctx_.ctx_.getTranslationUnitDecl());
}
~FakeTUScope() {
ctx_.sema_.TUScope->setEntity(nullptr);
ctx_.sema_.TUScope = old_tu_scope_;
}
private:
ImportContext& ctx_;
std::unique_ptr<clang::Scope> scope_;
clang::Scope* old_tu_scope_;
};
} // namespace
static bool IsInStdNamespace(const clang::FunctionDecl* decl) {
const clang::DeclContext* context = decl->getDeclContext();
while (context) {
if (context->isStdNamespace()) {
return true;
}
context = context->getParent();
}
return false;
}
Identifier FunctionDeclImporter::GetTranslatedParamName(
const clang::ParmVarDecl* param_decl) {
int param_pos = param_decl->getFunctionScopeIndex();
absl::StatusOr<TranslatedIdentifier> name =
ictx_.GetTranslatedIdentifier(param_decl);
if (!name.ok()) {
return {Identifier(absl::StrCat("__param_", param_pos))};
}
if (auto* sttpt =
param_decl->getType()->getAs<clang::SubstTemplateTypeParmType>();
sttpt && sttpt->getReplacedParameter()->isParameterPack()) {
// Avoid giving the same name to all parameters expanded from a pack.
return {Identifier(
absl::StrCat("__", name->rs_identifier().Ident(), "_", param_pos))};
}
return Identifier(std::string((*name).rs_identifier().Ident()));
}
std::optional<IR::Item> FunctionDeclImporter::Import(
clang::FunctionDecl* function_decl) {
if (!ictx_.IsFromCurrentTarget(function_decl)) return std::nullopt;
if (function_decl->isDeleted()) return std::nullopt;
if (IsInStdNamespace(function_decl)) {
if (clang::IdentifierInfo* id = function_decl->getIdentifier();
id != nullptr && id->getName().find("__") != llvm::StringRef::npos) {
return ictx_.ImportUnsupportedItem(
*function_decl, std::nullopt,
FormattedError::Static("Internal functions from the standard "
"library are not supported"));
}
}
// Method is private, we don't need to import it.
if (auto* method_decl =
clang::dyn_cast<clang::CXXMethodDecl>(function_decl)) {
switch (method_decl->getAccess()) {
case clang::AS_public:
break;
case clang::AS_protected:
case clang::AS_private:
case clang::AS_none:
// No need for IR to include Func representing private methods.
// TODO(lukasza): Revisit this for protected methods.
return std::nullopt;
}
}
absl::StatusOr<TranslatedUnqualifiedIdentifier> translated_name =
ictx_.GetTranslatedName(function_decl);
if (!translated_name.ok()) {
return ictx_.ImportUnsupportedItem(
*function_decl, std::nullopt,
FormattedError::PrefixedStrCat("Function name is not supported",
translated_name.status().message()));
}
auto enclosing_item_id = ictx_.GetEnclosingItemId(function_decl);
if (!enclosing_item_id.ok()) {
return ictx_.ImportUnsupportedItem(
*function_decl, std::nullopt,
FormattedError::FromStatus(std::move(enclosing_item_id.status())));
}
// Reports an unsupported function with the given error.
//
// This is preferred to invoking `ImportUnsupportedItem` directly because it
// ensures that the path is set correctly. Note that this cannot be used above
// because the enclosing item ID and translated name are not yet available.
auto unsupported = [this, &translated_name, &enclosing_item_id,
function_decl](FormattedError error) {
return ictx_.ImportUnsupportedItem(
*function_decl,
UnsupportedItem::Path{.ident = (*translated_name).cc_identifier,
.enclosing_item_id = *enclosing_item_id},
error);
};
// We should only import methods of class template specializations
// that can be instantiated: the template may spell out the method,
// but it's not guaranteed to be instantiable for the template parameter(s);
// importing an un-instantiable method causes Crubit to generate a thunk to
// invoke this method, which triggers instantiation when compiling the
// generated bindings, which fails the build.
clang::FunctionDecl* template_decl_for_method =
function_decl->getInstantiatedFromMemberFunction();
if (template_decl_for_method) {
// Some methods in STL are explicitly marked with
// `__attribute__((exclude_from_explicit_instantiation))`, and attempt to
// instantiate them may crash clang, so we skip them for now.
bool skip_instantiation = false;
if (template_decl_for_method->hasAttrs()) {
skip_instantiation = std::any_of(
function_decl->attr_begin(), function_decl->attr_end(),
[](auto attr) {
return clang::isa<clang::ExcludeFromExplicitInstantiationAttr>(
attr);
});
}
if (!function_decl->isDefined() && !skip_instantiation) {
// Here, we have the option to instantiate the function
// definition recursively, that is, to instantiate the function
// templates invoked within the (templated) body. This checks the
// validity of the function template more thoroughly than simply
// ensuring the type of the invoked function template is correct: e.g.,
// If `Recursive` is set, a diagnostic would be emitted if the function
// template invoked in the body fails to instantiate (e.g., due to a
// static_assert) but still passes type checking. However, this has the
// side effect of actually instantiating the invoked function template
// and the invoked function template would be considered "defined", so
// we wouldn't be able to get diagnostics when actually importing the
// invoked function template.
// TODO(b/248542210): Propagate the validity check of function templates
// in a function template body.
// TODO(b/248542210): `clang::Sema::InstantiateClassMembers` checks more
// constraints (than here) when instantiating the methods, consider use
// that API instead (and avoid calling `InstantiateFunctionDefinition`
// here). We don't use it now because we cannot clearly attribute
// emitted diagnostics to a member decl (we need diagnostics because the
// decl may be considered valid `!decl->isInvalidDecl()` while its
// instantiation may fail.)
auto point_of_instantiation = function_decl->getPointOfInstantiation();
// Point of instantiation is invalid if Crubit is eagerly
// instantiating a method of a class template specialization.
if (point_of_instantiation.isInvalid()) {
point_of_instantiation = function_decl->getLocation();
}
crubit::RecordingDiagnosticConsumer diagnostic_recorder =
crubit::RecordDiagnostics(ictx_.sema_.getDiagnostics(), [&] {
// Generally, clang is able to instantiate templates like this even
// after parsing completes. However, in rare cases it accesses
// transient parsing state (Scope) which was already cleaned up.
//
// HACK: We need to create a fake TU scope to avoid a crash in
// `clang::Sema::InstantiateFunctionDefinition` when it tries to
// access the translation unit scope, which it incorrectly assumes
// is always non-null. This should be fixed in clang.
//
// Specifically, the crash happens when Crubit instantiates a
// class template with a defaulted copy constructor, introducing
// lazily-injected builtins such as `memcpy` to be introduced to the
// TU scope.
//
// See b/401857961 where this was observed and cl/265779405 where a
// similar issue was fixed in CLIF.
FakeTUScope fake_tu_scope(ictx_);
ictx_.sema_.InstantiateFunctionDefinition(point_of_instantiation,
function_decl);
});
std::string diagnostics =
diagnostic_recorder.ConcatenatedDiagnostics("Diagnostics emitted:\n");
if (diagnostic_recorder.getNumErrors() != 0) {
// Clang considers the function decl valid even fatal diagnostics is
// emitted during instantiation. However, such diagnostics would fail
// compilation of generated bindings, so it's invalid as far as Crubit
// is concerned, thus set it as invalid here.
function_decl->setInvalidDecl();
return unsupported(FormattedError::PrefixedStrCat(
"Failed to instantiate the function/method template", diagnostics));
}
}
}
if (function_decl->isInvalidDecl()) {
return unsupported(
FormattedError::Static("Function declaration is considered invalid"));
}
clang::tidy::lifetimes::LifetimeSymbolTable lifetime_symbol_table;
std::optional<clang::tidy::lifetimes::FunctionLifetimes> lifetimes;
llvm::Expected<clang::tidy::lifetimes::FunctionLifetimes> lifetimes_or_err =
clang::tidy::lifetimes::GetLifetimeAnnotations(
function_decl, *ictx_.invocation_.lifetime_context_,
&lifetime_symbol_table);
if (lifetimes_or_err) {
lifetimes = std::move(*lifetimes_or_err);
} else {
using clang::tidy::lifetimes::LifetimeError;
llvm::Error remaining_err = llvm::handleErrors(
lifetimes_or_err.takeError(),
[](std::unique_ptr<LifetimeError> lifetime_err) -> llvm::Error {
switch (lifetime_err->type()) {
case LifetimeError::Type::ElisionNotEnabled:
case LifetimeError::Type::CannotElideOutputLifetimes:
// If elision is not enabled or output lifetimes cannot be
// elided, we want to import the function with raw lifetime-less
// pointers. Just return success here; this will leave the
// `lifetimes` optional empty, and we will then handle this
// accordingly below.
return llvm::Error::success();
break;
default:
return llvm::Error(std::move(lifetime_err));
break;
}
});
if (remaining_err) {
return unsupported(FormattedError::PrefixedStrCat(
"Unable to get lifetime annotations",
llvm::toString(std::move(remaining_err))));
}
}
std::vector<FuncParam> params;
Errors errors;
if (auto* method_decl =
clang::dyn_cast<clang::CXXMethodDecl>(function_decl)) {
if (!ictx_.HasBeenAlreadySuccessfullyImported(method_decl->getParent())) {
return unsupported(FormattedError::Static("Couldn't import the parent"));
}
// non-static member functions receive an implicit `this` parameter.
if (method_decl->isInstance()) {
const clang::tidy::lifetimes::ValueLifetimes* this_lifetimes = nullptr;
if (lifetimes) {
this_lifetimes = &lifetimes->GetThisLifetimes();
}
absl::StatusOr<CcType> this_param_type =
ictx_.ConvertQualType(method_decl->getThisType(), this_lifetimes,
/*nullable=*/false);
if (!this_param_type.ok()) {
errors.Add(
FormattedError::PrefixedStrCat("`this` parameter is not supported",
this_param_type.status().message()));
} else {
ApplyRefQualifierToThisPointer(*this_param_type,
method_decl->getRefQualifier());
params.push_back(
{.type = *std::move(this_param_type),
.identifier = Identifier("__this"),
// TODO(b/319524852): catch `[[clang::lifetimebound]]` on `this`.
.unknown_attr = {}});
}
}
}
if (lifetimes) {
CHECK(lifetimes->IsValidForDecl(function_decl));
}
for (unsigned i = 0; i < function_decl->getNumParams(); ++i) {
const clang::ParmVarDecl* param = function_decl->getParamDecl(i);
const clang::tidy::lifetimes::ValueLifetimes* param_lifetimes = nullptr;
if (lifetimes) {
param_lifetimes = &lifetimes->GetParamLifetimes(i);
}
auto param_type = ictx_.ConvertQualType(param->getType(), param_lifetimes);
if (!param_type.ok()) {
errors.Add(
FormattedError::Substitute("Parameter #$0 is not supported: $1", i,
param_type.status().message()));
continue;
}
std::optional<Identifier> param_name = GetTranslatedParamName(param);
CHECK(param_name.has_value()); // No known failure cases.
absl::StatusOr<std::optional<std::string>> unknown_attr =
CollectUnknownAttrs(*param);
if (!unknown_attr.ok()) {
errors.Add(FormattedError::FromStatus(std::move(unknown_attr.status())));
continue;
}
params.push_back({.type = *param_type,
.identifier = *std::move(param_name),
.unknown_attr = std::move(*unknown_attr)});
}
bool undeduced_return_type =
function_decl->getReturnType()->isUndeducedType();
if (undeduced_return_type) {
// Use a custom diagnoser as the `DeduceReturnType` call may fail, which
// is OK if this is a method of a class template, since Crubit
// instantiates the members of the class templates eagerly.
crubit::RecordingDiagnosticConsumer diagnostic_recorder =
crubit::RecordDiagnostics(ictx_.sema_.getDiagnostics(), [&] {
undeduced_return_type = ictx_.sema_.DeduceReturnType(
function_decl, function_decl->getLocation());
});
if (undeduced_return_type) {
errors.Add(FormattedError::PrefixedStrCat(
"Couldn't deduce the return type",
diagnostic_recorder.ConcatenatedDiagnostics(
"Diagnostics emitted:\n")));
}
}
absl::StatusOr<CcType> return_type;
if (!undeduced_return_type) {
const clang::tidy::lifetimes::ValueLifetimes* return_lifetimes = nullptr;
if (lifetimes) {
return_lifetimes = &lifetimes->GetReturnLifetimes();
}
return_type =
ictx_.ConvertQualType(function_decl->getReturnType(), return_lifetimes);
if (!return_type.ok()) {
errors.Add(FormattedError::PrefixedStrCat(
"Return type is not supported", return_type.status().message()));
}
}
llvm::DenseSet<clang::tidy::lifetimes::Lifetime> all_free_lifetimes;
if (lifetimes) {
all_free_lifetimes = lifetimes->AllFreeLifetimes();
}
std::vector<LifetimeName> lifetime_params;
for (clang::tidy::lifetimes::Lifetime lifetime : all_free_lifetimes) {
std::optional<llvm::StringRef> name =
lifetime_symbol_table.LookupLifetime(lifetime);
CHECK(name.has_value());
lifetime_params.push_back(
{.name = name->str(), .id = LifetimeId(lifetime.Id())});
}
llvm::sort(lifetime_params,
[](const LifetimeName& l1, const LifetimeName& l2) {
return l1.name < l2.name;
});
bool is_inline = false;
bool is_defined = false;
for (auto* def : function_decl->redecls()) {
if (def->isInlined()) is_inline = true;
if (def->isThisDeclarationADefinition()) is_defined = true;
}
if (!is_defined) // Template members may not be defined until instantiation.
if (auto* pat = function_decl->getTemplateInstantiationPattern()) {
if (pat->isThisDeclarationADefinition()) is_defined = true;
}
// It is valid to declare an inline function but not define it, as long as it
// is not odr-used. Our thunk can't call it, so it is not callable from Rust.
if (is_inline && !is_defined) {
errors.Add(FormattedError::Static("Inline function is not defined"));
}
std::optional<MemberFuncMetadata> member_func_metadata;
if (auto* method_decl =
clang::dyn_cast<clang::CXXMethodDecl>(function_decl)) {
std::optional<MemberFuncMetadata::InstanceMethodMetadata> instance_metadata;
if (method_decl->isInstance()) {
MemberFuncMetadata::ReferenceQualification reference;
switch (method_decl->getRefQualifier()) {
case clang::RQ_LValue:
reference = MemberFuncMetadata::kLValue;
break;
case clang::RQ_RValue:
reference = MemberFuncMetadata::kRValue;
break;
case clang::RQ_None:
reference = MemberFuncMetadata::kUnqualified;
break;
}
instance_metadata = MemberFuncMetadata::InstanceMethodMetadata{
.reference = reference,
.is_const = method_decl->isConst(),
.is_virtual = method_decl->isVirtual(),
};
}
member_func_metadata = MemberFuncMetadata{
.record_id = ictx_.GenerateItemId(method_decl->getParent()),
.instance_method_metadata = instance_metadata};
}
if (!errors.error_set.empty()) {
return ictx_.ImportUnsupportedItem(
*function_decl,
UnsupportedItem::Path{.ident = (*translated_name).cc_identifier,
.enclosing_item_id = *enclosing_item_id},
std::vector(errors.error_set.begin(), errors.error_set.end()));
}
bool has_c_calling_convention =
function_decl->getType()->getAs<clang::FunctionType>()->getCallConv() ==
clang::CC_C;
bool is_member_or_descendant_of_class_template =
IsFullClassTemplateSpecializationOrChild(function_decl);
SafetyAnnotation safety_annotation =
GetSafetyAnnotation(*function_decl, errors);
std::optional<std::string> doc_comment = ictx_.GetComment(function_decl);
if (!doc_comment.has_value() && is_member_or_descendant_of_class_template) {
// Despite `is_member_or_descendant_of_class_template` check above, we are
// not guaranteed that a `func_pattern` exists below. For example, it may
// be missing when `function_decl` is an implicitly defined constructor of
// a class template -- such decls are generated, not instantiated.
if (clang::FunctionDecl* func_pattern =
function_decl->getTemplateInstantiationPattern()) {
doc_comment = ictx_.GetComment(func_pattern);
}
}
std::optional<std::string> nodiscard;
std::optional<std::string> deprecated;
absl::StatusOr<std::optional<std::string>> unknown_attr =
CollectUnknownAttrs(*function_decl, [&](const clang::Attr& attr) {
if (auto* unused_attr =
clang::dyn_cast<clang::WarnUnusedResultAttr>(&attr)) {
nodiscard.emplace(unused_attr->getMessage());
return true;
} else if (auto* deprecated_attr =
clang::dyn_cast<clang::DeprecatedAttr>(&attr)) {
deprecated.emplace(deprecated_attr->getMessage());
return true;
} else if (clang::isa<clang::NoReturnAttr>(attr)) {
return true; // we call isNoReturn below, instead
} else if (clang::isa<clang::NoThrowAttr>(attr)) {
// nothrow attributes don't affect Rust.
return true;
}
return false;
});
if (!unknown_attr.ok()) {
return ictx_.ImportUnsupportedItem(
*function_decl,
UnsupportedItem::Path{.ident = (*translated_name).cc_identifier,
.enclosing_item_id = *enclosing_item_id},
FormattedError::FromStatus(std::move(unknown_attr.status())));
}
// Silence ClangTidy, checked above: calling `errors.Add` if
// `!return_type.ok()` and returning early if `!errors.empty()`.
CHECK_OK(return_type);
return Func{
.cc_name = (*translated_name).cc_identifier,
.rs_name = (*translated_name).rs_identifier(),
.owning_target = ictx_.GetOwningTarget(function_decl),
.doc_comment = std::move(doc_comment),
.mangled_name = ictx_.GetMangledName(function_decl),
.return_type = *return_type,
.params = std::move(params),
.lifetime_params = std::move(lifetime_params),
.is_inline = is_inline,
.member_func_metadata = std::move(member_func_metadata),
.is_extern_c = function_decl->isExternC(),
.is_noreturn = function_decl->isNoReturn(),
.is_variadic = function_decl->isVariadic(),
.is_consteval = function_decl->isConsteval(),
.nodiscard = std::move(nodiscard),
.deprecated = std::move(deprecated),
.unknown_attr = std::move(*unknown_attr),
.has_c_calling_convention = has_c_calling_convention,
.is_member_or_descendant_of_class_template =
is_member_or_descendant_of_class_template,
.safety_annotation = safety_annotation,
.source_loc = ictx_.ConvertSourceLocation(function_decl->getBeginLoc()),
.id = ictx_.GenerateItemId(function_decl),
.enclosing_item_id = *std::move(enclosing_item_id),
};
}
} // namespace crubit