blob: 0898cf9fd7ce7eede3fa753e8460850545f893e6 [file] [log] [blame]
// 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.
#ifndef LIB_FIDL_WALKER_H_
#define LIB_FIDL_WALKER_H_
#include <lib/fidl/coding.h>
#include <lib/fidl/internal.h>
#include <lib/fidl/visitor.h>
#include <stdalign.h>
#include <zircon/assert.h>
#include <zircon/compiler.h>
#include <cstdint>
#include <cstdlib>
#include <limits>
#include <type_traits>
namespace fidl {
namespace internal {
// The MSB is an ownership bit in the count field of vectors.
constexpr uint64_t kVectorOwnershipMask = uint64_t(1) << 63;
// The LSB is an ownership bit in tracking_ptr of non-array type.
constexpr uint64_t kNonArrayTrackingPtrOwnershipMask = uint64_t(1) << 0;
// Some assumptions about data type layout.
static_assert(offsetof(fidl_string_t, size) == 0u, "fidl_string_t layout");
static_assert(offsetof(fidl_string_t, data) == 8u, "fidl_string_t layout");
static_assert(sizeof(fidl_string_t) == 16u, "fidl_string_t layout");
static_assert(offsetof(fidl_vector_t, count) == 0u, "fidl_vector_t layout");
static_assert(offsetof(fidl_vector_t, data) == 8u, "fidl_vector_t layout");
static_assert(sizeof(fidl_vector_t) == 16u, "fidl_vector_t layout");
static_assert(offsetof(fidl_envelope_t, num_bytes) == 0u, "fidl_envelope_t layout");
static_assert(offsetof(fidl_envelope_t, num_handles) == 4u, "fidl_envelope_t layout");
static_assert(offsetof(fidl_envelope_t, data) == 8u, "fidl_envelope_t layout");
static_assert(sizeof(fidl_envelope_t) == 16u, "fidl_envelope_t layout");
static_assert(ZX_HANDLE_INVALID == FIDL_HANDLE_ABSENT, "invalid handle equals absence marker");
constexpr uint32_t PrimitiveSize(const FidlCodedPrimitive primitive) {
switch (primitive) {
case kFidlCodedPrimitive_Bool:
case kFidlCodedPrimitive_Int8:
case kFidlCodedPrimitive_Uint8:
return 1;
case kFidlCodedPrimitive_Int16:
case kFidlCodedPrimitive_Uint16:
return 2;
case kFidlCodedPrimitive_Int32:
case kFidlCodedPrimitive_Uint32:
case kFidlCodedPrimitive_Float32:
return 4;
case kFidlCodedPrimitive_Int64:
case kFidlCodedPrimitive_Uint64:
case kFidlCodedPrimitive_Float64:
return 8;
}
__builtin_unreachable();
}
constexpr uint32_t TypeSize(const fidl_type_t* type) {
switch (type->type_tag) {
case kFidlTypePrimitive:
return PrimitiveSize(type->coded_primitive);
case kFidlTypeEnum:
return PrimitiveSize(type->coded_enum.underlying_type);
case kFidlTypeBits:
return PrimitiveSize(type->coded_bits.underlying_type);
case kFidlTypeStructPointer:
case kFidlTypeUnionPointer:
return sizeof(uint64_t);
case kFidlTypeHandle:
return sizeof(zx_handle_t);
case kFidlTypeStruct:
return type->coded_struct.size;
case kFidlTypeTable:
return sizeof(fidl_vector_t);
case kFidlTypeUnion:
return type->coded_union.size;
case kFidlTypeXUnion:
return sizeof(fidl_xunion_t);
case kFidlTypeString:
return sizeof(fidl_string_t);
case kFidlTypeArray:
return type->coded_array.array_size;
case kFidlTypeVector:
return sizeof(fidl_vector_t);
}
__builtin_unreachable();
}
constexpr bool IsPrimitive(const fidl_type_t* type) {
switch (type->type_tag) {
case kFidlTypePrimitive:
return true;
default:
return false;
}
}
// The Walker class traverses through a FIDL message by following its coding table and
// calling the visitor implementation. VisitorImpl must be a concrete implementation of the
// fidl::Visitor interface. The concrete type is used to eliminate dynamic dispatch.
template <typename VisitorImpl>
class Walker final {
private:
using MutationTrait = typename VisitorImpl::MutationTrait;
using StartingPoint = typename VisitorImpl::StartingPoint;
using Position = typename VisitorImpl::Position;
using VisitorSuper = Visitor<MutationTrait, StartingPoint, Position>;
using Status = typename VisitorSuper::Status;
static_assert(CheckVisitorInterface<VisitorSuper, VisitorImpl>(), "");
public:
Walker(const fidl_type_t* type, StartingPoint start) : type_(type), start_(start) {}
// Walk the object/buffer located at |start_|.
void Walk(VisitorImpl& visitor);
private:
// Optionally uses non-const pointers depending on if the visitor
// is declared as mutating or not.
template <typename T>
using Ptr = typename VisitorImpl::template Ptr<T>;
// Wrapper around Position::Get with friendlier syntax.
template <typename T>
Ptr<T> PtrTo(Position position) {
return position.template Get<T>(start_);
}
// Functions that manipulate the coding stack frames.
struct Frame {
Frame(const fidl_type_t* fidl_type, Position position) : position(position) {
switch (fidl_type->type_tag) {
case kFidlTypeEnum:
state = kStateEnum;
enum_state.underlying_type = fidl_type->coded_enum.underlying_type;
enum_state.validate = fidl_type->coded_enum.validate;
break;
case kFidlTypeBits:
state = kStateBits;
bits_state.underlying_type = fidl_type->coded_bits.underlying_type;
bits_state.mask = fidl_type->coded_bits.mask;
break;
case kFidlTypeStruct:
state = kStateStruct;
struct_state.fields = fidl_type->coded_struct.fields;
struct_state.field_count = fidl_type->coded_struct.field_count;
struct_state.field = 0;
struct_state.struct_size = fidl_type->coded_struct.size;
break;
case kFidlTypeStructPointer:
state = kStateStructPointer;
struct_pointer_state.struct_type = fidl_type->coded_struct_pointer.struct_type;
break;
case kFidlTypeTable:
state = kStateTable;
table_state.field = fidl_type->coded_table.fields;
table_state.remaining_fields = fidl_type->coded_table.field_count;
table_state.present_count = 0;
table_state.ordinal = 0;
break;
case kFidlTypeUnion:
state = kStateUnion;
union_state.fields = fidl_type->coded_union.fields;
union_state.field_count = fidl_type->coded_union.field_count;
union_state.data_offset = fidl_type->coded_union.data_offset;
union_state.union_size = fidl_type->coded_union.size;
break;
case kFidlTypeUnionPointer:
state = kStateUnionPointer;
union_pointer_state.union_type = fidl_type->coded_union_pointer.union_type;
break;
case kFidlTypeXUnion:
state = kStateXUnion;
xunion_state.fields = fidl_type->coded_xunion.fields;
xunion_state.field_count = fidl_type->coded_xunion.field_count;
xunion_state.inside_envelope = false;
xunion_state.nullable = fidl_type->coded_xunion.nullable;
xunion_state.strictness = fidl_type->coded_xunion.strictness;
break;
case kFidlTypeArray:
state = kStateArray;
array_state.element = fidl_type->coded_array.element;
array_state.array_size = fidl_type->coded_array.array_size;
array_state.element_size = fidl_type->coded_array.element_size;
array_state.element_offset = 0;
break;
case kFidlTypeString:
state = kStateString;
string_state.max_size = fidl_type->coded_string.max_size;
string_state.nullable = fidl_type->coded_string.nullable;
break;
case kFidlTypeHandle:
state = kStateHandle;
handle_state.handle_rights = fidl_type->coded_handle.handle_rights;
handle_state.handle_subtype = fidl_type->coded_handle.handle_subtype;
handle_state.nullable = fidl_type->coded_handle.nullable;
break;
case kFidlTypeVector:
state = kStateVector;
vector_state.element = fidl_type->coded_vector.element;
vector_state.max_count = fidl_type->coded_vector.max_count;
vector_state.element_size = fidl_type->coded_vector.element_size;
vector_state.nullable = fidl_type->coded_vector.nullable;
break;
case kFidlTypePrimitive:
state = kStatePrimitive;
break;
}
}
Frame(const FidlCodedStruct* coded_struct, Position position) : position(position) {
state = kStateStruct;
struct_state.fields = coded_struct->fields;
struct_state.field_count = coded_struct->field_count;
struct_state.field = 0;
struct_state.struct_size = coded_struct->size;
}
Frame(const FidlCodedTable* coded_table, Position position) : position(position) {
state = kStateStruct;
table_state.field = coded_table->fields;
table_state.remaining_fields = coded_table->field_count;
table_state.present_count = 0;
table_state.ordinal = 0;
}
Frame(const FidlCodedUnion* coded_union, Position position) : position(position) {
state = kStateUnion;
union_state.fields = coded_union->fields;
union_state.field_count = coded_union->field_count;
union_state.data_offset = coded_union->data_offset;
union_state.union_size = coded_union->size;
}
Frame(const FidlCodedXUnion* coded_xunion, Position position)
: state(kStateXUnion), position(position) {
// This initialization is done in the ctor body instead of in an
// initialization list since we need to set fields in unions, which
// is much more involved in a ctor initialization list.
xunion_state.fields = coded_xunion->fields;
xunion_state.field_count = coded_xunion->field_count;
xunion_state.inside_envelope = false;
xunion_state.nullable = coded_xunion->nullable;
xunion_state.strictness = coded_xunion->strictness;
}
Frame(const fidl_type_t* element, uint32_t array_size, uint32_t element_size, Position position)
: position(position) {
state = kStateArray;
array_state.element = element;
array_state.array_size = array_size;
array_state.element_size = element_size;
array_state.element_offset = 0;
}
// The default constructor does nothing when initializing the stack of frames.
Frame() = default;
static Frame DoneSentinel() {
Frame frame;
frame.state = kStateDone;
return frame;
}
uint32_t NextStructField() {
ZX_DEBUG_ASSERT(state == kStateStruct);
uint32_t current = struct_state.field;
struct_state.field++;
return current;
}
uint32_t NextArrayOffset() {
ZX_DEBUG_ASSERT(state == kStateArray);
uint32_t current = array_state.element_offset;
array_state.element_offset += array_state.element_size;
return current;
}
enum : int {
kStateEnum,
kStateBits,
kStateStruct,
kStateStructPointer,
kStateTable,
kStateUnion,
kStateUnionPointer,
kStateXUnion,
kStateArray,
kStateString,
kStateHandle,
kStateVector,
kStatePrimitive,
kStateDone,
} state;
// Position into the message.
Position position;
// This is a subset of the information recorded in the
// fidl_type structures needed for coding state. For
// example, struct sizes do not need to be present here.
union {
struct {
FidlCodedPrimitive underlying_type;
EnumValidationPredicate validate;
} enum_state;
struct {
FidlCodedPrimitive underlying_type;
uint64_t mask;
} bits_state;
struct {
const FidlStructField* fields;
uint32_t field_count;
// Index of the currently processing field.
uint32_t field;
// Size of the entire struct.
uint32_t struct_size;
} struct_state;
struct {
const FidlCodedStruct* struct_type;
} struct_pointer_state;
struct {
// Sparse (but monotonically increasing) coding table array for fields;
// advance the |field| pointer on every matched ordinal to save space
const FidlTableField* field;
// Number of unseen fields in the coding table
uint32_t remaining_fields;
// How many fields are stored in the message
uint32_t present_count;
// Current ordinal (valid ordinals start at 1)
uint32_t ordinal;
// When true, the walker is currently working within an envelope, or equivalently,
// |EnterEnvelope| was successful.
bool inside_envelope;
} table_state;
struct {
// Array of coding table corresponding to each union variant.
// The union tag counts upwards from 0 without breaks; hence it can be used to
// index into the |fields| array.
const FidlUnionField* fields;
// Size of the |fields| array. Equal to the number of tags.
uint32_t field_count;
// Offset of the payload in the wire format (size of tag + padding).
uint32_t data_offset;
// Size of the entire union.
uint32_t union_size;
} union_state;
struct {
const FidlCodedUnion* union_type;
} union_pointer_state;
struct {
const FidlXUnionField* fields;
// Number of known ordinals declared in the coding table
uint32_t field_count;
// When true, the walker is currently working within an envelope, or equivalently,
// |EnterEnvelope| was successful.
bool inside_envelope;
FidlNullability nullable;
FidlStrictness strictness;
} xunion_state;
struct {
const fidl_type_t* element;
// Size of the entire array in bytes
uint32_t array_size;
// Size of a single element in bytes
uint32_t element_size;
// Byte offset of the current element being processed
uint32_t element_offset;
} array_state;
struct {
uint32_t max_size;
bool nullable;
} string_state;
struct {
zx_rights_t handle_rights;
zx_obj_type_t handle_subtype;
bool nullable;
} handle_state;
struct {
const fidl_type_t* element;
// Upperbound on number of elements.
uint32_t max_count;
// Size of a single element in bytes
uint32_t element_size;
bool nullable;
} vector_state;
};
};
// Returns true on success and false on recursion overflow.
bool Push(Frame frame) {
if (depth_ == FIDL_RECURSION_DEPTH) {
return false;
}
coding_frames_[depth_] = frame;
++depth_;
return true;
}
void Pop() {
ZX_DEBUG_ASSERT(depth_ != 0u);
--depth_;
}
Frame* Peek() {
ZX_DEBUG_ASSERT(depth_ != 0u);
return &coding_frames_[depth_ - 1];
}
const fidl_type_t* const type_;
const StartingPoint start_;
// Decoding stack state.
uint32_t depth_ = 0u;
Frame coding_frames_[FIDL_RECURSION_DEPTH];
};
template <typename VisitorImpl>
void Walker<VisitorImpl>::Walk(VisitorImpl& visitor) {
Push(Frame::DoneSentinel());
Push(Frame(type_, start_.ToPosition()));
// Macro to insert the relevant goop required to support two control flows here in case of error:
// one where we keep reading after error, and another where we return immediately.
#define FIDL_STATUS_GUARD_IMPL(status, pop) \
switch ((status)) { \
case Status::kSuccess: \
break; \
case Status::kConstraintViolationError: \
if (VisitorImpl::kContinueAfterConstraintViolation) { \
if ((pop)) { \
Pop(); \
} \
continue; \
} else { \
return; \
} \
case Status::kMemoryError: \
return; \
}
#define FIDL_STATUS_GUARD(status) FIDL_STATUS_GUARD_IMPL(status, true)
#define FIDL_STATUS_GUARD_NO_POP(status) FIDL_STATUS_GUARD_IMPL(status, false)
for (;;) {
Frame* frame = Peek();
switch (frame->state) {
case Frame::kStateEnum: {
uint64_t value;
switch (frame->enum_state.underlying_type) {
case kFidlCodedPrimitive_Uint8:
value = *PtrTo<uint8_t>(frame->position);
break;
case kFidlCodedPrimitive_Uint16:
value = *PtrTo<uint16_t>(frame->position);
break;
case kFidlCodedPrimitive_Uint32:
value = *PtrTo<uint32_t>(frame->position);
break;
case kFidlCodedPrimitive_Uint64:
value = *PtrTo<uint64_t>(frame->position);
break;
case kFidlCodedPrimitive_Int8:
value = static_cast<uint64_t>(*PtrTo<int8_t>(frame->position));
break;
case kFidlCodedPrimitive_Int16:
value = static_cast<uint64_t>(*PtrTo<int16_t>(frame->position));
break;
case kFidlCodedPrimitive_Int32:
value = static_cast<uint64_t>(*PtrTo<int32_t>(frame->position));
break;
case kFidlCodedPrimitive_Int64:
value = static_cast<uint64_t>(*PtrTo<int64_t>(frame->position));
break;
default:
__builtin_unreachable();
}
if (!frame->enum_state.validate(value)) {
// TODO(FIDL-523): Make this strictness dependent.
visitor.OnError("not a valid enum member");
FIDL_STATUS_GUARD(Status::kConstraintViolationError);
}
Pop();
continue;
}
case Frame::kStateBits: {
uint64_t value;
switch (frame->bits_state.underlying_type) {
case kFidlCodedPrimitive_Uint8:
value = *PtrTo<uint8_t>(frame->position);
break;
case kFidlCodedPrimitive_Uint16:
value = *PtrTo<uint16_t>(frame->position);
break;
case kFidlCodedPrimitive_Uint32:
value = *PtrTo<uint32_t>(frame->position);
break;
case kFidlCodedPrimitive_Uint64:
value = *PtrTo<uint64_t>(frame->position);
break;
default:
__builtin_unreachable();
}
if (value & ~frame->bits_state.mask) {
visitor.OnError("not a valid bits member");
FIDL_STATUS_GUARD(Status::kConstraintViolationError);
}
Pop();
continue;
}
case Frame::kStateStruct: {
const uint32_t field_index = frame->NextStructField();
if (field_index == frame->struct_state.field_count) {
Pop();
continue;
}
const FidlStructField& field = frame->struct_state.fields[field_index];
const fidl_type_t* field_type = field.type;
Position field_position = frame->position + field.offset;
if (field.padding > 0) {
Position padding_position;
if (field_type) {
padding_position = field_position + TypeSize(field_type);
} else {
// Current type does not have coding information. |field.offset| stores the
// offset of the padding.
padding_position = field_position;
}
auto status = visitor.VisitInternalPadding(padding_position, field.padding);
FIDL_STATUS_GUARD(status);
}
if (!field_type) {
// Skip fields that do not contain codable types.
// Such fields only serve to provide padding information.
continue;
}
if (!Push(Frame(field_type, field_position))) {
visitor.OnError("recursion depth exceeded processing struct");
FIDL_STATUS_GUARD(Status::kConstraintViolationError);
}
continue;
}
case Frame::kStateStructPointer: {
if (*PtrTo<Ptr<void>>(frame->position) == nullptr) {
Pop();
continue;
}
auto status = visitor.VisitPointer(
frame->position, VisitorImpl::PointeeType::kOther, PtrTo<Ptr<void>>(frame->position),
frame->struct_pointer_state.struct_type->size, &frame->position);
FIDL_STATUS_GUARD(status);
const FidlCodedStruct* coded_struct = frame->struct_pointer_state.struct_type;
*frame = Frame(coded_struct, frame->position);
continue;
}
case Frame::kStateTable: {
auto& table_frame = frame->table_state;
// Utility to locate the position of the Nth-ordinal envelope header
auto envelope_position = [&frame](uint32_t ordinal) -> Position {
return frame->position + (ordinal - 1) * static_cast<uint32_t>(sizeof(fidl_envelope_t));
};
if (table_frame.ordinal == 0) {
// Process the vector part of the table
auto envelope_vector_ptr = PtrTo<fidl_vector_t>(frame->position);
if (envelope_vector_ptr->data == nullptr) {
// The vector of envelope headers in a table is always non-nullable.
if (!VisitorImpl::kAllowNonNullableCollectionsToBeAbsent) {
visitor.OnError("Table data cannot be absent");
FIDL_STATUS_GUARD(Status::kConstraintViolationError);
}
if (envelope_vector_ptr->count != 0) {
visitor.OnError("Table envelope vector data absent but non-zero count");
FIDL_STATUS_GUARD(Status::kConstraintViolationError);
}
}
uint32_t size;
if (mul_overflow(envelope_vector_ptr->count, sizeof(fidl_envelope_t), &size)) {
visitor.OnError("integer overflow calculating table size");
return;
}
auto status = visitor.VisitPointer(frame->position, VisitorImpl::PointeeType::kOther,
&envelope_vector_ptr->data, size, &frame->position);
FIDL_STATUS_GUARD(status);
table_frame.ordinal = 1;
table_frame.present_count = static_cast<uint32_t>(envelope_vector_ptr->count);
table_frame.inside_envelope = false;
continue;
}
if (table_frame.inside_envelope) {
// Leave the envelope that was entered during the last iteration
uint32_t last_ordinal = table_frame.ordinal - 1;
ZX_DEBUG_ASSERT(last_ordinal >= 1);
Position envelope_pos = envelope_position(last_ordinal);
auto envelope_ptr = PtrTo<fidl_envelope_t>(envelope_pos);
table_frame.inside_envelope = false;
auto status = visitor.LeaveEnvelope(envelope_pos, envelope_ptr);
FIDL_STATUS_GUARD(status);
}
if (table_frame.ordinal > table_frame.present_count) {
// Processed last stored field in table. Done with this table.
Pop();
continue;
}
const FidlTableField* known_field = nullptr;
if (table_frame.remaining_fields > 0) {
const FidlTableField* field = table_frame.field;
if (field->ordinal == table_frame.ordinal) {
known_field = field;
table_frame.field++;
table_frame.remaining_fields--;
}
}
Position envelope_pos = envelope_position(table_frame.ordinal);
auto envelope_ptr = PtrTo<fidl_envelope_t>(envelope_pos);
// Process the next ordinal in the following state machine iteration
table_frame.ordinal++;
// Make sure we don't process a malformed envelope
const fidl_type_t* payload_type = known_field ? known_field->type : nullptr;
auto status = visitor.EnterEnvelope(envelope_pos, envelope_ptr, payload_type);
FIDL_STATUS_GUARD(status);
table_frame.inside_envelope = true;
// Skip empty envelopes
if (envelope_ptr->data == nullptr) {
continue;
}
uint32_t num_bytes =
payload_type != nullptr ? TypeSize(payload_type) : envelope_ptr->num_bytes;
Position position;
status =
visitor.VisitPointer(frame->position, VisitorImpl::PointeeType::kOther,
// casting since |envelope_ptr->data| is always void*
&const_cast<Ptr<void>&>(envelope_ptr->data), num_bytes, &position);
// Do not pop the table frame, to guarantee calling |LeaveEnvelope|
FIDL_STATUS_GUARD_NO_POP(status);
if (payload_type != nullptr && !IsPrimitive(payload_type)) {
if (!Push(Frame(payload_type, position))) {
visitor.OnError("recursion depth exceeded processing table");
FIDL_STATUS_GUARD_NO_POP(Status::kConstraintViolationError);
}
}
continue;
}
case Frame::kStateUnion: {
auto union_tag = *PtrTo<fidl_union_tag_t>(frame->position);
if (union_tag >= frame->union_state.field_count) {
visitor.OnError("Bad union discriminant");
FIDL_STATUS_GUARD(Status::kConstraintViolationError);
}
auto variant = frame->union_state.fields[union_tag];
if (variant.padding > 0) {
Position padding_position =
frame->position + (frame->union_state.union_size - variant.padding);
auto status = visitor.VisitInternalPadding(padding_position, variant.padding);
FIDL_STATUS_GUARD(status);
}
auto data_offset = frame->union_state.data_offset;
ZX_DEBUG_ASSERT(data_offset == 4 || data_offset == 8);
if (data_offset == 8) {
// There is an additional 4 byte of padding after the tag.
auto status = visitor.VisitInternalPadding(frame->position + 4, 4);
FIDL_STATUS_GUARD(status);
}
const fidl_type_t* member = variant.type;
if (!member) {
Pop();
continue;
}
frame->position += data_offset;
*frame = Frame(member, frame->position);
continue;
}
case Frame::kStateUnionPointer: {
if (*PtrTo<Ptr<fidl_union_tag_t>>(frame->position) == nullptr) {
Pop();
continue;
}
auto status = visitor.VisitPointer(
frame->position, VisitorImpl::PointeeType::kOther, PtrTo<Ptr<void>>(frame->position),
frame->union_pointer_state.union_type->size, &frame->position);
FIDL_STATUS_GUARD(status);
const FidlCodedUnion* coded_union = frame->union_pointer_state.union_type;
*frame = Frame(coded_union, frame->position);
continue;
}
case Frame::kStateXUnion: {
auto xunion = PtrTo<fidl_xunion_t>(frame->position);
const auto envelope_pos = frame->position + offsetof(fidl_xunion_t, envelope);
auto envelope_ptr = &xunion->envelope;
// |inside_envelope| is always false when first encountering an xunion.
if (frame->xunion_state.inside_envelope) {
// Finished processing the xunion field, and is in clean-up state
auto status = visitor.LeaveEnvelope(envelope_pos, envelope_ptr);
FIDL_STATUS_GUARD(status);
Pop();
continue;
}
// Validate zero-ordinal invariants
if (xunion->tag == 0) {
if (envelope_ptr->data != nullptr || envelope_ptr->num_bytes != 0 ||
envelope_ptr->num_handles != 0) {
visitor.OnError("xunion with zero as ordinal must be empty");
FIDL_STATUS_GUARD(Status::kConstraintViolationError);
}
if (!frame->xunion_state.nullable) {
visitor.OnError("non-nullable xunion is absent");
FIDL_STATUS_GUARD(Status::kConstraintViolationError);
}
Pop();
continue;
}
// Find coding table corresponding to the ordinal via linear search
const FidlXUnionField* known_field = nullptr;
for (size_t i = 0; i < frame->xunion_state.field_count; i++) {
const auto field = frame->xunion_state.fields + i;
if (field->ordinal == xunion->tag) {
known_field = field;
break;
}
}
if (!known_field && frame->xunion_state.strictness == kFidlStrictness_Strict) {
visitor.OnError("strict xunion has unknown ordinal");
FIDL_STATUS_GUARD(Status::kConstraintViolationError);
}
// Make sure we don't process a malformed envelope
const fidl_type_t* payload_type = known_field ? known_field->type : nullptr;
auto status = visitor.EnterEnvelope(envelope_pos, envelope_ptr, payload_type);
FIDL_STATUS_GUARD(status);
frame->xunion_state.inside_envelope = true;
// Skip empty envelopes
if (envelope_ptr->data == nullptr) {
if (xunion->tag != 0) {
visitor.OnError("empty xunion must have zero as ordinal");
FIDL_STATUS_GUARD_NO_POP(Status::kConstraintViolationError);
}
continue;
}
uint32_t num_bytes =
payload_type != nullptr ? TypeSize(payload_type) : envelope_ptr->num_bytes;
Position position;
status =
visitor.VisitPointer(frame->position, VisitorImpl::PointeeType::kOther,
&const_cast<Ptr<void>&>(envelope_ptr->data), num_bytes, &position);
FIDL_STATUS_GUARD_NO_POP(status);
if (payload_type != nullptr && !IsPrimitive(payload_type)) {
if (!Push(Frame(payload_type, position))) {
visitor.OnError("recursion depth exceeded processing xunion");
FIDL_STATUS_GUARD_NO_POP(Status::kConstraintViolationError);
}
}
continue;
}
case Frame::kStateArray: {
const uint32_t element_offset = frame->NextArrayOffset();
if (element_offset == frame->array_state.array_size) {
Pop();
continue;
}
const fidl_type_t* element_type = frame->array_state.element;
if (element_type) {
Position position = frame->position + element_offset;
if (!Push(Frame(element_type, position))) {
visitor.OnError("recursion depth exceeded processing array");
FIDL_STATUS_GUARD(Status::kConstraintViolationError);
}
} else {
// If there is no element type pointer, the array contents
// do not need extra processing, but the array coding table
// is present to provide size information when linearizing
// envelopes. Just continue.
Pop();
}
continue;
}
case Frame::kStateString: {
auto string_ptr = PtrTo<fidl_string_t>(frame->position);
// The MSB of the size is reserved for an ownership bit used by fidl::StringView.
// fidl::StringView's count() would be ideally used in place of the direct bit masking
// here, but because of build dependencies this is currently not possible.
const uint64_t size = string_ptr->size & ~kVectorOwnershipMask;
auto status = visitor.VisitVectorOrStringCount(&string_ptr->size);
FIDL_STATUS_GUARD(status);
if (string_ptr->data == nullptr) {
if (!frame->string_state.nullable &&
!VisitorImpl::kAllowNonNullableCollectionsToBeAbsent) {
visitor.OnError("non-nullable string is absent");
FIDL_STATUS_GUARD(Status::kConstraintViolationError);
}
if (size == 0) {
if (frame->string_state.nullable ||
!VisitorImpl::kAllowNonNullableCollectionsToBeAbsent) {
Pop();
continue;
}
} else {
visitor.OnError("string is absent but length is not zero");
FIDL_STATUS_GUARD(Status::kConstraintViolationError);
}
}
uint64_t bound = frame->string_state.max_size;
if (size > std::numeric_limits<uint32_t>::max()) {
visitor.OnError("string size overflows 32 bits");
FIDL_STATUS_GUARD(Status::kMemoryError);
}
if (size > bound) {
visitor.OnError("message tried to access too large of a bounded string");
FIDL_STATUS_GUARD(Status::kConstraintViolationError);
}
Position position;
status = visitor.VisitPointer(
position, VisitorImpl::PointeeType::kVectorOrString,
&reinterpret_cast<Ptr<void>&>(const_cast<Ptr<char>&>(string_ptr->data)),
static_cast<uint32_t>(size), &position);
FIDL_STATUS_GUARD(status);
Pop();
continue;
}
case Frame::kStateHandle: {
auto handle_ptr = PtrTo<zx_handle_t>(frame->position);
if (*handle_ptr == ZX_HANDLE_INVALID) {
if (!frame->handle_state.nullable) {
visitor.OnError("message is missing a non-nullable handle");
FIDL_STATUS_GUARD(Status::kConstraintViolationError);
}
Pop();
continue;
}
auto status =
visitor.VisitHandle(frame->position, handle_ptr, frame->handle_state.handle_rights,
frame->handle_state.handle_subtype);
FIDL_STATUS_GUARD(status);
Pop();
continue;
}
case Frame::kStateVector: {
auto vector_ptr = PtrTo<fidl_vector_t>(frame->position);
// The MSB of the count is reserved for an ownership bit used by fidl::VectorView.
// fidl::VectorView's count() would be ideally used in place of the direct bit masking
// here, but because of build dependencies this is currently not possible.
const uint64_t count = vector_ptr->count & ~kVectorOwnershipMask;
auto status = visitor.VisitVectorOrStringCount(&vector_ptr->count);
FIDL_STATUS_GUARD(status);
if (vector_ptr->data == nullptr) {
if (!frame->vector_state.nullable &&
!VisitorImpl::kAllowNonNullableCollectionsToBeAbsent) {
visitor.OnError("non-nullable vector is absent");
FIDL_STATUS_GUARD(Status::kConstraintViolationError);
}
if (count == 0) {
if (frame->vector_state.nullable ||
!VisitorImpl::kAllowNonNullableCollectionsToBeAbsent) {
Pop();
continue;
}
} else {
visitor.OnError("absent vector of non-zero elements");
FIDL_STATUS_GUARD(Status::kConstraintViolationError);
}
}
if (count > frame->vector_state.max_count) {
visitor.OnError("message tried to access too large of a bounded vector");
FIDL_STATUS_GUARD(Status::kConstraintViolationError);
}
uint32_t size;
if (mul_overflow(count, frame->vector_state.element_size, &size)) {
visitor.OnError("integer overflow calculating vector size");
return;
}
status = visitor.VisitPointer(frame->position, VisitorImpl::PointeeType::kVectorOrString,
&vector_ptr->data, size, &frame->position);
FIDL_STATUS_GUARD(status);
if (frame->vector_state.element) {
// Continue by visiting the vector elements as an array.
*frame = Frame(frame->vector_state.element, size, frame->vector_state.element_size,
frame->position);
} else {
// If there is no element type pointer, there is
// nothing to process in the vector secondary
// payload. So just continue.
Pop();
}
continue;
}
case Frame::kStatePrimitive: {
// Nothing to do for primitives.
Pop();
continue;
}
case Frame::kStateDone: {
return;
}
}
}
}
} // namespace internal
// Walks the FIDL message, calling hooks in the concrete VisitorImpl.
//
// |visitor| is an implementation of the fidl::Visitor interface.
// |type| is the coding table for the FIDL type. It cannot be null.
// |start| is the starting point for the walk.
template <typename VisitorImpl>
void Walk(VisitorImpl& visitor, const fidl_type_t* type,
typename VisitorImpl::StartingPoint start) {
internal::Walker<VisitorImpl> walker(type, start);
walker.Walk(visitor);
}
// Infer the size of the primary object, from the coding table in |type|.
// Ensures that the primary object is of one of the expected types.
//
// An error is returned if:
// - |type| is null
// - The primary object is neither a struct nor a table.
zx_status_t PrimaryObjectSize(const fidl_type_t* type, size_t* out_size, const char** out_error);
// Calculate the offset of the first out-of-line object, from the coding table in |type|.
// Ensures that the primary object is of one of the expected types, and the offset falls within the
// |buffer_size| constraints.
//
// An error is returned if:
// - |type| is null
// - The primary object is neither a struct nor a table.
// - The offset overflows, or is larger than |buffer_size|.
zx_status_t StartingOutOfLineOffset(const fidl_type_t* type, uint32_t buffer_size,
uint32_t* out_first_out_of_line, const char** out_error);
} // namespace fidl
#endif // LIB_FIDL_WALKER_H_