| // 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 <regex> |
| #include <sstream> |
| |
| #include "fidl/attributes.h" |
| #include "fidl/lexer.h" |
| #include "fidl/names.h" |
| #include "fidl/ordinals.h" |
| #include "fidl/parser.h" |
| #include "fidl/raw_ast.h" |
| #include "fidl/utils.h" |
| |
| namespace fidl { |
| namespace flat { |
| |
| namespace { |
| |
| class ScopeInsertResult { |
| public: |
| explicit ScopeInsertResult(std::unique_ptr<SourceLocation> previous_occurrence) |
| : previous_occurrence_(std::move(previous_occurrence)) {} |
| |
| static ScopeInsertResult Ok() { return ScopeInsertResult(nullptr); } |
| static ScopeInsertResult FailureAt(SourceLocation previous) { |
| return ScopeInsertResult(std::make_unique<SourceLocation>(previous)); |
| } |
| |
| bool ok() const { |
| return previous_occurrence_ == nullptr; |
| } |
| |
| const SourceLocation& previous_occurrence() const { |
| assert(!ok()); |
| return *previous_occurrence_; |
| } |
| |
| private: |
| std::unique_ptr<SourceLocation> previous_occurrence_; |
| }; |
| |
| template <typename T> |
| class Scope { |
| public: |
| ScopeInsertResult Insert(const T& t, SourceLocation location) { |
| auto iter = scope_.find(t); |
| if (iter != scope_.end()) { |
| return ScopeInsertResult::FailureAt(iter->second); |
| } else { |
| scope_.emplace(t, location); |
| return ScopeInsertResult::Ok(); |
| } |
| } |
| |
| typename std::map<T, SourceLocation>::const_iterator begin() const { |
| return scope_.begin(); |
| } |
| |
| typename std::map<T, SourceLocation>::const_iterator end() const { |
| return scope_.end(); |
| } |
| |
| private: |
| std::map<T, SourceLocation> scope_; |
| }; |
| |
| struct MethodScope { |
| Scope<uint32_t> ordinals; |
| Scope<StringView> names; |
| Scope<const Interface*> interfaces; |
| }; |
| |
| // 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_; |
| }; |
| |
| } // namespace |
| |
| constexpr uint32_t kMessageAlign = 8u; |
| |
| uint32_t AlignTo(uint64_t size, uint64_t alignment) { |
| return static_cast<uint32_t>( |
| std::min((size + alignment - 1) & -alignment, |
| static_cast<uint64_t>(std::numeric_limits<uint32_t>::max()))); |
| } |
| |
| uint32_t ClampedMultiply(uint32_t a, uint32_t b) { |
| return static_cast<uint32_t>( |
| std::min(static_cast<uint64_t>(a) * static_cast<uint64_t>(b), |
| static_cast<uint64_t>(std::numeric_limits<uint32_t>::max()))); |
| } |
| |
| uint32_t ClampedAdd(uint32_t a, uint32_t b) { |
| return static_cast<uint32_t>( |
| std::min(static_cast<uint64_t>(a) + static_cast<uint64_t>(b), |
| static_cast<uint64_t>(std::numeric_limits<uint32_t>::max()))); |
| } |
| |
| TypeShape AlignTypeshape(TypeShape shape, uint32_t alignment) { |
| uint32_t new_alignment = std::max(shape.Alignment(), alignment); |
| uint32_t new_size = AlignTo(shape.Size(), new_alignment); |
| return TypeShape(new_size, new_alignment, shape.Depth(), shape.MaxHandles(), shape.MaxOutOfLine()); |
| } |
| |
| TypeShape Struct::Shape(std::vector<FieldShape*>* fields, uint32_t extra_handles) { |
| uint32_t size = 0u; |
| uint32_t alignment = 1u; |
| uint32_t depth = 0u; |
| uint32_t max_handles = 0u; |
| uint32_t max_out_of_line = 0u; |
| |
| for (FieldShape* field : *fields) { |
| TypeShape typeshape = field->Typeshape(); |
| alignment = std::max(alignment, typeshape.Alignment()); |
| size = AlignTo(size, typeshape.Alignment()); |
| field->SetOffset(size); |
| size += typeshape.Size(); |
| depth = std::max(depth, typeshape.Depth()); |
| max_handles = ClampedAdd(max_handles, typeshape.MaxHandles()); |
| max_out_of_line = ClampedAdd(max_out_of_line, typeshape.MaxOutOfLine()); |
| } |
| |
| max_handles = ClampedAdd(max_handles, extra_handles); |
| |
| size = AlignTo(size, alignment); |
| |
| if (fields->empty()) { |
| assert(size == 0); |
| assert(alignment == 1); |
| |
| // Empty structs are defined to have a size of 1 (a single byte). |
| size = 1; |
| } |
| |
| return TypeShape(size, alignment, depth, max_handles, max_out_of_line); |
| } |
| |
| TypeShape Union::Shape(const std::vector<flat::Union::Member>& members) { |
| uint32_t size = 0u; |
| uint32_t alignment = 1u; |
| uint32_t depth = 0u; |
| uint32_t max_handles = 0u; |
| uint32_t max_out_of_line = 0u; |
| |
| for (const auto& member : members) { |
| const auto& fieldshape = member.fieldshape; |
| size = std::max(size, fieldshape.Size()); |
| alignment = std::max(alignment, fieldshape.Alignment()); |
| depth = std::max(depth, fieldshape.Depth()); |
| max_handles = std::max(max_handles, fieldshape.Typeshape().MaxHandles()); |
| max_out_of_line = std::max(max_out_of_line, fieldshape.Typeshape().MaxOutOfLine()); |
| } |
| |
| size = AlignTo(size, alignment); |
| return TypeShape(size, alignment, depth, max_handles, max_out_of_line); |
| } |
| |
| TypeShape FidlMessageTypeShape(std::vector<FieldShape*>* fields) { |
| auto struct_shape = Struct::Shape(fields); |
| return AlignTypeshape(struct_shape, kMessageAlign); |
| } |
| |
| TypeShape PointerTypeShape(const TypeShape& element, uint32_t max_element_count = 1u) { |
| // Because FIDL supports recursive data structures, we might not have |
| // computed the TypeShape for the element we're pointing to. In that case, |
| // the size will be zero and we'll use |numeric_limits<uint32_t>::max()| as |
| // the depth. We'll never see a zero size for a real TypeShape because empty |
| // structs are banned. |
| // |
| // We're careful to check for saturation before incrementing the depth |
| // because recursive data structures have a depth pegged at the numeric |
| // limit. |
| uint32_t depth = std::numeric_limits<uint32_t>::max(); |
| if (element.Size() > 0 && element.Depth() < std::numeric_limits<uint32_t>::max()) |
| depth = ClampedAdd(element.Depth(), 1); |
| |
| // The element(s) will be stored out-of-line. |
| uint32_t elements_size = ClampedMultiply(element.Size(), max_element_count); |
| // Out-of-line data is aligned to 8 bytes. |
| elements_size = AlignTo(elements_size, 8); |
| // The elements may each carry their own out-of-line data. |
| uint32_t elements_out_of_line = ClampedMultiply(element.MaxOutOfLine(), max_element_count); |
| |
| uint32_t max_handles = ClampedMultiply(element.MaxHandles(), max_element_count); |
| uint32_t max_out_of_line = ClampedAdd(elements_size, elements_out_of_line); |
| |
| return TypeShape(8u, 8u, depth, max_handles, max_out_of_line); |
| } |
| |
| TypeShape CEnvelopeTypeShape(const TypeShape& contained_type) { |
| auto packed_sizes_field = FieldShape(PrimitiveType::Shape(types::PrimitiveSubtype::kUint64)); |
| auto pointer_type = FieldShape(PointerTypeShape(contained_type)); |
| std::vector<FieldShape*> header{&packed_sizes_field, &pointer_type}; |
| return Struct::Shape(&header); |
| } |
| |
| TypeShape Table::Shape(std::vector<TypeShape*>* fields, uint32_t extra_handles) { |
| uint32_t element_depth = 0u; |
| uint32_t max_handles = 0u; |
| uint32_t max_out_of_line = 0u; |
| uint32_t array_size = 0u; |
| for (auto field : *fields) { |
| if (field == nullptr) { |
| continue; |
| } |
| auto envelope = CEnvelopeTypeShape(*field); |
| element_depth = std::max(element_depth, envelope.Depth()); |
| array_size = ClampedAdd(array_size, envelope.Size()); |
| max_handles = ClampedAdd(max_handles, envelope.MaxHandles()); |
| max_out_of_line = ClampedAdd(max_out_of_line, envelope.MaxOutOfLine()); |
| assert(envelope.Alignment() == 8u); |
| } |
| auto pointer_element = TypeShape(array_size, 8u, 1 + element_depth, |
| max_handles, max_out_of_line); |
| auto num_fields = FieldShape(PrimitiveType::Shape(types::PrimitiveSubtype::kUint32)); |
| auto data_field = FieldShape(PointerTypeShape(pointer_element)); |
| std::vector<FieldShape*> header{&num_fields, &data_field}; |
| return Struct::Shape(&header, extra_handles); |
| } |
| |
| TypeShape XUnion::Shape(const std::vector<flat::XUnion::Member>& members, uint32_t extra_handles) { |
| uint32_t depth = 0u; |
| uint32_t max_handles = 0u; |
| uint32_t max_out_of_line = 0u; |
| |
| for (const auto& member : members) { |
| const auto& envelope = CEnvelopeTypeShape(member.fieldshape.Typeshape()); |
| |
| depth = ClampedAdd(depth, envelope.Depth()); |
| max_handles = ClampedAdd(max_handles, envelope.MaxHandles()); |
| max_out_of_line = std::max(max_out_of_line, envelope.MaxOutOfLine()); |
| } |
| |
| return TypeShape(24u, 8u, depth, max_handles, max_out_of_line); |
| } |
| |
| TypeShape ArrayType::Shape(TypeShape element, uint32_t count) { |
| // TODO(FIDL-345): once TypeShape builders are done and methods can fail, do a |
| // __builtin_mul_overflow and fail on overflow instead of ClampedMultiply(element.Size(), count) |
| return TypeShape(ClampedMultiply(element.Size(), count), |
| element.Alignment(), |
| element.Depth(), |
| ClampedMultiply(element.MaxHandles(), count), |
| ClampedMultiply(element.MaxOutOfLine(), count)); |
| } |
| |
| TypeShape VectorType::Shape(TypeShape element, uint32_t max_element_count) { |
| auto size = FieldShape(PrimitiveType::Shape(types::PrimitiveSubtype::kUint64)); |
| auto data = FieldShape(PointerTypeShape(element, max_element_count)); |
| std::vector<FieldShape*> header{&size, &data}; |
| return Struct::Shape(&header); |
| } |
| |
| TypeShape StringType::Shape(uint32_t max_length) { |
| auto size = FieldShape(PrimitiveType::Shape(types::PrimitiveSubtype::kInt64)); |
| auto data = FieldShape(PointerTypeShape(PrimitiveType::Shape(types::PrimitiveSubtype::kUint8), max_length)); |
| std::vector<FieldShape*> header{&size, &data}; |
| return Struct::Shape(&header, 0); |
| } |
| |
| TypeShape HandleType::Shape() { |
| return TypeShape(4u, 4u, 0u, 1u); |
| } |
| |
| 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; |
| } |
| } |
| |
| TypeShape PrimitiveType::Shape(types::PrimitiveSubtype subtype) { |
| return TypeShape(SubtypeSize(subtype), SubtypeSize(subtype)); |
| } |
| |
| bool Decl::HasAttribute(fidl::StringView name) const { |
| if (!attributes) |
| return false; |
| return attributes->HasAttribute(name); |
| } |
| |
| fidl::StringView Decl::GetAttribute(fidl::StringView name) const { |
| if (!attributes) |
| return fidl::StringView(); |
| for (const auto& attribute : attributes->attributes) { |
| if (StringView(attribute.name) == name) { |
| if (attribute.value != "") { |
| const auto& value = attribute.value; |
| return fidl::StringView(value.data(), value.size()); |
| } |
| // Don't search for another attribute with the same name. |
| break; |
| } |
| } |
| return fidl::StringView(); |
| } |
| |
| std::string Decl::GetName() const { |
| return name.name_part(); |
| } |
| |
| bool IsSimple(const Type* type, const FieldShape& fieldshape) { |
| 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 fieldshape.Depth() == 0u; |
| case Type::Kind::kIdentifier: { |
| auto identifier_type = static_cast<const IdentifierType*>(type); |
| 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 fieldshape.Depth() <= 1u; |
| case types::Nullability::kNonnullable: |
| return fieldshape.Depth() == 0u; |
| } |
| } |
| } |
| } |
| |
| bool Typespace::Create(const flat::Name& name, |
| const Type* arg_type, |
| const types::HandleSubtype* handle_subtype, |
| const Size* size, |
| types::Nullability nullability, |
| const Type** out_type) { |
| std::unique_ptr<Type> type; |
| if (!CreateNotOwned(name, arg_type, handle_subtype, size, nullability, &type)) |
| return false; |
| types_.push_back(std::move(type)); |
| *out_type = types_.back().get(); |
| return true; |
| } |
| |
| bool Typespace::CreateNotOwned(const flat::Name& name, |
| const Type* arg_type, |
| const types::HandleSubtype* handle_subtype, |
| const Size* size, |
| types::Nullability nullability, |
| std::unique_ptr<Type>* out_type) { |
| // 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 maybe_location = name.maybe_location(); |
| auto type_template = LookupTemplate(name); |
| if (type_template == nullptr) { |
| std::string message("unknown type "); |
| message.append(name.name_part()); |
| error_reporter_->ReportError(maybe_location, message); |
| return false; |
| } |
| return type_template->Create(maybe_location, arg_type, handle_subtype, size, |
| nullability, out_type); |
| |
| } |
| |
| 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 { |
| Name global_name(nullptr, name.name_part()); |
| auto iter1 = templates_.find(&global_name); |
| if (iter1 != templates_.end()) |
| return iter1->second.get(); |
| |
| auto iter2 = templates_.find(&name); |
| if (iter2 != templates_.end()) |
| return iter2->second.get(); |
| |
| return nullptr; |
| } |
| |
| bool TypeTemplate::Fail(const SourceLocation* maybe_location, const std::string& content) const { |
| std::string message(NameName(name_, ".", "/")); |
| message.append(" "); |
| message.append(content); |
| error_reporter_->ReportError(maybe_location, message); |
| return false; |
| } |
| |
| class PrimitiveTypeTemplate : public TypeTemplate { |
| public: |
| PrimitiveTypeTemplate(Typespace* typespace, ErrorReporter* error_reporter, |
| const std::string& name, types::PrimitiveSubtype subtype) |
| : TypeTemplate(Name(nullptr, name), typespace, error_reporter), |
| subtype_(subtype) {} |
| |
| bool Create(const SourceLocation* maybe_location, |
| const Type* maybe_arg_type, |
| const types::HandleSubtype* handle_subtype, |
| const Size* maybe_size, |
| types::Nullability nullability, |
| std::unique_ptr<Type>* out_type) const { |
| if (maybe_arg_type != nullptr) |
| return CannotBeParameterized(maybe_location); |
| if (maybe_size != nullptr) |
| return CannotHaveSize(maybe_location); |
| if (nullability == types::Nullability::kNullable) |
| return CannotBeNullable(maybe_location); |
| |
| *out_type = std::make_unique<PrimitiveType>(subtype_); |
| return true; |
| } |
| |
| private: |
| const types::PrimitiveSubtype subtype_; |
| }; |
| |
| class BytesTypeTemplate : public TypeTemplate { |
| public: |
| BytesTypeTemplate(Typespace* typespace, ErrorReporter* error_reporter) |
| : TypeTemplate(Name(nullptr, "bytes"), typespace, error_reporter), |
| uint8_type_(types::PrimitiveSubtype::kUint8) {} |
| |
| bool Create(const SourceLocation* maybe_location, |
| const Type* maybe_arg_type, |
| const types::HandleSubtype* handle_subtype, |
| const Size* size, |
| types::Nullability nullability, |
| std::unique_ptr<Type>* out_type) const { |
| if (maybe_arg_type != nullptr) |
| return CannotBeParameterized(maybe_location); |
| if (size == nullptr) |
| size = &max_size; |
| |
| *out_type = std::make_unique<VectorType>(&uint8_type_, size, nullability); |
| return true; |
| } |
| |
| private: |
| const PrimitiveType uint8_type_; |
| Size max_size = Size::Max(); |
| }; |
| |
| class ArrayTypeTemplate : public TypeTemplate { |
| public: |
| ArrayTypeTemplate(Typespace* typespace, ErrorReporter* error_reporter) |
| : TypeTemplate(Name(nullptr, "array"), typespace, error_reporter) {} |
| |
| bool Create(const SourceLocation* maybe_location, |
| const Type* arg_type, |
| const types::HandleSubtype* handle_subtype, |
| const Size* size, |
| types::Nullability nullability, |
| std::unique_ptr<Type>* out_type) const { |
| if (arg_type == nullptr) |
| return MustBeParameterized(maybe_location); |
| if (size == nullptr) |
| return MustHaveSize(maybe_location); |
| if (nullability == types::Nullability::kNullable) |
| return CannotBeNullable(maybe_location); |
| |
| *out_type = std::make_unique<ArrayType>(arg_type, size); |
| return true; |
| } |
| }; |
| |
| class VectorTypeTemplate : public TypeTemplate { |
| public: |
| VectorTypeTemplate(Typespace* typespace, ErrorReporter* error_reporter) |
| : TypeTemplate(Name(nullptr, "vector"), typespace, error_reporter) {} |
| |
| bool Create(const SourceLocation* maybe_location, |
| const Type* arg_type, |
| const types::HandleSubtype* handle_subtype, |
| const Size* size, |
| types::Nullability nullability, |
| std::unique_ptr<Type>* out_type) const { |
| if (arg_type == nullptr) |
| return MustBeParameterized(maybe_location); |
| if (size == nullptr) |
| size = &max_size; |
| |
| *out_type = std::make_unique<VectorType>(arg_type, size, nullability); |
| return true; |
| } |
| |
| private: |
| Size max_size = Size::Max(); |
| }; |
| |
| class StringTypeTemplate : public TypeTemplate { |
| public: |
| StringTypeTemplate(Typespace* typespace, ErrorReporter* error_reporter) |
| : TypeTemplate(Name(nullptr, "string"), typespace, error_reporter) {} |
| |
| bool Create(const SourceLocation* maybe_location, |
| const Type* arg_type, |
| const types::HandleSubtype* handle_subtype, |
| const Size* size, |
| types::Nullability nullability, |
| std::unique_ptr<Type>* out_type) const { |
| if (arg_type != nullptr) |
| return CannotBeParameterized(maybe_location); |
| if (size == nullptr) |
| size = &max_size; |
| |
| *out_type = std::make_unique<StringType>(size, nullability); |
| return true; |
| } |
| |
| private: |
| Size max_size = Size::Max(); |
| }; |
| |
| class HandleTypeTemplate : public TypeTemplate { |
| public: |
| HandleTypeTemplate(Typespace* typespace, ErrorReporter* error_reporter) |
| : TypeTemplate(Name(nullptr, "handle"), typespace, error_reporter) {} |
| |
| bool Create(const SourceLocation* maybe_location, |
| const Type* maybe_arg_type, |
| const types::HandleSubtype* maybe_handle_subtype, |
| const Size* maybe_size, |
| types::Nullability nullability, |
| std::unique_ptr<Type>* out_type) const { |
| assert(maybe_arg_type == nullptr); |
| |
| if (maybe_size != nullptr) |
| return CannotHaveSize(maybe_location); |
| |
| auto handle_subtype = types::HandleSubtype::kHandle; |
| if (maybe_handle_subtype != nullptr) |
| handle_subtype = *maybe_handle_subtype; |
| |
| *out_type = std::make_unique<HandleType>(handle_subtype, nullability); |
| return true; |
| } |
| }; |
| |
| class RequestTypeTemplate : public TypeTemplate { |
| public: |
| RequestTypeTemplate(Typespace* typespace, ErrorReporter* error_reporter) |
| : TypeTemplate(Name(nullptr, "request"), typespace, error_reporter) {} |
| |
| bool Create(const SourceLocation* maybe_location, |
| const Type* arg_type, |
| const types::HandleSubtype* handle_subtype, |
| const Size* maybe_size, |
| types::Nullability nullability, |
| std::unique_ptr<Type>* out_type) const { |
| if (arg_type == nullptr) |
| return MustBeParameterized(maybe_location); |
| if (arg_type->kind != Type::Kind::kIdentifier) |
| return Fail(maybe_location, "must be an interface"); |
| auto interface_type = static_cast<const IdentifierType*>(arg_type); |
| if (interface_type->type_decl->kind != Decl::Kind::kInterface) |
| return Fail(maybe_location, "must be an interface"); |
| if (maybe_size != nullptr) |
| return CannotHaveSize(maybe_location); |
| |
| *out_type = std::make_unique<RequestHandleType>(interface_type, nullability); |
| return true; |
| } |
| |
| private: |
| // TODO(pascallouis): Make Min/Max an actual value on NumericConstantValue |
| // class, to simply write &Size::Max() above. |
| Size max_size = Size::Max(); |
| }; |
| |
| class TypeDeclTypeTemplate : public TypeTemplate { |
| public: |
| TypeDeclTypeTemplate(Name name, Typespace* typespace, ErrorReporter* error_reporter, |
| Library* library, TypeDecl* type_decl) |
| : TypeTemplate(std::move(name), typespace, error_reporter), |
| library_(library), type_decl_(type_decl) {} |
| |
| bool Create(const SourceLocation* maybe_location, |
| const Type* arg_type, |
| const types::HandleSubtype* handle_subtype, |
| const Size* size, |
| types::Nullability nullability, |
| std::unique_ptr<Type>* out_type) const { |
| if (!type_decl_->compiled && type_decl_->kind != Decl::Kind::kInterface) { |
| if (type_decl_->compiling) { |
| type_decl_->recursive = true; |
| } else { |
| if (!library_->CompileDecl(type_decl_)) { |
| return false; |
| } |
| } |
| } |
| auto typeshape = type_decl_->typeshape; |
| switch (type_decl_->kind) { |
| case Decl::Kind::kInterface: |
| typeshape = HandleType::Shape(); |
| break; |
| |
| case Decl::Kind::kXUnion: |
| // Do nothing here: nullable XUnions have the same encoding |
| // representation as non-optional XUnions (i.e. nullable XUnions are |
| // inlined). |
| break; |
| |
| case Decl::Kind::kTable: |
| if (nullability == types::Nullability::kNullable) |
| return CannotBeNullable(maybe_location); |
| break; |
| |
| default: |
| if (nullability == types::Nullability::kNullable) |
| typeshape = PointerTypeShape(typeshape); |
| break; |
| } |
| |
| *out_type = std::make_unique<IdentifierType>( |
| // TODO(FIDL-447): We have to create a copy because IdentifierType |
| // has an owned name. Fix this. |
| Name(name()->library(), std::string(name()->name_part())), |
| nullability, type_decl_, typeshape); |
| return true; |
| } |
| |
| private: |
| Library* library_; |
| TypeDecl* type_decl_; |
| }; |
| |
| class TypeAliasTypeTemplate : public TypeTemplate { |
| public: |
| TypeAliasTypeTemplate(Name name, Typespace* typespace, ErrorReporter* error_reporter, |
| Library* library, |
| std::unique_ptr<TypeConstructor> partial_type_ctor) |
| : TypeTemplate(std::move(name), typespace, error_reporter), |
| library_(library), partial_type_ctor_(std::move(partial_type_ctor)) {} |
| |
| bool Create(const SourceLocation* maybe_location, |
| const Type* maybe_arg_type, |
| const types::HandleSubtype* no_handle_subtype, |
| const Size* maybe_size, |
| types::Nullability maybe_nullability, |
| std::unique_ptr<Type>* out_type) const { |
| assert(!no_handle_subtype); |
| |
| const Type* arg_type = nullptr; |
| if (partial_type_ctor_->maybe_arg_type_ctor) { |
| if (maybe_arg_type) { |
| return Fail(maybe_location, "cannot parametrize twice"); |
| } |
| if (!partial_type_ctor_->maybe_arg_type_ctor->type) { |
| if (!library_->CompileTypeConstructor( |
| partial_type_ctor_->maybe_arg_type_ctor.get(), |
| nullptr /* out_typeshape */)) |
| return false; |
| } |
| arg_type = partial_type_ctor_->maybe_arg_type_ctor->type; |
| } else { |
| arg_type = maybe_arg_type; |
| } |
| |
| const Size* size = nullptr; |
| if (partial_type_ctor_->maybe_size) { |
| if (maybe_size) { |
| return Fail(maybe_location, "cannot bound twice"); |
| } |
| if (!library_->ResolveConstant(partial_type_ctor_->maybe_size.get(), &library_->kSizeType)) |
| return Fail(maybe_location, "unable to parse size bound"); |
| size = static_cast<const Size*>(&partial_type_ctor_->maybe_size->Value()); |
| } else { |
| size = maybe_size; |
| } |
| |
| types::Nullability nullability; |
| if (partial_type_ctor_->nullability == types::Nullability::kNullable) { |
| if (maybe_nullability == types::Nullability::kNullable) { |
| return Fail(maybe_location, "cannot indicate nullability twice"); |
| } |
| nullability = types::Nullability::kNullable; |
| } else { |
| nullability = maybe_nullability; |
| } |
| |
| return typespace_->CreateNotOwned(partial_type_ctor_->name, arg_type, |
| nullptr /* handle_subtype */, |
| size, nullability, out_type); |
| } |
| |
| private: |
| Library* library_; |
| std::unique_ptr<TypeConstructor> partial_type_ctor_; |
| }; |
| |
| Typespace Typespace::RootTypes(ErrorReporter* error_reporter) { |
| Typespace root_typespace(error_reporter); |
| |
| auto add_template = [&](std::unique_ptr<TypeTemplate> type_template) { |
| auto 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, error_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(FIDL-483): Remove when there is generalized support. |
| add_primitive("byte", types::PrimitiveSubtype::kUint8); |
| add_template(std::make_unique<BytesTypeTemplate>( |
| &root_typespace, error_reporter)); |
| |
| add_template(std::make_unique<ArrayTypeTemplate>( |
| &root_typespace, error_reporter)); |
| add_template(std::make_unique<VectorTypeTemplate>( |
| &root_typespace, error_reporter)); |
| add_template(std::make_unique<StringTypeTemplate>( |
| &root_typespace, error_reporter)); |
| add_template(std::make_unique<HandleTypeTemplate>( |
| &root_typespace, error_reporter)); |
| add_template(std::make_unique<RequestTypeTemplate>( |
| &root_typespace, error_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)) {} |
| |
| void AttributeSchema::ValidatePlacement(ErrorReporter* error_reporter, |
| const raw::Attribute& attribute, |
| Placement placement) const { |
| if (allowed_placements_.size() == 0) |
| return; |
| auto iter = allowed_placements_.find(placement); |
| if (iter != allowed_placements_.end()) |
| return; |
| std::string message("placement of attribute '"); |
| message.append(attribute.name); |
| message.append("' disallowed here"); |
| error_reporter->ReportError(attribute.location(), message); |
| } |
| |
| void AttributeSchema::ValidateValue(ErrorReporter* error_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; |
| std::string message("attribute '"); |
| message.append(attribute.name); |
| message.append("' has invalid value '"); |
| message.append(attribute.value); |
| message.append("', should be one of '"); |
| bool first = true; |
| for (const auto& hint : allowed_values_) { |
| if (!first) |
| message.append(", "); |
| message.append(hint); |
| message.append("'"); |
| first = false; |
| } |
| error_reporter->ReportError(attribute.location(), message); |
| } |
| |
| void AttributeSchema::ValidateConstraint(ErrorReporter* error_reporter, |
| const raw::Attribute& attribute, |
| const Decl* decl) const { |
| auto check = error_reporter->Checkpoint(); |
| auto passed = constraint_(error_reporter, attribute, decl); |
| if (passed) { |
| assert(check.NoNewErrors() && "cannot add errors and pass"); |
| } else if (check.NoNewErrors()) { |
| std::string message("declaration did not satisfy constraint of attribute '"); |
| message.append(attribute.name); |
| message.append("' with value '"); |
| message.append(attribute.value); |
| message.append("'"); |
| // TODO(pascallouis): It would be nicer to use the location of |
| // the declaration, however we do not keep it around today. |
| error_reporter->ReportError(attribute.location(), message); |
| } |
| } |
| |
| bool SimpleLayoutConstraint(ErrorReporter* error_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(member.type_ctor.get()->type, member.fieldshape)) { |
| std::string message("member '"); |
| message.append(member.name.data()); |
| message.append("' is not simple"); |
| error_reporter->ReportError(member.name, message); |
| ok = false; |
| } |
| } |
| return ok; |
| } |
| |
| bool ParseBound(ErrorReporter* error_reporter, const SourceLocation& location, |
| const std::string& input, uint32_t* out_value) { |
| auto result = utils::ParseNumeric(input, out_value, 10); |
| switch (result) { |
| case utils::ParseNumericResult::kOutOfBounds: |
| error_reporter->ReportError(location, "bound is too big"); |
| return false; |
| case utils::ParseNumericResult::kMalformed: { |
| std::string message("unable to parse bound '"); |
| message.append(input); |
| message.append("'"); |
| error_reporter->ReportError(location, message); |
| return false; |
| } |
| case utils::ParseNumericResult::kSuccess: |
| return true; |
| } |
| } |
| |
| bool MaxBytesConstraint(ErrorReporter* error_reporter, |
| const raw::Attribute& attribute, |
| const Decl* decl) { |
| uint32_t bound; |
| if (!ParseBound(error_reporter, attribute.location(), 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.Size() + struct_decl->typeshape.MaxOutOfLine(); |
| break; |
| } |
| case Decl::Kind::kTable: { |
| auto table_decl = static_cast<const Table*>(decl); |
| max_bytes = table_decl->typeshape.Size() + table_decl->typeshape.MaxOutOfLine(); |
| break; |
| } |
| case Decl::Kind::kUnion: { |
| auto union_decl = static_cast<const Union*>(decl); |
| max_bytes = union_decl->typeshape.Size() + union_decl->typeshape.MaxOutOfLine(); |
| break; |
| } |
| case Decl::Kind::kXUnion: { |
| auto xunion_decl = static_cast<const XUnion*>(decl); |
| max_bytes = xunion_decl->typeshape.Size() + xunion_decl->typeshape.MaxOutOfLine(); |
| break; |
| } |
| default: |
| assert(false && "unexpected kind"); |
| return false; |
| } |
| if (max_bytes > bound) { |
| std::ostringstream message; |
| message << "too large: only "; |
| message << bound; |
| message << " bytes allowed, but "; |
| message << max_bytes; |
| message << " bytes found"; |
| error_reporter->ReportError(attribute.location(), message.str()); |
| return false; |
| } |
| return true; |
| } |
| |
| bool MaxHandlesConstraint(ErrorReporter* error_reporter, |
| const raw::Attribute& attribute, |
| const Decl* decl) { |
| uint32_t bound; |
| if (!ParseBound(error_reporter, attribute.location(), attribute.value.c_str(), &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.MaxHandles(); |
| break; |
| } |
| case Decl::Kind::kTable: { |
| auto table_decl = static_cast<const Table*>(decl); |
| max_handles = table_decl->typeshape.MaxHandles(); |
| break; |
| } |
| case Decl::Kind::kUnion: { |
| auto union_decl = static_cast<const Union*>(decl); |
| max_handles = union_decl->typeshape.MaxHandles(); |
| break; |
| } |
| case Decl::Kind::kXUnion: { |
| auto xunion_decl = static_cast<const XUnion*>(decl); |
| max_handles = xunion_decl->typeshape.MaxHandles(); |
| break; |
| } |
| default: |
| assert(false && "unexpected kind"); |
| return false; |
| } |
| if (max_handles > bound) { |
| std::ostringstream message; |
| message << "too many handles: only "; |
| message << bound; |
| message << " allowed, but "; |
| message << max_handles; |
| message << " found"; |
| error_reporter->ReportError(attribute.location(), message.str()); |
| return false; |
| } |
| return true; |
| } |
| |
| bool ResultShapeConstraint(ErrorReporter* error_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_type = union_decl->members.at(1).type_ctor->type; |
| |
| 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(error_enum->subtype_ctor->type->kind == Type::Kind::kPrimitive); |
| error_primitive = static_cast<const PrimitiveType*>(error_enum->subtype_ctor->type); |
| } |
| } |
| |
| if (!error_primitive || |
| (error_primitive->subtype != types::PrimitiveSubtype::kInt32 && |
| error_primitive->subtype != types::PrimitiveSubtype::kUint32)) { |
| error_reporter->ReportError( |
| decl->name.maybe_location(), |
| "invalid error type: must be int32, uint32 or an enum therof"); |
| 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 !std::isspace(ch); |
| })); |
| s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { |
| return !std::isspace(ch); |
| }) |
| .base(), |
| s.end()); |
| return s; |
| } |
| |
| bool TransportConstraint(ErrorReporter* error_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 |
| static const std::set<std::string> kValidTransports = { |
| "Channel", |
| "SocketControl", |
| "OvernetEmbedded", |
| "OvernetInternal", |
| }; |
| for (auto transport : transports) { |
| if (kValidTransports.count(transport) == 0) { |
| std::ostringstream out; |
| out << "invalid transport type: got " << transport << " expected one of "; |
| bool first = true; |
| for (const auto& t : kValidTransports) { |
| if (!first) { |
| out << ", "; |
| } |
| first = false; |
| out << t; |
| } |
| error_reporter->ReportError(decl->name.maybe_location(), out.str()); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| Libraries::Libraries() { |
| // clang-format off |
| AddAttributeSchema("Discoverable", AttributeSchema({ |
| AttributeSchema::Placement::kInterfaceDecl, |
| }, { |
| "", |
| })); |
| AddAttributeSchema("Doc", AttributeSchema({ |
| /* any placement */ |
| }, { |
| /* any value */ |
| })); |
| AddAttributeSchema("FragileBase", AttributeSchema({ |
| AttributeSchema::Placement::kInterfaceDecl, |
| }, { |
| "", |
| })); |
| AddAttributeSchema("Layout", AttributeSchema({ |
| AttributeSchema::Placement::kInterfaceDecl, |
| }, { |
| "Simple", |
| }, |
| SimpleLayoutConstraint)); |
| AddAttributeSchema("MaxBytes", AttributeSchema({ |
| AttributeSchema::Placement::kInterfaceDecl, |
| AttributeSchema::Placement::kMethod, |
| AttributeSchema::Placement::kStructDecl, |
| AttributeSchema::Placement::kTableDecl, |
| AttributeSchema::Placement::kUnionDecl, |
| AttributeSchema::Placement::kXUnionDecl, |
| }, { |
| /* any value */ |
| }, |
| MaxBytesConstraint)); |
| AddAttributeSchema("MaxHandles", AttributeSchema({ |
| AttributeSchema::Placement::kInterfaceDecl, |
| AttributeSchema::Placement::kMethod, |
| AttributeSchema::Placement::kStructDecl, |
| AttributeSchema::Placement::kTableDecl, |
| AttributeSchema::Placement::kUnionDecl, |
| AttributeSchema::Placement::kXUnionDecl, |
| }, { |
| /* any value */ |
| }, |
| MaxHandlesConstraint)); |
| AddAttributeSchema("Result", AttributeSchema({ |
| AttributeSchema::Placement::kUnionDecl, |
| }, { |
| "", |
| }, |
| ResultShapeConstraint)); |
| AddAttributeSchema("Selector", AttributeSchema({ |
| AttributeSchema::Placement::kMethod, |
| AttributeSchema::Placement::kXUnionMember, |
| }, { |
| /* any value */ |
| })); |
| AddAttributeSchema("Transport", AttributeSchema({ |
| AttributeSchema::Placement::kInterfaceDecl, |
| }, { |
| /* any value */ |
| }, TransportConstraint)); |
| // clang-format on |
| } |
| |
| bool Libraries::Insert(std::unique_ptr<Library> library) { |
| std::vector<fidl::StringView> library_name = library->name(); |
| auto iter = all_libraries_.emplace(library_name, std::move(library)); |
| return iter.second; |
| } |
| |
| bool Libraries::Lookup(const std::vector<StringView>& 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; |
| } |
| |
| 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(ErrorReporter* error_reporter, |
| const raw::Attribute& attribute) const { |
| const auto& attribute_name = 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 (error_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) { |
| std::string message("suspect attribute with name '"); |
| message.append(attribute_name); |
| message.append("'; did you mean '"); |
| message.append(name_and_schema.first); |
| message.append("'?"); |
| error_reporter->ReportWarning(attribute.location(), message); |
| return nullptr; |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| bool Dependencies::Register(StringView filename, Library* dep_library, |
| const std::unique_ptr<raw::Identifier>& maybe_alias) { |
| auto library_name = dep_library->name(); |
| if (!InsertByName(filename, library_name, dep_library)) { |
| return false; |
| } |
| |
| if (maybe_alias) { |
| std::vector<StringView> alias_name = {maybe_alias->location().data()}; |
| if (!InsertByName(filename, alias_name, dep_library)) { |
| return false; |
| } |
| } |
| |
| dependencies_aggregate_.insert(dep_library); |
| |
| return true; |
| } |
| |
| bool Dependencies::InsertByName(StringView filename, const std::vector<StringView>& name, |
| Library* library) { |
| auto iter = dependencies_.find(filename); |
| if (iter == dependencies_.end()) { |
| dependencies_.emplace(filename, std::make_unique<ByName>()); |
| } |
| |
| iter = dependencies_.find(filename); |
| assert(iter != dependencies_.end()); |
| |
| auto insert = iter->second->emplace(name, library); |
| return insert.second; |
| } |
| |
| bool Dependencies::Lookup(StringView filename, const std::vector<StringView>& name, |
| Library** out_library) { |
| auto iter1 = dependencies_.find(filename); |
| if (iter1 == dependencies_.end()) { |
| return false; |
| } |
| |
| auto iter2 = iter1->second->find(name); |
| if (iter2 == iter1->second->end()) { |
| return false; |
| } |
| |
| *out_library = iter2->second; |
| return true; |
| } |
| |
| // 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 an interface out to the top level and |
| // so on. |
| |
| std::string LibraryName(const Library* library, StringView separator) { |
| if (library != nullptr) { |
| return StringJoin(library->name(), separator); |
| } |
| return std::string(); |
| } |
| |
| bool Library::Fail(StringView message) { |
| error_reporter_->ReportError(message); |
| return false; |
| } |
| |
| bool Library::Fail(const SourceLocation* maybe_location, StringView message) { |
| error_reporter_->ReportError(maybe_location, message); |
| 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(error_reporter_, attribute); |
| if (schema != nullptr) { |
| schema->ValidatePlacement(error_reporter_, attribute, placement); |
| schema->ValidateValue(error_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(error_reporter_, attribute, decl); |
| } |
| } |
| |
| SourceLocation Library::GeneratedSimpleName(const std::string& name) { |
| return generated_source_file_.AddLine(name); |
| } |
| |
| Name Library::NextAnonymousName() { |
| // TODO(FIDL-596): 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 Name(this, GeneratedSimpleName(data.str())); |
| } |
| |
| Name Library::DerivedName(const std::vector<StringView>& components) { |
| return Name(this, GeneratedSimpleName(StringJoin(components, "_"))); |
| } |
| |
| bool Library::CompileCompoundIdentifier(const raw::CompoundIdentifier* compound_identifier, |
| SourceLocation location, Name* name_out) { |
| const auto& components = compound_identifier->components; |
| assert(components.size() >= 1); |
| |
| SourceLocation decl_name = components.back()->location(); |
| |
| if (components.size() == 1) { |
| *name_out = Name(this, decl_name); |
| return true; |
| } |
| |
| std::vector<StringView> library_name; |
| for (auto iter = components.begin(); |
| iter != components.end() - 1; |
| ++iter) { |
| library_name.push_back((*iter)->location().data()); |
| } |
| |
| auto filename = location.source_file().filename(); |
| Library* dep_library = nullptr; |
| if (!dependencies_.Lookup(filename, library_name, &dep_library)) { |
| std::string message("Unknown dependent library "); |
| message += NameLibrary(library_name); |
| message += ". Did you require it with `using`?"; |
| const auto& location = components[0]->location(); |
| return Fail(location, message); |
| } |
| |
| // Resolve the name. |
| *name_out = Name(dep_library, decl_name); |
| return true; |
| } |
| |
| void Library::RegisterConst(Const* decl) { |
| const Name* name = &decl->name; |
| constants_.emplace(name, decl); |
| } |
| |
| bool Library::RegisterDecl(Decl* decl) { |
| const Name* name = &decl->name; |
| auto iter = declarations_.emplace(name, decl); |
| if (!iter.second) { |
| std::string message = "Name collision: "; |
| message.append(name->name_part()); |
| return Fail(*name, message); |
| } |
| switch (decl->kind) { |
| case Decl::Kind::kBits: |
| case Decl::Kind::kEnum: |
| case Decl::Kind::kStruct: |
| case Decl::Kind::kTable: |
| case Decl::Kind::kUnion: |
| case Decl::Kind::kXUnion: |
| case Decl::Kind::kInterface: { |
| auto type_decl = static_cast<TypeDecl*>(decl); |
| auto type_template = std::make_unique<TypeDeclTypeTemplate>( |
| Name(name->library(), std::string(name->name_part())), |
| typespace_, error_reporter_, this, type_decl); |
| typespace_->AddTemplate(std::move(type_template)); |
| break; |
| } |
| default: |
| assert(decl->kind == Decl::Kind::kConst); |
| } |
| return true; |
| } |
| |
| bool Library::ConsumeConstant(std::unique_ptr<raw::Constant> raw_constant, SourceLocation location, |
| std::unique_ptr<Constant>* out_constant) { |
| switch (raw_constant->kind) { |
| case raw::Constant::Kind::kIdentifier: { |
| auto identifier = static_cast<raw::IdentifierConstant*>(raw_constant.get()); |
| Name name; |
| if (!CompileCompoundIdentifier(identifier->identifier.get(), location, &name)) { |
| return false; |
| } |
| *out_constant = std::make_unique<IdentifierConstant>(std::move(name)); |
| break; |
| } |
| case raw::Constant::Kind::kLiteral: { |
| auto literal = static_cast<raw::LiteralConstant*>(raw_constant.get()); |
| *out_constant = std::make_unique<LiteralConstant>(std::move(literal->literal)); |
| break; |
| } |
| } |
| return true; |
| } |
| |
| bool Library::ConsumeTypeConstructor(std::unique_ptr<raw::TypeConstructor> raw_type_ctor, |
| SourceLocation location, |
| std::unique_ptr<TypeConstructor>* out_type_ctor) { |
| Name name; |
| if (!CompileCompoundIdentifier(raw_type_ctor->identifier.get(), location, &name)) |
| return false; |
| |
| std::unique_ptr<TypeConstructor> maybe_arg_type_ctor; |
| if (raw_type_ctor->maybe_arg_type_ctor != nullptr) { |
| if (!ConsumeTypeConstructor(std::move(raw_type_ctor->maybe_arg_type_ctor), location, &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), location, &maybe_size)) |
| return false; |
| } |
| |
| *out_type_ctor = std::make_unique<TypeConstructor>( |
| std::move(name), |
| std::move(maybe_arg_type_ctor), |
| std::move(raw_type_ctor->maybe_handle_subtype), |
| std::move(maybe_size), |
| raw_type_ctor->nullability); |
| return true; |
| } |
| |
| bool Library::ConsumeUsing(std::unique_ptr<raw::Using> using_directive) { |
| if (using_directive->maybe_type_ctor) |
| return ConsumeTypeAlias(std::move(using_directive)); |
| |
| std::vector<StringView> library_name; |
| for (const auto& component : using_directive->using_path->components) { |
| library_name.push_back(component->location().data()); |
| } |
| |
| Library* dep_library = nullptr; |
| if (!all_libraries_->Lookup(library_name, &dep_library)) { |
| std::string message("Could not find library named "); |
| message += NameLibrary(library_name); |
| message += ". Did you include its sources with --files?"; |
| const auto& location = using_directive->using_path->components[0]->location(); |
| return Fail(location, message); |
| } |
| |
| auto filename = using_directive->location().source_file().filename(); |
| if (!dependencies_.Register(filename, dep_library, using_directive->maybe_alias)) { |
| std::string message("Library "); |
| message += NameLibrary(library_name); |
| message += " already imported. Did you require it twice?"; |
| return Fail(message); |
| } |
| |
| // Import declarations, and type aliases of dependent library. |
| const auto& declarations = dep_library->declarations_; |
| declarations_.insert(declarations.begin(), declarations.end()); |
| return true; |
| } |
| |
| bool Library::ConsumeTypeAlias(std::unique_ptr<raw::Using> using_directive) { |
| assert(using_directive->maybe_type_ctor); |
| |
| auto location = using_directive->using_path->components[0]->location(); |
| auto alias_name = Name(this, location); |
| std::unique_ptr<TypeConstructor> partial_type_ctor_; |
| if (!ConsumeTypeConstructor(std::move(using_directive->maybe_type_ctor), location, |
| &partial_type_ctor_)) |
| return false; |
| typespace_->AddTemplate(std::make_unique<TypeAliasTypeTemplate>( |
| std::move(alias_name), typespace_, error_reporter_, this, std::move(partial_type_ctor_))); |
| return true; |
| } |
| |
| bool Library::ConsumeBitsDeclaration(std::unique_ptr<raw::BitsDeclaration> bits_declaration) { |
| std::vector<Bits::Member> members; |
| for (auto& member : bits_declaration->members) { |
| auto location = member->identifier->location(); |
| std::unique_ptr<Constant> value; |
| if (!ConsumeConstant(std::move(member->value), location, &value)) |
| return false; |
| members.emplace_back(location, 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<TypeConstructor> type_ctor; |
| if (bits_declaration->maybe_type_ctor) { |
| if (!ConsumeTypeConstructor(std::move(bits_declaration->maybe_type_ctor), |
| bits_declaration->location(), &type_ctor)) |
| return false; |
| } else { |
| type_ctor = std::make_unique<TypeConstructor>( |
| Name(nullptr, "uint32"), |
| nullptr /* maybe_arg_type */, |
| nullptr /* maybe_handle_subtype */, |
| nullptr /* maybe_size */, |
| types::Nullability::kNonnullable); |
| } |
| |
| bits_declarations_.push_back(std::make_unique<Bits>( |
| std::move(bits_declaration->attributes), |
| Name(this, bits_declaration->identifier->location()), |
| std::move(type_ctor), |
| std::move(members))); |
| if (!RegisterDecl(bits_declarations_.back().get())) |
| return false; |
| |
| return true; |
| } |
| |
| bool Library::ConsumeConstDeclaration(std::unique_ptr<raw::ConstDeclaration> const_declaration) { |
| auto attributes = std::move(const_declaration->attributes); |
| auto location = const_declaration->identifier->location(); |
| auto name = Name(this, location); |
| std::unique_ptr<TypeConstructor> type_ctor; |
| if (!ConsumeTypeConstructor(std::move(const_declaration->type_ctor), location, &type_ctor)) |
| return false; |
| |
| std::unique_ptr<Constant> constant; |
| if (!ConsumeConstant(std::move(const_declaration->constant), location, &constant)) |
| return false; |
| |
| const_declarations_.push_back(std::make_unique<Const>(std::move(attributes), std::move(name), |
| std::move(type_ctor), std::move(constant))); |
| auto decl = const_declarations_.back().get(); |
| RegisterConst(decl); |
| return RegisterDecl(decl); |
| } |
| |
| bool Library::ConsumeEnumDeclaration(std::unique_ptr<raw::EnumDeclaration> enum_declaration) { |
| std::vector<Enum::Member> members; |
| for (auto& member : enum_declaration->members) { |
| auto location = member->identifier->location(); |
| std::unique_ptr<Constant> value; |
| if (!ConsumeConstant(std::move(member->value), location, &value)) |
| return false; |
| members.emplace_back(location, 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<TypeConstructor> type_ctor; |
| if (enum_declaration->maybe_type_ctor) { |
| if (!ConsumeTypeConstructor(std::move(enum_declaration->maybe_type_ctor), |
| enum_declaration->location(), &type_ctor)) |
| return false; |
| } else { |
| type_ctor = std::make_unique<TypeConstructor>( |
| Name(nullptr, "uint32"), |
| nullptr /* maybe_arg_type */, |
| nullptr /* maybe_handle_subtype */, |
| nullptr /* maybe_size */, |
| types::Nullability::kNonnullable); |
| } |
| |
| enum_declarations_.push_back(std::make_unique<Enum>( |
| std::move(enum_declaration->attributes), |
| Name(this, enum_declaration->identifier->location()), |
| std::move(type_ctor), |
| std::move(members))); |
| if (!RegisterDecl(enum_declarations_.back().get())) |
| return false; |
| |
| return true; |
| } |
| |
| bool Library::CreateMethodResult(const Name& interface_name, |
| raw::InterfaceMethod* method, |
| Struct* in_response, |
| Struct** out_response) { |
| // Compile the error type. |
| auto error_location = method->maybe_error_ctor->location(); |
| std::unique_ptr<TypeConstructor> error_type_ctor; |
| if (!ConsumeTypeConstructor(std::move(method->maybe_error_ctor), error_location, &error_type_ctor)) |
| return false; |
| |
| // Make the Result union containing the response struct and the |
| // error type. |
| Union::Member response_member{ |
| IdentifierTypeForDecl(in_response, types::Nullability::kNonnullable), |
| GeneratedSimpleName("response"), |
| nullptr}; |
| Union::Member error_member{ |
| std::move(error_type_ctor), |
| GeneratedSimpleName("err"), |
| nullptr}; |
| SourceLocation method_name = method->identifier->location(); |
| Name result_name = DerivedName({interface_name.name_part(), method_name.data(), "Result"}); |
| 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, "Result", ""); |
| auto result_attributelist = std::make_unique<raw::AttributeList>(*method, |
| std::move(result_attributes)); |
| union_declarations_.push_back(std::make_unique<Union>(std::move(result_attributelist), |
| std::move(result_name), |
| std::move(result_members))); |
| auto result_decl = union_declarations_.back().get(); |
| if (!RegisterDecl(result_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)); |
| struct_declarations_.push_back(std::make_unique<Struct>(nullptr, NextAnonymousName(), std::move(response_members), true)); |
| *out_response = struct_declarations_.back().get(); |
| if (!RegisterDecl(*out_response)) |
| return false; |
| |
| return true; |
| } |
| |
| bool Library::ConsumeInterfaceDeclaration( |
| std::unique_ptr<raw::InterfaceDeclaration> interface_declaration) { |
| auto attributes = std::move(interface_declaration->attributes); |
| auto name = Name(this, interface_declaration->identifier->location()); |
| |
| std::set<Name> superinterfaces; |
| for (auto& superinterface : interface_declaration->superinterfaces) { |
| auto& protocol_name = superinterface->protocol_name; |
| auto location = protocol_name->components[0]->location(); |
| Name superinterface_name; |
| if (!CompileCompoundIdentifier(protocol_name.get(), location, &superinterface_name)) { |
| return false; |
| } |
| if (!superinterfaces.insert(std::move(superinterface_name)).second) |
| return Fail(superinterface_name, "protocol composed multiple times"); |
| } |
| |
| std::vector<Interface::Method> methods; |
| for (auto& method : interface_declaration->methods) { |
| std::unique_ptr<raw::Ordinal> ordinal_literal = |
| std::make_unique<raw::Ordinal>(fidl::ordinals::GetOrdinal(library_name_, name.name_part(), *method)); |
| std::unique_ptr<raw::Ordinal> generated_ordinal = |
| std::make_unique<raw::Ordinal>(fidl::ordinals::GetGeneratedOrdinal(library_name_, name.name_part(), *method)); |
| auto attributes = std::move(method->attributes); |
| SourceLocation method_name = method->identifier->location(); |
| |
| Struct* maybe_request = nullptr; |
| if (method->maybe_request != nullptr) { |
| Name request_name = NextAnonymousName(); |
| if (!ConsumeParameterList(std::move(request_name), std::move(method->maybe_request), true, &maybe_request)) |
| return false; |
| } |
| |
| bool has_error = (method->maybe_error_ctor != nullptr); |
| |
| Struct* maybe_response = nullptr; |
| if (method->maybe_response != nullptr) { |
| Name response_name = has_error ? DerivedName({name.name_part(), method_name.data(), "Response"}) : NextAnonymousName(); |
| if (!ConsumeParameterList(std::move(response_name), std::move(method->maybe_response), !has_error, &maybe_response)) |
| return false; |
| } |
| |
| if (has_error) { |
| if (!CreateMethodResult(name, method.get(), maybe_response, &maybe_response)) |
| return false; |
| } |
| |
| assert(maybe_request != nullptr || maybe_response != nullptr); |
| methods.emplace_back(std::move(attributes), |
| std::move(generated_ordinal), |
| std::move(ordinal_literal), |
| std::move(method_name), std::move(maybe_request), |
| std::move(maybe_response)); |
| } |
| |
| interface_declarations_.push_back( |
| std::make_unique<Interface>(std::move(attributes), std::move(name), |
| std::move(superinterfaces), std::move(methods))); |
| return RegisterDecl(interface_declarations_.back().get()); |
| } |
| |
| std::unique_ptr<TypeConstructor> Library::IdentifierTypeForDecl(const Decl* decl, types::Nullability nullability) { |
| return std::make_unique<TypeConstructor>( |
| Name(decl->name.library(), decl->name.name_part()), |
| nullptr /* maybe_arg_type */, |
| nullptr /* maybe_handle_subtype */, |
| nullptr /* maybe_size */, |
| nullability); |
| } |
| |
| bool Library::ConsumeParameterList(Name name, std::unique_ptr<raw::ParameterList> parameter_list, |
| bool anonymous, Struct** out_struct_decl) { |
| std::vector<Struct::Member> members; |
| for (auto& parameter : parameter_list->parameter_list) { |
| const SourceLocation name = parameter->identifier->location(); |
| std::unique_ptr<TypeConstructor> type_ctor; |
| if (!ConsumeTypeConstructor(std::move(parameter->type_ctor), name, &type_ctor)) |
| return false; |
| members.emplace_back( |
| std::move(type_ctor), name, |
| nullptr /* maybe_default_value */, |
| nullptr /* attributes */); |
| } |
| |
| struct_declarations_.push_back( |
| std::make_unique<Struct>(nullptr /* attributes */, std::move(name), std::move(members), |
| anonymous)); |
| |
| auto struct_decl = struct_declarations_.back().get(); |
| if (!RegisterDecl(struct_decl)) |
| return false; |
| |
| *out_struct_decl = struct_decl; |
| return true; |
| } |
| |
| bool Library::ConsumeStructDeclaration(std::unique_ptr<raw::StructDeclaration> struct_declaration) { |
| auto attributes = std::move(struct_declaration->attributes); |
| auto name = Name(this, struct_declaration->identifier->location()); |
| |
| std::vector<Struct::Member> members; |
| for (auto& member : struct_declaration->members) { |
| std::unique_ptr<TypeConstructor> type_ctor; |
| auto location = member->identifier->location(); |
| if (!ConsumeTypeConstructor(std::move(member->type_ctor), location, &type_ctor)) |
| return false; |
| std::unique_ptr<Constant> maybe_default_value; |
| if (member->maybe_default_value != nullptr) { |
| if (!ConsumeConstant(std::move(member->maybe_default_value), location, |
| &maybe_default_value)) |
| return false; |
| } |
| auto attributes = std::move(member->attributes); |
| members.emplace_back(std::move(type_ctor), member->identifier->location(), |
| std::move(maybe_default_value), std::move(attributes)); |
| } |
| |
| struct_declarations_.push_back( |
| std::make_unique<Struct>(std::move(attributes), std::move(name), std::move(members))); |
| return RegisterDecl(struct_declarations_.back().get()); |
| } |
| |
| bool Library::ConsumeTableDeclaration(std::unique_ptr<raw::TableDeclaration> table_declaration) { |
| auto attributes = std::move(table_declaration->attributes); |
| auto name = Name(this, table_declaration->identifier->location()); |
| |
| 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<TypeConstructor> type_ctor; |
| if (!ConsumeTypeConstructor(std::move(member->maybe_used->type_ctor), member->location(), &type_ctor)) |
| return false; |
| std::unique_ptr<Constant> maybe_default_value; |
| if (member->maybe_used->maybe_default_value != nullptr) { |
| if (!ConsumeConstant(std::move(member->maybe_used->maybe_default_value), |
| member->location(), &maybe_default_value)) |
| return false; |
| } |
| if (type_ctor->nullability != types::Nullability::kNonnullable) { |
| return Fail(member->location(), "Table members cannot be nullable"); |
| } |
| auto attributes = std::move(member->maybe_used->attributes); |
| members.emplace_back(std::move(ordinal_literal), std::move(type_ctor), |
| member->maybe_used->identifier->location(), |
| std::move(maybe_default_value), std::move(attributes)); |
| } else { |
| members.emplace_back(std::move(ordinal_literal), member->location()); |
| } |
| } |
| |
| table_declarations_.push_back( |
| std::make_unique<Table>(std::move(attributes), std::move(name), std::move(members))); |
| return RegisterDecl(table_declarations_.back().get()); |
| } |
| |
| bool Library::ConsumeUnionDeclaration(std::unique_ptr<raw::UnionDeclaration> union_declaration) { |
| std::vector<Union::Member> members; |
| for (auto& member : union_declaration->members) { |
| auto location = member->identifier->location(); |
| std::unique_ptr<TypeConstructor> type_ctor; |
| if (!ConsumeTypeConstructor(std::move(member->type_ctor), location, &type_ctor)) |
| return false; |
| auto attributes = std::move(member->attributes); |
| members.emplace_back(std::move(type_ctor), location, std::move(attributes)); |
| } |
| |
| auto attributes = std::move(union_declaration->attributes); |
| auto name = Name(this, union_declaration->identifier->location()); |
| |
| union_declarations_.push_back( |
| std::make_unique<Union>(std::move(attributes), std::move(name), std::move(members))); |
| return RegisterDecl(union_declarations_.back().get()); |
| } |
| |
| bool Library::ConsumeXUnionDeclaration(std::unique_ptr<raw::XUnionDeclaration> xunion_declaration) { |
| auto name = Name(this, xunion_declaration->identifier->location()); |
| |
| std::vector<XUnion::Member> members; |
| for (auto& member : xunion_declaration->members) { |
| std::unique_ptr<raw::Ordinal> ordinal = |
| std::make_unique<raw::Ordinal>(fidl::ordinals::GetOrdinal(library_name_, name.name_part(), *member)); |
| |
| auto location = member->identifier->location(); |
| std::unique_ptr<TypeConstructor> type_ctor; |
| if (!ConsumeTypeConstructor(std::move(member->type_ctor), location, &type_ctor)) |
| return false; |
| |
| if (type_ctor->nullability != types::Nullability::kNonnullable) { |
| return Fail(member->location(), "Extensible union members cannot be nullable"); |
| } |
| |
| members.emplace_back(std::move(ordinal), std::move(type_ctor), location, std::move(member->attributes)); |
| } |
| |
| xunion_declarations_.push_back( |
| std::make_unique<XUnion>(std::move(xunion_declaration->attributes), std::move(name), std::move(members))); |
| return RegisterDecl(xunion_declarations_.back().get()); |
| } |
| |
| 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(error_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<StringView> new_name; |
| for (const auto& part : file->library_name->components) { |
| new_name.push_back(part->location().data()); |
| } |
| if (!library_name_.empty()) { |
| if (new_name != library_name_) { |
| return Fail(file->library_name->components[0]->location(), |
| "Two files in the library disagree about the name of the library"); |
| } |
| } else { |
| library_name_ = new_name; |
| } |
| |
| auto using_list = std::move(file->using_list); |
| for (auto& using_directive : using_list) { |
| if (!ConsumeUsing(std::move(using_directive))) { |
| return false; |
| } |
| } |
| |
| auto bits_declaration_list = std::move(file->bits_declaration_list); |
| for (auto& bits_declaration : bits_declaration_list) { |
| if (!ConsumeBitsDeclaration(std::move(bits_declaration))) { |
| return false; |
| } |
| } |
| |
| auto const_declaration_list = std::move(file->const_declaration_list); |
| for (auto& const_declaration : const_declaration_list) { |
| if (!ConsumeConstDeclaration(std::move(const_declaration))) { |
| return false; |
| } |
| } |
| |
| auto enum_declaration_list = std::move(file->enum_declaration_list); |
| for (auto& enum_declaration : enum_declaration_list) { |
| if (!ConsumeEnumDeclaration(std::move(enum_declaration))) { |
| return false; |
| } |
| } |
| |
| auto interface_declaration_list = std::move(file->interface_declaration_list); |
| for (auto& interface_declaration : interface_declaration_list) { |
| if (!ConsumeInterfaceDeclaration(std::move(interface_declaration))) { |
| return false; |
| } |
| } |
| |
| auto struct_declaration_list = std::move(file->struct_declaration_list); |
| for (auto& struct_declaration : struct_declaration_list) { |
| if (!ConsumeStructDeclaration(std::move(struct_declaration))) { |
| return false; |
| } |
| } |
| |
| auto table_declaration_list = std::move(file->table_declaration_list); |
| for (auto& table_declaration : table_declaration_list) { |
| if (!ConsumeTableDeclaration(std::move(table_declaration))) { |
| return false; |
| } |
| } |
| |
| auto union_declaration_list = std::move(file->union_declaration_list); |
| for (auto& union_declaration : union_declaration_list) { |
| if (!ConsumeUnionDeclaration(std::move(union_declaration))) { |
| return false; |
| } |
| } |
| |
| auto xunion_declaration_list = std::move(file->xunion_declaration_list); |
| for (auto& xunion_declaration : xunion_declaration_list) { |
| if (!ConsumeXUnionDeclaration(std::move(xunion_declaration))) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool Library::ResolveConstant(Constant* constant, const Type* type) { |
| assert(constant != nullptr); |
| |
| if (constant->IsResolved()) |
| return 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!"); |
| } |
| } |
| |
| __UNREACHABLE; |
| } |
| |
| 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); |
| if (!decl || decl->kind != Decl::Kind::kConst) |
| return false; |
| |
| // Recursively resolve constants |
| auto const_decl = static_cast<Const*>(decl); |
| if (!CompileConst(const_decl)) |
| return false; |
| assert(const_decl->value->IsResolved()); |
| |
| const ConstantValue& const_val = const_decl->value->Value(); |
| std::unique_ptr<ConstantValue> resolved_val; |
| switch (type->kind) { |
| case Type::Kind::kString: { |
| if (!TypeIsConvertibleTo(const_decl->type_ctor->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); |
| switch (primitive_type->subtype) { |
| case types::PrimitiveSubtype::kBool: |
| if (!const_val.Convert(ConstantValue::Kind::kBool, &resolved_val)) |
| goto fail_cannot_convert; |
| break; |
| case types::PrimitiveSubtype::kInt8: |
| if (!const_val.Convert(ConstantValue::Kind::kInt8, &resolved_val)) |
| goto fail_cannot_convert; |
| break; |
| case types::PrimitiveSubtype::kInt16: |
| if (!const_val.Convert(ConstantValue::Kind::kInt16, &resolved_val)) |
| goto fail_cannot_convert; |
| break; |
| case types::PrimitiveSubtype::kInt32: |
| if (!const_val.Convert(ConstantValue::Kind::kInt32, &resolved_val)) |
| goto fail_cannot_convert; |
| break; |
| case types::PrimitiveSubtype::kInt64: |
| if (!const_val.Convert(ConstantValue::Kind::kInt64, &resolved_val)) |
| goto fail_cannot_convert; |
| break; |
| case types::PrimitiveSubtype::kUint8: |
| if (!const_val.Convert(ConstantValue::Kind::kUint8, &resolved_val)) |
| goto fail_cannot_convert; |
| break; |
| case types::PrimitiveSubtype::kUint16: |
| if (!const_val.Convert(ConstantValue::Kind::kUint16, &resolved_val)) |
| goto fail_cannot_convert; |
| break; |
| case types::PrimitiveSubtype::kUint32: |
| if (!const_val.Convert(ConstantValue::Kind::kUint32, &resolved_val)) |
| goto fail_cannot_convert; |
| break; |
| case types::PrimitiveSubtype::kUint64: |
| if (!const_val.Convert(ConstantValue::Kind::kUint64, &resolved_val)) |
| goto fail_cannot_convert; |
| break; |
| case types::PrimitiveSubtype::kFloat32: |
| if (!const_val.Convert(ConstantValue::Kind::kFloat32, &resolved_val)) |
| goto fail_cannot_convert; |
| break; |
| case types::PrimitiveSubtype::kFloat64: |
| if (!const_val.Convert(ConstantValue::Kind::kFloat64, &resolved_val)) |
| goto fail_cannot_convert; |
| break; |
| } |
| break; |
| } |
| default: { |
| assert(false && |
| "Compiler bug: const-able type not handled during identifier constant resolution!"); |
| } |
| } |
| |
| identifier_constant->ResolveTo(std::move(resolved_val)); |
| return true; |
| |
| fail_cannot_convert: |
| std::ostringstream msg_stream; |
| msg_stream << NameFlatConstant(identifier_constant) << ", of type "; |
| msg_stream << NameFlatTypeConstructor(const_decl->type_ctor.get()); |
| msg_stream << ", cannot be converted to type " << NameFlatType(type); |
| return Fail(msg_stream.str()); |
| } |
| |
| 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->location().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) { |
| std::ostringstream msg_stream; |
| msg_stream << NameFlatConstant(literal_constant) << " (string:" << string_size; |
| msg_stream << ") exceeds the size bound of type " << NameFlatType(type); |
| return Fail(literal_constant->literal->location(), msg_stream.str()); |
| } |
| |
| literal_constant->ResolveTo( |
| std::make_unique<StringConstantValue>(string_literal->location().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: |
| std::ostringstream msg_stream; |
| msg_stream << NameFlatConstant(literal_constant) << " cannot be interpreted as type "; |
| msg_stream << NameFlatType(type); |
| return Fail(literal_constant->literal->location(), msg_stream.str()); |
| } |
| } |
| } |
| |
| 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; |
| 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 |
| } |
| |
| Decl* Library::LookupConstant(const TypeConstructor* type_ctor, const Name& name) { |
| auto decl = LookupDeclByName(type_ctor->name); |
| if (decl == nullptr) { |
| // This wasn't a named type. Thus we are looking up a |
| // top-level constant, of string or primitive type. |
| auto iter = constants_.find(&name); |
| if (iter == constants_.end()) { |
| return nullptr; |
| } |
| return iter->second; |
| } |
| // We must otherwise be looking for an enum member. |
| if (decl->kind != Decl::Kind::kEnum) { |
| return nullptr; |
| } |
| auto enum_decl = static_cast<Enum*>(decl); |
| for (auto& member : enum_decl->members) { |
| if (member.name.data() == name.name_part()) { |
| return enum_decl; |
| } |
| } |
| // The enum didn't have a member of that name! |
| return nullptr; |
| } |
| |
| // Library resolution is concerned with resolving identifiers to their |
| // declarations, and with computing type sizes and alignments. |
| |
| Decl* Library::LookupDeclByName(const Name& 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->location().data(); |
| std::string string_data(data.data(), data.data() + data.size()); |
| auto result = utils::ParseNumeric(string_data, out_value); |
| return result == utils::ParseNumericResult::kSuccess; |
| } |
| |
| // 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. |
| // - However, xunions always require dependency edges since nullability does not affect |
| // their layout. |
| bool Library::DeclDependencies(Decl* decl, std::set<Decl*>* out_edges) { |
| std::set<Decl*> edges; |
| auto maybe_add_decl = [this, &edges](const TypeConstructor* type_ctor) { |
| for (;;) { |
| const auto& name = type_ctor->name; |
| if (name.name_part() == "request") { |
| return; |
| } else if (type_ctor->maybe_arg_type_ctor) { |
| type_ctor = type_ctor->maybe_arg_type_ctor.get(); |
| } else if (type_ctor->nullability == types::Nullability::kNullable) { |
| if (auto decl = LookupDeclByName(name); decl && decl->kind == Decl::Kind::kXUnion) { |
| edges.insert(decl); |
| } |
| return; |
| } else { |
| if (auto decl = LookupDeclByName(name); decl && decl->kind != Decl::Kind::kInterface) { |
| edges.insert(decl); |
| } |
| return; |
| } |
| } |
| }; |
| auto maybe_add_constant = [this, &edges](const TypeConstructor* type_ctor, |
| const Constant* constant) -> bool { |
| switch (constant->kind) { |
| case Constant::Kind::kIdentifier: { |
| auto identifier = static_cast<const flat::IdentifierConstant*>(constant); |
| auto decl = LookupConstant(type_ctor, identifier->name); |
| if (decl == nullptr) { |
| std::string message("Unable to find the constant named: "); |
| message += identifier->name.name_part(); |
| return Fail(identifier->name, message.data()); |
| } |
| edges.insert(decl); |
| break; |
| } |
| case Constant::Kind::kLiteral: |
| case Constant::Kind::kSynthesized: { |
| // Literal and synthesized constants have no dependencies on other declarations. |
| break; |
| } |
| } |
| return true; |
| }; |
| switch (decl->kind) { |
| case Decl::Kind::kBits: { |
| auto bits_decl = static_cast<const Bits*>(decl); |
| for (const auto& member : bits_decl->members) { |
| maybe_add_constant(bits_decl->subtype_ctor.get(), member.value.get()); |
| } |
| break; |
| } |
| case Decl::Kind::kConst: { |
| auto const_decl = static_cast<const Const*>(decl); |
| if (!maybe_add_constant(const_decl->type_ctor.get(), const_decl->value.get())) |
| return false; |
| break; |
| } |
| case Decl::Kind::kEnum: { |
| auto enum_decl = static_cast<const Enum*>(decl); |
| for (const auto& member : enum_decl->members) { |
| maybe_add_constant(enum_decl->subtype_ctor.get(), member.value.get()); |
| } |
| break; |
| } |
| case Decl::Kind::kInterface: { |
| auto interface_decl = static_cast<const Interface*>(decl); |
| for (const auto& superinterface : interface_decl->superinterfaces) { |
| if (auto type_decl = LookupDeclByName(superinterface); type_decl) |
| edges.insert(type_decl); |
| } |
| for (const auto& method : interface_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::kStruct: { |
| auto struct_decl = static_cast<const Struct*>(decl); |
| for (const auto& member : struct_decl->members) { |
| maybe_add_decl(member.type_ctor.get()); |
| if (member.maybe_default_value) { |
| if (!maybe_add_constant(member.type_ctor.get(), member.maybe_default_value.get())) |
| 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(member.maybe_used->type_ctor.get()); |
| if (member.maybe_used->maybe_default_value) { |
| if (!maybe_add_constant(member.maybe_used->type_ctor.get(), |
| member.maybe_used->maybe_default_value.get())) |
| return false; |
| } |
| } |
| break; |
| } |
| case Decl::Kind::kUnion: { |
| auto union_decl = static_cast<const Union*>(decl); |
| for (const auto& member : union_decl->members) { |
| maybe_add_decl(member.type_ctor.get()); |
| } |
| break; |
| } |
| case Decl::Kind::kXUnion: { |
| auto xunion_decl = static_cast<const XUnion*>(decl); |
| for (const auto& member : xunion_decl->members) { |
| maybe_add_decl(member.type_ctor.get()); |
| } |
| break; |
| } |
| } |
| *out_edges = std::move(edges); |
| return true; |
| } |
| |
| namespace { |
| // To compare two Decl's in the same library, it suffices to compare the unqualified names of the Decl's. |
| struct CmpDeclInLibrary { |
| bool operator()(const Decl* a, const Decl* b) const { |
| assert(a->name != b->name || a == b); |
| return a->name < b->name; |
| } |
| }; |
| } // namespace |
| |
| bool Library::SortDeclarations() { |
| // |degree| is the number of undeclared dependencies for each decl. |
| std::map<Decl*, uint32_t, CmpDeclInLibrary> degrees; |
| // |inverse_dependencies| records the decls that depend on each decl. |
| std::map<Decl*, std::vector<Decl*>, CmpDeclInLibrary> inverse_dependencies; |
| for (auto& name_and_decl : declarations_) { |
| Decl* decl = name_and_decl.second; |
| degrees[decl] = 0u; |
| } |
| for (auto& name_and_decl : declarations_) { |
| Decl* decl = name_and_decl.second; |
| std::set<Decl*> deps; |
| if (!DeclDependencies(decl, &deps)) |
| return false; |
| degrees[decl] += deps.size(); |
| for (Decl* dep : deps) { |
| inverse_dependencies[dep].push_back(decl); |
| } |
| } |
| |
| // Start with all decls that have no incoming edges. |
| std::vector<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 (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("There is an includes-cycle in declarations"); |
| } |
| |
| return true; |
| } |
| |
| bool Library::CompileDecl(Decl* decl) { |
| 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::kInterface: { |
| auto interface_decl = static_cast<Interface*>(decl); |
| if (!CompileInterface(interface_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::kXUnion: { |
| auto xunion_decl = static_cast<XUnion*>(decl); |
| if (!CompileXUnion(xunion_decl)) |
| return false; |
| break; |
| } |
| } // switch |
| return true; |
| } |
| |
| bool Library::VerifyDeclAttributes(Decl* decl) { |
| assert(decl->compiled && "verification must happen after compilation of decls"); |
| auto placement_ok = error_reporter_->Checkpoint(); |
| switch (decl->kind) { |
| case Decl::Kind::kBits: { |
| auto bits_declaration = static_cast<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*>(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<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::kInterface: { |
| auto interface_declaration = static_cast<Interface*>(decl); |
| // Attributes: check placement. |
| ValidateAttributesPlacement( |
| AttributeSchema::Placement::kInterfaceDecl, |
| interface_declaration->attributes.get()); |
| for (const auto method : interface_declaration->all_methods) { |
| ValidateAttributesPlacement( |
| AttributeSchema::Placement::kMethod, |
| method->attributes.get()); |
| } |
| if (placement_ok.NoNewErrors()) { |
| // Attributes: check constraints. |
| for (const auto method : interface_declaration->all_methods) { |
| if (method->maybe_request) { |
| ValidateAttributesConstraints( |
| method->maybe_request, |
| interface_declaration->attributes.get()); |
| ValidateAttributesConstraints( |
| method->maybe_request, |
| method->attributes.get()); |
| } |
| if (method->maybe_response) { |
| ValidateAttributesConstraints( |
| method->maybe_response, |
| interface_declaration->attributes.get()); |
| ValidateAttributesConstraints( |
| method->maybe_response, |
| method->attributes.get()); |
| } |
| } |
| } |
| break; |
| } |
| case Decl::Kind::kStruct: { |
| auto struct_declaration = static_cast<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<Table*>(decl); |
| // Attributes: check placement. |
| ValidateAttributesPlacement( |
| AttributeSchema::Placement::kTableDecl, |
| table_declaration->attributes.get()); |
| for (const auto& member : table_declaration->members) { |
| if (member.maybe_used) { |
| 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<Union*>(decl); |
| // Attributes: check placement. |
| ValidateAttributesPlacement( |
| AttributeSchema::Placement::kUnionDecl, |
| union_declaration->attributes.get()); |
| for (const auto& member : union_declaration->members) { |
| ValidateAttributesPlacement( |
| AttributeSchema::Placement::kUnionMember, |
| member.attributes.get()); |
| } |
| if (placement_ok.NoNewErrors()) { |
| // Attributes: check constraint. |
| ValidateAttributesConstraints( |
| union_declaration, |
| union_declaration->attributes.get()); |
| } |
| break; |
| } |
| case Decl::Kind::kXUnion: { |
| auto xunion_declaration = static_cast<XUnion*>(decl); |
| // Attributes: check placement. |
| ValidateAttributesPlacement( |
| AttributeSchema::Placement::kXUnionDecl, |
| xunion_declaration->attributes.get()); |
| for (const auto& member : xunion_declaration->members) { |
| ValidateAttributesPlacement( |
| AttributeSchema::Placement::kXUnionMember, |
| member.attributes.get()); |
| } |
| if (placement_ok.NoNewErrors()) { |
| // Attributes: check constraint. |
| ValidateAttributesConstraints( |
| xunion_declaration, |
| xunion_declaration->attributes.get()); |
| } |
| } |
| } // switch |
| return true; |
| } |
| |
| bool Library::CompileBits(Bits* bits_declaration) { |
| if (!CompileTypeConstructor(bits_declaration->subtype_ctor.get(), &bits_declaration->typeshape)) |
| return false; |
| |
| if (bits_declaration->subtype_ctor->type->kind != Type::Kind::kPrimitive) { |
| std::string message("bits may only be of unsigned integral primitive type, found "); |
| message.append(NameFlatType(bits_declaration->subtype_ctor->type)); |
| return Fail(*bits_declaration, message); |
| } |
| |
| // Validate constants. |
| auto primitive_type = static_cast<const PrimitiveType*>(bits_declaration->subtype_ctor->type); |
| switch (primitive_type->subtype) { |
| case types::PrimitiveSubtype::kUint8: |
| if (!ValidateBitsMembers<uint8_t>(bits_declaration)) |
| return false; |
| break; |
| case types::PrimitiveSubtype::kUint16: |
| if (!ValidateBitsMembers<uint16_t>(bits_declaration)) |
| return false; |
| break; |
| case types::PrimitiveSubtype::kUint32: |
| if (!ValidateBitsMembers<uint32_t>(bits_declaration)) |
| return false; |
| break; |
| case types::PrimitiveSubtype::kUint64: |
| if (!ValidateBitsMembers<uint64_t>(bits_declaration)) |
| return false; |
| 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: |
| std::string message("bits may only be of unsigned integral primitive type, found "); |
| message.append(NameFlatType(bits_declaration->subtype_ctor->type)); |
| return Fail(*bits_declaration, message); |
| } |
| |
| return true; |
| } |
| |
| bool Library::CompileConst(Const* const_declaration) { |
| TypeShape typeshape; |
| if (!CompileTypeConstructor(const_declaration->type_ctor.get(), &typeshape)) |
| return false; |
| const auto* const_type = const_declaration->type_ctor.get()->type; |
| if (!TypeCanBeConst(const_type)) { |
| std::ostringstream msg_stream; |
| msg_stream << "invalid constant type " << NameFlatType(const_type); |
| return Fail(*const_declaration, msg_stream.str()); |
| } |
| if (!ResolveConstant(const_declaration->value.get(), const_type)) |
| return Fail(*const_declaration, "unable to resolve constant value"); |
| |
| return true; |
| } |
| |
| bool Library::CompileEnum(Enum* enum_declaration) { |
| if (!CompileTypeConstructor(enum_declaration->subtype_ctor.get(), &enum_declaration->typeshape)) |
| return false; |
| |
| if (enum_declaration->subtype_ctor->type->kind != Type::Kind::kPrimitive) { |
| std::string message("enums may only be of integral primitive type, found "); |
| message.append(NameFlatType(enum_declaration->subtype_ctor->type)); |
| return Fail(*enum_declaration, message); |
| } |
| |
| // Validate constants. |
| auto primitive_type = static_cast<const PrimitiveType*>(enum_declaration->subtype_ctor->type); |
| enum_declaration->type = primitive_type; |
| switch (primitive_type->subtype) { |
| case types::PrimitiveSubtype::kInt8: |
| if (!ValidateEnumMembers<int8_t>(enum_declaration)) |
| return false; |
| break; |
| case types::PrimitiveSubtype::kInt16: |
| if (!ValidateEnumMembers<int16_t>(enum_declaration)) |
| return false; |
| break; |
| case types::PrimitiveSubtype::kInt32: |
| if (!ValidateEnumMembers<int32_t>(enum_declaration)) |
| return false; |
| break; |
| case types::PrimitiveSubtype::kInt64: |
| if (!ValidateEnumMembers<int64_t>(enum_declaration)) |
| return false; |
| break; |
| case types::PrimitiveSubtype::kUint8: |
| if (!ValidateEnumMembers<uint8_t>(enum_declaration)) |
| return false; |
| break; |
| case types::PrimitiveSubtype::kUint16: |
| if (!ValidateEnumMembers<uint16_t>(enum_declaration)) |
| return false; |
| break; |
| case types::PrimitiveSubtype::kUint32: |
| if (!ValidateEnumMembers<uint32_t>(enum_declaration)) |
| return false; |
| break; |
| case types::PrimitiveSubtype::kUint64: |
| if (!ValidateEnumMembers<uint64_t>(enum_declaration)) |
| return false; |
| break; |
| case types::PrimitiveSubtype::kBool: |
| case types::PrimitiveSubtype::kFloat32: |
| case types::PrimitiveSubtype::kFloat64: |
| std::string message("enums may only be of integral primitive type, found "); |
| message.append(NameFlatType(enum_declaration->subtype_ctor->type)); |
| return Fail(*enum_declaration, message); |
| } |
| |
| return true; |
| } |
| |
| bool HasSimpleLayout(const Decl* decl) { |
| return decl->GetAttribute("Layout") == "Simple"; |
| } |
| |
| bool Library::CompileInterface(Interface* interface_declaration) { |
| MethodScope method_scope; |
| auto CheckScopes = [this, &interface_declaration, &method_scope](const Interface* interface, auto Visitor) -> bool { |
| for (const auto& name : interface->superinterfaces) { |
| auto decl = LookupDeclByName(name); |
| // TODO(FIDL-603): Special handling here should not be required, we |
| // should first rely on creating the types representing composed |
| // protocols. |
| if (!decl) { |
| std::string message("unknown type "); |
| message.append(name.name_part()); |
| return Fail(name, message); |
| } |
| if (decl->kind != Decl::Kind::kInterface) |
| return Fail(name, "This superinterface declaration is not an interface"); |
| if (!decl->HasAttribute("FragileBase")) { |
| std::string message = "interface "; |
| message += NameName(name, ".", "/"); |
| message += " is not marked by [FragileBase] attribute, disallowing interface "; |
| message += NameName(interface_declaration->name, ".", "/"); |
| message += " from inheriting from it"; |
| return Fail(name, message); |
| } |
| auto superinterface = static_cast<const Interface*>(decl); |
| auto maybe_location = superinterface->name.maybe_location(); |
| assert(maybe_location); |
| if (method_scope.interfaces.Insert(superinterface, *maybe_location).ok()) { |
| if (!Visitor(superinterface, Visitor)) |
| return false; |
| } else { |
| // Otherwise we have already seen this interface in |
| // the inheritance graph. |
| } |
| } |
| for (const auto& method : interface->methods) { |
| auto name_result = method_scope.names.Insert(method.name.data(), method.name); |
| if (!name_result.ok()) |
| return Fail(method.name, |
| "Multiple methods with the same name in an interface; last occurrence was at " + |
| name_result.previous_occurrence().position()); |
| auto ordinal_result = method_scope.ordinals.Insert(method.ordinal->value, method.name); |
| if (method.ordinal->value == 0) |
| return Fail(method.ordinal->location(), "Ordinal value 0 disallowed."); |
| if (!ordinal_result.ok()) { |
| std::string replacement_method( |
| fidl::ordinals::GetSelector(method.attributes.get(), method.name)); |
| replacement_method.push_back('_'); |
| return Fail(method.ordinal->location(), |
| "Multiple methods with the same ordinal in an interface; previous was at " + |
| ordinal_result.previous_occurrence().position() + ". If these " + |
| "were automatically generated, consider using attribute " + |
| "[Selector=\"" + replacement_method + "\"] to change the " + |
| "name used to calculate the ordinal."); |
| } |
| |
| // Add a pointer to this method to the interface_declarations list. |
| interface_declaration->all_methods.push_back(&method); |
| } |
| return true; |
| }; |
| if (!CheckScopes(interface_declaration, CheckScopes)) |
| return false; |
| |
| interface_declaration->typeshape = HandleType::Shape(); |
| |
| for (auto& method : interface_declaration->methods) { |
| auto CreateMessage = [&](Struct* message) -> bool { |
| Scope<StringView> scope; |
| for (auto& param : message->members) { |
| if (!scope.Insert(param.name.data(), param.name).ok()) |
| return Fail(param.name, "Multiple parameters with the same name in a method"); |
| if (!CompileTypeConstructor(param.type_ctor.get(), ¶m.fieldshape.Typeshape())) |
| return false; |
| } |
| return true; |
| }; |
| if (method.maybe_request) { |
| if (!CreateMessage(method.maybe_request)) |
| return false; |
| } |
| if (method.maybe_response) { |
| if (!CreateMessage(method.maybe_response)) |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool Library::CompileStruct(Struct* struct_declaration) { |
| Scope<StringView> scope; |
| std::vector<FieldShape*> fidl_struct; |
| |
| uint32_t max_member_handles = 0; |
| for (auto& member : struct_declaration->members) { |
| auto name_result = scope.Insert(member.name.data(), member.name); |
| if (!name_result.ok()) |
| return Fail(member.name, |
| "Multiple struct fields with the same name; previous was at " + |
| name_result.previous_occurrence().position()); |
| if (!CompileTypeConstructor(member.type_ctor.get(), &member.fieldshape.Typeshape())) |
| return false; |
| fidl_struct.push_back(&member.fieldshape); |
| } |
| |
| if (struct_declaration->recursive) { |
| max_member_handles = std::numeric_limits<uint32_t>::max(); |
| } else { |
| // Member handles will be counted by CStructTypeShape. |
| max_member_handles = 0; |
| } |
| |
| struct_declaration->typeshape = Struct::Shape(&fidl_struct, max_member_handles); |
| |
| return true; |
| } |
| |
| bool Library::CompileTable(Table* table_declaration) { |
| Scope<StringView> name_scope; |
| Scope<uint32_t> ordinal_scope; |
| |
| uint32_t max_member_handles = 0; |
| for (auto& member : table_declaration->members) { |
| auto ordinal_result = ordinal_scope.Insert(member.ordinal->value, member.ordinal->location()); |
| if (!ordinal_result.ok()) |
| return Fail(member.ordinal->location(), |
| "Multiple table fields with the same ordinal; previous was at " + |
| ordinal_result.previous_occurrence().position()); |
| if (member.maybe_used) { |
| auto name_result = name_scope.Insert(member.maybe_used->name.data(), member.maybe_used->name); |
| if (!name_result.ok()) |
| return Fail(member.maybe_used->name, |
| "Multiple table fields with the same name; previous was at " + |
| name_result.previous_occurrence().position()); |
| if (!CompileTypeConstructor(member.maybe_used->type_ctor.get(), &member.maybe_used->typeshape)) |
| return false; |
| } |
| } |
| |
| uint32_t last_ordinal_seen = 0; |
| for (const auto& ordinal_and_loc : ordinal_scope) { |
| if (ordinal_and_loc.first != last_ordinal_seen + 1) { |
| return Fail(ordinal_and_loc.second, |
| "Missing ordinal (table ordinals do not form a dense space)"); |
| } |
| last_ordinal_seen = ordinal_and_loc.first; |
| } |
| |
| if (table_declaration->recursive) { |
| max_member_handles = std::numeric_limits<uint32_t>::max(); |
| } else { |
| // Member handles will be counted by CTableTypeShape. |
| max_member_handles = 0; |
| } |
| |
| std::vector<TypeShape*> fields(table_declaration->members.size()); |
| for (auto& member : table_declaration->members) { |
| if (member.maybe_used) { |
| fields[member.ordinal->value - 1] = &member.maybe_used->typeshape; |
| } |
| } |
| |
| table_declaration->typeshape = Table::Shape(&fields, max_member_handles); |
| |
| return true; |
| } |
| |
| bool Library::CompileUnion(Union* union_declaration) { |
| Scope<StringView> scope; |
| for (auto& member : union_declaration->members) { |
| auto name_result = scope.Insert(member.name.data(), member.name); |
| if (!name_result.ok()) |
| return Fail(member.name, |
| "Multiple union members with the same name; previous was at " + |
| name_result.previous_occurrence().position()); |
| if (!CompileTypeConstructor(member.type_ctor.get(), &member.fieldshape.Typeshape())) |
| return false; |
| } |
| |
| auto tag = FieldShape(PrimitiveType::Shape(types::PrimitiveSubtype::kUint32)); |
| union_declaration->membershape = FieldShape(Union::Shape(union_declaration->members)); |
| uint32_t extra_handles = 0; |
| if (union_declaration->recursive && union_declaration->membershape.MaxHandles()) { |
| extra_handles = std::numeric_limits<uint32_t>::max(); |
| } |
| std::vector<FieldShape*> fidl_union = {&tag, &union_declaration->membershape}; |
| union_declaration->typeshape = Struct::Shape(&fidl_union, extra_handles); |
| |
| // This is either 4 or 8, depending on whether any union members |
| // have alignment 8. |
| auto offset = union_declaration->membershape.Offset(); |
| for (auto& member : union_declaration->members) { |
| member.fieldshape.SetOffset(offset); |
| } |
| |
| return true; |
| } |
| |
| bool Library::CompileXUnion(XUnion* xunion_declaration) { |
| Scope<StringView> scope; |
| Scope<uint32_t> ordinal_scope; |
| |
| for (auto& member : xunion_declaration->members) { |
| auto ordinal_result = ordinal_scope.Insert(member.ordinal->value, member.ordinal->location()); |
| if (!ordinal_result.ok()) |
| return Fail(member.ordinal->location(), |
| "Multiple xunion fields with the same ordinal; previous was at " + |
| ordinal_result.previous_occurrence().position()); |
| |
| auto name_result = scope.Insert(member.name.data(), member.name); |
| if (!name_result.ok()) |
| return Fail(member.name, |
| "Multiple xunion members with the same name; previous was at " + |
| name_result.previous_occurrence().position()); |
| |
| if (!CompileTypeConstructor(member.type_ctor.get(), &member.fieldshape.Typeshape())) |
| return false; |
| } |
| |
| uint32_t max_member_handles; |
| if (xunion_declaration->recursive) { |
| max_member_handles = std::numeric_limits<uint32_t>::max(); |
| } else { |
| // Member handles will be counted by CXUnionTypeShape. |
| max_member_handles = 0u; |
| } |
| |
| xunion_declaration->typeshape = XUnion::Shape(xunion_declaration->members, max_member_handles); |
| |
| return true; |
| } |
| |
| bool Library::CompileLibraryName() { |
| const std::regex pattern("^[a-z][a-z0-9]*$"); |
| for (const auto& part_view : library_name_) { |
| std::string part = part_view; |
| if (!std::regex_match(part, pattern)) { |
| return Fail("Invalid library name part " + part); |
| } |
| } |
| return true; |
| } |
| |
| bool Library::Compile() { |
| for (const auto& dep_library : dependencies_.dependencies()) { |
| constants_.insert(dep_library->constants_.begin(), dep_library->constants_.end()); |
| } |
| |
| // Verify that the library's name is valid. |
| if (!CompileLibraryName()) { |
| return false; |
| } |
| |
| if (!SortDeclarations()) { |
| return false; |
| } |
| |
| // We process declarations in topologically sorted order. For |
| // example, we process a struct member's type before the entire |
| // struct. |
| for (Decl* decl : declaration_order_) { |
| if (!CompileDecl(decl)) |
| return false; |
| } |
| |
| // Beware, hacky solution: method request and response are structs, whose |
| // typeshape is computed separately. However, in the JSON IR, we add 16 bytes |
| // to request and response to account for the header size (and ensure the |
| // alignment is at least 4 bytes). Now that we represent request and responses |
| // as structs, we should represent this differently in the JSON IR, and let the |
| // backends figure this out, or better yet, describe the header directly. |
| // |
| // For now though, we fixup the representation after the fact. |
| for (auto& interface_decl : interface_declarations_) { |
| for (auto& method : interface_decl->all_methods) { |
| auto FixupMessage = [&](Struct* message) { |
| auto header_field_shape = FieldShape(TypeShape(16u, 4u)); |
| std::vector<FieldShape*> message_struct; |
| message_struct.push_back(&header_field_shape); |
| for (auto& param : message->members) |
| message_struct.push_back(¶m.fieldshape); |
| message->typeshape = FidlMessageTypeShape(&message_struct); |
| }; |
| if (method->maybe_request) |
| FixupMessage(method->maybe_request); |
| if (method->maybe_response) |
| FixupMessage(method->maybe_response); |
| } |
| } |
| |
| for (Decl* decl : declaration_order_) { |
| if (!VerifyDeclAttributes(decl)) |
| return false; |
| } |
| |
| return error_reporter_->errors().size() == 0; |
| } |
| |
| bool Library::CompileTypeConstructor(TypeConstructor* type_ctor, TypeShape* out_typeshape) { |
| const Type* maybe_arg_type = nullptr; |
| if (type_ctor->maybe_arg_type_ctor != nullptr) { |
| if (!CompileTypeConstructor(type_ctor->maybe_arg_type_ctor.get(), nullptr)) |
| return false; |
| maybe_arg_type = type_ctor->maybe_arg_type_ctor->type; |
| } |
| const Size* size = nullptr; |
| if (type_ctor->maybe_size != nullptr) { |
| if (!ResolveConstant(type_ctor->maybe_size.get(), &kSizeType)) |
| return Fail(type_ctor->name.maybe_location(), "unable to parse size bound"); |
| size = static_cast<const Size*>(&type_ctor->maybe_size->Value()); |
| } |
| if (!typespace_->Create(type_ctor->name, maybe_arg_type, type_ctor->maybe_handle_subtype.get(), |
| size, type_ctor->nullability, |
| &type_ctor->type)) |
| return false; |
| if (out_typeshape) |
| *out_typeshape = type_ctor->type->shape; |
| 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 (auto& member : decl->members) { |
| assert(member.value != nullptr && "Compiler bug: member value is null!"); |
| |
| if (!ResolveConstant(member.value.get(), decl->subtype_ctor->type)) { |
| std::string failure_message = "unable to resolve "; |
| failure_message += decl_type; |
| failure_message += " member"; |
| return Fail(member.name, failure_message); |
| } |
| |
| // Check that the member identifier hasn't been used yet |
| std::string name = NameIdentifier(member.name); |
| auto name_result = name_scope.Insert(name, member.name); |
| if (!name_result.ok()) { |
| std::ostringstream msg_stream; |
| msg_stream << "name of member " << name; |
| msg_stream << " conflicts with previously declared member in the "; |
| msg_stream << decl_type << " " << decl->GetName(); |
| |
| // We can log the error and then continue validating for other issues in the decl |
| success = Fail(member.name, msg_stream.str()); |
| } |
| |
| MemberType value = static_cast<const NumericConstantValue<MemberType>&>( |
| member.value->Value()) |
| .value; |
| auto value_result = value_scope.Insert(value, member.name); |
| if (!value_result.ok()) { |
| std::ostringstream msg_stream; |
| msg_stream << "value of member " << name; |
| msg_stream << " conflicts with previously declared member "; |
| msg_stream << NameIdentifier(value_result.previous_occurrence()) << " in the "; |
| msg_stream << decl_type << " " << decl->GetName(); |
| |
| // We can log the error and then continue validating other members for other bugs |
| success = Fail(member.name, msg_stream.str()); |
| } |
| |
| std::string validation_failure; |
| if (!validator(value, &validation_failure)) { |
| success = Fail(member.name, validation_failure); |
| } |
| } |
| |
| 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::ValidateBitsMembers(Bits* bits_decl) { |
| 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. |
| auto validator = [](MemberType member, std::string* out_error) { |
| if (!IsPowerOfTwo(member)) { |
| *out_error = "bits members must be powers of two"; |
| return false; |
| } |
| return true; |
| }; |
| return ValidateMembers<Bits, MemberType>(bits_decl, validator); |
| } |
| |
| template <typename MemberType> |
| bool Library::ValidateEnumMembers(Enum* enum_decl) { |
| static_assert(std::is_integral<MemberType>::value && !std::is_same<MemberType, bool>::value, |
| "Enum members must be an integral type!"); |
| // No additional validation is required for enums. |
| auto validator = [](MemberType member, std::string* out_error) { return true; }; |
| return ValidateMembers<Enum, MemberType>(enum_decl, validator); |
| } |
| |
| bool Library::HasAttribute(fidl::StringView name) const { |
| if (!attributes_) |
| return false; |
| return attributes_->HasAttribute(name); |
| } |
| |
| const std::set<Library*>& Library::dependencies() const { |
| return dependencies_.dependencies(); |
| } |
| |
| } // namespace flat |
| } // namespace fidl |