| // 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 "tools/fidl/fidlc/include/fidl/flat/compile_step.h" |
| |
| #include <zircon/assert.h> |
| |
| #include <algorithm> |
| #include <optional> |
| |
| #include "tools/fidl/fidlc/include/fidl/diagnostics.h" |
| #include "tools/fidl/fidlc/include/fidl/flat/attribute_schema.h" |
| #include "tools/fidl/fidlc/include/fidl/flat/type_resolver.h" |
| #include "tools/fidl/fidlc/include/fidl/flat_ast.h" |
| #include "tools/fidl/fidlc/include/fidl/names.h" |
| #include "tools/fidl/fidlc/include/fidl/ordinals.h" |
| #include "tools/fidl/fidlc/include/fidl/program_invocation.h" |
| |
| namespace fidl::flat { |
| |
| // See RFC-0132 for the origin of this table limit. |
| constexpr size_t kMaxTableOrdinals = 64; |
| |
| void CompileStep::RunImpl() { |
| CompileAttributeList(library()->attributes.get()); |
| for (auto& [name, decl] : library()->declarations.all) { |
| CompileDecl(decl); |
| } |
| } |
| |
| namespace { |
| |
| class ScopeInsertResult { |
| public: |
| explicit ScopeInsertResult(std::unique_ptr<SourceSpan> previous_occurrence) |
| : previous_occurrence_(std::move(previous_occurrence)) {} |
| |
| static ScopeInsertResult Ok() { return ScopeInsertResult(nullptr); } |
| static ScopeInsertResult FailureAt(SourceSpan previous) { |
| return ScopeInsertResult(std::make_unique<SourceSpan>(previous)); |
| } |
| |
| bool ok() const { return previous_occurrence_ == nullptr; } |
| |
| const SourceSpan& previous_occurrence() const { |
| ZX_ASSERT(!ok()); |
| return *previous_occurrence_; |
| } |
| |
| private: |
| std::unique_ptr<SourceSpan> previous_occurrence_; |
| }; |
| |
| template <typename T> |
| class Scope { |
| public: |
| ScopeInsertResult Insert(const T& t, SourceSpan span) { |
| auto iter = scope_.find(t); |
| if (iter != scope_.end()) { |
| return ScopeInsertResult::FailureAt(iter->second); |
| } |
| scope_.emplace(t, span); |
| return ScopeInsertResult::Ok(); |
| } |
| |
| typename std::map<T, SourceSpan>::const_iterator begin() const { return scope_.begin(); } |
| |
| typename std::map<T, SourceSpan>::const_iterator end() const { return scope_.end(); } |
| |
| private: |
| std::map<T, SourceSpan> scope_; |
| }; |
| |
| using Ordinal64Scope = Scope<uint64_t>; |
| |
| std::optional<std::pair<uint64_t, SourceSpan>> FindFirstNonDenseOrdinal( |
| const Ordinal64Scope& scope) { |
| uint64_t last_ordinal_seen = 0; |
| for (const auto& ordinal_and_loc : scope) { |
| uint64_t next_expected_ordinal = last_ordinal_seen + 1; |
| if (ordinal_and_loc.first != next_expected_ordinal) { |
| return std::optional{std::make_pair(next_expected_ordinal, ordinal_and_loc.second)}; |
| } |
| last_ordinal_seen = ordinal_and_loc.first; |
| } |
| return std::nullopt; |
| } |
| |
| struct MethodScope { |
| Ordinal64Scope ordinals; |
| Scope<std::string> canonical_names; |
| Scope<const Protocol*> protocols; |
| }; |
| |
| // A helper class to derive the resourceness of synthesized decls based on their |
| // members. If the given std::optional<types::Resourceness> is already set |
| // (meaning the decl is user-defined, not synthesized), this does nothing. |
| // |
| // Types added via AddType must already be compiled. In other words, there must |
| // not be cycles among the synthesized decls. |
| class DeriveResourceness { |
| public: |
| explicit DeriveResourceness(std::optional<types::Resourceness>* target) |
| : target_(target), derive_(!target->has_value()) {} |
| |
| ~DeriveResourceness() { |
| if (derive_) { |
| *target_ = result_; |
| } |
| } |
| |
| void AddType(const Type* type) { |
| if (derive_ && result_ == types::Resourceness::kValue && |
| type->Resourceness() == types::Resourceness::kResource) { |
| result_ = types::Resourceness::kResource; |
| } |
| } |
| |
| private: |
| std::optional<types::Resourceness>* const target_; |
| const bool derive_; |
| types::Resourceness result_ = types::Resourceness::kValue; |
| }; |
| |
| // A helper class to track when a Decl is compiling and compiled. |
| class Compiling { |
| public: |
| explicit Compiling(Decl* decl, std::vector<const Decl*>& decl_stack) |
| : decl_(decl), decl_stack_(decl_stack) { |
| decl_->compiling = true; |
| decl_stack_.push_back(decl); |
| } |
| |
| ~Compiling() { |
| decl_->compiling = false; |
| decl_->compiled = true; |
| decl_stack_.pop_back(); |
| } |
| |
| private: |
| Decl* decl_; |
| // Stack trace of decl compile calls. |
| std::vector<const Decl*>& decl_stack_; |
| }; |
| |
| } // namespace |
| |
| std::optional<std::vector<const Decl*>> CompileStep::GetDeclCycle(const Decl* decl) { |
| if (!decl->compiled && decl->compiling) { |
| auto decl_pos = std::find(decl_stack_.begin(), decl_stack_.end(), decl); |
| // Decl should already be in the stack somewhere because compiling is set to |
| // true iff the decl is in the decl stack. |
| ZX_ASSERT(decl_pos != decl_stack_.end()); |
| // Copy the part of the cycle we care about so Compiling guards can pop |
| // normally when returning. |
| std::vector<const Decl*> cycle(decl_pos, decl_stack_.end()); |
| // Add a second instance of the decl at the end of the list so it shows as |
| // both the beginning and end of the cycle. |
| cycle.push_back(decl); |
| return cycle; |
| } |
| return std::nullopt; |
| } |
| |
| void CompileStep::CompileDecl(Decl* decl) { |
| if (decl->compiled) { |
| return; |
| } |
| if (auto cycle = GetDeclCycle(decl); cycle) { |
| Fail(ErrIncludeCycle, decl->name.span().value(), cycle.value()); |
| return; |
| } |
| Compiling guard(decl, decl_stack_); |
| switch (decl->kind) { |
| case Decl::Kind::kBuiltin: |
| // Nothing to do. |
| break; |
| case Decl::Kind::kBits: |
| CompileBits(static_cast<Bits*>(decl)); |
| break; |
| case Decl::Kind::kConst: |
| CompileConst(static_cast<Const*>(decl)); |
| break; |
| case Decl::Kind::kEnum: |
| CompileEnum(static_cast<Enum*>(decl)); |
| break; |
| case Decl::Kind::kProtocol: |
| CompileProtocol(static_cast<Protocol*>(decl)); |
| break; |
| case Decl::Kind::kResource: |
| CompileResource(static_cast<Resource*>(decl)); |
| break; |
| case Decl::Kind::kService: |
| CompileService(static_cast<Service*>(decl)); |
| break; |
| case Decl::Kind::kStruct: |
| CompileStruct(static_cast<Struct*>(decl)); |
| break; |
| case Decl::Kind::kTable: |
| CompileTable(static_cast<Table*>(decl)); |
| break; |
| case Decl::Kind::kUnion: |
| CompileUnion(static_cast<Union*>(decl)); |
| break; |
| case Decl::Kind::kAlias: |
| CompileAlias(static_cast<Alias*>(decl)); |
| break; |
| case Decl::Kind::kNewType: |
| CompileNewType(static_cast<NewType*>(decl)); |
| break; |
| } // switch |
| } |
| |
| bool CompileStep::ResolveOrOperatorConstant(Constant* constant, std::optional<const Type*> opt_type, |
| const ConstantValue& left_operand, |
| const ConstantValue& right_operand) { |
| ZX_ASSERT_MSG(left_operand.kind == right_operand.kind, |
| "left and right operands of or operator must be of the same kind"); |
| ZX_ASSERT_MSG(opt_type, "type inference not implemented for or operator"); |
| const auto type = UnderlyingType(opt_type.value()); |
| if (type == nullptr) |
| return false; |
| if (type->kind != Type::Kind::kPrimitive) { |
| return Fail(ErrOrOperatorOnNonPrimitiveValue, constant->span); |
| } |
| std::unique_ptr<ConstantValue> left_operand_u64; |
| std::unique_ptr<ConstantValue> right_operand_u64; |
| if (!left_operand.Convert(ConstantValue::Kind::kUint64, &left_operand_u64)) |
| return false; |
| if (!right_operand.Convert(ConstantValue::Kind::kUint64, &right_operand_u64)) |
| return false; |
| NumericConstantValue<uint64_t> result = |
| *static_cast<NumericConstantValue<uint64_t>*>(left_operand_u64.get()) | |
| *static_cast<NumericConstantValue<uint64_t>*>(right_operand_u64.get()); |
| std::unique_ptr<ConstantValue> converted_result; |
| if (!result.Convert(ConstantValuePrimitiveKind(static_cast<const PrimitiveType*>(type)->subtype), |
| &converted_result)) |
| return false; |
| constant->ResolveTo(std::move(converted_result), type); |
| return true; |
| } |
| |
| bool CompileStep::ResolveConstant(Constant* constant, std::optional<const Type*> opt_type) { |
| ZX_ASSERT(constant != nullptr); |
| |
| // Prevent re-entry. |
| if (constant->compiled) |
| return constant->IsResolved(); |
| constant->compiled = true; |
| |
| switch (constant->kind) { |
| case Constant::Kind::kIdentifier: |
| return ResolveIdentifierConstant(static_cast<IdentifierConstant*>(constant), opt_type); |
| case Constant::Kind::kLiteral: |
| return ResolveLiteralConstant(static_cast<LiteralConstant*>(constant), opt_type); |
| case Constant::Kind::kBinaryOperator: { |
| auto binary_operator_constant = static_cast<BinaryOperatorConstant*>(constant); |
| if (!ResolveConstant(binary_operator_constant->left_operand.get(), opt_type)) { |
| return false; |
| } |
| if (!ResolveConstant(binary_operator_constant->right_operand.get(), opt_type)) { |
| return false; |
| } |
| switch (binary_operator_constant->op) { |
| case BinaryOperatorConstant::Operator::kOr: |
| return ResolveOrOperatorConstant(constant, opt_type, |
| binary_operator_constant->left_operand->Value(), |
| binary_operator_constant->right_operand->Value()); |
| default: |
| ZX_PANIC("unhandled binary operator"); |
| } |
| } |
| } |
| } |
| |
| ConstantValue::Kind CompileStep::ConstantValuePrimitiveKind( |
| const types::PrimitiveSubtype primitive_subtype) { |
| switch (primitive_subtype) { |
| case types::PrimitiveSubtype::kBool: |
| return ConstantValue::Kind::kBool; |
| case types::PrimitiveSubtype::kInt8: |
| return ConstantValue::Kind::kInt8; |
| case types::PrimitiveSubtype::kInt16: |
| return ConstantValue::Kind::kInt16; |
| case types::PrimitiveSubtype::kInt32: |
| return ConstantValue::Kind::kInt32; |
| case types::PrimitiveSubtype::kInt64: |
| return ConstantValue::Kind::kInt64; |
| case types::PrimitiveSubtype::kUint8: |
| return ConstantValue::Kind::kUint8; |
| case types::PrimitiveSubtype::kZxUchar: |
| return ConstantValue::Kind::kZxUchar; |
| case types::PrimitiveSubtype::kUint16: |
| return ConstantValue::Kind::kUint16; |
| case types::PrimitiveSubtype::kUint32: |
| return ConstantValue::Kind::kUint32; |
| case types::PrimitiveSubtype::kUint64: |
| return ConstantValue::Kind::kUint64; |
| case types::PrimitiveSubtype::kZxUsize: |
| return ConstantValue::Kind::kZxUsize; |
| case types::PrimitiveSubtype::kZxUintptr: |
| return ConstantValue::Kind::kZxUintptr; |
| case types::PrimitiveSubtype::kFloat32: |
| return ConstantValue::Kind::kFloat32; |
| case types::PrimitiveSubtype::kFloat64: |
| return ConstantValue::Kind::kFloat64; |
| } |
| } |
| |
| bool CompileStep::ResolveIdentifierConstant(IdentifierConstant* identifier_constant, |
| std::optional<const Type*> opt_type) { |
| if (opt_type) { |
| ZX_ASSERT_MSG(TypeCanBeConst(opt_type.value()), |
| "resolving identifier constant to non-const-able type"); |
| } |
| |
| auto& reference = identifier_constant->reference; |
| Decl* parent = reference.resolved().element_or_parent_decl(); |
| Element* target = reference.resolved().element(); |
| CompileDecl(parent); |
| |
| const Type* const_type = nullptr; |
| const ConstantValue* const_val = nullptr; |
| switch (target->kind) { |
| case Element::Kind::kBuiltin: { |
| // TODO(fxbug.dev/99665): In some cases we want to return a more specific |
| // error message from here, but right now we can't due to the way |
| // TypeResolver::ResolveConstraintAs tries multiple interpretations. |
| return false; |
| } |
| case Element::Kind::kConst: { |
| auto const_decl = static_cast<Const*>(target); |
| if (!const_decl->value->IsResolved()) { |
| return false; |
| } |
| const_type = const_decl->type_ctor->type; |
| const_val = &const_decl->value->Value(); |
| break; |
| } |
| case Element::Kind::kEnumMember: { |
| ZX_ASSERT(parent->kind == Decl::Kind::kEnum); |
| const_type = static_cast<Enum*>(parent)->subtype_ctor->type; |
| auto member = static_cast<Enum::Member*>(target); |
| if (!member->value->IsResolved()) { |
| return false; |
| } |
| const_val = &member->value->Value(); |
| break; |
| } |
| case Element::Kind::kBitsMember: { |
| ZX_ASSERT(parent->kind == Decl::Kind::kBits); |
| const_type = static_cast<Bits*>(parent)->subtype_ctor->type; |
| auto member = static_cast<Bits::Member*>(target); |
| if (!member->value->IsResolved()) { |
| return false; |
| } |
| const_val = &member->value->Value(); |
| break; |
| } |
| default: { |
| return Fail(ErrExpectedValueButGotType, reference.span(), reference.resolved().name()); |
| break; |
| } |
| } |
| |
| ZX_ASSERT_MSG(const_val, "did not set const_val"); |
| ZX_ASSERT_MSG(const_type, "did not set const_type"); |
| |
| std::unique_ptr<ConstantValue> resolved_val; |
| const auto type = opt_type ? opt_type.value() : const_type; |
| switch (type->kind) { |
| case Type::Kind::kString: { |
| if (!TypeIsConvertibleTo(const_type, type)) |
| goto fail_cannot_convert; |
| |
| if (!const_val->Convert(ConstantValue::Kind::kString, &resolved_val)) |
| goto fail_cannot_convert; |
| break; |
| } |
| case Type::Kind::kPrimitive: { |
| auto primitive_type = static_cast<const PrimitiveType*>(type); |
| if (!const_val->Convert(ConstantValuePrimitiveKind(primitive_type->subtype), &resolved_val)) |
| goto fail_cannot_convert; |
| break; |
| } |
| case Type::Kind::kIdentifier: { |
| auto identifier_type = static_cast<const IdentifierType*>(type); |
| const PrimitiveType* primitive_type; |
| switch (identifier_type->type_decl->kind) { |
| case Decl::Kind::kEnum: { |
| auto enum_decl = static_cast<const Enum*>(identifier_type->type_decl); |
| if (!enum_decl->subtype_ctor->type) { |
| return false; |
| } |
| ZX_ASSERT(enum_decl->subtype_ctor->type->kind == Type::Kind::kPrimitive); |
| primitive_type = static_cast<const PrimitiveType*>(enum_decl->subtype_ctor->type); |
| break; |
| } |
| case Decl::Kind::kBits: { |
| auto bits_decl = static_cast<const Bits*>(identifier_type->type_decl); |
| ZX_ASSERT(bits_decl->subtype_ctor->type->kind == Type::Kind::kPrimitive); |
| if (!bits_decl->subtype_ctor->type) { |
| return false; |
| } |
| primitive_type = static_cast<const PrimitiveType*>(bits_decl->subtype_ctor->type); |
| break; |
| } |
| default: { |
| ZX_PANIC("identifier not of const-able type."); |
| } |
| } |
| |
| auto fail_with_mismatched_type = [this, identifier_type, |
| identifier_constant](const Name& type_name) { |
| return Fail(ErrMismatchedNameTypeAssignment, identifier_constant->span, |
| identifier_type->type_decl->name, type_name); |
| }; |
| |
| switch (parent->kind) { |
| case Decl::Kind::kConst: { |
| if (const_type->name != identifier_type->type_decl->name) |
| return fail_with_mismatched_type(const_type->name); |
| break; |
| } |
| case Decl::Kind::kBits: |
| case Decl::Kind::kEnum: { |
| if (parent->name != identifier_type->type_decl->name) |
| return fail_with_mismatched_type(parent->name); |
| break; |
| } |
| default: { |
| ZX_PANIC("identifier not of const-able type."); |
| } |
| } |
| |
| if (!const_val->Convert(ConstantValuePrimitiveKind(primitive_type->subtype), &resolved_val)) |
| goto fail_cannot_convert; |
| break; |
| } |
| default: { |
| ZX_PANIC("identifier not of const-able type."); |
| } |
| } |
| |
| identifier_constant->ResolveTo(std::move(resolved_val), type); |
| return true; |
| |
| fail_cannot_convert: |
| return Fail(ErrTypeCannotBeConvertedToType, reference.span(), identifier_constant, const_type, |
| type); |
| } |
| |
| bool CompileStep::ResolveLiteralConstant(LiteralConstant* literal_constant, |
| std::optional<const Type*> opt_type) { |
| auto inferred_type = InferType(static_cast<flat::Constant*>(literal_constant)); |
| const Type* type = opt_type ? opt_type.value() : inferred_type; |
| if (!TypeIsConvertibleTo(inferred_type, type)) { |
| return Fail(ErrTypeCannotBeConvertedToType, literal_constant->literal->span(), literal_constant, |
| inferred_type, type); |
| } |
| switch (literal_constant->literal->kind) { |
| case raw::Literal::Kind::kDocComment: { |
| auto doc_comment_literal = |
| static_cast<const raw::DocCommentLiteral*>(literal_constant->literal); |
| literal_constant->ResolveTo( |
| std::make_unique<DocCommentConstantValue>(doc_comment_literal->span().data()), |
| typespace()->GetUnboundedStringType()); |
| return true; |
| } |
| case raw::Literal::Kind::kString: { |
| literal_constant->ResolveTo( |
| std::make_unique<StringConstantValue>(literal_constant->literal->span().data()), |
| typespace()->GetUnboundedStringType()); |
| return true; |
| } |
| case raw::Literal::Kind::kBool: { |
| auto bool_literal = static_cast<const raw::BoolLiteral*>(literal_constant->literal); |
| literal_constant->ResolveTo(std::make_unique<BoolConstantValue>(bool_literal->value), |
| typespace()->GetPrimitiveType(types::PrimitiveSubtype::kBool)); |
| return true; |
| } |
| case raw::Literal::Kind::kNumeric: { |
| // Even though `untyped numeric` is convertible to any numeric type, we |
| // still need to check for overflows which is done in |
| // ResolveLiteralConstantKindNumericLiteral. |
| switch (static_cast<const PrimitiveType*>(type)->subtype) { |
| case types::PrimitiveSubtype::kInt8: |
| return ResolveLiteralConstantKindNumericLiteral<int8_t>(literal_constant, type); |
| case types::PrimitiveSubtype::kInt16: |
| return ResolveLiteralConstantKindNumericLiteral<int16_t>(literal_constant, type); |
| case types::PrimitiveSubtype::kInt32: |
| return ResolveLiteralConstantKindNumericLiteral<int32_t>(literal_constant, type); |
| case types::PrimitiveSubtype::kInt64: |
| return ResolveLiteralConstantKindNumericLiteral<int64_t>(literal_constant, type); |
| case types::PrimitiveSubtype::kUint8: |
| case types::PrimitiveSubtype::kZxUchar: |
| return ResolveLiteralConstantKindNumericLiteral<uint8_t>(literal_constant, type); |
| case types::PrimitiveSubtype::kUint16: |
| return ResolveLiteralConstantKindNumericLiteral<uint16_t>(literal_constant, type); |
| case types::PrimitiveSubtype::kUint32: |
| return ResolveLiteralConstantKindNumericLiteral<uint32_t>(literal_constant, type); |
| case types::PrimitiveSubtype::kUint64: |
| case types::PrimitiveSubtype::kZxUsize: |
| case types::PrimitiveSubtype::kZxUintptr: |
| return ResolveLiteralConstantKindNumericLiteral<uint64_t>(literal_constant, type); |
| case types::PrimitiveSubtype::kFloat32: |
| return ResolveLiteralConstantKindNumericLiteral<float>(literal_constant, type); |
| case types::PrimitiveSubtype::kFloat64: |
| return ResolveLiteralConstantKindNumericLiteral<double>(literal_constant, type); |
| default: |
| ZX_PANIC("should not have any other primitive type reachable"); |
| } |
| } |
| } // switch |
| } |
| |
| template <typename NumericType> |
| bool CompileStep::ResolveLiteralConstantKindNumericLiteral(LiteralConstant* literal_constant, |
| const Type* type) { |
| NumericType value; |
| const auto span = literal_constant->literal->span(); |
| std::string string_data(span.data().data(), span.data().data() + span.data().size()); |
| switch (utils::ParseNumeric(string_data, &value)) { |
| case utils::ParseNumericResult::kSuccess: |
| literal_constant->ResolveTo(std::make_unique<NumericConstantValue<NumericType>>(value), type); |
| return true; |
| case utils::ParseNumericResult::kMalformed: |
| // The caller (ResolveLiteralConstant) ensures that the constant kind is |
| // a numeric literal, which means that it follows the grammar for |
| // numerical types. As a result, an error to parse the data here is due |
| // to the data being too large, rather than bad input. |
| [[fallthrough]]; |
| case utils::ParseNumericResult::kOutOfBounds: |
| return Fail(ErrConstantOverflowsType, span, literal_constant, type); |
| } |
| } |
| |
| const Type* CompileStep::InferType(Constant* constant) { |
| switch (constant->kind) { |
| case Constant::Kind::kLiteral: { |
| auto literal = |
| static_cast<const raw::Literal*>(static_cast<const LiteralConstant*>(constant)->literal); |
| switch (literal->kind) { |
| case raw::Literal::Kind::kString: { |
| auto string_literal = static_cast<const raw::StringLiteral*>(literal); |
| auto inferred_size = utils::string_literal_length(string_literal->span().data()); |
| return typespace()->GetStringType(inferred_size); |
| } |
| case raw::Literal::Kind::kNumeric: |
| return typespace()->GetUntypedNumericType(); |
| case raw::Literal::Kind::kBool: |
| return typespace()->GetPrimitiveType(types::PrimitiveSubtype::kBool); |
| case raw::Literal::Kind::kDocComment: |
| return typespace()->GetUnboundedStringType(); |
| } |
| return nullptr; |
| } |
| case Constant::Kind::kIdentifier: |
| if (!ResolveConstant(constant, std::nullopt)) { |
| return nullptr; |
| } |
| return constant->type; |
| case Constant::Kind::kBinaryOperator: |
| ZX_PANIC("type inference not implemented for binops"); |
| } |
| } |
| |
| bool CompileStep::ResolveAsOptional(Constant* constant) { |
| ZX_ASSERT(constant); |
| |
| if (constant->kind != Constant::Kind::kIdentifier) |
| return false; |
| |
| auto identifier_constant = static_cast<IdentifierConstant*>(constant); |
| auto element = identifier_constant->reference.resolved().element(); |
| if (element->kind != Element::Kind::kBuiltin) |
| return false; |
| auto builtin = static_cast<Builtin*>(element); |
| return builtin->id == Builtin::Identity::kOptional; |
| } |
| |
| void CompileStep::CompileAttributeList(AttributeList* attributes) { |
| Scope<std::string> scope; |
| for (auto& attribute : attributes->attributes) { |
| const auto original_name = attribute->name.data(); |
| const auto canonical_name = utils::canonicalize(original_name); |
| const auto result = scope.Insert(canonical_name, attribute->name); |
| if (!result.ok()) { |
| const auto previous_span = result.previous_occurrence(); |
| if (original_name == previous_span.data()) { |
| Fail(ErrDuplicateAttribute, attribute->name, original_name, previous_span); |
| } else { |
| Fail(ErrDuplicateAttributeCanonical, attribute->name, original_name, previous_span.data(), |
| previous_span, canonical_name); |
| } |
| } |
| CompileAttribute(attribute.get()); |
| } |
| } |
| |
| void CompileStep::CompileAttribute(Attribute* attribute, bool early) { |
| if (attribute->compiled) { |
| return; |
| } |
| |
| Scope<std::string> scope; |
| for (auto& arg : attribute->args) { |
| if (!arg->name.has_value()) { |
| continue; |
| } |
| const auto original_name = arg->name.value().data(); |
| const auto canonical_name = utils::canonicalize(original_name); |
| const auto result = scope.Insert(canonical_name, arg->name.value()); |
| if (!result.ok()) { |
| const auto previous_span = result.previous_occurrence(); |
| if (original_name == previous_span.data()) { |
| Fail(ErrDuplicateAttributeArg, attribute->span, attribute, original_name, previous_span); |
| } else { |
| Fail(ErrDuplicateAttributeArgCanonical, attribute->span, attribute, original_name, |
| previous_span.data(), previous_span, canonical_name); |
| } |
| } |
| } |
| |
| const AttributeSchema& schema = all_libraries()->RetrieveAttributeSchema(attribute); |
| if (early) { |
| ZX_ASSERT_MSG(schema.CanCompileEarly(), "attribute is not allowed to be compiled early"); |
| } |
| schema.ResolveArgs(this, attribute); |
| attribute->compiled = true; |
| } |
| |
| // static |
| void CompileStep::CompileAttributeEarly(Compiler* compiler, Attribute* attribute) { |
| CompileStep(compiler).CompileAttribute(attribute, /* early = */ true); |
| } |
| |
| const Type* CompileStep::UnderlyingType(const Type* type) { |
| if (type->kind != Type::Kind::kIdentifier) { |
| return type; |
| } |
| auto identifier_type = static_cast<const IdentifierType*>(type); |
| Decl* decl = identifier_type->type_decl; |
| CompileDecl(decl); |
| switch (decl->kind) { |
| case Decl::Kind::kBits: |
| return static_cast<const Bits*>(decl)->subtype_ctor->type; |
| case Decl::Kind::kEnum: |
| return static_cast<const Enum*>(decl)->subtype_ctor->type; |
| default: |
| return type; |
| } |
| } |
| |
| bool CompileStep::TypeCanBeConst(const Type* type) { |
| switch (type->kind) { |
| case flat::Type::Kind::kString: |
| return type->nullability != types::Nullability::kNullable; |
| case flat::Type::Kind::kPrimitive: |
| return true; |
| case flat::Type::Kind::kIdentifier: { |
| auto identifier_type = static_cast<const IdentifierType*>(type); |
| switch (identifier_type->type_decl->kind) { |
| case Decl::Kind::kEnum: |
| case Decl::Kind::kBits: |
| return true; |
| default: |
| return false; |
| } |
| } |
| default: |
| return false; |
| } // switch |
| } |
| |
| bool CompileStep::TypeIsConvertibleTo(const Type* from_type, const Type* to_type) { |
| switch (to_type->kind) { |
| case flat::Type::Kind::kString: { |
| if (from_type->kind != flat::Type::Kind::kString) |
| return false; |
| |
| auto from_string_type = static_cast<const flat::StringType*>(from_type); |
| auto to_string_type = static_cast<const flat::StringType*>(to_type); |
| |
| if (to_string_type->nullability == types::Nullability::kNonnullable && |
| from_string_type->nullability != types::Nullability::kNonnullable) |
| return false; |
| |
| if (to_string_type->max_size->value < from_string_type->max_size->value) |
| return false; |
| |
| return true; |
| } |
| case flat::Type::Kind::kPrimitive: { |
| auto to_primitive_type = static_cast<const flat::PrimitiveType*>(to_type); |
| switch (from_type->kind) { |
| case flat::Type::Kind::kUntypedNumeric: |
| return to_primitive_type->subtype != types::PrimitiveSubtype::kBool; |
| case flat::Type::Kind::kPrimitive: |
| break; // handled below |
| default: |
| return false; |
| } |
| auto from_primitive_type = static_cast<const flat::PrimitiveType*>(from_type); |
| switch (to_primitive_type->subtype) { |
| case types::PrimitiveSubtype::kBool: |
| return from_primitive_type->subtype == types::PrimitiveSubtype::kBool; |
| default: |
| // TODO(pascallouis): be more precise about convertibility, e.g. it |
| // should not be allowed to convert a float to an int. |
| return from_primitive_type->subtype != types::PrimitiveSubtype::kBool; |
| } |
| } |
| default: |
| return false; |
| } // switch |
| } |
| |
| void CompileStep::CompileBits(Bits* bits_declaration) { |
| CompileAttributeList(bits_declaration->attributes.get()); |
| for (auto& member : bits_declaration->members) { |
| CompileAttributeList(member.attributes.get()); |
| } |
| |
| CompileTypeConstructor(bits_declaration->subtype_ctor.get()); |
| if (!bits_declaration->subtype_ctor->type) { |
| return; |
| } |
| |
| if (bits_declaration->subtype_ctor->type->kind != Type::Kind::kPrimitive) { |
| Fail(ErrBitsTypeMustBeUnsignedIntegralPrimitive, bits_declaration->name.span().value(), |
| bits_declaration->subtype_ctor->type); |
| return; |
| } |
| |
| // Validate constants. |
| auto primitive_type = static_cast<const PrimitiveType*>(bits_declaration->subtype_ctor->type); |
| switch (primitive_type->subtype) { |
| case types::PrimitiveSubtype::kUint8: { |
| uint8_t mask; |
| if (!ValidateBitsMembersAndCalcMask<uint8_t>(bits_declaration, &mask)) |
| return; |
| bits_declaration->mask = mask; |
| break; |
| } |
| case types::PrimitiveSubtype::kUint16: { |
| uint16_t mask; |
| if (!ValidateBitsMembersAndCalcMask<uint16_t>(bits_declaration, &mask)) |
| return; |
| bits_declaration->mask = mask; |
| break; |
| } |
| case types::PrimitiveSubtype::kUint32: { |
| uint32_t mask; |
| if (!ValidateBitsMembersAndCalcMask<uint32_t>(bits_declaration, &mask)) |
| return; |
| bits_declaration->mask = mask; |
| break; |
| } |
| case types::PrimitiveSubtype::kUint64: { |
| uint64_t mask; |
| if (!ValidateBitsMembersAndCalcMask<uint64_t>(bits_declaration, &mask)) |
| return; |
| bits_declaration->mask = mask; |
| break; |
| } |
| case types::PrimitiveSubtype::kBool: |
| case types::PrimitiveSubtype::kInt8: |
| case types::PrimitiveSubtype::kInt16: |
| case types::PrimitiveSubtype::kInt32: |
| case types::PrimitiveSubtype::kInt64: |
| case types::PrimitiveSubtype::kZxUchar: |
| case types::PrimitiveSubtype::kZxUsize: |
| case types::PrimitiveSubtype::kZxUintptr: |
| case types::PrimitiveSubtype::kFloat32: |
| case types::PrimitiveSubtype::kFloat64: |
| Fail(ErrBitsTypeMustBeUnsignedIntegralPrimitive, bits_declaration->name.span().value(), |
| bits_declaration->subtype_ctor->type); |
| return; |
| } |
| } |
| |
| void CompileStep::CompileConst(Const* const_declaration) { |
| CompileAttributeList(const_declaration->attributes.get()); |
| CompileTypeConstructor(const_declaration->type_ctor.get()); |
| const auto* const_type = const_declaration->type_ctor->type; |
| if (!const_type) { |
| return; |
| } |
| if (!TypeCanBeConst(const_type)) { |
| Fail(ErrInvalidConstantType, const_declaration->name.span().value(), const_type); |
| } else if (!ResolveConstant(const_declaration->value.get(), const_type)) { |
| Fail(ErrCannotResolveConstantValue, const_declaration->name.span().value()); |
| } |
| } |
| |
| void CompileStep::CompileEnum(Enum* enum_declaration) { |
| CompileAttributeList(enum_declaration->attributes.get()); |
| for (auto& member : enum_declaration->members) { |
| CompileAttributeList(member.attributes.get()); |
| } |
| |
| CompileTypeConstructor(enum_declaration->subtype_ctor.get()); |
| if (!enum_declaration->subtype_ctor->type) { |
| return; |
| } |
| |
| if (enum_declaration->subtype_ctor->type->kind != Type::Kind::kPrimitive) { |
| Fail(ErrEnumTypeMustBeIntegralPrimitive, enum_declaration->name.span().value(), |
| enum_declaration->subtype_ctor->type); |
| return; |
| } |
| |
| // Validate constants. |
| auto primitive_type = static_cast<const PrimitiveType*>(enum_declaration->subtype_ctor->type); |
| enum_declaration->type = primitive_type; |
| switch (primitive_type->subtype) { |
| case types::PrimitiveSubtype::kInt8: { |
| int8_t unknown_value; |
| if (ValidateEnumMembersAndCalcUnknownValue<int8_t>(enum_declaration, &unknown_value)) { |
| enum_declaration->unknown_value_signed = unknown_value; |
| } |
| break; |
| } |
| case types::PrimitiveSubtype::kInt16: { |
| int16_t unknown_value; |
| if (ValidateEnumMembersAndCalcUnknownValue<int16_t>(enum_declaration, &unknown_value)) { |
| enum_declaration->unknown_value_signed = unknown_value; |
| } |
| break; |
| } |
| case types::PrimitiveSubtype::kInt32: { |
| int32_t unknown_value; |
| if (ValidateEnumMembersAndCalcUnknownValue<int32_t>(enum_declaration, &unknown_value)) { |
| enum_declaration->unknown_value_signed = unknown_value; |
| } |
| break; |
| } |
| case types::PrimitiveSubtype::kInt64: { |
| int64_t unknown_value; |
| if (ValidateEnumMembersAndCalcUnknownValue<int64_t>(enum_declaration, &unknown_value)) { |
| enum_declaration->unknown_value_signed = unknown_value; |
| } |
| break; |
| } |
| case types::PrimitiveSubtype::kUint8: { |
| uint8_t unknown_value; |
| if (ValidateEnumMembersAndCalcUnknownValue<uint8_t>(enum_declaration, &unknown_value)) { |
| enum_declaration->unknown_value_unsigned = unknown_value; |
| } |
| break; |
| } |
| case types::PrimitiveSubtype::kUint16: { |
| uint16_t unknown_value; |
| if (ValidateEnumMembersAndCalcUnknownValue<uint16_t>(enum_declaration, &unknown_value)) { |
| enum_declaration->unknown_value_unsigned = unknown_value; |
| } |
| break; |
| } |
| case types::PrimitiveSubtype::kUint32: { |
| uint32_t unknown_value; |
| if (ValidateEnumMembersAndCalcUnknownValue<uint32_t>(enum_declaration, &unknown_value)) { |
| enum_declaration->unknown_value_unsigned = unknown_value; |
| } |
| break; |
| } |
| case types::PrimitiveSubtype::kUint64: { |
| uint64_t unknown_value; |
| if (ValidateEnumMembersAndCalcUnknownValue<uint64_t>(enum_declaration, &unknown_value)) { |
| enum_declaration->unknown_value_unsigned = unknown_value; |
| } |
| break; |
| } |
| case types::PrimitiveSubtype::kBool: |
| case types::PrimitiveSubtype::kFloat32: |
| case types::PrimitiveSubtype::kFloat64: |
| case types::PrimitiveSubtype::kZxUsize: |
| case types::PrimitiveSubtype::kZxUintptr: |
| case types::PrimitiveSubtype::kZxUchar: |
| Fail(ErrEnumTypeMustBeIntegralPrimitive, enum_declaration->name.span().value(), |
| enum_declaration->subtype_ctor->type); |
| break; |
| } |
| } |
| |
| void CompileStep::CompileResource(Resource* resource_declaration) { |
| Scope<std::string> scope; |
| |
| CompileAttributeList(resource_declaration->attributes.get()); |
| CompileTypeConstructor(resource_declaration->subtype_ctor.get()); |
| if (!resource_declaration->subtype_ctor->type) { |
| return; |
| } |
| |
| if (resource_declaration->subtype_ctor->type->kind != Type::Kind::kPrimitive || |
| static_cast<const PrimitiveType*>(resource_declaration->subtype_ctor->type)->subtype != |
| types::PrimitiveSubtype::kUint32) { |
| Fail(ErrResourceMustBeUint32Derived, resource_declaration->name.span().value(), |
| resource_declaration->name); |
| } |
| |
| for (auto& property : resource_declaration->properties) { |
| CompileAttributeList(property.attributes.get()); |
| const auto original_name = property.name.data(); |
| const auto canonical_name = utils::canonicalize(original_name); |
| const auto name_result = scope.Insert(canonical_name, property.name); |
| if (!name_result.ok()) { |
| const auto previous_span = name_result.previous_occurrence(); |
| if (original_name == previous_span.data()) { |
| Fail(ErrDuplicateResourcePropertyName, property.name, original_name, previous_span); |
| } else { |
| Fail(ErrDuplicateResourcePropertyNameCanonical, property.name, original_name, |
| previous_span.data(), previous_span, canonical_name); |
| } |
| } |
| CompileTypeConstructor(property.type_ctor.get()); |
| } |
| |
| // All properties have been compiled at this point, so we can reason about their types. |
| auto subtype_property = resource_declaration->LookupProperty("subtype"); |
| if (subtype_property != nullptr) { |
| const Type* subtype_type = subtype_property->type_ctor->type; |
| |
| // If the |subtype_type is a |nullptr|, we are in a cycle, which means that the |subtype| |
| // property could not possibly be an enum declaration. |
| if (subtype_type == nullptr || subtype_type->kind != Type::Kind::kIdentifier || |
| static_cast<const IdentifierType*>(subtype_type)->type_decl->kind != Decl::Kind::kEnum) { |
| Fail(ErrResourceSubtypePropertyMustReferToEnum, subtype_property->name, |
| resource_declaration->name); |
| } |
| } else { |
| Fail(ErrResourceMissingSubtypeProperty, resource_declaration->name.span().value(), |
| resource_declaration->name); |
| } |
| |
| auto rights_property = resource_declaration->LookupProperty("rights"); |
| if (rights_property != nullptr) { |
| const Type* rights_type = rights_property->type_ctor->type; |
| const Type* rights_underlying_type = UnderlyingType(rights_type); |
| if (!(rights_underlying_type->kind == Type::Kind::kPrimitive && |
| static_cast<const PrimitiveType*>(rights_underlying_type)->subtype == |
| types::PrimitiveSubtype::kUint32)) { |
| Fail(ErrResourceRightsPropertyMustReferToBits, rights_property->name, |
| resource_declaration->name); |
| } |
| } |
| } |
| |
| void CompileStep::CompileProtocol(Protocol* protocol_declaration) { |
| CompileAttributeList(protocol_declaration->attributes.get()); |
| |
| MethodScope method_scope; |
| auto CheckScopes = [this, &protocol_declaration, &method_scope](const Protocol* protocol, |
| auto Visitor) -> void { |
| for (const auto& composed_protocol : protocol->composed_protocols) { |
| auto target = composed_protocol.reference.resolved().element(); |
| if (target->kind != Element::Kind::kProtocol) { |
| // No need to report an error here since it was already done by the loop |
| // after the definition of CheckScopes (before calling CheckScopes). |
| continue; |
| } |
| auto composed_protocol_declaration = static_cast<const Protocol*>(target); |
| auto span = composed_protocol_declaration->name.span(); |
| ZX_ASSERT(span); |
| if (method_scope.protocols.Insert(composed_protocol_declaration, span.value()).ok()) { |
| Visitor(composed_protocol_declaration, Visitor); |
| } else { |
| // Otherwise we have already seen this protocol in |
| // the inheritance graph. |
| } |
| } |
| for (const auto& method : protocol->methods) { |
| const auto original_name = method.name.data(); |
| const auto canonical_name = utils::canonicalize(original_name); |
| const auto name_result = method_scope.canonical_names.Insert(canonical_name, method.name); |
| if (!name_result.ok()) { |
| const auto previous_span = name_result.previous_occurrence(); |
| if (original_name == previous_span.data()) { |
| Fail(ErrDuplicateMethodName, method.name, original_name, previous_span); |
| } else { |
| Fail(ErrDuplicateMethodNameCanonical, method.name, original_name, previous_span.data(), |
| previous_span, canonical_name); |
| } |
| } |
| if (!method.generated_ordinal64) { |
| // If a composed method failed to compile, we do not associate have a |
| // generated ordinal, and proceeding leads to a segfault. Instead, |
| // continue to the next method, without reporting additional errors (the |
| // error emitted when compiling the composed method is sufficient). |
| continue; |
| } |
| if (method.generated_ordinal64->value == 0) { |
| Fail(ErrGeneratedZeroValueOrdinal, method.generated_ordinal64->span()); |
| } |
| auto ordinal_result = |
| method_scope.ordinals.Insert(method.generated_ordinal64->value, method.name); |
| if (!ordinal_result.ok()) { |
| Fail(ErrDuplicateMethodOrdinal, method.generated_ordinal64->span(), |
| ordinal_result.previous_occurrence()); |
| } |
| |
| // Add a pointer to this method to the protocol_declarations list. |
| bool is_composed = protocol_declaration != protocol; |
| protocol_declaration->all_methods.emplace_back(&method, is_composed); |
| } |
| }; |
| |
| // Before scope checking can occur, ordinals must be generated for each of the |
| // protocol's methods, including those that were composed from transitive |
| // child protocols. This means that child protocols must be compiled prior to |
| // this one, or they will not have generated_ordinal64s on their methods, and |
| // will fail the scope check. Also check for duplicate composed protocols. |
| Scope<const Protocol*> scope; |
| for (const auto& composed_protocol : protocol_declaration->composed_protocols) { |
| CompileAttributeList(composed_protocol.attributes.get()); |
| auto target = composed_protocol.reference.resolved().element(); |
| auto span = composed_protocol.reference.span(); |
| if (target->kind != Element::Kind::kProtocol) { |
| Fail(ErrComposingNonProtocol, span); |
| continue; |
| } |
| auto target_protocol = static_cast<Protocol*>(target); |
| auto result = scope.Insert(target_protocol, span); |
| if (!result.ok()) { |
| Fail(ErrProtocolComposedMultipleTimes, span, result.previous_occurrence()); |
| } |
| CompileDecl(target_protocol); |
| } |
| for (auto& method : protocol_declaration->methods) { |
| CompileAttributeList(method.attributes.get()); |
| auto selector = fidl::ordinals::GetSelector(method.attributes.get(), method.name); |
| if (!utils::IsValidIdentifierComponent(selector) && |
| !utils::IsValidFullyQualifiedMethodIdentifier(selector)) { |
| Fail(ErrInvalidSelectorValue, |
| method.attributes->Get("selector")->GetArg(AttributeArg::kDefaultAnonymousName)->span); |
| continue; |
| } |
| // TODO(fxbug.dev/77623): Remove. |
| auto library_name = library()->name; |
| if (library_name.size() == 2 && library_name[0] == "fuchsia" && library_name[1] == "io" && |
| selector.find('/') == std::string::npos) { |
| Fail(ErrFuchsiaIoExplicitOrdinals, method.name); |
| continue; |
| } |
| method.generated_ordinal64 = std::make_unique<raw::Ordinal64>(method_hasher()( |
| library_name, protocol_declaration->name.decl_name(), selector, *method.identifier)); |
| } |
| |
| CheckScopes(protocol_declaration, CheckScopes); |
| |
| // Ensure that the method's type constructors for request/response payloads actually exist, and |
| // are the right kind of layout. |
| std::function<void(const SourceSpan&, const Decl*, bool)> CheckPayloadDeclKind = |
| [&](const SourceSpan& method_name, const Decl* decl, bool empty_payload_allowed) -> void { |
| switch (decl->kind) { |
| case Decl::Kind::kStruct: { |
| const auto struct_decl = static_cast<const Struct*>(decl); |
| if (!empty_payload_allowed && struct_decl->members.empty()) { |
| Fail(ErrEmptyPayloadStructs, method_name, method_name.data()); |
| } |
| break; |
| } |
| case Decl::Kind::kTable: |
| case Decl::Kind::kUnion: { |
| break; |
| } |
| case Decl::Kind::kAlias: { |
| const auto as_alias = static_cast<const Alias*>(decl); |
| const Type* aliased_type = as_alias->partial_type_ctor->type; |
| switch (aliased_type->kind) { |
| case Type::Kind::kIdentifier: { |
| const auto as_identifier_type = static_cast<const IdentifierType*>(aliased_type); |
| CheckPayloadDeclKind(method_name, as_identifier_type->type_decl, empty_payload_allowed); |
| break; |
| } |
| default: { |
| Fail(ErrInvalidMethodPayloadLayoutClass, method_name, decl->kind); |
| break; |
| } |
| } |
| break; |
| } |
| default: { |
| Fail(ErrInvalidMethodPayloadLayoutClass, method_name, decl->kind); |
| break; |
| } |
| } |
| }; |
| |
| // Ensure that structs used as message payloads do not have default values. |
| auto CheckNoDefaultMembers = [this](const Decl* decl) -> void { |
| switch (decl->kind) { |
| case Decl::Kind::kStruct: { |
| auto struct_decl = static_cast<const Struct*>(decl); |
| for (auto& member : struct_decl->members) { |
| if (member.maybe_default_value != nullptr) { |
| Fail(ErrPayloadStructHasDefaultMembers, member.name); |
| break; |
| } |
| } |
| |
| break; |
| } |
| default: { |
| return; |
| } |
| } |
| }; |
| |
| for (auto& method : protocol_declaration->methods) { |
| if (method.maybe_request) { |
| CompileTypeConstructor(method.maybe_request.get()); |
| if (auto type = method.maybe_request->type) { |
| if (type->kind != Type::Kind::kIdentifier) { |
| Fail(ErrInvalidMethodPayloadType, method.name, type); |
| } else { |
| ZX_ASSERT(type->kind == Type::Kind::kIdentifier); |
| auto decl = static_cast<const flat::IdentifierType*>(type)->type_decl; |
| CompileDecl(decl); |
| CheckNoDefaultMembers(decl); |
| CheckPayloadDeclKind(method.name, decl, false); |
| } |
| } |
| } |
| if (method.maybe_response) { |
| CompileTypeConstructor(method.maybe_response.get()); |
| if (auto type = method.maybe_response->type) { |
| if (type->kind != Type::Kind::kIdentifier) { |
| Fail(ErrInvalidMethodPayloadType, method.name, type); |
| } else { |
| ZX_ASSERT(type->kind == Type::Kind::kIdentifier); |
| auto decl = static_cast<const flat::IdentifierType*>(type)->type_decl; |
| CompileDecl(decl); |
| if (method.HasResultUnion()) { |
| ZX_ASSERT(decl->kind == Decl::Kind::kStruct); |
| auto response_struct = static_cast<const flat::Struct*>(decl); |
| const auto* result_union_type = static_cast<const flat::IdentifierType*>( |
| response_struct->members[0].type_ctor->type); |
| |
| ZX_ASSERT(result_union_type->type_decl->kind == Decl::Kind::kUnion); |
| const auto* result_union = |
| static_cast<const flat::Union*>(result_union_type->type_decl); |
| ZX_ASSERT(!result_union->members.empty()); |
| ZX_ASSERT(result_union->members[0].maybe_used); |
| const auto* success_variant_type = result_union->members[0].maybe_used->type_ctor->type; |
| if (success_variant_type) { |
| if (success_variant_type->kind != Type::Kind::kIdentifier) { |
| Fail(ErrInvalidMethodPayloadType, method.name, success_variant_type); |
| } else { |
| const auto* success_decl = |
| static_cast<const IdentifierType*>(success_variant_type)->type_decl; |
| CheckNoDefaultMembers(success_decl); |
| bool empty_payload_allowed = true; |
| if (experimental_flags().IsFlagEnabled( |
| ExperimentalFlags::Flag::kSimpleEmptyResponseSyntax)) { |
| auto* anonymous = success_decl->name.as_anonymous(); |
| empty_payload_allowed = |
| anonymous && anonymous->provenance == Name::Provenance::kCompilerGenerated; |
| } |
| CheckPayloadDeclKind(method.name, success_decl, empty_payload_allowed); |
| } |
| } |
| } else { |
| CheckNoDefaultMembers(decl); |
| CheckPayloadDeclKind(method.name, decl, false); |
| } |
| } |
| } |
| } |
| } |
| |
| // Ensure that events do not use the error syntax except those in an allowlist. |
| // TODO(fxbug.dev/98319): Error syntax in events should not parse. |
| auto CheckNoEventErrorSyntax = [this](const Protocol::Method& event) -> void { |
| if (!event.maybe_response) |
| return; |
| if (!event.HasResultUnion()) |
| return; |
| const auto& protocol = *event.owning_protocol; |
| const Library& library = *protocol.name.library(); |
| // TODO(fxbug.dev/98319): Migrate test libraries. |
| ZX_ASSERT(!library.name.empty()); |
| if (library.name[0] == "test" || library.name[0] == "fidl") { |
| return; |
| } |
| // TODO(fxbug.dev/99924): Migrate fuchsia.hardware.radar. |
| if (library.name.size() == 3) { |
| if (library.name[0] == "fuchsia" && library.name[1] == "hardware" && |
| library.name[2] == "radar") { |
| return; |
| } |
| } |
| Fail(ErrEventErrorSyntaxDeprecated, event.name, event.name.data()); |
| }; |
| for (auto& method : protocol_declaration->methods) { |
| if (method.has_response && !method.has_request) { |
| CheckNoEventErrorSyntax(method); |
| } |
| } |
| } |
| |
| void CompileStep::CompileService(Service* service_decl) { |
| Scope<std::string> scope; |
| std::string_view associated_transport; |
| std::string_view first_member_with_that_transport; |
| |
| CompileAttributeList(service_decl->attributes.get()); |
| for (auto& member : service_decl->members) { |
| CompileAttributeList(member.attributes.get()); |
| const auto original_name = member.name.data(); |
| const auto canonical_name = utils::canonicalize(original_name); |
| const auto name_result = scope.Insert(canonical_name, member.name); |
| if (!name_result.ok()) { |
| const auto previous_span = name_result.previous_occurrence(); |
| if (original_name == previous_span.data()) { |
| Fail(ErrDuplicateServiceMemberName, member.name, original_name, previous_span); |
| } else { |
| Fail(ErrDuplicateServiceMemberNameCanonical, member.name, original_name, |
| previous_span.data(), previous_span, canonical_name); |
| } |
| } |
| CompileTypeConstructor(member.type_ctor.get()); |
| if (!member.type_ctor->type) { |
| continue; |
| } |
| if (member.type_ctor->type->kind != Type::Kind::kTransportSide) { |
| Fail(ErrOnlyClientEndsInServices, member.name); |
| continue; |
| } |
| const auto transport_side_type = static_cast<const TransportSideType*>(member.type_ctor->type); |
| if (transport_side_type->end != TransportSide::kClient) { |
| Fail(ErrOnlyClientEndsInServices, member.name); |
| } |
| if (member.type_ctor->type->nullability != types::Nullability::kNonnullable) { |
| Fail(ErrOptionalServiceMember, member.name); |
| } |
| |
| // Enforce that all client_end members are over the same transport. |
| // TODO(fxbug.dev/106184): We may need to revisit this restriction. |
| if (associated_transport.empty()) { |
| associated_transport = transport_side_type->protocol_transport; |
| first_member_with_that_transport = member.name.data(); |
| continue; |
| } |
| if (associated_transport != transport_side_type->protocol_transport) { |
| Fail(ErrMismatchedTransportInServices, member.name, member.name.data(), |
| transport_side_type->protocol_transport, first_member_with_that_transport, |
| associated_transport); |
| } |
| } |
| } |
| |
| void CompileStep::CompileStruct(Struct* struct_declaration) { |
| Scope<std::string> scope; |
| DeriveResourceness derive_resourceness(&struct_declaration->resourceness); |
| |
| CompileAttributeList(struct_declaration->attributes.get()); |
| for (auto& member : struct_declaration->members) { |
| CompileAttributeList(member.attributes.get()); |
| const auto original_name = member.name.data(); |
| const auto canonical_name = utils::canonicalize(original_name); |
| const auto name_result = scope.Insert(canonical_name, member.name); |
| if (!name_result.ok()) { |
| const auto previous_span = name_result.previous_occurrence(); |
| if (original_name == previous_span.data()) { |
| Fail(ErrDuplicateStructMemberName, member.name, original_name, previous_span); |
| } else { |
| Fail(ErrDuplicateStructMemberNameCanonical, member.name, original_name, |
| previous_span.data(), previous_span, canonical_name); |
| } |
| } |
| |
| CompileTypeConstructor(member.type_ctor.get()); |
| if (!member.type_ctor->type) { |
| continue; |
| } |
| if (member.maybe_default_value) { |
| const auto* default_value_type = member.type_ctor->type; |
| if (!TypeCanBeConst(default_value_type)) { |
| Fail(ErrInvalidStructMemberType, struct_declaration->name.span().value(), |
| NameIdentifier(member.name), default_value_type); |
| } else if (!ResolveConstant(member.maybe_default_value.get(), default_value_type)) { |
| Fail(ErrCouldNotResolveMemberDefault, member.name, NameIdentifier(member.name)); |
| } |
| } |
| derive_resourceness.AddType(member.type_ctor->type); |
| } |
| } |
| |
| void CompileStep::CompileTable(Table* table_declaration) { |
| Scope<std::string> name_scope; |
| Ordinal64Scope ordinal_scope; |
| |
| CompileAttributeList(table_declaration->attributes.get()); |
| if (table_declaration->members.size() > kMaxTableOrdinals) { |
| Fail(ErrTooManyTableOrdinals, table_declaration->name.span().value()); |
| } |
| |
| for (size_t i = 0; i < table_declaration->members.size(); i++) { |
| auto& member = table_declaration->members[i]; |
| CompileAttributeList(member.attributes.get()); |
| const auto ordinal_result = ordinal_scope.Insert(member.ordinal->value, member.ordinal->span()); |
| if (!ordinal_result.ok()) { |
| Fail(ErrDuplicateTableFieldOrdinal, member.ordinal->span(), |
| ordinal_result.previous_occurrence()); |
| } |
| if (!member.maybe_used) { |
| continue; |
| } |
| auto& member_used = *member.maybe_used; |
| const auto original_name = member_used.name.data(); |
| const auto canonical_name = utils::canonicalize(original_name); |
| const auto name_result = name_scope.Insert(canonical_name, member_used.name); |
| if (!name_result.ok()) { |
| const auto previous_span = name_result.previous_occurrence(); |
| if (original_name == previous_span.data()) { |
| Fail(ErrDuplicateTableFieldName, member_used.name, original_name, previous_span); |
| } else { |
| Fail(ErrDuplicateTableFieldNameCanonical, member_used.name, original_name, |
| previous_span.data(), previous_span, canonical_name); |
| } |
| } |
| CompileTypeConstructor(member_used.type_ctor.get()); |
| if (!member_used.type_ctor->type) { |
| continue; |
| } |
| if (member_used.type_ctor->type->nullability != types::Nullability::kNonnullable) { |
| Fail(ErrOptionalTableMember, member_used.name); |
| } |
| if (i == kMaxTableOrdinals - 1) { |
| if (member_used.type_ctor->type->kind != Type::Kind::kIdentifier) { |
| Fail(ErrMaxOrdinalNotTable, member_used.name); |
| } else { |
| auto identifier_type = static_cast<const IdentifierType*>(member_used.type_ctor->type); |
| if (identifier_type->type_decl->kind != Decl::Kind::kTable) { |
| Fail(ErrMaxOrdinalNotTable, member_used.name); |
| } |
| } |
| } |
| } |
| |
| if (auto ordinal_and_loc = FindFirstNonDenseOrdinal(ordinal_scope)) { |
| auto [ordinal, span] = *ordinal_and_loc; |
| Fail(ErrNonDenseOrdinal, span, ordinal); |
| } |
| } |
| |
| void CompileStep::CompileUnion(Union* union_declaration) { |
| Scope<std::string> scope; |
| Ordinal64Scope ordinal_scope; |
| DeriveResourceness derive_resourceness(&union_declaration->resourceness); |
| |
| CompileAttributeList(union_declaration->attributes.get()); |
| bool contains_non_reserved_member = false; |
| for (const auto& member : union_declaration->members) { |
| CompileAttributeList(member.attributes.get()); |
| const auto ordinal_result = ordinal_scope.Insert(member.ordinal->value, member.ordinal->span()); |
| if (!ordinal_result.ok()) { |
| Fail(ErrDuplicateUnionMemberOrdinal, member.ordinal->span(), |
| ordinal_result.previous_occurrence()); |
| } |
| if (!member.maybe_used) { |
| continue; |
| } |
| |
| contains_non_reserved_member = true; |
| const auto& member_used = *member.maybe_used; |
| const auto original_name = member_used.name.data(); |
| const auto canonical_name = utils::canonicalize(original_name); |
| const auto name_result = scope.Insert(canonical_name, member_used.name); |
| if (!name_result.ok()) { |
| const auto previous_span = name_result.previous_occurrence(); |
| if (original_name == previous_span.data()) { |
| Fail(ErrDuplicateUnionMemberName, member_used.name, original_name, previous_span); |
| } else { |
| Fail(ErrDuplicateUnionMemberNameCanonical, member_used.name, original_name, |
| previous_span.data(), previous_span, canonical_name); |
| } |
| } |
| |
| CompileTypeConstructor(member_used.type_ctor.get()); |
| if (!member_used.type_ctor->type) { |
| continue; |
| } |
| if (member_used.type_ctor->type->nullability != types::Nullability::kNonnullable) { |
| Fail(ErrOptionalUnionMember, member_used.name); |
| } |
| derive_resourceness.AddType(member_used.type_ctor->type); |
| } |
| |
| if (union_declaration->strictness == types::Strictness::kStrict && |
| !contains_non_reserved_member) { |
| Fail(ErrStrictUnionMustHaveNonReservedMember, union_declaration->name.span().value()); |
| } |
| |
| if (auto ordinal_and_loc = FindFirstNonDenseOrdinal(ordinal_scope)) { |
| auto [ordinal, span] = *ordinal_and_loc; |
| Fail(ErrNonDenseOrdinal, span, ordinal); |
| } |
| } |
| |
| void CompileStep::CompileAlias(Alias* alias) { |
| CompileAttributeList(alias->attributes.get()); |
| CompileTypeConstructor(alias->partial_type_ctor.get()); |
| } |
| |
| void CompileStep::CompileNewType(NewType* new_type) { |
| CompileAttributeList(new_type->attributes.get()); |
| CompileTypeConstructor(new_type->type_ctor.get()); |
| } |
| |
| void CompileStep::CompileTypeConstructor(TypeConstructor* type_ctor) { |
| if (type_ctor->type != nullptr) { |
| return; |
| } |
| TypeResolver type_resolver(this); |
| type_ctor->type = typespace()->Create(&type_resolver, type_ctor->layout, *type_ctor->parameters, |
| *type_ctor->constraints, &type_ctor->resolved_params); |
| } |
| |
| bool CompileStep::ResolveHandleRightsConstant(Resource* resource, Constant* constant, |
| const HandleRights** out_rights) { |
| auto rights_property = resource->LookupProperty("rights"); |
| if (!rights_property) { |
| return false; |
| } |
| ZX_ASSERT_MSG(rights_property->type_ctor->type, "resource must already be compiled"); |
| if (!ResolveConstant(constant, rights_property->type_ctor->type)) { |
| return false; |
| } |
| |
| if (out_rights) { |
| *out_rights = static_cast<const HandleRights*>(&constant->Value()); |
| } |
| return true; |
| } |
| |
| bool CompileStep::ResolveHandleSubtypeIdentifier(Resource* resource, Constant* constant, |
| uint32_t* out_obj_type) { |
| auto subtype_property = resource->LookupProperty("subtype"); |
| if (!subtype_property) { |
| return false; |
| } |
| ZX_ASSERT_MSG(subtype_property->type_ctor->type, "resource must already be compiled"); |
| if (!ResolveConstant(constant, subtype_property->type_ctor->type)) { |
| return false; |
| } |
| |
| if (out_obj_type) { |
| *out_obj_type = static_cast<const HandleSubtype*>(&constant->Value())->value; |
| } |
| return true; |
| } |
| |
| bool CompileStep::ResolveSizeBound(Constant* size_constant, const Size** out_size) { |
| if (size_constant->kind == Constant::Kind::kIdentifier) { |
| auto identifier_constant = static_cast<IdentifierConstant*>(size_constant); |
| auto target = identifier_constant->reference.resolved().element(); |
| if (target->kind == Element::Kind::kBuiltin && |
| static_cast<Builtin*>(target)->id == Builtin::Identity::kMax) { |
| size_constant->ResolveTo(Size::Max().Clone(), |
| typespace()->GetPrimitiveType(types::PrimitiveSubtype::kUint32)); |
| } |
| } |
| if (!size_constant->IsResolved()) { |
| if (!ResolveConstant(size_constant, |
| typespace()->GetPrimitiveType(types::PrimitiveSubtype::kUint32))) { |
| return false; |
| } |
| } |
| if (out_size) { |
| *out_size = static_cast<const Size*>(&size_constant->Value()); |
| } |
| return true; |
| } |
| |
| template <typename DeclType, typename MemberType> |
| bool CompileStep::ValidateMembers(DeclType* decl, MemberValidator<MemberType> validator) { |
| ZX_ASSERT(decl != nullptr); |
| auto checkpoint = reporter()->Checkpoint(); |
| |
| constexpr const char* decl_type = std::is_same_v<DeclType, Enum> ? "enum" : "bits"; |
| |
| Scope<std::string> name_scope; |
| Scope<MemberType> value_scope; |
| for (const auto& member : decl->members) { |
| ZX_ASSERT_MSG(member.value != nullptr, "member value is null"); |
| |
| // Check that the member identifier hasn't been used yet |
| const auto original_name = member.name.data(); |
| const auto canonical_name = utils::canonicalize(original_name); |
| const auto name_result = name_scope.Insert(canonical_name, member.name); |
| if (!name_result.ok()) { |
| const auto previous_span = name_result.previous_occurrence(); |
| // We can log the error and then continue validating for other issues in the decl |
| if (original_name == name_result.previous_occurrence().data()) { |
| Fail(ErrDuplicateMemberName, member.name, decl_type, original_name, previous_span); |
| } else { |
| Fail(ErrDuplicateMemberNameCanonical, member.name, decl_type, original_name, |
| previous_span.data(), previous_span, canonical_name); |
| } |
| } |
| |
| if (!ResolveConstant(member.value.get(), decl->subtype_ctor->type)) { |
| Fail(ErrCouldNotResolveMember, member.name, decl_type); |
| continue; |
| } |
| |
| MemberType value = |
| static_cast<const NumericConstantValue<MemberType>&>(member.value->Value()).value; |
| const auto value_result = value_scope.Insert(value, member.name); |
| if (!value_result.ok()) { |
| const auto previous_span = value_result.previous_occurrence(); |
| // We can log the error and then continue validating other members for other bugs |
| Fail(ErrDuplicateMemberValue, member.name, decl_type, original_name, previous_span.data(), |
| previous_span); |
| } |
| |
| auto err = validator(value, member.attributes.get(), member.name); |
| if (err) { |
| Report(std::move(err)); |
| } |
| } |
| |
| return checkpoint.NoNewErrors(); |
| } |
| |
| template <typename T> |
| static bool IsPowerOfTwo(T t) { |
| if (t == 0) { |
| return false; |
| } |
| if ((t & (t - 1)) != 0) { |
| return false; |
| } |
| return true; |
| } |
| |
| template <typename MemberType> |
| bool CompileStep::ValidateBitsMembersAndCalcMask(Bits* bits_decl, MemberType* out_mask) { |
| static_assert(std::is_unsigned<MemberType>::value && !std::is_same<MemberType, bool>::value, |
| "bits members must be an unsigned integral type"); |
| // Each bits member must be a power of two. |
| MemberType mask = 0u; |
| auto validator = [&mask](MemberType member, const AttributeList*, |
| SourceSpan span) -> std::unique_ptr<Diagnostic> { |
| if (!IsPowerOfTwo(member)) { |
| return Diagnostic::MakeError(ErrBitsMemberMustBePowerOfTwo, span); |
| } |
| mask |= member; |
| return nullptr; |
| }; |
| if (!ValidateMembers<Bits, MemberType>(bits_decl, validator)) { |
| return false; |
| } |
| *out_mask = mask; |
| return true; |
| } |
| |
| template <typename MemberType> |
| bool CompileStep::ValidateEnumMembersAndCalcUnknownValue(Enum* enum_decl, |
| MemberType* out_unknown_value) { |
| static_assert(std::is_integral<MemberType>::value && !std::is_same<MemberType, bool>::value, |
| "enum members must be an integral type"); |
| |
| const auto default_unknown_value = std::numeric_limits<MemberType>::max(); |
| std::optional<MemberType> explicit_unknown_value; |
| for (const auto& member : enum_decl->members) { |
| if (!ResolveConstant(member.value.get(), enum_decl->subtype_ctor->type)) { |
| // ValidateMembers will resolve each member and report errors. |
| continue; |
| } |
| if (member.attributes->Get("unknown") != nullptr) { |
| if (explicit_unknown_value.has_value()) { |
| return Fail(ErrUnknownAttributeOnMultipleEnumMembers, member.name); |
| } |
| explicit_unknown_value = |
| static_cast<const NumericConstantValue<MemberType>&>(member.value->Value()).value; |
| } |
| } |
| |
| auto validator = [enum_decl, &explicit_unknown_value]( |
| MemberType member, const AttributeList* attributes, |
| SourceSpan span) -> std::unique_ptr<Diagnostic> { |
| switch (enum_decl->strictness) { |
| case types::Strictness::kStrict: |
| if (attributes->Get("unknown") != nullptr) { |
| return Diagnostic::MakeError(ErrUnknownAttributeOnStrictEnumMember, span); |
| } |
| return nullptr; |
| case types::Strictness::kFlexible: |
| if (member == default_unknown_value && !explicit_unknown_value.has_value()) { |
| return Diagnostic::MakeError(ErrFlexibleEnumMemberWithMaxValue, span, |
| std::to_string(default_unknown_value)); |
| } |
| return nullptr; |
| } |
| }; |
| if (!ValidateMembers<Enum, MemberType>(enum_decl, validator)) { |
| return false; |
| } |
| *out_unknown_value = explicit_unknown_value.value_or(default_unknown_value); |
| return true; |
| } |
| |
| } // namespace fidl::flat |