390 lines
14 KiB
C++
390 lines
14 KiB
C++
// 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_
|