[fidl] Enforce zero-ordinal xunion invariants.

For extensible unions, zero is an allowed ordinal, and indicates that
the union is empty. It is a validation error for a xunion to be zero and
non-empty, or non-zero and empty.

This patch adds tests for that and updates the walker to enforce that.

TEST: /boot/test/sys/fidl-test
Change-Id: I2ece2b1ca6a6821cfe1d73cf85da902dd5d51180
diff --git a/system/ulib/fidl/walker.h b/system/ulib/fidl/walker.h
index bc1cab2..188fce5 100644
--- a/system/ulib/fidl/walker.h
+++ b/system/ulib/fidl/walker.h
@@ -2,7 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#pragma once
+#ifndef ZIRCON_SYSTEM_ULIB_FIDL_WALKER_H_
+#define ZIRCON_SYSTEM_ULIB_FIDL_WALKER_H_
 
 #include <cstdint>
 #include <cstdlib>
@@ -39,27 +40,27 @@
 
 constexpr uint32_t TypeSize(const fidl_type_t* type) {
     switch (type->type_tag) {
-        case fidl::kFidlTypeStructPointer:
-        case fidl::kFidlTypeTablePointer:
-        case fidl::kFidlTypeUnionPointer:
-        case fidl::kFidlTypeXUnionPointer:
-            return sizeof(uint64_t);
-        case fidl::kFidlTypeHandle:
-            return sizeof(zx_handle_t);
-        case fidl::kFidlTypeStruct:
-            return type->coded_struct.size;
-        case fidl::kFidlTypeTable:
-            return sizeof(fidl_vector_t);
-        case fidl::kFidlTypeUnion:
-            return type->coded_union.size;
-        case fidl::kFidlTypeXUnion:
-            return sizeof(fidl_xunion_t);
-        case fidl::kFidlTypeString:
-            return sizeof(fidl_string_t);
-        case fidl::kFidlTypeArray:
-            return type->coded_array.array_size;
-        case fidl::kFidlTypeVector:
-            return sizeof(fidl_vector_t);
+    case fidl::kFidlTypeStructPointer:
+    case fidl::kFidlTypeTablePointer:
+    case fidl::kFidlTypeUnionPointer:
+    case fidl::kFidlTypeXUnionPointer:
+        return sizeof(uint64_t);
+    case fidl::kFidlTypeHandle:
+        return sizeof(zx_handle_t);
+    case fidl::kFidlTypeStruct:
+        return type->coded_struct.size;
+    case fidl::kFidlTypeTable:
+        return sizeof(fidl_vector_t);
+    case fidl::kFidlTypeUnion:
+        return type->coded_union.size;
+    case fidl::kFidlTypeXUnion:
+        return sizeof(fidl_xunion_t);
+    case fidl::kFidlTypeString:
+        return sizeof(fidl_string_t);
+    case fidl::kFidlTypeArray:
+        return type->coded_array.array_size;
+    case fidl::kFidlTypeVector:
+        return sizeof(fidl_vector_t);
     }
     __builtin_unreachable();
 }
@@ -83,7 +84,8 @@
     static_assert(CheckVisitorInterface<VisitorSuper, VisitorImpl>(), "");
 
 public:
-    Walker(const fidl_type_t* type, StartingPoint start) : type_(type), start_(start) {}
+    Walker(const fidl_type_t* type, StartingPoint start)
+        : type_(type), start_(start) {}
 
     // Walk the object/buffer located at |start_|.
     void Walk(VisitorImpl& visitor);
@@ -105,70 +107,70 @@
         Frame(const fidl_type_t* fidl_type, Position position)
             : position(position) {
             switch (fidl_type->type_tag) {
-                case fidl::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;
-                    break;
-                case fidl::kFidlTypeStructPointer:
-                    state = kStateStructPointer;
-                    struct_pointer_state.struct_type = fidl_type->coded_struct_pointer.struct_type;
-                    break;
-                case fidl::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 fidl::kFidlTypeTablePointer:
-                    state = kStateTablePointer;
-                    table_pointer_state.table_type = fidl_type->coded_table_pointer.table_type;
-                    break;
-                case fidl::kFidlTypeUnion:
-                    state = kStateUnion;
-                    union_state.types = fidl_type->coded_union.types;
-                    union_state.type_count = fidl_type->coded_union.type_count;
-                    union_state.data_offset = fidl_type->coded_union.data_offset;
-                    break;
-                case fidl::kFidlTypeUnionPointer:
-                    state = kStateUnionPointer;
-                    union_pointer_state.union_type = fidl_type->coded_union_pointer.union_type;
-                    break;
-                case fidl::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;
-                    break;
-                case fidl::kFidlTypeXUnionPointer:
-                    state = kStateXUnionPointer;
-                    xunion_pointer_state.xunion_type = fidl_type->coded_xunion_pointer.xunion_type;
-                    break;
-                case fidl::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 fidl::kFidlTypeString:
-                    state = kStateString;
-                    string_state.max_size = fidl_type->coded_string.max_size;
-                    string_state.nullable = fidl_type->coded_string.nullable;
-                    break;
-                case fidl::kFidlTypeHandle:
-                    state = kStateHandle;
-                    handle_state.nullable = fidl_type->coded_handle.nullable;
-                    break;
-                case fidl::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 fidl::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;
+                break;
+            case fidl::kFidlTypeStructPointer:
+                state = kStateStructPointer;
+                struct_pointer_state.struct_type = fidl_type->coded_struct_pointer.struct_type;
+                break;
+            case fidl::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 fidl::kFidlTypeTablePointer:
+                state = kStateTablePointer;
+                table_pointer_state.table_type = fidl_type->coded_table_pointer.table_type;
+                break;
+            case fidl::kFidlTypeUnion:
+                state = kStateUnion;
+                union_state.types = fidl_type->coded_union.types;
+                union_state.type_count = fidl_type->coded_union.type_count;
+                union_state.data_offset = fidl_type->coded_union.data_offset;
+                break;
+            case fidl::kFidlTypeUnionPointer:
+                state = kStateUnionPointer;
+                union_pointer_state.union_type = fidl_type->coded_union_pointer.union_type;
+                break;
+            case fidl::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;
+                break;
+            case fidl::kFidlTypeXUnionPointer:
+                state = kStateXUnionPointer;
+                xunion_pointer_state.xunion_type = fidl_type->coded_xunion_pointer.xunion_type;
+                break;
+            case fidl::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 fidl::kFidlTypeString:
+                state = kStateString;
+                string_state.max_size = fidl_type->coded_string.max_size;
+                string_state.nullable = fidl_type->coded_string.nullable;
+                break;
+            case fidl::kFidlTypeHandle:
+                state = kStateHandle;
+                handle_state.nullable = fidl_type->coded_handle.nullable;
+                break;
+            case fidl::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;
             }
         }
 
@@ -378,20 +380,22 @@
 
 // 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_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)
@@ -400,351 +404,365 @@
         Frame* frame = Peek();
 
         switch (frame->state) {
-            case Frame::kStateStruct: {
-                const uint32_t field_index = frame->NextStructField();
-                if (field_index == frame->struct_state.field_count) {
-                    Pop();
-                    continue;
-                }
-                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))) {
-                    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,
-                                                   PtrTo<Ptr<void>>(frame->position),
-                                                   frame->struct_pointer_state.struct_type->size,
-                                                   &frame->position);
-                FIDL_STATUS_GUARD(status);
-                const fidl::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) {
-                        visitor.OnError("Table data cannot be absent");
-                        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,
-                                                       &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 fidl::FidlTableField* known_field = nullptr;
-                if (table_frame.remaining_fields > 0) {
-                    const fidl::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;
-                }
-                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(payload_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_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::kStateTablePointer: {
-                if (*PtrTo<Ptr<fidl_vector_t>>(frame->position) == nullptr) {
-                    Pop();
-                    continue;
-                }
-                auto status = visitor.VisitPointer(frame->position,
-                                                   PtrTo<Ptr<void>>(frame->position),
-                                                   static_cast<uint32_t>(sizeof(fidl_vector_t)),
-                                                   &frame->position);
-                FIDL_STATUS_GUARD(status);
-                const fidl::FidlCodedTable* coded_table = frame->table_pointer_state.table_type;
-                *frame = Frame(coded_table, frame->position);
-                continue;
-            }
-            case Frame::kStateUnion: {
-                auto union_tag = *PtrTo<fidl_union_tag_t>(frame->position);
-                if (union_tag >= frame->union_state.type_count) {
-                    visitor.OnError("Bad union discriminant");
-                    FIDL_STATUS_GUARD(Status::kConstraintViolationError);
-                }
-                const fidl_type_t* member = frame->union_state.types[union_tag];
-                if (!member) {
-                    Pop();
-                    continue;
-                }
-                frame->position += frame->union_state.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,
-                                                   PtrTo<Ptr<void>>(frame->position),
-                                                   frame->union_pointer_state.union_type->size,
-                                                   &frame->position);
-                FIDL_STATUS_GUARD(status);
-                const fidl::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;
-                }
-                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: {
-                if (*PtrTo<Ptr<fidl_xunion_t>>(frame->position) == nullptr) {
-                    Pop();
-                    continue;
-                }
-                auto status = visitor.VisitPointer(frame->position,
-                                                   PtrTo<Ptr<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: {
-                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;
-                Position position = frame->position + element_offset;
-                if (!Push(Frame(element_type, position))) {
-                    visitor.OnError("recursion depth exceeded processing array");
-                    FIDL_STATUS_GUARD(Status::kConstraintViolationError);
-                }
-                continue;
-            }
-            case Frame::kStateString: {
-                auto string_ptr = PtrTo<fidl_string_t>(frame->position);
-                if (string_ptr->data == nullptr) {
-                    if (!frame->string_state.nullable) {
-                        visitor.OnError("non-nullable string is absent");
-                        FIDL_STATUS_GUARD(Status::kConstraintViolationError);
-                    }
-                    if (string_ptr->size != 0) {
-                        visitor.OnError("string is absent but length is not zero");
-                        FIDL_STATUS_GUARD(Status::kConstraintViolationError);
-                    }
-                    Pop();
-                    continue;
-                }
-                uint64_t bound = frame->string_state.max_size;
-                uint64_t size = string_ptr->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;
-                auto status = visitor.VisitPointer(position,
-                                                   &reinterpret_cast<Ptr<void>&>(
-                                                       const_cast<Ptr<char>&>(
-                                                           string_ptr->data)),
-                                                   static_cast<uint32_t>(size),
-                                                   &position);
-                FIDL_STATUS_GUARD(status);
+        case Frame::kStateStruct: {
+            const uint32_t field_index = frame->NextStructField();
+            if (field_index == frame->struct_state.field_count) {
                 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);
-                FIDL_STATUS_GUARD(status);
+            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))) {
+                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;
             }
-            case Frame::kStateVector: {
-                auto vector_ptr = PtrTo<fidl_vector_t>(frame->position);
-                if (vector_ptr->data == nullptr) {
-                    if (!frame->vector_state.nullable) {
-                        visitor.OnError("non-nullable vector is absent");
-                        FIDL_STATUS_GUARD(Status::kConstraintViolationError);
-                    }
-                    if (vector_ptr->count != 0) {
-                        visitor.OnError("absent vector of non-zero elements");
-                        FIDL_STATUS_GUARD(Status::kConstraintViolationError);
-                    }
-                    Pop();
-                    continue;
-                }
-                if (vector_ptr->count > frame->vector_state.max_count) {
-                    visitor.OnError("message tried to access too large of a bounded vector");
+            auto status = visitor.VisitPointer(frame->position,
+                                               PtrTo<Ptr<void>>(frame->position),
+                                               frame->struct_pointer_state.struct_type->size,
+                                               &frame->position);
+            FIDL_STATUS_GUARD(status);
+            const fidl::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) {
+                    visitor.OnError("Table data cannot be absent");
                     FIDL_STATUS_GUARD(Status::kConstraintViolationError);
                 }
                 uint32_t size;
-                if (mul_overflow(vector_ptr->count, frame->vector_state.element_size, &size)) {
-                    visitor.OnError("integer overflow calculating vector 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,
-                                                   &vector_ptr->data,
+                                                   &envelope_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();
+                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 fidl::FidlTableField* known_field = nullptr;
+            if (table_frame.remaining_fields > 0) {
+                const fidl::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;
+            }
+            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(payload_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_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::kStateTablePointer: {
+            if (*PtrTo<Ptr<fidl_vector_t>>(frame->position) == nullptr) {
+                Pop();
+                continue;
+            }
+            auto status = visitor.VisitPointer(frame->position,
+                                               PtrTo<Ptr<void>>(frame->position),
+                                               static_cast<uint32_t>(sizeof(fidl_vector_t)),
+                                               &frame->position);
+            FIDL_STATUS_GUARD(status);
+            const fidl::FidlCodedTable* coded_table = frame->table_pointer_state.table_type;
+            *frame = Frame(coded_table, frame->position);
+            continue;
+        }
+        case Frame::kStateUnion: {
+            auto union_tag = *PtrTo<fidl_union_tag_t>(frame->position);
+            if (union_tag >= frame->union_state.type_count) {
+                visitor.OnError("Bad union discriminant");
+                FIDL_STATUS_GUARD(Status::kConstraintViolationError);
+            }
+            const fidl_type_t* member = frame->union_state.types[union_tag];
+            if (!member) {
+                Pop();
+                continue;
+            }
+            frame->position += frame->union_state.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,
+                                               PtrTo<Ptr<void>>(frame->position),
+                                               frame->union_pointer_state.union_type->size,
+                                               &frame->position);
+            FIDL_STATUS_GUARD(status);
+            const fidl::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;
+            }
+            if (xunion->padding != 0) {
+                visitor.OnError("xunion padding after discriminant are non-zero");
+                FIDL_STATUS_GUARD(Status::kConstraintViolationError);
+            }
+            // 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);
+                }
+                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;
+                }
+            }
+            // 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;
             }
-            case Frame::kStateDone: {
+            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: {
+            if (*PtrTo<Ptr<fidl_xunion_t>>(frame->position) == nullptr) {
+                Pop();
+                continue;
+            }
+            auto status = visitor.VisitPointer(frame->position,
+                                               PtrTo<Ptr<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: {
+            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;
+            Position position = frame->position + element_offset;
+            if (!Push(Frame(element_type, position))) {
+                visitor.OnError("recursion depth exceeded processing array");
+                FIDL_STATUS_GUARD(Status::kConstraintViolationError);
+            }
+            continue;
+        }
+        case Frame::kStateString: {
+            auto string_ptr = PtrTo<fidl_string_t>(frame->position);
+            if (string_ptr->data == nullptr) {
+                if (!frame->string_state.nullable) {
+                    visitor.OnError("non-nullable string is absent");
+                    FIDL_STATUS_GUARD(Status::kConstraintViolationError);
+                }
+                if (string_ptr->size != 0) {
+                    visitor.OnError("string is absent but length is not zero");
+                    FIDL_STATUS_GUARD(Status::kConstraintViolationError);
+                }
+                Pop();
+                continue;
+            }
+            uint64_t bound = frame->string_state.max_size;
+            uint64_t size = string_ptr->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;
+            auto status = visitor.VisitPointer(position,
+                                               &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);
+            FIDL_STATUS_GUARD(status);
+            Pop();
+            continue;
+        }
+        case Frame::kStateVector: {
+            auto vector_ptr = PtrTo<fidl_vector_t>(frame->position);
+            if (vector_ptr->data == nullptr) {
+                if (!frame->vector_state.nullable) {
+                    visitor.OnError("non-nullable vector is absent");
+                    FIDL_STATUS_GUARD(Status::kConstraintViolationError);
+                }
+                if (vector_ptr->count != 0) {
+                    visitor.OnError("absent vector of non-zero elements");
+                    FIDL_STATUS_GUARD(Status::kConstraintViolationError);
+                }
+                Pop();
+                continue;
+            }
+            if (vector_ptr->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(vector_ptr->count, frame->vector_state.element_size, &size)) {
+                visitor.OnError("integer overflow calculating vector size");
                 return;
             }
+            auto status = visitor.VisitPointer(frame->position,
+                                               &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::kStateDone: {
+            return;
+        }
         }
     }
 }
@@ -788,3 +806,5 @@
                                     const char** out_error);
 
 } // namespace fidl
+
+#endif  // ZIRCON_SYSTEM_ULIB_FIDL_WALKER_H_
diff --git a/system/utest/fidl/fidl/extra_messages.cpp b/system/utest/fidl/fidl/extra_messages.cpp
index db66efa..243e4d2 100644
--- a/system/utest/fidl/fidl/extra_messages.cpp
+++ b/system/utest/fidl/fidl/extra_messages.cpp
@@ -11,13 +11,15 @@
 extern const fidl_type_t fidl_test_coding_OlderSimpleTableTable;
 extern const fidl_type_t fidl_test_coding_NewerSimpleTableTable;
 extern const fidl_type_t fidl_test_coding_SimpleTableTable;
+extern const fidl_type_t fidl_test_coding_SampleXUnionTable;
+extern const fidl_type_t fidl_test_coding_SampleXUnionStructTable;
 
 
 static const fidl_type_t Vectoruint324294967295nonnullableTable = fidl_type_t(::fidl::FidlCodedVector(nullptr, 4294967295, 4, ::fidl::kNonnullable));
 
 extern const fidl_type_t fidl_test_coding_LinearizerTestVectorOfUint32RequestTable;
-static const ::fidl::FidlField fidl_test_coding_LinearizerTestVectorOfUint32RequestFields[] = {
-    ::fidl::FidlField(&Vectoruint324294967295nonnullableTable, 16)
+static const ::fidl::FidlStructField fidl_test_coding_LinearizerTestVectorOfUint32RequestFields[] = {
+    ::fidl::FidlStructField(&Vectoruint324294967295nonnullableTable, 16)
 };
 const fidl_type_t fidl_test_coding_LinearizerTestVectorOfUint32RequestTable = fidl_type_t(::fidl::FidlCodedStruct(fidl_test_coding_LinearizerTestVectorOfUint32RequestFields, 1, 32, "fidl.test.coding/LinearizerTestVectorOfUint32Request"));
 
@@ -26,8 +28,8 @@
 static const fidl_type_t VectorString4294967295nonnullable4294967295nonnullableTable = fidl_type_t(::fidl::FidlCodedVector(&String4294967295nonnullableTable, 4294967295, 16, ::fidl::kNonnullable));
 
 extern const fidl_type_t fidl_test_coding_LinearizerTestVectorOfStringRequestTable;
-static const ::fidl::FidlField fidl_test_coding_LinearizerTestVectorOfStringRequestFields[] = {
-    ::fidl::FidlField(&VectorString4294967295nonnullable4294967295nonnullableTable, 16)
+static const ::fidl::FidlStructField fidl_test_coding_LinearizerTestVectorOfStringRequestFields[] = {
+    ::fidl::FidlStructField(&VectorString4294967295nonnullable4294967295nonnullableTable, 16)
 };
 const fidl_type_t fidl_test_coding_LinearizerTestVectorOfStringRequestTable = fidl_type_t(::fidl::FidlCodedStruct(fidl_test_coding_LinearizerTestVectorOfStringRequestFields, 1, 32, "fidl.test.coding/LinearizerTestVectorOfStringRequest"));
 
@@ -35,15 +37,15 @@
 
 static const fidl_type_t VectorHandlehandlenonnullable2nonnullableTable = fidl_type_t(::fidl::FidlCodedVector(&HandlehandlenonnullableTable, 2, 4, ::fidl::kNonnullable));
 
-static const ::fidl::FidlField fidl_test_coding_StructWithManyHandlesFields[] = {
-    ::fidl::FidlField(&HandlehandlenonnullableTable, 0),
-    ::fidl::FidlField(&HandlehandlenonnullableTable, 4),
-    ::fidl::FidlField(&VectorHandlehandlenonnullable2nonnullableTable, 8)
+static const ::fidl::FidlStructField fidl_test_coding_StructWithManyHandlesFields[] = {
+    ::fidl::FidlStructField(&HandlehandlenonnullableTable, 0),
+    ::fidl::FidlStructField(&HandlehandlenonnullableTable, 4),
+    ::fidl::FidlStructField(&VectorHandlehandlenonnullable2nonnullableTable, 8)
 };
 const fidl_type_t fidl_test_coding_StructWithManyHandlesTable = fidl_type_t(::fidl::FidlCodedStruct(fidl_test_coding_StructWithManyHandlesFields, 3, 24, "fidl.test.coding/StructWithManyHandles"));
 
-static const ::fidl::FidlField fidl_test_coding_StructWithHandleFields[] = {
-    ::fidl::FidlField(&HandlehandlenonnullableTable, 0)
+static const ::fidl::FidlStructField fidl_test_coding_StructWithHandleFields[] = {
+    ::fidl::FidlStructField(&HandlehandlenonnullableTable, 0)
 };
 const fidl_type_t fidl_test_coding_StructWithHandleTable = fidl_type_t(::fidl::FidlCodedStruct(fidl_test_coding_StructWithHandleFields, 1, 8, "fidl.test.coding/StructWithHandle"));
 
@@ -53,7 +55,7 @@
 };
 const fidl_type_t fidl_test_coding_TableOfStructWithHandleTable = fidl_type_t(::fidl::FidlCodedTable(fidl_test_coding_TableOfStructWithHandleFields, 2, "fidl.test.coding/TableOfStructWithHandle"));
 
-static const ::fidl::FidlField fidl_test_coding_IntStructFields[] = {};
+static const ::fidl::FidlStructField fidl_test_coding_IntStructFields[] = {};
 const fidl_type_t fidl_test_coding_IntStructTable = fidl_type_t(::fidl::FidlCodedStruct(fidl_test_coding_IntStructFields, 0, 8, "fidl.test.coding/IntStruct"));
 
 static const ::fidl::FidlTableField fidl_test_coding_OlderSimpleTableFields[] = {
@@ -74,4 +76,15 @@
 };
 const fidl_type_t fidl_test_coding_SimpleTableTable = fidl_type_t(::fidl::FidlCodedTable(fidl_test_coding_SimpleTableFields, 2, "fidl.test.coding/SimpleTable"));
 
+static const ::fidl::FidlXUnionField fidl_test_coding_SampleXUnionFields[] = {
+    ::fidl::FidlXUnionField(&fidl_test_coding_IntStructTable,376675050),
+    ::fidl::FidlXUnionField(&fidl_test_coding_SimpleTableTable,586453270)
+};
+const fidl_type_t fidl_test_coding_SampleXUnionTable = fidl_type_t(::fidl::FidlCodedXUnion(2, fidl_test_coding_SampleXUnionFields, "fidl.test.coding/SampleXUnion"));
+
+static const ::fidl::FidlStructField fidl_test_coding_SampleXUnionStructFields[] = {
+    ::fidl::FidlStructField(&fidl_test_coding_SampleXUnionTable, 0)
+};
+const fidl_type_t fidl_test_coding_SampleXUnionStructTable = fidl_type_t(::fidl::FidlCodedStruct(fidl_test_coding_SampleXUnionStructFields, 1, 24, "fidl.test.coding/SampleXUnionStruct"));
+
 } // extern "C"
diff --git a/system/utest/fidl/fidl/extra_messages.fidl b/system/utest/fidl/fidl/extra_messages.fidl
index b8b5fb3..3d14fe1 100644
--- a/system/utest/fidl/fidl/extra_messages.fidl
+++ b/system/utest/fidl/fidl/extra_messages.fidl
@@ -56,3 +56,12 @@
     1: StructWithHandle a;
     2: StructWithManyHandles b;
 };
+
+xunion SampleXUnion {
+    IntStruct i;
+    SimpleTable st;
+};
+
+struct SampleXUnionStruct {
+    SampleXUnion xu;
+};
diff --git a/system/utest/fidl/fidl/extra_messages.h b/system/utest/fidl/fidl/extra_messages.h
index 9fdda5f..9877c53 100644
--- a/system/utest/fidl/fidl/extra_messages.h
+++ b/system/utest/fidl/fidl/extra_messages.h
@@ -2,6 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <lib/fidl/coding.h>
+#include <lib/fidl/cpp/string_view.h>
+#include <lib/fidl/cpp/vector_view.h>
 #include <lib/fidl/internal.h>
 
 // "extern" definitions copied from extra_messages.cpp
@@ -16,10 +19,46 @@
 extern const fidl_type_t fidl_test_coding_OlderSimpleTableTable;
 extern const fidl_type_t fidl_test_coding_NewerSimpleTableTable;
 extern const fidl_type_t fidl_test_coding_SimpleTableTable;
+extern const fidl_type_t fidl_test_coding_SampleXUnionTable;
+extern const fidl_type_t fidl_test_coding_SampleXUnionStructTable;
 
 extern const fidl_type_t fidl_test_coding_LinearizerTestVectorOfUint32RequestTable;
 extern const fidl_type_t fidl_test_coding_LinearizerTestVectorOfStringRequestTable;
 
+using SimpleTable = fidl::VectorView<fidl_envelope_t>;
+struct SimpleTableEnvelopes {
+    alignas(FIDL_ALIGNMENT)
+    fidl_envelope_t x;
+    fidl_envelope_t reserved1;
+    fidl_envelope_t reserved2;
+    fidl_envelope_t reserved3;
+    fidl_envelope_t y;
+};
+struct IntStruct {
+    alignas(FIDL_ALIGNMENT)
+    int64_t v;
+};
+
+struct SampleXUnion {
+    FIDL_ALIGNDECL
+    fidl_xunion_t header;
+
+    // Representing out-of-line part
+    union {
+        FIDL_ALIGNDECL
+        IntStruct i;
+
+        FIDL_ALIGNDECL
+        SimpleTable st;
+    };
+};
+constexpr uint32_t kSampleXUnionIntStructOrdinal = 376675050;
+
+struct SampleXUnionStruct {
+    FIDL_ALIGNDECL
+    SampleXUnion xu;
+};
+
 #if defined(__cplusplus)
 }
 #endif
diff --git a/system/utest/fidl/linearizing_tests.cpp b/system/utest/fidl/linearizing_tests.cpp
index fc8a4db..c2d865b 100644
--- a/system/utest/fidl/linearizing_tests.cpp
+++ b/system/utest/fidl/linearizing_tests.cpp
@@ -338,21 +338,6 @@
 bool linearize_simple_table() {
     BEGIN_TEST;
 
-    // Define the memory-layout of the inline object
-    using SimpleTable = fidl::VectorView<fidl_envelope_t>;
-    struct SimpleTableEnvelopes {
-        alignas(FIDL_ALIGNMENT)
-        fidl_envelope_t x;
-        fidl_envelope_t reserved1;
-        fidl_envelope_t reserved2;
-        fidl_envelope_t reserved3;
-        fidl_envelope_t y;
-    };
-    struct IntStruct {
-        alignas(FIDL_ALIGNMENT)
-        int64_t v;
-    };
-
     SimpleTableEnvelopes envelopes = {};
     SimpleTable simple_table;
     simple_table.set_count(5);
@@ -587,6 +572,69 @@
 
 } // namespace
 
+bool linearize_xunion_empty_invariant_empty() {
+    BEGIN_TEST;
+
+    // Non-zero ordinal with empty envelope is an error
+    SampleXUnionStruct xunion = {};
+    xunion.xu.header = (fidl_xunion_t) {
+        .tag = kSampleXUnionIntStructOrdinal,
+        .padding = 0,
+        .envelope = {}
+    };
+    constexpr uint32_t buf_size = 512;
+    uint8_t buffer[buf_size];
+    const char* error = nullptr;
+    zx_status_t status;
+    uint32_t actual_num_bytes = 0;
+    status = fidl_linearize(&fidl_test_coding_SampleXUnionStructTable,
+                            &xunion,
+                            buffer,
+                            buf_size,
+                            &actual_num_bytes,
+                            &error);
+    EXPECT_EQ(status, ZX_ERR_INVALID_ARGS);
+    EXPECT_NONNULL(error);
+    EXPECT_STR_EQ(error, "empty xunion must have zero as ordinal");
+
+    END_TEST;
+}
+
+bool linearize_xunion_empty_invariant_zero_ordinal() {
+    BEGIN_TEST;
+
+    // Zero ordinal with non-empty envelope is an error
+    IntStruct int_struct = {
+        .v = 100
+    };
+    SampleXUnionStruct xunion = {};
+    xunion.xu.header = (fidl_xunion_t) {
+        .tag = 0,
+        .padding = 0,
+        .envelope = (fidl_envelope_t) {
+            .num_bytes = 8,
+            .num_handles = 0,
+            .data = &int_struct
+        }
+    };
+    constexpr uint32_t buf_size = 512;
+    uint8_t buffer[buf_size];
+    const char* error = nullptr;
+    zx_status_t status;
+    uint32_t actual_num_bytes = 0;
+    status = fidl_linearize(&fidl_test_coding_SampleXUnionStructTable,
+                            &xunion,
+                            buffer,
+                            buf_size,
+                            &actual_num_bytes,
+                            &error);
+    EXPECT_EQ(status, ZX_ERR_INVALID_ARGS);
+    EXPECT_NONNULL(error);
+    EXPECT_STR_EQ(error, "xunion with zero as ordinal must be empty");
+
+    END_TEST;
+}
+
 BEGIN_TEST_CASE(strings)
 RUN_TEST(linearize_present_nonnullable_string)
 RUN_TEST(linearize_present_nonnullable_longer_string)
@@ -608,5 +656,11 @@
 RUN_TEST(linearize_table_field_2)
 END_TEST_CASE(tables)
 
+BEGIN_TEST_CASE(xunions)
+RUN_TEST(linearize_xunion_empty_invariant_empty)
+RUN_TEST(linearize_xunion_empty_invariant_zero_ordinal)
+END_TEST_CASE(xunions)
+
+
 } // namespace
 } // namespace fidl
diff --git a/system/utest/fidl/validating_tests.cpp b/system/utest/fidl/validating_tests.cpp
index 5da5312..d1efc7a 100644
--- a/system/utest/fidl/validating_tests.cpp
+++ b/system/utest/fidl/validating_tests.cpp
@@ -10,6 +10,7 @@
 
 #include "fidl_coded_types.h"
 #include "fidl_structs.h"
+#include "fidl/extra_messages.h"
 
 namespace fidl {
 namespace {
@@ -1554,6 +1555,56 @@
     END_TEST;
 }
 
+bool validate_valid_empty_xunion() {
+    BEGIN_TEST;
+
+    SampleXUnionStruct message = {};
+
+    const char* error = nullptr;
+    auto status = fidl_validate(&fidl_test_coding_SampleXUnionStructTable, &message,
+                                sizeof(fidl_xunion_t), 0, &error);
+    EXPECT_EQ(status, ZX_OK);
+    EXPECT_NULL(error, error);
+
+    END_TEST;
+}
+
+bool validate_empty_xunion_nonzero_ordinal() {
+    BEGIN_TEST;
+
+    SampleXUnionStruct message = {};
+    message.xu.header.tag = kSampleXUnionIntStructOrdinal;
+
+    const char* error = nullptr;
+    auto status = fidl_validate(&fidl_test_coding_SampleXUnionStructTable, &message,
+                                sizeof(fidl_xunion_t), 0, &error);
+    EXPECT_EQ(status, ZX_ERR_INVALID_ARGS);
+    EXPECT_NONNULL(error, error);
+    EXPECT_STR_EQ(error, "empty xunion must have zero as ordinal");
+
+    END_TEST;
+}
+
+bool validate_nonempty_xunion_zero_ordinal() {
+    BEGIN_TEST;
+
+    SampleXUnionStruct message = {};
+    message.xu.header.envelope = (fidl_envelope_t) {
+        .num_bytes = 8,
+        .num_handles = 0,
+        .presence = FIDL_ALLOC_PRESENT
+    };
+
+    const char* error = nullptr;
+    auto status = fidl_validate(&fidl_test_coding_SampleXUnionStructTable, &message,
+                                sizeof(SampleXUnionStruct), 0, &error);
+    EXPECT_EQ(status, ZX_ERR_INVALID_ARGS);
+    EXPECT_NONNULL(error, error);
+    EXPECT_STR_EQ(error, "xunion with zero as ordinal must be empty");
+
+    END_TEST;
+}
+
 BEGIN_TEST_CASE(null_parameters)
 RUN_TEST(validate_null_validate_parameters)
 END_TEST_CASE(null_parameters)
@@ -1630,5 +1681,11 @@
 RUN_TEST(validate_nested_struct_recursion_too_deep_error)
 END_TEST_CASE(structs)
 
+BEGIN_TEST_CASE(xunions)
+RUN_TEST(validate_valid_empty_xunion)
+RUN_TEST(validate_empty_xunion_nonzero_ordinal)
+RUN_TEST(validate_nonempty_xunion_zero_ordinal)
+END_TEST_CASE(xunions)
+
 } // namespace
 } // namespace fidl