// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/trace_event/traced_value.h"

#include <inttypes.h>
#include <stdint.h>

#include <atomic>
#include <string_view>
#include <utility>

#include "base/bits.h"
#include "base/containers/circular_deque.h"
#include "base/containers/span.h"
#include "base/json/json_writer.h"
#include "base/json/string_escape.h"
#include "base/memory/ptr_util.h"
#include "base/notreached.h"
#include "base/pickle.h"
#include "base/strings/stringprintf.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/trace_event_impl.h"
#include "base/trace_event/trace_event_memory_overhead.h"
#include "base/trace_event/trace_log.h"
#include "base/values.h"

namespace base {
namespace trace_event {

namespace {
const char kTypeStartDict = '{';
const char kTypeEndDict = '}';
const char kTypeStartArray = '[';
const char kTypeEndArray = ']';
const char kTypeBool = 'b';
const char kTypeInt = 'i';
const char kTypeDouble = 'd';
const char kTypeString = 's';
const char kTypeCStr = '*';  // only used for key names

std::atomic<TracedValue::WriterFactoryCallback> g_writer_factory_callback;

#ifndef NDEBUG
const bool kStackTypeDict = false;
const bool kStackTypeArray = true;
#define DCHECK_CURRENT_CONTAINER_IS(x) DCHECK_EQ(x, nesting_stack_.back())
#define DCHECK_CONTAINER_STACK_DEPTH_EQ(x) DCHECK_EQ(x, nesting_stack_.size())
#define DEBUG_PUSH_CONTAINER(x) nesting_stack_.push_back(x)
#define DEBUG_POP_CONTAINER() nesting_stack_.pop_back()
#else
#define DCHECK_CURRENT_CONTAINER_IS(x) \
  do {                                 \
  } while (0)
#define DCHECK_CONTAINER_STACK_DEPTH_EQ(x) \
  do {                                     \
  } while (0)
#define DEBUG_PUSH_CONTAINER(x) \
  do {                          \
  } while (0)
#define DEBUG_POP_CONTAINER() \
  do {                        \
  } while (0)
#endif

inline void WriteKeyNameAsRawPtr(Pickle& pickle, const char* ptr) {
  pickle.WriteBytes(as_bytes(make_span(&kTypeCStr, 1u)));
  pickle.WriteUInt64(static_cast<uint64_t>(reinterpret_cast<uintptr_t>(ptr)));
}

inline void WriteKeyNameWithCopy(Pickle& pickle, std::string_view str) {
  pickle.WriteBytes(as_bytes(make_span(&kTypeString, 1u)));
  pickle.WriteString(str);
}

std::string ReadKeyName(PickleIterator& pickle_iterator) {
  const char* type = nullptr;
  bool res = pickle_iterator.ReadBytes(&type, 1);
  std::string key_name;
  if (res && *type == kTypeCStr) {
    uint64_t ptr_value = 0;
    res = pickle_iterator.ReadUInt64(&ptr_value);
    key_name = reinterpret_cast<const char*>(static_cast<uintptr_t>(ptr_value));
  } else if (res && *type == kTypeString) {
    res = pickle_iterator.ReadString(&key_name);
  }
  DCHECK(res);
  return key_name;
}

class PickleWriter final : public TracedValue::Writer {
 public:
  explicit PickleWriter(size_t capacity) {
    if (capacity) {
      pickle_.Reserve(capacity);
    }
  }

  bool IsPickleWriter() const override { return true; }
  bool IsProtoWriter() const override { return false; }

  void SetInteger(const char* name, int value) override {
    pickle_.WriteBytes(as_bytes(make_span(&kTypeInt, 1u)));
    pickle_.WriteInt(value);
    WriteKeyNameAsRawPtr(pickle_, name);
  }

  void SetIntegerWithCopiedName(std::string_view name, int value) override {
    pickle_.WriteBytes(as_bytes(make_span(&kTypeInt, 1u)));
    pickle_.WriteInt(value);
    WriteKeyNameWithCopy(pickle_, name);
  }

  void SetDouble(const char* name, double value) override {
    pickle_.WriteBytes(as_bytes(make_span(&kTypeDouble, 1u)));
    pickle_.WriteDouble(value);
    WriteKeyNameAsRawPtr(pickle_, name);
  }

  void SetDoubleWithCopiedName(std::string_view name, double value) override {
    pickle_.WriteBytes(as_bytes(make_span(&kTypeDouble, 1u)));
    pickle_.WriteDouble(value);
    WriteKeyNameWithCopy(pickle_, name);
  }

  void SetBoolean(const char* name, bool value) override {
    pickle_.WriteBytes(as_bytes(make_span(&kTypeBool, 1u)));
    pickle_.WriteBool(value);
    WriteKeyNameAsRawPtr(pickle_, name);
  }

  void SetBooleanWithCopiedName(std::string_view name, bool value) override {
    pickle_.WriteBytes(as_bytes(make_span(&kTypeBool, 1u)));
    pickle_.WriteBool(value);
    WriteKeyNameWithCopy(pickle_, name);
  }

  void SetString(const char* name, std::string_view value) override {
    pickle_.WriteBytes(as_bytes(make_span(&kTypeString, 1u)));
    pickle_.WriteString(value);
    WriteKeyNameAsRawPtr(pickle_, name);
  }

  void SetStringWithCopiedName(std::string_view name,
                               std::string_view value) override {
    pickle_.WriteBytes(as_bytes(make_span(&kTypeString, 1u)));
    pickle_.WriteString(value);
    WriteKeyNameWithCopy(pickle_, name);
  }

  void SetValue(const char* name, Writer* value) override {
    CHECK(value->IsPickleWriter());
    const PickleWriter* pickle_writer = static_cast<const PickleWriter*>(value);

    BeginDictionary(name);
    pickle_.WriteBytes(pickle_writer->pickle_.payload_bytes());
    EndDictionary();
  }

  void SetValueWithCopiedName(std::string_view name, Writer* value) override {
    CHECK(value->IsPickleWriter());
    const PickleWriter* pickle_writer = static_cast<const PickleWriter*>(value);

    BeginDictionaryWithCopiedName(name);
    pickle_.WriteBytes(pickle_writer->pickle_.payload_bytes());
    EndDictionary();
  }

  void BeginArray() override {
    pickle_.WriteBytes(as_bytes(make_span(&kTypeStartArray, 1u)));
  }

  void BeginDictionary() override {
    pickle_.WriteBytes(as_bytes(make_span(&kTypeStartDict, 1u)));
  }

  void BeginDictionary(const char* name) override {
    pickle_.WriteBytes(as_bytes(make_span(&kTypeStartDict, 1u)));
    WriteKeyNameAsRawPtr(pickle_, name);
  }

  void BeginDictionaryWithCopiedName(std::string_view name) override {
    pickle_.WriteBytes(as_bytes(make_span(&kTypeStartDict, 1u)));
    WriteKeyNameWithCopy(pickle_, name);
  }

  void BeginArray(const char* name) override {
    pickle_.WriteBytes(as_bytes(make_span(&kTypeStartArray, 1u)));
    WriteKeyNameAsRawPtr(pickle_, name);
  }

  void BeginArrayWithCopiedName(std::string_view name) override {
    pickle_.WriteBytes(as_bytes(make_span(&kTypeStartArray, 1u)));
    WriteKeyNameWithCopy(pickle_, name);
  }

  void EndDictionary() override {
    pickle_.WriteBytes(as_bytes(make_span(&kTypeEndDict, 1u)));
  }
  void EndArray() override {
    pickle_.WriteBytes(as_bytes(make_span(&kTypeEndArray, 1u)));
  }

  void AppendInteger(int value) override {
    pickle_.WriteBytes(as_bytes(make_span(&kTypeInt, 1u)));
    pickle_.WriteInt(value);
  }

  void AppendDouble(double value) override {
    pickle_.WriteBytes(as_bytes(make_span(&kTypeDouble, 1u)));
    pickle_.WriteDouble(value);
  }

  void AppendBoolean(bool value) override {
    pickle_.WriteBytes(as_bytes(make_span(&kTypeBool, 1u)));
    pickle_.WriteBool(value);
  }

  void AppendString(std::string_view value) override {
    pickle_.WriteBytes(as_bytes(make_span(&kTypeString, 1u)));
    pickle_.WriteString(value);
  }

  void AppendAsTraceFormat(std::string* out) const override {
    struct State {
      enum Type { kTypeDict, kTypeArray };
      Type type;
      bool needs_comma;
    };

    auto maybe_append_key_name = [](State current_state, PickleIterator* it,
                                    std::string* out) {
      if (current_state.type == State::kTypeDict) {
        EscapeJSONString(ReadKeyName(*it), true, out);
        out->append(":");
      }
    };

    base::circular_deque<State> state_stack;

    out->append("{");
    state_stack.push_back({State::kTypeDict});

    PickleIterator it(pickle_);
    for (const char* type; it.ReadBytes(&type, 1);) {
      switch (*type) {
        case kTypeEndDict:
          out->append("}");
          state_stack.pop_back();
          continue;

        case kTypeEndArray:
          out->append("]");
          state_stack.pop_back();
          continue;
      }

      // Use an index so it will stay valid across resizes.
      size_t current_state_index = state_stack.size() - 1;
      if (state_stack[current_state_index].needs_comma) {
        out->append(",");
      }

      switch (*type) {
        case kTypeStartDict: {
          maybe_append_key_name(state_stack[current_state_index], &it, out);
          out->append("{");
          state_stack.push_back({State::kTypeDict});
          break;
        }

        case kTypeStartArray: {
          maybe_append_key_name(state_stack[current_state_index], &it, out);
          out->append("[");
          state_stack.push_back({State::kTypeArray});
          break;
        }

        case kTypeBool: {
          TraceEvent::TraceValue json_value;
          CHECK(it.ReadBool(&json_value.as_bool));
          maybe_append_key_name(state_stack[current_state_index], &it, out);
          json_value.AppendAsJSON(TRACE_VALUE_TYPE_BOOL, out);
          break;
        }

        case kTypeInt: {
          int value;
          CHECK(it.ReadInt(&value));
          maybe_append_key_name(state_stack[current_state_index], &it, out);
          TraceEvent::TraceValue json_value;
          json_value.as_int = value;
          json_value.AppendAsJSON(TRACE_VALUE_TYPE_INT, out);
          break;
        }

        case kTypeDouble: {
          TraceEvent::TraceValue json_value;
          CHECK(it.ReadDouble(&json_value.as_double));
          maybe_append_key_name(state_stack[current_state_index], &it, out);
          json_value.AppendAsJSON(TRACE_VALUE_TYPE_DOUBLE, out);
          break;
        }

        case kTypeString: {
          std::string value;
          CHECK(it.ReadString(&value));
          maybe_append_key_name(state_stack[current_state_index], &it, out);
          TraceEvent::TraceValue json_value;
          json_value.as_string = value.c_str();
          json_value.AppendAsJSON(TRACE_VALUE_TYPE_STRING, out);
          break;
        }

        default:
          NOTREACHED();
      }

      state_stack[current_state_index].needs_comma = true;
    }

    out->append("}");
    state_stack.pop_back();

    DCHECK(state_stack.empty());
  }

  void EstimateTraceMemoryOverhead(
      TraceEventMemoryOverhead* overhead) override {
    overhead->Add(TraceEventMemoryOverhead::kTracedValue,
                  /* allocated size */
                  pickle_.GetTotalAllocatedSize(),
                  /* resident size */
                  pickle_.size());
  }

  std::unique_ptr<base::Value> ToBaseValue() const {
    base::Value root(base::Value::Type::DICT);
    Value* cur_dict = &root;
    Value* cur_list = nullptr;
    std::vector<Value*> stack;
    PickleIterator it(pickle_);
    const char* type;

    while (it.ReadBytes(&type, 1)) {
      DCHECK((cur_dict && !cur_list) || (cur_list && !cur_dict));
      switch (*type) {
        case kTypeStartDict: {
          if (cur_dict) {
            stack.push_back(cur_dict);
            cur_dict = cur_dict->GetDict().Set(ReadKeyName(it),
                                               Value(Value::Type::DICT));
          } else {
            cur_list->GetList().Append(Value(Value::Type::DICT));
            // Update |cur_dict| to point to the newly added dictionary.
            cur_dict = &cur_list->GetList().back();
            stack.push_back(cur_list);
            cur_list = nullptr;
          }
        } break;

        case kTypeEndArray:
        case kTypeEndDict: {
          if (stack.back()->is_dict()) {
            cur_dict = stack.back();
            cur_list = nullptr;
          } else if (stack.back()->is_list()) {
            cur_list = stack.back();
            cur_dict = nullptr;
          }
          stack.pop_back();
        } break;

        case kTypeStartArray: {
          Value::List new_list;
          if (cur_dict) {
            stack.push_back(cur_dict);
            cur_list =
                cur_dict->GetDict().Set(ReadKeyName(it), std::move(new_list));
            cur_dict = nullptr;
          } else {
            cur_list->GetList().Append(std::move(new_list));
            stack.push_back(cur_list);
            // |cur_list| is invalidated at this point by the Append, so it
            // needs to be reset.
            cur_list = &cur_list->GetList().back();
          }
        } break;

        case kTypeBool: {
          bool value;
          CHECK(it.ReadBool(&value));
          if (cur_dict) {
            cur_dict->GetDict().Set(ReadKeyName(it), value);
          } else {
            cur_list->GetList().Append(value);
          }
        } break;

        case kTypeInt: {
          int value;
          CHECK(it.ReadInt(&value));
          if (cur_dict) {
            cur_dict->GetDict().Set(ReadKeyName(it), value);
          } else {
            cur_list->GetList().Append(value);
          }
        } break;

        case kTypeDouble: {
          TraceEvent::TraceValue trace_value;
          CHECK(it.ReadDouble(&trace_value.as_double));
          Value base_value;
          if (!std::isfinite(trace_value.as_double)) {
            // base::Value doesn't support nan and infinity values. Use strings
            // for them instead. This follows the same convention in
            // AppendAsTraceFormat(), supported by TraceValue::Append*().
            std::string value_string;
            trace_value.AppendAsString(TRACE_VALUE_TYPE_DOUBLE, &value_string);
            base_value = Value(value_string);
          } else {
            base_value = Value(trace_value.as_double);
          }
          if (cur_dict) {
            cur_dict->GetDict().Set(ReadKeyName(it), std::move(base_value));
          } else {
            cur_list->GetList().Append(std::move(base_value));
          }
        } break;

        case kTypeString: {
          std::string value;
          CHECK(it.ReadString(&value));
          if (cur_dict) {
            cur_dict->GetDict().Set(ReadKeyName(it), std::move(value));
          } else {
            cur_list->GetList().Append(std::move(value));
          }
        } break;

        default:
          NOTREACHED();
      }
    }
    DCHECK(stack.empty());
    return base::Value::ToUniquePtrValue(std::move(root));
  }

 private:
  Pickle pickle_;
};

std::unique_ptr<TracedValue::Writer> CreateWriter(size_t capacity) {
  TracedValue::WriterFactoryCallback callback =
      g_writer_factory_callback.load(std::memory_order_relaxed);
  if (callback) {
    return callback(capacity);
  }

  return std::make_unique<PickleWriter>(capacity);
}

}  // namespace

bool TracedValue::Writer::AppendToProto(ProtoAppender* appender) {
  return false;
}

// static
void TracedValue::SetWriterFactoryCallback(WriterFactoryCallback callback) {
  g_writer_factory_callback.store(callback);
}

TracedValue::TracedValue(size_t capacity)
    : TracedValue(capacity, /*forced_json*/ false) {}

TracedValue::TracedValue(size_t capacity, bool forced_json) {
  DEBUG_PUSH_CONTAINER(kStackTypeDict);

  writer_ = forced_json ? std::make_unique<PickleWriter>(capacity)
                        : CreateWriter(capacity);
}

TracedValue::~TracedValue() {
  DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
  DEBUG_POP_CONTAINER();
  DCHECK_CONTAINER_STACK_DEPTH_EQ(0u);
}

void TracedValue::SetInteger(const char* name, int value) {
  DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
  writer_->SetInteger(name, value);
}

void TracedValue::SetIntegerWithCopiedName(std::string_view name, int value) {
  DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
  writer_->SetIntegerWithCopiedName(name, value);
}

void TracedValue::SetDouble(const char* name, double value) {
  DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
  writer_->SetDouble(name, value);
}

void TracedValue::SetDoubleWithCopiedName(std::string_view name, double value) {
  DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
  writer_->SetDoubleWithCopiedName(name, value);
}

void TracedValue::SetBoolean(const char* name, bool value) {
  DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
  writer_->SetBoolean(name, value);
}

void TracedValue::SetBooleanWithCopiedName(std::string_view name, bool value) {
  DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
  writer_->SetBooleanWithCopiedName(name, value);
}

void TracedValue::SetString(const char* name, std::string_view value) {
  DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
  writer_->SetString(name, value);
}

void TracedValue::SetStringWithCopiedName(std::string_view name,
                                          std::string_view value) {
  DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
  writer_->SetStringWithCopiedName(name, value);
}

void TracedValue::SetValue(const char* name, TracedValue* value) {
  DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
  writer_->SetValue(name, value->writer_.get());
}

void TracedValue::SetValueWithCopiedName(std::string_view name,
                                         TracedValue* value) {
  DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
  writer_->SetValueWithCopiedName(name, value->writer_.get());
}

namespace {

// TODO(altimin): Add native support for pointers for nested values in
// DebugAnnotation proto.
std::string PointerToString(const void* value) {
  return base::StringPrintf(
      "0x%" PRIx64, static_cast<uint64_t>(reinterpret_cast<uintptr_t>(value)));
}

}  // namespace

void TracedValue::SetPointer(const char* name, const void* value) {
  DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
  writer_->SetString(name, PointerToString(value));
}

void TracedValue::SetPointerWithCopiedName(std::string_view name,
                                           const void* value) {
  DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
  writer_->SetStringWithCopiedName(name, PointerToString(value));
}

void TracedValue::BeginDictionary(const char* name) {
  DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
  DEBUG_PUSH_CONTAINER(kStackTypeDict);
  writer_->BeginDictionary(name);
}

void TracedValue::BeginDictionaryWithCopiedName(std::string_view name) {
  DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
  DEBUG_PUSH_CONTAINER(kStackTypeDict);
  writer_->BeginDictionaryWithCopiedName(name);
}

void TracedValue::BeginArray(const char* name) {
  DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
  DEBUG_PUSH_CONTAINER(kStackTypeArray);
  writer_->BeginArray(name);
}

void TracedValue::BeginArrayWithCopiedName(std::string_view name) {
  DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
  DEBUG_PUSH_CONTAINER(kStackTypeArray);
  writer_->BeginArrayWithCopiedName(name);
}

void TracedValue::AppendInteger(int value) {
  DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray);
  writer_->AppendInteger(value);
}

void TracedValue::AppendDouble(double value) {
  DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray);
  writer_->AppendDouble(value);
}

void TracedValue::AppendBoolean(bool value) {
  DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray);
  writer_->AppendBoolean(value);
}

void TracedValue::AppendString(std::string_view value) {
  DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray);
  writer_->AppendString(value);
}

void TracedValue::AppendPointer(const void* value) {
  DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray);
  writer_->AppendString(PointerToString(value));
}

void TracedValue::BeginArray() {
  DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray);
  DEBUG_PUSH_CONTAINER(kStackTypeArray);
  writer_->BeginArray();
}

void TracedValue::BeginDictionary() {
  DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray);
  DEBUG_PUSH_CONTAINER(kStackTypeDict);
  writer_->BeginDictionary();
}

void TracedValue::EndArray() {
  DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray);
  DEBUG_POP_CONTAINER();
  writer_->EndArray();
}

void TracedValue::EndDictionary() {
  DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
  DEBUG_POP_CONTAINER();
  writer_->EndDictionary();
}

std::unique_ptr<base::Value> TracedValue::ToBaseValue() const {
  DCHECK(writer_->IsPickleWriter());
  return static_cast<const PickleWriter*>(writer_.get())->ToBaseValue();
}

void TracedValue::AppendAsTraceFormat(std::string* out) const {
  DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
  DCHECK_CONTAINER_STACK_DEPTH_EQ(1u);

  writer_->AppendAsTraceFormat(out);
}

bool TracedValue::AppendToProto(ProtoAppender* appender) const {
  return writer_->AppendToProto(appender);
}

void TracedValue::EstimateTraceMemoryOverhead(
    TraceEventMemoryOverhead* overhead) {
  writer_->EstimateTraceMemoryOverhead(overhead);
}

TracedValue::Array::Array(const std::initializer_list<ArrayItem> items) {
  items_ = std::move(items);
}

TracedValue::Array::Array(TracedValue::Array&& other) {
  items_ = std::move(other.items_);
}

void TracedValue::Array::WriteToValue(TracedValue* value) const {
  for (const auto& item : items_) {
    item.WriteToValue(value);
  }
}

TracedValue::Dictionary::Dictionary(
    const std::initializer_list<DictionaryItem> items) {
  items_ = items;
}

TracedValue::Dictionary::Dictionary(TracedValue::Dictionary&& other) {
  items_ = std::move(other.items_);
}

void TracedValue::Dictionary::WriteToValue(TracedValue* value) const {
  for (const auto& item : items_) {
    item.WriteToValue(value);
  }
}

TracedValue::ValueHolder::ValueHolder(int value) {
  kept_value_.int_value = value;
  kept_value_type_ = KeptValueType::kIntType;
}

TracedValue::ValueHolder::ValueHolder(double value) {
  kept_value_.double_value = value;
  kept_value_type_ = KeptValueType::kDoubleType;
}

TracedValue::ValueHolder::ValueHolder(bool value) {
  kept_value_.bool_value = value;
  kept_value_type_ = KeptValueType::kBoolType;
}

TracedValue::ValueHolder::ValueHolder(std::string_view value) {
  kept_value_.string_piece_value = value;
  kept_value_type_ = KeptValueType::kStringPieceType;
}

TracedValue::ValueHolder::ValueHolder(std::string value) {
  new (&kept_value_.std_string_value) std::string(std::move(value));
  kept_value_type_ = KeptValueType::kStdStringType;
}

TracedValue::ValueHolder::ValueHolder(void* value) {
  kept_value_.void_ptr_value = value;
  kept_value_type_ = KeptValueType::kVoidPtrType;
}

TracedValue::ValueHolder::ValueHolder(const char* value) {
  kept_value_.string_piece_value = value;
  kept_value_type_ = KeptValueType::kStringPieceType;
}

TracedValue::ValueHolder::ValueHolder(TracedValue::Dictionary& value) {
  new (&kept_value_.dictionary_value) TracedValue::Dictionary(std::move(value));
  kept_value_type_ = KeptValueType::kDictionaryType;
}

TracedValue::ValueHolder::ValueHolder(TracedValue::Array& value) {
  new (&kept_value_.array_value) TracedValue::Array(std::move(value));
  kept_value_type_ = KeptValueType::kArrayType;
}

TracedValue::ValueHolder::ValueHolder(TracedValue::ValueHolder&& other) {
  // Remember to call a destructor if necessary.
  if (kept_value_type_ == KeptValueType::kStdStringType) {
    delete (&kept_value_.std_string_value);
  }
  switch (other.kept_value_type_) {
    case KeptValueType::kIntType: {
      kept_value_.int_value = other.kept_value_.int_value;
      break;
    }
    case KeptValueType::kDoubleType: {
      kept_value_.double_value = other.kept_value_.double_value;
      break;
    }
    case KeptValueType::kBoolType: {
      kept_value_.bool_value = other.kept_value_.bool_value;
      break;
    }
    case KeptValueType::kStringPieceType: {
      kept_value_.string_piece_value = other.kept_value_.string_piece_value;
      break;
    }
    case KeptValueType::kStdStringType: {
      new (&kept_value_.std_string_value)
          std::string(std::move(other.kept_value_.std_string_value));
      break;
    }
    case KeptValueType::kVoidPtrType: {
      kept_value_.void_ptr_value = other.kept_value_.void_ptr_value;
      break;
    }
    case KeptValueType::kArrayType: {
      new (&kept_value_.array_value)
          TracedValue::Array(std::move(other.kept_value_.array_value));
      break;
    }
    case KeptValueType::kDictionaryType: {
      new (&kept_value_.dictionary_value) TracedValue::Dictionary(
          std::move(other.kept_value_.dictionary_value));
      break;
    }
  }
  kept_value_type_ = other.kept_value_type_;
}

void TracedValue::ValueHolder::WriteToValue(TracedValue* value) const {
  switch (kept_value_type_) {
    case KeptValueType::kIntType: {
      value->AppendInteger(kept_value_.int_value);
      break;
    }
    case KeptValueType::kDoubleType: {
      value->AppendDouble(kept_value_.double_value);
      break;
    }
    case KeptValueType::kBoolType: {
      value->AppendBoolean(kept_value_.bool_value);
      break;
    }
    case KeptValueType::kStringPieceType: {
      value->AppendString(kept_value_.string_piece_value);
      break;
    }
    case KeptValueType::kStdStringType: {
      value->AppendString(kept_value_.std_string_value);
      break;
    }
    case KeptValueType::kVoidPtrType: {
      value->AppendPointer(kept_value_.void_ptr_value);
      break;
    }
    case KeptValueType::kArrayType: {
      value->BeginArray();
      kept_value_.array_value.WriteToValue(value);
      value->EndArray();
      break;
    }
    case KeptValueType::kDictionaryType: {
      value->BeginDictionary();
      kept_value_.dictionary_value.WriteToValue(value);
      value->EndDictionary();
      break;
    }
  }
}

void TracedValue::ValueHolder::WriteToValue(const char* name,
                                            TracedValue* value) const {
  switch (kept_value_type_) {
    case KeptValueType::kIntType: {
      value->SetInteger(name, kept_value_.int_value);
      break;
    }
    case KeptValueType::kDoubleType: {
      value->SetDouble(name, kept_value_.double_value);
      break;
    }
    case KeptValueType::kBoolType: {
      value->SetBoolean(name, kept_value_.bool_value);
      break;
    }
    case KeptValueType::kStringPieceType: {
      value->SetString(name, kept_value_.string_piece_value);
      break;
    }
    case KeptValueType::kStdStringType: {
      value->SetString(name, kept_value_.std_string_value);
      break;
    }
    case KeptValueType::kVoidPtrType: {
      value->SetPointer(name, kept_value_.void_ptr_value);
      break;
    }
    case KeptValueType::kArrayType: {
      value->BeginArray(name);
      kept_value_.array_value.WriteToValue(value);
      value->EndArray();
      break;
    }
    case KeptValueType::kDictionaryType: {
      value->BeginDictionary(name);
      kept_value_.dictionary_value.WriteToValue(value);
      value->EndDictionary();
      break;
    }
  }
}

void TracedValue::ArrayItem::WriteToValue(TracedValue* value) const {
  ValueHolder::WriteToValue(value);
}

void TracedValue::DictionaryItem::WriteToValue(TracedValue* value) const {
  ValueHolder::WriteToValue(name_, value);
}

std::unique_ptr<TracedValue> TracedValue::Build(
    const std::initializer_list<DictionaryItem> items) {
  std::unique_ptr<TracedValue> value(new TracedValue());
  for (const auto& item : items) {
    item.WriteToValue(value.get());
  }
  return value;
}

std::string TracedValueJSON::ToJSON() const {
  std::string result;
  AppendAsTraceFormat(&result);
  return result;
}

std::string TracedValueJSON::ToFormattedJSON() const {
  std::string str;
  base::JSONWriter::WriteWithOptions(
      *ToBaseValue(),
      base::JSONWriter::OPTIONS_OMIT_DOUBLE_TYPE_PRESERVATION |
          base::JSONWriter::OPTIONS_PRETTY_PRINT,
      &str);
  return str;
}

TracedValue::ArrayScope::ArrayScope(TracedValue* value) : value_(value) {}

TracedValue::ArrayScope::~ArrayScope() {
  value_->EndArray();
}

TracedValue::ArrayScope TracedValue::AppendArrayScoped() {
  BeginArray();
  return TracedValue::ArrayScope(this);
}

TracedValue::ArrayScope TracedValue::BeginArrayScoped(const char* name) {
  BeginArray(name);
  return TracedValue::ArrayScope(this);
}

TracedValue::ArrayScope TracedValue::BeginArrayScopedWithCopiedName(
    std::string_view name) {
  BeginArrayWithCopiedName(name);
  return TracedValue::ArrayScope(this);
}

TracedValue::DictionaryScope::DictionaryScope(TracedValue* value)
    : value_(value) {}

TracedValue::DictionaryScope::~DictionaryScope() {
  value_->EndDictionary();
}

TracedValue::DictionaryScope TracedValue::AppendDictionaryScoped() {
  BeginDictionary();
  return TracedValue::DictionaryScope(this);
}

TracedValue::DictionaryScope TracedValue::BeginDictionaryScoped(
    const char* name) {
  BeginDictionary(name);
  return TracedValue::DictionaryScope(this);
}

TracedValue::DictionaryScope TracedValue::BeginDictionaryScopedWithCopiedName(
    std::string_view name) {
  BeginDictionaryWithCopiedName(name);
  return TracedValue::DictionaryScope(this);
}

}  // namespace trace_event
}  // namespace base
