| // 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 "include/dart_api.h" | 
 | #include "include/dart_native_api.h" | 
 | #include "platform/globals.h" | 
 |  | 
 | #include "vm/base64.h" | 
 | #include "vm/compiler/jit/compiler.h" | 
 | #include "vm/cpu.h" | 
 | #include "vm/dart_api_impl.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/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/type_table.h" | 
 | #include "vm/unicode.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.raw(), 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::graph_stream("_Graph"); | 
 | StreamInfo Service::logging_stream("_Logging"); | 
 | StreamInfo Service::extension_stream("Extension"); | 
 | StreamInfo Service::timeline_stream("Timeline"); | 
 |  | 
 | static StreamInfo* streams_[] = { | 
 |     &Service::vm_stream,      &Service::isolate_stream, | 
 |     &Service::debug_stream,   &Service::gc_stream, | 
 |     &Service::echo_stream,    &Service::graph_stream, | 
 |     &Service::logging_stream, &Service::extension_stream, | 
 |     &Service::timeline_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_) { | 
 |     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_) { | 
 |     Thread* T = Thread::Current(); | 
 |     TransitionVMToNative transition(T); | 
 |     return (*stream_cancel_callback_)(stream_id); | 
 |   } | 
 | } | 
 |  | 
 | RawObject* Service::RequestAssets() { | 
 |   Thread* T = Thread::Current(); | 
 |   Object& object = Object::Handle(); | 
 |   { | 
 |     Api::Scope api_scope(T); | 
 |     TransitionVMToNative transition(T); | 
 |     if (get_service_assets_callback_ == NULL) { | 
 |       return Object::null(); | 
 |     } | 
 |     Dart_Handle 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.raw(); | 
 | } | 
 |  | 
 | static uint8_t* allocator(uint8_t* ptr, intptr_t old_size, intptr_t new_size) { | 
 |   void* new_ptr = realloc(reinterpret_cast<void*>(ptr), new_size); | 
 |   if (new_ptr == NULL) { | 
 |     OUT_OF_MEMORY(); | 
 |   } | 
 |   return reinterpret_cast<uint8_t*>(new_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 PrintIllegalParamError(JSONStream* js, const char* param) { | 
 |   js->PrintError(kInvalidParams, "%s: illegal '%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) { | 
 |   Isolate* isolate = thread->isolate(); | 
 |   if (!isolate->compilation_allowed()) { | 
 |     js->PrintError(kFeatureDisabled, "Debugger is disabled in AOT mode."); | 
 |     return true; | 
 |   } | 
 |   if (isolate->debugger() == NULL) { | 
 |     js->PrintError(kFeatureDisabled, "Debugger is disabled."); | 
 |     return true; | 
 |   } | 
 |   return false; | 
 | } | 
 |  | 
 | static bool CheckCompilerDisabled(Thread* thread, JSONStream* js) { | 
 |   Isolate* isolate = thread->isolate(); | 
 |   if (!isolate->compilation_allowed()) { | 
 |     js->PrintError(kFeatureDisabled, "Compiler is disabled in AOT mode."); | 
 |     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->class_table(); | 
 |   ASSERT(class_table != NULL); | 
 |   return class_table->IsValidIndex(cid) && class_table->HasValidClassAt(cid); | 
 | } | 
 |  | 
 | static RawClass* GetClassForId(Isolate* isolate, intptr_t cid) { | 
 |   ASSERT(isolate == Isolate::Current()); | 
 |   ASSERT(isolate != NULL); | 
 |   ClassTable* class_table = isolate->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 intptr_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 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 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; | 
 |           if (enums_ != NULL) { | 
 |             for (intptr_t i = 0; enums_[i] != NULL; i++) { | 
 |               intptr_t len = strlen(enums_[i]); | 
 |               if (strncmp(cp, enums_[i], len) == 0) { | 
 |                 element_count++; | 
 |                 valid_enum = true; | 
 |                 cp += len; | 
 |                 element_allowed = false;  // we need a comma first. | 
 |                 break; | 
 |               } | 
 |             } | 
 |           } else { | 
 |             // Allow any identifiers | 
 |             const char* id_start = cp; | 
 |             while (IsEnumChar(*cp)) { | 
 |               cp++; | 
 |             } | 
 |             if (cp == id_start) { | 
 |               // Empty identifier, something like this [,]. | 
 |               return -1; | 
 |             } | 
 |           } | 
 |           if (!valid_enum) { | 
 |             return -1; | 
 |           } | 
 |           break; | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   const char* const* enums_; | 
 | }; | 
 |  | 
 | typedef bool (*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(); | 
 | } | 
 |  | 
 | RawError* 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->get_and_clear_sticky_error(); | 
 |       } | 
 |     } | 
 |     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->get_and_clear_sticky_error(); | 
 |       } | 
 |       if (method->entry(T, &js)) { | 
 |         js.PostReply(); | 
 |       } else { | 
 |         // NOTE(turnidge): All message handlers currently return true, | 
 |         // so this case shouldn't be reached, at present. | 
 |         UNIMPLEMENTED(); | 
 |       } | 
 |       return T->get_and_clear_sticky_error(); | 
 |     } | 
 |  | 
 |     EmbedderServiceHandler* handler = FindIsolateEmbedderHandler(c_method_name); | 
 |     if (handler == NULL) { | 
 |       handler = FindRootEmbedderHandler(c_method_name); | 
 |     } | 
 |  | 
 |     if (handler != NULL) { | 
 |       EmbedderHandleMessage(handler, &js); | 
 |       return T->get_and_clear_sticky_error(); | 
 |     } | 
 |  | 
 |     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->get_and_clear_sticky_error(); | 
 |     } | 
 |  | 
 |     PrintUnrecognizedMethodError(&js); | 
 |     js.PostReply(); | 
 |     return T->get_and_clear_sticky_error(); | 
 |   } | 
 | } | 
 |  | 
 | RawError* Service::HandleRootMessage(const Array& msg_instance) { | 
 |   Isolate* isolate = Isolate::Current(); | 
 |   return InvokeMethod(isolate, msg_instance); | 
 | } | 
 |  | 
 | RawError* Service::HandleObjectRootMessage(const Array& msg_instance) { | 
 |   Isolate* isolate = Isolate::Current(); | 
 |   return InvokeMethod(isolate, msg_instance, true); | 
 | } | 
 |  | 
 | RawError* 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, | 
 |                       Dart_WeakPersistentHandle handle, | 
 |                       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); | 
 |   ASSERT(!Isolate::IsVMInternalIsolate(isolate)); | 
 |  | 
 |   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; | 
 |   { | 
 |     TransitionVMToNative transition(thread); | 
 |     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; | 
 |     result = Dart_PostCObject(ServiceIsolate::Port(), &message); | 
 |   } | 
 |  | 
 |   if (!result) { | 
 |     free(bytes); | 
 |   } | 
 | } | 
 |  | 
 | void Service::SendEventWithData(const char* stream_id, | 
 |                                 const char* event_type, | 
 |                                 const char* metadata, | 
 |                                 intptr_t metadata_size, | 
 |                                 const uint8_t* data, | 
 |                                 intptr_t data_size) { | 
 |   // Bitstream: [metadata size (big-endian 64 bit)] [metadata (UTF-8)] [data] | 
 |   const intptr_t total_bytes = sizeof(uint64_t) + metadata_size + data_size; | 
 |  | 
 |   uint8_t* message = static_cast<uint8_t*>(malloc(total_bytes)); | 
 |   if (message == NULL) { | 
 |     OUT_OF_MEMORY(); | 
 |   } | 
 |   intptr_t offset = 0; | 
 |  | 
 |   // Metadata size. | 
 |   reinterpret_cast<uint64_t*>(message)[0] = | 
 |       Utils::HostToBigEndian64(metadata_size); | 
 |   offset += sizeof(uint64_t); | 
 |  | 
 |   // Metadata. | 
 |   memmove(&message[offset], metadata, metadata_size); | 
 |   offset += metadata_size; | 
 |  | 
 |   // Data. | 
 |   memmove(&message[offset], data, data_size); | 
 |   offset += data_size; | 
 |  | 
 |   ASSERT(offset == total_bytes); | 
 |   SendEvent(stream_id, event_type, message, total_bytes); | 
 | } | 
 |  | 
 | 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; | 
 |   } | 
 |   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); | 
 | } | 
 |  | 
 | void Service::PostEvent(Isolate* isolate, | 
 |                         const char* stream_id, | 
 |                         const char* kind, | 
 |                         JSONStream* event) { | 
 |   ASSERT(stream_id != NULL); | 
 |   ASSERT(kind != NULL); | 
 |   ASSERT(event != NULL); | 
 |  | 
 |   // 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; | 
 |  | 
 |   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); | 
 |     } | 
 |   } | 
 |  | 
 |   Dart_PostCObject(ServiceIsolate::Port(), &list_cobj); | 
 | } | 
 |  | 
 | class EmbedderServiceHandler { | 
 |  public: | 
 |   explicit EmbedderServiceHandler(const char* name) | 
 |       : name_(NULL), callback_(NULL), user_data_(NULL), next_(NULL) { | 
 |     ASSERT(name != NULL); | 
 |     name_ = 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::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; | 
 | } | 
 |  | 
 | 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* get_isolate_params[] = { | 
 |     ISOLATE_PARAMETER, NULL, | 
 | }; | 
 |  | 
 | static bool GetIsolate(Thread* thread, JSONStream* js) { | 
 |   thread->isolate()->PrintJSON(js, false); | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* get_scripts_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, | 
 |     NULL, | 
 | }; | 
 |  | 
 | static bool GetScripts(Thread* thread, JSONStream* js) { | 
 |   Isolate* isolate = thread->isolate(); | 
 |   Zone* zone = thread->zone(); | 
 |   ASSERT(isolate != NULL); | 
 |  | 
 |   const GrowableObjectArray& libs = | 
 |       GrowableObjectArray::Handle(zone, isolate->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); | 
 |       } | 
 |     } | 
 |   } | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* get_unused_changes_in_last_reload_params[] = { | 
 |     ISOLATE_PARAMETER, NULL, | 
 | }; | 
 |  | 
 | static bool GetUnusedChangesInLastReload(Thread* thread, JSONStream* js) { | 
 |   if (CheckCompilerDisabled(thread, js)) { | 
 |     return true; | 
 |   } | 
 |  | 
 |   const GrowableObjectArray& changed_in_last_reload = | 
 |       GrowableObjectArray::Handle( | 
 |           thread->isolate()->object_store()->changed_in_last_reload()); | 
 |   if (changed_in_last_reload.IsNull()) { | 
 |     js->PrintError(kIsolateMustHaveReloaded, "No change to compare with."); | 
 |     return true; | 
 |   } | 
 |   JSONObject jsobj(js); | 
 |   jsobj.AddProperty("type", "UnusedChangesInLastReload"); | 
 |   JSONArray jsarr(&jsobj, "unused"); | 
 |   Object& changed = Object::Handle(); | 
 |   Function& function = Function::Handle(); | 
 |   Field& field = Field::Handle(); | 
 |   Class& cls = Class::Handle(); | 
 |   Array& functions = Array::Handle(); | 
 |   Array& fields = Array::Handle(); | 
 |   for (intptr_t i = 0; i < changed_in_last_reload.Length(); i++) { | 
 |     changed = changed_in_last_reload.At(i); | 
 |     if (changed.IsFunction()) { | 
 |       function ^= changed.raw(); | 
 |       if (!function.WasExecuted()) { | 
 |         jsarr.AddValue(function); | 
 |       } | 
 |     } else if (changed.IsField()) { | 
 |       field ^= changed.raw(); | 
 |       if (field.IsUninitialized() || | 
 |           field.initializer_changed_after_initialization()) { | 
 |         jsarr.AddValue(field); | 
 |       } | 
 |     } else if (changed.IsClass()) { | 
 |       cls ^= changed.raw(); | 
 |       if (!cls.is_finalized()) { | 
 |         // Not used at all. | 
 |         jsarr.AddValue(cls); | 
 |       } else { | 
 |         functions = cls.functions(); | 
 |         for (intptr_t j = 0; j < functions.Length(); j++) { | 
 |           function ^= functions.At(j); | 
 |           if (!function.WasExecuted()) { | 
 |             jsarr.AddValue(function); | 
 |           } | 
 |         } | 
 |         fields = cls.fields(); | 
 |         for (intptr_t j = 0; j < fields.Length(); j++) { | 
 |           field ^= fields.At(j); | 
 |           if (field.IsUninitialized()) { | 
 |             jsarr.AddValue(field); | 
 |           } | 
 |         } | 
 |       } | 
 |     } | 
 |   } | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* get_stack_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, new BoolParameter("_full", false), NULL, | 
 | }; | 
 |  | 
 | static bool GetStack(Thread* thread, JSONStream* js) { | 
 |   if (CheckDebuggerDisabled(thread, js)) { | 
 |     return true; | 
 |   } | 
 |  | 
 |   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. | 
 |   const bool full = BoolParameter::Parse(js->LookupParam("_full"), false); | 
 |   JSONObject jsobj(js); | 
 |   jsobj.AddProperty("type", "Stack"); | 
 |   { | 
 |     JSONArray jsarr(&jsobj, "frames"); | 
 |  | 
 |     intptr_t num_frames = stack->Length(); | 
 |     for (intptr_t i = 0; i < num_frames; i++) { | 
 |       ActivationFrame* frame = stack->FrameAt(i); | 
 |       JSONObject jsobj(&jsarr); | 
 |       frame->PrintToJSONObject(&jsobj, full); | 
 |       jsobj.AddProperty("index", i); | 
 |     } | 
 |   } | 
 |  | 
 |   if (async_causal_stack != NULL) { | 
 |     JSONArray jsarr(&jsobj, "asyncCausalFrames"); | 
 |     intptr_t num_frames = 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, full); | 
 |       jsobj.AddProperty("index", i); | 
 |     } | 
 |   } | 
 |  | 
 |   if (awaiter_stack != NULL) { | 
 |     JSONArray jsarr(&jsobj, "awaiterFrames"); | 
 |     intptr_t num_frames = awaiter_stack->Length(); | 
 |     for (intptr_t i = 0; i < num_frames; i++) { | 
 |       ActivationFrame* frame = awaiter_stack->FrameAt(i); | 
 |       JSONObject jsobj(&jsarr); | 
 |       frame->PrintToJSONObject(&jsobj, full); | 
 |       jsobj.AddProperty("index", i); | 
 |     } | 
 |   } | 
 |  | 
 |   { | 
 |     MessageHandler::AcquiredQueues aq(isolate->message_handler()); | 
 |     jsobj.AddProperty("messages", aq.queue()); | 
 |   } | 
 |  | 
 |   return true; | 
 | } | 
 |  | 
 | static bool HandleCommonEcho(JSONObject* jsobj, JSONStream* js) { | 
 |   jsobj->AddProperty("type", "_EchoResponse"); | 
 |   if (js->HasParam("text")) { | 
 |     jsobj->AddProperty("text", js->LookupParam("text")); | 
 |   } | 
 |   return true; | 
 | } | 
 |  | 
 | 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(¶ms, "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()); | 
 |       } | 
 |     } | 
 |   } | 
 |   uint8_t data[] = {0, 128, 255}; | 
 |   SendEventWithData(echo_stream.id(), "_Echo", js.buffer()->buf(), | 
 |                     js.buffer()->length(), data, sizeof(data)); | 
 | } | 
 |  | 
 | static bool TriggerEchoEvent(Thread* thread, JSONStream* js) { | 
 |   if (Service::echo_stream.enabled()) { | 
 |     Service::SendEchoEvent(thread->isolate(), js->LookupParam("text")); | 
 |   } | 
 |   JSONObject jsobj(js); | 
 |   return HandleCommonEcho(&jsobj, js); | 
 | } | 
 |  | 
 | static bool DumpIdZone(Thread* thread, JSONStream* js) { | 
 |   // TODO(johnmccutchan): Respect _idZone parameter passed to RPC. For now, | 
 |   // always send the ObjectIdRing. | 
 |   // | 
 |   ObjectIdRing* ring = thread->isolate()->object_id_ring(); | 
 |   ASSERT(ring != NULL); | 
 |   // When printing the ObjectIdRing, force object id reuse policy. | 
 |   RingServiceIdZone reuse_zone; | 
 |   reuse_zone.Init(ring, ObjectIdRing::kReuseId); | 
 |   js->set_id_zone(&reuse_zone); | 
 |   ring->PrintJSON(js); | 
 |   return true; | 
 | } | 
 |  | 
 | static bool Echo(Thread* thread, JSONStream* js) { | 
 |   JSONObject jsobj(js); | 
 |   return 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 RawObject* 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.raw(); | 
 |   } else if (strcmp(arg, "bool-true") == 0) { | 
 |     return Bool::True().raw(); | 
 |   } else if (strcmp(arg, "bool-false") == 0) { | 
 |     return Bool::False().raw(); | 
 |   } else if (strcmp(arg, "null") == 0) { | 
 |     return Object::null(); | 
 |   } | 
 |  | 
 |   ObjectIdRing* ring = thread->isolate()->object_id_ring(); | 
 |   ASSERT(ring != NULL); | 
 |   intptr_t id = -1; | 
 |   if (!GetIntegerId(arg, &id)) { | 
 |     *kind = ObjectIdRing::kInvalid; | 
 |     return Object::null(); | 
 |   } | 
 |   return ring->GetObjectForId(id, kind); | 
 | } | 
 |  | 
 | static RawObject* LookupHeapObjectLibraries(Isolate* isolate, | 
 |                                             char** parts, | 
 |                                             int num_parts) { | 
 |   // Library ids look like "libraries/35" | 
 |   if (num_parts < 2) { | 
 |     return Object::sentinel().raw(); | 
 |   } | 
 |   const GrowableObjectArray& libs = | 
 |       GrowableObjectArray::Handle(isolate->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().raw(); | 
 |   } | 
 |   if (num_parts == 2) { | 
 |     return lib.raw(); | 
 |   } | 
 |   if (strcmp(parts[2], "scripts") == 0) { | 
 |     // Script ids look like "libraries/35/scripts/library%2Furl.dart/12345" | 
 |     if (num_parts != 5) { | 
 |       return Object::sentinel().raw(); | 
 |     } | 
 |     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], ×tamp, 16) || (timestamp < 0)) { | 
 |       return Object::sentinel().raw(); | 
 |     } | 
 |  | 
 |     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.raw(); | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   // Not found. | 
 |   return Object::sentinel().raw(); | 
 | } | 
 |  | 
 | static RawObject* LookupHeapObjectClasses(Thread* thread, | 
 |                                           char** parts, | 
 |                                           int num_parts) { | 
 |   // Class ids look like: "classes/17" | 
 |   if (num_parts < 2) { | 
 |     return Object::sentinel().raw(); | 
 |   } | 
 |   Isolate* isolate = thread->isolate(); | 
 |   Zone* zone = thread->zone(); | 
 |   ClassTable* table = isolate->class_table(); | 
 |   intptr_t id; | 
 |   if (!GetIntegerId(parts[1], &id) || !table->IsValidIndex(id)) { | 
 |     return Object::sentinel().raw(); | 
 |   } | 
 |   Class& cls = Class::Handle(zone, table->At(id)); | 
 |   if (num_parts == 2) { | 
 |     return cls.raw(); | 
 |   } | 
 |   if (strcmp(parts[2], "closures") == 0) { | 
 |     // Closure ids look like: "classes/17/closures/11" | 
 |     if (num_parts != 4) { | 
 |       return Object::sentinel().raw(); | 
 |     } | 
 |     intptr_t id; | 
 |     if (!GetIntegerId(parts[3], &id)) { | 
 |       return Object::sentinel().raw(); | 
 |     } | 
 |     Function& func = Function::Handle(zone); | 
 |     func ^= isolate->ClosureFunctionFromIndex(id); | 
 |     if (func.IsNull()) { | 
 |       return Object::sentinel().raw(); | 
 |     } | 
 |     return func.raw(); | 
 |  | 
 |   } else if (strcmp(parts[2], "fields") == 0) { | 
 |     // Field ids look like: "classes/17/fields/name" | 
 |     if (num_parts != 4) { | 
 |       return Object::sentinel().raw(); | 
 |     } | 
 |     const char* encoded_id = parts[3]; | 
 |     String& id = String::Handle(zone, String::New(encoded_id)); | 
 |     id = String::DecodeIRI(id); | 
 |     if (id.IsNull()) { | 
 |       return Object::sentinel().raw(); | 
 |     } | 
 |     Field& field = Field::Handle(zone, cls.LookupField(id)); | 
 |     if (field.IsNull()) { | 
 |       return Object::sentinel().raw(); | 
 |     } | 
 |     return field.raw(); | 
 |  | 
 |   } else if (strcmp(parts[2], "functions") == 0) { | 
 |     // Function ids look like: "classes/17/functions/name" | 
 |     if (num_parts != 4) { | 
 |       return Object::sentinel().raw(); | 
 |     } | 
 |     const char* encoded_id = parts[3]; | 
 |     String& id = String::Handle(zone, String::New(encoded_id)); | 
 |     id = String::DecodeIRI(id); | 
 |     if (id.IsNull()) { | 
 |       return Object::sentinel().raw(); | 
 |     } | 
 |     Function& func = Function::Handle(zone, cls.LookupFunction(id)); | 
 |     if (func.IsNull()) { | 
 |       return Object::sentinel().raw(); | 
 |     } | 
 |     return func.raw(); | 
 |  | 
 |   } else if (strcmp(parts[2], "implicit_closures") == 0) { | 
 |     // Function ids look like: "classes/17/implicit_closures/11" | 
 |     if (num_parts != 4) { | 
 |       return Object::sentinel().raw(); | 
 |     } | 
 |     intptr_t id; | 
 |     if (!GetIntegerId(parts[3], &id)) { | 
 |       return Object::sentinel().raw(); | 
 |     } | 
 |     Function& func = Function::Handle(zone); | 
 |     func ^= cls.ImplicitClosureFunctionFromIndex(id); | 
 |     if (func.IsNull()) { | 
 |       return Object::sentinel().raw(); | 
 |     } | 
 |     return func.raw(); | 
 |  | 
 |   } else if (strcmp(parts[2], "dispatchers") == 0) { | 
 |     // Dispatcher Function ids look like: "classes/17/dispatchers/11" | 
 |     if (num_parts != 4) { | 
 |       return Object::sentinel().raw(); | 
 |     } | 
 |     intptr_t id; | 
 |     if (!GetIntegerId(parts[3], &id)) { | 
 |       return Object::sentinel().raw(); | 
 |     } | 
 |     Function& func = Function::Handle(zone); | 
 |     func ^= cls.InvocationDispatcherFunctionFromIndex(id); | 
 |     if (func.IsNull()) { | 
 |       return Object::sentinel().raw(); | 
 |     } | 
 |     return func.raw(); | 
 |  | 
 |   } else if (strcmp(parts[2], "types") == 0) { | 
 |     // Type ids look like: "classes/17/types/11" | 
 |     if (num_parts != 4) { | 
 |       return Object::sentinel().raw(); | 
 |     } | 
 |     intptr_t id; | 
 |     if (!GetIntegerId(parts[3], &id)) { | 
 |       return Object::sentinel().raw(); | 
 |     } | 
 |     if (id != 0) { | 
 |       return Object::sentinel().raw(); | 
 |     } | 
 |     const Type& type = Type::Handle(zone, cls.CanonicalType()); | 
 |     if (!type.IsNull()) { | 
 |       return type.raw(); | 
 |     } | 
 |   } | 
 |  | 
 |   // Not found. | 
 |   return Object::sentinel().raw(); | 
 | } | 
 |  | 
 | static RawObject* LookupHeapObjectTypeArguments(Thread* thread, | 
 |                                                 char** parts, | 
 |                                                 int num_parts) { | 
 |   Isolate* isolate = thread->isolate(); | 
 |   // TypeArguments ids look like: "typearguments/17" | 
 |   if (num_parts < 2) { | 
 |     return Object::sentinel().raw(); | 
 |   } | 
 |   intptr_t id; | 
 |   if (!GetIntegerId(parts[1], &id)) { | 
 |     return Object::sentinel().raw(); | 
 |   } | 
 |   ObjectStore* object_store = isolate->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().raw(); | 
 |   } | 
 |   return table.At(id); | 
 | } | 
 |  | 
 | static RawObject* LookupHeapObjectCode(Isolate* isolate, | 
 |                                        char** parts, | 
 |                                        int num_parts) { | 
 |   if (num_parts != 2) { | 
 |     return Object::sentinel().raw(); | 
 |   } | 
 |   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().raw(); | 
 |     } | 
 |     // TODO(turnidge): Return "collected" instead. | 
 |     return Object::null(); | 
 |   } | 
 |   if (strncmp(kNativePrefix, id, kNativePrefixLen) == 0) { | 
 |     if (!GetUnsignedIntegerId(&id[kNativePrefixLen], &pc, 16)) { | 
 |       return Object::sentinel().raw(); | 
 |     } | 
 |     // TODO(johnmccutchan): Support native Code. | 
 |     return Object::null(); | 
 |   } | 
 |   if (strncmp(kReusedPrefix, id, kReusedPrefixLen) == 0) { | 
 |     if (!GetUnsignedIntegerId(&id[kReusedPrefixLen], &pc, 16)) { | 
 |       return Object::sentinel().raw(); | 
 |     } | 
 |     // TODO(turnidge): Return "expired" instead. | 
 |     return Object::null(); | 
 |   } | 
 |   int64_t timestamp = 0; | 
 |   if (!GetCodeId(id, ×tamp, &pc) || (timestamp < 0)) { | 
 |     return Object::sentinel().raw(); | 
 |   } | 
 |   Code& code = Code::Handle(Code::FindCode(pc, timestamp)); | 
 |   if (!code.IsNull()) { | 
 |     return code.raw(); | 
 |   } | 
 |   Bytecode& bytecode = Bytecode::Handle(Bytecode::FindCode(pc)); | 
 |   if (!bytecode.IsNull()) { | 
 |     return bytecode.raw(); | 
 |   } | 
 |  | 
 |   // Not found. | 
 |   return Object::sentinel().raw(); | 
 | } | 
 |  | 
 | static RawObject* LookupHeapObjectMessage(Thread* thread, | 
 |                                           char** parts, | 
 |                                           int num_parts) { | 
 |   if (num_parts != 2) { | 
 |     return Object::sentinel().raw(); | 
 |   } | 
 |   uword message_id = 0; | 
 |   if (!GetUnsignedIntegerId(parts[1], &message_id, 16)) { | 
 |     return Object::sentinel().raw(); | 
 |   } | 
 |   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().raw(); | 
 |   } | 
 |   if (message->IsRaw()) { | 
 |     return message->raw_obj(); | 
 |   } else { | 
 |     MessageSnapshotReader reader(message, thread); | 
 |     return reader.ReadObject(); | 
 |   } | 
 | } | 
 |  | 
 | static RawObject* 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().raw(); | 
 |     } | 
 |     return obj.raw(); | 
 |  | 
 |   } else if (strcmp(parts[0], "libraries") == 0) { | 
 |     return LookupHeapObjectLibraries(isolate, 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(isolate, parts, num_parts); | 
 |   } else if (strcmp(parts[0], "messages") == 0) { | 
 |     return LookupHeapObjectMessage(thread, parts, num_parts); | 
 |   } | 
 |  | 
 |   // Not found. | 
 |   return Object::sentinel().raw(); | 
 | } | 
 |  | 
 | 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 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) { | 
 |         *result = ObjectIdRing::kValid; | 
 |         return bpt; | 
 |       } | 
 |       if (bpt_id < isolate->debugger()->limitBreakpointId()) { | 
 |         *result = ObjectIdRing::kCollected; | 
 |         return NULL; | 
 |       } | 
 |     } | 
 |   } | 
 |   return NULL; | 
 | } | 
 |  | 
 | static bool 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); | 
 |   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) >> kWordSizeLog2); | 
 |         jselement.AddProperty("parentListIndex", element_index); | 
 |       } else if (source.IsInstance()) { | 
 |         source_class ^= source.clazz(); | 
 |         parent_field_map = source_class.OffsetToFieldMap(); | 
 |         intptr_t offset = slot_offset.Value(); | 
 |         if (offset > 0 && offset < parent_field_map.Length()) { | 
 |           field ^= parent_field_map.At(offset); | 
 |           jselement.AddProperty("parentField", field); | 
 |         } | 
 |       } else { | 
 |         intptr_t element_index = slot_offset.Value(); | 
 |         jselement.AddProperty("_parentWordOffset", element_index); | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   // 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()); | 
 |   } | 
 |  | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* get_inbound_references_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, NULL, | 
 | }; | 
 |  | 
 | static bool GetInboundReferences(Thread* thread, JSONStream* js) { | 
 |   const char* target_id = js->LookupParam("targetId"); | 
 |   if (target_id == NULL) { | 
 |     PrintMissingParamError(js, "targetId"); | 
 |     return true; | 
 |   } | 
 |   const char* limit_cstr = js->LookupParam("limit"); | 
 |   if (limit_cstr == NULL) { | 
 |     PrintMissingParamError(js, "limit"); | 
 |     return true; | 
 |   } | 
 |   intptr_t limit; | 
 |   if (!GetIntegerId(limit_cstr, &limit)) { | 
 |     PrintInvalidParamError(js, "limit"); | 
 |     return true; | 
 |   } | 
 |  | 
 |   Object& obj = Object::Handle(thread->zone()); | 
 |   ObjectIdRing::LookupResult lookup_result; | 
 |   { | 
 |     HANDLESCOPE(thread); | 
 |     obj = LookupHeapObject(thread, target_id, &lookup_result); | 
 |   } | 
 |   if (obj.raw() == Object::sentinel().raw()) { | 
 |     if (lookup_result == ObjectIdRing::kCollected) { | 
 |       PrintSentinel(js, kCollectedSentinel); | 
 |     } else if (lookup_result == ObjectIdRing::kExpired) { | 
 |       PrintSentinel(js, kExpiredSentinel); | 
 |     } else { | 
 |       PrintInvalidParamError(js, "targetId"); | 
 |     } | 
 |     return true; | 
 |   } | 
 |   return PrintInboundReferences(thread, &obj, limit, js); | 
 | } | 
 |  | 
 | static bool PrintRetainingPath(Thread* thread, | 
 |                                Object* obj, | 
 |                                intptr_t limit, | 
 |                                JSONStream* js) { | 
 |   ObjectGraph graph(thread); | 
 |   Array& path = Array::Handle(Array::New(limit * 2)); | 
 |   intptr_t length = graph.RetainingPath(obj, path); | 
 |   JSONObject jsobj(js); | 
 |   jsobj.AddProperty("type", "RetainingPath"); | 
 |   jsobj.AddProperty("length", length); | 
 |   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(); | 
 |   limit = Utils::Minimum(limit, length); | 
 |   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 | 
 |     // or instance field. | 
 |     if (i > 0) { | 
 |       slot_offset ^= path.At((i * 2) - 1); | 
 |       jselement.AddProperty("offset", slot_offset.Value()); | 
 |       if (element.IsArray() || element.IsGrowableObjectArray()) { | 
 |         intptr_t element_index = | 
 |             slot_offset.Value() - (Array::element_offset(0) >> kWordSizeLog2); | 
 |         jselement.AddProperty("parentListIndex", element_index); | 
 |       } else if (element.IsLinkedHashMap()) { | 
 |         map = static_cast<RawLinkedHashMap*>(path.At(i * 2)); | 
 |         map_data = map.data(); | 
 |         intptr_t element_index = | 
 |             slot_offset.Value() - (Array::element_offset(0) >> kWordSizeLog2); | 
 |         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.IsInstance()) { | 
 |         element_class ^= element.clazz(); | 
 |         element_field_map = element_class.OffsetToFieldMap(); | 
 |         intptr_t offset = slot_offset.Value(); | 
 |         if (offset > 0 && offset < element_field_map.Length()) { | 
 |           field ^= element_field_map.At(offset); | 
 |           jselement.AddProperty("parentField", field); | 
 |         } | 
 |       } else { | 
 |         intptr_t element_index = slot_offset.Value(); | 
 |         jselement.AddProperty("_parentWordOffset", element_index); | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   // 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()); | 
 |   } | 
 |  | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* get_retaining_path_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, NULL, | 
 | }; | 
 |  | 
 | static bool GetRetainingPath(Thread* thread, JSONStream* js) { | 
 |   const char* target_id = js->LookupParam("targetId"); | 
 |   if (target_id == NULL) { | 
 |     PrintMissingParamError(js, "targetId"); | 
 |     return true; | 
 |   } | 
 |   const char* limit_cstr = js->LookupParam("limit"); | 
 |   if (limit_cstr == NULL) { | 
 |     PrintMissingParamError(js, "limit"); | 
 |     return true; | 
 |   } | 
 |   intptr_t limit; | 
 |   if (!GetIntegerId(limit_cstr, &limit)) { | 
 |     PrintInvalidParamError(js, "limit"); | 
 |     return true; | 
 |   } | 
 |  | 
 |   Object& obj = Object::Handle(thread->zone()); | 
 |   ObjectIdRing::LookupResult lookup_result; | 
 |   { | 
 |     HANDLESCOPE(thread); | 
 |     obj = LookupHeapObject(thread, target_id, &lookup_result); | 
 |   } | 
 |   if (obj.raw() == Object::sentinel().raw()) { | 
 |     if (lookup_result == ObjectIdRing::kCollected) { | 
 |       PrintSentinel(js, kCollectedSentinel); | 
 |     } else if (lookup_result == ObjectIdRing::kExpired) { | 
 |       PrintSentinel(js, kExpiredSentinel); | 
 |     } else { | 
 |       PrintInvalidParamError(js, "targetId"); | 
 |     } | 
 |     return true; | 
 |   } | 
 |   return PrintRetainingPath(thread, &obj, limit, js); | 
 | } | 
 |  | 
 | static const MethodParameter* get_retained_size_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, new IdParameter("targetId", true), NULL, | 
 | }; | 
 |  | 
 | static bool 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.raw() == Object::sentinel().raw()) { | 
 |     if (lookup_result == ObjectIdRing::kCollected) { | 
 |       PrintSentinel(js, kCollectedSentinel); | 
 |     } else if (lookup_result == ObjectIdRing::kExpired) { | 
 |       PrintSentinel(js, kExpiredSentinel); | 
 |     } else { | 
 |       PrintInvalidParamError(js, "targetId"); | 
 |     } | 
 |     return true; | 
 |   } | 
 |   // 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 true; | 
 |   } | 
 |  | 
 |   ObjectGraph graph(thread); | 
 |   intptr_t retained_size = graph.SizeRetainedByInstance(obj); | 
 |   const Object& result = Object::Handle(Integer::New(retained_size)); | 
 |   result.PrintJSON(js, true); | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* get_reachable_size_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, new IdParameter("targetId", true), NULL, | 
 | }; | 
 |  | 
 | static bool 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.raw() == Object::sentinel().raw()) { | 
 |     if (lookup_result == ObjectIdRing::kCollected) { | 
 |       PrintSentinel(js, kCollectedSentinel); | 
 |     } else if (lookup_result == ObjectIdRing::kExpired) { | 
 |       PrintSentinel(js, kExpiredSentinel); | 
 |     } else { | 
 |       PrintInvalidParamError(js, "targetId"); | 
 |     } | 
 |     return true; | 
 |   } | 
 |   // 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 true; | 
 |   } | 
 |  | 
 |   ObjectGraph graph(thread); | 
 |   intptr_t retained_size = graph.SizeReachableByInstance(obj); | 
 |   const Object& result = Object::Handle(Integer::New(retained_size)); | 
 |   result.PrintJSON(js, true); | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* invoke_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, | 
 |     NULL, | 
 | }; | 
 |  | 
 | static bool Invoke(Thread* thread, JSONStream* js) { | 
 |   if (CheckDebuggerDisabled(thread, js)) { | 
 |     return true; | 
 |   } | 
 |  | 
 |   const char* receiver_id = js->LookupParam("targetId"); | 
 |   if (receiver_id == NULL) { | 
 |     PrintMissingParamError(js, "targetId"); | 
 |     return true; | 
 |   } | 
 |   const char* selector_cstr = js->LookupParam("selector"); | 
 |   if (selector_cstr == NULL) { | 
 |     PrintMissingParamError(js, "selector"); | 
 |     return true; | 
 |   } | 
 |   const char* argument_ids = js->LookupParam("argumentIds"); | 
 |   if (argument_ids == NULL) { | 
 |     PrintMissingParamError(js, "argumentIds"); | 
 |     return true; | 
 |   } | 
 |  | 
 |   Zone* zone = thread->zone(); | 
 |   ObjectIdRing::LookupResult lookup_result; | 
 |   Object& receiver = Object::Handle( | 
 |       zone, LookupHeapObject(thread, receiver_id, &lookup_result)); | 
 |   if (receiver.raw() == Object::sentinel().raw()) { | 
 |     if (lookup_result == ObjectIdRing::kCollected) { | 
 |       PrintSentinel(js, kCollectedSentinel); | 
 |     } else if (lookup_result == ObjectIdRing::kExpired) { | 
 |       PrintSentinel(js, kExpiredSentinel); | 
 |     } else { | 
 |       PrintInvalidParamError(js, "targetId"); | 
 |     } | 
 |     return true; | 
 |   } | 
 |  | 
 |   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 true; | 
 |   } | 
 |   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 true; | 
 |       } | 
 |  | 
 |       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)); | 
 |       if (argument.raw() == Object::sentinel().raw()) { | 
 |         if (lookup_result == ObjectIdRing::kCollected) { | 
 |           PrintSentinel(js, kCollectedSentinel); | 
 |         } else if (lookup_result == ObjectIdRing::kExpired) { | 
 |           PrintSentinel(js, kExpiredSentinel); | 
 |         } else { | 
 |           PrintInvalidParamError(js, "argumentIds"); | 
 |         } | 
 |         return true; | 
 |       } | 
 |       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 true; | 
 |   } | 
 |   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 true; | 
 |   } | 
 |   if (is_instance) { | 
 |     // We don't use Instance::Cast here because it doesn't allow null. | 
 |     Instance& instance = Instance::Handle(zone); | 
 |     instance ^= receiver.raw(); | 
 |     const Object& result = | 
 |         Object::Handle(zone, instance.Invoke(selector, args, arg_names)); | 
 |     result.PrintJSON(js, true); | 
 |     return true; | 
 |   } | 
 |   js->PrintError(kInvalidParams, | 
 |                  "%s: invalid 'targetId' parameter: " | 
 |                  "Cannot invoke against a VM-internal object", | 
 |                  js->method()); | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* 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.raw() == Object::sentinel().raw()) { | 
 |         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 bool 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()); | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* build_expression_evaluation_scope_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, | 
 |     new IdParameter("frameIndex", false), | 
 |     new IdParameter("targetId", false), | 
 |     NULL, | 
 | }; | 
 |  | 
 | static bool BuildExpressionEvaluationScope(Thread* thread, JSONStream* js) { | 
 |   if (CheckDebuggerDisabled(thread, js)) { | 
 |     return true; | 
 |   } | 
 |  | 
 |   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 true; | 
 |   } | 
 |  | 
 |   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 true; | 
 |   } | 
 |  | 
 |   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 true; | 
 |     } | 
 |  | 
 |     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 = !cls.IsTopLevel(); | 
 |     } 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(); | 
 |     } | 
 |   } 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 true; | 
 |     } | 
 |     const char* target_id = js->LookupParam("targetId"); | 
 |  | 
 |     ObjectIdRing::LookupResult lookup_result; | 
 |     Object& obj = Object::Handle( | 
 |         zone, LookupHeapObject(thread, target_id, &lookup_result)); | 
 |     if (obj.raw() == Object::sentinel().raw()) { | 
 |       PrintInvalidParamError(js, "targetId"); | 
 |       return true; | 
 |     } | 
 |     if (obj.IsLibrary()) { | 
 |       const Library& lib = Library::Cast(obj); | 
 |       library_uri ^= lib.url(); | 
 |     } else if (obj.IsClass() || ((obj.IsInstance() || obj.IsNull()) && | 
 |                                  !ContainsNonInstance(obj))) { | 
 |       Class& cls = Class::Handle(zone); | 
 |       if (obj.IsClass()) { | 
 |         cls ^= obj.raw(); | 
 |       } else { | 
 |         Instance& instance = Instance::Handle(zone); | 
 |         instance ^= obj.raw(); | 
 |         cls ^= instance.clazz(); | 
 |       } | 
 |       if (cls.id() < kInstanceCid || cls.id() == kTypeArgumentsCid) { | 
 |         js->PrintError( | 
 |             kInvalidParams, | 
 |             "Expressions can be evaluated only with regular Dart instances"); | 
 |         return true; | 
 |       } | 
 |  | 
 |       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()); | 
 |     } | 
 |   } | 
 |  | 
 |   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); | 
 |  | 
 |   return true; | 
 | } | 
 |  | 
 | #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* 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 bool CompileExpression(Thread* thread, JSONStream* js) { | 
 | #if defined(DART_PRECOMPILED_RUNTIME) | 
 |   js->PrintError(kFeatureDisabled, "Debugger is disabled in AOT mode."); | 
 |   return true; | 
 | #else | 
 |   if (CheckDebuggerDisabled(thread, js)) { | 
 |     return true; | 
 |   } | 
 |  | 
 |   if (!KernelIsolate::IsRunning()) { | 
 |     // Assume we are in dart1 mode where separate compilation is not required. | 
 |     // 0-length kernelBytes signals that we should evaluate expression in dart1 | 
 |     // mode. | 
 |     // TODO(aam): When dart1 is no longer supported we need to return error | 
 |     // here. | 
 |     JSONObject report(js); | 
 |     const uint8_t kernel_bytes[] = {0}; | 
 |     report.AddPropertyBase64("kernelBytes", kernel_bytes, 0); | 
 |     return true; | 
 |   } | 
 |  | 
 |   bool is_static = BoolParameter::Parse(js->LookupParam("isStatic"), false); | 
 |  | 
 |   const GrowableObjectArray& params = | 
 |       GrowableObjectArray::Handle(thread->zone(), GrowableObjectArray::New()); | 
 |   if (!ParseCSVList(js->LookupParam("definitions"), params)) { | 
 |     PrintInvalidParamError(js, "definitions"); | 
 |     return true; | 
 |   } | 
 |  | 
 |   const GrowableObjectArray& type_params = | 
 |       GrowableObjectArray::Handle(thread->zone(), GrowableObjectArray::New()); | 
 |   if (!ParseCSVList(js->LookupParam("typeDefinitions"), type_params)) { | 
 |     PrintInvalidParamError(js, "typedDefinitions"); | 
 |     return true; | 
 |   } | 
 |  | 
 |   Dart_KernelCompilationResult compilation_result = | 
 |       KernelIsolate::CompileExpressionToKernel( | 
 |           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 true; | 
 |   } | 
 |  | 
 |   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); | 
 |   return true; | 
 | #endif | 
 | } | 
 |  | 
 | static const MethodParameter* evaluate_compiled_expression_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, | 
 |     new UIntParameter("frameIndex", false), | 
 |     new IdParameter("targetId", false), | 
 |     new StringParameter("kernelBytes", true), | 
 |     NULL, | 
 | }; | 
 |  | 
 | static bool EvaluateCompiledExpression(Thread* thread, JSONStream* js) { | 
 |   if (CheckDebuggerDisabled(thread, js)) { | 
 |     return true; | 
 |   } | 
 |  | 
 |   Isolate* isolate = thread->isolate(); | 
 |   DebuggerStackTrace* stack = isolate->debugger()->StackTrace(); | 
 |   intptr_t frame_pos = UIntParameter::Parse(js->LookupParam("frameIndex")); | 
 |   if (frame_pos >= stack->Length()) { | 
 |     PrintInvalidParamError(js, "frameIndex"); | 
 |     return true; | 
 |   } | 
 |   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 true; | 
 |   } | 
 |   const GrowableObjectArray& type_params_names = | 
 |       GrowableObjectArray::Handle(zone, GrowableObjectArray::New()); | 
 |  | 
 |   intptr_t kernel_length; | 
 |   const char* kernel_bytes_str = js->LookupParam("kernelBytes"); | 
 |   uint8_t* kernel_bytes = DecodeBase64(zone, kernel_bytes_str, &kernel_length); | 
 |  | 
 |   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 true; | 
 |     } | 
 |  | 
 |     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_bytes, kernel_length, | 
 |             Array::Handle(zone, Array::MakeFixedLength(type_params_names)), | 
 |             Array::Handle(zone, Array::MakeFixedLength(param_values)), | 
 |             type_arguments)); | 
 |     result.PrintJSON(js, true); | 
 |     return 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 true; | 
 |     } | 
 |     const char* target_id = js->LookupParam("targetId"); | 
 |     ObjectIdRing::LookupResult lookup_result; | 
 |     Object& obj = Object::Handle( | 
 |         zone, LookupHeapObject(thread, target_id, &lookup_result)); | 
 |     if (obj.raw() == Object::sentinel().raw()) { | 
 |       if (lookup_result == ObjectIdRing::kCollected) { | 
 |         PrintSentinel(js, kCollectedSentinel); | 
 |       } else if (lookup_result == ObjectIdRing::kExpired) { | 
 |         PrintSentinel(js, kExpiredSentinel); | 
 |       } else { | 
 |         PrintInvalidParamError(js, "targetId"); | 
 |       } | 
 |       return true; | 
 |     } | 
 |     TypeArguments& type_arguments = TypeArguments::Handle(zone); | 
 |     if (obj.IsLibrary()) { | 
 |       const Library& lib = Library::Cast(obj); | 
 |       const Object& result = Object::Handle( | 
 |           zone, | 
 |           lib.EvaluateCompiledExpression( | 
 |               kernel_bytes, kernel_length, | 
 |               Array::Handle(zone, Array::MakeFixedLength(type_params_names)), | 
 |               Array::Handle(zone, Array::MakeFixedLength(param_values)), | 
 |               type_arguments)); | 
 |       result.PrintJSON(js, true); | 
 |       return true; | 
 |     } | 
 |     if (obj.IsClass()) { | 
 |       const Class& cls = Class::Cast(obj); | 
 |       const Object& result = Object::Handle( | 
 |           zone, | 
 |           cls.EvaluateCompiledExpression( | 
 |               kernel_bytes, kernel_length, | 
 |               Array::Handle(zone, Array::MakeFixedLength(type_params_names)), | 
 |               Array::Handle(zone, Array::MakeFixedLength(param_values)), | 
 |               type_arguments)); | 
 |       result.PrintJSON(js, true); | 
 |       return true; | 
 |     } | 
 |     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.raw(); | 
 |       const Class& receiver_cls = Class::Handle(zone, instance.clazz()); | 
 |       const Object& result = Object::Handle( | 
 |           zone, | 
 |           instance.EvaluateCompiledExpression( | 
 |               receiver_cls, kernel_bytes, kernel_length, | 
 |               Array::Handle(zone, Array::MakeFixedLength(type_params_names)), | 
 |               Array::Handle(zone, Array::MakeFixedLength(param_values)), | 
 |               type_arguments)); | 
 |       result.PrintJSON(js, true); | 
 |       return true; | 
 |     } | 
 |     js->PrintError(kInvalidParams, | 
 |                    "%s: invalid 'targetId' parameter: " | 
 |                    "Cannot evaluate against a VM-internal object", | 
 |                    js->method()); | 
 |     return true; | 
 |   } | 
 | } | 
 |  | 
 | static const MethodParameter* evaluate_in_frame_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, new UIntParameter("frameIndex", true), | 
 |     new MethodParameter("expression", true), NULL, | 
 | }; | 
 |  | 
 | static bool 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()); | 
 |   return true; | 
 | } | 
 |  | 
 | class GetInstancesVisitor : public ObjectGraph::Visitor { | 
 |  public: | 
 |   GetInstancesVisitor(const Class& cls, const Array& storage) | 
 |       : cls_(cls), storage_(storage), count_(0) {} | 
 |  | 
 |   virtual Direction VisitObject(ObjectGraph::StackIterator* it) { | 
 |     RawObject* raw_obj = it->Get(); | 
 |     if (raw_obj->IsPseudoObject()) { | 
 |       return kProceed; | 
 |     } | 
 |     Thread* thread = Thread::Current(); | 
 |     REUSABLE_OBJECT_HANDLESCOPE(thread); | 
 |     Object& obj = thread->ObjectHandle(); | 
 |     obj = raw_obj; | 
 |     if (obj.GetClassId() == cls_.id()) { | 
 |       if (!storage_.IsNull() && count_ < storage_.Length()) { | 
 |         storage_.SetAt(count_, obj); | 
 |       } | 
 |       ++count_; | 
 |     } | 
 |     return kProceed; | 
 |   } | 
 |  | 
 |   intptr_t count() const { return count_; } | 
 |  | 
 |  private: | 
 |   const Class& cls_; | 
 |   const Array& storage_; | 
 |   intptr_t count_; | 
 | }; | 
 |  | 
 | static const MethodParameter* get_instances_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, NULL, | 
 | }; | 
 |  | 
 | static bool GetInstances(Thread* thread, JSONStream* js) { | 
 |   const char* target_id = js->LookupParam("classId"); | 
 |   if (target_id == NULL) { | 
 |     PrintMissingParamError(js, "classId"); | 
 |     return true; | 
 |   } | 
 |   const char* limit_cstr = js->LookupParam("limit"); | 
 |   if (limit_cstr == NULL) { | 
 |     PrintMissingParamError(js, "limit"); | 
 |     return true; | 
 |   } | 
 |   intptr_t limit; | 
 |   if (!GetIntegerId(limit_cstr, &limit)) { | 
 |     PrintInvalidParamError(js, "limit"); | 
 |     return true; | 
 |   } | 
 |   const Object& obj = Object::Handle(LookupHeapObject(thread, target_id, NULL)); | 
 |   if (obj.raw() == Object::sentinel().raw() || !obj.IsClass()) { | 
 |     PrintInvalidParamError(js, "classId"); | 
 |     return true; | 
 |   } | 
 |   const Class& cls = Class::Cast(obj); | 
 |   Array& storage = Array::Handle(Array::New(limit)); | 
 |   GetInstancesVisitor visitor(cls, storage); | 
 |   ObjectGraph graph(thread); | 
 |   HeapIterationScope iteration_scope(Thread::Current(), true); | 
 |   graph.IterateObjects(&visitor); | 
 |   intptr_t count = visitor.count(); | 
 |   JSONObject jsobj(js); | 
 |   jsobj.AddProperty("type", "InstanceSet"); | 
 |   jsobj.AddProperty("totalCount", count); | 
 |   { | 
 |     JSONArray samples(&jsobj, "samples"); | 
 |     for (int i = 0; (i < storage.Length()) && (i < count); i++) { | 
 |       const Object& sample = Object::Handle(storage.At(i)); | 
 |       samples.AddValue(sample); | 
 |     } | 
 |   } | 
 |  | 
 |   // We nil out the array after generating the response to prevent | 
 |   // reporting spurious references when looking for inbound references | 
 |   // after looking at allInstances. | 
 |   for (intptr_t i = 0; i < storage.Length(); i++) { | 
 |     storage.SetAt(i, Object::null_object()); | 
 |   } | 
 |  | 
 |   return true; | 
 | } | 
 |  | 
 | static const char* const report_enum_names[] = { | 
 |     SourceReport::kCallSitesStr, | 
 |     SourceReport::kCoverageStr, | 
 |     SourceReport::kPossibleBreakpointsStr, | 
 |     SourceReport::kProfileStr, | 
 |     NULL, | 
 | }; | 
 |  | 
 | static const MethodParameter* get_source_report_params[] = { | 
 |     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), | 
 |     NULL, | 
 | }; | 
 |  | 
 | static bool GetSourceReport(Thread* thread, JSONStream* js) { | 
 |   if (CheckCompilerDisabled(thread, js)) { | 
 |     return true; | 
 |   } | 
 |  | 
 |   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.raw() == Object::sentinel().raw() || !obj.IsScript()) { | 
 |       PrintInvalidParamError(js, "scriptId"); | 
 |       return true; | 
 |     } | 
 |     script ^= obj.raw(); | 
 |   } else { | 
 |     if (js->HasParam("tokenPos")) { | 
 |       js->PrintError( | 
 |           kInvalidParams, | 
 |           "%s: the 'tokenPos' parameter requires the 'scriptId' parameter", | 
 |           js->method()); | 
 |       return true; | 
 |     } | 
 |     if (js->HasParam("endTokenPos")) { | 
 |       js->PrintError( | 
 |           kInvalidParams, | 
 |           "%s: the 'endTokenPos' parameter requires the 'scriptId' parameter", | 
 |           js->method()); | 
 |       return true; | 
 |     } | 
 |   } | 
 |   SourceReport report(report_set, compile_mode); | 
 |   report.PrintJSON(js, script, TokenPosition(start_pos), | 
 |                    TokenPosition(end_pos)); | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* reload_sources_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, | 
 |     new BoolParameter("force", false), | 
 |     new BoolParameter("pause", false), | 
 |     new StringParameter("rootLibUri", false), | 
 |     new StringParameter("packagesUri", false), | 
 |     NULL, | 
 | }; | 
 |  | 
 | static bool ReloadSources(Thread* thread, JSONStream* js) { | 
 | #if defined(DART_PRECOMPILED_RUNTIME) | 
 |   js->PrintError(kFeatureDisabled, "Compiler is disabled in AOT mode."); | 
 |   return true; | 
 | #else | 
 |   if (CheckCompilerDisabled(thread, js)) { | 
 |     return true; | 
 |   } | 
 |  | 
 |   Isolate* isolate = thread->isolate(); | 
 |   if (!isolate->HasTagHandler()) { | 
 |     js->PrintError(kFeatureDisabled, | 
 |                    "A library tag handler must be installed."); | 
 |     return true; | 
 |   } | 
 |   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 true; | 
 |   } | 
 |   if (isolate->IsReloading()) { | 
 |     js->PrintError(kIsolateIsReloading, "This isolate is being reloaded."); | 
 |     return true; | 
 |   } | 
 |   if (!isolate->CanReload()) { | 
 |     js->PrintError(kFeatureDisabled, | 
 |                    "This isolate cannot reload sources right now."); | 
 |     return true; | 
 |   } | 
 |   const bool force_reload = | 
 |       BoolParameter::Parse(js->LookupParam("force"), false); | 
 |  | 
 |   isolate->ReloadSources(js, force_reload, js->LookupParam("rootLibUri"), | 
 |                          js->LookupParam("packagesUri")); | 
 |  | 
 |   Service::CheckForPause(isolate, js); | 
 |  | 
 |   return true; | 
 | #endif | 
 | } | 
 |  | 
 | void Service::CheckForPause(Isolate* isolate, JSONStream* stream) { | 
 |   // Should we pause? | 
 |   isolate->set_should_pause_post_service_request( | 
 |       BoolParameter::Parse(stream->LookupParam("pause"), false)); | 
 | } | 
 |  | 
 | RawError* 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.raw(); | 
 | } | 
 |  | 
 | static bool AddBreakpointCommon(Thread* thread, | 
 |                                 JSONStream* js, | 
 |                                 const String& script_uri) { | 
 |   if (CheckDebuggerDisabled(thread, js)) { | 
 |     return true; | 
 |   } | 
 |  | 
 |   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 true; | 
 |     } | 
 |   } | 
 |   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 true; | 
 |   } | 
 |   bpt->PrintJSON(js); | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* add_breakpoint_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, | 
 |     new IdParameter("scriptId", true), | 
 |     new UIntParameter("line", true), | 
 |     new UIntParameter("column", false), | 
 |     NULL, | 
 | }; | 
 |  | 
 | static bool AddBreakpoint(Thread* thread, JSONStream* js) { | 
 |   if (CheckDebuggerDisabled(thread, js)) { | 
 |     return true; | 
 |   } | 
 |  | 
 |   const char* script_id_param = js->LookupParam("scriptId"); | 
 |   Object& obj = Object::Handle(LookupHeapObject(thread, script_id_param, NULL)); | 
 |   if (obj.raw() == Object::sentinel().raw() || !obj.IsScript()) { | 
 |     PrintInvalidParamError(js, "scriptId"); | 
 |     return true; | 
 |   } | 
 |   const Script& script = Script::Cast(obj); | 
 |   const String& script_uri = String::Handle(script.url()); | 
 |   ASSERT(!script_uri.IsNull()); | 
 |   return AddBreakpointCommon(thread, js, script_uri); | 
 | } | 
 |  | 
 | static const MethodParameter* add_breakpoint_with_script_uri_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, | 
 |     new IdParameter("scriptUri", true), | 
 |     new UIntParameter("line", true), | 
 |     new UIntParameter("column", false), | 
 |     NULL, | 
 | }; | 
 |  | 
 | static bool AddBreakpointWithScriptUri(Thread* thread, JSONStream* js) { | 
 |   if (CheckDebuggerDisabled(thread, js)) { | 
 |     return true; | 
 |   } | 
 |  | 
 |   const char* script_uri_param = js->LookupParam("scriptUri"); | 
 |   const String& script_uri = String::Handle(String::New(script_uri_param)); | 
 |   return AddBreakpointCommon(thread, js, script_uri); | 
 | } | 
 |  | 
 | static const MethodParameter* add_breakpoint_at_entry_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, new IdParameter("functionId", true), NULL, | 
 | }; | 
 |  | 
 | static bool AddBreakpointAtEntry(Thread* thread, JSONStream* js) { | 
 |   if (CheckDebuggerDisabled(thread, js)) { | 
 |     return true; | 
 |   } | 
 |  | 
 |   const char* function_id = js->LookupParam("functionId"); | 
 |   Object& obj = Object::Handle(LookupHeapObject(thread, function_id, NULL)); | 
 |   if (obj.raw() == Object::sentinel().raw() || !obj.IsFunction()) { | 
 |     PrintInvalidParamError(js, "functionId"); | 
 |     return true; | 
 |   } | 
 |   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 true; | 
 |   } | 
 |   bpt->PrintJSON(js); | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* add_breakpoint_at_activation_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, new IdParameter("objectId", true), NULL, | 
 | }; | 
 |  | 
 | static bool AddBreakpointAtActivation(Thread* thread, JSONStream* js) { | 
 |   if (CheckDebuggerDisabled(thread, js)) { | 
 |     return true; | 
 |   } | 
 |  | 
 |   const char* object_id = js->LookupParam("objectId"); | 
 |   Object& obj = Object::Handle(LookupHeapObject(thread, object_id, NULL)); | 
 |   if (obj.raw() == Object::sentinel().raw() || !obj.IsInstance()) { | 
 |     PrintInvalidParamError(js, "objectId"); | 
 |     return true; | 
 |   } | 
 |   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 true; | 
 |   } | 
 |   bpt->PrintJSON(js); | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* remove_breakpoint_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, NULL, | 
 | }; | 
 |  | 
 | static bool RemoveBreakpoint(Thread* thread, JSONStream* js) { | 
 |   if (CheckDebuggerDisabled(thread, js)) { | 
 |     return true; | 
 |   } | 
 |  | 
 |   if (!js->HasParam("breakpointId")) { | 
 |     PrintMissingParamError(js, "breakpointId"); | 
 |     return true; | 
 |   } | 
 |   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 true; | 
 |   } | 
 |   isolate->debugger()->RemoveBreakpoint(bpt->id()); | 
 |   PrintSuccess(js); | 
 |   return true; | 
 | } | 
 |  | 
 | static RawClass* 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.raw(); | 
 | } | 
 |  | 
 | static bool HandleNativeMetricsList(Thread* thread, JSONStream* js) { | 
 |   JSONObject obj(js); | 
 |   obj.AddProperty("type", "MetricList"); | 
 |   { | 
 |     JSONArray metrics(&obj, "metrics"); | 
 |     Metric* current = thread->isolate()->metrics_list_head(); | 
 |     while (current != NULL) { | 
 |       metrics.AddValue(current); | 
 |       current = current->next(); | 
 |     } | 
 |   } | 
 |   return true; | 
 | } | 
 |  | 
 | static bool HandleNativeMetric(Thread* thread, JSONStream* js, const char* id) { | 
 |   Metric* current = thread->isolate()->metrics_list_head(); | 
 |   while (current != NULL) { | 
 |     const char* name = current->name(); | 
 |     ASSERT(name != NULL); | 
 |     if (strcmp(name, id) == 0) { | 
 |       current->PrintJSON(js); | 
 |       return true; | 
 |     } | 
 |     current = current->next(); | 
 |   } | 
 |   PrintInvalidParamError(js, "metricId"); | 
 |   return true; | 
 | } | 
 |  | 
 | static bool HandleDartMetricsList(Thread* thread, JSONStream* js) { | 
 |   Zone* zone = thread->zone(); | 
 |   const Class& metrics_cls = Class::Handle(zone, GetMetricsClass(thread)); | 
 |   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()); | 
 |   return true; | 
 | } | 
 |  | 
 | static bool 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 true; | 
 |   } | 
 |   PrintInvalidParamError(js, "metricId"); | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* get_isolate_metric_list_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, NULL, | 
 | }; | 
 |  | 
 | static bool 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 true; | 
 |     } | 
 |   } else { | 
 |     PrintMissingParamError(js, "type"); | 
 |     return true; | 
 |   } | 
 |   if (native_metrics) { | 
 |     return HandleNativeMetricsList(thread, js); | 
 |   } | 
 |   return HandleDartMetricsList(thread, js); | 
 | } | 
 |  | 
 | static const MethodParameter* get_isolate_metric_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, NULL, | 
 | }; | 
 |  | 
 | static bool GetIsolateMetric(Thread* thread, JSONStream* js) { | 
 |   const char* metric_id = js->LookupParam("metricId"); | 
 |   if (metric_id == NULL) { | 
 |     PrintMissingParamError(js, "metricId"); | 
 |     return true; | 
 |   } | 
 |   // 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 true; | 
 |   } | 
 |   // 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; | 
 |   if (native_metric) { | 
 |     const char* id = metric_id + kNativeMetricIdPrefixLen; | 
 |     return HandleNativeMetric(thread, js, id); | 
 |   } | 
 |   const char* id = metric_id + kMetricIdPrefixLen; | 
 |   return HandleDartMetric(thread, js, id); | 
 | } | 
 |  | 
 | static const MethodParameter* get_vm_metric_list_params[] = { | 
 |     NO_ISOLATE_PARAMETER, NULL, | 
 | }; | 
 |  | 
 | static bool GetVMMetricList(Thread* thread, JSONStream* js) { | 
 |   return false; | 
 | } | 
 |  | 
 | static const MethodParameter* get_vm_metric_params[] = { | 
 |     NO_ISOLATE_PARAMETER, NULL, | 
 | }; | 
 |  | 
 | static bool GetVMMetric(Thread* thread, JSONStream* js) { | 
 |   const char* metric_id = js->LookupParam("metricId"); | 
 |   if (metric_id == NULL) { | 
 |     PrintMissingParamError(js, "metricId"); | 
 |   } | 
 |   return false; | 
 | } | 
 |  | 
 | 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* set_vm_timeline_flags_params[] = { | 
 |     NO_ISOLATE_PARAMETER, | 
 |     new EnumListParameter("recordedStreams", | 
 |                           false, | 
 |                           timeline_streams_enum_names), | 
 |     NULL, | 
 | }; | 
 |  | 
 | 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; | 
 | } | 
 |  | 
 | static bool SetVMTimelineFlags(Thread* thread, JSONStream* js) { | 
 |   if (!FLAG_support_timeline) { | 
 |     PrintSuccess(js); | 
 |     return true; | 
 |   } | 
 |   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 | 
 |  | 
 |   PrintSuccess(js); | 
 |  | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* get_vm_timeline_flags_params[] = { | 
 |     NO_ISOLATE_PARAMETER, NULL, | 
 | }; | 
 |  | 
 | static bool GetVMTimelineFlags(Thread* thread, JSONStream* js) { | 
 |   if (!FLAG_support_timeline) { | 
 |     JSONObject obj(js); | 
 |     obj.AddProperty("type", "TimelineFlags"); | 
 |     return true; | 
 |   } | 
 |   Isolate* isolate = thread->isolate(); | 
 |   ASSERT(isolate != NULL); | 
 |   StackZone zone(thread); | 
 |   Timeline::PrintFlagsToJSON(js); | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* clear_vm_timeline_params[] = { | 
 |     NO_ISOLATE_PARAMETER, NULL, | 
 | }; | 
 |  | 
 | static bool ClearVMTimeline(Thread* thread, JSONStream* js) { | 
 |   Isolate* isolate = thread->isolate(); | 
 |   ASSERT(isolate != NULL); | 
 |   StackZone zone(thread); | 
 |  | 
 |   Timeline::Clear(); | 
 |  | 
 |   PrintSuccess(js); | 
 |  | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* get_vm_timeline_params[] = { | 
 |     NO_ISOLATE_PARAMETER, new Int64Parameter("timeOriginMicros", false), | 
 |     new Int64Parameter("timeExtentMicros", false), NULL, | 
 | }; | 
 |  | 
 | static bool 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); | 
 |   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); | 
 |   return true; | 
 | } | 
 |  | 
 | 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* resume_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, | 
 |     new EnumParameter("step", false, step_enum_names), | 
 |     new UIntParameter("frameIndex", false), NULL, | 
 | }; | 
 |  | 
 | static bool 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 true; | 
 |     } | 
 |     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 true; | 
 |   } | 
 |   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 true; | 
 |   } | 
 |   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 true; | 
 |   } | 
 |   if (isolate->debugger()->PauseEvent() == NULL) { | 
 |     js->PrintError(kIsolateMustBePaused, NULL); | 
 |     return true; | 
 |   } | 
 |  | 
 |   const char* error = NULL; | 
 |   if (!isolate->debugger()->SetResumeAction(step, frame_index, &error)) { | 
 |     js->PrintError(kCannotResume, "%s", error); | 
 |     return true; | 
 |   } | 
 |   isolate->SetResumeRequest(); | 
 |   PrintSuccess(js); | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* kill_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, | 
 |     NULL, | 
 | }; | 
 |  | 
 | static bool 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); | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* pause_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, NULL, | 
 | }; | 
 |  | 
 | static bool 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); | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* enable_profiler_params[] = { | 
 |     NULL, | 
 | }; | 
 |  | 
 | static bool EnableProfiler(Thread* thread, JSONStream* js) { | 
 |   if (!FLAG_profiler) { | 
 |     FLAG_profiler = true; | 
 |     Profiler::Init(); | 
 |   } | 
 |   PrintSuccess(js); | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* get_tag_profile_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, NULL, | 
 | }; | 
 |  | 
 | static bool GetTagProfile(Thread* thread, JSONStream* js) { | 
 |   JSONObject miniProfile(js); | 
 |   miniProfile.AddProperty("type", "TagProfile"); | 
 |   thread->isolate()->vm_tag_counters()->PrintToJSONObject(&miniProfile); | 
 |   return true; | 
 | } | 
 |  | 
 | static const char* const tags_enum_names[] = { | 
 |     "None", "UserVM", "UserOnly", "VMUser", "VMOnly", NULL, | 
 | }; | 
 |  | 
 | static const Profile::TagOrder tags_enum_values[] = { | 
 |     Profile::kNoTags, Profile::kUserVM, Profile::kUser, | 
 |     Profile::kVMUser, Profile::kVM, | 
 |     Profile::kNoTags,  // Default value. | 
 | }; | 
 |  | 
 | static const MethodParameter* get_cpu_profile_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, | 
 |     new EnumParameter("tags", true, tags_enum_names), | 
 |     new BoolParameter("_codeTransitionTags", false), | 
 |     new Int64Parameter("timeOriginMicros", false), | 
 |     new Int64Parameter("timeExtentMicros", false), | 
 |     NULL, | 
 | }; | 
 |  | 
 | // TODO(johnmccutchan): Rename this to GetCpuSamples. | 
 | static bool GetCpuProfile(Thread* thread, JSONStream* js) { | 
 |   Profile::TagOrder tag_order = | 
 |       EnumMapper(js->LookupParam("tags"), tags_enum_names, tags_enum_values); | 
 |   intptr_t extra_tags = 0; | 
 |   if (BoolParameter::Parse(js->LookupParam("_codeTransitionTags"))) { | 
 |     extra_tags |= ProfilerService::kCodeTransitionTagsBit; | 
 |   } | 
 |   int64_t time_origin_micros = | 
 |       Int64Parameter::Parse(js->LookupParam("timeOriginMicros")); | 
 |   int64_t time_extent_micros = | 
 |       Int64Parameter::Parse(js->LookupParam("timeExtentMicros")); | 
 |   ProfilerService::PrintJSON(js, tag_order, extra_tags, time_origin_micros, | 
 |                              time_extent_micros); | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* get_cpu_profile_timeline_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, | 
 |     new EnumParameter("tags", true, tags_enum_names), | 
 |     new Int64Parameter("timeOriginMicros", false), | 
 |     new Int64Parameter("timeExtentMicros", false), | 
 |     NULL, | 
 | }; | 
 |  | 
 | static bool GetCpuProfileTimeline(Thread* thread, JSONStream* js) { | 
 |   Profile::TagOrder tag_order = | 
 |       EnumMapper(js->LookupParam("tags"), tags_enum_names, tags_enum_values); | 
 |   int64_t time_origin_micros = | 
 |       UIntParameter::Parse(js->LookupParam("timeOriginMicros")); | 
 |   int64_t time_extent_micros = | 
 |       UIntParameter::Parse(js->LookupParam("timeExtentMicros")); | 
 |   ProfilerService::PrintTimelineJSON(js, tag_order, time_origin_micros, | 
 |                                      time_extent_micros); | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* get_allocation_samples_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, | 
 |     new EnumParameter("tags", true, tags_enum_names), | 
 |     new IdParameter("classId", false), | 
 |     new Int64Parameter("timeOriginMicros", false), | 
 |     new Int64Parameter("timeExtentMicros", false), | 
 |     NULL, | 
 | }; | 
 |  | 
 | static bool GetAllocationSamples(Thread* thread, JSONStream* js) { | 
 |   Profile::TagOrder tag_order = | 
 |       EnumMapper(js->LookupParam("tags"), tags_enum_names, tags_enum_values); | 
 |   int64_t time_origin_micros = | 
 |       Int64Parameter::Parse(js->LookupParam("timeOriginMicros")); | 
 |   int64_t time_extent_micros = | 
 |       Int64Parameter::Parse(js->LookupParam("timeExtentMicros")); | 
 |   const char* class_id = js->LookupParam("classId"); | 
 |   intptr_t cid = -1; | 
 |   GetPrefixedIntegerId(class_id, "classes/", &cid); | 
 |   Isolate* isolate = thread->isolate(); | 
 |   if (IsValidClassId(isolate, cid)) { | 
 |     const Class& cls = Class::Handle(GetClassForId(isolate, cid)); | 
 |     ProfilerService::PrintAllocationJSON(js, tag_order, cls, time_origin_micros, | 
 |                                          time_extent_micros); | 
 |   } else { | 
 |     PrintInvalidParamError(js, "classId"); | 
 |   } | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* get_native_allocation_samples_params[] = { | 
 |     NO_ISOLATE_PARAMETER, | 
 |     new EnumParameter("tags", true, tags_enum_names), | 
 |     new Int64Parameter("timeOriginMicros", false), | 
 |     new Int64Parameter("timeExtentMicros", false), | 
 |     NULL, | 
 | }; | 
 |  | 
 | static bool GetNativeAllocationSamples(Thread* thread, JSONStream* js) { | 
 |   Profile::TagOrder tag_order = | 
 |       EnumMapper(js->LookupParam("tags"), tags_enum_names, tags_enum_values); | 
 |   int64_t time_origin_micros = | 
 |       Int64Parameter::Parse(js->LookupParam("timeOriginMicros")); | 
 |   int64_t time_extent_micros = | 
 |       Int64Parameter::Parse(js->LookupParam("timeExtentMicros")); | 
 | #if defined(DEBUG) | 
 |   Isolate::Current()->heap()->CollectAllGarbage(); | 
 | #endif | 
 |   ProfilerService::PrintNativeAllocationJSON(js, tag_order, time_origin_micros, | 
 |                                              time_extent_micros); | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* clear_cpu_profile_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, NULL, | 
 | }; | 
 |  | 
 | static bool ClearCpuProfile(Thread* thread, JSONStream* js) { | 
 |   ProfilerService::ClearSamples(); | 
 |   PrintSuccess(js); | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* get_allocation_profile_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, NULL, | 
 | }; | 
 |  | 
 | static bool GetAllocationProfile(Thread* thread, JSONStream* js) { | 
 |   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 true; | 
 |     } | 
 |   } | 
 |   if (js->HasParam("gc")) { | 
 |     if (js->ParamIs("gc", "full")) { | 
 |       should_collect = true; | 
 |     } else { | 
 |       PrintInvalidParamError(js, "gc"); | 
 |       return true; | 
 |     } | 
 |   } | 
 |   Isolate* isolate = thread->isolate(); | 
 |   if (should_reset_accumulator) { | 
 |     isolate->UpdateLastAllocationProfileAccumulatorResetTimestamp(); | 
 |     isolate->class_table()->ResetAllocationAccumulators(); | 
 |   } | 
 |   if (should_collect) { | 
 |     isolate->UpdateLastAllocationProfileGCTimestamp(); | 
 |     isolate->heap()->CollectAllGarbage(); | 
 |   } | 
 |   isolate->class_table()->AllocationProfilePrintJSON(js); | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* collect_all_garbage_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, NULL, | 
 | }; | 
 |  | 
 | static bool CollectAllGarbage(Thread* thread, JSONStream* js) { | 
 |   Isolate* isolate = thread->isolate(); | 
 |   isolate->heap()->CollectAllGarbage(Heap::kDebugging); | 
 |   PrintSuccess(js); | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* get_heap_map_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, NULL, | 
 | }; | 
 |  | 
 | static bool GetHeapMap(Thread* thread, JSONStream* js) { | 
 |   Isolate* isolate = thread->isolate(); | 
 |   if (js->HasParam("gc")) { | 
 |     if (js->ParamIs("gc", "scavenge")) { | 
 |       isolate->heap()->CollectGarbage(Heap::kScavenge, Heap::kDebugging); | 
 |     } else if (js->ParamIs("gc", "mark-sweep")) { | 
 |       isolate->heap()->CollectGarbage(Heap::kMarkSweep, Heap::kDebugging); | 
 |     } else if (js->ParamIs("gc", "mark-compact")) { | 
 |       isolate->heap()->CollectGarbage(Heap::kMarkCompact, Heap::kDebugging); | 
 |     } else { | 
 |       PrintInvalidParamError(js, "gc"); | 
 |       return true; | 
 |     } | 
 |   } | 
 |   isolate->heap()->PrintHeapMapToJSONStream(isolate, js); | 
 |   return true; | 
 | } | 
 |  | 
 | static const char* snapshot_roots_names[] = { | 
 |     "User", "VM", NULL, | 
 | }; | 
 |  | 
 | static ObjectGraph::SnapshotRoots snapshot_roots_values[] = { | 
 |     ObjectGraph::kUser, ObjectGraph::kVM, | 
 | }; | 
 |  | 
 | static const MethodParameter* request_heap_snapshot_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, | 
 |     new EnumParameter("roots", false /* not required */, snapshot_roots_names), | 
 |     new BoolParameter("collectGarbage", false /* not required */), NULL, | 
 | }; | 
 |  | 
 | static bool RequestHeapSnapshot(Thread* thread, JSONStream* js) { | 
 |   ObjectGraph::SnapshotRoots roots = ObjectGraph::kVM; | 
 |   const char* roots_arg = js->LookupParam("roots"); | 
 |   if (roots_arg != NULL) { | 
 |     roots = EnumMapper(roots_arg, snapshot_roots_names, snapshot_roots_values); | 
 |   } | 
 |   const bool collect_garbage = | 
 |       BoolParameter::Parse(js->LookupParam("collectGarbage"), true); | 
 |   if (Service::graph_stream.enabled()) { | 
 |     Service::SendGraphEvent(thread, roots, collect_garbage); | 
 |   } | 
 |   // TODO(koda): Provide some id that ties this request to async response(s). | 
 |   PrintSuccess(js); | 
 |   return true; | 
 | } | 
 |  | 
 | void Service::SendGraphEvent(Thread* thread, | 
 |                              ObjectGraph::SnapshotRoots roots, | 
 |                              bool collect_garbage) { | 
 |   uint8_t* buffer = NULL; | 
 |   WriteStream stream(&buffer, &allocator, 1 * MB); | 
 |   ObjectGraph graph(thread); | 
 |   intptr_t node_count = graph.Serialize(&stream, roots, collect_garbage); | 
 |  | 
 |   // Chrome crashes receiving a single tens-of-megabytes blob, so send the | 
 |   // snapshot in megabyte-sized chunks instead. | 
 |   const intptr_t kChunkSize = 1 * MB; | 
 |   intptr_t num_chunks = | 
 |       (stream.bytes_written() + (kChunkSize - 1)) / kChunkSize; | 
 |   for (intptr_t i = 0; i < num_chunks; i++) { | 
 |     JSONStream js; | 
 |     { | 
 |       JSONObject jsobj(&js); | 
 |       jsobj.AddProperty("jsonrpc", "2.0"); | 
 |       jsobj.AddProperty("method", "streamNotify"); | 
 |       { | 
 |         JSONObject params(&jsobj, "params"); | 
 |         params.AddProperty("streamId", graph_stream.id()); | 
 |         { | 
 |           JSONObject event(¶ms, "event"); | 
 |           event.AddProperty("type", "Event"); | 
 |           event.AddProperty("kind", "_Graph"); | 
 |           event.AddProperty("isolate", thread->isolate()); | 
 |           event.AddPropertyTimeMillis("timestamp", OS::GetCurrentTimeMillis()); | 
 |  | 
 |           event.AddProperty("chunkIndex", i); | 
 |           event.AddProperty("chunkCount", num_chunks); | 
 |           event.AddProperty("nodeCount", node_count); | 
 |         } | 
 |       } | 
 |     } | 
 |  | 
 |     uint8_t* chunk_start = buffer + (i * kChunkSize); | 
 |     intptr_t chunk_size = (i + 1 == num_chunks) | 
 |                               ? stream.bytes_written() - (i * kChunkSize) | 
 |                               : kChunkSize; | 
 |  | 
 |     SendEventWithData(graph_stream.id(), "_Graph", js.buffer()->buf(), | 
 |                       js.buffer()->length(), chunk_start, chunk_size); | 
 |   } | 
 | } | 
 |  | 
 | 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) { | 
 |   if (!Service::debug_stream.enabled()) { | 
 |     return; | 
 |   } | 
 |   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); | 
 | } | 
 |  | 
 | class ContainsAddressVisitor : public FindObjectVisitor { | 
 |  public: | 
 |   explicit ContainsAddressVisitor(uword addr) : addr_(addr) {} | 
 |   virtual ~ContainsAddressVisitor() {} | 
 |  | 
 |   virtual uword filter_addr() const { return addr_; } | 
 |  | 
 |   virtual bool FindObject(RawObject* obj) const { | 
 |     if (obj->IsPseudoObject()) { | 
 |       return false; | 
 |     } | 
 |     uword obj_begin = RawObject::ToAddr(obj); | 
 |     uword obj_end = obj_begin + obj->Size(); | 
 |     return obj_begin <= addr_ && addr_ < obj_end; | 
 |   } | 
 |  | 
 |  private: | 
 |   uword addr_; | 
 | }; | 
 |  | 
 | static const MethodParameter* get_object_by_address_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, NULL, | 
 | }; | 
 |  | 
 | static RawObject* GetObjectHelper(Thread* thread, uword addr) { | 
 |   Object& object = Object::Handle(thread->zone()); | 
 |  | 
 |   { | 
 |     NoSafepointScope no_safepoint; | 
 |     Isolate* isolate = thread->isolate(); | 
 |     ContainsAddressVisitor visitor(addr); | 
 |     object = isolate->heap()->FindObject(&visitor); | 
 |   } | 
 |  | 
 |   if (!object.IsNull()) { | 
 |     return object.raw(); | 
 |   } | 
 |  | 
 |   { | 
 |     NoSafepointScope no_safepoint; | 
 |     ContainsAddressVisitor visitor(addr); | 
 |     object = Dart::vm_isolate()->heap()->FindObject(&visitor); | 
 |   } | 
 |  | 
 |   return object.raw(); | 
 | } | 
 |  | 
 | static bool GetObjectByAddress(Thread* thread, JSONStream* js) { | 
 |   const char* addr_str = js->LookupParam("address"); | 
 |   if (addr_str == NULL) { | 
 |     PrintMissingParamError(js, "address"); | 
 |     return true; | 
 |   } | 
 |  | 
 |   // Handle heap objects. | 
 |   uword addr = 0; | 
 |   if (!GetUnsignedIntegerId(addr_str, &addr, 16)) { | 
 |     PrintInvalidParamError(js, "address"); | 
 |     return true; | 
 |   } | 
 |   bool ref = js->HasParam("ref") && js->ParamIs("ref", "true"); | 
 |   const Object& obj = | 
 |       Object::Handle(thread->zone(), GetObjectHelper(thread, addr)); | 
 |   if (obj.IsNull()) { | 
 |     PrintSentinel(js, kFreeSentinel); | 
 |   } else { | 
 |     obj.PrintJSON(js, ref); | 
 |   } | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* 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->raw()); | 
 |     obj.AddProperty("object", object); | 
 |   } | 
 |  | 
 |   void Append(FinalizablePersistentHandle* weak_persistent_handle) { | 
 |     if (!weak_persistent_handle->raw()->IsHeapObject()) { | 
 |       return;  // Free handle. | 
 |     } | 
 |  | 
 |     JSONObject obj(handles_); | 
 |     obj.AddProperty("type", "_WeakPersistentHandle"); | 
 |     const Object& object = Object::Handle(weak_persistent_handle->raw()); | 
 |     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<uintptr_t>(weak_persistent_handle->callback()), NULL); | 
 |     obj.AddProperty("callbackSymbolName", (name == NULL) ? "" : name); | 
 |     if (name != NULL) { | 
 |       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 bool GetPersistentHandles(Thread* thread, JSONStream* js) { | 
 |   Isolate* isolate = thread->isolate(); | 
 |   ASSERT(isolate != NULL); | 
 |  | 
 |   ApiState* api_state = isolate->api_state(); | 
 |   ASSERT(api_state != NULL); | 
 |  | 
 |   { | 
 |     JSONObject obj(js); | 
 |     obj.AddProperty("type", "_PersistentHandles"); | 
 |     // Persistent handles. | 
 |     { | 
 |       JSONArray persistent_handles(&obj, "persistentHandles"); | 
 |       PersistentHandles& handles = api_state->persistent_handles(); | 
 |       PersistentHandleVisitor<PersistentHandle> visitor(thread, | 
 |                                                         &persistent_handles); | 
 |       handles.Visit(&visitor); | 
 |     } | 
 |     // Weak persistent handles. | 
 |     { | 
 |       JSONArray weak_persistent_handles(&obj, "weakPersistentHandles"); | 
 |       FinalizablePersistentHandles& handles = | 
 |           api_state->weak_persistent_handles(); | 
 |       PersistentHandleVisitor<FinalizablePersistentHandle> visitor( | 
 |           thread, &weak_persistent_handles); | 
 |       handles.VisitHandles(&visitor); | 
 |     } | 
 |   } | 
 |  | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* get_ports_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, NULL, | 
 | }; | 
 |  | 
 | static bool GetPorts(Thread* thread, JSONStream* js) { | 
 |   MessageHandler* message_handler = thread->isolate()->message_handler(); | 
 |   PortMap::PrintPortsForMessageHandler(message_handler, js); | 
 |   return true; | 
 | } | 
 |  | 
 | static bool 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"); | 
 |   return true; | 
 | } | 
 |  | 
 | static bool RespondWithMalformedObject(Thread* thread, JSONStream* js) { | 
 |   JSONObject jsobj(js); | 
 |   jsobj.AddProperty("bart", "simpson"); | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* get_object_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, new UIntParameter("offset", false), | 
 |     new UIntParameter("count", false), NULL, | 
 | }; | 
 |  | 
 | static bool GetObject(Thread* thread, JSONStream* js) { | 
 |   const char* id = js->LookupParam("objectId"); | 
 |   if (id == NULL) { | 
 |     PrintMissingParamError(js, "objectId"); | 
 |     return true; | 
 |   } | 
 |   if (js->HasParam("offset")) { | 
 |     intptr_t value = UIntParameter::Parse(js->LookupParam("offset")); | 
 |     if (value < 0) { | 
 |       PrintInvalidParamError(js, "offset"); | 
 |       return true; | 
 |     } | 
 |     js->set_offset(value); | 
 |   } | 
 |   if (js->HasParam("count")) { | 
 |     intptr_t value = UIntParameter::Parse(js->LookupParam("count")); | 
 |     if (value < 0) { | 
 |       PrintInvalidParamError(js, "count"); | 
 |       return true; | 
 |     } | 
 |     js->set_count(value); | 
 |   } | 
 |  | 
 |   // Handle heap objects. | 
 |   ObjectIdRing::LookupResult lookup_result; | 
 |   const Object& obj = | 
 |       Object::Handle(LookupHeapObject(thread, id, &lookup_result)); | 
 |   if (obj.raw() != Object::sentinel().raw()) { | 
 |     // We found a heap object for this id.  Return it. | 
 |     obj.PrintJSON(js, false); | 
 |     return true; | 
 |   } else if (lookup_result == ObjectIdRing::kCollected) { | 
 |     PrintSentinel(js, kCollectedSentinel); | 
 |     return true; | 
 |   } else if (lookup_result == ObjectIdRing::kExpired) { | 
 |     PrintSentinel(js, kExpiredSentinel); | 
 |     return true; | 
 |   } | 
 |  | 
 |   // Handle non-heap objects. | 
 |   Breakpoint* bpt = LookupBreakpoint(thread->isolate(), id, &lookup_result); | 
 |   if (bpt != NULL) { | 
 |     bpt->PrintJSON(js); | 
 |     return true; | 
 |   } else if (lookup_result == ObjectIdRing::kCollected) { | 
 |     PrintSentinel(js, kCollectedSentinel); | 
 |     return true; | 
 |   } | 
 |  | 
 |   PrintInvalidParamError(js, "objectId"); | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* get_object_store_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, NULL, | 
 | }; | 
 |  | 
 | static bool GetObjectStore(Thread* thread, JSONStream* js) { | 
 |   JSONObject jsobj(js); | 
 |   thread->isolate()->object_store()->PrintToJSONObject(&jsobj); | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* get_class_list_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, NULL, | 
 | }; | 
 |  | 
 | static bool GetClassList(Thread* thread, JSONStream* js) { | 
 |   ClassTable* table = thread->isolate()->class_table(); | 
 |   JSONObject jsobj(js); | 
 |   table->PrintToJSONObject(&jsobj); | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* get_type_arguments_list_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, NULL, | 
 | }; | 
 |  | 
 | static bool 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()->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(); | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* get_version_params[] = { | 
 |     NO_ISOLATE_PARAMETER, NULL, | 
 | }; | 
 |  | 
 | static bool 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)); | 
 |   return true; | 
 | } | 
 |  | 
 | class ServiceIsolateVisitor : public IsolateVisitor { | 
 |  public: | 
 |   explicit ServiceIsolateVisitor(JSONArray* jsarr) : jsarr_(jsarr) {} | 
 |   virtual ~ServiceIsolateVisitor() {} | 
 |  | 
 |   void VisitIsolate(Isolate* isolate) { | 
 |     if (!IsVMInternalIsolate(isolate)) { | 
 |       jsarr_->AddValue(isolate); | 
 |     } | 
 |   } | 
 |  | 
 |  private: | 
 |   JSONArray* jsarr_; | 
 | }; | 
 |  | 
 | static const MethodParameter* 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("targetCPU", CPU::Id()); | 
 |   jsobj.AddProperty("hostCPU", HostCPUFeatures::hardware()); | 
 |   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()); | 
 |   MallocHooks::PrintToJSONObject(&jsobj); | 
 |   PrintJSONForEmbedderInformation(&jsobj); | 
 |   // Construct the isolate list. | 
 |   { | 
 |     JSONArray jsarr(&jsobj, "isolates"); | 
 |     ServiceIsolateVisitor visitor(&jsarr); | 
 |     Isolate::VisitIsolates(&visitor); | 
 |   } | 
 | } | 
 |  | 
 | static bool GetVM(Thread* thread, JSONStream* js) { | 
 |   Service::PrintJSONForVM(js, false); | 
 |   return true; | 
 | } | 
 |  | 
 | static const char* exception_pause_mode_names[] = { | 
 |     "All", "None", "Unhandled", NULL, | 
 | }; | 
 |  | 
 | static Dart_ExceptionPauseInfo exception_pause_mode_values[] = { | 
 |     kPauseOnAllExceptions, kNoPauseOnExceptions, kPauseOnUnhandledExceptions, | 
 |     kInvalidExceptionPauseInfo, | 
 | }; | 
 |  | 
 | static const MethodParameter* set_exception_pause_mode_params[] = { | 
 |     ISOLATE_PARAMETER, | 
 |     new EnumParameter("mode", true, exception_pause_mode_names), NULL, | 
 | }; | 
 |  | 
 | static bool SetExceptionPauseMode(Thread* thread, JSONStream* js) { | 
 |   const char* mode = js->LookupParam("mode"); | 
 |   if (mode == NULL) { | 
 |     PrintMissingParamError(js, "mode"); | 
 |     return true; | 
 |   } | 
 |   Dart_ExceptionPauseInfo info = | 
 |       EnumMapper(mode, exception_pause_mode_names, exception_pause_mode_values); | 
 |   if (info == kInvalidExceptionPauseInfo) { | 
 |     PrintInvalidParamError(js, "mode"); | 
 |     return true; | 
 |   } | 
 |   Isolate* isolate = thread->isolate(); | 
 |   isolate->debugger()->SetExceptionPauseInfo(info); | 
 |   if (Service::debug_stream.enabled()) { | 
 |     ServiceEvent event(isolate, ServiceEvent::kDebuggerSettingsUpdate); | 
 |     Service::HandleEvent(&event); | 
 |   } | 
 |   PrintSuccess(js); | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* get_flag_list_params[] = { | 
 |     NO_ISOLATE_PARAMETER, NULL, | 
 | }; | 
 |  | 
 | static bool GetFlagList(Thread* thread, JSONStream* js) { | 
 |   Flags::PrintJSON(js); | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* set_flags_params[] = { | 
 |     NO_ISOLATE_PARAMETER, NULL, | 
 | }; | 
 |  | 
 | static bool SetFlag(Thread* thread, JSONStream* js) { | 
 |   const char* flag_name = js->LookupParam("name"); | 
 |   if (flag_name == NULL) { | 
 |     PrintMissingParamError(js, "name"); | 
 |     return true; | 
 |   } | 
 |   const char* flag_value = js->LookupParam("value"); | 
 |   if (flag_value == NULL) { | 
 |     PrintMissingParamError(js, "value"); | 
 |     return true; | 
 |   } | 
 |  | 
 |   if (Flags::Lookup(flag_name) == NULL) { | 
 |     JSONObject jsobj(js); | 
 |     jsobj.AddProperty("type", "Error"); | 
 |     jsobj.AddProperty("message", "Cannot set flag: flag not found"); | 
 |     return true; | 
 |   } | 
 |  | 
 |   // Changing most flags at runtime is dangerous because e.g., it may leave the | 
 |   // behavior generated code and the runtime out of sync. | 
 |   const char* kAllowedFlags[] = { | 
 |       "pause_isolates_on_start", | 
 |       "pause_isolates_on_exit", | 
 |       "pause_isolates_on_unhandled_exceptions", | 
 |   }; | 
 |  | 
 |   bool allowed = false; | 
 |   for (size_t i = 0; i < ARRAY_SIZE(kAllowedFlags); i++) { | 
 |     if (strcmp(flag_name, kAllowedFlags[i]) == 0) { | 
 |       allowed = true; | 
 |       break; | 
 |     } | 
 |   } | 
 |  | 
 |   if (!allowed) { | 
 |     JSONObject jsobj(js); | 
 |     jsobj.AddProperty("type", "Error"); | 
 |     jsobj.AddProperty("message", "Cannot set flag: cannot change at runtime"); | 
 |     return true; | 
 |   } | 
 |  | 
 |   const char* error = NULL; | 
 |   if (Flags::SetFlag(flag_name, flag_value, &error)) { | 
 |     PrintSuccess(js); | 
 |     return true; | 
 |   } else { | 
 |     JSONObject jsobj(js); | 
 |     jsobj.AddProperty("type", "Error"); | 
 |     jsobj.AddProperty("message", error); | 
 |     return true; | 
 |   } | 
 | } | 
 |  | 
 | static const MethodParameter* set_library_debuggable_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, new IdParameter("libraryId", true), | 
 |     new BoolParameter("isDebuggable", true), NULL, | 
 | }; | 
 |  | 
 | static bool 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); | 
 |     if (lib.is_dart_scheme()) { | 
 |       const String& url = String::Handle(lib.url()); | 
 |       if (url.StartsWith(Symbols::DartSchemePrivate())) { | 
 |         PrintIllegalParamError(js, "libraryId"); | 
 |         return true; | 
 |       } | 
 |     } | 
 |     lib.set_debuggable(is_debuggable); | 
 |     PrintSuccess(js); | 
 |     return true; | 
 |   } | 
 |   PrintInvalidParamError(js, "libraryId"); | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* set_name_params[] = { | 
 |     ISOLATE_PARAMETER, new MethodParameter("name", true), NULL, | 
 | }; | 
 |  | 
 | static bool 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 true; | 
 | } | 
 |  | 
 | static const MethodParameter* set_vm_name_params[] = { | 
 |     NO_ISOLATE_PARAMETER, new MethodParameter("name", true), NULL, | 
 | }; | 
 |  | 
 | static bool SetVMName(Thread* thread, JSONStream* js) { | 
 |   const char* name_param = js->LookupParam("name"); | 
 |   free(vm_name); | 
 |   vm_name = strdup(name_param); | 
 |   if (Service::vm_stream.enabled()) { | 
 |     ServiceEvent event(NULL, ServiceEvent::kVMUpdate); | 
 |     Service::HandleEvent(&event); | 
 |   } | 
 |   PrintSuccess(js); | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* set_trace_class_allocation_params[] = { | 
 |     RUNNABLE_ISOLATE_PARAMETER, new IdParameter("classId", true), | 
 |     new BoolParameter("enable", true), NULL, | 
 | }; | 
 |  | 
 | static bool SetTraceClassAllocation(Thread* thread, JSONStream* js) { | 
 |   if (CheckCompilerDisabled(thread, js)) { | 
 |     return true; | 
 |   } | 
 |  | 
 |   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 true; | 
 |   } | 
 |   const Class& cls = Class::Handle(GetClassForId(isolate, cid)); | 
 |   ASSERT(!cls.IsNull()); | 
 |   cls.SetTraceAllocation(enable); | 
 |   PrintSuccess(js); | 
 |   return true; | 
 | } | 
 |  | 
 | static const MethodParameter* get_default_classes_aliases_params[] = { | 
 |     NO_ISOLATE_PARAMETER, NULL, | 
 | }; | 
 |  | 
 | static bool 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 <= kMixinAppTypeCid; ++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"); | 
 |     DEFINE_ADD_VALUE_F_CID(LinkedHashMap) | 
 |   } | 
 | #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 | 
 | #undef DEFINE_ADD_VALUE_F_CID | 
 | #undef DEFINE_ADD_VALUE_F | 
 |  | 
 |   return true; | 
 | } | 
 |  | 
 | // clang-format off | 
 | static const ServiceMethodDescriptor service_methods_[] = { | 
 |   { "_dumpIdZone", DumpIdZone, NULL }, | 
 |   { "_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 }, | 
 |   { "_clearCpuProfile", ClearCpuProfile, | 
 |     clear_cpu_profile_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 }, | 
 |   { "_getAllocationSamples", GetAllocationSamples, | 
 |       get_allocation_samples_params }, | 
 |   { "_getNativeAllocationSamples", GetNativeAllocationSamples, | 
 |       get_native_allocation_samples_params }, | 
 |   { "getClassList", GetClassList, | 
 |     get_class_list_params }, | 
 |   { "_getCpuProfile", GetCpuProfile, | 
 |     get_cpu_profile_params }, | 
 |   { "_getCpuProfileTimeline", GetCpuProfileTimeline, | 
 |     get_cpu_profile_timeline_params }, | 
 |   { "getFlagList", GetFlagList, | 
 |     get_flag_list_params }, | 
 |   { "_getHeapMap", GetHeapMap, | 
 |     get_heap_map_params }, | 
 |   { "_getInboundReferences", GetInboundReferences, | 
 |     get_inbound_references_params }, | 
 |   { "_getInstances", GetInstances, | 
 |     get_instances_params }, | 
 |   { "getIsolate", GetIsolate, | 
 |     get_isolate_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 }, | 
 |   { "_getObjectByAddress", GetObjectByAddress, | 
 |     get_object_by_address_params }, | 
 |   { "_getPersistentHandles", GetPersistentHandles, | 
 |       get_persistent_handles_params, }, | 
 |   { "_getPorts", GetPorts, | 
 |     get_ports_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 }, | 
 |   { "_getUnusedChangesInLastReload", GetUnusedChangesInLastReload, | 
 |     get_unused_changes_in_last_reload_params }, | 
 |   { "_getTagProfile", GetTagProfile, | 
 |     get_tag_profile_params }, | 
 |   { "_getTypeArgumentsList", GetTypeArgumentsList, | 
 |     get_type_arguments_list_params }, | 
 |   { "getVersion", GetVersion, | 
 |     get_version_params }, | 
 |   { "getVM", GetVM, | 
 |     get_vm_params }, | 
 |   { "_getVMMetric", GetVMMetric, | 
 |     get_vm_metric_params }, | 
 |   { "_getVMMetricList", GetVMMetricList, | 
 |     get_vm_metric_list_params }, | 
 |   { "_getVMTimeline", GetVMTimeline, | 
 |     get_vm_timeline_params }, | 
 |   { "_getVMTimelineFlags", GetVMTimelineFlags, | 
 |     get_vm_timeline_flags_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 }, | 
 |   { "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 |