blob: 59257b00a7e7044f620da91963585bb01485dbed [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "tools/fidl/fidlc/src/compiler.h"
#include <zircon/assert.h>
#include <algorithm>
#include "tools/fidl/fidlc/src/attribute_schema.h"
#include "tools/fidl/fidlc/src/availability_step.h"
#include "tools/fidl/fidlc/src/compile_step.h"
#include "tools/fidl/fidlc/src/consume_step.h"
#include "tools/fidl/fidlc/src/diagnostics.h"
#include "tools/fidl/fidlc/src/flat_ast.h"
#include "tools/fidl/fidlc/src/names.h"
#include "tools/fidl/fidlc/src/resolve_step.h"
#include "tools/fidl/fidlc/src/type_shape_step.h"
#include "tools/fidl/fidlc/src/verify_steps.h"
namespace fidlc {
Compiler::Compiler(Libraries* all_libraries, const VersionSelection* version_selection,
MethodHasher method_hasher, ExperimentalFlagSet experimental_flags)
: reporter_(all_libraries->reporter()),
library_(std::make_unique<Library>()),
all_libraries_(all_libraries),
version_selection(version_selection),
method_hasher_(std::move(method_hasher)),
experimental_flags_(experimental_flags),
typespace_start_index_(all_libraries->typespace()->types().size()) {}
bool Compiler::ConsumeFile(std::unique_ptr<File> file) {
return ConsumeStep(this, std::move(file)).Run();
}
bool Compiler::Compile() {
auto checkpoint = reporter_->Checkpoint();
if (!AvailabilityStep(this).Run())
return false;
if (!ResolveStep(this).Run())
return false;
if (!CompileStep(this).Run())
return false;
if (!TypeShapeStep(this).Run())
return false;
if (!VerifyHandleTransportStep(this).Run())
return false;
if (!VerifyAttributesStep(this).Run())
return false;
if (!VerifyDependenciesStep(this).Run())
return false;
if (!all_libraries_->Insert(std::move(library_)))
return false;
ZX_ASSERT_MSG(checkpoint.NoNewErrors(), "errors should have caused an early return");
return true;
}
bool Compiler::Step::Run() {
auto checkpoint = reporter()->Checkpoint();
RunImpl();
return checkpoint.NoNewErrors();
}
Typespace* Compiler::Step::typespace() { return compiler_->all_libraries_->typespace(); }
cpp20::span<const std::unique_ptr<Type>> Compiler::Step::created_types() {
auto& types = typespace()->types();
auto start = static_cast<ssize_t>(compiler_->typespace_start_index_);
return cpp20::span(types.begin() + start, types.end());
}
VirtualSourceFile* Compiler::Step::generated_source_file() {
return compiler_->all_libraries_->generated_source_file();
}
bool Libraries::Insert(std::unique_ptr<Library> library) {
auto [_, inserted] = libraries_by_name_.try_emplace(library->name, library.get());
if (!inserted) {
return reporter_->Fail(ErrMultipleLibrariesWithSameName, library->arbitrary_name_span,
library->name);
}
libraries_.push_back(std::move(library));
return true;
}
Library* Libraries::Lookup(const std::vector<std::string_view>& library_name) const {
auto iter = libraries_by_name_.find(library_name);
return iter == libraries_by_name_.end() ? nullptr : iter->second;
}
void Libraries::Remove(const Library* library) {
auto num_removed = libraries_by_name_.erase(library->name);
ZX_ASSERT_MSG(num_removed == 1, "library not in libraries_by_name_");
auto iter = std::find_if(libraries_.begin(), libraries_.end(),
[&](auto& lib) { return lib.get() == library; });
ZX_ASSERT_MSG(iter != libraries_.end(), "library not in libraries_");
libraries_.erase(iter);
}
AttributeSchema& Libraries::AddAttributeSchema(std::string name) {
auto [it, inserted] = attribute_schemas_.try_emplace(std::move(name));
ZX_ASSERT_MSG(inserted, "do not add schemas twice");
return it->second;
}
std::set<const Library*, LibraryComparator> Libraries::Unused() const {
std::set<const Library*, LibraryComparator> unused;
auto target = libraries_.back().get();
ZX_ASSERT_MSG(target, "must have inserted at least one library");
for (auto& library : libraries_) {
if (library.get() != target) {
unused.insert(library.get());
}
}
std::set<const Library*> worklist = {target};
while (!worklist.empty()) {
auto it = worklist.begin();
auto next = *it;
worklist.erase(it);
for (const auto dependency : next->dependencies.all()) {
unused.erase(dependency);
worklist.insert(dependency);
}
}
return unused;
}
static size_t EditDistance(std::string_view sequence1, std::string_view sequence2) {
size_t s1_length = sequence1.length();
size_t s2_length = sequence2.length();
std::vector<size_t> row1(s1_length + 1);
std::vector<size_t> row2(s1_length + 1);
size_t* last_row = row1.data();
size_t* this_row = row2.data();
for (size_t j = 0; j < s2_length; j++) {
this_row[0] = j + 1;
auto s2c = sequence2[j];
for (size_t i = 1; i <= s1_length; i++) {
auto s1c = sequence1[i - 1];
this_row[i] = std::min(std::min(last_row[i] + 1, this_row[i - 1] + 1),
last_row[i - 1] + (s1c == s2c ? 0 : 1));
}
std::swap(last_row, this_row);
}
return last_row[s1_length];
}
const AttributeSchema& Libraries::RetrieveAttributeSchema(const Attribute* attribute) const {
auto attribute_name = attribute->name.data();
auto iter = attribute_schemas_.find(attribute_name);
if (iter != attribute_schemas_.end()) {
return iter->second;
}
return AttributeSchema::kUserDefined;
}
void Libraries::WarnOnAttributeTypo(const Attribute* attribute) const {
auto attribute_name = attribute->name.data();
auto iter = attribute_schemas_.find(attribute_name);
if (iter != attribute_schemas_.end()) {
return;
}
for (const auto& [suspected_name, schema] : attribute_schemas_) {
auto supplied_name = attribute_name;
auto edit_distance = EditDistance(supplied_name, suspected_name);
if (0 < edit_distance && edit_distance < 2) {
reporter_->Warn(WarnAttributeTypo, attribute->span, supplied_name, suspected_name);
}
}
}
static std::vector<const Struct*> ExternalStructs(const Library* target_library,
const std::vector<const Protocol*>& protocols) {
// Ensure deterministic ordering.
auto ordering = [](const Struct* a, const Struct* b) {
return NameFlatName(a->name) < NameFlatName(b->name);
};
std::set<const Struct*, decltype(ordering)> external_structs(ordering);
auto visit = [&](const Type* type) {
if (type->kind == Type::Kind::kIdentifier) {
auto decl = static_cast<const IdentifierType*>(type)->type_decl;
if (decl->kind == Decl::Kind::kStruct && type->name.library() != target_library) {
external_structs.insert(static_cast<const Struct*>(decl));
}
}
};
for (auto& protocol : protocols) {
for (auto& method_with_info : protocol->all_methods) {
if (auto& request = method_with_info.method->maybe_request) {
visit(request->type);
}
if (auto& response = method_with_info.method->maybe_response) {
visit(response->type);
}
if (auto union_decl = method_with_info.method->result_union) {
for (auto& member : union_decl->members) {
visit(member.type_ctor->type);
}
}
}
}
return std::vector(external_structs.begin(), external_structs.end());
}
namespace {
// Helper class to calculate Compilation::direct_and_composed_dependencies.
class CalcDependencies {
public:
std::set<const Library*, LibraryComparator> From(std::vector<const Decl*>& roots) && {
for (const Decl* decl : roots) {
VisitDecl(decl);
}
return std::move(deps_);
}
private:
std::set<const Library*, LibraryComparator> deps_;
void VisitDecl(const Decl* decl) {
switch (decl->kind) {
case Decl::Kind::kBuiltin: {
ZX_PANIC("unexpected builtin");
}
case Decl::Kind::kBits: {
auto bits_decl = static_cast<const Bits*>(decl);
VisitTypeConstructor(bits_decl->subtype_ctor.get());
for (auto& member : bits_decl->members) {
VisitConstant(member.value.get());
}
break;
}
case Decl::Kind::kConst: {
auto const_decl = static_cast<const Const*>(decl);
VisitTypeConstructor(const_decl->type_ctor.get());
VisitConstant(const_decl->value.get());
break;
}
case Decl::Kind::kEnum: {
auto enum_decl = static_cast<const Enum*>(decl);
VisitTypeConstructor(enum_decl->subtype_ctor.get());
for (auto& member : enum_decl->members) {
VisitConstant(member.value.get());
}
break;
}
case Decl::Kind::kProtocol: {
auto protocol_decl = static_cast<const Protocol*>(decl);
// Make sure we insert libraries for composed protocols, even if those
// protocols are empty (so we don't get the dependency from a method).
for (auto& composed_protocol : protocol_decl->composed_protocols) {
VisitReference(composed_protocol.reference);
}
for (auto& method_with_info : protocol_decl->all_methods) {
auto method = method_with_info.method;
// Make sure we insert libraries for all transitive composed
// protocols, even if they have no methods with payloads.
deps_.insert(method->owning_protocol->name.library());
if (auto request = method->maybe_request.get()) {
VisitTypeConstructorAndStructFields(request);
}
if (auto union_decl = method->result_union) {
for (const auto& member : union_decl->members) {
VisitTypeConstructorAndStructFields(member.type_ctor.get());
}
} else if (auto response = method->maybe_response.get()) {
VisitTypeConstructorAndStructFields(response);
}
}
break;
}
case Decl::Kind::kResource: {
auto resource_decl = static_cast<const Resource*>(decl);
VisitTypeConstructor(resource_decl->subtype_ctor.get());
for (auto& property : resource_decl->properties) {
VisitTypeConstructor(property.type_ctor.get());
}
break;
}
case Decl::Kind::kService: {
auto service_decl = static_cast<const Service*>(decl);
for (auto& member : service_decl->members) {
VisitTypeConstructor(member.type_ctor.get());
}
break;
}
case Decl::Kind::kStruct: {
auto struct_decl = static_cast<const Struct*>(decl);
for (auto& member : struct_decl->members) {
VisitTypeConstructor(member.type_ctor.get());
if (auto& value = member.maybe_default_value) {
VisitConstant(value.get());
}
}
break;
}
case Decl::Kind::kTable: {
auto table_decl = static_cast<const Table*>(decl);
for (auto& member : table_decl->members) {
VisitTypeConstructor(member.type_ctor.get());
}
break;
}
case Decl::Kind::kAlias: {
auto alias_decl = static_cast<const Alias*>(decl);
VisitTypeConstructor(alias_decl->partial_type_ctor.get());
break;
}
case Decl::Kind::kNewType: {
auto new_type_decl = static_cast<const NewType*>(decl);
VisitTypeConstructor(new_type_decl->type_ctor.get());
break;
}
case Decl::Kind::kUnion: {
auto union_decl = static_cast<const Union*>(decl);
for (auto& member : union_decl->members) {
VisitTypeConstructor(member.type_ctor.get());
}
break;
}
case Decl::Kind::kOverlay: {
auto overlay_decl = static_cast<const Overlay*>(decl);
for (auto& member : overlay_decl->members) {
VisitTypeConstructor(member.type_ctor.get());
}
}
}
}
// Like `VisitTypeConstructor`, but also visits the struct fields if it is a
// struct. We use this for method requests and responses because some bindings
// flatten struct requests/responses into lists of parameters.
void VisitTypeConstructorAndStructFields(const TypeConstructor* type_ctor) {
VisitTypeConstructor(type_ctor);
if (type_ctor->type->kind == Type::Kind::kIdentifier) {
auto type_decl = static_cast<const IdentifierType*>(type_ctor->type)->type_decl;
if (type_decl->kind == Decl::Kind::kStruct) {
VisitDecl(static_cast<const Struct*>(type_decl));
}
}
}
void VisitTypeConstructor(const TypeConstructor* type_ctor) {
VisitReference(type_ctor->layout);
// TODO(https://fxbug.dev/42143256): Add dependencies introduced through handle constraints.
// This code currently assumes the handle constraints are always defined in the same
// library as the resource_definition and so does not check for them separately.
const auto& invocation = type_ctor->resolved_params;
if (invocation.size_raw) {
VisitConstant(invocation.size_raw);
}
if (invocation.protocol_decl_raw) {
VisitConstant(invocation.protocol_decl_raw);
}
if (invocation.element_type_raw) {
VisitReference(invocation.element_type_raw->layout);
}
if (invocation.boxed_type_raw) {
VisitReference(invocation.boxed_type_raw->layout);
}
}
void VisitConstant(const Constant* constant) {
switch (constant->kind) {
case Constant::Kind::kLiteral:
break;
case Constant::Kind::kIdentifier: {
auto identifier_constant = static_cast<const IdentifierConstant*>(constant);
VisitReference(identifier_constant->reference);
break;
}
case Constant::Kind::kBinaryOperator: {
auto binop_constant = static_cast<const BinaryOperatorConstant*>(constant);
VisitConstant(binop_constant->left_operand.get());
VisitConstant(binop_constant->right_operand.get());
break;
}
}
}
void VisitReference(const Reference& reference) { deps_.insert(reference.resolved().library()); }
};
} // namespace
std::unique_ptr<Compilation> Libraries::Filter(const VersionSelection* version_selection) {
// Returns true if decl should be included based on the version selection.
auto keep = [&](const auto& decl) {
return decl->availability.range().Contains(
version_selection->Lookup(decl->name.library()->platform.value()));
};
// Copies decl pointers for which keep() returns true from src to dst.
auto filter = [&](auto* dst, const auto& src) {
for (const auto& decl : src) {
if (keep(decl)) {
dst->push_back(&*decl);
}
}
};
// Filters a Library::Declarations into a Compilation::Declarations.
auto filter_declarations = [&](Compilation::Declarations* dst, const Library::Declarations& src) {
filter(&dst->bits, src.bits);
filter(&dst->builtins, src.builtins);
filter(&dst->consts, src.consts);
filter(&dst->enums, src.enums);
filter(&dst->new_types, src.new_types);
filter(&dst->protocols, src.protocols);
filter(&dst->resources, src.resources);
filter(&dst->services, src.services);
filter(&dst->structs, src.structs);
filter(&dst->tables, src.tables);
filter(&dst->aliases, src.aliases);
filter(&dst->unions, src.unions);
filter(&dst->overlays, src.overlays);
};
ZX_ASSERT(!libraries_.empty());
auto library = libraries_.back().get();
auto compilation = std::make_unique<Compilation>();
compilation->platform = &library->platform.value();
compilation->library_name = library->name;
compilation->library_declarations = library->library_name_declarations;
compilation->library_attributes = library->attributes.get();
filter_declarations(&compilation->declarations, library->declarations);
compilation->external_structs = ExternalStructs(library, compilation->declarations.protocols);
compilation->using_references = library->dependencies.library_references();
filter(&compilation->declaration_order, library->declaration_order);
auto dependencies = CalcDependencies().From(compilation->declaration_order);
dependencies.erase(library);
dependencies.erase(root_library());
for (auto dep_library : dependencies) {
auto& dep = compilation->direct_and_composed_dependencies.emplace_back();
dep.library = dep_library;
filter_declarations(&dep.declarations, dep_library->declarations);
}
compilation->version_selection_ = version_selection;
return compilation;
}
} // namespace fidlc