| // Copyright 2018 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_ast.h" |
| |
| #include <assert.h> |
| #include <stdio.h> |
| |
| #include <algorithm> |
| #include <iostream> |
| #include <sstream> |
| #include <utility> |
| |
| #include "fidl/attributes.h" |
| #include "fidl/diagnostic_types.h" |
| #include "fidl/diagnostics.h" |
| #include "fidl/experimental_flags.h" |
| #include "fidl/flat/types.h" |
| #include "fidl/lexer.h" |
| #include "fidl/names.h" |
| #include "fidl/ordinals.h" |
| #include "fidl/parser.h" |
| #include "fidl/raw_ast.h" |
| #include "fidl/types.h" |
| #include "fidl/utils.h" |
| |
| namespace fidl { |
| namespace flat { |
| |
| using namespace diagnostics; |
| |
| 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 { |
| 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); |
| } else { |
| 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()), result_(types::Resourceness::kValue) {} |
| |
| ~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_; |
| }; |
| |
| // A helper class to track when a Decl is compiling and compiled. |
| class Compiling { |
| public: |
| explicit Compiling(Decl* decl) : decl_(decl) { decl_->compiling = true; } |
| |
| ~Compiling() { |
| decl_->compiling = false; |
| decl_->compiled = true; |
| } |
| |
| private: |
| Decl* decl_; |
| }; |
| |
| template <typename T> |
| std::unique_ptr<Diagnostic> ValidateUnknownConstraints(const Decl& decl, |
| types::Strictness decl_strictness, |
| const std::vector<const T*>* members) { |
| if (!members) |
| return nullptr; |
| |
| const bool is_transitional = decl.HasAttribute("Transitional"); |
| |
| const bool is_strict = [&] { |
| switch (decl_strictness) { |
| case types::Strictness::kStrict: |
| return true; |
| case types::Strictness::kFlexible: |
| return false; |
| } |
| }(); |
| |
| bool found_member = false; |
| for (const auto* member : *members) { |
| const bool has_unknown = member->attributes && member->attributes->HasAttribute("Unknown"); |
| if (!has_unknown) |
| continue; |
| |
| if (is_strict && !is_transitional) { |
| return Reporter::MakeError(ErrUnknownAttributeOnInvalidType, member->name); |
| } |
| |
| if (found_member) { |
| return Reporter::MakeError(ErrUnknownAttributeOnMultipleMembers, member->name); |
| } |
| |
| found_member = true; |
| } |
| |
| return nullptr; |
| } |
| |
| } // namespace |
| |
| TypeConstructorPtr GetTypeCtorAsPtr(const TypeConstructor& type_ctor) { |
| return std::visit(fidl::utils::matchers{ |
| [](const std::unique_ptr<TypeConstructorOld>& e) -> TypeConstructorPtr { |
| return e.get(); |
| }, |
| [](const std::unique_ptr<TypeConstructorNew>& e) -> TypeConstructorPtr { |
| return e.get(); |
| }, |
| }, |
| type_ctor); |
| } |
| |
| uint32_t PrimitiveType::SubtypeSize(types::PrimitiveSubtype subtype) { |
| switch (subtype) { |
| case types::PrimitiveSubtype::kBool: |
| case types::PrimitiveSubtype::kInt8: |
| case types::PrimitiveSubtype::kUint8: |
| return 1u; |
| |
| case types::PrimitiveSubtype::kInt16: |
| case types::PrimitiveSubtype::kUint16: |
| return 2u; |
| |
| case types::PrimitiveSubtype::kFloat32: |
| case types::PrimitiveSubtype::kInt32: |
| case types::PrimitiveSubtype::kUint32: |
| return 4u; |
| |
| case types::PrimitiveSubtype::kFloat64: |
| case types::PrimitiveSubtype::kInt64: |
| case types::PrimitiveSubtype::kUint64: |
| return 8u; |
| } |
| } |
| |
| bool Decl::HasAttribute(std::string_view name) const { |
| if (!attributes) |
| return false; |
| return attributes->HasAttribute(std::string(name)); |
| } |
| |
| std::string_view Decl::GetAttribute(std::string_view name) const { |
| if (!attributes) |
| return std::string_view(); |
| for (const auto& attribute : attributes->attributes) { |
| if (attribute.name == name) { |
| if (attribute.value != "") { |
| const auto& value = attribute.value; |
| return std::string_view(value.data(), value.size()); |
| } |
| // Don't search for another attribute with the same name. |
| break; |
| } |
| } |
| return std::string_view(); |
| } |
| |
| std::string Decl::GetName() const { return std::string(name.decl_name()); } |
| |
| const std::set<std::pair<std::string, std::string_view>> allowed_simple_unions{{ |
| {"fuchsia.io", "NodeInfo"}, |
| }}; |
| |
| bool IsSimple(const Type* type, Reporter* reporter) { |
| auto depth = fidl::OldWireFormatDepth(type); |
| switch (type->kind) { |
| case Type::Kind::kVector: { |
| auto vector_type = static_cast<const VectorType*>(type); |
| if (*vector_type->element_count == Size::Max()) |
| return false; |
| switch (vector_type->element_type->kind) { |
| case Type::Kind::kHandle: |
| case Type::Kind::kRequestHandle: |
| case Type::Kind::kPrimitive: |
| return true; |
| case Type::Kind::kArray: |
| case Type::Kind::kVector: |
| case Type::Kind::kString: |
| case Type::Kind::kIdentifier: |
| return false; |
| } |
| } |
| case Type::Kind::kString: { |
| auto string_type = static_cast<const StringType*>(type); |
| return *string_type->max_size < Size::Max(); |
| } |
| case Type::Kind::kArray: |
| case Type::Kind::kHandle: |
| case Type::Kind::kRequestHandle: |
| case Type::Kind::kPrimitive: |
| return depth == 0u; |
| case Type::Kind::kIdentifier: { |
| auto identifier_type = static_cast<const IdentifierType*>(type); |
| if (identifier_type->type_decl->kind == Decl::Kind::kUnion) { |
| auto union_name = std::make_pair<const std::string&, const std::string_view&>( |
| LibraryName(identifier_type->name.library(), "."), identifier_type->name.decl_name()); |
| if (allowed_simple_unions.find(union_name) == allowed_simple_unions.end()) { |
| // Any unions not in the allow-list are treated as non-simple. |
| reporter->Report(ErrUnionCannotBeSimple, identifier_type->name.span(), |
| identifier_type->name); |
| return false; |
| } |
| } |
| switch (identifier_type->nullability) { |
| case types::Nullability::kNullable: |
| // If the identifier is nullable, then we can handle a depth of 1 |
| // because the secondary object is directly accessible. |
| return depth <= 1u; |
| case types::Nullability::kNonnullable: |
| return depth == 0u; |
| } |
| } |
| } |
| } |
| |
| FieldShape Struct::Member::fieldshape(WireFormat wire_format) const { |
| return FieldShape(*this, wire_format); |
| } |
| |
| FieldShape Table::Member::Used::fieldshape(WireFormat wire_format) const { |
| return FieldShape(*this, wire_format); |
| } |
| |
| FieldShape Union::Member::Used::fieldshape(WireFormat wire_format) const { |
| return FieldShape(*this, wire_format); |
| } |
| |
| std::vector<std::reference_wrapper<const Union::Member>> Union::MembersSortedByXUnionOrdinal() |
| const { |
| std::vector<std::reference_wrapper<const Member>> sorted_members(members.cbegin(), |
| members.cend()); |
| std::sort(sorted_members.begin(), sorted_members.end(), |
| [](const auto& member1, const auto& member2) { |
| return member1.get().ordinal->value < member2.get().ordinal->value; |
| }); |
| return sorted_members; |
| } |
| |
| bool Typespace::Create(const LibraryMediator& lib, const flat::Name& name, |
| const std::unique_ptr<TypeConstructorOld>& maybe_arg_type_ctor, |
| const std::optional<Name>& handle_subtype_identifier, |
| const std::unique_ptr<Constant>& handle_rights, |
| const std::unique_ptr<Constant>& maybe_size, types::Nullability nullability, |
| const Type** out_type, LayoutInvocation* out_params) { |
| std::unique_ptr<Type> type; |
| if (!CreateNotOwned(lib, name, maybe_arg_type_ctor, handle_subtype_identifier, handle_rights, |
| maybe_size, nullability, &type, out_params)) |
| return false; |
| types_.push_back(std::move(type)); |
| *out_type = types_.back().get(); |
| return true; |
| } |
| |
| bool Typespace::Create(const LibraryMediator& lib, const flat::Name& name, |
| const std::unique_ptr<LayoutParameterList>& parameters, |
| const std::unique_ptr<TypeConstraints>& constraints, const Type** out_type, |
| LayoutInvocation* out_params) { |
| std::unique_ptr<Type> type; |
| if (!CreateNotOwned(lib, name, parameters, constraints, &type, out_params)) |
| return false; |
| types_.push_back(std::move(type)); |
| *out_type = types_.back().get(); |
| return true; |
| } |
| |
| bool Typespace::CreateNotOwned(const LibraryMediator& lib, const flat::Name& name, |
| const std::unique_ptr<TypeConstructorOld>& maybe_arg_type_ctor, |
| const std::optional<Name>& handle_subtype_identifier, |
| const std::unique_ptr<Constant>& handle_rights, |
| const std::unique_ptr<Constant>& maybe_size, |
| types::Nullability nullability, std::unique_ptr<Type>* out_type, |
| LayoutInvocation* out_params) { |
| // TODO(pascallouis): lookup whether we've already created the type, and |
| // return it rather than create a new one. Lookup must be by name, |
| // arg_type, size, and nullability. |
| |
| auto type_template = LookupTemplate(name); |
| if (type_template == nullptr) { |
| reporter_->Report(ErrUnknownType, name.span(), name); |
| return false; |
| } |
| return type_template->Create(lib, |
| {.name = name, |
| .maybe_arg_type_ctor = maybe_arg_type_ctor, |
| .handle_subtype_identifier = handle_subtype_identifier, |
| .handle_rights = handle_rights, |
| .maybe_size = maybe_size, |
| .nullability = nullability}, |
| out_type, out_params); |
| } |
| |
| bool Typespace::CreateNotOwned(const LibraryMediator& lib, const flat::Name& name, |
| const std::unique_ptr<LayoutParameterList>& parameters, |
| const std::unique_ptr<TypeConstraints>& constraints, |
| std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) { |
| // TODO(pascallouis): lookup whether we've already created the type, and |
| // return it rather than create a new one. Lookup must be by name, |
| // arg_type, size, and nullability. |
| |
| auto type_template = LookupTemplate(name); |
| if (type_template == nullptr) { |
| reporter_->Report(ErrUnknownType, name.span(), name); |
| return false; |
| } |
| return type_template->Create(lib, |
| {.name = name, .parameters = parameters, .constraints = constraints}, |
| out_type, out_params); |
| } |
| |
| void Typespace::AddTemplate(std::unique_ptr<TypeTemplate> type_template) { |
| templates_.emplace(type_template->name(), std::move(type_template)); |
| } |
| |
| const TypeTemplate* Typespace::LookupTemplate(const flat::Name& name) const { |
| auto global_name = Name::Key(nullptr, name.decl_name()); |
| if (auto iter = templates_.find(global_name); iter != templates_.end()) { |
| return iter->second.get(); |
| } |
| |
| if (auto iter = templates_.find(name); iter != templates_.end()) { |
| return iter->second.get(); |
| } |
| |
| return nullptr; |
| } |
| |
| template <typename... Args> |
| bool TypeTemplate::Fail(const ErrorDef<const TypeTemplate*, Args...>& err, |
| const std::optional<SourceSpan>& span, const Args&... args) const { |
| reporter_->Report(err, span, this, args...); |
| return false; |
| } |
| |
| template <typename... Args> |
| bool TypeTemplate::Fail(const ErrorDef<Args...>& err, const Args&... args) const { |
| reporter_->Report(err, args...); |
| return false; |
| } |
| |
| bool TypeTemplate::ResolveOldSyntaxArgs(const LibraryMediator& lib, |
| const OldSyntaxParamsAndConstraints& unresolved_args, |
| std::unique_ptr<CreateInvocation>* out_args, |
| LayoutInvocation* out_params) const { |
| const Type* maybe_arg_type = nullptr; |
| if (unresolved_args.maybe_arg_type_ctor != nullptr) { |
| if (!lib.ResolveType(unresolved_args.maybe_arg_type_ctor.get())) |
| return false; |
| maybe_arg_type = unresolved_args.maybe_arg_type_ctor->type; |
| out_params->element_type_resolved = maybe_arg_type; |
| out_params->element_type_raw = unresolved_args.maybe_arg_type_ctor.get(); |
| } |
| |
| const Size* size = nullptr; |
| if (unresolved_args.maybe_size != nullptr) { |
| if (!lib.ResolveSizeBound(unresolved_args.maybe_size.get(), &size)) { |
| reporter_->Report(ErrCouldNotParseSizeBound, unresolved_args.maybe_size->span); |
| return false; |
| } |
| out_params->size_resolved = size; |
| out_params->size_raw = unresolved_args.maybe_size.get(); |
| } |
| |
| Resource* handle_resource_decl = nullptr; |
| if (unresolved_args.handle_subtype_identifier || unresolved_args.handle_rights) { |
| if (!GetResource(lib, unresolved_args.name, &handle_resource_decl)) |
| return false; |
| // TODO(fxbug.dev/74909): Once bare handles are disallowed we won't need to |
| // throw this error "at use time" anymore and can move it back into |
| // HandleTypeTemplate::GetResource. At that point, we can also add back some |
| // more helpful data to the error (like the offending type constructor's name |
| // and span) which we don't have access to here. |
| if (!handle_resource_decl) |
| return Fail(ErrHandleSubtypeNotResource); |
| } |
| |
| std::optional<uint32_t> obj_type = std::nullopt; |
| std::optional<types::HandleSubtype> handle_subtype = std::nullopt; |
| if (unresolved_args.handle_subtype_identifier) { |
| const Name& name = *unresolved_args.handle_subtype_identifier; |
| // the new path uses Constants, the old path uses Names; convert the Name |
| // to a Constant here to share code paths. |
| auto into_constant = static_cast<std::unique_ptr<Constant>>( |
| std::make_unique<IdentifierConstant>(name, *name.span())); |
| assert(handle_resource_decl); |
| uint32_t raw_obj_type; |
| if (!lib.ResolveAsHandleSubtype(handle_resource_decl, into_constant, &raw_obj_type)) |
| return Fail(ErrCouldNotResolveHandleSubtype, name); |
| obj_type = raw_obj_type; |
| handle_subtype = types::HandleSubtype(raw_obj_type); |
| out_params->subtype_resolved = raw_obj_type; |
| } |
| |
| const HandleRights* rights = nullptr; |
| if (unresolved_args.handle_rights) { |
| if (!lib.ResolveAsHandleRights(handle_resource_decl, unresolved_args.handle_rights.get(), |
| &rights)) |
| return Fail(ErrCouldNotResolveHandleRights); |
| out_params->rights_resolved = rights; |
| out_params->rights_raw = unresolved_args.handle_rights.get(); |
| } |
| |
| // No work needed for nullability - in the old syntax there's nothing to resolve |
| // because ? always indicates nullable. |
| out_params->nullability = unresolved_args.nullability; |
| |
| *out_args = |
| std::make_unique<CreateInvocation>(unresolved_args.name, maybe_arg_type, obj_type, |
| handle_subtype, rights, size, unresolved_args.nullability); |
| return true; |
| } |
| |
| bool TypeTemplate::GetResource(const LibraryMediator& lib, const Name& name, |
| Resource** out_resource) const { |
| assert(false && |
| "Only the HandleTypeTemplate should ever need to do this, because of hardcoding in the " |
| "parser"); |
| __builtin_unreachable(); |
| } |
| |
| class PrimitiveTypeTemplate : public TypeTemplate { |
| public: |
| PrimitiveTypeTemplate(Typespace* typespace, Reporter* reporter, const std::string& name, |
| types::PrimitiveSubtype subtype) |
| : TypeTemplate(Name::CreateIntrinsic(name), typespace, reporter), subtype_(subtype) {} |
| |
| bool Create(const LibraryMediator& lib, const NewSyntaxParamsAndConstraints& unresolved_args, |
| std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override { |
| size_t num_params = unresolved_args.parameters->items.size(); |
| if (num_params != 0) { |
| return Fail(ErrWrongNumberOfLayoutParameters, unresolved_args.parameters->span, size_t(0), |
| num_params); |
| } |
| |
| PrimitiveType type(name_, subtype_); |
| return type.ApplyConstraints(lib, *unresolved_args.constraints, this, out_type, out_params); |
| } |
| |
| bool Create(const LibraryMediator& lib, const OldSyntaxParamsAndConstraints& unresolved_args, |
| std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override { |
| std::unique_ptr<CreateInvocation> args; |
| if (!ResolveOldSyntaxArgs(lib, unresolved_args, &args, out_params)) |
| return false; |
| |
| assert(!args->handle_subtype); |
| assert(!args->handle_rights); |
| |
| if (args->arg_type != nullptr) |
| return Fail(ErrCannotBeParameterized, args->name.span()); |
| |
| PrimitiveType type(name_, subtype_); |
| return type.ApplySomeLayoutParametersAndConstraints(lib, *args, this, out_type, out_params); |
| } |
| |
| private: |
| const types::PrimitiveSubtype subtype_; |
| }; |
| |
| bool PrimitiveType::ApplyConstraints(const flat::LibraryMediator& lib, |
| const TypeConstraints& constraints, const TypeTemplate* layout, |
| std::unique_ptr<Type>* out_type, |
| LayoutInvocation* out_params) const { |
| size_t num_constraints = constraints.items.size(); |
| // assume that a lone constraint was an attempt at specifying `optional` and provide a more |
| // specific error |
| // TOOD(fxbug.dev/75112): actually try to compile the optional constraint |
| if (num_constraints == 1) |
| return lib.Fail(ErrCannotBeNullable, constraints.items[0]->span, layout); |
| if (num_constraints > 1) |
| return lib.Fail(ErrTooManyConstraints, constraints.span, layout, size_t(0), num_constraints); |
| *out_type = std::make_unique<PrimitiveType>(name, subtype); |
| return true; |
| } |
| |
| bool PrimitiveType::ApplySomeLayoutParametersAndConstraints( |
| const LibraryMediator& lib, const CreateInvocation& create_invocation, |
| const TypeTemplate* layout, std::unique_ptr<Type>* out_type, |
| LayoutInvocation* out_params) const { |
| if (create_invocation.size != nullptr) |
| return lib.Fail(ErrCannotHaveSize, create_invocation.name.span(), layout); |
| if (create_invocation.nullability == types::Nullability::kNullable) |
| return lib.Fail(ErrCannotBeNullable, create_invocation.name.span(), layout); |
| *out_type = std::make_unique<PrimitiveType>(name, subtype); |
| return true; |
| } |
| |
| class ArrayTypeTemplate final : public TypeTemplate { |
| public: |
| ArrayTypeTemplate(Typespace* typespace, Reporter* reporter) |
| : TypeTemplate(Name::CreateIntrinsic("array"), typespace, reporter) {} |
| |
| bool Create(const LibraryMediator& lib, const NewSyntaxParamsAndConstraints& unresolved_args, |
| std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override { |
| size_t num_params = unresolved_args.parameters->items.size(); |
| size_t expected_params = 2; |
| if (num_params != expected_params) { |
| return Fail(ErrWrongNumberOfLayoutParameters, unresolved_args.parameters->span, |
| expected_params, num_params); |
| } |
| |
| const Type* element_type = nullptr; |
| if (!lib.ResolveParamAsType(this, unresolved_args.parameters->items[0], &element_type)) |
| return false; |
| out_params->element_type_resolved = element_type; |
| out_params->element_type_raw = unresolved_args.parameters->items[0]->AsTypeCtor(); |
| |
| const Size* size = nullptr; |
| if (!lib.ResolveParamAsSize(this, unresolved_args.parameters->items[1], &size)) |
| return false; |
| out_params->size_resolved = size; |
| out_params->size_raw = unresolved_args.parameters->items[1]->AsConstant(); |
| |
| ArrayType type(name_, element_type, size); |
| return type.ApplyConstraints(lib, *unresolved_args.constraints, this, out_type, out_params); |
| } |
| |
| bool Create(const LibraryMediator& lib, const OldSyntaxParamsAndConstraints& unresolved_args, |
| std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override { |
| std::unique_ptr<CreateInvocation> args; |
| if (!ResolveOldSyntaxArgs(lib, unresolved_args, &args, out_params)) |
| return false; |
| |
| assert(!args->handle_subtype); |
| assert(!args->handle_rights); |
| |
| if (args->arg_type == nullptr) |
| return Fail(ErrMustBeParameterized, args->name.span()); |
| if (args->size == nullptr) |
| return Fail(ErrMustHaveSize, args->name.span()); |
| if (args->size->value == 0) |
| return Fail(ErrMustHaveNonZeroSize, args->name.span()); |
| |
| ArrayType type(name_, args->arg_type, args->size); |
| return type.ApplySomeLayoutParametersAndConstraints(lib, *args, this, out_type, out_params); |
| } |
| }; |
| |
| bool ArrayType::ApplyConstraints(const flat::LibraryMediator& lib, |
| const TypeConstraints& constraints, const TypeTemplate* layout, |
| std::unique_ptr<Type>* out_type, |
| LayoutInvocation* out_params) const { |
| size_t num_constraints = constraints.items.size(); |
| // assume that a lone constraint was an attempt at specifying `optional` and provide a more |
| // specific error |
| // TOOD(fxbug.dev/75112): actually try to compile the optional constraint |
| if (num_constraints == 1) |
| return lib.Fail(ErrCannotBeNullable, constraints.items[0]->span, layout); |
| if (num_constraints > 1) |
| return lib.Fail(ErrTooManyConstraints, constraints.span, layout, size_t(0), num_constraints); |
| *out_type = std::make_unique<ArrayType>(name, element_type, element_count); |
| return true; |
| } |
| |
| bool ArrayType::ApplySomeLayoutParametersAndConstraints(const LibraryMediator& lib, |
| const CreateInvocation& create_invocation, |
| const TypeTemplate* layout, |
| std::unique_ptr<Type>* out_type, |
| LayoutInvocation* out_params) const { |
| if (create_invocation.size && create_invocation.size != element_count) |
| return lib.Fail(ErrCannotParameterizeAlias, create_invocation.name.span(), layout); |
| if (create_invocation.nullability == types::Nullability::kNullable) |
| return lib.Fail(ErrCannotBeNullable, create_invocation.name.span(), layout); |
| *out_type = std::make_unique<ArrayType>(name, element_type, element_count); |
| return true; |
| } |
| |
| class BytesTypeTemplate final : public TypeTemplate { |
| public: |
| BytesTypeTemplate(Typespace* typespace, Reporter* reporter) |
| : TypeTemplate(Name::CreateIntrinsic("vector"), typespace, reporter), |
| uint8_type_(kUint8Type) {} |
| |
| bool Create(const LibraryMediator& lib, const NewSyntaxParamsAndConstraints& unresolved_args, |
| std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override { |
| size_t num_params = unresolved_args.parameters->items.size(); |
| if (num_params != 0) { |
| return Fail(ErrWrongNumberOfLayoutParameters, unresolved_args.parameters->span, size_t(0), |
| num_params); |
| } |
| |
| VectorType type(name_, &uint8_type_); |
| return type.ApplyConstraints(lib, *unresolved_args.constraints, this, out_type, out_params); |
| } |
| |
| bool Create(const LibraryMediator& lib, const OldSyntaxParamsAndConstraints& unresolved_args, |
| std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override { |
| std::unique_ptr<CreateInvocation> args; |
| if (!ResolveOldSyntaxArgs(lib, unresolved_args, &args, out_params)) |
| return false; |
| |
| assert(!args->handle_subtype); |
| assert(!args->handle_rights); |
| |
| if (args->arg_type != nullptr) |
| return Fail(ErrCannotBeParameterized, args->name.span()); |
| |
| VectorType type(name_, &uint8_type_); |
| return type.ApplySomeLayoutParametersAndConstraints(lib, *args, this, out_type, out_params); |
| } |
| |
| private: |
| // TODO(fxbug.dev/7724): Remove when canonicalizing types. |
| const Name kUint8TypeName = Name::CreateIntrinsic("uint8"); |
| const PrimitiveType kUint8Type = PrimitiveType(kUint8TypeName, types::PrimitiveSubtype::kUint8); |
| |
| const PrimitiveType uint8_type_; |
| }; |
| |
| bool VectorBaseType::ResolveSizeAndNullability(const LibraryMediator& lib, |
| const TypeConstraints& constraints, |
| const TypeTemplate* layout, |
| LayoutInvocation* out_params) { |
| size_t num_constraints = constraints.items.size(); |
| if (num_constraints == 1) { |
| LibraryMediator::ResolvedConstraint resolved; |
| if (!lib.ResolveConstraintAs( |
| constraints.items[0], |
| {LibraryMediator::ConstraintKind::kSize, LibraryMediator::ConstraintKind::kNullability}, |
| nullptr /* resource_decl */, &resolved)) |
| return lib.Fail(ErrUnexpectedConstraint, constraints.items[0]->span, layout); |
| switch (resolved.kind) { |
| case LibraryMediator::ConstraintKind::kSize: |
| out_params->size_resolved = resolved.value.size; |
| out_params->size_raw = constraints.items[0].get(); |
| break; |
| case LibraryMediator::ConstraintKind::kNullability: |
| out_params->nullability = types::Nullability::kNullable; |
| break; |
| default: |
| assert(false && "Compiler bug: resolved to wrong constraint kind"); |
| } |
| } else if (num_constraints == 2) { |
| // first constraint must be size, followed by optional |
| if (!lib.ResolveSizeBound(constraints.items[0].get(), &out_params->size_resolved)) |
| return lib.Fail(ErrCouldNotParseSizeBound, std::nullopt); |
| out_params->size_raw = constraints.items[0].get(); |
| if (!lib.ResolveAsOptional(constraints.items[1].get())) { |
| return lib.Fail(ErrUnexpectedConstraint, constraints.items[1]->span, layout); |
| } |
| out_params->nullability = types::Nullability::kNullable; |
| } else if (num_constraints >= 3) { |
| return lib.Fail(ErrTooManyConstraints, constraints.span, layout, size_t(2), num_constraints); |
| } |
| return true; |
| } |
| |
| const Size VectorBaseType::kMaxSize = Size::Max(); |
| |
| class VectorTypeTemplate final : public TypeTemplate { |
| public: |
| VectorTypeTemplate(Typespace* typespace, Reporter* reporter) |
| : TypeTemplate(Name::CreateIntrinsic("vector"), typespace, reporter) {} |
| |
| bool Create(const LibraryMediator& lib, const NewSyntaxParamsAndConstraints& unresolved_args, |
| std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override { |
| size_t num_params = unresolved_args.parameters->items.size(); |
| if (num_params != 1) { |
| return Fail(ErrWrongNumberOfLayoutParameters, unresolved_args.parameters->span, size_t(1), |
| num_params); |
| } |
| |
| const Type* element_type = nullptr; |
| if (!lib.ResolveParamAsType(this, unresolved_args.parameters->items[0], &element_type)) |
| return false; |
| out_params->element_type_resolved = element_type; |
| out_params->element_type_raw = unresolved_args.parameters->items[0]->AsTypeCtor(); |
| |
| VectorType type(name_, element_type); |
| return type.ApplyConstraints(lib, *unresolved_args.constraints, this, out_type, out_params); |
| } |
| |
| bool Create(const LibraryMediator& lib, const OldSyntaxParamsAndConstraints& unresolved_args, |
| std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override { |
| std::unique_ptr<CreateInvocation> args; |
| if (!ResolveOldSyntaxArgs(lib, unresolved_args, &args, out_params)) |
| return false; |
| |
| assert(!args->handle_subtype); |
| assert(!args->handle_rights); |
| |
| if (args->arg_type == nullptr) |
| return Fail(ErrMustBeParameterized, args->name.span()); |
| VectorType type(name_, args->arg_type); |
| return type.ApplySomeLayoutParametersAndConstraints(lib, *args, this, out_type, out_params); |
| } |
| }; |
| |
| bool VectorType::ApplyConstraints(const flat::LibraryMediator& lib, |
| const TypeConstraints& constraints, const TypeTemplate* layout, |
| std::unique_ptr<Type>* out_type, |
| LayoutInvocation* out_params) const { |
| if (!ResolveSizeAndNullability(lib, constraints, layout, out_params)) |
| return false; |
| |
| bool is_already_nullable = nullability == types::Nullability::kNullable; |
| bool is_nullability_applied = out_params->nullability == types::Nullability::kNullable; |
| if (is_already_nullable && is_nullability_applied) |
| return lib.Fail(ErrCannotIndicateNullabilityTwice, std::nullopt, layout); |
| auto merged_nullability = is_already_nullable || is_nullability_applied |
| ? types::Nullability::kNullable |
| : types::Nullability::kNonnullable; |
| |
| if (element_count != &kMaxSize && out_params->size_resolved) |
| return lib.Fail(ErrCannotBoundTwice, std::nullopt, layout); |
| auto merged_size = out_params->size_resolved ? out_params->size_resolved : element_count; |
| |
| *out_type = std::make_unique<VectorType>(name, element_type, merged_size, merged_nullability); |
| return true; |
| } |
| |
| bool VectorType::ApplySomeLayoutParametersAndConstraints(const LibraryMediator& lib, |
| const CreateInvocation& create_invocation, |
| const TypeTemplate* layout, |
| std::unique_ptr<Type>* out_type, |
| LayoutInvocation* out_params) const { |
| bool is_already_nullable = nullability == types::Nullability::kNullable; |
| bool is_nullability_applied = create_invocation.nullability == types::Nullability::kNullable; |
| if (is_already_nullable && is_nullability_applied) |
| return lib.Fail(ErrCannotIndicateNullabilityTwice, create_invocation.name.span(), layout); |
| auto merged_nullability = is_already_nullable || is_nullability_applied |
| ? types::Nullability::kNullable |
| : types::Nullability::kNonnullable; |
| |
| // TODO(fxbug.dev/74193): take the smaller bound |
| if (element_count != &kMaxSize && create_invocation.size) { |
| return lib.Fail(ErrCannotBoundTwice, std::nullopt, layout); |
| } |
| auto merged_size = create_invocation.size ? create_invocation.size : element_count; |
| |
| *out_type = std::make_unique<VectorType>(name, element_type, merged_size, merged_nullability); |
| return true; |
| } |
| |
| class StringTypeTemplate final : public TypeTemplate { |
| public: |
| StringTypeTemplate(Typespace* typespace, Reporter* reporter) |
| : TypeTemplate(Name::CreateIntrinsic("string"), typespace, reporter) {} |
| |
| bool Create(const LibraryMediator& lib, const NewSyntaxParamsAndConstraints& unresolved_args, |
| std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override { |
| size_t num_params = unresolved_args.parameters->items.size(); |
| if (num_params != 0) { |
| return Fail(ErrWrongNumberOfLayoutParameters, unresolved_args.parameters->span, size_t(0), |
| num_params); |
| } |
| |
| StringType type(name_); |
| return type.ApplyConstraints(lib, *unresolved_args.constraints, this, out_type, out_params); |
| } |
| |
| bool Create(const LibraryMediator& lib, const OldSyntaxParamsAndConstraints& unresolved_args, |
| std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override { |
| std::unique_ptr<CreateInvocation> args; |
| if (!ResolveOldSyntaxArgs(lib, unresolved_args, &args, out_params)) |
| return false; |
| |
| assert(!args->handle_subtype); |
| assert(!args->handle_rights); |
| |
| if (args->arg_type != nullptr) |
| return Fail(ErrCannotBeParameterized, args->name.span()); |
| |
| StringType type(name_); |
| return type.ApplySomeLayoutParametersAndConstraints(lib, *args, this, out_type, out_params); |
| } |
| }; |
| |
| bool StringType::ApplyConstraints(const flat::LibraryMediator& lib, |
| const TypeConstraints& constraints, const TypeTemplate* layout, |
| std::unique_ptr<Type>* out_type, |
| LayoutInvocation* out_params) const { |
| if (!ResolveSizeAndNullability(lib, constraints, layout, out_params)) |
| return false; |
| |
| bool is_already_nullable = nullability == types::Nullability::kNullable; |
| bool is_nullability_applied = out_params->nullability == types::Nullability::kNullable; |
| if (is_already_nullable && is_nullability_applied) |
| return lib.Fail(ErrCannotIndicateNullabilityTwice, std::nullopt, layout); |
| auto merged_nullability = is_already_nullable || is_nullability_applied |
| ? types::Nullability::kNullable |
| : types::Nullability::kNonnullable; |
| |
| if (max_size != &kMaxSize && out_params->size_resolved) |
| return lib.Fail(ErrCannotBoundTwice, std::nullopt, layout); |
| auto merged_size = out_params->size_resolved ? out_params->size_resolved : max_size; |
| |
| *out_type = std::make_unique<StringType>(name, merged_size, merged_nullability); |
| return true; |
| } |
| |
| bool StringType::ApplySomeLayoutParametersAndConstraints(const LibraryMediator& lib, |
| const CreateInvocation& create_invocation, |
| const TypeTemplate* layout, |
| std::unique_ptr<Type>* out_type, |
| LayoutInvocation* out_params) const { |
| bool is_already_nullable = nullability == types::Nullability::kNullable; |
| bool is_nullability_applied = create_invocation.nullability == types::Nullability::kNullable; |
| if (is_already_nullable && is_nullability_applied) |
| return lib.Fail(ErrCannotIndicateNullabilityTwice, create_invocation.name.span(), layout); |
| auto merged_nullability = is_already_nullable || is_nullability_applied |
| ? types::Nullability::kNullable |
| : types::Nullability::kNonnullable; |
| |
| // Note that we don't have a way of knowing whether a size was actually specified, |
| // since unspecified sizes are always replaced with a MAX default. Assume that |
| // MAX means unspecified (this means that we would allow bounding twice if the |
| // user uses MAX both times). |
| // TODO(fxbug.dev/74193): take the smaller bound |
| if (*max_size != kMaxSize && create_invocation.size) |
| return lib.Fail(ErrCannotBoundTwice, std::nullopt, layout); |
| auto merged_size = create_invocation.size ? create_invocation.size : max_size; |
| |
| *out_type = std::make_unique<StringType>(name, merged_size, merged_nullability); |
| return true; |
| } |
| |
| class HandleTypeTemplate final : public TypeTemplate { |
| public: |
| HandleTypeTemplate(Typespace* typespace, Reporter* reporter) |
| : TypeTemplate(Name::CreateIntrinsic("handle"), typespace, reporter) {} |
| |
| // Currently we take a name as parameter, but the parser restricts this name to be |
| // something that ends in "handle". |
| // In a more general implementation, we would add such an entry at "Consume" time of |
| // the resource in question, allowing us to set a pointer to the Resource declaration |
| // on the HandleTypeTemplate itself. We can't currently do this because we don't have |
| // access to the definition of "handle" when we insert it into the root typespace, so we |
| // need to resort to looking it up and doing validation at runtime. |
| bool GetResource(const LibraryMediator& lib, const Name& name, |
| Resource** out_resource) const override { |
| Decl* handle_decl = lib.LookupDeclByName(name); |
| if (!handle_decl || handle_decl->kind != Decl::Kind::kResource) { |
| // TODO(fxbug.dev/74909): We can't error yet, because this may be a bare |
| // `handle` with no constraints applied. Leave out_resource as a nullptr |
| // for the caller to handle and break out early. |
| return true; |
| } |
| |
| auto* resource = static_cast<Resource*>(handle_decl); |
| if (!IsTypeConstructorDefined(resource->subtype_ctor) || |
| GetName(resource->subtype_ctor).full_name() != "uint32") { |
| reporter_->Report(ErrResourceMustBeUint32Derived, resource->name); |
| return false; |
| } |
| |
| *out_resource = resource; |
| return true; |
| } |
| |
| bool Create(const LibraryMediator& lib, const NewSyntaxParamsAndConstraints& args, |
| std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override { |
| size_t num_params = !args.parameters->items.empty(); |
| if (num_params) |
| return Fail(ErrWrongNumberOfLayoutParameters, args.parameters->span, size_t(0), num_params); |
| |
| Resource* handle_resource_decl = nullptr; |
| if (!GetResource(lib, args.name, &handle_resource_decl)) |
| return false; |
| |
| HandleType type(name_, handle_resource_decl); |
| return type.ApplyConstraints(lib, *args.constraints, this, out_type, out_params); |
| } |
| |
| bool Create(const LibraryMediator& lib, const OldSyntaxParamsAndConstraints& args, |
| std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override { |
| std::unique_ptr<CreateInvocation> resolved; |
| if (!ResolveOldSyntaxArgs(lib, args, &resolved, out_params)) |
| return false; |
| |
| assert(resolved->arg_type == nullptr); |
| |
| if (resolved->size != nullptr) |
| return Fail(ErrCannotHaveSize, resolved->name.span()); |
| |
| // Note that in the old syntax, we'll already have looked up the Resource* |
| // (if necessary) since the old syntax resolves arguments ahead of time (see |
| // call to ResolveOldSyntaxArgs above). However, we still need to obtain the |
| // Resource* and pass it to the HandleType, since it may be used to resolve |
| // more constraints later (e.g. if there's an alias to this handle that also |
| // specifies more constraints) in the new syntax. |
| Resource* handle_resource_decl = nullptr; |
| if (!GetResource(lib, args.name, &handle_resource_decl)) |
| return false; |
| |
| HandleType type(name_, handle_resource_decl); |
| return type.ApplySomeLayoutParametersAndConstraints(lib, *resolved, this, out_type, out_params); |
| } |
| |
| private: |
| const static HandleRights kSameRights; |
| }; |
| |
| const HandleRights HandleType::kSameRights = HandleRights(kHandleSameRights); |
| |
| bool HandleType::ApplyConstraints(const flat::LibraryMediator& lib, |
| const TypeConstraints& constraints, const TypeTemplate* layout, |
| std::unique_ptr<Type>* out_type, |
| LayoutInvocation* out_params) const { |
| // We need to store this separately from out_params, because out_params doesn't |
| // store the raw Constant that gets resolved to a nullability constraint. |
| std::optional<SourceSpan> applied_nullability_span; |
| |
| size_t num_constraints = constraints.items.size(); |
| |
| // There's a bit of a chicken and egg problem here: we only want to error when the |
| // resource_decl is undefined while trying to resolve a subtype or handle rights |
| // constant, but we don't know if a constraint is a subtype or handle rights or |
| // something else (like optional) without the resource_decl. |
| // In practice, such an edge case is almost guaranteed not to happen, so just error |
| // if there is no resource_decl and there are any constraints at all. |
| if (num_constraints > 0 && !resource_decl) { |
| // TODO(fxbug.dev/74909): Once bare handles are disallowed we won't need to |
| // throw this error "at use time" anymore and can move it back into |
| // HandleTypeTemplate::GetResource. At that point, we can also add back some |
| // more helpful data to the error (like the offending type constructor's name |
| // and span) which we don't have access to here. |
| return lib.Fail(ErrHandleSubtypeNotResource, std::nullopt); |
| } |
| |
| if (num_constraints == 0) { |
| // no constraints: set to default subtype below |
| } else if (num_constraints == 1) { |
| // lone constraint can be either subtype or optional |
| auto constraint_span = constraints.items[0]->span; |
| LibraryMediator::ResolvedConstraint resolved; |
| if (!lib.ResolveConstraintAs(constraints.items[0], |
| {LibraryMediator::ConstraintKind::kHandleSubtype, |
| LibraryMediator::ConstraintKind::kNullability}, |
| resource_decl, &resolved)) |
| return lib.Fail(ErrUnexpectedConstraint, constraint_span, layout); |
| switch (resolved.kind) { |
| case LibraryMediator::ConstraintKind::kHandleSubtype: |
| out_params->subtype_resolved = resolved.value.handle_subtype; |
| out_params->subtype_raw = constraints.items[0].get(); |
| break; |
| case LibraryMediator::ConstraintKind::kNullability: |
| out_params->nullability = types::Nullability::kNullable; |
| applied_nullability_span = constraint_span; |
| break; |
| default: |
| assert(false && "Compiler bug: resolved to wrong constraint kind"); |
| } |
| } else if (num_constraints == 2) { |
| // the first constraint must be subtype |
| auto constraint_span = constraints.items[0]->span; |
| uint32_t obj_type = 0; |
| if (!lib.ResolveAsHandleSubtype(resource_decl, constraints.items[0], &obj_type)) |
| return lib.Fail(ErrUnexpectedConstraint, constraint_span, layout); |
| out_params->subtype_resolved = obj_type; |
| out_params->subtype_raw = constraints.items[0].get(); |
| |
| // the second constraint can either be rights or optional |
| constraint_span = constraints.items[1]->span; |
| LibraryMediator::ResolvedConstraint resolved; |
| if (!lib.ResolveConstraintAs(constraints.items[1], |
| {LibraryMediator::ConstraintKind::kHandleRights, |
| LibraryMediator::ConstraintKind::kNullability}, |
| resource_decl, &resolved)) |
| return lib.Fail(ErrUnexpectedConstraint, constraint_span, layout); |
| switch (resolved.kind) { |
| case LibraryMediator::ConstraintKind::kHandleRights: |
| out_params->rights_resolved = resolved.value.handle_rights; |
| out_params->rights_raw = constraints.items[1].get(); |
| break; |
| case LibraryMediator::ConstraintKind::kNullability: |
| out_params->nullability = types::Nullability::kNullable; |
| applied_nullability_span = constraint_span; |
| break; |
| default: |
| assert(false && "Compiler bug: resolved to wrong constraint kind"); |
| } |
| } else if (num_constraints == 3) { |
| // no degrees of freedom: must be subtype, followed by rights, then optional |
| uint32_t obj_type = 0; |
| if (!lib.ResolveAsHandleSubtype(resource_decl, constraints.items[0], &obj_type)) |
| return lib.Fail(ErrUnexpectedConstraint, constraints.items[0]->span, layout); |
| out_params->subtype_resolved = obj_type; |
| out_params->subtype_raw = constraints.items[0].get(); |
| const HandleRights* rights = nullptr; |
| if (!lib.ResolveAsHandleRights(resource_decl, constraints.items[1].get(), &rights)) |
| return lib.Fail(ErrUnexpectedConstraint, constraints.items[1]->span, layout); |
| out_params->rights_resolved = rights; |
| out_params->rights_raw = constraints.items[1].get(); |
| if (!lib.ResolveAsOptional(constraints.items[2].get())) |
| return lib.Fail(ErrUnexpectedConstraint, constraints.items[2]->span, layout); |
| out_params->nullability = types::Nullability::kNullable; |
| applied_nullability_span = constraints.items[2]->span; |
| } else { |
| return lib.Fail(ErrTooManyConstraints, constraints.span, layout, size_t(3), num_constraints); |
| } |
| |
| bool has_obj_type = subtype != types::HandleSubtype::kHandle; |
| if (has_obj_type && out_params->subtype_resolved) |
| return lib.Fail(ErrCannotConstrainTwice, out_params->subtype_raw->span, layout); |
| // TODO(fxbug.dev/64629): We need to allow setting a default obj_type in |
| // resource_definition declarations rather than hard-coding. |
| uint32_t merged_obj_type = obj_type; |
| if (out_params->subtype_resolved) { |
| merged_obj_type = out_params->subtype_resolved.value(); |
| } |
| |
| bool has_nullability = nullability == types::Nullability::kNullable; |
| if (has_nullability && out_params->nullability == types::Nullability::kNullable) |
| return lib.Fail(ErrCannotIndicateNullabilityTwice, applied_nullability_span, layout); |
| auto merged_nullability = |
| has_nullability || out_params->nullability == types::Nullability::kNullable |
| ? types::Nullability::kNullable |
| : types::Nullability::kNonnullable; |
| |
| bool has_rights = rights != &kSameRights; |
| if (has_rights && out_params->rights_resolved) |
| return lib.Fail(ErrCannotConstrainTwice, out_params->rights_raw->span, layout); |
| auto merged_rights = rights; |
| if (out_params->rights_resolved) { |
| merged_rights = out_params->rights_resolved; |
| } |
| |
| *out_type = std::make_unique<HandleType>(name, resource_decl, merged_obj_type, |
| types::HandleSubtype(merged_obj_type), merged_rights, |
| merged_nullability); |
| return true; |
| } |
| |
| bool HandleType::ApplySomeLayoutParametersAndConstraints(const LibraryMediator& lib, |
| const CreateInvocation& create_invocation, |
| const TypeTemplate* layout, |
| std::unique_ptr<Type>* out_type, |
| LayoutInvocation* out_params) const { |
| if (create_invocation.size) |
| return lib.Fail(ErrCannotHaveSize, create_invocation.name.span(), layout); |
| |
| bool has_obj_type = subtype != types::HandleSubtype::kHandle; |
| if (has_obj_type && create_invocation.obj_type) |
| return lib.Fail(ErrCannotConstrainTwice, std::nullopt, layout); |
| uint32_t merged_obj_type = obj_type; |
| if (create_invocation.obj_type.has_value()) |
| merged_obj_type = create_invocation.obj_type.value(); |
| |
| bool has_nullability = nullability == types::Nullability::kNullable; |
| if (has_nullability && create_invocation.nullability == types::Nullability::kNullable) |
| return lib.Fail(ErrCannotIndicateNullabilityTwice, std::nullopt, layout); |
| auto merged_nullability = |
| has_nullability || create_invocation.nullability == types::Nullability::kNullable |
| ? types::Nullability::kNullable |
| : types::Nullability::kNonnullable; |
| |
| bool has_rights = rights != &kSameRights; |
| if (has_rights && create_invocation.handle_rights) |
| return lib.Fail(ErrCannotConstrainTwice, std::nullopt, layout); |
| auto merged_rights = rights; |
| if (create_invocation.handle_rights) |
| merged_rights = create_invocation.handle_rights; |
| |
| *out_type = std::make_unique<HandleType>(name, resource_decl, merged_obj_type, |
| types::HandleSubtype(merged_obj_type), merged_rights, |
| merged_nullability); |
| return true; |
| } |
| |
| class RequestTypeTemplate final : public TypeTemplate { |
| public: |
| RequestTypeTemplate(Typespace* typespace, Reporter* reporter) |
| : TypeTemplate(Name::CreateIntrinsic("request"), typespace, reporter) {} |
| |
| bool Create(const LibraryMediator& lib, const NewSyntaxParamsAndConstraints& unresolved_args, |
| std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override { |
| assert(false && "Compiler bug: this type template should only be used in the old syntax"); |
| return false; |
| } |
| |
| bool Create(const LibraryMediator& lib, const OldSyntaxParamsAndConstraints& unresolved_args, |
| std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override { |
| std::unique_ptr<CreateInvocation> args; |
| if (!ResolveOldSyntaxArgs(lib, unresolved_args, &args, out_params)) |
| return false; |
| |
| assert(!args->handle_subtype); |
| assert(!args->handle_rights); |
| |
| if (args->arg_type == nullptr) |
| return Fail(ErrMustBeParameterized, args->name.span()); |
| if (args->arg_type->kind != Type::Kind::kIdentifier) |
| return Fail(ErrMustBeAProtocol, args->name.span()); |
| auto protocol_type = static_cast<const IdentifierType*>(args->arg_type); |
| if (protocol_type->type_decl->kind != Decl::Kind::kProtocol) |
| return Fail(ErrMustBeAProtocol, args->name.span()); |
| if (args->size != nullptr) |
| return Fail(ErrCannotHaveSize, args->name.span()); |
| |
| RequestHandleType type(name_, protocol_type); |
| return type.ApplySomeLayoutParametersAndConstraints(lib, *args, this, out_type, out_params); |
| } |
| }; |
| |
| bool RequestHandleType::ApplyConstraints(const flat::LibraryMediator& lib, |
| const TypeConstraints& constraints, |
| const TypeTemplate* layout, |
| std::unique_ptr<Type>* out_type, |
| LayoutInvocation* out_params) const { |
| assert(false && "Compiler bug: this type should only be used in the old syntax"); |
| return false; |
| } |
| |
| bool RequestHandleType::ApplySomeLayoutParametersAndConstraints( |
| const LibraryMediator& lib, const CreateInvocation& create_invocation, |
| const TypeTemplate* layout, std::unique_ptr<Type>* out_type, |
| LayoutInvocation* out_params) const { |
| if (create_invocation.size) |
| return lib.Fail(ErrCannotHaveSize, create_invocation.name.span(), layout); |
| |
| if (nullability == types::Nullability::kNullable && |
| create_invocation.nullability == types::Nullability::kNullable) |
| return lib.Fail(ErrCannotIndicateNullabilityTwice, std::nullopt, layout); |
| auto merged_nullability = nullability; |
| if (create_invocation.nullability == types::Nullability::kNullable) |
| merged_nullability = create_invocation.nullability; |
| |
| *out_type = std::make_unique<RequestHandleType>(name, protocol_type, merged_nullability); |
| return true; |
| } |
| |
| class TypeDeclTypeTemplate final : public TypeTemplate { |
| public: |
| TypeDeclTypeTemplate(Name name, Typespace* typespace, Reporter* reporter, Library* library, |
| TypeDecl* type_decl) |
| : TypeTemplate(std::move(name), typespace, reporter), |
| library_(library), |
| type_decl_(type_decl) {} |
| |
| bool Create(const LibraryMediator& lib, const NewSyntaxParamsAndConstraints& unresolved_args, |
| std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override { |
| if (!type_decl_->compiled && type_decl_->kind != Decl::Kind::kProtocol) { |
| if (type_decl_->compiling) { |
| type_decl_->recursive = true; |
| } else { |
| if (!library_->CompileDecl(type_decl_)) { |
| return false; |
| } |
| } |
| } |
| |
| size_t num_params = unresolved_args.parameters->items.size(); |
| if (num_params != 0) { |
| return Fail(ErrWrongNumberOfLayoutParameters, unresolved_args.parameters->span, size_t(0), |
| num_params); |
| } |
| |
| IdentifierType type(name_, type_decl_); |
| return type.ApplyConstraints(lib, *unresolved_args.constraints, this, out_type, out_params); |
| } |
| |
| bool Create(const LibraryMediator& lib, const OldSyntaxParamsAndConstraints& unresolved_args, |
| std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override { |
| std::unique_ptr<CreateInvocation> args; |
| if (!ResolveOldSyntaxArgs(lib, unresolved_args, &args, out_params)) |
| return false; |
| |
| assert(!args->handle_subtype); |
| |
| if (!type_decl_->compiled && type_decl_->kind != Decl::Kind::kProtocol) { |
| if (type_decl_->compiling) { |
| type_decl_->recursive = true; |
| } else { |
| if (!library_->CompileDecl(type_decl_)) { |
| return false; |
| } |
| } |
| } |
| |
| if (args->arg_type != nullptr) |
| return Fail(ErrCannotBeParameterized, args->name.span()); |
| |
| IdentifierType type(name_, type_decl_); |
| return type.ApplySomeLayoutParametersAndConstraints(lib, *args, this, out_type, out_params); |
| } |
| |
| private: |
| Library* library_; |
| TypeDecl* type_decl_; |
| }; |
| |
| bool IdentifierType::ApplyConstraints(const flat::LibraryMediator& lib, |
| const TypeConstraints& constraints, |
| const TypeTemplate* layout, std::unique_ptr<Type>* out_type, |
| LayoutInvocation* out_params) const { |
| size_t num_constraints = constraints.items.size(); |
| switch (type_decl->kind) { |
| // These types have no allowed constraints |
| case Decl::Kind::kBits: |
| case Decl::Kind::kEnum: |
| case Decl::Kind::kTable: |
| // assume that a lone constraint was an attempt at specifying `optional` and provide a more |
| // specific error |
| // TOOD(fxbug.dev/75112): actually try to compile the optional constraint |
| if (num_constraints == 1) |
| return lib.Fail(ErrCannotBeNullable, constraints.items[0]->span, layout); |
| if (num_constraints > 1) { |
| return lib.Fail(ErrTooManyConstraints, constraints.span, layout, size_t(0), |
| num_constraints); |
| } |
| break; |
| |
| // These types have one allowed constraint (`optional`). For type aliases, |
| // we need to allow the possibility that the concrete type does allow `optional`, |
| // if it doesn't the Type itself will catch the error. |
| case Decl::Kind::kTypeAlias: |
| case Decl::Kind::kStruct: |
| case Decl::Kind::kUnion: |
| if (num_constraints > 1) { |
| return lib.Fail(ErrTooManyConstraints, constraints.span, layout, size_t(1), |
| num_constraints); |
| } |
| break; |
| |
| case Decl::Kind::kConst: |
| case Decl::Kind::kResource: |
| // Cannot have const: entries for constants do not exist in the typespace, so |
| // they're caught earlier. |
| // Cannot have resource: resource types should have resolved to the HandleTypeTemplate |
| assert(false && "Compiler bug: unexpected identifier type decl kind"); |
| break; |
| |
| // TODO(fxbug.dev/75837): |
| // These can't be used as types. This will be caught later, in VerifyTypeCategory. |
| case Decl::Kind::kService: |
| case Decl::Kind::kProtocol: |
| break; |
| } |
| |
| types::Nullability applied_nullability = types::Nullability::kNonnullable; |
| if (num_constraints == 1) { |
| // must be optional |
| if (!lib.ResolveAsOptional(constraints.items[0].get())) |
| return lib.Fail(ErrUnexpectedConstraint, constraints.items[0]->span, layout); |
| applied_nullability = types::Nullability::kNullable; |
| } |
| |
| if (nullability == types::Nullability::kNullable && |
| applied_nullability == types::Nullability::kNullable) |
| return lib.Fail(ErrCannotIndicateNullabilityTwice, std::nullopt, layout); |
| auto merged_nullability = nullability; |
| if (applied_nullability == types::Nullability::kNullable) |
| merged_nullability = applied_nullability; |
| |
| out_params->nullability = applied_nullability; |
| *out_type = std::make_unique<IdentifierType>(name, type_decl, merged_nullability); |
| return true; |
| } |
| |
| bool IdentifierType::ApplySomeLayoutParametersAndConstraints( |
| const LibraryMediator& lib, const CreateInvocation& create_invocation, |
| const TypeTemplate* layout, std::unique_ptr<Type>* out_type, |
| LayoutInvocation* out_params) const { |
| switch (type_decl->kind) { |
| // These types can't be nullable |
| case Decl::Kind::kBits: |
| case Decl::Kind::kEnum: |
| case Decl::Kind::kTable: |
| if (create_invocation.nullability == types::Nullability::kNullable) |
| return lib.Fail(ErrCannotBeNullable, create_invocation.name.span(), layout); |
| break; |
| |
| // These types have one allowed constraint (`optional`). For type aliases, |
| // we need to allow the possibility that the concrete type does allow `optional`, |
| // if it doesn't the Type itself will catch the error. |
| case Decl::Kind::kProtocol: |
| case Decl::Kind::kTypeAlias: |
| case Decl::Kind::kStruct: |
| case Decl::Kind::kUnion: |
| if (nullability == types::Nullability::kNullable && |
| create_invocation.nullability == types::Nullability::kNullable) |
| return lib.Fail(ErrCannotIndicateNullabilityTwice, std::nullopt, layout); |
| break; |
| |
| // These should never be encountered |
| case Decl::Kind::kConst: |
| case Decl::Kind::kResource: { |
| // Cannot have const: entries for constants do not exist in the typespace |
| // Cannot have resource: resource types should have resolved to the HandleTypeTemplate |
| assert(false); |
| break; |
| } |
| |
| // TODO(fxbug.dev/75837): |
| // Services are not allowed to be used as types. This is caught later, during |
| // VerifyTypeCategory. |
| case Decl::Kind::kService: |
| break; |
| } |
| |
| auto merged_nullability = nullability; |
| if (create_invocation.nullability == types::Nullability::kNullable) |
| merged_nullability = create_invocation.nullability; |
| |
| *out_type = std::make_unique<IdentifierType>(name, type_decl, merged_nullability); |
| return true; |
| } |
| |
| class TypeAliasTypeTemplate final : public TypeTemplate { |
| public: |
| TypeAliasTypeTemplate(Name name, Typespace* typespace, Reporter* reporter, TypeAlias* decl) |
| : TypeTemplate(std::move(name), typespace, reporter), decl_(decl) {} |
| |
| bool Create(const LibraryMediator& lib, const NewSyntaxParamsAndConstraints& unresolved_args, |
| std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override { |
| if (!decl_->compiled) { |
| if (decl_->compiling) { |
| return Fail(ErrIncludeCycle); |
| } |
| |
| if (!lib.CompileDecl(decl_)) { |
| return false; |
| } |
| } |
| |
| size_t num_params = unresolved_args.parameters->items.size(); |
| if (num_params != 0) |
| return Fail(ErrWrongNumberOfLayoutParameters, unresolved_args.parameters->span, size_t(0), |
| num_params); |
| |
| // Compilation failed while trying to resolve something farther up the chain; |
| // exit early |
| if (!GetType(decl_->partial_type_ctor)) |
| return false; |
| const auto& aliased_type = GetType(decl_->partial_type_ctor); |
| out_params->from_type_alias = decl_; |
| return aliased_type->ApplyConstraints(lib, *unresolved_args.constraints, this, out_type, |
| out_params); |
| } |
| |
| bool Create(const LibraryMediator& lib, const OldSyntaxParamsAndConstraints& unresolved_args, |
| std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override { |
| std::unique_ptr<CreateInvocation> args; |
| if (!ResolveOldSyntaxArgs(lib, unresolved_args, &args, out_params)) |
| return false; |
| |
| // Note that because fidlc only populates these handle fields if it sees |
| // "handle" in the type constructor, aliases of handles will never correctly |
| // capture any handle constraints. It is not a TODO to fix this since this |
| // issue does not exist in the new syntax. |
| assert(!args->handle_subtype); |
| assert(!args->handle_rights); |
| |
| if (!decl_->compiled) { |
| if (decl_->compiling) { |
| return Fail(ErrIncludeCycle); |
| } |
| |
| if (!lib.CompileDecl(decl_)) { |
| return false; |
| } |
| } |
| |
| if (unresolved_args.maybe_arg_type_ctor != nullptr) |
| return Fail(ErrCannotParameterizeAlias, args->name.span()); |
| |
| // Compilation failed while trying to resolve something farther up the chain; |
| // exit early |
| if (!GetType(decl_->partial_type_ctor)) |
| return false; |
| const auto& aliased_type = GetType(decl_->partial_type_ctor); |
| out_params->from_type_alias = decl_; |
| return aliased_type->ApplySomeLayoutParametersAndConstraints(lib, *args, this, out_type, |
| out_params); |
| } |
| |
| private: |
| TypeAlias* decl_; |
| }; |
| |
| Typespace Typespace::RootTypes(Reporter* reporter) { |
| Typespace root_typespace(reporter); |
| |
| auto add_template = [&](std::unique_ptr<TypeTemplate> type_template) { |
| const Name& name = type_template->name(); |
| root_typespace.templates_.emplace(name, std::move(type_template)); |
| }; |
| |
| auto add_primitive = [&](const std::string& name, types::PrimitiveSubtype subtype) { |
| add_template(std::make_unique<PrimitiveTypeTemplate>(&root_typespace, reporter, name, subtype)); |
| }; |
| |
| add_primitive("bool", types::PrimitiveSubtype::kBool); |
| |
| add_primitive("int8", types::PrimitiveSubtype::kInt8); |
| add_primitive("int16", types::PrimitiveSubtype::kInt16); |
| add_primitive("int32", types::PrimitiveSubtype::kInt32); |
| add_primitive("int64", types::PrimitiveSubtype::kInt64); |
| add_primitive("uint8", types::PrimitiveSubtype::kUint8); |
| add_primitive("uint16", types::PrimitiveSubtype::kUint16); |
| add_primitive("uint32", types::PrimitiveSubtype::kUint32); |
| add_primitive("uint64", types::PrimitiveSubtype::kUint64); |
| |
| add_primitive("float32", types::PrimitiveSubtype::kFloat32); |
| add_primitive("float64", types::PrimitiveSubtype::kFloat64); |
| |
| // TODO(fxbug.dev/7807): Remove when there is generalized support. |
| const static auto kByteName = Name::CreateIntrinsic("byte"); |
| const static auto kBytesName = Name::CreateIntrinsic("bytes"); |
| root_typespace.templates_.emplace( |
| kByteName, std::make_unique<PrimitiveTypeTemplate>(&root_typespace, reporter, "uint8", |
| types::PrimitiveSubtype::kUint8)); |
| root_typespace.templates_.emplace(kBytesName, |
| std::make_unique<BytesTypeTemplate>(&root_typespace, reporter)); |
| |
| add_template(std::make_unique<ArrayTypeTemplate>(&root_typespace, reporter)); |
| add_template(std::make_unique<VectorTypeTemplate>(&root_typespace, reporter)); |
| add_template(std::make_unique<StringTypeTemplate>(&root_typespace, reporter)); |
| add_template(std::make_unique<HandleTypeTemplate>(&root_typespace, reporter)); |
| add_template(std::make_unique<RequestTypeTemplate>(&root_typespace, reporter)); |
| |
| return root_typespace; |
| } |
| |
| AttributeSchema::AttributeSchema(const std::set<Placement>& allowed_placements, |
| const std::set<std::string> allowed_values, Constraint constraint) |
| : allowed_placements_(allowed_placements), |
| allowed_values_(allowed_values), |
| constraint_(std::move(constraint)) {} |
| |
| AttributeSchema AttributeSchema::Deprecated() { |
| return AttributeSchema({Placement::kDeprecated}, {}); |
| } |
| |
| void AttributeSchema::ValidatePlacement(Reporter* reporter, const raw::Attribute& attribute, |
| Placement placement) const { |
| if (allowed_placements_.size() == 0) |
| return; |
| |
| if (allowed_placements_.size() == 1 && *allowed_placements_.cbegin() == Placement::kDeprecated) { |
| reporter->Report(ErrDeprecatedAttribute, attribute.span(), attribute); |
| return; |
| } |
| |
| auto iter = allowed_placements_.find(placement); |
| if (iter != allowed_placements_.end()) |
| return; |
| reporter->Report(ErrInvalidAttributePlacement, attribute.span(), attribute); |
| } |
| |
| void AttributeSchema::ValidateValue(Reporter* reporter, const raw::Attribute& attribute) const { |
| if (allowed_values_.size() == 0) |
| return; |
| auto iter = allowed_values_.find(attribute.value); |
| if (iter != allowed_values_.end()) |
| return; |
| reporter->Report(ErrInvalidAttributeValue, attribute.span(), attribute, attribute.value, |
| allowed_values_); |
| } |
| |
| void AttributeSchema::ValidateConstraint(Reporter* reporter, const raw::Attribute& attribute, |
| const Decl* decl) const { |
| auto check = reporter->Checkpoint(); |
| auto passed = constraint_(reporter, attribute, decl); |
| if (passed) { |
| assert(check.NoNewErrors() && "cannot add errors and pass"); |
| } else if (check.NoNewErrors()) { |
| // TODO(pascallouis): It would be nicer to use the span of |
| // the declaration, however we do not keep it around today. |
| reporter->Report(ErrAttributeConstraintNotSatisfied, attribute.span(), attribute, |
| attribute.value); |
| } |
| } |
| |
| bool SimpleLayoutConstraint(Reporter* reporter, const raw::Attribute& attribute, const Decl* decl) { |
| assert(decl->kind == Decl::Kind::kStruct); |
| auto struct_decl = static_cast<const Struct*>(decl); |
| bool ok = true; |
| for (const auto& member : struct_decl->members) { |
| if (!IsSimple(GetType(member.type_ctor), reporter)) { |
| reporter->Report(ErrMemberMustBeSimple, member.name, member.name.data()); |
| ok = false; |
| } |
| } |
| return ok; |
| } |
| |
| bool ParseBound(Reporter* reporter, const SourceSpan& span, const std::string& input, |
| uint32_t* out_value) { |
| auto result = utils::ParseNumeric(input, out_value, 10); |
| switch (result) { |
| case utils::ParseNumericResult::kOutOfBounds: |
| reporter->Report(ErrBoundIsTooBig, span); |
| return false; |
| case utils::ParseNumericResult::kMalformed: { |
| reporter->Report(ErrUnableToParseBound, span, input); |
| return false; |
| } |
| case utils::ParseNumericResult::kSuccess: |
| return true; |
| } |
| } |
| |
| bool Library::VerifyInlineSize(const Struct* struct_decl) { |
| if (struct_decl->typeshape(WireFormat::kV1NoEe).InlineSize() >= 65536) { |
| return Library::Fail(ErrInlineSizeExceeds64k, *struct_decl); |
| } |
| return true; |
| } |
| |
| bool MaxBytesConstraint(Reporter* reporter, const raw::Attribute& attribute, const Decl* decl) { |
| uint32_t bound; |
| if (!ParseBound(reporter, attribute.span(), attribute.value, &bound)) |
| return false; |
| uint32_t max_bytes = std::numeric_limits<uint32_t>::max(); |
| switch (decl->kind) { |
| case Decl::Kind::kStruct: { |
| auto struct_decl = static_cast<const Struct*>(decl); |
| max_bytes = struct_decl->typeshape(WireFormat::kV1NoEe).InlineSize() + |
| struct_decl->typeshape(WireFormat::kV1NoEe).MaxOutOfLine(); |
| break; |
| } |
| case Decl::Kind::kTable: { |
| auto table_decl = static_cast<const Table*>(decl); |
| max_bytes = table_decl->typeshape(WireFormat::kV1NoEe).InlineSize() + |
| table_decl->typeshape(WireFormat::kV1NoEe).MaxOutOfLine(); |
| break; |
| } |
| case Decl::Kind::kUnion: { |
| auto union_decl = static_cast<const Union*>(decl); |
| max_bytes = union_decl->typeshape(WireFormat::kV1NoEe).InlineSize() + |
| union_decl->typeshape(WireFormat::kV1NoEe).MaxOutOfLine(); |
| break; |
| } |
| default: |
| assert(false && "unexpected kind"); |
| return false; |
| } |
| if (max_bytes > bound) { |
| reporter->Report(ErrTooManyBytes, attribute.span(), bound, max_bytes); |
| return false; |
| } |
| return true; |
| } |
| |
| bool MaxHandlesConstraint(Reporter* reporter, const raw::Attribute& attribute, const Decl* decl) { |
| uint32_t bound; |
| if (!ParseBound(reporter, attribute.span(), attribute.value, &bound)) |
| return false; |
| uint32_t max_handles = std::numeric_limits<uint32_t>::max(); |
| switch (decl->kind) { |
| case Decl::Kind::kStruct: { |
| auto struct_decl = static_cast<const Struct*>(decl); |
| max_handles = struct_decl->typeshape(WireFormat::kV1NoEe).MaxHandles(); |
| break; |
| } |
| case Decl::Kind::kTable: { |
| auto table_decl = static_cast<const Table*>(decl); |
| max_handles = table_decl->typeshape(WireFormat::kV1NoEe).MaxHandles(); |
| break; |
| } |
| case Decl::Kind::kUnion: { |
| auto union_decl = static_cast<const Union*>(decl); |
| max_handles = union_decl->typeshape(WireFormat::kV1NoEe).MaxHandles(); |
| break; |
| } |
| default: |
| assert(false && "unexpected kind"); |
| return false; |
| } |
| if (max_handles > bound) { |
| reporter->Report(ErrTooManyHandles, attribute.span(), bound, max_handles); |
| return false; |
| } |
| return true; |
| } |
| |
| bool ResultShapeConstraint(Reporter* reporter, const raw::Attribute& attribute, const Decl* decl) { |
| assert(decl->kind == Decl::Kind::kUnion); |
| auto union_decl = static_cast<const Union*>(decl); |
| assert(union_decl->members.size() == 2); |
| auto& error_member = union_decl->members.at(1); |
| assert(error_member.maybe_used && "must have an error member"); |
| auto error_type = GetType(error_member.maybe_used->type_ctor); |
| |
| const PrimitiveType* error_primitive = nullptr; |
| if (error_type->kind == Type::Kind::kPrimitive) { |
| error_primitive = static_cast<const PrimitiveType*>(error_type); |
| } else if (error_type->kind == Type::Kind::kIdentifier) { |
| auto identifier_type = static_cast<const IdentifierType*>(error_type); |
| if (identifier_type->type_decl->kind == Decl::Kind::kEnum) { |
| auto error_enum = static_cast<const Enum*>(identifier_type->type_decl); |
| assert(GetType(error_enum->subtype_ctor)->kind == Type::Kind::kPrimitive); |
| error_primitive = static_cast<const PrimitiveType*>(GetType(error_enum->subtype_ctor)); |
| } |
| } |
| |
| if (!error_primitive || (error_primitive->subtype != types::PrimitiveSubtype::kInt32 && |
| error_primitive->subtype != types::PrimitiveSubtype::kUint32)) { |
| reporter->Report(ErrInvalidErrorType, decl->name.span()); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static std::string Trim(std::string s) { |
| s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) { |
| return !utils::IsWhitespace(static_cast<char>(ch)); |
| })); |
| s.erase(std::find_if(s.rbegin(), s.rend(), |
| [](int ch) { return !utils::IsWhitespace(static_cast<char>(ch)); }) |
| .base(), |
| s.end()); |
| return s; |
| } |
| |
| bool TransportConstraint(Reporter* reporter, const raw::Attribute& attribute, const Decl* decl) { |
| // Parse comma separated transports |
| const std::string& value = attribute.value; |
| std::string::size_type prev_pos = 0; |
| std::string::size_type pos; |
| std::vector<std::string> transports; |
| while ((pos = value.find(',', prev_pos)) != std::string::npos) { |
| transports.emplace_back(Trim(value.substr(prev_pos, pos - prev_pos))); |
| prev_pos = pos + 1; |
| } |
| transports.emplace_back(Trim(value.substr(prev_pos))); |
| |
| // Validate that they're ok |
| |
| // function-local static pointer to non-trivially-destructible type |
| // is allowed by styleguide |
| static const auto kValidTransports = new std::set<std::string>{ |
| "Banjo", |
| "Channel", |
| "Syscall", |
| }; |
| for (auto transport : transports) { |
| if (kValidTransports->count(transport) == 0) { |
| reporter->Report(ErrInvalidTransportType, decl->name.span(), transport, *kValidTransports); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| Resource::Property* Resource::LookupProperty(std::string_view name) { |
| for (Property& property : properties) { |
| if (property.name.data() == name.data()) { |
| return &property; |
| } |
| } |
| return nullptr; |
| } |
| |
| Libraries::Libraries() { |
| // clang-format off |
| AddAttributeSchema("Discoverable", AttributeSchema({ |
| AttributeSchema::Placement::kProtocolDecl, |
| }, { |
| "", |
| })); |
| AddAttributeSchema("Doc", AttributeSchema({ |
| /* any placement */ |
| }, { |
| /* any value */ |
| })); |
| AddAttributeSchema("Layout", AttributeSchema::Deprecated()), |
| AddAttributeSchema("ForDeprecatedCBindings", AttributeSchema({ |
| AttributeSchema::Placement::kProtocolDecl, |
| AttributeSchema::Placement::kStructDecl, |
| }, { |
| "", |
| }, |
| SimpleLayoutConstraint)); |
| AddAttributeSchema("MaxBytes", AttributeSchema({ |
| AttributeSchema::Placement::kProtocolDecl, |
| AttributeSchema::Placement::kMethod, |
| AttributeSchema::Placement::kStructDecl, |
| AttributeSchema::Placement::kTableDecl, |
| AttributeSchema::Placement::kUnionDecl, |
| }, { |
| /* any value */ |
| }, |
| MaxBytesConstraint)); |
| AddAttributeSchema("MaxHandles", AttributeSchema({ |
| AttributeSchema::Placement::kProtocolDecl, |
| AttributeSchema::Placement::kMethod, |
| AttributeSchema::Placement::kStructDecl, |
| AttributeSchema::Placement::kTableDecl, |
| AttributeSchema::Placement::kUnionDecl, |
| }, { |
| /* any value */ |
| }, |
| MaxHandlesConstraint)); |
| AddAttributeSchema("Result", AttributeSchema({ |
| AttributeSchema::Placement::kUnionDecl, |
| }, { |
| "", |
| }, |
| ResultShapeConstraint)); |
| AddAttributeSchema("Selector", AttributeSchema({ |
| AttributeSchema::Placement::kMethod, |
| }, { |
| /* any value */ |
| })); |
| AddAttributeSchema("Transitional", AttributeSchema({ |
| AttributeSchema::Placement::kMethod, |
| AttributeSchema::Placement::kBitsDecl, |
| AttributeSchema::Placement::kEnumDecl, |
| AttributeSchema::Placement::kUnionDecl, |
| }, { |
| /* any value */ |
| })); |
| AddAttributeSchema("Transport", AttributeSchema({ |
| AttributeSchema::Placement::kProtocolDecl, |
| }, { |
| /* any value */ |
| }, TransportConstraint)); |
| AddAttributeSchema("Unknown", AttributeSchema({ |
| AttributeSchema::Placement::kEnumMember, |
| AttributeSchema::Placement::kUnionMember, |
| }, { |
| "" |
| })); |
| // clang-format on |
| } |
| |
| bool Libraries::Insert(std::unique_ptr<Library> library) { |
| std::vector<std::string_view> library_name = library->name(); |
| auto iter = all_libraries_.emplace(library_name, std::move(library)); |
| return iter.second; |
| } |
| |
| bool Libraries::Lookup(const std::vector<std::string_view>& library_name, |
| Library** out_library) const { |
| auto iter = all_libraries_.find(library_name); |
| if (iter == all_libraries_.end()) { |
| return false; |
| } |
| |
| *out_library = iter->second.get(); |
| return true; |
| } |
| |
| std::set<std::vector<std::string_view>> Libraries::Unused(const Library* target_library) const { |
| std::set<std::vector<std::string_view>> unused; |
| for (auto& name_library : all_libraries_) |
| unused.insert(name_library.first); |
| unused.erase(target_library->name()); |
| std::set<const Library*> worklist = {target_library}; |
| while (worklist.size() != 0) { |
| auto it = worklist.begin(); |
| auto next = *it; |
| worklist.erase(it); |
| for (const auto dependency : next->dependencies()) { |
| unused.erase(dependency->name()); |
| worklist.insert(dependency); |
| } |
| } |
| return unused; |
| } |
| |
| size_t EditDistance(const std::string& sequence1, const std::string& sequence2) { |
| size_t s1_length = sequence1.length(); |
| size_t s2_length = sequence2.length(); |
| size_t row1[s1_length + 1]; |
| size_t row2[s1_length + 1]; |
| size_t* last_row = row1; |
| size_t* this_row = row2; |
| for (size_t i = 0; i <= s1_length; i++) |
| last_row[i] = i; |
| 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(Reporter* reporter, |
| const raw::Attribute& attribute) const { |
| const auto& attribute_name = fidl::utils::to_upper_camel_case(attribute.name); |
| auto iter = attribute_schemas_.find(attribute_name); |
| if (iter != attribute_schemas_.end()) { |
| const auto& schema = iter->second; |
| return &schema; |
| } |
| |
| // Skip typo check? |
| if (reporter == nullptr) |
| return nullptr; |
| |
| // Match against all known attributes. |
| for (const auto& name_and_schema : attribute_schemas_) { |
| auto edit_distance = EditDistance(name_and_schema.first, attribute_name); |
| if (0 < edit_distance && edit_distance < 2) { |
| reporter->Report(WarnAttributeTypo, attribute.span(), attribute_name, name_and_schema.first); |
| return nullptr; |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| bool Dependencies::Register(const SourceSpan& span, std::string_view filename, Library* dep_library, |
| const std::unique_ptr<raw::Identifier>& maybe_alias) { |
| refs_.push_back(std::make_unique<LibraryRef>(span, dep_library)); |
| auto ref = refs_.back().get(); |
| |
| auto library_name = dep_library->name(); |
| if (!InsertByName(filename, library_name, ref)) { |
| return false; |
| } |
| |
| if (maybe_alias) { |
| std::vector<std::string_view> alias_name = {maybe_alias->span().data()}; |
| if (!InsertByName(filename, alias_name, ref)) { |
| return false; |
| } |
| } |
| |
| dependencies_aggregate_.insert(dep_library); |
| |
| return true; |
| } |
| |
| bool Dependencies::InsertByName(std::string_view filename, |
| const std::vector<std::string_view>& name, LibraryRef* ref) { |
| auto iter = dependencies_.find(std::string(filename)); |
| if (iter == dependencies_.end()) { |
| dependencies_.emplace(filename, std::make_unique<ByName>()); |
| } |
| |
| iter = dependencies_.find(std::string(filename)); |
| assert(iter != dependencies_.end()); |
| |
| auto insert = iter->second->emplace(name, ref); |
| return insert.second; |
| } |
| |
| bool Dependencies::Contains(std::string_view filename, const std::vector<std::string_view>& name) { |
| auto iter1 = dependencies_.find(std::string(filename)); |
| if (iter1 == dependencies_.end()) { |
| return false; |
| } |
| |
| auto iter2 = iter1->second->find(name); |
| return iter2 != iter1->second->end(); |
| } |
| |
| bool Dependencies::Lookup(std::string_view filename, const std::vector<std::string_view>& name, |
| Dependencies::LookupMode mode, Library** out_library) const { |
| auto iter1 = dependencies_.find(std::string(filename)); |
| if (iter1 == dependencies_.end()) { |
| return false; |
| } |
| |
| auto iter2 = iter1->second->find(name); |
| if (iter2 == iter1->second->end()) { |
| return false; |
| } |
| |
| auto ref = iter2->second; |
| if (mode == Dependencies::LookupMode::kUse) { |
| ref->used_ = true; |
| } |
| *out_library = ref->library_; |
| return true; |
| } |
| |
| bool Dependencies::VerifyAllDependenciesWereUsed(const Library& for_library, Reporter* reporter) { |
| auto checkpoint = reporter->Checkpoint(); |
| for (auto by_name_iter = dependencies_.begin(); by_name_iter != dependencies_.end(); |
| by_name_iter++) { |
| const auto& by_name = *by_name_iter->second; |
| for (const auto& name_to_ref : by_name) { |
| const auto& ref = name_to_ref.second; |
| if (ref->used_) |
| continue; |
| reporter->Report(ErrUnusedImport, ref->span_, for_library.name(), ref->library_->name(), |
| ref->library_->name()); |
| } |
| } |
| return checkpoint.NoNewErrors(); |
| } |
| |
| // Consuming the AST is primarily concerned with walking the tree and |
| // flattening the representation. The AST's declaration nodes are |
| // converted into the Library's foo_declaration structures. This means pulling |
| // a struct declaration inside a protocol out to the top level and |
| // so on. |
| |
| std::string LibraryName(const Library* library, std::string_view separator) { |
| if (library != nullptr) { |
| return StringJoin(library->name(), separator); |
| } |
| return std::string(); |
| } |
| |
| bool Library::Fail(std::unique_ptr<Diagnostic> err) { |
| assert(err && "should not report nullptr error"); |
| reporter_->Report(std::move(err)); |
| return false; |
| } |
| |
| template <typename... Args> |
| bool Library::Fail(const ErrorDef<Args...>& err, const Args&... args) { |
| reporter_->Report(err, args...); |
| return false; |
| } |
| |
| template <typename... Args> |
| bool Library::Fail(const ErrorDef<Args...>& err, const std::optional<SourceSpan>& span, |
| const Args&... args) { |
| reporter_->Report(err, span, args...); |
| return false; |
| } |
| |
| void Library::ValidateAttributesPlacement(AttributeSchema::Placement placement, |
| const raw::AttributeList* attributes) { |
| if (attributes == nullptr) |
| return; |
| for (const auto& attribute : attributes->attributes) { |
| auto schema = all_libraries_->RetrieveAttributeSchema(reporter_, attribute); |
| if (schema != nullptr) { |
| schema->ValidatePlacement(reporter_, attribute, placement); |
| schema->ValidateValue(reporter_, attribute); |
| } |
| } |
| } |
| |
| void Library::ValidateAttributesConstraints(const Decl* decl, |
| const raw::AttributeList* attributes) { |
| if (attributes == nullptr) |
| return; |
| for (const auto& attribute : attributes->attributes) { |
| auto schema = all_libraries_->RetrieveAttributeSchema(nullptr, attribute); |
| if (schema != nullptr) |
| schema->ValidateConstraint(reporter_, attribute, decl); |
| } |
| } |
| |
| bool Library::LookupDependency(std::string_view filename, const std::vector<std::string_view>& name, |
| Library** out_library) const { |
| return dependencies_.Lookup(filename, name, Dependencies::LookupMode::kSilent, out_library); |
| } |
| |
| SourceSpan Library::GeneratedSimpleName(const std::string& name) { |
| return generated_source_file_.AddLine(name); |
| } |
| |
| std::string Library::NextAnonymousName() { |
| // TODO(fxbug.dev/7920): Improve anonymous name generation. We want to be |
| // specific about how these names are generated once they appear in the |
| // JSON IR, and are exposed to the backends. |
| std::ostringstream data; |
| data << "SomeLongAnonymousPrefix"; |
| data << anon_counter_++; |
| |
| return data.str(); |
| } |
| |
| std::optional<Name> Library::CompileCompoundIdentifier( |
| const raw::CompoundIdentifier* compound_identifier) { |
| const auto& components = compound_identifier->components; |
| assert(components.size() >= 1); |
| |
| SourceSpan decl_name = components.back()->span(); |
| |
| // First try resolving the identifier in the library. |
| if (components.size() == 1) { |
| return Name::CreateSourced(this, decl_name); |
| } |
| |
| std::vector<std::string_view> library_name; |
| for (auto iter = components.begin(); iter != components.end() - 1; ++iter) { |
| library_name.push_back((*iter)->span().data()); |
| } |
| |
| auto filename = compound_identifier->span().source_file().filename(); |
| Library* dep_library = nullptr; |
| if (dependencies_.Lookup(filename, library_name, Dependencies::LookupMode::kUse, &dep_library)) { |
| return Name::CreateSourced(dep_library, decl_name); |
| } |
| |
| // If the identifier is not found in the library it might refer to a |
| // declaration with a member (e.g. library.EnumX.val or BitsY.val). |
| SourceSpan member_name = decl_name; |
| SourceSpan member_decl_name = components.rbegin()[1]->span(); |
| |
| if (components.size() == 2) { |
| return Name::CreateSourced(this, member_decl_name, std::string(member_name.data())); |
| } |
| |
| std::vector<std::string_view> member_library_name(library_name); |
| member_library_name.pop_back(); |
| |
| Library* member_dep_library = nullptr; |
| if (dependencies_.Lookup(filename, member_library_name, Dependencies::LookupMode::kUse, |
| &member_dep_library)) { |
| return Name::CreateSourced(member_dep_library, member_decl_name, |
| std::string(member_name.data())); |
| } |
| |
| Fail(ErrUnknownDependentLibrary, components[0]->span(), library_name, member_library_name); |
| return std::nullopt; |
| } |
| |
| namespace { |
| |
| template <typename T> |
| void StoreDecl(Decl* decl_ptr, std::vector<std::unique_ptr<T>>* declarations) { |
| std::unique_ptr<T> t_decl; |
| t_decl.reset(static_cast<T*>(decl_ptr)); |
| declarations->push_back(std::move(t_decl)); |
| } |
| |
| } // namespace |
| |
| bool Library::RegisterDecl(std::unique_ptr<Decl> decl) { |
| assert(decl); |
| |
| auto decl_ptr = decl.release(); |
| auto kind = decl_ptr->kind; |
| switch (kind) { |
| case Decl::Kind::kBits: |
| StoreDecl(decl_ptr, &bits_declarations_); |
| break; |
| case Decl::Kind::kConst: |
| StoreDecl(decl_ptr, &const_declarations_); |
| break; |
| case Decl::Kind::kEnum: |
| StoreDecl(decl_ptr, &enum_declarations_); |
| break; |
| case Decl::Kind::kProtocol: |
| StoreDecl(decl_ptr, &protocol_declarations_); |
| break; |
| case Decl::Kind::kResource: |
| StoreDecl(decl_ptr, &resource_declarations_); |
| break; |
| case Decl::Kind::kService: |
| StoreDecl(decl_ptr, &service_declarations_); |
| break; |
| case Decl::Kind::kStruct: |
| StoreDecl(decl_ptr, &struct_declarations_); |
| break; |
| case Decl::Kind::kTable: |
| StoreDecl(decl_ptr, &table_declarations_); |
| break; |
| case Decl::Kind::kTypeAlias: |
| StoreDecl(decl_ptr, &type_alias_declarations_); |
| break; |
| case Decl::Kind::kUnion: |
| StoreDecl(decl_ptr, &union_declarations_); |
| break; |
| } // switch |
| |
| const Name& name = decl_ptr->name; |
| { |
| const auto it = declarations_.emplace(name, decl_ptr); |
| if (!it.second) { |
| const auto previous_name = it.first->second->name; |
| assert(previous_name.span() && "declarations_ has a name with no span"); |
| return Fail(ErrNameCollision, name.span(), name, *previous_name.span()); |
| } |
| } |
| |
| const auto canonical_decl_name = utils::canonicalize(name.decl_name()); |
| { |
| const auto it = declarations_by_canonical_name_.emplace(canonical_decl_name, decl_ptr); |
| if (!it.second) { |
| const auto previous_name = it.first->second->name; |
| assert(previous_name.span() && "declarations_by_canonical_name_ has a name with no span"); |
| return Fail(ErrNameCollisionCanonical, name.span(), name, previous_name, |
| *previous_name.span(), canonical_decl_name); |
| } |
| } |
| |
| if (name.span()) { |
| if (dependencies_.Contains(name.span()->source_file().filename(), {name.span()->data()})) { |
| return Fail(ErrDeclNameConflictsWithLibraryImport, name, name); |
| } |
| if (dependencies_.Contains(name.span()->source_file().filename(), {canonical_decl_name})) { |
| return Fail(ErrDeclNameConflictsWithLibraryImportCanonical, name, name, canonical_decl_name); |
| } |
| } |
| |
| switch (kind) { |
| case Decl::Kind::kBits: |
| case Decl::Kind::kEnum: |
| case Decl::Kind::kService: |
| case Decl::Kind::kStruct: |
| case Decl::Kind::kTable: |
| case Decl::Kind::kUnion: |
| case Decl::Kind::kProtocol: { |
| auto type_decl = static_cast<TypeDecl*>(decl_ptr); |
| auto type_template = |
| std::make_unique<TypeDeclTypeTemplate>(name, typespace_, reporter_, this, type_decl); |
| typespace_->AddTemplate(std::move(type_template)); |
| break; |
| } |
| case Decl::Kind::kTypeAlias: { |
| auto type_alias_decl = static_cast<TypeAlias*>(decl_ptr); |
| auto type_alias_template = |
| std::make_unique<TypeAliasTypeTemplate>(name, typespace_, reporter_, type_alias_decl); |
| typespace_->AddTemplate(std::move(type_alias_template)); |
| break; |
| } |
| case Decl::Kind::kConst: |
| case Decl::Kind::kResource: |
| break; |
| } // switch |
| return true; |
| } |
| |
| ConsumeStep Library::StartConsumeStep(fidl::utils::Syntax syntax) { |
| return ConsumeStep(this, syntax); |
| } |
| CompileStep Library::StartCompileStep() { return CompileStep(this); } |
| VerifyResourcenessStep Library::StartVerifyResourcenessStep() { |
| return VerifyResourcenessStep(this); |
| } |
| VerifyAttributesStep Library::StartVerifyAttributesStep() { return VerifyAttributesStep(this); } |
| |
| bool Library::ConsumeConstant(std::unique_ptr<raw::Constant> raw_constant, |
| std::unique_ptr<Constant>* out_constant) { |
| switch (raw_constant->kind) { |
| case raw::Constant::Kind::kIdentifier: { |
| auto identifier = static_cast<raw::IdentifierConstant*>(raw_constant.get()); |
| auto name = CompileCompoundIdentifier(identifier->identifier.get()); |
| if (!name) |
| return false; |
| *out_constant = |
| std::make_unique<IdentifierConstant>(std::move(name.value()), identifier->span()); |
| break; |
| } |
| case raw::Constant::Kind::kLiteral: { |
| auto literal = static_cast<raw::LiteralConstant*>(raw_constant.get()); |
| std::unique_ptr<LiteralConstant> out; |
| ConsumeLiteralConstant(literal, &out); |
| *out_constant = std::unique_ptr<Constant>(out.release()); |
| break; |
| } |
| case raw::Constant::Kind::kBinaryOperator: { |
| auto binary_operator_constant = static_cast<raw::BinaryOperatorConstant*>(raw_constant.get()); |
| BinaryOperatorConstant::Operator op; |
| switch (binary_operator_constant->op) { |
| case raw::BinaryOperatorConstant::Operator::kOr: |
| op = BinaryOperatorConstant::Operator::kOr; |
| break; |
| } |
| std::unique_ptr<Constant> left_operand; |
| if (!ConsumeConstant(std::move(binary_operator_constant->left_operand), &left_operand)) { |
| return false; |
| } |
| std::unique_ptr<Constant> right_operand; |
| if (!ConsumeConstant(std::move(binary_operator_constant->right_operand), &right_operand)) { |
| return false; |
| } |
| *out_constant = std::make_unique<BinaryOperatorConstant>( |
| std::move(left_operand), std::move(right_operand), op, binary_operator_constant->span()); |
| break; |
| } |
| } |
| return true; |
| } |
| |
| void Library::ConsumeLiteralConstant(raw::LiteralConstant* raw_constant, |
| std::unique_ptr<LiteralConstant>* out_constant) { |
| *out_constant = std::make_unique<LiteralConstant>(std::move(raw_constant->literal)); |
| } |
| |
| bool Library::ConsumeTypeConstructorOld(std::unique_ptr<raw::TypeConstructorOld> raw_type_ctor, |
| std::unique_ptr<TypeConstructorOld>* out_type_ctor) { |
| auto name = CompileCompoundIdentifier(raw_type_ctor->identifier.get()); |
| if (!name) |
| return false; |
| |
| std::unique_ptr<TypeConstructorOld> maybe_arg_type_ctor; |
| if (raw_type_ctor->maybe_arg_type_ctor != nullptr) { |
| if (!ConsumeTypeConstructorOld(std::move(raw_type_ctor->maybe_arg_type_ctor), |
| &maybe_arg_type_ctor)) |
| return false; |
| } |
| |
| std::unique_ptr<Constant> maybe_size; |
| if (raw_type_ctor->maybe_size != nullptr) { |
| if (!ConsumeConstant(std::move(raw_type_ctor->maybe_size), &maybe_size)) |
| return false; |
| } |
| |
| std::unique_ptr<Constant> handle_rights; |
| if (raw_type_ctor->handle_rights != nullptr) { |
| if (!ConsumeConstant(std::move(raw_type_ctor->handle_rights), &handle_rights)) |
| return false; |
| } |
| |
| std::optional<Name> handle_subtype_identifier; |
| if (raw_type_ctor->handle_subtype_identifier) { |
| handle_subtype_identifier = |
| Name::CreateSourced(this, raw_type_ctor->handle_subtype_identifier->span()); |
| } |
| |
| *out_type_ctor = std::make_unique<TypeConstructorOld>( |
| std::move(name.value()), std::move(maybe_arg_type_ctor), std::move(handle_subtype_identifier), |
| std::move(handle_rights), std::move(maybe_size), raw_type_ctor->nullability); |
| return true; |
| } |
| |
| void Library::ConsumeUsing(std::unique_ptr<raw::Using> using_directive) { |
| if (using_directive->attributes && using_directive->attributes->attributes.size() != 0) { |
| Fail(ErrAttributesNotAllowedOnLibraryImport, using_directive->span(), |
| *(using_directive->attributes)); |
| return; |
| } |
| |
| std::vector<std::string_view> library_name; |
| for (const auto& component : using_directive->using_path->components) { |
| library_name.push_back(component->span().data()); |
| } |
| |
| Library* dep_library = nullptr; |
| if (!all_libraries_->Lookup(library_name, &dep_library)) { |
| Fail(ErrUnknownLibrary, using_directive->using_path->components[0]->span(), library_name); |
| return; |
| } |
| |
| auto filename = using_directive->span().source_file().filename(); |
| if (!dependencies_.Register(using_directive->span(), filename, dep_library, |
| using_directive->maybe_alias)) { |
| Fail(ErrDuplicateLibraryImport, library_name); |
| return; |
| } |
| |
| // Import declarations, and type aliases of dependent library. |
| const auto& declarations = dep_library->declarations_; |
| declarations_.insert(declarations.begin(), declarations.end()); |
| } |
| |
| bool Library::ConsumeTypeAlias(std::unique_ptr<raw::AliasDeclaration> alias_declaration) { |
| assert(alias_declaration->alias && IsTypeConstructorDefined(alias_declaration->type_ctor)); |
| |
| auto alias_name = Name::CreateSourced(this, alias_declaration->alias->span()); |
| TypeConstructor type_ctor_; |
| |
| if (!ConsumeTypeConstructor(std::move(alias_declaration->type_ctor), alias_name, &type_ctor_)) |
| return false; |
| |
| return RegisterDecl(std::make_unique<TypeAlias>(std::move(alias_declaration->attributes), |
| std::move(alias_name), std::move(type_ctor_))); |
| } |
| |
| void Library::ConsumeBitsDeclaration(std::unique_ptr<raw::BitsDeclaration> bits_declaration) { |
| std::vector<Bits::Member> members; |
| for (auto& member : bits_declaration->members) { |
| auto span = member->identifier->span(); |
| std::unique_ptr<Constant> value; |
| if (!ConsumeConstant(std::move(member->value), &value)) |
| return; |
| members.emplace_back(span, std::move(value), std::move(member->attributes)); |
| // TODO(pascallouis): right now, members are not registered. Look into |
| // registering them, potentially under the bits name qualifier such as |
| // <name_of_bits>.<name_of_member>. |
| } |
| |
| std::unique_ptr<TypeConstructorOld> type_ctor; |
| if (bits_declaration->maybe_type_ctor) { |
| if (!ConsumeTypeConstructorOld(std::move(bits_declaration->maybe_type_ctor), &type_ctor)) |
| return; |
| } else { |
| type_ctor = TypeConstructorOld::CreateSizeType(); |
| } |
| |
| RegisterDecl(std::make_unique<Bits>( |
| std::move(bits_declaration->attributes), |
| Name::CreateSourced(this, bits_declaration->identifier->span()), std::move(type_ctor), |
| std::move(members), bits_declaration->strictness)); |
| } |
| |
| void Library::ConsumeConstDeclaration(std::unique_ptr<raw::ConstDeclaration> const_declaration) { |
| auto attributes = std::move(const_declaration->attributes); |
| auto span = const_declaration->identifier->span(); |
| auto name = Name::CreateSourced(this, span); |
| TypeConstructor type_ctor; |
| // TODO(fxbug.dev/73285): shouldn't need to care about context here |
| if (!ConsumeTypeConstructor(std::move(const_declaration->type_ctor), name, &type_ctor)) |
| return; |
| |
| std::unique_ptr<Constant> constant; |
| if (!ConsumeConstant(std::move(const_declaration->constant), &constant)) |
| return; |
| |
| RegisterDecl(std::make_unique<Const>(std::move(attributes), std::move(name), std::move(type_ctor), |
| std::move(constant))); |
| } |
| |
| void Library::ConsumeEnumDeclaration(std::unique_ptr<raw::EnumDeclaration> enum_declaration) { |
| std::vector<Enum::Member> members; |
| for (auto& member : enum_declaration->members) { |
| auto span = member->identifier->span(); |
| std::unique_ptr<Constant> value; |
| if (!ConsumeConstant(std::move(member->value), &value)) |
| return; |
| members.emplace_back(span, std::move(value), std::move(member->attributes)); |
| // TODO(pascallouis): right now, members are not registered. Look into |
| // registering them, potentially under the enum name qualifier such as |
| // <name_of_enum>.<name_of_member>. |
| } |
| |
| std::unique_ptr<TypeConstructorOld> type_ctor; |
| if (enum_declaration->maybe_type_ctor) { |
| if (!ConsumeTypeConstructorOld(std::move(enum_declaration->maybe_type_ctor), &type_ctor)) |
| return; |
| } else { |
| type_ctor = TypeConstructorOld::CreateSizeType(); |
| } |
| |
| RegisterDecl(std::make_unique<Enum>( |
| std::move(enum_declaration->attributes), |
| Name::CreateSourced(this, enum_declaration->identifier->span()), std::move(type_ctor), |
| std::move(members), enum_declaration->strictness)); |
| } |
| |
| bool Library::CreateMethodResult(const Name& protocol_name, SourceSpan response_span, |
| raw::ProtocolMethod* method, Struct* in_response, |
| Struct** out_response) { |
| // Compile the error type. |
| std::unique_ptr<TypeConstructorOld> error_type_ctor; |
| if (!ConsumeTypeConstructorOld(std::move(method->maybe_error_ctor), &error_type_ctor)) |
| return false; |
| |
| // Make the Result union containing the response struct and the |
| // error type. |
| SourceSpan method_name_span = method->identifier->span(); |
| |
| // TODO(fxbug.dev/8027): Join spans of response and error constructor for `result_name`. |
| auto result_name = Name::CreateDerived( |
| this, response_span, |
| StringJoin({protocol_name.decl_name(), method_name_span.data(), "Result"}, "_")); |
| |
| raw::SourceElement sourceElement = raw::SourceElement(fidl::Token(), fidl::Token()); |
| Union::Member response_member{ |
| std::make_unique<raw::Ordinal64>(sourceElement, |
| 1), // success case explicitly has ordinal 1 |
| IdentifierTypeForDecl(in_response, types::Nullability::kNonnullable), |
| GeneratedSimpleName("response"), nullptr}; |
| Union::Member error_member{ |
| std::make_unique<raw::Ordinal64>(sourceElement, 2), // error case explicitly has ordinal 2 |
| std::move(error_type_ctor), GeneratedSimpleName("err"), nullptr}; |
| std::vector<Union::Member> result_members; |
| result_members.push_back(std::move(response_member)); |
| result_members.push_back(std::move(error_member)); |
| std::vector<raw::Attribute> result_attributes; |
| result_attributes.emplace_back(*method, raw::Attribute::Provenance::kDefault, "Result", ""); |
| auto result_attributelist = |
| std::make_unique<raw::AttributeList>(*method, std::move(result_attributes)); |
| auto union_decl = std::make_unique<Union>(std::move(result_attributelist), std::move(result_name), |
| std::move(result_members), types::Strictness::kStrict, |
| std::nullopt /* resourceness */); |
| auto result_decl = union_decl.get(); |
| if (!RegisterDecl(std::move(union_decl))) |
| return false; |
| |
| // Make a new response struct for the method containing just the |
| // result union. |
| std::vector<Struct::Member> response_members; |
| response_members.push_back( |
| Struct::Member(IdentifierTypeForDecl(result_decl, types::Nullability::kNonnullable), |
| GeneratedSimpleName("result"), nullptr, nullptr)); |
| |
| auto struct_decl = std::make_unique<Struct>( |
| nullptr /* attributes */, Name::CreateDerived(this, response_span, NextAnonymousName()), |
| std::move(response_members), std::nullopt /* resourceness */, |
| true /* is_request_or_response */); |
| auto struct_decl_ptr = struct_decl.get(); |
| if (!RegisterDecl(std::move(struct_decl))) |
| return false; |
| *out_response = struct_decl_ptr; |
| return true; |
| } |
| |
| void Library::ConsumeProtocolDeclaration( |
| std::unique_ptr<raw::ProtocolDeclaration> protocol_declaration) { |
| auto attributes = std::move(protocol_declaration->attributes); |
| auto name = Name::CreateSourced(this, protocol_declaration->identifier->span()); |
| |
| std::set<Name> composed_protocols; |
| for (auto& composed_protocol : protocol_declaration->composed_protocols) { |
| auto& protocol_name = composed_protocol->protocol_name; |
| auto composed_protocol_name = CompileCompoundIdentifier(protocol_name.get()); |
| if (!composed_protocol_name) |
| return; |
| if (!composed_protocols.insert(std::move(composed_protocol_name.value())).second) { |
| Fail(ErrProtocolComposedMultipleTimes, composed_protocol_name->span()); |
| return; |
| } |
| } |
| |
| std::vector<Protocol::Method> methods; |
| for (auto& method : protocol_declaration->methods) { |
| auto selector = |
| fidl::ordinals::GetSelector(method->attributes.get(), method->identifier->span()); |
| if (!utils::IsValidIdentifierComponent(selector) && |
| !utils::IsValidFullyQualifiedMethodIdentifier(selector)) { |
| Fail(ErrInvalidSelectorValue, method->identifier->span()); |
| } |
| auto generated_ordinal64 = std::make_unique<raw::Ordinal64>( |
| method_hasher_(library_name_, name.decl_name(), selector, *method->identifier)); |
| auto attributes = std::move(method->attributes); |
| SourceSpan method_name = method->identifier->span(); |
| |
| Struct* maybe_request = nullptr; |
| if (method->maybe_request != nullptr) { |
| auto request_span = method->maybe_request->span(); |
| auto request_name = Name::CreateDerived(this, request_span, NextAnonymousName()); |
| if (!ConsumeParameterList(std::move(request_name), std::move(method->maybe_request), true, |
| &maybe_request)) |
| return; |
| } |
| |
| Struct* maybe_response = nullptr; |
| if (method->maybe_response != nullptr) { |
| const bool has_error = (method->maybe_error_ctor != nullptr); |
| |
| SourceSpan response_span = method->maybe_response->span(); |
| Name response_name = Name::CreateDerived( |
| this, response_span, |
| has_error ? StringJoin({name.decl_name(), method_name.data(), "Response"}, "_") |
| : NextAnonymousName()); |
| if (!ConsumeParameterList(std::move(response_name), std::move(method->maybe_response), |
| !has_error, &maybe_response)) |
| return; |
| |
| if (has_error) { |
| if (!CreateMethodResult(name, response_span, method.get(), maybe_response, &maybe_response)) |
| return; |
| } |
| } |
| |
| assert(maybe_request != nullptr || maybe_response != nullptr); |
| methods.emplace_back(std::move(attributes), std::move(generated_ordinal64), |
| std::move(method_name), std::move(maybe_request), |
| std::move(maybe_response)); |
| } |
| |
| RegisterDecl(std::make_unique<Protocol>(std::move(attributes), std::move(name), |
| std::move(composed_protocols), std::move(methods))); |
| } |
| |
| bool Library::ConsumeResourceDeclaration( |
| std::unique_ptr<raw::ResourceDeclaration> resource_declaration) { |
| auto name = Name::CreateSourced(this, resource_declaration->identifier->span()); |
| std::vector<Resource::Property> properties; |
| for (auto& property : resource_declaration->properties) { |
| TypeConstructor type_ctor; |
| auto anonymous_resource_name = Name::CreateDerived( |
| this, property->span(), |
| std::string(name.decl_name()) + std::string(property->identifier->span().data())); |
| if (!ConsumeTypeConstructor(std::move(property->type_ctor), anonymous_resource_name, |
| &type_ctor)) |
| return false; |
| auto attributes = std::move(property->attributes); |
| properties.emplace_back(std::move(type_ctor), property->identifier->span(), |
| std::move(attributes)); |
| } |
| |
| TypeConstructor type_ctor; |
| if (raw::IsTypeConstructorDefined(resource_declaration->maybe_type_ctor)) { |
| // TODO(fxbug.dev/73285): shouldn't need to care about the naming context here. |
| if (!ConsumeTypeConstructor(std::move(resource_declaration->maybe_type_ctor), name, &type_ctor)) |
| return false; |
| } else { |
| type_ctor = TypeConstructorOld::CreateSizeType(); |
| } |
| |
| return RegisterDecl(std::make_unique<Resource>(std::move(resource_declaration->attributes), |
| std::move(name), std::move(type_ctor), |
| std::move(properties))); |
| } |
| |
| std::unique_ptr<TypeConstructorOld> Library::IdentifierTypeForDecl(const Decl* decl, |
| types::Nullability nullability) { |
| return std::make_unique<TypeConstructorOld>(decl->name, nullptr /* maybe_arg_type */, |
| std::optional<Name>() /* handle_subtype_identifier */, |
| nullptr /* handle_rights */, nullptr /* maybe_size */, |
| nullability); |
| } |
| |
| bool Library::ConsumeParameterList(Name name, std::unique_ptr<raw::ParameterList> parameter_list, |
| bool is_request_or_response, Struct** out_struct_decl) { |
| std::vector<Struct::Member> members; |
| for (auto& parameter : parameter_list->parameter_list) { |
| TypeConstructor type_ctor; |
| // TODO(fxbug.dev/73285): finalize layout naming |
| auto param_name = Name::CreateDerived( |
| this, parameter->span(), |
| std::string(name.decl_name()) + |
| utils::to_upper_camel_case(std::string(parameter->identifier->span().data()))); |
| if (!ConsumeTypeConstructor(std::move(parameter->type_ctor), param_name, &type_ctor)) |
| return false; |
| ValidateAttributesPlacement(AttributeSchema::Placement::kStructMember, |
| parameter->attributes.get()); |
| members.emplace_back(std::move(type_ctor), parameter->identifier->span(), |
| nullptr /* maybe_default_value */, std::move(parameter->attributes)); |
| } |
| |
| if (!RegisterDecl(std::make_unique<Struct>(nullptr /* attributes */, std::move(name), |
| std::move(members), std::nullopt /* resourceness */, |
| is_request_or_response))) |
| return false; |
| *out_struct_decl = struct_declarations_.back().get(); |
| return true; |
| } |
| |
| void Library::ConsumeServiceDeclaration(std::unique_ptr<raw::ServiceDeclaration> service_decl) { |
| auto attributes = std::move(service_decl->attributes); |
| auto name = Name::CreateSourced(this, service_decl->identifier->span()); |
| |
| std::vector<Service::Member> members; |
| for (auto& member : service_decl->members) { |
| TypeConstructor type_ctor; |
| // TODO(fxbug.dev/73285): shouldn't need to care about naming context here. |
| if (!ConsumeTypeConstructor(std::move(member->type_ctor), name, &type_ctor)) |
| return; |
| members.emplace_back(std::move(type_ctor), member->identifier->span(), |
| std::move(member->attributes)); |
| } |
| |
| RegisterDecl( |
| std::make_unique<Service>(std::move(attributes), std::move(name), std::move(members))); |
| } |
| |
| void Library::ConsumeStructDeclaration(std::unique_ptr<raw::StructDeclaration> struct_declaration) { |
| auto attributes = std::move(struct_declaration->attributes); |
| auto name = Name::CreateSourced(this, struct_declaration->identifier->span()); |
| |
| std::vector<Struct::Member> members; |
| for (auto& member : struct_declaration->members) { |
| std::unique_ptr<TypeConstructorOld> type_ctor; |
| if (!ConsumeTypeConstructorOld(std::move(member->type_ctor), &type_ctor)) |
| return; |
| std::unique_ptr<Constant> maybe_default_value; |
| if (member->maybe_default_value != nullptr) { |
| if (!ConsumeConstant(std::move(member->maybe_default_value), &maybe_default_value)) |
| return; |
| } |
| auto attributes = std::move(member->attributes); |
| members.emplace_back(std::move(type_ctor), member->identifier->span(), |
| std::move(maybe_default_value), std::move(attributes)); |
| } |
| |
| RegisterDecl(std::make_unique<Struct>(std::move(attributes), std::move(name), std::move(members), |
| struct_declaration->resourceness)); |
| } |
| |
| void Library::ConsumeTableDeclaration(std::unique_ptr<raw::TableDeclaration> table_declaration) { |
| auto attributes = std::move(table_declaration->attributes); |
| auto name = Name::CreateSourced(this, table_declaration->identifier->span()); |
| |
| std::vector<Table::Member> members; |
| for (auto& member : table_declaration->members) { |
| auto ordinal_literal = std::move(member->ordinal); |
| |
| if (member->maybe_used) { |
| std::unique_ptr<TypeConstructorOld> type_ctor; |
| if (!ConsumeTypeConstructorOld(std::move(member->maybe_used->type_ctor), &type_ctor)) |
| return; |
| std::unique_ptr<Constant> maybe_default_value; |
| if (member->maybe_used->maybe_default_value) { |
| // TODO(fxbug.dev/7932): Support defaults on tables. |
| const auto default_value = member->maybe_used->maybe_default_value.get(); |
| reporter_->Report(ErrDefaultsOnTablesNotSupported, default_value->span()); |
| } |
| if (type_ctor->nullability != types::Nullability::kNonnullable) { |
| Fail(ErrNullableTableMember, member->span()); |
| return; |
| } |
| auto attributes = std::move(member->maybe_used->attributes); |
| members.emplace_back(std::move(ordinal_literal), std::move(type_ctor), |
| member->maybe_used->identifier->span(), std::move(maybe_default_value), |
| std::move(attributes)); |
| } else { |
| members.emplace_back(std::move(ordinal_literal), member->span()); |
| } |
| } |
| |
| RegisterDecl(std::make_unique<Table>(std::move(attributes), std::move(name), std::move(members), |
| table_declaration->strictness, |
| table_declaration->resourceness)); |
| } |
| |
| void Library::ConsumeUnionDeclaration(std::unique_ptr<raw::UnionDeclaration> union_declaration) { |
| auto name = Name::CreateSourced(this, union_declaration->identifier->span()); |
| |
| assert(!union_declaration->members.empty() && "unions must have at least one member"); |
| auto union_name = |
| std::pair<std::string, std::string_view>(LibraryName(this, "."), name.decl_name()); |
| std::vector<Union::Member> members; |
| for (auto& member : union_declaration->members) { |
| auto explicit_ordinal = std::move(member->ordinal); |
| |
| if (member->maybe_used) { |
| std::unique_ptr<TypeConstructorOld> type_ctor; |
| if (!ConsumeTypeConstructorOld(std::move(member->maybe_used->type_ctor), &type_ctor)) |
| return; |
| if (member->maybe_used->maybe_default_value) { |
| const auto default_value = member->maybe_used->maybe_default_value.get(); |
| reporter_->Report(ErrDefaultsOnUnionsNotSupported, default_value->span()); |
| } |
| if (type_ctor->nullability != types::Nullability::kNonnullable) { |
| Fail(ErrNullableUnionMember, member->span()); |
| return; |
| } |
| |
| members.emplace_back(std::move(explicit_ordinal), std::move(type_ctor), |
| member->maybe_used->identifier->span(), |
| std::move(member->maybe_used->attributes)); |
| } else { |
| members.emplace_back(std::move(explicit_ordinal), member->span()); |
| } |
| } |
| |
| RegisterDecl(std::make_unique<Union>(std::move(union_declaration->attributes), std::move(name), |
| std::move(members), union_declaration->strictness, |
| union_declaration->resourceness)); |
| } |
| |
| // TODO(fxbug.dev/71536): these conversion methods may need to be refactored |
| // once the new flat AST lands, and such coercion is no longer needed. |
| template <typename T, typename M> |
| bool Library::ConsumeValueLayout(std::unique_ptr<raw::Layout> layout, const Name& context) { |
| std::vector<M> members; |
| size_t index = 0; |
| for (auto& mem : layout->members) { |
| auto member = static_cast<raw::ValueLayoutMember*>(mem.get()); |
| auto span = member->identifier->span(); |
| std::unique_ptr<Constant> value; |
| if (!ConsumeConstant(std::move(member->value), &value)) |
| return false; |
| |
| members.emplace_back(span, std::move(value), /*attributes=*/nullptr); |
| index++; |
| } |
| |
| std::unique_ptr<TypeConstructorNew> subtype_ctor; |
| if (layout->subtype_ctor != nullptr) { |
| if (!ConsumeTypeConstructorNew(std::move(layout->subtype_ctor), context, &subtype_ctor)) |
| return false; |
| } else { |
| subtype_ctor = TypeConstructorNew::CreateSizeType(); |
| } |
| |
| RegisterDecl(std::make_unique<T>( |
| /*attributes=*/nullptr, context, std::move(subtype_ctor), std::move(members), |
| layout->strictness.value_or(types::Strictness::kFlexible))); |
| return true; |
| } |
| |
| template <typename T, typename M> |
| bool Library::ConsumeOrdinaledLayout(std::unique_ptr<raw::Layout> layout, const Name& context) { |
| std::vector<M> members; |
| for (auto& mem : layout->members) { |
| auto member = static_cast<raw::OrdinaledLayoutMember*>(mem.get()); |
| if (member->reserved) { |
| members.emplace_back(std::move(member->ordinal), member->span()); |
| continue; |
| } |
| |
| auto name_of_anonymous_layout = Name::CreateDerived( |
| this, member->span(), |
| std::string(context.decl_name()) + |
| utils::to_upper_camel_case(std::string(member->identifier->span().data()))); |
| std::unique_ptr<TypeConstructorNew> type_ctor; |
| if (!ConsumeTypeConstructorNew(std::move(member->type_ctor), name_of_anonymous_layout, |
| &type_ctor)) |
| return false; |
| |
| members.emplace_back(std::move(member->ordinal), std::move(type_ctor), |
| member->identifier->span(), /*attributes=*/nullptr); |
| } |
| |
| RegisterDecl(std::make_unique<T>( |
| /*attributes=*/nullptr, context, std::move(members), |
| layout->strictness.value_or(types::Strictness::kFlexible), layout->resourceness)); |
| return true; |
| } |
| |
| bool Library::ConsumeStructLayout(std::unique_ptr<raw::Layout> layout, const Name& context) { |
| std::vector<Struct::Member> members; |
| for (auto& mem : layout->members) { |
| auto member = static_cast<raw::StructLayoutMember*>(mem.get()); |
| auto name_of_anonymous_layout = Name::CreateDerived( |
| this, member->span(), |
| std::string(context.decl_name()) + |
| utils::to_upper_camel_case(std::string(member->identifier->span().data()))); |
| |
| std::unique_ptr<TypeConstructorNew> type_ctor; |
| if (!ConsumeTypeConstructorNew(std::move(member->type_ctor), name_of_anonymous_layout, |
| &type_ctor)) |
| return false; |
| |
| std::unique_ptr<Constant> default_value; |
| if (member->default_value != nullptr) { |
| ConsumeConstant(std::move(member->default_value), &default_value); |
| } |
| |
| members.emplace_back(std::move(type_ctor), member->identifier->span(), std::move(default_value), |
| /*attributes=*/nullptr); |
| } |
| |
| RegisterDecl(std::make_unique<Struct>(/*attributes=*/nullptr, context, std::move(members), |
| layout->resourceness)); |
| return true; |
| } |
| |
| bool Library::ConsumeLayout(std::unique_ptr<raw::Layout> layout, const Name& context) { |
| switch (layout->kind) { |
| case raw::Layout::Kind::kBits: { |
| return ConsumeValueLayout<Bits, Bits::Member>(std::move(layout), context); |
| } |
| case raw::Layout::Kind::kEnum: { |
| return ConsumeValueLayout<Enum, Enum::Member>(std::move(layout), context); |
| } |
| case raw::Layout::Kind::kStruct: { |
| return ConsumeStructLayout(std::move(layout), context); |
| } |
| case raw::Layout::Kind::kTable: { |
| return ConsumeOrdinaledLayout<Table, Table::Member>(std::move(layout), context); |
| } |
| case raw::Layout::Kind::kUnion: { |
| return ConsumeOrdinaledLayout<Union, Union::Member>(std::move(layout), context); |
| } |
| } |
| assert(false && "layouts must be of type bits, enum, struct, table, or union"); |
| return true; |
| } |
| |
| bool Library::ConsumeTypeConstructorNew(std::unique_ptr<raw::TypeConstructorNew> raw_type_ctor, |
| const Name& context, |
| std::unique_ptr<TypeConstructorNew>* out_type_ctor) { |
| if (raw_type_ctor->layout_ref->kind == raw::LayoutReference::Kind::kInline) { |
| // TODO(fxbug.dev/74683): If we don't want users to be able to refer to anonymous |
| // layouts by figuring out their generated name, we'll need to update this section |
| const auto& params = raw_type_ctor->parameters; |
| const auto& constraints = raw_type_ctor->constraints; |
| if (params && !params->items.empty()) { |
| return Fail(ErrLayoutCannotBeParameterized, params->span(), context); |
| } |
| if (constraints && !constraints->items.empty()) { |
| return Fail(ErrCannotConstrainInLayoutDecl, constraints->span()); |
| } |
| auto inline_ref = static_cast<raw::InlineLayoutReference*>(raw_type_ctor->layout_ref.get()); |
| if (!ConsumeLayout(std::move(inline_ref->layout), context)) |
| return false; |
| |
| std::vector<std::unique_ptr<LayoutParameter>> no_params; |
| std::vector<std::unique_ptr<Constant>> no_constraints; |
| if (out_type_ctor) |
| *out_type_ctor = std::make_unique<TypeConstructorNew>( |
| context, std::make_unique<LayoutParameterList>(std::move(no_params), std::nullopt), |
| std::make_unique<TypeConstraints>(std::move(no_constraints), std::nullopt)); |
| return true; |
| } |
| |
| auto named_ref = static_cast<raw::NamedLayoutReference*>(raw_type_ctor->layout_ref.get()); |
| SourceSpan name_span = named_ref->identifier->span(); |
| auto name = CompileCompoundIdentifier(named_ref->identifier.get()); |
| if (!name) |
| return false; |
| |
| std::vector<std::unique_ptr<LayoutParameter>> params; |
| std::optional<SourceSpan> params_span; |
| if (raw_type_ctor->parameters) { |
| params_span = raw_type_ctor->parameters->span(); |
| for (auto& p : raw_type_ctor->parameters->items) { |
| auto param = std::move(p); |
| auto span = param->span(); |
| switch (param->kind) { |
| case raw::LayoutParameter::Kind::kLiteral: { |
| auto literal_param = static_cast<raw::LiteralLayoutParameter*>(param.get()); |
| std::unique_ptr<LiteralConstant> constant; |
| ConsumeLiteralConstant(literal_param->literal.get(), &constant); |
| |
| std::unique_ptr<LayoutParameter> consumed = |
| std::make_unique<LiteralLayoutParameter>(std::move(constant), span); |
| params.push_back(std::move(consumed)); |
| break; |
| } |
| case raw::LayoutParameter::Kind::kType: { |
| auto type_param = static_cast<raw::TypeLayoutParameter*>(param.get()); |
| std::unique_ptr<TypeConstructorNew> type_ctor; |
| auto nested_name = Name::CreateDerived(this, name_span, context.full_name()); |
| if (!ConsumeTypeConstructorNew(std::move(type_param->type_ctor), nested_name, &type_ctor)) |
| return false; |
| |
| std::unique_ptr<LayoutParameter> consumed = |
| std::make_unique<TypeLayoutParameter>(std::move(type_ctor), span); |
| params.push_back(std::move(consumed)); |
| break; |
| } |
| case raw::LayoutParameter::Kind::kIdentifier: { |
| auto id_param = static_cast<raw::IdentifierLayoutParameter*>(param.get()); |
| auto name = CompileCompoundIdentifier(id_param->identifier.get()); |
| if (!name) |
| return false; |
| |
| std::unique_ptr<LayoutParameter> consumed = |
| std::make_unique<IdentifierLayoutParameter>(std::move(name.value()), span); |
| params.push_back(std::move(consumed)); |
| break; |
| } |
| } |
| } |
| } |
| |
| std::vector<std::unique_ptr<Constant>> constraints; |
| std::optional<SourceSpan> constraints_span; |
| if (raw_type_ctor->constraints) { |
| constraints_span = raw_type_ctor->constraints->span(); |
| for (auto& c : raw_type_ctor->constraints->items) { |
| std::unique_ptr<Constant> constraint; |
| if (!ConsumeConstant(std::move(c), &constraint)) |
| return false; |
| constraints.push_back(std::move(constraint)); |
| } |
| } |
| |
| assert(out_type_ctor && "out type ctors should always be provided for a named type ctor"); |
| *out_type_ctor = std::make_unique<TypeConstructorNew>( |
| std::move(name.value()), |
| std::make_unique<LayoutParameterList>(std::move(params), params_span), |
| std::make_unique<TypeConstraints>(std::move(constraints), constraints_span)); |
| return true; |
| } |
| |
| bool Library::ConsumeTypeConstructor(raw::TypeConstructor raw_type_ctor, const Name& context, |
| TypeConstructor* out_type) { |
| return std::visit(fidl::utils::matchers{ |
| [&, this](std::unique_ptr<raw::TypeConstructorOld> e) -> bool { |
| std::unique_ptr<TypeConstructorOld> out; |
| bool result = ConsumeTypeConstructorOld(std::move(e), &out); |
| *out_type = std::move(out); |
| return result; |
| }, |
| [&, this](std::unique_ptr<raw::TypeConstructorNew> e) -> bool { |
| std::unique_ptr<TypeConstructorNew> out; |
| bool result = ConsumeTypeConstructorNew(std::move(e), context, &out); |
| *out_type = std::move(out); |
| return result; |
| }, |
| }, |
| std::move(raw_type_ctor)); |
| } |
| |
| void Library::ConsumeTypeDecl(std::unique_ptr<raw::TypeDecl> type_decl) { |
| auto name = Name::CreateSourced(this, type_decl->identifier->span()); |
| auto& layout_ref = type_decl->type_ctor->layout_ref; |
| // TODO(fxbug.dev/7807) |
| if (layout_ref->kind == raw::LayoutReference::Kind::kNamed) { |
| auto named_ref = static_cast<raw::NamedLayoutReference*>(layout_ref.get()); |
| Fail(ErrNewTypesNotAllowed, name, named_ref->span().data()); |
| return; |
| } |
| |
| ConsumeTypeConstructorNew(std::move(type_decl->type_ctor), name, nullptr); |
| } |
| |
| bool Library::ConsumeFile(std::unique_ptr<raw::File> file) { |
| if (file->attributes) { |
| ValidateAttributesPlacement(AttributeSchema::Placement::kLibrary, file->attributes.get()); |
| if (!attributes_) { |
| attributes_ = std::move(file->attributes); |
| } else { |
| AttributesBuilder attributes_builder(reporter_, std::move(attributes_->attributes)); |
| for (auto& attribute : file->attributes->attributes) { |
| if (!attributes_builder.Insert(std::move(attribute))) |
| return false; |
| } |
| attributes_ = std::make_unique<raw::AttributeList>( |
| raw::SourceElement(file->attributes->start_, file->attributes->end_), |
| attributes_builder.Done()); |
| } |
| } |
| |
| // All fidl files in a library should agree on the library name. |
| std::vector<std::string_view> new_name; |
| for (const auto& part : file->library_name->components) { |
| new_name.push_back(part->span().data()); |
| } |
| if (!library_name_.empty()) { |
| if (new_name != library_name_) { |
| return Fail(ErrFilesDisagreeOnLibraryName, file->library_name->components[0]->span()); |
| } |
| } else { |
| library_name_ = new_name; |
| } |
| |
| auto step = StartConsumeStep(file->syntax); |
| |
| auto using_list = std::move(file->using_list); |
| for (auto& using_directive : using_list) { |
| step.ForUsing(std::move(using_directive)); |
| } |
| |
| auto alias_list = std::move(file->alias_list); |
| for (auto& alias_declaration : alias_list) { |
| step.ForAliasDeclaration(std::move(alias_declaration)); |
| } |
| |
| auto bits_declaration_list = std::move(file->bits_declaration_list); |
| for (auto& bits_declaration : bits_declaration_list) { |
| step.ForBitsDeclaration(std::move(bits_declaration)); |
| } |
| |
| auto const_declaration_list = std::move(file->const_declaration_list); |
| for (auto& const_declaration : const_declaration_list) { |
| step.ForConstDeclaration(std::move(const_declaration)); |
| } |
| |
| auto enum_declaration_list = std::move(file->enum_declaration_list); |
| for (auto& enum_declaration : enum_declaration_list) { |
| step.ForEnumDeclaration(std::move(enum_declaration)); |
| } |
| |
| auto protocol_declaration_list = std::move(file->protocol_declaration_list); |
| for (auto& protocol_declaration : protocol_declaration_list) { |
| step.ForProtocolDeclaration(std::move(protocol_declaration)); |
| } |
| |
| auto resource_declaration_list = std::move(file->resource_declaration_list); |
| for (auto& resource_declaration : resource_declaration_list) { |
| step.ForResourceDeclaration(std::move(resource_declaration)); |
| } |
| |
| auto service_declaration_list = std::move(file->service_declaration_list); |
| for (auto& service_declaration : service_declaration_list) { |
| step.ForServiceDeclaration(std::move(service_declaration)); |
| } |
| |
| auto struct_declaration_list = std::move(file->struct_declaration_list); |
| for (auto& struct_declaration : struct_declaration_list) { |
| step.ForStructDeclaration(std::move(struct_declaration)); |
| } |
| |
| auto table_declaration_list = std::move(file->table_declaration_list); |
| for (auto& table_declaration : table_declaration_list) { |
| step.ForTableDeclaration(std::move(table_declaration)); |
| } |
| |
| auto union_declaration_list = std::move(file->union_declaration_list); |
| for (auto& union_declaration : union_declaration_list) { |
| step.ForUnionDeclaration(std::move(union_declaration)); |
| } |
| |
| auto type_decls = std::move(file->type_decls); |
| for (auto& type_decl : type_decls) { |
| step.ForTypeDecl(std::move(type_decl)); |
| } |
| |
| return step.Done(); |
| } |
| |
| bool Library::ResolveOrOperatorConstant(Constant* constant, const Type* type, |
| const ConstantValue& left_operand, |
| const ConstantValue& right_operand) { |
| assert(left_operand.kind == right_operand.kind && |
| "left and right operands of or operator must be of the same kind"); |
| type = TypeResolve(type); |
| if (type == nullptr) |
| return false; |
| if (type->kind != Type::Kind::kPrimitive) { |
| return Fail(ErrOrOperatorOnNonPrimitiveValue); |
| } |
| 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)); |
| return true; |
| } |
| |
| bool Library::ResolveConstant(Constant* constant, const Type* type) { |
| assert(constant != nullptr); |
| |
| // Prevent re-entry. |
| if (constant->compiled) |
| return constant->IsResolved(); |
| constant->compiled = true; |
| |
| switch (constant->kind) { |
| case Constant::Kind::kIdentifier: { |
| auto identifier_constant = static_cast<IdentifierConstant*>(constant); |
| return ResolveIdentifierConstant(identifier_constant, type); |
| } |
| case Constant::Kind::kLiteral: { |
| auto literal_constant = static_cast<LiteralConstant*>(constant); |
| return ResolveLiteralConstant(literal_constant, type); |
| } |
| case Constant::Kind::kSynthesized: { |
| assert(false && "Compiler bug: synthesized constant does not have a resolved value!"); |
| break; |
| } |
| case Constant::Kind::kBinaryOperator: { |
| auto binary_operator_constant = static_cast<BinaryOperatorConstant*>(constant); |
| if (!ResolveConstant(binary_operator_constant->left_operand.get(), type)) { |
| return false; |
| } |
| if (!ResolveConstant(binary_operator_constant->right_operand.get(), type)) { |
| return false; |
| } |
| switch (binary_operator_constant->op) { |
| case BinaryOperatorConstant::Operator::kOr: { |
| return ResolveOrOperatorConstant(constant, type, |
| binary_operator_constant->left_operand->Value(), |
| binary_operator_constant->right_operand->Value()); |
| } |
| } |
| assert(false && "Compiler bug: unhandled binary operator"); |
| break; |
| } |
| } |
| |
| __builtin_unreachable(); |
| } |
| |
| ConstantValue::Kind Library::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::kUint16: |
| return ConstantValue::Kind::kUint16; |
| case types::PrimitiveSubtype::kUint32: |
| return ConstantValue::Kind::kUint32; |
| case types::PrimitiveSubtype::kUint64: |
| return ConstantValue::Kind::kUint64; |
| case types::PrimitiveSubtype::kFloat32: |
| return ConstantValue::Kind::kFloat32; |
| case types::PrimitiveSubtype::kFloat64: |
| return ConstantValue::Kind::kFloat64; |
| } |
| assert(false && "Compiler bug: unhandled primitive subtype"); |
| } |
| |
| bool Library::ResolveIdentifierConstant(IdentifierConstant* identifier_constant, const Type* type) { |
| assert(TypeCanBeConst(type) && |
| "Compiler bug: resolving identifier constant to non-const-able type!"); |
| |
| auto decl = LookupDeclByName(identifier_constant->name.memberless_key()); |
| if (!decl) |
| return false; |
| |
| if (!CompileDecl(decl)) { |
| return false; |
| } |
| |
| const Type* const_type = nullptr; |
| const ConstantValue* const_val = nullptr; |
| switch (decl->kind) { |
| case Decl::Kind::kConst: { |
| auto const_decl = static_cast<Const*>(decl); |
| const_type = GetType(const_decl->type_ctor); |
| const_val = &const_decl->value->Value(); |
| break; |
| } |
| case Decl::Kind::kEnum: { |
| // If there is no member name, fallthrough to default. |
| if (auto member_name = identifier_constant->name.member_name(); member_name) { |
| auto enum_decl = static_cast<Enum*>(decl); |
| const_type = GetType(enum_decl->subtype_ctor); |
| for (auto& member : enum_decl->members) { |
| if (member.name.data() == member_name) { |
| const_val = &member.value->Value(); |
| } |
| } |
| if (!const_val) { |
| return Fail(ErrUnknownEnumMember, identifier_constant->name.span(), |
| std::string_view(*member_name)); |
| } |
| break; |
| } |
| [[fallthrough]]; |
| } |
| case Decl::Kind::kBits: { |
| // If there is no member name, fallthrough to default. |
| if (auto member_name = identifier_constant->name.member_name(); member_name) { |
| auto bits_decl = static_cast<Bits*>(decl); |
| const_type = GetType(bits_decl->subtype_ctor); |
| for (auto& member : bits_decl->members) { |
| if (member.name.data() == member_name) { |
| const_val = &member.value->Value(); |
| } |
| } |
| if (!const_val) { |
| return Fail(ErrUnknownBitsMember, identifier_constant->name.span(), |
| std::string_view(*member_name)); |
| } |
| break; |
| } |
| [[fallthrough]]; |
| } |
| default: { |
| return Fail(ErrExpectedValueButGotType, identifier_constant->name.span(), |
| identifier_constant->name); |
| } |
| } |
| |
| assert(const_val && "Compiler bug: did not set const_val"); |
| assert(const_type && "Compiler bug: did not set const_type"); |
| |
| std::unique_ptr<ConstantValue> resolved_val; |
| 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); |
| assert(GetType(enum_decl->subtype_ctor)->kind == Type::Kind::kPrimitive); |
| primitive_type = static_cast<const PrimitiveType*>(GetType(enum_decl->subtype_ctor)); |
| break; |
| } |
| case Decl::Kind::kBits: { |
| auto bits_decl = static_cast<const Bits*>(identifier_type->type_decl); |
| assert(GetType(bits_decl->subtype_ctor)->kind == Type::Kind::kPrimitive); |
| primitive_type = static_cast<const PrimitiveType*>(GetType(bits_decl->subtype_ctor)); |
| break; |
| } |
| default: { |
| assert(false && "Compiler bug: identifier not of const-able type."); |
| } |
| } |
| |
| auto fail_with_mismatched_type = [this, identifier_type](const Name& type_name) { |
| return Fail(ErrMismatchedNameTypeAssignment, identifier_type->type_decl->name, type_name); |
| }; |
| |
| switch (decl->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 (decl->name != identifier_type->type_decl->name) |
| return fail_with_mismatched_type(decl->name); |
| break; |
| } |
| default: { |
| assert(false && "Compiler bug: identifier not of const-able type."); |
| } |
| } |
| |
| if (!const_val->Convert(ConstantValuePrimitiveKind(primitive_type->subtype), &resolved_val)) |
| goto fail_cannot_convert; |
| break; |
| } |
| default: { |
| assert(false && "Compiler bug: identifier not of const-able type."); |
| } |
| } |
| |
| identifier_constant->ResolveTo(std::move(resolved_val)); |
| return true; |
| |
| fail_cannot_convert: |
| return Fail(ErrCannotConvertConstantToType, identifier_constant, const_type, type); |
| } |
| |
| bool Library::ResolveLiteralConstant(LiteralConstant* literal_constant, const Type* type) { |
| switch (literal_constant->literal->kind) { |
| case raw::Literal::Kind::kString: { |
| if (type->kind != Type::Kind::kString) |
| goto return_fail; |
| auto string_type = static_cast<const StringType*>(type); |
| auto string_literal = static_cast<raw::StringLiteral*>(literal_constant->literal.get()); |
| auto string_data = string_literal->span().data(); |
| |
| // TODO(pascallouis): because data() contains the raw content, |
| // with the two " to identify strings, we need to take this |
| // into account. We should expose the actual size of string |
| // literals properly, and take into account escaping. |
| uint64_t string_size = string_data.size() - 2; |
| if (string_type->max_size->value < string_size) { |
| return Fail(ErrStringConstantExceedsSizeBound, literal_constant->literal->span(), |
| literal_constant, string_size, type); |
| } |
| |
| literal_constant->ResolveTo( |
| std::make_unique<StringConstantValue>(string_literal->span().data())); |
| return true; |
| } |
| case raw::Literal::Kind::kTrue: { |
| if (type->kind != Type::Kind::kPrimitive) |
| goto return_fail; |
| if (static_cast<const PrimitiveType*>(type)->subtype != types::PrimitiveSubtype::kBool) |
| goto return_fail; |
| literal_constant->ResolveTo(std::make_unique<BoolConstantValue>(true)); |
| return true; |
| } |
| case raw::Literal::Kind::kFalse: { |
| if (type->kind != Type::Kind::kPrimitive) |
| goto return_fail; |
| if (static_cast<const PrimitiveType*>(type)->subtype != types::PrimitiveSubtype::kBool) |
| goto return_fail; |
| literal_constant->ResolveTo(std::make_unique<BoolConstantValue>(false)); |
| return true; |
| } |
| case raw::Literal::Kind::kNumeric: { |
| if (type->kind != Type::Kind::kPrimitive) |
| goto return_fail; |
| |
| // These must be initialized out of line to allow for goto statement |
| const raw::NumericLiteral* numeric_literal; |
| const PrimitiveType* primitive_type; |
| numeric_literal = static_cast<const raw::NumericLiteral*>(literal_constant->literal.get()); |
| primitive_type = static_cast<const PrimitiveType*>(type); |
| switch (primitive_type->subtype) { |
| case types::PrimitiveSubtype::kInt8: { |
| int8_t value; |
| if (!ParseNumericLiteral<int8_t>(numeric_literal, &value)) |
| goto return_fail; |
| literal_constant->ResolveTo(std::make_unique<NumericConstantValue<int8_t>>(value)); |
| return true; |
| } |
| case types::PrimitiveSubtype::kInt16: { |
| int16_t value; |
| if (!ParseNumericLiteral<int16_t>(numeric_literal, &value)) |
| goto return_fail; |
| |
| literal_constant->ResolveTo(std::make_unique<NumericConstantValue<int16_t>>(value)); |
| return true; |
| } |
| case types::PrimitiveSubtype::kInt32: { |
| int32_t value; |
| if (!ParseNumericLiteral<int32_t>(numeric_literal, &value)) |
| goto return_fail; |
| |
| literal_constant->ResolveTo(std::make_unique<NumericConstantValue<int32_t>>(value)); |
| return true; |
| } |
| case types::PrimitiveSubtype::kInt64: { |
| int64_t value; |
| if (!ParseNumericLiteral<int64_t>(numeric_literal, &value)) |
| goto return_fail; |
| |
| literal_constant->ResolveTo(std::make_unique<NumericConstantValue<int64_t>>(value)); |
| return true; |
| } |
| case types::PrimitiveSubtype::kUint8: { |
| uint8_t value; |
| if (!ParseNumericLiteral<uint8_t>(numeric_literal, &value)) |
| goto return_fail; |
| |
| literal_constant->ResolveTo(std::make_unique<NumericConstantValue<uint8_t>>(value)); |
| return true; |
| } |
| case types::PrimitiveSubtype::kUint16: { |
| uint16_t value; |
| if (!ParseNumericLiteral<uint16_t>(numeric_literal, &value)) |
| goto return_fail; |
| |
| literal_constant->ResolveTo(std::make_unique<NumericConstantValue<uint16_t>>(value)); |
| return true; |
| } |
| case types::PrimitiveSubtype::kUint32: { |
| uint32_t value; |
| if (!ParseNumericLiteral<uint32_t>(numeric_literal, &value)) |
| goto return_fail; |
| |
| literal_constant->ResolveTo(std::make_unique<NumericConstantValue<uint32_t>>(value)); |
| return true; |
| } |
| case types::PrimitiveSubtype::kUint64: { |
| uint64_t value; |
| if (!ParseNumericLiteral<uint64_t>(numeric_literal, &value)) |
| goto return_fail; |
| |
| literal_constant->ResolveTo(std::make_unique<NumericConstantValue<uint64_t>>(value)); |
| return true; |
| } |
| case types::PrimitiveSubtype::kFloat32: { |
| float value; |
| if (!ParseNumericLiteral<float>(numeric_literal, &value)) |
| goto return_fail; |
| literal_constant->ResolveTo(std::make_unique<NumericConstantValue<float>>(value)); |
| return true; |
| } |
| case types::PrimitiveSubtype::kFloat64: { |
| double value; |
| if (!ParseNumericLiteral<double>(numeric_literal, &value)) |
| goto return_fail; |
| |
| literal_constant->ResolveTo(std::make_unique<NumericConstantValue<double>>(value)); |
| return true; |
| } |
| default: |
| goto return_fail; |
| } |
| |
| return_fail: |
| return Fail(ErrConstantCannotBeInterpretedAsType, literal_constant->literal->span(), |
| literal_constant, type); |
| } |
| } |
| } |
| |
| bool Library::ResolveAsOptional(Constant* constant) const { |
| assert(constant); |
| |
| if (constant->kind != Constant::Kind::kIdentifier) |
| return false; |
| |
| // This refers to the `optional` constraint only if it is "optional" AND |
| // it is not shadowed by a previous definition. |
| // Note that as we improve scoping rules, we would need to allow `fidl.optional` |
| // to be the FQN for the `optional` constant. |
| auto identifier_constant = static_cast<IdentifierConstant*>(constant); |
| auto decl = LookupDeclByName(identifier_constant->name.memberless_key()); |
| if (decl) |
| return false; |
| |
| return identifier_constant->name.decl_name() == "optional"; |
| } |
| |
| const Type* Library::TypeResolve(const Type* type) { |
| if (type->kind != Type::Kind::kIdentifier) { |
| return type; |
| } |
| auto identifier_type = static_cast<const IdentifierType*>(type); |
| Decl* decl = LookupDeclByName(identifier_type->name); |
| if (!decl) { |
| Fail(ErrCouldNotResolveIdentifierToType); |
| return nullptr; |
| } |
| if (!CompileDecl(decl)) |
| return nullptr; |
| switch (decl->kind) { |
| case Decl::Kind::kBits: |
| return GetType(static_cast<const Bits*>(decl)->subtype_ctor); |
| case Decl::Kind::kEnum: |
| return GetType(static_cast<const Enum*>(decl)->subtype_ctor); |
| default: |
| return type; |
| } |
| } |
| |
| bool Library::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 Library::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: { |
| if (from_type->kind != flat::Type::Kind::kPrimitive) { |
| return false; |
| } |
| |
| auto from_primitive_type = static_cast<const flat::PrimitiveType*>(from_type); |
| auto to_primitive_type = static_cast<const flat::PrimitiveType*>(to_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 |
| } |
| |
| // Library resolution is concerned with resolving identifiers to their |
| // declarations, and with computing type sizes and alignments. |
| |
| Decl* Library::LookupDeclByName(Name::Key name) const { |
| auto iter = declarations_.find(name); |
| if (iter == declarations_.end()) { |
| return nullptr; |
| } |
| return iter->second; |
| } |
| |
| template <typename NumericType> |
| bool Library::ParseNumericLiteral(const raw::NumericLiteral* literal, |
| NumericType* out_value) const { |
| assert(literal != nullptr); |
| assert(out_value != nullptr); |
| |
| auto data = literal->span().data(); |
| std::string string_data(data.data(), data.data() + data.size()); |
| auto result = utils::ParseNumeric(string_data, out_value); |
| return result == utils::ParseNumericResult::kSuccess; |
| } |
| |
| bool Library::AddConstantDependencies(const Constant* constant, std::set<const Decl*>* out_edges) { |
| switch (constant->kind) { |
| case Constant::Kind::kIdentifier: { |
| auto identifier = static_cast<const flat::IdentifierConstant*>(constant); |
| auto decl = LookupDeclByName(identifier->name.memberless_key()); |
| if (decl == nullptr) { |
| return Fail(ErrFailedConstantLookup, identifier->name, identifier->name); |
| } |
| out_edges->insert(decl); |
| break; |
| } |
| case Constant::Kind::kLiteral: |
| case Constant::Kind::kSynthesized: { |
| // Literal and synthesized constants have no dependencies on other declarations. |
| break; |
| } |
| case Constant::Kind::kBinaryOperator: { |
| auto op = static_cast<const flat::BinaryOperatorConstant*>(constant); |
| return AddConstantDependencies(op->left_operand.get(), out_edges) && |
| AddConstantDependencies(op->right_operand.get(), out_edges); |
| } |
| } |
| return true; |
| } |
| |
| // Calculating declaration dependencies is largely serving the C/C++ family of languages bindings. |
| // For instance, the declaration of a struct member type must be defined before the containing |
| // struct if that member is stored inline. |
| // Given the FIDL declarations: |
| // |
| // struct D2 { D1 d; } |
| // struct D1 { int32 x; } |
| // |
| // We must first declare D1, followed by D2 when emitting C code. |
| // |
| // Below, an edge from D1 to D2 means that we must see the declaration of of D1 before |
| // the declaration of D2, i.e. the calculated set of |out_edges| represents all the declarations |
| // that |decl| depends on. |
| // |
| // Notes: |
| // - Nullable structs do not require dependency edges since they are boxed via a |
| // pointer indirection, and their content placed out-of-line. |
| bool Library::DeclDependencies(const Decl* decl, std::set<const Decl*>* out_edges) { |
| std::set<const Decl*> edges; |
| |
| auto maybe_add_decl = [&edges](TypeConstructorPtr type_ctor) { |
| TypeConstructorPtr current = type_ctor; |
| for (;;) { |
| const auto& invocation = GetLayoutInvocation(current); |
| if (invocation.from_type_alias) { |
| assert(!invocation.element_type_resolved && |
| "Compiler bug: partial aliases should be disallowed"); |
| edges.insert(invocation.from_type_alias); |
| return; |
| } |
| |
| const Type* type = GetType(current); |
| if (type->nullability == types::Nullability::kNullable) |
| return; |
| |
| switch (type->kind) { |
| case Type::Kind::kHandle: { |
| auto handle_type = static_cast<const HandleType*>(type); |
| // TODO(fxbug.dev/74909): the only case where resource_decl is nullptr |
| // is when the type constructor is referring to a bare, unconstrained |
| // `handle`, in which case there is no resource definition to depend |
| // on. Once this is disallowed, we can remove this check. |
| if (handle_type->resource_decl) { |
| auto decl = static_cast<const Decl*>(handle_type->resource_decl); |
| edges.insert(decl); |
| } |
| return; |
| } |
| case Type::Kind::kPrimitive: |
| case Type::Kind::kString: |
| case Type::Kind::kRequestHandle: |
| return; |
| case Type::Kind::kArray: |
| case Type::Kind::kVector: { |
| if (IsTypeConstructorDefined(invocation.element_type_raw)) { |
| current = invocation.element_type_raw; |
| break; |
| } |
| // The type_ctor won't have an arg_type_ctor if the type is Bytes. |
| // In that case, just return since there are no edges |
| return; |
| } |
| case Type::Kind::kIdentifier: { |
| // should have been caught above and returned early. |
| assert(type->nullability != types::Nullability::kNullable); |
| auto identifier_type = static_cast<const IdentifierType*>(type); |
| auto decl = static_cast<const Decl*>(identifier_type->type_decl); |
| if (decl->kind != Decl::Kind::kProtocol) { |
| edges.insert(decl); |
| } |
| return; |
| } |
| } |
| } |
| }; |
| |
| switch (decl->kind) { |
| case Decl::Kind::kBits: { |
| auto bits_decl = static_cast<const Bits*>(decl); |
| maybe_add_decl(GetTypeCtorAsPtr(bits_decl->subtype_ctor)); |
| for (const auto& member : bits_decl->members) { |
| if (!AddConstantDependencies(member.value.get(), &edges)) { |
| return false; |
| } |
| } |
| break; |
| } |
| case Decl::Kind::kConst: { |
| auto const_decl = static_cast<const Const*>(decl); |
| maybe_add_decl(GetTypeCtorAsPtr(const_decl->type_ctor)); |
| if (!AddConstantDependencies(const_decl->value.get(), &edges)) { |
| return false; |
| } |
| break; |
| } |
| case Decl::Kind::kEnum: { |
| auto enum_decl = static_cast<const Enum*>(decl); |
| maybe_add_decl(GetTypeCtorAsPtr(enum_decl->subtype_ctor)); |
| for (const auto& member : enum_decl->members) { |
| if (!AddConstantDependencies(member.value.get(), &edges)) { |
| return false; |
| } |
| } |
| break; |
| } |
| case Decl::Kind::kProtocol: { |
| auto protocol_decl = static_cast<const Protocol*>(decl); |
| for (const auto& composed_protocol : protocol_decl->composed_protocols) { |
| if (auto type_decl = LookupDeclByName(composed_protocol); type_decl) { |
| edges.insert(type_decl); |
| } |
| } |
| for (const auto& method : protocol_decl->methods) { |
| if (method.maybe_request != nullptr) { |
| edges.insert(method.maybe_request); |
| } |
| if (method.maybe_response != nullptr) { |
| edges.insert(method.maybe_response); |
| } |
| } |
| break; |
| } |
| case Decl::Kind::kResource: { |
| auto resource_decl = static_cast<const Resource*>(decl); |
| maybe_add_decl(GetTypeCtorAsPtr(resource_decl->subtype_ctor)); |
| break; |
| } |
| case Decl::Kind::kService: { |
| auto service_decl = static_cast<const Service*>(decl); |
| for (const auto& member : service_decl->members) { |
| maybe_add_decl(GetTypeCtorAsPtr(member.type_ctor)); |
| } |
| break; |
| } |
| case Decl::Kind::kStruct: { |
| auto struct_decl = static_cast<const Struct*>(decl); |
| for (const auto& member : struct_decl->members) { |
| maybe_add_decl(GetTypeCtorAsPtr(member.type_ctor)); |
| if (member.maybe_default_value) { |
| if (!AddConstantDependencies(member.maybe_default_value.get(), &edges)) { |
| return false; |
| } |
| } |
| } |
| 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; |
| maybe_add_decl(GetTypeCtorAsPtr(member.maybe_used->type_ctor)); |
| if (member.maybe_used->maybe_default_value) { |
| if (!AddConstantDependencies(member.maybe_used->maybe_default_value.get(), &edges)) { |
| return false; |
| } |
| } |
| } |
| 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; |
| maybe_add_decl(GetTypeCtorAsPtr(member.maybe_used->type_ctor)); |
| } |
| break; |
| } |
| case Decl::Kind::kTypeAlias: { |
| auto type_alias_decl = static_cast<const TypeAlias*>(decl); |
| maybe_add_decl(GetTypeCtorAsPtr(type_alias_decl->partial_type_ctor)); |
| } |
| } // switch |
| *out_edges = std::move(edges); |
| return true; |
| } |
| |
| namespace { |
| // Declaration comparator. |
| // |
| // (1) To compare two Decl's in the same library, it suffices to compare the |
| // unqualified names of the Decl's. (This is faster.) |
| // |
| // (2) To compare two Decl's across libraries, we rely on the fully qualified |
| // names of the Decl's. (This is slower.) |
| struct CmpDeclInLibrary { |
| bool operator()(const Decl* a, const Decl* b) const { |
| assert(a->name != b->name || a == b); |
| const Library* a_library = a->name.library(); |
| const Library* b_library = b->name.library(); |
| if (a_library != b_library) { |
| return NameFlatName(a->name) < NameFlatName(b->name); |
| } else { |
| return a->name.decl_name() < b->name.decl_name(); |
| } |
| } |
| }; |
| } // namespace |
| |
| bool Library::SortDeclarations() { |
| // |degree| is the number of undeclared dependencies for each decl. |
| std::map<const Decl*, uint32_t, CmpDeclInLibrary> degrees; |
| // |inverse_dependencies| records the decls that depend on each decl. |
| std::map<const Decl*, std::vector<const Decl*>, CmpDeclInLibrary> inverse_dependencies; |
| for (const auto& name_and_decl : declarations_) { |
| const Decl* decl = name_and_decl.second; |
| std::set<const Decl*> deps; |
| if (!DeclDependencies(decl, &deps)) |
| return false; |
| degrees[decl] = static_cast<uint32_t>(deps.size()); |
| for (const Decl* dep : deps) { |
| inverse_dependencies[dep].push_back(decl); |
| } |
| } |
| |
| // Start with all decls that have no incoming edges. |
| std::vector<const Decl*> decls_without_deps; |
| for (const auto& decl_and_degree : degrees) { |
| if (decl_and_degree.second == 0u) { |
| decls_without_deps.push_back(decl_and_degree.first); |
| } |
| } |
| |
| while (!decls_without_deps.empty()) { |
| // Pull one out of the queue. |
| auto decl = decls_without_deps.back(); |
| decls_without_deps.pop_back(); |
| assert(degrees[decl] == 0u); |
| declaration_order_.push_back(decl); |
| |
| // Decrement the incoming degree of all the other decls it |
| // points to. |
| auto& inverse_deps = inverse_dependencies[decl]; |
| for (const Decl* inverse_dep : inverse_deps) { |
| uint32_t& degree = degrees[inverse_dep]; |
| assert(degree != 0u); |
| degree -= 1; |
| if (degree == 0u) |
| decls_without_deps.push_back(inverse_dep); |
| } |
| } |
| |
| if (declaration_order_.size() != degrees.size()) { |
| // We didn't visit all the edges! There was a cycle. |
| return Fail(ErrIncludeCycle); |
| } |
| |
| return true; |
| } |
| |
| bool Library::CompileDecl(Decl* decl) { |
| if (decl->compiled) |
| return true; |
| Compiling guard(decl); |
| switch (decl->kind) { |
| case Decl::Kind::kBits: { |
| auto bits_decl = static_cast<Bits*>(decl); |
| if (!CompileBits(bits_decl)) |
| return false; |
| break; |
| } |
| case Decl::Kind::kConst: { |
| auto const_decl = static_cast<Const*>(decl); |
| if (!CompileConst(const_decl)) |
| return false; |
| break; |
| } |
| case Decl::Kind::kEnum: { |
| auto enum_decl = static_cast<Enum*>(decl); |
| if (!CompileEnum(enum_decl)) |
| return false; |
| break; |
| } |
| case Decl::Kind::kProtocol: { |
| auto protocol_decl = static_cast<Protocol*>(decl); |
| if (!CompileProtocol(protocol_decl)) |
| return false; |
| break; |
| } |
| case Decl::Kind::kResource: { |
| auto resource_decl = static_cast<Resource*>(decl); |
| if (!CompileResource(resource_decl)) |
| return false; |
| break; |
| } |
| case Decl::Kind::kService: { |
| auto service_decl = static_cast<Service*>(decl); |
| if (!CompileService(service_decl)) |
| return false; |
| break; |
| } |
| case Decl::Kind::kStruct: { |
| auto struct_decl = static_cast<Struct*>(decl); |
| if (!CompileStruct(struct_decl)) |
| return false; |
| break; |
| } |
| case Decl::Kind::kTable: { |
| auto table_decl = static_cast<Table*>(decl); |
| if (!CompileTable(table_decl)) |
| return false; |
| break; |
| } |
| case Decl::Kind::kUnion: { |
| auto union_decl = static_cast<Union*>(decl); |
| if (!CompileUnion(union_decl)) |
| return false; |
| break; |
| } |
| case Decl::Kind::kTypeAlias: { |
| auto type_alias_decl = static_cast<TypeAlias*>(decl); |
| if (!CompileTypeAlias(type_alias_decl)) |
| return false; |
| break; |
| } |
| } // switch |
| return true; |
| } |
| |
| void Library::VerifyDeclAttributes(const Decl* decl) { |
| assert(decl->compiled && "verification must happen after compilation of decls"); |
| auto placement_ok = reporter_->Checkpoint(); |
| switch (decl->kind) { |
| case Decl::Kind::kBits: { |
| auto bits_declaration = static_cast<const Bits*>(decl); |
| // Attributes: check placement. |
| ValidateAttributesPlacement(AttributeSchema::Placement::kBitsDecl, |
| bits_declaration->attributes.get()); |
| for (const auto& member : bits_declaration->members) { |
| ValidateAttributesPlacement(AttributeSchema::Placement::kBitsMember, |
| member.attributes.get()); |
| } |
| if (placement_ok.NoNewErrors()) { |
| // Attributes: check constraints. |
| ValidateAttributesConstraints(bits_declaration, bits_declaration->attributes.get()); |
| } |
| break; |
| } |
| case Decl::Kind::kConst: { |
| auto const_decl = static_cast<const Const*>(decl); |
| // Attributes: for const declarations, we only check placement. |
| ValidateAttributesPlacement(AttributeSchema::Placement::kConstDecl, |
| const_decl->attributes.get()); |
| break; |
| } |
| case Decl::Kind::kEnum: { |
| auto enum_declaration = static_cast<const Enum*>(decl); |
| // Attributes: check placement. |
| ValidateAttributesPlacement(AttributeSchema::Placement::kEnumDecl, |
| enum_declaration->attributes.get()); |
| for (const auto& member : enum_declaration->members) { |
| ValidateAttributesPlacement(AttributeSchema::Placement::kEnumMember, |
| member.attributes.get()); |
| } |
| if (placement_ok.NoNewErrors()) { |
| // Attributes: check constraints. |
| ValidateAttributesConstraints(enum_declaration, enum_declaration->attributes.get()); |
| } |
| break; |
| } |
| case Decl::Kind::kProtocol: { |
| auto protocol_declaration = static_cast<const Protocol*>(decl); |
| // Attributes: check placement. |
| ValidateAttributesPlacement(AttributeSchema::Placement::kProtocolDecl, |
| protocol_declaration->attributes.get()); |
| for (const auto& method_with_info : protocol_declaration->all_methods) { |
| ValidateAttributesPlacement(AttributeSchema::Placement::kMethod, |
| method_with_info.method->attributes.get()); |
| } |
| if (placement_ok.NoNewErrors()) { |
| // Attributes: check constraints. |
| for (const auto method_with_info : protocol_declaration->all_methods) { |
| const auto& method = *method_with_info.method; |
| if (method.maybe_request) { |
| ValidateAttributesConstraints(method.maybe_request, |
| protocol_declaration->attributes.get()); |
| ValidateAttributesConstraints(method.maybe_request, method.attributes.get()); |
| } |
| if (method.maybe_response) { |
| ValidateAttributesConstraints(method.maybe_response, |
| protocol_declaration->attributes.get()); |
| ValidateAttributesConstraints(method.maybe_response, method.attributes.get()); |
| } |
| } |
| } |
| break; |
| } |
| case Decl::Kind::kResource: { |
| auto resource_declaration = static_cast<const Resource*>(decl); |
| // Attributes: check placement. |
| ValidateAttributesPlacement(AttributeSchema::Placement::kResourceDecl, |
| resource_declaration->attributes.get()); |
| for (const auto& property : resource_declaration->properties) { |
| ValidateAttributesPlacement(AttributeSchema::Placement::kResourceProperty, |
| property.attributes.get()); |
| } |
| if (placement_ok.NoNewErrors()) { |
| // Attributes: check constraints. |
| ValidateAttributesConstraints(resource_declaration, resource_declaration->attributes.get()); |
| } |
| break; |
| } |
| case Decl::Kind::kService: { |
| auto service_decl = static_cast<const Service*>(decl); |
| // Attributes: check placement. |
| ValidateAttributesPlacement(AttributeSchema::Placement::kServiceDecl, |
| service_decl->attributes.get()); |
| for (const auto& member : service_decl->members) { |
| ValidateAttributesPlacement(AttributeSchema::Placement::kServiceMember, |
| member.attributes.get()); |
| } |
| if (placement_ok.NoNewErrors()) { |
| // Attributes: check constraint. |
| ValidateAttributesConstraints(service_decl, service_decl->attributes.get()); |
| } |
| break; |
| } |
| case Decl::Kind::kStruct: { |
| auto struct_declaration = static_cast<const Struct*>(decl); |
| // Attributes: check placement. |
| ValidateAttributesPlacement(AttributeSchema::Placement::kStructDecl, |
| struct_declaration->attributes.get()); |
| for (const auto& member : struct_declaration->members) { |
| ValidateAttributesPlacement(AttributeSchema::Placement::kStructMember, |
| member.attributes.get()); |
| } |
| if (placement_ok.NoNewErrors()) { |
| // Attributes: check constraint. |
| ValidateAttributesConstraints(struct_declaration, struct_declaration->attributes.get()); |
| } |
| break; |
| } |
| case Decl::Kind::kTable: { |
| auto table_declaration = static_cast<const Table*>(decl); |
| // Attributes: check placement. |
| ValidateAttributesPlacement(AttributeSchema::Placement::kTableDecl, |
| table_declaration->attributes.get()); |
| for (const auto& member : table_declaration->members) { |
| if (!member.maybe_used) |
| continue; |
| ValidateAttributesPlacement(AttributeSchema::Placement::kTableMember, |
| member.maybe_used->attributes.get()); |
| } |
| if (placement_ok.NoNewErrors()) { |
| // Attributes: check constraint. |
| ValidateAttributesConstraints(table_declaration, table_declaration->attributes.get()); |
| } |
| break; |
| } |
| case Decl::Kind::kUnion: { |
| auto union_declaration = static_cast<const Union*>(decl); |
| // Attributes: check placement. |
| ValidateAttributesPlacement(AttributeSchema::Placement::kUnionDecl, |
| union_declaration->attributes.get()); |
| for (const auto& member : union_declaration->members) { |
| if (!member.maybe_used) |
| continue; |
| ValidateAttributesPlacement(AttributeSchema::Placement::kUnionMember, |
| member.maybe_used->attributes.get()); |
| } |
| if (placement_ok.NoNewErrors()) { |
| // Attributes: check constraint. |
| ValidateAttributesConstraints(union_declaration, union_declaration->attributes.get()); |
| } |
| break; |
| } |
| case Decl::Kind::kTypeAlias: { |
| auto type_alias_declaration = static_cast<const TypeAlias*>(decl); |
| // Attributes: check placement. |
| ValidateAttributesPlacement(AttributeSchema::Placement::kTypeAliasDecl, |
| type_alias_declaration->attributes.get()); |
| if (placement_ok.NoNewErrors()) { |
| // Attributes: check constraints. |
| ValidateAttributesConstraints(type_alias_declaration, |
| type_alias_declaration->attributes.get()); |
| } |
| break; |
| } |
| } // switch |
| } |
| |
| void VerifyResourcenessStep::ForDecl(const Decl* decl) { |
| assert(decl->compiled && "verification must happen after compilation of decls"); |
| switch (decl->kind) { |
| case Decl::Kind::kStruct: { |
| const auto* struct_decl = static_cast<const Struct*>(decl); |
| if (struct_decl->resourceness == types::Resourceness::kValue) { |
| for (const auto& member : struct_decl->members) { |
| if (EffectiveResourceness(GetType(member.type_ctor)) == types::Resourceness::kResource) { |
| library_->reporter_->Report(ErrTypeMustBeResource, struct_decl->name.span(), |
| struct_decl->name, member.name.data(), |
| std::string_view("struct"), struct_decl->name); |
| } |
| } |
| } |
| break; |
| } |
| case Decl::Kind::kTable: { |
| const auto* table_decl = static_cast<const Table*>(decl); |
| if (table_decl->resourceness == types::Resourceness::kValue) { |
| for (const auto& member : table_decl->members) { |
| if (member.maybe_used) { |
| const auto& used = *member.maybe_used; |
| if (EffectiveResourceness(GetType(used.type_ctor)) == types::Resourceness::kResource) { |
| library_->reporter_->Report(ErrTypeMustBeResource, table_decl->name.span(), |
| table_decl->name, used.name.data(), |
| std::string_view("table"), table_decl->name); |
| } |
| } |
| } |
| } |
| break; |
| } |
| case Decl::Kind::kUnion: { |
| const auto* union_decl = static_cast<const Union*>(decl); |
| if (union_decl->resourceness == types::Resourceness::kValue) { |
| for (const auto& member : union_decl->members) { |
| if (member.maybe_used) { |
| const auto& used = *member.maybe_used; |
| if (EffectiveResourceness(GetType(used.type_ctor)) == types::Resourceness::kResource) { |
| library_->reporter_->Report(ErrTypeMustBeResource, union_decl->name.span(), |
| union_decl->name, used.name.data(), |
| std::string_view("union"), union_decl->name); |
| } |
| } |
| } |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| |
| types::Resourceness Type::Resourceness() const { |
| switch (this->kind) { |
| case Type::Kind::kPrimitive: |
| case Type::Kind::kString: |
| return types::Resourceness::kValue; |
| case Type::Kind::kHandle: |
| case Type::Kind::kRequestHandle: |
| return types::Resourceness::kResource; |
| case Type::Kind::kArray: |
| return static_cast<const ArrayType*>(this)->element_type->Resourceness(); |
| case Type::Kind::kVector: |
| return static_cast<const VectorType*>(this)->element_type->Resourceness(); |
| case Type::Kind::kIdentifier: |
| break; |
| } |
| |
| auto decl = static_cast<const IdentifierType*>(this)->type_decl; |
| switch (decl->kind) { |
| case Decl::Kind::kBits: |
| case Decl::Kind::kEnum: |
| return types::Resourceness::kValue; |
| case Decl::Kind::kProtocol: |
| return types::Resourceness::kResource; |
| case Decl::Kind::kStruct: |
| assert(decl->compiled && "Compiler bug: accessing resourceness of not-yet-compiled struct"); |
| return static_cast<const Struct*>(decl)->resourceness.value(); |
| case Decl::Kind::kTable: |
| return static_cast<const Table*>(decl)->resourceness; |
| case Decl::Kind::kUnion: |
| assert(decl->compiled && "Compiler bug: accessing resourceness of not-yet-compiled union"); |
| return static_cast<const Union*>(decl)->resourceness.value(); |
| case Decl::Kind::kConst: |
| case Decl::Kind::kResource: |
| case Decl::Kind::kService: |
| case Decl::Kind::kTypeAlias: |
| assert(false && "Compiler bug: unexpected kind"); |
| } |
| |
| __builtin_unreachable(); |
| } |
| |
| types::Resourceness VerifyResourcenessStep::EffectiveResourceness(const Type* type) { |
| switch (type->kind) { |
| case Type::Kind::kPrimitive: |
| case Type::Kind::kString: |
| return types::Resourceness::kValue; |
| case Type::Kind::kHandle: |
| case Type::Kind::kRequestHandle: |
| return types::Resourceness::kResource; |
| case Type::Kind::kArray: |
| return EffectiveResourceness(static_cast<const ArrayType*>(type)->element_type); |
| case Type::Kind::kVector: |
| return EffectiveResourceness(static_cast<const VectorType*>(type)->element_type); |
| case Type::Kind::kIdentifier: |
| break; |
| } |
| |
| auto decl = static_cast<const IdentifierType*>(type)->type_decl; |
| switch (decl->kind) { |
| case Decl::Kind::kBits: |
| case Decl::Kind::kEnum: |
| return types::Resourceness::kValue; |
| case Decl::Kind::kProtocol: |
| return types::Resourceness::kResource; |
| case Decl::Kind::kStruct: |
| if (static_cast<const Struct*>(decl)->resourceness.value() == |
| types::Resourceness::kResource) { |
| return types::Resourceness::kResource; |
| } |
| break; |
| case Decl::Kind::kTable: |
| if (static_cast<const Table*>(decl)->resourceness == types::Resourceness::kResource) { |
| return types::Resourceness::kResource; |
| } |
| break; |
| case Decl::Kind::kUnion: |
| if (static_cast<const Union*>(decl)->resourceness.value() == types::Resourceness::kResource) { |
| return types::Resourceness::kResource; |
| } |
| break; |
| case Decl::Kind::kService: |
| return types::Resourceness::kValue; |
| case Decl::Kind::kConst: |
| case Decl::Kind::kResource: |
| case Decl::Kind::kTypeAlias: |
| assert(false && "Compiler bug: unexpected kind"); |
| } |
| |
| const auto [it, inserted] = effective_resourceness_.try_emplace(decl, std::nullopt); |
| if (!inserted) { |
| const auto& maybe_value = it->second; |
| // If we already computed effective resourceness, return it. If we started |
| // computing it but did not complete (nullopt), we're in a cycle, so return |
| // kValue as the default assumption. |
| return maybe_value.value_or(types::Resourceness::kValue); |
| } |
| |
| switch (decl->kind) { |
| case Decl::Kind::kStruct: |
| for (const auto& member : static_cast<const Struct*>(decl)->members) { |
| if (EffectiveResourceness(GetType(member.type_ctor)) == types::Resourceness::kResource) { |
| effective_resourceness_[decl] = types::Resourceness::kResource; |
| return types::Resourceness::kResource; |
| } |
| } |
| break; |
| case Decl::Kind::kTable: |
| for (const auto& member : static_cast<const Table*>(decl)->members) { |
| const auto& used = member.maybe_used; |
| if (used && |
| EffectiveResourceness(GetType(used->type_ctor)) == types::Resourceness::kResource) { |
| effective_resourceness_[decl] = types::Resourceness::kResource; |
| return types::Resourceness::kResource; |
| } |
| } |
| break; |
| case Decl::Kind::kUnion: |
| for (const auto& member : static_cast<const Union*>(decl)->members) { |
| const auto& used = member.maybe_used; |
| if (used && |
| EffectiveResourceness(GetType(used->type_ctor)) == types::Resourceness::kResource) { |
| effective_resourceness_[decl] = types::Resourceness::kResource; |
| return types::Resourceness::kResource; |
| } |
| } |
| break; |
| default: |
| assert(false && "Compiler bug: unexpected kind"); |
| } |
| |
| effective_resourceness_[decl] = types::Resourceness::kValue; |
| return types::Resourceness::kValue; |
| } |
| |
| bool Library::CompileBits(Bits* bits_declaration) { |
| if (!CompileTypeConstructor(&bits_declaration->subtype_ctor)) |
| return false; |
| |
| if (GetType(bits_declaration->subtype_ctor)->kind != Type::Kind::kPrimitive) { |
| return Fail(ErrBitsTypeMustBeUnsignedIntegralPrimitive, *bits_declaration, |
| GetType(bits_declaration->subtype_ctor)); |
| } |
| |
| // Validate constants. |
| auto primitive_type = static_cast<const PrimitiveType*>(GetType(bits_declaration->subtype_ctor)); |
| switch (primitive_type->subtype) { |
| case types::PrimitiveSubtype::kUint8: { |
| uint8_t mask; |
| if (!ValidateBitsMembersAndCalcMask<uint8_t>(bits_declaration, &mask)) |
| return false; |
| bits_declaration->mask = mask; |
| break; |
| } |
| case types::PrimitiveSubtype::kUint16: { |
| uint16_t mask; |
| if (!ValidateBitsMembersAndCalcMask<uint16_t>(bits_declaration, &mask)) |
| return false; |
| bits_declaration->mask = mask; |
| break; |
| } |
| case types::PrimitiveSubtype::kUint32: { |
| uint32_t mask; |
| if (!ValidateBitsMembersAndCalcMask<uint32_t>(bits_declaration, &mask)) |
| return false; |
| bits_declaration->mask = mask; |
| break; |
| } |
| case types::PrimitiveSubtype::kUint64: { |
| uint64_t mask; |
| if (!ValidateBitsMembersAndCalcMask<uint64_t>(bits_declaration, &mask)) |
| return false; |
| 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::kFloat32: |
| case types::PrimitiveSubtype::kFloat64: |
| return Fail(ErrBitsTypeMustBeUnsignedIntegralPrimitive, *bits_declaration, |
| GetType(bits_declaration->subtype_ctor)); |
| } |
| |
| { |
| // In the line below, `nullptr` needs an explicit cast to the pointer type due to |
| // C++ template mechanics. |
| auto err = ValidateUnknownConstraints<const Bits::Member>( |
| *bits_declaration, bits_declaration->strictness, nullptr); |
| if (err) { |
| return Fail(std::move(err)); |
| } |
| } |
| |
| return true; |
| } |
| |
| bool Library::CompileConst(Const* const_declaration) { |
| if (!CompileTypeConstructor(&const_declaration->type_ctor)) |
| return false; |
| const auto* const_type = GetType(const_declaration->type_ctor); |
| if (!TypeCanBeConst(const_type)) { |
| return Fail(ErrInvalidConstantType, *const_declaration, const_type); |
| } |
| if (!ResolveConstant(const_declaration->value.get(), const_type)) |
| return Fail(ErrCannotResolveConstantValue, *const_declaration); |
| |
| return true; |
| } |
| |
| bool Library::CompileEnum(Enum* enum_declaration) { |
| if (!CompileTypeConstructor(&enum_declaration->subtype_ctor)) |
| return false; |
| |
| if (GetType(enum_declaration->subtype_ctor)->kind != Type::Kind::kPrimitive) { |
| return Fail(ErrEnumTypeMustBeIntegralPrimitive, *enum_declaration, |
| GetType(enum_declaration->subtype_ctor)); |
| } |
| |
| // Validate constants. |
| auto primitive_type = static_cast<const PrimitiveType*>(GetType(enum_declaration->subtype_ctor)); |
| 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)) |
| return false; |
| enum_declaration->unknown_value_signed = unknown_value; |
| break; |
| } |
| case types::PrimitiveSubtype::kInt16: { |
| int16_t unknown_value; |
| if (!ValidateEnumMembersAndCalcUnknownValue<int16_t>(enum_declaration, &unknown_value)) |
| return false; |
| enum_declaration->unknown_value_signed = unknown_value; |
| break; |
| } |
| case types::PrimitiveSubtype::kInt32: { |
| int32_t unknown_value; |
| if (!ValidateEnumMembersAndCalcUnknownValue<int32_t>(enum_declaration, &unknown_value)) |
| return false; |
| enum_declaration->unknown_value_signed = unknown_value; |
| break; |
| } |
| case types::PrimitiveSubtype::kInt64: { |
| int64_t unknown_value; |
| if (!ValidateEnumMembersAndCalcUnknownValue<int64_t>(enum_declaration, &unknown_value)) |
| return false; |
| enum_declaration->unknown_value_signed = unknown_value; |
| break; |
| } |
| case types::PrimitiveSubtype::kUint8: { |
| uint8_t unknown_value; |
| if (!ValidateEnumMembersAndCalcUnknownValue<uint8_t>(enum_declaration, &unknown_value)) |
| return false; |
| enum_declaration->unknown_value_unsigned = unknown_value; |
| break; |
| } |
| case types::PrimitiveSubtype::kUint16: { |
| uint16_t unknown_value; |
| if (!ValidateEnumMembersAndCalcUnknownValue<uint16_t>(enum_declaration, &unknown_value)) |
| return false; |
| enum_declaration->unknown_value_unsigned = unknown_value; |
| break; |
| } |
| case types::PrimitiveSubtype::kUint32: { |
| uint32_t unknown_value; |
| if (!ValidateEnumMembersAndCalcUnknownValue<uint32_t>(enum_declaration, &unknown_value)) |
| return false; |
| enum_declaration->unknown_value_unsigned = unknown_value; |
| break; |
| } |
| case types::PrimitiveSubtype::kUint64: { |
| uint64_t unknown_value; |
| if (!ValidateEnumMembersAndCalcUnknownValue<uint64_t>(enum_declaration, &unknown_value)) |
| return false; |
| enum_declaration->unknown_value_unsigned = unknown_value; |
| break; |
| } |
| case types::PrimitiveSubtype::kBool: |
| case types::PrimitiveSubtype::kFloat32: |
| case types::PrimitiveSubtype::kFloat64: |
| return Fail(ErrEnumTypeMustBeIntegralPrimitive, *enum_declaration, |
| GetType(enum_declaration->subtype_ctor)); |
| } |
| |
| return true; |
| } |
| |
| bool HasSimpleLayout(const Decl* decl) { return decl->HasAttribute("ForDeprecatedCBindings"); } |
| |
| bool Library::CompileResource(Resource* resource_declaration) { |
| Scope<std::string_view> scope; |
| if (!CompileTypeConstructor(&resource_declaration->subtype_ctor)) |
| return false; |
| |
| if (GetType(resource_declaration->subtype_ctor)->kind != Type::Kind::kPrimitive) { |
| return Fail(ErrEnumTypeMustBeIntegralPrimitive, *resource_declaration, |
| GetType(resource_declaration->subtype_ctor)); |
| } |
| |
| for (auto& property : resource_declaration->properties) { |
| auto name_result = scope.Insert(property.name.data(), property.name); |
| if (!name_result.ok()) |
| return Fail(ErrDuplicateResourcePropertyName, property.name, |
| name_result.previous_occurrence()); |
| |
| if (!CompileTypeConstructor(&property.type_ctor)) |
| return false; |
| } |
| return true; |
| } |
| |
| bool Library::CompileProtocol(Protocol* protocol_declaration) { |
| MethodScope method_scope; |
| auto CheckScopes = [this, &protocol_declaration, &method_scope](const Protocol* protocol, |
| auto Visitor) -> bool { |
| for (const auto& name : protocol->composed_protocols) { |
| auto decl = LookupDeclByName(name); |
| // TODO(fxbug.dev/7926): Special handling here should not be required, we |
| // should first rely on creating the types representing composed |
| // protocols. |
| if (!decl) { |
| return Fail(ErrUnknownType, name, name); |
| } |
| if (decl->kind != Decl::Kind::kProtocol) |
| return Fail(ErrComposingNonProtocol, name); |
| auto composed_protocol = static_cast<const Protocol*>(decl); |
| auto span = composed_protocol->name.span(); |
| assert(span); |
| if (method_scope.protocols.Insert(composed_protocol, span.value()).ok()) { |
| if (!Visitor(composed_protocol, Visitor)) |
| return false; |
| } 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()) { |
| if (original_name == name_result.previous_occurrence().data()) { |
| return Fail(ErrDuplicateMethodName, method.name, original_name, |
| name_result.previous_occurrence()); |
| } |
| const auto previous_span = name_result.previous_occurrence(); |
| return Fail(ErrDuplicateMethodNameCanonical, method.name, original_name, |
| previous_span.data(), previous_span, canonical_name); |
| } |
| if (method.generated_ordinal64->value == 0) |
| return Fail(ErrGeneratedZeroValueOrdinal, method.generated_ordinal64->span()); |
| auto ordinal_result = |
| method_scope.ordinals.Insert(method.generated_ordinal64->value, method.name); |
| if (!ordinal_result.ok()) { |
| std::string replacement_method( |
| fidl::ordinals::GetSelector(method.attributes.get(), method.name)); |
| replacement_method.push_back('_'); |
| return Fail(ErrDuplicateMethodOrdinal, method.generated_ordinal64->span(), |
| ordinal_result.previous_occurrence(), replacement_method); |
| } |
| |
| // 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); |
| } |
| return true; |
| }; |
| if (!CheckScopes(protocol_declaration, CheckScopes)) |
| return false; |
| |
| for (auto& method : protocol_declaration->methods) { |
| if (method.maybe_request) { |
| if (!CompileDecl(method.maybe_request)) |
| return false; |
| } |
| if (method.maybe_response) { |
| if (!CompileDecl(method.maybe_response)) |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool Library::CompileService(Service* service_decl) { |
| Scope<std::string> scope; |
| for (auto& member : service_decl->members) { |
| 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 == name_result.previous_occurrence().data()) { |
| return Fail(ErrDuplicateServiceMemberName, member.name, original_name, previous_span); |
| } |
| return Fail(ErrDuplicateServiceMemberNameCanonical, member.name, original_name, |
| previous_span.data(), previous_span, canonical_name); |
| } |
| if (!CompileTypeConstructor(&member.type_ctor)) |
| return false; |
| // There's a mismatch between the "default" allowed categories and what is actually allowed |
| // in this context: in the new syntax, nothing changes. In the old syntax, we are more |
| // restrictive in this context, requiring kProtocolOnly rather than kTypeOrProtocol (which is |
| // the default for TypeConstructorOld). |
| bool ok = |
| std::visit(fidl::utils::matchers{ |
| [this](const std::unique_ptr<TypeConstructorOld>& type_ctor) -> bool { |
| return VerifyTypeCategory(type_ctor->type, type_ctor->name.span(), |
| AllowedCategories::kProtocolOnly); |
| }, |
| [](const std::unique_ptr<TypeConstructorNew>& t) -> bool { return true; }}, |
| member.type_ctor); |
| if (!ok) |
| return false; |
| if (GetType(member.type_ctor)->nullability != types::Nullability::kNonnullable) |
| return Fail(ErrNullableServiceMember, member.name); |
| } |
| return true; |
| } |
| |
| bool Library::CompileStruct(Struct* struct_declaration) { |
| Scope<std::string> scope; |
| DeriveResourceness derive_resourceness(&struct_declaration->resourceness); |
| for (auto& member : struct_declaration->members) { |
| 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()) { |
| return Fail(struct_declaration->is_request_or_response ? ErrDuplicateMethodParameterName |
| : ErrDuplicateStructMemberName, |
| member.name, original_name, previous_span); |
| } |
| return Fail(struct_declaration->is_request_or_response |
| ? ErrDuplicateMethodParameterNameCanonical |
| : ErrDuplicateStructMemberNameCanonical, |
| member.name, original_name, previous_span.data(), previous_span, canonical_name); |
| } |
| |
| if (!CompileTypeConstructor(&member.type_ctor)) |
| return false; |
| assert(!(struct_declaration->is_request_or_response && member.maybe_default_value) && |
| "method parameters cannot have default values"); |
| if (member.maybe_default_value) { |
| const auto* default_value_type = GetType(member.type_ctor); |
| if (!TypeCanBeConst(default_value_type)) { |
| return Fail(ErrInvalidStructMemberType, *struct_declaration, NameIdentifier(member.name), |
| default_value_type); |
| } |
| if (!ResolveConstant(member.maybe_default_value.get(), default_value_type)) { |
| return false; |
| } |
| } |
| derive_resourceness.AddType(GetType(member.type_ctor)); |
| } |
| |
| return true; |
| } |
| |
| bool Library::CompileTable(Table* table_declaration) { |
| Scope<std::string> name_scope; |
| Ordinal64Scope ordinal_scope; |
| |
| for (auto& member : table_declaration->members) { |
| const auto ordinal_result = ordinal_scope.Insert(member.ordinal->value, member.ordinal->span()); |
| if (!ordinal_result.ok()) { |
| return Fail(ErrDuplicateTableFieldOrdinal, member.ordinal->span(), |
| ordinal_result.previous_occurrence()); |
| } |
| if (member.maybe_used) { |
| 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 == name_result.previous_occurrence().data()) { |
| return Fail(ErrDuplicateTableFieldName, member_used.name, original_name, previous_span); |
| } |
| return Fail(ErrDuplicateTableFieldNameCanonical, member_used.name, original_name, |
| previous_span.data(), previous_span, canonical_name); |
| } |
| if (!CompileTypeConstructor(&member_used.type_ctor)) { |
| return false; |
| } |
| if (GetType(member_used.type_ctor)->nullability != types::Nullability::kNonnullable) { |
| return Fail(ErrNullableTableMember, member_used.name); |
| } |
| } |
| } |
| |
| if (auto ordinal_and_loc = FindFirstNonDenseOrdinal(ordinal_scope)) { |
| auto [ordinal, span] = *ordinal_and_loc; |
| return Fail(ErrNonDenseOrdinal, span, ordinal); |
| } |
| |
| return true; |
| } |
| |
| bool Library::CompileUnion(Union* union_declaration) { |
| Scope<std::string> scope; |
| Ordinal64Scope ordinal_scope; |
| DeriveResourceness derive_resourceness(&union_declaration->resourceness); |
| |
| for (const auto& member : union_declaration->members) { |
| const auto ordinal_result = ordinal_scope.Insert(member.ordinal->value, member.ordinal->span()); |
| if (!ordinal_result.ok()) { |
| return Fail(ErrDuplicateUnionMemberOrdinal, member.ordinal->span(), |
| ordinal_result.previous_occurrence()); |
| } |
| if (member.maybe_used) { |
| 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 == name_result.previous_occurrence().data()) { |
| return Fail(ErrDuplicateUnionMemberName, member_used.name, original_name, previous_span); |
| } |
| return Fail(ErrDuplicateUnionMemberNameCanonical, member_used.name, original_name, |
| previous_span.data(), previous_span, canonical_name); |
| } |
| |
| if (!CompileTypeConstructor(const_cast<TypeConstructor*>(&member_used.type_ctor))) { |
| return false; |
| } |
| if (GetType(member_used.type_ctor)->nullability != types::Nullability::kNonnullable) { |
| return Fail(ErrNullableUnionMember, member_used.name); |
| } |
| derive_resourceness.AddType(GetType(member_used.type_ctor)); |
| } |
| } |
| |
| if (auto ordinal_and_loc = FindFirstNonDenseOrdinal(ordinal_scope)) { |
| auto [ordinal, span] = *ordinal_and_loc; |
| return Fail(ErrNonDenseOrdinal, span, ordinal); |
| } |
| |
| { |
| std::vector<const Union::Member::Used*> used_members; |
| for (const auto& member : union_declaration->members) { |
| if (member.maybe_used) |
| used_members.push_back(member.maybe_used.get()); |
| } |
| |
| auto err = ValidateUnknownConstraints(*union_declaration, union_declaration->strictness, |
| &used_members); |
| if (err) { |
| return Fail(std::move(err)); |
| } |
| } |
| |
| return true; |
| } |
| |
| bool Library::CompileTypeAlias(TypeAlias* type_alias) { |
| if (GetName(type_alias->partial_type_ctor) == type_alias->name) |
| // fidlc's current semantics for cases like `alias foo = foo;` is to |
| // include the LHS in the scope while compiling the RHS. Note that because |
| // of an interaction with a fidlc scoping bug that prevents shadowing builtins, |
| // this means that `alias Recursive = Recursive;` will fail with an includes |
| // cycle error, but e.g. `alias uint32 = uint32;` won't because the user |
| // defined `uint32` fails to shadow the builtin which means that we successfully |
| // resolve the RHS. To avoid inconsistent semantics, we need to manually |
| // catch this case and fail. |
| return Fail(ErrIncludeCycle); |
| return CompileTypeConstructor(&type_alias->partial_type_ctor); |
| } |
| |
| bool Library::Compile() { |
| // We process declarations in topologically sorted order. For |
| // example, we process a struct member's type before the entire |
| // struct. |
| auto compile_step = StartCompileStep(); |
| for (auto& name_and_decl : declarations_) { |
| Decl* decl = name_and_decl.second; |
| compile_step.ForDecl(decl); |
| } |
| if (!compile_step.Done()) |
| return false; |
| |
| if (!SortDeclarations()) { |
| return false; |
| } |
| |
| auto verify_resourceness_step = StartVerifyResourcenessStep(); |
| for (const Decl* decl : declaration_order_) { |
| verify_resourceness_step.ForDecl(decl); |
| } |
| if (!verify_resourceness_step.Done()) |
| return false; |
| |
| auto verify_attributes_step = StartVerifyAttributesStep(); |
| for (const Decl* decl : declaration_order_) { |
| verify_attributes_step.ForDecl(decl); |
| } |
| if (!verify_attributes_step.Done()) |
| return false; |
| |
| for (const Decl* decl : declaration_order_) { |
| if (decl->kind == Decl::Kind::kStruct) { |
| auto struct_decl = static_cast<const Struct*>(decl); |
| if (!VerifyInlineSize(struct_decl)) { |
| return false; |
| } |
| } |
| } |
| |
| if (!dependencies_.VerifyAllDependenciesWereUsed(*this, reporter_)) |
| return false; |
| |
| return reporter_->errors().size() == 0; |
| } |
| |
| bool Library::CompileTypeConstructor(TypeConstructor* type_ctor) { |
| return std::visit(fidl::utils::matchers{ |
| [&, this](const std::unique_ptr<TypeConstructorOld>& type_ctor) -> bool { |
| return CompileTypeConstructorOld(type_ctor.get()); |
| }, |
| [&, this](const std::unique_ptr<TypeConstructorNew>& type_ctor) -> bool { |
| return CompileTypeConstructorNew(type_ctor.get()); |
| }, |
| }, |
| *type_ctor); |
| } |
| |
| bool Library::CompileTypeConstructorOld(TypeConstructorOld* type_ctor) { |
| if (!typespace_->Create(LibraryMediator(this), type_ctor->name, type_ctor->maybe_arg_type_ctor, |
| type_ctor->handle_subtype_identifier, type_ctor->handle_rights, |
| type_ctor->maybe_size, type_ctor->nullability, &type_ctor->type, |
| &type_ctor->resolved_params)) |
| return false; |
| |
| // postcondition: compilation sets the Type of the TypeConstructor |
| assert(type_ctor->type && "type constructors' type not resolved after compilation"); |
| return VerifyTypeCategory(type_ctor->type, type_ctor->name.span(), |
| AllowedCategories::kTypeOrProtocol); |
| } |
| |
| bool Library::CompileTypeConstructorNew(TypeConstructorNew* type_ctor) { |
| if (!typespace_->Create(LibraryMediator(this), type_ctor->name, type_ctor->parameters, |
| type_ctor->constraints, &type_ctor->type, &type_ctor->resolved_params)) |
| return false; |
| |
| // // postcondition: compilation sets the Type of the TypeConstructor |
| assert(type_ctor->type && "type constructors' type not resolved after compilation"); |
| return VerifyTypeCategory(type_ctor->type, type_ctor->name.span(), AllowedCategories::kTypeOnly); |
| return false; |
| } |
| |
| bool Library::VerifyTypeCategory(const Type* type, std::optional<SourceSpan> span, |
| AllowedCategories category) { |
| assert(type && "CompileTypeConstructor did not set Type"); |
| if (type->kind != Type::Kind::kIdentifier) { |
| // we assume that all non-identifier types (i.e. builtins) are actually |
| // types (and not e.g. protocols or services). |
| return category == AllowedCategories::kProtocolOnly ? Fail(ErrCannotUseType, span) : true; |
| } |
| |
| auto identifier_type = static_cast<const IdentifierType*>(type); |
| switch (identifier_type->type_decl->kind) { |
| // services are never allowed in any context |
| case Decl::Kind::kService: |
| return Fail(ErrCannotUseService, span); |
| break; |
| case Decl::Kind::kProtocol: |
| if (category == AllowedCategories::kTypeOnly) |
| return Fail(ErrCannotUseProtocol, span); |
| break; |
| default: |
| if (category == AllowedCategories::kProtocolOnly) |
| return Fail(ErrCannotUseType, span); |
| break; |
| } |
| return true; |
| } |
| |
| bool Library::ResolveHandleRightsConstant(Resource* resource, Constant* constant, |
| const HandleRights** out_rights) { |
| if (!IsTypeConstructorDefined(resource->subtype_ctor) || |
| GetName(resource->subtype_ctor).full_name() != "uint32") { |
| return Fail(ErrResourceMustBeUint32Derived, resource->name); |
| } |
| |
| auto rights_property = resource->LookupProperty("rights"); |
| if (!rights_property) { |
| return Fail(ErrResourceMissingRightsProperty, resource->name); |
| } |
| |
| Decl* rights_decl = LookupDeclByName(GetName(rights_property->type_ctor)); |
| if (!rights_decl || rights_decl->kind != Decl::Kind::kBits) { |
| return Fail(ErrResourceRightsPropertyMustReferToBits, resource->name); |
| } |
| |
| if (!GetType(rights_property->type_ctor)) { |
| if (!CompileTypeConstructor(&rights_property->type_ctor)) |
| return false; |
| } |
| const Type* rights_type = GetType(rights_property->type_ctor); |
| |
| if (!ResolveConstant(constant, rights_type)) |
| return false; |
| |
| if (out_rights) |
| *out_rights = static_cast<const HandleRights*>(&constant->Value()); |
| return true; |
| } |
| |
| bool Library::ResolveHandleSubtypeIdentifier(Resource* resource, |
| const std::unique_ptr<Constant>& constant, |
| uint32_t* out_obj_type) { |
| // We only support an extremely limited form of resource suitable for |
| // handles here, where it must be: |
| // - derived from uint32 |
| // - have a single properties element |
| // - the single property element must be a reference to an enum |
| // - the single property must be named "subtype". |
| if (constant->kind != Constant::Kind::kIdentifier) { |
| return Fail(ErrHandleSubtypeMustReferToResourceSubtype, constant->span); |
| } |
| auto identifier_constant = static_cast<IdentifierConstant*>(constant.get()); |
| const Name& handle_subtype_identifier = identifier_constant->name; |
| |
| if (!IsTypeConstructorDefined(resource->subtype_ctor) || |
| GetName(resource->subtype_ctor).full_name() != "uint32") { |
| return Fail(ErrResourceMustBeUint32Derived, resource->name); |
| } |
| auto subtype_property = resource->LookupProperty("subtype"); |
| if (!subtype_property) { |
| return Fail(ErrResourceMissingSubtypeProperty, resource->name); |
| } |
| |
| Decl* subtype_decl = LookupDeclByName(GetName(subtype_property->type_ctor)); |
| if (!subtype_decl || subtype_decl->kind != Decl::Kind::kEnum) { |
| return Fail(ErrResourceSubtypePropertyMustReferToEnum, resource->name); |
| } |
| |
| if (!GetType(subtype_property->type_ctor)) { |
| if (!CompileTypeConstructor(&subtype_property->type_ctor)) |
| return false; |
| } |
| const Type* subtype_type = GetType(subtype_property->type_ctor); |
| |
| auto* subtype_enum = static_cast<Enum*>(subtype_decl); |
| for (const auto& member : subtype_enum->members) { |
| if (member.name.data() == handle_subtype_identifier.span()->data()) { |
| if (!ResolveConstant(member.value.get(), subtype_type)) { |
| return false; |
| } |
| const flat::ConstantValue& value = member.value->Value(); |
| auto obj_type = static_cast<uint32_t>( |
| reinterpret_cast<const flat::NumericConstantValue<uint32_t>&>(value)); |
| *out_obj_type = obj_type; |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| bool Library::ResolveSizeBound(Constant* size_constant, const Size** out_size) { |
| if (!ResolveConstant(size_constant, &kSizeType)) { |
| if (size_constant->kind == Constant::Kind::kIdentifier) { |
| auto name = static_cast<IdentifierConstant*>(size_constant)->name; |
| if (name.library() == this && name.decl_name() == "MAX" && !name.member_name()) { |
| size_constant->ResolveTo(std::make_unique<Size>(Size::Max())); |
| } |
| } |
| } |
| if (!size_constant->IsResolved()) { |
| return false; |
| } |
| if (out_size) { |
| *out_size = static_cast<const Size*>(&size_constant->Value()); |
| } |
| return true; |
| } |
| |
| template <typename DeclType, typename MemberType> |
| bool Library::ValidateMembers(DeclType* decl, MemberValidator<MemberType> validator) { |
| assert(decl != nullptr); |
| |
| constexpr const char* decl_type = std::is_same_v<DeclType, Enum> ? "enum" : "bits"; |
| |
| Scope<std::string> name_scope; |
| Scope<MemberType> value_scope; |
| bool success = true; |
| for (const auto& member : decl->members) { |
| assert(member.value != nullptr && "Compiler bug: member value is null!"); |
| |
| if (!ResolveConstant(member.value.get(), GetType(decl->subtype_ctor))) { |
| return Fail(ErrCouldNotResolveMember, member.name, std::string(decl_type)); |
| } |
| |
| // 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()) { |
| success = Fail(ErrDuplicateMemberName, member.name, std::string_view(decl_type), |
| original_name, previous_span); |
| } else { |
| success = Fail(ErrDuplicateMemberNameCanonical, member.name, std::string_view(decl_type), |
| original_name, previous_span.data(), previous_span, canonical_name); |
| } |
| } |
| |
| 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 |
| success = Fail(ErrDuplicateMemberValue, member.name, std::string_view(decl_type), |
| original_name, previous_span.data(), previous_span); |
| } |
| |
| auto err = validator(value, member.attributes.get()); |
| if (err) { |
| err->span = member.name; |
| success = Fail(std::move(err)); |
| } |
| } |
| |
| return success; |
| } |
| |
| 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 Library::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 raw::AttributeList*) -> std::unique_ptr<Diagnostic> { |
| if (!IsPowerOfTwo(member)) { |
| return Reporter::MakeError(ErrBitsMemberMustBePowerOfTwo); |
| } |
| mask |= member; |
| return nullptr; |
| }; |
| if (!ValidateMembers<Bits, MemberType>(bits_decl, validator)) { |
| return false; |
| } |
| *out_mask = mask; |
| return true; |
| } |
| |
| template <typename MemberType> |
| bool Library::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!"); |
| |
| auto unknown_value = std::numeric_limits<MemberType>::max(); |
| for (const auto& member : enum_decl->members) { |
| if (!ResolveConstant(member.value.get(), GetType(enum_decl->subtype_ctor))) { |
| return Fail(ErrCouldNotResolveMember, member.name, std::string("enum")); |
| } |
| auto attributes = member.attributes.get(); |
| if (attributes && attributes->HasAttribute("Unknown")) { |
| unknown_value = |
| static_cast<const NumericConstantValue<MemberType>&>(member.value->Value()).value; |
| } |
| } |
| *out_unknown_value = unknown_value; |
| |
| auto validator = [enum_decl, unknown_value]( |
| MemberType member, |
| const raw::AttributeList* attributes) -> std::unique_ptr<Diagnostic> { |
| switch (enum_decl->strictness) { |
| case types::Strictness::kFlexible: |
| break; |
| case types::Strictness::kStrict: |
| // Strict enums cannot have [Unknown] attributes on members, but that will be validated by |
| // ValidateUnknownConstraints() (called later in this method). |
| return nullptr; |
| } |
| |
| if (member != unknown_value) |
| return nullptr; |
| |
| if (attributes && attributes->HasAttribute("Unknown")) |
| return nullptr; |
| |
| return Reporter::MakeError(ErrFlexibleEnumMemberWithMaxValue, std::to_string(unknown_value), |
| std::to_string(unknown_value), std::to_string(unknown_value), |
| std::to_string(unknown_value)); |
| }; |
| |
| if (!ValidateMembers<Enum, MemberType>(enum_decl, validator)) |
| return false; |
| |
| { |
| std::vector<const Enum::Member*> members; |
| members.reserve(enum_decl->members.size()); |
| for (const auto& member : enum_decl->members) { |
| members.push_back(&member); |
| } |
| |
| auto err = ValidateUnknownConstraints(*enum_decl, enum_decl->strictness, &members); |
| if (err) { |
| return Fail(std::move(err)); |
| } |
| } |
| |
| return true; |
| } |
| |
| bool Library::HasAttribute(std::string_view name) const { |
| if (!attributes_) |
| return false; |
| return attributes_->HasAttribute(std::string(name)); |
| } |
| |
| const std::set<Library*>& Library::dependencies() const { return dependencies_.dependencies(); } |
| |
| std::unique_ptr<TypeConstructorOld> TypeConstructorOld::CreateSizeType() { |
| return std::make_unique<TypeConstructorOld>( |
| Name::CreateIntrinsic("uint32"), nullptr /* maybe_arg_type */, |
| std::optional<Name>() /* handle_subtype_identifier */, nullptr /* handle_rights */, |
| nullptr /* maybe_size */, types::Nullability::kNonnullable); |
| } |
| |
| std::unique_ptr<TypeConstructorNew> TypeConstructorNew::CreateSizeType() { |
| std::vector<std::unique_ptr<LayoutParameter>> no_params; |
| std::vector<std::unique_ptr<Constant>> no_constraints; |
| return std::make_unique<TypeConstructorNew>( |
| Name::CreateIntrinsic("uint32"), |
| std::make_unique<LayoutParameterList>(std::move(no_params), std::nullopt /* span */), |
| std::make_unique<TypeConstraints>(std::move(no_constraints), std::nullopt /* span */)); |
| } |
| |
| bool LibraryMediator::ResolveParamAsType(const flat::TypeTemplate* layout, |
| const std::unique_ptr<LayoutParameter>& param, |
| const Type** out_type) const { |
| auto type_ctor = param->AsTypeCtor(); |
| auto check = library_->reporter_->Checkpoint(); |
| if (!type_ctor || !ResolveType(type_ctor)) { |
| // if there were no errors reported but we couldn't resolve to a type, it must |
| // mean that the parameter referred to a non-type, so report a new error here. |
| if (check.NoNewErrors()) { |
| return library_->Fail(ErrExpectedType, param->span); |
| } |
| // otherwise, there was an error during the type resolution process, so we |
| // should just report that rather than add an extra error here |
| return false; |
| } |
| *out_type = type_ctor->type; |
| return true; |
| } |
| |
| bool LibraryMediator::ResolveParamAsSize(const flat::TypeTemplate* layout, |
| const std::unique_ptr<LayoutParameter>& param, |
| const Size** out_size) const { |
| // We could use param->AsConstant() here, leading to code similar to ResolveParamAsType. |
| // However, unlike ErrExpectedType, ErrExpectedValueButGotType requires a name to be |
| // reported, which would require doing a switch on the parameter kind anyway to find |
| // its Name. So we just handle all the cases ourselves from the start. |
| switch (param->kind) { |
| case LayoutParameter::Kind::kLiteral: { |
| auto literal_param = static_cast<LiteralLayoutParameter*>(param.get()); |
| if (!ResolveSizeBound(literal_param->literal.get(), out_size)) |
| return library_->Fail(ErrCouldNotParseSizeBound); |
| break; |
| } |
| case LayoutParameter::kType: { |
| auto type_param = static_cast<TypeLayoutParameter*>(param.get()); |
| return library_->Fail(ErrExpectedValueButGotType, type_param->type_ctor->name); |
| } |
| case LayoutParameter::Kind::kIdentifier: { |
| auto ambig_param = static_cast<IdentifierLayoutParameter*>(param.get()); |
| auto as_constant = ambig_param->AsConstant(); |
| if (!ResolveSizeBound(as_constant, out_size)) |
| return library_->Fail(ErrExpectedValueButGotType, ambig_param->name); |
| break; |
| } |
| } |
| assert(*out_size); |
| if ((*out_size)->value == 0) |
| return library_->Fail(ErrMustHaveNonZeroSize, param->span, layout); |
| return true; |
| } |
| |
| bool LibraryMediator::ResolveConstraintAs(const std::unique_ptr<Constant>& constraint, |
| const std::vector<ConstraintKind>& interpretations, |
| Resource* resource, ResolvedConstraint* out) const { |
| for (const auto& constraint_kind : interpretations) { |
| out->kind = constraint_kind; |
| switch (constraint_kind) { |
| case ConstraintKind::kHandleSubtype: { |
| assert(resource && |
| "Compiler bug: must pass resource if trying to resolve to handle subtype"); |
| if (ResolveAsHandleSubtype(resource, constraint, &out->value.handle_subtype)) |
| return true; |
| break; |
| } |
| case ConstraintKind::kHandleRights: { |
| assert(resource && |
| "Compiler bug: must pass resource if trying to resolve to handle rights"); |
| if (ResolveAsHandleRights(resource, constraint.get(), &(out->value.handle_rights))) |
| return true; |
| break; |
| } |
| case ConstraintKind::kSize: { |
| if (ResolveSizeBound(constraint.get(), &(out->value.size))) |
| return true; |
| break; |
| } |
| case ConstraintKind::kNullability: { |
| if (ResolveAsOptional(constraint.get())) |
| return true; |
| break; |
| } |
| } |
| } |
| return false; |
| } |
| |
| bool LibraryMediator::ResolveType(TypeConstructorOld* type) const { |
| return library_->CompileTypeConstructorOld(type); |
| } |
| |
| bool LibraryMediator::ResolveType(TypeConstructorNew* type) const { |
| return library_->CompileTypeConstructorNew(type); |
| } |
| |
| bool LibraryMediator::ResolveSizeBound(Constant* size_constant, const Size** out_size) const { |
| return library_->ResolveSizeBound(size_constant, out_size); |
| } |
| |
| bool LibraryMediator::ResolveAsOptional(Constant* constant) const { |
| return library_->ResolveAsOptional(constant); |
| } |
| |
| bool LibraryMediator::ResolveAsHandleSubtype(Resource* resource, |
| const std::unique_ptr<Constant>& constant, |
| uint32_t* out_obj_type) const { |
| return library_->ResolveHandleSubtypeIdentifier(resource, constant, out_obj_type); |
| } |
| |
| bool LibraryMediator::ResolveAsHandleRights(Resource* resource, Constant* constant, |
| const HandleRights** out_rights) const { |
| return library_->ResolveHandleRightsConstant(resource, constant, out_rights); |
| } |
| |
| template <typename... Args> |
| bool LibraryMediator::Fail(const ErrorDef<Args...>& err, const std::optional<SourceSpan>& span, |
| const Args&... args) const { |
| return library_->Fail(err, span, args...); |
| } |
| |
| Decl* LibraryMediator::LookupDeclByName(Name::Key name) const { |
| return library_->LookupDeclByName(name); |
| } |
| |
| TypeConstructorNew* LiteralLayoutParameter::AsTypeCtor() const { return nullptr; } |
| TypeConstructorNew* TypeLayoutParameter::AsTypeCtor() const { return type_ctor.get(); } |
| TypeConstructorNew* IdentifierLayoutParameter::AsTypeCtor() const { |
| if (!as_type_ctor) { |
| std::vector<std::unique_ptr<LayoutParameter>> no_params; |
| std::vector<std::unique_ptr<Constant>> no_constraints; |
| as_type_ctor = std::make_unique<TypeConstructorNew>( |
| name, std::make_unique<LayoutParameterList>(std::move(no_params), std::nullopt), |
| std::make_unique<TypeConstraints>(std::move(no_constraints), std::nullopt)); |
| } |
| |
| return as_type_ctor.get(); |
| } |
| |
| Constant* LiteralLayoutParameter::AsConstant() const { return literal.get(); } |
| Constant* TypeLayoutParameter::AsConstant() const { return nullptr; } |
| Constant* IdentifierLayoutParameter::AsConstant() const { |
| if (!as_constant) { |
| as_constant = std::make_unique<IdentifierConstant>(name, span); |
| } |
| return as_constant.get(); |
| } |
| |
| bool LibraryMediator::CompileDecl(Decl* decl) const { return library_->CompileDecl(decl); } |
| |
| } // namespace flat |
| } // namespace fidl |