blob: ecc519b3ab389e9486118db89c514f4b403a742d [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/ir_from_cc.h"
#include <cstddef>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include "absl/log/check.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "absl/strings/substitute.h"
#include "absl/types/span.h"
#include "common/status_macros.h"
#include "common/string_view_conversion.h"
#include "rs_bindings_from_cc/bazel_types.h"
#include "rs_bindings_from_cc/decl_importer.h"
#include "rs_bindings_from_cc/frontend_action.h"
#include "rs_bindings_from_cc/ir.h"
#include "clang/Serialization/PCHContainerOperations.h"
#include "clang/Tooling/Tooling.h"
namespace crubit {
static constexpr absl::string_view kVirtualHeaderPath =
"ir_from_cc_virtual_header.h";
static constexpr absl::string_view kVirtualInputPath =
"ir_from_cc_virtual_input.cc";
namespace {
struct UseModFromSrc {
UseMod use_mod;
// The namespace that this UseMod should be added to. If not set, the UseMod
// is a top-level item.
std::optional<Namespace*> enclosing_namespace;
};
absl::StatusOr<std::vector<UseModFromSrc>> CreateUseModsFromExtraRustSrcs(
IR& ir, absl::Span<const std::string> extra_rs_srcs) {
std::vector<Namespace*> all_namespaces = ir.get_items_if<Namespace>();
absl::flat_hash_map<std::string, ItemId> name_to_top_level_ns;
absl::flat_hash_set<ItemId> top_level_item_id_set =
absl::flat_hash_set<ItemId>(
ir.top_level_item_ids[ir.current_target].begin(),
ir.top_level_item_ids[ir.current_target].end());
absl::flat_hash_map<ItemId, Namespace*> id_to_namespace;
for (auto ns : all_namespaces) {
if (ns->owning_target != ir.current_target) {
continue;
}
// If a namespace is open more than once, we pick the last one of them as
// it will serve as the canonical namespace without any number suffix in
// the name.
if (top_level_item_id_set.contains(ns->id)) {
name_to_top_level_ns[ns->cc_name.Ident()] = ns->id;
}
id_to_namespace.insert({ns->id, ns});
}
auto follow_mod_path_to_ns =
[&](absl::string_view mod_path) -> std::optional<Namespace*> {
if (mod_path.empty()) {
return std::nullopt;
}
std::vector<absl::string_view> parts = absl::StrSplit(mod_path, "::");
// In case there are some inner namespaces with the same name, we need to
// first find the top-level namespace and then follow its children to match
// the full path.
auto it = name_to_top_level_ns.find(parts[0]);
if (it == name_to_top_level_ns.end()) {
return std::nullopt;
}
ItemId ns_id = it->second;
for (size_t i = 1; i < parts.size(); ++i) {
const auto& part = parts[i];
bool found = false;
for (auto child_id : id_to_namespace[ns_id]->child_item_ids) {
if (id_to_namespace.contains(child_id) &&
id_to_namespace[child_id]->cc_name.Ident() == part) {
ns_id = child_id;
found = true;
break;
}
}
if (!found) {
return std::nullopt;
}
}
return id_to_namespace[ns_id];
};
int i = 0;
std::vector<UseModFromSrc> use_mods;
use_mods.reserve(extra_rs_srcs.size());
for (const std::string& extra_source_info : extra_rs_srcs) {
std::pair<absl::string_view, absl::string_view> parts =
absl::StrSplit(extra_source_info, absl::MaxSplits('=', 1));
absl::string_view extra_source_file_path = parts.first;
absl::string_view mod_path = parts.second;
if (absl::StrContains(mod_path, '=')) {
return absl::InvalidArgumentError(
absl::StrCat("Invalid extra_rs_srcs entry: ", extra_source_info));
}
// TODO(jeanpierreda): It'd be nice to give these human-readable names, e.g. the
// name of the file without the `.rs`, but it's also annoying to handle name
// collisions.
ItemId id(reinterpret_cast<uintptr_t>(&extra_source_info));
UseMod use_mod = UseMod{
.path = std::string(extra_source_file_path),
.mod_name = Identifier(absl::StrCat("__crubit_mod_", i++)),
.id = id,
};
std::optional<Namespace*> enclosing_namespace = std::nullopt;
if (!mod_path.empty()) {
if (auto ns = follow_mod_path_to_ns(mod_path); ns.has_value()) {
enclosing_namespace = std::move(ns);
} else {
return absl::InvalidArgumentError(absl::Substitute(
"Specified a namespace path '$0' that does not exist. If "
"you want to create a new module, use pub mod.",
mod_path));
}
}
use_mods.push_back(UseModFromSrc{
.use_mod = std::move(use_mod),
.enclosing_namespace = std::move(enclosing_namespace),
});
}
return use_mods;
}
// Convert the extra_rs_srcs into UseMod items and add them to the IR.
absl::Status AddUseModToIr(IR& ir,
absl::Span<const std::string> extra_rs_srcs) {
// We have to reserve the space for the new items here because below we store
// pointers to the Namespace items in the `UseModFromSrc`. If we reserve the
// space after creating the `UseModFromSrc`, the pointers might be
// invalidated.
ir.items.reserve(ir.items.size() + extra_rs_srcs.size());
CRUBIT_ASSIGN_OR_RETURN(std::vector<UseModFromSrc> use_mods,
CreateUseModsFromExtraRustSrcs(ir, extra_rs_srcs));
for (auto& use_mod_from_src : use_mods) {
ir.items.push_back(std::move(use_mod_from_src.use_mod));
if (use_mod_from_src.enclosing_namespace.has_value()) {
use_mod_from_src.enclosing_namespace.value()->child_item_ids.push_back(
use_mod_from_src.use_mod.id);
} else {
ir.top_level_item_ids[ir.current_target].push_back(
use_mod_from_src.use_mod.id);
}
}
return absl::OkStatus();
}
} // namespace
absl::StatusOr<IR> IrFromCc(IrFromCcOptions options) {
// Caller should verify that the inputs are not empty.
CHECK(!options.extra_source_code_for_testing.empty() ||
!options.public_headers.empty() ||
!options.extra_instantiations.empty());
clang::tooling::FileContentMappings file_contents;
for (auto const& name_and_content :
options.virtual_headers_contents_for_testing) {
file_contents.push_back({std::string(name_and_content.first.IncludePath()),
name_and_content.second});
}
// Tests may inject `extra_source_code_for_testing` - it needs to be appended
// to `public_headers` and exposed via `file_contents` virtual file system.
std::vector<HeaderName> augmented_public_headers(
options.public_headers.begin(), options.public_headers.end());
if (!options.extra_source_code_for_testing.empty()) {
file_contents.push_back(
{std::string(kVirtualHeaderPath),
std::string(options.extra_source_code_for_testing)});
HeaderName header_name = HeaderName(std::string(kVirtualHeaderPath));
augmented_public_headers.push_back(header_name);
options.headers_to_targets.insert({header_name, options.current_target});
}
std::string virtual_input_file_content;
for (const HeaderName& header_name : augmented_public_headers) {
absl::SubstituteAndAppend(&virtual_input_file_content, "#include \"$0\"\n",
header_name.IncludePath());
}
if (!options.extra_instantiations.empty()) {
absl::SubstituteAndAppend(&virtual_input_file_content, "namespace $0 {\n",
kInstantiationsNamespaceName);
int counter = 0;
for (const std::string& extra_instantiation :
options.extra_instantiations) {
absl::SubstituteAndAppend(&virtual_input_file_content,
"using __cc_template_instantiation_$0 = $1;\n",
counter++, extra_instantiation);
}
absl::SubstituteAndAppend(&virtual_input_file_content,
"} // namespace $0\n",
kInstantiationsNamespaceName);
}
std::vector<std::string> args_as_strings = {
// Parse non-doc comments that are used as documentation
"-fparse-all-comments"};
args_as_strings.insert(args_as_strings.end(), options.clang_args.begin(),
options.clang_args.end());
Invocation invocation(options.current_target, augmented_public_headers,
options.headers_to_targets,
std::move(options.do_not_bind_allowlist));
if (!clang::tooling::runToolOnCodeWithArgs(
std::make_unique<FrontendAction>(invocation),
virtual_input_file_content, args_as_strings,
StringRefFromStringView(kVirtualInputPath),
// Passing the path to the driver script here allows Clang to find the
// resource directory relative to this path.
StringRefFromStringView(options.driver_path),
std::make_shared<clang::PCHContainerOperations>(), file_contents)) {
return absl::Status(absl::StatusCode::kInvalidArgument,
"Could not compile header contents");
}
if (absl::Status status =
AddUseModToIr(invocation.ir_, options.extra_rs_srcs);
!status.ok()) {
return status;
}
invocation.ir_.crubit_features = std::move(options.crubit_features);
return invocation.ir_;
}
} // namespace crubit