/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "src/protovm/rw_proto_cursor.h"

#include "perfetto/protozero/proto_decoder.h"
#include "src/protovm/error_handling.h"

namespace perfetto {
namespace protovm {

RwProtoCursor::RepeatedFieldIterator::RepeatedFieldIterator()
    : allocator_{nullptr}, it_{} {}

RwProtoCursor::RepeatedFieldIterator::RepeatedFieldIterator(
    Allocator* allocator,
    IntrusiveMap::Iterator it)
    : allocator_{allocator}, it_{it} {}

RwProtoCursor::RepeatedFieldIterator&
RwProtoCursor::RepeatedFieldIterator::RepeatedFieldIterator::operator++() {
  ++it_;
  return *this;
}

RwProtoCursor RwProtoCursor::RepeatedFieldIterator::GetCursor() {
  return RwProtoCursor{it_->value.get(), allocator_};
}

RwProtoCursor::RepeatedFieldIterator::operator bool() const {
  return static_cast<bool>(it_);
}

RwProtoCursor::RwProtoCursor() = default;

RwProtoCursor::RwProtoCursor(Node* node, Allocator* allocator)
    : node_{node}, allocator_{allocator} {}

bool RwProtoCursor::operator==(const RwProtoCursor& other) const {
  return node_ == other.node_;
}
bool RwProtoCursor::operator!=(const RwProtoCursor& other) const {
  return !(*this == other);
}

StatusOr<bool> RwProtoCursor::IsRoot() const {
  PROTOVM_RETURN_IF_NOT_OK(CheckIsValid());
  return parent_link_.node == nullptr;
}

StatusOr<bool> RwProtoCursor::HasField(uint32_t field_id) {
  PROTOVM_RETURN_IF_NOT_OK(CheckIsValid());

  // Eagerly decompose bytes because the field being tested will be entered
  // later anyways. See Executor::EnterField().
  auto status = ConvertToMessageIfNeeded(node_);
  PROTOVM_RETURN_IF_NOT_OK(status);
  auto* message = node_->GetIf<Node::Message>();
  bool found = static_cast<bool>(message->field_id_to_node.Find(field_id));
  return found;
}

StatusOr<void> RwProtoCursor::EnterField(uint32_t field_id) {
  PROTOVM_RETURN_IF_NOT_OK(CheckIsValid());

  auto status_or_it = FindOrCreateMessageField(node_, field_id);
  PROTOVM_RETURN_IF_NOT_OK(status_or_it);
  auto it = *status_or_it;

  if (it->value->GetIf<Node::IndexedRepeatedField>()) {
    PROTOVM_ABORT(
        "Attempted to enter field (id=%u) as a simple field but it is an "
        "indexed repeated field",
        field_id);
  }

  if (it->value->GetIf<Node::MappedRepeatedField>()) {
    PROTOVM_ABORT(
        "Attempted to enter field (id=%u) as a simple field but it is a "
        "mapped repeated field",
        field_id);
  }

  parent_link_ = {node_, &node_->GetIf<Node::Message>()->field_id_to_node,
                  std::addressof(*it)};
  node_ = it->value.get();
  return StatusOr<void>::Ok();
}

StatusOr<void> RwProtoCursor::EnterRepeatedFieldAt(uint32_t field_id,
                                                   uint32_t index) {
  PROTOVM_RETURN_IF_NOT_OK(CheckIsValid());

  auto status_or_message_field = FindOrCreateMessageField(node_, field_id);
  PROTOVM_RETURN_IF_NOT_OK(status_or_message_field);
  auto message_field = *status_or_message_field;

  auto status =
      ConvertToIndexedRepeatedFieldIfNeeded(message_field->value.get());
  PROTOVM_RETURN_IF_NOT_OK(status);

  auto status_or_repeated_field =
      FindOrCreateIndexedRepeatedField(message_field->value.get(), index);
  PROTOVM_RETURN_IF_NOT_OK(status_or_repeated_field);

  parent_link_ = {
      message_field->value.get(),
      &message_field->value->GetIf<Node::IndexedRepeatedField>()->index_to_node,
      std::addressof(**status_or_repeated_field)};
  node_ = (*status_or_repeated_field)->value.get();

  return StatusOr<void>::Ok();
}

StatusOr<RwProtoCursor::RepeatedFieldIterator>
RwProtoCursor::IterateRepeatedField(uint32_t field_id) {
  PROTOVM_RETURN_IF_NOT_OK(CheckIsValid());

  auto status_convertion_to_message = ConvertToMessageIfNeeded(node_);
  PROTOVM_RETURN_IF_NOT_OK(status_convertion_to_message);

  auto* message = node_->GetIf<Node::Message>();
  auto it = message->field_id_to_node.Find(field_id);

  if (!it) {
    return RepeatedFieldIterator{};
  }

  auto* field = it->value.get();
  auto status_convertion_to_repeated_field =
      ConvertToIndexedRepeatedFieldIfNeeded(field);
  PROTOVM_RETURN_IF_NOT_OK(status_convertion_to_repeated_field);

  return RepeatedFieldIterator{
      allocator_,
      field->GetIf<Node::IndexedRepeatedField>()->index_to_node.begin()};
}

StatusOr<void> RwProtoCursor::EnterRepeatedFieldByKey(uint32_t field_id,
                                                      uint32_t map_key_field_id,
                                                      uint64_t key) {
  PROTOVM_RETURN_IF_NOT_OK(CheckIsValid());

  auto status_or_message_field = FindOrCreateMessageField(node_, field_id);
  PROTOVM_RETURN_IF_NOT_OK(status_or_message_field);
  auto message_field = *status_or_message_field;

  auto status_conversion = ConvertToMappedRepeatedFieldIfNeeded(
      message_field->value.get(), map_key_field_id);
  PROTOVM_RETURN_IF_NOT_OK(status_conversion);

  auto status_or_repeated_field =
      FindOrCreateMappedRepeatedField(message_field->value.get(), key);
  PROTOVM_RETURN_IF_NOT_OK(status_or_repeated_field);

  parent_link_ = {
      message_field->value.get(),
      &message_field->value->GetIf<Node::MappedRepeatedField>()->key_to_node,
      std::addressof(**status_or_repeated_field)};
  node_ = (*status_or_repeated_field)->value.get();

  return StatusOr<void>::Ok();
}

StatusOr<Scalar> RwProtoCursor::GetScalar() const {
  PROTOVM_RETURN_IF_NOT_OK(CheckIsValid());

  auto* scalar = node_->GetIf<Scalar>();
  if (!scalar) {
    PROTOVM_ABORT("Attempted \"get scalar\" operation but node has type %s",
                  node_->GetTypeName());
  }
  return *scalar;
}

StatusOr<void> RwProtoCursor::SetBytes(protozero::ConstBytes data) {
  PROTOVM_RETURN_IF_NOT_OK(CheckIsValid());

  if (bool is_compatible = node_->GetIf<Node::Empty>() ||
                           node_->GetIf<Node::Bytes>() ||
                           node_->GetIf<Node::Message>();
      !is_compatible) {
    PROTOVM_ABORT("Attempted \"set bytes\" operation but node has type %s",
                  node_->GetTypeName());
  }

  auto status_or_bytes = allocator_->AllocateAndCopyBytes(data);
  PROTOVM_RETURN_IF_NOT_OK(status_or_bytes);

  allocator_->DeleteReferencedData(node_);
  node_->value = Node::Bytes{std::move(*status_or_bytes)};

  return StatusOr<void>::Ok();
}

StatusOr<void> RwProtoCursor::SetScalar(Scalar scalar) {
  PROTOVM_RETURN_IF_NOT_OK(CheckIsValid());

  if (bool is_compatible =
          node_->GetIf<Node::Empty>() || node_->GetIf<Scalar>();
      !is_compatible) {
    PROTOVM_ABORT("Attempted \"set scalar\" operation but node has type %s",
                  node_->GetTypeName());
  }

  node_->value = scalar;
  return StatusOr<void>::Ok();
}

StatusOr<void> RwProtoCursor::Merge(protozero::ConstBytes data,
                                    uint32_t flags) {
  PROTOVM_RETURN_IF_NOT_OK(CheckIsValid());

  if (bool is_compatible = node_->GetIf<Node::Empty>() ||
                           node_->GetIf<Node::Message>() ||
                           node_->GetIf<Node::Bytes>();
      !is_compatible) {
    PROTOVM_ABORT("Attempted MERGE operation but node has type %s",
                  node_->GetTypeName());
  }

  if (data.size == 0) {
    return StatusOr<void>::Ok();
  }

  auto status_convert_to_message = ConvertToMessageIfNeeded(node_);
  PROTOVM_RETURN_IF_NOT_OK(status_convert_to_message);
  auto* message = node_->GetIf<Node::Message>();

  protozero::ProtoDecoder decoder(data);

  for (auto field = decoder.ReadField(); field.valid();
       field = decoder.ReadField()) {
    auto it = message->field_id_to_node.Find(field.id());

    bool skip_submessages = (flags & kSkipSubmessages) != 0;
    if (skip_submessages && it && it->value->GetIf<Node::Message>()) {
      // Implements deep merge semantics: skip this message that was already
      // merged by a previous operation
      continue;
    }

    bool del_if_src_empty = (flags & kDelIfSrcEmpty) != 0;
    if (it && del_if_src_empty &&
        field.type() ==
            protozero::proto_utils::ProtoWireType::kLengthDelimited &&
        field.size() == 0) {
      // Implements remove submessage semantics: empty src field means delete
      // the dst field
      message->field_id_to_node.Remove(*it);
      allocator_->Delete(&*it);
      continue;
    }

    auto status_or_map_value = CreateNodeFromField(field);
    PROTOVM_RETURN_IF_NOT_OK(status_or_map_value);

    if (!it) {
      auto status_or_it = MapInsert(&message->field_id_to_node, field.id(),
                                    std::move(*status_or_map_value));
      PROTOVM_RETURN_IF_NOT_OK(status_or_it);
      (*status_or_it)->value->has_been_merged = true;
      continue;
    }

    if (it->value->GetIf<Node::MappedRepeatedField>()) {
      PROTOVM_ABORT(
          "Merge operation of mapped repeated field is not supported (field id "
          "= %u)",
          field.id());
    }

    // Promote field as repeated if needed:
    // If this is the second time that the same field ID is being merged, then
    // they are a repeated fields and they should live within an
    // IndexedRepeatedField node.
    if (it->value->has_been_merged) {
      auto status_convert_to_indexed =
          ConvertToIndexedRepeatedFieldIfNeeded(it->value.get());
      PROTOVM_RETURN_IF_NOT_OK(status_convert_to_indexed);
    }

    if (auto* indexed_fields = it->value->GetIf<Node::IndexedRepeatedField>()) {
      // Implements merge semantics for repeated fields: all existing fields are
      // removed and replaced with the newly received fields.
      if (!it->value->has_been_merged) {
        // Optimization opportunity: reuse the existing nodes to avoid N
        // allocation-deallocation pairs, where N is the number of newly
        // received repeated fields.
        allocator_->DeleteReferencedData(it->value.get());
        it->value->has_been_merged = true;
      }
      MapInsert(&indexed_fields->index_to_node,
                indexed_fields->index_to_node.Size(),
                std::move(*status_or_map_value));
      continue;
    }

    // Optimization oppurtunity: reuse the existing node to avoid one
    // allocation-deallocation pair
    allocator_->Delete(it->value.release());
    it->value = std::move(*status_or_map_value);
    it->value->has_been_merged = true;
  }

  // Reset the merge flags
  for (auto& field : message->field_id_to_node) {
    field.value->has_been_merged = false;
  }

  return StatusOr<void>::Ok();
}

StatusOr<void> RwProtoCursor::Delete() {
  PROTOVM_RETURN_IF_NOT_OK(CheckIsValid());

  PROTOVM_ASSIGN_OR_RETURN(bool is_root, IsRoot());
  if (is_root) {
    node_->value = Node::Empty{};
    return StatusOr<void>::Ok();
  }

  PERFETTO_DCHECK(parent_link_.map);
  PERFETTO_DCHECK(parent_link_.map_node);

  parent_link_.map->Remove(*parent_link_.map_node);
  allocator_->Delete(parent_link_.map_node);

  // Delete operation invalidates cursor
  node_ = nullptr;
  parent_link_ = {};

  return StatusOr<void>::Ok();
}

StatusOr<void> RwProtoCursor::ConvertToMessageIfNeeded(Node* node) {
  if (node->GetIf<Node::Message>()) {
    return StatusOr<void>::Ok();
  }

  if (node->GetIf<Node::Empty>()) {
    node->value = Node::Message{};
    return StatusOr<void>::Ok();
  }

  auto* bytes = node->GetIf<Node::Bytes>();
  if (!bytes) {
    PROTOVM_ABORT("Attempted conversion to message but node has type %s",
                  node->GetTypeName());
  }

  Node::Message message;

  protozero::ProtoDecoder decoder(protozero::ConstBytes{
      static_cast<const uint8_t*>(bytes->data.get()), bytes->size});

  for (auto field = decoder.ReadField(); field.valid();
       field = decoder.ReadField()) {
    auto status_or_map_value = CreateNodeFromField(field);
    if (!status_or_map_value.IsOk()) {
      allocator_->DeleteReferencedData(&message);
      PROTOVM_RETURN(status_or_map_value);
    }

    auto it = message.field_id_to_node.Find(field.id());

    // First occurrence of this field id. Just insert a new field into the
    // map.
    if (!it) {
      auto status_or_it = MapInsert(&message.field_id_to_node, field.id(),
                                    std::move(*status_or_map_value));
      if (!status_or_it.IsOk()) {
        allocator_->DeleteReferencedData(&message);
        PROTOVM_RETURN(status_or_it, "Insert message field (id = %u)",
                       field.id());
      }
      continue;
    }

    // Nth occurrence of this field id:
    // 1. Make sure we have an IndexedRepeatedField node
    // 2. Append into the IndexedRepeatedField's map
    auto status_conversion =
        ConvertToIndexedRepeatedFieldIfNeeded(it->value.get());
    if (!status_conversion.IsOk()) {
      allocator_->DeleteReferencedData(&message);
      allocator_->Delete(status_or_map_value->release());
      PROTOVM_RETURN(status_conversion);
    }

    auto& index_to_node =
        it->value->GetIf<Node::IndexedRepeatedField>()->index_to_node;
    auto status_or_it = MapInsert(&index_to_node, index_to_node.Size(),
                                  std::move(*status_or_map_value));
    if (!status_or_it.IsOk()) {
      allocator_->DeleteReferencedData(&message);
      PROTOVM_RETURN(status_or_it,
                     "Insert repeated field (id = %u, index = %d)", field.id(),
                     static_cast<int>(index_to_node.Size()));
    }
  }

  allocator_->DeleteReferencedData(node);
  node->value = message;

  return StatusOr<void>::Ok();
}

StatusOr<OwnedPtr<Node>> RwProtoCursor::CreateNodeFromField(
    protozero::Field field) {
  if (field.type() == protozero::proto_utils::ProtoWireType::kLengthDelimited) {
    auto status_or_bytes = allocator_->AllocateAndCopyBytes(field.as_bytes());
    PROTOVM_RETURN_IF_NOT_OK(status_or_bytes);
    auto status_or_node =
        allocator_->CreateNode<Node::Bytes>(std::move(*status_or_bytes));
    if (!status_or_node.IsOk()) {
      allocator_->DeleteReferencedData(&status_or_bytes.value());
      PROTOVM_RETURN(status_or_node);
    }

    return std::move(*status_or_node);
  }

  auto status_or_node =
      allocator_->CreateNode<Scalar>(field.type(), field.as_uint64());
  if (!status_or_node.IsOk()) {
    PROTOVM_RETURN(status_or_node);
  }

  return std::move(*status_or_node);
}

StatusOr<void> RwProtoCursor::ConvertToMappedRepeatedFieldIfNeeded(
    Node* node,
    uint32_t map_key_field_id) {
  if (node->GetIf<Node::MappedRepeatedField>()) {
    return StatusOr<void>::Ok();
  }

  if (node->GetIf<Node::Empty>()) {
    node->value = Node::MappedRepeatedField{};
    return StatusOr<void>::Ok();
  }

  // If the current node contains a message (either raw bytes or a decomposed
  // Node::Message), convert it into a Node::MappedRepeatedField.
  //
  // The process is:
  // 1. Extract the key (identified by 'map_key_field_id') from the message data
  // within the current 'node'.
  // 2. Allocate a new node. The original message content from the current node
  // is moved into this new node.
  // 3. Repurpose the current node, replacing it with an empty
  // Node::MappedRepeatedField
  //    (intrusive tree that stores message fields ordered by key).
  // 4. Insert the new node (containing Node::Message) into the repurposed
  // current node (Node::MappedRepeatedField) using the key extracted in step 1.
  if (node->GetIf<Node::Bytes>() || node->GetIf<Node::Message>()) {
    auto status_or_key = ReadScalarField(*node, map_key_field_id);
    PROTOVM_RETURN_IF_NOT_OK(status_or_key);

    auto status_or_map_value = allocator_->CreateNode<Node::Empty>();
    PROTOVM_RETURN_IF_NOT_OK(status_or_map_value);

    auto map_value = std::move(*status_or_map_value);
    map_value->value = std::move(node->value);
    node->value = Node::MappedRepeatedField{};

    auto status_or_it =
        MapInsert(&node->GetIf<Node::MappedRepeatedField>()->key_to_node,
                  *status_or_key, std::move(map_value));
    PROTOVM_RETURN(status_or_it);
  }

  // If the current node is a Node::IndexedRepeatedField (intrusive tree with
  // message fields ordered by index), this block converts it into a
  // Node::MappedRepeatedField (intrusive tree with message fields ordered by
  // key).
  //
  // The process is:
  // 1. Initialize an empty Node::MappedRepeatedField
  // 2. For each field in the current node (Node::IndexedRepeatedField):
  //    2.1. Extract the key (identified by 'map_key_field_id'). Note that the
  //         iterated fields must be messages.
  //    2.2. Remove the field from the current node
  //         (Node::IndexedRepeatedField).
  //    2.3. Insert the field into the Node::MappedRepeatedField using the key
  //         extracted in step 2.1.
  // 3. Assign the Node::MappedRepeatedField to the current node, i.e. repurpose
  //    current node from Node::IndexedRepeatedField to
  //    Node::MappedRepeatedField)
  if (auto* indexed = node->GetIf<Node::IndexedRepeatedField>()) {
    Node::MappedRepeatedField mapped_repeated_field;

    for (auto it = indexed->index_to_node.begin(); it;) {
      auto& map_entry = *it;
      auto& value_node = *it->value;

      it = indexed->index_to_node.Remove(it);

      auto status_or_key = ReadScalarField(value_node, map_key_field_id);
      PROTOVM_RETURN_IF_NOT_OK(status_or_key);

      map_entry.key = *status_or_key;
      mapped_repeated_field.key_to_node.Insert(map_entry);
    }

    node->value = mapped_repeated_field;

    return StatusOr<void>::Ok();
  }

  PROTOVM_ABORT(
      "Attempted to access field as MappedRepeatedField but node has type "
      "%s",
      node->GetTypeName());
}

StatusOr<void> RwProtoCursor::ConvertToIndexedRepeatedFieldIfNeeded(
    Node* node) {
  if (node->GetIf<Node::IndexedRepeatedField>()) {
    return StatusOr<void>::Ok();
  }

  if (node->GetIf<Node::MappedRepeatedField>()) {
    PROTOVM_ABORT(
        "Attempted \"convert to indexed repeated field\" operation but "
        "node has type %s",
        node->GetTypeName());
  }

  if (node->GetIf<Node::Empty>()) {
    node->value = Node::IndexedRepeatedField{};
    return StatusOr<void>::Ok();
  }

  auto status_or_map_value = allocator_->CreateNode<Node::Empty>();
  PROTOVM_RETURN_IF_NOT_OK(status_or_map_value);

  auto map_value = std::move(*status_or_map_value);
  map_value->value = std::move(node->value);
  node->value = Node::IndexedRepeatedField{};

  auto status_or_it =
      MapInsert(&node->GetIf<Node::IndexedRepeatedField>()->index_to_node, 0,
                std::move(map_value));
  PROTOVM_RETURN(status_or_it);
}

StatusOr<IntrusiveMap::Iterator> RwProtoCursor::FindOrCreateMessageField(
    Node* node,
    uint32_t field_id) {
  auto status = ConvertToMessageIfNeeded(node);
  PROTOVM_RETURN_IF_NOT_OK(status);

  auto* message = node->GetIf<Node::Message>();
  auto it = message->field_id_to_node.Find(field_id);
  if (it) {
    return it;
  }

  auto status_or_map_value = allocator_->CreateNode<Node::Empty>();
  PROTOVM_RETURN_IF_NOT_OK(status_or_map_value);

  auto status_or_it = MapInsert(&message->field_id_to_node, field_id,
                                std::move(*status_or_map_value));
  PROTOVM_RETURN(status_or_it);
}

StatusOr<IntrusiveMap::Iterator>
RwProtoCursor::FindOrCreateIndexedRepeatedField(Node* node, uint32_t index) {
  auto& index_to_node =
      node->GetIf<Node::IndexedRepeatedField>()->index_to_node;

  auto it = index_to_node.Find(index);
  if (it) {
    return it;
  }

  bool requires_creation_and_is_not_simple_append =
      index > index_to_node.Size();
  if (requires_creation_and_is_not_simple_append) {
    PROTOVM_ABORT(
        "Attempted to insert repeated field at arbitrary position (only "
        "append operation is supported)");
  }

  auto status_or_map_value = allocator_->CreateNode<Node::Empty>();
  PROTOVM_RETURN_IF_NOT_OK(status_or_map_value);

  return MapInsert(&node->GetIf<Node::IndexedRepeatedField>()->index_to_node,
                   index, std::move(*status_or_map_value));
}

StatusOr<IntrusiveMap::Iterator> RwProtoCursor::FindOrCreateMappedRepeatedField(
    Node* node,
    uint64_t key) {
  auto it = node->GetIf<Node::MappedRepeatedField>()->key_to_node.Find(key);

  if (it) {
    return it;
  }

  auto status_or_map_value = allocator_->CreateNode<Node::Empty>();
  PROTOVM_RETURN_IF_NOT_OK(status_or_map_value);

  return MapInsert(&node->GetIf<Node::MappedRepeatedField>()->key_to_node, key,
                   std::move(*status_or_map_value));
}

StatusOr<IntrusiveMap::Iterator> RwProtoCursor::MapInsert(
    IntrusiveMap* map,
    uint64_t key,
    OwnedPtr<Node> map_value) {
  auto status_or_map_node =
      allocator_->CreateMapNode(key, std::move(map_value));
  PROTOVM_RETURN_IF_NOT_OK(status_or_map_node, "Failed to allocate node");

  auto [it, inserted] = map->Insert(*status_or_map_node->get());
  if (!inserted) {
    allocator_->Delete(status_or_map_node->release());
    PROTOVM_ABORT(
        "Failed to insert intrusive map entry (key = %d). Duplicated key?",
        static_cast<int>(key));
  }

  status_or_map_node->release();
  return it;
}

StatusOr<uint64_t> RwProtoCursor::ReadScalarField(const Node& node,
                                                  uint32_t field_id) {
  if (auto* bytes = node.GetIf<Node::Bytes>()) {
    protozero::ProtoDecoder decoder(protozero::ConstBytes{
        static_cast<const uint8_t*>(bytes->data.get()), bytes->size});

    auto field = decoder.ReadField();
    while (field.valid() && field.id() != field_id) {
      field = decoder.ReadField();
    }

    if (!field.valid()) {
      PROTOVM_ABORT(
          "Attempted to read scalar field (id=%u) but it is not present",
          field_id);
    }

    if (field.type() ==
        protozero::proto_utils::ProtoWireType::kLengthDelimited) {
      PROTOVM_ABORT("Attempted to length-delimited field (id=%u) as scalar",
                    field_id);
    }

    return field.as_uint64();
  }

  if (auto* message = node.GetIf<Node::Message>()) {
    auto it = message->field_id_to_node.Find(field_id);
    if (!it) {
      PROTOVM_ABORT(
          "Attempted to read scalar field (id=%u) but it is not present",
          field_id);
    }

    auto* scalar = it->value->GetIf<Scalar>();
    if (!scalar) {
      PROTOVM_ABORT(
          "Attempted to read scalar field (id=%u) from node with type %s",
          field_id, it->value->GetTypeName());
    }

    return scalar->value;
  }

  PROTOVM_ABORT(
      "Attempted to read scalar field (id=%u) but parent node has type %s",
      field_id, node.GetTypeName());
}

StatusOr<void> RwProtoCursor::CheckIsValid() const {
  if (!node_) {
    PROTOVM_ABORT("Attempted to access invalid cursor");
  }
  return StatusOr<void>::Ok();
}

}  // namespace protovm
}  // namespace perfetto
