// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_ARRAY_INTERNAL_H_ #define MOJO_PUBLIC_CPP_BINDINGS_LIB_ARRAY_INTERNAL_H_ #include <stddef.h> #include <stdint.h> #include <limits> #include <new> #include "base/component_export.h" #include "base/logging.h" #include "base/macros.h" #include "mojo/public/c/system/macros.h" #include "mojo/public/cpp/bindings/lib/bindings_internal.h" #include "mojo/public/cpp/bindings/lib/buffer.h" #include "mojo/public/cpp/bindings/lib/serialization_util.h" #include "mojo/public/cpp/bindings/lib/template_util.h" #include "mojo/public/cpp/bindings/lib/validate_params.h" #include "mojo/public/cpp/bindings/lib/validation_context.h" #include "mojo/public/cpp/bindings/lib/validation_errors.h" #include "mojo/public/cpp/bindings/lib/validation_util.h" namespace mojo { namespace internal { template <typename K, typename V> class Map_Data; COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE) std::string MakeMessageWithArrayIndex(const char* message, size_t size, size_t index); COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE) std::string MakeMessageWithExpectedArraySize(const char* message, size_t size, size_t expected_size); template <typename T> struct ArrayDataTraits { using StorageType = T; using Ref = T&; using ConstRef = const T&; static const uint32_t kMaxNumElements = (std::numeric_limits<uint32_t>::max() - sizeof(ArrayHeader)) / sizeof(StorageType); static uint32_t GetStorageSize(uint32_t num_elements) { DCHECK(num_elements <= kMaxNumElements); return sizeof(ArrayHeader) + sizeof(StorageType) * num_elements; } static Ref ToRef(StorageType* storage, size_t offset) { return storage[offset]; } static ConstRef ToConstRef(const StorageType* storage, size_t offset) { return storage[offset]; } }; // Specialization of Arrays for bools, optimized for space. It has the // following differences from a generalized Array: // * Each element takes up a single bit of memory. // * Accessing a non-const single element uses a helper class |BitRef|, which // emulates a reference to a bool. template <> struct ArrayDataTraits<bool> { // Helper class to emulate a reference to a bool, used for direct element // access. class COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE) BitRef { public: ~BitRef(); BitRef& operator=(bool value); BitRef& operator=(const BitRef& value); operator bool() const; private: friend struct ArrayDataTraits<bool>; BitRef(uint8_t* storage, uint8_t mask); BitRef(); uint8_t* storage_; uint8_t mask_; }; // Because each element consumes only 1/8 byte. static const uint32_t kMaxNumElements = std::numeric_limits<uint32_t>::max(); using StorageType = uint8_t; using Ref = BitRef; using ConstRef = bool; static uint32_t GetStorageSize(uint32_t num_elements) { return sizeof(ArrayHeader) + ((num_elements + 7) / 8); } static BitRef ToRef(StorageType* storage, size_t offset) { return BitRef(&storage[offset / 8], 1 << (offset % 8)); } static bool ToConstRef(const StorageType* storage, size_t offset) { return (storage[offset / 8] & (1 << (offset % 8))) != 0; } }; // What follows is code to support the serialization/validation of // Array_Data<T>. There are four interesting cases: arrays of primitives, // arrays of handles/interfaces, arrays of objects and arrays of unions. // Arrays of objects are represented as arrays of pointers to objects. Arrays // of unions are inlined so they are not pointers, but comparing with primitives // they require more work for serialization/validation. // // TODO(yzshen): Validation code should be organzied in a way similar to // Serializer<>, or merged into it. It should be templatized with the mojo // data view type instead of the data type, that way we can use MojomTypeTraits // to determine the categories. template <typename T, bool is_union, bool is_handle_or_interface> struct ArraySerializationHelper; template <typename T> struct ArraySerializationHelper<T, false, false> { using ElementType = typename ArrayDataTraits<T>::StorageType; static bool ValidateElements(const ArrayHeader* header, const ElementType* elements, ValidationContext* validation_context, const ContainerValidateParams* validate_params) { DCHECK(!validate_params->element_is_nullable) << "Primitive type should be non-nullable"; DCHECK(!validate_params->element_validate_params) << "Primitive type should not have array validate params"; if (!validate_params->validate_enum_func) return true; // Enum validation. for (uint32_t i = 0; i < header->num_elements; ++i) { if (!validate_params->validate_enum_func(elements[i], validation_context)) return false; } return true; } }; template <typename T> struct ArraySerializationHelper<T, false, true> { using ElementType = typename ArrayDataTraits<T>::StorageType; static bool ValidateElements(const ArrayHeader* header, const ElementType* elements, ValidationContext* validation_context, const ContainerValidateParams* validate_params) { DCHECK(!validate_params->element_validate_params) << "Handle or interface type should not have array validate params"; for (uint32_t i = 0; i < header->num_elements; ++i) { if (!validate_params->element_is_nullable && !IsHandleOrInterfaceValid(elements[i])) { static const ValidationError kError = std::is_same<T, Interface_Data>::value || std::is_same<T, Handle_Data>::value ? VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE : VALIDATION_ERROR_UNEXPECTED_INVALID_INTERFACE_ID; ReportValidationError( validation_context, kError, MakeMessageWithArrayIndex( "invalid handle or interface ID in array expecting valid " "handles or interface IDs", header->num_elements, i) .c_str()); return false; } if (!ValidateHandleOrInterface(elements[i], validation_context)) return false; } return true; } }; template <typename T> struct ArraySerializationHelper<Pointer<T>, false, false> { using ElementType = typename ArrayDataTraits<Pointer<T>>::StorageType; static bool ValidateElements(const ArrayHeader* header, const ElementType* elements, ValidationContext* validation_context, const ContainerValidateParams* validate_params) { for (uint32_t i = 0; i < header->num_elements; ++i) { if (!validate_params->element_is_nullable && !elements[i].offset) { ReportValidationError( validation_context, VALIDATION_ERROR_UNEXPECTED_NULL_POINTER, MakeMessageWithArrayIndex("null in array expecting valid pointers", header->num_elements, i).c_str()); return false; } if (!ValidateCaller<T>::Run(elements[i], validation_context, validate_params->element_validate_params)) { return false; } } return true; } private: template <typename U, bool is_array_or_map = IsSpecializationOf<Array_Data, U>::value || IsSpecializationOf<Map_Data, U>::value> struct ValidateCaller { static bool Run(const Pointer<U>& data, ValidationContext* validation_context, const ContainerValidateParams* validate_params) { DCHECK(!validate_params) << "Struct type should not have array validate params"; return ValidateStruct(data, validation_context); } }; template <typename U> struct ValidateCaller<U, true> { static bool Run(const Pointer<U>& data, ValidationContext* validation_context, const ContainerValidateParams* validate_params) { return ValidateContainer(data, validation_context, validate_params); } }; }; template <typename U> struct ArraySerializationHelper<U, true, false> { using ElementType = typename ArrayDataTraits<U>::StorageType; static bool ValidateElements(const ArrayHeader* header, const ElementType* elements, ValidationContext* validation_context, const ContainerValidateParams* validate_params) { for (uint32_t i = 0; i < header->num_elements; ++i) { if (!validate_params->element_is_nullable && elements[i].is_null()) { ReportValidationError( validation_context, VALIDATION_ERROR_UNEXPECTED_NULL_POINTER, MakeMessageWithArrayIndex("null in array expecting valid unions", header->num_elements, i) .c_str()); return false; } if (!ValidateInlinedUnion(elements[i], validation_context)) return false; } return true; } }; template <typename T> class Array_Data { public: using Traits = ArrayDataTraits<T>; using StorageType = typename Traits::StorageType; using Ref = typename Traits::Ref; using ConstRef = typename Traits::ConstRef; using Helper = ArraySerializationHelper< T, IsUnionDataType<T>::value, std::is_same<T, AssociatedInterface_Data>::value || std::is_same<T, AssociatedEndpointHandle_Data>::value || std::is_same<T, Interface_Data>::value || std::is_same<T, Handle_Data>::value>; using Element = T; class BufferWriter { public: BufferWriter() = default; void Allocate(size_t num_elements, Buffer* buffer) { if (num_elements > Traits::kMaxNumElements) return; uint32_t num_bytes = Traits::GetStorageSize(static_cast<uint32_t>(num_elements)); buffer_ = buffer; index_ = buffer_->Allocate(num_bytes); new (data()) Array_Data<T>(num_bytes, static_cast<uint32_t>(num_elements)); } bool is_null() const { return !buffer_; } Array_Data<T>* data() { DCHECK(!is_null()); return buffer_->Get<Array_Data<T>>(index_); } Array_Data<T>* operator->() { return data(); } private: Buffer* buffer_ = nullptr; size_t index_ = 0; DISALLOW_COPY_AND_ASSIGN(BufferWriter); }; static bool Validate(const void* data, ValidationContext* validation_context, const ContainerValidateParams* validate_params) { if (!data) return true; if (!IsAligned(data)) { ReportValidationError(validation_context, VALIDATION_ERROR_MISALIGNED_OBJECT); return false; } if (!validation_context->IsValidRange(data, sizeof(ArrayHeader))) { ReportValidationError(validation_context, VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE); return false; } const ArrayHeader* header = static_cast<const ArrayHeader*>(data); if (header->num_elements > Traits::kMaxNumElements || header->num_bytes < Traits::GetStorageSize(header->num_elements)) { ReportValidationError(validation_context, VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER); return false; } if (validate_params->expected_num_elements != 0 && header->num_elements != validate_params->expected_num_elements) { ReportValidationError( validation_context, VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER, MakeMessageWithExpectedArraySize( "fixed-size array has wrong number of elements", header->num_elements, validate_params->expected_num_elements).c_str()); return false; } if (!validation_context->ClaimMemory(data, header->num_bytes)) { ReportValidationError(validation_context, VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE); return false; } const Array_Data<T>* object = static_cast<const Array_Data<T>*>(data); return Helper::ValidateElements(&object->header_, object->storage(), validation_context, validate_params); } size_t size() const { return header_.num_elements; } Ref at(size_t offset) { DCHECK(offset < static_cast<size_t>(header_.num_elements)); return Traits::ToRef(storage(), offset); } ConstRef at(size_t offset) const { DCHECK(offset < static_cast<size_t>(header_.num_elements)); return Traits::ToConstRef(storage(), offset); } StorageType* storage() { return reinterpret_cast<StorageType*>(reinterpret_cast<char*>(this) + sizeof(*this)); } const StorageType* storage() const { return reinterpret_cast<const StorageType*>( reinterpret_cast<const char*>(this) + sizeof(*this)); } private: Array_Data(uint32_t num_bytes, uint32_t num_elements) { header_.num_bytes = num_bytes; header_.num_elements = num_elements; } ~Array_Data() = delete; internal::ArrayHeader header_; // Elements of type internal::ArrayDataTraits<T>::StorageType follow. }; static_assert(sizeof(Array_Data<char>) == 8, "Bad sizeof(Array_Data)"); // UTF-8 encoded using String_Data = Array_Data<char>; } // namespace internal } // namespace mojo #endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_ARRAY_INTERNAL_H_