blob: 5d4b3d33bd4b94bf7c6c115d8797212edcfe15e2 [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 "rs_bindings_from_cc/importers/function.h"
#include <algorithm>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "absl/log/check.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/substitute.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/bazel_types.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/DeclarationName.h"
#include "clang/AST/Type.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/LLVM.h"
#include "clang/Basic/Specifiers.h"
#include "clang/Sema/Sema.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Error.h"
namespace crubit {
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<Identifier> 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->Ident(), "_", param_pos))};
}
return *name;
}
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;
clang::FunctionDecl* template_decl_for_method =
function_decl->getInstantiatedFromMemberFunction();
if (IsInStdNamespace(function_decl)) {
if (clang::IdentifierInfo* id = function_decl->getIdentifier();
id != nullptr && id->getName().find("__") != llvm::StringRef::npos) {
return ictx_.ImportUnsupportedItem(
function_decl,
"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;
}
}
// 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.
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(), [&] {
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 ictx_.ImportUnsupportedItem(
function_decl,
absl::StrCat("Failed to instantiate the function/method template",
diagnostics));
}
}
}
if (function_decl->isInvalidDecl()) {
return ictx_.ImportUnsupportedItem(
function_decl, "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 ictx_.ImportUnsupportedItem(
function_decl, llvm::toString(std::move(remaining_err)));
}
}
absl::StatusOr<UnqualifiedIdentifier> translated_name =
ictx_.GetTranslatedName(function_decl);
if (!translated_name.ok()) {
return ictx_.ImportUnsupportedItem(
function_decl, absl::StrCat("Function name is not supported: ",
translated_name.status().message()));
}
std::vector<FuncParam> params;
std::set<std::string> errors;
auto add_error = [&errors](std::string msg) {
auto result = errors.insert(std::move(msg));
CHECK(result.second) << "Duplicated error message";
};
if (auto* method_decl =
clang::dyn_cast<clang::CXXMethodDecl>(function_decl)) {
if (!ictx_.HasBeenAlreadySuccessfullyImported(method_decl->getParent())) {
return ictx_.ImportUnsupportedItem(function_decl,
"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();
}
auto param_type =
ictx_.ConvertQualType(method_decl->getThisType(), this_lifetimes,
std::optional<clang::RefQualifierKind>(
method_decl->getRefQualifier()),
/*nullable=*/false);
if (!param_type.ok()) {
add_error(absl::StrCat("`this` parameter is not supported: ",
param_type.status().message()));
} else {
params.push_back(
{.type = *std::move(param_type),
.identifier = Identifier("__this"),
// TODO(b/319524852): catch `[[clang::lifetimbound]]` 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, std::nullopt);
if (!param_type.ok()) {
add_error(absl::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.
params.push_back({.type = *param_type,
.identifier = *std::move(param_name),
.unknown_attr = CollectUnknownAttrs(*param)});
}
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) {
add_error(absl::StrCat("Couldn't deduce the return type",
diagnostic_recorder.ConcatenatedDiagnostics(
": Diagnostics emitted:\n")));
}
}
absl::StatusOr<MappedType> 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, std::nullopt);
if (!return_type.ok()) {
add_error(absl::StrCat("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;
});
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.empty()) {
return ictx_.ImportUnsupportedItem(function_decl, errors);
}
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);
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;
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;
});
// Silence ClangTidy, checked above: calling `add_error` if
// `!return_type.ok()` and returning early if `!errors.empty()`.
CHECK_OK(return_type);
auto enclosing_item_id = ictx_.GetEnclosingItemId(function_decl);
if (!enclosing_item_id.ok()) {
return ictx_.ImportUnsupportedItem(
function_decl, std::string(enclosing_item_id.status().message()));
}
return Func{
.name = *translated_name,
.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 = function_decl->isInlined(),
.member_func_metadata = std::move(member_func_metadata),
.is_extern_c = function_decl->isExternC(),
.is_noreturn = function_decl->isNoReturn(),
.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,
.source_loc = ictx_.ConvertSourceLocation(function_decl->getBeginLoc()),
.id = ictx_.GenerateItemId(function_decl),
.enclosing_item_id = *std::move(enclosing_item_id),
};
}
} // namespace crubit