[fidl] Implement decoder using visitor.
Migrates the FIDL decoder from buffer_walker to visitor.
There should be no external behavioral change.
+apang: I grafted your xunion implementation.
TEST: CQ, and tryjobs in higher layers
Change-Id: Id3e3a2c21acfaaea68eb4af7f09ff35c5a0adfd9
diff --git a/system/ulib/fidl/decoding.cpp b/system/ulib/fidl/decoding.cpp
index 3066778..732e779 100644
--- a/system/ulib/fidl/decoding.cpp
+++ b/system/ulib/fidl/decoding.cpp
@@ -16,86 +16,223 @@
#include <zircon/syscalls.h>
#endif
-#include "buffer_walker.h"
+#include "visitor.h"
+#include "walker.h"
// TODO(kulakowski) Design zx_status_t error values.
namespace {
-class FidlDecoder final : public fidl::internal::BufferWalker<FidlDecoder, true, false> {
- typedef fidl::internal::BufferWalker<FidlDecoder, true, false> Super;
+struct Position;
+struct StartingPoint {
+ uint8_t* const addr;
+ Position ToPosition() const;
+};
+
+struct Position {
+ uint32_t offset;
+ Position operator+(uint32_t size) const {
+ return Position { offset + size };
+ }
+ Position& operator+=(uint32_t size) {
+ offset += size;
+ return *this;
+ }
+ template <typename T>
+ constexpr T* Get(StartingPoint start) const {
+ return reinterpret_cast<T*>(start.addr + offset);
+ }
+};
+
+Position StartingPoint::ToPosition() const {
+ return Position { 0 };
+}
+
+constexpr uintptr_t kAllocPresenceMarker = FIDL_ALLOC_PRESENT;
+constexpr uintptr_t kAllocAbsenceMarker = FIDL_ALLOC_ABSENT;
+
+class FidlDecoder final : public fidl::Visitor<
+ fidl::MutatingVisitorTrait, StartingPoint, Position> {
public:
- FidlDecoder(const fidl_type_t* type, void* bytes, uint32_t num_bytes,
- const zx_handle_t* handles, uint32_t num_handles, const char** out_error_msg)
- : Super(type), bytes_(static_cast<uint8_t*>(bytes)), num_bytes_(num_bytes),
- handles_(handles), num_handles_(num_handles), out_error_msg_(out_error_msg) {}
+ FidlDecoder(void* bytes, uint32_t num_bytes, const zx_handle_t* handles, uint32_t num_handles,
+ uint32_t next_out_of_line, const char** out_error_msg)
+ : bytes_(static_cast<uint8_t*>(bytes)), num_bytes_(num_bytes),
+ handles_(handles), num_handles_(num_handles), next_out_of_line_(next_out_of_line),
+ out_error_msg_(out_error_msg) {}
- void Walk() {
- if (handles_ == nullptr && num_handles_ != 0u) {
- SetError("Cannot provide non-zero handle count and null handle pointer");
- return;
+ using StartingPoint = StartingPoint;
+
+ using Position = Position;
+
+ static constexpr bool kContinueAfterConstraintViolation = true;
+
+ Status VisitPointer(Position ptr_position,
+ ObjectPointerPointer object_ptr_ptr,
+ uint32_t inline_size,
+ Position* out_position) {
+ if (reinterpret_cast<uintptr_t>(*object_ptr_ptr) != kAllocPresenceMarker) {
+ SetError("decoder encountered invalid pointer");
+ return Status::kConstraintViolationError;
}
- Super::Walk();
- if (status_ == ZX_OK && handle_idx() != num_handles()) {
- SetError("message did not contain the specified number of handles");
+ // We have to manually maintain alignment here. For example, a pointer
+ // to a struct that is 4 bytes still needs to advance the next
+ // out-of-line offset by 8 to maintain the aligned-to-FIDL_ALIGNMENT
+ // property.
+ static constexpr uint32_t mask = FIDL_ALIGNMENT - 1;
+ uint32_t new_offset = next_out_of_line_;
+ if (add_overflow(new_offset, inline_size, &new_offset)
+ || add_overflow(new_offset, mask, &new_offset)) {
+ SetError("overflow updating out-of-line offset");
+ return Status::kMemoryError;
}
+ new_offset &= ~mask;
+
+ if (new_offset > num_bytes_) {
+ SetError("message tried to decode more than provided number of bytes");
+ return Status::kMemoryError;
+ }
+
+ *out_position = Position { next_out_of_line_ };
+ *object_ptr_ptr = reinterpret_cast<void*>(&bytes_[next_out_of_line_]);
+
+ next_out_of_line_ = new_offset;
+ return Status::kSuccess;
}
- uint8_t* bytes() const { return bytes_; }
- uint32_t num_bytes() const { return num_bytes_; }
- uint32_t num_handles() const { return num_handles_; }
-
- bool ValidateOutOfLineStorageClaim(const void* a, const void* b) {
- return true;
+ Status VisitHandle(Position handle_position, HandlePointer handle) {
+ if (*handle != FIDL_HANDLE_PRESENT) {
+ SetError("message tried to decode a garbage handle");
+ return Status::kConstraintViolationError;
+ }
+ if (handle_idx_ == num_handles_) {
+ SetError("message decoded too many handles");
+ return Status::kConstraintViolationError;
+ }
+ if (handles_ == nullptr) {
+ SetError("decoder noticed a handle is present but the handle table is empty");
+ *handle = ZX_HANDLE_INVALID;
+ return Status::kConstraintViolationError;
+ }
+ if (handles_[handle_idx_] == ZX_HANDLE_INVALID) {
+ SetError("invalid handle detected in handle table");
+ return Status::kConstraintViolationError;
+ }
+ *handle = handles_[handle_idx_];
+ handle_idx_++;
+ return Status::kSuccess;
}
- void UnclaimedHandle(zx_handle_t* out_handle) {}
- void ClaimedHandle(zx_handle_t* out_handle, uint32_t idx) {
- if (out_handle != nullptr) {
- *out_handle = handles_[idx];
+ Status EnterEnvelope(Position envelope_position,
+ EnvelopePointer envelope,
+ const fidl_type_t* payload_type) {
+ if (envelope->presence == kAllocAbsenceMarker &&
+ (envelope->num_bytes != 0 || envelope->num_handles != 0)) {
+ SetError("Envelope has absent data pointer, yet has data and/or handles");
+ return Status::kConstraintViolationError;
+ }
+ if (envelope->presence != kAllocAbsenceMarker && envelope->num_bytes == 0) {
+ SetError("Envelope has present data pointer, but zero byte count");
+ return Status::kConstraintViolationError;
+ }
+ uint32_t expected_handle_count;
+ if (add_overflow(handle_idx_, envelope->num_handles, &expected_handle_count) ||
+ expected_handle_count > num_handles_) {
+ SetError("Envelope has more handles than expected");
+ return Status::kConstraintViolationError;
+ }
+ // Remember the current watermark of bytes and handles, so that after processing
+ // the envelope, we can validate that the claimed num_bytes/num_handles matches the reality.
+ if (!Push(next_out_of_line_, handle_idx_)) {
+ SetError("Overly deep nested envelopes");
+ return Status::kConstraintViolationError;
+ }
+ // If we do not have the coding table for this payload,
+ // treat it as unknown and close its contained handles
+ if (envelope->presence != kAllocAbsenceMarker && payload_type == nullptr &&
+ envelope->num_handles > 0) {
#ifdef __Fuchsia__
- } else {
- // Return value intentionally ignored: this is best-effort cleanup.
- zx_handle_close(handles_[idx]);
+ zx_handle_close_many(&handles_[handle_idx_], envelope->num_handles);
#endif
+ handle_idx_ += num_handles_;
}
+ return Status::kSuccess;
}
- PointerState GetPointerState(const void* ptr) const {
- return static_cast<PointerState>(*static_cast<const uintptr_t*>(ptr));
- }
- HandleState GetHandleState(zx_handle_t p) const {
- return static_cast<HandleState>(p);
+ Status LeaveEnvelope(Position envelope_position, EnvelopePointer envelope) {
+ // Now that the envelope has been consumed, check the correctness of the envelope header.
+ auto& starting_state = Pop();
+ uint32_t num_bytes = next_out_of_line_ - starting_state.bytes_so_far;
+ uint32_t num_handles = handle_idx_ - starting_state.handles_so_far;
+ if (envelope->num_bytes != num_bytes) {
+ SetError("Envelope num_bytes was mis-sized");
+ return Status::kConstraintViolationError;
+ }
+ if (envelope->num_handles != num_handles) {
+ SetError("Envelope num_handles was mis-sized");
+ return Status::kConstraintViolationError;
+ }
+ return Status::kSuccess;
}
- template <class T>
- void UpdatePointer(T* p, T v) {
- *p = v;
- }
-
- void SetError(const char* error_msg) {
- status_ = ZX_ERR_INVALID_ARGS;
- if (out_error_msg_ != nullptr) {
- *out_error_msg_ = error_msg;
- }
-#ifdef __Fuchsia__
- if (handles_) {
- // Return value intentionally ignored: this is best-effort cleanup.
- zx_handle_close_many(handles_, num_handles());
- }
-#endif
+ void OnError(const char* error) {
+ SetError(error);
}
zx_status_t status() const { return status_; }
+ bool DidConsumeAllBytes() const { return next_out_of_line_ == num_bytes_; }
+
+ bool DidConsumeAllHandles() const { return handle_idx_ == num_handles_; }
+
private:
+ void SetError(const char* error) {
+ if (status_ != ZX_OK) {
+ return;
+ }
+ status_ = ZX_ERR_INVALID_ARGS;
+ if (!out_error_msg_) {
+ return;
+ }
+ *out_error_msg_ = error;
+ }
+
+ struct EnvelopeState {
+ uint32_t bytes_so_far;
+ uint32_t handles_so_far;
+ };
+
+ const EnvelopeState& Pop() {
+ ZX_ASSERT(envelope_depth_ != 0);
+ envelope_depth_--;
+ return envelope_states_[envelope_depth_];
+ }
+
+ bool Push(uint32_t num_bytes, uint32_t num_handles) {
+ if (envelope_depth_ == FIDL_RECURSION_DEPTH) {
+ return false;
+ }
+ envelope_states_[envelope_depth_] = (EnvelopeState) {
+ .bytes_so_far = num_bytes,
+ .handles_so_far = num_handles,
+ };
+ envelope_depth_++;
+ return true;
+ }
+
+ // Message state passed in to the constructor.
uint8_t* const bytes_;
const uint32_t num_bytes_;
const zx_handle_t* const handles_;
const uint32_t num_handles_;
+ uint32_t next_out_of_line_;
const char** const out_error_msg_;
+
+ // Decoder state
zx_status_t status_ = ZX_OK;
+ uint32_t handle_idx_ = 0;
+ uint32_t envelope_depth_ = 0;
+ EnvelopeState envelope_states_[FIDL_RECURSION_DEPTH];
};
} // namespace
@@ -103,8 +240,61 @@
zx_status_t fidl_decode(const fidl_type_t* type, void* bytes, uint32_t num_bytes,
const zx_handle_t* handles, uint32_t num_handles,
const char** out_error_msg) {
- FidlDecoder decoder(type, bytes, num_bytes, handles, num_handles, out_error_msg);
- decoder.Walk();
+ auto set_error = [&out_error_msg] (const char* msg) {
+ if (out_error_msg) *out_error_msg = msg;
+ };
+ if (bytes == nullptr) {
+ set_error("Cannot decode null bytes");
+ return ZX_ERR_INVALID_ARGS;
+ }
+ if (handles == nullptr && num_handles != 0) {
+ set_error("Cannot provide non-zero handle count and null handle pointer");
+ return ZX_ERR_INVALID_ARGS;
+ }
+ if (type == nullptr) {
+ set_error("Cannot decode a null fidl type");
+ return ZX_ERR_INVALID_ARGS;
+ }
+
+ size_t primary_size;
+ zx_status_t status;
+ if ((status = fidl::GetPrimaryObjectSize(type, &primary_size, out_error_msg)) != ZX_OK) {
+ return status;
+ }
+ if (primary_size > num_bytes) {
+ set_error("Buffer is too small for first inline object");
+ return ZX_ERR_INVALID_ARGS;
+ }
+ uint64_t next_out_of_line = fidl::FidlAlign(static_cast<uint32_t>(primary_size));
+ if (next_out_of_line > std::numeric_limits<uint32_t>::max()) {
+ set_error("Out of line starting offset overflows");
+ return ZX_ERR_INVALID_ARGS;
+ }
+
+ FidlDecoder decoder(bytes, num_bytes, handles, num_handles,
+ static_cast<uint32_t>(next_out_of_line), out_error_msg);
+ fidl::Walk(decoder,
+ type,
+ StartingPoint { reinterpret_cast<uint8_t*>(bytes) });
+
+ if (decoder.status() == ZX_OK) {
+ if (!decoder.DidConsumeAllBytes()) {
+ set_error("message did not decode all provided bytes");
+ return ZX_ERR_INVALID_ARGS;
+ }
+ if (!decoder.DidConsumeAllHandles()) {
+ set_error("message did not decode all provided handles");
+ return ZX_ERR_INVALID_ARGS;
+ }
+ } else {
+#ifdef __Fuchsia__
+ if (handles) {
+ // Return value intentionally ignored. This is best-effort cleanup.
+ (void)zx_handle_close_many(handles, num_handles);
+ }
+#endif
+ }
+
return decoder.status();
}
diff --git a/system/ulib/fidl/encoding.cpp b/system/ulib/fidl/encoding.cpp
index 54a41cb..f94a65c 100644
--- a/system/ulib/fidl/encoding.cpp
+++ b/system/ulib/fidl/encoding.cpp
@@ -69,10 +69,6 @@
ObjectPointerPointer object_ptr_ptr,
uint32_t inline_size,
Position* out_position) {
- if (inline_size > std::numeric_limits<uint32_t>::max()) {
- SetError("inline size is too big");
- return Status::kMemoryError;
- }
// Make sure objects in secondary storage are contiguous
if (!ClaimOutOfLineStorage(static_cast<uint32_t>(inline_size),
*object_ptr_ptr,
diff --git a/system/ulib/fidl/visitor.h b/system/ulib/fidl/visitor.h
index 45b82a2..ea3f46a 100644
--- a/system/ulib/fidl/visitor.h
+++ b/system/ulib/fidl/visitor.h
@@ -99,7 +99,7 @@
private:
// Visit an indirection, which can be the data pointer of a string/vector, the data pointer
// of an envelope from a table, the pointer in a nullable type, etc.
- // Only called when the pointer is non-null.
+ // Only called when the pointer is present.
//
// |ptr_position| Position of the pointer.
// |object_ptr_ptr| Pointer to the data pointer, obtained from |ptr_position.Get(start)|.
@@ -116,7 +116,7 @@
}
// Visit a handle. The handle pointer will be mutable if the visitor is mutating.
- // Only called when the handle is valid.
+ // Only called when the handle is present.
// The handle pointer is derived from |handle_position.Get(start)|.
Status VisitHandle(Position handle_position, HandlePointer handle_ptr) {
return Status::kSuccess;
@@ -145,6 +145,7 @@
// Decoder/encoder should validate that the expected number of bytes/handles have been consumed.
// Linearizer can use this opportunity to set the appropriate num_bytes/num_handles value.
// It is possible to have nested enter/leave envelope pairs.
+ // There will be a matching call to |LeaveEnvelope| for every successful |EnterEnvelope|.
//
// |envelope_position| Position of the envelope header.
// |envelope_ptr| Pointer to the envelope header that was just processed.
diff --git a/system/ulib/fidl/walker.h b/system/ulib/fidl/walker.h
index 68d2066..1b1cec0 100644
--- a/system/ulib/fidl/walker.h
+++ b/system/ulib/fidl/walker.h
@@ -140,6 +140,7 @@
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;
break;
case fidl::kFidlTypeXUnionPointer:
state = kStateXUnionPointer;
@@ -203,6 +204,7 @@
// 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;
}
Frame(const fidl_type_t* element, uint32_t array_size, uint32_t element_size,
@@ -274,8 +276,8 @@
const fidl::FidlCodedStruct* struct_type;
} struct_pointer_state;
struct {
- // Sparse coding table array for fields;
- // advance the field pointer on every matched ordinal to save space
+ // Sparse (but monotonically increasing) coding table array for fields;
+ // advance the |field| pointer on every matched ordinal to save space
const fidl::FidlTableField* field;
// Number of unseen fields in the coding table
uint32_t remaining_fields;
@@ -305,7 +307,11 @@
} union_pointer_state;
struct {
const fidl::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;
} xunion_state;
struct {
const fidl::FidlCodedXUnion* xunion_type;
@@ -372,13 +378,13 @@
// 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(status) \
+#define FIDL_STATUS_GUARD_IMPL(status, pop) \
switch ((status)) { \
case Status::kSuccess: \
break; \
case Status::kConstraintViolationError: \
if (VisitorImpl::kContinueAfterConstraintViolation) { \
- Pop(); \
+ if ((pop)) { Pop(); } \
continue; \
} else { \
return; \
@@ -387,6 +393,9 @@
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();
@@ -397,7 +406,7 @@
Pop();
continue;
}
- const fidl::FidlField& field = frame->struct_state.fields[field_index];
+ const fidl::FidlStructField& field = frame->struct_state.fields[field_index];
const fidl_type_t* field_type = field.type;
Position field_position = frame->position + field.offset;
if (!Push(Frame(field_type, field_position))) {
@@ -486,19 +495,19 @@
if (envelope_ptr->data == nullptr) {
continue;
}
- if (known_field != nullptr) {
- const fidl_type_t* field_type = known_field->type;
+ if (payload_type != nullptr) {
Position position;
auto status =
visitor.VisitPointer(frame->position,
// casting since |envelope_ptr->data| is always void*
&const_cast<Ptr<void>&>(envelope_ptr->data),
- TypeSize(field_type),
+ TypeSize(payload_type),
&position);
- FIDL_STATUS_GUARD(status);
- if (!Push(Frame(field_type, position))) {
+ // Do not pop the table frame, to guarantee calling |LeaveEnvelope|
+ FIDL_STATUS_GUARD_NO_POP(status);
+ if (!Push(Frame(payload_type, position))) {
visitor.OnError("recursion depth exceeded processing table");
- FIDL_STATUS_GUARD(Status::kConstraintViolationError);
+ FIDL_STATUS_GUARD_NO_POP(Status::kConstraintViolationError);
}
} else {
// No coding table for this ordinal.
@@ -509,7 +518,7 @@
&const_cast<Ptr<void>&>(envelope_ptr->data),
envelope_ptr->num_bytes,
&position);
- FIDL_STATUS_GUARD(status);
+ FIDL_STATUS_GUARD_NO_POP(status);
}
continue;
}
@@ -557,11 +566,76 @@
continue;
}
case Frame::kStateXUnion: {
- assert(false && "xunions not implemented yet");
+ 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;
+ }
+ if (xunion->padding != 0) {
+ visitor.OnError("xunion padding after discriminant are non-zero");
+ FIDL_STATUS_GUARD(Status::kConstraintViolationError);
+ }
+ // 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;
+ }
+ }
+ // 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) {
+ continue;
+ }
+ if (payload_type != nullptr) {
+ Position position;
+ auto status =
+ visitor.VisitPointer(frame->position,
+ &const_cast<Ptr<void>&>(envelope_ptr->data),
+ TypeSize(payload_type),
+ &position);
+ FIDL_STATUS_GUARD_NO_POP(status);
+ if (!Push(Frame(payload_type, position))) {
+ visitor.OnError("recursion depth exceeded processing xunion");
+ FIDL_STATUS_GUARD_NO_POP(Status::kConstraintViolationError);
+ }
+ } else {
+ // No coding table for this ordinal.
+ // Still patch pointers, but cannot recurse into the payload.
+ Position position;
+ auto status =
+ visitor.VisitPointer(frame->position,
+ &const_cast<Ptr<void>&>(envelope_ptr->data),
+ envelope_ptr->num_bytes,
+ &position);
+ FIDL_STATUS_GUARD_NO_POP(status);
+ }
continue;
}
case Frame::kStateXUnionPointer: {
- assert(false && "xunion pointers not implemented yet");
+ if (*PtrTo<fidl_xunion_t*>(frame->position) == nullptr) {
+ Pop();
+ continue;
+ }
+ auto status = visitor.VisitPointer(frame->position,
+ PtrTo<void*>(frame->position),
+ static_cast<uint32_t>(sizeof(fidl_xunion_t)),
+ &frame->position);
+ FIDL_STATUS_GUARD(status);
+ const fidl::FidlCodedXUnion* coded_xunion = frame->xunion_pointer_state.xunion_type;
+ *frame = Frame(coded_xunion, frame->position);
continue;
}
case Frame::kStateArray: {