blob: 2364cd3e719e22a6b4b315bd55b69a5a2b61c9c7 [file] [log] [blame]
// Copyright 2021 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 "fidl/flat/sort_step.h"
#include <zircon/assert.h>
#include <algorithm>
#include "fidl/names.h"
namespace fidl::flat {
namespace {
// Compares decls by name lexicographically, then by availability.
struct CmpDeclName {
bool operator()(const Decl* a, const Decl* b) const {
if (a->name == b->name) {
auto ar = a->availability.range();
auto br = b->availability.range();
ZX_ASSERT(ar != br || a == b);
return ar < br;
}
// Avoid constructing the full name when libraries are the same (faster).
if (a->name.library() == b->name.library()) {
return a->name.decl_name() < b->name.decl_name();
}
return NameFlatName(a->name) < NameFlatName(b->name);
}
};
// Helper class for calculating a decl's dependencies.
class CalcDependencies {
public:
explicit CalcDependencies(const Decl* decl);
std::set<const Decl*, CmpDeclName> get() && { return std::move(dependencies_); }
private:
void AddDependency(const Decl* decl);
void VisitConstant(const Constant* constant);
void VisitTypeConstructor(const TypeConstructor* type_ctor);
const Library* library_;
std::set<const Decl*, CmpDeclName> dependencies_;
};
} // namespace
void CalcDependencies::AddDependency(const Decl* decl) {
// Only process decls from the current library.
if (decl->name.library() == library_) {
dependencies_.insert(decl);
}
}
void CalcDependencies::VisitConstant(const Constant* constant) {
switch (constant->kind) {
case Constant::Kind::kIdentifier: {
auto identifier = static_cast<const flat::IdentifierConstant*>(constant);
AddDependency(identifier->reference.resolved().element_or_parent_decl());
break;
}
case Constant::Kind::kLiteral: {
// Literal and synthesized constants have no dependencies on other declarations.
break;
}
case Constant::Kind::kBinaryOperator: {
auto op = static_cast<const flat::BinaryOperatorConstant*>(constant);
VisitConstant(op->left_operand.get());
VisitConstant(op->right_operand.get());
break;
}
}
}
void CalcDependencies::VisitTypeConstructor(const TypeConstructor* type_ctor) {
const auto& invocation = type_ctor->resolved_params;
if (invocation.from_type_alias) {
ZX_ASSERT_MSG(!invocation.element_type_resolved, "partial aliases should be disallowed");
AddDependency(invocation.from_type_alias);
return;
}
const Type* type = type_ctor->type;
if (type->nullability == types::Nullability::kNullable) {
// We do not create edges for nullable types. This makes it possible to
// write some recursive types in FIDL. However, we do not have comprehensive
// support for recursive types. See fxbug.dev/35218 for details.
return;
}
switch (type->kind) {
case Type::Kind::kHandle: {
auto handle_type = static_cast<const HandleType*>(type);
ZX_ASSERT(handle_type->resource_decl);
auto decl = static_cast<const Decl*>(handle_type->resource_decl);
AddDependency(decl);
break;
}
case Type::Kind::kPrimitive:
case Type::Kind::kString:
case Type::Kind::kTransportSide:
case Type::Kind::kBox:
break;
case Type::Kind::kArray:
case Type::Kind::kVector: {
if (invocation.element_type_raw != nullptr) {
VisitTypeConstructor(invocation.element_type_raw);
}
// The type_ctor won't have an element_type_raw if the type is Bytes.
// In that case, do nothing since there are no edges.
break;
}
case Type::Kind::kIdentifier: {
auto identifier_type = static_cast<const IdentifierType*>(type);
auto decl = static_cast<const Decl*>(identifier_type->type_decl);
if (decl->kind != Decl::Kind::kProtocol) {
AddDependency(decl);
}
break;
}
case Type::Kind::kUntypedNumeric:
ZX_PANIC("should not have untyped numeric here");
}
}
CalcDependencies::CalcDependencies(const Decl* decl) : library_(decl->name.library()) {
switch (decl->kind) {
case Decl::Kind::kBuiltin:
break;
case Decl::Kind::kBits: {
auto bits_decl = static_cast<const Bits*>(decl);
VisitTypeConstructor(bits_decl->subtype_ctor.get());
for (const 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 (const auto& member : enum_decl->members) {
VisitConstant(member.value.get());
}
break;
}
case Decl::Kind::kProtocol: {
auto protocol_decl = static_cast<const Protocol*>(decl);
for (const auto& composed_protocol : protocol_decl->composed_protocols) {
AddDependency(composed_protocol.reference.resolved().element()->AsDecl());
}
for (const auto& method : protocol_decl->methods) {
if (method.maybe_request) {
AddDependency(method.maybe_request->layout.resolved().element()->AsDecl());
}
if (method.maybe_response) {
AddDependency(method.maybe_response->layout.resolved().element()->AsDecl());
}
}
break;
}
case Decl::Kind::kResource: {
auto resource_decl = static_cast<const Resource*>(decl);
VisitTypeConstructor(resource_decl->subtype_ctor.get());
break;
}
case Decl::Kind::kService: {
auto service_decl = static_cast<const Service*>(decl);
for (const auto& member : service_decl->members) {
VisitTypeConstructor(member.type_ctor.get());
}
break;
}
case Decl::Kind::kStruct: {
auto struct_decl = static_cast<const Struct*>(decl);
for (const auto& member : struct_decl->members) {
VisitTypeConstructor(member.type_ctor.get());
if (member.maybe_default_value) {
VisitConstant(member.maybe_default_value.get());
}
}
break;
}
case Decl::Kind::kTable: {
auto table_decl = static_cast<const Table*>(decl);
for (const auto& member : table_decl->members) {
if (!member.maybe_used) {
continue;
}
VisitTypeConstructor(member.maybe_used->type_ctor.get());
}
break;
}
case Decl::Kind::kUnion: {
auto union_decl = static_cast<const Union*>(decl);
for (const auto& member : union_decl->members) {
if (!member.maybe_used) {
continue;
}
VisitTypeConstructor(member.maybe_used->type_ctor.get());
}
break;
}
case Decl::Kind::kTypeAlias: {
auto type_alias_decl = static_cast<const TypeAlias*>(decl);
VisitTypeConstructor(type_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;
}
}
}
// Recursive helper for building a cycle to use as the example in
// ErrIncludeCycle. Given |dependencies|, the set of remaining undeclared
// dependencies of each decl and |inverse_dependencies|, the list other decls
// which depend on each decl, build out |cycle| by recursively searching all
// dependents of the last element in the cycle until one is found which is
// already a member of the cycle.
//
// The cycle must not be empty, as the last element of the cycle is the current
// search point in the recursion.
static bool BuildExampleCycle(
std::map<const Decl*, std::set<const Decl*, CmpDeclName>, CmpDeclName>& dependencies,
std::vector<const Decl*>& cycle) {
const Decl* const decl = cycle.back();
for (const Decl* dep : dependencies[decl]) {
auto dep_pos = std::find(cycle.begin(), cycle.end(), dep);
if (dep_pos != cycle.end()) {
// We found a decl that is in the cycle already. That means we have a
// cycle from dep_pos to cycle.end(), but there may be other decls from
// cycle.begin() to decl_pos which are either leaf elements not part of
// this cycle or part of another cycle which intersects this one.
cycle.erase(cycle.begin(), dep_pos);
// Add another reference to the same decl so it gets printed at both the
// start and end of the cycle.
cycle.push_back(dep);
return true;
}
cycle.push_back(dep);
if (BuildExampleCycle(dependencies, cycle)) {
return true;
}
cycle.pop_back();
}
return false;
}
void SortStep::RunImpl() {
// |dependences| is the number of undeclared dependencies left for each decl.
std::map<const Decl*, std::set<const Decl*, CmpDeclName>, CmpDeclName> dependencies;
// |inverse_dependencies| records the decls that depend on each decl.
std::map<const Decl*, std::vector<const Decl*>, CmpDeclName> inverse_dependencies;
for (const auto& [name, decl] : library()->declarations.all) {
auto deps = CalcDependencies(decl).get();
for (const Decl* dep : deps) {
inverse_dependencies[dep].push_back(decl);
}
dependencies[decl] = std::move(deps);
}
// Start with all decls that have no incoming edges.
std::vector<const Decl*> decls_without_deps;
for (const auto& [decl, deps] : dependencies) {
if (deps.empty()) {
decls_without_deps.push_back(decl);
}
}
while (!decls_without_deps.empty()) {
// Pull one out of the queue.
auto decl = decls_without_deps.back();
decls_without_deps.pop_back();
ZX_ASSERT(dependencies[decl].empty());
library()->declaration_order.push_back(decl);
// Since this decl is now declared, remove it from the set of undeclared
// dependencies for every other decl that depends on it.
auto& inverse_deps = inverse_dependencies[decl];
for (const Decl* inverse_dep : inverse_deps) {
auto& inverse_dep_forward_edges = dependencies[inverse_dep];
auto incoming_edge = inverse_dep_forward_edges.find(decl);
ZX_ASSERT(incoming_edge != inverse_dep_forward_edges.end());
inverse_dep_forward_edges.erase(incoming_edge);
if (inverse_dep_forward_edges.empty())
decls_without_deps.push_back(inverse_dep);
}
}
if (library()->declaration_order.size() != dependencies.size()) {
// We didn't visit all the edges! There was a cycle.
// Find a cycle to use as an example in the error message. We start from
// some type that still has undeclared outgoing dependencies, then do a DFS
// until we get back to a type we've visited before.
std::vector<const Decl*> cycle;
for (auto const& [decl, deps] : dependencies) {
// Find the first type that still has undeclared deps.
if (!deps.empty()) {
cycle.push_back(decl);
break;
}
}
// There is a cycle so we should find at least one decl with remaining
// undeclared deps.
ZX_ASSERT(!cycle.empty());
// Because there is a cycle, building a cycle should always succeed.
bool found_cycle = BuildExampleCycle(dependencies, cycle);
ZX_ASSERT(found_cycle);
// Even if there is only one element in the cycle (a self-loop),
// BuildExampleCycle should add a second entry so when printing we get A->A,
// so the list should always end up with at least two elements.
ZX_ASSERT(cycle.size() > 1);
Fail(ErrIncludeCycle, cycle.front()->name.span().value(), cycle);
}
}
} // namespace fidl::flat