text: // Copyright (c) 2013, 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/service.h"
  
  #include <memory>
  #include <utility>
  
  #include "include/dart_api.h"
  #include "include/dart_native_api.h"
  #include "platform/globals.h"
  
  #include "platform/unicode.h"
  #include "platform/utils.h"
  #include "vm/base64.h"
  #include "vm/canonical_tables.h"
  #include "vm/closure_functions_cache.h"
  #include "vm/compiler/jit/compiler.h"
  #include "vm/cpu.h"
  #include "vm/dart_api_impl.h"
  #include "vm/dart_api_message.h"
  #include "vm/dart_api_state.h"
  #include "vm/dart_entry.h"
  #include "vm/debugger.h"
  #include "vm/heap/safepoint.h"
  #include "vm/isolate.h"
  #include "vm/kernel_isolate.h"
  #include "vm/lockers.h"
  #include "vm/malloc_hooks.h"
  #include "vm/message.h"
  #include "vm/message_handler.h"
  #include "vm/native_arguments.h"
  #include "vm/native_entry.h"
  #include "vm/native_symbol.h"
  #include "vm/object.h"
  #include "vm/object_graph.h"
  #include "vm/object_id_ring.h"
  #include "vm/object_store.h"
  #include "vm/parser.h"
  #include "vm/port.h"
  #include "vm/profiler.h"
  #include "vm/profiler_service.h"
  #include "vm/raw_object_fields.h"
  #include "vm/resolver.h"
  #include "vm/reusable_handles.h"
  #include "vm/service_event.h"
  #include "vm/service_isolate.h"
  #include "vm/source_report.h"
  #include "vm/stack_frame.h"
  #include "vm/symbols.h"
  #include "vm/timeline.h"
  #include "vm/version.h"
  
  namespace dart {
  
  #define Z (T->zone())
  
  DECLARE_FLAG(bool, trace_service);
  DECLARE_FLAG(bool, trace_service_pause_events);
  DECLARE_FLAG(bool, profile_vm);
  DEFINE_FLAG(charp,
              vm_name,
              "vm",
              "The default name of this vm as reported by the VM service "
              "protocol");
  
  DEFINE_FLAG(bool,
              warn_on_pause_with_no_debugger,
              false,
              "Print a message when an isolate is paused but there is no "
              "debugger attached.");
  
  #ifndef PRODUCT
  // The name of this of this vm as reported by the VM service protocol.
  static char* vm_name = NULL;
  
  static const char* GetVMName() {
    if (vm_name == NULL) {
      return FLAG_vm_name;
    }
    return vm_name;
  }
  
  ServiceIdZone::ServiceIdZone() {}
  
  ServiceIdZone::~ServiceIdZone() {}
  
  RingServiceIdZone::RingServiceIdZone()
      : ring_(NULL), policy_(ObjectIdRing::kAllocateId) {}
  
  RingServiceIdZone::~RingServiceIdZone() {}
  
  void RingServiceIdZone::Init(ObjectIdRing* ring,
                               ObjectIdRing::IdPolicy policy) {
    ring_ = ring;
    policy_ = policy;
  }
  
  char* RingServiceIdZone::GetServiceId(const Object& obj) {
    ASSERT(ring_ != NULL);
    Thread* thread = Thread::Current();
    Zone* zone = thread->zone();
    ASSERT(zone != NULL);
    const intptr_t id = ring_->GetIdForObject(obj.ptr(), policy_);
    return zone->PrintToString("objects/%" Pd "", id);
  }
  
  // TODO(johnmccutchan): Unify embedder service handler lists and their APIs.
  EmbedderServiceHandler* Service::isolate_service_handler_head_ = NULL;
  EmbedderServiceHandler* Service::root_service_handler_head_ = NULL;
  struct ServiceMethodDescriptor;
  const ServiceMethodDescriptor* FindMethod(const char* method_name);
  
  // Support for streams defined in embedders.
  Dart_ServiceStreamListenCallback Service::stream_listen_callback_ = NULL;
  Dart_ServiceStreamCancelCallback Service::stream_cancel_callback_ = NULL;
  Dart_GetVMServiceAssetsArchive Service::get_service_assets_callback_ = NULL;
  Dart_EmbedderInformationCallback Service::embedder_information_callback_ = NULL;
  
  // These are the set of streams known to the core VM.
  StreamInfo Service::vm_stream("VM");
  StreamInfo Service::isolate_stream("Isolate");
  StreamInfo Service::debug_stream("Debug");
  StreamInfo Service::gc_stream("GC");
  StreamInfo Service::echo_stream("_Echo");
  StreamInfo Service::heapsnapshot_stream("HeapSnapshot");
  StreamInfo Service::logging_stream("Logging");
  StreamInfo Service::extension_stream("Extension");
  StreamInfo Service::timeline_stream("Timeline");
  StreamInfo Service::profiler_stream("Profiler");
  
  const uint8_t* Service::dart_library_kernel_ = NULL;
  intptr_t Service::dart_library_kernel_len_ = 0;
  
  static StreamInfo* const streams_[] = {
      &Service::vm_stream,       &Service::isolate_stream,
      &Service::debug_stream,    &Service::gc_stream,
      &Service::echo_stream,     &Service::heapsnapshot_stream,
      &Service::logging_stream,  &Service::extension_stream,
      &Service::timeline_stream, &Service::profiler_stream,
  };
  
  bool Service::ListenStream(const char* stream_id) {
    if (FLAG_trace_service) {
      OS::PrintErr("vm-service: starting stream '%s'\n", stream_id);
    }
    intptr_t num_streams = sizeof(streams_) / sizeof(streams_[0]);
    for (intptr_t i = 0; i < num_streams; i++) {
      if (strcmp(stream_id, streams_[i]->id()) == 0) {
        streams_[i]->set_enabled(true);
        return true;
      }
    }
    if (stream_listen_callback_ != nullptr) {
      Thread* T = Thread::Current();
      TransitionVMToNative transition(T);
      return (*stream_listen_callback_)(stream_id);
    }
    return false;
  }
  
  void Service::CancelStream(const char* stream_id) {
    if (FLAG_trace_service) {
      OS::PrintErr("vm-service: stopping stream '%s'\n", stream_id);
    }
    intptr_t num_streams = sizeof(streams_) / sizeof(streams_[0]);
    for (intptr_t i = 0; i < num_streams; i++) {
      if (strcmp(stream_id, streams_[i]->id()) == 0) {
        streams_[i]->set_enabled(false);
        return;
      }
    }
    if (stream_cancel_callback_ != nullptr) {
      Thread* T = Thread::Current();
      TransitionVMToNative transition(T);
      return (*stream_cancel_callback_)(stream_id);
    }
  }
  
  ObjectPtr Service::RequestAssets() {
    Thread* T = Thread::Current();
    Object& object = Object::Handle();
    {
      Api::Scope api_scope(T);
      Dart_Handle handle;
      {
        TransitionVMToNative transition(T);
        if (get_service_assets_callback_ == NULL) {
          return Object::null();
        }
        handle = get_service_assets_callback_();
        if (Dart_IsError(handle)) {
          Dart_PropagateError(handle);
        }
      }
      object = Api::UnwrapHandle(handle);
    }
    if (object.IsNull()) {
      return Object::null();
    }
    if (!object.IsTypedData()) {
      const String& error_message = String::Handle(
          String::New("An implementation of Dart_GetVMServiceAssetsArchive "
                      "should return a Uint8Array or null."));
      const Error& error = Error::Handle(ApiError::New(error_message));
      Exceptions::PropagateError(error);
      return Object::null();
    }
    const TypedData& typed_data = TypedData::Cast(object);
    if (typed_data.ElementSizeInBytes() != 1) {
      const String& error_message = String::Handle(
          String::New("An implementation of Dart_GetVMServiceAssetsArchive "
                      "should return a Uint8Array or null."));
      const Error& error = Error::Handle(ApiError::New(error_message));
      Exceptions::PropagateError(error);
      return Object::null();
    }
    return object.ptr();
  }
  
  static void PrintMissingParamError(JSONStream* js, const char* param) {
    js->PrintError(kInvalidParams, "%s expects the '%s' parameter", js->method(),
                   param);
  }
  
  static void PrintInvalidParamError(JSONStream* js, const char* param) {
    js->PrintError(kInvalidParams, "%s: invalid '%s' parameter: %s", js->method(),
                   param, js->LookupParam(param));
  }
  
  static void PrintUnrecognizedMethodError(JSONStream* js) {
    js->PrintError(kMethodNotFound, NULL);
  }
  
  static void PrintSuccess(JSONStream* js) {
    JSONObject jsobj(js);
    jsobj.AddProperty("type", "Success");
  }
  
  static bool CheckDebuggerDisabled(Thread* thread, JSONStream* js) {
  #if defined(DART_PRECOMPILED_RUNTIME)
    js->PrintError(kFeatureDisabled, "Debugger is disabled in AOT mode.");
    return true;
  #else
    if (thread->isolate()->debugger() == NULL) {
      js->PrintError(kFeatureDisabled, "Debugger is disabled.");
      return true;
    }
    return false;
  #endif
  }
  
  static bool CheckCompilerDisabled(Thread* thread, JSONStream* js) {
  #if defined(DART_PRECOMPILED_RUNTIME)
    js->PrintError(kFeatureDisabled, "Compiler is disabled in AOT mode.");
    return true;
  #else
    return false;
  #endif
  }
  
  static bool CheckProfilerDisabled(Thread* thread, JSONStream* js) {
    if (!FLAG_profiler) {
      js->PrintError(kFeatureDisabled, "Profiler is disabled.");
      return true;
    }
    return false;
  }
  
  static bool CheckNativeAllocationProfilerDisabled(Thread* thread,
                                                    JSONStream* js) {
    if (CheckProfilerDisabled(thread, js)) {
      return true;
    }
    if (!FLAG_profiler_native_memory) {
      js->PrintError(kFeatureDisabled, "Native memory profiling is disabled.");
      return true;
    }
    return false;
  }
  
  static bool GetIntegerId(const char* s, intptr_t* id, int base = 10) {
    if ((s == NULL) || (*s == '\0')) {
      // Empty string.
      return false;
    }
    if (id == NULL) {
      // No id pointer.
      return false;
    }
    intptr_t r = 0;
    char* end_ptr = NULL;
  #if defined(ARCH_IS_32_BIT)
    r = strtol(s, &end_ptr, base);
  #else
    r = strtoll(s, &end_ptr, base);
  #endif
    if (end_ptr == s) {
      // String was not advanced at all, cannot be valid.
      return false;
    }
    *id = r;
    return true;
  }
  
  static bool GetUnsignedIntegerId(const char* s, uintptr_t* id, int base = 10) {
    if ((s == NULL) || (*s == '\0')) {
      // Empty string.
      return false;
    }
    if (id == NULL) {
      // No id pointer.
      return false;
    }
    uintptr_t r = 0;
    char* end_ptr = NULL;
  #if defined(ARCH_IS_32_BIT)
    r = strtoul(s, &end_ptr, base);
  #else
    r = strtoull(s, &end_ptr, base);
  #endif
    if (end_ptr == s) {
      // String was not advanced at all, cannot be valid.
      return false;
    }
    *id = r;
    return true;
  }
  
  static bool GetInteger64Id(const char* s, int64_t* id, int base = 10) {
    if ((s == NULL) || (*s == '\0')) {
      // Empty string.
      return false;
    }
    if (id == NULL) {
      // No id pointer.
      return false;
    }
    int64_t r = 0;
    char* end_ptr = NULL;
    r = strtoll(s, &end_ptr, base);
    if (end_ptr == s) {
      // String was not advanced at all, cannot be valid.
      return false;
    }
    *id = r;
    return true;
  }
  
  // Scans the string until the '-' character. Returns pointer to string
  // at '-' character. Returns NULL if not found.
  static const char* ScanUntilDash(const char* s) {
    if ((s == NULL) || (*s == '\0')) {
      // Empty string.
      return NULL;
    }
    while (*s != '\0') {
      if (*s == '-') {
        return s;
      }
      s++;
    }
    return NULL;
  }
  
  static bool GetCodeId(const char* s, int64_t* timestamp, uword* address) {
    if ((s == NULL) || (*s == '\0')) {
      // Empty string.
      return false;
    }
    if ((timestamp == NULL) || (address == NULL)) {
      // Bad arguments.
      return false;
    }
    // Extract the timestamp.
    if (!GetInteger64Id(s, timestamp, 16) || (*timestamp < 0)) {
      return false;
    }
    s = ScanUntilDash(s);
    if (s == NULL) {
      return false;
    }
    // Skip the dash.
    s++;
    // Extract the PC.
    if (!GetUnsignedIntegerId(s, address, 16)) {
      return false;
    }
    return true;
  }
  
  // Verifies that |s| begins with |prefix| and then calls |GetIntegerId| on
  // the remainder of |s|.
  static bool GetPrefixedIntegerId(const char* s,
                                   const char* prefix,
                                   intptr_t* service_id) {
    if (s == NULL) {
      return false;
    }
    ASSERT(prefix != NULL);
    const intptr_t kInputLen = strlen(s);
    const intptr_t kPrefixLen = strlen(prefix);
    ASSERT(kPrefixLen > 0);
    if (kInputLen <= kPrefixLen) {
      return false;
    }
    if (strncmp(s, prefix, kPrefixLen) != 0) {
      return false;
    }
    // Prefix satisfied. Move forward.
    s += kPrefixLen;
    // Attempt to read integer id.
    return GetIntegerId(s, service_id);
  }
  
  static bool IsValidClassId(Isolate* isolate, intptr_t cid) {
    ASSERT(isolate != NULL);
    ClassTable* class_table = isolate->group()->class_table();
    ASSERT(class_table != NULL);
    return class_table->IsValidIndex(cid) && class_table->HasValidClassAt(cid);
  }
  
  static ClassPtr GetClassForId(Isolate* isolate, intptr_t cid) {
    ASSERT(isolate == Isolate::Current());
    ASSERT(isolate != NULL);
    ClassTable* class_table = isolate->group()->class_table();
    ASSERT(class_table != NULL);
    return class_table->At(cid);
  }
  
  // TODO(johnmccutchan): Split into separate file and write unit tests.
  class MethodParameter {
   public:
    MethodParameter(const char* name, bool required)
        : name_(name), required_(required) {}
  
    virtual ~MethodParameter() {}
  
    virtual bool Validate(const char* value) const { return true; }
  
    virtual bool ValidateObject(const Object& value) const { return true; }
  
    const char* name() const { return name_; }
  
    bool required() const { return required_; }
  
    virtual void PrintError(const char* name,
                            const char* value,
                            JSONStream* js) const {
      PrintInvalidParamError(js, name);
    }
  
    virtual void PrintErrorObject(const char* name,
                                  const Object& value,
                                  JSONStream* js) const {
      PrintInvalidParamError(js, name);
    }
  
   private:
    const char* name_;
    bool required_;
  };
  
  class DartStringParameter : public MethodParameter {
   public:
    DartStringParameter(const char* name, bool required)
        : MethodParameter(name, required) {}
  
    virtual bool ValidateObject(const Object& value) const {
      return value.IsString();
    }
  };
  
  class DartListParameter : public MethodParameter {
   public:
    DartListParameter(const char* name, bool required)
        : MethodParameter(name, required) {}
  
    virtual bool ValidateObject(const Object& value) const {
      return value.IsArray() || value.IsGrowableObjectArray();
    }
  };
  
  class NoSuchParameter : public MethodParameter {
   public:
    explicit NoSuchParameter(const char* name) : MethodParameter(name, false) {}
  
    virtual bool Validate(const char* value) const { return (value == NULL); }
  
    virtual bool ValidateObject(const Object& value) const {
      return value.IsNull();
    }
  };
  
  class BoolParameter : public MethodParameter {
   public:
    BoolParameter(const char* name, bool required)
        : MethodParameter(name, required) {}
  
    virtual bool Validate(const char* value) const {
      if (value == NULL) {
        return false;
      }
      return (strcmp("true", value) == 0) || (strcmp("false", value) == 0);
    }
  
    static bool Parse(const char* value, bool default_value = false) {
      if (value == NULL) {
        return default_value;
      }
      return strcmp("true", value) == 0;
    }
  };
  
  class UIntParameter : public MethodParameter {
   public:
    UIntParameter(const char* name, bool required)
        : MethodParameter(name, required) {}
  
    virtual bool Validate(const char* value) const {
      if (value == NULL) {
        return false;
      }
      for (const char* cp = value; *cp != '\0'; cp++) {
        if (*cp < '0' || *cp > '9') {
          return false;
        }
      }
      return true;
    }
  
    static uintptr_t Parse(const char* value) {
      if (value == NULL) {
        return -1;
      }
      char* end_ptr = NULL;
      uintptr_t result = strtoul(value, &end_ptr, 10);
      ASSERT(*end_ptr == '\0');  // Parsed full string
      return result;
    }
  };
  
  class Int64Parameter : public MethodParameter {
   public:
    Int64Parameter(const char* name, bool required)
        : MethodParameter(name, required) {}
  
    virtual bool Validate(const char* value) const {
      if (value == NULL) {
        return false;
      }
      for (const char* cp = value; *cp != '\0'; cp++) {
        if ((*cp < '0' || *cp > '9') && (*cp != '-')) {
          return false;
        }
      }
      return true;
    }
  
    static int64_t Parse(const char* value, int64_t default_value = -1) {
      if ((value == NULL) || (*value == '\0')) {
        return default_value;
      }
      char* end_ptr = NULL;
      int64_t result = strtoll(value, &end_ptr, 10);
      ASSERT(*end_ptr == '\0');  // Parsed full string
      return result;
    }
  };
  
  class UInt64Parameter : public MethodParameter {
   public:
    UInt64Parameter(const char* name, bool required)
        : MethodParameter(name, required) {}
  
    virtual bool Validate(const char* value) const {
      if (value == NULL) {
        return false;
      }
      for (const char* cp = value; *cp != '\0'; cp++) {
        if ((*cp < '0' || *cp > '9') && (*cp != '-')) {
          return false;
        }
      }
      return true;
    }
  
    static uint64_t Parse(const char* value, uint64_t default_value = 0) {
      if ((value == NULL) || (*value == '\0')) {
        return default_value;
      }
      char* end_ptr = NULL;
      uint64_t result = strtoull(value, &end_ptr, 10);
      ASSERT(*end_ptr == '\0');  // Parsed full string
      return result;
    }
  };
  
  class IdParameter : public MethodParameter {
   public:
    IdParameter(const char* name, bool required)
        : MethodParameter(name, required) {}
  
    virtual bool Validate(const char* value) const { return (value != NULL); }
  };
  
  class StringParameter : public MethodParameter {
   public:
    StringParameter(const char* name, bool required)
        : MethodParameter(name, required) {}
  
    virtual bool Validate(const char* value) const { return (value != NULL); }
  };
  
  class RunnableIsolateParameter : public MethodParameter {
   public:
    explicit RunnableIsolateParameter(const char* name)
        : MethodParameter(name, true) {}
  
    virtual bool Validate(const char* value) const {
      Isolate* isolate = Isolate::Current();
      return (value != NULL) && (isolate != NULL) && (isolate->is_runnable());
    }
  
    virtual void PrintError(const char* name,
                            const char* value,
                            JSONStream* js) const {
      js->PrintError(kIsolateMustBeRunnable,
                     "Isolate must be runnable before this request is made.");
    }
  };
  
  #define ISOLATE_PARAMETER new IdParameter("isolateId", true)
  #define ISOLATE_GROUP_PARAMETER new IdParameter("isolateGroupId", true)
  #define NO_ISOLATE_PARAMETER new NoSuchParameter("isolateId")
  #define RUNNABLE_ISOLATE_PARAMETER new RunnableIsolateParameter("isolateId")
  
  class EnumParameter : public MethodParameter {
   public:
    EnumParameter(const char* name, bool required, const char* const* enums)
        : MethodParameter(name, required), enums_(enums) {}
  
    virtual bool Validate(const char* value) const {
      if (value == NULL) {
        return true;
      }
      for (intptr_t i = 0; enums_[i] != NULL; i++) {
        if (strcmp(value, enums_[i]) == 0) {
          return true;
        }
      }
      return false;
    }
  
   private:
    const char* const* enums_;
  };
  
  // If the key is not found, this function returns the last element in the
  // values array. This can be used to encode the default value.
  template <typename T>
  T EnumMapper(const char* value, const char* const* enums, T* values) {
    ASSERT(value != NULL);
    intptr_t i = 0;
    for (i = 0; enums[i] != NULL; i++) {
      if (strcmp(value, enums[i]) == 0) {
        return values[i];
      }
    }
    // Default value.
    return values[i];
  }
  
  class EnumListParameter : public MethodParameter {
   public:
    EnumListParameter(const char* name, bool required, const char* const* enums)
        : MethodParameter(name, required), enums_(enums) {}
  
    virtual bool Validate(const char* value) const {
      return ElementCount(value) >= 0;
    }
  
    const char** Parse(Zone* zone, const char* value_in) const {
      const char* kJsonChars = " \t\r\n[,]";
  
      // Make a writeable copy of the value.
      char* value = zone->MakeCopyOfString(value_in);
      intptr_t element_count = ElementCount(value);
      intptr_t element_pos = 0;
  
      // Allocate our element array.  +1 for NULL terminator.
      char** elements = zone->Alloc<char*>(element_count + 1);
      elements[element_count] = NULL;
  
      // Parse the string destructively.  Build the list of elements.
      while (element_pos < element_count) {
        // Skip to the next element.
        value += strspn(value, kJsonChars);
  
        intptr_t len = strcspn(value, kJsonChars);
        ASSERT(len > 0);  // We rely on the parameter being validated already.
        value[len] = '\0';
        elements[element_pos++] = value;
  
        // Advance.  +1 for null terminator.
        value += (len + 1);
      }
      return const_cast<const char**>(elements);
    }
  
   private:
    // For now observatory enums are ascii letters plus underscore.
    static bool IsEnumChar(char c) {
      return (((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')) ||
              (c == '_'));
    }
  
    // Returns number of elements in the list.  -1 on parse error.
    intptr_t ElementCount(const char* value) const {
      const char* kJsonWhitespaceChars = " \t\r\n";
      if (value == NULL) {
        return -1;
      }
      const char* cp = value;
      cp += strspn(cp, kJsonWhitespaceChars);
      if (*cp++ != '[') {
        // Missing initial [.
        return -1;
      }
      bool closed = false;
      bool element_allowed = true;
      intptr_t element_count = 0;
      while (true) {
        // Skip json whitespace.
        cp += strspn(cp, kJsonWhitespaceChars);
        switch (*cp) {
          case '\0':
            return closed ? element_count : -1;
          case ']':
            closed = true;
            cp++;
            break;
          case ',':
            if (element_allowed) {
              return -1;
            }
            element_allowed = true;
            cp++;
            break;
          default:
            if (!element_allowed) {
              return -1;
            }
            bool valid_enum = false;
            const char* id_start = cp;
            while (IsEnumChar(*cp)) {
              cp++;
            }
            if (cp == id_start) {
              // Empty identifier, something like this [,].
              return -1;
            }
            intptr_t id_len = cp - id_start;
            if (enums_ != NULL) {
              for (intptr_t i = 0; enums_[i] != NULL; i++) {
                intptr_t len = strlen(enums_[i]);
                if (len == id_len && strncmp(id_start, enums_[i], len) == 0) {
                  element_count++;
                  valid_enum = true;
                  element_allowed = false;  // we need a comma first.
                  break;
                }
              }
            }
            if (!valid_enum) {
              return -1;
            }
            break;
        }
      }
    }
  
    const char* const* enums_;
  };
  
  typedef void (*ServiceMethodEntry)(Thread* thread, JSONStream* js);
  
  struct ServiceMethodDescriptor {
    const char* name;
    const ServiceMethodEntry entry;
    const MethodParameter* const* parameters;
  };
  
  // TODO(johnmccutchan): Do we reject unexpected parameters?
  static bool ValidateParameters(const MethodParameter* const* parameters,
                                 JSONStream* js) {
    if (parameters == NULL) {
      return true;
    }
    if (js->NumObjectParameters() > 0) {
      Object& value = Object::Handle();
      for (intptr_t i = 0; parameters[i] != NULL; i++) {
        const MethodParameter* parameter = parameters[i];
        const char* name = parameter->name();
        const bool required = parameter->required();
        value = js->LookupObjectParam(name);
        const bool has_parameter = !value.IsNull();
        if (required && !has_parameter) {
          PrintMissingParamError(js, name);
          return false;
        }
        if (has_parameter && !parameter->ValidateObject(value)) {
          parameter->PrintErrorObject(name, value, js);
          return false;
        }
      }
    } else {
      for (intptr_t i = 0; parameters[i] != NULL; i++) {
        const MethodParameter* parameter = parameters[i];
        const char* name = parameter->name();
        const bool required = parameter->required();
        const char* value = js->LookupParam(name);
        const bool has_parameter = (value != NULL);
        if (required && !has_parameter) {
          PrintMissingParamError(js, name);
          return false;
        }
        if (has_parameter && !parameter->Validate(value)) {
          parameter->PrintError(name, value, js);
          return false;
        }
      }
    }
    return true;
  }
  
  void Service::PostError(const String& method_name,
                          const Array& parameter_keys,
                          const Array& parameter_values,
                          const Instance& reply_port,
                          const Instance& id,
                          const Error& error) {
    Thread* T = Thread::Current();
    StackZone zone(T);
    HANDLESCOPE(T);
    JSONStream js;
    js.Setup(zone.GetZone(), SendPort::Cast(reply_port).Id(), id, method_name,
             parameter_keys, parameter_values);
    js.PrintError(kExtensionError, "Error in extension `%s`: %s", js.method(),
                  error.ToErrorCString());
    js.PostReply();
  }
  
  ErrorPtr Service::InvokeMethod(Isolate* I,
                                 const Array& msg,
                                 bool parameters_are_dart_objects) {
    Thread* T = Thread::Current();
    ASSERT(I == T->isolate());
    ASSERT(I != NULL);
    ASSERT(T->execution_state() == Thread::kThreadInVM);
    ASSERT(!msg.IsNull());
    ASSERT(msg.Length() == 6);
  
    {
      StackZone zone(T);
      HANDLESCOPE(T);
  
      Instance& reply_port = Instance::Handle(Z);
      Instance& seq = String::Handle(Z);
      String& method_name = String::Handle(Z);
      Array& param_keys = Array::Handle(Z);
      Array& param_values = Array::Handle(Z);
      reply_port ^= msg.At(1);
      seq ^= msg.At(2);
      method_name ^= msg.At(3);
      param_keys ^= msg.At(4);
      param_values ^= msg.At(5);
  
      ASSERT(!method_name.IsNull());
      ASSERT(seq.IsNull() || seq.IsString() || seq.IsNumber());
      ASSERT(!param_keys.IsNull());
      ASSERT(!param_values.IsNull());
      ASSERT(param_keys.Length() == param_values.Length());
  
      // We expect a reply port unless there is a null sequence id,
      // which indicates that no reply should be sent.  We use this in
      // tests.
      if (!seq.IsNull() && !reply_port.IsSendPort()) {
        FATAL("SendPort expected.");
      }
  
      JSONStream js;
      Dart_Port reply_port_id =
          (reply_port.IsNull() ? ILLEGAL_PORT : SendPort::Cast(reply_port).Id());
      js.Setup(zone.GetZone(), reply_port_id, seq, method_name, param_keys,
               param_values, parameters_are_dart_objects);
  
      // RPC came in with a custom service id zone.
      const char* id_zone_param = js.LookupParam("_idZone");
  
      if (id_zone_param != NULL) {
        // Override id zone.
        if (strcmp("default", id_zone_param) == 0) {
          // Ring with eager id allocation. This is the default ring and default
          // policy.
          // Nothing to do.
        } else if (strcmp("default.reuse", id_zone_param) == 0) {
          // Change the default ring's policy.
          RingServiceIdZone* zone =
              reinterpret_cast<RingServiceIdZone*>(js.id_zone());
          zone->set_policy(ObjectIdRing::kReuseId);
        } else {
          // TODO(johnmccutchan): Support creating, deleting, and selecting
          // custom service id zones.
          // For now, always return an error.
          PrintInvalidParamError(&js, "_idZone");
          js.PostReply();
          return T->StealStickyError();
        }
      }
      const char* c_method_name = method_name.ToCString();
  
      const ServiceMethodDescriptor* method = FindMethod(c_method_name);
      if (method != NULL) {
        if (!ValidateParameters(method->parameters, &js)) {
          js.PostReply();
          return T->StealStickyError();
        }
        method->entry(T, &js);
        js.PostReply();
        return T->StealStickyError();
      }
  
      EmbedderServiceHandler* handler = FindIsolateEmbedderHandler(c_method_name);
      if (handler == NULL) {
        handler = FindRootEmbedderHandler(c_method_name);
      }
  
      if (handler != NULL) {
        EmbedderHandleMessage(handler, &js);
        return T->StealStickyError();
      }
  
      const Instance& extension_handler =
          Instance::Handle(Z, I->LookupServiceExtensionHandler(method_name));
      if (!extension_handler.IsNull()) {
        ScheduleExtensionHandler(extension_handler, method_name, param_keys,
                                 param_values, reply_port, seq);
        // Schedule was successful. Extension code will post a reply
        // asynchronously.
        return T->StealStickyError();
      }
  
      PrintUnrecognizedMethodError(&js);
      js.PostReply();
      return T->StealStickyError();
    }
  }
  
  ErrorPtr Service::HandleRootMessage(const Array& msg_instance) {
    Isolate* isolate = Isolate::Current();
    return InvokeMethod(isolate, msg_instance);
  }
  
  ErrorPtr Service::HandleObjectRootMessage(const Array& msg_instance) {
    Isolate* isolate = Isolate::Current();
    return InvokeMethod(isolate, msg_instance, true);
  }
  
  ErrorPtr Service::HandleIsolateMessage(Isolate* isolate, const Array& msg) {
    ASSERT(isolate != NULL);
    const Error& error = Error::Handle(InvokeMethod(isolate, msg));
    return MaybePause(isolate, error);
  }
  
  static void Finalizer(void* isolate_callback_data, void* buffer) {
    free(buffer);
  }
  
  void Service::SendEvent(const char* stream_id,
                          const char* event_type,
                          uint8_t* bytes,
                          intptr_t bytes_length) {
    Thread* thread = Thread::Current();
    Isolate* isolate = thread->isolate();
    ASSERT(isolate != NULL);
  
    if (FLAG_trace_service) {
      OS::PrintErr(
          "vm-service: Pushing ServiceEvent(isolate='%s', "
          "isolateId='" ISOLATE_SERVICE_ID_FORMAT_STRING
          "', kind='%s',"
          " len=%" Pd ") to stream %s\n",
          isolate->name(), static_cast<int64_t>(isolate->main_port()), event_type,
          bytes_length, stream_id);
    }
  
    bool result;
    {
      Dart_CObject cbytes;
      cbytes.type = Dart_CObject_kExternalTypedData;
      cbytes.value.as_external_typed_data.type = Dart_TypedData_kUint8;
      cbytes.value.as_external_typed_data.length = bytes_length;
      cbytes.value.as_external_typed_data.data = bytes;
      cbytes.value.as_external_typed_data.peer = bytes;
      cbytes.value.as_external_typed_data.callback = Finalizer;
  
      Dart_CObject cstream_id;
      cstream_id.type = Dart_CObject_kString;
      cstream_id.value.as_string = const_cast<char*>(stream_id);
  
      Dart_CObject* elements[2];
      elements[0] = &cstream_id;
      elements[1] = &cbytes;
      Dart_CObject message;
      message.type = Dart_CObject_kArray;
      message.value.as_array.length = 2;
      message.value.as_array.values = elements;
  
      ApiMessageWriter writer;
      std::unique_ptr<Message> msg = writer.WriteCMessage(
          &message, ServiceIsolate::Port(), Message::kNormalPriority);
      if (msg == nullptr) {
        result = false;
      } else {
        result = PortMap::PostMessage(std::move(msg));
      }
    }
  
    if (!result) {
      free(bytes);
    }
  }
  
  void Service::SendEventWithData(const char* stream_id,
                                  const char* event_type,
                                  intptr_t reservation,
                                  const char* metadata,
                                  intptr_t metadata_size,
                                  uint8_t* data,
                                  intptr_t data_size) {
    ASSERT(kInt32Size + metadata_size <= reservation);
    // Using a SPACE creates valid JSON. Our goal here is to prevent the memory
    // overhead of copying to concatenate metadata and payload together by
    // over-allocating to underlying buffer before we know how long the metadata
    // will be.
    memset(data, ' ', reservation);
    reinterpret_cast<uint32_t*>(data)[0] = reservation;
    memmove(&(reinterpret_cast<uint32_t*>(data)[1]), metadata, metadata_size);
    Service::SendEvent(stream_id, event_type, data, data_size);
  }
  
  static void ReportPauseOnConsole(ServiceEvent* event) {
    const char* name = event->isolate()->name();
    const int64_t main_port = static_cast<int64_t>(event->isolate()->main_port());
    switch (event->kind()) {
      case ServiceEvent::kPauseStart:
        OS::PrintErr("vm-service: isolate(%" Pd64
                     ") '%s' has no debugger attached and is paused at start.",
                     main_port, name);
        break;
      case ServiceEvent::kPauseExit:
        OS::PrintErr("vm-service: isolate(%" Pd64
                     ")  '%s' has no debugger attached and is paused at exit.",
                     main_port, name);
        break;
      case ServiceEvent::kPauseException:
        OS::PrintErr(
            "vm-service: isolate (%" Pd64
            ") '%s' has no debugger attached and is paused due to exception.",
            main_port, name);
        break;
      case ServiceEvent::kPauseInterrupted:
        OS::PrintErr(
            "vm-service: isolate (%" Pd64
            ") '%s' has no debugger attached and is paused due to interrupt.",
            main_port, name);
        break;
      case ServiceEvent::kPauseBreakpoint:
        OS::PrintErr("vm-service: isolate (%" Pd64
                     ") '%s' has no debugger attached and is paused.",
                     main_port, name);
        break;
      case ServiceEvent::kPausePostRequest:
        OS::PrintErr("vm-service: isolate (%" Pd64
                     ") '%s' has no debugger attached and is paused post reload.",
                     main_port, name);
        break;
      default:
        UNREACHABLE();
        break;
    }
    if (!ServiceIsolate::IsRunning()) {
      OS::PrintErr("  Start the vm-service to debug.\n");
    } else if (ServiceIsolate::server_address() == NULL) {
      OS::PrintErr("  Connect to Observatory to debug.\n");
    } else {
      OS::PrintErr("  Connect to Observatory at %s to debug.\n",
                   ServiceIsolate::server_address());
    }
    const Error& err = Error::Handle(Thread::Current()->sticky_error());
    if (!err.IsNull()) {
      OS::PrintErr("%s\n", err.ToErrorCString());
    }
  }
  
  void Service::HandleEvent(ServiceEvent* event) {
    if (event->stream_info() != NULL && !event->stream_info()->enabled()) {
      if (FLAG_warn_on_pause_with_no_debugger && event->IsPause()) {
        // If we are about to pause a running program which has no
        // debugger connected, tell the user about it.
        ReportPauseOnConsole(event);
      }
      // Ignore events when no one is listening to the event stream.
      return;
    } else if (event->stream_info() != NULL &&
               FLAG_warn_on_pause_with_no_debugger && event->IsPause()) {
      ReportPauseOnConsole(event);
    }
    if (!ServiceIsolate::IsRunning()) {
      return;
    }
    JSONStream js;
    const char* stream_id = event->stream_id();
    ASSERT(stream_id != NULL);
    {
      JSONObject jsobj(&js);
      jsobj.AddProperty("jsonrpc", "2.0");
      jsobj.AddProperty("method", "streamNotify");
      JSONObject params(&jsobj, "params");
      params.AddProperty("streamId", stream_id);
      params.AddProperty("event", event);
    }
    PostEvent(event->isolate(), stream_id, event->KindAsCString(), &js);
  
    // Post event to the native Service Stream handlers if set.
    if (event->stream_info() != nullptr &&
        event->stream_info()->consumer() != nullptr) {
      auto length = js.buffer()->length();
      event->stream_info()->consumer()(
          reinterpret_cast<uint8_t*>(js.buffer()->buffer()), length);
    }
  }
  
  void Service::PostEvent(Isolate* isolate,
                          const char* stream_id,
                          const char* kind,
                          JSONStream* event) {
    ASSERT(stream_id != NULL);
    ASSERT(kind != NULL);
    ASSERT(event != NULL);
  
    if (FLAG_trace_service) {
      if (isolate != NULL) {
        OS::PrintErr(
            "vm-service: Pushing ServiceEvent(isolate='%s', "
            "isolateId='" ISOLATE_SERVICE_ID_FORMAT_STRING
            "', kind='%s') to stream %s\n",
            isolate->name(), static_cast<int64_t>(isolate->main_port()), kind,
            stream_id);
      } else {
        OS::PrintErr(
            "vm-service: Pushing ServiceEvent(isolate='<no current isolate>', "
            "kind='%s') to stream %s\n",
            kind, stream_id);
      }
    }
  
    // Message is of the format [<stream id>, <json string>].
    //
    // Build the event message in the C heap to avoid dart heap
    // allocation.  This method can be called while we have acquired a
    // direct pointer to typed data, so we can't allocate here.
    Dart_CObject list_cobj;
    Dart_CObject* list_values[2];
    list_cobj.type = Dart_CObject_kArray;
    list_cobj.value.as_array.length = 2;
    list_cobj.value.as_array.values = list_values;
  
    Dart_CObject stream_id_cobj;
    stream_id_cobj.type = Dart_CObject_kString;
    stream_id_cobj.value.as_string = const_cast<char*>(stream_id);
    list_values[0] = &stream_id_cobj;
  
    Dart_CObject json_cobj;
    json_cobj.type = Dart_CObject_kString;
    json_cobj.value.as_string = const_cast<char*>(event->ToCString());
    list_values[1] = &json_cobj;
  
    ApiMessageWriter writer;
    std::unique_ptr<Message> msg = writer.WriteCMessage(
        &list_cobj, ServiceIsolate::Port(), Message::kNormalPriority);
    if (msg != nullptr) {
      PortMap::PostMessage(std::move(msg));
    }
  }
  
  class EmbedderServiceHandler {
   public:
    explicit EmbedderServiceHandler(const char* name)
        : name_(NULL), callback_(NULL), user_data_(NULL), next_(NULL) {
      ASSERT(name != NULL);
      name_ = Utils::StrDup(name);
    }
  
    ~EmbedderServiceHandler() { free(name_); }
  
    const char* name() const { return name_; }
  
    Dart_ServiceRequestCallback callback() const { return callback_; }
    void set_callback(Dart_ServiceRequestCallback callback) {
      callback_ = callback;
    }
  
    void* user_data() const { return user_data_; }
    void set_user_data(void* user_data) { user_data_ = user_data; }
  
    EmbedderServiceHandler* next() const { return next_; }
    void set_next(EmbedderServiceHandler* next) { next_ = next; }
  
   private:
    char* name_;
    Dart_ServiceRequestCallback callback_;
    void* user_data_;
    EmbedderServiceHandler* next_;
  };
  
  void Service::EmbedderHandleMessage(EmbedderServiceHandler* handler,
                                      JSONStream* js) {
    ASSERT(handler != NULL);
    Dart_ServiceRequestCallback callback = handler->callback();
    ASSERT(callback != NULL);
    const char* response = NULL;
    bool success;
    {
      TransitionVMToNative transition(Thread::Current());
      success = callback(js->method(), js->param_keys(), js->param_values(),
                         js->num_params(), handler->user_data(), &response);
    }
    ASSERT(response != NULL);
    if (!success) {
      js->SetupError();
    }
    js->buffer()->AddString(response);
    js->PostReply();
    free(const_cast<char*>(response));
  }
  
  void Service::RegisterIsolateEmbedderCallback(
      const char* name,
      Dart_ServiceRequestCallback callback,
      void* user_data) {
    if (name == NULL) {
      return;
    }
    EmbedderServiceHandler* handler = FindIsolateEmbedderHandler(name);
    if (handler != NULL) {
      // Update existing handler entry.
      handler->set_callback(callback);
      handler->set_user_data(user_data);
      return;
    }
    // Create a new handler.
    handler = new EmbedderServiceHandler(name);
    handler->set_callback(callback);
    handler->set_user_data(user_data);
  
    // Insert into isolate_service_handler_head_ list.
    handler->set_next(isolate_service_handler_head_);
    isolate_service_handler_head_ = handler;
  }
  
  EmbedderServiceHandler* Service::FindIsolateEmbedderHandler(const char* name) {
    EmbedderServiceHandler* current = isolate_service_handler_head_;
    while (current != NULL) {
      if (strcmp(name, current->name()) == 0) {
        return current;
      }
      current = current->next();
    }
    return NULL;
  }
  
  void Service::RegisterRootEmbedderCallback(const char* name,
                                             Dart_ServiceRequestCallback callback,
                                             void* user_data) {
    if (name == NULL) {
      return;
    }
    EmbedderServiceHandler* handler = FindRootEmbedderHandler(name);
    if (handler != NULL) {
      // Update existing handler entry.
      handler->set_callback(callback);
      handler->set_user_data(user_data);
      return;
    }
    // Create a new handler.
    handler = new EmbedderServiceHandler(name);
    handler->set_callback(callback);
    handler->set_user_data(user_data);
  
    // Insert into root_service_handler_head_ list.
    handler->set_next(root_service_handler_head_);
    root_service_handler_head_ = handler;
  }
  
  void Service::SetEmbedderStreamCallbacks(
      Dart_ServiceStreamListenCallback listen_callback,
      Dart_ServiceStreamCancelCallback cancel_callback) {
    stream_listen_callback_ = listen_callback;
    stream_cancel_callback_ = cancel_callback;
  }
  
  void Service::SetNativeServiceStreamCallback(Dart_NativeStreamConsumer consumer,
                                               const char* stream_id) {
    for (auto stream : streams_) {
      if (stream->id() == stream_id) {
        stream->set_consumer(consumer);
      }
    }
    // Enable stream.
    ListenStream(stream_id);
  }
  
  void Service::SetGetServiceAssetsCallback(
      Dart_GetVMServiceAssetsArchive get_service_assets) {
    get_service_assets_callback_ = get_service_assets;
  }
  
  void Service::SetEmbedderInformationCallback(
      Dart_EmbedderInformationCallback callback) {
    embedder_information_callback_ = callback;
  }
  
  int64_t Service::CurrentRSS() {
    if (embedder_information_callback_ == NULL) {
      return -1;
    }
    Dart_EmbedderInformation info = {
        0,     // version
        NULL,  // name
        0,     // max_rss
        0      // current_rss
    };
    embedder_information_callback_(&info);
    ASSERT(info.version == DART_EMBEDDER_INFORMATION_CURRENT_VERSION);
    return info.current_rss;
  }
  
  int64_t Service::MaxRSS() {
    if (embedder_information_callback_ == NULL) {
      return -1;
    }
    Dart_EmbedderInformation info = {
        0,     // version
        NULL,  // name
        0,     // max_rss
        0      // current_rss
    };
    embedder_information_callback_(&info);
    ASSERT(info.version == DART_EMBEDDER_INFORMATION_CURRENT_VERSION);
    return info.max_rss;
  }
  
  void Service::SetDartLibraryKernelForSources(const uint8_t* kernel_bytes,
                                               intptr_t kernel_length) {
    dart_library_kernel_ = kernel_bytes;
    dart_library_kernel_len_ = kernel_length;
  }
  
  EmbedderServiceHandler* Service::FindRootEmbedderHandler(const char* name) {
    EmbedderServiceHandler* current = root_service_handler_head_;
    while (current != NULL) {
      if (strcmp(name, current->name()) == 0) {
        return current;
      }
      current = current->next();
    }
    return NULL;
  }
  
  void Service::ScheduleExtensionHandler(const Instance& handler,
                                         const String& method_name,
                                         const Array& parameter_keys,
                                         const Array& parameter_values,
                                         const Instance& reply_port,
                                         const Instance& id) {
    ASSERT(!handler.IsNull());
    ASSERT(!method_name.IsNull());
    ASSERT(!parameter_keys.IsNull());
    ASSERT(!parameter_values.IsNull());
    ASSERT(!reply_port.IsNull());
    Isolate* isolate = Isolate::Current();
    ASSERT(isolate != NULL);
    isolate->AppendServiceExtensionCall(handler, method_name, parameter_keys,
                                        parameter_values, reply_port, id);
  }
  
  static const MethodParameter* const get_isolate_params[] = {
      ISOLATE_PARAMETER,
      NULL,
  };
  
  static void GetIsolate(Thread* thread, JSONStream* js) {
    thread->isolate()->PrintJSON(js, false);
  }
  
  static const MethodParameter* const get_isolate_group_params[] = {
      ISOLATE_GROUP_PARAMETER,
      NULL,
  };
  
  enum SentinelType {
    kCollectedSentinel,
    kExpiredSentinel,
    kFreeSentinel,
  };
  
  static void PrintSentinel(JSONStream* js, SentinelType sentinel_type) {
    JSONObject jsobj(js);
    jsobj.AddProperty("type", "Sentinel");
    switch (sentinel_type) {
      case kCollectedSentinel:
        jsobj.AddProperty("kind", "Collected");
        jsobj.AddProperty("valueAsString", "<collected>");
        break;
      case kExpiredSentinel:
        jsobj.AddProperty("kind", "Expired");
        jsobj.AddProperty("valueAsString", "<expired>");
        break;
      case kFreeSentinel:
        jsobj.AddProperty("kind", "Free");
        jsobj.AddProperty("valueAsString", "<free>");
        break;
      default:
        UNIMPLEMENTED();
        break;
    }
  }
  
  static void ActOnIsolateGroup(JSONStream* js,
                                std::function<void(IsolateGroup*)> visitor) {
    const String& prefix =
        String::Handle(String::New(ISOLATE_GROUP_SERVICE_ID_PREFIX));
  
    const String& s =
        String::Handle(String::New(js->LookupParam("isolateGroupId")));
    if (!s.StartsWith(prefix)) {
      PrintInvalidParamError(js, "isolateGroupId");
      return;
    }
    uint64_t isolate_group_id = UInt64Parameter::Parse(
        String::Handle(String::SubString(s, prefix.Length())).ToCString());
    IsolateGroup::RunWithIsolateGroup(
        isolate_group_id,
        [&visitor](IsolateGroup* isolate_group) { visitor(isolate_group); },
        /*if_not_found=*/[&js]() { PrintSentinel(js, kExpiredSentinel); });
  }
  
  static void GetIsolateGroup(Thread* thread, JSONStream* js) {
    ActOnIsolateGroup(js, [&](IsolateGroup* isolate_group) {
      isolate_group->PrintJSON(js, false);
    });
  }
  
  static const MethodParameter* const get_memory_usage_params[] = {
      ISOLATE_PARAMETER,
      NULL,
  };
  
  static void GetMemoryUsage(Thread* thread, JSONStream* js) {
    thread->isolate()->PrintMemoryUsageJSON(js);
  }
  
  static const MethodParameter* const get_isolate_group_memory_usage_params[] = {
      ISOLATE_GROUP_PARAMETER,
      NULL,
  };
  
  static void GetIsolateGroupMemoryUsage(Thread* thread, JSONStream* js) {
    ActOnIsolateGroup(js, [&](IsolateGroup* isolate_group) {
      isolate_group->PrintMemoryUsageJSON(js);
    });
  }
  
  static const MethodParameter* const get_scripts_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      NULL,
  };
  
  static void GetScripts(Thread* thread, JSONStream* js) {
    auto object_store = thread->isolate_group()->object_store();
    Zone* zone = thread->zone();
  
    const auto& libs =
        GrowableObjectArray::Handle(zone, object_store->libraries());
    intptr_t num_libs = libs.Length();
  
    Library& lib = Library::Handle(zone);
    Array& scripts = Array::Handle(zone);
    Script& script = Script::Handle(zone);
  
    JSONObject jsobj(js);
    {
      jsobj.AddProperty("type", "ScriptList");
      JSONArray script_array(&jsobj, "scripts");
      for (intptr_t i = 0; i < num_libs; i++) {
        lib ^= libs.At(i);
        ASSERT(!lib.IsNull());
        scripts = lib.LoadedScripts();
        for (intptr_t j = 0; j < scripts.Length(); j++) {
          script ^= scripts.At(j);
          ASSERT(!script.IsNull());
          script_array.AddValue(script);
        }
      }
    }
  }
  
  static const MethodParameter* const get_stack_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      new UIntParameter("limit", false),
      NULL,
  };
  
  static void GetStack(Thread* thread, JSONStream* js) {
    if (CheckDebuggerDisabled(thread, js)) {
      return;
    }
    intptr_t limit = 0;
    bool has_limit = js->HasParam("limit");
    if (has_limit) {
      limit = UIntParameter::Parse(js->LookupParam("limit"));
      if (limit < 0) {
        PrintInvalidParamError(js, "limit");
        return;
      }
    }
    Isolate* isolate = thread->isolate();
    DebuggerStackTrace* stack = isolate->debugger()->StackTrace();
    DebuggerStackTrace* async_causal_stack =
        isolate->debugger()->AsyncCausalStackTrace();
    DebuggerStackTrace* awaiter_stack = isolate->debugger()->AwaiterStackTrace();
    // Do we want the complete script object and complete local variable objects?
    // This is true for dump requests.
    JSONObject jsobj(js);
    jsobj.AddProperty("type", "Stack");
    {
      JSONArray jsarr(&jsobj, "frames");
  
      intptr_t num_frames =
          has_limit ? Utils::Minimum(stack->Length(), limit) : stack->Length();
  
      for (intptr_t i = 0; i < num_frames; i++) {
        ActivationFrame* frame = stack->FrameAt(i);
        JSONObject jsobj(&jsarr);
        frame->PrintToJSONObject(&jsobj);
        jsobj.AddProperty("index", i);
      }
    }
  
    if (async_causal_stack != NULL) {
      JSONArray jsarr(&jsobj, "asyncCausalFrames");
      intptr_t num_frames =
          has_limit ? Utils::Minimum(async_causal_stack->Length(), limit)
                    : async_causal_stack->Length();
      for (intptr_t i = 0; i < num_frames; i++) {
        ActivationFrame* frame = async_causal_stack->FrameAt(i);
        JSONObject jsobj(&jsarr);
        frame->PrintToJSONObject(&jsobj);
        jsobj.AddProperty("index", i);
      }
    }
  
    if (awaiter_stack != NULL) {
      JSONArray jsarr(&jsobj, "awaiterFrames");
      intptr_t num_frames = has_limit
                                ? Utils::Minimum(awaiter_stack->Length(), limit)
                                : awaiter_stack->Length();
      for (intptr_t i = 0; i < num_frames; i++) {
        ActivationFrame* frame = awaiter_stack->FrameAt(i);
        JSONObject jsobj(&jsarr);
        frame->PrintToJSONObject(&jsobj);
        jsobj.AddProperty("index", i);
      }
    }
  
    const bool truncated =
        (has_limit &&
         (limit < stack->Length() ||
          (async_causal_stack != nullptr &&
           limit < async_causal_stack->Length()) ||
          (awaiter_stack != nullptr && limit < awaiter_stack->Length())));
    jsobj.AddProperty("truncated", truncated);
  
    {
      MessageHandler::AcquiredQueues aq(isolate->message_handler());
      jsobj.AddProperty("messages", aq.queue());
    }
  }
  
  static void HandleCommonEcho(JSONObject* jsobj, JSONStream* js) {
    jsobj->AddProperty("type", "_EchoResponse");
    if (js->HasParam("text")) {
      jsobj->AddProperty("text", js->LookupParam("text"));
    }
  }
  
  void Service::SendEchoEvent(Isolate* isolate, const char* text) {
    JSONStream js;
    {
      JSONObject jsobj(&js);
      jsobj.AddProperty("jsonrpc", "2.0");
      jsobj.AddProperty("method", "streamNotify");
      {
        JSONObject params(&jsobj, "params");
        params.AddProperty("streamId", echo_stream.id());
        {
          JSONObject event(&params, "event");
          event.AddProperty("type", "Event");
          event.AddProperty("kind", "_Echo");
          event.AddProperty("isolate", isolate);
          if (text != NULL) {
            event.AddProperty("text", text);
          }
          event.AddPropertyTimeMillis("timestamp", OS::GetCurrentTimeMillis());
        }
      }
    }
  
    intptr_t reservation = js.buffer()->length() + sizeof(int32_t);
    intptr_t data_size = reservation + 3;
    uint8_t* data = reinterpret_cast<uint8_t*>(malloc(data_size));
    data[reservation + 0] = 0;
    data[reservation + 1] = 128;
    data[reservation + 2] = 255;
    SendEventWithData(echo_stream.id(), "_Echo", reservation,
                      js.buffer()->buffer(), js.buffer()->length(), data,
                      data_size);
  }
  
  static void TriggerEchoEvent(Thread* thread, JSONStream* js) {
    if (Service::echo_stream.enabled()) {
      Service::SendEchoEvent(thread->isolate(), js->LookupParam("text"));
    }
    JSONObject jsobj(js);
    HandleCommonEcho(&jsobj, js);
  }
  
  static void Echo(Thread* thread, JSONStream* js) {
    JSONObject jsobj(js);
    HandleCommonEcho(&jsobj, js);
  }
  
  static bool ContainsNonInstance(const Object& obj) {
    if (obj.IsArray()) {
      const Array& array = Array::Cast(obj);
      Object& element = Object::Handle();
      for (intptr_t i = 0; i < array.Length(); ++i) {
        element = array.At(i);
        if (!(element.IsInstance() || element.IsNull())) {
          return true;
        }
      }
      return false;
    } else if (obj.IsGrowableObjectArray()) {
      const GrowableObjectArray& array = GrowableObjectArray::Cast(obj);
      Object& element = Object::Handle();
      for (intptr_t i = 0; i < array.Length(); ++i) {
        element = array.At(i);
        if (!(element.IsInstance() || element.IsNull())) {
          return true;
        }
      }
      return false;
    } else {
      return !(obj.IsInstance() || obj.IsNull());
    }
  }
  
  static ObjectPtr LookupObjectId(Thread* thread,
                                  const char* arg,
                                  ObjectIdRing::LookupResult* kind) {
    *kind = ObjectIdRing::kValid;
    if (strncmp(arg, "int-", 4) == 0) {
      arg += 4;
      int64_t value = 0;
      if (!OS::StringToInt64(arg, &value) || !Smi::IsValid(value)) {
        *kind = ObjectIdRing::kInvalid;
        return Object::null();
      }
      const Integer& obj =
          Integer::Handle(thread->zone(), Smi::New(static_cast<intptr_t>(value)));
      return obj.ptr();
    } else if (strcmp(arg, "bool-true") == 0) {
      return Bool::True().ptr();
    } else if (strcmp(arg, "bool-false") == 0) {
      return Bool::False().ptr();
    } else if (strcmp(arg, "null") == 0) {
      return Object::null();
    }
  
    ObjectIdRing* ring = thread->isolate()->EnsureObjectIdRing();
    intptr_t id = -1;
    if (!GetIntegerId(arg, &id)) {
      *kind = ObjectIdRing::kInvalid;
      return Object::null();
    }
    return ring->GetObjectForId(id, kind);
  }
  
  static ObjectPtr LookupClassMembers(Thread* thread,
                                      const Class& klass,
                                      char** parts,
                                      int num_parts) {
    auto zone = thread->zone();
  
    if (num_parts != 4) {
      return Object::sentinel().ptr();
    }
  
    const char* encoded_id = parts[3];
    auto& id = String::Handle(String::New(encoded_id));
    id = String::DecodeIRI(id);
    if (id.IsNull()) {
      return Object::sentinel().ptr();
    }
  
    if (strcmp(parts[2], "fields") == 0) {
      // Field ids look like: "classes/17/fields/name"
      const auto& field = Field::Handle(klass.LookupField(id));
      if (field.IsNull()) {
        return Object::sentinel().ptr();
      }
      return field.ptr();
    }
    if (strcmp(parts[2], "functions") == 0) {
      // Function ids look like: "classes/17/functions/name"
  
      const auto& function =
          Function::Handle(Resolver::ResolveFunction(zone, klass, id));
      if (function.IsNull()) {
        return Object::sentinel().ptr();
      }
      return function.ptr();
    }
    if (strcmp(parts[2], "implicit_closures") == 0) {
      // Function ids look like: "classes/17/implicit_closures/11"
      intptr_t id;
      if (!GetIntegerId(parts[3], &id)) {
        return Object::sentinel().ptr();
      }
      const auto& func =
          Function::Handle(zone, klass.ImplicitClosureFunctionFromIndex(id));
      if (func.IsNull()) {
        return Object::sentinel().ptr();
      }
      return func.ptr();
    }
    if (strcmp(parts[2], "dispatchers") == 0) {
      // Dispatcher Function ids look like: "classes/17/dispatchers/11"
      intptr_t id;
      if (!GetIntegerId(parts[3], &id)) {
        return Object::sentinel().ptr();
      }
      const auto& func =
          Function::Handle(zone, klass.InvocationDispatcherFunctionFromIndex(id));
      if (func.IsNull()) {
        return Object::sentinel().ptr();
      }
      return func.ptr();
    }
    if (strcmp(parts[2], "closures") == 0) {
      // Closure ids look like: "classes/17/closures/11"
      intptr_t id;
      if (!GetIntegerId(parts[3], &id)) {
        return Object::sentinel().ptr();
      }
      Function& func = Function::Handle(zone);
      func = ClosureFunctionsCache::ClosureFunctionFromIndex(id);
      if (func.IsNull()) {
        return Object::sentinel().ptr();
      }
      return func.ptr();
    }
  
    UNREACHABLE();
    return Object::sentinel().ptr();
  }
  
  static ObjectPtr LookupHeapObjectLibraries(IsolateGroup* isolate_group,
                                             char** parts,
                                             int num_parts) {
    // Library ids look like "libraries/35"
    if (num_parts < 2) {
      return Object::sentinel().ptr();
    }
    const auto& libs =
        GrowableObjectArray::Handle(isolate_group->object_store()->libraries());
    ASSERT(!libs.IsNull());
    const String& id = String::Handle(String::New(parts[1]));
    // Scan for private key.
    String& private_key = String::Handle();
    Library& lib = Library::Handle();
    bool lib_found = false;
    for (intptr_t i = 0; i < libs.Length(); i++) {
      lib ^= libs.At(i);
      ASSERT(!lib.IsNull());
      private_key = lib.private_key();
      if (private_key.Equals(id)) {
        lib_found = true;
        break;
      }
    }
    if (!lib_found) {
      return Object::sentinel().ptr();
    }
  
    const auto& klass = Class::Handle(lib.toplevel_class());
    ASSERT(!klass.IsNull());
  
    if (num_parts == 2) {
      return lib.ptr();
    }
    if (strcmp(parts[2], "fields") == 0) {
      // Library field ids look like: "libraries/17/fields/name"
      return LookupClassMembers(Thread::Current(), klass, parts, num_parts);
    }
    if (strcmp(parts[2], "functions") == 0) {
      // Library function ids look like: "libraries/17/functions/name"
      return LookupClassMembers(Thread::Current(), klass, parts, num_parts);
    }
    if (strcmp(parts[2], "closures") == 0) {
      // Library function ids look like: "libraries/17/closures/name"
      return LookupClassMembers(Thread::Current(), klass, parts, num_parts);
    }
    if (strcmp(parts[2], "implicit_closures") == 0) {
      // Library function ids look like: "libraries/17/implicit_closures/name"
      return LookupClassMembers(Thread::Current(), klass, parts, num_parts);
    }
  
    if (strcmp(parts[2], "scripts") == 0) {
      // Script ids look like "libraries/35/scripts/library%2Furl.dart/12345"
      if (num_parts != 5) {
        return Object::sentinel().ptr();
      }
      const String& id = String::Handle(String::New(parts[3]));
      ASSERT(!id.IsNull());
      // The id is the url of the script % encoded, decode it.
      const String& requested_url = String::Handle(String::DecodeIRI(id));
  
      // Each script id is tagged with a load time.
      int64_t timestamp;
      if (!GetInteger64Id(parts[4], &timestamp, 16) || (timestamp < 0)) {
        return Object::sentinel().ptr();
      }
  
      Script& script = Script::Handle();
      String& script_url = String::Handle();
      const Array& loaded_scripts = Array::Handle(lib.LoadedScripts());
      ASSERT(!loaded_scripts.IsNull());
      intptr_t i;
      for (i = 0; i < loaded_scripts.Length(); i++) {
        script ^= loaded_scripts.At(i);
        ASSERT(!script.IsNull());
        script_url = script.url();
        if (script_url.Equals(requested_url) &&
            (timestamp == script.load_timestamp())) {
          return script.ptr();
        }
      }
    }
  
    // Not found.
    return Object::sentinel().ptr();
  }
  
  static ObjectPtr LookupHeapObjectClasses(Thread* thread,
                                           char** parts,
                                           int num_parts) {
    // Class ids look like: "classes/17"
    if (num_parts < 2) {
      return Object::sentinel().ptr();
    }
    Zone* zone = thread->zone();
    auto table = thread->isolate_group()->class_table();
    intptr_t id;
    if (!GetIntegerId(parts[1], &id) || !table->IsValidIndex(id)) {
      return Object::sentinel().ptr();
    }
    Class& cls = Class::Handle(zone, table->At(id));
    if (num_parts == 2) {
      return cls.ptr();
    }
    if (strcmp(parts[2], "closures") == 0) {
      // Closure ids look like: "classes/17/closures/11"
      return LookupClassMembers(thread, cls, parts, num_parts);
    } else if (strcmp(parts[2], "fields") == 0) {
      // Field ids look like: "classes/17/fields/name"
      return LookupClassMembers(thread, cls, parts, num_parts);
    } else if (strcmp(parts[2], "functions") == 0) {
      // Function ids look like: "classes/17/functions/name"
      return LookupClassMembers(thread, cls, parts, num_parts);
    } else if (strcmp(parts[2], "implicit_closures") == 0) {
      // Function ids look like: "classes/17/implicit_closures/11"
      return LookupClassMembers(thread, cls, parts, num_parts);
    } else if (strcmp(parts[2], "dispatchers") == 0) {
      // Dispatcher Function ids look like: "classes/17/dispatchers/11"
      return LookupClassMembers(thread, cls, parts, num_parts);
    } else if (strcmp(parts[2], "types") == 0) {
      // Type ids look like: "classes/17/types/11"
      if (num_parts != 4) {
        return Object::sentinel().ptr();
      }
      intptr_t id;
      if (!GetIntegerId(parts[3], &id)) {
        return Object::sentinel().ptr();
      }
      if (id != 0) {
        return Object::sentinel().ptr();
      }
      const Type& type = Type::Handle(zone, cls.DeclarationType());
      if (!type.IsNull()) {
        return type.ptr();
      }
    }
  
    // Not found.
    return Object::sentinel().ptr();
  }
  
  static ObjectPtr LookupHeapObjectTypeArguments(Thread* thread,
                                                 char** parts,
                                                 int num_parts) {
    // TypeArguments ids look like: "typearguments/17"
    if (num_parts < 2) {
      return Object::sentinel().ptr();
    }
    intptr_t id;
    if (!GetIntegerId(parts[1], &id)) {
      return Object::sentinel().ptr();
    }
    ObjectStore* object_store = thread->isolate_group()->object_store();
    const Array& table =
        Array::Handle(thread->zone(), object_store->canonical_type_arguments());
    ASSERT(table.Length() > 0);
    const intptr_t table_size = table.Length() - 1;
    if ((id < 0) || (id >= table_size) || (table.At(id) == Object::null())) {
      return Object::sentinel().ptr();
    }
    return table.At(id);
  }
  
  static ObjectPtr LookupHeapObjectCode(char** parts, int num_parts) {
    if (num_parts != 2) {
      return Object::sentinel().ptr();
    }
    uword pc;
    static const char* const kCollectedPrefix = "collected-";
    static intptr_t kCollectedPrefixLen = strlen(kCollectedPrefix);
    static const char* const kNativePrefix = "native-";
    static const intptr_t kNativePrefixLen = strlen(kNativePrefix);
    static const char* const kReusedPrefix = "reused-";
    static const intptr_t kReusedPrefixLen = strlen(kReusedPrefix);
    const char* id = parts[1];
    if (strncmp(kCollectedPrefix, id, kCollectedPrefixLen) == 0) {
      if (!GetUnsignedIntegerId(&id[kCollectedPrefixLen], &pc, 16)) {
        return Object::sentinel().ptr();
      }
      // TODO(turnidge): Return "collected" instead.
      return Object::null();
    }
    if (strncmp(kNativePrefix, id, kNativePrefixLen) == 0) {
      if (!GetUnsignedIntegerId(&id[kNativePrefixLen], &pc, 16)) {
        return Object::sentinel().ptr();
      }
      // TODO(johnmccutchan): Support native Code.
      return Object::null();
    }
    if (strncmp(kReusedPrefix, id, kReusedPrefixLen) == 0) {
      if (!GetUnsignedIntegerId(&id[kReusedPrefixLen], &pc, 16)) {
        return Object::sentinel().ptr();
      }
      // TODO(turnidge): Return "expired" instead.
      return Object::null();
    }
    int64_t timestamp = 0;
    if (!GetCodeId(id, &timestamp, &pc) || (timestamp < 0)) {
      return Object::sentinel().ptr();
    }
    Code& code = Code::Handle(Code::FindCode(pc, timestamp));
    if (!code.IsNull()) {
      return code.ptr();
    }
  
    // Not found.
    return Object::sentinel().ptr();
  }
  
  static ObjectPtr LookupHeapObjectMessage(Thread* thread,
                                           char** parts,
                                           int num_parts) {
    if (num_parts != 2) {
      return Object::sentinel().ptr();
    }
    uword message_id = 0;
    if (!GetUnsignedIntegerId(parts[1], &message_id, 16)) {
      return Object::sentinel().ptr();
    }
    MessageHandler::AcquiredQueues aq(thread->isolate()->message_handler());
    Message* message = aq.queue()->FindMessageById(message_id);
    if (message == NULL) {
      // The user may try to load an expired message.
      return Object::sentinel().ptr();
    }
    if (message->IsRaw()) {
      return message->raw_obj();
    } else {
      MessageSnapshotReader reader(message, thread);
      return reader.ReadObject();
    }
  }
  
  static ObjectPtr LookupHeapObject(Thread* thread,
                                    const char* id_original,
                                    ObjectIdRing::LookupResult* result) {
    char* id = thread->zone()->MakeCopyOfString(id_original);
  
    // Parse the id by splitting at each '/'.
    const int MAX_PARTS = 8;
    char* parts[MAX_PARTS];
    int num_parts = 0;
    int i = 0;
    int start_pos = 0;
    while (id[i] != '\0') {
      if (id[i] == '/') {
        id[i++] = '\0';
        parts[num_parts++] = &id[start_pos];
        if (num_parts == MAX_PARTS) {
          break;
        }
        start_pos = i;
      } else {
        i++;
      }
    }
    if (num_parts < MAX_PARTS) {
      parts[num_parts++] = &id[start_pos];
    }
  
    if (result != NULL) {
      *result = ObjectIdRing::kValid;
    }
  
    Isolate* isolate = thread->isolate();
    if (strcmp(parts[0], "objects") == 0) {
      // Object ids look like "objects/1123"
      Object& obj = Object::Handle(thread->zone());
      ObjectIdRing::LookupResult lookup_result;
      obj = LookupObjectId(thread, parts[1], &lookup_result);
      if (lookup_result != ObjectIdRing::kValid) {
        if (result != NULL) {
          *result = lookup_result;
        }
        return Object::sentinel().ptr();
      }
      return obj.ptr();
  
    } else if (strcmp(parts[0], "libraries") == 0) {
      return LookupHeapObjectLibraries(isolate->group(), parts, num_parts);
    } else if (strcmp(parts[0], "classes") == 0) {
      return LookupHeapObjectClasses(thread, parts, num_parts);
    } else if (strcmp(parts[0], "typearguments") == 0) {
      return LookupHeapObjectTypeArguments(thread, parts, num_parts);
    } else if (strcmp(parts[0], "code") == 0) {
      return LookupHeapObjectCode(parts, num_parts);
    } else if (strcmp(parts[0], "messages") == 0) {
      return LookupHeapObjectMessage(thread, parts, num_parts);
    }
  
    // Not found.
    return Object::sentinel().ptr();
  }
  
  static Breakpoint* LookupBreakpoint(Isolate* isolate,
                                      const char* id,
                                      ObjectIdRing::LookupResult* result) {
    *result = ObjectIdRing::kInvalid;
    size_t end_pos = strcspn(id, "/");
    if (end_pos == strlen(id)) {
      return NULL;
    }
    const char* rest = id + end_pos + 1;  // +1 for '/'.
    if (strncmp("breakpoints", id, end_pos) == 0) {
      intptr_t bpt_id = 0;
      Breakpoint* bpt = NULL;
      if (GetIntegerId(rest, &bpt_id)) {
        bpt = isolate->debugger()->GetBreakpointById(bpt_id);
        if (bpt != nullptr) {
          *result = ObjectIdRing::kValid;
          return bpt;
        }
        if (bpt_id < isolate->debugger()->limitBreakpointId()) {
          *result = ObjectIdRing::kCollected;
          return NULL;
        }
      }
    }
    return NULL;
  }
  
  static void PrintInboundReferences(Thread* thread,
                                     Object* target,
                                     intptr_t limit,
                                     JSONStream* js) {
    ObjectGraph graph(thread);
    Array& path = Array::Handle(Array::New(limit * 2));
    intptr_t length = graph.InboundReferences(target, path);
    OffsetsTable offsets_table(thread->zone());
    JSONObject jsobj(js);
    jsobj.AddProperty("type", "InboundReferences");
    {
      JSONArray elements(&jsobj, "references");
      Object& source = Object::Handle();
      Smi& slot_offset = Smi::Handle();
      Class& source_class = Class::Handle();
      Field& field = Field::Handle();
      Array& parent_field_map = Array::Handle();
      limit = Utils::Minimum(limit, length);
      for (intptr_t i = 0; i < limit; ++i) {
        JSONObject jselement(&elements);
        source = path.At(i * 2);
        slot_offset ^= path.At((i * 2) + 1);
  
        jselement.AddProperty("source", source);
        if (source.IsArray()) {
          intptr_t element_index =
              (slot_offset.Value() - Array::element_offset(0)) /
              Array::kBytesPerElement;
          jselement.AddProperty("parentListIndex", element_index);
        } else {
          if (source.IsInstance()) {
            source_class = source.clazz();
            parent_field_map = source_class.OffsetToFieldMap();
            intptr_t index = slot_offset.Value() >> kCompressedWordSizeLog2;
            if (index > 0 && index < parent_field_map.Length()) {
              field ^= parent_field_map.At(index);
              if (!field.IsNull()) {
                jselement.AddProperty("parentField", field);
                continue;
              }
            }
          }
          const char* field_name = offsets_table.FieldNameForOffset(
              source.GetClassId(), slot_offset.Value());
          if (field_name != nullptr) {
            jselement.AddProperty("_parentWordOffset", slot_offset.Value());
            // TODO(vm-service): Adjust RPC type to allow returning a field name
            // without a field object, or reify the fields described by
            // raw_object_fields.cc
            // jselement.AddProperty("_parentFieldName", field_name);
          } else if (source.IsContext()) {
            intptr_t element_index =
                (slot_offset.Value() - Context::variable_offset(0)) /
                Context::kBytesPerElement;
            jselement.AddProperty("parentListIndex", element_index);
          } else {
            jselement.AddProperty("_parentWordOffset", slot_offset.Value());
          }
        }
      }
    }
  
    // We nil out the array after generating the response to prevent
    // reporting suprious references when repeatedly looking for the
    // references to an object.
    for (intptr_t i = 0; i < path.Length(); i++) {
      path.SetAt(i, Object::null_object());
    }
  }
  
  static const MethodParameter* const get_inbound_references_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      NULL,
  };
  
  static void GetInboundReferences(Thread* thread, JSONStream* js) {
    const char* target_id = js->LookupParam("targetId");
    if (target_id == NULL) {
      PrintMissingParamError(js, "targetId");
      return;
    }
    const char* limit_cstr = js->LookupParam("limit");
    if (limit_cstr == NULL) {
      PrintMissingParamError(js, "limit");
      return;
    }
    intptr_t limit;
    if (!GetIntegerId(limit_cstr, &limit)) {
      PrintInvalidParamError(js, "limit");
      return;
    }
  
    Object& obj = Object::Handle(thread->zone());
    ObjectIdRing::LookupResult lookup_result;
    {
      HANDLESCOPE(thread);
      obj = LookupHeapObject(thread, target_id, &lookup_result);
    }
    if (obj.ptr() == Object::sentinel().ptr()) {
      if (lookup_result == ObjectIdRing::kCollected) {
        PrintSentinel(js, kCollectedSentinel);
      } else if (lookup_result == ObjectIdRing::kExpired) {
        PrintSentinel(js, kExpiredSentinel);
      } else {
        PrintInvalidParamError(js, "targetId");
      }
      return;
    }
    PrintInboundReferences(thread, &obj, limit, js);
  }
  
  static void PrintRetainingPath(Thread* thread,
                                 Object* obj,
                                 intptr_t limit,
                                 JSONStream* js) {
    ObjectGraph graph(thread);
    Array& path = Array::Handle(Array::New(limit * 2));
    auto result = graph.RetainingPath(obj, path);
    intptr_t length = result.length;
    JSONObject jsobj(js);
    jsobj.AddProperty("type", "RetainingPath");
    jsobj.AddProperty("length", length);
    jsobj.AddProperty("gcRootType", result.gc_root_type);
    JSONArray elements(&jsobj, "elements");
    Object& element = Object::Handle();
    Smi& slot_offset = Smi::Handle();
    Class& element_class = Class::Handle();
    Array& element_field_map = Array::Handle();
    LinkedHashMap& map = LinkedHashMap::Handle();
    Array& map_data = Array::Handle();
    Field& field = Field::Handle();
    WeakProperty& wp = WeakProperty::Handle();
    String& name = String::Handle();
    limit = Utils::Minimum(limit, length);
    OffsetsTable offsets_table(thread->zone());
    for (intptr_t i = 0; i < limit; ++i) {
      JSONObject jselement(&elements);
      element = path.At(i * 2);
      jselement.AddProperty("value", element);
      // Interpret the word offset from parent as list index, map key,
      // weak property, or instance field.
      if (i > 0) {
        slot_offset ^= path.At((i * 2) - 1);
        if (element.IsArray() || element.IsGrowableObjectArray()) {
          intptr_t element_index =
              (slot_offset.Value() - Array::element_offset(0)) /
              Array::kBytesPerElement;
          jselement.AddProperty("parentListIndex", element_index);
        } else if (element.IsLinkedHashMap()) {
          map = static_cast<LinkedHashMapPtr>(path.At(i * 2));
          map_data = map.data();
          intptr_t element_index =
              (slot_offset.Value() - Array::element_offset(0)) /
              Array::kBytesPerElement;
          LinkedHashMap::Iterator iterator(map);
          while (iterator.MoveNext()) {
            if (iterator.CurrentKey() == map_data.At(element_index) ||
                iterator.CurrentValue() == map_data.At(element_index)) {
              element = iterator.CurrentKey();
              jselement.AddProperty("parentMapKey", element);
              break;
            }
          }
        } else if (element.IsWeakProperty()) {
          wp ^= static_cast<WeakPropertyPtr>(element.ptr());
          element = wp.key();
          jselement.AddProperty("parentMapKey", element);
        } else {
          if (element.IsInstance()) {
            element_class = element.clazz();
            element_field_map = element_class.OffsetToFieldMap();
            intptr_t index = slot_offset.Value() >> kCompressedWordSizeLog2;
            if ((index > 0) && (index < element_field_map.Length())) {
              field ^= element_field_map.At(index);
              if (!field.IsNull()) {
                name ^= field.name();
                jselement.AddProperty("parentField", name.ToCString());
                continue;
              }
            }
          }
          const char* field_name = offsets_table.FieldNameForOffset(
              element.GetClassId(), slot_offset.Value());
          if (field_name != nullptr) {
            jselement.AddProperty("parentField", field_name);
          } else if (element.IsContext()) {
            intptr_t element_index =
                (slot_offset.Value() - Context::variable_offset(0)) /
                Context::kBytesPerElement;
            jselement.AddProperty("parentListIndex", element_index);
          } else {
            jselement.AddProperty("_parentWordOffset", slot_offset.Value());
          }
        }
      }
    }
  
    // We nil out the array after generating the response to prevent
    // reporting spurious references when looking for inbound references
    // after looking for a retaining path.
    for (intptr_t i = 0; i < path.Length(); i++) {
      path.SetAt(i, Object::null_object());
    }
  }
  
  static const MethodParameter* const get_retaining_path_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      NULL,
  };
  
  static void GetRetainingPath(Thread* thread, JSONStream* js) {
    const char* target_id = js->LookupParam("targetId");
    if (target_id == NULL) {
      PrintMissingParamError(js, "targetId");
      return;
    }
    const char* limit_cstr = js->LookupParam("limit");
    if (limit_cstr == NULL) {
      PrintMissingParamError(js, "limit");
      return;
    }
    intptr_t limit;
    if (!GetIntegerId(limit_cstr, &limit)) {
      PrintInvalidParamError(js, "limit");
      return;
    }
  
    Object& obj = Object::Handle(thread->zone());
    ObjectIdRing::LookupResult lookup_result;
    {
      HANDLESCOPE(thread);
      obj = LookupHeapObject(thread, target_id, &lookup_result);
    }
    if (obj.ptr() == Object::sentinel().ptr()) {
      if (lookup_result == ObjectIdRing::kCollected) {
        PrintSentinel(js, kCollectedSentinel);
      } else if (lookup_result == ObjectIdRing::kExpired) {
        PrintSentinel(js, kExpiredSentinel);
      } else {
        PrintInvalidParamError(js, "targetId");
      }
      return;
    }
    PrintRetainingPath(thread, &obj, limit, js);
  }
  
  static const MethodParameter* const get_retained_size_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      new IdParameter("targetId", true),
      NULL,
  };
  
  static void GetRetainedSize(Thread* thread, JSONStream* js) {
    const char* target_id = js->LookupParam("targetId");
    ASSERT(target_id != NULL);
    ObjectIdRing::LookupResult lookup_result;
    Object& obj =
        Object::Handle(LookupHeapObject(thread, target_id, &lookup_result));
    if (obj.ptr() == Object::sentinel().ptr()) {
      if (lookup_result == ObjectIdRing::kCollected) {
        PrintSentinel(js, kCollectedSentinel);
      } else if (lookup_result == ObjectIdRing::kExpired) {
        PrintSentinel(js, kExpiredSentinel);
      } else {
        PrintInvalidParamError(js, "targetId");
      }
      return;
    }
    // TODO(rmacnak): There is no way to get the size retained by a class object.
    // SizeRetainedByClass should be a separate RPC.
    if (obj.IsClass()) {
      const Class& cls = Class::Cast(obj);
      ObjectGraph graph(thread);
      intptr_t retained_size = graph.SizeRetainedByClass(cls.id());
      const Object& result = Object::Handle(Integer::New(retained_size));
      result.PrintJSON(js, true);
      return;
    }
  
    ObjectGraph graph(thread);
    intptr_t retained_size = graph.SizeRetainedByInstance(obj);
    const Object& result = Object::Handle(Integer::New(retained_size));
    result.PrintJSON(js, true);
  }
  
  static const MethodParameter* const get_reachable_size_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      new IdParameter("targetId", true),
      NULL,
  };
  
  static void GetReachableSize(Thread* thread, JSONStream* js) {
    const char* target_id = js->LookupParam("targetId");
    ASSERT(target_id != NULL);
    ObjectIdRing::LookupResult lookup_result;
    Object& obj =
        Object::Handle(LookupHeapObject(thread, target_id, &lookup_result));
    if (obj.ptr() == Object::sentinel().ptr()) {
      if (lookup_result == ObjectIdRing::kCollected) {
        PrintSentinel(js, kCollectedSentinel);
      } else if (lookup_result == ObjectIdRing::kExpired) {
        PrintSentinel(js, kExpiredSentinel);
      } else {
        PrintInvalidParamError(js, "targetId");
      }
      return;
    }
    // TODO(rmacnak): There is no way to get the size retained by a class object.
    // SizeRetainedByClass should be a separate RPC.
    if (obj.IsClass()) {
      const Class& cls = Class::Cast(obj);
      ObjectGraph graph(thread);
      intptr_t retained_size = graph.SizeReachableByClass(cls.id());
      const Object& result = Object::Handle(Integer::New(retained_size));
      result.PrintJSON(js, true);
      return;
    }
  
    ObjectGraph graph(thread);
    intptr_t retained_size = graph.SizeReachableByInstance(obj);
    const Object& result = Object::Handle(Integer::New(retained_size));
    result.PrintJSON(js, true);
  }
  
  static const MethodParameter* const invoke_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      NULL,
  };
  
  static void Invoke(Thread* thread, JSONStream* js) {
    const char* receiver_id = js->LookupParam("targetId");
    if (receiver_id == NULL) {
      PrintMissingParamError(js, "targetId");
      return;
    }
    const char* selector_cstr = js->LookupParam("selector");
    if (selector_cstr == NULL) {
      PrintMissingParamError(js, "selector");
      return;
    }
    const char* argument_ids = js->LookupParam("argumentIds");
    if (argument_ids == NULL) {
      PrintMissingParamError(js, "argumentIds");
      return;
    }
  
  #if !defined(DART_PRECOMPILED_RUNTIME)
    bool disable_breakpoints =
        BoolParameter::Parse(js->LookupParam("disableBreakpoints"), false);
    DisableBreakpointsScope db(thread->isolate()->debugger(),
                               disable_breakpoints);
  #endif
  
    Zone* zone = thread->zone();
    ObjectIdRing::LookupResult lookup_result;
    Object& receiver = Object::Handle(
        zone, LookupHeapObject(thread, receiver_id, &lookup_result));
    if (receiver.ptr() == Object::sentinel().ptr()) {
      if (lookup_result == ObjectIdRing::kCollected) {
        PrintSentinel(js, kCollectedSentinel);
      } else if (lookup_result == ObjectIdRing::kExpired) {
        PrintSentinel(js, kExpiredSentinel);
      } else {
        PrintInvalidParamError(js, "targetId");
      }
      return;
    }
  
    const GrowableObjectArray& growable_args =
        GrowableObjectArray::Handle(zone, GrowableObjectArray::New());
  
    bool is_instance = (receiver.IsInstance() || receiver.IsNull()) &&
                       !ContainsNonInstance(receiver);
    if (is_instance) {
      growable_args.Add(receiver);
    }
  
    intptr_t n = strlen(argument_ids);
    if ((n < 2) || (argument_ids[0] != '[') || (argument_ids[n - 1] != ']')) {
      PrintInvalidParamError(js, "argumentIds");
      return;
    }
    if (n > 2) {
      intptr_t start = 1;
      while (start < n) {
        intptr_t end = start;
        while ((argument_ids[end + 1] != ',') && (argument_ids[end + 1] != ']')) {
          end++;
        }
        if (end == start) {
          // Empty element.
          PrintInvalidParamError(js, "argumentIds");
          return;
        }
  
        const char* argument_id =
            zone->MakeCopyOfStringN(&argument_ids[start], end - start + 1);
  
        ObjectIdRing::LookupResult lookup_result;
        Object& argument = Object::Handle(
            zone, LookupHeapObject(thread, argument_id, &lookup_result));
        // Invoke only accepts Instance arguments.
        if (!(argument.IsInstance() || argument.IsNull()) ||
            ContainsNonInstance(argument)) {
          PrintInvalidParamError(js, "argumentIds");
          return;
        }
        if (argument.ptr() == Object::sentinel().ptr()) {
          if (lookup_result == ObjectIdRing::kCollected) {
            PrintSentinel(js, kCollectedSentinel);
          } else if (lookup_result == ObjectIdRing::kExpired) {
            PrintSentinel(js, kExpiredSentinel);
          } else {
            PrintInvalidParamError(js, "argumentIds");
          }
          return;
        }
        growable_args.Add(argument);
  
        start = end + 3;
      }
    }
  
    const String& selector = String::Handle(zone, String::New(selector_cstr));
    const Array& args =
        Array::Handle(zone, Array::MakeFixedLength(growable_args));
    const Array& arg_names = Object::empty_array();
  
    if (receiver.IsLibrary()) {
      const Library& lib = Library::Cast(receiver);
      const Object& result =
          Object::Handle(zone, lib.Invoke(selector, args, arg_names));
      result.PrintJSON(js, true);
      return;
    }
    if (receiver.IsClass()) {
      const Class& cls = Class::Cast(receiver);
      const Object& result =
          Object::Handle(zone, cls.Invoke(selector, args, arg_names));
      result.PrintJSON(js, true);
      return;
    }
    if (is_instance) {
      // We don't use Instance::Cast here because it doesn't allow null.
      Instance& instance = Instance::Handle(zone);
      instance ^= receiver.ptr();
      const Object& result =
          Object::Handle(zone, instance.Invoke(selector, args, arg_names));
      result.PrintJSON(js, true);
      return;
    }
    js->PrintError(kInvalidParams,
                   "%s: invalid 'targetId' parameter: "
                   "Cannot invoke against a VM-internal object",
                   js->method());
  }
  
  static const MethodParameter* const evaluate_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      NULL,
  };
  
  static bool IsAlpha(char c) {
    return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
  }
  static bool IsAlphaOrDollar(char c) {
    return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '$');
  }
  static bool IsAlphaNum(char c) {
    return (c >= '0' && c <= '9') || IsAlpha(c);
  }
  static bool IsAlphaNumOrDollar(char c) {
    return (c >= '0' && c <= '9') || IsAlphaOrDollar(c);
  }
  static bool IsWhitespace(char c) {
    return c <= ' ';
  }
  static bool IsObjectIdChar(char c) {
    return IsAlphaNum(c) || c == '/' || c == '-' || c == '@' || c == '%';
  }
  
  // TODO(vm-service): Consider whether we should pass structured objects in
  // service messages instead of always flattening them to C strings.
  static bool ParseScope(const char* scope,
                         GrowableArray<const char*>* names,
                         GrowableArray<const char*>* ids) {
    Zone* zone = Thread::Current()->zone();
    const char* c = scope;
    if (*c++ != '{') return false;
  
    for (;;) {
      while (IsWhitespace(*c)) {
        c++;
      }
  
      if (*c == '}') return true;
  
      const char* name = c;
      if (!IsAlphaOrDollar(*c)) return false;
      while (IsAlphaNumOrDollar(*c)) {
        c++;
      }
      names->Add(zone->MakeCopyOfStringN(name, c - name));
  
      while (IsWhitespace(*c)) {
        c++;
      }
  
      if (*c++ != ':') return false;
  
      while (IsWhitespace(*c)) {
        c++;
      }
  
      const char* id = c;
      if (!IsObjectIdChar(*c)) return false;
      while (IsObjectIdChar(*c)) {
        c++;
      }
      ids->Add(zone->MakeCopyOfStringN(id, c - id));
  
      while (IsWhitespace(*c)) {
        c++;
      }
      if (*c == ',') c++;
    }
  
    return false;
  }
  
  static bool BuildScope(Thread* thread,
                         JSONStream* js,
                         const GrowableObjectArray& names,
                         const GrowableObjectArray& values) {
    const char* scope = js->LookupParam("scope");
    GrowableArray<const char*> cnames;
    GrowableArray<const char*> cids;
    if (scope != NULL) {
      if (!ParseScope(scope, &cnames, &cids)) {
        PrintInvalidParamError(js, "scope");
        return true;
      }
      String& name = String::Handle();
      Object& obj = Object::Handle();
      for (intptr_t i = 0; i < cids.length(); i++) {
        ObjectIdRing::LookupResult lookup_result;
        obj = LookupHeapObject(thread, cids[i], &lookup_result);
        if (obj.ptr() == Object::sentinel().ptr()) {
          if (lookup_result == ObjectIdRing::kCollected) {
            PrintSentinel(js, kCollectedSentinel);
          } else if (lookup_result == ObjectIdRing::kExpired) {
            PrintSentinel(js, kExpiredSentinel);
          } else {
            PrintInvalidParamError(js, "targetId");
          }
          return true;
        }
        if ((!obj.IsInstance() && !obj.IsNull()) || ContainsNonInstance(obj)) {
          js->PrintError(kInvalidParams,
                         "%s: invalid scope 'targetId' parameter: "
                         "Cannot evaluate against a VM-internal object",
                         js->method());
          return true;
        }
        name = String::New(cnames[i]);
        names.Add(name);
        values.Add(obj);
      }
    }
    return false;
  }
  
  static void Evaluate(Thread* thread, JSONStream* js) {
    // If a compilation service is available, this RPC invocation will have been
    // intercepted by RunningIsolates.routeRequest.
    js->PrintError(
        kExpressionCompilationError,
        "%s: No compilation service available; cannot evaluate from source.",
        js->method());
  }
  
  static const MethodParameter* const build_expression_evaluation_scope_params[] =
      {
          RUNNABLE_ISOLATE_PARAMETER,
          new IdParameter("frameIndex", false),
          new IdParameter("targetId", false),
          NULL,
  };
  
  static void BuildExpressionEvaluationScope(Thread* thread, JSONStream* js) {
    if (CheckDebuggerDisabled(thread, js)) {
      return;
    }
  
    Isolate* isolate = thread->isolate();
    DebuggerStackTrace* stack = isolate->debugger()->StackTrace();
    intptr_t framePos = UIntParameter::Parse(js->LookupParam("frameIndex"));
    if (framePos >= stack->Length()) {
      PrintInvalidParamError(js, "frameIndex");
      return;
    }
  
    Zone* zone = thread->zone();
    const GrowableObjectArray& param_names =
        GrowableObjectArray::Handle(zone, GrowableObjectArray::New());
    const GrowableObjectArray& param_values =
        GrowableObjectArray::Handle(zone, GrowableObjectArray::New());
    const GrowableObjectArray& type_params_names =
        GrowableObjectArray::Handle(zone, GrowableObjectArray::New());
    String& klass_name = String::Handle(zone);
    String& library_uri = String::Handle(zone);
    bool isStatic = false;
  
    if (BuildScope(thread, js, param_names, param_values)) {
      return;
    }
  
    if (js->HasParam("frameIndex")) {
      // building scope in the context of a given frame
      DebuggerStackTrace* stack = isolate->debugger()->StackTrace();
      intptr_t framePos = UIntParameter::Parse(js->LookupParam("frameIndex"));
      if (framePos >= stack->Length()) {
        PrintInvalidParamError(js, "frameIndex");
        return;
      }
  
      ActivationFrame* frame = stack->FrameAt(framePos);
      frame->BuildParameters(param_names, param_values, type_params_names);
  
      if (frame->function().is_static()) {
        const Class& cls = Class::Handle(zone, frame->function().Owner());
        if (!cls.IsTopLevel()) {
          klass_name = cls.UserVisibleName();
        }
        library_uri = Library::Handle(zone, cls.library()).url();
        isStatic = true;
      } else {
        const Class& method_cls = Class::Handle(zone, frame->function().origin());
        library_uri = Library::Handle(zone, method_cls.library()).url();
        klass_name = method_cls.UserVisibleName();
        isStatic = false;
      }
    } else {
      // building scope in the context of a given object
      if (!js->HasParam("targetId")) {
        js->PrintError(kInvalidParams,
                       "Either targetId or frameIndex has to be provided.");
        return;
      }
      const char* target_id = js->LookupParam("targetId");
  
      ObjectIdRing::LookupResult lookup_result;
      Object& obj = Object::Handle(
          zone, LookupHeapObject(thread, target_id, &lookup_result));
      if (obj.ptr() == Object::sentinel().ptr()) {
        PrintInvalidParamError(js, "targetId");
        return;
      }
      if (obj.IsLibrary()) {
        const Library& lib = Library::Cast(obj);
        library_uri = lib.url();
        isStatic = true;
      } else if (obj.IsClass() || ((obj.IsInstance() || obj.IsNull()) &&
                                   !ContainsNonInstance(obj))) {
        Class& cls = Class::Handle(zone);
        if (obj.IsClass()) {
          cls ^= obj.ptr();
          isStatic = true;
        } else {
          Instance& instance = Instance::Handle(zone);
          instance ^= obj.ptr();
          cls = instance.clazz();
          isStatic = false;
        }
        if (!cls.IsTopLevel() &&
            (cls.id() < kInstanceCid || cls.id() == kTypeArgumentsCid)) {
          js->PrintError(
              kInvalidParams,
              "Expressions can be evaluated only with regular Dart instances");
          return;
        }
  
        if (!cls.IsTopLevel()) {
          klass_name = cls.UserVisibleName();
        }
        library_uri = Library::Handle(zone, cls.library()).url();
      } else {
        js->PrintError(kInvalidParams,
                       "%s: invalid 'targetId' parameter: "
                       "Cannot evaluate against a VM-internal object",
                       js->method());
        return;
      }
    }
  
    JSONObject report(js);
    {
      JSONArray jsonParamNames(&report, "param_names");
  
      String& param_name = String::Handle(zone);
      for (intptr_t i = 0; i < param_names.Length(); i++) {
        param_name ^= param_names.At(i);
        jsonParamNames.AddValue(param_name.ToCString());
      }
    }
  
    {
      JSONArray jsonTypeParamsNames(&report, "type_params_names");
      String& type_param_name = String::Handle(zone);
      for (intptr_t i = 0; i < type_params_names.Length(); i++) {
        type_param_name ^= type_params_names.At(i);
        jsonTypeParamsNames.AddValue(type_param_name.ToCString());
      }
    }
    report.AddProperty("libraryUri", library_uri.ToCString());
    if (!klass_name.IsNull()) {
      report.AddProperty("klass", klass_name.ToCString());
    }
    report.AddProperty("isStatic", isStatic);
  }
  
  #if !defined(DART_PRECOMPILED_RUNTIME)
  // Parse comma-separated list of values, put them into values
  static bool ParseCSVList(const char* csv_list,
                           const GrowableObjectArray& values) {
    Zone* zone = Thread::Current()->zone();
    String& s = String::Handle(zone);
    const char* c = csv_list;
    if (*c++ != '[') return false;
    while (IsWhitespace(*c) && *c != '\0') {
      c++;
    }
    while (*c != '\0') {
      const char* value = c;
      while (*c != '\0' && *c != ']' && *c != ',' && !IsWhitespace(*c)) {
        c++;
      }
      if (c > value) {
        s = String::New(zone->MakeCopyOfStringN(value, c - value));
        values.Add(s);
      }
      switch (*c) {
        case '\0':
          return false;
        case ',':
          c++;
          break;
        case ']':
          return true;
      }
      while (IsWhitespace(*c) && *c != '\0') {
        c++;
      }
    }
    return false;
  }
  #endif
  
  static const MethodParameter* const compile_expression_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      new StringParameter("expression", true),
      new StringParameter("definitions", false),
      new StringParameter("typeDefinitions", false),
      new StringParameter("libraryUri", true),
      new StringParameter("klass", false),
      new BoolParameter("isStatic", false),
      NULL,
  };
  
  static void CompileExpression(Thread* thread, JSONStream* js) {
  #if defined(DART_PRECOMPILED_RUNTIME)
    js->PrintError(kFeatureDisabled, "Debugger is disabled in AOT mode.");
  #else
    if (CheckDebuggerDisabled(thread, js)) {
      return;
    }
  
    if (!KernelIsolate::IsRunning() && !KernelIsolate::Start()) {
      js->PrintError(
          kExpressionCompilationError,
          "%s: No compilation service available; cannot evaluate from source.",
          js->method());
      return;
    }
  
    const char* klass = js->LookupParam("klass");
    bool is_static =
        BoolParameter::Parse(js->LookupParam("isStatic"), (klass == nullptr));
  
    const GrowableObjectArray& params =
        GrowableObjectArray::Handle(thread->zone(), GrowableObjectArray::New());
    if (!ParseCSVList(js->LookupParam("definitions"), params)) {
      PrintInvalidParamError(js, "definitions");
      return;
    }
  
    const GrowableObjectArray& type_params =
        GrowableObjectArray::Handle(thread->zone(), GrowableObjectArray::New());
    if (!ParseCSVList(js->LookupParam("typeDefinitions"), type_params)) {
      PrintInvalidParamError(js, "typedDefinitions");
      return;
    }
  
    const uint8_t* kernel_buffer = Service::dart_library_kernel();
    const intptr_t kernel_buffer_len = Service::dart_library_kernel_length();
  
    Dart_KernelCompilationResult compilation_result =
        KernelIsolate::CompileExpressionToKernel(
            kernel_buffer, kernel_buffer_len, js->LookupParam("expression"),
            Array::Handle(Array::MakeFixedLength(params)),
            Array::Handle(Array::MakeFixedLength(type_params)),
            js->LookupParam("libraryUri"), js->LookupParam("klass"), is_static);
  
    if (compilation_result.status != Dart_KernelCompilationStatus_Ok) {
      js->PrintError(kExpressionCompilationError, "%s", compilation_result.error);
      free(compilation_result.error);
      return;
    }
  
    const uint8_t* kernel_bytes = compilation_result.kernel;
    intptr_t kernel_length = compilation_result.kernel_size;
    ASSERT(kernel_bytes != NULL);
  
    JSONObject report(js);
    report.AddPropertyBase64("kernelBytes", kernel_bytes, kernel_length);
  #endif
  }
  
  static const MethodParameter* const evaluate_compiled_expression_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      new UIntParameter("frameIndex", false),
      new IdParameter("targetId", false),
      new StringParameter("kernelBytes", true),
      NULL,
  };
  
  ExternalTypedDataPtr DecodeKernelBuffer(const char* kernel_buffer_base64) {
    intptr_t kernel_length;
    uint8_t* kernel_buffer = DecodeBase64(kernel_buffer_base64, &kernel_length);
    return ExternalTypedData::NewFinalizeWithFree(kernel_buffer, kernel_length);
  }
  
  static void EvaluateCompiledExpression(Thread* thread, JSONStream* js) {
    if (CheckDebuggerDisabled(thread, js)) {
      return;
    }
  
    Isolate* isolate = thread->isolate();
  
    bool disable_breakpoints =
        BoolParameter::Parse(js->LookupParam("disableBreakpoints"), false);
    DisableBreakpointsScope db(isolate->debugger(), disable_breakpoints);
  
    DebuggerStackTrace* stack = isolate->debugger()->StackTrace();
    intptr_t frame_pos = UIntParameter::Parse(js->LookupParam("frameIndex"));
    if (frame_pos >= stack->Length()) {
      PrintInvalidParamError(js, "frameIndex");
      return;
    }
    Zone* zone = thread->zone();
    const GrowableObjectArray& param_names =
        GrowableObjectArray::Handle(zone, GrowableObjectArray::New());
    const GrowableObjectArray& param_values =
        GrowableObjectArray::Handle(zone, GrowableObjectArray::New());
    if (BuildScope(thread, js, param_names, param_values)) {
      return;
    }
    const GrowableObjectArray& type_params_names =
        GrowableObjectArray::Handle(zone, GrowableObjectArray::New());
  
    const ExternalTypedData& kernel_data = ExternalTypedData::Handle(
        zone, DecodeKernelBuffer(js->LookupParam("kernelBytes")));
  
    if (js->HasParam("frameIndex")) {
      DebuggerStackTrace* stack = isolate->debugger()->StackTrace();
      intptr_t frame_pos = UIntParameter::Parse(js->LookupParam("frameIndex"));
      if (frame_pos >= stack->Length()) {
        PrintInvalidParamError(js, "frameIndex");
        return;
      }
  
      ActivationFrame* frame = stack->FrameAt(frame_pos);
      TypeArguments& type_arguments = TypeArguments::Handle(
          zone,
          frame->BuildParameters(param_names, param_values, type_params_names));
  
      const Object& result = Object::Handle(
          zone,
          frame->EvaluateCompiledExpression(
              kernel_data,
              Array::Handle(zone, Array::MakeFixedLength(type_params_names)),
              Array::Handle(zone, Array::MakeFixedLength(param_values)),
              type_arguments));
      result.PrintJSON(js, true);
    } else {
      // evaluating expression in the context of a given object
      if (!js->HasParam("targetId")) {
        js->PrintError(kInvalidParams,
                       "Either targetId or frameIndex has to be provided.");
        return;
      }
      const char* target_id = js->LookupParam("targetId");
      ObjectIdRing::LookupResult lookup_result;
      Object& obj = Object::Handle(
          zone, LookupHeapObject(thread, target_id, &lookup_result));
      if (obj.ptr() == Object::sentinel().ptr()) {
        if (lookup_result == ObjectIdRing::kCollected) {
          PrintSentinel(js, kCollectedSentinel);
        } else if (lookup_result == ObjectIdRing::kExpired) {
          PrintSentinel(js, kExpiredSentinel);
        } else {
          PrintInvalidParamError(js, "targetId");
        }
        return;
      }
      TypeArguments& type_arguments = TypeArguments::Handle(zone);
      if (obj.IsLibrary()) {
        const Library& lib = Library::Cast(obj);
        const Object& result = Object::Handle(
            zone,
            lib.EvaluateCompiledExpression(
                kernel_data,
                Array::Handle(zone, Array::MakeFixedLength(type_params_names)),
                Array::Handle(zone, Array::MakeFixedLength(param_values)),
                type_arguments));
        result.PrintJSON(js, true);
        return;
      }
      if (obj.IsClass()) {
        const Class& cls = Class::Cast(obj);
        const Object& result = Object::Handle(
            zone,
            cls.EvaluateCompiledExpression(
                kernel_data,
                Array::Handle(zone, Array::MakeFixedLength(type_params_names)),
                Array::Handle(zone, Array::MakeFixedLength(param_values)),
                type_arguments));
        result.PrintJSON(js, true);
        return;
      }
      if ((obj.IsInstance() || obj.IsNull()) && !ContainsNonInstance(obj)) {
        // We don't use Instance::Cast here because it doesn't allow null.
        Instance& instance = Instance::Handle(zone);
        instance ^= obj.ptr();
        const Class& receiver_cls = Class::Handle(zone, instance.clazz());
        const Object& result = Object::Handle(
            zone,
            instance.EvaluateCompiledExpression(
                receiver_cls, kernel_data,
                Array::Handle(zone, Array::MakeFixedLength(type_params_names)),
                Array::Handle(zone, Array::MakeFixedLength(param_values)),
                type_arguments));
        result.PrintJSON(js, true);
        return;
      }
      js->PrintError(kInvalidParams,
                     "%s: invalid 'targetId' parameter: "
                     "Cannot evaluate against a VM-internal object",
                     js->method());
    }
  }
  
  static const MethodParameter* const evaluate_in_frame_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      new UIntParameter("frameIndex", true),
      new MethodParameter("expression", true),
      NULL,
  };
  
  static void EvaluateInFrame(Thread* thread, JSONStream* js) {
    // If a compilation service is available, this RPC invocation will have been
    // intercepted by RunningIsolates.routeRequest.
    js->PrintError(
        kExpressionCompilationError,
        "%s: No compilation service available; cannot evaluate from source.",
        js->method());
  }
  
  static void MarkClasses(const Class& root,
                          bool include_subclasses,
                          bool include_implementors) {
    Thread* thread = Thread::Current();
    HANDLESCOPE(thread);
    SharedClassTable* table = thread->isolate()->group()->shared_class_table();
    GrowableArray<const Class*> worklist;
    table->SetCollectInstancesFor(root.id(), true);
    worklist.Add(&root);
    GrowableObjectArray& subclasses = GrowableObjectArray::Handle();
    GrowableObjectArray& implementors = GrowableObjectArray::Handle();
    while (!worklist.is_empty()) {
      const Class& cls = *worklist.RemoveLast();
      // All subclasses are implementors, but they are not included in
      // `direct_implementors`.
      if (include_subclasses || include_implementors) {
        subclasses = cls.direct_subclasses_unsafe();
        if (!subclasses.IsNull()) {
          for (intptr_t j = 0; j < subclasses.Length(); j++) {
            Class& subclass = Class::Handle();
            subclass ^= subclasses.At(j);
            if (!table->CollectInstancesFor(subclass.id())) {
              table->SetCollectInstancesFor(subclass.id(), true);
              worklist.Add(&subclass);
            }
          }
        }
      }
      if (include_implementors) {
        implementors = cls.direct_implementors_unsafe();
        if (!implementors.IsNull()) {
          for (intptr_t j = 0; j < implementors.Length(); j++) {
            Class& implementor = Class::Handle();
            implementor ^= implementors.At(j);
            if (!table->CollectInstancesFor(implementor.id())) {
              table->SetCollectInstancesFor(implementor.id(), true);
              worklist.Add(&implementor);
            }
          }
        }
      }
    }
  }
  
  static void UnmarkClasses() {
    SharedClassTable* table = IsolateGroup::Current()->shared_class_table();
    for (intptr_t i = 1; i < table->NumCids(); i++) {
      table->SetCollectInstancesFor(i, false);
    }
  }
  
  class GetInstancesVisitor : public ObjectGraph::Visitor {
   public:
    GetInstancesVisitor(ZoneGrowableHandlePtrArray<Object>* storage,
                        intptr_t limit)
        : table_(IsolateGroup::Current()->shared_class_table()),
          storage_(storage),
          limit_(limit),
          count_(0) {}
  
    virtual Direction VisitObject(ObjectGraph::StackIterator* it) {
      ObjectPtr raw_obj = it->Get();
      if (raw_obj->IsPseudoObject()) {
        return kProceed;
      }
      if (table_->CollectInstancesFor(raw_obj->GetClassId())) {
        if (count_ < limit_) {
          storage_->Add(Object::Handle(raw_obj));
        }
        ++count_;
      }
      return kProceed;
    }
  
    intptr_t count() const { return count_; }
  
   private:
    SharedClassTable* const table_;
    ZoneGrowableHandlePtrArray<Object>* storage_;
    const intptr_t limit_;
    intptr_t count_;
  };
  
  static const MethodParameter* const get_instances_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      NULL,
  };
  
  static void GetInstances(Thread* thread, JSONStream* js) {
    const char* object_id = js->LookupParam("objectId");
    if (object_id == NULL) {
      PrintMissingParamError(js, "objectId");
      return;
    }
    const char* limit_cstr = js->LookupParam("limit");
    if (limit_cstr == NULL) {
      PrintMissingParamError(js, "limit");
      return;
    }
    intptr_t limit;
    if (!GetIntegerId(limit_cstr, &limit)) {
      PrintInvalidParamError(js, "limit");
      return;
    }
  
    const Object& obj = Object::Handle(LookupHeapObject(thread, object_id, NULL));
    if (obj.ptr() == Object::sentinel().ptr() || !obj.IsClass()) {
      PrintInvalidParamError(js, "objectId");
      return;
    }
    const Class& cls = Class::Cast(obj);
  
    // Ensure the array and handles created below are promptly destroyed.
    StackZone zone(thread);
    HANDLESCOPE(thread);
  
    ZoneGrowableHandlePtrArray<Object> storage(thread->zone(), limit);
    GetInstancesVisitor visitor(&storage, limit);
    {
      ObjectGraph graph(thread);
      HeapIterationScope iteration_scope(Thread::Current(), true);
      MarkClasses(cls, false, false);
      graph.IterateObjects(&visitor);
      UnmarkClasses();
    }
    intptr_t count = visitor.count();
    JSONObject jsobj(js);
    jsobj.AddProperty("type", "InstanceSet");
    jsobj.AddProperty("totalCount", count);
    {
      JSONArray samples(&jsobj, "instances");
      for (int i = 0; (i < limit) && (i < count); i++) {
        samples.AddValue(storage.At(i));
      }
    }
  }
  
  static const MethodParameter* const get_instances_as_array_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      NULL,
  };
  
  static void GetInstancesAsArray(Thread* thread, JSONStream* js) {
    const char* object_id = js->LookupParam("objectId");
    if (object_id == NULL) {
      PrintMissingParamError(js, "objectId");
      return;
    }
  
    bool include_subclasses =
        BoolParameter::Parse(js->LookupParam("includeSubclasses"), false);
    bool include_implementors =
        BoolParameter::Parse(js->LookupParam("includeImplementors"), false);
  
    const Object& obj = Object::Handle(LookupHeapObject(thread, object_id, NULL));
    if (obj.ptr() == Object::sentinel().ptr() || !obj.IsClass()) {
      PrintInvalidParamError(js, "objectId");
      return;
    }
    const Class& cls = Class::Cast(obj);
  
    // Ensure the array and handles created below are promptly destroyed.
    Array& instances = Array::Handle();
    {
      StackZone zone(thread);
      HANDLESCOPE(thread);
  
      ZoneGrowableHandlePtrArray<Object> storage(thread->zone(), 1024);
      GetInstancesVisitor visitor(&storage, kSmiMax);
      {
        ObjectGraph graph(thread);
        HeapIterationScope iteration_scope(Thread::Current(), true);
        MarkClasses(cls, include_subclasses, include_implementors);
        graph.IterateObjects(&visitor);
        UnmarkClasses();
      }
      intptr_t count = visitor.count();
      instances = Array::New(count);
      for (intptr_t i = 0; i < count; i++) {
        instances.SetAt(i, storage.At(i));
      }
    }
    instances.PrintJSON(js, /* as_ref */ true);
  }
  
  static const MethodParameter* const get_ports_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      NULL,
  };
  
  static void GetPorts(Thread* thread, JSONStream* js) {
    // Ensure the array and handles created below are promptly destroyed.
    StackZone zone(thread);
    HANDLESCOPE(thread);
    const GrowableObjectArray& ports = GrowableObjectArray::Handle(
        GrowableObjectArray::RawCast(DartLibraryCalls::LookupOpenPorts()));
    JSONObject jsobj(js);
    jsobj.AddProperty("type", "PortList");
    {
      ReceivePort& port = ReceivePort::Handle(zone.GetZone());
      JSONArray arr(&jsobj, "ports");
      for (int i = 0; i < ports.Length(); ++i) {
        port ^= ports.At(i);
        // Don't report inactive ports.
        if (PortMap::IsLivePort(port.Id())) {
          arr.AddValue(port);
        }
      }
    }
  }
  
  #if !defined(DART_PRECOMPILED_RUNTIME)
  static const char* const report_enum_names[] = {
      SourceReport::kCallSitesStr,
      SourceReport::kCoverageStr,
      SourceReport::kPossibleBreakpointsStr,
      SourceReport::kProfileStr,
      NULL,
  };
  #endif
  
  static const MethodParameter* const get_source_report_params[] = {
  #if !defined(DART_PRECOMPILED_RUNTIME)
      RUNNABLE_ISOLATE_PARAMETER,
      new EnumListParameter("reports", true, report_enum_names),
      new IdParameter("scriptId", false),
      new UIntParameter("tokenPos", false),
      new UIntParameter("endTokenPos", false),
      new BoolParameter("forceCompile", false),
  #endif
      NULL,
  };
  
  static void GetSourceReport(Thread* thread, JSONStream* js) {
  #if defined(DART_PRECOMPILED_RUNTIME)
    js->PrintError(kFeatureDisabled, "disabled in AOT mode and PRODUCT.");
  #else
    if (CheckCompilerDisabled(thread, js)) {
      return;
    }
  
    const char* reports_str = js->LookupParam("reports");
    const EnumListParameter* reports_parameter =
        static_cast<const EnumListParameter*>(get_source_report_params[1]);
    const char** reports = reports_parameter->Parse(thread->zone(), reports_str);
    intptr_t report_set = 0;
    while (*reports != NULL) {
      if (strcmp(*reports, SourceReport::kCallSitesStr) == 0) {
        report_set |= SourceReport::kCallSites;
      } else if (strcmp(*reports, SourceReport::kCoverageStr) == 0) {
        report_set |= SourceReport::kCoverage;
      } else if (strcmp(*reports, SourceReport::kPossibleBreakpointsStr) == 0) {
        report_set |= SourceReport::kPossibleBreakpoints;
      } else if (strcmp(*reports, SourceReport::kProfileStr) == 0) {
        report_set |= SourceReport::kProfile;
      }
      reports++;
    }
  
    SourceReport::CompileMode compile_mode = SourceReport::kNoCompile;
    if (BoolParameter::Parse(js->LookupParam("forceCompile"), false)) {
      compile_mode = SourceReport::kForceCompile;
    }
  
    Script& script = Script::Handle();
    intptr_t start_pos = UIntParameter::Parse(js->LookupParam("tokenPos"));
    intptr_t end_pos = UIntParameter::Parse(js->LookupParam("endTokenPos"));
  
    if (js->HasParam("scriptId")) {
      // Get the target script.
      const char* script_id_param = js->LookupParam("scriptId");
      const Object& obj =
          Object::Handle(LookupHeapObject(thread, script_id_param, NULL));
      if (obj.ptr() == Object::sentinel().ptr() || !obj.IsScript()) {
        PrintInvalidParamError(js, "scriptId");
        return;
      }
      script ^= obj.ptr();
    } else {
      if (js->HasParam("tokenPos")) {
        js->PrintError(
            kInvalidParams,
            "%s: the 'tokenPos' parameter requires the 'scriptId' parameter",
            js->method());
        return;
      }
      if (js->HasParam("endTokenPos")) {
        js->PrintError(
            kInvalidParams,
            "%s: the 'endTokenPos' parameter requires the 'scriptId' parameter",
            js->method());
        return;
      }
    }
    SourceReport report(report_set, compile_mode);
    report.PrintJSON(js, script, TokenPosition::Deserialize(start_pos),
                     TokenPosition::Deserialize(end_pos));
  #endif  // !DART_PRECOMPILED_RUNTIME
  }
  
  static const MethodParameter* const reload_sources_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      new BoolParameter("force", false),
      new BoolParameter("pause", false),
      new StringParameter("rootLibUri", false),
      new StringParameter("packagesUri", false),
      NULL,
  };
  
  static void ReloadSources(Thread* thread, JSONStream* js) {
  #if defined(DART_PRECOMPILED_RUNTIME)
    js->PrintError(kFeatureDisabled, "Compiler is disabled in AOT mode.");
  #else
    if (CheckCompilerDisabled(thread, js)) {
      return;
    }
  
    IsolateGroup* isolate_group = thread->isolate_group();
    if (isolate_group->library_tag_handler() == nullptr) {
      js->PrintError(kFeatureDisabled,
                     "A library tag handler must be installed.");
      return;
    }
    // TODO(dartbug.com/36097): We need to change the "reloadSources" service-api
    // call to accept an isolate group instead of an isolate.
    Isolate* isolate = thread->isolate();
    if ((isolate->sticky_error() != Error::null()) ||
        (Thread::Current()->sticky_error() != Error::null())) {
      js->PrintError(kIsolateReloadBarred,
                     "This isolate cannot reload sources anymore because there "
                     "was an unhandled exception error. Restart the isolate.");
      return;
    }
    if (isolate_group->IsReloading()) {
      js->PrintError(kIsolateIsReloading, "This isolate is being reloaded.");
      return;
    }
    if (!isolate_group->CanReload()) {
      js->PrintError(kFeatureDisabled,
                     "This isolate cannot reload sources right now.");
      return;
    }
    const bool force_reload =
        BoolParameter::Parse(js->LookupParam("force"), false);
  
    isolate_group->ReloadSources(js, force_reload, js->LookupParam("rootLibUri"),
                                 js->LookupParam("packagesUri"));
  
    Service::CheckForPause(isolate, js);
  
  #endif
  }
  
  void Service::CheckForPause(Isolate* isolate, JSONStream* stream) {
    // Should we pause?
    isolate->set_should_pause_post_service_request(
        BoolParameter::Parse(stream->LookupParam("pause"), false));
  }
  
  ErrorPtr Service::MaybePause(Isolate* isolate, const Error& error) {
    // Don't pause twice.
    if (!isolate->IsPaused()) {
      if (isolate->should_pause_post_service_request()) {
        isolate->set_should_pause_post_service_request(false);
        if (!error.IsNull()) {
          // Before pausing, restore the sticky error. The debugger will return it
          // from PausePostRequest.
          Thread::Current()->set_sticky_error(error);
        }
        return isolate->PausePostRequest();
      }
    }
    return error.ptr();
  }
  
  static void AddBreakpointCommon(Thread* thread,
                                  JSONStream* js,
                                  const String& script_uri) {
    if (CheckDebuggerDisabled(thread, js)) {
      return;
    }
  
    const char* line_param = js->LookupParam("line");
    intptr_t line = UIntParameter::Parse(line_param);
    const char* col_param = js->LookupParam("column");
    intptr_t col = -1;
    if (col_param != NULL) {
      col = UIntParameter::Parse(col_param);
      if (col == 0) {
        // Column number is 1-based.
        PrintInvalidParamError(js, "column");
        return;
      }
    }
    ASSERT(!script_uri.IsNull());
    Breakpoint* bpt = NULL;
    bpt = thread->isolate()->debugger()->SetBreakpointAtLineCol(script_uri, line,
                                                                col);
    if (bpt == NULL) {
      js->PrintError(kCannotAddBreakpoint,
                     "%s: Cannot add breakpoint at line '%s'", js->method(),
                     line_param);
      return;
    }
    bpt->PrintJSON(js);
  }
  
  static const MethodParameter* const add_breakpoint_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      new IdParameter("scriptId", true),
      new UIntParameter("line", true),
      new UIntParameter("column", false),
      NULL,
  };
  
  static void AddBreakpoint(Thread* thread, JSONStream* js) {
    if (CheckDebuggerDisabled(thread, js)) {
      return;
    }
  
    const char* script_id_param = js->LookupParam("scriptId");
    Object& obj = Object::Handle(LookupHeapObject(thread, script_id_param, NULL));
    if (obj.ptr() == Object::sentinel().ptr() || !obj.IsScript()) {
      PrintInvalidParamError(js, "scriptId");
      return;
    }
    const Script& script = Script::Cast(obj);
    const String& script_uri = String::Handle(script.url());
    ASSERT(!script_uri.IsNull());
    AddBreakpointCommon(thread, js, script_uri);
  }
  
  static const MethodParameter* const add_breakpoint_with_script_uri_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      new IdParameter("scriptUri", true),
      new UIntParameter("line", true),
      new UIntParameter("column", false),
      NULL,
  };
  
  static void AddBreakpointWithScriptUri(Thread* thread, JSONStream* js) {
    if (CheckDebuggerDisabled(thread, js)) {
      return;
    }
  
    const char* script_uri_param = js->LookupParam("scriptUri");
    const String& script_uri = String::Handle(String::New(script_uri_param));
    AddBreakpointCommon(thread, js, script_uri);
  }
  
  static const MethodParameter* const add_breakpoint_at_entry_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      new IdParameter("functionId", true),
      NULL,
  };
  
  static void AddBreakpointAtEntry(Thread* thread, JSONStream* js) {
    if (CheckDebuggerDisabled(thread, js)) {
      return;
    }
  
    const char* function_id = js->LookupParam("functionId");
    Object& obj = Object::Handle(LookupHeapObject(thread, function_id, NULL));
    if (obj.ptr() == Object::sentinel().ptr() || !obj.IsFunction()) {
      PrintInvalidParamError(js, "functionId");
      return;
    }
    const Function& function = Function::Cast(obj);
    Breakpoint* bpt =
        thread->isolate()->debugger()->SetBreakpointAtEntry(function, false);
    if (bpt == NULL) {
      js->PrintError(kCannotAddBreakpoint,
                     "%s: Cannot add breakpoint at function '%s'", js->method(),
                     function.ToCString());
      return;
    }
    bpt->PrintJSON(js);
  }
  
  static const MethodParameter* const add_breakpoint_at_activation_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      new IdParameter("objectId", true),
      NULL,
  };
  
  static void AddBreakpointAtActivation(Thread* thread, JSONStream* js) {
    if (CheckDebuggerDisabled(thread, js)) {
      return;
    }
  
    const char* object_id = js->LookupParam("objectId");
    Object& obj = Object::Handle(LookupHeapObject(thread, object_id, NULL));
    if (obj.ptr() == Object::sentinel().ptr() || !obj.IsInstance()) {
      PrintInvalidParamError(js, "objectId");
      return;
    }
    const Instance& closure = Instance::Cast(obj);
    Breakpoint* bpt =
        thread->isolate()->debugger()->SetBreakpointAtActivation(closure, false);
    if (bpt == NULL) {
      js->PrintError(kCannotAddBreakpoint,
                     "%s: Cannot add breakpoint at activation", js->method());
      return;
    }
    bpt->PrintJSON(js);
  }
  
  static const MethodParameter* const remove_breakpoint_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      NULL,
  };
  
  static void RemoveBreakpoint(Thread* thread, JSONStream* js) {
    if (CheckDebuggerDisabled(thread, js)) {
      return;
    }
  
    if (!js->HasParam("breakpointId")) {
      PrintMissingParamError(js, "breakpointId");
      return;
    }
    const char* bpt_id = js->LookupParam("breakpointId");
    ObjectIdRing::LookupResult lookup_result;
    Isolate* isolate = thread->isolate();
    Breakpoint* bpt = LookupBreakpoint(isolate, bpt_id, &lookup_result);
    // TODO(turnidge): Should we return a different error for bpts whic
    // have been already removed?
    if (bpt == NULL) {
      PrintInvalidParamError(js, "breakpointId");
      return;
    }
    isolate->debugger()->RemoveBreakpoint(bpt->id());
    PrintSuccess(js);
  }
  
  static ClassPtr GetMetricsClass(Thread* thread) {
    Zone* zone = thread->zone();
    const Library& prof_lib = Library::Handle(zone, Library::DeveloperLibrary());
    ASSERT(!prof_lib.IsNull());
    const String& metrics_cls_name = String::Handle(zone, String::New("Metrics"));
    ASSERT(!metrics_cls_name.IsNull());
    const Class& metrics_cls =
        Class::Handle(zone, prof_lib.LookupClass(metrics_cls_name));
    ASSERT(!metrics_cls.IsNull());
    return metrics_cls.ptr();
  }
  
  static void HandleNativeMetricsList(Thread* thread, JSONStream* js) {
    JSONObject obj(js);
    obj.AddProperty("type", "MetricList");
    {
      JSONArray metrics(&obj, "metrics");
  
      auto isolate = thread->isolate();
  #define ADD_METRIC(type, variable, name, unit)                                 \
    metrics.AddValue(isolate->Get##variable##Metric());
      ISOLATE_METRIC_LIST(ADD_METRIC);
  #undef ADD_METRIC
  
      auto isolate_group = thread->isolate_group();
  #define ADD_METRIC(type, variable, name, unit)                                 \
    metrics.AddValue(isolate_group->Get##variable##Metric());
      ISOLATE_GROUP_METRIC_LIST(ADD_METRIC);
  #undef ADD_METRIC
    }
  }
  
  static void HandleNativeMetric(Thread* thread, JSONStream* js, const char* id) {
    auto isolate = thread->isolate();
  #define ADD_METRIC(type, variable, name, unit)                                 \
    if (strcmp(id, name) == 0) {                                                 \
      isolate->Get##variable##Metric()->PrintJSON(js);                           \
      return;                                                                    \
    }
    ISOLATE_METRIC_LIST(ADD_METRIC);
  #undef ADD_METRIC
  
    auto isolate_group = thread->isolate_group();
  #define ADD_METRIC(type, variable, name, unit)                                 \
    if (strcmp(id, name) == 0) {                                                 \
      isolate_group->Get##variable##Metric()->PrintJSON(js);                     \
      return;                                                                    \
    }
    ISOLATE_GROUP_METRIC_LIST(ADD_METRIC);
  #undef ADD_METRIC
  
    PrintInvalidParamError(js, "metricId");
  }
  
  static void HandleDartMetricsList(Thread* thread, JSONStream* js) {
    Zone* zone = thread->zone();
    const Class& metrics_cls = Class::Handle(zone, GetMetricsClass(thread));
    const auto& error = metrics_cls.EnsureIsFinalized(Thread::Current());
    ASSERT(error == Error::null());
    const String& print_metrics_name =
        String::Handle(String::New("_printMetrics"));
    ASSERT(!print_metrics_name.IsNull());
    const Function& print_metrics = Function::Handle(
        zone, metrics_cls.LookupStaticFunctionAllowPrivate(print_metrics_name));
    ASSERT(!print_metrics.IsNull());
    const Array& args = Object::empty_array();
    const Object& result =
        Object::Handle(zone, DartEntry::InvokeFunction(print_metrics, args));
    ASSERT(!result.IsNull());
    ASSERT(result.IsString());
    TextBuffer* buffer = js->buffer();
    buffer->AddString(String::Cast(result).ToCString());
  }
  
  static void HandleDartMetric(Thread* thread, JSONStream* js, const char* id) {
    Zone* zone = thread->zone();
    const Class& metrics_cls = Class::Handle(zone, GetMetricsClass(thread));
    const String& print_metric_name = String::Handle(String::New("_printMetric"));
    ASSERT(!print_metric_name.IsNull());
    const Function& print_metric = Function::Handle(
        zone, metrics_cls.LookupStaticFunctionAllowPrivate(print_metric_name));
    ASSERT(!print_metric.IsNull());
    const String& arg0 = String::Handle(String::New(id));
    ASSERT(!arg0.IsNull());
    const Array& args = Array::Handle(Array::New(1));
    ASSERT(!args.IsNull());
    args.SetAt(0, arg0);
    const Object& result =
        Object::Handle(zone, DartEntry::InvokeFunction(print_metric, args));
    if (!result.IsNull()) {
      ASSERT(result.IsString());
      TextBuffer* buffer = js->buffer();
      buffer->AddString(String::Cast(result).ToCString());
      return;
    }
    PrintInvalidParamError(js, "metricId");
  }
  
  static const MethodParameter* const get_isolate_metric_list_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      NULL,
  };
  
  static void GetIsolateMetricList(Thread* thread, JSONStream* js) {
    bool native_metrics = false;
    if (js->HasParam("type")) {
      if (js->ParamIs("type", "Native")) {
        native_metrics = true;
      } else if (js->ParamIs("type", "Dart")) {
        native_metrics = false;
      } else {
        PrintInvalidParamError(js, "type");
        return;
      }
    } else {
      PrintMissingParamError(js, "type");
      return;
    }
    if (native_metrics) {
      HandleNativeMetricsList(thread, js);
    } else {
      HandleDartMetricsList(thread, js);
    }
  }
  
  static const MethodParameter* const get_isolate_metric_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      NULL,
  };
  
  static void GetIsolateMetric(Thread* thread, JSONStream* js) {
    const char* metric_id = js->LookupParam("metricId");
    if (metric_id == NULL) {
      PrintMissingParamError(js, "metricId");
      return;
    }
    // Verify id begins with "metrics/".
    static const char* const kMetricIdPrefix = "metrics/";
    static intptr_t kMetricIdPrefixLen = strlen(kMetricIdPrefix);
    if (strncmp(metric_id, kMetricIdPrefix, kMetricIdPrefixLen) != 0) {
      PrintInvalidParamError(js, "metricId");
      return;
    }
    // Check if id begins with "metrics/native/".
    static const char* const kNativeMetricIdPrefix = "metrics/native/";
    static intptr_t kNativeMetricIdPrefixLen = strlen(kNativeMetricIdPrefix);
    const bool native_metric =
        strncmp(metric_id, kNativeMetricIdPrefix, kNativeMetricIdPrefixLen) == 0;
    const char* id = metric_id + (native_metric ? kNativeMetricIdPrefixLen
                                                : kMetricIdPrefixLen);
    if (native_metric) {
      HandleNativeMetric(thread, js, id);
    } else {
      HandleDartMetric(thread, js, id);
    }
  }
  
  static const char* const timeline_streams_enum_names[] = {
      "all",
  #define DEFINE_NAME(name, unused) #name,
      TIMELINE_STREAM_LIST(DEFINE_NAME)
  #undef DEFINE_NAME
          NULL};
  
  static const MethodParameter* const set_vm_timeline_flags_params[] = {
      NO_ISOLATE_PARAMETER,
      new EnumListParameter("recordedStreams",
                            false,
                            timeline_streams_enum_names),
      NULL,
  };
  
  #if defined(SUPPORT_TIMELINE)
  static bool HasStream(const char** recorded_streams, const char* stream) {
    while (*recorded_streams != NULL) {
      if ((strstr(*recorded_streams, "all") != NULL) ||
          (strstr(*recorded_streams, stream) != NULL)) {
        return true;
      }
      recorded_streams++;
    }
    return false;
  }
  #endif
  
  static void SetVMTimelineFlags(Thread* thread, JSONStream* js) {
  #if !defined(SUPPORT_TIMELINE)
    PrintSuccess(js);
  #else
    Isolate* isolate = thread->isolate();
    ASSERT(isolate != NULL);
    StackZone zone(thread);
  
    const EnumListParameter* recorded_streams_param =
        static_cast<const EnumListParameter*>(set_vm_timeline_flags_params[1]);
  
    const char* recorded_streams_str = js->LookupParam("recordedStreams");
    const char** recorded_streams =
        recorded_streams_param->Parse(thread->zone(), recorded_streams_str);
  
  #define SET_ENABLE_STREAM(name, unused)                                        \
    Timeline::SetStream##name##Enabled(HasStream(recorded_streams, #name));
    TIMELINE_STREAM_LIST(SET_ENABLE_STREAM);
  #undef SET_ENABLE_STREAM
  
    // Notify clients that the set of subscribed streams has been updated.
    if (Service::timeline_stream.enabled()) {
      ServiceEvent event(ServiceEvent::kTimelineStreamSubscriptionsUpdate);
      Service::HandleEvent(&event);
    }
  
    PrintSuccess(js);
  #endif
  }
  
  static const MethodParameter* const get_vm_timeline_flags_params[] = {
      NO_ISOLATE_PARAMETER,
      NULL,
  };
  
  static void GetVMTimelineFlags(Thread* thread, JSONStream* js) {
  #if !defined(SUPPORT_TIMELINE)
    JSONObject obj(js);
    obj.AddProperty("type", "TimelineFlags");
  #else
    Isolate* isolate = thread->isolate();
    ASSERT(isolate != NULL);
    StackZone zone(thread);
    Timeline::PrintFlagsToJSON(js);
  #endif
  }
  
  static const MethodParameter* const get_vm_timeline_micros_params[] = {
      NO_ISOLATE_PARAMETER,
      NULL,
  };
  
  static void GetVMTimelineMicros(Thread* thread, JSONStream* js) {
    JSONObject obj(js);
    obj.AddProperty("type", "Timestamp");
    obj.AddPropertyTimeMicros("timestamp", OS::GetCurrentMonotonicMicros());
  }
  
  static const MethodParameter* const clear_vm_timeline_params[] = {
      NO_ISOLATE_PARAMETER,
      NULL,
  };
  
  static void ClearVMTimeline(Thread* thread, JSONStream* js) {
    Isolate* isolate = thread->isolate();
    ASSERT(isolate != NULL);
    StackZone zone(thread);
  
    Timeline::Clear();
  
    PrintSuccess(js);
  }
  
  static const MethodParameter* const get_vm_timeline_params[] = {
      NO_ISOLATE_PARAMETER,
      new Int64Parameter("timeOriginMicros", false),
      new Int64Parameter("timeExtentMicros", false),
      NULL,
  };
  
  static void GetVMTimeline(Thread* thread, JSONStream* js) {
    Isolate* isolate = thread->isolate();
    ASSERT(isolate != NULL);
    StackZone zone(thread);
    Timeline::ReclaimCachedBlocksFromThreads();
    TimelineEventRecorder* timeline_recorder = Timeline::recorder();
    // TODO(johnmccutchan): Return an error.
    ASSERT(timeline_recorder != NULL);
    const char* name = timeline_recorder->name();
    if ((strcmp(name, FUCHSIA_RECORDER_NAME) == 0) ||
        (strcmp(name, SYSTRACE_RECORDER_NAME) == 0)) {
      js->PrintError(kInvalidTimelineRequest,
                     "A recorder of type \"%s\" is "
                     "currently in use. As a result, timeline events are handled "
                     "by the OS rather than the VM. See the VM service "
                     "documentation for more details on where timeline events "
                     "can be found for this recorder type.",
                     timeline_recorder->name());
      return;
    }
    int64_t time_origin_micros =
        Int64Parameter::Parse(js->LookupParam("timeOriginMicros"));
    int64_t time_extent_micros =
        Int64Parameter::Parse(js->LookupParam("timeExtentMicros"));
    TimelineEventFilter filter(time_origin_micros, time_extent_micros);
    timeline_recorder->PrintJSON(js, &filter);
  }
  
  static const char* const step_enum_names[] = {
      "None", "Into", "Over", "Out", "Rewind", "OverAsyncSuspension", NULL,
  };
  
  static const Debugger::ResumeAction step_enum_values[] = {
      Debugger::kContinue,   Debugger::kStepInto,
      Debugger::kStepOver,   Debugger::kStepOut,
      Debugger::kStepRewind, Debugger::kStepOverAsyncSuspension,
      Debugger::kContinue,  // Default value
  };
  
  static const MethodParameter* const resume_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      new EnumParameter("step", false, step_enum_names),
      new UIntParameter("frameIndex", false),
      NULL,
  };
  
  static void Resume(Thread* thread, JSONStream* js) {
    const char* step_param = js->LookupParam("step");
    Debugger::ResumeAction step = Debugger::kContinue;
    if (step_param != NULL) {
      step = EnumMapper(step_param, step_enum_names, step_enum_values);
    }
    intptr_t frame_index = 1;
    const char* frame_index_param = js->LookupParam("frameIndex");
    if (frame_index_param != NULL) {
      if (step != Debugger::kStepRewind) {
        // Only rewind supports the frameIndex parameter.
        js->PrintError(
            kInvalidParams,
            "%s: the 'frameIndex' parameter can only be used when rewinding",
            js->method());
        return;
      }
      frame_index = UIntParameter::Parse(js->LookupParam("frameIndex"));
    }
    Isolate* isolate = thread->isolate();
    if (isolate->message_handler()->is_paused_on_start()) {
      // If the user is issuing a 'Over' or an 'Out' step, that is the
      // same as a regular resume request.
      if (step == Debugger::kStepInto) {
        isolate->debugger()->EnterSingleStepMode();
      }
      isolate->message_handler()->set_should_pause_on_start(false);
      isolate->SetResumeRequest();
      if (Service::debug_stream.enabled()) {
        ServiceEvent event(isolate, ServiceEvent::kResume);
        Service::HandleEvent(&event);
      }
      PrintSuccess(js);
      return;
    }
    if (isolate->message_handler()->should_pause_on_start()) {
      isolate->message_handler()->set_should_pause_on_start(false);
      isolate->SetResumeRequest();
      if (Service::debug_stream.enabled()) {
        ServiceEvent event(isolate, ServiceEvent::kResume);
        Service::HandleEvent(&event);
      }
      PrintSuccess(js);
      return;
    }
    if (isolate->message_handler()->is_paused_on_exit()) {
      isolate->message_handler()->set_should_pause_on_exit(false);
      isolate->SetResumeRequest();
      // We don't send a resume event because we will be exiting.
      PrintSuccess(js);
      return;
    }
    if (isolate->debugger()->PauseEvent() == NULL) {
      js->PrintError(kIsolateMustBePaused, NULL);
      return;
    }
  
    const char* error = NULL;
    if (!isolate->debugger()->SetResumeAction(step, frame_index, &error)) {
      js->PrintError(kCannotResume, "%s", error);
      return;
    }
    isolate->SetResumeRequest();
    PrintSuccess(js);
  }
  
  static const MethodParameter* const kill_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      NULL,
  };
  
  static void Kill(Thread* thread, JSONStream* js) {
    const String& msg =
        String::Handle(String::New("isolate terminated by Kill service request"));
    const UnwindError& error = UnwindError::Handle(UnwindError::New(msg));
    error.set_is_user_initiated(true);
    Thread::Current()->set_sticky_error(error);
    PrintSuccess(js);
  }
  
  static const MethodParameter* const pause_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      NULL,
  };
  
  static void Pause(Thread* thread, JSONStream* js) {
    // TODO(turnidge): This interrupt message could have been sent from
    // the service isolate directly, but would require some special case
    // code.  That would prevent this isolate getting double-interrupted
    // with OOB messages.
    Isolate* isolate = thread->isolate();
    isolate->SendInternalLibMessage(Isolate::kInterruptMsg,
                                    isolate->pause_capability());
    PrintSuccess(js);
  }
  
  static const MethodParameter* const enable_profiler_params[] = {
      NULL,
  };
  
  static void EnableProfiler(Thread* thread, JSONStream* js) {
    if (!FLAG_profiler) {
      FLAG_profiler = true;
      Profiler::Init();
    }
    PrintSuccess(js);
  }
  
  static const MethodParameter* const get_tag_profile_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      NULL,
  };
  
  static void GetTagProfile(Thread* thread, JSONStream* js) {
    JSONObject miniProfile(js);
    miniProfile.AddProperty("type", "TagProfile");
    thread->isolate()->vm_tag_counters()->PrintToJSONObject(&miniProfile);
  }
  
  static const MethodParameter* const get_cpu_samples_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      new Int64Parameter("timeOriginMicros", false),
      new Int64Parameter("timeExtentMicros", false),
      NULL,
  };
  
  static void GetCpuSamples(Thread* thread, JSONStream* js) {
    int64_t time_origin_micros =
        Int64Parameter::Parse(js->LookupParam("timeOriginMicros"));
    int64_t time_extent_micros =
        Int64Parameter::Parse(js->LookupParam("timeExtentMicros"));
    const bool include_code_samples =
        BoolParameter::Parse(js->LookupParam("_code"), false);
    if (CheckProfilerDisabled(thread, js)) {
      return;
    }
    ProfilerService::PrintJSON(js, time_origin_micros, time_extent_micros,
                               include_code_samples);
  }
  
  static const MethodParameter* const get_allocation_traces_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      new IdParameter("classId", false),
      new Int64Parameter("timeOriginMicros", false),
      new Int64Parameter("timeExtentMicros", false),
      NULL,
  };
  
  static void GetAllocationTraces(Thread* thread, JSONStream* js) {
    int64_t time_origin_micros =
        Int64Parameter::Parse(js->LookupParam("timeOriginMicros"));
    int64_t time_extent_micros =
        Int64Parameter::Parse(js->LookupParam("timeExtentMicros"));
    Isolate* isolate = thread->isolate();
  
    // Return only allocations for objects with classId.
    if (js->HasParam("classId")) {
      const char* class_id = js->LookupParam("classId");
      intptr_t cid = -1;
      GetPrefixedIntegerId(class_id, "classes/", &cid);
      if (IsValidClassId(isolate, cid)) {
        if (CheckProfilerDisabled(thread, js)) {
          return;
        }
        const Class& cls = Class::Handle(GetClassForId(isolate, cid));
        ProfilerService::PrintAllocationJSON(js, cls, time_origin_micros,
                                             time_extent_micros);
      } else {
        PrintInvalidParamError(js, "classId");
      }
    } else {
      // Otherwise, return allocations for all traced class IDs.
      if (CheckProfilerDisabled(thread, js)) {
        return;
      }
      ProfilerService::PrintAllocationJSON(js, time_origin_micros,
                                           time_extent_micros);
    }
  }
  
  static const MethodParameter* const get_native_allocation_samples_params[] = {
      NO_ISOLATE_PARAMETER,
      new Int64Parameter("timeOriginMicros", false),
      new Int64Parameter("timeExtentMicros", false),
      NULL,
  };
  
  static void GetNativeAllocationSamples(Thread* thread, JSONStream* js) {
    int64_t time_origin_micros =
        Int64Parameter::Parse(js->LookupParam("timeOriginMicros"));
    int64_t time_extent_micros =
        Int64Parameter::Parse(js->LookupParam("timeExtentMicros"));
    bool include_code_samples =
        BoolParameter::Parse(js->LookupParam("_code"), false);
  #if defined(DEBUG)
    IsolateGroup::Current()->heap()->CollectAllGarbage();
  #endif
    if (CheckNativeAllocationProfilerDisabled(thread, js)) {
      return;
    }
    ProfilerService::PrintNativeAllocationJSON(
        js, time_origin_micros, time_extent_micros, include_code_samples);
  }
  
  static const MethodParameter* const clear_cpu_samples_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      NULL,
  };
  
  static void ClearCpuSamples(Thread* thread, JSONStream* js) {
    ProfilerService::ClearSamples();
    PrintSuccess(js);
  }
  
  static void GetAllocationProfileImpl(Thread* thread,
                                       JSONStream* js,
                                       bool internal) {
    bool should_reset_accumulator = false;
    bool should_collect = false;
    if (js->HasParam("reset")) {
      if (js->ParamIs("reset", "true")) {
        should_reset_accumulator = true;
      } else {
        PrintInvalidParamError(js, "reset");
        return;
      }
    }
    if (js->HasParam("gc")) {
      if (js->ParamIs("gc", "true")) {
        should_collect = true;
      } else {
        PrintInvalidParamError(js, "gc");
        return;
      }
    }
    auto isolate_group = thread->isolate_group();
    if (should_reset_accumulator) {
      isolate_group->UpdateLastAllocationProfileAccumulatorResetTimestamp();
    }
    if (should_collect) {
      isolate_group->UpdateLastAllocationProfileGCTimestamp();
      isolate_group->heap()->CollectAllGarbage();
    }
    isolate_group->class_table()->AllocationProfilePrintJSON(js, internal);
  }
  
  static const MethodParameter* const get_allocation_profile_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      NULL,
  };
  
  static void GetAllocationProfilePublic(Thread* thread, JSONStream* js) {
    GetAllocationProfileImpl(thread, js, false);
  }
  
  static void GetAllocationProfile(Thread* thread, JSONStream* js) {
    GetAllocationProfileImpl(thread, js, true);
  }
  
  static const MethodParameter* const collect_all_garbage_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      NULL,
  };
  
  static void CollectAllGarbage(Thread* thread, JSONStream* js) {
    auto heap = thread->isolate_group()->heap();
    heap->CollectAllGarbage(Heap::kDebugging);
    PrintSuccess(js);
  }
  
  static const MethodParameter* const get_heap_map_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      NULL,
  };
  
  static void GetHeapMap(Thread* thread, JSONStream* js) {
    auto isolate_group = thread->isolate_group();
    if (js->HasParam("gc")) {
      if (js->ParamIs("gc", "scavenge")) {
        isolate_group->heap()->CollectGarbage(Heap::kScavenge, Heap::kDebugging);
      } else if (js->ParamIs("gc", "mark-sweep")) {
        isolate_group->heap()->CollectGarbage(Heap::kMarkSweep, Heap::kDebugging);
      } else if (js->ParamIs("gc", "mark-compact")) {
        isolate_group->heap()->CollectGarbage(Heap::kMarkCompact,
                                              Heap::kDebugging);
      } else {
        PrintInvalidParamError(js, "gc");
        return;
      }
    }
    isolate_group->heap()->PrintHeapMapToJSONStream(isolate_group, js);
  }
  
  static const MethodParameter* const request_heap_snapshot_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      NULL,
  };
  
  static void RequestHeapSnapshot(Thread* thread, JSONStream* js) {
    if (Service::heapsnapshot_stream.enabled()) {
      HeapSnapshotWriter writer(thread);
      writer.Write();
    }
    // TODO(koda): Provide some id that ties this request to async response(s).
    PrintSuccess(js);
  }
  
  #if defined(DART_HOST_OS_LINUX) || defined(DART_HOST_OS_ANDROID)
  struct VMMapping {
    char path[256];
    size_t size;
  };
  
  static void AddVMMappings(JSONArray* rss_children) {
    FILE* fp = fopen("/proc/self/smaps", "r");
    if (fp == nullptr) {
      return;
    }
  
    MallocGrowableArray<VMMapping> mappings(10);
    char line[256];
    char path[256];
    char property[32];
    size_t start, end, size;
    while (fgets(line, sizeof(line), fp) != nullptr) {
      if (sscanf(line, "%zx-%zx", &start, &end) == 2) {
        // Mapping line.
        strncpy(path, strrchr(line, ' ') + 1, sizeof(path) - 1);
        int len = strlen(path);
        if ((len > 0) && path[len - 1] == '\n') {
          path[len - 1] = 0;
        }
      } else if (sscanf(line, "%s%zd", property, &size) == 2) {
        // Property line.
        // Skipping a few paths to avoid double counting:
        // (deleted) - memfd dual mapping in Dart heap
        // [heap] - sbrk area, should already included with malloc
        // <empty> - anonymous mappings, mostly in Dart heap
        if ((strcmp(property, "Rss:") == 0) && (size != 0) &&
            (strcmp(path, "(deleted)") != 0) && (strcmp(path, "[heap]") != 0) &&
            (strcmp(path, "") != 0)) {
          bool updated = false;
          for (intptr_t i = 0; i < mappings.length(); i++) {
            if (strcmp(mappings[i].path, path) == 0) {
              mappings[i].size += size;
              updated = true;
              break;
            }
          }
          if (!updated) {
            VMMapping mapping;
            strncpy(mapping.path, path, sizeof(mapping.path));
            mapping.size = size;
            mappings.Add(mapping);
          }
        }
      }
    }
    fclose(fp);
  
    for (intptr_t i = 0; i < mappings.length(); i++) {
      JSONObject mapping(rss_children);
      mapping.AddProperty("name", mappings[i].path);
      mapping.AddProperty("description",
                          "Mapped file / shared library / executable");
      mapping.AddProperty64("size", mappings[i].size * KB);
      JSONArray(&mapping, "children");
    }
  }
  #endif
  
  static intptr_t GetProcessMemoryUsageHelper(JSONStream* js) {
    JSONObject response(js);
    response.AddProperty("type", "ProcessMemoryUsage");
  
    JSONObject rss(&response, "root");
    rss.AddPropertyF("name", "Process %" Pd "", OS::ProcessId());
    rss.AddProperty("description", "Resident set size");
    rss.AddProperty64("size", Service::CurrentRSS());
    JSONArray rss_children(&rss, "children");
  
    intptr_t vm_size = 0;
    {
      JSONObject vm(&rss_children);
      {
        JSONArray vm_children(&vm, "children");
  
        {
          JSONObject profiler(&vm_children);
          profiler.AddProperty("name", "Profiler");
          profiler.AddProperty("description",
                               "Samples from the Dart VM's profiler");
          intptr_t size = Profiler::Size();
          vm_size += size;
          profiler.AddProperty64("size", size);
          JSONArray(&profiler, "children");
        }
  
        {
          JSONObject timeline(&vm_children);
          timeline.AddProperty("name", "Timeline");
          timeline.AddProperty(
              "description",
              "Timeline events from dart:developer and Dart_TimelineEvent");
          intptr_t size = Timeline::recorder()->Size();
          vm_size += size;
          timeline.AddProperty64("size", size);
          JSONArray(&timeline, "children");
        }
  
        {
          JSONObject zone(&vm_children);
          zone.AddProperty("name", "Zone");
          zone.AddProperty("description", "Arena allocation in the Dart VM");
          intptr_t size = Zone::Size();
          vm_size += size;
          zone.AddProperty64("size", size);
          JSONArray(&zone, "children");
        }
  
        {
          JSONObject semi(&vm_children);
          semi.AddProperty("name", "SemiSpace Cache");
          semi.AddProperty("description", "Cached heap regions");
          intptr_t size = SemiSpace::CachedSize();
          vm_size += size;
          semi.AddProperty64("size", size);
          JSONArray(&semi, "children");
        }
  
        IsolateGroup::ForEach([&vm_children,
                               &vm_size](IsolateGroup* isolate_group) {
          // Note: new_space()->CapacityInWords() includes memory that hasn't been
          // allocated from the OS yet.
          int64_t capacity =
              (isolate_group->heap()->new_space()->UsedInWords() +
               isolate_group->heap()->old_space()->CapacityInWords()) *
              kWordSize;
          int64_t used = isolate_group->heap()->TotalUsedInWords() * kWordSize;
          int64_t free = capacity - used;
  
          JSONObject group(&vm_children);
          group.AddPropertyF("name", "IsolateGroup %s",
                             isolate_group->source()->name);
          group.AddProperty("description", "Dart heap capacity");
          vm_size += capacity;
          group.AddProperty64("size", capacity);
          JSONArray group_children(&group, "children");
  
          {
            JSONObject jsused(&group_children);
            jsused.AddProperty("name", "Used");
            jsused.AddProperty("description", "");
            jsused.AddProperty64("size", used);
            JSONArray(&jsused, "children");
          }
  
          {
            JSONObject jsfree(&group_children);
            jsfree.AddProperty("name", "Free");
            jsfree.AddProperty("description", "");
            jsfree.AddProperty64("size", free);
            JSONArray(&jsfree, "children");
          }
        });
      }  // vm_children
  
      vm.AddProperty("name", "Dart VM");
      vm.AddProperty("description", "");
      vm.AddProperty64("size", vm_size);
    }
  
    // On Android, malloc is better labeled by /proc/self/smaps.
  #if !defined(DART_HOST_OS_ANDROID)
    intptr_t used, capacity;
    const char* implementation;
    if (MallocHooks::GetStats(&used, &capacity, &implementation)) {
      JSONObject malloc(&rss_children);
      malloc.AddPropertyF("name", "Malloc (%s)", implementation);
      malloc.AddProperty("description", "");
      malloc.AddProperty64("size", capacity);
      JSONArray malloc_children(&malloc, "children");
  
      {
        JSONObject malloc_used(&malloc_children);
        malloc_used.AddProperty("name", "Used");
        malloc_used.AddProperty("description", "");
        malloc_used.AddProperty64("size", used);
        JSONArray(&malloc_used, "children");
      }
  
      {
        JSONObject malloc_free(&malloc_children);
        malloc_free.AddProperty("name", "Free");
        malloc_free.AddProperty("description", "");
        malloc_free.AddProperty64("size", capacity - used);
        JSONArray(&malloc_free, "children");
      }
    }
  #endif
  
  #if defined(DART_HOST_OS_LINUX) || defined(DART_HOST_OS_ANDROID)
    AddVMMappings(&rss_children);
  #endif
    // TODO(46166): Implement for other operating systems.
  
    return vm_size;
  }
  
  static const MethodParameter* const get_process_memory_usage_params[] = {
      NULL,
  };
  
  static void GetProcessMemoryUsage(Thread* thread, JSONStream* js) {
    GetProcessMemoryUsageHelper(js);
  }
  
  void Service::SendInspectEvent(Isolate* isolate, const Object& inspectee) {
    if (!Service::debug_stream.enabled()) {
      return;
    }
    ServiceEvent event(isolate, ServiceEvent::kInspect);
    event.set_inspectee(&inspectee);
    Service::HandleEvent(&event);
  }
  
  void Service::SendEmbedderEvent(Isolate* isolate,
                                  const char* stream_id,
                                  const char* event_kind,
                                  const uint8_t* bytes,
                                  intptr_t bytes_len) {
    ServiceEvent event(isolate, ServiceEvent::kEmbedder);
    event.set_embedder_kind(event_kind);
    event.set_embedder_stream_id(stream_id);
    event.set_bytes(bytes, bytes_len);
    Service::HandleEvent(&event);
  }
  
  void Service::SendLogEvent(Isolate* isolate,
                             int64_t sequence_number,
                             int64_t timestamp,
                             intptr_t level,
                             const String& name,
                             const String& message,
                             const Instance& zone,
                             const Object& error,
                             const Instance& stack_trace) {
    if (!Service::logging_stream.enabled()) {
      return;
    }
    ServiceEvent::LogRecord log_record;
    log_record.sequence_number = sequence_number;
    log_record.timestamp = timestamp;
    log_record.level = level;
    log_record.name = &name;
    log_record.message = &message;
    log_record.zone = &zone;
    log_record.error = &error;
    log_record.stack_trace = &stack_trace;
    ServiceEvent event(isolate, ServiceEvent::kLogging);
    event.set_log_record(log_record);
    Service::HandleEvent(&event);
  }
  
  void Service::SendExtensionEvent(Isolate* isolate,
                                   const String& event_kind,
                                   const String& event_data) {
    if (!Service::extension_stream.enabled()) {
      return;
    }
    ServiceEvent::ExtensionEvent extension_event;
    extension_event.event_kind = &event_kind;
    extension_event.event_data = &event_data;
    ServiceEvent event(isolate, ServiceEvent::kExtension);
    event.set_extension_event(extension_event);
    Service::HandleEvent(&event);
  }
  
  static const MethodParameter* const get_persistent_handles_params[] = {
      ISOLATE_PARAMETER,
      NULL,
  };
  
  template <typename T>
  class PersistentHandleVisitor : public HandleVisitor {
   public:
    PersistentHandleVisitor(Thread* thread, JSONArray* handles)
        : HandleVisitor(thread), handles_(handles) {
      ASSERT(handles_ != NULL);
    }
  
    void Append(PersistentHandle* persistent_handle) {
      JSONObject obj(handles_);
      obj.AddProperty("type", "_PersistentHandle");
      const Object& object = Object::Handle(persistent_handle->ptr());
      obj.AddProperty("object", object);
    }
  
    void Append(FinalizablePersistentHandle* weak_persistent_handle) {
      if (!weak_persistent_handle->ptr()->IsHeapObject()) {
        return;  // Free handle.
      }
  
      JSONObject obj(handles_);
      obj.AddProperty("type", "_WeakPersistentHandle");
      const Object& object = Object::Handle(weak_persistent_handle->ptr());
      obj.AddProperty("object", object);
      obj.AddPropertyF(
          "peer", "0x%" Px "",
          reinterpret_cast<uintptr_t>(weak_persistent_handle->peer()));
      obj.AddPropertyF(
          "callbackAddress", "0x%" Px "",
          reinterpret_cast<uintptr_t>(weak_persistent_handle->callback()));
      // Attempt to include a native symbol name.
      char* name = NativeSymbolResolver::LookupSymbolName(
          reinterpret_cast<uword>(weak_persistent_handle->callback()), nullptr);
      obj.AddProperty("callbackSymbolName", (name == nullptr) ? "" : name);
      if (name != nullptr) {
        NativeSymbolResolver::FreeSymbolName(name);
      }
      obj.AddPropertyF("externalSize", "%" Pd "",
                       weak_persistent_handle->external_size());
    }
  
   protected:
    virtual void VisitHandle(uword addr) {
      T* handle = reinterpret_cast<T*>(addr);
      Append(handle);
    }
  
    JSONArray* handles_;
  };
  
  static void GetPersistentHandles(Thread* thread, JSONStream* js) {
    Isolate* isolate = thread->isolate();
    ASSERT(isolate != NULL);
  
    ApiState* api_state = isolate->group()->api_state();
    ASSERT(api_state != NULL);
  
    {
      JSONObject obj(js);
      obj.AddProperty("type", "_PersistentHandles");
      // Persistent handles.
      {
        JSONArray persistent_handles(&obj, "persistentHandles");
        api_state->RunWithLockedPersistentHandles(
            [&](PersistentHandles& handles) {
              PersistentHandleVisitor<FinalizablePersistentHandle> visitor(
                  thread, &persistent_handles);
              handles.Visit(&visitor);
            });
      }
      // Weak persistent handles.
      {
        JSONArray weak_persistent_handles(&obj, "weakPersistentHandles");
        api_state->RunWithLockedWeakPersistentHandles(
            [&](FinalizablePersistentHandles& handles) {
              PersistentHandleVisitor<FinalizablePersistentHandle> visitor(
                  thread, &weak_persistent_handles);
              handles.VisitHandles(&visitor);
            });
      }
    }
  }
  
  static const MethodParameter* const get_ports_private_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      NULL,
  };
  
  static void GetPortsPrivate(Thread* thread, JSONStream* js) {
    MessageHandler* message_handler = thread->isolate()->message_handler();
    PortMap::PrintPortsForMessageHandler(message_handler, js);
  }
  
  static void RespondWithMalformedJson(Thread* thread, JSONStream* js) {
    JSONObject jsobj(js);
    jsobj.AddProperty("a", "a");
    JSONObject jsobj1(js);
    jsobj1.AddProperty("a", "a");
    JSONObject jsobj2(js);
    jsobj2.AddProperty("a", "a");
    JSONObject jsobj3(js);
    jsobj3.AddProperty("a", "a");
  }
  
  static void RespondWithMalformedObject(Thread* thread, JSONStream* js) {
    JSONObject jsobj(js);
    jsobj.AddProperty("bart", "simpson");
  }
  
  static const MethodParameter* const get_object_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      new UIntParameter("offset", false),
      new UIntParameter("count", false),
      NULL,
  };
  
  static void GetObject(Thread* thread, JSONStream* js) {
    const char* id = js->LookupParam("objectId");
    if (id == NULL) {
      PrintMissingParamError(js, "objectId");
      return;
    }
    if (js->HasParam("offset")) {
      intptr_t value = UIntParameter::Parse(js->LookupParam("offset"));
      if (value < 0) {
        PrintInvalidParamError(js, "offset");
        return;
      }
      js->set_offset(value);
    }
    if (js->HasParam("count")) {
      intptr_t value = UIntParameter::Parse(js->LookupParam("count"));
      if (value < 0) {
        PrintInvalidParamError(js, "count");
        return;
      }
      js->set_count(value);
    }
  
    // Handle heap objects.
    ObjectIdRing::LookupResult lookup_result;
    Object& obj = Object::Handle(LookupHeapObject(thread, id, &lookup_result));
    if (obj.ptr() != Object::sentinel().ptr()) {
  #if !defined(DART_PRECOMPILED_RUNTIME)
      // If obj is a script from dart:* and doesn't have source loaded, try and
      // load the source before sending the response.
      if (obj.IsScript()) {
        const Script& script = Script::Cast(obj);
        script.LookupSourceAndLineStarts(thread->zone());
        if (!script.HasSource() && script.IsPartOfDartColonLibrary() &&
            Service::HasDartLibraryKernelForSources()) {
          const uint8_t* kernel_buffer = Service::dart_library_kernel();
          const intptr_t kernel_buffer_len =
              Service::dart_library_kernel_length();
          script.LoadSourceFromKernel(kernel_buffer, kernel_buffer_len);
        }
      }
  #endif  // !defined(DART_PRECOMPILED_RUNTIME)
      // We found a heap object for this id.  Return it.
      obj.PrintJSON(js, false);
      return;
    } else if (lookup_result == ObjectIdRing::kCollected) {
      PrintSentinel(js, kCollectedSentinel);
      return;
    } else if (lookup_result == ObjectIdRing::kExpired) {
      PrintSentinel(js, kExpiredSentinel);
      return;
    }
  
    // Handle non-heap objects.
    Breakpoint* bpt = LookupBreakpoint(thread->isolate(), id, &lookup_result);
    if (bpt != NULL) {
      bpt->PrintJSON(js);
      return;
    } else if (lookup_result == ObjectIdRing::kCollected) {
      PrintSentinel(js, kCollectedSentinel);
      return;
    }
  
    PrintInvalidParamError(js, "objectId");
  }
  
  static const MethodParameter* const get_object_store_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      NULL,
  };
  
  static void GetObjectStore(Thread* thread, JSONStream* js) {
    JSONObject jsobj(js);
    thread->isolate_group()->object_store()->PrintToJSONObject(&jsobj);
  }
  
  static const MethodParameter* const get_isolate_object_store_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      NULL,
  };
  
  static void GetIsolateObjectStore(Thread* thread, JSONStream* js) {
    JSONObject jsobj(js);
    thread->isolate()->isolate_object_store()->PrintToJSONObject(&jsobj);
  }
  
  static const MethodParameter* const get_class_list_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      NULL,
  };
  
  static void GetClassList(Thread* thread, JSONStream* js) {
    ClassTable* table = thread->isolate_group()->class_table();
    JSONObject jsobj(js);
    table->PrintToJSONObject(&jsobj);
  }
  
  static const MethodParameter* const get_type_arguments_list_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      NULL,
  };
  
  static void GetTypeArgumentsList(Thread* thread, JSONStream* js) {
    bool only_with_instantiations = false;
    if (js->ParamIs("onlyWithInstantiations", "true")) {
      only_with_instantiations = true;
    }
    Zone* zone = thread->zone();
    ObjectStore* object_store = thread->isolate_group()->object_store();
    CanonicalTypeArgumentsSet typeargs_table(
        zone, object_store->canonical_type_arguments());
    const intptr_t table_size = typeargs_table.NumEntries();
    const intptr_t table_used = typeargs_table.NumOccupied();
    const Array& typeargs_array =
        Array::Handle(zone, HashTables::ToArray(typeargs_table, false));
    ASSERT(typeargs_array.Length() == table_used);
    TypeArguments& typeargs = TypeArguments::Handle(zone);
    JSONObject jsobj(js);
    jsobj.AddProperty("type", "TypeArgumentsList");
    jsobj.AddProperty("canonicalTypeArgumentsTableSize", table_size);
    jsobj.AddProperty("canonicalTypeArgumentsTableUsed", table_used);
    JSONArray members(&jsobj, "typeArguments");
    for (intptr_t i = 0; i < table_used; i++) {
      typeargs ^= typeargs_array.At(i);
      if (!typeargs.IsNull()) {
        if (!only_with_instantiations || typeargs.HasInstantiations()) {
          members.AddValue(typeargs);
        }
      }
    }
    typeargs_table.Release();
  }
  
  static const MethodParameter* const get_version_params[] = {
      NO_ISOLATE_PARAMETER,
      NULL,
  };
  
  static void GetVersion(Thread* thread, JSONStream* js) {
    JSONObject jsobj(js);
    jsobj.AddProperty("type", "Version");
    jsobj.AddProperty("major",
                      static_cast<intptr_t>(SERVICE_PROTOCOL_MAJOR_VERSION));
    jsobj.AddProperty("minor",
                      static_cast<intptr_t>(SERVICE_PROTOCOL_MINOR_VERSION));
    jsobj.AddProperty("_privateMajor", static_cast<intptr_t>(0));
    jsobj.AddProperty("_privateMinor", static_cast<intptr_t>(0));
  }
  
  class ServiceIsolateVisitor : public IsolateVisitor {
   public:
    explicit ServiceIsolateVisitor(JSONArray* jsarr) : jsarr_(jsarr) {}
    virtual ~ServiceIsolateVisitor() {}
  
    void VisitIsolate(Isolate* isolate) {
      if (!IsSystemIsolate(isolate)) {
        jsarr_->AddValue(isolate);
      }
    }
  
   private:
    JSONArray* jsarr_;
  };
  
  class SystemServiceIsolateVisitor : public IsolateVisitor {
   public:
    explicit SystemServiceIsolateVisitor(JSONArray* jsarr) : jsarr_(jsarr) {}
    virtual ~SystemServiceIsolateVisitor() {}
  
    void VisitIsolate(Isolate* isolate) {
      if (IsSystemIsolate(isolate) &&
          !Dart::VmIsolateNameEquals(isolate->name())) {
        jsarr_->AddValue(isolate);
      }
    }
  
   private:
    JSONArray* jsarr_;
  };
  
  static const MethodParameter* const get_vm_params[] = {
      NO_ISOLATE_PARAMETER,
      NULL,
  };
  
  void Service::PrintJSONForEmbedderInformation(JSONObject* jsobj) {
    if (embedder_information_callback_ != NULL) {
      Dart_EmbedderInformation info = {
          0,     // version
          NULL,  // name
          -1,    // max_rss
          -1     // current_rss
      };
      embedder_information_callback_(&info);
      ASSERT(info.version == DART_EMBEDDER_INFORMATION_CURRENT_VERSION);
      if (info.name != NULL) {
        jsobj->AddProperty("_embedder", info.name);
      }
      if (info.max_rss >= 0) {
        jsobj->AddProperty64("_maxRSS", info.max_rss);
      }
      if (info.current_rss >= 0) {
        jsobj->AddProperty64("_currentRSS", info.current_rss);
      }
    }
  }
  
  void Service::PrintJSONForVM(JSONStream* js, bool ref) {
    JSONObject jsobj(js);
    jsobj.AddProperty("type", (ref ? "@VM" : "VM"));
    jsobj.AddProperty("name", GetVMName());
    if (ref) {
      return;
    }
    jsobj.AddProperty("architectureBits", static_cast<intptr_t>(kBitsPerWord));
    jsobj.AddProperty("hostCPU", HostCPUFeatures::hardware());
    jsobj.AddProperty("operatingSystem", OS::Name());
    jsobj.AddProperty("targetCPU", CPU::Id());
    jsobj.AddProperty("version", Version::String());
    jsobj.AddProperty("_profilerMode", FLAG_profile_vm ? "VM" : "Dart");
    jsobj.AddProperty64("_nativeZoneMemoryUsage",
                        ApiNativeScope::current_memory_usage());
    jsobj.AddProperty64("pid", OS::ProcessId());
    jsobj.AddPropertyTimeMillis(
        "startTime", OS::GetCurrentTimeMillis() - Dart::UptimeMillis());
    {
      intptr_t used, capacity;
      const char* implementation;
      if (MallocHooks::GetStats(&used, &capacity, &implementation)) {
        jsobj.AddProperty("_mallocUsed", used);
        jsobj.AddProperty("_mallocCapacity", capacity);
        jsobj.AddProperty("_mallocImplementation", implementation);
      }
    }
    PrintJSONForEmbedderInformation(&jsobj);
    // Construct the isolate and isolate_groups list.
    {
      JSONArray jsarr(&jsobj, "isolates");
      ServiceIsolateVisitor visitor(&jsarr);
      Isolate::VisitIsolates(&visitor);
    }
    {
      JSONArray jsarr(&jsobj, "systemIsolates");
      SystemServiceIsolateVisitor visitor(&jsarr);
      Isolate::VisitIsolates(&visitor);
    }
    {
      JSONArray jsarr_isolate_groups(&jsobj, "isolateGroups");
      IsolateGroup::ForEach([&jsarr_isolate_groups](IsolateGroup* isolate_group) {
        if (!isolate_group->is_system_isolate_group()) {
          jsarr_isolate_groups.AddValue(isolate_group);
        }
      });
    }
    {
      JSONArray jsarr_isolate_groups(&jsobj, "systemIsolateGroups");
      IsolateGroup::ForEach([&jsarr_isolate_groups](IsolateGroup* isolate_group) {
        // Don't surface the vm-isolate since it's not a "real" isolate.
        if (Dart::VmIsolateNameEquals(isolate_group->source()->name)) {
          return;
        }
        if (isolate_group->is_system_isolate_group()) {
          jsarr_isolate_groups.AddValue(isolate_group);
        }
      });
    }
    {
      JSONStream discard_js;
      intptr_t vm_memory = GetProcessMemoryUsageHelper(&discard_js);
      jsobj.AddProperty("_currentMemory", vm_memory);
    }
  }
  
  static void GetVM(Thread* thread, JSONStream* js) {
    Service::PrintJSONForVM(js, false);
  }
  
  static const char* const exception_pause_mode_names[] = {
      "All",
      "None",
      "Unhandled",
      NULL,
  };
  
  static Dart_ExceptionPauseInfo exception_pause_mode_values[] = {
      kPauseOnAllExceptions,
      kNoPauseOnExceptions,
      kPauseOnUnhandledExceptions,
      kInvalidExceptionPauseInfo,
  };
  
  static const MethodParameter* const set_exception_pause_mode_params[] = {
      ISOLATE_PARAMETER,
      new EnumParameter("mode", true, exception_pause_mode_names),
      NULL,
  };
  
  static void SetExceptionPauseMode(Thread* thread, JSONStream* js) {
    const char* mode = js->LookupParam("mode");
    if (mode == NULL) {
      PrintMissingParamError(js, "mode");
      return;
    }
    Dart_ExceptionPauseInfo info =
        EnumMapper(mode, exception_pause_mode_names, exception_pause_mode_values);
    if (info == kInvalidExceptionPauseInfo) {
      PrintInvalidParamError(js, "mode");
      return;
    }
    Isolate* isolate = thread->isolate();
    isolate->debugger()->SetExceptionPauseInfo(info);
    if (Service::debug_stream.enabled()) {
      ServiceEvent event(isolate, ServiceEvent::kDebuggerSettingsUpdate);
      Service::HandleEvent(&event);
    }
    PrintSuccess(js);
  }
  
  static const MethodParameter* const set_breakpoint_state_params[] = {
      ISOLATE_PARAMETER,
      new IdParameter("breakpointId", true),
      new BoolParameter("enable", true),
      nullptr,
  };
  
  static void SetBreakpointState(Thread* thread, JSONStream* js) {
    Isolate* isolate = thread->isolate();
    const char* bpt_id = js->LookupParam("breakpointId");
    bool enable = BoolParameter::Parse(js->LookupParam("enable"), true);
    ObjectIdRing::LookupResult lookup_result;
    Breakpoint* bpt = LookupBreakpoint(isolate, bpt_id, &lookup_result);
    // TODO(bkonyi): Should we return a different error for bpts which
    // have been already removed?
    if (bpt == nullptr) {
      PrintInvalidParamError(js, "breakpointId");
      return;
    }
    if (isolate->debugger()->SetBreakpointState(bpt, enable)) {
      if (Service::debug_stream.enabled()) {
        ServiceEvent event(isolate, ServiceEvent::kBreakpointUpdated);
        event.set_breakpoint(bpt);
        Service::HandleEvent(&event);
      }
    }
    bpt->PrintJSON(js);
  }
  
  static const MethodParameter* const get_flag_list_params[] = {
      NO_ISOLATE_PARAMETER,
      NULL,
  };
  
  static void GetFlagList(Thread* thread, JSONStream* js) {
    Flags::PrintJSON(js);
  }
  
  static const MethodParameter* const set_flags_params[] = {
      NO_ISOLATE_PARAMETER,
      NULL,
  };
  
  static void SetFlag(Thread* thread, JSONStream* js) {
    const char* flag_name = js->LookupParam("name");
    if (flag_name == NULL) {
      PrintMissingParamError(js, "name");
      return;
    }
    const char* flag_value = js->LookupParam("value");
    if (flag_value == NULL) {
      PrintMissingParamError(js, "value");
      return;
    }
  
    if (Flags::Lookup(flag_name) == NULL) {
      JSONObject jsobj(js);
      jsobj.AddProperty("type", "Error");
      jsobj.AddProperty("message", "Cannot set flag: flag not found");
      return;
    }
  
    // Changing most flags at runtime is dangerous because e.g., it may leave the
    // behavior generated code and the runtime out of sync.
    const uintptr_t kProfilePeriodIndex = 3;
    const uintptr_t kProfilerIndex = 4;
    const char* kAllowedFlags[] = {
        "pause_isolates_on_start",
        "pause_isolates_on_exit",
        "pause_isolates_on_unhandled_exceptions",
        "profile_period",
        "profiler",
    };
  
    bool allowed = false;
    bool profile_period = false;
    bool profiler = false;
    for (size_t i = 0; i < ARRAY_SIZE(kAllowedFlags); i++) {
      if (strcmp(flag_name, kAllowedFlags[i]) == 0) {
        allowed = true;
        profile_period = (i == kProfilePeriodIndex);
        profiler = (i == kProfilerIndex);
        break;
      }
    }
  
    if (!allowed) {
      JSONObject jsobj(js);
      jsobj.AddProperty("type", "Error");
      jsobj.AddProperty("message", "Cannot set flag: cannot change at runtime");
      return;
    }
  
    const char* error = NULL;
    if (Flags::SetFlag(flag_name, flag_value, &error)) {
      PrintSuccess(js);
      if (profile_period) {
        // FLAG_profile_period has already been set to the new value. Now we need
        // to notify the ThreadInterrupter to pick up the change.
        Profiler::UpdateSamplePeriod();
      } else if (profiler) {
        // FLAG_profiler has already been set to the new value.
        Profiler::UpdateRunningState();
      }
      if (Service::vm_stream.enabled()) {
        ServiceEvent event(ServiceEvent::kVMFlagUpdate);
        event.set_flag_name(flag_name);
        event.set_flag_new_value(flag_value);
        Service::HandleEvent(&event);
      }
    } else {
      JSONObject jsobj(js);
      jsobj.AddProperty("type", "Error");
      jsobj.AddProperty("message", error);
    }
  }
  
  static const MethodParameter* const set_library_debuggable_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      new IdParameter("libraryId", true),
      new BoolParameter("isDebuggable", true),
      NULL,
  };
  
  static void SetLibraryDebuggable(Thread* thread, JSONStream* js) {
    const char* lib_id = js->LookupParam("libraryId");
    ObjectIdRing::LookupResult lookup_result;
    Object& obj =
        Object::Handle(LookupHeapObject(thread, lib_id, &lookup_result));
    const bool is_debuggable =
        BoolParameter::Parse(js->LookupParam("isDebuggable"), false);
    if (obj.IsLibrary()) {
      const Library& lib = Library::Cast(obj);
      lib.set_debuggable(is_debuggable);
      PrintSuccess(js);
      return;
    }
    PrintInvalidParamError(js, "libraryId");
  }
  
  static const MethodParameter* const set_name_params[] = {
      ISOLATE_PARAMETER,
      new MethodParameter("name", true),
      NULL,
  };
  
  static void SetName(Thread* thread, JSONStream* js) {
    Isolate* isolate = thread->isolate();
    isolate->set_name(js->LookupParam("name"));
    if (Service::isolate_stream.enabled()) {
      ServiceEvent event(isolate, ServiceEvent::kIsolateUpdate);
      Service::HandleEvent(&event);
    }
    PrintSuccess(js);
    return;
  }
  
  static const MethodParameter* const set_vm_name_params[] = {
      NO_ISOLATE_PARAMETER,
      new MethodParameter("name", true),
      NULL,
  };
  
  static void SetVMName(Thread* thread, JSONStream* js) {
    const char* name_param = js->LookupParam("name");
    free(vm_name);
    vm_name = Utils::StrDup(name_param);
    if (Service::vm_stream.enabled()) {
      ServiceEvent event(ServiceEvent::kVMUpdate);
      Service::HandleEvent(&event);
    }
    PrintSuccess(js);
    return;
  }
  
  static const MethodParameter* const set_trace_class_allocation_params[] = {
      RUNNABLE_ISOLATE_PARAMETER,
      new IdParameter("classId", true),
      new BoolParameter("enable", true),
      NULL,
  };
  
  static void SetTraceClassAllocation(Thread* thread, JSONStream* js) {
    if (CheckCompilerDisabled(thread, js)) {
      return;
    }
  
    const char* class_id = js->LookupParam("classId");
    const bool enable = BoolParameter::Parse(js->LookupParam("enable"));
    intptr_t cid = -1;
    GetPrefixedIntegerId(class_id, "classes/", &cid);
    Isolate* isolate = thread->isolate();
    if (!IsValidClassId(isolate, cid)) {
      PrintInvalidParamError(js, "classId");
      return;
    }
    const Class& cls = Class::Handle(GetClassForId(isolate, cid));
    ASSERT(!cls.IsNull());
    cls.SetTraceAllocation(enable);
    PrintSuccess(js);
  }
  
  static const MethodParameter* const get_default_classes_aliases_params[] = {
      NO_ISOLATE_PARAMETER,
      NULL,
  };
  
  static void GetDefaultClassesAliases(Thread* thread, JSONStream* js) {
    JSONObject jsobj(js);
    jsobj.AddProperty("type", "ClassesAliasesMap");
  
    JSONObject map(&jsobj, "map");
  
  #define DEFINE_ADD_VALUE_F(id)                                                 \
    internals.AddValueF("classes/%" Pd, static_cast<intptr_t>(id));
  #define DEFINE_ADD_VALUE_F_CID(clazz) DEFINE_ADD_VALUE_F(k##clazz##Cid)
    {
      JSONArray internals(&map, "<VM Internals>");
      for (intptr_t id = kClassCid; id < kInstanceCid; ++id) {
        DEFINE_ADD_VALUE_F(id);
      }
      DEFINE_ADD_VALUE_F_CID(LibraryPrefix);
    }
    {
      JSONArray internals(&map, "Type");
      for (intptr_t id = kAbstractTypeCid; id <= kTypeParameterCid; ++id) {
        DEFINE_ADD_VALUE_F(id);
      }
    }
    {
      JSONArray internals(&map, "Object");
      DEFINE_ADD_VALUE_F_CID(Instance);
    }
    {
      JSONArray internals(&map, "Closure");
      DEFINE_ADD_VALUE_F_CID(Closure);
      DEFINE_ADD_VALUE_F_CID(Context);
    }
    {
      JSONArray internals(&map, "Int");
      for (intptr_t id = kIntegerCid; id <= kMintCid; ++id) {
        DEFINE_ADD_VALUE_F(id);
      }
    }
    {
      JSONArray internals(&map, "Double");
      DEFINE_ADD_VALUE_F_CID(Double);
    }
    {
      JSONArray internals(&map, "String");
      CLASS_LIST_STRINGS(DEFINE_ADD_VALUE_F_CID)
    }
    {
      JSONArray internals(&map, "List");
      CLASS_LIST_ARRAYS(DEFINE_ADD_VALUE_F_CID)
      DEFINE_ADD_VALUE_F_CID(GrowableObjectArray)
      DEFINE_ADD_VALUE_F_CID(ByteBuffer)
    }
    {
      JSONArray internals(&map, "Map");
      CLASS_LIST_MAPS(DEFINE_ADD_VALUE_F_CID)
    }
  
    {
      JSONArray internals(&map, "Set");
      CLASS_LIST_SETS(DEFINE_ADD_VALUE_F_CID)
    }
  #define DEFINE_ADD_MAP_KEY(clazz)                                              \
    {                                                                            \
      JSONArray internals(&map, #clazz);                                         \
      DEFINE_ADD_VALUE_F_CID(TypedData##clazz)                                   \
      DEFINE_ADD_VALUE_F_CID(TypedData##clazz)                                   \
      DEFINE_ADD_VALUE_F_CID(ExternalTypedData##clazz)                           \
    }
    CLASS_LIST_TYPED_DATA(DEFINE_ADD_MAP_KEY)
  #undef DEFINE_ADD_MAP_KEY
  #define DEFINE_ADD_MAP_KEY(clazz)                                              \
    {                                                                            \
      JSONArray internals(&map, #clazz);                                         \
      DEFINE_ADD_VALUE_F_CID(Ffi##clazz)                                         \
    }
    CLASS_LIST_FFI(DEFINE_ADD_MAP_KEY)
  #undef DEFINE_ADD_MAP_KEY
  #undef DEFINE_ADD_VALUE_F_CID
  #undef DEFINE_ADD_VALUE_F
  }
  
  // clang-format off
  static const ServiceMethodDescriptor service_methods_[] = {
    { "_echo", Echo,
      NULL },
    { "_respondWithMalformedJson", RespondWithMalformedJson,
      NULL },
    { "_respondWithMalformedObject", RespondWithMalformedObject,
      NULL },
    { "_triggerEchoEvent", TriggerEchoEvent,
      NULL },
    { "addBreakpoint", AddBreakpoint,
      add_breakpoint_params },
    { "addBreakpointWithScriptUri", AddBreakpointWithScriptUri,
      add_breakpoint_with_script_uri_params },
    { "addBreakpointAtEntry", AddBreakpointAtEntry,
      add_breakpoint_at_entry_params },
    { "_addBreakpointAtActivation", AddBreakpointAtActivation,
      add_breakpoint_at_activation_params },
    { "_buildExpressionEvaluationScope", BuildExpressionEvaluationScope,
      build_expression_evaluation_scope_params },
    { "clearCpuSamples", ClearCpuSamples,
      clear_cpu_samples_params },
    { "clearVMTimeline", ClearVMTimeline,
      clear_vm_timeline_params, },
    { "_compileExpression", CompileExpression, compile_expression_params },
    { "_enableProfiler", EnableProfiler,
      enable_profiler_params, },
    { "evaluate", Evaluate,
      evaluate_params },
    { "evaluateInFrame", EvaluateInFrame,
      evaluate_in_frame_params },
    { "_getAllocationProfile", GetAllocationProfile,
      get_allocation_profile_params },
    { "getAllocationProfile", GetAllocationProfilePublic,
      get_allocation_profile_params },
    { "getAllocationTraces", GetAllocationTraces,
        get_allocation_traces_params },
    { "_getNativeAllocationSamples", GetNativeAllocationSamples,
        get_native_allocation_samples_params },
    { "getClassList", GetClassList,
      get_class_list_params },
    { "getCpuSamples", GetCpuSamples,
      get_cpu_samples_params },
    { "getFlagList", GetFlagList,
      get_flag_list_params },
    { "_getHeapMap", GetHeapMap,
      get_heap_map_params },
    { "getInboundReferences", GetInboundReferences,
      get_inbound_references_params },
    { "getInstances", GetInstances,
      get_instances_params },
    { "_getInstancesAsArray", GetInstancesAsArray,
      get_instances_as_array_params },
    { "getPorts", GetPorts,
      get_ports_params },
    { "getIsolate", GetIsolate,
      get_isolate_params },
    { "_getIsolateObjectStore", GetIsolateObjectStore,
      get_isolate_object_store_params },
    { "getIsolateGroup", GetIsolateGroup,
      get_isolate_group_params },
    { "getMemoryUsage", GetMemoryUsage,
      get_memory_usage_params },
    { "getIsolateGroupMemoryUsage", GetIsolateGroupMemoryUsage,
      get_isolate_group_memory_usage_params },
    { "_getIsolateMetric", GetIsolateMetric,
      get_isolate_metric_params },
    { "_getIsolateMetricList", GetIsolateMetricList,
      get_isolate_metric_list_params },
    { "getObject", GetObject,
      get_object_params },
    { "_getObjectStore", GetObjectStore,
      get_object_store_params },
    { "_getPersistentHandles", GetPersistentHandles,
        get_persistent_handles_params, },
    { "_getPorts", GetPortsPrivate,
      get_ports_private_params },
    { "getProcessMemoryUsage", GetProcessMemoryUsage,
      get_process_memory_usage_params },
    { "_getReachableSize", GetReachableSize,
      get_reachable_size_params },
    { "_getRetainedSize", GetRetainedSize,
      get_retained_size_params },
    { "getRetainingPath", GetRetainingPath,
      get_retaining_path_params },
    { "getScripts", GetScripts,
      get_scripts_params },
    { "getSourceReport", GetSourceReport,
      get_source_report_params },
    { "getStack", GetStack,
      get_stack_params },
    { "_getTagProfile", GetTagProfile,
      get_tag_profile_params },
    { "_getTypeArgumentsList", GetTypeArgumentsList,
      get_type_arguments_list_params },
    { "getVersion", GetVersion,
      get_version_params },
    { "getVM", GetVM,
      get_vm_params },
    { "getVMTimeline", GetVMTimeline,
      get_vm_timeline_params },
    { "getVMTimelineFlags", GetVMTimelineFlags,
      get_vm_timeline_flags_params },
    { "getVMTimelineMicros", GetVMTimelineMicros,
      get_vm_timeline_micros_params },
    { "invoke", Invoke, invoke_params },
    { "kill", Kill, kill_params },
    { "pause", Pause,
      pause_params },
    { "removeBreakpoint", RemoveBreakpoint,
      remove_breakpoint_params },
    { "reloadSources", ReloadSources,
      reload_sources_params },
    { "_reloadSources", ReloadSources,
      reload_sources_params },
    { "resume", Resume,
      resume_params },
    { "requestHeapSnapshot", RequestHeapSnapshot,
      request_heap_snapshot_params },
    { "_evaluateCompiledExpression", EvaluateCompiledExpression,
      evaluate_compiled_expression_params },
    { "setBreakpointState", SetBreakpointState,
      set_breakpoint_state_params },
    { "setExceptionPauseMode", SetExceptionPauseMode,
      set_exception_pause_mode_params },
    { "setFlag", SetFlag,
      set_flags_params },
    { "setLibraryDebuggable", SetLibraryDebuggable,
      set_library_debuggable_params },
    { "setName", SetName,
      set_name_params },
    { "setTraceClassAllocation", SetTraceClassAllocation,
      set_trace_class_allocation_params },
    { "setVMName", SetVMName,
      set_vm_name_params },
    { "setVMTimelineFlags", SetVMTimelineFlags,
      set_vm_timeline_flags_params },
    { "_collectAllGarbage", CollectAllGarbage,
      collect_all_garbage_params },
    { "_getDefaultClassesAliases", GetDefaultClassesAliases,
      get_default_classes_aliases_params },
  };
  // clang-format on
  
  const ServiceMethodDescriptor* FindMethod(const char* method_name) {
    intptr_t num_methods = sizeof(service_methods_) / sizeof(service_methods_[0]);
    for (intptr_t i = 0; i < num_methods; i++) {
      const ServiceMethodDescriptor& method = service_methods_[i];
      if (strcmp(method_name, method.name) == 0) {
        return &method;
      }
    }
    return NULL;
  }
  
  #endif  // !PRODUCT
  
  }  // namespace dart
  