// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#include "vm/canonical_tables.h"
#include "vm/closure_functions_cache.h"
#include "vm/compiler/assembler/disassembler.h"
#include "vm/debugger.h"
#include "vm/object.h"
#include "vm/object_graph.h"
#include "vm/object_store.h"
#include "vm/resolver.h"
#include "vm/stub_code.h"
#include "vm/symbols.h"

namespace dart {

#ifndef PRODUCT

static void AddNameProperties(JSONObject* jsobj,
                              const char* name,
                              const char* vm_name) {
  jsobj->AddProperty("name", name);
  if (strcmp(name, vm_name) != 0) {
    jsobj->AddProperty("_vmName", vm_name);
  }
}

void Object::AddCommonObjectProperties(JSONObject* jsobj,
                                       const char* protocol_type,
                                       bool ref) const {
  const char* vm_type = JSONType();
  bool same_type = (strcmp(protocol_type, vm_type) == 0);
  if (ref) {
    jsobj->AddPropertyF("type", "@%s", protocol_type);
  } else {
    jsobj->AddProperty("type", protocol_type);
  }
  if (!same_type) {
    jsobj->AddProperty("_vmType", vm_type);
  }
  if (!ref || IsInstance() || IsNull()) {
    // TODO(turnidge): Provide the type arguments here too?
    const Class& cls = Class::Handle(this->clazz());
    jsobj->AddProperty("class", cls);
  }
  if (!ref) {
    if (ptr()->IsHeapObject()) {
      jsobj->AddProperty("size", ptr()->untag()->HeapSize());
    } else {
      jsobj->AddProperty("size", (intptr_t)0);
    }
  }
}

void Object::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  AddCommonObjectProperties(&jsobj, "Object", ref);
  jsobj.AddServiceId(*this);
  if (ref) {
    return;
  }
}

void Object::PrintJSON(JSONStream* stream, bool ref) const {
  if (IsNull()) {
    JSONObject jsobj(stream);
    AddCommonObjectProperties(&jsobj, "Instance", ref);
    jsobj.AddProperty("kind", "Null");
    jsobj.AddFixedServiceId("objects/null");
    jsobj.AddProperty("valueAsString", "null");
  } else {
    PrintJSONImpl(stream, ref);
  }
}

void Object::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {
  UNREACHABLE();
}

void Object::PrintImplementationFields(JSONStream* stream) const {
  JSONObject jsobj(stream);
  jsobj.AddProperty("type", "ImplementationFields");
  JSONArray jsarr_fields(&jsobj, "fields");
  if (!IsNull()) {
    PrintImplementationFieldsImpl(jsarr_fields);
  }
}

void Class::PrintJSONImpl(JSONStream* stream, bool ref) const {
  Isolate* isolate = Isolate::Current();
  JSONObject jsobj(stream);
  if ((ptr() == Class::null()) || (id() == kFreeListElement)) {
    // TODO(turnidge): This is weird and needs to be changed.
    jsobj.AddProperty("type", "null");
    return;
  }
  AddCommonObjectProperties(&jsobj, "Class", ref);
  jsobj.AddFixedServiceId("classes/%" Pd "", id());
  const String& scrubbed_name = String::Handle(ScrubbedName());
  const String& vm_name = String::Handle(Name());
  AddNameProperties(&jsobj, scrubbed_name.ToCString(), vm_name.ToCString());
  const Script& script = Script::Handle(this->script());
  if (!script.IsNull()) {
    jsobj.AddLocation(script, token_pos(), end_token_pos());
  }

  jsobj.AddProperty("library", Object::Handle(library()));

  const intptr_t num_type_params = NumTypeParameters();
  if (num_type_params > 0) {
    JSONArray jsarr(&jsobj, "typeParameters");
    TypeParameter& type_param = TypeParameter::Handle();
    for (intptr_t i = 0; i < num_type_params; ++i) {
      type_param = TypeParameterAt(i);
      jsarr.AddValue(type_param);
    }
  }
  if (ref) {
    return;
  }

  const Error& err = Error::Handle(EnsureIsFinalized(Thread::Current()));
  if (!err.IsNull()) {
    jsobj.AddProperty("error", err);
  }
  jsobj.AddProperty("abstract", is_abstract());
  jsobj.AddProperty("const", is_const());
  jsobj.AddProperty("isSealed", is_sealed());
  jsobj.AddProperty("isMixinClass", is_mixin_class());
  jsobj.AddProperty("isBaseClass", is_base_class());
  jsobj.AddProperty("isInterfaceClass", is_interface_class());
  jsobj.AddProperty("isFinal", is_final());
  jsobj.AddProperty("_finalized", is_finalized());
  jsobj.AddProperty("_implemented", is_implemented());
  jsobj.AddProperty("_patch", false);
  jsobj.AddProperty("traceAllocations", TraceAllocation(isolate->group()));

  const Class& superClass = Class::Handle(SuperClass());
  if (!superClass.IsNull()) {
    jsobj.AddProperty("super", superClass);
  }
  const AbstractType& superType = AbstractType::Handle(super_type());
  if (!superType.IsNull()) {
    jsobj.AddProperty("superType", superType);
  }
  const Array& interface_array = Array::Handle(interfaces());
  if (is_transformed_mixin_application()) {
    Type& mix = Type::Handle();
    mix ^= interface_array.At(interface_array.Length() - 1);
    jsobj.AddProperty("mixin", mix);
  }
  {
    JSONArray interfaces_array(&jsobj, "interfaces");
    Type& interface_type = Type::Handle();
    if (!interface_array.IsNull()) {
      for (intptr_t i = 0; i < interface_array.Length(); ++i) {
        interface_type ^= interface_array.At(i);
        interfaces_array.AddValue(interface_type);
      }
    }
  }
  {
    JSONArray fields_array(&jsobj, "fields");
    const Array& field_array = Array::Handle(fields());
    Field& field = Field::Handle();
    if (!field_array.IsNull()) {
      for (intptr_t i = 0; i < field_array.Length(); ++i) {
        field ^= field_array.At(i);
        fields_array.AddValue(field);
      }
    }
  }
  {
    JSONArray functions_array(&jsobj, "functions");
    const Array& function_array = Array::Handle(current_functions());
    Function& function = Function::Handle();
    if (!function_array.IsNull()) {
      for (intptr_t i = 0; i < function_array.Length(); i++) {
        function ^= function_array.At(i);
        functions_array.AddValue(function);
      }
    }
  }
  {
    JSONArray subclasses_array(&jsobj, "subclasses");
    const GrowableObjectArray& subclasses =
        GrowableObjectArray::Handle(direct_subclasses_unsafe());
    if (!subclasses.IsNull()) {
      Class& subclass = Class::Handle();
      for (intptr_t i = 0; i < subclasses.Length(); ++i) {
        // TODO(turnidge): Use the Type directly once regis has added
        // types to the vmservice.
        subclass ^= subclasses.At(i);
        subclasses_array.AddValue(subclass);
      }
    }
  }
}

void Class::PrintImplementationFieldsImpl(const JSONArray& jsarr_fields) const {
}

void TypeParameters::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  AddCommonObjectProperties(&jsobj, "TypeParameters", ref);
  jsobj.AddServiceId(*this);
  if (ref) {
    return;
  }
  jsobj.AddProperty("flags", Array::Handle(flags()));
  jsobj.AddProperty("names", Array::Handle(names()));
  jsobj.AddProperty("bounds", TypeArguments::Handle(bounds()));
  jsobj.AddProperty("defaults", TypeArguments::Handle(defaults()));
}

void TypeParameters::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void TypeArguments::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  // The index in the canonical_type_arguments table cannot be used as part of
  // the object id (as in typearguments/id), because the indices are not
  // preserved when the table grows and the entries get rehashed. Use the ring.
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  auto object_store = thread->isolate_group()->object_store();
  CanonicalTypeArgumentsSet typeargs_table(
      zone, object_store->canonical_type_arguments());
  const Array& table =
      Array::Handle(HashTables::ToArray(typeargs_table, false));
  typeargs_table.Release();
  ASSERT(table.Length() > 0);
  AddCommonObjectProperties(&jsobj, "TypeArguments", ref);
  jsobj.AddServiceId(*this);
  const String& user_name = String::Handle(UserVisibleName());
  const String& vm_name = String::Handle(Name());
  AddNameProperties(&jsobj, user_name.ToCString(), vm_name.ToCString());
  if (ref) {
    return;
  }
  {
    JSONArray jsarr(&jsobj, "types");
    AbstractType& type_arg = AbstractType::Handle();
    for (intptr_t i = 0; i < Length(); i++) {
      type_arg = TypeAt(i);
      jsarr.AddValue(type_arg);
    }
  }
  if (!IsInstantiated()) {
    JSONArray jsarr(&jsobj, "_instantiations");
    Array& prior_instantiations = Array::Handle(zone, instantiations());
    TypeArguments& type_args = TypeArguments::Handle(zone);
    InstantiationsCacheTable table(prior_instantiations);
    for (const auto& tuple : table) {
      // Skip unoccupied entries.
      if (tuple.Get<Cache::kSentinelIndex>() == Cache::Sentinel()) continue;
      JSONObject instantiation(&jsarr);
      type_args ^= tuple.Get<Cache::kInstantiatorTypeArgsIndex>();
      instantiation.AddProperty("instantiatorTypeArguments", type_args, true);
      type_args = tuple.Get<Cache::kFunctionTypeArgsIndex>();
      instantiation.AddProperty("functionTypeArguments", type_args, true);
      type_args = tuple.Get<Cache::kInstantiatedTypeArgsIndex>();
      instantiation.AddProperty("instantiated", type_args, true);
    }
  }
}

void TypeArguments::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void PatchClass::PrintJSONImpl(JSONStream* stream, bool ref) const {
  Object::PrintJSONImpl(stream, ref);
}

void PatchClass::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void Function::AddFunctionServiceId(const JSONObject& jsobj) const {
  Class& cls = Class::Handle(Owner());
  // Special kinds of functions use indices in their respective lists.
  intptr_t id = -1;
  const char* selector = nullptr;
  // Regular functions known to their owner use their name (percent-encoded).
  String& name = String::Handle(this->name());

  if (IsNonImplicitClosureFunction()) {
    id = ClosureFunctionsCache::FindClosureIndex(*this);
    selector = "closures";
  } else if (IsImplicitClosureFunction()) {
    id = cls.FindImplicitClosureFunctionIndex(*this);
    selector = "implicit_closures";
  } else if (IsNoSuchMethodDispatcher() || IsInvokeFieldDispatcher()) {
    id = cls.FindInvocationDispatcherFunctionIndex(*this);
    selector = "dispatchers";
  } else if (IsFieldInitializer()) {
    name ^= Field::NameFromInit(name);
    const char* encoded_field_name = String::EncodeIRI(name);
    if (cls.IsTopLevel()) {
      const auto& library = Library::Handle(cls.library());
      const auto& private_key = String::Handle(library.private_key());
      jsobj.AddFixedServiceId("libraries/%s/field_inits/%s",
                              private_key.ToCString(), encoded_field_name);
    } else {
      jsobj.AddFixedServiceId("classes/%" Pd "/field_inits/%s", cls.id(),
                              encoded_field_name);
    }
    return;
  }
  if (id != -1) {
    ASSERT(selector != nullptr);
    if (cls.IsTopLevel()) {
      const auto& library = Library::Handle(cls.library());
      const auto& private_key = String::Handle(library.private_key());
      jsobj.AddFixedServiceId("libraries/%s/%s/%" Pd "",
                              private_key.ToCString(), selector, id);
    } else {
      jsobj.AddFixedServiceId("classes/%" Pd "/%s/%" Pd "", cls.id(), selector,
                              id);
    }
    return;
  }
  Thread* thread = Thread::Current();
  if (Resolver::ResolveFunction(thread->zone(), cls, name) == ptr()) {
    const char* encoded_name = String::EncodeIRI(name);
    if (cls.IsTopLevel()) {
      const auto& library = Library::Handle(cls.library());
      const auto& private_key = String::Handle(library.private_key());
      jsobj.AddFixedServiceId("libraries/%s/functions/%s",
                              private_key.ToCString(), encoded_name);
    } else {
      jsobj.AddFixedServiceId("classes/%" Pd "/functions/%s", cls.id(),
                              encoded_name);
    }
    return;
  }
  // Oddball functions (not known to their owner) fall back to use the object
  // id ring. Current known examples are signature functions of closures
  // and stubs like 'megamorphic_call_miss'.
  jsobj.AddServiceId(*this);
}

void Function::PrintJSONImpl(JSONStream* stream, bool ref) const {
  Class& cls = Class::Handle(Owner());
  ASSERT(!cls.IsNull());
  Error& err = Error::Handle();
  err = cls.EnsureIsFinalized(Thread::Current());
  ASSERT(err.IsNull());
  JSONObject jsobj(stream);
  AddCommonObjectProperties(&jsobj, "Function", ref);
  AddFunctionServiceId(jsobj);
  const char* user_name = UserVisibleNameCString();
  const String& vm_name = String::Handle(name());
  AddNameProperties(&jsobj, user_name, vm_name.ToCString());
  const Function& parent = Function::Handle(parent_function());
  if (!parent.IsNull()) {
    jsobj.AddProperty("owner", parent);
  } else if (!cls.IsNull()) {
    if (cls.IsTopLevel()) {
      const Library& library = Library::Handle(cls.library());
      jsobj.AddProperty("owner", library);
    } else {
      jsobj.AddProperty("owner", cls);
    }
  }

  const char* kind_string = Function::KindToCString(kind());
  jsobj.AddProperty("_kind", kind_string);
  jsobj.AddProperty("static", is_static());
  jsobj.AddProperty("const", is_const());
  jsobj.AddProperty("implicit", IsImplicitGetterOrSetter());
  jsobj.AddProperty("abstract", is_abstract());
  jsobj.AddProperty("_intrinsic", is_intrinsic());
  jsobj.AddProperty("_native", is_native());
  jsobj.AddProperty("isGetter", kind() == UntaggedFunction::kGetterFunction);
  jsobj.AddProperty("isSetter", kind() == UntaggedFunction::kSetterFunction);

  const Script& script = Script::Handle(this->script());
  if (!script.IsNull()) {
#if defined(DART_PRECOMPILED_RUNTIME)
    // Token position information is stripped in AOT snapshots, but the line
    // number is still included.
    jsobj.AddLocationLine(script, line());
#else
    jsobj.AddLocation(script, token_pos(), end_token_pos());
#endif  // defined(DART_PRECOMPILED_RUNTIME)
  }

  if (ref) {
    return;
  }
  const FunctionType& sig = FunctionType::Handle(signature());
  jsobj.AddProperty("signature", sig);

  Code& code = Code::Handle(CurrentCode());
  if (!code.IsNull()) {
    jsobj.AddProperty("code", code);
  }
  Array& ics = Array::Handle(ic_data_array());
  if (!ics.IsNull()) {
    jsobj.AddProperty("_icDataArray", ics);
  }
  jsobj.AddProperty("_optimizable", is_optimizable());
  jsobj.AddProperty("_inlinable", is_inlinable());
  jsobj.AddProperty("_recognized", IsRecognized());
  code = unoptimized_code();
  if (!code.IsNull()) {
    jsobj.AddProperty("_unoptimizedCode", code);
  }
  jsobj.AddProperty("_usageCounter", usage_counter());
  jsobj.AddProperty("_optimizedCallSiteCount", optimized_call_site_count());
  jsobj.AddProperty("_deoptimizations",
                    static_cast<intptr_t>(deoptimization_counter()));
  if ((kind() == UntaggedFunction::kImplicitGetter) ||
      (kind() == UntaggedFunction::kImplicitSetter) ||
      (kind() == UntaggedFunction::kImplicitStaticGetter) ||
      (kind() == UntaggedFunction::kFieldInitializer)) {
    const Field& field = Field::Handle(accessor_field());
    if (!field.IsNull()) {
      jsobj.AddProperty("_field", field);
    }
  }
}

void Function::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void FfiTrampolineData::PrintJSONImpl(JSONStream* stream, bool ref) const {
  Object::PrintJSONImpl(stream, ref);
}

void FfiTrampolineData::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void Field::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  Class& cls = Class::Handle(Owner());
  String& field_name = String::Handle(name());
  const char* encoded_field_name = String::EncodeIRI(field_name);
  AddCommonObjectProperties(&jsobj, "Field", ref);
  if (cls.IsTopLevel()) {
    const auto& library = Library::Handle(cls.library());
    const auto& private_key = String::Handle(library.private_key());
    jsobj.AddFixedServiceId("libraries/%s/fields/%s", private_key.ToCString(),
                            encoded_field_name);
  } else {
    jsobj.AddFixedServiceId("classes/%" Pd "/fields/%s", cls.id(),
                            encoded_field_name);
  }

  const char* user_name = UserVisibleNameCString();
  const String& vm_name = String::Handle(name());
  AddNameProperties(&jsobj, user_name, vm_name.ToCString());
  if (cls.IsTopLevel()) {
    const Library& library = Library::Handle(cls.library());
    jsobj.AddProperty("owner", library);
  } else {
    jsobj.AddProperty("owner", cls);
  }

  AbstractType& declared_type = AbstractType::Handle(type());
  jsobj.AddProperty("declaredType", declared_type);
  jsobj.AddProperty("static", is_static());
  jsobj.AddProperty("final", is_final());
  jsobj.AddProperty("const", is_const());

  const class Script& script = Script::Handle(Script());
  if (!script.IsNull()) {
    jsobj.AddLocation(script, token_pos(), end_token_pos());
  }

  if (ref) {
    return;
  }
  if (is_static()) {
    const Object& valueObj = Object::Handle(StaticValue());
    jsobj.AddProperty("staticValue", valueObj);
  }

  jsobj.AddProperty("_guardNullable", is_nullable());
  if (guarded_cid() == kIllegalCid) {
    jsobj.AddProperty("_guardClass", "unknown");
  } else if (guarded_cid() == kDynamicCid) {
    jsobj.AddProperty("_guardClass", "dynamic");
  } else {
    ClassTable* table = IsolateGroup::Current()->class_table();
    ASSERT(table->IsValidIndex(guarded_cid()));
    cls = table->At(guarded_cid());
    jsobj.AddProperty("_guardClass", cls);
  }
  if (guarded_list_length() == kUnknownFixedLength) {
    jsobj.AddProperty("_guardLength", "unknown");
  } else if (guarded_list_length() == kNoFixedLength) {
    jsobj.AddProperty("_guardLength", "variable");
  } else {
    jsobj.AddPropertyF("_guardLength", "%" Pd, guarded_list_length());
  }
}

void Field::PrintImplementationFieldsImpl(const JSONArray& jsarr_fields) const {
}

// See also Dart_ScriptGetTokenInfo.
void Script::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  AddCommonObjectProperties(&jsobj, "Script", ref);
  const String& uri = String::Handle(url());
  ASSERT(!uri.IsNull());
  const char* encoded_uri = String::EncodeIRI(uri);
  const Library& lib = Library::Handle(FindLibrary());
  if (lib.IsNull()) {
    jsobj.AddServiceId(*this);
  } else {
    const String& lib_id = String::Handle(lib.private_key());
    jsobj.AddFixedServiceId("libraries/%s/scripts/%s/%" Px64 "",
                            lib_id.ToCString(), encoded_uri, load_timestamp());
  }
  jsobj.AddPropertyStr("uri", uri);
  jsobj.AddProperty("_kind", "kernel");
  if (ref) {
    return;
  }
  jsobj.AddPropertyTimeMillis("_loadTime", load_timestamp());
  if (!lib.IsNull()) {
    jsobj.AddProperty("library", lib);
  }
  const String& source = String::Handle(Source());
  jsobj.AddProperty("lineOffset", line_offset());
  jsobj.AddProperty("columnOffset", col_offset());
  if (!source.IsNull()) {
    jsobj.AddPropertyStr("source", source);
  }

  // Print the line number table
  const GrowableObjectArray& lineNumberArray =
      GrowableObjectArray::Handle(GenerateLineNumberArray());
  if (!lineNumberArray.IsNull() && (lineNumberArray.Length() > 0)) {
    JSONArray tokenPosTable(&jsobj, "tokenPosTable");

    Object& value = Object::Handle();
    intptr_t pos = 0;

    // Skip leading null.
    ASSERT(lineNumberArray.Length() > 0);
    value = lineNumberArray.At(pos);
    ASSERT(value.IsNull());
    pos++;

    while (pos < lineNumberArray.Length()) {
      JSONArray lineInfo(&tokenPosTable);
      while (pos < lineNumberArray.Length()) {
        value = lineNumberArray.At(pos);
        pos++;
        if (value.IsNull()) {
          break;
        }
        const Smi& smi = Smi::Cast(value);
        lineInfo.AddValue(smi.Value());
      }
    }
  }
}

void Script::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

static void PrintShowHideNamesToJSON(JSONObject* jsobj, const Namespace& ns) {
  Array& arr = Array::Handle();
  String& name = String::Handle();
  arr ^= ns.show_names();
  if (!arr.IsNull()) {
    JSONArray jsarr(jsobj, "shows");
    for (intptr_t i = 0; i < arr.Length(); ++i) {
      name ^= arr.At(i);
      jsarr.AddValue(name.ToCString());
    }
  }
  arr ^= ns.hide_names();
  if (!arr.IsNull()) {
    JSONArray jsarr(jsobj, "hides");
    for (intptr_t i = 0; i < arr.Length(); ++i) {
      name ^= arr.At(i);
      jsarr.AddValue(name.ToCString());
    }
  }
}

void Library::PrintJSONImpl(JSONStream* stream, bool ref) const {
  const String& id = String::Handle(private_key());
  JSONObject jsobj(stream);
  AddCommonObjectProperties(&jsobj, "Library", ref);
  jsobj.AddFixedServiceId("libraries/%s", id.ToCString());
  const String& vm_name = String::Handle(name());
  const char* scrubbed_name = String::ScrubName(vm_name);
  AddNameProperties(&jsobj, scrubbed_name, vm_name.ToCString());
  const String& library_url = String::Handle(url());
  jsobj.AddPropertyStr("uri", library_url);
  if (ref) {
    return;
  }
  jsobj.AddProperty("debuggable", IsDebuggable());
  {
    JSONArray jsarr(&jsobj, "classes");
    ClassDictionaryIterator class_iter(*this);
    Class& klass = Class::Handle();
    while (class_iter.HasNext()) {
      klass = class_iter.GetNextClass();
      jsarr.AddValue(klass);
    }
  }
  {
    JSONArray jsarr(&jsobj, "dependencies");

    Namespace& ns = Namespace::Handle();
    Library& target = Library::Handle();

    // Unprefixed imports.
    Array& imports = Array::Handle(this->imports());
    for (intptr_t i = 0; i < imports.Length(); i++) {
      ns ^= imports.At(i);
      if (ns.IsNull()) continue;

      JSONObject jsdep(&jsarr);
      jsdep.AddProperty("isDeferred", false);
      jsdep.AddProperty("isExport", false);
      jsdep.AddProperty("isImport", true);
      target = ns.target();
      jsdep.AddProperty("target", target);
      PrintShowHideNamesToJSON(&jsdep, ns);
    }

    // Exports.
    const Array& exports = Array::Handle(this->exports());
    for (intptr_t i = 0; i < exports.Length(); i++) {
      ns ^= exports.At(i);
      if (ns.IsNull()) continue;

      JSONObject jsdep(&jsarr);
      jsdep.AddProperty("isDeferred", false);
      jsdep.AddProperty("isExport", true);
      jsdep.AddProperty("isImport", false);
      target = ns.target();
      jsdep.AddProperty("target", target);
      PrintShowHideNamesToJSON(&jsdep, ns);
    }

    // Prefixed imports.
    DictionaryIterator entries(*this);
    Object& entry = Object::Handle();
    LibraryPrefix& prefix = LibraryPrefix::Handle();
    String& prefix_name = String::Handle();
    while (entries.HasNext()) {
      entry = entries.GetNext();
      if (entry.IsLibraryPrefix()) {
        prefix ^= entry.ptr();
        imports = prefix.imports();
        if (!imports.IsNull()) {
          for (intptr_t i = 0; i < imports.Length(); i++) {
            ns ^= imports.At(i);
            if (ns.IsNull()) continue;

            JSONObject jsdep(&jsarr);
            jsdep.AddProperty("isDeferred", prefix.is_deferred_load());
            jsdep.AddProperty("isExport", false);
            jsdep.AddProperty("isImport", true);
            prefix_name = prefix.name();
            ASSERT(!prefix_name.IsNull());
            jsdep.AddProperty("prefix", prefix_name.ToCString());
            target = ns.target();
            jsdep.AddProperty("target", target);
            PrintShowHideNamesToJSON(&jsdep, ns);
          }
        }
      }
    }
  }
  {
    JSONArray jsarr(&jsobj, "variables");
    DictionaryIterator entries(*this);
    Object& entry = Object::Handle();
    while (entries.HasNext()) {
      entry = entries.GetNext();
      if (entry.IsField()) {
        jsarr.AddValue(entry);
      }
    }
  }
  {
    JSONArray jsarr(&jsobj, "functions");
    DictionaryIterator entries(*this);
    Object& entry = Object::Handle();
    while (entries.HasNext()) {
      entry = entries.GetNext();
      if (entry.IsFunction()) {
        const Function& func = Function::Cast(entry);
        if (func.kind() == UntaggedFunction::kRegularFunction ||
            func.kind() == UntaggedFunction::kGetterFunction ||
            func.kind() == UntaggedFunction::kSetterFunction) {
          jsarr.AddValue(func);
        }
      }
    }
  }
  {
    JSONArray jsarr(&jsobj, "scripts");
    Array& scripts = Array::Handle(LoadedScripts());
    Script& script = Script::Handle();
    for (intptr_t i = 0; i < scripts.Length(); i++) {
      script ^= scripts.At(i);
      jsarr.AddValue(script);
    }
  }
}

void Library::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void LibraryPrefix::PrintJSONImpl(JSONStream* stream, bool ref) const {
  Object::PrintJSONImpl(stream, ref);
}

void LibraryPrefix::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void Namespace::PrintJSONImpl(JSONStream* stream, bool ref) const {
  Object::PrintJSONImpl(stream, ref);
}

void Namespace::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void KernelProgramInfo::PrintJSONImpl(JSONStream* stream, bool ref) const {
  Object::PrintJSONImpl(stream, ref);
}

void KernelProgramInfo::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void Instructions::PrintJSONImpl(JSONStream* stream, bool ref) const {
  Object::PrintJSONImpl(stream, ref);
}

void Instructions::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void InstructionsSection::PrintJSONImpl(JSONStream* stream, bool ref) const {
  Object::PrintJSONImpl(stream, ref);
}

void InstructionsSection::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void InstructionsTable::PrintJSONImpl(JSONStream* stream, bool ref) const {
  Object::PrintJSONImpl(stream, ref);
}

void InstructionsTable::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void WeakSerializationReference::PrintJSONImpl(JSONStream* stream,
                                               bool ref) const {
  JSONObject jsobj(stream);
  AddCommonObjectProperties(&jsobj, "Object", ref);
  jsobj.AddServiceId(*this);
  if (ref) return;
  auto& obj = Object::Handle(target());
  jsobj.AddProperty("target", obj);
}

void WeakSerializationReference::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void WeakArray::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  AddCommonObjectProperties(&jsobj, "Object", ref);
  jsobj.AddServiceId(*this);
  jsobj.AddProperty("length", Length());
  if (ref) {
    return;
  }
  intptr_t offset;
  intptr_t count;
  stream->ComputeOffsetAndCount(Length(), &offset, &count);
  if (offset > 0) {
    jsobj.AddProperty("offset", offset);
  }
  if (count < Length()) {
    jsobj.AddProperty("count", count);
  }
  intptr_t limit = offset + count;
  ASSERT(limit <= Length());
  {
    JSONArray jsarr(&jsobj, "elements");
    Object& element = Object::Handle();
    for (intptr_t index = offset; index < limit; index++) {
      element = At(index);
      jsarr.AddValue(element);
    }
  }
}

void WeakArray::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void ObjectPool::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  AddCommonObjectProperties(&jsobj, "Object", ref);
  jsobj.AddServiceId(*this);
  jsobj.AddProperty("length", Length());
  if (ref) {
    return;
  }

  {
    JSONArray jsarr(&jsobj, "_entries");
    uword imm;
    Object& obj = Object::Handle();
    for (intptr_t i = 0; i < Length(); i++) {
      JSONObject jsentry(stream);
      jsentry.AddProperty("offset", OffsetFromIndex(i));
      switch (TypeAt(i)) {
        case ObjectPool::EntryType::kTaggedObject:
          obj = ObjectAt(i);
          jsentry.AddProperty("kind", "Object");
          jsentry.AddProperty("value", obj);
          break;
        case ObjectPool::EntryType::kImmediate:
          imm = RawValueAt(i);
          jsentry.AddProperty("kind", "Immediate");
          jsentry.AddPropertyF("value", "0x%" Px, imm);
          break;
        case ObjectPool::EntryType::kNativeFunction:
          imm = RawValueAt(i);
          jsentry.AddProperty("kind", "NativeFunction");
          jsentry.AddPropertyF("value", "0x%" Px, imm);
          break;
        default:
          UNREACHABLE();
      }
    }
  }
}

void ObjectPool::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void PcDescriptors::PrintToJSONObject(JSONObject* jsobj, bool ref) const {
  AddCommonObjectProperties(jsobj, "Object", ref);
  // TODO(johnmccutchan): Generate a stable id. PcDescriptors hang off a Code
  // object but do not have a back reference to generate an ID.
  jsobj->AddServiceId(*this);
  if (ref) {
    return;
  }
  JSONArray members(jsobj, "members");
  Iterator iter(*this, UntaggedPcDescriptors::kAnyKind);
  while (iter.MoveNext()) {
    JSONObject descriptor(&members);
    descriptor.AddPropertyF("pcOffset", "%" Px "", iter.PcOffset());
    descriptor.AddProperty("kind", KindAsStr(iter.Kind()));
    descriptor.AddProperty("deoptId", iter.DeoptId());
    // TODO(turnidge): Use AddLocation instead.
    descriptor.AddProperty("tokenPos", iter.TokenPos());
    descriptor.AddProperty("tryIndex", iter.TryIndex());
  }
}

void PcDescriptors::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  PrintToJSONObject(&jsobj, ref);
}

void PcDescriptors::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void CodeSourceMap::PrintJSONImpl(JSONStream* stream, bool ref) const {
  Object::PrintJSONImpl(stream, ref);
}

void CodeSourceMap::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void CompressedStackMaps::PrintJSONImpl(JSONStream* stream, bool ref) const {
  Object::PrintJSONImpl(stream, ref);
}

void CompressedStackMaps::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void LocalVarDescriptors::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  AddCommonObjectProperties(&jsobj, "Object", ref);
  // TODO(johnmccutchan): Generate a stable id. LocalVarDescriptors hang off
  // a Code object but do not have a back reference to generate an ID.
  jsobj.AddServiceId(*this);
  if (ref) {
    return;
  }
  JSONArray members(&jsobj, "members");
  String& var_name = String::Handle();
  for (intptr_t i = 0; i < Length(); i++) {
    UntaggedLocalVarDescriptors::VarInfo info;
    var_name = GetName(i);
    GetInfo(i, &info);
    JSONObject var(&members);
    var.AddProperty("name", var_name.ToCString());
    var.AddProperty("index", static_cast<intptr_t>(info.index()));
    var.AddProperty("declarationTokenPos", info.declaration_pos);
    var.AddProperty("scopeStartTokenPos", info.begin_pos);
    var.AddProperty("scopeEndTokenPos", info.end_pos);
    var.AddProperty("scopeId", static_cast<intptr_t>(info.scope_id));
    var.AddProperty("kind", KindToCString(info.kind()));
  }
}

void LocalVarDescriptors::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void ExceptionHandlers::PrintJSONImpl(JSONStream* stream, bool ref) const {
  Object::PrintJSONImpl(stream, ref);
}

void ExceptionHandlers::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void SingleTargetCache::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  AddCommonObjectProperties(&jsobj, "Object", ref);
  jsobj.AddServiceId(*this);
  jsobj.AddProperty("_target", Code::Handle(target()));
  if (ref) {
    return;
  }
  jsobj.AddProperty("_lowerLimit", lower_limit());
  jsobj.AddProperty("_upperLimit", upper_limit());
}

void SingleTargetCache::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void UnlinkedCall::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  AddCommonObjectProperties(&jsobj, "Object", ref);
  jsobj.AddServiceId(*this);
  jsobj.AddProperty("_selector", String::Handle(target_name()).ToCString());
  if (ref) {
    return;
  }
  jsobj.AddProperty("_argumentsDescriptor",
                    Array::Handle(arguments_descriptor()));
}

void UnlinkedCall::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void MonomorphicSmiableCall::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  AddCommonObjectProperties(&jsobj, "Object", ref);
  jsobj.AddServiceId(*this);
  jsobj.AddProperty("_expectedClassId", Smi::Handle(Smi::New(expected_cid())));
  if (ref) {
    return;
  }
}

void MonomorphicSmiableCall::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void CallSiteData::PrintJSONImpl(JSONStream* stream, bool ref) const {
  UNREACHABLE();
}

void CallSiteData::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {
  UNREACHABLE();
}

void ICData::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  AddCommonObjectProperties(&jsobj, "Object", ref);
  jsobj.AddServiceId(*this);
  jsobj.AddProperty("_owner", Object::Handle(Owner()));
  jsobj.AddProperty("_selector", String::Handle(target_name()).ToCString());
  if (ref) {
    return;
  }
  jsobj.AddProperty("_argumentsDescriptor",
                    Object::Handle(arguments_descriptor()));
  jsobj.AddProperty("_entries", Object::Handle(entries()));
}

void ICData::PrintToJSONArray(const JSONArray& jsarray,
                              TokenPosition token_pos) const {
  auto class_table = IsolateGroup::Current()->class_table();
  Class& cls = Class::Handle();
  Function& func = Function::Handle();

  JSONObject jsobj(&jsarray);
  jsobj.AddProperty("name", String::Handle(target_name()).ToCString());
  jsobj.AddProperty("tokenPos", static_cast<intptr_t>(token_pos.Serialize()));
  // TODO(rmacnak): Figure out how to stringify DeoptReasons().
  // jsobj.AddProperty("deoptReasons", ...);

  JSONArray cache_entries(&jsobj, "cacheEntries");
  for (intptr_t i = 0; i < NumberOfChecks(); i++) {
    JSONObject cache_entry(&cache_entries);
    func = GetTargetAt(i);
    intptr_t count = GetCountAt(i);
    if (!is_static_call()) {
      intptr_t cid = GetReceiverClassIdAt(i);
      cls = class_table->At(cid);
      cache_entry.AddProperty("receiver", cls);
    }
    cache_entry.AddProperty("target", func);
    cache_entry.AddProperty("count", count);
  }
}

void ICData::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void Code::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  AddCommonObjectProperties(&jsobj, "Code", ref);
  jsobj.AddFixedServiceId("code/%" Px64 "-%" Px "", compile_timestamp(),
                          PayloadStart());
  const char* qualified_name = QualifiedName(
      NameFormattingParams(kUserVisibleName, NameDisambiguation::kNo));
  const char* vm_name = Name();
  AddNameProperties(&jsobj, qualified_name, vm_name);
  const bool is_stub =
      IsStubCode() || IsAllocationStubCode() || IsTypeTestStubCode();
  if (is_stub) {
    jsobj.AddProperty("kind", "Stub");
  } else {
    jsobj.AddProperty("kind", "Dart");
  }
  jsobj.AddProperty("_optimized", is_optimized());
  const Object& obj = Object::Handle(owner());
  if (obj.IsFunction()) {
    const Function& func = Function::Cast(obj);
    jsobj.AddProperty("_intrinsic", func.is_intrinsic());
    jsobj.AddProperty("_native", func.is_native());
  } else {
    jsobj.AddProperty("_intrinsic", false);
    jsobj.AddProperty("_native", false);
  }
  if (obj.IsFunction()) {
    jsobj.AddProperty("function", obj);
  } else {
    // Generate a fake function reference.
    JSONObject func(&jsobj, "function");
    func.AddProperty("type", "@Function");
    func.AddProperty("_kind", "Stub");
    ASSERT(strcmp(qualified_name, vm_name) == 0);
    func.AddProperty("name", vm_name);
    AddNameProperties(&func, vm_name, vm_name);
  }
  if (ref) {
    return;
  }
  jsobj.AddPropertyF("_startAddress", "%" Px "", PayloadStart());
  jsobj.AddPropertyF("_endAddress", "%" Px "", PayloadStart() + Size());
  jsobj.AddProperty("_alive", is_alive());
  const ObjectPool& object_pool = ObjectPool::Handle(GetObjectPool());
  jsobj.AddProperty("_objectPool", object_pool);
  {
    JSONArray jsarr(&jsobj, "_disassembly");
    if (is_alive()) {
      // Only disassemble alive code objects.
      DisassembleToJSONStream formatter(jsarr);
      Disassemble(&formatter);
    }
  }
  const PcDescriptors& descriptors = PcDescriptors::Handle(pc_descriptors());
  if (!descriptors.IsNull()) {
    JSONObject desc(&jsobj, "_descriptors");
    descriptors.PrintToJSONObject(&desc, false);
  }

  PrintJSONInlineIntervals(&jsobj);
}

void Code::PrintImplementationFieldsImpl(const JSONArray& jsarr_fields) const {}

void Bytecode::PrintJSONImpl(JSONStream* stream, bool ref) const {
  // N.B. This is polymorphic with Code.

  JSONObject jsobj(stream);
  AddCommonObjectProperties(&jsobj, "Code", ref);
  int64_t compile_timestamp = 0;
  jsobj.AddFixedServiceId("code/%" Px64 "-%" Px "", compile_timestamp,
                          PayloadStart());
  const char* qualified_name = QualifiedName();
  const char* vm_name = Name();
  AddNameProperties(&jsobj, qualified_name, vm_name);

  jsobj.AddProperty("kind", "Dart");
  jsobj.AddProperty("_optimized", false);
  jsobj.AddProperty("_intrinsic", false);
  jsobj.AddProperty("_native", false);
  if (ref) {
    return;
  }
  const Function& fun = Function::Handle(function());
  jsobj.AddProperty("function", fun);
  jsobj.AddPropertyF("_startAddress", "%" Px "", PayloadStart());
  jsobj.AddPropertyF("_endAddress", "%" Px "", PayloadStart() + Size());
  jsobj.AddProperty("_alive", true);
  const ObjectPool& obj_pool = ObjectPool::Handle(object_pool());
  jsobj.AddProperty("_objectPool", obj_pool);
  {
    JSONArray jsarr(&jsobj, "_disassembly");
    DisassembleToJSONStream formatter(jsarr);
    Disassemble(&formatter);
  }
  const PcDescriptors& descriptors = PcDescriptors::Handle(pc_descriptors());
  if (!descriptors.IsNull()) {
    JSONObject desc(&jsobj, "_descriptors");
    descriptors.PrintToJSONObject(&desc, false);
  }

  {
    JSONArray inlined_functions(&jsobj, "_inlinedFunctions");
  }
  {
    JSONArray inline_intervals(&jsobj, "_inlinedIntervals");
  }
}

void Bytecode::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void Context::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  // TODO(turnidge): Should the user level type for Context be Context
  // or Object?
  AddCommonObjectProperties(&jsobj, "Context", ref);
  jsobj.AddServiceId(*this);

  jsobj.AddProperty("length", num_variables());

  if (ref) {
    return;
  }

  const Context& parent_context = Context::Handle(parent());
  if (!parent_context.IsNull()) {
    jsobj.AddProperty("parent", parent_context);
  }

  JSONArray jsarr(&jsobj, "variables");
  Object& var = Object::Handle();
  for (intptr_t index = 0; index < num_variables(); index++) {
    var = At(index);
    JSONObject jselement(&jsarr);
    jselement.AddProperty("value", var);
  }
}

void Context::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void ContextScope::PrintJSONImpl(JSONStream* stream, bool ref) const {
  Object::PrintJSONImpl(stream, ref);
}

void ContextScope::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void Sentinel::PrintJSONImpl(JSONStream* stream, bool ref) const {
  // Handle certain special sentinel values.
  if (ptr() == Object::sentinel().ptr()) {
    JSONObject jsobj(stream);
    jsobj.AddProperty("type", "Sentinel");
    jsobj.AddProperty("kind", "NotInitialized");
    jsobj.AddProperty("valueAsString", "<not initialized>");
    return;
  } else if (ptr() == Object::optimized_out().ptr()) {
    JSONObject jsobj(stream);
    jsobj.AddProperty("type", "Sentinel");
    jsobj.AddProperty("kind", "OptimizedOut");
    jsobj.AddProperty("valueAsString", "<optimized out>");
    return;
  }

  Object::PrintJSONImpl(stream, ref);
}

void Sentinel::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void MegamorphicCache::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  AddCommonObjectProperties(&jsobj, "Object", ref);
  jsobj.AddServiceId(*this);
  jsobj.AddProperty("_selector", String::Handle(target_name()).ToCString());
  if (ref) {
    return;
  }
  jsobj.AddProperty("_buckets", Object::Handle(buckets()));
  jsobj.AddProperty("_mask", mask());
  jsobj.AddProperty("_argumentsDescriptor",
                    Object::Handle(arguments_descriptor()));
}

void MegamorphicCache::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void SubtypeTestCache::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  AddCommonObjectProperties(&jsobj, "Object", ref);
  jsobj.AddServiceId(*this);
  if (ref) {
    return;
  }
  jsobj.AddProperty("_cache", Array::Handle(cache()));
}

void SubtypeTestCache::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void LoadingUnit::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  AddCommonObjectProperties(&jsobj, "Object", ref);
  jsobj.AddServiceId(*this);
  if (ref) {
    return;
  }
  jsobj.AddProperty("_parent", LoadingUnit::Handle(parent()));
  jsobj.AddProperty("_baseObjects", Array::Handle(base_objects()));
  jsobj.AddProperty("_id", static_cast<intptr_t>(id()));
  jsobj.AddProperty("_loaded", loaded());
  jsobj.AddProperty("_loadOutstanding", load_outstanding());
}

void LoadingUnit::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void Error::PrintJSONImpl(JSONStream* stream, bool ref) const {
  UNREACHABLE();
}

void Error::PrintImplementationFieldsImpl(const JSONArray& jsarr_fields) const {
  UNREACHABLE();
}

void ApiError::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  AddCommonObjectProperties(&jsobj, "Error", ref);
  jsobj.AddProperty("kind", "InternalError");
  jsobj.AddServiceId(*this);
  jsobj.AddProperty("message", ToErrorCString());
}

void ApiError::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void LanguageError::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  AddCommonObjectProperties(&jsobj, "Error", ref);
  jsobj.AddProperty("kind", "LanguageError");
  jsobj.AddServiceId(*this);
  jsobj.AddProperty("message", ToErrorCString());
}

void LanguageError::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void UnhandledException::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  AddCommonObjectProperties(&jsobj, "Error", ref);
  jsobj.AddProperty("kind", "UnhandledException");
  jsobj.AddServiceId(*this);
  jsobj.AddProperty("message", ToErrorCString());
  if (ref) {
    return;
  }
  Instance& instance = Instance::Handle();
  instance = exception();
  jsobj.AddProperty("exception", instance);
  instance = stacktrace();
  jsobj.AddProperty("stacktrace", instance);
}

void UnhandledException::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void UnwindError::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  AddCommonObjectProperties(&jsobj, "Error", ref);
  jsobj.AddProperty("kind", "TerminationError");
  jsobj.AddServiceId(*this);
  jsobj.AddProperty("message", ToErrorCString());
  jsobj.AddProperty("_is_user_initiated", is_user_initiated());
}

void UnwindError::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void Instance::PrintSharedInstanceJSON(JSONObject* jsobj,
                                       bool ref,
                                       bool include_id) const {
  AddCommonObjectProperties(jsobj, "Instance", ref);
  {
    NoSafepointScope safepoint_scope;
    uint32_t hash_code = HeapSnapshotWriter::GetHeapSnapshotIdentityHash(
        Thread::Current(), ptr());
    jsobj->AddProperty64("identityHashCode", hash_code);
  }
  if (include_id) {
    jsobj->AddServiceId(*this);
  }
  if (ref) {
    return;
  }

  // Add all fields in layout order, from superclass to subclass.
  GrowableArray<Class*> classes;
  Class& cls = Class::Handle(this->clazz());
  if (IsClosure()) {
    // Closure fields are not instances. Skip them.
    cls = cls.SuperClass();
  }
  do {
    classes.Add(&Class::Handle(cls.ptr()));
    cls = cls.SuperClass();
  } while (!cls.IsNull());

  Array& field_array = Array::Handle();
  Field& field = Field::Handle();
  Object& field_value = Object::Handle();
  {
    JSONArray jsarr(jsobj, "fields");
    for (intptr_t i = classes.length() - 1; i >= 0; i--) {
      field_array = classes[i]->fields();
      if (!field_array.IsNull()) {
        for (intptr_t j = 0; j < field_array.Length(); j++) {
          field ^= field_array.At(j);
          if (!field.is_static()) {
            field_value = GetField(field);
            JSONObject jsfield(&jsarr);
            jsfield.AddProperty("decl", field);
            jsfield.AddProperty("name", field.UserVisibleNameCString());
            jsfield.AddProperty("value", field_value);
          }
        }
      }
    }
  }

  if (NumNativeFields() > 0) {
    JSONArray jsarr(jsobj, "_nativeFields");
    for (intptr_t i = 0; i < NumNativeFields(); i++) {
      intptr_t value = GetNativeField(i);
      JSONObject jsfield(&jsarr);
      jsfield.AddProperty("index", i);
      jsfield.AddProperty("value", value);
    }
  }
}

void Instance::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  PrintSharedInstanceJSON(&jsobj, ref);
  jsobj.AddProperty("kind", "PlainInstance");

  GrowableArray<Class*> classes;
  Array& field_array = Array::Handle();
  Field& field = Field::Handle();
  Class& cls = Class::Handle(this->clazz());
  if (IsClosure()) {
    // Closure fields are not instances. Skip them.
    cls = cls.SuperClass();
  }
  do {
    classes.Add(&Class::Handle(cls.ptr()));
    cls = cls.SuperClass();
  } while (!cls.IsNull());

  intptr_t num_fields = 0;
  for (intptr_t i = classes.length() - 1; i >= 0; --i) {
    field_array ^= classes[i]->fields();
    if (!field_array.IsNull()) {
      for (intptr_t j = 0; j < field_array.Length(); ++j) {
        field ^= field_array.At(j);
        if (!field.is_static()) {
          ++num_fields;
        }
      }
    }
  }
  jsobj.AddProperty("length", num_fields);
}

void Instance::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void AbstractType::PrintJSONImpl(JSONStream* stream, bool ref) const {
  UNREACHABLE();
}

void AbstractType::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {
  UNREACHABLE();
}

void Type::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  PrintSharedInstanceJSON(&jsobj, ref, /*include_id=*/false);
  jsobj.AddProperty("kind", "Type");
  const Class& type_cls = Class::Handle(type_class());
  if (type_cls.DeclarationType() == ptr()) {
    intptr_t cid = type_cls.id();
    jsobj.AddFixedServiceId("classes/%" Pd "/types/%d", cid, 0);
  } else {
    jsobj.AddServiceId(*this);
  }
  jsobj.AddProperty("typeClass", type_cls);
  const String& user_name = String::Handle(UserVisibleName());
  const String& vm_name = String::Handle(Name());
  AddNameProperties(&jsobj, user_name.ToCString(), vm_name.ToCString());
  if (ref) {
    return;
  }
  const TypeArguments& typeArgs = TypeArguments::Handle(arguments());
  if (!typeArgs.IsNull()) {
    jsobj.AddProperty("typeArguments", typeArgs);
  }
}

void Type::PrintImplementationFieldsImpl(const JSONArray& jsarr_fields) const {}

void FunctionType::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  PrintSharedInstanceJSON(&jsobj, ref);
  jsobj.AddProperty("kind", "FunctionType");
  AbstractType& type = AbstractType::Handle(result_type());
  jsobj.AddProperty("returnType", type);

  const int type_params_count = NumTypeParameters();
  if (type_params_count > 0) {
    JSONArray arr(&jsobj, "typeParameters");
    TypeParameter& type_param = TypeParameter::Handle();
    for (intptr_t i = 0; i < type_params_count; ++i) {
      type_param = TypeParameterAt(i);
      arr.AddValue(type_param);
    }
  }

  {
    JSONArray jsarr(&jsobj, "parameters");
    String& name = String::Handle();
    const intptr_t param_count = NumParameters();
    const intptr_t fixed_param_count = num_fixed_parameters();
    const bool has_named = HasOptionalNamedParameters();
    for (intptr_t i = 0; i < param_count; ++i) {
      JSONObject param(&jsarr);
      type = ParameterTypeAt(i);
      param.AddProperty("parameterType", type);
      bool fixed = i < fixed_param_count;
      param.AddProperty("fixed", fixed);
      if (!fixed && has_named) {
        name = ParameterNameAt(i);
        param.AddProperty("name", name.ToCString());
        param.AddProperty("required", IsRequiredAt(i));
      }
    }
  }
}

void FunctionType::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void RecordType::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  PrintSharedInstanceJSON(&jsobj, ref);
  jsobj.AddProperty("kind", "RecordType");
  if (ref) {
    return;
  }

  {
    JSONArray jsarr(&jsobj, "fields");
    String& name = String::Handle();
    AbstractType& type = AbstractType::Handle();
    const intptr_t num_fields = NumFields();
    const Array& field_names = Array::Handle(GetFieldNames(Thread::Current()));
    const intptr_t num_positional_fields = num_fields - field_names.Length();
    for (intptr_t index = 0; index < num_fields; ++index) {
      JSONObject jsfield(&jsarr);
      if (index < num_positional_fields) {
        jsfield.AddProperty("name", index + 1);
      } else {
        name ^= field_names.At(index - num_positional_fields);
        jsfield.AddProperty("name", name.ToCString());
      }
      type = FieldTypeAt(index);
      jsfield.AddProperty("value", type);
    }
  }
}

void RecordType::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void TypeParameter::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  PrintSharedInstanceJSON(&jsobj, ref);
  jsobj.AddProperty("kind", "TypeParameter");
  const String& user_name = String::Handle(UserVisibleName());
  const String& vm_name = String::Handle(Name());
  AddNameProperties(&jsobj, user_name.ToCString(), vm_name.ToCString());
  // TODO(regis): parameterizedClass is meaningless and always null.
  jsobj.AddProperty("parameterizedClass", Object::null_class());
  if (ref) {
    return;
  }
  jsobj.AddProperty("parameterIndex", index());
  const AbstractType& upper_bound = AbstractType::Handle(bound());
  jsobj.AddProperty("bound", upper_bound);
}

void TypeParameter::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void Number::PrintJSONImpl(JSONStream* stream, bool ref) const {
  UNREACHABLE();
}

void Number::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {
  UNREACHABLE();
}

void Integer::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  PrintSharedInstanceJSON(&jsobj, ref);
  jsobj.AddProperty("kind", "Int");
  jsobj.AddProperty("valueAsString", ToCString());
}

void Integer::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void Smi::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  PrintSharedInstanceJSON(&jsobj, ref, /*include_id=*/false);
  jsobj.AddProperty("kind", "Int");
  jsobj.AddFixedServiceId("objects/int-%" Pd "", Value());
  jsobj.AddPropertyF("valueAsString", "%" Pd "", Value());
}

void Smi::PrintImplementationFieldsImpl(const JSONArray& jsarr_fields) const {}

void Mint::PrintJSONImpl(JSONStream* stream, bool ref) const {
  Integer::PrintJSONImpl(stream, ref);
}

void Mint::PrintImplementationFieldsImpl(const JSONArray& jsarr_fields) const {}

void Double::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  PrintSharedInstanceJSON(&jsobj, ref);
  jsobj.AddProperty("kind", "Double");
  jsobj.AddProperty("valueAsString", ToCString());
}

void Double::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void String::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  PrintSharedInstanceJSON(&jsobj, ref);
  jsobj.AddProperty("kind", "String");
  jsobj.AddProperty("length", Length());
  if (ref) {
    // String refs always truncate to a fixed count;
    const intptr_t kFixedCount = 128;
    if (jsobj.AddPropertyStr("valueAsString", *this, 0, kFixedCount)) {
      jsobj.AddProperty("count", kFixedCount);
      jsobj.AddProperty("valueAsStringIsTruncated", true);
    }
    return;
  }

  intptr_t offset;
  intptr_t count;
  stream->ComputeOffsetAndCount(Length(), &offset, &count);
  if (offset > 0) {
    jsobj.AddProperty("offset", offset);
  }
  if (count < Length()) {
    jsobj.AddProperty("count", count);
  }
  jsobj.AddPropertyStr("valueAsString", *this, offset, count);
}

void String::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void Bool::PrintJSONImpl(JSONStream* stream, bool ref) const {
  const char* str = ToCString();
  JSONObject jsobj(stream);
  PrintSharedInstanceJSON(&jsobj, ref, /*include_id=*/false);
  jsobj.AddProperty("kind", "Bool");
  jsobj.AddFixedServiceId("objects/bool-%s", str);
  jsobj.AddPropertyF("valueAsString", "%s", str);
}

void Bool::PrintImplementationFieldsImpl(const JSONArray& jsarr_fields) const {}

void Array::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  PrintSharedInstanceJSON(&jsobj, ref);
  jsobj.AddProperty("kind", "List");
  jsobj.AddProperty("length", Length());
  if (ref) {
    return;
  }
  intptr_t offset;
  intptr_t count;
  stream->ComputeOffsetAndCount(Length(), &offset, &count);
  if (offset > 0) {
    jsobj.AddProperty("offset", offset);
  }
  if (count < Length()) {
    jsobj.AddProperty("count", count);
  }
  intptr_t limit = offset + count;
  ASSERT(limit <= Length());
  {
    JSONArray jsarr(&jsobj, "elements");
    Object& element = Object::Handle();
    for (intptr_t index = offset; index < limit; index++) {
      element = At(index);
      jsarr.AddValue(element);
    }
  }
}

void Array::PrintImplementationFieldsImpl(const JSONArray& jsarr_fields) const {
}

void GrowableObjectArray::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  PrintSharedInstanceJSON(&jsobj, ref);
  jsobj.AddProperty("kind", "List");
  jsobj.AddProperty("length", Length());
  if (ref) {
    return;
  }
  intptr_t offset;
  intptr_t count;
  stream->ComputeOffsetAndCount(Length(), &offset, &count);
  if (offset > 0) {
    jsobj.AddProperty("offset", offset);
  }
  if (count < Length()) {
    jsobj.AddProperty("count", count);
  }
  intptr_t limit = offset + count;
  ASSERT(limit <= Length());
  {
    JSONArray jsarr(&jsobj, "elements");
    Object& element = Object::Handle();
    for (intptr_t index = offset; index < limit; index++) {
      element = At(index);
      jsarr.AddValue(element);
    }
  }
}

void GrowableObjectArray::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void Map::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  PrintSharedInstanceJSON(&jsobj, ref);
  jsobj.AddProperty("kind", "Map");
  jsobj.AddProperty("length", Length());
  if (ref) {
    return;
  }
  intptr_t offset;
  intptr_t count;
  stream->ComputeOffsetAndCount(Length(), &offset, &count);
  if (offset > 0) {
    jsobj.AddProperty("offset", offset);
  }
  if (count < Length()) {
    jsobj.AddProperty("count", count);
  }
  intptr_t limit = offset + count;
  ASSERT(limit <= Length());
  {
    JSONArray jsarr(&jsobj, "associations");
    Object& object = Object::Handle();
    Map::Iterator iterator(*this);
    int i = 0;
    while (iterator.MoveNext() && i < limit) {
      if (i >= offset) {
        JSONObject jsassoc(&jsarr);
        object = iterator.CurrentKey();
        jsassoc.AddProperty("key", object);
        object = iterator.CurrentValue();
        jsassoc.AddProperty("value", object);
      }
      i++;
    }
  }
}

void Map::PrintImplementationFieldsImpl(const JSONArray& jsarr_fields) const {}

void Set::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  PrintSharedInstanceJSON(&jsobj, ref);
  jsobj.AddProperty("kind", "Set");
  jsobj.AddProperty("length", Length());
  if (ref) {
    return;
  }
  intptr_t offset;
  intptr_t count;
  stream->ComputeOffsetAndCount(Length(), &offset, &count);
  if (offset > 0) {
    jsobj.AddProperty("offset", offset);
  }
  if (count < Length()) {
    jsobj.AddProperty("count", count);
  }
  intptr_t limit = offset + count;
  ASSERT(limit <= Length());
  {
    JSONArray jsarr(&jsobj, "elements");
    Object& object = Object::Handle();
    Set::Iterator iterator(*this);
    int i = 0;
    while (iterator.MoveNext() && i < limit) {
      if (i >= offset) {
        object = iterator.CurrentKey();
        jsarr.AddValue(object);
      }
      i++;
    }
  }
}

void Set::PrintImplementationFieldsImpl(const JSONArray& jsarr_fields) const {}

void Float32x4::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  PrintSharedInstanceJSON(&jsobj, ref);
  jsobj.AddProperty("kind", "Float32x4");
  jsobj.AddProperty("valueAsString", ToCString());
}

void Float32x4::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void Int32x4::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  PrintSharedInstanceJSON(&jsobj, ref);
  jsobj.AddProperty("kind", "Int32x4");
  jsobj.AddProperty("valueAsString", ToCString());
}

void Int32x4::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void Float64x2::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  PrintSharedInstanceJSON(&jsobj, ref);
  jsobj.AddProperty("kind", "Float64x2");
  jsobj.AddProperty("valueAsString", ToCString());
}

void Float64x2::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void TypedDataBase::PrintJSONImpl(JSONStream* stream, bool ref) const {
  UNREACHABLE();
}

void TypedDataBase::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {
  UNREACHABLE();
}

void TypedData::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  PrintSharedInstanceJSON(&jsobj, ref);
  const Class& cls = Class::Handle(clazz());
  const char* kind = cls.UserVisibleNameCString();
  jsobj.AddProperty("kind", kind);
  jsobj.AddProperty("length", Length());
  if (ref) {
    return;
  }
  intptr_t offset;
  intptr_t count;
  stream->ComputeOffsetAndCount(Length(), &offset, &count);
  if (offset > 0) {
    jsobj.AddProperty("offset", offset);
  }
  if (count < Length()) {
    jsobj.AddProperty("count", count);
  }
  if (count == 0) {
    jsobj.AddProperty("bytes", "");
  } else {
    NoSafepointScope no_safepoint;
    jsobj.AddPropertyBase64("bytes",
                            reinterpret_cast<const uint8_t*>(
                                DataAddr(offset * ElementSizeInBytes())),
                            count * ElementSizeInBytes());
  }
}

void TypedData::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void TypedDataView::PrintJSONImpl(JSONStream* stream, bool ref) const {
  Instance::PrintJSONImpl(stream, ref);
}

void TypedDataView::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void ExternalTypedData::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  PrintSharedInstanceJSON(&jsobj, ref);
  const Class& cls = Class::Handle(clazz());
  const char* kind = cls.UserVisibleNameCString();
  jsobj.AddProperty("kind", kind);
  jsobj.AddProperty("length", Length());
  if (ref) {
    return;
  }
  intptr_t offset;
  intptr_t count;
  stream->ComputeOffsetAndCount(Length(), &offset, &count);
  if (offset > 0) {
    jsobj.AddProperty("offset", offset);
  }
  if (count < Length()) {
    jsobj.AddProperty("count", count);
  }
  if (count == 0) {
    jsobj.AddProperty("bytes", "");
  } else {
    NoSafepointScope no_safepoint;
    jsobj.AddPropertyBase64("bytes",
                            reinterpret_cast<const uint8_t*>(
                                DataAddr(offset * ElementSizeInBytes())),
                            count * ElementSizeInBytes());
  }
}

void ExternalTypedData::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void Pointer::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  PrintSharedInstanceJSON(&jsobj, ref);
  jsobj.AddProperty("kind", "Pointer");
  jsobj.AddPropertyF("valueAsString", "0x%" Px "", NativeAddress());
}

void Pointer::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void DynamicLibrary::PrintJSONImpl(JSONStream* stream, bool ref) const {
  Instance::PrintJSONImpl(stream, ref);
}

void DynamicLibrary::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void Capability::PrintJSONImpl(JSONStream* stream, bool ref) const {
  Instance::PrintJSONImpl(stream, ref);
}

void Capability::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void ReceivePort::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject obj(stream);
  Instance::PrintSharedInstanceJSON(&obj, ref);
  const StackTrace& allocation_location_ =
      StackTrace::Handle(allocation_location());
  const String& debug_name_ = String::Handle(debug_name());
  obj.AddProperty("kind", "ReceivePort");
  obj.AddProperty64("portId", Id());
  obj.AddProperty("debugName", debug_name_.ToCString());
  obj.AddProperty("allocationLocation", allocation_location_);
}

void ReceivePort::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void SendPort::PrintJSONImpl(JSONStream* stream, bool ref) const {
  Instance::PrintJSONImpl(stream, ref);
}

void SendPort::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void TransferableTypedData::PrintJSONImpl(JSONStream* stream, bool ref) const {
  Instance::PrintJSONImpl(stream, ref);
}

void TransferableTypedData::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void ClosureData::PrintJSONImpl(JSONStream* stream, bool ref) const {
  Object::PrintJSONImpl(stream, ref);
}

void ClosureData::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void Closure::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  PrintSharedInstanceJSON(&jsobj, ref);
  jsobj.AddProperty("kind", "Closure");
  const auto& func = Function::Handle(function());
  jsobj.AddProperty("closureFunction", func);
  if (!func.IsImplicitClosureFunction()) {
    jsobj.AddProperty("closureContext", Context::Handle(GetContext()));
  } else {
    jsobj.AddProperty("closureContext", Object::null_object());
  }
  if (func.IsImplicitInstanceClosureFunction()) {
    jsobj.AddProperty("closureReceiver",
                      Object::Handle(GetImplicitClosureReceiver()));
  } else {
    jsobj.AddProperty("closureReceiver", Object::null_object());
  }
  if (ref) {
    return;
  }

  Debugger* debugger = Isolate::Current()->debugger();
  Breakpoint* bpt = debugger->BreakpointAtActivation(*this);
  if (bpt != nullptr) {
    jsobj.AddProperty("_activationBreakpoint", bpt);
  }
}

void Closure::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void Record::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  PrintSharedInstanceJSON(&jsobj, ref);
  jsobj.AddProperty("kind", "Record");
  const intptr_t num_fields = this->num_fields();
  jsobj.AddProperty("length", num_fields);
  if (ref) {
    return;
  }

  {
    JSONArray jsarr(&jsobj, "fields");
    String& name = String::Handle();
    Object& value = Object::Handle();
    const Array& field_names = Array::Handle(GetFieldNames(Thread::Current()));
    const intptr_t num_positional_fields = num_fields - field_names.Length();
    for (intptr_t index = 0; index < num_fields; ++index) {
      JSONObject jsfield(&jsarr);
      if (index < num_positional_fields) {
        jsfield.AddProperty("name", index + 1);
      } else {
        name ^= field_names.At(index - num_positional_fields);
        jsfield.AddProperty("name", name.ToCString());
      }
      value = FieldAt(index);
      jsfield.AddProperty("value", value);
    }
  }
}

void Record::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void StackTrace::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  PrintSharedInstanceJSON(&jsobj, ref);
  jsobj.AddProperty("kind", "StackTrace");
  jsobj.AddProperty("valueAsString", ToCString());
}

void StackTrace::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void RegExp::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  PrintSharedInstanceJSON(&jsobj, ref);
  jsobj.AddProperty("kind", "RegExp");

  jsobj.AddProperty("pattern", String::Handle(pattern()));

  if (ref) {
    return;
  }

  jsobj.AddProperty("isCaseSensitive", !IsIgnoreCase(flags()));
  jsobj.AddProperty("isMultiLine", IsMultiline(flags()));
  jsobj.AddProperty("isUnicode", IsUnicode(flags()));
  jsobj.AddProperty("isDotAll", IsDotAll(flags()));

  TypedData& bc = TypedData::Handle();
  bc = bytecode(/*is_one_byte=*/true, /*sticky=*/false);
  jsobj.AddProperty("_oneByteBytecode", bc);
  bc = bytecode(/*is_one_byte=*/false, /*sticky=*/false);
  jsobj.AddProperty("_twoByteBytecode", bc);
  bc = bytecode(/*is_one_byte=*/true, /*sticky=*/true);
  jsobj.AddProperty("_oneByteBytecodeSticky", bc);
  bc = bytecode(/*is_one_byte=*/false, /*sticky=*/true);
  jsobj.AddProperty("_twoByteBytecodeSticky", bc);
}

void RegExp::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void WeakProperty::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  PrintSharedInstanceJSON(&jsobj, ref);
  jsobj.AddProperty("kind", "WeakProperty");
  if (ref) {
    return;
  }

  const Object& key_handle = Object::Handle(key());
  jsobj.AddProperty("propertyKey", key_handle);
  const Object& value_handle = Object::Handle(value());
  jsobj.AddProperty("propertyValue", value_handle);
}

void WeakProperty::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void WeakReference::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  PrintSharedInstanceJSON(&jsobj, ref);
  jsobj.AddProperty("kind", "WeakReference");
  if (ref) {
    return;
  }

  const Object& target_handle = Object::Handle(target());
  jsobj.AddProperty("target", target_handle);
}

void WeakReference::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void FinalizerBase::PrintJSONImpl(JSONStream* stream, bool ref) const {
  UNREACHABLE();
}

void FinalizerBase::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {
  UNREACHABLE();
}

void Finalizer::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  PrintSharedInstanceJSON(&jsobj, ref);
  jsobj.AddProperty("kind", "Finalizer");
  if (ref) {
    return;
  }

  Object& object = Object::Handle();
  object = callback();
  jsobj.AddProperty("callback", object);
  object = all_entries();
  jsobj.AddProperty("allEntries", object);
  object = entries_collected();
  jsobj.AddProperty("_entriesCollected", object);
}

void Finalizer::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void NativeFinalizer::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  PrintSharedInstanceJSON(&jsobj, ref);
  jsobj.AddProperty("kind", "NativeFinalizer");
  if (ref) {
    return;
  }

  Object& object = Object::Handle();
  object = callback();
  jsobj.AddProperty("callbackAddress", object);
  object = all_entries();
  jsobj.AddProperty("allEntries", object);
  object = entries_collected();
  jsobj.AddProperty("_entriesCollected", object);
}

void NativeFinalizer::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void FinalizerEntry::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  PrintSharedInstanceJSON(&jsobj, ref);
  jsobj.AddProperty("kind", "FinalizerEntry");
  if (ref) {
    return;
  }

  Object& object = Object::Handle();
  object = value();
  jsobj.AddProperty("value", object);
  object = detach();
  jsobj.AddProperty("detach", object);
  object = token();
  jsobj.AddProperty("token", object);
  object = finalizer();
  jsobj.AddProperty("_finalizer", object);
  object = next();
  jsobj.AddProperty("_next", object);
}

void FinalizerEntry::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void MirrorReference::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  PrintSharedInstanceJSON(&jsobj, ref);
  jsobj.AddProperty("kind", "MirrorReference");

  if (ref) {
    return;
  }

  const Object& referent_handle = Object::Handle(referent());
  jsobj.AddProperty("mirrorReferent", referent_handle);
}

void MirrorReference::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void UserTag::PrintJSONImpl(JSONStream* stream, bool ref) const {
  JSONObject jsobj(stream);
  PrintSharedInstanceJSON(&jsobj, ref);
  jsobj.AddProperty("kind", "UserTag");

  String& tag_label = String::Handle(label());
  jsobj.AddProperty("label", tag_label.ToCString());
}

void UserTag::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void FutureOr::PrintJSONImpl(JSONStream* stream, bool ref) const {
  Instance::PrintJSONImpl(stream, ref);
}

void FutureOr::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

void SuspendState::PrintJSONImpl(JSONStream* stream, bool ref) const {
  Instance::PrintJSONImpl(stream, ref);
}

void SuspendState::PrintImplementationFieldsImpl(
    const JSONArray& jsarr_fields) const {}

#endif

}  // namespace dart
