// 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 #include "include/dart_api.h" #include "include/dart_native_api.h" #include "platform/globals.h" #include "platform/unicode.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_message.h" #include "vm/dart_api_state.h" #include "vm/dart_entry.h" #include "vm/debugger.h" #include "vm/heap/safepoint.h" #include "vm/isolate.h" #include "vm/kernel_isolate.h" #include "vm/lockers.h" #include "vm/malloc_hooks.h" #include "vm/message.h" #include "vm/message_handler.h" #include "vm/native_arguments.h" #include "vm/native_entry.h" #include "vm/native_symbol.h" #include "vm/object.h" #include "vm/object_graph.h" #include "vm/object_id_ring.h" #include "vm/object_store.h" #include "vm/parser.h" #include "vm/port.h" #include "vm/profiler.h" #include "vm/profiler_service.h" #include "vm/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/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"); const uint8_t* Service::dart_library_kernel_ = NULL; intptr_t Service::dart_library_kernel_len_ = 0; 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); Dart_Handle handle; { TransitionVMToNative transition(T); if (get_service_assets_callback_ == NULL) { return Object::null(); } handle = get_service_assets_callback_(); if (Dart_IsError(handle)) { Dart_PropagateError(handle); } } object = Api::UnwrapHandle(handle); } if (object.IsNull()) { return Object::null(); } if (!object.IsTypedData()) { const String& error_message = String::Handle( String::New("An implementation of Dart_GetVMServiceAssetsArchive " "should return a Uint8Array or null.")); const Error& error = Error::Handle(ApiError::New(error_message)); Exceptions::PropagateError(error); return Object::null(); } const TypedData& typed_data = TypedData::Cast(object); if (typed_data.ElementSizeInBytes() != 1) { const String& error_message = String::Handle( String::New("An implementation of Dart_GetVMServiceAssetsArchive " "should return a Uint8Array or null.")); const Error& error = Error::Handle(ApiError::New(error_message)); Exceptions::PropagateError(error); return Object::null(); } return object.raw(); } static uint8_t* allocator(uint8_t* ptr, intptr_t old_size, intptr_t new_size) { void* new_ptr = realloc(reinterpret_cast(ptr), new_size); if (new_ptr == NULL) { OUT_OF_MEMORY(); } return reinterpret_cast(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 CheckProfilerDisabled(Thread* thread, JSONStream* js) { if (Profiler::sample_buffer() == NULL) { js->PrintError(kFeatureDisabled, "Profiler is disabled."); return true; } return false; } static bool GetIntegerId(const char* s, intptr_t* id, int base = 10) { if ((s == NULL) || (*s == '\0')) { // Empty string. return false; } if (id == NULL) { // No id pointer. return false; } intptr_t r = 0; char* end_ptr = NULL; #if defined(ARCH_IS_32_BIT) r = strtol(s, &end_ptr, base); #else r = strtoll(s, &end_ptr, base); #endif if (end_ptr == s) { // String was not advanced at all, cannot be valid. return false; } *id = r; return true; } static bool GetUnsignedIntegerId(const char* s, uintptr_t* id, int base = 10) { if ((s == NULL) || (*s == '\0')) { // Empty string. return false; } if (id == NULL) { // No id pointer. return false; } uintptr_t r = 0; char* end_ptr = NULL; #if defined(ARCH_IS_32_BIT) r = strtoul(s, &end_ptr, base); #else r = strtoull(s, &end_ptr, base); #endif if (end_ptr == s) { // String was not advanced at all, cannot be valid. return false; } *id = r; return true; } static bool GetInteger64Id(const char* s, int64_t* id, int base = 10) { if ((s == NULL) || (*s == '\0')) { // Empty string. return false; } if (id == NULL) { // No id pointer. return false; } int64_t r = 0; char* end_ptr = NULL; r = strtoll(s, &end_ptr, base); if (end_ptr == s) { // String was not advanced at all, cannot be valid. return false; } *id = r; return true; } // Scans the string until the '-' character. Returns pointer to string // at '-' character. Returns NULL if not found. static const char* ScanUntilDash(const char* s) { if ((s == NULL) || (*s == '\0')) { // Empty string. return NULL; } while (*s != '\0') { if (*s == '-') { return s; } s++; } return NULL; } static bool GetCodeId(const char* s, int64_t* timestamp, uword* address) { if ((s == NULL) || (*s == '\0')) { // Empty string. return false; } if ((timestamp == NULL) || (address == NULL)) { // Bad arguments. return false; } // Extract the timestamp. if (!GetInteger64Id(s, timestamp, 16) || (*timestamp < 0)) { return false; } s = ScanUntilDash(s); if (s == NULL) { return false; } // Skip the dash. s++; // Extract the PC. if (!GetUnsignedIntegerId(s, address, 16)) { return false; } return true; } // Verifies that |s| begins with |prefix| and then calls |GetIntegerId| on // the remainder of |s|. static bool GetPrefixedIntegerId(const char* s, const char* prefix, intptr_t* service_id) { if (s == NULL) { return false; } ASSERT(prefix != NULL); const intptr_t kInputLen = strlen(s); const intptr_t kPrefixLen = strlen(prefix); ASSERT(kPrefixLen > 0); if (kInputLen <= kPrefixLen) { return false; } if (strncmp(s, prefix, kPrefixLen) != 0) { return false; } // Prefix satisfied. Move forward. s += kPrefixLen; // Attempt to read integer id. return GetIntegerId(s, service_id); } static bool IsValidClassId(Isolate* isolate, intptr_t cid) { ASSERT(isolate != NULL); ClassTable* class_table = isolate->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 uintptr_t Parse(const char* value) { if (value == NULL) { return -1; } char* end_ptr = NULL; uintptr_t result = strtoul(value, &end_ptr, 10); ASSERT(*end_ptr == '\0'); // Parsed full string return result; } }; class Int64Parameter : public MethodParameter { public: Int64Parameter(const char* name, bool required) : MethodParameter(name, required) {} virtual bool Validate(const char* value) const { if (value == NULL) { return false; } for (const char* cp = value; *cp != '\0'; cp++) { if ((*cp < '0' || *cp > '9') && (*cp != '-')) { return false; } } return true; } static int64_t Parse(const char* value, int64_t default_value = -1) { if ((value == NULL) || (*value == '\0')) { return default_value; } char* end_ptr = NULL; int64_t result = strtoll(value, &end_ptr, 10); ASSERT(*end_ptr == '\0'); // Parsed full string return result; } }; class 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 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(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(elements); } private: // For now observatory enums are ascii letters plus underscore. static bool IsEnumChar(char c) { return (((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')) || (c == '_')); } // Returns number of elements in the list. -1 on parse error. intptr_t ElementCount(const char* value) const { const char* kJsonWhitespaceChars = " \t\r\n"; if (value == NULL) { return -1; } const char* cp = value; cp += strspn(cp, kJsonWhitespaceChars); if (*cp++ != '[') { // Missing initial [. return -1; } bool closed = false; bool element_allowed = true; intptr_t element_count = 0; while (true) { // Skip json whitespace. cp += strspn(cp, kJsonWhitespaceChars); switch (*cp) { case '\0': return closed ? element_count : -1; case ']': closed = true; cp++; break; case ',': if (element_allowed) { return -1; } element_allowed = true; cp++; break; default: if (!element_allowed) { return -1; } bool valid_enum = false; const char* id_start = cp; while (IsEnumChar(*cp)) { cp++; } if (cp == id_start) { // Empty identifier, something like this [,]. return -1; } intptr_t id_len = cp - id_start; if (enums_ != NULL) { for (intptr_t i = 0; enums_[i] != NULL; i++) { intptr_t len = strlen(enums_[i]); if (len == id_len && strncmp(id_start, enums_[i], len) == 0) { element_count++; valid_enum = true; element_allowed = false; // we need a comma first. break; } } } if (!valid_enum) { return -1; } break; } } } const char* const* enums_; }; typedef 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(js.id_zone()); zone->set_policy(ObjectIdRing::kReuseId); } else { // TODO(johnmccutchan): Support creating, deleting, and selecting // custom service id zones. // For now, always return an error. PrintInvalidParamError(&js, "_idZone"); js.PostReply(); return T->StealStickyError(); } } const char* c_method_name = method_name.ToCString(); const ServiceMethodDescriptor* method = FindMethod(c_method_name); if (method != NULL) { if (!ValidateParameters(method->parameters, &js)) { js.PostReply(); return T->StealStickyError(); } 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->StealStickyError(); } EmbedderServiceHandler* handler = FindIsolateEmbedderHandler(c_method_name); if (handler == NULL) { handler = FindRootEmbedderHandler(c_method_name); } if (handler != NULL) { EmbedderHandleMessage(handler, &js); return T->StealStickyError(); } const Instance& extension_handler = Instance::Handle(Z, I->LookupServiceExtensionHandler(method_name)); if (!extension_handler.IsNull()) { ScheduleExtensionHandler(extension_handler, method_name, param_keys, param_values, reply_port, seq); // Schedule was successful. Extension code will post a reply // asynchronously. return T->StealStickyError(); } PrintUnrecognizedMethodError(&js); js.PostReply(); return T->StealStickyError(); } } 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(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(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(malloc(total_bytes)); if (message == NULL) { OUT_OF_MEMORY(); } intptr_t offset = 0; // Metadata size. reinterpret_cast(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(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); // Post event to the native Service Stream handlers if set. if (event->stream_info() != nullptr && event->stream_info()->consumer() != nullptr) { auto length = js.buffer()->length(); event->stream_info()->consumer()( reinterpret_cast(js.buffer()->buf()), length); } } void Service::PostEvent(Isolate* isolate, const char* stream_id, const char* kind, JSONStream* event) { ASSERT(stream_id != NULL); ASSERT(kind != NULL); ASSERT(event != NULL); if (FLAG_trace_service) { if (isolate != NULL) { OS::PrintErr( "vm-service: Pushing ServiceEvent(isolate='%s', " "isolateId='" ISOLATE_SERVICE_ID_FORMAT_STRING "', kind='%s') to stream %s\n", isolate->name(), static_cast(isolate->main_port()), kind, stream_id); } else { OS::PrintErr( "vm-service: Pushing ServiceEvent(isolate='', " "kind='%s') to stream %s\n", kind, stream_id); } } // Message is of the format [, ]. // // 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(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(event->ToCString()); list_values[1] = &json_cobj; ApiMessageWriter writer; std::unique_ptr msg = writer.WriteCMessage( &list_cobj, ServiceIsolate::Port(), Message::kNormalPriority); if (msg != nullptr) { PortMap::PostMessage(std::move(msg)); } } class EmbedderServiceHandler { public: explicit EmbedderServiceHandler(const char* name) : name_(NULL), callback_(NULL), user_data_(NULL), next_(NULL) { ASSERT(name != NULL); name_ = 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(response)); } void Service::RegisterIsolateEmbedderCallback( const char* name, Dart_ServiceRequestCallback callback, void* user_data) { if (name == NULL) { return; } EmbedderServiceHandler* handler = FindIsolateEmbedderHandler(name); if (handler != NULL) { // Update existing handler entry. handler->set_callback(callback); handler->set_user_data(user_data); return; } // Create a new handler. handler = new EmbedderServiceHandler(name); handler->set_callback(callback); handler->set_user_data(user_data); // Insert into isolate_service_handler_head_ list. handler->set_next(isolate_service_handler_head_); isolate_service_handler_head_ = handler; } EmbedderServiceHandler* Service::FindIsolateEmbedderHandler(const char* name) { EmbedderServiceHandler* current = isolate_service_handler_head_; while (current != NULL) { if (strcmp(name, current->name()) == 0) { return current; } current = current->next(); } return NULL; } void Service::RegisterRootEmbedderCallback(const char* name, Dart_ServiceRequestCallback callback, void* user_data) { if (name == NULL) { return; } EmbedderServiceHandler* handler = FindRootEmbedderHandler(name); if (handler != NULL) { // Update existing handler entry. handler->set_callback(callback); handler->set_user_data(user_data); return; } // Create a new handler. handler = new EmbedderServiceHandler(name); handler->set_callback(callback); handler->set_user_data(user_data); // Insert into root_service_handler_head_ list. handler->set_next(root_service_handler_head_); root_service_handler_head_ = handler; } void Service::SetEmbedderStreamCallbacks( Dart_ServiceStreamListenCallback listen_callback, Dart_ServiceStreamCancelCallback cancel_callback) { stream_listen_callback_ = listen_callback; stream_cancel_callback_ = cancel_callback; } void Service::SetNativeServiceStreamCallback(Dart_NativeStreamConsumer consumer, const char* stream_id) { for (auto stream : streams_) { if (stream->id() == stream_id) { stream->set_consumer(consumer); } } // Enable stream. ListenStream(stream_id); } void Service::SetGetServiceAssetsCallback( Dart_GetVMServiceAssetsArchive get_service_assets) { get_service_assets_callback_ = get_service_assets; } void Service::SetEmbedderInformationCallback( Dart_EmbedderInformationCallback callback) { embedder_information_callback_ = callback; } int64_t Service::CurrentRSS() { if (embedder_information_callback_ == NULL) { return -1; } Dart_EmbedderInformation info = { 0, // version NULL, // name 0, // max_rss 0 // current_rss }; embedder_information_callback_(&info); ASSERT(info.version == DART_EMBEDDER_INFORMATION_CURRENT_VERSION); return info.current_rss; } int64_t Service::MaxRSS() { if (embedder_information_callback_ == NULL) { return -1; } Dart_EmbedderInformation info = { 0, // version NULL, // name 0, // max_rss 0 // current_rss }; embedder_information_callback_(&info); ASSERT(info.version == DART_EMBEDDER_INFORMATION_CURRENT_VERSION); return info.max_rss; } void Service::SetDartLibraryKernelForSources(const uint8_t* kernel_bytes, intptr_t kernel_length) { dart_library_kernel_ = kernel_bytes; dart_library_kernel_len_ = kernel_length; } EmbedderServiceHandler* Service::FindRootEmbedderHandler(const char* name) { EmbedderServiceHandler* current = root_service_handler_head_; while (current != NULL) { if (strcmp(name, current->name()) == 0) { return current; } current = current->next(); } return NULL; } void Service::ScheduleExtensionHandler(const Instance& handler, const String& method_name, const Array& parameter_keys, const Array& parameter_values, const Instance& reply_port, const Instance& id) { ASSERT(!handler.IsNull()); ASSERT(!method_name.IsNull()); ASSERT(!parameter_keys.IsNull()); ASSERT(!parameter_values.IsNull()); ASSERT(!reply_port.IsNull()); Isolate* isolate = Isolate::Current(); ASSERT(isolate != NULL); isolate->AppendServiceExtensionCall(handler, method_name, parameter_keys, parameter_values, reply_port, id); } static const MethodParameter* get_isolate_params[] = { ISOLATE_PARAMETER, NULL, }; static bool GetIsolate(Thread* thread, JSONStream* js) { thread->isolate()->PrintJSON(js, false); return true; } static const MethodParameter* get_memory_usage_params[] = { ISOLATE_PARAMETER, NULL, }; static bool GetMemoryUsage(Thread* thread, JSONStream* js) { thread->isolate()->PrintMemoryUsageJSON(js); 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, 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. 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); 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); 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); 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 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(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.DeclarationType()); 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", ""); break; case kExpiredSentinel: jsobj.AddProperty("kind", "Expired"); jsobj.AddProperty("valueAsString", ""); break; case kFreeSentinel: jsobj.AddProperty("kind", "Free"); jsobj.AddProperty("valueAsString", ""); 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(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; } bool disable_breakpoints = BoolParameter::Parse(js->LookupParam("disableBreakpoints"), false); DisableBreakpointsScope db(thread->isolate()->debugger(), disable_breakpoints); 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* names, GrowableArray* 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 cnames; GrowableArray 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(); bool disable_breakpoints = BoolParameter::Parse(js->LookupParam("disableBreakpoints"), false); DisableBreakpointsScope db(isolate->debugger(), disable_breakpoints); DebuggerStackTrace* stack = isolate->debugger()->StackTrace(); intptr_t frame_pos = UIntParameter::Parse(js->LookupParam("frameIndex")); if (frame_pos >= stack->Length()) { PrintInvalidParamError(js, "frameIndex"); return 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, ZoneGrowableHandlePtrArray* storage, intptr_t limit) : cls_(cls), storage_(storage), limit_(limit), 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 (count_ < limit_) { storage_->Add(Object::Handle(raw_obj)); } ++count_; } return kProceed; } intptr_t count() const { return count_; } private: const Class& cls_; ZoneGrowableHandlePtrArray* storage_; const intptr_t limit_; intptr_t count_; }; static const MethodParameter* get_instances_params[] = { RUNNABLE_ISOLATE_PARAMETER, NULL, }; static bool GetInstances(Thread* thread, JSONStream* js) { const char* object_id = js->LookupParam("objectId"); if (object_id == NULL) { PrintMissingParamError(js, "objectId"); 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, object_id, NULL)); if (obj.raw() == Object::sentinel().raw() || !obj.IsClass()) { PrintInvalidParamError(js, "objectId"); return true; } const Class& cls = Class::Cast(obj); // Ensure the array and handles created below are promptly destroyed. StackZone zone(thread); HANDLESCOPE(thread); ZoneGrowableHandlePtrArray storage(thread->zone(), limit); GetInstancesVisitor visitor(cls, &storage, limit); 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, "instances"); for (int i = 0; (i < limit) && (i < count); i++) { samples.AddValue(storage.At(i)); } } return true; } #if !defined(DART_PRECOMPILED_RUNTIME) static const char* const report_enum_names[] = { SourceReport::kCallSitesStr, SourceReport::kCoverageStr, SourceReport::kPossibleBreakpointsStr, SourceReport::kProfileStr, NULL, }; #endif static const MethodParameter* get_source_report_params[] = { #if !defined(DART_PRECOMPILED_RUNTIME) RUNNABLE_ISOLATE_PARAMETER, new EnumListParameter("reports", true, report_enum_names), new IdParameter("scriptId", false), new UIntParameter("tokenPos", false), new UIntParameter("endTokenPos", false), new BoolParameter("forceCompile", false), #endif NULL, }; static bool GetSourceReport(Thread* thread, JSONStream* js) { #if defined(DART_PRECOMPILED_RUNTIME) js->PrintError(kFeatureDisabled, "disabled in AOT mode and PRODUCT."); return false; #else if (CheckCompilerDisabled(thread, js)) { return true; } const char* reports_str = js->LookupParam("reports"); const EnumListParameter* reports_parameter = static_cast(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; #endif // !DART_PRECOMPILED_RUNTIME } 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, }; #if defined(SUPPORT_TIMELINE) static bool HasStream(const char** recorded_streams, const char* stream) { while (*recorded_streams != NULL) { if ((strstr(*recorded_streams, "all") != NULL) || (strstr(*recorded_streams, stream) != NULL)) { return true; } recorded_streams++; } return false; } #endif static bool SetVMTimelineFlags(Thread* thread, JSONStream* js) { #if !defined(SUPPORT_TIMELINE) PrintSuccess(js); return true; #else Isolate* isolate = thread->isolate(); ASSERT(isolate != NULL); StackZone zone(thread); const EnumListParameter* recorded_streams_param = static_cast(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; #endif } static const MethodParameter* get_vm_timeline_flags_params[] = { NO_ISOLATE_PARAMETER, NULL, }; static bool GetVMTimelineFlags(Thread* thread, JSONStream* js) { #if !defined(SUPPORT_TIMELINE) JSONObject obj(js); obj.AddProperty("type", "TimelineFlags"); return true; #else Isolate* isolate = thread->isolate(); ASSERT(isolate != NULL); StackZone zone(thread); Timeline::PrintFlagsToJSON(js); return true; #endif } 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); const char* name = timeline_recorder->name(); if ((strcmp(name, FUCHSIA_RECORDER_NAME) == 0) || (strcmp(name, SYSTRACE_RECORDER_NAME) == 0)) { js->PrintError(kInvalidTimelineRequest, "A recorder of type \"%s\" is " "currently in use. As a result, timeline events are handled " "by the OS rather than the VM. See the VM service " "documentation for more details on where timeline events " "can be found for this recorder type.", timeline_recorder->name()); return true; } 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) { if (CheckProfilerDisabled(thread, js)) { return true; } 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) { if (CheckProfilerDisabled(thread, js)) { return true; } 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")); bool code_trie = BoolParameter::Parse(js->LookupParam("code"), false); ProfilerService::PrintTimelineJSON(js, tag_order, time_origin_micros, time_extent_micros, code_trie); return true; } static const MethodParameter* write_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 WriteCpuProfileTimeline(Thread* thread, JSONStream* js) { if (CheckProfilerDisabled(thread, js)) { return true; } 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")); bool code_trie = BoolParameter::Parse(js->LookupParam("code"), true); ProfilerService::AddToTimeline(tag_order, time_origin_micros, time_extent_micros, code_trie); PrintSuccess(js); // The "result" is a side-effect in the timeline. 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 bool GetAllocationProfileImpl(Thread* thread, JSONStream* js, bool internal) { bool should_reset_accumulator = false; bool should_collect = false; if (js->HasParam("reset")) { if (js->ParamIs("reset", "true")) { should_reset_accumulator = true; } else { PrintInvalidParamError(js, "reset"); return true; } } if (js->HasParam("gc")) { if (js->ParamIs("gc", "true")) { 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, internal); return true; } static const MethodParameter* get_allocation_profile_params[] = { RUNNABLE_ISOLATE_PARAMETER, NULL, }; static bool GetAllocationProfilePublic(Thread* thread, JSONStream* js) { return GetAllocationProfileImpl(thread, js, false); } static bool GetAllocationProfile(Thread* thread, JSONStream* js) { return GetAllocationProfileImpl(thread, js, 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->HeapSize(); 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) { HeapIterationScope iteration(thread); 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 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(weak_persistent_handle->peer())); obj.AddPropertyF( "callbackAddress", "0x%" Px "", reinterpret_cast(weak_persistent_handle->callback())); // Attempt to include a native symbol name. char* name = NativeSymbolResolver::LookupSymbolName( reinterpret_cast(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(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 visitor(thread, &persistent_handles); handles.Visit(&visitor); } // Weak persistent handles. { JSONArray weak_persistent_handles(&obj, "weakPersistentHandles"); FinalizablePersistentHandles& handles = api_state->weak_persistent_handles(); PersistentHandleVisitor 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; Object& obj = Object::Handle(LookupHeapObject(thread, id, &lookup_result)); if (obj.raw() != Object::sentinel().raw()) { #if !defined(DART_PRECOMPILED_RUNTIME) // If obj is a script from dart:* and doesn't have source loaded, try and // load the source before sending the response. if (obj.IsScript()) { const Script& script = Script::Cast(obj); if (!script.HasSource() && script.IsPartOfDartColonLibrary() && Service::HasDartLibraryKernelForSources()) { const uint8_t* kernel_buffer = Service::dart_library_kernel(); const intptr_t kernel_buffer_len = Service::dart_library_kernel_length(); script.LoadSourceFromKernel(kernel_buffer, kernel_buffer_len); } } #endif // !defined(DART_PRECOMPILED_RUNTIME) // We found a heap object for this id. Return it. obj.PrintJSON(js, false); return 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(SERVICE_PROTOCOL_MAJOR_VERSION)); jsobj.AddProperty("minor", static_cast(SERVICE_PROTOCOL_MINOR_VERSION)); jsobj.AddProperty("_privateMajor", static_cast(0)); jsobj.AddProperty("_privateMinor", static_cast(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(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 uintptr_t kProfilePeriodIndex = 3; const char* kAllowedFlags[] = { "pause_isolates_on_start", "pause_isolates_on_exit", "pause_isolates_on_unhandled_exceptions", "profile_period", }; bool allowed = false; bool profile_period = false; for (size_t i = 0; i < ARRAY_SIZE(kAllowedFlags); i++) { if (strcmp(flag_name, kAllowedFlags[i]) == 0) { allowed = true; profile_period = (i == kProfilePeriodIndex); 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); if (profile_period) { // FLAG_profile_period has already been set to the new value. Now we need // to notify the ThreadInterrupter to pick up the change. Profiler::UpdateSamplePeriod(); } 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(id)); #define DEFINE_ADD_VALUE_F_CID(clazz) DEFINE_ADD_VALUE_F(k##clazz##Cid) { JSONArray internals(&map, ""); for (intptr_t id = kClassCid; id < kInstanceCid; ++id) { DEFINE_ADD_VALUE_F(id); } DEFINE_ADD_VALUE_F_CID(LibraryPrefix); } { JSONArray internals(&map, "Type"); for (intptr_t id = kAbstractTypeCid; id <= kTypeParameterCid; ++id) { DEFINE_ADD_VALUE_F(id); } } { JSONArray internals(&map, "Object"); DEFINE_ADD_VALUE_F_CID(Instance); } { JSONArray internals(&map, "Closure"); DEFINE_ADD_VALUE_F_CID(Closure); DEFINE_ADD_VALUE_F_CID(Context); } { JSONArray internals(&map, "Int"); for (intptr_t id = kIntegerCid; id <= kMintCid; ++id) { DEFINE_ADD_VALUE_F(id); } } { JSONArray internals(&map, "Double"); DEFINE_ADD_VALUE_F_CID(Double); } { JSONArray internals(&map, "String"); CLASS_LIST_STRINGS(DEFINE_ADD_VALUE_F_CID) } { JSONArray internals(&map, "List"); CLASS_LIST_ARRAYS(DEFINE_ADD_VALUE_F_CID) DEFINE_ADD_VALUE_F_CID(GrowableObjectArray) DEFINE_ADD_VALUE_F_CID(ByteBuffer) } { JSONArray internals(&map, "Map"); 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 #define DEFINE_ADD_MAP_KEY(clazz) \ { \ JSONArray internals(&map, #clazz); \ DEFINE_ADD_VALUE_F_CID(Ffi##clazz) \ } CLASS_LIST_FFI(DEFINE_ADD_MAP_KEY) #undef DEFINE_ADD_MAP_KEY #undef DEFINE_ADD_VALUE_F_CID #undef DEFINE_ADD_VALUE_F return true; } // clang-format off static const ServiceMethodDescriptor service_methods_[] = { { "_echo", Echo, NULL }, { "_respondWithMalformedJson", RespondWithMalformedJson, NULL }, { "_respondWithMalformedObject", RespondWithMalformedObject, NULL }, { "_triggerEchoEvent", TriggerEchoEvent, NULL }, { "addBreakpoint", AddBreakpoint, add_breakpoint_params }, { "addBreakpointWithScriptUri", AddBreakpointWithScriptUri, add_breakpoint_with_script_uri_params }, { "addBreakpointAtEntry", AddBreakpointAtEntry, add_breakpoint_at_entry_params }, { "_addBreakpointAtActivation", AddBreakpointAtActivation, add_breakpoint_at_activation_params }, { "_buildExpressionEvaluationScope", BuildExpressionEvaluationScope, build_expression_evaluation_scope_params }, { "_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 }, { "getAllocationProfile", GetAllocationProfilePublic, 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 }, { "_writeCpuProfileTimeline", WriteCpuProfileTimeline, write_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 }, { "getMemoryUsage", GetMemoryUsage, get_memory_usage_params }, { "_getIsolateMetric", GetIsolateMetric, get_isolate_metric_params }, { "_getIsolateMetricList", GetIsolateMetricList, get_isolate_metric_list_params }, { "getObject", GetObject, get_object_params }, { "_getObjectStore", GetObjectStore, get_object_store_params }, { "_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